QFileSystemEngine: split dir creation into mkdir/mkpath

Split the logic of createDirectory() into mkdir and mkpath.
This matches QDir::mkdir()/mkpath().

"mkdir()" won't confuse the compiler about which method to call,
because libc's mkdir() is either used via QT_MKDIR which expands to
"::mkdir" or directly as ::mdkir(), whereas the static
QFileSystemEngine::mkdir() is always called with full scope.

Change-Id: I31b67727cce23f1bc560432d40231a24dc560d5b
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Ahmad Samir 2024-09-25 21:07:46 +03:00
parent 09f0edb781
commit e275db9d88
5 changed files with 100 additions and 72 deletions

View File

@ -1506,7 +1506,7 @@ bool QDir::mkdir(const QString &dirName, QFile::Permissions permissions) const
QString fn = filePath(dirName);
if (!d->fileEngine)
return QFileSystemEngine::createDirectory(QFileSystemEntry(fn), false, permissions);
return QFileSystemEngine::mkdir(QFileSystemEntry(fn), permissions);
return d->fileEngine->mkdir(fn, false, permissions);
}
@ -1528,7 +1528,7 @@ bool QDir::mkdir(const QString &dirName) const
QString fn = filePath(dirName);
if (!d->fileEngine)
return QFileSystemEngine::createDirectory(QFileSystemEntry(fn), false);
return QFileSystemEngine::mkdir(QFileSystemEntry(fn));
return d->fileEngine->mkdir(fn, false);
}
@ -1580,7 +1580,7 @@ bool QDir::mkpath(const QString &dirPath) const
QString fn = filePath(dirPath);
if (!d->fileEngine)
return QFileSystemEngine::createDirectory(QFileSystemEntry(fn), true);
return QFileSystemEngine::mkpath(QFileSystemEntry(fn));
return d->fileEngine->mkdir(fn, true);
}

View File

