From ff2313102100d10b9f85f7ec24b68b388947e631 Mon Sep 17 00:00:00 2001
From: Samuli Piippo <samuli.piippo@qt.io>
Date: Wed, 20 Mar 2019 15:39:59 +0200
Subject: [PATCH 09/11] QStandardPaths: Correct handling for XDG_RUNTIME_DIR

Always try to create the runtime directory and never change
the permissions of an existing directory. Conform to the
XDG Base Directory Specification:

"If, when attempting to write a file, the destination directory
is non-existent an attempt should be made to create it with
permission 0700. If the destination directory exists already
the permissions should not be changed."

Fixes: QTBUG-68338
Change-Id: Iaf854d69225fc46e43abae86232d749e5c247df0
Reviewed-by: David Faure <david.faure@kdab.com>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
(cherry picked from commit 55427858e3f9d98dc7a75638c54a4fffde6be73a)
---
 src/corelib/io/qstandardpaths_unix.cpp        | 51 ++++++++++---------
 .../io/qstandardpaths/tst_qstandardpaths.cpp  | 34 ++++++++-----
 2 files changed, 48 insertions(+), 37 deletions(-)

diff --git a/src/corelib/io/qstandardpaths_unix.cpp b/src/corelib/io/qstandardpaths_unix.cpp
index eaa545b4fd..9234d836bd 100644
--- a/src/corelib/io/qstandardpaths_unix.cpp
+++ b/src/corelib/io/qstandardpaths_unix.cpp
@@ -120,54 +120,59 @@ QString QStandardPaths::writableLocation(StandardLocation type)
     }
     case RuntimeLocation:
     {
-        const uint myUid = uint(geteuid());
         // http://standards.freedesktop.org/basedir-spec/latest/
+        const uint myUid = uint(geteuid());
+        // since the current user is the owner, set both xxxUser and xxxOwner
+        const QFile::Permissions wantedPerms = QFile::ReadUser | QFile::WriteUser | QFile::ExeUser
+                                               | QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner;
         QFileInfo fileInfo;
         QString xdgRuntimeDir = QFile::decodeName(qgetenv("XDG_RUNTIME_DIR"));
         if (xdgRuntimeDir.isEmpty()) {
             const QString userName = QFileSystemEngine::resolveUserName(myUid);
             xdgRuntimeDir = QDir::tempPath() + QLatin1String("/runtime-") + userName;
             fileInfo.setFile(xdgRuntimeDir);
-            if (!fileInfo.isDir()) {
-                if (!QDir().mkdir(xdgRuntimeDir)) {
-                    qWarning("QStandardPaths: error creating runtime directory %s: %s", qPrintable(xdgRuntimeDir), qPrintable(qt_error_string(errno)));
-                    return QString();
-                }
-            }
 #ifndef Q_OS_WASM
             qWarning("QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '%s'", qPrintable(xdgRuntimeDir));
 #endif
         } else {
             fileInfo.setFile(xdgRuntimeDir);
-            if (!fileInfo.exists()) {
-                qWarning("QStandardPaths: XDG_RUNTIME_DIR points to non-existing path '%s', "
-                         "please create it with 0700 permissions.", qPrintable(xdgRuntimeDir));
-                return QString();
-            }
+        }
+        if (fileInfo.exists()) {
             if (!fileInfo.isDir()) {
                 qWarning("QStandardPaths: XDG_RUNTIME_DIR points to '%s' which is not a directory",
                          qPrintable(xdgRuntimeDir));
                 return QString();
             }
+        } else {
+            QFileSystemEntry entry(xdgRuntimeDir);
+            if (!QFileSystemEngine::createDirectory(entry, false)) {
+                if (errno != EEXIST) {
+                    qWarning("QStandardPaths: error creating runtime directory %s: %s",
+                             qPrintable(xdgRuntimeDir), qPrintable(qt_error_string(errno)));
+                    return QString();
+                }
+            } else {
+                QSystemError error;
+                if (!QFileSystemEngine::setPermissions(entry, wantedPerms, error)) {
+                    qWarning("QStandardPaths: could not set correct permissions on runtime directory %s: %s",
+                             qPrintable(xdgRuntimeDir), qPrintable(error.toString()));
+                    return QString();
+                }
+            }
         }
         // "The directory MUST be owned by the user"
         if (fileInfo.ownerId() != myUid) {
-            qWarning("QStandardPaths: wrong ownership on runtime directory %s, %d instead of %d", qPrintable(xdgRuntimeDir),
-                     fileInfo.ownerId(), myUid);
+            qWarning("QStandardPaths: wrong ownership on runtime directory %s, %d instead of %d",
+                        qPrintable(xdgRuntimeDir), fileInfo.ownerId(), myUid);
             return QString();
         }
         // "and he MUST be the only one having read and write access to it. Its Unix access mode MUST be 0700."
-        // since the current user is the owner, set both xxxUser and xxxOwner
-        const QFile::Permissions wantedPerms = QFile::ReadUser | QFile::WriteUser | QFile::ExeUser
-                                               | QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner;
         if (fileInfo.permissions() != wantedPerms) {
-            QFile file(xdgRuntimeDir);
-            if (!file.setPermissions(wantedPerms)) {
-                qWarning("QStandardPaths: could not set correct permissions on runtime directory %s: %s",
-                         qPrintable(xdgRuntimeDir), qPrintable(file.errorString()));
-                return QString();
-            }
+            qWarning("QStandardPaths: wrong permissions on runtime directory %s, %x instead of %x",
+                        qPrintable(xdgRuntimeDir), uint(fileInfo.permissions()), uint(wantedPerms));
+            return QString();
         }
+
         return xdgRuntimeDir;
     }
     default:
