qsettings_XDG_CONFIG_DIRS.patch 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. From 4758555f3e44af3425f0b691dc38fb40f3c9413d Mon Sep 17 00:00:00 2001
  2. From: Palo Kisa <palo.kisa@gmail.com>
  3. Date: Wed, 24 Aug 2016 02:29:59 +0200
  4. Subject: QSettings: Add proper support for XDG_CONFIG_DIRS
  5. Update fallback mechanism for Q_XDG_PLATFORM based systems to follow the
  6. Xdg specification.
  7. [ChangeLog][QtCore][QSettings] Added proper support for system-wide
  8. configuration file lookup based on Xdg spec (XDG_CONFIG_DIRS) on Unix
  9. based systems
  10. Task-number: QTBUG-34919
  11. Change-Id: Ieddee1c0b3b1506bf19aa865bdab87fc81d58cfd
  12. Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
  13. ---
  14. src/corelib/io/qsettings.cpp | 94 +++++++++++++++++------
  15. tests/auto/corelib/io/qsettings/tst_qsettings.cpp | 73 ++++++++++++++++++
  16. 2 files changed, 145 insertions(+), 22 deletions(-)
  17. diff --git a/src/corelib/io/qsettings.cpp b/src/corelib/io/qsettings.cpp
  18. index c3b2176..b67afe0 100644
  19. --- a/src/corelib/io/qsettings.cpp
  20. +++ b/src/corelib/io/qsettings.cpp
  21. @@ -136,7 +136,18 @@ Q_DECLARE_TYPEINFO(QConfFileCustomFormat, Q_MOVABLE_TYPE);
  22. typedef QHash<QString, QConfFile *> ConfFileHash;
  23. typedef QCache<QString, QConfFile> ConfFileCache;
  24. -typedef QHash<int, QString> PathHash;
  25. +namespace {
  26. + struct Path
  27. + {
  28. + // Note: Defining constructors explicitly because of buggy C++11
  29. + // implementation in MSVC (uniform initialization).
  30. + Path() {}
  31. + Path(const QString & p, bool ud) : path(p), userDefined(ud) {}
  32. + QString path;
  33. + bool userDefined; //!< true - user defined, overridden by setPath
  34. + };
  35. +}
  36. +typedef QHash<int, Path> PathHash;
  37. typedef QVector<QConfFileCustomFormat> CustomFormatVector;
  38. Q_GLOBAL_STATIC(ConfFileHash, usedHashFunc)
  39. @@ -1065,22 +1076,22 @@ static void initDefaultPaths(QMutexLocker *locker)
  40. */
  41. #ifdef Q_OS_WIN
  42. pathHash->insert(pathHashKey(QSettings::IniFormat, QSettings::UserScope),
  43. - windowsConfigPath(CSIDL_APPDATA) + QDir::separator());
  44. + Path(windowsConfigPath(CSIDL_APPDATA) + QDir::separator(), false));
  45. pathHash->insert(pathHashKey(QSettings::IniFormat, QSettings::SystemScope),
  46. - windowsConfigPath(CSIDL_COMMON_APPDATA) + QDir::separator());
  47. + Path(windowsConfigPath(CSIDL_COMMON_APPDATA) + QDir::separator(), false));
  48. #else
  49. const QString userPath = make_user_path();
  50. - pathHash->insert(pathHashKey(QSettings::IniFormat, QSettings::UserScope), userPath);
  51. - pathHash->insert(pathHashKey(QSettings::IniFormat, QSettings::SystemScope), systemPath);
  52. + pathHash->insert(pathHashKey(QSettings::IniFormat, QSettings::UserScope), Path(userPath, false));
  53. + pathHash->insert(pathHashKey(QSettings::IniFormat, QSettings::SystemScope), Path(systemPath, false));
  54. #ifndef Q_OS_MAC
  55. - pathHash->insert(pathHashKey(QSettings::NativeFormat, QSettings::UserScope), userPath);
  56. - pathHash->insert(pathHashKey(QSettings::NativeFormat, QSettings::SystemScope), systemPath);
  57. + pathHash->insert(pathHashKey(QSettings::NativeFormat, QSettings::UserScope), Path(userPath, false));
  58. + pathHash->insert(pathHashKey(QSettings::NativeFormat, QSettings::SystemScope), Path(systemPath, false));
  59. #endif
  60. #endif // Q_OS_WIN
  61. }
  62. }
  63. -static QString getPath(QSettings::Format format, QSettings::Scope scope)
  64. +static Path getPath(QSettings::Format format, QSettings::Scope scope)
  65. {
  66. Q_ASSERT((int)QSettings::NativeFormat == 0);
  67. Q_ASSERT((int)QSettings::IniFormat == 1);
  68. @@ -1090,14 +1101,23 @@ static QString getPath(QSettings::Format format, QSettings::Scope scope)
  69. if (pathHash->isEmpty())
  70. initDefaultPaths(&locker);
  71. - QString result = pathHash->value(pathHashKey(format, scope));
  72. - if (!result.isEmpty())
  73. + Path result = pathHash->value(pathHashKey(format, scope));
  74. + if (!result.path.isEmpty())
  75. return result;
  76. // fall back on INI path
  77. return pathHash->value(pathHashKey(QSettings::IniFormat, scope));
  78. }
  79. +#if defined(QT_BUILD_INTERNAL) && defined(Q_XDG_PLATFORM) && !defined(QT_NO_STANDARDPATHS)
  80. +// Note: Suitable only for autotests.
  81. +void Q_AUTOTEST_EXPORT clearDefaultPaths()
  82. +{
  83. + QMutexLocker locker(&settingsGlobalMutex);
  84. + pathHashFunc()->clear();
  85. +}
  86. +#endif // QT_BUILD_INTERNAL && Q_XDG_PLATFORM && !QT_NO_STANDARDPATHS
  87. +
  88. QConfFileSettingsPrivate::QConfFileSettingsPrivate(QSettings::Format format,
  89. QSettings::Scope scope,
  90. const QString &organization,
  91. @@ -1117,16 +1137,44 @@ QConfFileSettingsPrivate::QConfFileSettingsPrivate(QSettings::Format format,
  92. QString orgFile = org + extension;
  93. if (scope == QSettings::UserScope) {
  94. - QString userPath = getPath(format, QSettings::UserScope);
  95. + Path userPath = getPath(format, QSettings::UserScope);
  96. if (!application.isEmpty())
  97. - confFiles.append(QConfFile::fromName(userPath + appFile, true));
  98. - confFiles.append(QConfFile::fromName(userPath + orgFile, true));
  99. + confFiles.append(QConfFile::fromName(userPath.path + appFile, true));
  100. + confFiles.append(QConfFile::fromName(userPath.path + orgFile, true));
  101. }
  102. - QString systemPath = getPath(format, QSettings::SystemScope);
  103. - if (!application.isEmpty())
  104. - confFiles.append(QConfFile::fromName(systemPath + appFile, false));
  105. - confFiles.append(QConfFile::fromName(systemPath + orgFile, false));
  106. + Path systemPath = getPath(format, QSettings::SystemScope);
  107. +#if defined(Q_XDG_PLATFORM) && !defined(QT_NO_STANDARDPATHS)
  108. + // check if the systemPath wasn't overridden by QSettings::setPath()
  109. + if (!systemPath.userDefined) {
  110. + // Note: We can't use QStandardPaths::locateAll() as we need all the
  111. + // possible files (not just the existing ones) and there is no way
  112. + // to exclude user specific (XDG_CONFIG_HOME) directory from the search.
  113. + QStringList dirs = QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation);
  114. + // remove the QStandardLocation::writableLocation() (XDG_CONFIG_HOME)
  115. + if (!dirs.isEmpty())
  116. + dirs.takeFirst();
  117. + QStringList paths;
  118. + if (!application.isEmpty()) {
  119. + paths.reserve(dirs.size() * 2);
  120. + for (const auto &dir : qAsConst(dirs))
  121. + paths.append(dir + QLatin1Char('/') + appFile);
  122. + } else {
  123. + paths.reserve(dirs.size());
  124. + }
  125. + for (const auto &dir : qAsConst(dirs))
  126. + paths.append(dir + QLatin1Char('/') + orgFile);
  127. +
  128. + // Note: No check for existence of files is done intentionaly.
  129. + for (const auto &path : qAsConst(paths))
  130. + confFiles.append(QConfFile::fromName(path, false));
  131. + } else
  132. +#endif // Q_XDG_PLATFORM && !QT_NO_STANDARDPATHS
  133. + {
  134. + if (!application.isEmpty())
  135. + confFiles.append(QConfFile::fromName(systemPath.path + appFile, false));
  136. + confFiles.append(QConfFile::fromName(systemPath.path + orgFile, false));
  137. + }
  138. initAccess();
  139. }
  140. @@ -2169,9 +2217,10 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile,
  141. \list 1
  142. \li \c{$HOME/.config/MySoft/Star Runner.conf} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft/Star Runner.conf})
  143. \li \c{$HOME/.config/MySoft.conf} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft.conf})
  144. - \li \c{/etc/xdg/MySoft/Star Runner.conf}
  145. - \li \c{/etc/xdg/MySoft.conf}
  146. + \li for each directory <dir> in $XDG_CONFIG_DIRS: \c{<dir>/MySoft/Star Runner.conf}
  147. + \li for each directory <dir> in $XDG_CONFIG_DIRS: \c{<dir>/MySoft.conf}
  148. \endlist
  149. + \note If XDG_CONFIG_DIRS is unset, the default value of \c{/etc/xdg} is used.
  150. On \macos versions 10.2 and 10.3, these files are used by
  151. default:
  152. @@ -2206,9 +2255,10 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile,
  153. \list 1
  154. \li \c{$HOME/.config/MySoft/Star Runner.ini} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft/Star Runner.ini})
  155. \li \c{$HOME/.config/MySoft.ini} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft.ini})
  156. - \li \c{/etc/xdg/MySoft/Star Runner.ini}
  157. - \li \c{/etc/xdg/MySoft.ini}
  158. + \li for each directory <dir> in $XDG_CONFIG_DIRS: \c{<dir>/MySoft/Star Runner.ini}
  159. + \li for each directory <dir> in $XDG_CONFIG_DIRS: \c{<dir>/MySoft.ini}
  160. \endlist
  161. + \note If XDG_CONFIG_DIRS is unset, the default value of \c{/etc/xdg} is used.
  162. On Windows, the following files are used:
  163. @@ -3360,7 +3410,7 @@ void QSettings::setPath(Format format, Scope scope, const QString &path)
  164. PathHash *pathHash = pathHashFunc();
  165. if (pathHash->isEmpty())
  166. initDefaultPaths(&locker);
  167. - pathHash->insert(pathHashKey(format, scope), path + QDir::separator());
  168. + pathHash->insert(pathHashKey(format, scope), Path(path + QDir::separator(), true));
  169. }
  170. /*!
  171. diff --git a/tests/auto/corelib/io/qsettings/tst_qsettings.cpp b/tests/auto/corelib/io/qsettings/tst_qsettings.cpp
  172. index 2f039cb..0e8a784 100644
  173. --- a/tests/auto/corelib/io/qsettings/tst_qsettings.cpp
  174. +++ b/tests/auto/corelib/io/qsettings/tst_qsettings.cpp
  175. @@ -159,6 +159,7 @@ private slots:
  176. void iniCodec();
  177. void bom();
  178. + void testXdg();
  179. private:
  180. void cleanupTestFiles();
  181. @@ -3397,5 +3398,77 @@ void tst_QSettings::consistentRegistryStorage()
  182. }
  183. #endif
  184. +#if defined(QT_BUILD_INTERNAL) && defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID) && !defined(QT_NO_STANDARDPATHS)
  185. +QT_BEGIN_NAMESPACE
  186. +extern void clearDefaultPaths();
  187. +QT_END_NAMESPACE
  188. +#endif
  189. +void tst_QSettings::testXdg()
  190. +{
  191. +#if defined(QT_BUILD_INTERNAL) && defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID) && !defined(QT_NO_STANDARDPATHS)
  192. + // Note: The XDG_CONFIG_DIRS test must be done before overriding the system path
  193. + // by QSettings::setPath/setSystemIniPath (used in cleanupTestFiles()).
  194. + clearDefaultPaths();
  195. +
  196. + // Initialize the env. variable & populate testing files.
  197. + const QStringList config_dirs = { settingsPath("xdg_1st"), settingsPath("xdg_2nd"), settingsPath("xdg_3rd") };
  198. + qputenv("XDG_CONFIG_DIRS", config_dirs.join(':').toUtf8());
  199. + QList<QSettings *> xdg_orgs, xdg_apps;
  200. + for (const auto & dir : config_dirs) {
  201. + xdg_orgs << new QSettings{dir + "/software.org.conf", QSettings::NativeFormat};
  202. + xdg_apps << new QSettings{dir + "/software.org/KillerAPP.conf", QSettings::NativeFormat};
  203. + }
  204. + Q_ASSERT(config_dirs.size() == 3 && xdg_orgs.size() == 3 && xdg_apps.size() == 3);
  205. + for (int i = 0; i < 3; ++i) {
  206. + xdg_orgs[i]->setValue("all", QString{"all_org%1"}.arg(i));
  207. + xdg_apps[i]->setValue("all", QString{"all_app%1"}.arg(i));
  208. + xdg_orgs[i]->setValue("all_only_org", QString{"all_only_org%1"}.arg(i));
  209. + xdg_apps[i]->setValue("all_only_app", QString{"all_only_app%1"}.arg(i));
  210. +
  211. + if (i > 0) {
  212. + xdg_orgs[i]->setValue("from2nd", QString{"from2nd_org%1"}.arg(i));
  213. + xdg_apps[i]->setValue("from2nd", QString{"from2nd_app%1"}.arg(i));
  214. + xdg_orgs[i]->setValue("from2nd_only_org", QString{"from2nd_only_org%1"}.arg(i));
  215. + xdg_apps[i]->setValue("from2nd_only_app", QString{"from2nd_only_app%1"}.arg(i));
  216. + }
  217. +
  218. + if (i > 1) {
  219. + xdg_orgs[i]->setValue("from3rd", QString{"from3rd_org%1"}.arg(i));
  220. + xdg_apps[i]->setValue("from3rd", QString{"from3rd_app%1"}.arg(i));
  221. + xdg_orgs[i]->setValue("from3rd_only_org", QString{"from3rd_only_org%1"}.arg(i));
  222. + xdg_apps[i]->setValue("from3rd_only_app", QString{"from3rd_only_app%1"}.arg(i));
  223. + }
  224. + }
  225. + qDeleteAll(xdg_apps);
  226. + qDeleteAll(xdg_orgs);
  227. +
  228. + // Do the test.
  229. + QSettings app{QSettings::SystemScope, "software.org", "KillerAPP"}, org{QSettings::SystemScope, "software.org"};
  230. +
  231. + QVERIFY(app.value("all").toString() == "all_app0");
  232. + QVERIFY(org.value("all").toString() == "all_org0");
  233. + QVERIFY(app.value("all_only_org").toString() == "all_only_org0");
  234. + QVERIFY(org.value("all_only_org").toString() == "all_only_org0");
  235. + QVERIFY(app.value("all_only_app").toString() == "all_only_app0");
  236. + QVERIFY(org.value("all_only_app").toString() == QString{});
  237. +
  238. + QVERIFY(app.value("from2nd").toString() == "from2nd_app1");
  239. + QVERIFY(org.value("from2nd").toString() == "from2nd_org1");
  240. + QVERIFY(app.value("from2nd_only_org").toString() == "from2nd_only_org1");
  241. + QVERIFY(org.value("from2nd_only_org").toString() == "from2nd_only_org1");
  242. + QVERIFY(app.value("from2nd_only_app").toString() == "from2nd_only_app1");
  243. + QVERIFY(org.value("from2nd_only_app").toString() == QString{});
  244. +
  245. + QVERIFY(app.value("from3rd").toString() == "from3rd_app2");
  246. + QVERIFY(org.value("from3rd").toString() == "from3rd_org2");
  247. + QVERIFY(app.value("from3rd_only_org").toString() == "from3rd_only_org2");
  248. + QVERIFY(org.value("from3rd_only_org").toString() == "from3rd_only_org2");
  249. + QVERIFY(app.value("from3rd_only_app").toString() == "from3rd_only_app2");
  250. + QVERIFY(org.value("from3rd_only_app").toString() == QString{});
  251. +#else
  252. + QSKIP("This test is performed in QT_BUILD_INTERNAL on Q_XDG_PLATFORM with use of standard paths only.");
  253. +#endif
  254. +}
  255. +
  256. QTEST_MAIN(tst_QSettings)
  257. #include "tst_qsettings.moc"
  258. --
  259. cgit v1.0-4-g1e03