Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qfilesystemengine_win.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
7#include "qplatformdefs.h"
8#include "qsysinfo.h"
9#include "qscopeguard.h"
10#include "private/qabstractfileengine_p.h"
11#include "private/qfiledevice_p.h"
12#include "private/qfsfileengine_p.h"
13#include <private/qsystemlibrary_p.h>
14#include <qdebug.h>
15
16#include "qdir.h"
17#include "qdatetime.h"
18#include "qfile.h"
20#include "qt_windows.h"
21#if QT_CONFIG(regularexpression)
22#include "qregularexpression.h"
23#endif
24#include "qstring.h"
25
26#include <sys/types.h>
27#include <direct.h>
28#include <winioctl.h>
29#include <objbase.h>
30#include <shlobj.h>
31#include <shobjidl.h>
32#include <shellapi.h>
33#include <lm.h>
34#include <accctrl.h>
35#include <initguid.h>
36#include <ctype.h>
37#include <limits.h>
38#define SECURITY_WIN32
39#include <security.h>
40
41#include <cstdio>
42
43#include <QtCore/private/qfunctions_win_p.h>
44#include <QtCore/private/wcharhelpers_win_p.h>
45
46#ifndef SPI_GETPLATFORMTYPE
47#define SPI_GETPLATFORMTYPE 257
48#endif
49
50#ifndef PATH_MAX
51#define PATH_MAX FILENAME_MAX
52#endif
53
54#ifndef _INTPTR_T_DEFINED
55#ifdef _WIN64
56typedef __int64 intptr_t;
57#else
58#ifdef _W64
59typedef _W64 int intptr_t;
60#else
61typedef INT_PTR intptr_t;
62#endif
63#endif
64#define _INTPTR_T_DEFINED
65#endif
66
67#ifndef INVALID_FILE_ATTRIBUTES
68# define INVALID_FILE_ATTRIBUTES (DWORD (-1))
69#endif
70
71#if !defined(REPARSE_DATA_BUFFER_HEADER_SIZE)
72typedef struct _REPARSE_DATA_BUFFER {
76 union {
77 struct {
84 } SymbolicLinkReparseBuffer;
85 struct {
86 USHORT SubstituteNameOffset;
87 USHORT SubstituteNameLength;
88 USHORT PrintNameOffset;
89 USHORT PrintNameLength;
90 WCHAR PathBuffer[1];
91 } MountPointReparseBuffer;
92 struct {
94 } GenericReparseBuffer;
95 };
96} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
97# define REPARSE_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer)
98#endif // !defined(REPARSE_DATA_BUFFER_HEADER_SIZE)
99
100#ifndef MAXIMUM_REPARSE_DATA_BUFFER_SIZE
101# define MAXIMUM_REPARSE_DATA_BUFFER_SIZE 16384
102#endif
103#ifndef IO_REPARSE_TAG_SYMLINK
104# define IO_REPARSE_TAG_SYMLINK (0xA000000CL)
105#endif
106#ifndef FSCTL_GET_REPARSE_POINT
107# define FSCTL_GET_REPARSE_POINT
108 CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS)
109#endif
110
111#if QT_CONFIG(fslibs)
112#include <aclapi.h>
113#include <authz.h>
114#include <userenv.h>
115static PSID currentUserSID = nullptr;
116static PSID currentGroupSID = nullptr;
117static PSID worldSID = nullptr;
118static HANDLE currentUserImpersonatedToken = nullptr;
119#endif // fslibs
120
121QT_BEGIN_NAMESPACE
122using namespace Qt::StringLiterals;
123
124#if QT_CONFIG(fslibs)
125namespace {
126struct GlobalSid
127{
128 GlobalSid();
129 ~GlobalSid();
130};
131
132GlobalSid::~GlobalSid()
133{
134 free(currentUserSID);
135 currentUserSID = nullptr;
136
137 free(currentGroupSID);
138 currentGroupSID = nullptr;
139
140 // worldSID was allocated with AllocateAndInitializeSid so it needs to be freed with FreeSid
141 if (worldSID) {
142 ::FreeSid(worldSID);
143 worldSID = nullptr;
144 }
145
146 if (currentUserImpersonatedToken) {
147 ::CloseHandle(currentUserImpersonatedToken);
148 currentUserImpersonatedToken = nullptr;
149 }
150}
151
152/*
153 Helper for GetTokenInformation that allocates chunk of memory to hold the requested information.
154
155 The memory size is determined by doing a dummy call first. The returned memory should be
156 freed by calling free().
157*/
158template<typename T>
159static T *getTokenInfo(HANDLE token, TOKEN_INFORMATION_CLASS infoClass)
160{
161 DWORD retsize = 0;
162 GetTokenInformation(token, infoClass, nullptr, 0, &retsize);
163 if (retsize) {
164 void *tokenBuffer = malloc(retsize);
165 if (::GetTokenInformation(token, infoClass, tokenBuffer, retsize, &retsize))
166 return reinterpret_cast<T *>(tokenBuffer);
167 else
168 free(tokenBuffer);
169 }
170 return nullptr;
171}
172
173/*
174 Takes a copy of the original SID and stores it into dstSid.
175 The copy can be destroyed using free().
176*/
177static void copySID(PSID &dstSid, PSID srcSid)
178{
179 DWORD sidLen = GetLengthSid(srcSid);
180 dstSid = reinterpret_cast<PSID>(malloc(sidLen));
181 Q_CHECK_PTR(dstSid);
182 CopySid(sidLen, dstSid, srcSid);
183}
184
185GlobalSid::GlobalSid()
186{
187 HANDLE hnd = ::GetCurrentProcess();
188 HANDLE token = nullptr;
189 if (::OpenProcessToken(hnd, TOKEN_QUERY, &token)) {
190 // Create SID for current user
191 if (auto info = getTokenInfo<TOKEN_USER>(token, TokenUser)) {
192 copySID(currentUserSID, info->User.Sid);
193 free(info);
194 }
195
196 // Create SID for the current user's primary group.
197 if (auto info = getTokenInfo<TOKEN_GROUPS>(token, TokenGroups)) {
198 copySID(currentGroupSID, info->Groups[0].Sid);
199 free(info);
200 }
201 ::CloseHandle(token);
202 }
203
204 token = nullptr;
205 if (::OpenProcessToken(hnd,
206 TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ,
207 &token)) {
208 ::DuplicateToken(token, SecurityImpersonation, &currentUserImpersonatedToken);
209 ::CloseHandle(token);
210 }
211
212 // Create SID for Everyone (World)
213 SID_IDENTIFIER_AUTHORITY worldAuth = { SECURITY_WORLD_SID_AUTHORITY };
214 AllocateAndInitializeSid(&worldAuth, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &worldSID);
215}
216
217Q_GLOBAL_STATIC(GlobalSid, initGlobalSid)
218
219/*!
220 \class QAuthzResourceManager
221 \internal
222
223 RAII wrapper around Windows Authz resource manager.
224*/
225class QAuthzResourceManager
226{
227public:
228 QAuthzResourceManager();
229 ~QAuthzResourceManager();
230
231 bool isValid() const { return resourceManager != nullptr; }
232
233private:
234 friend class QAuthzClientContext;
235 Q_DISABLE_COPY_MOVE(QAuthzResourceManager)
236
237 AUTHZ_RESOURCE_MANAGER_HANDLE resourceManager;
238};
239
240/*!
241 \class QAuthzClientContext
242 \internal
243
244 RAII wrapper around Windows Authz client context.
245*/
246class QAuthzClientContext
247{
248public:
249 // Tag to differentiate SID and TOKEN constructors. Those two types are pointers to void.
250 struct TokenTag
251 {
252 };
253
254 QAuthzClientContext(const QAuthzResourceManager &rm, PSID pSID);
255 QAuthzClientContext(const QAuthzResourceManager &rm, HANDLE tokenHandle, TokenTag);
256
257 ~QAuthzClientContext();
258
259 bool isValid() const { return context != nullptr; }
260
261 static constexpr ACCESS_MASK InvalidAccess = ~ACCESS_MASK(0);
262
263 ACCESS_MASK accessMask(PSECURITY_DESCRIPTOR pSD) const;
264
265private:
266 Q_DISABLE_COPY_MOVE(QAuthzClientContext)
267 AUTHZ_CLIENT_CONTEXT_HANDLE context = nullptr;
268};
269
270QAuthzResourceManager::QAuthzResourceManager()
271{
272 if (!AuthzInitializeResourceManager(AUTHZ_RM_FLAG_NO_AUDIT, nullptr, nullptr, nullptr, nullptr,
273 &resourceManager)) {
274 resourceManager = nullptr;
275 }
276}
277
278QAuthzResourceManager::~QAuthzResourceManager()
279{
280 if (resourceManager)
281 AuthzFreeResourceManager(resourceManager);
282}
283
284/*!
285 \internal
286
287 Create an Authz client context from a security identifier.
288
289 The created context will not include any group information associated with \a pSID.
290*/
291QAuthzClientContext::QAuthzClientContext(const QAuthzResourceManager &rm, PSID pSID)
292{
293 if (!rm.isValid())
294 return;
295
296 LUID unusedId = {};
297
298 if (!AuthzInitializeContextFromSid(AUTHZ_SKIP_TOKEN_GROUPS, pSID, rm.resourceManager, nullptr,
299 unusedId, nullptr, &context)) {
300 context = nullptr;
301 }
302}
303
304/*!
305 \internal
306
307 Create an Authz client context from a token handle.
308*/
309QAuthzClientContext::QAuthzClientContext(const QAuthzResourceManager &rm, HANDLE tokenHandle,
310 TokenTag)
311{
312 if (!rm.isValid())
313 return;
314
315 LUID unusedId = {};
316
317 if (!AuthzInitializeContextFromToken(0, tokenHandle, rm.resourceManager, nullptr, unusedId,
318 nullptr, &context)) {
319 context = nullptr;
320 }
321}
322
323QAuthzClientContext::~QAuthzClientContext()
324{
325 if (context)
326 AuthzFreeContext(context);
327}
328
329/*!
330 \internal
331
332 Returns permissions that are granted to this client by \a pSD.
333
334 Returns \c InvalidAccess in case of an error.
335*/
336ACCESS_MASK QAuthzClientContext::accessMask(PSECURITY_DESCRIPTOR pSD) const
337{
338 if (!isValid())
339 return InvalidAccess;
340
341 AUTHZ_ACCESS_REQUEST accessRequest = {};
342 AUTHZ_ACCESS_REPLY accessReply = {};
343 ACCESS_MASK accessMask = 0;
344 DWORD error = 0;
345
346 accessRequest.DesiredAccess = MAXIMUM_ALLOWED;
347
348 accessReply.ResultListLength = 1;
349 accessReply.GrantedAccessMask = &accessMask;
350 accessReply.Error = &error;
351
352 if (!AuthzAccessCheck(0, context, &accessRequest, nullptr, pSD, nullptr, 0, &accessReply,
353 nullptr)
354 || error != 0) {
355 return InvalidAccess;
356 }
357
358 return accessMask;
359}
360
361enum NonSpecificPermission {
362 ReadPermission = 0x4,
363 WritePermission = 0x2,
364 ExePermission = 0x1,
365 AllPermissions = ReadPermission | WritePermission | ExePermission
366};
367Q_DECLARE_FLAGS(NonSpecificPermissions, NonSpecificPermission)
368Q_DECLARE_OPERATORS_FOR_FLAGS(NonSpecificPermissions)
369
370enum PermissionTag { OtherTag = 0, GroupTag = 4, UserTag = 8, OwnerTag = 12 };
371
372constexpr NonSpecificPermissions toNonSpecificPermissions(PermissionTag tag,
373 QFileDevice::Permissions permissions)
374{
375 return NonSpecificPermissions::fromInt((permissions.toInt() >> int(tag)) & 0x7);
376}
377
378[[maybe_unused]] // Not currently used; included to show how to do it (without bit-rotting).
379constexpr QFileDevice::Permissions toSpecificPermissions(PermissionTag tag,
380 NonSpecificPermissions permissions)
381{
382 return QFileDevice::Permissions::fromInt(permissions.toInt() << int(tag));
383}
384
385} // anonymous namespace
386#endif // QT_CONFIG(fslibs)
387
388#if QT_DEPRECATED_SINCE(6,6)
389int qt_ntfs_permission_lookup = 0;
390#endif
391
392static QBasicAtomicInt qt_ntfs_permission_lookup_v2 = Q_BASIC_ATOMIC_INITIALIZER(0);
393
394QT_WARNING_PUSH
395QT_WARNING_DISABLE_DEPRECATED
396
398{
399 return qt_ntfs_permission_lookup_v2.fetchAndAddRelaxed(1)
400QT_IF_DEPRECATED_SINCE(6, 6, /*nothing*/, + qt_ntfs_permission_lookup)
401 != 0;
402}
403
405{
406 return qt_ntfs_permission_lookup_v2.fetchAndSubRelaxed(1)
407QT_IF_DEPRECATED_SINCE(6, 6, /*nothing*/, + qt_ntfs_permission_lookup)
408 == 1;
409}
410
412{
413 return qt_ntfs_permission_lookup_v2.loadRelaxed()
414QT_IF_DEPRECATED_SINCE(6, 6, /*nothing*/, + qt_ntfs_permission_lookup)
415 ;
416}
417QT_WARNING_POP
418
419/*!
420 \class QNativeFilePermissions
421 \internal
422
423 This class can be used to produce a security descriptor that contains ACL that produces
424 result similar to what is expected for POSIX permission corresponding to the supplied
425 \c QFileDevice::Permissions value. When supplied optional value is empty, a null
426 security descriptor is produced. Files or directories with such null security descriptor
427 will inherit ACLs from parent directories. Otherwise an ACL is generated and applied to
428 the security descriptor. The created ACL has permission bits set similar to what Cygwin
429 does. Unlike Cygwin, this code tries to reorder the access control entries (ACE) inside
430 the ACL to match the canonical ordering (deny ACEs followed by allow ACEs) if possible.
431
432 The default ordering of ACEs is as follows:
433
434 * User deny ACE, only lists permission that may be granted by the subsequent Group and
435 Other allow ACEs.
436 * User allow ACE.
437 * Group deny ACE, only lists permissions that may be granted by the subsequent Other
438 allow ACE.
439 * Group allow ACE.
440 * Other allow ACE.
441
442 Any ACEs that would have zero mask are skipped. Group deny ACE may be moved to before
443 User allow ACE if these 2 ACEs don't have any common mask bits set. This allows use of
444 canonical ordering in more cases. ACLs for permissions with group having less permissions
445 than both user and others (ex.: 0757) are still in noncanonical order. Files with
446 noncanonical ACLs generate warnings when one tries to edit permissions with Windows GUI,
447 and don't work correctly with API like GetEffectiveRightsFromAcl(), but otherwise access
448 checks work fine and such ACLs can still be edited with the "Advanced" GUI.
449*/
450QNativeFilePermissions::QNativeFilePermissions(std::optional<QFileDevice::Permissions> perms,
451 bool isDir)
452{
453#if QT_CONFIG(fslibs)
454 if (!perms) {
455 ok = true;
456 return;
457 }
458
459 initGlobalSid();
460
461 const auto permissions = *perms;
462
463 PACL acl = reinterpret_cast<PACL>(aclStorage);
464
465 if (!InitializeAcl(acl, sizeof(aclStorage), ACL_REVISION))
466 return;
467
468 struct Masks
469 {
470 ACCESS_MASK denyMask, allowMask;
471 };
472
473 auto makeMasks = [isDir](NonSpecificPermissions allowPermissions,
474 NonSpecificPermissions denyPermissions, bool owner) {
475 constexpr ACCESS_MASK AllowRead = FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA;
476 constexpr ACCESS_MASK DenyRead = FILE_READ_DATA | FILE_READ_EA;
477
478 constexpr ACCESS_MASK AllowWrite =
479 FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA;
480 constexpr ACCESS_MASK DenyWrite = AllowWrite | FILE_DELETE_CHILD;
481 constexpr ACCESS_MASK DenyWriteOwner =
482 FILE_WRITE_DATA | FILE_WRITE_EA | FILE_APPEND_DATA | FILE_DELETE_CHILD;
483
484 constexpr ACCESS_MASK AllowExe = FILE_EXECUTE;
485 constexpr ACCESS_MASK DenyExe = AllowExe;
486
487 constexpr ACCESS_MASK StdRightsOther =
488 STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES | SYNCHRONIZE;
489 constexpr ACCESS_MASK StdRightsOwner =
490 STANDARD_RIGHTS_ALL | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE;
491
492 ACCESS_MASK allow = owner ? StdRightsOwner : StdRightsOther;
493 ACCESS_MASK deny = 0;
494
495 if (denyPermissions & ReadPermission)
496 deny |= DenyRead;
497
498 if (denyPermissions & WritePermission)
499 deny |= owner ? DenyWriteOwner : DenyWrite;
500
501 if (denyPermissions & ExePermission)
502 deny |= DenyExe;
503
504 if (allowPermissions & ReadPermission)
505 allow |= AllowRead;
506
507 if (allowPermissions & WritePermission)
508 allow |= AllowWrite;
509
510 if (allowPermissions & ExePermission)
511 allow |= AllowExe;
512
513 // Give the owner "full access" if all the permissions are allowed
514 if (owner && allowPermissions == AllPermissions)
515 allow |= FILE_DELETE_CHILD;
516
517 if (isDir
518 && (allowPermissions & (WritePermission | ExePermission))
519 == (WritePermission | ExePermission)) {
520 allow |= FILE_DELETE_CHILD;
521 }
522
523 return Masks { deny, allow };
524 };
525
526 auto userPermissions = toNonSpecificPermissions(OwnerTag, permissions)
527 | toNonSpecificPermissions(UserTag, permissions);
528 auto groupPermissions = toNonSpecificPermissions(GroupTag, permissions);
529 auto otherPermissions = toNonSpecificPermissions(OtherTag, permissions);
530
531 auto userMasks = makeMasks(userPermissions,
532 ~userPermissions & (groupPermissions | otherPermissions), true);
533 auto groupMasks = makeMasks(groupPermissions, ~groupPermissions & otherPermissions, false);
534 auto otherMasks = makeMasks(otherPermissions, {}, false);
535
536 const DWORD aceFlags = isDir ? OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE : 0;
537 const bool reorderGroupDeny = (groupMasks.denyMask & userMasks.allowMask) == 0;
538
539 const auto addDenyAce = [acl, aceFlags](const Masks &masks, PSID pSID) {
540 if (masks.denyMask)
541 return AddAccessDeniedAceEx(acl, ACL_REVISION, aceFlags, masks.denyMask, pSID);
542 return TRUE;
543 };
544
545 const auto addAllowAce = [acl, aceFlags](const Masks &masks, PSID pSID) {
546 if (masks.allowMask)
547 return AddAccessAllowedAceEx(acl, ACL_REVISION, aceFlags, masks.allowMask, pSID);
548 return TRUE;
549 };
550
551 if (!addDenyAce(userMasks, currentUserSID))
552 return;
553
554 if (reorderGroupDeny) {
555 if (!addDenyAce(groupMasks, currentGroupSID))
556 return;
557 }
558
559 if (!addAllowAce(userMasks, currentUserSID))
560 return;
561
562 if (!reorderGroupDeny) {
563 if (!addDenyAce(groupMasks, currentGroupSID))
564 return;
565 }
566
567 if (!addAllowAce(groupMasks, currentGroupSID))
568 return;
569
570 if (!addAllowAce(otherMasks, worldSID))
571 return;
572
573 if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))
574 return;
575
576 if (!SetSecurityDescriptorOwner(&sd, currentUserSID, FALSE))
577 return;
578
579 if (!SetSecurityDescriptorGroup(&sd, currentGroupSID, FALSE))
580 return;
581
582 if (!SetSecurityDescriptorDacl(&sd, TRUE, acl, FALSE))
583 return;
584
585 sa.nLength = sizeof(sa);
586 sa.lpSecurityDescriptor = &sd;
587 sa.bInheritHandle = FALSE;
588
589 isNull = false;
590#else
591 Q_UNUSED(perms);
592 Q_UNUSED(isDir);
593#endif // QT_CONFIG(fslibs)
594 ok = true;
595}
596
597/*!
598 \internal
599 Return pointer to a \c SECURITY_ATTRIBUTES object describing the permissions.
600
601 The returned pointer many be null if default permissions were requested or
602 during bootstrap. The callers must call \c isOk() to check if the object
603 was successfully constructed before using this method.
604*/
605SECURITY_ATTRIBUTES *QNativeFilePermissions::securityAttributes()
606{
607 Q_ASSERT(ok);
608 return isNull ? nullptr : &sa;
609}
610
611static inline bool toFileTime(const QDateTime &date, FILETIME *fileTime)
612{
613 SYSTEMTIME sTime;
614 if (date.timeSpec() == Qt::LocalTime) {
615 SYSTEMTIME lTime;
616 const QDate d = date.date();
617 const QTime t = date.time();
618
619 lTime.wYear = d.year();
620 lTime.wMonth = d.month();
621 lTime.wDay = d.day();
622 lTime.wHour = t.hour();
623 lTime.wMinute = t.minute();
624 lTime.wSecond = t.second();
625 lTime.wMilliseconds = t.msec();
626 lTime.wDayOfWeek = d.dayOfWeek() % 7;
627
628 if (!::TzSpecificLocalTimeToSystemTime(nullptr, &lTime, &sTime))
629 return false;
630 } else {
631 QDateTime utcDate = date.toUTC();
632 const QDate d = utcDate.date();
633 const QTime t = utcDate.time();
634
635 sTime.wYear = d.year();
636 sTime.wMonth = d.month();
637 sTime.wDay = d.day();
638 sTime.wHour = t.hour();
639 sTime.wMinute = t.minute();
640 sTime.wSecond = t.second();
641 sTime.wMilliseconds = t.msec();
642 sTime.wDayOfWeek = d.dayOfWeek() % 7;
643 }
644
645 return ::SystemTimeToFileTime(&sTime, fileTime);
646}
647
648static QString readSymLink(const QFileSystemEntry &link)
649{
650 QString result;
651 HANDLE handle = CreateFile((wchar_t *)link.nativeFilePath().utf16(), FILE_READ_EA,
652 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
653 OPEN_EXISTING,
654 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
655 if (handle != INVALID_HANDLE_VALUE) {
656 DWORD bufsize = MAXIMUM_REPARSE_DATA_BUFFER_SIZE;
657 REPARSE_DATA_BUFFER *rdb = (REPARSE_DATA_BUFFER*)malloc(bufsize);
658 Q_CHECK_PTR(rdb);
659 DWORD retsize = 0;
660 if (::DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, nullptr, 0, rdb, bufsize, &retsize,
661 nullptr)) {
662 if (rdb->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
663 int length = rdb->MountPointReparseBuffer.SubstituteNameLength / sizeof(wchar_t);
664 int offset = rdb->MountPointReparseBuffer.SubstituteNameOffset / sizeof(wchar_t);
665 const wchar_t* PathBuffer = &rdb->MountPointReparseBuffer.PathBuffer[offset];
666 result = QString::fromWCharArray(PathBuffer, length);
667 } else if (rdb->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
668 int length = rdb->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(wchar_t);
669 int offset = rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(wchar_t);
670 const wchar_t* PathBuffer = &rdb->SymbolicLinkReparseBuffer.PathBuffer[offset];
671 result = QString::fromWCharArray(PathBuffer, length);
672 }
673 // remove "\\?\", "\??\" or "\\?\UNC\"
674 result = QFileSystemEntry::removeUncOrLongPathPrefix(result);
675 }
676 free(rdb);
677 CloseHandle(handle);
678
679#if QT_CONFIG(fslibs) && QT_CONFIG(regularexpression)
680 initGlobalSid();
681 QRegularExpression matchVolumeRe("^Volume\\{([a-z]|[0-9]|-)+\\}\\\\"_L1,
682 QRegularExpression::CaseInsensitiveOption);
683 auto matchVolume = matchVolumeRe.match(result);
684 if (matchVolume.hasMatch()) {
685 Q_ASSERT(matchVolume.capturedStart() == 0);
686 DWORD len;
687 wchar_t buffer[MAX_PATH];
688 const QString volumeName = "\\\\?\\"_L1 + matchVolume.captured();
689 if (GetVolumePathNamesForVolumeName(qt_castToWchar(volumeName), buffer, MAX_PATH, &len)
690 != 0) {
691 result.replace(0, matchVolume.capturedLength(), QString::fromWCharArray(buffer));
692 }
693 }
694#endif // QT_CONFIG(fslibs)
695 }
696 return result;
697}
698
699static QString readLink(const QFileSystemEntry &link)
700{
701#if QT_CONFIG(fslibs)
702 QString ret;
703
704 IShellLink *psl; // pointer to IShellLink i/f
705 WIN32_FIND_DATA wfd;
706 wchar_t szGotPath[MAX_PATH];
707
708 QComHelper comHelper;
709
710 // Get pointer to the IShellLink interface.
711 HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink,
712 (LPVOID *)&psl);
713
714 if (SUCCEEDED(hres)) { // Get pointer to the IPersistFile interface.
715 IPersistFile *ppf;
716 hres = psl->QueryInterface(IID_IPersistFile, (LPVOID *)&ppf);
717 if (SUCCEEDED(hres)) {
718 hres = ppf->Load((LPOLESTR)link.nativeFilePath().utf16(), STGM_READ);
719 //The original path of the link is retrieved. If the file/folder
720 //was moved, the return value still have the old path.
721 if (SUCCEEDED(hres)) {
722 if (psl->GetPath(szGotPath, MAX_PATH, &wfd, SLGP_UNCPRIORITY) == NOERROR)
723 ret = QString::fromWCharArray(szGotPath);
724 }
725 ppf->Release();
726 }
727 psl->Release();
728 }
729
730 return ret;
731#else
732 Q_UNUSED(link);
733 return QString();
734#endif // QT_CONFIG(fslibs)
735}
736
737static bool uncShareExists(const QString &server)
738{
739 // This code assumes the UNC path is always like \\?\UNC\server...
740 const auto parts = QStringView{server}.split(u'\\', Qt::SkipEmptyParts);
741 if (parts.count() >= 3) {
742 QStringList shares;
743 if (QFileSystemEngine::uncListSharesOnServer("\\\\"_L1 + parts.at(2), &shares))
744 return parts.count() < 4
745 || shares.contains(parts.at(3).toString(), Qt::CaseInsensitive);
746 }
747 return false;
748}
749
750static inline bool getFindData(QString path, WIN32_FIND_DATA &findData)
751{
752 // path should not end with a trailing slash
753 while (path.endsWith(u'\\'))
754 path.chop(1);
755
756 // can't handle drives
757 if (!path.endsWith(u':')) {
758 HANDLE hFind = ::FindFirstFile((wchar_t*)path.utf16(), &findData);
759 if (hFind != INVALID_HANDLE_VALUE) {
760 ::FindClose(hFind);
761 return true;
762 }
763 }
764
765 return false;
766}
767
769{
770public:
775
776 ULONG STDMETHODCALLTYPE AddRef() override { return ++ref; }
778 {
779 if (--ref == 0) {
780 delete this;
781 return 0;
782 }
783 return ref;
784 }
786 {
787 if (!ppvObject)
788 return E_POINTER;
789
790 *ppvObject = nullptr;
791
792 if (iid == __uuidof(IUnknown)) {
793 *ppvObject = static_cast<IUnknown*>(this);
794 } else if (iid == __uuidof(IFileOperationProgressSink)) {
795 *ppvObject = static_cast<IFileOperationProgressSink*>(this);
796 }
797
798 if (*ppvObject) {
799 AddRef();
800 return S_OK;
801 }
802
803 return E_NOINTERFACE;
804 }
805
823 {
824 // stop the operation if the file will be deleted rather than trashed
826 }
830 {
832 if (psiNewlyCreated) {
833 wchar_t *pszName = nullptr;
835 if (pszName) {
838 }
839 }
840 return S_OK;
841 }
850
853private:
854 ULONG ref;
855};
856
857bool QFileSystemEngine::uncListSharesOnServer(const QString &server, QStringList *list)
858{
859 DWORD res = ERROR_NOT_SUPPORTED;
860 SHARE_INFO_1 *BufPtr, *p;
861 DWORD er = 0, tr = 0, resume = 0, i;
862 do {
863 res = NetShareEnum((wchar_t *)server.utf16(), 1, (LPBYTE *)&BufPtr, DWORD(-1), &er, &tr,
864 &resume);
865 if (res == ERROR_SUCCESS || res == ERROR_MORE_DATA) {
866 p = BufPtr;
867 for (i = 1; i <= er; ++i) {
868 if (list && p->shi1_type == 0)
869 list->append(QString::fromWCharArray(p->shi1_netname));
870 p++;
871 }
872 }
873 NetApiBufferFree(BufPtr);
874 } while (res == ERROR_MORE_DATA);
875 return res == ERROR_SUCCESS;
876}
877
878void QFileSystemEngine::clearWinStatData(QFileSystemMetaData &data)
879{
880 data.size_ = 0;
881 data.fileAttribute_ = 0;
882 data.birthTime_ = FILETIME();
883 data.changeTime_ = FILETIME();
884 data.lastAccessTime_ = FILETIME();
885 data.lastWriteTime_ = FILETIME();
886}
887
888//static
889QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link,
890 QFileSystemMetaData &data)
891{
892 QFileSystemEntry ret = getRawLinkPath(link, data);
893 if (!ret.isEmpty() && ret.isRelative()) {
894 QString target = absoluteName(link).path() + u'/' + ret.filePath();
895 ret = QFileSystemEntry(QDir::cleanPath(target));
896 }
897 return ret;
898}
899
900//static
901QFileSystemEntry QFileSystemEngine::getRawLinkPath(const QFileSystemEntry &link,
902 QFileSystemMetaData &data)
903{
904 Q_CHECK_FILE_NAME(link, link);
905
906 if (data.missingFlags(QFileSystemMetaData::LinkType))
907 QFileSystemEngine::fillMetaData(link, data, QFileSystemMetaData::LinkType);
908
909 QString target;
910 if (data.isLnkFile())
911 target = readLink(link);
912 else if (data.isLink())
913 target = readSymLink(link);
914 return QFileSystemEntry(target);
915}
916
917//static
918QFileSystemEntry QFileSystemEngine::junctionTarget(const QFileSystemEntry &link,
919 QFileSystemMetaData &data)
920{
921 Q_CHECK_FILE_NAME(link, link);
922
923 if (data.missingFlags(QFileSystemMetaData::JunctionType))
924 QFileSystemEngine::fillMetaData(link, data, QFileSystemMetaData::LinkType);
925
926 QString target;
927 if (data.isJunction())
928 target = readSymLink(link);
929 QFileSystemEntry ret(target);
930 if (!target.isEmpty() && ret.isRelative()) {
931 target.prepend(absoluteName(link).path() + u'/');
932 ret = QFileSystemEntry(QDir::cleanPath(target));
933 }
934 return ret;
935}
936
937//static
938QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry,
939 QFileSystemMetaData &data)
940{
941 Q_CHECK_FILE_NAME(entry, entry);
942
943 if (data.missingFlags(QFileSystemMetaData::ExistsAttribute))
944 QFileSystemEngine::fillMetaData(entry, data, QFileSystemMetaData::ExistsAttribute);
945
946 if (data.exists())
947 return QFileSystemEntry(slowCanonicalized(absoluteName(entry).filePath()));
948 else
949 return QFileSystemEntry();
950}
951
952//static
953QString QFileSystemEngine::nativeAbsoluteFilePath(const QString &path)
954{
955 Q_CHECK_FILE_NAME(path, QString());
956
957 // can be //server or //server/share
958 QString absPath;
959 QVarLengthArray<wchar_t, MAX_PATH> buf(qMax(MAX_PATH, path.size() + 1));
960 wchar_t *fileName = nullptr;
961 DWORD retLen = GetFullPathName((wchar_t*)path.utf16(), buf.size(), buf.data(), &fileName);
962 if (retLen > (DWORD)buf.size()) {
963 buf.resize(retLen);
964 retLen = GetFullPathName((wchar_t*)path.utf16(), buf.size(), buf.data(), &fileName);
965 }
966 if (retLen != 0)
967 absPath = QString::fromWCharArray(buf.data(), retLen);
968
969 // This is really ugly, but GetFullPathName strips off whitespace at the end.
970 // If you for instance write ". " in the lineedit of QFileDialog,
971 // (which is an invalid filename) this function will strip the space off and viola,
972 // the file is later reported as existing. Therefore, we re-add the whitespace that
973 // was at the end of path in order to keep the filename invalid.
974 if (!path.isEmpty() && path.at(path.size() - 1) == u' ')
975 absPath.append(u' ');
976 return absPath;
977}
978
979//static
980QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry)
981{
982 Q_CHECK_FILE_NAME(entry, entry);
983
984 QString ret;
985
986 if (!entry.isRelative()) {
987 if (entry.isAbsolute() && entry.isClean())
988 ret = entry.filePath();
989 else
990 ret = QDir::fromNativeSeparators(nativeAbsoluteFilePath(entry.filePath()));
991 } else {
992 ret = QDir::cleanPath(QDir::currentPath() + u'/' + entry.filePath());
993 }
994
995 // The path should be absolute at this point.
996 // From the docs :
997 // Absolute paths begin with the directory separator "/"
998 // (optionally preceded by a drive specification under Windows).
999 if (ret.at(0) != u'/') {
1000 Q_ASSERT(ret.length() >= 2);
1001 Q_ASSERT(ret.at(0).isLetter());
1002 Q_ASSERT(ret.at(1) == u':');
1003
1004 // Force uppercase drive letters.
1005 ret[0] = ret.at(0).toUpper();
1006 }
1007 return QFileSystemEntry(ret, QFileSystemEntry::FromInternalPath());
1008}
1009
1010// File ID for Windows up to version 7 and FAT32 drives
1011static inline QFileSystemNativeId fileId(HANDLE handle)
1012{
1013 BY_HANDLE_FILE_INFORMATION info;
1014 if (GetFileInformationByHandle(handle, &info)) {
1016 id.volumeId = info.dwVolumeSerialNumber;
1017 id.fileId[0] = (quint64(info.nFileIndexHigh) << 32) | info.nFileIndexLow;
1018 return id;
1019 }
1020 return {};
1021}
1022
1023// File ID for Windows starting from version 8.
1024static QFileSystemNativeId fileIdWin8(HANDLE handle)
1025{
1026#if !defined(QT_BOOTSTRAPPED)
1027 FILE_ID_INFO infoEx;
1028 if (GetFileInformationByHandleEx(
1029 handle,
1030 static_cast<FILE_INFO_BY_HANDLE_CLASS>(18), // FileIdInfo in Windows 8
1031 &infoEx, sizeof(FILE_ID_INFO))) {
1033 id.volumeId = infoEx.VolumeSerialNumber;
1034 // Note: MinGW-64's definition of FILE_ID_128 differs from the MSVC one.
1035 static_assert(sizeof(infoEx.FileId) == sizeof(id.fileId));
1036 memcpy(id.fileId.data(), &infoEx.FileId, sizeof(infoEx.FileId));
1037 return id;
1038 }
1039 // GetFileInformationByHandleEx() is observed to fail for FAT32, QTBUG-74759
1040 return fileId(handle);
1041#else // !QT_BOOTSTRAPPED
1042 return fileId(handle);
1043#endif
1044}
1045
1046QByteArray QFileSystemNativeId::toByteArray() const
1047{
1048 if (!isValid())
1049 return {};
1050 char buffer[sizeof "0123456789abcdef:0123456789abcdef0123456789abcdef"];
1051 std::snprintf(buffer, sizeof(buffer), "%llx:%016llx%016llx",
1052 qulonglong(volumeId), qulonglong(fileId[1]), qulonglong(fileId[0]));
1053 return buffer;
1054}
1055
1056//static
1057QFileSystemNativeId QFileSystemEngine::nativeId(const QFileSystemEntry &entry)
1058{
1059 Q_CHECK_FILE_NAME(entry, QFileSystemNativeId());
1060
1061 QFileSystemNativeId result;
1062 const HANDLE handle = CreateFile((wchar_t *)entry.nativeFilePath().utf16(), 0, FILE_SHARE_READ,
1063 nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
1064 if (handle != INVALID_HANDLE_VALUE) {
1065 result = nativeId(handle);
1066 CloseHandle(handle);
1067 }
1068 return result;
1069}
1070
1071//static
1072QFileSystemNativeId QFileSystemEngine::nativeId(HANDLE fHandle)
1073{
1074 return fileIdWin8(HANDLE(fHandle));
1075}
1076
1077//static
1078bool QFileSystemEngine::setFileTime(HANDLE fHandle, const QDateTime &newDate,
1079 QFile::FileTime time, QSystemError &error)
1080{
1081 FILETIME fTime;
1082 FILETIME *pLastWrite = nullptr;
1083 FILETIME *pLastAccess = nullptr;
1084 FILETIME *pCreationTime = nullptr;
1085
1086 switch (time) {
1087 case QFile::FileModificationTime:
1088 pLastWrite = &fTime;
1089 break;
1090
1091 case QFile::FileAccessTime:
1092 pLastAccess = &fTime;
1093 break;
1094
1095 case QFile::FileBirthTime:
1096 pCreationTime = &fTime;
1097 break;
1098
1099 default:
1100 error = QSystemError(ERROR_INVALID_PARAMETER, QSystemError::NativeError);
1101 return false;
1102 }
1103
1104 if (!toFileTime(newDate, &fTime))
1105 return false;
1106
1107 if (!::SetFileTime(fHandle, pCreationTime, pLastAccess, pLastWrite)) {
1108 error = QSystemError(::GetLastError(), QSystemError::NativeError);
1109 return false;
1110 }
1111 return true;
1112}
1113
1114QString QFileSystemEngine::owner(const QFileSystemEntry &entry, QAbstractFileEngine::FileOwner own)
1115{
1116 QString name;
1117#if QT_CONFIG(fslibs)
1118 if (qAreNtfsPermissionChecksEnabled()) {
1119 initGlobalSid();
1120 {
1121 PSID pOwner = 0;
1122 PSECURITY_DESCRIPTOR pSD;
1123 if (GetNamedSecurityInfo(
1124 reinterpret_cast<const wchar_t *>(entry.nativeFilePath().utf16()),
1125 SE_FILE_OBJECT,
1126 own == QAbstractFileEngine::OwnerGroup ? GROUP_SECURITY_INFORMATION
1127 : OWNER_SECURITY_INFORMATION,
1128 own == QAbstractFileEngine::OwnerUser ? &pOwner : nullptr,
1129 own == QAbstractFileEngine::OwnerGroup ? &pOwner : nullptr, nullptr,
1130 nullptr, &pSD)
1131 == ERROR_SUCCESS) {
1132 DWORD lowner = 64;
1133 DWORD ldomain = 64;
1134 QVarLengthArray<wchar_t, 64> owner(lowner);
1135 QVarLengthArray<wchar_t, 64> domain(ldomain);
1136 SID_NAME_USE use = SidTypeUnknown;
1137 // First call, to determine size of the strings (with '\0').
1138 if (!LookupAccountSid(nullptr, pOwner, (LPWSTR)owner.data(), &lowner, domain.data(),
1139 &ldomain, &use)) {
1140 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
1141 if (lowner > (DWORD)owner.size())
1142 owner.resize(lowner);
1143 if (ldomain > (DWORD)domain.size())
1144 domain.resize(ldomain);
1145 // Second call, try on resized buf-s
1146 if (!LookupAccountSid(nullptr, pOwner, owner.data(), &lowner, domain.data(),
1147 &ldomain, &use)) {
1148 lowner = 0;
1149 }
1150 } else {
1151 lowner = 0;
1152 }
1153 }
1154 if (lowner != 0)
1155 name = QString::fromWCharArray(owner.data());
1156 LocalFree(pSD);
1157 }
1158 }
1159 }
1160#else
1161 Q_UNUSED(entry);
1162 Q_UNUSED(own);
1163#endif
1164 return name;
1165}
1166
1167//static
1168bool QFileSystemEngine::fillPermissions(const QFileSystemEntry &entry, QFileSystemMetaData &data,
1169 QFileSystemMetaData::MetaDataFlags what)
1170{
1171#if QT_CONFIG(fslibs)
1172 if (qAreNtfsPermissionChecksEnabled()) {
1173 initGlobalSid();
1174
1175 QString fname = entry.nativeFilePath();
1176 PSID pOwner;
1177 PSID pGroup;
1178 PACL pDacl;
1179 PSECURITY_DESCRIPTOR pSD;
1180
1181 // pDacl is unused directly by the code below, but it is still needed here because
1182 // access checks below return incorrect results unless DACL_SECURITY_INFORMATION is
1183 // passed to this call.
1184 DWORD res = GetNamedSecurityInfo(
1185 reinterpret_cast<const wchar_t *>(fname.utf16()), SE_FILE_OBJECT,
1186 OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
1187 &pOwner, &pGroup, &pDacl, nullptr, &pSD);
1188 if (res == ERROR_SUCCESS) {
1189 QAuthzResourceManager rm;
1190
1191 auto addPermissions = [&data](ACCESS_MASK accessMask,
1192 QFileSystemMetaData::MetaDataFlag readFlags,
1193 QFileSystemMetaData::MetaDataFlag writeFlags,
1194 QFileSystemMetaData::MetaDataFlag executeFlags) {
1195 // Check for generic permissions and file-specific bits that most closely
1196 // represent POSIX permissions.
1197
1198 // Constants like FILE_GENERIC_{READ,WRITE,EXECUTE} cannot be used
1199 // here because they contain permission bits shared between all of them.
1200 if (accessMask & (GENERIC_READ | FILE_READ_DATA))
1201 data.entryFlags |= readFlags;
1202 if (accessMask & (GENERIC_WRITE | FILE_WRITE_DATA))
1203 data.entryFlags |= writeFlags;
1204 if (accessMask & (GENERIC_EXECUTE | FILE_EXECUTE))
1205 data.entryFlags |= executeFlags;
1206 };
1207
1208 if (what & QFileSystemMetaData::UserPermissions && currentUserImpersonatedToken) {
1209 data.knownFlagsMask |= QFileSystemMetaData::UserPermissions;
1210 QAuthzClientContext context(rm, currentUserImpersonatedToken,
1211 QAuthzClientContext::TokenTag {});
1212 addPermissions(context.accessMask(pSD),
1213 QFileSystemMetaData::UserReadPermission,
1214 QFileSystemMetaData::UserWritePermission,
1215 QFileSystemMetaData::UserExecutePermission);
1216 }
1217
1218 if (what & QFileSystemMetaData::OwnerPermissions) {
1219 data.knownFlagsMask |= QFileSystemMetaData::OwnerPermissions;
1220 QAuthzClientContext context(rm, pOwner);
1221 addPermissions(context.accessMask(pSD),
1222 QFileSystemMetaData::OwnerReadPermission,
1223 QFileSystemMetaData::OwnerWritePermission,
1224 QFileSystemMetaData::OwnerExecutePermission);
1225 }
1226
1227 if (what & QFileSystemMetaData::GroupPermissions) {
1228 data.knownFlagsMask |= QFileSystemMetaData::GroupPermissions;
1229 QAuthzClientContext context(rm, pGroup);
1230 addPermissions(context.accessMask(pSD),
1231 QFileSystemMetaData::GroupReadPermission,
1232 QFileSystemMetaData::GroupWritePermission,
1233 QFileSystemMetaData::GroupExecutePermission);
1234 }
1235
1236 if (what & QFileSystemMetaData::OtherPermissions) {
1237 data.knownFlagsMask |= QFileSystemMetaData::OtherPermissions;
1238 QAuthzClientContext context(rm, worldSID);
1239 addPermissions(context.accessMask(pSD),
1240 QFileSystemMetaData::OtherReadPermission,
1241 QFileSystemMetaData::OtherWritePermission,
1242 QFileSystemMetaData::OtherExecutePermission);
1243 }
1244
1245 LocalFree(pSD);
1246 }
1247 } else
1248#endif
1249 {
1250 //### what to do with permissions if we don't use NTFS
1251 // for now just add all permissions and what about exe missions ??
1252 // also qt_ntfs_permission_lookup is now not set by default ... should it ?
1253 data.entryFlags |= QFileSystemMetaData::OwnerReadPermission
1254 | QFileSystemMetaData::GroupReadPermission
1255 | QFileSystemMetaData::OtherReadPermission;
1256
1257 if (!(data.fileAttribute_ & FILE_ATTRIBUTE_READONLY)) {
1258 data.entryFlags |= QFileSystemMetaData::OwnerWritePermission
1259 | QFileSystemMetaData::GroupWritePermission
1260 | QFileSystemMetaData::OtherWritePermission;
1261 }
1262
1263 QString fname = entry.filePath();
1264 QString ext = fname.right(4).toLower();
1265 if (data.isDirectory() || ext == ".exe"_L1 || ext == ".com"_L1
1266 || ext == ".bat"_L1 || ext == ".pif"_L1 || ext == ".cmd"_L1) {
1267 data.entryFlags |= QFileSystemMetaData::OwnerExecutePermission
1268 | QFileSystemMetaData::GroupExecutePermission
1269 | QFileSystemMetaData::OtherExecutePermission
1270 | QFileSystemMetaData::UserExecutePermission;
1271 }
1272 data.knownFlagsMask |= QFileSystemMetaData::OwnerPermissions
1273 | QFileSystemMetaData::GroupPermissions | QFileSystemMetaData::OtherPermissions
1274 | QFileSystemMetaData::UserExecutePermission;
1275 // calculate user permissions
1276 if (what & QFileSystemMetaData::UserReadPermission) {
1277 if (::_waccess((wchar_t*)entry.nativeFilePath().utf16(), R_OK) == 0)
1278 data.entryFlags |= QFileSystemMetaData::UserReadPermission;
1279 data.knownFlagsMask |= QFileSystemMetaData::UserReadPermission;
1280 }
1281 if (what & QFileSystemMetaData::UserWritePermission) {
1282 if (::_waccess((wchar_t*)entry.nativeFilePath().utf16(), W_OK) == 0)
1283 data.entryFlags |= QFileSystemMetaData::UserWritePermission;
1284 data.knownFlagsMask |= QFileSystemMetaData::UserWritePermission;
1285 }
1286 }
1287
1288 return data.hasFlags(what);
1289}
1290
1291static bool tryDriveUNCFallback(const QFileSystemEntry &fname, QFileSystemMetaData &data)
1292{
1293 bool entryExists = false;
1294 DWORD fileAttrib = 0;
1295 if (fname.isDriveRoot()) {
1296 // a valid drive ??
1297 const UINT oldErrorMode = ::SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
1298 DWORD drivesBitmask = ::GetLogicalDrives();
1299 ::SetErrorMode(oldErrorMode);
1300 int drivebit =
1301 1 << (fname.filePath().at(0).toUpper().unicode() - u'A');
1302 if (drivesBitmask & drivebit) {
1303 fileAttrib = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM;
1304 entryExists = true;
1305 }
1306 } else {
1307 const QString &path = fname.nativeFilePath();
1308 bool is_dir = false;
1309 if (path.startsWith("\\\\?\\UNC"_L1)) {
1310 // UNC - stat doesn't work for all cases (Windows bug)
1311 int s = path.indexOf(path.at(0),7);
1312 if (s > 0) {
1313 // "\\?\UNC\server\..."
1314 s = path.indexOf(path.at(0),s+1);
1315 if (s > 0) {
1316 // "\\?\UNC\server\share\..."
1317 if (s == path.size() - 1) {
1318 // "\\?\UNC\server\share\"
1319 is_dir = true;
1320 } else {
1321 // "\\?\UNC\server\share\notfound"
1322 }
1323 } else {
1324 // "\\?\UNC\server\share"
1325 is_dir = true;
1326 }
1327 } else {
1328 // "\\?\UNC\server"
1329 is_dir = true;
1330 }
1331 }
1332 if (is_dir && uncShareExists(path)) {
1333 // looks like a UNC dir, is a dir.
1334 fileAttrib = FILE_ATTRIBUTE_DIRECTORY;
1335 entryExists = true;
1336 }
1337 }
1338 if (entryExists)
1339 data.fillFromFileAttribute(fileAttrib);
1340 return entryExists;
1341}
1342
1343static bool tryFindFallback(const QFileSystemEntry &fname, QFileSystemMetaData &data)
1344{
1345 bool filledData = false;
1346 // This assumes the last call to a Windows API failed.
1347 int errorCode = GetLastError();
1348 if (errorCode == ERROR_ACCESS_DENIED || errorCode == ERROR_SHARING_VIOLATION) {
1349 WIN32_FIND_DATA findData;
1350 if (getFindData(fname.nativeFilePath(), findData)
1351 && findData.dwFileAttributes != INVALID_FILE_ATTRIBUTES) {
1352 data.fillFromFindData(findData, true, fname.isDriveRoot());
1353 filledData = true;
1354 }
1355 }
1356 return filledData;
1357}
1358
1359//static
1360bool QFileSystemEngine::fillMetaData(int fd, QFileSystemMetaData &data,
1361 QFileSystemMetaData::MetaDataFlags what)
1362{
1363 auto fHandle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
1364 if (fHandle != INVALID_HANDLE_VALUE) {
1365 return fillMetaData(fHandle, data, what);
1366 }
1367 return false;
1368}
1369
1370//static
1371bool QFileSystemEngine::fillMetaData(HANDLE fHandle, QFileSystemMetaData &data,
1372 QFileSystemMetaData::MetaDataFlags what)
1373{
1374 data.entryFlags &= ~what;
1375 clearWinStatData(data);
1376 BY_HANDLE_FILE_INFORMATION fileInfo;
1377 UINT oldmode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
1378 if (GetFileInformationByHandle(fHandle , &fileInfo)) {
1379 data.fillFromFindInfo(fileInfo);
1380 }
1381 SetErrorMode(oldmode);
1382 return data.hasFlags(what);
1383}
1384
1385//static
1386bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemMetaData &data,
1387 QFileSystemMetaData::MetaDataFlags what)
1388{
1389 Q_CHECK_FILE_NAME(entry, false);
1390 what |= QFileSystemMetaData::WinLnkType | QFileSystemMetaData::WinStatFlags;
1391 data.entryFlags &= ~what;
1392
1393 QFileSystemEntry fname;
1394 data.knownFlagsMask |= QFileSystemMetaData::WinLnkType;
1395 // Check for ".lnk": Directories named ".lnk" should be skipped, corrupted
1396 // link files should still be detected as links.
1397 const QString origFilePath = entry.filePath();
1398 if (bool exists; origFilePath.endsWith(".lnk"_L1) && !isDirPath(origFilePath, &exists) && exists) {
1399 data.entryFlags |= QFileSystemMetaData::WinLnkType;
1400 fname = QFileSystemEntry(readLink(entry));
1401 } else {
1402 fname = entry;
1403 }
1404
1405 if (fname.isEmpty()) {
1406 data.knownFlagsMask |= what;
1407 clearWinStatData(data);
1408 return false;
1409 }
1410
1411 if (what & QFileSystemMetaData::WinStatFlags) {
1412 UINT oldmode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
1413 clearWinStatData(data);
1414 WIN32_FIND_DATA findData;
1415 // The memory structure for WIN32_FIND_DATA is same as WIN32_FILE_ATTRIBUTE_DATA
1416 // for all members used by fillFindData().
1417 bool ok = ::GetFileAttributesEx(
1418 reinterpret_cast<const wchar_t *>(fname.nativeFilePath().utf16()),
1419 GetFileExInfoStandard, reinterpret_cast<WIN32_FILE_ATTRIBUTE_DATA *>(&findData));
1420 if (ok) {
1421 data.fillFromFindData(findData, false, fname.isDriveRoot());
1422 } else {
1423 const DWORD lastError = GetLastError();
1424 // disconnected drive
1425 if (lastError == ERROR_LOGON_FAILURE || lastError == ERROR_BAD_NETPATH
1426 || (!tryFindFallback(fname, data) && !tryDriveUNCFallback(fname, data))) {
1427 data.clearFlags();
1428 SetErrorMode(oldmode);
1429 return false;
1430 }
1431 }
1432 SetErrorMode(oldmode);
1433 }
1434
1435 if (what & QFileSystemMetaData::Permissions)
1436 fillPermissions(fname, data, what);
1437 if (what & QFileSystemMetaData::LinkType) {
1438 data.knownFlagsMask |= QFileSystemMetaData::LinkType;
1439 if (data.fileAttribute_ & FILE_ATTRIBUTE_REPARSE_POINT) {
1440 WIN32_FIND_DATA findData;
1441 if (getFindData(fname.nativeFilePath(), findData))
1442 data.fillFromFindData(findData, true);
1443 }
1444 }
1445 data.knownFlagsMask |= what;
1446 return data.hasFlags(what);
1447}
1448
1449static inline bool mkDir(const QString &path, SECURITY_ATTRIBUTES *securityAttributes,
1450 DWORD *lastError = nullptr)
1451{
1452 if (lastError)
1453 *lastError = 0;
1454 const QString longPath = QFSFileEnginePrivate::longFileName(path);
1455 const bool result = ::CreateDirectory((wchar_t *)longPath.utf16(), securityAttributes);
1456 // Capture lastError before any QString is freed since custom allocators might change it.
1457 if (lastError)
1458 *lastError = GetLastError();
1459 return result;
1460}
1461
1462static inline bool rmDir(const QString &path)
1463{
1464 return ::RemoveDirectory((wchar_t*)QFSFileEnginePrivate::longFileName(path).utf16());
1465}
1466
1467//static
1468bool QFileSystemEngine::isDirPath(const QString &dirPath, bool *existed)
1469{
1470 QString path = dirPath;
1471 if (path.length() == 2 && path.at(1) == u':')
1472 path += u'\\';
1473
1474 const QString longPath = QFSFileEnginePrivate::longFileName(path);
1475 DWORD fileAttrib = ::GetFileAttributes(reinterpret_cast<const wchar_t*>(longPath.utf16()));
1476 if (fileAttrib == INVALID_FILE_ATTRIBUTES) {
1477 int errorCode = GetLastError();
1478 if (errorCode == ERROR_ACCESS_DENIED || errorCode == ERROR_SHARING_VIOLATION) {
1479 WIN32_FIND_DATA findData;
1480 if (getFindData(longPath, findData))
1481 fileAttrib = findData.dwFileAttributes;
1482 }
1483 }
1484
1485 if (existed)
1486 *existed = fileAttrib != INVALID_FILE_ATTRIBUTES;
1487
1488 if (fileAttrib == INVALID_FILE_ATTRIBUTES)
1489 return false;
1490
1491 return fileAttrib & FILE_ATTRIBUTE_DIRECTORY;
1492}
1493
1494// NOTE: if \a shouldMkdirFirst is false, we assume the caller did try to mkdir
1495// before calling this function.
1496static bool createDirectoryWithParents(const QString &nativeName,
1497 SECURITY_ATTRIBUTES *securityAttributes,
1498 bool shouldMkdirFirst = true)
1499{
1500 const auto isUNCRoot = [](const QString &nativeName) {
1501 return nativeName.startsWith("\\\\"_L1)
1502 && nativeName.count(QDir::separator()) <= 3;
1503 };
1504 const auto isDriveName = [](const QString &nativeName) {
1505 return nativeName.size() == 2 && nativeName.at(1) == u':';
1506 };
1507 const auto isDir = [](const QString &nativeName) {
1508 bool exists = false;
1509 return QFileSystemEngine::isDirPath(nativeName, &exists) && exists;
1510 };
1511 // Do not try to mkdir a UNC root path or a drive letter.
1512 if (isUNCRoot(nativeName) || isDriveName(nativeName))
1513 return false;
1514
1515 if (shouldMkdirFirst) {
1516 if (mkDir(nativeName, securityAttributes))
1517 return true;
1518 }
1519
1520 const int backSlash = nativeName.lastIndexOf(QDir::separator());
1521 if (backSlash < 1)
1522 return false;
1523
1524 const QString parentNativeName = nativeName.left(backSlash);
1525 if (!createDirectoryWithParents(parentNativeName, securityAttributes))
1526 return false;
1527
1528 // try again
1529 if (mkDir(nativeName, securityAttributes))
1530 return true;
1531 return isDir(nativeName);
1532}
1533
1534bool QFileSystemEngine::mkpath(const QFileSystemEntry &entry,
1535 std::optional<QFile::Permissions> permissions)
1536{
1537 QString dirName = entry.filePath();
1538 Q_CHECK_FILE_NAME(dirName, false);
1539
1540 QNativeFilePermissions nativePermissions(permissions, true);
1541 if (!nativePermissions.isOk())
1542 return false;
1543
1544 auto securityAttributes = nativePermissions.securityAttributes();
1545 dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName));
1546
1547 // try to mkdir this directory
1548 DWORD lastError;
1549 if (mkDir(dirName, securityAttributes, &lastError))
1550 return true;
1551 // mkpath should return true, if the directory already exists
1552 if (lastError == ERROR_ALREADY_EXISTS || lastError == ERROR_ACCESS_DENIED)
1553 return isDirPath(dirName, nullptr);
1554
1555 return createDirectoryWithParents(dirName, securityAttributes, false);
1556}
1557
1558bool QFileSystemEngine::mkdir(const QFileSystemEntry &entry,
1559 std::optional<QFile::Permissions> permissions)
1560{
1561 QString dirName = entry.filePath();
1562 Q_CHECK_FILE_NAME(dirName, false);
1563
1564
1565 QNativeFilePermissions nativePermissions(permissions, true);
1566 if (!nativePermissions.isOk())
1567 return false;
1568
1569 dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName));
1570 return mkDir(dirName, nativePermissions.securityAttributes());
1571}
1572
1573bool QFileSystemEngine::rmdir(const QFileSystemEntry &entry)
1574{
1575 QString dirName = entry.filePath();
1576 Q_CHECK_FILE_NAME(dirName, false);
1577
1578 return rmDir(dirName);
1579}
1580
1581bool QFileSystemEngine::rmpath(const QFileSystemEntry &entry)
1582{
1583 const QString dirName = QDir::toNativeSeparators(QDir::cleanPath(entry.filePath()));
1584 Q_CHECK_FILE_NAME(dirName, false);
1585
1586 for (int oldslash = 0, slash = dirName.size(); slash > 0; oldslash = slash) {
1587 const auto chunkRef = QStringView{dirName}.left(slash);
1588 if (chunkRef.length() == 2 && chunkRef.at(0).isLetter()
1589 && chunkRef.at(1) == u':') {
1590 break;
1591 }
1592 const QString chunk = chunkRef.toString();
1593 // TODO: get isDirPath() and rmDir() to accept QStringView
1594 if (!isDirPath(chunk, nullptr))
1595 return false;
1596 if (!rmDir(chunk))
1597 return oldslash != 0;
1598 slash = dirName.lastIndexOf(QDir::separator(), oldslash - 1);
1599 }
1600
1601 return true;
1602}
1603
1604//static
1605QString QFileSystemEngine::rootPath()
1606{
1607 QString ret = qEnvironmentVariable("SystemDrive");
1608 if (ret.isEmpty())
1609 ret = "c:"_L1;
1610 ret.append(u'/');
1611 return ret;
1612}
1613
1614//static
1615QString QFileSystemEngine::homePath()
1616{
1617 QString ret;
1618#if QT_CONFIG(fslibs)
1619 initGlobalSid();
1620 {
1621 HANDLE hnd = ::GetCurrentProcess();
1622 HANDLE token = nullptr;
1623 BOOL ok = ::OpenProcessToken(hnd, TOKEN_QUERY, &token);
1624 if (ok) {
1625 DWORD dwBufferSize = 0;
1626 // First call, to determine size of the strings (with '\0').
1627 ok = GetUserProfileDirectory(token, nullptr, &dwBufferSize);
1628 if (!ok && dwBufferSize != 0) { // We got the required buffer size
1629 wchar_t *userDirectory = new wchar_t[dwBufferSize];
1630 // Second call, now we can fill the allocated buffer.
1631 ok = GetUserProfileDirectory(token, userDirectory, &dwBufferSize);
1632 if (ok)
1633 ret = QString::fromWCharArray(userDirectory);
1634 delete [] userDirectory;
1635 }
1636 ::CloseHandle(token);
1637 }
1638 }
1639#endif
1640 if (ret.isEmpty() || !QFile::exists(ret)) {
1641 ret = qEnvironmentVariable("USERPROFILE");
1642 if (ret.isEmpty() || !QFile::exists(ret)) {
1643 ret = qEnvironmentVariable("HOMEDRIVE")
1644 + qEnvironmentVariable("HOMEPATH");
1645 if (ret.isEmpty() || !QFile::exists(ret)) {
1646 ret = qEnvironmentVariable("HOME");
1647 if (ret.isEmpty() || !QFile::exists(ret))
1648 ret = rootPath();
1649 }
1650 }
1651 }
1652 return QDir::fromNativeSeparators(ret);
1653}
1654
1655QString QFileSystemEngine::tempPath()
1656{
1657 QString ret;
1658 wchar_t tempPath[MAX_PATH];
1659 using GetTempPathPrototype = DWORD (WINAPI *)(DWORD, LPWSTR);
1660 // We try to resolve GetTempPath2 and use that, otherwise fall back to GetTempPath:
1661 static GetTempPathPrototype getTempPathW = []() {
1662 const HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll");
1663 if (auto *func = QFunctionPointer(GetProcAddress(kernel32, "GetTempPath2W")))
1664 return GetTempPathPrototype(func);
1665 return GetTempPath;
1666 }();
1667 const DWORD len = getTempPathW(MAX_PATH, tempPath);
1668 if (len) { // GetTempPath() can return short names, expand.
1669 wchar_t longTempPath[MAX_PATH];
1670 const DWORD longLen = GetLongPathName(tempPath, longTempPath, MAX_PATH);
1671 ret = longLen && longLen < MAX_PATH ?
1672 QString::fromWCharArray(longTempPath, longLen) :
1673 QString::fromWCharArray(tempPath, len);
1674 }
1675 if (!ret.isEmpty()) {
1676 while (ret.endsWith(u'\\'))
1677 ret.chop(1);
1678 ret = QDir::fromNativeSeparators(ret);
1679 }
1680 if (ret.isEmpty()) {
1681 ret = "C:/tmp"_L1;
1682 } else if (ret.length() >= 2 && ret[1] == u':')
1683 ret[0] = ret.at(0).toUpper(); // Force uppercase drive letters.
1684 return ret;
1685}
1686
1687bool QFileSystemEngine::setCurrentPath(const QFileSystemEntry &entry)
1688{
1689 QFileSystemMetaData meta;
1690 fillMetaData(entry, meta,
1691 QFileSystemMetaData::ExistsAttribute | QFileSystemMetaData::DirectoryType);
1692 if (!(meta.exists() && meta.isDirectory()))
1693 return false;
1694
1695 // TODO: this should really be using nativeFilePath(), but that returns a path
1696 // in long format \\?\c:\foo which causes many problems later on when it's
1697 // returned through currentPath()
1698 return ::SetCurrentDirectory(reinterpret_cast<const wchar_t *>(
1699 QDir::toNativeSeparators(entry.filePath()).utf16()))
1700 != 0;
1701}
1702
1703QFileSystemEntry QFileSystemEngine::currentPath()
1704{
1705 QString ret(PATH_MAX, Qt::Uninitialized);
1706 DWORD size = GetCurrentDirectoryW(PATH_MAX, reinterpret_cast<wchar_t *>(ret.data()));
1707 if (size > PATH_MAX) {
1708 // try again after enlarging the buffer
1709 ret.resize(size);
1710 size = GetCurrentDirectoryW(size, reinterpret_cast<wchar_t *>(ret.data()));
1711
1712 // note: the current directory may have changed underneath us; if the
1713 // new one is even bigger, we may return a truncated string!
1714 }
1715 if (size >= 2 && ret.at(1) == u':')
1716 ret[0] = ret.at(0).toUpper(); // Force uppercase drive letters.
1717 ret.resize(size);
1718 return QFileSystemEntry(std::move(ret), QFileSystemEntry::FromNativePath());
1719}
1720
1721//static
1722bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSystemEntry &target,
1723 QSystemError &error)
1724{
1725 bool ret = false;
1726 QComHelper comHelper;
1727 IShellLink *psl = nullptr;
1728 HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink,
1729 reinterpret_cast<void **>(&psl));
1730
1731 if (SUCCEEDED(hres)) {
1732 const auto name = QDir::toNativeSeparators(source.filePath());
1733 const auto pathName = QDir::toNativeSeparators(source.path());
1734 if (SUCCEEDED(psl->SetPath(reinterpret_cast<const wchar_t *>(name.utf16())))
1735 && SUCCEEDED(psl->SetWorkingDirectory(
1736 reinterpret_cast<const wchar_t *>(pathName.utf16())))) {
1737 IPersistFile *ppf = nullptr;
1738 if (SUCCEEDED(psl->QueryInterface(IID_IPersistFile, reinterpret_cast<void **>(&ppf)))) {
1739 ret = SUCCEEDED(ppf->Save(
1740 reinterpret_cast<const wchar_t *>(target.filePath().utf16()), TRUE));
1741 ppf->Release();
1742 }
1743 }
1744 psl->Release();
1745 }
1746
1747 if (!ret)
1748 error = QSystemError(::GetLastError(), QSystemError::NativeError);
1749
1750 return ret;
1751}
1752
1753//static
1754bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target,
1755 QSystemError &error)
1756{
1757 bool ret = ::CopyFile((wchar_t*)source.nativeFilePath().utf16(),
1758 (wchar_t*)target.nativeFilePath().utf16(), true) != 0;
1759 if (!ret)
1760 error = QSystemError(::GetLastError(), QSystemError::NativeError);
1761 return ret;
1762}
1763
1764//static
1765bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target,
1766 QSystemError &error)
1767{
1768 Q_CHECK_FILE_NAME(source, false);
1769 Q_CHECK_FILE_NAME(target, false);
1770
1771 bool ret = ::MoveFile((wchar_t*)source.nativeFilePath().utf16(),
1772 (wchar_t*)target.nativeFilePath().utf16()) != 0;
1773 if (!ret)
1774 error = QSystemError(::GetLastError(), QSystemError::NativeError);
1775 return ret;
1776}
1777
1778//static
1779bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source,
1780 const QFileSystemEntry &target, QSystemError &error)
1781{
1782 Q_CHECK_FILE_NAME(source, false);
1783 Q_CHECK_FILE_NAME(target, false);
1784
1785 bool ret = ::MoveFileEx(reinterpret_cast<const wchar_t *>(source.nativeFilePath().utf16()),
1786 reinterpret_cast<const wchar_t *>(target.nativeFilePath().utf16()),
1787 MOVEFILE_REPLACE_EXISTING) != 0;
1788 if (!ret)
1789 error = QSystemError(::GetLastError(), QSystemError::NativeError);
1790 return ret;
1791}
1792
1793//static
1794bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError &error)
1795{
1796 Q_CHECK_FILE_NAME(entry, false);
1797
1798 bool ret = ::DeleteFile((wchar_t*)entry.nativeFilePath().utf16()) != 0;
1799 if (!ret)
1800 error = QSystemError(::GetLastError(), QSystemError::NativeError);
1801 return ret;
1802}
1803
1804//static
1805bool QFileSystemEngine::supportsMoveFileToTrash()
1806{
1807 return true;
1808}
1809
1810//static
1811bool QFileSystemEngine::supportsRmdirRecursively() noexcept
1812{
1813 return false;
1814}
1815
1816//static
1817bool QFileSystemEngine::rmdirRecursively(const QFileSystemEntry &, QSystemError &error)
1818{
1819 error = QSystemError(ENOSYS, QSystemError::StandardLibraryError);
1820 return false;
1821}
1822
1823/*
1824 If possible, we use the IFileOperation implementation, which allows us to determine
1825 the location of the object in the trash.
1826 If not (likely on mingw), we fall back to the old API, which won't allow us to know
1827 that.
1828*/
1829//static
1830bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &source,
1831 QFileSystemEntry &newLocation, QSystemError &error)
1832{
1833 // we need the "display name" of the file, so can't use nativeAbsoluteFilePath
1834 const QString sourcePath = QDir::toNativeSeparators(absoluteName(source).filePath());
1835
1836 QComHelper comHelper;
1837
1838 IFileOperation *pfo = nullptr;
1839 IShellItem *deleteItem = nullptr;
1840 FileOperationProgressSink *sink = nullptr;
1841 HRESULT hres = E_FAIL;
1842
1843 auto coUninitialize = qScopeGuard([&](){
1844 if (sink)
1845 sink->Release();
1846 if (deleteItem)
1847 deleteItem->Release();
1848 if (pfo)
1849 pfo->Release();
1850 if (!SUCCEEDED(hres))
1851 error = QSystemError(hres, QSystemError::NativeError);
1852 });
1853
1854 hres = CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pfo));
1855 if (!pfo)
1856 return false;
1857 pfo->SetOperationFlags(FOF_ALLOWUNDO | FOFX_RECYCLEONDELETE | FOF_NOCONFIRMATION
1858 | FOF_SILENT | FOF_NOERRORUI);
1859 hres = SHCreateItemFromParsingName(reinterpret_cast<const wchar_t*>(sourcePath.utf16()),
1860 nullptr, IID_PPV_ARGS(&deleteItem));
1861 if (!deleteItem)
1862 return false;
1863 sink = new FileOperationProgressSink;
1864 hres = pfo->DeleteItem(deleteItem, static_cast<IFileOperationProgressSink*>(sink));
1865 if (!SUCCEEDED(hres))
1866 return false;
1867 hres = pfo->PerformOperations();
1868 if (!SUCCEEDED(hres))
1869 return false;
1870
1871 if (!SUCCEEDED(sink->deleteResult)) {
1872 error = QSystemError(sink->deleteResult, QSystemError::NativeError);
1873 return false;
1874 }
1875 newLocation = QFileSystemEntry(sink->targetPath);
1876 return true;
1877}
1878
1879//static
1880bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry,
1881 QFile::Permissions permissions, QSystemError &error)
1882{
1883 Q_CHECK_FILE_NAME(entry, false);
1884
1885 int mode = 0;
1886
1887 if (permissions & (QFile::ReadOwner | QFile::ReadUser | QFile::ReadGroup | QFile::ReadOther))
1888 mode |= _S_IREAD;
1889 if (permissions
1890 & (QFile::WriteOwner | QFile::WriteUser | QFile::WriteGroup | QFile::WriteOther)) {
1891 mode |= _S_IWRITE;
1892 }
1893
1894 if (mode == 0) // not supported
1895 return false;
1896
1897 bool ret =
1898 ::_wchmod(reinterpret_cast<const wchar_t *>(entry.nativeFilePath().utf16()), mode) == 0;
1899 if (!ret)
1900 error = QSystemError(errno, QSystemError::StandardLibraryError);
1901 return ret;
1902}
1903
1904bool QFileSystemEngine::isCaseSensitive(const QFileSystemEntry &, QFileSystemMetaData &)
1905{
1906 // FIXME: This may not be accurate for all file systems (QTBUG-28246)
1907 return false;
1908}
1909
1910static inline QDateTime fileTimeToQDateTime(const FILETIME *time)
1911{
1912 if (time->dwHighDateTime == 0 && time->dwLowDateTime == 0)
1913 return QDateTime();
1914
1915 SYSTEMTIME sTime;
1916 FileTimeToSystemTime(time, &sTime);
1917 return QDateTime(QDate(sTime.wYear, sTime.wMonth, sTime.wDay),
1918 QTime(sTime.wHour, sTime.wMinute, sTime.wSecond, sTime.wMilliseconds),
1919 QTimeZone::UTC);
1920}
1921
1922QDateTime QFileSystemMetaData::birthTime() const
1923{
1924 return fileTimeToQDateTime(&birthTime_);
1925}
1926QDateTime QFileSystemMetaData::metadataChangeTime() const
1927{
1928 return fileTimeToQDateTime(&changeTime_);
1929}
1930QDateTime QFileSystemMetaData::modificationTime() const
1931{
1932 return fileTimeToQDateTime(&lastWriteTime_);
1933}
1934QDateTime QFileSystemMetaData::accessTime() const
1935{
1936 return fileTimeToQDateTime(&lastAccessTime_);
1937}
1938
1939QT_END_NAMESPACE
qEnableNtfsPermissionChecks()
qDisableNtfsPermissionChecks()
qAreNtfsPermissionChecksEnabled()
[raii]
#define Q_CHECK_FILE_NAME(name, result)
#define FSCTL_GET_REPARSE_POINT
static bool toFileTime(const QDateTime &date, FILETIME *fileTime)
static QString readSymLink(const QFileSystemEntry &link)
static bool tryFindFallback(const QFileSystemEntry &fname, QFileSystemMetaData &data)
static QFileSystemNativeId fileId(HANDLE handle)
static bool rmDir(const QString &path)
#define INVALID_FILE_ATTRIBUTES
static QBasicAtomicInt qt_ntfs_permission_lookup_v2
static bool mkDir(const QString &path, SECURITY_ATTRIBUTES *securityAttributes, DWORD *lastError=nullptr)
static bool tryDriveUNCFallback(const QFileSystemEntry &fname, QFileSystemMetaData &data)
static QFileSystemNativeId fileIdWin8(HANDLE handle)
#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE
static QString readLink(const QFileSystemEntry &link)
static bool createDirectoryWithParents(const QString &nativeName, SECURITY_ATTRIBUTES *securityAttributes, bool shouldMkdirFirst=true)
static QDateTime fileTimeToQDateTime(const FILETIME *time)
static bool uncShareExists(const QString &server)
#define IO_REPARSE_TAG_SYMLINK
static bool getFindData(QString path, WIN32_FIND_DATA &findData)
#define PATH_MAX
bool isValid() const noexcept