1/****************************************************************************
2**
3** Copyright (C) 2018 Intel Corporation.
4** Copyright (C) 2016 The Qt Company Ltd.
5** Copyright (C) 2013 Samuel Gaist <samuel.gaist@edeltech.ch>
6** Contact: https://www.qt.io/licensing/
7**
8** This file is part of the QtCore module of the Qt Toolkit.
9**
10** $QT_BEGIN_LICENSE:LGPL$
11** Commercial License Usage
12** Licensees holding valid commercial Qt licenses may use this file in
13** accordance with the commercial license agreement provided with the
14** Software or, alternatively, in accordance with the terms contained in
15** a written agreement between you and The Qt Company. For licensing terms
16** and conditions see https://www.qt.io/terms-conditions. For further
17** information use the contact form at https://www.qt.io/contact-us.
18**
19** GNU Lesser General Public License Usage
20** Alternatively, this file may be used under the terms of the GNU Lesser
21** General Public License version 3 as published by the Free Software
22** Foundation and appearing in the file LICENSE.LGPL3 included in the
23** packaging of this file. Please review the following information to
24** ensure the GNU Lesser General Public License version 3 requirements
25** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
26**
27** GNU General Public License Usage
28** Alternatively, this file may be used under the terms of the GNU
29** General Public License version 2.0 or (at your option) the GNU General
30** Public license version 3 or any later version approved by the KDE Free
31** Qt Foundation. The licenses are as published by the Free Software
32** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
33** included in the packaging of this file. Please review the following
34** information to ensure the GNU General Public License requirements will
35** be met: https://www.gnu.org/licenses/gpl-2.0.html and
36** https://www.gnu.org/licenses/gpl-3.0.html.
37**
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qplatformdefs.h"
43#include "qfilesystemengine_p.h"
44#include "qfile.h"
45#include "qstorageinfo.h"
46#include "qtextstream.h"
47
48#include <QtCore/qoperatingsystemversion.h>
49#include <QtCore/private/qcore_unix_p.h>
50#include <QtCore/qvarlengtharray.h>
51#ifndef QT_BOOTSTRAPPED
52# include <QtCore/qstandardpaths.h>
53#endif // QT_BOOTSTRAPPED
54
55#include <pwd.h>
56#include <stdlib.h> // for realpath()
57#include <sys/types.h>
58#include <sys/stat.h>
59#include <unistd.h>
60#include <stdio.h>
61#include <errno.h>
62
63#if __has_include(<paths.h>)
64# include <paths.h>
65#endif
66#ifndef _PATH_TMP // from <paths.h>
67# define _PATH_TMP "/tmp"
68#endif
69
70#if defined(Q_OS_MAC)
71# include <QtCore/private/qcore_mac_p.h>
72# include <CoreFoundation/CFBundle.h>
73#endif
74
75#ifdef Q_OS_MACOS
76#include <CoreServices/CoreServices.h>
77#endif
78
79#if defined(QT_PLATFORM_UIKIT)
80#include <MobileCoreServices/MobileCoreServices.h>
81#endif
82
83#if defined(Q_OS_DARWIN)
84# include <sys/clonefile.h>
85# include <copyfile.h>
86// We cannot include <Foundation/Foundation.h> (it's an Objective-C header), but
87// we need these declarations:
88Q_FORWARD_DECLARE_OBJC_CLASS(NSString);
89extern "C" NSString *NSTemporaryDirectory();
90#endif
91
92#if defined(Q_OS_LINUX)
93# include <sys/ioctl.h>
94# include <sys/sendfile.h>
95# include <linux/fs.h>
96
97// in case linux/fs.h is too old and doesn't define it:
98#ifndef FICLONE
99# define FICLONE _IOW(0x94, 9, int)
100#endif
101#endif
102
103#if defined(Q_OS_ANDROID)
104// statx() is disabled on Android because quite a few systems
105// come with sandboxes that kill applications that make system calls outside a
106// whitelist and several Android vendors can't be bothered to update the list.
107# undef STATX_BASIC_STATS
108#endif
109
110#ifndef STATX_ALL
111struct statx { mode_t stx_mode; }; // dummy
112#endif
113
114QT_BEGIN_NAMESPACE
115
116enum {
117#ifdef Q_OS_ANDROID
118 // On Android, the link(2) system call has been observed to always fail
119 // with EACCES, regardless of whether there are permission problems or not.
120 SupportsHardlinking = false
121#else
122 SupportsHardlinking = true
123#endif
124};
125
126#if defined(Q_OS_DARWIN)
127static inline bool hasResourcePropertyFlag(const QFileSystemMetaData &data,
128 const QFileSystemEntry &entry,
129 CFStringRef key)
130{
131 QCFString path = CFStringCreateWithFileSystemRepresentation(0,
132 entry.nativeFilePath().constData());
133 if (!path)
134 return false;
135
136 QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, path, kCFURLPOSIXPathStyle,
137 data.hasFlags(QFileSystemMetaData::DirectoryType));
138 if (!url)
139 return false;
140
141 CFBooleanRef value;
142 if (CFURLCopyResourcePropertyForKey(url, key, &value, NULL)) {
143 if (value == kCFBooleanTrue)
144 return true;
145 }
146
147 return false;
148}
149
150static bool isPackage(const QFileSystemMetaData &data, const QFileSystemEntry &entry)
151{
152 if (!data.isDirectory())
153 return false;
154
155 QFileInfo info(entry.filePath());
156 QString suffix = info.suffix();
157
158 if (suffix.length() > 0) {
159 // First step: is the extension known ?
160 QCFType<CFStringRef> extensionRef = suffix.toCFString();
161 QCFType<CFStringRef> uniformTypeIdentifier = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, extensionRef, NULL);
162 if (UTTypeConformsTo(uniformTypeIdentifier, kUTTypeBundle))
163 return true;
164
165 // Second step: check if an application knows the package type
166 QCFType<CFStringRef> path = entry.filePath().toCFString();
167 QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, path, kCFURLPOSIXPathStyle, true);
168
169 UInt32 type, creator;
170 // Well created packages have the PkgInfo file
171 if (CFBundleGetPackageInfoInDirectory(url, &type, &creator))
172 return true;
173
174#ifdef Q_OS_MACOS
175 // Find if an application other than Finder claims to know how to handle the package
176 QCFType<CFURLRef> application = LSCopyDefaultApplicationURLForURL(url,
177 kLSRolesEditor | kLSRolesViewer, nullptr);
178
179 if (application) {
180 QCFType<CFBundleRef> bundle = CFBundleCreate(kCFAllocatorDefault, application);
181 CFStringRef identifier = CFBundleGetIdentifier(bundle);
182 QString applicationId = QString::fromCFString(identifier);
183 if (applicationId != QLatin1String("com.apple.finder"))
184 return true;
185 }
186#endif
187 }
188
189 // Third step: check if the directory has the package bit set
190 return hasResourcePropertyFlag(data, entry, kCFURLIsPackageKey);
191}
192#endif
193
194namespace {
195namespace GetFileTimes {
196#if !QT_CONFIG(futimens) && (QT_CONFIG(futimes))
197template <typename T>
198static inline typename QtPrivate::QEnableIf<(&T::st_atim, &T::st_mtim, true)>::Type get(const T *p, struct timeval *access, struct timeval *modification)
199{
200 access->tv_sec = p->st_atim.tv_sec;
201 access->tv_usec = p->st_atim.tv_nsec / 1000;
202
203 modification->tv_sec = p->st_mtim.tv_sec;
204 modification->tv_usec = p->st_mtim.tv_nsec / 1000;
205}
206
207template <typename T>
208static inline typename QtPrivate::QEnableIf<(&T::st_atimespec, &T::st_mtimespec, true)>::Type get(const T *p, struct timeval *access, struct timeval *modification)
209{
210 access->tv_sec = p->st_atimespec.tv_sec;
211 access->tv_usec = p->st_atimespec.tv_nsec / 1000;
212
213 modification->tv_sec = p->st_mtimespec.tv_sec;
214 modification->tv_usec = p->st_mtimespec.tv_nsec / 1000;
215}
216
217# ifndef st_atimensec
218// if "st_atimensec" is defined, this would expand to invalid C++
219template <typename T>
220static inline typename QtPrivate::QEnableIf<(&T::st_atimensec, &T::st_mtimensec, true)>::Type get(const T *p, struct timeval *access, struct timeval *modification)
221{
222 access->tv_sec = p->st_atime;
223 access->tv_usec = p->st_atimensec / 1000;
224
225 modification->tv_sec = p->st_mtime;
226 modification->tv_usec = p->st_mtimensec / 1000;
227}
228# endif
229#endif
230
231qint64 timespecToMSecs(const timespec &spec)
232{
233 return (qint64(spec.tv_sec) * 1000) + (spec.tv_nsec / 1000000);
234}
235
236// fallback set
237Q_DECL_UNUSED qint64 atime(const QT_STATBUF &statBuffer, ulong) { return qint64(statBuffer.st_atime) * 1000; }
238Q_DECL_UNUSED qint64 birthtime(const QT_STATBUF &, ulong) { return Q_INT64_C(0); }
239Q_DECL_UNUSED qint64 ctime(const QT_STATBUF &statBuffer, ulong) { return qint64(statBuffer.st_ctime) * 1000; }
240Q_DECL_UNUSED qint64 mtime(const QT_STATBUF &statBuffer, ulong) { return qint64(statBuffer.st_mtime) * 1000; }
241
242// Xtim, POSIX.1-2008
243template <typename T>
244Q_DECL_UNUSED static typename std::enable_if<(&T::st_atim, true), qint64>::type
245atime(const T &statBuffer, int)
246{ return timespecToMSecs(statBuffer.st_atim); }
247
248template <typename T>
249Q_DECL_UNUSED static typename std::enable_if<(&T::st_birthtim, true), qint64>::type
250birthtime(const T &statBuffer, int)
251{ return timespecToMSecs(statBuffer.st_birthtim); }
252
253template <typename T>
254Q_DECL_UNUSED static typename std::enable_if<(&T::st_ctim, true), qint64>::type
255ctime(const T &statBuffer, int)
256{ return timespecToMSecs(statBuffer.st_ctim); }
257
258template <typename T>
259Q_DECL_UNUSED static typename std::enable_if<(&T::st_mtim, true), qint64>::type
260mtime(const T &statBuffer, int)
261{ return timespecToMSecs(statBuffer.st_mtim); }
262
263#ifndef st_mtimespec
264// Xtimespec
265template <typename T>
266Q_DECL_UNUSED static typename std::enable_if<(&T::st_atimespec, true), qint64>::type
267atime(const T &statBuffer, int)
268{ return timespecToMSecs(statBuffer.st_atimespec); }
269
270template <typename T>
271Q_DECL_UNUSED static typename std::enable_if<(&T::st_birthtimespec, true), qint64>::type
272birthtime(const T &statBuffer, int)
273{ return timespecToMSecs(statBuffer.st_birthtimespec); }
274
275template <typename T>
276Q_DECL_UNUSED static typename std::enable_if<(&T::st_ctimespec, true), qint64>::type
277ctime(const T &statBuffer, int)
278{ return timespecToMSecs(statBuffer.st_ctimespec); }
279
280template <typename T>
281Q_DECL_UNUSED static typename std::enable_if<(&T::st_mtimespec, true), qint64>::type
282mtime(const T &statBuffer, int)
283{ return timespecToMSecs(statBuffer.st_mtimespec); }
284#endif
285
286#if !defined(st_mtimensec) && !defined(__alpha__)
287// Xtimensec
288template <typename T>
289Q_DECL_UNUSED static typename std::enable_if<(&T::st_atimensec, true), qint64>::type
290atime(const T &statBuffer, int)
291{ return statBuffer.st_atime * Q_INT64_C(1000) + statBuffer.st_atimensec / 1000000; }
292
293template <typename T>
294Q_DECL_UNUSED static typename std::enable_if<(&T::st_birthtimensec, true), qint64>::type
295birthtime(const T &statBuffer, int)
296{ return statBuffer.st_birthtime * Q_INT64_C(1000) + statBuffer.st_birthtimensec / 1000000; }
297
298template <typename T>
299Q_DECL_UNUSED static typename std::enable_if<(&T::st_ctimensec, true), qint64>::type
300ctime(const T &statBuffer, int)
301{ return statBuffer.st_ctime * Q_INT64_C(1000) + statBuffer.st_ctimensec / 1000000; }
302
303template <typename T>
304Q_DECL_UNUSED static typename std::enable_if<(&T::st_mtimensec, true), qint64>::type
305mtime(const T &statBuffer, int)
306{ return statBuffer.st_mtime * Q_INT64_C(1000) + statBuffer.st_mtimensec / 1000000; }
307#endif
308} // namespace GetFileTimes
309} // unnamed namespace
310
311#ifdef STATX_BASIC_STATS
312static int qt_real_statx(int fd, const char *pathname, int flags, struct statx *statxBuffer)
313{
314 unsigned mask = STATX_BASIC_STATS | STATX_BTIME;
315 int ret = statx(dirfd: fd, path: pathname, flags: flags | AT_NO_AUTOMOUNT, mask: mask, buf: statxBuffer);
316 return ret == -1 ? -errno : 0;
317}
318
319static int qt_statx(const char *pathname, struct statx *statxBuffer)
320{
321 return qt_real_statx(AT_FDCWD, pathname, flags: 0, statxBuffer);
322}
323
324static int qt_lstatx(const char *pathname, struct statx *statxBuffer)
325{
326 return qt_real_statx(AT_FDCWD, pathname, AT_SYMLINK_NOFOLLOW, statxBuffer);
327}
328
329static int qt_fstatx(int fd, struct statx *statxBuffer)
330{
331 return qt_real_statx(fd, pathname: "", AT_EMPTY_PATH, statxBuffer);
332}
333
334inline void QFileSystemMetaData::fillFromStatxBuf(const struct statx &statxBuffer)
335{
336 // Permissions
337 if (statxBuffer.stx_mode & S_IRUSR)
338 entryFlags |= QFileSystemMetaData::OwnerReadPermission;
339 if (statxBuffer.stx_mode & S_IWUSR)
340 entryFlags |= QFileSystemMetaData::OwnerWritePermission;
341 if (statxBuffer.stx_mode & S_IXUSR)
342 entryFlags |= QFileSystemMetaData::OwnerExecutePermission;
343
344 if (statxBuffer.stx_mode & S_IRGRP)
345 entryFlags |= QFileSystemMetaData::GroupReadPermission;
346 if (statxBuffer.stx_mode & S_IWGRP)
347 entryFlags |= QFileSystemMetaData::GroupWritePermission;
348 if (statxBuffer.stx_mode & S_IXGRP)
349 entryFlags |= QFileSystemMetaData::GroupExecutePermission;
350
351 if (statxBuffer.stx_mode & S_IROTH)
352 entryFlags |= QFileSystemMetaData::OtherReadPermission;
353 if (statxBuffer.stx_mode & S_IWOTH)
354 entryFlags |= QFileSystemMetaData::OtherWritePermission;
355 if (statxBuffer.stx_mode & S_IXOTH)
356 entryFlags |= QFileSystemMetaData::OtherExecutePermission;
357
358 // Type
359 if (S_ISLNK(statxBuffer.stx_mode))
360 entryFlags |= QFileSystemMetaData::LinkType;
361 if ((statxBuffer.stx_mode & S_IFMT) == S_IFREG)
362 entryFlags |= QFileSystemMetaData::FileType;
363 else if ((statxBuffer.stx_mode & S_IFMT) == S_IFDIR)
364 entryFlags |= QFileSystemMetaData::DirectoryType;
365 else if ((statxBuffer.stx_mode & S_IFMT) != S_IFBLK)
366 entryFlags |= QFileSystemMetaData::SequentialType;
367
368 // Attributes
369 entryFlags |= QFileSystemMetaData::ExistsAttribute; // inode exists
370 if (statxBuffer.stx_nlink == 0)
371 entryFlags |= QFileSystemMetaData::WasDeletedAttribute;
372 size_ = qint64(statxBuffer.stx_size);
373
374 // Times
375 auto toMSecs = [](struct statx_timestamp ts)
376 {
377 return qint64(ts.tv_sec) * 1000 + (ts.tv_nsec / 1000000);
378 };
379 accessTime_ = toMSecs(statxBuffer.stx_atime);
380 metadataChangeTime_ = toMSecs(statxBuffer.stx_ctime);
381 modificationTime_ = toMSecs(statxBuffer.stx_mtime);
382 if (statxBuffer.stx_mask & STATX_BTIME)
383 birthTime_ = toMSecs(statxBuffer.stx_btime);
384 else
385 birthTime_ = 0;
386
387 userId_ = statxBuffer.stx_uid;
388 groupId_ = statxBuffer.stx_gid;
389}
390#else
391static int qt_statx(const char *, struct statx *)
392{ return -ENOSYS; }
393
394static int qt_lstatx(const char *, struct statx *)
395{ return -ENOSYS; }
396
397static int qt_fstatx(int, struct statx *)
398{ return -ENOSYS; }
399
400inline void QFileSystemMetaData::fillFromStatxBuf(const struct statx &)
401{ }
402#endif
403
404//static
405bool QFileSystemEngine::fillMetaData(int fd, QFileSystemMetaData &data)
406{
407 data.entryFlags &= ~QFileSystemMetaData::PosixStatFlags;
408 data.knownFlagsMask |= QFileSystemMetaData::PosixStatFlags;
409
410 union {
411 struct statx statxBuffer;
412 QT_STATBUF statBuffer;
413 };
414
415 int ret = qt_fstatx(fd, statxBuffer: &statxBuffer);
416 if (ret != -ENOSYS) {
417 if (ret == 0) {
418 data.fillFromStatxBuf(statxBuffer: statxBuffer);
419 return true;
420 }
421 return false;
422 }
423
424 if (QT_FSTAT(fd: fd, buf: &statBuffer) == 0) {
425 data.fillFromStatBuf(statBuffer: statBuffer);
426 return true;
427 }
428
429 return false;
430}
431
432#if defined(_DEXTRA_FIRST)
433static void fillStat64fromStat32(struct stat64 *statBuf64, const struct stat &statBuf32)
434{
435 statBuf64->st_mode = statBuf32.st_mode;
436 statBuf64->st_size = statBuf32.st_size;
437#if _POSIX_VERSION >= 200809L
438 statBuf64->st_ctim = statBuf32.st_ctim;
439 statBuf64->st_mtim = statBuf32.st_mtim;
440 statBuf64->st_atim = statBuf32.st_atim;
441#else
442 statBuf64->st_ctime = statBuf32.st_ctime;
443 statBuf64->st_mtime = statBuf32.st_mtime;
444 statBuf64->st_atime = statBuf32.st_atime;
445#endif
446 statBuf64->st_uid = statBuf32.st_uid;
447 statBuf64->st_gid = statBuf32.st_gid;
448}
449#endif
450
451void QFileSystemMetaData::fillFromStatBuf(const QT_STATBUF &statBuffer)
452{
453 // Permissions
454 if (statBuffer.st_mode & S_IRUSR)
455 entryFlags |= QFileSystemMetaData::OwnerReadPermission;
456 if (statBuffer.st_mode & S_IWUSR)
457 entryFlags |= QFileSystemMetaData::OwnerWritePermission;
458 if (statBuffer.st_mode & S_IXUSR)
459 entryFlags |= QFileSystemMetaData::OwnerExecutePermission;
460
461 if (statBuffer.st_mode & S_IRGRP)
462 entryFlags |= QFileSystemMetaData::GroupReadPermission;
463 if (statBuffer.st_mode & S_IWGRP)
464 entryFlags |= QFileSystemMetaData::GroupWritePermission;
465 if (statBuffer.st_mode & S_IXGRP)
466 entryFlags |= QFileSystemMetaData::GroupExecutePermission;
467
468 if (statBuffer.st_mode & S_IROTH)
469 entryFlags |= QFileSystemMetaData::OtherReadPermission;
470 if (statBuffer.st_mode & S_IWOTH)
471 entryFlags |= QFileSystemMetaData::OtherWritePermission;
472 if (statBuffer.st_mode & S_IXOTH)
473 entryFlags |= QFileSystemMetaData::OtherExecutePermission;
474
475 // Type
476 if ((statBuffer.st_mode & S_IFMT) == S_IFREG)
477 entryFlags |= QFileSystemMetaData::FileType;
478 else if ((statBuffer.st_mode & S_IFMT) == S_IFDIR)
479 entryFlags |= QFileSystemMetaData::DirectoryType;
480 else if ((statBuffer.st_mode & S_IFMT) != S_IFBLK)
481 entryFlags |= QFileSystemMetaData::SequentialType;
482
483 // Attributes
484 entryFlags |= QFileSystemMetaData::ExistsAttribute; // inode exists
485 if (statBuffer.st_nlink == 0)
486 entryFlags |= QFileSystemMetaData::WasDeletedAttribute;
487 size_ = statBuffer.st_size;
488#ifdef UF_HIDDEN
489 if (statBuffer.st_flags & UF_HIDDEN) {
490 entryFlags |= QFileSystemMetaData::HiddenAttribute;
491 knownFlagsMask |= QFileSystemMetaData::HiddenAttribute;
492 }
493#endif
494
495 // Times
496 accessTime_ = GetFileTimes::atime(statBuffer, 0);
497 birthTime_ = GetFileTimes::birthtime(statBuffer, 0);
498 metadataChangeTime_ = GetFileTimes::ctime(statBuffer, 0);
499 modificationTime_ = GetFileTimes::mtime(statBuffer, 0);
500
501 userId_ = statBuffer.st_uid;
502 groupId_ = statBuffer.st_gid;
503}
504
505void QFileSystemMetaData::fillFromDirEnt(const QT_DIRENT &entry)
506{
507#if defined(_DEXTRA_FIRST)
508 knownFlagsMask = {};
509 entryFlags = {};
510 for (dirent_extra *extra = _DEXTRA_FIRST(&entry); _DEXTRA_VALID(extra, &entry);
511 extra = _DEXTRA_NEXT(extra)) {
512 if (extra->d_type == _DTYPE_STAT || extra->d_type == _DTYPE_LSTAT) {
513
514 const struct dirent_extra_stat * const extra_stat =
515 reinterpret_cast<struct dirent_extra_stat *>(extra);
516
517 // Remember whether this was a link or not, this saves an lstat() call later.
518 if (extra->d_type == _DTYPE_LSTAT) {
519 knownFlagsMask |= QFileSystemMetaData::LinkType;
520 if (S_ISLNK(extra_stat->d_stat.st_mode))
521 entryFlags |= QFileSystemMetaData::LinkType;
522 }
523
524 // For symlinks, the extra type _DTYPE_LSTAT doesn't work for filling out the meta data,
525 // as we need the stat() information there, not the lstat() information.
526 // In this case, don't use the extra information.
527 // Unfortunately, readdir() never seems to return extra info of type _DTYPE_STAT, so for
528 // symlinks, we always incur the cost of an extra stat() call later.
529 if (S_ISLNK(extra_stat->d_stat.st_mode) && extra->d_type == _DTYPE_LSTAT)
530 continue;
531
532#if defined(QT_USE_XOPEN_LFS_EXTENSIONS) && defined(QT_LARGEFILE_SUPPORT)
533 // Even with large file support, d_stat is always of type struct stat, not struct stat64,
534 // so it needs to be converted
535 struct stat64 statBuf;
536 fillStat64fromStat32(&statBuf, extra_stat->d_stat);
537 fillFromStatBuf(statBuf);
538#else
539 fillFromStatBuf(extra_stat->d_stat);
540#endif
541 knownFlagsMask |= QFileSystemMetaData::PosixStatFlags;
542 if (!S_ISLNK(extra_stat->d_stat.st_mode)) {
543 knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
544 entryFlags |= QFileSystemMetaData::ExistsAttribute;
545 }
546 }
547 }
548#elif defined(_DIRENT_HAVE_D_TYPE) || defined(Q_OS_BSD4)
549 // BSD4 includes OS X and iOS
550
551 // ### This will clear all entry flags and knownFlagsMask
552 switch (entry.d_type)
553 {
554 case DT_DIR:
555 knownFlagsMask = QFileSystemMetaData::LinkType
556 | QFileSystemMetaData::FileType
557 | QFileSystemMetaData::DirectoryType
558 | QFileSystemMetaData::SequentialType
559 | QFileSystemMetaData::ExistsAttribute;
560
561 entryFlags = QFileSystemMetaData::DirectoryType
562 | QFileSystemMetaData::ExistsAttribute;
563
564 break;
565
566 case DT_BLK:
567 knownFlagsMask = QFileSystemMetaData::LinkType
568 | QFileSystemMetaData::FileType
569 | QFileSystemMetaData::DirectoryType
570 | QFileSystemMetaData::BundleType
571 | QFileSystemMetaData::AliasType
572 | QFileSystemMetaData::SequentialType
573 | QFileSystemMetaData::ExistsAttribute;
574
575 entryFlags = QFileSystemMetaData::ExistsAttribute;
576
577 break;
578
579 case DT_CHR:
580 case DT_FIFO:
581 case DT_SOCK:
582 // ### System attribute
583 knownFlagsMask = QFileSystemMetaData::LinkType
584 | QFileSystemMetaData::FileType
585 | QFileSystemMetaData::DirectoryType
586 | QFileSystemMetaData::BundleType
587 | QFileSystemMetaData::AliasType
588 | QFileSystemMetaData::SequentialType
589 | QFileSystemMetaData::ExistsAttribute;
590
591 entryFlags = QFileSystemMetaData::SequentialType
592 | QFileSystemMetaData::ExistsAttribute;
593
594 break;
595
596 case DT_LNK:
597 knownFlagsMask = QFileSystemMetaData::LinkType;
598 entryFlags = QFileSystemMetaData::LinkType;
599 break;
600
601 case DT_REG:
602 knownFlagsMask = QFileSystemMetaData::LinkType
603 | QFileSystemMetaData::FileType
604 | QFileSystemMetaData::DirectoryType
605 | QFileSystemMetaData::BundleType
606 | QFileSystemMetaData::SequentialType
607 | QFileSystemMetaData::ExistsAttribute;
608
609 entryFlags = QFileSystemMetaData::FileType
610 | QFileSystemMetaData::ExistsAttribute;
611
612 break;
613
614 case DT_UNKNOWN:
615 default:
616 clear();
617 }
618#else
619 Q_UNUSED(entry)
620#endif
621}
622
623//static
624QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data)
625{
626 Q_CHECK_FILE_NAME(link, link);
627
628 QByteArray s = qt_readlink(path: link.nativeFilePath().constData());
629 if (s.length() > 0) {
630 QString ret;
631 if (!data.hasFlags(flags: QFileSystemMetaData::DirectoryType))
632 fillMetaData(entry: link, data, what: QFileSystemMetaData::DirectoryType);
633 if (data.isDirectory() && s[0] != '/') {
634 QDir parent(link.filePath());
635 parent.cdUp();
636 ret = parent.path();
637 if (!ret.isEmpty() && !ret.endsWith(c: QLatin1Char('/')))
638 ret += QLatin1Char('/');
639 }
640 ret += QFile::decodeName(localFileName: s);
641
642 if (!ret.startsWith(c: QLatin1Char('/')))
643 ret.prepend(s: absoluteName(entry: link).path() + QLatin1Char('/'));
644 ret = QDir::cleanPath(path: ret);
645 if (ret.size() > 1 && ret.endsWith(c: QLatin1Char('/')))
646 ret.chop(n: 1);
647 return QFileSystemEntry(ret);
648 }
649#if defined(Q_OS_DARWIN)
650 {
651 QCFString path = CFStringCreateWithFileSystemRepresentation(0,
652 QFile::encodeName(QDir::cleanPath(link.filePath())).data());
653 if (!path)
654 return QFileSystemEntry();
655
656 QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, path, kCFURLPOSIXPathStyle,
657 data.hasFlags(QFileSystemMetaData::DirectoryType));
658 if (!url)
659 return QFileSystemEntry();
660
661 QCFType<CFDataRef> bookmarkData = CFURLCreateBookmarkDataFromFile(0, url, NULL);
662 if (!bookmarkData)
663 return QFileSystemEntry();
664
665 QCFType<CFURLRef> resolvedUrl = CFURLCreateByResolvingBookmarkData(0,
666 bookmarkData,
667 (CFURLBookmarkResolutionOptions)(kCFBookmarkResolutionWithoutUIMask
668 | kCFBookmarkResolutionWithoutMountingMask), NULL, NULL, NULL, NULL);
669 if (!resolvedUrl)
670 return QFileSystemEntry();
671
672 QCFString cfstr(CFURLCopyFileSystemPath(resolvedUrl, kCFURLPOSIXPathStyle));
673 if (!cfstr)
674 return QFileSystemEntry();
675
676 return QFileSystemEntry(QString::fromCFString(cfstr));
677 }
678#endif
679 return QFileSystemEntry();
680}
681
682//static
683QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data)
684{
685 Q_CHECK_FILE_NAME(entry, entry);
686
687#if !defined(Q_OS_MAC) && !defined(Q_OS_QNX) && !defined(Q_OS_ANDROID) && !defined(Q_OS_HAIKU) && _POSIX_VERSION < 200809L
688 // realpath(X,0) is not supported
689 Q_UNUSED(data);
690 return QFileSystemEntry(slowCanonicalized(absoluteName(entry).filePath()));
691#else
692 char stack_result[PATH_MAX+1];
693 char *resolved_name = nullptr;
694# if defined(Q_OS_DARWIN) || defined(Q_OS_ANDROID)
695 // On some Android and macOS versions, realpath() will return a path even if
696 // it does not exist. To work around this, we check existence in advance.
697 if (!data.hasFlags(QFileSystemMetaData::ExistsAttribute))
698 fillMetaData(entry, data, QFileSystemMetaData::ExistsAttribute);
699
700 if (!data.exists()) {
701 errno = ENOENT;
702 } else {
703 resolved_name = stack_result;
704 }
705 if (resolved_name && realpath(entry.nativeFilePath().constData(), resolved_name) == nullptr)
706 resolved_name = nullptr;
707# else
708# if _POSIX_VERSION >= 200801L // ask realpath to allocate memory
709 resolved_name = realpath(name: entry.nativeFilePath().constData(), resolved: nullptr);
710# else
711 resolved_name = stack_result;
712 if (realpath(entry.nativeFilePath().constData(), resolved_name) == nullptr)
713 resolved_name = nullptr;
714# endif
715# endif
716 if (resolved_name) {
717 data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
718 data.entryFlags |= QFileSystemMetaData::ExistsAttribute;
719 QString canonicalPath = QDir::cleanPath(path: QFile::decodeName(localFileName: resolved_name));
720 if (resolved_name != stack_result)
721 free(ptr: resolved_name);
722 return QFileSystemEntry(canonicalPath);
723 } else if (errno == ENOENT || errno == ENOTDIR) { // file doesn't exist
724 data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
725 data.entryFlags &= ~(QFileSystemMetaData::ExistsAttribute);
726 return QFileSystemEntry();
727 }
728 return entry;
729#endif
730}
731
732//static
733QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry)
734{
735 Q_CHECK_FILE_NAME(entry, entry);
736
737 if (entry.isAbsolute() && entry.isClean())
738 return entry;
739
740 QByteArray orig = entry.nativeFilePath();
741 QByteArray result;
742 if (orig.isEmpty() || !orig.startsWith(c: '/')) {
743 QFileSystemEntry cur(currentPath());
744 result = cur.nativeFilePath();
745 }
746 if (!orig.isEmpty() && !(orig.length() == 1 && orig[0] == '.')) {
747 if (!result.isEmpty() && !result.endsWith(c: '/'))
748 result.append(c: '/');
749 result.append(a: orig);
750 }
751
752 if (result.length() == 1 && result[0] == '/')
753 return QFileSystemEntry(result, QFileSystemEntry::FromNativePath());
754 const bool isDir = result.endsWith(c: '/');
755
756 /* as long as QDir::cleanPath() operates on a QString we have to convert to a string here.
757 * ideally we never convert to a string since that loses information. Please fix after
758 * we get a QByteArray version of QDir::cleanPath()
759 */
760 QFileSystemEntry resultingEntry(result, QFileSystemEntry::FromNativePath());
761 QString stringVersion = QDir::cleanPath(path: resultingEntry.filePath());
762 if (isDir)
763 stringVersion.append(c: QLatin1Char('/'));
764 return QFileSystemEntry(stringVersion);
765}
766
767//static
768QByteArray QFileSystemEngine::id(const QFileSystemEntry &entry)
769{
770 Q_CHECK_FILE_NAME(entry, QByteArray());
771
772 QT_STATBUF statResult;
773 if (QT_STAT(file: entry.nativeFilePath().constData(), buf: &statResult)) {
774 if (errno != ENOENT)
775 qErrnoWarning(msg: "stat() failed for '%s'", entry.nativeFilePath().constData());
776 return QByteArray();
777 }
778 QByteArray result = QByteArray::number(quint64(statResult.st_dev), base: 16);
779 result += ':';
780 result += QByteArray::number(quint64(statResult.st_ino));
781 return result;
782}
783
784//static
785QByteArray QFileSystemEngine::id(int fd)
786{
787 QT_STATBUF statResult;
788 if (QT_FSTAT(fd: fd, buf: &statResult)) {
789 qErrnoWarning(msg: "fstat() failed for fd %d", fd);
790 return QByteArray();
791 }
792 QByteArray result = QByteArray::number(quint64(statResult.st_dev), base: 16);
793 result += ':';
794 result += QByteArray::number(quint64(statResult.st_ino));
795 return result;
796}
797
798//static
799QString QFileSystemEngine::resolveUserName(uint userId)
800{
801#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
802 int size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
803 if (size_max == -1)
804 size_max = 1024;
805 QVarLengthArray<char, 1024> buf(size_max);
806#endif
807
808#if !defined(Q_OS_INTEGRITY) && !defined(Q_OS_WASM)
809 struct passwd *pw = nullptr;
810#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) && !defined(Q_OS_VXWORKS)
811 struct passwd entry;
812 getpwuid_r(uid: userId, resultbuf: &entry, buffer: buf.data(), buflen: buf.size(), result: &pw);
813#else
814 pw = getpwuid(userId);
815#endif
816 if (pw)
817 return QFile::decodeName(localFileName: QByteArray(pw->pw_name));
818#else // Integrity || WASM
819 Q_UNUSED(userId);
820#endif
821 return QString();
822}
823
824//static
825QString QFileSystemEngine::resolveGroupName(uint groupId)
826{
827#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
828 int size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
829 if (size_max == -1)
830 size_max = 1024;
831 QVarLengthArray<char, 1024> buf(size_max);
832#endif
833
834#if !defined(Q_OS_INTEGRITY) && !defined(Q_OS_WASM)
835 struct group *gr = nullptr;
836#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) && !defined(Q_OS_VXWORKS) && (!defined(Q_OS_ANDROID) || defined(Q_OS_ANDROID) && (__ANDROID_API__ >= 24))
837 size_max = sysconf(_SC_GETGR_R_SIZE_MAX);
838 if (size_max == -1)
839 size_max = 1024;
840 buf.resize(asize: size_max);
841 struct group entry;
842 // Some large systems have more members than the POSIX max size
843 // Loop over by doubling the buffer size (upper limit 250k)
844 for (unsigned size = size_max; size < 256000; size += size)
845 {
846 buf.resize(asize: size);
847 // ERANGE indicates that the buffer was too small
848 if (!getgrgid_r(gid: groupId, resultbuf: &entry, buffer: buf.data(), buflen: buf.size(), result: &gr)
849 || errno != ERANGE)
850 break;
851 }
852#else
853 gr = getgrgid(groupId);
854#endif
855 if (gr)
856 return QFile::decodeName(localFileName: QByteArray(gr->gr_name));
857#else // Integrity || WASM
858 Q_UNUSED(groupId);
859#endif
860 return QString();
861}
862
863#if defined(Q_OS_DARWIN)
864//static
865QString QFileSystemEngine::bundleName(const QFileSystemEntry &entry)
866{
867 QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, QCFString(entry.filePath()),
868 kCFURLPOSIXPathStyle, true);
869 if (QCFType<CFDictionaryRef> dict = CFBundleCopyInfoDictionaryForURL(url)) {
870 if (CFTypeRef name = (CFTypeRef)CFDictionaryGetValue(dict, kCFBundleNameKey)) {
871 if (CFGetTypeID(name) == CFStringGetTypeID())
872 return QString::fromCFString((CFStringRef)name);
873 }
874 }
875 return QString();
876}
877#endif
878
879//static
880bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemMetaData &data,
881 QFileSystemMetaData::MetaDataFlags what)
882{
883 Q_CHECK_FILE_NAME(entry, false);
884
885#if defined(Q_OS_DARWIN)
886 if (what & QFileSystemMetaData::BundleType) {
887 if (!data.hasFlags(QFileSystemMetaData::DirectoryType))
888 what |= QFileSystemMetaData::DirectoryType;
889 }
890 if (what & QFileSystemMetaData::AliasType)
891 what |= QFileSystemMetaData::LinkType;
892#endif
893#ifdef UF_HIDDEN
894 if (what & QFileSystemMetaData::HiddenAttribute) {
895 // OS X >= 10.5: st_flags & UF_HIDDEN
896 what |= QFileSystemMetaData::PosixStatFlags;
897 }
898#endif // defined(Q_OS_DARWIN)
899
900 // if we're asking for any of the stat(2) flags, then we're getting them all
901 if (what & QFileSystemMetaData::PosixStatFlags)
902 what |= QFileSystemMetaData::PosixStatFlags;
903
904 data.entryFlags &= ~what;
905
906 const QByteArray nativeFilePath = entry.nativeFilePath();
907 int entryErrno = 0; // innocent until proven otherwise
908
909 // first, we may try lstat(2). Possible outcomes:
910 // - success and is a symlink: filesystem entry exists, but we need stat(2)
911 // -> statResult = -1;
912 // - success and is not a symlink: filesystem entry exists and we're done
913 // -> statResult = 0
914 // - failure: really non-existent filesystem entry
915 // -> entryExists = false; statResult = 0;
916 // both stat(2) and lstat(2) may generate a number of different errno
917 // conditions, but of those, the only ones that could happen and the
918 // entry still exist are EACCES, EFAULT, ENOMEM and EOVERFLOW. If we get
919 // EACCES or ENOMEM, then we have no choice on how to proceed, so we may
920 // as well conclude it doesn't exist; EFAULT can't happen and EOVERFLOW
921 // shouldn't happen because we build in _LARGEFIE64.
922 union {
923 QT_STATBUF statBuffer;
924 struct statx statxBuffer;
925 };
926 int statResult = -1;
927 if (what & QFileSystemMetaData::LinkType) {
928 mode_t mode = 0;
929 statResult = qt_lstatx(pathname: nativeFilePath, statxBuffer: &statxBuffer);
930 if (statResult == -ENOSYS) {
931 // use lstst(2)
932 statResult = QT_LSTAT(file: nativeFilePath, buf: &statBuffer);
933 if (statResult == 0)
934 mode = statBuffer.st_mode;
935 } else if (statResult == 0) {
936 statResult = 1; // record it was statx(2) that succeeded
937 mode = statxBuffer.stx_mode;
938 }
939
940 if (statResult >= 0) {
941 if (S_ISLNK(mode)) {
942 // it's a symlink, we don't know if the file "exists"
943 data.entryFlags |= QFileSystemMetaData::LinkType;
944 statResult = -1; // force stat(2) below
945 } else {
946 // it's a reagular file and it exists
947 if (statResult)
948 data.fillFromStatxBuf(statxBuffer: statxBuffer);
949 else
950 data.fillFromStatBuf(statBuffer: statBuffer);
951 data.knownFlagsMask |= QFileSystemMetaData::PosixStatFlags
952 | QFileSystemMetaData::ExistsAttribute;
953 data.entryFlags |= QFileSystemMetaData::ExistsAttribute;
954 }
955 } else {
956 // it doesn't exist
957 entryErrno = errno;
958 data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
959 }
960
961 data.knownFlagsMask |= QFileSystemMetaData::LinkType;
962 }
963
964 // second, we try a regular stat(2)
965 if (statResult == -1 && (what & QFileSystemMetaData::PosixStatFlags)) {
966 if (entryErrno == 0 && statResult == -1) {
967 data.entryFlags &= ~QFileSystemMetaData::PosixStatFlags;
968 statResult = qt_statx(pathname: nativeFilePath, statxBuffer: &statxBuffer);
969 if (statResult == -ENOSYS) {
970 // use stat(2)
971 statResult = QT_STAT(file: nativeFilePath, buf: &statBuffer);
972 if (statResult == 0)
973 data.fillFromStatBuf(statBuffer: statBuffer);
974 } else if (statResult == 0) {
975 data.fillFromStatxBuf(statxBuffer: statxBuffer);
976 }
977 }
978
979 if (statResult != 0) {
980 entryErrno = errno;
981 data.birthTime_ = 0;
982 data.metadataChangeTime_ = 0;
983 data.modificationTime_ = 0;
984 data.accessTime_ = 0;
985 data.size_ = 0;
986 data.userId_ = (uint) -2;
987 data.groupId_ = (uint) -2;
988 }
989
990 // reset the mask
991 data.knownFlagsMask |= QFileSystemMetaData::PosixStatFlags
992 | QFileSystemMetaData::ExistsAttribute;
993 }
994
995 // third, we try access(2)
996 if (what & (QFileSystemMetaData::UserPermissions | QFileSystemMetaData::ExistsAttribute)) {
997 // calculate user permissions
998 auto checkAccess = [&](QFileSystemMetaData::MetaDataFlag flag, int mode) {
999 if (entryErrno != 0 || (what & flag) == 0)
1000 return;
1001 if (QT_ACCESS(name: nativeFilePath, type: mode) == 0) {
1002 // access ok (and file exists)
1003 data.entryFlags |= flag | QFileSystemMetaData::ExistsAttribute;
1004 } else if (errno != EACCES && errno != EROFS) {
1005 entryErrno = errno;
1006 }
1007 };
1008
1009 checkAccess(QFileSystemMetaData::UserReadPermission, R_OK);
1010 checkAccess(QFileSystemMetaData::UserWritePermission, W_OK);
1011 checkAccess(QFileSystemMetaData::UserExecutePermission, X_OK);
1012
1013 // if we still haven't found out if the file exists, try F_OK
1014 if (entryErrno == 0 && (data.entryFlags & QFileSystemMetaData::ExistsAttribute) == 0) {
1015 if (QT_ACCESS(name: nativeFilePath, F_OK) == -1)
1016 entryErrno = errno;
1017 else
1018 data.entryFlags |= QFileSystemMetaData::ExistsAttribute;
1019 }
1020
1021 data.knownFlagsMask |= (what & QFileSystemMetaData::UserPermissions) |
1022 QFileSystemMetaData::ExistsAttribute;
1023 }
1024
1025#if defined(Q_OS_DARWIN)
1026 if (what & QFileSystemMetaData::AliasType) {
1027 if (entryErrno == 0 && hasResourcePropertyFlag(data, entry, kCFURLIsAliasFileKey)) {
1028 // kCFURLIsAliasFileKey includes symbolic links, so filter those out
1029 if (!(data.entryFlags & QFileSystemMetaData::LinkType))
1030 data.entryFlags |= QFileSystemMetaData::AliasType;
1031 }
1032 data.knownFlagsMask |= QFileSystemMetaData::AliasType;
1033 }
1034
1035 if (what & QFileSystemMetaData::BundleType) {
1036 if (entryErrno == 0 && isPackage(data, entry))
1037 data.entryFlags |= QFileSystemMetaData::BundleType;
1038
1039 data.knownFlagsMask |= QFileSystemMetaData::BundleType;
1040 }
1041#endif
1042
1043 if (what & QFileSystemMetaData::HiddenAttribute
1044 && !data.isHidden()) {
1045 QString fileName = entry.fileName();
1046 if ((fileName.size() > 0 && fileName.at(i: 0) == QLatin1Char('.'))
1047#if defined(Q_OS_DARWIN)
1048 || (entryErrno == 0 && hasResourcePropertyFlag(data, entry, kCFURLIsHiddenKey))
1049#endif
1050 )
1051 data.entryFlags |= QFileSystemMetaData::HiddenAttribute;
1052 data.knownFlagsMask |= QFileSystemMetaData::HiddenAttribute;
1053 }
1054
1055 if (entryErrno != 0) {
1056 what &= ~QFileSystemMetaData::LinkType; // don't clear link: could be broken symlink
1057 data.clearFlags(flags: what);
1058 return false;
1059 }
1060 return true;
1061}
1062
1063// static
1064bool QFileSystemEngine::cloneFile(int srcfd, int dstfd, const QFileSystemMetaData &knownData)
1065{
1066 QT_STATBUF statBuffer;
1067 if (knownData.hasFlags(flags: QFileSystemMetaData::PosixStatFlags) &&
1068 knownData.isFile()) {
1069 statBuffer.st_mode = S_IFREG;
1070 } else if (knownData.hasFlags(flags: QFileSystemMetaData::PosixStatFlags) &&
1071 knownData.isDirectory()) {
1072 return false; // fcopyfile(3) returns success on directories
1073 } else if (QT_FSTAT(fd: srcfd, buf: &statBuffer) == -1) {
1074 return false;
1075 } else if (!S_ISREG((statBuffer.st_mode))) {
1076 // not a regular file, let QFile do the copy
1077 return false;
1078 }
1079
1080#if defined(Q_OS_LINUX)
1081 // first, try FICLONE (only works on regular files and only on certain fs)
1082 if (::ioctl(fd: dstfd, FICLONE, srcfd) == 0)
1083 return true;
1084
1085 // Second, try sendfile (it can send to some special types too).
1086 // sendfile(2) is limited in the kernel to 2G - 4k
1087 const size_t SendfileSize = 0x7ffff000;
1088
1089 ssize_t n = ::sendfile(out_fd: dstfd, in_fd: srcfd, offset: nullptr, count: SendfileSize);
1090 if (n == -1) {
1091 // if we got an error here, give up and try at an upper layer
1092 return false;
1093 }
1094
1095 while (n) {
1096 n = ::sendfile(out_fd: dstfd, in_fd: srcfd, offset: nullptr, count: SendfileSize);
1097 if (n == -1) {
1098 // uh oh, this is probably a real error (like ENOSPC), but we have
1099 // no way to notify QFile of partial success, so just erase any work
1100 // done (hopefully we won't get any errors, because there's nothing
1101 // we can do about them)
1102 n = ftruncate(fd: dstfd, length: 0);
1103 n = lseek(fd: srcfd, offset: 0, SEEK_SET);
1104 n = lseek(fd: dstfd, offset: 0, SEEK_SET);
1105 return false;
1106 }
1107 }
1108
1109 return true;
1110#elif defined(Q_OS_DARWIN)
1111 // try fcopyfile
1112 return fcopyfile(srcfd, dstfd, nullptr, COPYFILE_DATA | COPYFILE_STAT) == 0;
1113#else
1114 Q_UNUSED(dstfd);
1115 return false;
1116#endif
1117}
1118
1119// Note: if \a shouldMkdirFirst is false, we assume the caller did try to mkdir
1120// before calling this function.
1121static bool createDirectoryWithParents(const QByteArray &nativeName, bool shouldMkdirFirst = true)
1122{
1123 // helper function to check if a given path is a directory, since mkdir can
1124 // fail if the dir already exists (it may have been created by another
1125 // thread or another process)
1126 const auto isDir = [](const QByteArray &nativeName) {
1127 QT_STATBUF st;
1128 return QT_STAT(file: nativeName.constData(), buf: &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR;
1129 };
1130
1131 if (shouldMkdirFirst && QT_MKDIR(path: nativeName, mode: 0777) == 0)
1132 return true;
1133 if (errno == EEXIST)
1134 return isDir(nativeName);
1135 if (errno != ENOENT)
1136 return false;
1137
1138 // mkdir failed because the parent dir doesn't exist, so try to create it
1139 int slash = nativeName.lastIndexOf(c: '/');
1140 if (slash < 1)
1141 return false;
1142
1143 QByteArray parentNativeName = nativeName.left(len: slash);
1144 if (!createDirectoryWithParents(nativeName: parentNativeName))
1145 return false;
1146
1147 // try again
1148 if (QT_MKDIR(path: nativeName, mode: 0777) == 0)
1149 return true;
1150 return errno == EEXIST && isDir(nativeName);
1151}
1152
1153//static
1154bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents)
1155{
1156 QString dirName = entry.filePath();
1157 Q_CHECK_FILE_NAME(dirName, false);
1158
1159 // Darwin doesn't support trailing /'s, so remove for everyone
1160 while (dirName.size() > 1 && dirName.endsWith(c: QLatin1Char('/')))
1161 dirName.chop(n: 1);
1162
1163 // try to mkdir this directory
1164 QByteArray nativeName = QFile::encodeName(fileName: dirName);
1165 if (QT_MKDIR(path: nativeName, mode: 0777) == 0)
1166 return true;
1167 if (!createParents)
1168 return false;
1169
1170 return createDirectoryWithParents(nativeName, shouldMkdirFirst: false);
1171}
1172
1173//static
1174bool QFileSystemEngine::removeDirectory(const QFileSystemEntry &entry, bool removeEmptyParents)
1175{
1176 Q_CHECK_FILE_NAME(entry, false);
1177
1178 if (removeEmptyParents) {
1179 QString dirName = QDir::cleanPath(path: entry.filePath());
1180 for (int oldslash = 0, slash=dirName.length(); slash > 0; oldslash = slash) {
1181 const QByteArray chunk = QFile::encodeName(fileName: dirName.left(n: slash));
1182 QT_STATBUF st;
1183 if (QT_STAT(file: chunk.constData(), buf: &st) != -1) {
1184 if ((st.st_mode & S_IFMT) != S_IFDIR)
1185 return false;
1186 if (::rmdir(path: chunk.constData()) != 0)
1187 return oldslash != 0;
1188 } else {
1189 return false;
1190 }
1191 slash = dirName.lastIndexOf(c: QDir::separator(), from: oldslash-1);
1192 }
1193 return true;
1194 }
1195 return rmdir(path: QFile::encodeName(fileName: entry.filePath()).constData()) == 0;
1196}
1197
1198//static
1199bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1200{
1201 Q_CHECK_FILE_NAME(source, false);
1202 Q_CHECK_FILE_NAME(target, false);
1203
1204 if (::symlink(from: source.nativeFilePath().constData(), to: target.nativeFilePath().constData()) == 0)
1205 return true;
1206 error = QSystemError(errno, QSystemError::StandardLibraryError);
1207 return false;
1208}
1209
1210#ifndef Q_OS_DARWIN
1211/*
1212 Implementing as per https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
1213*/
1214
1215// bootstrapped tools don't need this, and we don't want QStorageInfo
1216#ifndef QT_BOOTSTRAPPED
1217static QString freeDesktopTrashLocation(const QString &sourcePath)
1218{
1219 auto makeTrashDir = [](const QDir &topDir, const QString &trashDir) -> QString {
1220 auto ownerPerms = QFileDevice::ReadOwner
1221 | QFileDevice::WriteOwner
1222 | QFileDevice::ExeOwner;
1223 QString targetDir = topDir.filePath(fileName: trashDir);
1224 // deliberately not using mkpath, since we want to fail if topDir doesn't exist
1225 if (topDir.mkdir(dirName: trashDir))
1226 QFile::setPermissions(filename: targetDir, permissionSpec: ownerPerms);
1227 if (QFileInfo(targetDir).isDir())
1228 return targetDir;
1229 return QString();
1230 };
1231 auto isSticky = [](const QFileInfo &fileInfo) -> bool {
1232 struct stat st;
1233 if (stat(file: QFile::encodeName(fileName: fileInfo.absoluteFilePath()).constData(), buf: &st) == 0)
1234 return st.st_mode & S_ISVTX;
1235
1236 return false;
1237 };
1238
1239 QString trash;
1240 const QStorageInfo sourceStorage(sourcePath);
1241 const QStorageInfo homeStorage(QDir::home());
1242 // We support trashing of files outside the users home partition
1243 if (sourceStorage != homeStorage) {
1244 const QLatin1String dotTrash(".Trash");
1245 QDir topDir(sourceStorage.rootPath());
1246 /*
1247 Method 1:
1248 "An administrator can create an $topdir/.Trash directory. The permissions on this
1249 directories should permit all users who can trash files at all to write in it;
1250 and the “sticky bit” in the permissions must be set, if the file system supports
1251 it.
1252 When trashing a file from a non-home partition/device, an implementation
1253 (if it supports trashing in top directories) MUST check for the presence
1254 of $topdir/.Trash."
1255 */
1256 const QString userID = QString::number(::getuid());
1257 if (topDir.cd(dirName: dotTrash)) {
1258 const QFileInfo trashInfo(topDir.path());
1259
1260 // we MUST check that the sticky bit is set, and that it is not a symlink
1261 if (trashInfo.isSymLink()) {
1262 // we SHOULD report the failed check to the administrator
1263 qCritical(msg: "Warning: '%s' is a symlink to '%s'",
1264 trashInfo.absoluteFilePath().toLocal8Bit().constData(),
1265 trashInfo.symLinkTarget().toLatin1().constData());
1266 } else if (!isSticky(trashInfo)) {
1267 // we SHOULD report the failed check to the administrator
1268 qCritical(msg: "Warning: '%s' doesn't have sticky bit set!",
1269 trashInfo.absoluteFilePath().toLocal8Bit().constData());
1270 } else if (trashInfo.isDir()) {
1271 /*
1272 "If the directory exists and passes the checks, a subdirectory of the
1273 $topdir/.Trash directory is to be used as the user's trash directory
1274 for this partition/device. The name of this subdirectory is the numeric
1275 identifier of the current user ($topdir/.Trash/$uid).
1276 When trashing a file, if this directory does not exist for the current user,
1277 the implementation MUST immediately create it, without any warnings or
1278 delays for the user."
1279 */
1280 trash = makeTrashDir(topDir, userID);
1281 }
1282 }
1283 /*
1284 Method 2:
1285 "If an $topdir/.Trash directory is absent, an $topdir/.Trash-$uid directory is to be
1286 used as the user's trash directory for this device/partition. [...] When trashing a
1287 file, if an $topdir/.Trash-$uid directory does not exist, the implementation MUST
1288 immediately create it, without any warnings or delays for the user."
1289 */
1290 if (trash.isEmpty()) {
1291 topDir = QDir(sourceStorage.rootPath());
1292 const QString userTrashDir = dotTrash + QLatin1Char('-') + userID;
1293 trash = makeTrashDir(topDir, userTrashDir);
1294 }
1295 }
1296 /*
1297 "If both (1) and (2) fail [...], the implementation MUST either trash the
1298 file into the user's “home trash” or refuse to trash it."
1299
1300 We trash the file into the user's home trash.
1301
1302 "Its name and location are $XDG_DATA_HOME/Trash"; $XDG_DATA_HOME is what
1303 QStandardPaths returns for GenericDataLocation. If that doesn't exist, then
1304 we are not running on a freedesktop.org-compliant environment, and give up.
1305 */
1306 if (trash.isEmpty()) {
1307 QDir topDir = QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation);
1308 trash = makeTrashDir(topDir, QLatin1String("Trash"));
1309 if (!QFileInfo(trash).isDir()) {
1310 qWarning(msg: "Unable to establish trash directory in %s",
1311 topDir.path().toLocal8Bit().constData());
1312 }
1313 }
1314
1315 return trash;
1316}
1317#endif // QT_BOOTSTRAPPED
1318
1319//static
1320bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &source,
1321 QFileSystemEntry &newLocation, QSystemError &error)
1322{
1323#ifdef QT_BOOTSTRAPPED
1324 Q_UNUSED(source);
1325 Q_UNUSED(newLocation);
1326 error = QSystemError(ENOSYS, QSystemError::StandardLibraryError);
1327 return false;
1328#else
1329 const QFileInfo sourceInfo(source.filePath());
1330 if (!sourceInfo.exists()) {
1331 error = QSystemError(ENOENT, QSystemError::StandardLibraryError);
1332 return false;
1333 }
1334 const QString sourcePath = sourceInfo.absoluteFilePath();
1335
1336 QDir trashDir(freeDesktopTrashLocation(sourcePath));
1337 if (!trashDir.exists())
1338 return false;
1339 /*
1340 "A trash directory contains two subdirectories, named info and files."
1341 */
1342 const QLatin1String filesDir("files");
1343 const QLatin1String infoDir("info");
1344 trashDir.mkdir(dirName: filesDir);
1345 int savedErrno = errno;
1346 trashDir.mkdir(dirName: infoDir);
1347 if (!savedErrno)
1348 savedErrno = errno;
1349 if (!trashDir.exists(name: filesDir) || !trashDir.exists(name: infoDir)) {
1350 error = QSystemError(savedErrno, QSystemError::StandardLibraryError);
1351 return false;
1352 }
1353 /*
1354 "The $trash/files directory contains the files and directories that were trashed.
1355 The names of files in this directory are to be determined by the implementation;
1356 the only limitation is that they must be unique within the directory. Even if a
1357 file with the same name and location gets trashed many times, each subsequent
1358 trashing must not overwrite a previous copy."
1359 */
1360 const QString trashedName = sourceInfo.isDir()
1361 ? QDir(sourcePath).dirName()
1362 : sourceInfo.fileName();
1363 QString uniqueTrashedName = QLatin1Char('/') + trashedName;
1364 QString infoFileName;
1365 int counter = 0;
1366 QFile infoFile;
1367 auto makeUniqueTrashedName = [trashedName, &counter]() -> QString {
1368 return QString::asprintf(format: "/%ls-%04d", qUtf16Printable(trashedName), ++counter);
1369 };
1370 do {
1371 while (QFile::exists(fileName: trashDir.filePath(fileName: filesDir) + uniqueTrashedName))
1372 uniqueTrashedName = makeUniqueTrashedName();
1373 /*
1374 "The $trash/info directory contains an "information file" for every file and directory
1375 in $trash/files. This file MUST have exactly the same name as the file or directory in
1376 $trash/files, plus the extension ".trashinfo"
1377 [...]
1378 When trashing a file or directory, the implementation MUST create the corresponding
1379 file in $trash/info first. Moreover, it MUST try to do this in an atomic fashion,
1380 so that if two processes try to trash files with the same filename this will result
1381 in two different trash files. On Unix-like systems this is done by generating a
1382 filename, and then opening with O_EXCL. If that succeeds the creation was atomic
1383 (at least on the same machine), if it fails you need to pick another filename."
1384 */
1385 infoFileName = trashDir.filePath(fileName: infoDir)
1386 + uniqueTrashedName + QLatin1String(".trashinfo");
1387 infoFile.setFileName(infoFileName);
1388 if (!infoFile.open(flags: QIODevice::NewOnly | QIODevice::WriteOnly | QIODevice::Text))
1389 uniqueTrashedName = makeUniqueTrashedName();
1390 } while (!infoFile.isOpen());
1391
1392 const QString targetPath = trashDir.filePath(fileName: filesDir) + uniqueTrashedName;
1393 const QFileSystemEntry target(targetPath);
1394
1395 /*
1396 We might fail to rename if source and target are on different file systems.
1397 In that case, we don't try further, i.e. copying and removing the original
1398 is usually not what the user would expect to happen.
1399 */
1400 if (!renameFile(source, target, error)) {
1401 infoFile.close();
1402 infoFile.remove();
1403 return false;
1404 }
1405
1406 QTextStream out(&infoFile);
1407#if QT_CONFIG(textcodec)
1408 out.setCodec("UTF-8");
1409#endif
1410 out << "[Trash Info]" << Qt::endl;
1411 out << "Path=" << sourcePath << Qt::endl;
1412 out << "DeletionDate="
1413 << QDateTime::currentDateTime().toString(format: QLatin1String("yyyy-MM-ddThh:mm:ss")) << Qt::endl;
1414 infoFile.close();
1415
1416 newLocation = QFileSystemEntry(targetPath);
1417 return true;
1418#endif // QT_BOOTSTRAPPED
1419}
1420#endif // Q_OS_DARWIN
1421
1422//static
1423bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1424{
1425#if defined(Q_OS_DARWIN)
1426 if (::clonefile(source.nativeFilePath().constData(),
1427 target.nativeFilePath().constData(), 0) == 0)
1428 return true;
1429 error = QSystemError(errno, QSystemError::StandardLibraryError);
1430 return false;
1431#else
1432 Q_UNUSED(source);
1433 Q_UNUSED(target);
1434 error = QSystemError(ENOSYS, QSystemError::StandardLibraryError); //Function not implemented
1435 return false;
1436#endif
1437}
1438
1439//static
1440bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1441{
1442 QFileSystemEntry::NativePath srcPath = source.nativeFilePath();
1443 QFileSystemEntry::NativePath tgtPath = target.nativeFilePath();
1444
1445 Q_CHECK_FILE_NAME(srcPath, false);
1446 Q_CHECK_FILE_NAME(tgtPath, false);
1447
1448#if defined(RENAME_NOREPLACE) && QT_CONFIG(renameat2)
1449 if (renameat2(AT_FDCWD, old: srcPath, AT_FDCWD, new: tgtPath, RENAME_NOREPLACE) == 0)
1450 return true;
1451
1452 // We can also get EINVAL for some non-local filesystems.
1453 if (errno != EINVAL) {
1454 error = QSystemError(errno, QSystemError::StandardLibraryError);
1455 return false;
1456 }
1457#endif
1458#if defined(Q_OS_DARWIN) && defined(RENAME_EXCL)
1459 if (renameatx_np(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_EXCL) == 0)
1460 return true;
1461 if (errno != ENOTSUP) {
1462 error = QSystemError(errno, QSystemError::StandardLibraryError);
1463 return false;
1464 }
1465#endif
1466
1467 if (SupportsHardlinking && ::link(from: srcPath, to: tgtPath) == 0) {
1468 if (::unlink(name: srcPath) == 0)
1469 return true;
1470
1471 // if we managed to link but can't unlink the source, it's likely
1472 // it's in a directory we don't have write access to; fail the
1473 // renaming instead
1474 int savedErrno = errno;
1475
1476 // this could fail too, but there's nothing we can do about it now
1477 ::unlink(name: tgtPath);
1478
1479 error = QSystemError(savedErrno, QSystemError::StandardLibraryError);
1480 return false;
1481 } else if (!SupportsHardlinking) {
1482 // man 2 link on Linux has:
1483 // EPERM The filesystem containing oldpath and newpath does not
1484 // support the creation of hard links.
1485 errno = EPERM;
1486 }
1487
1488 switch (errno) {
1489 case EACCES:
1490 case EEXIST:
1491 case ENAMETOOLONG:
1492 case ENOENT:
1493 case ENOTDIR:
1494 case EROFS:
1495 case EXDEV:
1496 // accept the error from link(2) (especially EEXIST) and don't retry
1497 break;
1498
1499 default:
1500 // fall back to rename()
1501 // ### Race condition. If a file is moved in after this, it /will/ be
1502 // overwritten.
1503 if (::rename(old: srcPath, new: tgtPath) == 0)
1504 return true;
1505 }
1506
1507 error = QSystemError(errno, QSystemError::StandardLibraryError);
1508 return false;
1509}
1510
1511//static
1512bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1513{
1514 Q_CHECK_FILE_NAME(source, false);
1515 Q_CHECK_FILE_NAME(target, false);
1516
1517 if (::rename(old: source.nativeFilePath().constData(), new: target.nativeFilePath().constData()) == 0)
1518 return true;
1519 error = QSystemError(errno, QSystemError::StandardLibraryError);
1520 return false;
1521}
1522
1523//static
1524bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError &error)
1525{
1526 Q_CHECK_FILE_NAME(entry, false);
1527 if (unlink(name: entry.nativeFilePath().constData()) == 0)
1528 return true;
1529 error = QSystemError(errno, QSystemError::StandardLibraryError);
1530 return false;
1531
1532}
1533
1534static mode_t toMode_t(QFile::Permissions permissions)
1535{
1536 mode_t mode = 0;
1537 if (permissions & (QFile::ReadOwner | QFile::ReadUser))
1538 mode |= S_IRUSR;
1539 if (permissions & (QFile::WriteOwner | QFile::WriteUser))
1540 mode |= S_IWUSR;
1541 if (permissions & (QFile::ExeOwner | QFile::ExeUser))
1542 mode |= S_IXUSR;
1543 if (permissions & QFile::ReadGroup)
1544 mode |= S_IRGRP;
1545 if (permissions & QFile::WriteGroup)
1546 mode |= S_IWGRP;
1547 if (permissions & QFile::ExeGroup)
1548 mode |= S_IXGRP;
1549 if (permissions & QFile::ReadOther)
1550 mode |= S_IROTH;
1551 if (permissions & QFile::WriteOther)
1552 mode |= S_IWOTH;
1553 if (permissions & QFile::ExeOther)
1554 mode |= S_IXOTH;
1555 return mode;
1556}
1557
1558//static
1559bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry, QFile::Permissions permissions, QSystemError &error, QFileSystemMetaData *data)
1560{
1561 Q_CHECK_FILE_NAME(entry, false);
1562
1563 mode_t mode = toMode_t(permissions);
1564 bool success = ::chmod(file: entry.nativeFilePath().constData(), mode: mode) == 0;
1565 if (success && data) {
1566 data->entryFlags &= ~QFileSystemMetaData::Permissions;
1567 data->entryFlags |= QFileSystemMetaData::MetaDataFlag(uint(permissions));
1568 data->knownFlagsMask |= QFileSystemMetaData::Permissions;
1569 }
1570 if (!success)
1571 error = QSystemError(errno, QSystemError::StandardLibraryError);
1572 return success;
1573}
1574
1575//static
1576bool QFileSystemEngine::setPermissions(int fd, QFile::Permissions permissions, QSystemError &error, QFileSystemMetaData *data)
1577{
1578 mode_t mode = toMode_t(permissions);
1579
1580 bool success = ::fchmod(fd: fd, mode: mode) == 0;
1581 if (success && data) {
1582 data->entryFlags &= ~QFileSystemMetaData::Permissions;
1583 data->entryFlags |= QFileSystemMetaData::MetaDataFlag(uint(permissions));
1584 data->knownFlagsMask |= QFileSystemMetaData::Permissions;
1585 }
1586 if (!success)
1587 error = QSystemError(errno, QSystemError::StandardLibraryError);
1588 return success;
1589}
1590
1591//static
1592bool QFileSystemEngine::setFileTime(int fd, const QDateTime &newDate, QAbstractFileEngine::FileTime time, QSystemError &error)
1593{
1594 if (!newDate.isValid() || time == QAbstractFileEngine::BirthTime ||
1595 time == QAbstractFileEngine::MetadataChangeTime) {
1596 error = QSystemError(EINVAL, QSystemError::StandardLibraryError);
1597 return false;
1598 }
1599
1600#if QT_CONFIG(futimens)
1601 struct timespec ts[2];
1602
1603 ts[0].tv_sec = ts[1].tv_sec = 0;
1604 ts[0].tv_nsec = ts[1].tv_nsec = UTIME_OMIT;
1605
1606 const qint64 msecs = newDate.toMSecsSinceEpoch();
1607
1608 if (time == QAbstractFileEngine::AccessTime) {
1609 ts[0].tv_sec = msecs / 1000;
1610 ts[0].tv_nsec = (msecs % 1000) * 1000000;
1611 } else if (time == QAbstractFileEngine::ModificationTime) {
1612 ts[1].tv_sec = msecs / 1000;
1613 ts[1].tv_nsec = (msecs % 1000) * 1000000;
1614 }
1615
1616 if (futimens(fd: fd, times: ts) == -1) {
1617 error = QSystemError(errno, QSystemError::StandardLibraryError);
1618 return false;
1619 }
1620
1621 return true;
1622#elif QT_CONFIG(futimes)
1623 struct timeval tv[2];
1624 QT_STATBUF st;
1625
1626 if (QT_FSTAT(fd, &st) == -1) {
1627 error = QSystemError(errno, QSystemError::StandardLibraryError);
1628 return false;
1629 }
1630
1631 GetFileTimes::get(&st, &tv[0], &tv[1]);
1632
1633 const qint64 msecs = newDate.toMSecsSinceEpoch();
1634
1635 if (time == QAbstractFileEngine::AccessTime) {
1636 tv[0].tv_sec = msecs / 1000;
1637 tv[0].tv_usec = (msecs % 1000) * 1000;
1638 } else if (time == QAbstractFileEngine::ModificationTime) {
1639 tv[1].tv_sec = msecs / 1000;
1640 tv[1].tv_usec = (msecs % 1000) * 1000;
1641 }
1642
1643 if (futimes(fd, tv) == -1) {
1644 error = QSystemError(errno, QSystemError::StandardLibraryError);
1645 return false;
1646 }
1647
1648 return true;
1649#else
1650 Q_UNUSED(fd);
1651 error = QSystemError(ENOSYS, QSystemError::StandardLibraryError);
1652 return false;
1653#endif
1654}
1655
1656QString QFileSystemEngine::homePath()
1657{
1658 QString home = QFile::decodeName(localFileName: qgetenv(varName: "HOME"));
1659 if (home.isEmpty())
1660 home = rootPath();
1661 return QDir::cleanPath(path: home);
1662}
1663
1664QString QFileSystemEngine::rootPath()
1665{
1666 return QLatin1String("/");
1667}
1668
1669QString QFileSystemEngine::tempPath()
1670{
1671#ifdef QT_UNIX_TEMP_PATH_OVERRIDE
1672 return QLatin1String(QT_UNIX_TEMP_PATH_OVERRIDE);
1673#else
1674 QString temp = QFile::decodeName(localFileName: qgetenv(varName: "TMPDIR"));
1675 if (temp.isEmpty()) {
1676 if (false) {
1677#if defined(Q_OS_DARWIN) && !defined(QT_BOOTSTRAPPED)
1678 } else if (NSString *nsPath = NSTemporaryDirectory()) {
1679 temp = QString::fromCFString((CFStringRef)nsPath);
1680#endif
1681 } else {
1682 temp = QLatin1String(_PATH_TMP);
1683 }
1684 }
1685 return QDir(QDir::cleanPath(path: temp)).canonicalPath();
1686#endif
1687}
1688
1689bool QFileSystemEngine::setCurrentPath(const QFileSystemEntry &path)
1690{
1691 int r;
1692 r = QT_CHDIR(path: path.nativeFilePath().constData());
1693 return r >= 0;
1694}
1695
1696QFileSystemEntry QFileSystemEngine::currentPath()
1697{
1698 QFileSystemEntry result;
1699#if defined(__GLIBC__) && !defined(PATH_MAX)
1700 char *currentName = ::get_current_dir_name();
1701 if (currentName) {
1702 result = QFileSystemEntry(QByteArray(currentName), QFileSystemEntry::FromNativePath());
1703 ::free(currentName);
1704 }
1705#else
1706 char currentName[PATH_MAX+1];
1707 if (::getcwd(buf: currentName, PATH_MAX)) {
1708#if defined(Q_OS_VXWORKS) && defined(VXWORKS_VXSIM)
1709 QByteArray dir(currentName);
1710 if (dir.indexOf(':') < dir.indexOf('/'))
1711 dir.remove(0, dir.indexOf(':')+1);
1712
1713 qstrncpy(currentName, dir.constData(), PATH_MAX);
1714#endif
1715 result = QFileSystemEntry(QByteArray(currentName), QFileSystemEntry::FromNativePath());
1716 }
1717# if defined(QT_DEBUG)
1718 if (result.isEmpty())
1719 qWarning(msg: "QFileSystemEngine::currentPath: getcwd() failed");
1720# endif
1721#endif
1722 return result;
1723}
1724QT_END_NAMESPACE
1725

source code of qtbase/src/corelib/io/qfilesystemengine_unix.cpp