[system/kio-snapshot] /: Make filesnapshots work with Btrfs filesystems mounted at other than /, and make it work for directories
Bharadwaj Raju <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 941552b646035830af34483be8bacc611f4a8ea9 by Bharadwaj Raju.
Committed on 17/07/2026 at 03:59.
Pushed by bharadwaj-raju into branch 'master'.
Make filesnapshots work with Btrfs filesystems mounted at other than /, and make it work for directories
M +1 -0 CMakeLists.txt
M +32 -22 common/btrfssnapshots.cpp
M +7 -5 common/btrfssnapshots.h
M +1 -1 contextmenu/CMakeLists.txt
M +19 -2 contextmenu/snapshotfileitemaction.cpp
M +2 -2 kioworker/CMakeLists.txt
M +16 -4 kioworker/filesnapshots.cpp
https://invent.kde.org/system/kio-snapshot/-/commit/941552b646035830af34483be8bacc611f4a8ea9
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 602ba91..c6c9416 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -32,6 +32,7 @@ find_package(Qt6 ${QT_MIN_VERSION} REQUIRED COMPONENTS
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS
KIO
I18n
+ Solid
)
find_package(PkgConfig REQUIRED)
diff --git a/common/btrfssnapshots.cpp b/common/btrfssnapshots.cpp
index 394621f..4ba4a0b 100644
--- a/common/btrfssnapshots.cpp
+++ b/common/btrfssnapshots.cpp
@@ -15,12 +15,17 @@
using namespace Qt::StringLiterals;
-std::optional<qulonglong> BtrfsSnapshots::getSubvolumeForPath(const QString &path)
+const char *cstr(const QString &s)
+{
+ return s.toLocal8Bit().constData();
+}
+
+std::optional<qulonglong> BtrfsSnapshots::getSubvolumeForPath(const QString &path, const QString &fsRoot)
{
enum btrfs_util_error btrfs_err;
struct btrfs_util_subvolume_iterator *iter;
- btrfs_err = btrfs_util_subvolume_iter_create("/", 0, 0, &iter);
+ btrfs_err = btrfs_util_subvolume_iter_create(cstr(fsRoot), 0, 0, &iter);
if (btrfs_err != 0) {
return std::nullopt;
}
@@ -28,7 +33,7 @@ std::optional<qulonglong> BtrfsSnapshots::getSubvolumeForPath(const QString &pat
struct btrfs_util_subvolume_info iter_info;
char *iter_path;
while ((btrfs_err = btrfs_util_subvolume_iter_next_info(iter, &iter_path, &iter_info)) == 0) {
- if (path == "/"_L1 + QString::fromUtf8(iter_path)) {
+ if (path == QDir::cleanPath(fsRoot + "/"_L1 + QString::fromUtf8(iter_path))) {
free(iter_path);
return static_cast<qulonglong>(iter_info.id);
}
@@ -38,12 +43,12 @@ std::optional<qulonglong> BtrfsSnapshots::getSubvolumeForPath(const QString &pat
return std::nullopt;
}
-std::optional<QString> BtrfsSnapshots::getPathForSubvolume(qulonglong subvolume)
+std::optional<QString> BtrfsSnapshots::getPathForSubvolume(qulonglong subvolume, const QString &fsRoot)
{
enum btrfs_util_error btrfs_err;
struct btrfs_util_subvolume_iterator *iter;
- btrfs_err = btrfs_util_subvolume_iter_create("/", 0, 0, &iter);
+ btrfs_err = btrfs_util_subvolume_iter_create(cstr(fsRoot), 0, 0, &iter);
if (btrfs_err != 0) {
return std::nullopt;
}
@@ -52,7 +57,7 @@ std::optional<QString> BtrfsSnapshots::getPathForSubvolume(qulonglong subvolume)
char *iter_path;
while ((btrfs_err = btrfs_util_subvolume_iter_next_info(iter, &iter_path, &iter_info)) == 0) {
if (subvolume == static_cast<qulonglong>(iter_info.id)) {
- QString path = "/"_L1 + QString::fromUtf8(iter_path);
+ QString path = QDir::cleanPath(fsRoot + "/"_L1 + QString::fromUtf8(iter_path));
free(iter_path);
return path;
}
@@ -62,14 +67,19 @@ std::optional<QString> BtrfsSnapshots::getPathForSubvolume(qulonglong subvolume)
return std::nullopt;
}
-QList<BtrfsSnapshots::FileSnapshot> BtrfsSnapshots::getSnapshotsForFile(const QString &path)
+QList<BtrfsSnapshots::FileSnapshot> BtrfsSnapshots::getSnapshotsForFile(const QString &path, const QString &fsRoot)
{
QList<FileSnapshot> fileSnapshots;
-
- QDir subvolumeRoot(QFileInfo(path).absoluteDir());
- struct btrfs_util_subvolume_info info;
+ QDir subvolumeRoot;
+ QFileInfo fileInfo(path);
+ if (fileInfo.isDir()) {
+ subvolumeRoot = QDir(path);
+ } else {
+ QDir subvolumeRoot(QFileInfo(path).absoluteDir());
+ }
+ struct btrfs_util_subvolume_info subvolume_root_info;
enum btrfs_util_error btrfs_err;
- while (!subvolumeRoot.isRoot() && (btrfs_err = btrfs_util_subvolume_get_info(subvolumeRoot.absolutePath().toLocal8Bit().constData(), 0, &info)) != 0) {
+ while (!subvolumeRoot.isRoot() && (btrfs_err = btrfs_util_subvolume_get_info(cstr(subvolumeRoot.absolutePath()), 0, &subvolume_root_info)) != 0) {
subvolumeRoot.cdUp();
}
@@ -80,7 +90,7 @@ QList<BtrfsSnapshots::FileSnapshot> BtrfsSnapshots::getSnapshotsForFile(const QS
QString pathRel = subvolumeRoot.relativeFilePath(path);
struct btrfs_util_subvolume_iterator *iter;
- btrfs_err = btrfs_util_subvolume_iter_create(subvolumeRoot.absolutePath().toLocal8Bit().constData(), 0, 0, &iter);
+ btrfs_err = btrfs_util_subvolume_iter_create(cstr(fsRoot), 0, 0, &iter);
if (btrfs_err != 0) {
return fileSnapshots;
}
@@ -88,11 +98,11 @@ QList<BtrfsSnapshots::FileSnapshot> BtrfsSnapshots::getSnapshotsForFile(const QS
struct btrfs_util_subvolume_info iter_info;
char *iter_path;
while ((btrfs_err = btrfs_util_subvolume_iter_next_info(iter, &iter_path, &iter_info)) == 0) {
- if (QByteArrayView::fromArray(iter_info.parent_uuid) == QByteArrayView::fromArray(info.uuid)) {
- QString snapshotSubvolPath = QDir::cleanPath(subvolumeRoot.absolutePath() + "/"_L1 + QString::fromUtf8(iter_path));
+ if (QByteArrayView::fromArray(iter_info.parent_uuid) == QByteArrayView::fromArray(subvolume_root_info.uuid)) {
+ QString snapshotSubvolPath = QDir::cleanPath(fsRoot + "/"_L1 + QString::fromUtf8(iter_path));
QString filePath = QDir(snapshotSubvolPath).absoluteFilePath(pathRel);
QFileInfo file(filePath);
- if (file.exists() && file.isFile() && file.isReadable()) {
+ if (file.exists() && file.isReadable()) {
FileSnapshot snapshotInfo;
snapshotInfo.path = QDir(snapshotSubvolPath).absoluteFilePath(pathRel);
snapshotInfo.snapshotted = QDateTime::fromMSecsSinceEpoch(iter_info.otime.tv_sec * 1000 + iter_info.otime.tv_nsec / 1000000);
@@ -107,20 +117,20 @@ QList<BtrfsSnapshots::FileSnapshot> BtrfsSnapshots::getSnapshotsForFile(const QS
return fileSnapshots;
}
-QList<BtrfsSnapshots::SubvolumeSnapshot> BtrfsSnapshots::getSnapshotsForSubvolume(const QString &path)
+QList<BtrfsSnapshots::SubvolumeSnapshot> BtrfsSnapshots::getSnapshotsForSubvolume(const QString &path, const QString &fsRoot)
{
QList<SubvolumeSnapshot> subvolSnapshots;
struct btrfs_util_subvolume_info info;
enum btrfs_util_error btrfs_err;
- btrfs_err = btrfs_util_subvolume_get_info(path.toLocal8Bit().constData(), 0, &info);
+ btrfs_err = btrfs_util_subvolume_get_info(cstr(path), 0, &info);
if (btrfs_err != 0) {
return subvolSnapshots;
}
struct btrfs_util_subvolume_iterator *iter;
- btrfs_err = btrfs_util_subvolume_iter_create(path.toLocal8Bit().constData(), 0, 0, &iter);
+ btrfs_err = btrfs_util_subvolume_iter_create(cstr(fsRoot), 0, 0, &iter);
if (btrfs_err != 0) {
return subvolSnapshots;
}
@@ -130,7 +140,7 @@ QList<BtrfsSnapshots::SubvolumeSnapshot> BtrfsSnapshots::getSnapshotsForSubvolum
while ((btrfs_err = btrfs_util_subvolume_iter_next_info(iter, &iter_path, &iter_info)) == 0) {
if (QByteArrayView::fromArray(iter_info.parent_uuid) == QByteArrayView::fromArray(info.uuid)) {
SubvolumeSnapshot snapshotInfo;
- snapshotInfo.path = QString::fromUtf8(iter_path);
+ snapshotInfo.path = QDir::cleanPath(fsRoot + "/"_L1 + QString::fromUtf8(iter_path));
snapshotInfo.subvolumeId = static_cast<qulonglong>(iter_info.id);
snapshotInfo.snapshotted = QDateTime::fromMSecsSinceEpoch(iter_info.otime.tv_sec * 1000 + iter_info.otime.tv_nsec / 1000000);
subvolSnapshots << snapshotInfo;
@@ -141,14 +151,14 @@ QList<BtrfsSnapshots::SubvolumeSnapshot> BtrfsSnapshots::getSnapshotsForSubvolum
return subvolSnapshots;
}
-QMap<qulonglong, QString> BtrfsSnapshots::getNonSnapshotSubvolumes()
+QMap<qulonglong, QString> BtrfsSnapshots::getNonSnapshotSubvolumes(const QString &fsRoot)
{
QMap<qulonglong, QString> subvolumes;
enum btrfs_util_error btrfs_err;
struct btrfs_util_subvolume_iterator *iter;
- btrfs_err = btrfs_util_subvolume_iter_create("/", 0, 0, &iter);
+ btrfs_err = btrfs_util_subvolume_iter_create(cstr(fsRoot), 0, 0, &iter);
if (btrfs_err != 0) {
return subvolumes;
}
@@ -157,7 +167,7 @@ QMap<qulonglong, QString> BtrfsSnapshots::getNonSnapshotSubvolumes()
char *iter_path;
while ((btrfs_err = btrfs_util_subvolume_iter_next_info(iter, &iter_path, &iter_info)) == 0) {
if (QUuid::fromBytes(iter_info.parent_uuid).isNull()) {
- subvolumes[static_cast<qulonglong>(iter_info.id)] = "/"_L1 + QString::fromUtf8(iter_path);
+ subvolumes[static_cast<qulonglong>(iter_info.id)] = QDir::cleanPath(fsRoot + "/"_L1 + QString::fromUtf8(iter_path));
}
free(iter_path);
}
diff --git a/common/btrfssnapshots.h b/common/btrfssnapshots.h
index 52caf09..7ea4500 100644
--- a/common/btrfssnapshots.h
+++ b/common/btrfssnapshots.h
@@ -12,6 +12,8 @@
#ifndef BTRFSSNAPSHOTS_H
#define BTRFSSNAPSHOTS_H
+using namespace Qt::StringLiterals;
+
namespace BtrfsSnapshots
{
class FileSnapshot
@@ -31,11 +33,11 @@ public:
QDateTime snapshotted;
};
-std::optional<qulonglong> getSubvolumeForPath(const QString &path);
-std::optional<QString> getPathForSubvolume(qulonglong subvolume);
-QList<SubvolumeSnapshot> getSnapshotsForSubvolume(const QString &path);
-QList<FileSnapshot> getSnapshotsForFile(const QString &path);
-QMap<qulonglong, QString> getNonSnapshotSubvolumes();
+std::optional<qulonglong> getSubvolumeForPath(const QString &path, const QString &fsRoot = "/"_L1);
+std::optional<QString> getPathForSubvolume(qulonglong subvolume, const QString &fsRoot = "/"_L1);
+QList<SubvolumeSnapshot> getSnapshotsForSubvolume(const QString &path, const QString &fsRoot = "/"_L1);
+QList<FileSnapshot> getSnapshotsForFile(const QString &path, const QString &fsRoot = "/"_L1);
+QMap<qulonglong, QString> getNonSnapshotSubvolumes(const QString &fsRoot = "/"_L1);
}
#endif
diff --git a/contextmenu/CMakeLists.txt b/contextmenu/CMakeLists.txt
index 6944522..0d4a1ca 100644
--- a/contextmenu/CMakeLists.txt
+++ b/contextmenu/CMakeLists.txt
@@ -21,4 +21,4 @@ ecm_qt_declare_logging_category(snapshotfileitemaction
DESCRIPTION "Snapshot file item action"
)
-target_link_libraries(snapshotfileitemaction KF6::KIOCore KF6::KIOWidgets KF6::I18n btrfssnapshots_lib)
+target_link_libraries(snapshotfileitemaction KF6::KIOCore KF6::KIOWidgets KF6::I18n KF6::Solid btrfssnapshots_lib)
diff --git a/contextmenu/snapshotfileitemaction.cpp b/contextmenu/snapshotfileitemaction.cpp
index 91e10ea..c834638 100644
--- a/contextmenu/snapshotfileitemaction.cpp
+++ b/contextmenu/snapshotfileitemaction.cpp
@@ -12,6 +12,9 @@
#include <KIO/JobUiDelegate>
#include <KIO/OpenUrlJob>
+#include <Solid/Device>
+#include <Solid/StorageAccess>
+
#include <KFileItem>
#include <KLocalizedString>
#include <KPluginFactory>
@@ -47,7 +50,14 @@ QList<QAction *> SnapshotFileItemAction::actions(const KFileItemListProperties &
}
if (item.isDir()) {
- if (!BtrfsSnapshots::getSnapshotsForSubvolume(itemUrl.toLocalFile()).empty()) {
+ QString localPath = itemUrl.toLocalFile();
+ auto fsRoot = Solid::Device::storageAccessFromPath(localPath).as<Solid::StorageAccess>();
+ if (!fsRoot) {
+ qCCritical(SNAPSHOT_FILEITEMACTION()) << "could not determine fs root path for" << localPath;
+ return actions;
+ }
+ QString fsRootPath = fsRoot->filePath();
+ if (!BtrfsSnapshots::getSnapshotsForSubvolume(itemUrl.toLocalFile(), fsRootPath).empty()) {
auto subvolumeIdOpt = BtrfsSnapshots::getSubvolumeForPath(itemUrl.toLocalFile());
if (!subvolumeIdOpt.has_value()) {
qCCritical(SNAPSHOT_FILEITEMACTION()) << "found snapshots for dir" << itemUrl.toLocalFile() << "but it did not have a subvolume id";
@@ -62,7 +72,14 @@ QList<QAction *> SnapshotFileItemAction::actions(const KFileItemListProperties &
actions << action;
}
} else if (item.isLocalFile()) {
- if (!BtrfsSnapshots::getSnapshotsForFile(itemUrl.toLocalFile()).empty()) {
+ QString localPath = itemUrl.toLocalFile();
+ auto fsRoot = Solid::Device::storageAccessFromPath(localPath).as<Solid::StorageAccess>();
+ if (!fsRoot) {
+ qCCritical(SNAPSHOT_FILEITEMACTION()) << "could not determine fs root path for" << localPath;
+ return actions;
+ }
+ QString fsRootPath = fsRoot->filePath();
+ if (!BtrfsSnapshots::getSnapshotsForFile(itemUrl.toLocalFile(), fsRootPath).empty()) {
QAction *action = new QAction(QIcon::fromTheme("view-history"_L1), i18nc("@action:inmenu", "View snapshots…"), parentWidget);
connect(action, &QAction::triggered, this, [this, item]() {
KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl("filesnapshots://%1"_L1.arg(item.localPath())), "inode/directory"_L1, this);
diff --git a/kioworker/CMakeLists.txt b/kioworker/CMakeLists.txt
index 663b7c0..78f2907 100644
--- a/kioworker/CMakeLists.txt
+++ b/kioworker/CMakeLists.txt
@@ -43,5 +43,5 @@ ecm_qt_declare_logging_category(kio_filesnapshots
EXPORT KIO
)
-target_link_libraries(kio_snapshot KF6::KIOCore KF6::I18n btrfssnapshots_lib)
-target_link_libraries(kio_filesnapshots KF6::KIOCore KF6::I18n btrfssnapshots_lib)
+target_link_libraries(kio_snapshot KF6::KIOCore KF6::I18n KF6::Solid btrfssnapshots_lib)
+target_link_libraries(kio_filesnapshots KF6::KIOCore KF6::I18n KF6::Solid btrfssnapshots_lib)
diff --git a/kioworker/filesnapshots.cpp b/kioworker/filesnapshots.cpp
index 1ec5fdd..9f034bc 100644
--- a/kioworker/filesnapshots.cpp
+++ b/kioworker/filesnapshots.cpp
@@ -14,6 +14,9 @@
#include <KIO/UDSEntry>
#include <KIO/WorkerBase>
+#include <Solid/Device>
+#include <Solid/StorageAccess>
+
#include <KLocalizedString>
#include <QCoreApplication>
@@ -58,7 +61,15 @@ KIO::WorkerResult FileSnapshotsProtocol::listDir(const QUrl &url)
{
qCDebug(KIO_FILESNAPSHOTS) << "url" << url << "url.host" << url.host() << "subvolume" << url.path();
- QList<BtrfsSnapshots::FileSnapshot> snapshots = BtrfsSnapshots::getSnapshotsForFile(url.path());
+ QString localPath = url.path();
+ auto fsRoot = Solid::Device::storageAccessFromPath(localPath).as<Solid::StorageAccess>();
+ if (!fsRoot) {
+ qCCritical(KIO_FILESNAPSHOTS) << "could not determine fs root path for" << localPath;
+ return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST);
+ }
+ QString fsRootPath = fsRoot->filePath();
+
+ QList<BtrfsSnapshots::FileSnapshot> snapshots = BtrfsSnapshots::getSnapshotsForFile(url.path(), fsRootPath);
std::sort(snapshots.begin(), snapshots.end(), [](const BtrfsSnapshots::FileSnapshot &a, const BtrfsSnapshots::FileSnapshot &b) {
return a.snapshotted.toSecsSinceEpoch() > b.snapshotted.toSecsSinceEpoch();
@@ -67,14 +78,15 @@ KIO::WorkerResult FileSnapshotsProtocol::listDir(const QUrl &url)
QFileInfo currentInfo(url.path());
BtrfsSnapshots::FileSnapshot current;
current.path = url.path();
- current.snapshotted = currentInfo.lastModified();
+ current.snapshotted = QDateTime::currentDateTime();
current.modified = currentInfo.lastModified();
+ current.subvolumeId = 0;
snapshots.insert(0, current);
QList<BtrfsSnapshots::FileSnapshot> snapshotsFiltered;
for (qsizetype i = 0; i < snapshots.size(); i++) {
const BtrfsSnapshots::FileSnapshot &info = snapshots.at(i);
- if (i == 0 || snapshots.at(i - 1).modified != info.modified || snapshots.at(i - 1).modified != info.modified) {
+ if (true || i == 0 || snapshots.at(i - 1).modified != info.modified || snapshots.at(i - 1).modified != info.modified) {
snapshotsFiltered << info;
}
}
@@ -93,7 +105,7 @@ KIO::WorkerResult FileSnapshotsProtocol::listDir(const QUrl &url)
} else {
continue;
}
- entry.replace(KIO::UDSEntry::UDS_NAME, "%1-%2"_L1.arg(entry.stringValue(KIO::UDSEntry::UDS_NAME), QString::number(snapshot.subvolumeId)));
+ entry.replace(KIO::UDSEntry::UDS_NAME, "snapshot-%1-%2"_L1.arg(entry.stringValue(KIO::UDSEntry::UDS_NAME), QString::number(snapshot.subvolumeId)));
entry.replace(KIO::UDSEntry::UDS_ACCESS, S_IRUSR);
if (snapshot.path == url.path()) {
dateRepr = i18nc("denoting the present / most-recent version of the file", "Current");