diff --git a/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp b/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp
index 1379c788d1..40023d7fea 100644
--- a/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp
+++ b/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp
@@ -465,16 +465,6 @@ void tst_qstandardpaths::testRuntimeDirectory()
 #ifdef Q_XDG_PLATFORM
     const QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
     QVERIFY(!runtimeDir.isEmpty());
-
-    // Check that it can automatically fix permissions
-    QFile file(runtimeDir);
-    const QFile::Permissions wantedPerms = QFile::ReadUser | QFile::WriteUser | QFile::ExeUser;
-    const QFile::Permissions additionalPerms = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner;
-    QCOMPARE(file.permissions(), wantedPerms | additionalPerms);
-    QVERIFY(file.setPermissions(wantedPerms | QFile::ExeGroup));
-    const QString runtimeDirAgain = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
-    QCOMPARE(runtimeDirAgain, runtimeDir);
-    QCOMPARE(QFile(runtimeDirAgain).permissions(), wantedPerms | additionalPerms);
 #endif
 }
 
@@ -516,11 +506,27 @@ void tst_qstandardpaths::testCustomRuntimeDirectory()
     const QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
     QVERIFY2(runtimeDir.isEmpty(), qPrintable(runtimeDir));
 
-    // When $XDG_RUNTIME_DIR points to a non-existing directory, QStandardPaths should warn (QTBUG-48771)
-    qputenv("XDG_RUNTIME_DIR", "does_not_exist");
-    QTest::ignoreMessage(QtWarningMsg, "QStandardPaths: XDG_RUNTIME_DIR points to non-existing path 'does_not_exist', please create it with 0700 permissions.");
+    // When $XDG_RUNTIME_DIR points to a directory with wrong permissions, QStandardPaths should warn
+    const QByteArray wrongPermissionFileName = "wrong_permissions";
+    QDir::current().mkdir(wrongPermissionFileName);
+    QFile wrongPermissionFile(wrongPermissionFileName);
+    const QFile::Permissions wantedPerms = QFile::ReadUser | QFile::WriteUser | QFile::ExeUser;
+    QVERIFY(wrongPermissionFile.setPermissions(wantedPerms | QFile::ExeGroup));
+
+    qputenv("XDG_RUNTIME_DIR", wrongPermissionFileName);
+    QTest::ignoreMessage(QtWarningMsg,
+           qPrintable(QString::fromLatin1("QStandardPaths: wrong permissions on runtime directory " + wrongPermissionFileName + ", 7710 instead of 7700")));
+    const QString wrongPermissionRuntimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
+    QVERIFY(wrongPermissionRuntimeDir.isEmpty());
+    QDir::current().rmdir(wrongPermissionFileName);
+
+    // When $XDG_RUNTIME_DIR points to a non-existing directory, QStandardPaths should create it first
+    const QByteArray nonExistingDir = "does_not_exist";
+    qputenv("XDG_RUNTIME_DIR", nonExistingDir);
     const QString nonExistingRuntimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
-    QVERIFY2(nonExistingRuntimeDir.isEmpty(), qPrintable(nonExistingRuntimeDir));
+    QVERIFY2(!nonExistingRuntimeDir.compare(nonExistingDir), qPrintable(nonExistingRuntimeDir));
+    QVERIFY(QDir::current().exists(nonExistingRuntimeDir));
+    QDir::current().rmdir(nonExistingRuntimeDir);
 
     // When $XDG_RUNTIME_DIR points to a file, QStandardPaths should warn
     const QString file = QFINDTESTDATA("tst_qstandardpaths.cpp");
-- 
2.25.1

