diff options
-rw-r--r-- | src/corelib/io/qfilesystemengine_unix.cpp | 96 | ||||
-rw-r--r-- | tests/auto/corelib/io/qdir/tst_qdir.cpp | 2 |
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; |