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
|
Requests that the directory \a dirName be created with the specified \a permissions.
|
||||||
\a createParentDirectories is true, then any sub-directories in \a dirName
|
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
|
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
|
any sub-directories in \a dirName must already exist for the function to
|
||||||
succeed. If the operation succeeds return true; otherwise return
|
succeed. If the operation succeeds return true; otherwise return
|
||||||
false.
|
false.
|
||||||
|
|
||||||
|
If \a permissions is null then implementation-specific default permissions are
|
||||||
|
used.
|
||||||
|
|
||||||
\sa setFileName(), rmdir(), isRelativePath()
|
\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(dirName);
|
||||||
Q_UNUSED(createParentDirectories);
|
Q_UNUSED(createParentDirectories);
|
||||||
|
Q_UNUSED(permissions);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +55,8 @@
|
|||||||
#include "QtCore/qfile.h"
|
#include "QtCore/qfile.h"
|
||||||
#include "QtCore/qdir.h"
|
#include "QtCore/qdir.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#ifdef open
|
#ifdef open
|
||||||
#error qabstractfileengine_p.h must be included before any header file that defines open
|
#error qabstractfileengine_p.h must be included before any header file that defines open
|
||||||
#endif
|
#endif
|
||||||
@ -135,7 +137,8 @@ public:
|
|||||||
virtual bool rename(const QString &newName);
|
virtual bool rename(const QString &newName);
|
||||||
virtual bool renameOverwrite(const QString &newName);
|
virtual bool renameOverwrite(const QString &newName);
|
||||||
virtual bool link(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 rmdir(const QString &dirName, bool recurseParentDirectories) const;
|
||||||
virtual bool setSize(qint64 size);
|
virtual bool setSize(qint64 size);
|
||||||
virtual bool caseSensitive() const;
|
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.
|
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()
|
\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
|
bool QDir::mkdir(const QString &dirName) const
|
||||||
{
|
{
|
||||||
|
@ -196,6 +196,7 @@ public:
|
|||||||
SortFlags sort = NoSort) const;
|
SortFlags sort = NoSort) const;
|
||||||
|
|
||||||
bool mkdir(const QString &dirName) const;
|
bool mkdir(const QString &dirName) const;
|
||||||
|
bool mkdir(const QString &dirName, QFile::Permissions permissions) const;
|
||||||
bool rmdir(const QString &dirName) const;
|
bool rmdir(const QString &dirName) const;
|
||||||
bool mkpath(const QString &dirPath) const;
|
bool mkpath(const QString &dirPath) const;
|
||||||
bool rmpath(const QString &dirPath) const;
|
bool rmpath(const QString &dirPath) const;
|
||||||
|
@ -52,11 +52,16 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include "private/qiodevice_p.h"
|
#include "private/qiodevice_p.h"
|
||||||
|
#include "qfiledevice.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#ifdef Q_OS_UNIX
|
#if defined(Q_OS_UNIX)
|
||||||
# include <sys/types.h> // for mode_t
|
# 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
|
#endif
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
@ -130,6 +135,31 @@ constexpr mode_t toMode_t(QFileDevice::Permissions permissions)
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QtPrivate
|
} // 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
|
#endif // Q_OS_UNIX
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -56,6 +56,8 @@
|
|||||||
#include "qfilesystemmetadata_p.h"
|
#include "qfilesystemmetadata_p.h"
|
||||||
#include <QtCore/private/qsystemerror_p.h>
|
#include <QtCore/private/qsystemerror_p.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
#define Q_RETURN_ON_INVALID_FILENAME(message, result) \
|
#define Q_RETURN_ON_INVALID_FILENAME(message, result) \
|
||||||
@ -151,7 +153,8 @@ public:
|
|||||||
static QString rootPath();
|
static QString rootPath();
|
||||||
static QString tempPath();
|
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 removeDirectory(const QFileSystemEntry &entry, bool removeEmptyParents);
|
||||||
|
|
||||||
static bool createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error);
|
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
|
// Note: if \a shouldMkdirFirst is false, we assume the caller did try to mkdir
|
||||||
// before calling this function.
|
// 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
|
// 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
|
// 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;
|
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;
|
return true;
|
||||||
if (errno == EISDIR)
|
if (errno == EISDIR)
|
||||||
return true;
|
return true;
|
||||||
@ -1135,17 +1136,18 @@ static bool createDirectoryWithParents(const QByteArray &nativeName, bool should
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
QByteArray parentNativeName = nativeName.left(slash);
|
QByteArray parentNativeName = nativeName.left(slash);
|
||||||
if (!createDirectoryWithParents(parentNativeName))
|
if (!createDirectoryWithParents(parentNativeName, mode))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// try again
|
// try again
|
||||||
if (QT_MKDIR(nativeName, 0777) == 0)
|
if (QT_MKDIR(nativeName, mode) == 0)
|
||||||
return true;
|
return true;
|
||||||
return errno == EEXIST && isDir(nativeName);
|
return errno == EEXIST && isDir(nativeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
//static
|
//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();
|
QString dirName = entry.filePath();
|
||||||
Q_CHECK_FILE_NAME(dirName, false);
|
Q_CHECK_FILE_NAME(dirName, false);
|
||||||
@ -1156,12 +1158,13 @@ bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool crea
|
|||||||
|
|
||||||
// try to mkdir this directory
|
// try to mkdir this directory
|
||||||
QByteArray nativeName = QFile::encodeName(dirName);
|
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;
|
return true;
|
||||||
if (!createParents)
|
if (!createParents)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return createDirectoryWithParents(nativeName, false);
|
return createDirectoryWithParents(nativeName, mode, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
//static
|
//static
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
#include "qsysinfo.h"
|
#include "qsysinfo.h"
|
||||||
#include "qscopeguard.h"
|
#include "qscopeguard.h"
|
||||||
#include "private/qabstractfileengine_p.h"
|
#include "private/qabstractfileengine_p.h"
|
||||||
|
#include "private/qfiledevice_p.h"
|
||||||
#include "private/qfsfileengine_p.h"
|
#include "private/qfsfileengine_p.h"
|
||||||
#include <private/qsystemlibrary_p.h>
|
#include <private/qsystemlibrary_p.h>
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
@ -147,6 +148,7 @@ typedef struct _REPARSE_DATA_BUFFER {
|
|||||||
#include <authz.h>
|
#include <authz.h>
|
||||||
#include <userenv.h>
|
#include <userenv.h>
|
||||||
static PSID currentUserSID = nullptr;
|
static PSID currentUserSID = nullptr;
|
||||||
|
static PSID currentGroupSID = nullptr;
|
||||||
static PSID worldSID = nullptr;
|
static PSID worldSID = nullptr;
|
||||||
static HANDLE currentUserImpersonatedToken = nullptr;
|
static HANDLE currentUserImpersonatedToken = nullptr;
|
||||||
|
|
||||||
@ -164,6 +166,9 @@ GlobalSid::~GlobalSid()
|
|||||||
free(currentUserSID);
|
free(currentUserSID);
|
||||||
currentUserSID = nullptr;
|
currentUserSID = nullptr;
|
||||||
|
|
||||||
|
free(currentGroupSID);
|
||||||
|
currentGroupSID = nullptr;
|
||||||
|
|
||||||
// worldSID was allocated with AllocateAndInitializeSid so it needs to be freed with FreeSid
|
// worldSID was allocated with AllocateAndInitializeSid so it needs to be freed with FreeSid
|
||||||
if (worldSID) {
|
if (worldSID) {
|
||||||
::FreeSid(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()
|
GlobalSid::GlobalSid()
|
||||||
{
|
{
|
||||||
// Create TRUSTEE for current user
|
|
||||||
HANDLE hnd = ::GetCurrentProcess();
|
HANDLE hnd = ::GetCurrentProcess();
|
||||||
HANDLE token = nullptr;
|
HANDLE token = nullptr;
|
||||||
if (::OpenProcessToken(hnd, TOKEN_QUERY, &token)) {
|
if (::OpenProcessToken(hnd, TOKEN_QUERY, &token)) {
|
||||||
DWORD retsize = 0;
|
// Create SID for current user
|
||||||
// GetTokenInformation requires a buffer big enough for the TOKEN_USER struct and
|
if (auto info = getTokenInfo<TOKEN_USER>(token, TokenUser)) {
|
||||||
// the SID struct. Since the SID struct can have variable number of subauthorities
|
copySID(currentUserSID, info->User.Sid);
|
||||||
// tacked at the end, its size is variable. Obtain the required size by first
|
free(info);
|
||||||
// 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 the current user's primary group.
|
||||||
|
if (auto info = getTokenInfo<TOKEN_GROUPS>(token, TokenGroups)) {
|
||||||
|
copySID(currentGroupSID, info->Groups[0].Sid);
|
||||||
|
free(info);
|
||||||
}
|
}
|
||||||
::CloseHandle(token);
|
::CloseHandle(token);
|
||||||
}
|
}
|
||||||
@ -360,6 +390,29 @@ ACCESS_MASK QAuthzClientContext::accessMask(PSECURITY_DESCRIPTOR pSD) const
|
|||||||
return accessMask;
|
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
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
@ -369,6 +422,195 @@ QT_BEGIN_NAMESPACE
|
|||||||
|
|
||||||
Q_CORE_EXPORT int qt_ntfs_permission_lookup = 0;
|
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)
|
static inline bool toFileTime(const QDateTime &date, FILETIME *fileTime)
|
||||||
{
|
{
|
||||||
SYSTEMTIME sTime;
|
SYSTEMTIME sTime;
|
||||||
@ -1206,12 +1448,13 @@ bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemM
|
|||||||
return data.hasFlags(what);
|
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)
|
if (lastError)
|
||||||
*lastError = 0;
|
*lastError = 0;
|
||||||
const QString longPath = QFSFileEnginePrivate::longFileName(path);
|
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.
|
// Capture lastError before any QString is freed since custom allocators might change it.
|
||||||
if (lastError)
|
if (lastError)
|
||||||
*lastError = GetLastError();
|
*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
|
// NOTE: if \a shouldMkdirFirst is false, we assume the caller did try to mkdir
|
||||||
// before calling this function.
|
// 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) {
|
const auto isUNCRoot = [](const QString &nativeName) {
|
||||||
return nativeName.startsWith(QLatin1String("\\\\"))
|
return nativeName.startsWith(QLatin1String("\\\\"))
|
||||||
@ -1270,7 +1515,7 @@ static bool createDirectoryWithParents(const QString &nativeName, bool shouldMkd
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (shouldMkdirFirst) {
|
if (shouldMkdirFirst) {
|
||||||
if (mkDir(nativeName))
|
if (mkDir(nativeName, securityAttributes))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1279,26 +1524,33 @@ static bool createDirectoryWithParents(const QString &nativeName, bool shouldMkd
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
const QString parentNativeName = nativeName.left(backSlash);
|
const QString parentNativeName = nativeName.left(backSlash);
|
||||||
if (!createDirectoryWithParents(parentNativeName))
|
if (!createDirectoryWithParents(parentNativeName, securityAttributes))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// try again
|
// try again
|
||||||
if (mkDir(nativeName))
|
if (mkDir(nativeName, securityAttributes))
|
||||||
return true;
|
return true;
|
||||||
return isDir(nativeName);
|
return isDir(nativeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
//static
|
//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();
|
QString dirName = entry.filePath();
|
||||||
Q_CHECK_FILE_NAME(dirName, false);
|
Q_CHECK_FILE_NAME(dirName, false);
|
||||||
|
|
||||||
dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName));
|
dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName));
|
||||||
|
|
||||||
|
QNativeFilePermissions nativePermissions(permissions, true);
|
||||||
|
if (!nativePermissions.isOk())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto securityAttributes = nativePermissions.securityAttributes();
|
||||||
|
|
||||||
// try to mkdir this directory
|
// try to mkdir this directory
|
||||||
DWORD lastError;
|
DWORD lastError;
|
||||||
if (mkDir(dirName, &lastError))
|
if (mkDir(dirName, securityAttributes, &lastError))
|
||||||
return true;
|
return true;
|
||||||
// mkpath should return true, if the directory already exists, mkdir false.
|
// mkpath should return true, if the directory already exists, mkdir false.
|
||||||
if (!createParents)
|
if (!createParents)
|
||||||
@ -1306,7 +1558,7 @@ bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool crea
|
|||||||
if (lastError == ERROR_ALREADY_EXISTS || lastError == ERROR_ACCESS_DENIED)
|
if (lastError == ERROR_ALREADY_EXISTS || lastError == ERROR_ACCESS_DENIED)
|
||||||
return isDirPath(dirName, nullptr);
|
return isDirPath(dirName, nullptr);
|
||||||
|
|
||||||
return createDirectoryWithParents(dirName, false);
|
return createDirectoryWithParents(dirName, securityAttributes, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
//static
|
//static
|
||||||
|
@ -1057,9 +1057,11 @@ bool QFSFileEngine::renameOverwrite(const QString &newName)
|
|||||||
/*!
|
/*!
|
||||||
\reimp
|
\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 <QtCore/private/qfilesystemmetadata_p.h>
|
||||||
#include <qhash.h>
|
#include <qhash.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#ifndef QT_NO_FSFILEENGINE
|
#ifndef QT_NO_FSFILEENGINE
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
@ -93,7 +95,8 @@ public:
|
|||||||
bool rename(const QString &newName) override;
|
bool rename(const QString &newName) override;
|
||||||
bool renameOverwrite(const QString &newName) override;
|
bool renameOverwrite(const QString &newName) override;
|
||||||
bool link(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 rmdir(const QString &dirName, bool recurseParentDirectories) const override;
|
||||||
bool setSize(qint64 size) override;
|
bool setSize(qint64 size) override;
|
||||||
bool caseSensitive() const override;
|
bool caseSensitive() const override;
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qfileinfo.h>
|
#include <qfileinfo.h>
|
||||||
|
#include <qscopedvaluerollback.h>
|
||||||
#include <qstringlist.h>
|
#include <qstringlist.h>
|
||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
@ -65,6 +66,7 @@
|
|||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
#define DRIVE "Q:"
|
#define DRIVE "Q:"
|
||||||
|
extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;
|
||||||
#else
|
#else
|
||||||
#define DRIVE
|
#define DRIVE
|
||||||
#endif
|
#endif
|
||||||
@ -114,6 +116,8 @@ private slots:
|
|||||||
void mkdirRmdir_data();
|
void mkdirRmdir_data();
|
||||||
void mkdirRmdir();
|
void mkdirRmdir();
|
||||||
void mkdirOnSymlink();
|
void mkdirOnSymlink();
|
||||||
|
void mkdirWithPermissions_data();
|
||||||
|
void mkdirWithPermissions();
|
||||||
|
|
||||||
void makedirReturnCode();
|
void makedirReturnCode();
|
||||||
|
|
||||||
@ -451,6 +455,49 @@ void tst_QDir::mkdirOnSymlink()
|
|||||||
#endif
|
#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()
|
void tst_QDir::makedirReturnCode()
|
||||||
{
|
{
|
||||||
QString dirName = QString::fromLatin1("makedirReturnCode");
|
QString dirName = QString::fromLatin1("makedirReturnCode");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user