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 }