diff options
Diffstat (limited to 'src/corelib/io/qfilesystemengine_win.cpp')
-rw-r--r-- | src/corelib/io/qfilesystemengine_win.cpp | 1205 |
1 files changed, 788 insertions, 417 deletions
diff --git a/src/corelib/io/qfilesystemengine_win.cpp b/src/corelib/io/qfilesystemengine_win.cpp index 6fa6ec2dc2..3ec32e31a1 100644 --- a/src/corelib/io/qfilesystemengine_win.cpp +++ b/src/corelib/io/qfilesystemengine_win.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qfilesystemengine_p.h" #include "qoperatingsystemversion.h" @@ -43,6 +7,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> @@ -55,6 +20,7 @@ #if QT_CONFIG(regularexpression) #include "qregularexpression.h" #endif +#include "qstring.h" #include <sys/types.h> #include <direct.h> @@ -71,6 +37,8 @@ #define SECURITY_WIN32 #include <security.h> +#include <QtCore/private/qfunctions_win_p.h> + #ifndef SPI_GETPLATFORMTYPE #define SPI_GETPLATFORMTYPE 257 #endif @@ -132,26 +100,24 @@ typedef struct _REPARSE_DATA_BUFFER { # define IO_REPARSE_TAG_SYMLINK (0xA000000CL) #endif #ifndef FSCTL_GET_REPARSE_POINT -# define FSCTL_GET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS) +# define FSCTL_GET_REPARSE_POINT \ + CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS) #endif -#if defined(QT_BOOTSTRAPPED) -# define QT_FEATURE_fslibs -1 -#else -# define QT_FEATURE_fslibs 1 -#endif // QT_BOOTSTRAPPED - #if QT_CONFIG(fslibs) #include <aclapi.h> +#include <authz.h> #include <userenv.h> -static TRUSTEE_W currentUserTrusteeW; -static TRUSTEE_W worldTrusteeW; -static PSID currentUserSID = 0; -static PSID worldSID = 0; +static PSID currentUserSID = nullptr; +static PSID currentGroupSID = nullptr; +static PSID worldSID = nullptr; static HANDLE currentUserImpersonatedToken = nullptr; +#endif // fslibs QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; +#if QT_CONFIG(fslibs) namespace { struct GlobalSid { @@ -162,12 +128,15 @@ struct GlobalSid GlobalSid::~GlobalSid() { free(currentUserSID); - currentUserSID = 0; + currentUserSID = nullptr; + + free(currentGroupSID); + currentGroupSID = nullptr; // worldSID was allocated with AllocateAndInitializeSid so it needs to be freed with FreeSid if (worldSID) { ::FreeSid(worldSID); - worldSID = 0; + worldSID = nullptr; } if (currentUserImpersonatedToken) { @@ -176,62 +145,464 @@ GlobalSid::~GlobalSid() } } -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) { - { - { - // Create TRUSTEE for current user - HANDLE hnd = ::GetCurrentProcess(); - HANDLE token = 0; - 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, 0, 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); - if (::CopySid(sidLen, currentUserSID, tokenSid)) - BuildTrusteeWithSid(¤tUserTrusteeW, currentUserSID); - } - free(tokenBuffer); - } - ::CloseHandle(token); - } + 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; +} - token = nullptr; - if (::OpenProcessToken(hnd, TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ, &token)) { - ::DuplicateToken(token, SecurityImpersonation, ¤tUserImpersonatedToken); - ::CloseHandle(token); - } +/* + 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); +} - { - // Create TRUSTEE for Everyone (World) - SID_IDENTIFIER_AUTHORITY worldAuth = { SECURITY_WORLD_SID_AUTHORITY }; - if (AllocateAndInitializeSid(&worldAuth, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &worldSID)) - BuildTrusteeWithSid(&worldTrusteeW, worldSID); - } +GlobalSid::GlobalSid() +{ + HANDLE hnd = ::GetCurrentProcess(); + HANDLE token = nullptr; + if (::OpenProcessToken(hnd, TOKEN_QUERY, &token)) { + // 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); + } + + token = nullptr; + if (::OpenProcessToken(hnd, + TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ, + &token)) { + ::DuplicateToken(token, SecurityImpersonation, ¤tUserImpersonatedToken); + ::CloseHandle(token); } + + // Create SID for Everyone (World) + SID_IDENTIFIER_AUTHORITY worldAuth = { SECURITY_WORLD_SID_AUTHORITY }; + AllocateAndInitializeSid(&worldAuth, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &worldSID); } Q_GLOBAL_STATIC(GlobalSid, initGlobalSid) -QT_END_NAMESPACE +/*! + \class QAuthzResourceManager + \internal + + RAII wrapper around Windows Authz resource manager. +*/ +class QAuthzResourceManager +{ +public: + QAuthzResourceManager(); + ~QAuthzResourceManager(); + + bool isValid() const { return resourceManager != nullptr; } + +private: + friend class QAuthzClientContext; + Q_DISABLE_COPY_MOVE(QAuthzResourceManager) + + AUTHZ_RESOURCE_MANAGER_HANDLE resourceManager; +}; + +/*! + \class QAuthzClientContext + \internal + + RAII wrapper around Windows Authz client context. +*/ +class QAuthzClientContext +{ +public: + // Tag to differentiate SID and TOKEN constructors. Those two types are pointers to void. + struct TokenTag + { + }; + + QAuthzClientContext(const QAuthzResourceManager &rm, PSID pSID); + QAuthzClientContext(const QAuthzResourceManager &rm, HANDLE tokenHandle, TokenTag); + + ~QAuthzClientContext(); + + bool isValid() const { return context != nullptr; } + + static constexpr ACCESS_MASK InvalidAccess = ~ACCESS_MASK(0); + + ACCESS_MASK accessMask(PSECURITY_DESCRIPTOR pSD) const; + +private: + Q_DISABLE_COPY_MOVE(QAuthzClientContext) + AUTHZ_CLIENT_CONTEXT_HANDLE context = nullptr; +}; + +QAuthzResourceManager::QAuthzResourceManager() +{ + if (!AuthzInitializeResourceManager(AUTHZ_RM_FLAG_NO_AUDIT, nullptr, nullptr, nullptr, nullptr, + &resourceManager)) { + resourceManager = nullptr; + } +} + +QAuthzResourceManager::~QAuthzResourceManager() +{ + if (resourceManager) + AuthzFreeResourceManager(resourceManager); +} + +/*! + \internal + + Create an Authz client context from a security identifier. + + The created context will not include any group information associated with \a pSID. +*/ +QAuthzClientContext::QAuthzClientContext(const QAuthzResourceManager &rm, PSID pSID) +{ + if (!rm.isValid()) + return; + + LUID unusedId = {}; + + if (!AuthzInitializeContextFromSid(AUTHZ_SKIP_TOKEN_GROUPS, pSID, rm.resourceManager, nullptr, + unusedId, nullptr, &context)) { + context = nullptr; + } +} + +/*! + \internal + + Create an Authz client context from a token handle. +*/ +QAuthzClientContext::QAuthzClientContext(const QAuthzResourceManager &rm, HANDLE tokenHandle, + TokenTag) +{ + if (!rm.isValid()) + return; + + LUID unusedId = {}; + + if (!AuthzInitializeContextFromToken(0, tokenHandle, rm.resourceManager, nullptr, unusedId, + nullptr, &context)) { + context = nullptr; + } +} + +QAuthzClientContext::~QAuthzClientContext() +{ + if (context) + AuthzFreeContext(context); +} + +/*! + \internal + + Returns permissions that are granted to this client by \a pSD. + + Returns \c InvalidAccess in case of an error. +*/ +ACCESS_MASK QAuthzClientContext::accessMask(PSECURITY_DESCRIPTOR pSD) const +{ + if (!isValid()) + return InvalidAccess; + + AUTHZ_ACCESS_REQUEST accessRequest = {}; + AUTHZ_ACCESS_REPLY accessReply = {}; + ACCESS_MASK accessMask = 0; + DWORD error = 0; + + accessRequest.DesiredAccess = MAXIMUM_ALLOWED; + + accessReply.ResultListLength = 1; + accessReply.GrantedAccessMask = &accessMask; + accessReply.Error = &error; + + if (!AuthzAccessCheck(0, context, &accessRequest, nullptr, pSD, nullptr, 0, &accessReply, + nullptr) + || error != 0) { + return InvalidAccess; + } + + 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); +} + +[[maybe_unused]] // Not currently used; included to show how to do it (without bit-rotting). +constexpr QFileDevice::Permissions toSpecificPermissions(PermissionTag tag, + NonSpecificPermissions permissions) +{ + return QFileDevice::Permissions::fromInt(permissions.toInt() << int(tag)); +} } // anonymous namespace #endif // QT_CONFIG(fslibs) -QT_BEGIN_NAMESPACE +#if QT_DEPRECATED_SINCE(6,6) +int qt_ntfs_permission_lookup = 0; +#endif + +static QBasicAtomicInt qt_ntfs_permission_lookup_v2 = Q_BASIC_ATOMIC_INITIALIZER(0); + +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + +bool qEnableNtfsPermissionChecks() noexcept +{ + return qt_ntfs_permission_lookup_v2.fetchAndAddRelaxed(1) +QT_IF_DEPRECATED_SINCE(6, 6, /*nothing*/, + qt_ntfs_permission_lookup) + != 0; +} + +bool qDisableNtfsPermissionChecks() noexcept +{ + return qt_ntfs_permission_lookup_v2.fetchAndSubRelaxed(1) +QT_IF_DEPRECATED_SINCE(6, 6, /*nothing*/, + qt_ntfs_permission_lookup) + == 1; +} + +bool qAreNtfsPermissionChecksEnabled() noexcept +{ + return qt_ntfs_permission_lookup_v2.loadRelaxed() +QT_IF_DEPRECATED_SINCE(6, 6, /*nothing*/, + qt_ntfs_permission_lookup) + ; +} +QT_WARNING_POP + +/*! + \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 = [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; -Q_CORE_EXPORT int qt_ntfs_permission_lookup = 0; + 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; +#else + Q_UNUSED(perms); + Q_UNUSED(isDir); +#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 callers 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) { @@ -250,7 +621,7 @@ static inline bool toFileTime(const QDateTime &date, FILETIME *fileTime) lTime.wMilliseconds = t.msec(); lTime.wDayOfWeek = d.dayOfWeek() % 7; - if (!::TzSpecificLocalTimeToSystemTime(0, &lTime, &sTime)) + if (!::TzSpecificLocalTimeToSystemTime(nullptr, &lTime, &sTime)) return false; } else { QDateTime utcDate = date.toUTC(); @@ -273,19 +644,17 @@ static inline bool toFileTime(const QDateTime &date, FILETIME *fileTime) static QString readSymLink(const QFileSystemEntry &link) { QString result; - HANDLE handle = CreateFile((wchar_t*)link.nativeFilePath().utf16(), - FILE_READ_EA, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - 0, + HANDLE handle = CreateFile((wchar_t *)link.nativeFilePath().utf16(), FILE_READ_EA, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, - 0); + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (handle != INVALID_HANDLE_VALUE) { DWORD bufsize = MAXIMUM_REPARSE_DATA_BUFFER_SIZE; REPARSE_DATA_BUFFER *rdb = (REPARSE_DATA_BUFFER*)malloc(bufsize); Q_CHECK_PTR(rdb); DWORD retsize = 0; - if (::DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, 0, 0, rdb, bufsize, &retsize, 0)) { + if (::DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, nullptr, 0, rdb, bufsize, &retsize, + nullptr)) { if (rdb->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { int length = rdb->MountPointReparseBuffer.SubstituteNameLength / sizeof(wchar_t); int offset = rdb->MountPointReparseBuffer.SubstituteNameOffset / sizeof(wchar_t); @@ -297,33 +666,27 @@ static QString readSymLink(const QFileSystemEntry &link) const wchar_t* PathBuffer = &rdb->SymbolicLinkReparseBuffer.PathBuffer[offset]; result = QString::fromWCharArray(PathBuffer, length); } - // cut-off "\\?\" and "\??\" - if (result.size() > 4 - && result.at(0) == QLatin1Char('\\') - && result.at(2) == QLatin1Char('?') - && result.at(3) == QLatin1Char('\\')) { - result = result.mid(4); - // cut off UNC in addition when the link points at a UNC share - // in which case we need to prepend another backslash to get \\server\share - if (QStringView{result}.left(3) == QLatin1String("UNC")) { - result.replace(0, 3, QLatin1Char('\\')); - } - } + // remove "\\?\", "\??\" or "\\?\UNC\" + result = QFileSystemEntry::removeUncOrLongPathPrefix(result); } free(rdb); CloseHandle(handle); #if QT_CONFIG(fslibs) && QT_CONFIG(regularexpression) initGlobalSid(); - QRegularExpression matchVolumeRe(QLatin1String("^Volume\\{([a-z]|[0-9]|-)+\\}\\\\"), QRegularExpression::CaseInsensitiveOption); + QRegularExpression matchVolumeRe("^Volume\\{([a-z]|[0-9]|-)+\\}\\\\"_L1, + QRegularExpression::CaseInsensitiveOption); auto matchVolume = matchVolumeRe.match(result); if (matchVolume.hasMatch()) { Q_ASSERT(matchVolume.capturedStart() == 0); DWORD len; wchar_t buffer[MAX_PATH]; - const QString volumeName = QLatin1String("\\\\?\\") + matchVolume.captured(); - if (GetVolumePathNamesForVolumeName(reinterpret_cast<LPCWSTR>(volumeName.utf16()), buffer, MAX_PATH, &len) != 0) + const QString volumeName = "\\\\?\\"_L1 + matchVolume.captured(); + if (GetVolumePathNamesForVolumeName(reinterpret_cast<LPCWSTR>(volumeName.utf16()), + buffer, MAX_PATH, &len) + != 0) { result.replace(0, matchVolume.capturedLength(), QString::fromWCharArray(buffer)); + } } #endif // QT_CONFIG(fslibs) } @@ -335,20 +698,16 @@ static QString readLink(const QFileSystemEntry &link) #if QT_CONFIG(fslibs) QString ret; - bool neededCoInit = false; IShellLink *psl; // pointer to IShellLink i/f WIN32_FIND_DATA wfd; wchar_t szGotPath[MAX_PATH]; + QComHelper comHelper; + // Get pointer to the IShellLink interface. - HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&psl); + HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, + (LPVOID *)&psl); - if (hres == CO_E_NOTINITIALIZED) { // COM was not initialized - neededCoInit = true; - CoInitialize(NULL); - hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, - IID_IShellLink, (LPVOID *)&psl); - } if (SUCCEEDED(hres)) { // Get pointer to the IPersistFile interface. IPersistFile *ppf; hres = psl->QueryInterface(IID_IPersistFile, (LPVOID *)&ppf); @@ -364,8 +723,6 @@ static QString readLink(const QFileSystemEntry &link) } psl->Release(); } - if (neededCoInit) - CoUninitialize(); return ret; #else @@ -377,11 +734,12 @@ static QString readLink(const QFileSystemEntry &link) static bool uncShareExists(const QString &server) { // This code assumes the UNC path is always like \\?\UNC\server... - const auto parts = QStringView{server}.split(QLatin1Char('\\'), Qt::SkipEmptyParts); + const auto parts = QStringView{server}.split(u'\\', Qt::SkipEmptyParts); if (parts.count() >= 3) { QStringList shares; - if (QFileSystemEngine::uncListSharesOnServer(QLatin1String("\\\\") + parts.at(2), &shares)) - return parts.count() < 4 || shares.contains(parts.at(3).toString(), Qt::CaseInsensitive); + if (QFileSystemEngine::uncListSharesOnServer("\\\\"_L1 + parts.at(2), &shares)) + return parts.count() < 4 + || shares.contains(parts.at(3).toString(), Qt::CaseInsensitive); } return false; } @@ -389,11 +747,11 @@ static bool uncShareExists(const QString &server) static inline bool getFindData(QString path, WIN32_FIND_DATA &findData) { // path should not end with a trailing slash - while (path.endsWith(QLatin1Char('\\'))) + while (path.endsWith(u'\\')) path.chop(1); // can't handle drives - if (!path.endsWith(QLatin1Char(':'))) { + if (!path.endsWith(u':')) { HANDLE hFind = ::FindFirstFile((wchar_t*)path.utf16(), &findData); if (hFind != INVALID_HANDLE_VALUE) { ::FindClose(hFind); @@ -404,7 +762,6 @@ static inline bool getFindData(QString path, WIN32_FIND_DATA &findData) return false; } -#if defined(__IFileOperation_INTERFACE_DEFINED__) class FileOperationProgressSink : public IFileOperationProgressSink { public: @@ -465,9 +822,10 @@ public: return (dwFlags & TSF_DELETE_RECYCLE_IF_POSSIBLE) ? S_OK : E_FAIL; } HRESULT STDMETHODCALLTYPE PostDeleteItem(DWORD /* dwFlags */, IShellItem * /* psiItem */, - HRESULT /* hrDelete */, + HRESULT hrDelete, IShellItem *psiNewlyCreated) override { + deleteResult = hrDelete; if (psiNewlyCreated) { wchar_t *pszName = nullptr; psiNewlyCreated->GetDisplayName(SIGDN_FILESYSPATH, &pszName); @@ -488,10 +846,10 @@ public: HRESULT STDMETHODCALLTYPE ResumeTimer() override { return S_OK; } QString targetPath; + HRESULT deleteResult = S_OK; private: ULONG ref; }; -#endif bool QFileSystemEngine::uncListSharesOnServer(const QString &server, QStringList *list) { @@ -499,7 +857,8 @@ bool QFileSystemEngine::uncListSharesOnServer(const QString &server, QStringList SHARE_INFO_1 *BufPtr, *p; DWORD er = 0, tr = 0, resume = 0, i; do { - res = NetShareEnum((wchar_t*)server.utf16(), 1, (LPBYTE *)&BufPtr, DWORD(-1), &er, &tr, &resume); + res = NetShareEnum((wchar_t *)server.utf16(), 1, (LPBYTE *)&BufPtr, DWORD(-1), &er, &tr, + &resume); if (res == ERROR_SUCCESS || res == ERROR_MORE_DATA) { p = BufPtr; for (i = 1; i <= er; ++i) { @@ -527,6 +886,18 @@ void QFileSystemEngine::clearWinStatData(QFileSystemMetaData &data) QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data) { + QFileSystemEntry ret = getRawLinkPath(link, data); + if (!ret.isEmpty() && ret.isRelative()) { + QString target = absoluteName(link).path() + u'/' + ret.filePath(); + ret = QFileSystemEntry(QDir::cleanPath(target)); + } + return ret; +} + +//static +QFileSystemEntry QFileSystemEngine::getRawLinkPath(const QFileSystemEntry &link, + QFileSystemMetaData &data) +{ Q_CHECK_FILE_NAME(link, link); if (data.missingFlags(QFileSystemMetaData::LinkType)) @@ -537,16 +908,32 @@ QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, target = readLink(link); else if (data.isLink()) target = readSymLink(link); + return QFileSystemEntry(target); +} + +//static +QFileSystemEntry QFileSystemEngine::junctionTarget(const QFileSystemEntry &link, + QFileSystemMetaData &data) +{ + Q_CHECK_FILE_NAME(link, link); + + if (data.missingFlags(QFileSystemMetaData::JunctionType)) + QFileSystemEngine::fillMetaData(link, data, QFileSystemMetaData::LinkType); + + QString target; + if (data.isJunction()) + target = readSymLink(link); QFileSystemEntry ret(target); if (!target.isEmpty() && ret.isRelative()) { - target.prepend(absoluteName(link).path() + QLatin1Char('/')); + target.prepend(absoluteName(link).path() + u'/'); ret = QFileSystemEntry(QDir::cleanPath(target)); } return ret; } //static -QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data) +QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, + QFileSystemMetaData &data) { Q_CHECK_FILE_NAME(entry, entry); @@ -581,8 +968,8 @@ QString QFileSystemEngine::nativeAbsoluteFilePath(const QString &path) // (which is an invalid filename) this function will strip the space off and viola, // the file is later reported as existing. Therefore, we re-add the whitespace that // was at the end of path in order to keep the filename invalid. - if (!path.isEmpty() && path.at(path.size() - 1) == QLatin1Char(' ')) - absPath.append(QLatin1Char(' ')); + if (!path.isEmpty() && path.at(path.size() - 1) == u' ') + absPath.append(u' '); return absPath; } @@ -599,17 +986,17 @@ QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry) else ret = QDir::fromNativeSeparators(nativeAbsoluteFilePath(entry.filePath())); } else { - ret = QDir::cleanPath(QDir::currentPath() + QLatin1Char('/') + entry.filePath()); + ret = QDir::cleanPath(QDir::currentPath() + u'/' + entry.filePath()); } // The path should be absolute at this point. // From the docs : // Absolute paths begin with the directory separator "/" // (optionally preceded by a drive specification under Windows). - if (ret.at(0) != QLatin1Char('/')) { + if (ret.at(0) != u'/') { Q_ASSERT(ret.length() >= 2); Q_ASSERT(ret.at(0).isLetter()); - Q_ASSERT(ret.at(1) == QLatin1Char(':')); + Q_ASSERT(ret.at(1) == u':'); // Force uppercase drive letters. ret[0] = ret.at(0).toUpper(); @@ -617,15 +1004,6 @@ QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry) return QFileSystemEntry(ret, QFileSystemEntry::FromInternalPath()); } -#if defined(Q_CC_MINGW) && WINVER < 0x0602 // Windows 8 onwards - -typedef struct _FILE_ID_INFO { - ULONGLONG VolumeSerialNumber; - FILE_ID_128 FileId; -} FILE_ID_INFO, *PFILE_ID_INFO; - -#endif // if defined (Q_CC_MINGW) && WINVER < 0x0602 - // File ID for Windows up to version 7 and FAT32 drives static inline QByteArray fileId(HANDLE handle) { @@ -647,15 +1025,19 @@ QByteArray fileIdWin8(HANDLE handle) #if !defined(QT_BOOTSTRAPPED) QByteArray result; FILE_ID_INFO infoEx; - if (GetFileInformationByHandleEx(handle, - static_cast<FILE_INFO_BY_HANDLE_CLASS>(18), // FileIdInfo in Windows 8 - &infoEx, sizeof(FILE_ID_INFO))) { + if (GetFileInformationByHandleEx( + handle, + static_cast<FILE_INFO_BY_HANDLE_CLASS>(18), // FileIdInfo in Windows 8 + &infoEx, sizeof(FILE_ID_INFO))) { result = QByteArray::number(infoEx.VolumeSerialNumber, 16); result += ':'; // Note: MinGW-64's definition of FILE_ID_128 differs from the MSVC one. - result += QByteArray(reinterpret_cast<const char *>(&infoEx.FileId), int(sizeof(infoEx.FileId))).toHex(); + result += QByteArray(reinterpret_cast<const char *>(&infoEx.FileId), + int(sizeof(infoEx.FileId))) + .toHex(); } else { - result = fileId(handle); // GetFileInformationByHandleEx() is observed to fail for FAT32, QTBUG-74759 + // GetFileInformationByHandleEx() is observed to fail for FAT32, QTBUG-74759 + result = fileId(handle); } return result; #else // !QT_BOOTSTRAPPED @@ -670,10 +1052,8 @@ QByteArray QFileSystemEngine::id(const QFileSystemEntry &entry) QByteArray result; - const HANDLE handle = - CreateFile((wchar_t*)entry.nativeFilePath().utf16(), 0, - FILE_SHARE_READ, NULL, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, NULL); + const HANDLE handle = CreateFile((wchar_t *)entry.nativeFilePath().utf16(), 0, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (handle != INVALID_HANDLE_VALUE) { result = id(handle); CloseHandle(handle); @@ -684,29 +1064,28 @@ QByteArray QFileSystemEngine::id(const QFileSystemEntry &entry) //static QByteArray QFileSystemEngine::id(HANDLE fHandle) { - return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8 ? - fileIdWin8(HANDLE(fHandle)) : fileId(HANDLE(fHandle)); + return fileIdWin8(HANDLE(fHandle)); } //static bool QFileSystemEngine::setFileTime(HANDLE fHandle, const QDateTime &newDate, - QAbstractFileEngine::FileTime time, QSystemError &error) + QFile::FileTime time, QSystemError &error) { FILETIME fTime; - FILETIME *pLastWrite = NULL; - FILETIME *pLastAccess = NULL; - FILETIME *pCreationTime = NULL; + FILETIME *pLastWrite = nullptr; + FILETIME *pLastAccess = nullptr; + FILETIME *pCreationTime = nullptr; switch (time) { - case QAbstractFileEngine::ModificationTime: + case QFile::FileModificationTime: pLastWrite = &fTime; break; - case QAbstractFileEngine::AccessTime: + case QFile::FileAccessTime: pLastAccess = &fTime; break; - case QAbstractFileEngine::BirthTime: + case QFile::FileBirthTime: pCreationTime = &fTime; break; @@ -729,32 +1108,36 @@ QString QFileSystemEngine::owner(const QFileSystemEntry &entry, QAbstractFileEng { QString name; #if QT_CONFIG(fslibs) - extern int qt_ntfs_permission_lookup; - if (qt_ntfs_permission_lookup > 0) { + if (qAreNtfsPermissionChecksEnabled()) { initGlobalSid(); { PSID pOwner = 0; PSECURITY_DESCRIPTOR pSD; - if (GetNamedSecurityInfo(reinterpret_cast<const wchar_t*>(entry.nativeFilePath().utf16()), SE_FILE_OBJECT, - own == QAbstractFileEngine::OwnerGroup ? GROUP_SECURITY_INFORMATION : OWNER_SECURITY_INFORMATION, - own == QAbstractFileEngine::OwnerUser ? &pOwner : 0, own == QAbstractFileEngine::OwnerGroup ? &pOwner : 0, - 0, 0, &pSD) == ERROR_SUCCESS) { + if (GetNamedSecurityInfo( + reinterpret_cast<const wchar_t *>(entry.nativeFilePath().utf16()), + SE_FILE_OBJECT, + own == QAbstractFileEngine::OwnerGroup ? GROUP_SECURITY_INFORMATION + : OWNER_SECURITY_INFORMATION, + own == QAbstractFileEngine::OwnerUser ? &pOwner : nullptr, + own == QAbstractFileEngine::OwnerGroup ? &pOwner : nullptr, nullptr, + nullptr, &pSD) + == ERROR_SUCCESS) { DWORD lowner = 64; DWORD ldomain = 64; QVarLengthArray<wchar_t, 64> owner(lowner); QVarLengthArray<wchar_t, 64> domain(ldomain); SID_NAME_USE use = SidTypeUnknown; // First call, to determine size of the strings (with '\0'). - if (!LookupAccountSid(NULL, pOwner, (LPWSTR)owner.data(), &lowner, - domain.data(), &ldomain, &use)) { + if (!LookupAccountSid(nullptr, pOwner, (LPWSTR)owner.data(), &lowner, domain.data(), + &ldomain, &use)) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { if (lowner > (DWORD)owner.size()) owner.resize(lowner); if (ldomain > (DWORD)domain.size()) domain.resize(ldomain); // Second call, try on resized buf-s - if (!LookupAccountSid(NULL, pOwner, owner.data(), &lowner, - domain.data(), &ldomain, &use)) { + if (!LookupAccountSid(nullptr, pOwner, owner.data(), &lowner, domain.data(), + &ldomain, &use)) { lowner = 0; } } else { @@ -779,104 +1162,80 @@ bool QFileSystemEngine::fillPermissions(const QFileSystemEntry &entry, QFileSyst QFileSystemMetaData::MetaDataFlags what) { #if QT_CONFIG(fslibs) - if (qt_ntfs_permission_lookup > 0) { + if (qAreNtfsPermissionChecksEnabled()) { initGlobalSid(); - { - enum { ReadMask = 0x00000001, WriteMask = 0x00000002, ExecMask = 0x00000020 }; - QString fname = entry.nativeFilePath(); - PSID pOwner = 0; - PSID pGroup = 0; - PACL pDacl; - PSECURITY_DESCRIPTOR pSD; - DWORD res = GetNamedSecurityInfo(reinterpret_cast<const wchar_t*>(fname.utf16()), SE_FILE_OBJECT, - OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, - &pOwner, &pGroup, &pDacl, 0, &pSD); - if (res == ERROR_SUCCESS) { - ACCESS_MASK access_mask; - TRUSTEE_W trustee; - if (what & QFileSystemMetaData::UserPermissions) { // user - // Using AccessCheck because GetEffectiveRightsFromAcl doesn't account for elevation - if (currentUserImpersonatedToken) { - GENERIC_MAPPING mapping = {FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE, FILE_ALL_ACCESS}; - PRIVILEGE_SET privileges; - DWORD grantedAccess; - BOOL result; - - data.knownFlagsMask |= QFileSystemMetaData::UserPermissions; - DWORD genericAccessRights = GENERIC_READ; - ::MapGenericMask(&genericAccessRights, &mapping); - - DWORD privilegesLength = sizeof(privileges); - if (::AccessCheck(pSD, currentUserImpersonatedToken, genericAccessRights, - &mapping, &privileges, &privilegesLength, &grantedAccess, &result) && result) { - data.entryFlags |= QFileSystemMetaData::UserReadPermission; - } + QString fname = entry.nativeFilePath(); + PSID pOwner; + PSID pGroup; + PACL pDacl; + PSECURITY_DESCRIPTOR pSD; + + // pDacl is unused directly by the code below, but it is still needed here because + // access checks below return incorrect results unless DACL_SECURITY_INFORMATION is + // passed to this call. + DWORD res = GetNamedSecurityInfo( + reinterpret_cast<const wchar_t *>(fname.utf16()), SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, + &pOwner, &pGroup, &pDacl, nullptr, &pSD); + if (res == ERROR_SUCCESS) { + QAuthzResourceManager rm; + + auto addPermissions = [&data](ACCESS_MASK accessMask, + QFileSystemMetaData::MetaDataFlag readFlags, + QFileSystemMetaData::MetaDataFlag writeFlags, + QFileSystemMetaData::MetaDataFlag executeFlags) { + // Check for generic permissions and file-specific bits that most closely + // represent POSIX permissions. + + // Constants like FILE_GENERIC_{READ,WRITE,EXECUTE} cannot be used + // here because they contain permission bits shared between all of them. + if (accessMask & (GENERIC_READ | FILE_READ_DATA)) + data.entryFlags |= readFlags; + if (accessMask & (GENERIC_WRITE | FILE_WRITE_DATA)) + data.entryFlags |= writeFlags; + if (accessMask & (GENERIC_EXECUTE | FILE_EXECUTE)) + data.entryFlags |= executeFlags; + }; + + if (what & QFileSystemMetaData::UserPermissions && currentUserImpersonatedToken) { + data.knownFlagsMask |= QFileSystemMetaData::UserPermissions; + QAuthzClientContext context(rm, currentUserImpersonatedToken, + QAuthzClientContext::TokenTag {}); + addPermissions(context.accessMask(pSD), + QFileSystemMetaData::UserReadPermission, + QFileSystemMetaData::UserWritePermission, + QFileSystemMetaData::UserExecutePermission); + } - privilegesLength = sizeof(privileges); - genericAccessRights = GENERIC_WRITE; - ::MapGenericMask(&genericAccessRights, &mapping); - if (::AccessCheck(pSD, currentUserImpersonatedToken, genericAccessRights, - &mapping, &privileges, &privilegesLength, &grantedAccess, &result) && result) { - data.entryFlags |= QFileSystemMetaData::UserWritePermission; - } + if (what & QFileSystemMetaData::OwnerPermissions) { + data.knownFlagsMask |= QFileSystemMetaData::OwnerPermissions; + QAuthzClientContext context(rm, pOwner); + addPermissions(context.accessMask(pSD), + QFileSystemMetaData::OwnerReadPermission, + QFileSystemMetaData::OwnerWritePermission, + QFileSystemMetaData::OwnerExecutePermission); + } - privilegesLength = sizeof(privileges); - genericAccessRights = GENERIC_EXECUTE; - ::MapGenericMask(&genericAccessRights, &mapping); - if (::AccessCheck(pSD, currentUserImpersonatedToken, genericAccessRights, - &mapping, &privileges, &privilegesLength, &grantedAccess, &result) && result) { - data.entryFlags |= QFileSystemMetaData::UserExecutePermission; - } - } else { // fallback to GetEffectiveRightsFromAcl - data.knownFlagsMask |= QFileSystemMetaData::UserPermissions; - if (GetEffectiveRightsFromAclW(pDacl, ¤tUserTrusteeW, &access_mask) != ERROR_SUCCESS) - access_mask = ACCESS_MASK(-1); - if (access_mask & ReadMask) - data.entryFlags |= QFileSystemMetaData::UserReadPermission; - if (access_mask & WriteMask) - data.entryFlags|= QFileSystemMetaData::UserWritePermission; - if (access_mask & ExecMask) - data.entryFlags|= QFileSystemMetaData::UserExecutePermission; - } - } - if (what & QFileSystemMetaData::OwnerPermissions) { // owner - data.knownFlagsMask |= QFileSystemMetaData::OwnerPermissions; - BuildTrusteeWithSid(&trustee, pOwner); - if (GetEffectiveRightsFromAcl(pDacl, &trustee, &access_mask) != ERROR_SUCCESS) - access_mask = (ACCESS_MASK)-1; - if (access_mask & ReadMask) - data.entryFlags |= QFileSystemMetaData::OwnerReadPermission; - if (access_mask & WriteMask) - data.entryFlags |= QFileSystemMetaData::OwnerWritePermission; - if (access_mask & ExecMask) - data.entryFlags |= QFileSystemMetaData::OwnerExecutePermission; - } - if (what & QFileSystemMetaData::GroupPermissions) { // group - data.knownFlagsMask |= QFileSystemMetaData::GroupPermissions; - BuildTrusteeWithSid(&trustee, pGroup); - if (GetEffectiveRightsFromAcl(pDacl, &trustee, &access_mask) != ERROR_SUCCESS) - access_mask = (ACCESS_MASK)-1; - if (access_mask & ReadMask) - data.entryFlags |= QFileSystemMetaData::GroupReadPermission; - if (access_mask & WriteMask) - data.entryFlags |= QFileSystemMetaData::GroupWritePermission; - if (access_mask & ExecMask) - data.entryFlags |= QFileSystemMetaData::GroupExecutePermission; - } - if (what & QFileSystemMetaData::OtherPermissions) { // other (world) - data.knownFlagsMask |= QFileSystemMetaData::OtherPermissions; - if (GetEffectiveRightsFromAcl(pDacl, &worldTrusteeW, &access_mask) != ERROR_SUCCESS) - access_mask = (ACCESS_MASK)-1; // ### - if (access_mask & ReadMask) - data.entryFlags |= QFileSystemMetaData::OtherReadPermission; - if (access_mask & WriteMask) - data.entryFlags |= QFileSystemMetaData::OtherWritePermission; - if (access_mask & ExecMask) - data.entryFlags |= QFileSystemMetaData::OwnerExecutePermission; - } - LocalFree(pSD); + if (what & QFileSystemMetaData::GroupPermissions) { + data.knownFlagsMask |= QFileSystemMetaData::GroupPermissions; + QAuthzClientContext context(rm, pGroup); + addPermissions(context.accessMask(pSD), + QFileSystemMetaData::GroupReadPermission, + QFileSystemMetaData::GroupWritePermission, + QFileSystemMetaData::GroupExecutePermission); } + + if (what & QFileSystemMetaData::OtherPermissions) { + data.knownFlagsMask |= QFileSystemMetaData::OtherPermissions; + QAuthzClientContext context(rm, worldSID); + addPermissions(context.accessMask(pSD), + QFileSystemMetaData::OtherReadPermission, + QFileSystemMetaData::OtherWritePermission, + QFileSystemMetaData::OtherExecutePermission); + } + + LocalFree(pSD); } } else #endif @@ -896,14 +1255,16 @@ bool QFileSystemEngine::fillPermissions(const QFileSystemEntry &entry, QFileSyst QString fname = entry.filePath(); QString ext = fname.right(4).toLower(); - if (data.isDirectory() || - ext == QLatin1String(".exe") || ext == QLatin1String(".com") || ext == QLatin1String(".bat") || - ext == QLatin1String(".pif") || ext == QLatin1String(".cmd")) { - data.entryFlags |= QFileSystemMetaData::OwnerExecutePermission | QFileSystemMetaData::GroupExecutePermission - | QFileSystemMetaData::OtherExecutePermission | QFileSystemMetaData::UserExecutePermission; + if (data.isDirectory() || ext == ".exe"_L1 || ext == ".com"_L1 + || ext == ".bat"_L1 || ext == ".pif"_L1 || ext == ".cmd"_L1) { + data.entryFlags |= QFileSystemMetaData::OwnerExecutePermission + | QFileSystemMetaData::GroupExecutePermission + | QFileSystemMetaData::OtherExecutePermission + | QFileSystemMetaData::UserExecutePermission; } - data.knownFlagsMask |= QFileSystemMetaData::OwnerPermissions | QFileSystemMetaData::GroupPermissions - | QFileSystemMetaData::OtherPermissions | QFileSystemMetaData::UserExecutePermission; + data.knownFlagsMask |= QFileSystemMetaData::OwnerPermissions + | QFileSystemMetaData::GroupPermissions | QFileSystemMetaData::OtherPermissions + | QFileSystemMetaData::UserExecutePermission; // calculate user permissions if (what & QFileSystemMetaData::UserReadPermission) { if (::_waccess((wchar_t*)entry.nativeFilePath().utf16(), R_OK) == 0) @@ -929,7 +1290,8 @@ static bool tryDriveUNCFallback(const QFileSystemEntry &fname, QFileSystemMetaDa const UINT oldErrorMode = ::SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX); DWORD drivesBitmask = ::GetLogicalDrives(); ::SetErrorMode(oldErrorMode); - int drivebit = 1 << (fname.filePath().at(0).toUpper().unicode() - QLatin1Char('A').unicode()); + int drivebit = + 1 << (fname.filePath().at(0).toUpper().unicode() - u'A'); if (drivesBitmask & drivebit) { fileAttrib = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM; entryExists = true; @@ -937,7 +1299,7 @@ static bool tryDriveUNCFallback(const QFileSystemEntry &fname, QFileSystemMetaDa } else { const QString &path = fname.nativeFilePath(); bool is_dir = false; - if (path.startsWith(QLatin1String("\\\\?\\UNC"))) { + if (path.startsWith("\\\\?\\UNC"_L1)) { // UNC - stat doesn't work for all cases (Windows bug) int s = path.indexOf(path.at(0),7); if (s > 0) { @@ -1013,8 +1375,6 @@ bool QFileSystemEngine::fillMetaData(HANDLE fHandle, QFileSystemMetaData &data, return data.hasFlags(what); } -static bool isDirPath(const QString &dirPath, bool *existed); - //static bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemMetaData &data, QFileSystemMetaData::MetaDataFlags what) @@ -1028,7 +1388,7 @@ bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemM // Check for ".lnk": Directories named ".lnk" should be skipped, corrupted // link files should still be detected as links. const QString origFilePath = entry.filePath(); - if (origFilePath.endsWith(QLatin1String(".lnk")) && !isDirPath(origFilePath, 0)) { + if (origFilePath.endsWith(".lnk"_L1) && !isDirPath(origFilePath, nullptr)) { data.entryFlags |= QFileSystemMetaData::WinLnkType; fname = QFileSystemEntry(readLink(entry)); } else { @@ -1047,14 +1407,15 @@ bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemM WIN32_FIND_DATA findData; // The memory structure for WIN32_FIND_DATA is same as WIN32_FILE_ATTRIBUTE_DATA // for all members used by fillFindData(). - bool ok = ::GetFileAttributesEx(reinterpret_cast<const wchar_t*>(fname.nativeFilePath().utf16()), - GetFileExInfoStandard, - reinterpret_cast<WIN32_FILE_ATTRIBUTE_DATA *>(&findData)); + bool ok = ::GetFileAttributesEx( + reinterpret_cast<const wchar_t *>(fname.nativeFilePath().utf16()), + GetFileExInfoStandard, reinterpret_cast<WIN32_FILE_ATTRIBUTE_DATA *>(&findData)); if (ok) { data.fillFromFindData(findData, false, fname.isDriveRoot()); } else { const DWORD lastError = GetLastError(); - if (lastError == ERROR_LOGON_FAILURE || lastError == ERROR_BAD_NETPATH // disconnected drive + // disconnected drive + if (lastError == ERROR_LOGON_FAILURE || lastError == ERROR_BAD_NETPATH || (!tryFindFallback(fname, data) && !tryDriveUNCFallback(fname, data))) { data.clearFlags(); SetErrorMode(oldmode); @@ -1078,13 +1439,15 @@ bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemM return data.hasFlags(what); } -static inline bool mkDir(const QString &path, DWORD *lastError = 0) +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(), 0); - if (lastError) // Capture lastError before any QString is freed since custom allocators might change it. + 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(); return result; } @@ -1094,11 +1457,12 @@ static inline bool rmDir(const QString &path) return ::RemoveDirectory((wchar_t*)QFSFileEnginePrivate::longFileName(path).utf16()); } -static bool isDirPath(const QString &dirPath, bool *existed) +//static +bool QFileSystemEngine::isDirPath(const QString &dirPath, bool *existed) { QString path = dirPath; - if (path.length() == 2 && path.at(1) == QLatin1Char(':')) - path += QLatin1Char('\\'); + if (path.length() == 2 && path.at(1) == u':') + path += u'\\'; const QString longPath = QFSFileEnginePrivate::longFileName(path); DWORD fileAttrib = ::GetFileAttributes(reinterpret_cast<const wchar_t*>(longPath.utf16())); @@ -1122,24 +1486,27 @@ static bool 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("\\\\")) && nativeName.count(QDir::separator()) <= 3; + return nativeName.startsWith("\\\\"_L1) + && nativeName.count(QDir::separator()) <= 3; }; const auto isDriveName = [](const QString &nativeName) { - return nativeName.size() == 2 && nativeName.at(1) == QLatin1Char(':'); + return nativeName.size() == 2 && nativeName.at(1) == u':'; }; const auto isDir = [](const QString &nativeName) { bool exists = false; - return isDirPath(nativeName, &exists) && exists; + return QFileSystemEngine::isDirPath(nativeName, &exists) && exists; }; // Do not try to mkdir a UNC root path or a drive letter. if (isUNCRoot(nativeName) || isDriveName(nativeName)) return false; if (shouldMkdirFirst) { - if (mkDir(nativeName)) + if (mkDir(nativeName, securityAttributes)) return true; } @@ -1148,34 +1515,41 @@ 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) return false; - if (lastError == ERROR_ALREADY_EXISTS) + if (lastError == ERROR_ALREADY_EXISTS || lastError == ERROR_ACCESS_DENIED) return isDirPath(dirName, nullptr); - return createDirectoryWithParents(dirName, false); + return createDirectoryWithParents(dirName, securityAttributes, false); } //static @@ -1188,10 +1562,12 @@ bool QFileSystemEngine::removeDirectory(const QFileSystemEntry &entry, bool remo dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName)); for (int oldslash = 0, slash=dirName.length(); slash > 0; oldslash = slash) { const auto chunkRef = QStringView{dirName}.left(slash); - if (chunkRef.length() == 2 && chunkRef.at(0).isLetter() && chunkRef.at(1) == QLatin1Char(':')) + if (chunkRef.length() == 2 && chunkRef.at(0).isLetter() + && chunkRef.at(1) == u':') { break; + } const QString chunk = chunkRef.toString(); - if (!isDirPath(chunk, 0)) + if (!isDirPath(chunk, nullptr)) return false; if (!rmDir(chunk)) return oldslash != 0; @@ -1207,8 +1583,8 @@ QString QFileSystemEngine::rootPath() { QString ret = QString::fromLatin1(qgetenv("SystemDrive")); if (ret.isEmpty()) - ret = QLatin1String("c:"); - ret.append(QLatin1Char('/')); + ret = "c:"_L1; + ret.append(u'/'); return ret; } @@ -1220,12 +1596,12 @@ QString QFileSystemEngine::homePath() initGlobalSid(); { HANDLE hnd = ::GetCurrentProcess(); - HANDLE token = 0; + HANDLE token = nullptr; BOOL ok = ::OpenProcessToken(hnd, TOKEN_QUERY, &token); if (ok) { DWORD dwBufferSize = 0; // First call, to determine size of the strings (with '\0'). - ok = GetUserProfileDirectory(token, NULL, &dwBufferSize); + ok = GetUserProfileDirectory(token, nullptr, &dwBufferSize); if (!ok && dwBufferSize != 0) { // We got the required buffer size wchar_t *userDirectory = new wchar_t[dwBufferSize]; // Second call, now we can fill the allocated buffer. @@ -1266,13 +1642,13 @@ QString QFileSystemEngine::tempPath() QString::fromWCharArray(tempPath, len); } if (!ret.isEmpty()) { - while (ret.endsWith(QLatin1Char('\\'))) + while (ret.endsWith(u'\\')) ret.chop(1); ret = QDir::fromNativeSeparators(ret); } if (ret.isEmpty()) { - ret = QLatin1String("C:/tmp"); - } else if (ret.length() >= 2 && ret[1] == QLatin1Char(':')) + ret = "C:/tmp"_L1; + } else if (ret.length() >= 2 && ret[1] == u':') ret[0] = ret.at(0).toUpper(); // Force uppercase drive letters. return ret; } @@ -1280,49 +1656,72 @@ QString QFileSystemEngine::tempPath() bool QFileSystemEngine::setCurrentPath(const QFileSystemEntry &entry) { QFileSystemMetaData meta; - fillMetaData(entry, meta, QFileSystemMetaData::ExistsAttribute | QFileSystemMetaData::DirectoryType); + fillMetaData(entry, meta, + QFileSystemMetaData::ExistsAttribute | QFileSystemMetaData::DirectoryType); if (!(meta.exists() && meta.isDirectory())) return false; - //TODO: this should really be using nativeFilePath(), but that returns a path in long format \\?\c:\foo - //which causes many problems later on when it's returned through currentPath() - return ::SetCurrentDirectory(reinterpret_cast<const wchar_t*>(QDir::toNativeSeparators(entry.filePath()).utf16())) != 0; + // TODO: this should really be using nativeFilePath(), but that returns a path + // in long format \\?\c:\foo which causes many problems later on when it's + // returned through currentPath() + return ::SetCurrentDirectory(reinterpret_cast<const wchar_t *>( + QDir::toNativeSeparators(entry.filePath()).utf16())) + != 0; } QFileSystemEntry QFileSystemEngine::currentPath() { - QString ret; - DWORD size = 0; - wchar_t currentName[PATH_MAX]; - size = ::GetCurrentDirectory(PATH_MAX, currentName); - if (size != 0) { - if (size > PATH_MAX) { - wchar_t *newCurrentName = new wchar_t[size]; - if (::GetCurrentDirectory(PATH_MAX, newCurrentName) != 0) - ret = QString::fromWCharArray(newCurrentName, size); - delete [] newCurrentName; - } else { - ret = QString::fromWCharArray(currentName, size); - } + QString ret(PATH_MAX, Qt::Uninitialized); + DWORD size = GetCurrentDirectoryW(PATH_MAX, reinterpret_cast<wchar_t *>(ret.data())); + if (size > PATH_MAX) { + // try again after enlarging the buffer + ret.resize(size); + size = GetCurrentDirectoryW(size, reinterpret_cast<wchar_t *>(ret.data())); + + // note: the current directory may have changed underneath us; if the + // new one is even bigger, we may return a truncated string! } - if (ret.length() >= 2 && ret[1] == QLatin1Char(':')) + if (size >= 2 && ret.at(1) == u':') ret[0] = ret.at(0).toUpper(); // Force uppercase drive letters. - return QFileSystemEntry(ret, QFileSystemEntry::FromNativePath()); + ret.resize(size); + return QFileSystemEntry(std::move(ret), QFileSystemEntry::FromNativePath()); } //static -bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) +bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, + QSystemError &error) { - Q_ASSERT(false); - Q_UNUSED(source); - Q_UNUSED(target); - Q_UNUSED(error); + bool ret = false; + QComHelper comHelper; + IShellLink *psl = nullptr; + HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, + reinterpret_cast<void **>(&psl)); + + if (SUCCEEDED(hres)) { + const auto name = QDir::toNativeSeparators(source.filePath()); + const auto pathName = QDir::toNativeSeparators(source.path()); + if (SUCCEEDED(psl->SetPath(reinterpret_cast<const wchar_t *>(name.utf16()))) + && SUCCEEDED(psl->SetWorkingDirectory( + reinterpret_cast<const wchar_t *>(pathName.utf16())))) { + IPersistFile *ppf = nullptr; + if (SUCCEEDED(psl->QueryInterface(IID_IPersistFile, reinterpret_cast<void **>(&ppf)))) { + ret = SUCCEEDED(ppf->Save( + reinterpret_cast<const wchar_t *>(target.filePath().utf16()), TRUE)); + ppf->Release(); + } + } + psl->Release(); + } + + if (!ret) + error = QSystemError(::GetLastError(), QSystemError::NativeError); - return false; // TODO implement; - code needs to be moved from qfsfileengine_win.cpp + return ret; } //static -bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) +bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, + QSystemError &error) { bool ret = ::CopyFile((wchar_t*)source.nativeFilePath().utf16(), (wchar_t*)target.nativeFilePath().utf16(), true) != 0; @@ -1332,7 +1731,8 @@ bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSyst } //static -bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) +bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target, + QSystemError &error) { Q_CHECK_FILE_NAME(source, false); Q_CHECK_FILE_NAME(target, false); @@ -1345,7 +1745,8 @@ bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSy } //static -bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) +bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source, + const QFileSystemEntry &target, QSystemError &error) { Q_CHECK_FILE_NAME(source, false); Q_CHECK_FILE_NAME(target, false); @@ -1382,85 +1783,52 @@ bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &source, // we need the "display name" of the file, so can't use nativeAbsoluteFilePath const QString sourcePath = QDir::toNativeSeparators(absoluteName(source).filePath()); - /* - Windows 7 insists on showing confirmation dialogs and ignores the respective - flags set on IFileOperation. Fall back to SHFileOperation, even if it doesn't - give us the new location of the file. - */ - if (QOperatingSystemVersion::current() > QOperatingSystemVersion::Windows7) { -# if defined(__IFileOperation_INTERFACE_DEFINED__) - CoInitialize(NULL); - IFileOperation *pfo = nullptr; - IShellItem *deleteItem = nullptr; - FileOperationProgressSink *sink = nullptr; - HRESULT hres = E_FAIL; - - auto coUninitialize = qScopeGuard([&](){ - if (sink) - sink->Release(); - if (deleteItem) - deleteItem->Release(); - if (pfo) - pfo->Release(); - CoUninitialize(); - if (!SUCCEEDED(hres)) - error = QSystemError(hres, QSystemError::NativeError); - }); - - hres = CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pfo)); - if (!pfo) - return false; - pfo->SetOperationFlags(FOF_ALLOWUNDO | FOFX_RECYCLEONDELETE | FOF_NOCONFIRMATION - | FOF_SILENT | FOF_NOERRORUI); - hres = SHCreateItemFromParsingName(reinterpret_cast<const wchar_t*>(sourcePath.utf16()), - nullptr, IID_PPV_ARGS(&deleteItem)); - if (!deleteItem) - return false; - sink = new FileOperationProgressSink; - hres = pfo->DeleteItem(deleteItem, static_cast<IFileOperationProgressSink*>(sink)); - if (!SUCCEEDED(hres)) - return false; - hres = pfo->PerformOperations(); + QComHelper comHelper; + + IFileOperation *pfo = nullptr; + IShellItem *deleteItem = nullptr; + FileOperationProgressSink *sink = nullptr; + HRESULT hres = E_FAIL; + + auto coUninitialize = qScopeGuard([&](){ + if (sink) + sink->Release(); + if (deleteItem) + deleteItem->Release(); + if (pfo) + pfo->Release(); if (!SUCCEEDED(hres)) - return false; - newLocation = QFileSystemEntry(sink->targetPath); + error = QSystemError(hres, QSystemError::NativeError); + }); -# endif // no IFileOperation in SDK (mingw, likely) - fall back to SHFileOperation - } else { - // double null termination needed, so can't use QString::utf16 - QVarLengthArray<wchar_t, MAX_PATH + 1> winFile(sourcePath.length() + 2); - sourcePath.toWCharArray(winFile.data()); - winFile[sourcePath.length()] = wchar_t{}; - winFile[sourcePath.length() + 1] = wchar_t{}; - - SHFILEOPSTRUCTW operation; - operation.hwnd = nullptr; - operation.wFunc = FO_DELETE; - operation.pFrom = winFile.constData(); - operation.pTo = nullptr; - operation.fFlags = FOF_ALLOWUNDO | FOF_NO_UI; - operation.fAnyOperationsAborted = FALSE; - operation.hNameMappings = nullptr; - operation.lpszProgressTitle = nullptr; - - int result = SHFileOperation(&operation); - if (result != 0) { - error = QSystemError(result, QSystemError::NativeError); - return false; - } - /* - This implementation doesn't let us know where the file ended up, even if - we would specify FOF_WANTMAPPINGHANDLE | FOF_RENAMEONCOLLISION, as - FOF_RENAMEONCOLLISION has no effect unless files are moved, copied, or renamed. - */ - Q_UNUSED(newLocation); + hres = CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pfo)); + if (!pfo) + return false; + pfo->SetOperationFlags(FOF_ALLOWUNDO | FOFX_RECYCLEONDELETE | FOF_NOCONFIRMATION + | FOF_SILENT | FOF_NOERRORUI); + hres = SHCreateItemFromParsingName(reinterpret_cast<const wchar_t*>(sourcePath.utf16()), + nullptr, IID_PPV_ARGS(&deleteItem)); + if (!deleteItem) + return false; + sink = new FileOperationProgressSink; + hres = pfo->DeleteItem(deleteItem, static_cast<IFileOperationProgressSink*>(sink)); + if (!SUCCEEDED(hres)) + return false; + hres = pfo->PerformOperations(); + if (!SUCCEEDED(hres)) + return false; + + if (!SUCCEEDED(sink->deleteResult)) { + error = QSystemError(sink->deleteResult, QSystemError::NativeError); + return false; } + newLocation = QFileSystemEntry(sink->targetPath); return true; - } //static -bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry, QFile::Permissions permissions, QSystemError &error, +bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry, + QFile::Permissions permissions, QSystemError &error, QFileSystemMetaData *data) { Q_CHECK_FILE_NAME(entry, false); @@ -1470,13 +1838,16 @@ bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry, QFile::Per if (permissions & (QFile::ReadOwner | QFile::ReadUser | QFile::ReadGroup | QFile::ReadOther)) mode |= _S_IREAD; - if (permissions & (QFile::WriteOwner | QFile::WriteUser | QFile::WriteGroup | QFile::WriteOther)) + if (permissions + & (QFile::WriteOwner | QFile::WriteUser | QFile::WriteGroup | QFile::WriteOther)) { mode |= _S_IWRITE; + } if (mode == 0) // not supported return false; - bool ret = ::_wchmod(reinterpret_cast<const wchar_t*>(entry.nativeFilePath().utf16()), mode) == 0; + bool ret = + ::_wchmod(reinterpret_cast<const wchar_t *>(entry.nativeFilePath().utf16()), mode) == 0; if (!ret) error = QSystemError(errno, QSystemError::StandardLibraryError); return ret; @@ -1491,7 +1862,7 @@ static inline QDateTime fileTimeToQDateTime(const FILETIME *time) FileTimeToSystemTime(time, &sTime); return QDateTime(QDate(sTime.wYear, sTime.wMonth, sTime.wDay), QTime(sTime.wHour, sTime.wMinute, sTime.wSecond, sTime.wMilliseconds), - Qt::UTC); + QTimeZone::UTC); } QDateTime QFileSystemMetaData::birthTime() const |