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-03-20 21:54:43 +0000
commit9b3530a3aeda8ea5462922f2b53c771ca722d4cd (patch)
tree6c1d349f0f9df473162fd89568a55034fc1007a7
parent1cf75af3f2f3430bab11f9ef1eea5deb4c59aa2e (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> (cherry picked from commit d9a2dd8d3b55d16d2e38d124abb0ade490963b37) Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
-rw-r--r--src/corelib/io/qfilesystemengine_unix.cpp98
-rw-r--r--tests/auto/corelib/io/qdir/tst_qdir.cpp2
2 files changed, 66 insertions, 34 deletions
diff --git a/src/corelib/io/qfilesystemengine_unix.cpp b/src/corelib/io/qfilesystemengine_unix.cpp
index a3b28a8d95..7a3eca572c 100644
--- a/src/corelib/io/qfilesystemengine_unix.cpp
+++ b/src/corelib/io/qfilesystemengine_unix.cpp
@@ -1,5 +1,6 @@
/****************************************************************************
**
+** Copyright (C) 2017 Intel Corporation.
** Copyright (C) 2015 The Qt Company Ltd.
** Copyright (C) 2013 Samuel Gaist <samuel.gaist@edeltech.ch>
** Contact: http://www.qt.io/licensing/
@@ -549,45 +550,76 @@ bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemM
return data.hasFlags(what);
}
+static bool pathIsDir(const QByteArray &nativeName)
+{
+ // 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)
+ QT_STATBUF st;
+ return QT_STAT(nativeName.constData(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR;
+};
+
+// 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)
+{
+ if (shouldMkdirFirst && QT_MKDIR(nativeName, 0777) == 0)
+ return true;
+ if (errno == EEXIST)
+ return pathIsDir(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 && pathIsDir(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 90c5a4d907..abef4c8b9e 100644
--- a/tests/auto/corelib/io/qdir/tst_qdir.cpp
+++ b/tests/auto/corelib/io/qdir/tst_qdir.cpp
@@ -355,7 +355,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;