QDir: Add support for setting directory permissions to mkdir()

This patch adds an overload of the QDir::mkdir() method that
accepts permissions. This allows setting of the directory
permissions at the time of its creation.

[ChangeLog][QtCore][QDir] Added QDir::mdkir() overload that
accepts permissions argument.

Task-number: QTBUG-79750
Change-Id: Ic9db723b94ff0d2da6e0b819ac2e5d1f9a4e2049
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Ievgenii Meshcheriakov 2021-11-10 16:52:07 +01:00
parent 8c9875893b
commit 174af05400
11 changed files with 427 additions and 45 deletions

View File

@ -534,19 +534,24 @@ bool QAbstractFileEngine::link(const QString &newName)
}
/*!
Requests that the directory \a dirName be created. If
\a createParentDirectories is true, then any sub-directories in \a dirName
Requests that the directory \a dirName be created with the specified \a permissions.
If \a createParentDirectories is true, then any sub-directories in \a dirName
that don't exist must be created. If \a createParentDirectories is false then
any sub-directories in \a dirName must already exist for the function to
succeed. If the operation succeeds return true; otherwise return
false.
If \a permissions is null then implementation-specific default permissions are
used.
\sa setFileName(), rmdir(), isRelativePath()
*/
bool QAbstractFileEngine::mkdir(const QString &dirName, bool createParentDirectories) const
bool QAbstractFileEngine::mkdir(const QString &dirName, bool createParentDirectories,
std::optional<QFile::Permissions> permissions) const
{
Q_UNUSED(dirName);
Q_UNUSED(createParentDirectories);
Q_UNUSED(permissions);
return false;
}

View File

@ -55,6 +55,8 @@
#include "QtCore/qfile.h"
#include "QtCore/qdir.h"
#include <optional>
#ifdef open
#error qabstractfileengine_p.h must be included before any header file that defines open
#endif
@ -135,7 +137,8 @@ public:
virtual bool rename(const QString &newName);
virtual bool renameOverwrite(const QString &newName);
virtual bool link(const QString &newName);
virtual bool mkdir(const QString &dirName, bool createParentDirectories) const;
virtual bool mkdir(const QString &dirName, bool createParentDirectories,
std::optional<QFile::Permissions> permissions = std::nullopt) const;
virtual bool rmdir(const QString &dirName, bool recurseParentDirectories) const;
virtual bool setSize(qint64 size);
virtual bool caseSensitive() const;

View File

@ -1445,9 +1445,42 @@ QFileInfoList QDir::entryInfoList(const QStringList &nameFilters, Filters filter
Returns \c true on success; otherwise returns \c false.
If the directory already exists when this function is called, it will return false.
If the directory already exists when this function is called, it will return \c false.
The permissions of the created directory are set to \a{permissions}.
On POSIX systems the permissions are influenced by the value of \c umask.
On Windows the permissions are emulated using ACLs. These ACLs may be in non-canonical
order when the group is granted less permissions than others. Files and directories with
such permissions will generate warnings when the Security tab of the Properties dialog
is opened. Granting the group all permissions granted to others avoids such warnings.
\sa rmdir()
\since 6.3
*/
bool QDir::mkdir(const QString &dirName, QFile::Permissions permissions) const
{
const QDirPrivate *d = d_ptr.constData();
if (dirName.isEmpty()) {
qWarning("QDir::mkdir: Empty or null file name");
return false;
}
QString fn = filePath(dirName);
if (!d->fileEngine)
return QFileSystemEngine::createDirectory(QFileSystemEntry(fn), false, permissions);
return d->fileEngine->mkdir(fn, false, permissions);
}
/*!
\overload
Creates a sub-directory called \a dirName with default permissions.
On POSIX systems the default is to grant all permissions allowed by \c umask.
On Windows, the new directory inherits its permissions from its parent directory.
*/
bool QDir::mkdir(const QString &dirName) const
{

View File

@ -196,6 +196,7 @@ public:
SortFlags sort = NoSort) const;
bool mkdir(const QString &dirName) const;
bool mkdir(const QString &dirName, QFile::Permissions permissions) const;
bool rmdir(const QString &dirName) const;
bool mkpath(const QString &dirPath) const;
bool rmpath(const QString &dirPath) const;

View File

@ -52,11 +52,16 @@
//
#include "private/qiodevice_p.h"
#include "qfiledevice.h"
#include <memory>
#ifdef Q_OS_UNIX
#if defined(Q_OS_UNIX)
# include <sys/types.h> // for mode_t
# include <sys/stat.h> // for mode_t constants
# include <sys/stat.h> // for mode_t constants
#elif defined(Q_OS_WINDOWS)
# include <qt_windows.h>
# include <winnt.h> // for SECURITY_DESCRIPTOR
# include <optional>
#endif
QT_BEGIN_NAMESPACE
@ -130,6 +135,31 @@ constexpr mode_t toMode_t(QFileDevice::Permissions permissions)
}
} // namespace QtPrivate
#elif defined(Q_OS_WINDOWS)
class QNativeFilePermissions
{
public:
QNativeFilePermissions(std::optional<QFileDevice::Permissions> perms, bool isDir);
SECURITY_ATTRIBUTES *securityAttributes();
bool isOk() const { return ok; }
private:
bool ok = false;
bool isNull = true;
// At most 1 allow + 1 deny ACEs for user and group, 1 allow ACE for others
static constexpr auto MaxNumACEs = 5;
static constexpr auto MaxACLSize =
sizeof(ACL) + (sizeof(ACCESS_ALLOWED_ACE) + SECURITY_MAX_SID_SIZE) * MaxNumACEs;
SECURITY_DESCRIPTOR sd;
SECURITY_ATTRIBUTES sa;
alignas(DWORD) char aclStorage[MaxACLSize];
};
#endif // Q_OS_UNIX
QT_END_NAMESPACE

