File: fsexecutor.cpp

package info (click to toggle)
kup-backup 0.10.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,576 kB
  • sloc: cpp: 8,422; xml: 311; makefile: 6; sh: 3
file content (140 lines) | stat: -rw-r--r-- 4,404 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// SPDX-FileCopyrightText: 2020 Simon Persson <simon.persson@mykolab.com>
//
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL

#include "fsexecutor.h"
#include "backupplan.h"

#include <QAction>
#include <QDir>
#include <QFileInfo>
#include <QTextStream>
#include <QTimer>

#include <KDirWatch>

#include <fcntl.h>
#include <sys/select.h>
#include <sys/stat.h>

namespace
{

// very light check if a directory exists that works on automounts where QDir::exists fails
bool checkDirExists(const QDir &dir)
{
    struct stat s;
    return stat(dir.absolutePath().toLocal8Bit().data(), &s) == 0 && S_ISDIR(s.st_mode);
}
}

FSExecutor::FSExecutor(BackupPlan *pPlan, KupDaemon *pKupDaemon)
    : PlanExecutor(pPlan, pKupDaemon)
{
    mDestinationPath = QDir::cleanPath(mPlan->mFilesystemDestinationPath.toLocalFile());
    mDirWatch = new KDirWatch(this);
    connect(mDirWatch, SIGNAL(deleted(QString)), SLOT(checkStatus()));
    mMountWatcher.start();
}

FSExecutor::~FSExecutor()
{
    mMountWatcher.terminate();
    mMountWatcher.wait();
}

void FSExecutor::checkStatus()
{
    static bool lComingBackLater = false;
    if (!mWatchedParentDir.isEmpty() && !lComingBackLater) {
        // came here because something happened to a parent folder,
        // come back in a few seconds, give a new mount some time before checking
        // status of destination folder
        QTimer::singleShot(5000, this, SLOT(checkStatus()));
        lComingBackLater = true;
        return;
    }
    lComingBackLater = false;

    QDir lDir(mDestinationPath);
    if (!lDir.exists()) {
        // Destination doesn't exist, find nearest existing parent folder and
        // watch that for dirty or deleted
        if (mDirWatch->contains(mDestinationPath)) {
            mDirWatch->removeDir(mDestinationPath);
        }

        QString lExisting = mDestinationPath;
        do {
            lExisting += QStringLiteral("/..");
            lDir = QDir(QDir::cleanPath(lExisting));
        } while (!checkDirExists(lDir));
        lExisting = lDir.canonicalPath();

        if (lExisting != mWatchedParentDir) { // new parent to watch
            if (!mWatchedParentDir.isEmpty()) { // were already watching a parent
                mDirWatch->removeDir(mWatchedParentDir);
            } else { // start watching a parent
                connect(mDirWatch, SIGNAL(dirty(QString)), SLOT(checkStatus()));
                connect(&mMountWatcher, SIGNAL(mountsChanged()), SLOT(checkMountPoints()), Qt::QueuedConnection);
            }
            mWatchedParentDir = lExisting;
            mDirWatch->addDir(mWatchedParentDir);
        }
        if (mState != NOT_AVAILABLE) {
            enterNotAvailableState();
        }
    } else {
        // Destination exists... only watch for delete
        if (!mWatchedParentDir.isEmpty()) {
            disconnect(mDirWatch, SIGNAL(dirty(QString)), this, SLOT(checkStatus()));
            disconnect(&mMountWatcher, SIGNAL(mountsChanged()), this, SLOT(checkMountPoints()));
            mDirWatch->removeDir(mWatchedParentDir);
            mWatchedParentDir.clear();
        }
        mDirWatch->addDir(mDestinationPath);

        QFileInfo lInfo(mDestinationPath);
        if (lInfo.isWritable() && mState == NOT_AVAILABLE) {
            enterAvailableState();
        } else if (!lInfo.isWritable() && mState != NOT_AVAILABLE) {
            enterNotAvailableState();
        }
    }
}

void FSExecutor::checkMountPoints()
{
    QFile lMountsFile(QStringLiteral("/proc/mounts"));
    if (!lMountsFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
        return;
    }
    // don't use atEnd() to detect when finished reading file, size of
    // this special file is 0 but still returns data when read.
    forever {
        QByteArray lLine = lMountsFile.readLine();
        if (lLine.isEmpty()) {
            break;
        }
        QTextStream lTextStream(lLine);
        QString lDevice, lMountPoint;
        lTextStream >> lDevice >> lMountPoint;
        if (lMountPoint == mWatchedParentDir) {
            checkStatus();
        }
    }
}

void MountWatcher::run()
{
    int lMountsFd = open("/proc/mounts", O_RDONLY);
    fd_set lFdSet;

    forever {
        FD_ZERO(&lFdSet);
        FD_SET(lMountsFd, &lFdSet);
        if (select(lMountsFd + 1, nullptr, nullptr, &lFdSet, nullptr) > 0) {
            emit mountsChanged();
        }
    }
}