Warning: That file was not part of the compilation database. It may have many parsing errors.
1 | /**************************************************************************** |
---|---|
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtCore module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qfilesystemengine_p.h" |
41 | #include "qoperatingsystemversion.h" |
42 | #include "qplatformdefs.h" |
43 | #include "qsysinfo.h" |
44 | #include "private/qabstractfileengine_p.h" |
45 | #include "private/qfsfileengine_p.h" |
46 | #include <private/qsystemlibrary_p.h> |
47 | #include <qdebug.h> |
48 | |
49 | #include "qfile.h" |
50 | #include "qdir.h" |
51 | #include "qvarlengtharray.h" |
52 | #include "qdatetime.h" |
53 | #include "qt_windows.h" |
54 | #include "qvector.h" |
55 | |
56 | #include <sys/types.h> |
57 | #include <direct.h> |
58 | #include <winioctl.h> |
59 | #include <objbase.h> |
60 | #ifndef Q_OS_WINRT |
61 | # include <shlobj.h> |
62 | # include <lm.h> |
63 | # include <accctrl.h> |
64 | #endif |
65 | #include <initguid.h> |
66 | #include <ctype.h> |
67 | #include <limits.h> |
68 | #ifndef Q_OS_WINRT |
69 | # define SECURITY_WIN32 |
70 | # include <security.h> |
71 | #else // !Q_OS_WINRT |
72 | # include "qstandardpaths.h" |
73 | # include "qthreadstorage.h" |
74 | # include <wrl.h> |
75 | # include <windows.foundation.h> |
76 | # include <windows.storage.h> |
77 | # include <Windows.ApplicationModel.h> |
78 | |
79 | using namespace Microsoft::WRL; |
80 | using namespace Microsoft::WRL::Wrappers; |
81 | using namespace ABI::Windows::Foundation; |
82 | using namespace ABI::Windows::Storage; |
83 | using namespace ABI::Windows::ApplicationModel; |
84 | #endif // Q_OS_WINRT |
85 | |
86 | #ifndef SPI_GETPLATFORMTYPE |
87 | #define SPI_GETPLATFORMTYPE 257 |
88 | #endif |
89 | |
90 | #ifndef PATH_MAX |
91 | #define PATH_MAX FILENAME_MAX |
92 | #endif |
93 | |
94 | #ifndef _INTPTR_T_DEFINED |
95 | #ifdef _WIN64 |
96 | typedef __int64 intptr_t; |
97 | #else |
98 | #ifdef _W64 |
99 | typedef _W64 int intptr_t; |
100 | #else |
101 | typedef INT_PTR intptr_t; |
102 | #endif |
103 | #endif |
104 | #define _INTPTR_T_DEFINED |
105 | #endif |
106 | |
107 | #ifndef INVALID_FILE_ATTRIBUTES |
108 | # define INVALID_FILE_ATTRIBUTES (DWORD (-1)) |
109 | #endif |
110 | |
111 | #if !defined(REPARSE_DATA_BUFFER_HEADER_SIZE) |
112 | typedef struct _REPARSE_DATA_BUFFER { |
113 | ULONG ReparseTag; |
114 | USHORT ReparseDataLength; |
115 | USHORT Reserved; |
116 | union { |
117 | struct { |
118 | USHORT SubstituteNameOffset; |
119 | USHORT SubstituteNameLength; |
120 | USHORT PrintNameOffset; |
121 | USHORT PrintNameLength; |
122 | ULONG Flags; |
123 | WCHAR PathBuffer[1]; |
124 | } SymbolicLinkReparseBuffer; |
125 | struct { |
126 | USHORT SubstituteNameOffset; |
127 | USHORT SubstituteNameLength; |
128 | USHORT PrintNameOffset; |
129 | USHORT PrintNameLength; |
130 | WCHAR PathBuffer[1]; |
131 | } MountPointReparseBuffer; |
132 | struct { |
133 | UCHAR DataBuffer[1]; |
134 | } GenericReparseBuffer; |
135 | }; |
136 | } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; |
137 | # define REPARSE_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer) |
138 | #endif // !defined(REPARSE_DATA_BUFFER_HEADER_SIZE) |
139 | |
140 | #ifndef MAXIMUM_REPARSE_DATA_BUFFER_SIZE |
141 | # define MAXIMUM_REPARSE_DATA_BUFFER_SIZE 16384 |
142 | #endif |
143 | #ifndef IO_REPARSE_TAG_SYMLINK |
144 | # define IO_REPARSE_TAG_SYMLINK (0xA000000CL) |
145 | #endif |
146 | #ifndef FSCTL_GET_REPARSE_POINT |
147 | # define FSCTL_GET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS) |
148 | #endif |
149 | |
150 | #if defined(Q_OS_WINRT) || defined(QT_BOOTSTRAPPED) |
151 | # define QT_FEATURE_fslibs -1 |
152 | #else |
153 | # define QT_FEATURE_fslibs 1 |
154 | #endif // Q_OS_WINRT |
155 | |
156 | #if QT_CONFIG(fslibs) |
157 | #include <aclapi.h> |
158 | #include <userenv.h> |
159 | static TRUSTEE_W currentUserTrusteeW; |
160 | static TRUSTEE_W worldTrusteeW; |
161 | static PSID currentUserSID = 0; |
162 | static PSID worldSID = 0; |
163 | static HANDLE currentUserImpersonatedToken = nullptr; |
164 | |
165 | QT_BEGIN_NAMESPACE |
166 | |
167 | namespace { |
168 | struct GlobalSid |
169 | { |
170 | GlobalSid(); |
171 | ~GlobalSid(); |
172 | }; |
173 | |
174 | GlobalSid::~GlobalSid() |
175 | { |
176 | free(currentUserSID); |
177 | currentUserSID = 0; |
178 | |
179 | // worldSID was allocated with AllocateAndInitializeSid so it needs to be freed with FreeSid |
180 | if (worldSID) { |
181 | ::FreeSid(worldSID); |
182 | worldSID = 0; |
183 | } |
184 | |
185 | if (currentUserImpersonatedToken) { |
186 | ::CloseHandle(currentUserImpersonatedToken); |
187 | currentUserImpersonatedToken = nullptr; |
188 | } |
189 | } |
190 | |
191 | GlobalSid::GlobalSid() |
192 | { |
193 | { |
194 | { |
195 | // Create TRUSTEE for current user |
196 | HANDLE hnd = ::GetCurrentProcess(); |
197 | HANDLE token = 0; |
198 | if (::OpenProcessToken(hnd, TOKEN_QUERY, &token)) { |
199 | DWORD retsize = 0; |
200 | // GetTokenInformation requires a buffer big enough for the TOKEN_USER struct and |
201 | // the SID struct. Since the SID struct can have variable number of subauthorities |
202 | // tacked at the end, its size is variable. Obtain the required size by first |
203 | // doing a dummy GetTokenInformation call. |
204 | ::GetTokenInformation(token, TokenUser, 0, 0, &retsize); |
205 | if (retsize) { |
206 | void *tokenBuffer = malloc(retsize); |
207 | Q_CHECK_PTR(tokenBuffer); |
208 | if (::GetTokenInformation(token, TokenUser, tokenBuffer, retsize, &retsize)) { |
209 | PSID tokenSid = reinterpret_cast<PTOKEN_USER>(tokenBuffer)->User.Sid; |
210 | DWORD sidLen = ::GetLengthSid(tokenSid); |
211 | currentUserSID = reinterpret_cast<PSID>(malloc(sidLen)); |
212 | Q_CHECK_PTR(currentUserSID); |
213 | if (::CopySid(sidLen, currentUserSID, tokenSid)) |
214 | BuildTrusteeWithSid(¤tUserTrusteeW, currentUserSID); |
215 | } |
216 | free(tokenBuffer); |
217 | } |
218 | ::CloseHandle(token); |
219 | } |
220 | |
221 | token = nullptr; |
222 | if (::OpenProcessToken(hnd, TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ, &token)) { |
223 | ::DuplicateToken(token, SecurityImpersonation, ¤tUserImpersonatedToken); |
224 | ::CloseHandle(token); |
225 | } |
226 | |
227 | { |
228 | // Create TRUSTEE for Everyone (World) |
229 | SID_IDENTIFIER_AUTHORITY worldAuth = { SECURITY_WORLD_SID_AUTHORITY }; |
230 | if (AllocateAndInitializeSid(&worldAuth, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &worldSID)) |
231 | BuildTrusteeWithSid(&worldTrusteeW, worldSID); |
232 | } |
233 | } |
234 | } |
235 | } |
236 | |
237 | Q_GLOBAL_STATIC(GlobalSid, initGlobalSid) |
238 | |
239 | QT_END_NAMESPACE |
240 | |
241 | } // anonymous namespace |
242 | #endif // QT_CONFIG(fslibs) |
243 | |
244 | QT_BEGIN_NAMESPACE |
245 | |
246 | Q_CORE_EXPORT int qt_ntfs_permission_lookup = 0; |
247 | |
248 | static inline bool toFileTime(const QDateTime &date, FILETIME *fileTime) |
249 | { |
250 | SYSTEMTIME sTime; |
251 | if (date.timeSpec() == Qt::LocalTime) { |
252 | SYSTEMTIME lTime; |
253 | const QDate d = date.date(); |
254 | const QTime t = date.time(); |
255 | |
256 | lTime.wYear = d.year(); |
257 | lTime.wMonth = d.month(); |
258 | lTime.wDay = d.day(); |
259 | lTime.wHour = t.hour(); |
260 | lTime.wMinute = t.minute(); |
261 | lTime.wSecond = t.second(); |
262 | lTime.wMilliseconds = t.msec(); |
263 | lTime.wDayOfWeek = d.dayOfWeek() % 7; |
264 | |
265 | if (!::TzSpecificLocalTimeToSystemTime(0, &lTime, &sTime)) |
266 | return false; |
267 | } else { |
268 | QDateTime utcDate = date.toUTC(); |
269 | const QDate d = utcDate.date(); |
270 | const QTime t = utcDate.time(); |
271 | |
272 | sTime.wYear = d.year(); |
273 | sTime.wMonth = d.month(); |
274 | sTime.wDay = d.day(); |
275 | sTime.wHour = t.hour(); |
276 | sTime.wMinute = t.minute(); |
277 | sTime.wSecond = t.second(); |
278 | sTime.wMilliseconds = t.msec(); |
279 | sTime.wDayOfWeek = d.dayOfWeek() % 7; |
280 | } |
281 | |
282 | return ::SystemTimeToFileTime(&sTime, fileTime); |
283 | } |
284 | |
285 | static QString readSymLink(const QFileSystemEntry &link) |
286 | { |
287 | QString result; |
288 | #if !defined(Q_OS_WINRT) |
289 | HANDLE handle = CreateFile((wchar_t*)link.nativeFilePath().utf16(), |
290 | FILE_READ_EA, |
291 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
292 | 0, |
293 | OPEN_EXISTING, |
294 | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, |
295 | 0); |
296 | if (handle != INVALID_HANDLE_VALUE) { |
297 | DWORD bufsize = MAXIMUM_REPARSE_DATA_BUFFER_SIZE; |
298 | REPARSE_DATA_BUFFER *rdb = (REPARSE_DATA_BUFFER*)malloc(bufsize); |
299 | Q_CHECK_PTR(rdb); |
300 | DWORD retsize = 0; |
301 | if (::DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, 0, 0, rdb, bufsize, &retsize, 0)) { |
302 | if (rdb->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { |
303 | int length = rdb->MountPointReparseBuffer.SubstituteNameLength / sizeof(wchar_t); |
304 | int offset = rdb->MountPointReparseBuffer.SubstituteNameOffset / sizeof(wchar_t); |
305 | const wchar_t* PathBuffer = &rdb->MountPointReparseBuffer.PathBuffer[offset]; |
306 | result = QString::fromWCharArray(PathBuffer, length); |
307 | } else if (rdb->ReparseTag == IO_REPARSE_TAG_SYMLINK) { |
308 | int length = rdb->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(wchar_t); |
309 | int offset = rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(wchar_t); |
310 | const wchar_t* PathBuffer = &rdb->SymbolicLinkReparseBuffer.PathBuffer[offset]; |
311 | result = QString::fromWCharArray(PathBuffer, length); |
312 | } |
313 | // cut-off "\\?\" and "\??\" |
314 | if (result.size() > 4 |
315 | && result.at(0) == QLatin1Char('\\') |
316 | && result.at(2) == QLatin1Char('?') |
317 | && result.at(3) == QLatin1Char('\\')) { |
318 | result = result.mid(4); |
319 | // cut off UNC in addition when the link points at a UNC share |
320 | // in which case we need to prepend another backslash to get \\server\share |
321 | if (result.leftRef(3) == QLatin1String("UNC")) { |
322 | result.replace(0, 3, QLatin1Char('\\')); |
323 | } |
324 | } |
325 | } |
326 | free(rdb); |
327 | CloseHandle(handle); |
328 | |
329 | #if QT_CONFIG(fslibs) |
330 | initGlobalSid(); |
331 | QRegExp matchVolName(QLatin1String("^Volume\\{([a-z]|[0-9]|-)+\\}\\\\"), Qt::CaseInsensitive); |
332 | if (matchVolName.indexIn(result) == 0) { |
333 | DWORD len; |
334 | wchar_t buffer[MAX_PATH]; |
335 | const QString volumeName = QLatin1String("\\\\?\\") + result.leftRef(matchVolName.matchedLength()); |
336 | if (GetVolumePathNamesForVolumeName(reinterpret_cast<LPCWSTR>(volumeName.utf16()), buffer, MAX_PATH, &len) != 0) |
337 | result.replace(0,matchVolName.matchedLength(), QString::fromWCharArray(buffer)); |
338 | } |
339 | #endif // QT_CONFIG(fslibs) |
340 | } |
341 | #else |
342 | Q_UNUSED(link); |
343 | #endif // Q_OS_WINRT |
344 | return result; |
345 | } |
346 | |
347 | static QString readLink(const QFileSystemEntry &link) |
348 | { |
349 | #if QT_CONFIG(fslibs) |
350 | QString ret; |
351 | |
352 | bool neededCoInit = false; |
353 | IShellLink *psl; // pointer to IShellLink i/f |
354 | WIN32_FIND_DATA wfd; |
355 | wchar_t szGotPath[MAX_PATH]; |
356 | |
357 | // Get pointer to the IShellLink interface. |
358 | HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&psl); |
359 | |
360 | if (hres == CO_E_NOTINITIALIZED) { // COM was not initialized |
361 | neededCoInit = true; |
362 | CoInitialize(NULL); |
363 | hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, |
364 | IID_IShellLink, (LPVOID *)&psl); |
365 | } |
366 | if (SUCCEEDED(hres)) { // Get pointer to the IPersistFile interface. |
367 | IPersistFile *ppf; |
368 | hres = psl->QueryInterface(IID_IPersistFile, (LPVOID *)&ppf); |
369 | if (SUCCEEDED(hres)) { |
370 | hres = ppf->Load((LPOLESTR)link.nativeFilePath().utf16(), STGM_READ); |
371 | //The original path of the link is retrieved. If the file/folder |
372 | //was moved, the return value still have the old path. |
373 | if (SUCCEEDED(hres)) { |
374 | if (psl->GetPath(szGotPath, MAX_PATH, &wfd, SLGP_UNCPRIORITY) == NOERROR) |
375 | ret = QString::fromWCharArray(szGotPath); |
376 | } |
377 | ppf->Release(); |
378 | } |
379 | psl->Release(); |
380 | } |
381 | if (neededCoInit) |
382 | CoUninitialize(); |
383 | |
384 | return ret; |
385 | #else |
386 | Q_UNUSED(link); |
387 | return QString(); |
388 | #endif // QT_CONFIG(fslibs) |
389 | } |
390 | |
391 | static bool uncShareExists(const QString &server) |
392 | { |
393 | // This code assumes the UNC path is always like \\?\UNC\server... |
394 | const QVector<QStringRef> parts = server.splitRef(QLatin1Char('\\'), QString::SkipEmptyParts); |
395 | if (parts.count() >= 3) { |
396 | QStringList shares; |
397 | if (QFileSystemEngine::uncListSharesOnServer(QLatin1String("\\\\") + parts.at(2), &shares)) |
398 | return parts.count() < 4 || shares.contains(parts.at(3).toString(), Qt::CaseInsensitive); |
399 | } |
400 | return false; |
401 | } |
402 | |
403 | static inline bool getFindData(QString path, WIN32_FIND_DATA &findData) |
404 | { |
405 | // path should not end with a trailing slash |
406 | while (path.endsWith(QLatin1Char('\\'))) |
407 | path.chop(1); |
408 | |
409 | // can't handle drives |
410 | if (!path.endsWith(QLatin1Char(':'))) { |
411 | #ifndef Q_OS_WINRT |
412 | HANDLE hFind = ::FindFirstFile((wchar_t*)path.utf16(), &findData); |
413 | #else |
414 | HANDLE hFind = ::FindFirstFileEx((const wchar_t*)path.utf16(), FindExInfoStandard, &findData, FindExSearchNameMatch, NULL, 0); |
415 | #endif |
416 | if (hFind != INVALID_HANDLE_VALUE) { |
417 | ::FindClose(hFind); |
418 | return true; |
419 | } |
420 | } |
421 | |
422 | return false; |
423 | } |
424 | |
425 | bool QFileSystemEngine::uncListSharesOnServer(const QString &server, QStringList *list) |
426 | { |
427 | DWORD res = ERROR_NOT_SUPPORTED; |
428 | #ifndef Q_OS_WINRT |
429 | SHARE_INFO_1 *BufPtr, *p; |
430 | DWORD er = 0, tr = 0, resume = 0, i; |
431 | do { |
432 | res = NetShareEnum((wchar_t*)server.utf16(), 1, (LPBYTE *)&BufPtr, DWORD(-1), &er, &tr, &resume); |
433 | if (res == ERROR_SUCCESS || res == ERROR_MORE_DATA) { |
434 | p = BufPtr; |
435 | for (i = 1; i <= er; ++i) { |
436 | if (list && p->shi1_type == 0) |
437 | list->append(QString::fromWCharArray(p->shi1_netname)); |
438 | p++; |
439 | } |
440 | } |
441 | NetApiBufferFree(BufPtr); |
442 | } while (res == ERROR_MORE_DATA); |
443 | #else |
444 | Q_UNUSED(server); |
445 | Q_UNUSED(list); |
446 | #endif |
447 | return res == ERROR_SUCCESS; |
448 | } |
449 | |
450 | void QFileSystemEngine::clearWinStatData(QFileSystemMetaData &data) |
451 | { |
452 | data.size_ = 0; |
453 | data.fileAttribute_ = 0; |
454 | data.birthTime_ = FILETIME(); |
455 | data.changeTime_ = FILETIME(); |
456 | data.lastAccessTime_ = FILETIME(); |
457 | data.lastWriteTime_ = FILETIME(); |
458 | } |
459 | |
460 | //static |
461 | QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, |
462 | QFileSystemMetaData &data) |
463 | { |
464 | if (data.missingFlags(QFileSystemMetaData::LinkType)) |
465 | QFileSystemEngine::fillMetaData(link, data, QFileSystemMetaData::LinkType); |
466 | |
467 | QString target; |
468 | if (data.isLnkFile()) |
469 | target = readLink(link); |
470 | else if (data.isLink()) |
471 | target = readSymLink(link); |
472 | QFileSystemEntry ret(target); |
473 | if (!target.isEmpty() && ret.isRelative()) { |
474 | target.prepend(absoluteName(link).path() + QLatin1Char('/')); |
475 | ret = QFileSystemEntry(QDir::cleanPath(target)); |
476 | } |
477 | return ret; |
478 | } |
479 | |
480 | //static |
481 | QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data) |
482 | { |
483 | if (data.missingFlags(QFileSystemMetaData::ExistsAttribute)) |
484 | QFileSystemEngine::fillMetaData(entry, data, QFileSystemMetaData::ExistsAttribute); |
485 | |
486 | if (data.exists()) |
487 | return QFileSystemEntry(slowCanonicalized(absoluteName(entry).filePath())); |
488 | else |
489 | return QFileSystemEntry(); |
490 | } |
491 | |
492 | //static |
493 | QString QFileSystemEngine::nativeAbsoluteFilePath(const QString &path) |
494 | { |
495 | // can be //server or //server/share |
496 | QString absPath; |
497 | QVarLengthArray<wchar_t, MAX_PATH> buf(qMax(MAX_PATH, path.size() + 1)); |
498 | wchar_t *fileName = 0; |
499 | DWORD retLen = GetFullPathName((wchar_t*)path.utf16(), buf.size(), buf.data(), &fileName); |
500 | if (retLen > (DWORD)buf.size()) { |
501 | buf.resize(retLen); |
502 | retLen = GetFullPathName((wchar_t*)path.utf16(), buf.size(), buf.data(), &fileName); |
503 | } |
504 | if (retLen != 0) |
505 | absPath = QString::fromWCharArray(buf.data(), retLen); |
506 | # if defined(Q_OS_WINRT) |
507 | // Win32 returns eg C:/ as root directory with a trailing /. |
508 | // WinRT returns the sandbox root without /. |
509 | // Also C:/../.. returns C:/ on Win32, while for WinRT it steps outside the package |
510 | // and goes beyond package root. Hence force the engine to stay inside |
511 | // the package. |
512 | const QString rootPath = QDir::toNativeSeparators(QDir::rootPath()); |
513 | if (absPath.size() < rootPath.size() && rootPath.startsWith(absPath)) |
514 | absPath = rootPath; |
515 | # endif // Q_OS_WINRT |
516 | |
517 | // This is really ugly, but GetFullPathName strips off whitespace at the end. |
518 | // If you for instance write ". " in the lineedit of QFileDialog, |
519 | // (which is an invalid filename) this function will strip the space off and viola, |
520 | // the file is later reported as existing. Therefore, we re-add the whitespace that |
521 | // was at the end of path in order to keep the filename invalid. |
522 | if (!path.isEmpty() && path.at(path.size() - 1) == QLatin1Char(' ')) |
523 | absPath.append(QLatin1Char(' ')); |
524 | return absPath; |
525 | } |
526 | |
527 | //static |
528 | QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry) |
529 | { |
530 | QString ret; |
531 | |
532 | if (!entry.isRelative()) { |
533 | if (entry.isAbsolute() && entry.isClean()) |
534 | ret = entry.filePath(); |
535 | else |
536 | ret = QDir::fromNativeSeparators(nativeAbsoluteFilePath(entry.filePath())); |
537 | } else { |
538 | ret = QDir::cleanPath(QDir::currentPath() + QLatin1Char('/') + entry.filePath()); |
539 | } |
540 | |
541 | #ifndef Q_OS_WINRT |
542 | // The path should be absolute at this point. |
543 | // From the docs : |
544 | // Absolute paths begin with the directory separator "/" |
545 | // (optionally preceded by a drive specification under Windows). |
546 | if (ret.at(0) != QLatin1Char('/')) { |
547 | Q_ASSERT(ret.length() >= 2); |
548 | Q_ASSERT(ret.at(0).isLetter()); |
549 | Q_ASSERT(ret.at(1) == QLatin1Char(':')); |
550 | |
551 | // Force uppercase drive letters. |
552 | ret[0] = ret.at(0).toUpper(); |
553 | } |
554 | #endif // !Q_OS_WINRT |
555 | return QFileSystemEntry(ret, QFileSystemEntry::FromInternalPath()); |
556 | } |
557 | |
558 | #if defined(Q_CC_MINGW) && WINVER < 0x0602 // Windows 8 onwards |
559 | |
560 | typedef struct _FILE_ID_INFO { |
561 | ULONGLONG VolumeSerialNumber; |
562 | FILE_ID_128 FileId; |
563 | } FILE_ID_INFO, *PFILE_ID_INFO; |
564 | |
565 | #endif // if defined (Q_CC_MINGW) && WINVER < 0x0602 |
566 | |
567 | // File ID for Windows up to version 7 and FAT32 drives |
568 | static inline QByteArray fileId(HANDLE handle) |
569 | { |
570 | #ifndef Q_OS_WINRT |
571 | BY_HANDLE_FILE_INFORMATION info; |
572 | if (GetFileInformationByHandle(handle, &info)) { |
573 | char buffer[sizeof "01234567:0123456701234567"]; |
574 | qsnprintf(buffer, sizeof(buffer), "%lx:%08lx%08lx", |
575 | info.dwVolumeSerialNumber, |
576 | info.nFileIndexHigh, |
577 | info.nFileIndexLow); |
578 | return buffer; |
579 | } |
580 | #else // !Q_OS_WINRT |
581 | Q_UNUSED(handle); |
582 | Q_UNIMPLEMENTED(); |
583 | #endif // Q_OS_WINRT |
584 | return QByteArray(); |
585 | } |
586 | |
587 | // File ID for Windows starting from version 8. |
588 | QByteArray fileIdWin8(HANDLE handle) |
589 | { |
590 | #if !defined(QT_BOOTSTRAPPED) && !defined(QT_BUILD_QMAKE) |
591 | QByteArray result; |
592 | FILE_ID_INFO infoEx; |
593 | if (GetFileInformationByHandleEx(handle, |
594 | static_cast<FILE_INFO_BY_HANDLE_CLASS>(18), // FileIdInfo in Windows 8 |
595 | &infoEx, sizeof(FILE_ID_INFO))) { |
596 | result = QByteArray::number(infoEx.VolumeSerialNumber, 16); |
597 | result += ':'; |
598 | // Note: MinGW-64's definition of FILE_ID_128 differs from the MSVC one. |
599 | result += QByteArray(reinterpret_cast<const char *>(&infoEx.FileId), int(sizeof(infoEx.FileId))).toHex(); |
600 | } else { |
601 | result = fileId(handle); // GetFileInformationByHandleEx() is observed to fail for FAT32, QTBUG-74759 |
602 | } |
603 | return result; |
604 | #else // !QT_BOOTSTRAPPED && !QT_BUILD_QMAKE |
605 | return fileId(handle); |
606 | #endif |
607 | } |
608 | |
609 | //static |
610 | QByteArray QFileSystemEngine::id(const QFileSystemEntry &entry) |
611 | { |
612 | QByteArray result; |
613 | |
614 | #ifndef Q_OS_WINRT |
615 | const HANDLE handle = |
616 | CreateFile((wchar_t*)entry.nativeFilePath().utf16(), 0, |
617 | FILE_SHARE_READ, NULL, OPEN_EXISTING, |
618 | FILE_FLAG_BACKUP_SEMANTICS, NULL); |
619 | #else // !Q_OS_WINRT |
620 | CREATEFILE2_EXTENDED_PARAMETERS params; |
621 | params.dwSize = sizeof(params); |
622 | params.dwFileAttributes = FILE_ATTRIBUTE_NORMAL; |
623 | params.dwFileFlags = FILE_FLAG_BACKUP_SEMANTICS; |
624 | params.dwSecurityQosFlags = SECURITY_ANONYMOUS; |
625 | params.lpSecurityAttributes = NULL; |
626 | params.hTemplateFile = NULL; |
627 | const HANDLE handle = |
628 | CreateFile2((const wchar_t*)entry.nativeFilePath().utf16(), 0, |
629 | FILE_SHARE_READ, OPEN_EXISTING, ¶ms); |
630 | #endif // Q_OS_WINRT |
631 | if (handle != INVALID_HANDLE_VALUE) { |
632 | result = id(handle); |
633 | CloseHandle(handle); |
634 | } |
635 | return result; |
636 | } |
637 | |
638 | //static |
639 | QByteArray QFileSystemEngine::id(HANDLE fHandle) |
640 | { |
641 | return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8 ? |
642 | fileIdWin8(HANDLE(fHandle)) : fileId(HANDLE(fHandle)); |
643 | } |
644 | |
645 | //static |
646 | bool QFileSystemEngine::setFileTime(HANDLE fHandle, const QDateTime &newDate, |
647 | QAbstractFileEngine::FileTime time, QSystemError &error) |
648 | { |
649 | FILETIME fTime; |
650 | FILETIME *pLastWrite = NULL; |
651 | FILETIME *pLastAccess = NULL; |
652 | FILETIME *pCreationTime = NULL; |
653 | |
654 | switch (time) { |
655 | case QAbstractFileEngine::ModificationTime: |
656 | pLastWrite = &fTime; |
657 | break; |
658 | |
659 | case QAbstractFileEngine::AccessTime: |
660 | pLastAccess = &fTime; |
661 | break; |
662 | |
663 | case QAbstractFileEngine::BirthTime: |
664 | pCreationTime = &fTime; |
665 | break; |
666 | |
667 | default: |
668 | error = QSystemError(ERROR_INVALID_PARAMETER, QSystemError::NativeError); |
669 | return false; |
670 | } |
671 | |
672 | if (!toFileTime(newDate, &fTime)) |
673 | return false; |
674 | |
675 | if (!::SetFileTime(fHandle, pCreationTime, pLastAccess, pLastWrite)) { |
676 | error = QSystemError(::GetLastError(), QSystemError::NativeError); |
677 | return false; |
678 | } |
679 | return true; |
680 | } |
681 | |
682 | QString QFileSystemEngine::owner(const QFileSystemEntry &entry, QAbstractFileEngine::FileOwner own) |
683 | { |
684 | QString name; |
685 | #if QT_CONFIG(fslibs) |
686 | extern int qt_ntfs_permission_lookup; |
687 | if (qt_ntfs_permission_lookup > 0) { |
688 | initGlobalSid(); |
689 | { |
690 | PSID pOwner = 0; |
691 | PSECURITY_DESCRIPTOR pSD; |
692 | if (GetNamedSecurityInfo(reinterpret_cast<const wchar_t*>(entry.nativeFilePath().utf16()), SE_FILE_OBJECT, |
693 | own == QAbstractFileEngine::OwnerGroup ? GROUP_SECURITY_INFORMATION : OWNER_SECURITY_INFORMATION, |
694 | own == QAbstractFileEngine::OwnerUser ? &pOwner : 0, own == QAbstractFileEngine::OwnerGroup ? &pOwner : 0, |
695 | 0, 0, &pSD) == ERROR_SUCCESS) { |
696 | DWORD lowner = 64; |
697 | DWORD ldomain = 64; |
698 | QVarLengthArray<wchar_t, 64> owner(lowner); |
699 | QVarLengthArray<wchar_t, 64> domain(ldomain); |
700 | SID_NAME_USE use = SidTypeUnknown; |
701 | // First call, to determine size of the strings (with '\0'). |
702 | if (!LookupAccountSid(NULL, pOwner, (LPWSTR)owner.data(), &lowner, |
703 | domain.data(), &ldomain, &use)) { |
704 | if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { |
705 | if (lowner > (DWORD)owner.size()) |
706 | owner.resize(lowner); |
707 | if (ldomain > (DWORD)domain.size()) |
708 | domain.resize(ldomain); |
709 | // Second call, try on resized buf-s |
710 | if (!LookupAccountSid(NULL, pOwner, owner.data(), &lowner, |
711 | domain.data(), &ldomain, &use)) { |
712 | lowner = 0; |
713 | } |
714 | } else { |
715 | lowner = 0; |
716 | } |
717 | } |
718 | if (lowner != 0) |
719 | name = QString::fromWCharArray(owner.data()); |
720 | LocalFree(pSD); |
721 | } |
722 | } |
723 | } |
724 | #else |
725 | Q_UNUSED(entry); |
726 | Q_UNUSED(own); |
727 | #endif |
728 | return name; |
729 | } |
730 | |
731 | //static |
732 | bool QFileSystemEngine::fillPermissions(const QFileSystemEntry &entry, QFileSystemMetaData &data, |
733 | QFileSystemMetaData::MetaDataFlags what) |
734 | { |
735 | #if QT_CONFIG(fslibs) |
736 | if (qt_ntfs_permission_lookup > 0) { |
737 | initGlobalSid(); |
738 | { |
739 | enum { ReadMask = 0x00000001, WriteMask = 0x00000002, ExecMask = 0x00000020 }; |
740 | |
741 | QString fname = entry.nativeFilePath(); |
742 | PSID pOwner = 0; |
743 | PSID pGroup = 0; |
744 | PACL pDacl; |
745 | PSECURITY_DESCRIPTOR pSD; |
746 | DWORD res = GetNamedSecurityInfo(reinterpret_cast<const wchar_t*>(fname.utf16()), SE_FILE_OBJECT, |
747 | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, |
748 | &pOwner, &pGroup, &pDacl, 0, &pSD); |
749 | if(res == ERROR_SUCCESS) { |
750 | ACCESS_MASK access_mask; |
751 | TRUSTEE_W trustee; |
752 | if (what & QFileSystemMetaData::UserPermissions) { // user |
753 | // Using AccessCheck because GetEffectiveRightsFromAcl doesn't account for elevation |
754 | if (currentUserImpersonatedToken) { |
755 | GENERIC_MAPPING mapping = {FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE, FILE_ALL_ACCESS}; |
756 | PRIVILEGE_SET privileges; |
757 | DWORD grantedAccess; |
758 | BOOL result; |
759 | |
760 | data.knownFlagsMask |= QFileSystemMetaData::UserPermissions; |
761 | DWORD genericAccessRights = GENERIC_READ; |
762 | ::MapGenericMask(&genericAccessRights, &mapping); |
763 | |
764 | DWORD privilegesLength = sizeof(privileges); |
765 | if (::AccessCheck(pSD, currentUserImpersonatedToken, genericAccessRights, |
766 | &mapping, &privileges, &privilegesLength, &grantedAccess, &result) && result) { |
767 | data.entryFlags |= QFileSystemMetaData::UserReadPermission; |
768 | } |
769 | |
770 | privilegesLength = sizeof(privileges); |
771 | genericAccessRights = GENERIC_WRITE; |
772 | ::MapGenericMask(&genericAccessRights, &mapping); |
773 | if (::AccessCheck(pSD, currentUserImpersonatedToken, genericAccessRights, |
774 | &mapping, &privileges, &privilegesLength, &grantedAccess, &result) && result) { |
775 | data.entryFlags |= QFileSystemMetaData::UserWritePermission; |
776 | } |
777 | |
778 | privilegesLength = sizeof(privileges); |
779 | genericAccessRights = GENERIC_EXECUTE; |
780 | ::MapGenericMask(&genericAccessRights, &mapping); |
781 | if (::AccessCheck(pSD, currentUserImpersonatedToken, genericAccessRights, |
782 | &mapping, &privileges, &privilegesLength, &grantedAccess, &result) && result) { |
783 | data.entryFlags |= QFileSystemMetaData::UserExecutePermission; |
784 | } |
785 | } else { // fallback to GetEffectiveRightsFromAcl |
786 | data.knownFlagsMask |= QFileSystemMetaData::UserPermissions; |
787 | if (GetEffectiveRightsFromAclW(pDacl, ¤tUserTrusteeW, &access_mask) != ERROR_SUCCESS) |
788 | access_mask = ACCESS_MASK(-1); |
789 | if (access_mask & ReadMask) |
790 | data.entryFlags |= QFileSystemMetaData::UserReadPermission; |
791 | if (access_mask & WriteMask) |
792 | data.entryFlags|= QFileSystemMetaData::UserWritePermission; |
793 | if (access_mask & ExecMask) |
794 | data.entryFlags|= QFileSystemMetaData::UserExecutePermission; |
795 | } |
796 | } |
797 | if (what & QFileSystemMetaData::OwnerPermissions) { // owner |
798 | data.knownFlagsMask |= QFileSystemMetaData::OwnerPermissions; |
799 | BuildTrusteeWithSid(&trustee, pOwner); |
800 | if (GetEffectiveRightsFromAcl(pDacl, &trustee, &access_mask) != ERROR_SUCCESS) |
801 | access_mask = (ACCESS_MASK)-1; |
802 | if(access_mask & ReadMask) |
803 | data.entryFlags |= QFileSystemMetaData::OwnerReadPermission; |
804 | if(access_mask & WriteMask) |
805 | data.entryFlags |= QFileSystemMetaData::OwnerWritePermission; |
806 | if(access_mask & ExecMask) |
807 | data.entryFlags |= QFileSystemMetaData::OwnerExecutePermission; |
808 | } |
809 | if (what & QFileSystemMetaData::GroupPermissions) { // group |
810 | data.knownFlagsMask |= QFileSystemMetaData::GroupPermissions; |
811 | BuildTrusteeWithSid(&trustee, pGroup); |
812 | if (GetEffectiveRightsFromAcl(pDacl, &trustee, &access_mask) != ERROR_SUCCESS) |
813 | access_mask = (ACCESS_MASK)-1; |
814 | if(access_mask & ReadMask) |
815 | data.entryFlags |= QFileSystemMetaData::GroupReadPermission; |
816 | if(access_mask & WriteMask) |
817 | data.entryFlags |= QFileSystemMetaData::GroupWritePermission; |
818 | if(access_mask & ExecMask) |
819 | data.entryFlags |= QFileSystemMetaData::GroupExecutePermission; |
820 | } |
821 | if (what & QFileSystemMetaData::OtherPermissions) { // other (world) |
822 | data.knownFlagsMask |= QFileSystemMetaData::OtherPermissions; |
823 | if (GetEffectiveRightsFromAcl(pDacl, &worldTrusteeW, &access_mask) != ERROR_SUCCESS) |
824 | access_mask = (ACCESS_MASK)-1; // ### |
825 | if(access_mask & ReadMask) |
826 | data.entryFlags |= QFileSystemMetaData::OtherReadPermission; |
827 | if(access_mask & WriteMask) |
828 | data.entryFlags |= QFileSystemMetaData::OtherWritePermission; |
829 | if(access_mask & ExecMask) |
830 | data.entryFlags |= QFileSystemMetaData::OwnerExecutePermission; |
831 | } |
832 | LocalFree(pSD); |
833 | } |
834 | } |
835 | } else |
836 | #endif |
837 | { |
838 | //### what to do with permissions if we don't use NTFS |
839 | // for now just add all permissions and what about exe missions ?? |
840 | // also qt_ntfs_permission_lookup is now not set by default ... should it ? |
841 | data.entryFlags |= QFileSystemMetaData::OwnerReadPermission |
842 | | QFileSystemMetaData::GroupReadPermission |
843 | | QFileSystemMetaData::OtherReadPermission; |
844 | |
845 | if (!(data.fileAttribute_ & FILE_ATTRIBUTE_READONLY)) { |
846 | data.entryFlags |= QFileSystemMetaData::OwnerWritePermission |
847 | | QFileSystemMetaData::GroupWritePermission |
848 | | QFileSystemMetaData::OtherWritePermission; |
849 | } |
850 | |
851 | QString fname = entry.filePath(); |
852 | QString ext = fname.right(4).toLower(); |
853 | if (data.isDirectory() || |
854 | ext == QLatin1String(".exe") || ext == QLatin1String( ".com") || ext == QLatin1String( ".bat") || |
855 | ext == QLatin1String(".pif") || ext == QLatin1String( ".cmd")) { |
856 | data.entryFlags |= QFileSystemMetaData::OwnerExecutePermission | QFileSystemMetaData::GroupExecutePermission |
857 | | QFileSystemMetaData::OtherExecutePermission | QFileSystemMetaData::UserExecutePermission; |
858 | } |
859 | data.knownFlagsMask |= QFileSystemMetaData::OwnerPermissions | QFileSystemMetaData::GroupPermissions |
860 | | QFileSystemMetaData::OtherPermissions | QFileSystemMetaData::UserExecutePermission; |
861 | // calculate user permissions |
862 | if (what & QFileSystemMetaData::UserReadPermission) { |
863 | if (::_waccess((wchar_t*)entry.nativeFilePath().utf16(), R_OK) == 0) |
864 | data.entryFlags |= QFileSystemMetaData::UserReadPermission; |
865 | data.knownFlagsMask |= QFileSystemMetaData::UserReadPermission; |
866 | } |
867 | if (what & QFileSystemMetaData::UserWritePermission) { |
868 | if (::_waccess((wchar_t*)entry.nativeFilePath().utf16(), W_OK) == 0) |
869 | data.entryFlags |= QFileSystemMetaData::UserWritePermission; |
870 | data.knownFlagsMask |= QFileSystemMetaData::UserWritePermission; |
871 | } |
872 | } |
873 | |
874 | return data.hasFlags(what); |
875 | } |
876 | |
877 | static bool tryDriveUNCFallback(const QFileSystemEntry &fname, QFileSystemMetaData &data) |
878 | { |
879 | bool entryExists = false; |
880 | DWORD fileAttrib = 0; |
881 | #if !defined(Q_OS_WINRT) |
882 | if (fname.isDriveRoot()) { |
883 | // a valid drive ?? |
884 | const UINT oldErrorMode = ::SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX); |
885 | DWORD drivesBitmask = ::GetLogicalDrives(); |
886 | ::SetErrorMode(oldErrorMode); |
887 | int drivebit = 1 << (fname.filePath().at(0).toUpper().unicode() - QLatin1Char('A').unicode()); |
888 | if (drivesBitmask & drivebit) { |
889 | fileAttrib = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM; |
890 | entryExists = true; |
891 | } |
892 | } else { |
893 | #endif |
894 | const QString &path = fname.nativeFilePath(); |
895 | bool is_dir = false; |
896 | if (path.startsWith(QLatin1String("\\\\?\\UNC"))) { |
897 | // UNC - stat doesn't work for all cases (Windows bug) |
898 | int s = path.indexOf(path.at(0),7); |
899 | if (s > 0) { |
900 | // "\\?\UNC\server\..." |
901 | s = path.indexOf(path.at(0),s+1); |
902 | if (s > 0) { |
903 | // "\\?\UNC\server\share\..." |
904 | if (s == path.size() - 1) { |
905 | // "\\?\UNC\server\share\" |
906 | is_dir = true; |
907 | } else { |
908 | // "\\?\UNC\server\share\notfound" |
909 | } |
910 | } else { |
911 | // "\\?\UNC\server\share" |
912 | is_dir = true; |
913 | } |
914 | } else { |
915 | // "\\?\UNC\server" |
916 | is_dir = true; |
917 | } |
918 | } |
919 | if (is_dir && uncShareExists(path)) { |
920 | // looks like a UNC dir, is a dir. |
921 | fileAttrib = FILE_ATTRIBUTE_DIRECTORY; |
922 | entryExists = true; |
923 | } |
924 | #if !defined(Q_OS_WINRT) |
925 | } |
926 | #endif |
927 | if (entryExists) |
928 | data.fillFromFileAttribute(fileAttrib); |
929 | return entryExists; |
930 | } |
931 | |
932 | static bool tryFindFallback(const QFileSystemEntry &fname, QFileSystemMetaData &data) |
933 | { |
934 | bool filledData = false; |
935 | // This assumes the last call to a Windows API failed. |
936 | int errorCode = GetLastError(); |
937 | if (errorCode == ERROR_ACCESS_DENIED || errorCode == ERROR_SHARING_VIOLATION) { |
938 | WIN32_FIND_DATA findData; |
939 | if (getFindData(fname.nativeFilePath(), findData) |
940 | && findData.dwFileAttributes != INVALID_FILE_ATTRIBUTES) { |
941 | data.fillFromFindData(findData, true, fname.isDriveRoot()); |
942 | filledData = true; |
943 | } |
944 | } |
945 | return filledData; |
946 | } |
947 | |
948 | //static |
949 | bool QFileSystemEngine::fillMetaData(int fd, QFileSystemMetaData &data, |
950 | QFileSystemMetaData::MetaDataFlags what) |
951 | { |
952 | auto fHandle = reinterpret_cast<HANDLE>(_get_osfhandle(fd)); |
953 | if (fHandle != INVALID_HANDLE_VALUE) { |
954 | return fillMetaData(fHandle, data, what); |
955 | } |
956 | return false; |
957 | } |
958 | |
959 | //static |
960 | bool QFileSystemEngine::fillMetaData(HANDLE fHandle, QFileSystemMetaData &data, |
961 | QFileSystemMetaData::MetaDataFlags what) |
962 | { |
963 | data.entryFlags &= ~what; |
964 | clearWinStatData(data); |
965 | #ifndef Q_OS_WINRT |
966 | BY_HANDLE_FILE_INFORMATION fileInfo; |
967 | UINT oldmode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX); |
968 | if (GetFileInformationByHandle(fHandle , &fileInfo)) { |
969 | data.fillFromFindInfo(fileInfo); |
970 | } |
971 | SetErrorMode(oldmode); |
972 | #else // !Q_OS_WINRT |
973 | FILE_BASIC_INFO fileBasicInfo; |
974 | if (GetFileInformationByHandleEx(fHandle, FileBasicInfo, &fileBasicInfo, sizeof(fileBasicInfo))) { |
975 | data.fillFromFileAttribute(fileBasicInfo.FileAttributes); |
976 | data.birthTime_.dwHighDateTime = fileBasicInfo.CreationTime.HighPart; |
977 | data.birthTime_.dwLowDateTime = fileBasicInfo.CreationTime.LowPart; |
978 | data.changeTime_.dwHighDateTime = fileBasicInfo.ChangeTime.HighPart; |
979 | data.changeTime_.dwLowDateTime = fileBasicInfo.ChangeTime.LowPart; |
980 | data.lastAccessTime_.dwHighDateTime = fileBasicInfo.LastAccessTime.HighPart; |
981 | data.lastAccessTime_.dwLowDateTime = fileBasicInfo.LastAccessTime.LowPart; |
982 | data.lastWriteTime_.dwHighDateTime = fileBasicInfo.LastWriteTime.HighPart; |
983 | data.lastWriteTime_.dwLowDateTime = fileBasicInfo.LastWriteTime.LowPart; |
984 | if (!(data.fileAttribute_ & FILE_ATTRIBUTE_DIRECTORY)) { |
985 | FILE_STANDARD_INFO fileStandardInfo; |
986 | if (GetFileInformationByHandleEx(fHandle, FileStandardInfo, &fileStandardInfo, sizeof(fileStandardInfo))) |
987 | data.size_ = fileStandardInfo.EndOfFile.QuadPart; |
988 | } else |
989 | data.size_ = 0; |
990 | data.knownFlagsMask |= QFileSystemMetaData::Times | QFileSystemMetaData::SizeAttribute; |
991 | } |
992 | #endif // Q_OS_WINRT |
993 | return data.hasFlags(what); |
994 | } |
995 | |
996 | static bool isDirPath(const QString &dirPath, bool *existed); |
997 | |
998 | //static |
999 | bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemMetaData &data, |
1000 | QFileSystemMetaData::MetaDataFlags what) |
1001 | { |
1002 | what |= QFileSystemMetaData::WinLnkType | QFileSystemMetaData::WinStatFlags; |
1003 | data.entryFlags &= ~what; |
1004 | |
1005 | QFileSystemEntry fname; |
1006 | data.knownFlagsMask |= QFileSystemMetaData::WinLnkType; |
1007 | // Check for ".lnk": Directories named ".lnk" should be skipped, corrupted |
1008 | // link files should still be detected as links. |
1009 | const QString origFilePath = entry.filePath(); |
1010 | if (origFilePath.endsWith(QLatin1String(".lnk")) && !isDirPath(origFilePath, 0)) { |
1011 | data.entryFlags |= QFileSystemMetaData::WinLnkType; |
1012 | fname = QFileSystemEntry(readLink(entry)); |
1013 | } else { |
1014 | fname = entry; |
1015 | } |
1016 | |
1017 | if (fname.isEmpty()) { |
1018 | data.knownFlagsMask |= what; |
1019 | clearWinStatData(data); |
1020 | return false; |
1021 | } |
1022 | |
1023 | if (what & QFileSystemMetaData::WinStatFlags) { |
1024 | #ifndef Q_OS_WINRT |
1025 | UINT oldmode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX); |
1026 | #endif |
1027 | clearWinStatData(data); |
1028 | WIN32_FIND_DATA findData; |
1029 | // The memory structure for WIN32_FIND_DATA is same as WIN32_FILE_ATTRIBUTE_DATA |
1030 | // for all members used by fillFindData(). |
1031 | bool ok = ::GetFileAttributesEx(reinterpret_cast<const wchar_t*>(fname.nativeFilePath().utf16()), |
1032 | GetFileExInfoStandard, |
1033 | reinterpret_cast<WIN32_FILE_ATTRIBUTE_DATA *>(&findData)); |
1034 | if (ok) { |
1035 | data.fillFromFindData(findData, false, fname.isDriveRoot()); |
1036 | } else { |
1037 | if (!tryFindFallback(fname, data)) |
1038 | if (!tryDriveUNCFallback(fname, data)) { |
1039 | #ifndef Q_OS_WINRT |
1040 | SetErrorMode(oldmode); |
1041 | #endif |
1042 | return false; |
1043 | } |
1044 | } |
1045 | #ifndef Q_OS_WINRT |
1046 | SetErrorMode(oldmode); |
1047 | #endif |
1048 | } |
1049 | |
1050 | if (what & QFileSystemMetaData::Permissions) |
1051 | fillPermissions(fname, data, what); |
1052 | if (what & QFileSystemMetaData::LinkType) { |
1053 | data.knownFlagsMask |= QFileSystemMetaData::LinkType; |
1054 | if (data.fileAttribute_ & FILE_ATTRIBUTE_REPARSE_POINT) { |
1055 | WIN32_FIND_DATA findData; |
1056 | if (getFindData(fname.nativeFilePath(), findData)) |
1057 | data.fillFromFindData(findData, true); |
1058 | } |
1059 | } |
1060 | data.knownFlagsMask |= what; |
1061 | return data.hasFlags(what); |
1062 | } |
1063 | |
1064 | static inline bool mkDir(const QString &path, DWORD *lastError = 0) |
1065 | { |
1066 | if (lastError) |
1067 | *lastError = 0; |
1068 | const QString longPath = QFSFileEnginePrivate::longFileName(path); |
1069 | const bool result = ::CreateDirectory((wchar_t*)longPath.utf16(), 0); |
1070 | if (lastError) // Capture lastError before any QString is freed since custom allocators might change it. |
1071 | *lastError = GetLastError(); |
1072 | return result; |
1073 | } |
1074 | |
1075 | static inline bool rmDir(const QString &path) |
1076 | { |
1077 | return ::RemoveDirectory((wchar_t*)QFSFileEnginePrivate::longFileName(path).utf16()); |
1078 | } |
1079 | |
1080 | static bool isDirPath(const QString &dirPath, bool *existed) |
1081 | { |
1082 | QString path = dirPath; |
1083 | if (path.length() == 2 && path.at(1) == QLatin1Char(':')) |
1084 | path += QLatin1Char('\\'); |
1085 | |
1086 | const QString longPath = QFSFileEnginePrivate::longFileName(path); |
1087 | #ifndef Q_OS_WINRT |
1088 | DWORD fileAttrib = ::GetFileAttributes(reinterpret_cast<const wchar_t*>(longPath.utf16())); |
1089 | #else // Q_OS_WINRT |
1090 | DWORD fileAttrib = INVALID_FILE_ATTRIBUTES; |
1091 | WIN32_FILE_ATTRIBUTE_DATA data; |
1092 | if (::GetFileAttributesEx(reinterpret_cast<const wchar_t*>(longPath.utf16()), |
1093 | GetFileExInfoStandard, &data)) { |
1094 | fileAttrib = data.dwFileAttributes; |
1095 | } |
1096 | #endif // Q_OS_WINRT |
1097 | if (fileAttrib == INVALID_FILE_ATTRIBUTES) { |
1098 | int errorCode = GetLastError(); |
1099 | if (errorCode == ERROR_ACCESS_DENIED || errorCode == ERROR_SHARING_VIOLATION) { |
1100 | WIN32_FIND_DATA findData; |
1101 | if (getFindData(longPath, findData)) |
1102 | fileAttrib = findData.dwFileAttributes; |
1103 | } |
1104 | } |
1105 | |
1106 | if (existed) |
1107 | *existed = fileAttrib != INVALID_FILE_ATTRIBUTES; |
1108 | |
1109 | if (fileAttrib == INVALID_FILE_ATTRIBUTES) |
1110 | return false; |
1111 | |
1112 | return fileAttrib & FILE_ATTRIBUTE_DIRECTORY; |
1113 | } |
1114 | |
1115 | //static |
1116 | bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents) |
1117 | { |
1118 | QString dirName = entry.filePath(); |
1119 | if (createParents) { |
1120 | dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName)); |
1121 | // We spefically search for / so \ would break it.. |
1122 | int oldslash = -1; |
1123 | if (dirName.startsWith(QLatin1String("\\\\"))) { |
1124 | // Don't try to create the root path of a UNC path; |
1125 | // CreateDirectory() will just return ERROR_INVALID_NAME. |
1126 | for (int i = 0; i < dirName.size(); ++i) { |
1127 | if (dirName.at(i) != QDir::separator()) { |
1128 | oldslash = i; |
1129 | break; |
1130 | } |
1131 | } |
1132 | if (oldslash != -1) |
1133 | oldslash = dirName.indexOf(QDir::separator(), oldslash); |
1134 | } else if (dirName.size() > 2 |
1135 | && dirName.at(1) == QLatin1Char(':')) { |
1136 | // Don't try to call mkdir with just a drive letter |
1137 | oldslash = 2; |
1138 | } |
1139 | for (int slash=0; slash != -1; oldslash = slash) { |
1140 | slash = dirName.indexOf(QDir::separator(), oldslash+1); |
1141 | if (slash == -1) { |
1142 | if (oldslash == dirName.length()) |
1143 | break; |
1144 | slash = dirName.length(); |
1145 | } |
1146 | if (slash) { |
1147 | DWORD lastError; |
1148 | QString chunk = dirName.left(slash); |
1149 | if (!mkDir(chunk, &lastError)) { |
1150 | if (lastError == ERROR_ALREADY_EXISTS || lastError == ERROR_ACCESS_DENIED) { |
1151 | bool existed = false; |
1152 | if (isDirPath(chunk, &existed) && existed) |
1153 | continue; |
1154 | #ifdef Q_OS_WINRT |
1155 | static QThreadStorage<QString> dataLocation; |
1156 | if (!dataLocation.hasLocalData()) |
1157 | dataLocation.setLocalData(QDir::toNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation))); |
1158 | static QThreadStorage<QString> tempLocation; |
1159 | if (!tempLocation.hasLocalData()) |
1160 | tempLocation.setLocalData(QDir::toNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::TempLocation))); |
1161 | // We try to create something outside the sandbox, which is forbidden |
1162 | // However we could still try to pass into the sandbox |
1163 | if (dataLocation.localData().startsWith(chunk) || tempLocation.localData().startsWith(chunk)) |
1164 | continue; |
1165 | #endif |
1166 | } |
1167 | return false; |
1168 | } |
1169 | } |
1170 | } |
1171 | return true; |
1172 | } |
1173 | return mkDir(entry.filePath()); |
1174 | } |
1175 | |
1176 | //static |
1177 | bool QFileSystemEngine::removeDirectory(const QFileSystemEntry &entry, bool removeEmptyParents) |
1178 | { |
1179 | QString dirName = entry.filePath(); |
1180 | if (removeEmptyParents) { |
1181 | dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName)); |
1182 | for (int oldslash = 0, slash=dirName.length(); slash > 0; oldslash = slash) { |
1183 | const QStringRef chunkRef = dirName.leftRef(slash); |
1184 | if (chunkRef.length() == 2 && chunkRef.at(0).isLetter() && chunkRef.at(1) == QLatin1Char(':')) |
1185 | break; |
1186 | const QString chunk = chunkRef.toString(); |
1187 | if (!isDirPath(chunk, 0)) |
1188 | return false; |
1189 | if (!rmDir(chunk)) |
1190 | return oldslash != 0; |
1191 | slash = dirName.lastIndexOf(QDir::separator(), oldslash-1); |
1192 | } |
1193 | return true; |
1194 | } |
1195 | return rmDir(entry.filePath()); |
1196 | } |
1197 | |
1198 | //static |
1199 | QString QFileSystemEngine::rootPath() |
1200 | { |
1201 | #if defined(Q_OS_WINRT) |
1202 | // We specify the package root as root directory |
1203 | QString ret = QLatin1String("/"); |
1204 | // Get package location |
1205 | ComPtr<IPackageStatics> statics; |
1206 | if (FAILED(GetActivationFactory(HStringReference(RuntimeClass_Windows_ApplicationModel_Package).Get(), &statics))) |
1207 | return ret; |
1208 | ComPtr<IPackage> package; |
1209 | if (FAILED(statics->get_Current(&package))) |
1210 | return ret; |
1211 | ComPtr<IStorageFolder> installedLocation; |
1212 | if (FAILED(package->get_InstalledLocation(&installedLocation))) |
1213 | return ret; |
1214 | |
1215 | ComPtr<IStorageItem> item; |
1216 | if (FAILED(installedLocation.As(&item))) |
1217 | return ret; |
1218 | |
1219 | HString finalWinPath; |
1220 | if (FAILED(item->get_Path(finalWinPath.GetAddressOf()))) |
1221 | return ret; |
1222 | |
1223 | const QString qtWinPath = QDir::fromNativeSeparators(QString::fromWCharArray(finalWinPath.GetRawBuffer(nullptr))); |
1224 | ret = qtWinPath.endsWith(QLatin1Char('/')) ? qtWinPath : qtWinPath + QLatin1Char('/'); |
1225 | #else |
1226 | QString ret = QString::fromLatin1(qgetenv("SystemDrive")); |
1227 | if (ret.isEmpty()) |
1228 | ret = QLatin1String("c:"); |
1229 | ret.append(QLatin1Char('/')); |
1230 | #endif |
1231 | return ret; |
1232 | } |
1233 | |
1234 | //static |
1235 | QString QFileSystemEngine::homePath() |
1236 | { |
1237 | QString ret; |
1238 | #if QT_CONFIG(fslibs) |
1239 | initGlobalSid(); |
1240 | { |
1241 | HANDLE hnd = ::GetCurrentProcess(); |
1242 | HANDLE token = 0; |
1243 | BOOL ok = ::OpenProcessToken(hnd, TOKEN_QUERY, &token); |
1244 | if (ok) { |
1245 | DWORD dwBufferSize = 0; |
1246 | // First call, to determine size of the strings (with '\0'). |
1247 | ok = GetUserProfileDirectory(token, NULL, &dwBufferSize); |
1248 | if (!ok && dwBufferSize != 0) { // We got the required buffer size |
1249 | wchar_t *userDirectory = new wchar_t[dwBufferSize]; |
1250 | // Second call, now we can fill the allocated buffer. |
1251 | ok = GetUserProfileDirectory(token, userDirectory, &dwBufferSize); |
1252 | if (ok) |
1253 | ret = QString::fromWCharArray(userDirectory); |
1254 | delete [] userDirectory; |
1255 | } |
1256 | ::CloseHandle(token); |
1257 | } |
1258 | } |
1259 | #endif |
1260 | if (ret.isEmpty() || !QFile::exists(ret)) { |
1261 | ret = QString::fromLocal8Bit(qgetenv("USERPROFILE")); |
1262 | if (ret.isEmpty() || !QFile::exists(ret)) { |
1263 | ret = QString::fromLocal8Bit(qgetenv("HOMEDRIVE")) |
1264 | + QString::fromLocal8Bit(qgetenv("HOMEPATH")); |
1265 | if (ret.isEmpty() || !QFile::exists(ret)) { |
1266 | ret = QString::fromLocal8Bit(qgetenv("HOME")); |
1267 | if (ret.isEmpty() || !QFile::exists(ret)) |
1268 | ret = rootPath(); |
1269 | } |
1270 | } |
1271 | } |
1272 | return QDir::fromNativeSeparators(ret); |
1273 | } |
1274 | |
1275 | QString QFileSystemEngine::tempPath() |
1276 | { |
1277 | QString ret; |
1278 | #ifndef Q_OS_WINRT |
1279 | wchar_t tempPath[MAX_PATH]; |
1280 | const DWORD len = GetTempPath(MAX_PATH, tempPath); |
1281 | if (len) { // GetTempPath() can return short names, expand. |
1282 | wchar_t longTempPath[MAX_PATH]; |
1283 | const DWORD longLen = GetLongPathName(tempPath, longTempPath, MAX_PATH); |
1284 | ret = longLen && longLen < MAX_PATH ? |
1285 | QString::fromWCharArray(longTempPath, longLen) : |
1286 | QString::fromWCharArray(tempPath, len); |
1287 | } |
1288 | if (!ret.isEmpty()) { |
1289 | while (ret.endsWith(QLatin1Char('\\'))) |
1290 | ret.chop(1); |
1291 | ret = QDir::fromNativeSeparators(ret); |
1292 | } |
1293 | #else // !Q_OS_WINRT |
1294 | ComPtr<IApplicationDataStatics> applicationDataStatics; |
1295 | if (FAILED(GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_ApplicationData).Get(), &applicationDataStatics))) |
1296 | return ret; |
1297 | ComPtr<IApplicationData> applicationData; |
1298 | if (FAILED(applicationDataStatics->get_Current(&applicationData))) |
1299 | return ret; |
1300 | ComPtr<IStorageFolder> tempFolder; |
1301 | if (FAILED(applicationData->get_TemporaryFolder(&tempFolder))) |
1302 | return ret; |
1303 | ComPtr<IStorageItem> tempFolderItem; |
1304 | if (FAILED(tempFolder.As(&tempFolderItem))) |
1305 | return ret; |
1306 | HString path; |
1307 | if (FAILED(tempFolderItem->get_Path(path.GetAddressOf()))) |
1308 | return ret; |
1309 | ret = QDir::fromNativeSeparators(QString::fromWCharArray(path.GetRawBuffer(nullptr))); |
1310 | #endif // Q_OS_WINRT |
1311 | if (ret.isEmpty()) { |
1312 | ret = QLatin1String("C:/tmp"); |
1313 | } else if (ret.length() >= 2 && ret[1] == QLatin1Char(':')) |
1314 | ret[0] = ret.at(0).toUpper(); // Force uppercase drive letters. |
1315 | return ret; |
1316 | } |
1317 | |
1318 | bool QFileSystemEngine::setCurrentPath(const QFileSystemEntry &entry) |
1319 | { |
1320 | QFileSystemMetaData meta; |
1321 | fillMetaData(entry, meta, QFileSystemMetaData::ExistsAttribute | QFileSystemMetaData::DirectoryType); |
1322 | if(!(meta.exists() && meta.isDirectory())) |
1323 | return false; |
1324 | |
1325 | //TODO: this should really be using nativeFilePath(), but that returns a path in long format \\?\c:\foo |
1326 | //which causes many problems later on when it's returned through currentPath() |
1327 | return ::SetCurrentDirectory(reinterpret_cast<const wchar_t*>(QDir::toNativeSeparators(entry.filePath()).utf16())) != 0; |
1328 | } |
1329 | |
1330 | QFileSystemEntry QFileSystemEngine::currentPath() |
1331 | { |
1332 | QString ret; |
1333 | DWORD size = 0; |
1334 | wchar_t currentName[PATH_MAX]; |
1335 | size = ::GetCurrentDirectory(PATH_MAX, currentName); |
1336 | if (size != 0) { |
1337 | if (size > PATH_MAX) { |
1338 | wchar_t *newCurrentName = new wchar_t[size]; |
1339 | if (::GetCurrentDirectory(PATH_MAX, newCurrentName) != 0) |
1340 | ret = QString::fromWCharArray(newCurrentName, size); |
1341 | delete [] newCurrentName; |
1342 | } else { |
1343 | ret = QString::fromWCharArray(currentName, size); |
1344 | } |
1345 | } |
1346 | if (ret.length() >= 2 && ret[1] == QLatin1Char(':')) |
1347 | ret[0] = ret.at(0).toUpper(); // Force uppercase drive letters. |
1348 | return QFileSystemEntry(ret, QFileSystemEntry::FromNativePath()); |
1349 | } |
1350 | |
1351 | //static |
1352 | bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) |
1353 | { |
1354 | Q_ASSERT(false); |
1355 | Q_UNUSED(source) |
1356 | Q_UNUSED(target) |
1357 | Q_UNUSED(error) |
1358 | |
1359 | return false; // TODO implement; - code needs to be moved from qfsfileengine_win.cpp |
1360 | } |
1361 | |
1362 | //static |
1363 | bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) |
1364 | { |
1365 | #ifndef Q_OS_WINRT |
1366 | bool ret = ::CopyFile((wchar_t*)source.nativeFilePath().utf16(), |
1367 | (wchar_t*)target.nativeFilePath().utf16(), true) != 0; |
1368 | #else // !Q_OS_WINRT |
1369 | COPYFILE2_EXTENDED_PARAMETERS copyParams = { |
1370 | sizeof(copyParams), COPY_FILE_FAIL_IF_EXISTS, NULL, NULL, NULL |
1371 | }; |
1372 | HRESULT hres = ::CopyFile2((const wchar_t*)source.nativeFilePath().utf16(), |
1373 | (const wchar_t*)target.nativeFilePath().utf16(), ©Params); |
1374 | bool ret = SUCCEEDED(hres); |
1375 | #endif // Q_OS_WINRT |
1376 | if(!ret) |
1377 | error = QSystemError(::GetLastError(), QSystemError::NativeError); |
1378 | return ret; |
1379 | } |
1380 | |
1381 | //static |
1382 | bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) |
1383 | { |
1384 | #ifndef Q_OS_WINRT |
1385 | bool ret = ::MoveFile((wchar_t*)source.nativeFilePath().utf16(), |
1386 | (wchar_t*)target.nativeFilePath().utf16()) != 0; |
1387 | #else // !Q_OS_WINRT |
1388 | bool ret = ::MoveFileEx((const wchar_t*)source.nativeFilePath().utf16(), |
1389 | (const wchar_t*)target.nativeFilePath().utf16(), 0) != 0; |
1390 | #endif // Q_OS_WINRT |
1391 | if(!ret) |
1392 | error = QSystemError(::GetLastError(), QSystemError::NativeError); |
1393 | return ret; |
1394 | } |
1395 | |
1396 | //static |
1397 | bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) |
1398 | { |
1399 | bool ret = ::MoveFileEx(reinterpret_cast<const wchar_t *>(source.nativeFilePath().utf16()), |
1400 | reinterpret_cast<const wchar_t *>(target.nativeFilePath().utf16()), |
1401 | MOVEFILE_REPLACE_EXISTING) != 0; |
1402 | if (!ret) |
1403 | error = QSystemError(::GetLastError(), QSystemError::NativeError); |
1404 | return ret; |
1405 | } |
1406 | |
1407 | //static |
1408 | bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError &error) |
1409 | { |
1410 | bool ret = ::DeleteFile((wchar_t*)entry.nativeFilePath().utf16()) != 0; |
1411 | if(!ret) |
1412 | error = QSystemError(::GetLastError(), QSystemError::NativeError); |
1413 | return ret; |
1414 | } |
1415 | |
1416 | //static |
1417 | bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry, QFile::Permissions permissions, QSystemError &error, |
1418 | QFileSystemMetaData *data) |
1419 | { |
1420 | Q_UNUSED(data); |
1421 | int mode = 0; |
1422 | |
1423 | if (permissions & (QFile::ReadOwner | QFile::ReadUser | QFile::ReadGroup | QFile::ReadOther)) |
1424 | mode |= _S_IREAD; |
1425 | if (permissions & (QFile::WriteOwner | QFile::WriteUser | QFile::WriteGroup | QFile::WriteOther)) |
1426 | mode |= _S_IWRITE; |
1427 | |
1428 | if (mode == 0) // not supported |
1429 | return false; |
1430 | |
1431 | bool ret = ::_wchmod(reinterpret_cast<const wchar_t*>(entry.nativeFilePath().utf16()), mode) == 0; |
1432 | if(!ret) |
1433 | error = QSystemError(errno, QSystemError::StandardLibraryError); |
1434 | return ret; |
1435 | } |
1436 | |
1437 | static inline QDateTime fileTimeToQDateTime(const FILETIME *time) |
1438 | { |
1439 | if (time->dwHighDateTime == 0 && time->dwLowDateTime == 0) |
1440 | return QDateTime(); |
1441 | |
1442 | SYSTEMTIME sTime; |
1443 | FileTimeToSystemTime(time, &sTime); |
1444 | return QDateTime(QDate(sTime.wYear, sTime.wMonth, sTime.wDay), |
1445 | QTime(sTime.wHour, sTime.wMinute, sTime.wSecond, sTime.wMilliseconds), |
1446 | Qt::UTC); |
1447 | } |
1448 | |
1449 | QDateTime QFileSystemMetaData::birthTime() const |
1450 | { |
1451 | return fileTimeToQDateTime(&birthTime_); |
1452 | } |
1453 | QDateTime QFileSystemMetaData::metadataChangeTime() const |
1454 | { |
1455 | return fileTimeToQDateTime(&changeTime_); |
1456 | } |
1457 | QDateTime QFileSystemMetaData::modificationTime() const |
1458 | { |
1459 | return fileTimeToQDateTime(&lastWriteTime_); |
1460 | } |
1461 | QDateTime QFileSystemMetaData::accessTime() const |
1462 | { |
1463 | return fileTimeToQDateTime(&lastAccessTime_); |
1464 | } |
1465 | |
1466 | QT_END_NAMESPACE |
1467 |
Warning: That file was not part of the compilation database. It may have many parsing errors.