View File

@ -56,6 +56,8 @@
#include "qfilesystemmetadata_p.h"
#include <QtCore/private/qsystemerror_p.h>
#include <optional>
QT_BEGIN_NAMESPACE
#define Q_RETURN_ON_INVALID_FILENAME(message, result) \
@ -151,7 +153,8 @@ public:
static QString rootPath();
static QString tempPath();
static bool createDirectory(const QFileSystemEntry &entry, bool createParents);
static bool createDirectory(const QFileSystemEntry &entry, bool createParents,
std::optional<QFile::Permissions> permissions = std::nullopt);
static bool removeDirectory(const QFileSystemEntry &entry, bool removeEmptyParents);
static bool createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error);

View File

@ -1110,7 +1110,8 @@ bool QFileSystemEngine::cloneFile(int srcfd, int dstfd, const QFileSystemMetaDat
// 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)
static bool createDirectoryWithParents(const QByteArray &nativeName, mode_t mode,
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
@ -1120,7 +1121,7 @@ static bool createDirectoryWithParents(const QByteArray &nativeName, bool should
return QT_STAT(nativeName.constData(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR;
};
if (shouldMkdirFirst && QT_MKDIR(nativeName, 0777) == 0)
if (shouldMkdirFirst && QT_MKDIR(nativeName, mode) == 0)
return true;
if (errno == EISDIR)
return true;
@ -1135,17 +1136,18 @@ static bool createDirectoryWithParents(const QByteArray &nativeName, bool should
return false;
QByteArray parentNativeName = nativeName.left(slash);
if (!createDirectoryWithParents(parentNativeName))
if (!createDirectoryWithParents(parentNativeName, mode))
return false;
// try again
if (QT_MKDIR(nativeName, 0777) == 0)
if (QT_MKDIR(nativeName, mode) == 0)
return true;
return errno == EEXIST && isDir(nativeName);
}
//static
bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents)
bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents,
std::optional<QFile::Permissions> permissions)
{
QString dirName = entry.filePath();
Q_CHECK_FILE_NAME(dirName, false);
@ -1156,12 +1158,13 @@ bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool crea
// try to mkdir this directory
QByteArray nativeName = QFile::encodeName(dirName);
if (QT_MKDIR(nativeName, 0777) == 0)
mode_t mode = permissions ? QtPrivate::toMode_t(*permissions) : 0777;
if (QT_MKDIR(nativeName, mode) == 0)
return true;
if (!createParents)
return false;
return createDirectoryWithParents(nativeName, false);
return createDirectoryWithParents(nativeName, mode, false);
}
//static

View File

@ -43,6 +43,7 @@
#include "qsysinfo.h"
#include "qscopeguard.h"
#include "private/qabstractfileengine_p.h"
#include "private/qfiledevice_p.h"
#include "private/qfsfileengine_p.h"
#include <private/qsystemlibrary_p.h>
#include <qdebug.h>
@ -147,6 +148,7 @@ typedef struct _REPARSE_DATA_BUFFER {
#include <authz.h>
#include <userenv.h>
static PSID currentUserSID = nullptr;
static PSID currentGroupSID = nullptr;
static PSID worldSID = nullptr;
static HANDLE currentUserImpersonatedToken = nullptr;
@ -164,6 +166,9 @@ GlobalSid::~GlobalSid()
free(currentUserSID);
currentUserSID = nullptr;
free(currentGroupSID);
currentGroupSID = nullptr;
// worldSID was allocated with AllocateAndInitializeSid so it needs to be freed with FreeSid
if (worldSID) {
::FreeSid(worldSID);
@ -176,29 +181,54 @@ GlobalSid::~GlobalSid()
}
}
/*
Helper for GetTokenInformation that allocates chunk of memory to hold the requested information.
The memory size is determined by doing a dummy call first. The returned memory should be
freed by calling free().
*/
template<typename T>
static T *getTokenInfo(HANDLE token, TOKEN_INFORMATION_CLASS infoClass)
{
DWORD retsize = 0;
GetTokenInformation(token, infoClass, nullptr, 0, &retsize);
if (retsize) {
void *tokenBuffer = malloc(retsize);
if (::GetTokenInformation(token, infoClass, tokenBuffer, retsize, &retsize))
return reinterpret_cast<T *>(tokenBuffer);
else
free(tokenBuffer);
}
return nullptr;
}
/*
Takes a copy of the original SID and stores it into dstSid.
The copy can be destroyed using free().
*/
static void copySID(PSID &dstSid, PSID srcSid)
{
DWORD sidLen = GetLengthSid(srcSid);
dstSid = reinterpret_cast<PSID>(malloc(sidLen));
Q_CHECK_PTR(dstSid);
CopySid(sidLen, dstSid, srcSid);
}
GlobalSid::GlobalSid()
{
// Create TRUSTEE for current user
HANDLE hnd = ::GetCurrentProcess();
HANDLE token = nullptr;
if (::OpenProcessToken(hnd, TOKEN_QUERY, &token)) {
DWORD retsize = 0;
// GetTokenInformation requires a buffer big enough for the TOKEN_USER struct and
// the SID struct. Since the SID struct can have variable number of subauthorities
// tacked at the end, its size is variable. Obtain the required size by first
// doing a dummy GetTokenInformation call.
::GetTokenInformation(token, TokenUser, nullptr, 0, &retsize);
if (retsize) {
void *tokenBuffer = malloc(retsize);
Q_CHECK_PTR(tokenBuffer);
if (::GetTokenInformation(token, TokenUser, tokenBuffer, retsize, &retsize)) {
PSID tokenSid = reinterpret_cast<PTOKEN_USER>(tokenBuffer)->User.Sid;
DWORD sidLen = ::GetLengthSid(tokenSid);
currentUserSID = reinterpret_cast<PSID>(malloc(sidLen));
Q_CHECK_PTR(currentUserSID);
::CopySid(sidLen, currentUserSID, tokenSid);
}
free(tokenBuffer);
// Create SID for current user
if (auto info = getTokenInfo<TOKEN_USER>(token, TokenUser)) {
copySID(currentUserSID, info->User.Sid);
free(info);
}
// Create SID for the current user's primary group.
if (auto info = getTokenInfo<TOKEN_GROUPS>(token, TokenGroups)) {
copySID(currentGroupSID, info->Groups[0].Sid);
free(info);
}
::CloseHandle(token);
}
@ -360,6 +390,29 @@ ACCESS_MASK QAuthzClientContext::accessMask(PSECURITY_DESCRIPTOR pSD) const
return accessMask;
}
enum NonSpecificPermission {
ReadPermission = 0x4,
WritePermission = 0x2,
ExePermission = 0x1,
AllPermissions = ReadPermission | WritePermission | ExePermission
};
Q_DECLARE_FLAGS(NonSpecificPermissions, NonSpecificPermission)
Q_DECLARE_OPERATORS_FOR_FLAGS(NonSpecificPermissions)
enum PermissionTag { OtherTag = 0, GroupTag = 4, UserTag = 8, OwnerTag = 12 };
constexpr NonSpecificPermissions toNonSpecificPermissions(PermissionTag tag,
QFileDevice::Permissions permissions)
{
return NonSpecificPermissions::fromInt((permissions.toInt() >> int(tag)) & 0x7);
}
constexpr QFileDevice::Permissions toSpecificPermissions(PermissionTag tag,
NonSpecificPermissions permissions)
{
return QFileDevice::Permissions::fromInt(permissions.toInt() << int(tag));
}
QT_END_NAMESPACE
} // anonymous namespace
@ -369,6 +422,195 @@ QT_BEGIN_NAMESPACE
Q_CORE_EXPORT int qt_ntfs_permission_lookup = 0;
/*!
\class QNativeFilePermissions
\internal
This class can be used to produce a security descriptor that contains ACL that produces
result similar to what is expected for POSIX permission corresponding to the supplied
\c QFileDevice::Permissions value. When supplied optional value is empty, a null
security descriptor is produced. Files or directories with such null security descriptor
will inherit ACLs from parent directories. Otherwise an ACL is generated and applied to
the security descriptor. The created ACL has permission bits set similar to what Cygwin
does. Unlike Cygwin, this code tries to reorder the access control entries (ACE) inside
the ACL to match the canonical ordering (deny ACEs followed by allow ACEs) if possible.
The default ordering of ACEs is as follows:
* User deny ACE, only lists permission that may be granted by the subsequent Group and
Other allow ACEs.
* User allow ACE.
* Group deny ACE, only lists permissions that may be granted by the subsequent Other
allow ACE.
* Group allow ACE.
* Other allow ACE.
Any ACEs that would have zero mask are skipped. Group deny ACE may be moved to before
User allow ACE if these 2 ACEs don't have any common mask bits set. This allows use of
canonical ordering in more cases. ACLs for permissions with group having less permissions
than both user and others (ex.: 0757) are still in noncanonical order. Files with
noncanonical ACLs generate warnings when one tries to edit permissions with Windows GUI,
and don't work correctly with API like GetEffectiveRightsFromAcl(), but otherwise access
checks work fine and such ACLs can still be edited with the "Advanced" GUI.
*/
QNativeFilePermissions::QNativeFilePermissions(std::optional<QFileDevice::Permissions> perms,
bool isDir)
{
#if QT_CONFIG(fslibs)
if (!perms) {
ok = true;
return;
}
initGlobalSid();
const auto permissions = *perms;
PACL acl = reinterpret_cast<PACL>(aclStorage);
if (!InitializeAcl(acl, sizeof(aclStorage), ACL_REVISION))
return;
struct Masks
{
ACCESS_MASK denyMask, allowMask;
};
auto makeMasks = [this, isDir](NonSpecificPermissions allowPermissions,
NonSpecificPermissions denyPermissions, bool owner) {
constexpr ACCESS_MASK AllowRead = FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA;
constexpr ACCESS_MASK DenyRead = FILE_READ_DATA | FILE_READ_EA;
constexpr ACCESS_MASK AllowWrite =
FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA;
constexpr ACCESS_MASK DenyWrite = AllowWrite | FILE_DELETE_CHILD;
constexpr ACCESS_MASK DenyWriteOwner =
FILE_WRITE_DATA | FILE_WRITE_EA | FILE_APPEND_DATA | FILE_DELETE_CHILD;
constexpr ACCESS_MASK AllowExe = FILE_EXECUTE;
constexpr ACCESS_MASK DenyExe = AllowExe;
constexpr ACCESS_MASK StdRightsOther =
STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES | SYNCHRONIZE;
constexpr ACCESS_MASK StdRightsOwner =
STANDARD_RIGHTS_ALL | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE;
ACCESS_MASK allow = owner ? StdRightsOwner : StdRightsOther;
ACCESS_MASK deny = 0;
if (denyPermissions & ReadPermission)
deny |= DenyRead;
if (denyPermissions & WritePermission)
deny |= owner ? DenyWriteOwner : DenyWrite;
if (denyPermissions & ExePermission)
deny |= DenyExe;
if (allowPermissions & ReadPermission)
allow |= AllowRead;
if (allowPermissions & WritePermission)
allow |= AllowWrite;
if (allowPermissions & ExePermission)
allow |= AllowExe;
// Give the owner "full access" if all the permissions are allowed
if (owner && allowPermissions == AllPermissions)
allow |= FILE_DELETE_CHILD;
if (isDir
&& (allowPermissions & (WritePermission | ExePermission))
== (WritePermission | ExePermission)) {
allow |= FILE_DELETE_CHILD;
}
return Masks { deny, allow };
};
auto userPermissions = toNonSpecificPermissions(OwnerTag, permissions)
| toNonSpecificPermissions(UserTag, permissions);
auto groupPermissions = toNonSpecificPermissions(GroupTag, permissions);
auto otherPermissions = toNonSpecificPermissions(OtherTag, permissions);
auto userMasks = makeMasks(userPermissions,
~userPermissions & (groupPermissions | otherPermissions), true);
auto groupMasks = makeMasks(groupPermissions, ~groupPermissions & otherPermissions, false);
auto otherMasks = makeMasks(otherPermissions, {}, false);
const DWORD aceFlags = isDir ? OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE : 0;
const bool reorderGroupDeny = (groupMasks.denyMask & userMasks.allowMask) == 0;
const auto addDenyAce = [acl, aceFlags](const Masks &masks, PSID pSID) {
if (masks.denyMask)
return AddAccessDeniedAceEx(acl, ACL_REVISION, aceFlags, masks.denyMask, pSID);
return TRUE;
};
const auto addAllowAce = [acl, aceFlags](const Masks &masks, PSID pSID) {
if (masks.allowMask)
return AddAccessAllowedAceEx(acl, ACL_REVISION, aceFlags, masks.allowMask, pSID);
return TRUE;
};
if (!addDenyAce(userMasks, currentUserSID))
return;
if (reorderGroupDeny) {
if (!addDenyAce(groupMasks, currentGroupSID))
return;
}
if (!addAllowAce(userMasks, currentUserSID))
return;
if (!reorderGroupDeny) {
if (!addDenyAce(groupMasks, currentGroupSID))
return;
}
if (!addAllowAce(groupMasks, currentGroupSID))
return;
if (!addAllowAce(otherMasks, worldSID))
return;
if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))
return;
if (!SetSecurityDescriptorOwner(&sd, currentUserSID, FALSE))
return;
if (!SetSecurityDescriptorGroup(&sd, currentGroupSID, FALSE))
return;
if (!SetSecurityDescriptorDacl(&sd, TRUE, acl, FALSE))
return;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = &sd;
sa.bInheritHandle = FALSE;
isNull = false;
#endif // QT_CONFIG(fslibs)
ok = true;
}
/*!
\internal
Return pointer to a \c SECURITY_ATTRIBUTES object describing the permissions.
The returned pointer many be null if default permissions were requested or
during bootstrap. The calles must call \c isOk() to check if the object
was successfully constructed before using this method.
*/
SECURITY_ATTRIBUTES *QNativeFilePermissions::securityAttributes()
{
Q_ASSERT(ok);
return isNull ? nullptr : &sa;
}
static inline bool toFileTime(const QDateTime &date, FILETIME *fileTime)
{
SYSTEMTIME sTime;
@ -1206,12 +1448,13 @@ bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemM
return data.hasFlags(what);
}
static inline bool mkDir(const QString &path, DWORD *lastError = nullptr)
static inline bool mkDir(const QString &path, SECURITY_ATTRIBUTES *securityAttributes,
DWORD *lastError = nullptr)
{
if (lastError)
*lastError = 0;
const QString longPath = QFSFileEnginePrivate::longFileName(path);
const bool result = ::CreateDirectory((wchar_t *)longPath.utf16(), nullptr);
const bool result = ::CreateDirectory((wchar_t *)longPath.utf16(), securityAttributes);
// Capture lastError before any QString is freed since custom allocators might change it.
if (lastError)
*lastError = GetLastError();
@ -1252,7 +1495,9 @@ bool QFileSystemEngine::isDirPath(const QString &dirPath, bool *existed)
// NOTE: if \a shouldMkdirFirst is false, we assume the caller did try to mkdir
// before calling this function.
static bool createDirectoryWithParents(const QString &nativeName, bool shouldMkdirFirst = true)
static bool createDirectoryWithParents(const QString &nativeName,
SECURITY_ATTRIBUTES *securityAttributes,
bool shouldMkdirFirst = true)
{
const auto isUNCRoot = [](const QString &nativeName) {
return nativeName.startsWith(QLatin1String("\\\\"))
@ -1270,7 +1515,7 @@ static bool createDirectoryWithParents(const QString &nativeName, bool shouldMkd
return false;
if (shouldMkdirFirst) {
if (mkDir(nativeName))
if (mkDir(nativeName, securityAttributes))
return true;
}
@ -1279,26 +1524,33 @@ static bool createDirectoryWithParents(const QString &nativeName, bool shouldMkd
return false;
const QString parentNativeName = nativeName.left(backSlash);
if (!createDirectoryWithParents(parentNativeName))
if (!createDirectoryWithParents(parentNativeName, securityAttributes))
return false;
// try again
if (mkDir(nativeName))
if (mkDir(nativeName, securityAttributes))
return true;
return isDir(nativeName);
}
//static
bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents)
bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents,
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();
// try to mkdir this directory
DWORD lastError;
if (mkDir(dirName, &lastError))
if (mkDir(dirName, securityAttributes, &lastError))
return true;
// mkpath should return true, if the directory already exists, mkdir false.
if (!createParents)
@ -1306,7 +1558,7 @@ bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool crea
if (lastError == ERROR_ALREADY_EXISTS || lastError == ERROR_ACCESS_DENIED)
return isDirPath(dirName, nullptr);
return createDirectoryWithParents(dirName, false);
return createDirectoryWithParents(dirName, securityAttributes, false);
}
//static

View File

@ -1057,9 +1057,11 @@ bool QFSFileEngine::renameOverwrite(const QString &newName)
/*!
\reimp
*/
bool QFSFileEngine::mkdir(const QString &name, bool createParentDirectories) const
bool QFSFileEngine::mkdir(const QString &name, bool createParentDirectories,
std::optional<QFile::Permissions> permissions) const
{
return QFileSystemEngine::createDirectory(QFileSystemEntry(name), createParentDirectories);
return QFileSystemEngine::createDirectory(QFileSystemEntry(name), createParentDirectories,
permissions);
}
/*!

View File

@ -57,6 +57,8 @@
#include <QtCore/private/qfilesystemmetadata_p.h>
#include <qhash.h>
#include <optional>
#ifndef QT_NO_FSFILEENGINE
QT_BEGIN_NAMESPACE
@ -93,7 +95,8 @@ public:
bool rename(const QString &newName) override;
bool renameOverwrite(const QString &newName) override;
bool link(const QString &newName) override;
bool mkdir(const QString &dirName, bool createParentDirectories) const override;
bool mkdir(const QString &dirName, bool createParentDirectories,
std::optional<QFile::Permissions> permissions) const override;
bool rmdir(const QString &dirName, bool recurseParentDirectories) const override;
bool setSize(qint64 size) override;
bool caseSensitive() const override;

View File

@ -37,6 +37,7 @@
#include <qdebug.h>
#include <qdir.h>
#include <qfileinfo.h>
#include <qscopedvaluerollback.h>
#include <qstringlist.h>
#if defined(Q_OS_WIN)
@ -65,6 +66,7 @@
#ifdef Q_OS_WIN
#define DRIVE "Q:"
extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;
#else
#define DRIVE
#endif
@ -114,6 +116,8 @@ private slots:
void mkdirRmdir_data();
void mkdirRmdir();
void mkdirOnSymlink();
void mkdirWithPermissions_data();
void mkdirWithPermissions();
void makedirReturnCode();
@ -451,6 +455,49 @@ void tst_QDir::mkdirOnSymlink()
#endif
}
void tst_QDir::mkdirWithPermissions_data()
{
QTest::addColumn<QFile::Permissions>("permissions");
for (int u = 0; u < 8; ++u) {
for (int g = 0; g < 8; ++g) {
for (int o = 0; o < 8; ++o) {
auto permissions = QFileDevice::Permissions::fromInt((u << 12) | (g << 4) | o);
QTest::addRow("%04x", permissions.toInt()) << permissions;
}
}
}
}
void tst_QDir::mkdirWithPermissions()
{
QFETCH(QFile::Permissions, permissions);
#ifdef Q_OS_WIN
QScopedValueRollback<int> ntfsMode(qt_ntfs_permission_lookup);
++qt_ntfs_permission_lookup;
#endif
#ifdef Q_OS_UNIX
auto restoreMask = qScopeGuard([oldMask = umask(0)] { umask(oldMask); });
#endif
const QFile::Permissions setPermissions = {
QFile::ReadOther, QFile::WriteOther, QFile::ExeOther,
QFile::ReadGroup, QFile::WriteGroup, QFile::ExeGroup,
QFile::ReadOwner, QFile::WriteOwner, QFile::ExeOwner
};
const QString path = u"tmpdir"_qs;
QDir dir;
auto deleteDirectory = qScopeGuard([&dir, &path] { dir.rmdir(path); });
QVERIFY(dir.mkdir(path, permissions));
auto actualPermissions = QFileInfo(dir.filePath(path)).permissions();
QCOMPARE(actualPermissions & setPermissions, permissions);
QVERIFY(dir.rmdir(path));
deleteDirectory.dismiss();
}
void tst_QDir::makedirReturnCode()
{
QString dirName = QString::fromLatin1("makedirReturnCode");