@ -116,7 +116,18 @@ public:
static QString tempPath();
static bool createDirectory(const QFileSystemEntry &entry, bool createParents,
std::optional<QFile::Permissions> permissions = std::nullopt);
std::optional<QFile::Permissions> permissions = std::nullopt)
{
if (createParents)
return mkpath(entry, permissions);
return mkdir(entry, permissions);
}
static bool mkdir(const QFileSystemEntry &entry,
std::optional<QFile::Permissions> permissions = std::nullopt);
static bool mkpath(const QFileSystemEntry &entry,
std::optional<QFile::Permissions> permissions = std::nullopt);
static bool removeDirectory(const QFileSystemEntry &entry, bool removeEmptyParents)
{
if (removeEmptyParents)

View File

@ -85,6 +85,15 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
static QByteArray &removeTrailingSlashes(QByteArray &path)
{
// Darwin doesn't support trailing /'s, so remove for everyone
while (path.size() > 1 && path.endsWith('/'))
path.chop(1);
return path;
}
enum {
#ifdef Q_OS_ANDROID
// On Android, the link(2) system call has been observed to always fail
@ -1114,79 +1123,77 @@ bool QFileSystemEngine::cloneFile(int srcfd, int dstfd, const QFileSystemMetaDat
#endif
}
// Note: if \a shouldMkdirFirst is false, we assume the caller did try to mkdir
// before calling this function.
static bool createDirectoryWithParents(const QByteArray &nativeName, mode_t mode,
bool shouldMkdirFirst = true)
static QSystemError createDirectoryWithParents(const QByteArray &path, mode_t mode)
{
#ifdef Q_OS_WASM
if (nativeName.length() == 1 && nativeName[0] == '/')
return true;
if (path == '/')
return {};
#endif
// 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;
auto tryMkDir = [&path, mode]() -> QSystemError {
if (QT_MKDIR(path, mode) == 0) {
#ifdef Q_OS_VXWORKS
forceRequestedPermissionsOnVxWorks(path, mode);
#endif
return {};
}
// On macOS with APFS mkdir sets errno to EISDIR, QTBUG-97110
if (errno == EISDIR)
return {};
if (errno == EEXIST || errno == EROFS) {
// ::mkdir() can fail if the dir already exists (it may have been
// created by another thread or another process)
QT_STATBUF st;
if (QT_STAT(path.constData(), &st) != 0)
return QSystemError::stdError(errno);
const bool isDir = (st.st_mode & S_IFMT) == S_IFDIR;
return isDir ? QSystemError{} : QSystemError::stdError(EEXIST);
}
return QSystemError::stdError(errno);
};
if (shouldMkdirFirst && QT_MKDIR(nativeName, mode) == 0) {
#ifdef Q_OS_VXWORKS
forceRequestedPermissionsOnVxWorks(nativeName, mode);
#endif
return true;
}
if (errno == EISDIR)
return true;
if (errno == EEXIST || errno == EROFS)
return isDir(nativeName);
if (errno != ENOENT)
return false;
QSystemError result = tryMkDir();
if (result.ok())
return result;
// Only handle non-existing dir components in the path
if (result.errorCode != ENOENT)
return result;
qsizetype slash = path.lastIndexOf('/');
while (slash > 0 && path[slash - 1] == '/')
--slash;
if (slash < 1)
return result;
// mkdir failed because the parent dir doesn't exist, so try to create it
qsizetype slash = nativeName.lastIndexOf('/');
if (slash < 1)
return false;
QByteArray parentNativeName = nativeName.left(slash);
if (!createDirectoryWithParents(parentNativeName, mode))
return false;
QByteArray parentPath = path.first(slash);
if (result = createDirectoryWithParents(parentPath, mode); !result.ok())
return result;
// try again
if (QT_MKDIR(nativeName, mode) == 0) {
#ifdef Q_OS_VXWORKS
forceRequestedPermissionsOnVxWorks(nativeName, mode);
#endif
return true;
}
return errno == EEXIST && isDir(nativeName);
return tryMkDir();
}
//static
bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents,
std::optional<QFile::Permissions> permissions)
bool QFileSystemEngine::mkpath(const QFileSystemEntry &entry,
std::optional<QFile::Permissions> permissions)
{
QByteArray dirName = entry.nativeFilePath();
Q_CHECK_FILE_NAME(dirName, false);
QByteArray path = entry.nativeFilePath();
Q_CHECK_FILE_NAME(path, false);
// Darwin doesn't support trailing /'s, so remove for everyone
while (dirName.size() > 1 && dirName.endsWith(u'/'))
dirName.chop(1);
// try to mkdir this directory
mode_t mode = permissions ? QtPrivate::toMode_t(*permissions) : 0777;
if (QT_MKDIR(dirName, mode) == 0) {
#ifdef Q_OS_VXWORKS
forceRequestedPermissionsOnVxWorks(dirName, mode);
#endif
return true;
}
if (!createParents)
return false;
return createDirectoryWithParents(removeTrailingSlashes(path), mode).ok();
}
return createDirectoryWithParents(dirName, mode, false);
bool QFileSystemEngine::mkdir(const QFileSystemEntry &entry,
std::optional<QFile::Permissions> permissions)
{
QByteArray path = entry.nativeFilePath();
Q_CHECK_FILE_NAME(path, false);
mode_t mode = permissions ? QtPrivate::toMode_t(*permissions) : 0777;
return QT_MKDIR(removeTrailingSlashes(path), mode) == 0;
}
bool QFileSystemEngine::rmdir(const QFileSystemEntry &entry)

View File

@ -1526,34 +1526,45 @@ static bool createDirectoryWithParents(const QString &nativeName,
return isDir(nativeName);
}
//static
bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents,
std::optional<QFile::Permissions> permissions)
bool QFileSystemEngine::mkpath(const QFileSystemEntry &entry,
std::optional<QFile::Permissions> permissions)
{
QString dirName = entry.filePath();
Q_CHECK_FILE_NAME(dirName, false);
dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName));
QNativeFilePermissions nativePermissions(permissions, true);
if (!nativePermissions.isOk())
return false;
auto securityAttributes = nativePermissions.securityAttributes();
dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName));
// try to mkdir this directory
DWORD lastError;
if (mkDir(dirName, securityAttributes, &lastError))
return true;
// mkpath should return true, if the directory already exists, mkdir false.
if (!createParents)
return false;
// mkpath should return true, if the directory already exists
if (lastError == ERROR_ALREADY_EXISTS || lastError == ERROR_ACCESS_DENIED)
return isDirPath(dirName, nullptr);
return createDirectoryWithParents(dirName, securityAttributes, false);
}
bool QFileSystemEngine::mkdir(const QFileSystemEntry &entry,
std::optional<QFile::Permissions> permissions)
{
QString dirName = entry.filePath();
Q_CHECK_FILE_NAME(dirName, false);
QNativeFilePermissions nativePermissions(permissions, true);
if (!nativePermissions.isOk())
return false;
dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName));
return mkDir(dirName, nativePermissions.securityAttributes());
}
bool QFileSystemEngine::rmdir(const QFileSystemEntry &entry)
{
QString dirName = entry.filePath();

View File

@ -67,12 +67,11 @@ static QString defaultTemplateName()
void QTemporaryDirPrivate::create(const QString &templateName)
{
QTemporaryFileName tfn(templateName);
constexpr auto perms = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner;
for (int i = 0; i < 256; ++i) {
tfn.generateNext();
QFileSystemEntry fileSystemEntry(tfn.path, QFileSystemEntry::FromNativePath());
if (QFileSystemEngine::createDirectory(fileSystemEntry, false,
QFile::ReadOwner | QFile::WriteOwner
| QFile::ExeOwner)) {
if (QFileSystemEngine::mkdir(fileSystemEntry, perms)) {
success = true;
pathOrError = fileSystemEntry.filePath();
return;