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 QByteArray fileId(HANDLE handle)
1012{
1013 BY_HANDLE_FILE_INFORMATION info;
1014 if (GetFileInformationByHandle(handle, &info)) {
1015 char buffer[sizeof "01234567:0123456701234567"];
1016 std::snprintf(buffer, sizeof(buffer), "%lx:%08lx%08lx",
1017 info.dwVolumeSerialNumber,
1018 info.nFileIndexHigh,
1019 info.nFileIndexLow);
1020 return buffer;
1021 }
1022 return QByteArray();
1023}
1024
1025// File ID for Windows starting from version 8.
1027{
1028#if !defined(QT_BOOTSTRAPPED)
1029 QByteArray result;
1030 FILE_ID_INFO infoEx;
1031 if (GetFileInformationByHandleEx(
1032 handle,
1033 static_cast<FILE_INFO_BY_HANDLE_CLASS>(18), // FileIdInfo in Windows 8
1034 &infoEx, sizeof(FILE_ID_INFO))) {
1035 result = QByteArray::number(infoEx.VolumeSerialNumber, 16);
1036 result += ':';
1037 // Note: MinGW-64's definition of FILE_ID_128 differs from the MSVC one.
1038 result += QByteArray(reinterpret_cast<const char *>(&infoEx.FileId),
1039 int(sizeof(infoEx.FileId)))
1040 .toHex();
1041 } else {
1042 // GetFileInformationByHandleEx() is observed to fail for FAT32, QTBUG-74759
1043 result = fileId(handle);
1044 }
1045 return result;
1046#else // !QT_BOOTSTRAPPED
1047 return fileId(handle);
1048#endif
1049}
1050
1051//static
1052QByteArray QFileSystemEngine::id(const QFileSystemEntry &entry)
1053{
1054 Q_CHECK_FILE_NAME(entry, QByteArray());
1055
1056 QByteArray result;
1057
1058 const HANDLE handle = CreateFile((wchar_t *)entry.nativeFilePath().utf16(), 0, FILE_SHARE_READ,
1059 nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
1060 if (handle != INVALID_HANDLE_VALUE) {
1061 result = id(handle);
1062 CloseHandle(handle);
1063 }
1064 return result;
1065}
1066
1067//static
1068QByteArray QFileSystemEngine::id(HANDLE fHandle)
1069{
1070 return fileIdWin8(HANDLE(fHandle));
1071}
1072
1073//static
1074bool QFileSystemEngine::setFileTime(HANDLE fHandle, const QDateTime &newDate,
1075 QFile::FileTime time, QSystemError &error)
1076{
1077 FILETIME fTime;
1078 FILETIME *pLastWrite = nullptr;
1079 FILETIME *pLastAccess = nullptr;
1080 FILETIME *pCreationTime = nullptr;
1081
1082 switch (time) {
1083 case QFile::FileModificationTime:
1084 pLastWrite = &fTime;
1085 break;
1086
1087 case QFile::FileAccessTime:
1088 pLastAccess = &fTime;
1089 break;
1090
1091 case QFile::FileBirthTime:
1092 pCreationTime = &fTime;
1093 break;
1094
1095 default:
1096 error = QSystemError(ERROR_INVALID_PARAMETER, QSystemError::NativeError);
1097 return false;
1098 }
1099
1100 if (!toFileTime(newDate, &fTime))
1101 return false;
1102
1103 if (!::SetFileTime(fHandle, pCreationTime, pLastAccess, pLastWrite)) {
1104 error = QSystemError(::GetLastError(), QSystemError::NativeError);
1105 return false;
1106 }
1107 return true;
1108}
1109
1110QString QFileSystemEngine::owner(const QFileSystemEntry &entry, QAbstractFileEngine::FileOwner own)
1111{
1112 QString name;
1113#if QT_CONFIG(fslibs)
1114 if (qAreNtfsPermissionChecksEnabled()) {
1115 initGlobalSid();
1116 {
1117 PSID pOwner = 0;
1118 PSECURITY_DESCRIPTOR pSD;
1119 if (GetNamedSecurityInfo(
1120 reinterpret_cast<const wchar_t *>(entry.nativeFilePath().utf16()),
1121 SE_FILE_OBJECT,
1122 own == QAbstractFileEngine::OwnerGroup ? GROUP_SECURITY_INFORMATION
1123 : OWNER_SECURITY_INFORMATION,
1124 own == QAbstractFileEngine::OwnerUser ? &pOwner : nullptr,
1125 own == QAbstractFileEngine::OwnerGroup ? &pOwner : nullptr, nullptr,
1126 nullptr, &pSD)
1127 == ERROR_SUCCESS) {
1128 DWORD lowner = 64;
1129 DWORD ldomain = 64;
1130 QVarLengthArray<wchar_t, 64> owner(lowner);
1131 QVarLengthArray<wchar_t, 64> domain(ldomain);
1132 SID_NAME_USE use = SidTypeUnknown;
1133 // First call, to determine size of the strings (with '\0').
1134 if (!LookupAccountSid(nullptr, pOwner, (LPWSTR)owner.data(), &lowner, domain.data(),
1135 &ldomain, &use)) {
1136 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
1137 if (lowner > (DWORD)owner.size())
1138 owner.resize(lowner);
1139 if (ldomain > (DWORD)domain.size())
1140 domain.resize(ldomain);
1141 // Second call, try on resized buf-s
1142 if (!LookupAccountSid(nullptr, pOwner, owner.data(), &lowner, domain.data(),
1143 &ldomain, &use)) {
1144 lowner = 0;
1145 }
1146 } else {
1147 lowner = 0;
1148 }
1149 }
1150 if (lowner != 0)
1151 name = QString::fromWCharArray(owner.data());
1152 LocalFree(pSD);
1153 }
1154 }
1155 }
1156#else
1157 Q_UNUSED(entry);
1158 Q_UNUSED(own);
1159#endif
1160 return name;
1161}
1162
1163//static
1164bool QFileSystemEngine::fillPermissions(const QFileSystemEntry &entry, QFileSystemMetaData &data,
1165 QFileSystemMetaData::MetaDataFlags what)
1166{
1167#if QT_CONFIG(fslibs)
1168 if (qAreNtfsPermissionChecksEnabled()) {
1169 initGlobalSid();
1170
1171 QString fname = entry.nativeFilePath();
1172 PSID pOwner;
1173 PSID pGroup;
1174 PACL pDacl;
1175 PSECURITY_DESCRIPTOR pSD;
1176
1177 // pDacl is unused directly by the code below, but it is still needed here because
1178 // access checks below return incorrect results unless DACL_SECURITY_INFORMATION is
1179 // passed to this call.
1180 DWORD res = GetNamedSecurityInfo(
1181 reinterpret_cast<const wchar_t *>(fname.utf16()), SE_FILE_OBJECT,
1182 OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
1183 &pOwner, &pGroup, &pDacl, nullptr, &pSD);
1184 if (res == ERROR_SUCCESS) {
1185 QAuthzResourceManager rm;
1186
1187 auto addPermissions = [&data](ACCESS_MASK accessMask,
1188 QFileSystemMetaData::MetaDataFlag readFlags,
1189 QFileSystemMetaData::MetaDataFlag writeFlags,
1190 QFileSystemMetaData::MetaDataFlag executeFlags) {
1191 // Check for generic permissions and file-specific bits that most closely
1192 // represent POSIX permissions.
1193
1194 // Constants like FILE_GENERIC_{READ,WRITE,EXECUTE} cannot be used
1195 // here because they contain permission bits shared between all of them.
1196 if (accessMask & (GENERIC_READ | FILE_READ_DATA))
1197 data.entryFlags |= readFlags;
1198 if (accessMask & (GENERIC_WRITE | FILE_WRITE_DATA))
1199 data.entryFlags |= writeFlags;
1200 if (accessMask & (GENERIC_EXECUTE | FILE_EXECUTE))
1201 data.entryFlags |= executeFlags;
1202 };
1203
1204 if (what & QFileSystemMetaData::UserPermissions && currentUserImpersonatedToken) {
1205 data.knownFlagsMask |= QFileSystemMetaData::UserPermissions;
1206 QAuthzClientContext context(rm, currentUserImpersonatedToken,
1207 QAuthzClientContext::TokenTag {});
1208 addPermissions(context.accessMask(pSD),
1209 QFileSystemMetaData::UserReadPermission,
1210 QFileSystemMetaData::UserWritePermission,
1211 QFileSystemMetaData::UserExecutePermission);
1212 }
1213
1214 if (what & QFileSystemMetaData::OwnerPermissions) {
1215 data.knownFlagsMask |= QFileSystemMetaData::OwnerPermissions;
1216 QAuthzClientContext context(rm, pOwner);
1217 addPermissions(context.accessMask(pSD),
1218 QFileSystemMetaData::OwnerReadPermission,
1219 QFileSystemMetaData::OwnerWritePermission,
1220 QFileSystemMetaData::OwnerExecutePermission);
1221 }
1222
1223 if (what & QFileSystemMetaData::GroupPermissions) {
1224 data.knownFlagsMask |= QFileSystemMetaData::GroupPermissions;
1225 QAuthzClientContext context(rm, pGroup);
1226 addPermissions(context.accessMask(pSD),
1227 QFileSystemMetaData::GroupReadPermission,
1228 QFileSystemMetaData::GroupWritePermission,
1229 QFileSystemMetaData::GroupExecutePermission);
1230 }
1231
1232 if (what & QFileSystemMetaData::OtherPermissions) {
1233 data.knownFlagsMask |= QFileSystemMetaData::OtherPermissions;
1234 QAuthzClientContext context(rm, worldSID);
1235 addPermissions(context.accessMask(pSD),
1236 QFileSystemMetaData::OtherReadPermission,
1237 QFileSystemMetaData::OtherWritePermission,
1238 QFileSystemMetaData::OtherExecutePermission);
1239 }
1240
1241 LocalFree(pSD);
1242 }
1243 } else
1244#endif
1245 {
1246 //### what to do with permissions if we don't use NTFS
1247 // for now just add all permissions and what about exe missions ??
1248 // also qt_ntfs_permission_lookup is now not set by default ... should it ?
1249 data.entryFlags |= QFileSystemMetaData::OwnerReadPermission
1250 | QFileSystemMetaData::GroupReadPermission
1251 | QFileSystemMetaData::OtherReadPermission;
1252
1253 if (!(data.fileAttribute_ & FILE_ATTRIBUTE_READONLY)) {
1254 data.entryFlags |= QFileSystemMetaData::OwnerWritePermission
1255 | QFileSystemMetaData::GroupWritePermission
1256 | QFileSystemMetaData::OtherWritePermission;
1257 }
1258
1259 QString fname = entry.filePath();
1260 QString ext = fname.right(4).toLower();
1261 if (data.isDirectory() || ext == ".exe"_L1 || ext == ".com"_L1
1262 || ext == ".bat"_L1 || ext == ".pif"_L1 || ext == ".cmd"_L1) {
1263 data.entryFlags |= QFileSystemMetaData::OwnerExecutePermission
1264 | QFileSystemMetaData::GroupExecutePermission
1265 | QFileSystemMetaData::OtherExecutePermission
1266 | QFileSystemMetaData::UserExecutePermission;
1267 }
1268 data.knownFlagsMask |= QFileSystemMetaData::OwnerPermissions
1269 | QFileSystemMetaData::GroupPermissions | QFileSystemMetaData::OtherPermissions
1270 | QFileSystemMetaData::UserExecutePermission;
1271 // calculate user permissions
1272 if (what & QFileSystemMetaData::UserReadPermission) {
1273 if (::_waccess((wchar_t*)entry.nativeFilePath().utf16(), R_OK) == 0)
1274 data.entryFlags |= QFileSystemMetaData::UserReadPermission;
1275 data.knownFlagsMask |= QFileSystemMetaData::UserReadPermission;
1276 }
1277 if (what & QFileSystemMetaData::UserWritePermission) {
1278 if (::_waccess((wchar_t*)entry.nativeFilePath().utf16(), W_OK) == 0)
1279 data.entryFlags |= QFileSystemMetaData::UserWritePermission;
1280 data.knownFlagsMask |= QFileSystemMetaData::UserWritePermission;
1281 }
1282 }
1283
1284 return data.hasFlags(what);
1285}
1286
1287static bool tryDriveUNCFallback(const QFileSystemEntry &fname, QFileSystemMetaData &data)
1288{
1289 bool entryExists = false;
1290 DWORD fileAttrib = 0;
1291 if (fname.isDriveRoot()) {
1292 // a valid drive ??
1293 const UINT oldErrorMode = ::SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
1294 DWORD drivesBitmask = ::GetLogicalDrives();
1295 ::SetErrorMode(oldErrorMode);
1296 int drivebit =
1297 1 << (fname.filePath().at(0).toUpper().unicode() - u'A');
1298 if (drivesBitmask & drivebit) {
1299 fileAttrib = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM;
1300 entryExists = true;
1301 }
1302 } else {
1303 const QString &path = fname.nativeFilePath();
1304 bool is_dir = false;
1305 if (path.startsWith("\\\\?\\UNC"_L1)) {
1306 // UNC - stat doesn't work for all cases (Windows bug)
1307 int s = path.indexOf(path.at(0),7);
1308 if (s > 0) {
1309 // "\\?\UNC\server\..."
1310 s = path.indexOf(path.at(0),s+1);
1311 if (s > 0) {
1312 // "\\?\UNC\server\share\..."
1313 if (s == path.size() - 1) {
1314 // "\\?\UNC\server\share\"
1315 is_dir = true;
1316 } else {
1317 // "\\?\UNC\server\share\notfound"
1318 }
1319 } else {
1320 // "\\?\UNC\server\share"
1321 is_dir = true;
1322 }
1323 } else {
1324 // "\\?\UNC\server"
1325 is_dir = true;
1326 }
1327 }
1328 if (is_dir && uncShareExists(path)) {
1329 // looks like a UNC dir, is a dir.
1330 fileAttrib = FILE_ATTRIBUTE_DIRECTORY;
1331 entryExists = true;
1332 }
1333 }
1334 if (entryExists)
1335 data.fillFromFileAttribute(fileAttrib);
1336 return entryExists;
1337}
1338
1339static bool tryFindFallback(const QFileSystemEntry &fname, QFileSystemMetaData &data)
1340{
1341 bool filledData = false;
1342 // This assumes the last call to a Windows API failed.
1343 int errorCode = GetLastError();
1344 if (errorCode == ERROR_ACCESS_DENIED || errorCode == ERROR_SHARING_VIOLATION) {
1345 WIN32_FIND_DATA findData;
1346 if (getFindData(fname.nativeFilePath(), findData)
1347 && findData.dwFileAttributes != INVALID_FILE_ATTRIBUTES) {
1348 data.fillFromFindData(findData, true, fname.isDriveRoot());
1349 filledData = true;
1350 }
1351 }
1352 return filledData;
1353}
1354
1355//static
1356bool QFileSystemEngine::fillMetaData(int fd, QFileSystemMetaData &data,
1357 QFileSystemMetaData::MetaDataFlags what)
1358{
1359 auto fHandle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
1360 if (fHandle != INVALID_HANDLE_VALUE) {
1361 return fillMetaData(fHandle, data, what);
1362 }
1363 return false;
1364}
1365
1366//static
1367bool QFileSystemEngine::fillMetaData(HANDLE fHandle, QFileSystemMetaData &data,
1368 QFileSystemMetaData::MetaDataFlags what)
1369{
1370 data.entryFlags &= ~what;
1371 clearWinStatData(data);
1372 BY_HANDLE_FILE_INFORMATION fileInfo;
1373 UINT oldmode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
1374 if (GetFileInformationByHandle(fHandle , &fileInfo)) {
1375 data.fillFromFindInfo(fileInfo);
1376 }
1377 SetErrorMode(oldmode);
1378 return data.hasFlags(what);
1379}
1380
1381//static
1382bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemMetaData &data,
1383 QFileSystemMetaData::MetaDataFlags what)
1384{
1385 Q_CHECK_FILE_NAME(entry, false);
1386 what |= QFileSystemMetaData::WinLnkType | QFileSystemMetaData::WinStatFlags;
1387 data.entryFlags &= ~what;
1388
1389 QFileSystemEntry fname;
1390 data.knownFlagsMask |= QFileSystemMetaData::WinLnkType;
1391 // Check for ".lnk": Directories named ".lnk" should be skipped, corrupted
1392 // link files should still be detected as links.
1393 const QString origFilePath = entry.filePath();
1394 if (bool exists; origFilePath.endsWith(".lnk"_L1) && !isDirPath(origFilePath, &exists) && exists) {
1395 data.entryFlags |= QFileSystemMetaData::WinLnkType;
1396 fname = QFileSystemEntry(readLink(entry));
1397 } else {
1398 fname = entry;
1399 }
1400
1401 if (fname.isEmpty()) {
1402 data.knownFlagsMask |= what;
1403 clearWinStatData(data);
1404 return false;
1405 }
1406
1407 if (what & QFileSystemMetaData::WinStatFlags) {
1408 UINT oldmode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
1409 clearWinStatData(data);
1410 WIN32_FIND_DATA findData;
1411 // The memory structure for WIN32_FIND_DATA is same as WIN32_FILE_ATTRIBUTE_DATA
1412 // for all members used by fillFindData().
1413 bool ok = ::GetFileAttributesEx(
1414 reinterpret_cast<const wchar_t *>(fname.nativeFilePath().utf16()),
1415 GetFileExInfoStandard, reinterpret_cast<WIN32_FILE_ATTRIBUTE_DATA *>(&findData));
1416 if (ok) {
1417 data.fillFromFindData(findData, false, fname.isDriveRoot());
1418 } else {
1419 const DWORD lastError = GetLastError();
1420 // disconnected drive
1421 if (lastError == ERROR_LOGON_FAILURE || lastError == ERROR_BAD_NETPATH
1422 || (!tryFindFallback(fname, data) && !tryDriveUNCFallback(fname, data))) {
1423 data.clearFlags();
1424 SetErrorMode(oldmode);
1425 return false;
1426 }
1427 }
1428 SetErrorMode(oldmode);
1429 }
1430
1431 if (what & QFileSystemMetaData::Permissions)
1432 fillPermissions(fname, data, what);
1433 if (what & QFileSystemMetaData::LinkType) {
1434 data.knownFlagsMask |= QFileSystemMetaData::LinkType;
1435 if (data.fileAttribute_ & FILE_ATTRIBUTE_REPARSE_POINT) {
1436 WIN32_FIND_DATA findData;
1437 if (getFindData(fname.nativeFilePath(), findData))
1438 data.fillFromFindData(findData, true);
1439 }
1440 }
1441 data.knownFlagsMask |= what;
1442 return data.hasFlags(what);
1443}
1444
1445static inline bool mkDir(const QString &path, SECURITY_ATTRIBUTES *securityAttributes,
1446 DWORD *lastError = nullptr)
1447{
1448 if (lastError)
1449 *lastError = 0;
1450 const QString longPath = QFSFileEnginePrivate::longFileName(path);
1451 const bool result = ::CreateDirectory((wchar_t *)longPath.utf16(), securityAttributes);
1452 // Capture lastError before any QString is freed since custom allocators might change it.
1453 if (lastError)
1454 *lastError = GetLastError();
1455 return result;
1456}
1457
1458static inline bool rmDir(const QString &path)
1459{
1460 return ::RemoveDirectory((wchar_t*)QFSFileEnginePrivate::longFileName(path).utf16());
1461}
1462
1463//static
1464bool QFileSystemEngine::isDirPath(const QString &dirPath, bool *existed)
1465{
1466 QString path = dirPath;
1467 if (path.length() == 2 && path.at(1) == u':')
1468 path += u'\\';
1469
1470 const QString longPath = QFSFileEnginePrivate::longFileName(path);
1471 DWORD fileAttrib = ::GetFileAttributes(reinterpret_cast<const wchar_t*>(longPath.utf16()));
1472 if (fileAttrib == INVALID_FILE_ATTRIBUTES) {
1473 int errorCode = GetLastError();
1474 if (errorCode == ERROR_ACCESS_DENIED || errorCode == ERROR_SHARING_VIOLATION) {
1475 WIN32_FIND_DATA findData;
1476 if (getFindData(longPath, findData))
1477 fileAttrib = findData.dwFileAttributes;
1478 }
1479 }
1480
1481 if (existed)
1482 *existed = fileAttrib != INVALID_FILE_ATTRIBUTES;
1483
1484 if (fileAttrib == INVALID_FILE_ATTRIBUTES)
1485 return false;
1486
1487 return fileAttrib & FILE_ATTRIBUTE_DIRECTORY;
1488}
1489
1490// NOTE: if \a shouldMkdirFirst is false, we assume the caller did try to mkdir
1491// before calling this function.
1492static bool createDirectoryWithParents(const QString &nativeName,
1493 SECURITY_ATTRIBUTES *securityAttributes,
1494 bool shouldMkdirFirst = true)
1495{
1496 const auto isUNCRoot = [](const QString &nativeName) {
1497 return nativeName.startsWith("\\\\"_L1)
1498 && nativeName.count(QDir::separator()) <= 3;
1499 };
1500 const auto isDriveName = [](const QString &nativeName) {
1501 return nativeName.size() == 2 && nativeName.at(1) == u':';
1502 };
1503 const auto isDir = [](const QString &nativeName) {
1504 bool exists = false;
1505 return QFileSystemEngine::isDirPath(nativeName, &exists) && exists;
1506 };
1507 // Do not try to mkdir a UNC root path or a drive letter.
1508 if (isUNCRoot(nativeName) || isDriveName(nativeName))
1509 return false;
1510
1511 if (shouldMkdirFirst) {
1512 if (mkDir(nativeName, securityAttributes))
1513 return true;
1514 }
1515
1516 const int backSlash = nativeName.lastIndexOf(QDir::separator());
1517 if (backSlash < 1)
1518 return false;
1519
1520 const QString parentNativeName = nativeName.left(backSlash);
1521 if (!createDirectoryWithParents(parentNativeName, securityAttributes))
1522 return false;
1523
1524 // try again
1525 if (mkDir(nativeName, securityAttributes))
1526 return true;
1527 return isDir(nativeName);
1528}
1529
1530bool QFileSystemEngine::mkpath(const QFileSystemEntry &entry,
1531 std::optional<QFile::Permissions> permissions)
1532{
1533 QString dirName = entry.filePath();
1534 Q_CHECK_FILE_NAME(dirName, false);
1535
1536 QNativeFilePermissions nativePermissions(permissions, true);
1537 if (!nativePermissions.isOk())
1538 return false;
1539
1540 auto securityAttributes = nativePermissions.securityAttributes();
1541 dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName));
1542
1543 // try to mkdir this directory
1544 DWORD lastError;
1545 if (mkDir(dirName, securityAttributes, &lastError))
1546 return true;
1547 // mkpath should return true, if the directory already exists
1548 if (lastError == ERROR_ALREADY_EXISTS || lastError == ERROR_ACCESS_DENIED)
1549 return isDirPath(dirName, nullptr);
1550
1551 return createDirectoryWithParents(dirName, securityAttributes, false);
1552}
1553
1554bool QFileSystemEngine::mkdir(const QFileSystemEntry &entry,
1555 std::optional<QFile::Permissions> permissions)
1556{
1557 QString dirName = entry.filePath();
1558 Q_CHECK_FILE_NAME(dirName, false);
1559
1560
1561 QNativeFilePermissions nativePermissions(permissions, true);
1562 if (!nativePermissions.isOk())
1563 return false;
1564
1565 dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName));
1566 return mkDir(dirName, nativePermissions.securityAttributes());
1567}
1568
1569bool QFileSystemEngine::rmdir(const QFileSystemEntry &entry)
1570{
1571 QString dirName = entry.filePath();
1572 Q_CHECK_FILE_NAME(dirName, false);
1573
1574 return rmDir(dirName);
1575}
1576
1577bool QFileSystemEngine::rmpath(const QFileSystemEntry &entry)
1578{
1579 const QString dirName = QDir::toNativeSeparators(QDir::cleanPath(entry.filePath()));
1580 Q_CHECK_FILE_NAME(dirName, false);
1581
1582 for (int oldslash = 0, slash = dirName.size(); slash > 0; oldslash = slash) {
1583 const auto chunkRef = QStringView{dirName}.left(slash);
1584 if (chunkRef.length() == 2 && chunkRef.at(0).isLetter()
1585 && chunkRef.at(1) == u':') {
1586 break;
1587 }
1588 const QString chunk = chunkRef.toString();
1589 // TODO: get isDirPath() and rmDir() to accept QStringView
1590 if (!isDirPath(chunk, nullptr))
1591 return false;
1592 if (!rmDir(chunk))
1593 return oldslash != 0;
1594 slash = dirName.lastIndexOf(QDir::separator(), oldslash - 1);
1595 }
1596
1597 return true;
1598}
1599
1600//static
1601QString QFileSystemEngine::rootPath()
1602{
1603 QString ret = QString::fromLatin1(qgetenv("SystemDrive"));
1604 if (ret.isEmpty())
1605 ret = "c:"_L1;
1606 ret.append(u'/');
1607 return ret;
1608}
1609
1610//static
1611QString QFileSystemEngine::homePath()
1612{
1613 QString ret;
1614#if QT_CONFIG(fslibs)
1615 initGlobalSid();
1616 {
1617 HANDLE hnd = ::GetCurrentProcess();
1618 HANDLE token = nullptr;
1619 BOOL ok = ::OpenProcessToken(hnd, TOKEN_QUERY, &token);
1620 if (ok) {
1621 DWORD dwBufferSize = 0;
1622 // First call, to determine size of the strings (with '\0').
1623 ok = GetUserProfileDirectory(token, nullptr, &dwBufferSize);
1624 if (!ok && dwBufferSize != 0) { // We got the required buffer size
1625 wchar_t *userDirectory = new wchar_t[dwBufferSize];
1626 // Second call, now we can fill the allocated buffer.
1627 ok = GetUserProfileDirectory(token, userDirectory, &dwBufferSize);
1628 if (ok)
1629 ret = QString::fromWCharArray(userDirectory);
1630 delete [] userDirectory;
1631 }
1632 ::CloseHandle(token);
1633 }
1634 }
1635#endif
1636 if (ret.isEmpty() || !QFile::exists(ret)) {
1637 ret = QString::fromLocal8Bit(qgetenv("USERPROFILE"));
1638 if (ret.isEmpty() || !QFile::exists(ret)) {
1639 ret = QString::fromLocal8Bit(qgetenv("HOMEDRIVE"))
1640 + QString::fromLocal8Bit(qgetenv("HOMEPATH"));
1641 if (ret.isEmpty() || !QFile::exists(ret)) {
1642 ret = QString::fromLocal8Bit(qgetenv("HOME"));
1643 if (ret.isEmpty() || !QFile::exists(ret))
1644 ret = rootPath();
1645 }
1646 }
1647 }
1648 return QDir::fromNativeSeparators(ret);
1649}
1650
1651QString QFileSystemEngine::tempPath()
1652{
1653 QString ret;
1654 wchar_t tempPath[MAX_PATH];
1655 using GetTempPathPrototype = DWORD (WINAPI *)(DWORD, LPWSTR);
1656 // We try to resolve GetTempPath2 and use that, otherwise fall back to GetTempPath:
1657 static GetTempPathPrototype getTempPathW = []() {
1658 const HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll");
1659 if (auto *func = QFunctionPointer(GetProcAddress(kernel32, "GetTempPath2W")))
1660 return GetTempPathPrototype(func);
1661 return GetTempPath;
1662 }();
1663 const DWORD len = getTempPathW(MAX_PATH, tempPath);
1664 if (len) { // GetTempPath() can return short names, expand.
1665 wchar_t longTempPath[MAX_PATH];
1666 const DWORD longLen = GetLongPathName(tempPath, longTempPath, MAX_PATH);
1667 ret = longLen && longLen < MAX_PATH ?
1668 QString::fromWCharArray(longTempPath, longLen) :
1669 QString::fromWCharArray(tempPath, len);
1670 }
1671 if (!ret.isEmpty()) {
1672 while (ret.endsWith(u'\\'))
1673 ret.chop(1);
1674 ret = QDir::fromNativeSeparators(ret);
1675 }
1676 if (ret.isEmpty()) {
1677 ret = "C:/tmp"_L1;
1678 } else if (ret.length() >= 2 && ret[1] == u':')
1679 ret[0] = ret.at(0).toUpper(); // Force uppercase drive letters.
1680 return ret;
1681}
1682
1683bool QFileSystemEngine::setCurrentPath(const QFileSystemEntry &entry)
1684{
1685 QFileSystemMetaData meta;
1686 fillMetaData(entry, meta,
1687 QFileSystemMetaData::ExistsAttribute | QFileSystemMetaData::DirectoryType);
1688 if (!(meta.exists() && meta.isDirectory()))
1689 return false;
1690
1691 // TODO: this should really be using nativeFilePath(), but that returns a path
1692 // in long format \\?\c:\foo which causes many problems later on when it's
1693 // returned through currentPath()
1694 return ::SetCurrentDirectory(reinterpret_cast<const wchar_t *>(
1695 QDir::toNativeSeparators(entry.filePath()).utf16()))
1696 != 0;
1697}
1698
1699QFileSystemEntry QFileSystemEngine::currentPath()
1700{
1701 QString ret(PATH_MAX, Qt::Uninitialized);
1702 DWORD size = GetCurrentDirectoryW(PATH_MAX, reinterpret_cast<wchar_t *>(ret.data()));
1703 if (size > PATH_MAX) {
1704 // try again after enlarging the buffer
1705 ret.resize(size);
1706 size = GetCurrentDirectoryW(size, reinterpret_cast<wchar_t *>(ret.data()));
1707
1708 // note: the current directory may have changed underneath us; if the
1709 // new one is even bigger, we may return a truncated string!
1710 }
1711 if (size >= 2 && ret.at(1) == u':')
1712 ret[0] = ret.at(0).toUpper(); // Force uppercase drive letters.
1713 ret.resize(size);
1714 return QFileSystemEntry(std::move(ret), QFileSystemEntry::FromNativePath());
1715}
1716
1717//static
1718bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSystemEntry &target,
1719 QSystemError &error)
1720{
1721 bool ret = false;
1722 QComHelper comHelper;
1723 IShellLink *psl = nullptr;
1724 HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink,
1725 reinterpret_cast<void **>(&psl));
1726
1727 if (SUCCEEDED(hres)) {
1728 const auto name = QDir::toNativeSeparators(source.filePath());
1729 const auto pathName = QDir::toNativeSeparators(source.path());
1730 if (SUCCEEDED(psl->SetPath(reinterpret_cast<const wchar_t *>(name.utf16())))
1731 && SUCCEEDED(psl->SetWorkingDirectory(
1732 reinterpret_cast<const wchar_t *>(pathName.utf16())))) {
1733 IPersistFile *ppf = nullptr;
1734 if (SUCCEEDED(psl->QueryInterface(IID_IPersistFile, reinterpret_cast<void **>(&ppf)))) {
1735 ret = SUCCEEDED(ppf->Save(
1736 reinterpret_cast<const wchar_t *>(target.filePath().utf16()), TRUE));
1737 ppf->Release();
1738 }
1739 }
1740 psl->Release();
1741 }
1742
1743 if (!ret)
1744 error = QSystemError(::GetLastError(), QSystemError::NativeError);
1745
1746 return ret;
1747}
1748
1749//static
1750bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target,
1751 QSystemError &error)
1752{
1753 bool ret = ::CopyFile((wchar_t*)source.nativeFilePath().utf16(),
1754 (wchar_t*)target.nativeFilePath().utf16(), true) != 0;
1755 if (!ret)
1756 error = QSystemError(::GetLastError(), QSystemError::NativeError);
1757 return ret;
1758}
1759
1760//static
1761bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target,
1762 QSystemError &error)
1763{
1764 Q_CHECK_FILE_NAME(source, false);
1765 Q_CHECK_FILE_NAME(target, false);
1766
1767 bool ret = ::MoveFile((wchar_t*)source.nativeFilePath().utf16(),
1768 (wchar_t*)target.nativeFilePath().utf16()) != 0;
1769 if (!ret)
1770 error = QSystemError(::GetLastError(), QSystemError::NativeError);
1771 return ret;
1772}
1773
1774//static
1775bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source,
1776 const QFileSystemEntry &target, QSystemError &error)
1777{
1778 Q_CHECK_FILE_NAME(source, false);
1779 Q_CHECK_FILE_NAME(target, false);
1780
1781 bool ret = ::MoveFileEx(reinterpret_cast<const wchar_t *>(source.nativeFilePath().utf16()),
1782 reinterpret_cast<const wchar_t *>(target.nativeFilePath().utf16()),
1783 MOVEFILE_REPLACE_EXISTING) != 0;
1784 if (!ret)
1785 error = QSystemError(::GetLastError(), QSystemError::NativeError);
1786 return ret;
1787}
1788
1789//static
1790bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError &error)
1791{
1792 Q_CHECK_FILE_NAME(entry, false);
1793
1794 bool ret = ::DeleteFile((wchar_t*)entry.nativeFilePath().utf16()) != 0;
1795 if (!ret)
1796 error = QSystemError(::GetLastError(), QSystemError::NativeError);
1797 return ret;
1798}
1799
1800//static
1801bool QFileSystemEngine::supportsMoveFileToTrash()
1802{
1803 return true;
1804}
1805
1806/*
1807 If possible, we use the IFileOperation implementation, which allows us to determine
1808 the location of the object in the trash.
1809 If not (likely on mingw), we fall back to the old API, which won't allow us to know
1810 that.
1811*/
1812//static
1813bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &source,
1814 QFileSystemEntry &newLocation, QSystemError &error)
1815{
1816 // we need the "display name" of the file, so can't use nativeAbsoluteFilePath
1817 const QString sourcePath = QDir::toNativeSeparators(absoluteName(source).filePath());
1818
1819 QComHelper comHelper;
1820
1821 IFileOperation *pfo = nullptr;
1822 IShellItem *deleteItem = nullptr;
1823 FileOperationProgressSink *sink = nullptr;
1824 HRESULT hres = E_FAIL;
1825
1826 auto coUninitialize = qScopeGuard([&](){
1827 if (sink)
1828 sink->Release();
1829 if (deleteItem)
1830 deleteItem->Release();
1831 if (pfo)
1832 pfo->Release();
1833 if (!SUCCEEDED(hres))
1834 error = QSystemError(hres, QSystemError::NativeError);
1835 });
1836
1837 hres = CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pfo));
1838 if (!pfo)
1839 return false;
1840 pfo->SetOperationFlags(FOF_ALLOWUNDO | FOFX_RECYCLEONDELETE | FOF_NOCONFIRMATION
1841 | FOF_SILENT | FOF_NOERRORUI);
1842 hres = SHCreateItemFromParsingName(reinterpret_cast<const wchar_t*>(sourcePath.utf16()),
1843 nullptr, IID_PPV_ARGS(&deleteItem));
1844 if (!deleteItem)
1845 return false;
1846 sink = new FileOperationProgressSink;
1847 hres = pfo->DeleteItem(deleteItem, static_cast<IFileOperationProgressSink*>(sink));
1848 if (!SUCCEEDED(hres))
1849 return false;
1850 hres = pfo->PerformOperations();
1851 if (!SUCCEEDED(hres))
1852 return false;
1853
1854 if (!SUCCEEDED(sink->deleteResult)) {
1855 error = QSystemError(sink->deleteResult, QSystemError::NativeError);
1856 return false;
1857 }
1858 newLocation = QFileSystemEntry(sink->targetPath);
1859 return true;
1860}
1861
1862//static
1863bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry,
1864 QFile::Permissions permissions, QSystemError &error)
1865{
1866 Q_CHECK_FILE_NAME(entry, false);
1867
1868 int mode = 0;
1869
1870 if (permissions & (QFile::ReadOwner | QFile::ReadUser | QFile::ReadGroup | QFile::ReadOther))
1871 mode |= _S_IREAD;
1872 if (permissions
1873 & (QFile::WriteOwner | QFile::WriteUser | QFile::WriteGroup | QFile::WriteOther)) {
1874 mode |= _S_IWRITE;
1875 }
1876
1877 if (mode == 0) // not supported
1878 return false;
1879
1880 bool ret =
1881 ::_wchmod(reinterpret_cast<const wchar_t *>(entry.nativeFilePath().utf16()), mode) == 0;
1882 if (!ret)
1883 error = QSystemError(errno, QSystemError::StandardLibraryError);
1884 return ret;
1885}
1886
1887bool QFileSystemEngine::isCaseSensitive(const QFileSystemEntry &, QFileSystemMetaData &)
1888{
1889 // FIXME: This may not be accurate for all file systems (QTBUG-28246)
1890 return false;
1891}
1892
1893static inline QDateTime fileTimeToQDateTime(const FILETIME *time)
1894{
1895 if (time->dwHighDateTime == 0 && time->dwLowDateTime == 0)
1896 return QDateTime();
1897
1898 SYSTEMTIME sTime;
1899 FileTimeToSystemTime(time, &sTime);
1900 return QDateTime(QDate(sTime.wYear, sTime.wMonth, sTime.wDay),
1901 QTime(sTime.wHour, sTime.wMinute, sTime.wSecond, sTime.wMilliseconds),
1902 QTimeZone::UTC);
1903}
1904
1905QDateTime QFileSystemMetaData::birthTime() const
1906{
1907 return fileTimeToQDateTime(&birthTime_);
1908}
1909QDateTime QFileSystemMetaData::metadataChangeTime() const
1910{
1911 return fileTimeToQDateTime(&changeTime_);
1912}
1913QDateTime QFileSystemMetaData::modificationTime() const
1914{
1915 return fileTimeToQDateTime(&lastWriteTime_);
1916}
1917QDateTime QFileSystemMetaData::accessTime() const
1918{
1919 return fileTimeToQDateTime(&lastAccessTime_);
1920}
1921
1922QT_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 bool rmDir(const QString &path)
static QByteArray fileId(HANDLE handle)
#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)
QByteArray 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