[frameworks/kio/Frameworks/6.24] autotests: autotests: verify POSIX ACL preservation when copying a file

Marco Martin <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit f4c10424cc9f94886f9c168e52272ab281aa28ee by Marco Martin, on behalf of Méven Car.
Committed on 27/07/2026 at 13:48.
Pushed by mart into branch 'Frameworks/6.24'.

autotests: verify POSIX ACL preservation when copying a file

Adds JobTest::copyFilePreservesAcl: it puts an extended POSIX ACL (a named-user
entry) on a source file, copies it with KIO::file_copy and KIO::copy, and checks
the destination's access ACL matches the source's.

Skipped when ACL support is not compiled in, on FreeBSD (the helper is not
adapted there yet), or when the filesystem under the test home does not support
POSIX ACLs.

M  +7    -0    autotests/CMakeLists.txt
M  +74   -0    autotests/jobtest.cpp
M  +1    -0    autotests/jobtest.h

https://invent.kde.org/frameworks/kio/-/commit/f4c10424cc9f94886f9c168e52272ab281aa28ee

diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
index 06d94a548b..a711beb71d 100644
--- a/autotests/CMakeLists.txt
+++ b/autotests/CMakeLists.txt
@@ -56,6 +56,13 @@ if (NOT WIN32)
   )
 
   target_link_libraries(deleteortrashjobtest KF6::KIOWidgets)
+
+  if(TARGET Qt6::DBus)
+    target_link_libraries(deletejobtest Qt6::DBus)
+  endif()
+  if(ACL_FOUND)
+    target_link_libraries(jobtest ${ACL_LIBS}) # copyFilePreservesAcl()
+  endif()
 endif()
 
 ecm_add_test(
diff --git a/autotests/jobtest.cpp b/autotests/jobtest.cpp
index 7c578f845e..9848895991 100644
--- a/autotests/jobtest.cpp
+++ b/autotests/jobtest.cpp
@@ -8,6 +8,12 @@
 #include "jobtest.h"
 #include "mockcoredelegateextensions.h"
 
+#include <config-kiocore.h> // HAVE_POSIX_ACL
+#if HAVE_POSIX_ACL
+#include <cstring>
+#include <sys/acl.h>
+#endif
+
 #include "kio/job.h"
 #include "kiotesthelper.h" // createTestFile etc.
 #include "worker_p.h"
@@ -38,6 +44,7 @@
 #include <QHash>
 #include <QPointer>
 #include <QProcess>
+#include <QScopeGuard>
 #include <QSignalSpy>
 #include <QTemporaryFile>
 #include <QTest>
@@ -797,6 +804,73 @@ void JobTest::copyFileToSamePartition()
     copyLocalFile(filePath, dest);
 }
 
+void JobTest::copyFilePreservesAcl()
+{
+#if !HAVE_POSIX_ACL
+    QSKIP("POSIX ACL support not compiled in");
+#elif defined(Q_OS_FREEBSD)
+    QSKIP("The test is not adapted for FreeBSD yet");
+#else
+    const QString homeDir = homeTmpDir();
+    const QString src = homeDir + "fileWithAcl";
+    const QString dest = homeDir + "fileWithAcl_copied";
+    createTestFile(src);
+    // Remove both files however we leave this function: createTestFile() and the copy jobs run
+    // before QVERIFY/QCOMPARE checks that return early on failure, and listRecursive() lists
+    // homeTmpDir() against a fixed reference. These files exist only when the filesystem supports
+    // ACLs, so they cannot be part of that reference and must not be left behind.
+    auto cleanup = qScopeGuard([&] {
+        QFile::remove(src);
+        QFile::remove(dest);
+    });
+    const QByteArray srcEnc = QFile::encodeName(src);
+    const QByteArray destEnc = QFile::encodeName(dest);
+
+    // A named-user entry makes this an *extended* ACL (stored as the system.posix_acl_access
+    // xattr); a full-rights mask avoids acl_to_text "#effective" annotations so a plain string
+    // comparison suffices.
+    acl_t want = acl_from_text("u::rw-,u:0:rwx,g::r--,m::rwx,o::r--");
+    QVERIFY(want);
+    if (acl_valid(want) != 0 || acl_set_file(srcEnc.constData(), ACL_TYPE_ACCESS, want) != 0) {
+        acl_free(want);
+        QSKIP("Filesystem under the test home does not support POSIX ACLs");
+    }
+    acl_free(want);
+
+    auto aclText = [](const QByteArray &path) {
+        acl_t acl = acl_get_file(path.constData(), ACL_TYPE_ACCESS);
+        char *text = acl ? acl_to_text(acl, nullptr) : nullptr;
+        const QByteArray result = text ? QByteArray(text) : QByteArray();
+        if (text) {
+            acl_free(text);
+        }
+        if (acl) {
+            acl_free(acl);
+        }
+        return result;
+    };
+    const QByteArray srcAcl = aclText(srcEnc);
+    QVERIFY(!srcAcl.isEmpty());
+
+    const QUrl u = QUrl::fromLocalFile(src);
+    const QUrl d = QUrl::fromLocalFile(dest);
+
+    // file_copy preserving the source mode (-1) goes through copy()'s ACL-preservation path.
+    auto *fileCopy = KIO::file_copy(u, d, -1, KIO::HideProgressInfo);
+    fileCopy->setUiDelegate(nullptr);
+    QVERIFY2(fileCopy->exec(), qPrintable(fileCopy->errorString()));
+    QCOMPARE(aclText(destEnc), srcAcl);
+    QFile::remove(dest);
+
+    // And the high-level CopyJob path (what users actually trigger).
+    auto *copyJob = KIO::copy(u, d, KIO::HideProgressInfo);
+    copyJob->setUiDelegate(nullptr);
+    copyJob->setUiDelegateExtension(nullptr);
+    QVERIFY2(copyJob->exec(), qPrintable(copyJob->errorString()));
+    QCOMPARE(aclText(destEnc), srcAcl);
+#endif
+}
+
 void JobTest::copyDirectoryToSamePartition()
 {
     // qDebug();
diff --git a/autotests/jobtest.h b/autotests/jobtest.h
index 88e9c3067f..d4b38b0f2b 100644
--- a/autotests/jobtest.h
+++ b/autotests/jobtest.h
@@ -47,6 +47,7 @@ private Q_SLOTS:
     void storedPutIODeviceSlowDeviceBigChunk();
     void asyncStoredPutReadyReadAfterFinish();
     void copyFileToSamePartition();
+    void copyFilePreservesAcl();
     void testCopyFilePermissionsToSamePartition();
     void copyDirectoryToSamePartition();
     void copyDirectoryToExistingDirectory();
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.