fix QFileSystemEngine::createDirectory race condition

During a call to QDir::mkpath(), the same path could be created
by another process, in which case the OS mkdir will fail with EEXIST.
But the docs for mkpath() state that it's not an error if it
already exists, whereas for mkdir() it is an error.  So
QFileSystemEngine::createDirectory should accept the EEXIST error
silently if it occurs while creating the sequence of parent directories
and the final leaf directory, but should fail if EEXIST happens when
it was called from QDir::mkdir(), which is when the createParents
parameter is false.  We assume the operating system mkdir() and
CreateDirectory() are atomic, so there should be no race condition
in QDir::mkdir().  It's not necessary for mkpath() to call stat()
at each level, only to check whether an existing entry is a directory
or a file.  Also added to the autotest to verify that if the
path is an existing file, creating a dir with the same name will
fail in either mkdir or mkpath.

Task-number: QTBUG-30046
Change-Id: I926352f10654fdf3b322c8685bb85ad8b8844874
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@digia.com>
This commit is contained in:
Shawn Rutledge 2013-06-11 16:58:49 +02:00 committed by The Qt Project
parent 0bc96d1d6b
commit 3e2cd8ef6f
3 changed files with 29 additions and 13 deletions

View File

@ -510,11 +510,12 @@ bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool crea
}
if (slash) {
const QByteArray chunk = QFile::encodeName(dirName.left(slash));
QT_STATBUF st;
if (QT_STAT(chunk.constData(), &st) != -1) {
if ((st.st_mode & S_IFMT) != S_IFDIR)
return false;
} else if (QT_MKDIR(chunk.constData(), 0777) != 0) {
if (QT_MKDIR(chunk.constData(), 0777) != 0) {
if (errno == EEXIST) {
QT_STATBUF st;
if (QT_STAT(chunk.constData(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR)
continue;
}
return false;
}
}

View File

@ -1044,14 +1044,13 @@ bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool crea
}
if (slash) {
QString chunk = dirName.left(slash);
bool existed = false;
if (!isDirPath(chunk, &existed)) {
if (!existed) {
if (!mkDir(chunk))
return false;
} else {
return false;
if (!mkDir(chunk)) {
if (GetLastError() == ERROR_ALREADY_EXISTS) {
bool existed = false;
if (isDirPath(chunk, &existed) && existed)
continue;
}
return false;
}
}
}

View File

@ -311,12 +311,28 @@ void tst_QDir::mkdir()
void tst_QDir::makedirReturnCode()
{
QString dirName = QString::fromLatin1("makedirReturnCode");
QDir::current().rmdir(dirName); // cleanup a previous run.
QFile f(QDir::current().filePath(dirName));
// cleanup a previous run.
f.remove();
QDir::current().rmdir(dirName);
QDir dir(dirName);
QVERIFY(!dir.exists());
QVERIFY(QDir::current().mkdir(dirName));
QVERIFY(!QDir::current().mkdir(dirName)); // calling mkdir on an existing dir will fail.
QVERIFY(QDir::current().mkpath(dirName)); // calling mkpath on an existing dir will pass
// Remove the directory and create a file with the same path
QDir::current().rmdir(dirName);
QVERIFY(!f.exists());
f.open(QIODevice::WriteOnly);
f.write("test");
f.close();
QVERIFY(f.exists());
QVERIFY(!QDir::current().mkdir(dirName)); // calling mkdir on an existing file will fail.
QVERIFY(!QDir::current().mkpath(dirName)); // calling mkpath on an existing file will fail.
f.remove();
}
void tst_QDir::rmdir_data()