File indexing completed on 2024-04-21 16:30:24
0001 // SPDX-FileCopyrightText: 2020 Simon Persson <simon.persson@mykolab.com> 0002 // 0003 // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 0005 #include "fsexecutor.h" 0006 #include "backupplan.h" 0007 0008 #include <QAction> 0009 #include <QDir> 0010 #include <QFileInfo> 0011 #include <QTimer> 0012 #include <QTextStream> 0013 0014 #include <KDirWatch> 0015 0016 #include <fcntl.h> 0017 #include <sys/stat.h> 0018 #include <sys/select.h> 0019 0020 namespace { 0021 0022 // very light check if a directory exists that works on automounts where QDir::exists fails 0023 bool checkDirExists(const QDir &dir) 0024 { 0025 struct stat s; 0026 return stat(dir.absolutePath().toLocal8Bit().data(), &s) == 0 && S_ISDIR(s.st_mode); 0027 } 0028 } 0029 0030 FSExecutor::FSExecutor(BackupPlan *pPlan, KupDaemon *pKupDaemon) 0031 :PlanExecutor(pPlan, pKupDaemon) 0032 { 0033 mDestinationPath = QDir::cleanPath(mPlan->mFilesystemDestinationPath.toLocalFile()); 0034 mDirWatch = new KDirWatch(this); 0035 connect(mDirWatch, SIGNAL(deleted(QString)), SLOT(checkStatus())); 0036 mMountWatcher.start(); 0037 } 0038 0039 FSExecutor::~FSExecutor() { 0040 mMountWatcher.terminate(); 0041 mMountWatcher.wait(); 0042 } 0043 0044 void FSExecutor::checkStatus() { 0045 static bool lComingBackLater = false; 0046 if(!mWatchedParentDir.isEmpty() && !lComingBackLater) { 0047 // came here because something happened to a parent folder, 0048 // come back in a few seconds, give a new mount some time before checking 0049 // status of destination folder 0050 QTimer::singleShot(5000, this, SLOT(checkStatus())); 0051 lComingBackLater = true; 0052 return; 0053 } 0054 lComingBackLater = false; 0055 0056 QDir lDir(mDestinationPath); 0057 if(!lDir.exists()) { 0058 // Destination doesn't exist, find nearest existing parent folder and 0059 // watch that for dirty or deleted 0060 if(mDirWatch->contains(mDestinationPath)) { 0061 mDirWatch->removeDir(mDestinationPath); 0062 } 0063 0064 QString lExisting = mDestinationPath; 0065 do { 0066 lExisting += QStringLiteral("/.."); 0067 lDir = QDir(QDir::cleanPath(lExisting)); 0068 } while(!checkDirExists(lDir)); 0069 lExisting = lDir.canonicalPath(); 0070 0071 if(lExisting != mWatchedParentDir) { // new parent to watch 0072 if(!mWatchedParentDir.isEmpty()) { // were already watching a parent 0073 mDirWatch->removeDir(mWatchedParentDir); 0074 } else { // start watching a parent 0075 connect(mDirWatch, SIGNAL(dirty(QString)), SLOT(checkStatus())); 0076 connect(&mMountWatcher, SIGNAL(mountsChanged()), SLOT(checkMountPoints()), Qt::QueuedConnection); 0077 } 0078 mWatchedParentDir = lExisting; 0079 mDirWatch->addDir(mWatchedParentDir); 0080 } 0081 if(mState != NOT_AVAILABLE) { 0082 enterNotAvailableState(); 0083 } 0084 } else { 0085 // Destination exists... only watch for delete 0086 if(!mWatchedParentDir.isEmpty()) { 0087 disconnect(mDirWatch, SIGNAL(dirty(QString)), this, SLOT(checkStatus())); 0088 disconnect(&mMountWatcher, SIGNAL(mountsChanged()), this, SLOT(checkMountPoints())); 0089 mDirWatch->removeDir(mWatchedParentDir); 0090 mWatchedParentDir.clear(); 0091 } 0092 mDirWatch->addDir(mDestinationPath); 0093 0094 QFileInfo lInfo(mDestinationPath); 0095 if(lInfo.isWritable() && mState == NOT_AVAILABLE) { 0096 enterAvailableState(); 0097 }else if(!lInfo.isWritable() && mState != NOT_AVAILABLE) { 0098 enterNotAvailableState(); 0099 } 0100 } 0101 } 0102 0103 0104 void FSExecutor::checkMountPoints() { 0105 QFile lMountsFile(QStringLiteral("/proc/mounts")); 0106 if(!lMountsFile.open(QIODevice::ReadOnly | QIODevice::Text)) { 0107 return; 0108 } 0109 // don't use atEnd() to detect when finished reading file, size of 0110 // this special file is 0 but still returns data when read. 0111 forever { 0112 QByteArray lLine = lMountsFile.readLine(); 0113 if(lLine.isEmpty()) { 0114 break; 0115 } 0116 QTextStream lTextStream(lLine); 0117 QString lDevice, lMountPoint; 0118 lTextStream >> lDevice >> lMountPoint; 0119 if(lMountPoint == mWatchedParentDir) { 0120 checkStatus(); 0121 } 0122 } 0123 } 0124 0125 void MountWatcher::run() { 0126 int lMountsFd = open("/proc/mounts", O_RDONLY); 0127 fd_set lFdSet; 0128 0129 forever { 0130 FD_ZERO(&lFdSet); 0131 FD_SET(lMountsFd, &lFdSet); 0132 if(select(lMountsFd+1, nullptr, nullptr, &lFdSet, nullptr) > 0) { 0133 emit mountsChanged(); 0134 } 0135 } 0136 }