summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThiago Macieira <thiago.macieira@intel.com>2017-01-24 12:17:12 -0800
committerThiago Macieira <thiago.macieira@intel.com>2017-02-16 21:08:32 +0000
commitd9a2dd8d3b55d16d2e38d124abb0ade490963b37 (patch)
tree176b5146049076c892ab401b9bc61b330ced1933
parentde225ccdf95efb57866d62bc80872c1a2ab99703 (diff)
QDir::mkpath: don't try to mkdir in automount filesystems
Automount filesystems like /home on many operating systems (QNX and OpenIndiana, at least) don't like if you try to mkdir in them, even if the file path already exists. OpenIndiana even gives you an ENOSYS error. So instead, let's try to mkdir our target, if we fail because of ENOENT, we try to create the parent, then try again. Task-number: QTBUG-58390 Change-Id: Ibe5b1b60c6ea47e19612fffd149cce81589b0acd Reviewed-by: James McDonnell <jmcdonnell@blackberry.com> Reviewed-by: David Faure <david.faure@kdab.com>
-rw-r--r--src/corelib/io/qfilesystemengine_unix.cpp96
-rw-r--r--tests/auto/corelib/io/qdir/tst_qdir.cpp2
2 files changed, 64 insertions, 34 deletions
diff --git a/src/corelib/io/qfilesystemengine_unix.cpp b/src/corelib/io/qfilesystemengine_unix.cpp
index 1b908eac55..96829b3b03 100644
--- a/src/corelib/io/qfilesystemengine_unix.cpp
+++ b/src/corelib/io/qfilesystemengine_unix.cpp
@@ -556,45 +556,75 @@ bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemM
return data.hasFlags(what);
}
+// Note: if \a shouldMkdirFirst is false, we assume the caller did try to mkdir
+// before calling this function.
+static bool createDirectoryWithParents(const QByteArray &nativeName, bool shouldMkdirFirst = true)
+{
+ // helper function to check if a given path is a directory, since mkdir can
+ // fail if the dir already exists (it may have been created by another
+ // thread or another process)
+ const auto isDir = [](const QByteArray &nativeName) {
+ QT_STATBUF st;
+ return QT_STAT(nativeName.constData(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR;
+ };
+
+ if (shouldMkdirFirst && QT_MKDIR(nativeName, 0777) == 0)
+ return true;
+ if (errno == EEXIST)
+ return isDir(nativeName);
+ if (errno != ENOENT)
+ return false;
+
+ // mkdir failed because the parent dir doesn't exist, so try to create it
+ int slash = nativeName.lastIndexOf('/');
+ if (slash < 1)
+ return false;
+
+ QByteArray parentNativeName = nativeName.left(slash);
+ if (!createDirectoryWithParents(parentNativeName))
+ return false;
+
+ // try again
+ if (QT_MKDIR(nativeName, 0777) == 0)
+ return true;
+ return errno == EEXIST && isDir(nativeName);
+}
+
//static
bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents)
{
QString dirName = entry.filePath();
- if (createParents) {
- dirName = QDir::cleanPath(dirName);
- for (int oldslash = -1, slash=0; slash != -1; oldslash = slash) {
- slash = dirName.indexOf(QDir::separator(), oldslash+1);
- if (slash == -1) {
- if (oldslash == dirName.length())
- break;
- slash = dirName.length();
- }
- if (slash) {
- const QByteArray chunk = QFile::encodeName(dirName.left(slash));
- if (QT_MKDIR(chunk.constData(), 0777) != 0) {
- if (errno == EEXIST
-#if defined(Q_OS_QNX)
- // On QNX the QNet (VFS paths of other hosts mounted under a directory
- // such as /net) mountpoint returns ENOENT, despite existing. stat()
- // on the QNet mountpoint returns successfully and reports S_IFDIR.
- || errno == ENOENT
-#endif
- ) {
- QT_STATBUF st;
- if (QT_STAT(chunk.constData(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR)
- continue;
- }
- return false;
- }
- }
- }
+
+ // Darwin doesn't support trailing /'s, so remove for everyone
+ while (dirName.size() > 1 && dirName.endsWith(QLatin1Char('/')))
+ dirName.chop(1);
+
+ // try to mkdir this directory
+ QByteArray nativeName = QFile::encodeName(dirName);
+ if (QT_MKDIR(nativeName, 0777) == 0)
return true;
+ if (!createParents)
+ return false;
+
+ // we need the cleaned path in order to create the parents
+ // and we save errno just in case encodeName needs to load codecs
+ int savedErrno = errno;
+ bool pathChanged;
+ {
+ QString cleanName = QDir::cleanPath(dirName);
+
+ // Check if the cleaned name is the same or not. If we were given a
+ // path with resolvable "../" sections, cleanPath will remove them, but
+ // this may change the target dir if one of those segments was a
+ // symlink. This operation depends on cleanPath's optimization of
+ // returning the original string if it didn't modify anything.
+ pathChanged = !dirName.isSharedWith(cleanName);
+ if (pathChanged)
+ nativeName = QFile::encodeName(cleanName);
}
-#if defined(Q_OS_DARWIN) // Mac X doesn't support trailing /'s
- if (dirName.endsWith(QLatin1Char('/')))
- dirName.chop(1);
-#endif
- return (QT_MKDIR(QFile::encodeName(dirName).constData(), 0777) == 0);
+
+ errno = savedErrno;
+ return createDirectoryWithParents(nativeName, pathChanged);
}
//static
diff --git a/tests/auto/corelib/io/qdir/tst_qdir.cpp b/tests/auto/corelib/io/qdir/tst_qdir.cpp
index b86c6e4dfa..c3774997e9 100644
--- a/tests/auto/corelib/io/qdir/tst_qdir.cpp
+++ b/tests/auto/corelib/io/qdir/tst_qdir.cpp
@@ -350,7 +350,7 @@ void tst_QDir::mkdir_data()
<< QDir::currentPath() + "/testdir/two/three";
QTest::newRow("data0") << dirs.at(0) << true;
QTest::newRow("data1") << dirs.at(1) << false;
- QTest::newRow("data2") << dirs.at(2) << false;
+ QTest::newRow("data2") << dirs.at(2) << false; // note: requires data1 to have been run!
// Ensure that none of these directories already exist
QDir dir;