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:
parent
8c9875893b
commit
174af05400
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
|
Loading…
x
Reference in New Issue
Block a user