Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qfilesystemengine_unix.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 Intel Corporation.
2// Copyright (C) 2016 The Qt Company Ltd.
3// Copyright (C) 2013 Samuel Gaist <samuel.gaist@edeltech.ch>
4// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5// Qt-Security score:critical reason:data-parser
6
7#include "qplatformdefs.h"
9#include "qfile.h"
10#include "qstorageinfo.h"
11#include "qurl.h"
12
13#include <QtCore/qoperatingsystemversion.h>
14#include <QtCore/private/qcore_unix_p.h>
15#include <QtCore/private/qfiledevice_p.h>
16#include <QtCore/private/qfunctions_p.h>
17#include <QtCore/qvarlengtharray.h>
18#ifndef QT_BOOTSTRAPPED
19# include <QtCore/qstandardpaths.h>
20# include <QtCore/private/qtemporaryfile_p.h>
21#endif // QT_BOOTSTRAPPED
22
23#include <grp.h>
24#include <pwd.h>
25#include <stdlib.h> // for realpath()
26#include <unistd.h>
27#include <stdio.h>
28#include <errno.h>
29
30#include <chrono>
31#include <memory> // for std::unique_ptr
32
33#if __has_include(<paths.h>)
34# include <paths.h>
35#endif
36#ifndef _PATH_TMP // from <paths.h>
37# define _PATH_TMP "/tmp"
38#endif
39
40#if __has_include(<sys/disk.h>)
41// BSDs (including Apple Darwin)
42# include <sys/disk.h>
43#endif
44
45#if defined(Q_OS_DARWIN)
46# include <QtCore/private/qcore_mac_p.h>
47# include <CoreFoundation/CFBundle.h>
48# include <UniformTypeIdentifiers/UTType.h>
49# include <UniformTypeIdentifiers/UTCoreTypes.h>
50# include <Foundation/Foundation.h>
51# include <copyfile.h>
52#endif
53
54#ifdef Q_OS_MACOS
55#include <CoreServices/CoreServices.h>
56#endif
57
58#if defined(QT_PLATFORM_UIKIT)
59#include <MobileCoreServices/MobileCoreServices.h>
60#endif
61
62#if defined(Q_OS_LINUX)
63# include <sys/ioctl.h>
64# include <sys/sendfile.h>
65# include <linux/fs.h>
66
67// in case linux/fs.h is too old and doesn't define it:
68#ifndef FICLONE
69# define FICLONE _IOW(0x94, 9, int)
70#endif
71#endif
72
73#if defined(Q_OS_VXWORKS)
74# include <sys/statfs.h>
75# if __has_include(<dosFsLib.h>)
76# include <dosFsLib.h>
77# endif
78#endif
79
80#if defined(Q_OS_ANDROID)
81// statx() is disabled on Android because quite a few systems
82// come with sandboxes that kill applications that make system calls outside a
83// whitelist and several Android vendors can't be bothered to update the list.
84# undef STATX_BASIC_STATS
85#endif
86
87#ifndef STATX_ALL
88struct statx { mode_t stx_mode; }; // dummy
89#endif
90
91QT_BEGIN_NAMESPACE
92
93using namespace Qt::StringLiterals;
94
95static QByteArray &removeTrailingSlashes(QByteArray &path)
96{
97 // Darwin doesn't support trailing /'s, so remove for everyone
98 while (path.size() > 1 && path.endsWith('/'))
99 path.chop(1);
100
101 return path;
102}
103
104enum {
105#ifdef Q_OS_ANDROID
106 // On Android, the link(2) system call has been observed to always fail
107 // with EACCES, regardless of whether there are permission problems or not.
108 SupportsHardlinking = false
109#else
111#endif
112};
113
114#if defined(Q_OS_DARWIN)
115static inline bool hasResourcePropertyFlag(const QFileSystemMetaData &data,
116 const QFileSystemEntry &entry,
117 CFStringRef key, QCFType<CFURLRef> &url)
118{
119 if (!url) {
120 QCFString path = CFStringCreateWithFileSystemRepresentation(0,
121 entry.nativeFilePath().constData());
122 if (!path)
123 return false;
124
125 url = CFURLCreateWithFileSystemPath(0, path, kCFURLPOSIXPathStyle,
126 data.hasFlags(QFileSystemMetaData::DirectoryType));
127 }
128 if (!url)
129 return false;
130
131 CFBooleanRef value;
132 if (CFURLCopyResourcePropertyForKey(url, key, &value, NULL)) {
133 if (value == kCFBooleanTrue)
134 return true;
135 }
136
137 return false;
138}
139
140static bool isPackage(const QFileSystemMetaData &data, const QFileSystemEntry &entry,
141 QCFType<CFURLRef> &cachedUrl)
142{
143 if (!data.isDirectory())
144 return false;
145
146 QFileInfo info(entry.filePath());
147 QString suffix = info.suffix();
148
149 if (suffix.length() > 0) {
150 // First step: is it a bundle?
151 const auto *utType = [UTType typeWithFilenameExtension:suffix.toNSString()];
152 if ([utType conformsToType:UTTypeBundle])
153 return true;
154
155 // Second step: check if an application knows the package type
156 QCFType<CFStringRef> path = entry.filePath().toCFString();
157 QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, path, kCFURLPOSIXPathStyle, true);
158
159 UInt32 type, creator;
160 // Well created packages have the PkgInfo file
161 if (CFBundleGetPackageInfoInDirectory(url, &type, &creator))
162 return true;
163
164#ifdef Q_OS_MACOS
165 // Find if an application other than Finder claims to know how to handle the package
166 QCFType<CFURLRef> application = LSCopyDefaultApplicationURLForURL(url,
167 kLSRolesEditor | kLSRolesViewer, nullptr);
168
169 if (application) {
170 QCFType<CFBundleRef> bundle = CFBundleCreate(kCFAllocatorDefault, application);
171 CFStringRef identifier = CFBundleGetIdentifier(bundle);
172 QString applicationId = QString::fromCFString(identifier);
173 if (applicationId != "com.apple.finder"_L1)
174 return true;
175 }
176#endif
177 }
178
179 // Third step: check if the directory has the package bit set
180 return hasResourcePropertyFlag(data, entry, kCFURLIsPackageKey, cachedUrl);
181}
182#endif
183
184#ifdef Q_OS_VXWORKS
185static inline void forceRequestedPermissionsOnVxWorks(QByteArray dirName, mode_t mode)
186{
187 if (mode == 0) {
188 chmod(dirName, 0);
189 }
190}
191#endif
192
193namespace {
194namespace GetFileTimes {
195qint64 time_t_toMsecs(time_t t)
196{
197 using namespace std::chrono;
198 return milliseconds{seconds{t}}.count();
199}
200
201// fallback set
202[[maybe_unused]] qint64 atime(const QT_STATBUF &statBuffer, ulong)
203{
204 return time_t_toMsecs(statBuffer.st_atime);
205}
206[[maybe_unused]] qint64 birthtime(const QT_STATBUF &, ulong)
207{
208 return Q_INT64_C(0);
209}
210[[maybe_unused]] qint64 ctime(const QT_STATBUF &statBuffer, ulong)
211{
212 return time_t_toMsecs(statBuffer.st_ctime);
213}
214[[maybe_unused]] qint64 mtime(const QT_STATBUF &statBuffer, ulong)
215{
216 return time_t_toMsecs(statBuffer.st_mtime);
217}
218
219// T is either a stat.timespec or statx.statx_timestamp,
220// both have tv_sec and tv_nsec members
221template<typename T>
222qint64 timespecToMSecs(const T &spec)
223{
224 using namespace std::chrono;
225 const nanoseconds nsecs = seconds{spec.tv_sec} + nanoseconds{spec.tv_nsec};
226 return duration_cast<milliseconds>(nsecs).count();
227}
228
229// Xtim, POSIX.1-2008
230template <typename T>
231[[maybe_unused]] static typename std::enable_if<(&T::st_atim, true), qint64>::type
232atime(const T &statBuffer, int)
233{ return timespecToMSecs(statBuffer.st_atim); }
234
235template <typename T>
236[[maybe_unused]] static typename std::enable_if<(&T::st_birthtim, true), qint64>::type
237birthtime(const T &statBuffer, int)
238{ return timespecToMSecs(statBuffer.st_birthtim); }
239
240template <typename T>
241[[maybe_unused]] static typename std::enable_if<(&T::st_ctim, true), qint64>::type
242ctime(const T &statBuffer, int)
243{ return timespecToMSecs(statBuffer.st_ctim); }
244
245template <typename T>
246[[maybe_unused]] static typename std::enable_if<(&T::st_mtim, true), qint64>::type
247mtime(const T &statBuffer, int)
248{ return timespecToMSecs(statBuffer.st_mtim); }
249
250#ifndef st_mtimespec
251// Xtimespec
252template <typename T>
253[[maybe_unused]] static typename std::enable_if<(&T::st_atimespec, true), qint64>::type
254atime(const T &statBuffer, int)
255{ return timespecToMSecs(statBuffer.st_atimespec); }
256
257template <typename T>
258[[maybe_unused]] static typename std::enable_if<(&T::st_birthtimespec, true), qint64>::type
259birthtime(const T &statBuffer, int)
260{ return timespecToMSecs(statBuffer.st_birthtimespec); }
261
262template <typename T>
263[[maybe_unused]] static typename std::enable_if<(&T::st_ctimespec, true), qint64>::type
264ctime(const T &statBuffer, int)
265{ return timespecToMSecs(statBuffer.st_ctimespec); }
266
267template <typename T>
268[[maybe_unused]] static typename std::enable_if<(&T::st_mtimespec, true), qint64>::type
269mtime(const T &statBuffer, int)
270{ return timespecToMSecs(statBuffer.st_mtimespec); }
271#endif
272
273#if !defined(st_mtimensec) && !defined(__alpha__)
274// Xtimensec
275template <typename T>
276[[maybe_unused]] static typename std::enable_if<(&T::st_atimensec, true), qint64>::type
277atime(const T &statBuffer, int)
278{ return statBuffer.st_atime * Q_INT64_C(1000) + statBuffer.st_atimensec / 1000000; }
279
280template <typename T>
281[[maybe_unused]] static typename std::enable_if<(&T::st_birthtimensec, true), qint64>::type
282birthtime(const T &statBuffer, int)
283{ return statBuffer.st_birthtime * Q_INT64_C(1000) + statBuffer.st_birthtimensec / 1000000; }
284
285template <typename T>
286[[maybe_unused]] static typename std::enable_if<(&T::st_ctimensec, true), qint64>::type
287ctime(const T &statBuffer, int)
288{ return statBuffer.st_ctime * Q_INT64_C(1000) + statBuffer.st_ctimensec / 1000000; }
289
290template <typename T>
291[[maybe_unused]] static typename std::enable_if<(&T::st_mtimensec, true), qint64>::type
292mtime(const T &statBuffer, int)
293{ return statBuffer.st_mtime * Q_INT64_C(1000) + statBuffer.st_mtimensec / 1000000; }
294#endif
295} // namespace GetFileTimes
296} // unnamed namespace
297
298// converts QT_STATBUF::st_mode to QFSMD
299// the \a attributes parameter is OS-specific
301flagsFromStMode(mode_t mode, [[maybe_unused]] quint64 attributes)
302{
303 // inode exists
304 QFileSystemMetaData::MetaDataFlags entryFlags = QFileSystemMetaData::ExistsAttribute;
305
306 if (mode & S_IRUSR)
307 entryFlags |= QFileSystemMetaData::OwnerReadPermission;
308 if (mode & S_IWUSR)
309 entryFlags |= QFileSystemMetaData::OwnerWritePermission;
310 if (mode & S_IXUSR)
311 entryFlags |= QFileSystemMetaData::OwnerExecutePermission;
312
313 if (mode & S_IRGRP)
314 entryFlags |= QFileSystemMetaData::GroupReadPermission;
315 if (mode & S_IWGRP)
316 entryFlags |= QFileSystemMetaData::GroupWritePermission;
317 if (mode & S_IXGRP)
318 entryFlags |= QFileSystemMetaData::GroupExecutePermission;
319
320 if (mode & S_IROTH)
321 entryFlags |= QFileSystemMetaData::OtherReadPermission;
322 if (mode & S_IWOTH)
323 entryFlags |= QFileSystemMetaData::OtherWritePermission;
324 if (mode & S_IXOTH)
325 entryFlags |= QFileSystemMetaData::OtherExecutePermission;
326
327 // Type
328 Q_ASSERT(!S_ISLNK(mode)); // can only happen with lstat()
329 if ((mode & S_IFMT) == S_IFREG)
330 entryFlags |= QFileSystemMetaData::FileType;
331 else if ((mode & S_IFMT) == S_IFDIR)
332 entryFlags |= QFileSystemMetaData::DirectoryType;
333 else if ((mode & S_IFMT) != S_IFBLK) // char devices, sockets, FIFOs
334 entryFlags |= QFileSystemMetaData::SequentialType;
335
336 // OS-specific flags
337 // Potential flags for the future:
338 // UF_APPEND and STATX_ATTR_APPEND
339 // UF_COMPRESSED and STATX_ATTR_COMPRESSED
340 // UF_IMMUTABLE and STATX_ATTR_IMMUTABLE
341 // UF_NODUMP and STATX_ATTR_NODUMP
342
343#if defined(Q_OS_VXWORKS) && __has_include(<dosFsLib.h>)
344 if (attributes & DOS_ATTR_RDONLY) {
345 // on a DOS FS, stat() always returns 0777 bits set in st_mode
346 // when DOS FS is read only the write permissions are removed
347 entryFlags &= ~QFileSystemMetaData::OwnerWritePermission;
348 entryFlags &= ~QFileSystemMetaData::GroupWritePermission;
349 entryFlags &= ~QFileSystemMetaData::OtherWritePermission;
350 }
351#endif
352 return entryFlags;
353}
354
355#ifdef STATX_BASIC_STATS
356static int qt_real_statx(int fd, const char *pathname, int flags, struct statx *statxBuffer)
357{
358 unsigned mask = STATX_BASIC_STATS | STATX_BTIME;
359 int ret = statx(fd, pathname, flags | AT_NO_AUTOMOUNT, mask, statxBuffer);
360 return ret == -1 ? -errno : 0;
361}
362
363static int qt_statx(const char *pathname, struct statx *statxBuffer)
364{
365 return qt_real_statx(AT_FDCWD, pathname, 0, statxBuffer);
366}
367
368static int qt_lstatx(const char *pathname, struct statx *statxBuffer)
369{
370 return qt_real_statx(AT_FDCWD, pathname, AT_SYMLINK_NOFOLLOW, statxBuffer);
371}
372
373static int qt_fstatx(int fd, struct statx *statxBuffer)
374{
375 return qt_real_statx(fd, "", AT_EMPTY_PATH, statxBuffer);
376}
377
378inline void QFileSystemMetaData::fillFromStatxBuf(const struct statx &statxBuffer)
379{
380 // Permissions
381 MetaDataFlags flags = flagsFromStMode(statxBuffer.stx_mode, statxBuffer.stx_attributes);
382 entryFlags |= flags;
383 knownFlagsMask |= flags | PosixStatFlags;
384
385 // Attributes
386 if (statxBuffer.stx_nlink == 0)
387 entryFlags |= QFileSystemMetaData::WasDeletedAttribute;
388 size_ = qint64(statxBuffer.stx_size);
389
390 // Times
391 using namespace GetFileTimes;
392 accessTime_ = timespecToMSecs(statxBuffer.stx_atime);
393 metadataChangeTime_ = timespecToMSecs(statxBuffer.stx_ctime);
394 modificationTime_ = timespecToMSecs(statxBuffer.stx_mtime);
395 const bool birthMask = statxBuffer.stx_mask & STATX_BTIME;
396 birthTime_ = birthMask ? timespecToMSecs(statxBuffer.stx_btime) : 0;
397
398 userId_ = statxBuffer.stx_uid;
399 groupId_ = statxBuffer.stx_gid;
400}
401#else
402static int qt_statx(const char *, struct statx *)
403{ return -ENOSYS; }
404
405static int qt_lstatx(const char *, struct statx *)
406{ return -ENOSYS; }
407
408static int qt_fstatx(int, struct statx *)
409{ return -ENOSYS; }
410
411inline void QFileSystemMetaData::fillFromStatxBuf(const struct statx &)
412{ }
413#endif
414
415//static
416bool QFileSystemEngine::fillMetaData(int fd, QFileSystemMetaData &data)
417{
418 auto getSizeForBlockDev = [&](mode_t st_mode) {
419#ifdef BLKGETSIZE64
420 // Linux
421 if (quint64 sz; (st_mode & S_IFMT) == S_IFBLK && ioctl(fd, BLKGETSIZE64, &sz) == 0)
422 data.size_ = sz; // returns byte count
423#elif defined(BLKGETSIZE)
424 // older Linux
425 if (ulong sz; (st_mode & S_IFMT) == S_IFBLK && ioctl(fd, BLKGETSIZE, &sz) == 0)
426 data.size_ = sz * 512; // returns 512-byte sector count
427#elif defined(DKIOCGETBLOCKCOUNT)
428 // Apple Darwin
429 qint32 blksz;
430 if (quint64 count; (st_mode & S_IFMT) == S_IFBLK
431 && ioctl(fd, DKIOCGETBLOCKCOUNT, &count) == 0
432 && ioctl(fd, DKIOCGETBLOCKSIZE, &blksz) == 0)
433 data.size_ = count * blksz;
434#elif defined(DIOCGMEDIASIZE)
435 // FreeBSD
436 // see Linux-compat implementation in
437 // http://fxr.watson.org/fxr/source/compat/linux/linux_ioctl.c?v=FREEBSD-13-STABLE#L282
438 // S_IFCHR is correct: FreeBSD doesn't have block devices any more
439 if (QT_OFF_T sz; (st_mode & S_IFMT) == S_IFCHR && ioctl(fd, DIOCGMEDIASIZE, &sz) == 0)
440 data.size_ = sz; // returns byte count
441#else
442 Q_UNUSED(st_mode);
443#endif
444 };
445 data.entryFlags &= ~QFileSystemMetaData::PosixStatFlags;
446 data.knownFlagsMask |= QFileSystemMetaData::PosixStatFlags;
447
448 struct statx statxBuffer;
449
450 int ret = qt_fstatx(fd, &statxBuffer);
451 if (ret != -ENOSYS) {
452 if (ret == 0) {
453 data.fillFromStatxBuf(statxBuffer);
454 getSizeForBlockDev(statxBuffer.stx_mode);
455 return true;
456 }
457 return false;
458 }
459
460 QT_STATBUF statBuffer;
461
462 if (QT_FSTAT(fd, &statBuffer) == 0) {
463 data.fillFromStatBuf(statBuffer);
464 getSizeForBlockDev(statBuffer.st_mode);
465 return true;
466 }
467
468 return false;
469}
470
471#if defined(_DEXTRA_FIRST)
472static void fillStat64fromStat32(struct stat64 *statBuf64, const struct stat &statBuf32)
473{
474 statBuf64->st_mode = statBuf32.st_mode;
475 statBuf64->st_size = statBuf32.st_size;
476#if _POSIX_VERSION >= 200809L
477 statBuf64->st_ctim = statBuf32.st_ctim;
478 statBuf64->st_mtim = statBuf32.st_mtim;
479 statBuf64->st_atim = statBuf32.st_atim;
480#else
481 statBuf64->st_ctime = statBuf32.st_ctime;
482 statBuf64->st_mtime = statBuf32.st_mtime;
483 statBuf64->st_atime = statBuf32.st_atime;
484#endif
485 statBuf64->st_uid = statBuf32.st_uid;
486 statBuf64->st_gid = statBuf32.st_gid;
487}
488#endif
489
490void QFileSystemMetaData::fillFromStatBuf(const QT_STATBUF &statBuffer)
491{
492 quint64 attributes = 0;
493#if defined(UF_SETTABLE) // BSDs (incl. Darwin)
494 attributes = statBuffer.st_flags;
495#elif defined(Q_OS_VXWORKS) && __has_include(<dosFsLib.h>)
496 attributes = statBuffer.st_attrib;
497#endif
498 // Permissions
499 MetaDataFlags flags = flagsFromStMode(statBuffer.st_mode, attributes);
500 entryFlags |= flags;
501 knownFlagsMask |= flags | PosixStatFlags;
502
503 // Attributes
504 if (statBuffer.st_nlink == 0)
505 entryFlags |= QFileSystemMetaData::WasDeletedAttribute;
506 size_ = statBuffer.st_size;
507
508 // Times
509 accessTime_ = GetFileTimes::atime(statBuffer, 0);
510 birthTime_ = GetFileTimes::birthtime(statBuffer, 0);
511 metadataChangeTime_ = GetFileTimes::ctime(statBuffer, 0);
512 modificationTime_ = GetFileTimes::mtime(statBuffer, 0);
513
514 userId_ = statBuffer.st_uid;
515 groupId_ = statBuffer.st_gid;
516}
517
518void QFileSystemMetaData::fillFromDirEnt(const QT_DIRENT &entry)
519{
520#if defined(_DEXTRA_FIRST)
521 knownFlagsMask = {};
522 entryFlags = {};
523 for (dirent_extra *extra = _DEXTRA_FIRST(&entry); _DEXTRA_VALID(extra, &entry);
524 extra = _DEXTRA_NEXT(extra)) {
525 if (extra->d_type == _DTYPE_STAT || extra->d_type == _DTYPE_LSTAT) {
526
527 const struct dirent_extra_stat * const extra_stat =
528 reinterpret_cast<struct dirent_extra_stat *>(extra);
529
530 // Remember whether this was a link or not, this saves an lstat() call later.
531 if (extra->d_type == _DTYPE_LSTAT) {
532 knownFlagsMask |= QFileSystemMetaData::LinkType;
533 if (S_ISLNK(extra_stat->d_stat.st_mode))
534 entryFlags |= QFileSystemMetaData::LinkType;
535 }
536
537 // For symlinks, the extra type _DTYPE_LSTAT doesn't work for filling out the meta data,
538 // as we need the stat() information there, not the lstat() information.
539 // In this case, don't use the extra information.
540 // Unfortunately, readdir() never seems to return extra info of type _DTYPE_STAT, so for
541 // symlinks, we always incur the cost of an extra stat() call later.
542 if (S_ISLNK(extra_stat->d_stat.st_mode) && extra->d_type == _DTYPE_LSTAT)
543 continue;
544
545#if defined(QT_USE_XOPEN_LFS_EXTENSIONS) && defined(QT_LARGEFILE_SUPPORT)
546 // Even with large file support, d_stat is always of type struct stat, not struct stat64,
547 // so it needs to be converted
548 struct stat64 statBuf;
549 fillStat64fromStat32(&statBuf, extra_stat->d_stat);
550 fillFromStatBuf(statBuf);
551#else
552 fillFromStatBuf(extra_stat->d_stat);
553#endif
554 knownFlagsMask |= QFileSystemMetaData::PosixStatFlags;
555 if (!S_ISLNK(extra_stat->d_stat.st_mode)) {
556 knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
557 entryFlags |= QFileSystemMetaData::ExistsAttribute;
558 }
559 }
560 }
561#elif defined(_DIRENT_HAVE_D_TYPE) || defined(Q_OS_BSD4)
562 // BSD4 includes OS X and iOS
563
564 // ### This will clear all entry flags and knownFlagsMask
565 switch (entry.d_type)
566 {
567 case DT_DIR:
568 knownFlagsMask = QFileSystemMetaData::LinkType
569 | QFileSystemMetaData::FileType
570 | QFileSystemMetaData::DirectoryType
571 | QFileSystemMetaData::SequentialType
572 | QFileSystemMetaData::ExistsAttribute;
573
574 entryFlags = QFileSystemMetaData::DirectoryType
575 | QFileSystemMetaData::ExistsAttribute;
576
577 break;
578
579 case DT_BLK:
580 knownFlagsMask = QFileSystemMetaData::LinkType
581 | QFileSystemMetaData::FileType
582 | QFileSystemMetaData::DirectoryType
583 | QFileSystemMetaData::BundleType
584 | QFileSystemMetaData::AliasType
585 | QFileSystemMetaData::SequentialType
586 | QFileSystemMetaData::ExistsAttribute;
587
588 entryFlags = QFileSystemMetaData::ExistsAttribute;
589
590 break;
591
592 case DT_CHR:
593 case DT_FIFO:
594 case DT_SOCK:
595 // ### System attribute
596 knownFlagsMask = QFileSystemMetaData::LinkType
597 | QFileSystemMetaData::FileType
598 | QFileSystemMetaData::DirectoryType
599 | QFileSystemMetaData::BundleType
600 | QFileSystemMetaData::AliasType
601 | QFileSystemMetaData::SequentialType
602 | QFileSystemMetaData::ExistsAttribute;
603
604 entryFlags = QFileSystemMetaData::SequentialType
605 | QFileSystemMetaData::ExistsAttribute;
606
607 break;
608
609 case DT_LNK:
610 knownFlagsMask = QFileSystemMetaData::LinkType;
611 entryFlags = QFileSystemMetaData::LinkType;
612 break;
613
614 case DT_REG:
615 knownFlagsMask = QFileSystemMetaData::LinkType
616 | QFileSystemMetaData::FileType
617 | QFileSystemMetaData::DirectoryType
618 | QFileSystemMetaData::BundleType
619 | QFileSystemMetaData::SequentialType
620 | QFileSystemMetaData::ExistsAttribute;
621
622 entryFlags = QFileSystemMetaData::FileType
623 | QFileSystemMetaData::ExistsAttribute;
624
625 break;
626
627 case DT_UNKNOWN:
628 default:
629 clear();
630 }
631#else
632 Q_UNUSED(entry);
633#endif
634}
635
636//static
637QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data)
638{
639 Q_CHECK_FILE_NAME(link, link);
640
641 QByteArray s = qt_readlink(link.nativeFilePath().constData());
642 if (s.size() > 0) {
643 QString ret;
644 if (!data.hasFlags(QFileSystemMetaData::DirectoryType))
645 fillMetaData(link, data, QFileSystemMetaData::DirectoryType);
646 if (data.isDirectory() && s[0] != '/') {
647 QDir parent(link.filePath());
648 parent.cdUp();
649 ret = parent.path();
650 if (!ret.isEmpty() && !ret.endsWith(u'/'))
651 ret += u'/';
652 }
653 ret += QFile::decodeName(s);
654
655 if (!ret.startsWith(u'/'))
656 ret.prepend(absoluteName(link).path() + u'/');
657 ret = QDir::cleanPath(ret);
658 if (ret.size() > 1 && ret.endsWith(u'/'))
659 ret.chop(1);
660 return QFileSystemEntry(ret);
661 }
662#if defined(Q_OS_DARWIN)
663 {
664 QCFString path = CFStringCreateWithFileSystemRepresentation(0,
665 QFile::encodeName(QDir::cleanPath(link.filePath())).data());
666 if (!path)
667 return QFileSystemEntry();
668
669 QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, path, kCFURLPOSIXPathStyle,
670 data.hasFlags(QFileSystemMetaData::DirectoryType));
671 if (!url)
672 return QFileSystemEntry();
673
674 QCFType<CFDataRef> bookmarkData = CFURLCreateBookmarkDataFromFile(0, url, NULL);
675 if (!bookmarkData)
676 return QFileSystemEntry();
677
678 QCFType<CFURLRef> resolvedUrl = CFURLCreateByResolvingBookmarkData(0,
679 bookmarkData,
680 (CFURLBookmarkResolutionOptions)(kCFBookmarkResolutionWithoutUIMask
681 | kCFBookmarkResolutionWithoutMountingMask), NULL, NULL, NULL, NULL);
682 if (!resolvedUrl)
683 return QFileSystemEntry();
684
685 QCFString cfstr(CFURLCopyFileSystemPath(resolvedUrl, kCFURLPOSIXPathStyle));
686 if (!cfstr)
687 return QFileSystemEntry();
688
689 return QFileSystemEntry(QString::fromCFString(cfstr));
690 }
691#endif
692 return QFileSystemEntry();
693}
694
695//static
696QFileSystemEntry QFileSystemEngine::getRawLinkPath(const QFileSystemEntry &link,
697 QFileSystemMetaData &data)
698{
699 Q_UNUSED(data)
700 const QByteArray path = qt_readlink(link.nativeFilePath().constData());
701 const QString ret = QFile::decodeName(path);
702 return QFileSystemEntry(ret);
703}
704
705//static
706QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data)
707{
708 Q_CHECK_FILE_NAME(entry, entry);
709 char *resolved_name = nullptr;
710
711#ifdef PATH_MAX
712 // use the stack to avoid the overhead of memory allocation
713 char stack_result[PATH_MAX + 1];
714#else
715 // system with unlimited file paths -> must use heap
716 std::nullptr_t stack_result = nullptr;
717 auto freer = qScopeGuard([&] { free(resolved_name); });
718#endif
719
720# if defined(Q_OS_DARWIN) || defined(Q_OS_ANDROID)
721 // On some Android and macOS versions, realpath() will return a path even if
722 // it does not exist. To work around this, we check existence in advance.
723 if (!data.hasFlags(QFileSystemMetaData::ExistsAttribute))
724 fillMetaData(entry, data, QFileSystemMetaData::ExistsAttribute);
725
726 if (!data.exists())
727 errno = ENOENT;
728 else
729 resolved_name = realpath(entry.nativeFilePath().constData(), stack_result);
730# else
731 resolved_name = realpath(entry.nativeFilePath().constData(), stack_result);
732# endif
733 if (resolved_name) {
734 data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
735 data.entryFlags |= QFileSystemMetaData::ExistsAttribute;
736 return QFileSystemEntry(resolved_name, QFileSystemEntry::FromNativePath{});
737 } else if (errno == ENOENT || errno == ENOTDIR) { // file doesn't exist
738 data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
739 data.entryFlags &= ~(QFileSystemMetaData::ExistsAttribute);
740 return QFileSystemEntry();
741 }
742 return entry;
743}
744
745//static
746QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry)
747{
748 Q_CHECK_FILE_NAME(entry, entry);
749
750 if (entry.isAbsolute() && entry.isClean())
751 return entry;
752
753 QByteArray orig = entry.nativeFilePath();
754 QByteArray result;
755 if (orig.isEmpty() || !orig.startsWith('/')) {
756 QFileSystemEntry cur(currentPath());
757 result = cur.nativeFilePath();
758 }
759 if (!orig.isEmpty() && !(orig.size() == 1 && orig[0] == '.')) {
760 if (!result.isEmpty() && !result.endsWith('/'))
761 result.append('/');
762 result.append(orig);
763 }
764
765 if (result.size() == 1 && result[0] == '/')
766 return QFileSystemEntry(result, QFileSystemEntry::FromNativePath());
767 const bool isDir = result.endsWith('/');
768
769 /* as long as QDir::cleanPath() operates on a QString we have to convert to a string here.
770 * ideally we never convert to a string since that loses information. Please fix after
771 * we get a QByteArray version of QDir::cleanPath()
772 */
773 QFileSystemEntry resultingEntry(result, QFileSystemEntry::FromNativePath());
774 QString stringVersion = QDir::cleanPath(resultingEntry.filePath());
775 if (isDir)
776 stringVersion.append(u'/');
777 return QFileSystemEntry(stringVersion);
778}
779
780//static
781QByteArray QFileSystemEngine::id(const QFileSystemEntry &entry)
782{
783 Q_CHECK_FILE_NAME(entry, QByteArray());
784
785 QT_STATBUF statResult;
786 if (QT_STAT(entry.nativeFilePath().constData(), &statResult)) {
787 if (errno != ENOENT)
788 qErrnoWarning("stat() failed for '%s'", entry.nativeFilePath().constData());
789 return QByteArray();
790 }
791 QByteArray result = QByteArray::number(quint64(statResult.st_dev), 16);
792 result += ':';
793 result += QByteArray::number(quint64(statResult.st_ino));
794 return result;
795}
796
797//static
798QByteArray QFileSystemEngine::id(int fd)
799{
800 QT_STATBUF statResult;
801 if (QT_FSTAT(fd, &statResult)) {
802 qErrnoWarning("fstat() failed for fd %d", fd);
803 return QByteArray();
804 }
805 QByteArray result = QByteArray::number(quint64(statResult.st_dev), 16);
806 result += ':';
807 result += QByteArray::number(quint64(statResult.st_ino));
808 return result;
809}
810
811//static
812QString QFileSystemEngine::resolveUserName(uint userId)
813{
814#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
815 long size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
816 if (size_max == -1)
817 size_max = 1024;
818 QVarLengthArray<char, 1024> buf(size_max);
819#endif
820
821#if !defined(Q_OS_INTEGRITY) && !defined(Q_OS_WASM)
822 struct passwd *pw = nullptr;
823#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) && !defined(Q_OS_VXWORKS)
824 struct passwd entry;
825 getpwuid_r(userId, &entry, buf.data(), buf.size(), &pw);
826#else
827 pw = getpwuid(userId);
828#endif
829 if (pw)
830 return QFile::decodeName(QByteArray(pw->pw_name));
831#else // Integrity || WASM
832 Q_UNUSED(userId);
833#endif
834 return QString();
835}
836
837//static
838QString QFileSystemEngine::resolveGroupName(uint groupId)
839{
840#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
841 long size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
842 if (size_max == -1)
843 size_max = 1024;
844 QVarLengthArray<char, 1024> buf(size_max);
845#endif
846
847#if !defined(Q_OS_INTEGRITY) && !defined(Q_OS_WASM)
848 struct group *gr = nullptr;
849#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))
850 size_max = sysconf(_SC_GETGR_R_SIZE_MAX);
851 if (size_max == -1)
852 size_max = 1024;
853 buf.resize(size_max);
854 struct group entry;
855 // Some large systems have more members than the POSIX max size
856 // Loop over by doubling the buffer size (upper limit 250k)
857 for (long size = size_max; size < 256000; size += size)
858 {
859 buf.resize(size);
860 // ERANGE indicates that the buffer was too small
861 if (!getgrgid_r(groupId, &entry, buf.data(), buf.size(), &gr)
862 || errno != ERANGE)
863 break;
864 }
865#else
866 gr = getgrgid(groupId);
867#endif
868 if (gr)
869 return QFile::decodeName(QByteArray(gr->gr_name));
870#else // Integrity || WASM || VxWorks
871 Q_UNUSED(groupId);
872#endif
873 return QString();
874}
875
876#if defined(Q_OS_DARWIN)
877//static
878QString QFileSystemEngine::bundleName(const QFileSystemEntry &entry)
879{
880 QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, QCFString(entry.filePath()),
881 kCFURLPOSIXPathStyle, true);
882 if (QCFType<CFDictionaryRef> dict = CFBundleCopyInfoDictionaryForURL(url)) {
883 if (CFTypeRef name = (CFTypeRef)CFDictionaryGetValue(dict, kCFBundleNameKey)) {
884 if (CFGetTypeID(name) == CFStringGetTypeID())
885 return QString::fromCFString((CFStringRef)name);
886 }
887 }
888 return QString();
889}
890#endif
891
892//static
893bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemMetaData &data,
894 QFileSystemMetaData::MetaDataFlags what)
895{
896 Q_CHECK_FILE_NAME(entry, false);
897
898 // Detection of WasDeletedAttribute is imperfect: in general, if we can
899 // successfully stat() or access() a file, it hasn't been deleted (though
900 // there are exceptions, like /proc/XXX/fd/ entries on Linux). So we have
901 // to restore this flag in case we fail to stat() anything.
902 auto hadBeenDeleted = data.entryFlags & QFileSystemMetaData::WasDeletedAttribute;
903
904#if defined(Q_OS_DARWIN)
905 if (what & (QFileSystemMetaData::BundleType | QFileSystemMetaData::CaseSensitive)) {
906 if (!data.hasFlags(QFileSystemMetaData::DirectoryType))
907 what |= QFileSystemMetaData::DirectoryType;
908 }
909 if (what & QFileSystemMetaData::AliasType)
910 what |= QFileSystemMetaData::LinkType;
911#endif // defined(Q_OS_DARWIN)
912
913 bool needLstat = what.testAnyFlag(QFileSystemMetaData::LinkType);
914
915#ifdef UF_HIDDEN
916 if (what & QFileSystemMetaData::HiddenAttribute) {
917 // Some OSes (BSDs) have the ability to mark directory entries as
918 // hidden besides the usual Unix way of naming them with a leading dot.
919 // For those OSes, we must lstat() the entry itself so we can find
920 // out if a symlink is hidden or not.
921 needLstat = true;
922 }
923#endif
924
925 // if we're asking for any of the stat(2) flags, then we're getting them all
926 if (what & QFileSystemMetaData::PosixStatFlags)
927 what |= QFileSystemMetaData::PosixStatFlags;
928
929 data.entryFlags &= ~what;
930
931 const QByteArray nativeFilePath = entry.nativeFilePath();
932 int entryErrno = 0; // innocent until proven otherwise
933
934 // first, we may try lstat(2). Possible outcomes:
935 // - success and is a symlink: filesystem entry exists, but we need stat(2)
936 // -> statResult = -1;
937 // - success and is not a symlink: filesystem entry exists and we're done
938 // -> statResult = 0
939 // - failure: really non-existent filesystem entry
940 // -> entryExists = false; statResult = 0;
941 // both stat(2) and lstat(2) may generate a number of different errno
942 // conditions, but of those, the only ones that could happen and the
943 // entry still exist are EACCES, EFAULT, ENOMEM and EOVERFLOW. If we get
944 // EACCES or ENOMEM, then we have no choice on how to proceed, so we may
945 // as well conclude it doesn't exist; EFAULT can't happen and EOVERFLOW
946 // shouldn't happen because we build in _LARGEFILE64.
947 union {
948 QT_STATBUF statBuffer;
949 struct statx statxBuffer;
950 };
951 int statResult = -1;
952 if (needLstat) {
953 mode_t mode = 0;
954 statResult = qt_lstatx(nativeFilePath, &statxBuffer);
955 if (statResult == -ENOSYS) {
956 // use lstat(2)
957 statResult = QT_LSTAT(nativeFilePath, &statBuffer);
958 if (statResult == 0)
959 mode = statBuffer.st_mode;
960 } else if (statResult == 0) {
961 statResult = 1; // record it was statx(2) that succeeded
962 mode = statxBuffer.stx_mode;
963 }
964
965 if (statResult >= 0) {
966#ifdef UF_HIDDEN
967 // currently only supported on systems with no statx() call
968 Q_ASSERT(statResult == 0);
969 if (statBuffer.st_flags & UF_HIDDEN)
970 data.entryFlags |= QFileSystemMetaData::HiddenAttribute;
971 data.knownFlagsMask |= QFileSystemMetaData::HiddenAttribute;
972#endif
973
974 if (S_ISLNK(mode)) {
975 // it's a symlink, we don't know if the file "exists"
976 data.entryFlags |= QFileSystemMetaData::LinkType;
977 statResult = -1; // force stat(2) below
978 } else {
979 // it's a regular file and it exists
980 if (statResult)
981 data.fillFromStatxBuf(statxBuffer);
982 else
983 data.fillFromStatBuf(statBuffer);
984 }
985 } else {
986 // it doesn't exist
987 entryErrno = errno;
988 statResult = -1;
989 data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
990 }
991
992 data.knownFlagsMask |= QFileSystemMetaData::LinkType;
993 }
994
995 // second, we try a regular stat(2)
996 if (statResult == -1 && (what & QFileSystemMetaData::PosixStatFlags)) {
997 if (entryErrno == 0) {
998 data.entryFlags &= ~QFileSystemMetaData::PosixStatFlags;
999 statResult = qt_statx(nativeFilePath, &statxBuffer);
1000 if (statResult == -ENOSYS) {
1001 // use stat(2)
1002 statResult = QT_STAT(nativeFilePath, &statBuffer);
1003 if (statResult == 0)
1004 data.fillFromStatBuf(statBuffer);
1005 } else if (statResult == 0) {
1006 data.fillFromStatxBuf(statxBuffer);
1007 }
1008 }
1009
1010 if (statResult != 0) {
1011 entryErrno = errno;
1012 data.birthTime_ = 0;
1013 data.metadataChangeTime_ = 0;
1014 data.modificationTime_ = 0;
1015 data.accessTime_ = 0;
1016 data.size_ = 0;
1017 data.userId_ = (uint) -2;
1018 data.groupId_ = (uint) -2;
1019
1020 // reset the mask
1021 data.knownFlagsMask |= QFileSystemMetaData::PosixStatFlags
1022 | QFileSystemMetaData::ExistsAttribute;
1023 }
1024 }
1025
1026 // third, we try access(2)
1027 if (what & (QFileSystemMetaData::UserPermissions | QFileSystemMetaData::ExistsAttribute)) {
1028#if defined(Q_OS_VXWORKS)
1029 struct statfs statBuf;
1030 if (statfs(nativeFilePath, &statBuf) == 0) {
1031 if (statBuf.f_type != NFSV2_MAGIC && statBuf.f_type != NFSV3_MAGIC &&
1032 statBuf.f_type != HRFS_MAGIC) {
1033#if __has_include(<dosFsLib.h>)
1034 if (data.entryFlags & QFileSystemMetaData::OwnerWritePermission) {
1035 data.entryFlags |= QFileSystemMetaData::UserWritePermission;
1036 }
1037 if (data.entryFlags & QFileSystemMetaData::OwnerExecutePermission) {
1038 data.entryFlags |= QFileSystemMetaData::UserExecutePermission;
1039 }
1040#endif
1041 data.entryFlags |= QFileSystemMetaData::UserReadPermission |
1042 QFileSystemMetaData::ExistsAttribute;
1043 return true;
1044 }
1045 }
1046#if defined(QT_DEBUG)
1047 else {
1048 //on VxWorks hostfs, used for debugging, failes on statfs and falsely reports
1049 //WasDeleted
1050 statResult = QT_STAT(nativeFilePath, &statBuffer);
1051 if (statResult == 0) {
1052 data.entryFlags |= QFileSystemMetaData::UserReadPermission |
1053 QFileSystemMetaData::ExistsAttribute;
1054 data.entryFlags &= ~QFileSystemMetaData::WasDeletedAttribute;
1055 return true;
1056 }
1057 }
1058#endif
1059#endif
1060 // calculate user permissions
1061 auto checkAccess = [&](QFileSystemMetaData::MetaDataFlag flag, int mode) {
1062 if (entryErrno != 0 || (what & flag) == 0)
1063 return;
1064 if (QT_ACCESS(nativeFilePath, mode) == 0) {
1065 // access ok (and file exists)
1066 data.entryFlags |= flag | QFileSystemMetaData::ExistsAttribute;
1067 } else if (errno != EACCES && errno != EROFS) {
1068 entryErrno = errno;
1069 }
1070 };
1071 checkAccess(QFileSystemMetaData::UserReadPermission, R_OK);
1072 checkAccess(QFileSystemMetaData::UserWritePermission, W_OK);
1073 checkAccess(QFileSystemMetaData::UserExecutePermission, X_OK);
1074
1075 // if we still haven't found out if the file exists, try F_OK
1076 if (entryErrno == 0 && (data.entryFlags & QFileSystemMetaData::ExistsAttribute) == 0) {
1077 if (QT_ACCESS(nativeFilePath, F_OK) == -1)
1078 entryErrno = errno;
1079 else
1080 data.entryFlags |= QFileSystemMetaData::ExistsAttribute;
1081 }
1082
1083 data.knownFlagsMask |= (what & QFileSystemMetaData::UserPermissions) |
1084 QFileSystemMetaData::ExistsAttribute;
1085 }
1086
1087#if defined(Q_OS_DARWIN)
1088 QCFType<CFURLRef> cachedUrl;
1089 if (what & QFileSystemMetaData::AliasType) {
1090 if (entryErrno == 0 && hasResourcePropertyFlag(data, entry, kCFURLIsAliasFileKey, cachedUrl)) {
1091 // kCFURLIsAliasFileKey includes symbolic links, so filter those out
1092 if (!(data.entryFlags & QFileSystemMetaData::LinkType))
1093 data.entryFlags |= QFileSystemMetaData::AliasType;
1094 }
1095 data.knownFlagsMask |= QFileSystemMetaData::AliasType;
1096 }
1097
1098 if (what & QFileSystemMetaData::BundleType) {
1099 if (entryErrno == 0 && isPackage(data, entry, cachedUrl))
1100 data.entryFlags |= QFileSystemMetaData::BundleType;
1101
1102 data.knownFlagsMask |= QFileSystemMetaData::BundleType;
1103 }
1104
1105 if (what & QFileSystemMetaData::CaseSensitive) {
1106 if (entryErrno == 0 && hasResourcePropertyFlag(data, entry,
1107 kCFURLVolumeSupportsCaseSensitiveNamesKey, cachedUrl))
1108 data.entryFlags |= QFileSystemMetaData::CaseSensitive;
1109 data.knownFlagsMask |= QFileSystemMetaData::CaseSensitive;
1110 }
1111#endif
1112
1113 if (what & QFileSystemMetaData::HiddenAttribute
1114 && !data.isHidden()) {
1115 // reusing nativeFilePath from above instead of entry.fileName(), to
1116 // avoid memory allocation for the QString result.
1117 qsizetype lastSlash = nativeFilePath.size();
1118
1119 while (lastSlash && nativeFilePath.at(lastSlash - 1) == '/')
1120 --lastSlash; // skip ending slashes
1121 while (lastSlash && nativeFilePath.at(lastSlash - 1) != '/')
1122 --lastSlash; // skip non-slashes
1123 --lastSlash; // point to the slash or -1 if no slash
1124
1125 if (nativeFilePath.at(lastSlash + 1) == '.')
1126 data.entryFlags |= QFileSystemMetaData::HiddenAttribute;
1127 data.knownFlagsMask |= QFileSystemMetaData::HiddenAttribute;
1128 }
1129
1130 if (entryErrno != 0) {
1131 what &= ~QFileSystemMetaData::LinkType; // don't clear link: could be broken symlink
1132 data.clearFlags(what);
1133
1134 // see comment at the top
1135 data.entryFlags |= hadBeenDeleted;
1136 data.knownFlagsMask |= hadBeenDeleted;
1137
1138 return false;
1139 }
1140 return true;
1141}
1142
1143// static
1144auto QFileSystemEngine::cloneFile(int srcfd, int dstfd, const QFileSystemMetaData &knownData) -> TriStateResult
1145{
1146 QT_STATBUF statBuffer;
1147 if (knownData.hasFlags(QFileSystemMetaData::PosixStatFlags) &&
1148 knownData.isFile()) {
1149 statBuffer.st_mode = S_IFREG;
1150 } else if (knownData.hasFlags(QFileSystemMetaData::PosixStatFlags) &&
1151 knownData.isDirectory()) {
1152 errno = EISDIR;
1153 return TriStateResult::Failed; // fcopyfile(3) returns success on directories
1154 } else if (QT_FSTAT(srcfd, &statBuffer) == -1) {
1155 // errno was set
1156 return TriStateResult::Failed;
1157 } else if (!S_ISREG((statBuffer.st_mode))) {
1158 // not a regular file, let QFile do the copy
1159 return TriStateResult::NotSupported;
1160 }
1161
1162 [[maybe_unused]] auto destinationIsEmpty = [dstfd]() {
1163 QT_STATBUF statBuffer;
1164 return QT_FSTAT(dstfd, &statBuffer) == 0 && statBuffer.st_size == 0;
1165 };
1166 Q_ASSERT(destinationIsEmpty());
1167
1168#if defined(Q_OS_LINUX)
1169 // first, try FICLONE (only works on regular files and only on certain fs)
1170 if (::ioctl(dstfd, FICLONE, srcfd) == 0)
1171 return TriStateResult::Success;
1172#elif defined(Q_OS_DARWIN)
1173 // try fcopyfile
1174 if (fcopyfile(srcfd, dstfd, nullptr, COPYFILE_DATA | COPYFILE_STAT) == 0)
1175 return TriStateResult::Success;
1176 switch (errno) {
1177 case ENOTSUP:
1178 case ENOMEM:
1179 return TriStateResult::NotSupported; // let QFile try
1180 }
1181 return TriStateResult::Failed;
1182#endif
1183
1184#if QT_CONFIG(copy_file_range)
1185 // Second, try copy_file_range. Tested on Linux & FreeBSD: FreeBSD can copy
1186 // across mountpoints, Linux currently (6.12) can only if the source and
1187 // destination mountpoints are the same filesystem type.
1188 QT_OFF_T srcoffset = 0;
1189 ssize_t copied;
1190 do {
1191 copied = ::copy_file_range(srcfd, &srcoffset, dstfd, nullptr, SSIZE_MAX, 0);
1192 } while (copied > 0 || (copied < 0 && errno == EINTR));
1193 if (copied == 0)
1194 return TriStateResult::Success; // EOF -> success
1195 if (srcoffset) {
1196 // some bytes were copied, so this is a real error (like ENOSPC).
1197 copied = ftruncate(dstfd, 0);
1198 return TriStateResult::Failed;
1199 }
1200
1201 // We failed with no bytes copied, so is this a real filesystem failure
1202 // that will remain with sendfile() or the copy pump? Or is it a
1203 // copy_file_range() condition?
1204 switch (errno) {
1205 case EINVAL: // observed with some obscure filesystem combinations
1206 case EXDEV: // Linux can't do xdev file copies (FreeBSD can)
1207 break;
1208
1209 default:
1210 return TriStateResult::Failed;
1211 }
1212#endif
1213
1214#if defined(Q_OS_LINUX)
1215 // For Linux, try sendfile (it can send to some special types too).
1216 // sendfile(2) is limited in the kernel to 2G - 4k
1217 const size_t SendfileSize = 0x7ffff000;
1218
1219 ssize_t n = ::sendfile(dstfd, srcfd, nullptr, SendfileSize);
1220 if (n == -1) {
1221 switch (errno) {
1222 case ENOSPC:
1223 case EIO:
1224 return TriStateResult::Failed;
1225 }
1226
1227 // if we got an error here, give up and try at an upper layer
1228 return TriStateResult::NotSupported;
1229 }
1230
1231 while (n) {
1232 n = ::sendfile(dstfd, srcfd, nullptr, SendfileSize);
1233 if (n == -1) {
1234 // uh oh, this is probably a real error (like ENOSPC)
1235 n = ftruncate(dstfd, 0);
1236 n = lseek(srcfd, 0, SEEK_SET);
1237 n = lseek(dstfd, 0, SEEK_SET);
1238 return TriStateResult::Failed;
1239 }
1240 }
1241
1242 return TriStateResult::Success;
1243#else
1244 Q_UNUSED(dstfd);
1245 return TriStateResult::NotSupported;
1246#endif
1247}
1248
1249static QSystemError createDirectoryWithParents(const QByteArray &path, mode_t mode)
1250{
1251#ifdef Q_OS_WASM
1252 if (path == "/")
1253 return {};
1254#endif
1255
1256 auto tryMkDir = [&path, mode]() -> QSystemError {
1257 if (QT_MKDIR(path, mode) == 0) {
1258#ifdef Q_OS_VXWORKS
1259 forceRequestedPermissionsOnVxWorks(path, mode);
1260#endif
1261 return {};
1262 }
1263 // On macOS with APFS mkdir sets errno to EISDIR, QTBUG-97110
1264 if (errno == EISDIR)
1265 return {};
1266 if (errno == EEXIST || errno == EROFS) {
1267 // ::mkdir() can fail if the dir already exists (it may have been
1268 // created by another thread or another process)
1269 QT_STATBUF st;
1270 if (QT_STAT(path.constData(), &st) != 0)
1271 return QSystemError::stdError(errno);
1272 const bool isDir = (st.st_mode & S_IFMT) == S_IFDIR;
1273 return isDir ? QSystemError{} : QSystemError::stdError(EEXIST);
1274 }
1275 return QSystemError::stdError(errno);
1276 };
1277
1278 QSystemError result = tryMkDir();
1279 if (result.ok())
1280 return result;
1281
1282 // Only handle non-existing dir components in the path
1283 if (result.errorCode != ENOENT)
1284 return result;
1285
1286 qsizetype slash = path.lastIndexOf('/');
1287 while (slash > 0 && path[slash - 1] == '/')
1288 --slash;
1289
1290 if (slash < 1)
1291 return result;
1292
1293 // mkdir failed because the parent dir doesn't exist, so try to create it
1294 QByteArray parentPath = path.first(slash);
1295 if (result = createDirectoryWithParents(parentPath, mode); !result.ok())
1296 return result;
1297
1298 // try again
1299 return tryMkDir();
1300}
1301
1302bool QFileSystemEngine::mkpath(const QFileSystemEntry &entry,
1303 std::optional<QFile::Permissions> permissions)
1304{
1305 QByteArray path = entry.nativeFilePath();
1306 Q_CHECK_FILE_NAME(path, false);
1307
1308 mode_t mode = permissions ? QtPrivate::toMode_t(*permissions) : 0777;
1309 return createDirectoryWithParents(removeTrailingSlashes(path), mode).ok();
1310}
1311
1312bool QFileSystemEngine::mkdir(const QFileSystemEntry &entry,
1313 std::optional<QFile::Permissions> permissions)
1314{
1315 QByteArray path = entry.nativeFilePath();
1316 Q_CHECK_FILE_NAME(path, false);
1317
1318 mode_t mode = permissions ? QtPrivate::toMode_t(*permissions) : 0777;
1319 auto result = QT_MKDIR(removeTrailingSlashes(path), mode) == 0;
1320#if defined(Q_OS_VXWORKS)
1321 if (result)
1322 forceRequestedPermissionsOnVxWorks(path, mode);
1323#endif
1324 return result;
1325}
1326
1327bool QFileSystemEngine::rmdir(const QFileSystemEntry &entry)
1328{
1329 const QByteArray path = entry.nativeFilePath();
1330 Q_CHECK_FILE_NAME(path, false);
1331 return ::rmdir(path.constData()) == 0;
1332}
1333
1334bool QFileSystemEngine::rmpath(const QFileSystemEntry &entry)
1335{
1336 QByteArray path = QFile::encodeName(QDir::cleanPath(entry.filePath()));
1337 Q_CHECK_FILE_NAME(path, false);
1338
1339 if (::rmdir(path.constData()) != 0)
1340 return false; // Only return false if `entry` couldn't be deleted
1341
1342 const char sep = QDir::separator().toLatin1();
1343 qsizetype slash = path.lastIndexOf(sep);
1344 // `slash > 0` because truncate(0) would make `path` empty
1345 for (; slash > 0; slash = path.lastIndexOf(sep)) {
1346 path.truncate(slash);
1347 if (::rmdir(path.constData()) != 0)
1348 break;
1349 }
1350
1351 return true;
1352}
1353
1354//static
1355bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1356{
1357 Q_CHECK_FILE_NAME(source, false);
1358 Q_CHECK_FILE_NAME(target, false);
1359
1360 if (::symlink(source.nativeFilePath().constData(), target.nativeFilePath().constData()) == 0)
1361 return true;
1362 error = QSystemError(errno, QSystemError::StandardLibraryError);
1363 return false;
1364}
1365
1366#if defined(QT_BOOTSTRAPPED) || !defined(AT_FDCWD) || defined(Q_OS_ANDROID) || !QT_CONFIG(datestring) || defined(Q_OS_VXWORKS)
1367// bootstrapped tools don't need this, and we don't want QStorageInfo
1368//static
1369bool QFileSystemEngine::supportsMoveFileToTrash()
1370{
1371 return false;
1372}
1373
1374//static
1375bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &, QFileSystemEntry &,
1376 QSystemError &error)
1377{
1378 error = QSystemError(ENOSYS, QSystemError::StandardLibraryError);
1379 return false;
1380}
1381#elif defined(Q_OS_DARWIN)
1382// see qfilesystemengine_mac.mm
1383#else
1384/*
1385 Implementing as per https://specifications.freedesktop.org/trash-spec/1.0/
1386*/
1387//static
1388bool QFileSystemEngine::supportsMoveFileToTrash()
1389{
1390 return true;
1391}
1392
1393namespace {
1394struct FreeDesktopTrashOperation
1395{
1396 /*
1397 "A trash directory contains two subdirectories, named info and files."
1398 */
1399 QString trashPath;
1400 int filesDirFd = -1;
1401 int infoDirFd = -1;
1402 qsizetype volumePrefixLength = 0;
1403
1404 // relative file paths to the filesDirFd and infoDirFd from above
1405 QByteArray tempTrashFileName;
1406 QByteArray infoFilePath;
1407
1408 int infoFileFd = -1; // if we've already opened it
1409 ~FreeDesktopTrashOperation()
1410 {
1411 close();
1412 }
1413
1414 constexpr bool isTrashDirOpen() const { return filesDirFd != -1 && infoDirFd != -1; }
1415
1416 void close()
1417 {
1418 int savedErrno = errno;
1419 if (infoFileFd != -1) {
1420 Q_ASSERT(infoDirFd != -1);
1421 Q_ASSERT(!infoFilePath.isEmpty());
1422 Q_ASSERT(!trashPath.isEmpty());
1423
1424 QT_CLOSE(infoFileFd);
1425 unlinkat(infoDirFd, infoFilePath, 0);
1426 infoFileFd = -1;
1427 }
1428 if (!tempTrashFileName.isEmpty()) {
1429 Q_ASSERT(filesDirFd != -1);
1430 unlinkat(filesDirFd, tempTrashFileName, 0);
1431 }
1432 if (filesDirFd >= 0)
1433 QT_CLOSE(filesDirFd);
1434 if (infoDirFd >= 0)
1435 QT_CLOSE(infoDirFd);
1436 filesDirFd = infoDirFd = -1;
1437 errno = savedErrno;
1438 }
1439
1440 bool tryCreateInfoFile(const QString &filePath, QSystemError &error)
1441 {
1442 QByteArray p = QFile::encodeName(filePath) + ".trashinfo";
1443 infoFileFd = qt_safe_openat(infoDirFd, p, QT_OPEN_RDWR | QT_OPEN_CREAT | QT_OPEN_EXCL, 0666);
1444 if (infoFileFd < 0) {
1445 error = QSystemError(errno, QSystemError::StandardLibraryError);
1446 return false;
1447 }
1448 infoFilePath = std::move(p);
1449 return true;
1450 }
1451
1452 void commit()
1453 {
1454 QT_CLOSE(infoFileFd);
1455 infoFileFd = -1;
1456 tempTrashFileName = {};
1457 }
1458
1459 // opens a directory and returns the file descriptor
1460 static int openDirFd(int dfd, const char *path, int mode)
1461 {
1462 mode |= QT_OPEN_RDONLY | O_DIRECTORY;
1463 return qt_safe_openat(dfd, path, mode);
1464 }
1465
1466 // opens an XDG Trash directory that is a subdirectory of dfd, creating if necessary
1467 static int openOrCreateDir(int dfd, const char *path, int openmode = 0)
1468 {
1469 // try to open it as a dir, first
1470 int fd = openDirFd(dfd, path, openmode);
1471 if (fd >= 0 || errno != ENOENT)
1472 return fd;
1473
1474 // try to mkdirat
1475 if (mkdirat(dfd, path, 0700) < 0)
1476 return -1;
1477
1478 // try to open it again
1479 return openDirFd(dfd, path, openmode);
1480 }
1481
1482 // opens or makes the XDG Trash hierarchy on parentfd (may be -1) called targetDir
1483 QSystemError getTrashDir(int parentfd, QString targetDir, const QFileSystemEntry &source,
1484 int openmode)
1485 {
1486 if (parentfd == AT_FDCWD)
1487 trashPath = targetDir;
1488 QByteArray nativePath = QFile::encodeName(targetDir);
1489
1490 // open the directory
1491 int trashfd = openOrCreateDir(parentfd, nativePath, openmode);
1492 if (trashfd < 0 && errno != ENOENT)
1493 return QSystemError::stdError(errno);
1494
1495 // check if it is ours (even if we've just mkdirat'ed it)
1496 if (QT_STATBUF st; QT_FSTAT(trashfd, &st) < 0)
1497 return QSystemError::stdError(errno);
1498 else if (st.st_uid != getuid())
1499 return QSystemError::stdError(errno);
1500
1501 filesDirFd = openOrCreateDir(trashfd, "files");
1502 if (filesDirFd >= 0) {
1503 // try to link our file-to-be-trashed here
1504 QTemporaryFileName tfn("XXXXXX"_L1);
1505 for (int i = 0; i < 16; ++i) {
1506 QByteArray attempt = tfn.generateNext();
1507 if (linkat(AT_FDCWD, source.nativeFilePath(), filesDirFd, attempt, 0) == 0) {
1508 tempTrashFileName = std::move(attempt);
1509 break;
1510 }
1511 if (errno != EEXIST)
1512 break;
1513 }
1514
1515 // man 2 link on Linux has:
1516 // EPERM The filesystem containing oldpath and newpath does not
1517 // support the creation of hard links.
1518 // EPERM oldpath is a directory.
1519 // EPERM oldpath is marked immutable or append‐only.
1520 // EMLINK The file referred to by oldpath already has the maximum
1521 // number of links to it.
1522 if (!tempTrashFileName.isEmpty() || errno == EPERM || errno == EMLINK)
1523 infoDirFd = openOrCreateDir(trashfd, "info");
1524 }
1525
1526 if (infoDirFd < 0) {
1527 int saved_errno = errno;
1528 close();
1529 QT_CLOSE(trashfd);
1530 return QSystemError::stdError(saved_errno);
1531 }
1532
1533 QT_CLOSE(trashfd);
1534 return {};
1535 }
1536
1537 QSystemError openMountPointTrashLocation(const QFileSystemEntry &source,
1538 const QStorageInfo &sourceStorage)
1539 {
1540 /*
1541 Method 1:
1542 "An administrator can create an $topdir/.Trash directory. The permissions on this
1543 directories should permit all users who can trash files at all to write in it;
1544 and the “sticky bit” in the permissions must be set, if the file system supports
1545 it.
1546 When trashing a file from a non-home partition/device, an implementation
1547 (if it supports trashing in top directories) MUST check for the presence
1548 of $topdir/.Trash."
1549 */
1550
1551 QSystemError error;
1552 const auto dotTrash = "/.Trash"_L1;
1553 const QString userID = QString::number(::getuid());
1554 QFileSystemEntry dotTrashDir(sourceStorage.rootPath() + dotTrash);
1555
1556 // we MUST check that the sticky bit is set, and that it is not a symlink
1557 int genericTrashFd = openDirFd(AT_FDCWD, dotTrashDir.nativeFilePath(), O_NOFOLLOW);
1558 QT_STATBUF st = {};
1559 if (genericTrashFd < 0 && errno != ENOENT && errno != EACCES) {
1560 // O_DIRECTORY + O_NOFOLLOW produces ENOTDIR on Linux
1561 if (QT_LSTAT(dotTrashDir.nativeFilePath(), &st) == 0 && S_ISLNK(st.st_mode)) {
1562 // we SHOULD report the failed check to the administrator
1563 qCritical("Warning: '%s' is a symlink to '%s'",
1564 dotTrashDir.nativeFilePath().constData(),
1565 qt_readlink(dotTrashDir.nativeFilePath()).constData());
1566 error = QSystemError(ELOOP, QSystemError::StandardLibraryError);
1567 }
1568 } else if (genericTrashFd >= 0) {
1569 QT_FSTAT(genericTrashFd, &st);
1570 if ((st.st_mode & S_ISVTX) == 0) {
1571 // we SHOULD report the failed check to the administrator
1572 qCritical("Warning: '%s' doesn't have sticky bit set!",
1573 dotTrashDir.nativeFilePath().constData());
1574 error = QSystemError(EPERM, QSystemError::StandardLibraryError);
1575 } else {
1576 /*
1577 "If the directory exists and passes the checks, a subdirectory of the
1578 $topdir/.Trash directory is to be used as the user's trash directory
1579 for this partition/device. The name of this subdirectory is the numeric
1580 identifier of the current user ($topdir/.Trash/$uid).
1581 When trashing a file, if this directory does not exist for the current user,
1582 the implementation MUST immediately create it, without any warnings or
1583 delays for the user."
1584 */
1585 if (error = getTrashDir(genericTrashFd, userID, source, O_NOFOLLOW); error.ok()) {
1586 // recreate the resulting path
1587 trashPath = dotTrashDir.filePath() + u'/' + userID;
1588 }
1589 }
1590 QT_CLOSE(genericTrashFd);
1591 }
1592
1593 /*
1594 Method 2:
1595 "If an $topdir/.Trash directory is absent, an $topdir/.Trash-$uid directory is to be
1596 used as the user's trash directory for this device/partition. [...] When trashing a
1597 file, if an $topdir/.Trash-$uid directory does not exist, the implementation MUST
1598 immediately create it, without any warnings or delays for the user."
1599 */
1600 if (!isTrashDirOpen())
1601 error = getTrashDir(AT_FDCWD, sourceStorage.rootPath() + dotTrash + u'-' + userID, source,
1602 O_NOFOLLOW);
1603
1604 if (isTrashDirOpen()) {
1605 volumePrefixLength = sourceStorage.rootPath().size();
1606 if (volumePrefixLength == 1)
1607 volumePrefixLength = 0; // isRoot
1608 else
1609 ++volumePrefixLength; // to include the slash
1610 }
1611 return isTrashDirOpen() ? QSystemError() : error;
1612 }
1613
1614 QSystemError openHomeTrashLocation(const QFileSystemEntry &source)
1615 {
1616 QString topDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
1617 int openmode = 0; // do allow following symlinks
1618 return getTrashDir(AT_FDCWD, topDir + "/Trash"_L1, source, openmode);
1619 }
1620
1621 QSystemError findTrashFor(const QFileSystemEntry &source)
1622 {
1623 /*
1624 First, try the standard Trash in $XDG_DATA_DIRS:
1625 "Its name and location are $XDG_DATA_HOME/Trash"; $XDG_DATA_HOME is what
1626 QStandardPaths returns for GenericDataLocation. If that doesn't exist, then
1627 we are not running on a freedesktop.org-compliant environment, and give up.
1628 */
1629 if (QSystemError error = openHomeTrashLocation(source); error.ok())
1630 return QSystemError();
1631 else if (error.errorCode != EXDEV)
1632 return error;
1633
1634 // didn't work, try to find the trash outside the home filesystem
1635 const QStorageInfo sourceStorage(source.filePath());
1636 if (!sourceStorage.isValid())
1637 return QSystemError::stdError(ENODEV);
1638 return openMountPointTrashLocation(source, sourceStorage);
1639 }
1640};
1641} // unnamed namespace
1642
1643//static
1644bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &source,
1645 QFileSystemEntry &newLocation, QSystemError &error)
1646{
1647 const QFileSystemEntry sourcePath = [&] {
1648 if (QString path = source.filePath(); path.size() > 1 && path.endsWith(u'/')) {
1649 path.chop(1);
1650 return absoluteName(QFileSystemEntry(path));
1651 }
1652 return absoluteName(source);
1653 }();
1654 FreeDesktopTrashOperation op;
1655 if (error = op.findTrashFor(sourcePath); !error.ok())
1656 return false;
1657
1658 /*
1659 "The $trash/files directory contains the files and directories that were trashed.
1660 The names of files in this directory are to be determined by the implementation;
1661 the only limitation is that they must be unique within the directory. Even if a
1662 file with the same name and location gets trashed many times, each subsequent
1663 trashing must not overwrite a previous copy."
1664
1665 We first try the unchanged base name, then try something different if it collides.
1666
1667 "The $trash/info directory contains an "information file" for every file and directory
1668 in $trash/files. This file MUST have exactly the same name as the file or directory in
1669 $trash/files, plus the extension ".trashinfo"
1670 [...]
1671 When trashing a file or directory, the implementation MUST create the corresponding
1672 file in $trash/info first. Moreover, it MUST try to do this in an atomic fashion,
1673 so that if two processes try to trash files with the same filename this will result
1674 in two different trash files. On Unix-like systems this is done by generating a
1675 filename, and then opening with O_EXCL. If that succeeds the creation was atomic
1676 (at least on the same machine), if it fails you need to pick another filename."
1677 */
1678 QString uniqueTrashedName = sourcePath.fileName();
1679 if (!op.tryCreateInfoFile(uniqueTrashedName, error) && error.errorCode == EEXIST) {
1680 // we'll use a counter, starting with the file's inode number to avoid
1681 // collisions
1682 qulonglong counter;
1683 if (QT_STATBUF st; Q_LIKELY(QT_STAT(source.nativeFilePath(), &st) == 0)) {
1684 counter = st.st_ino;
1685 } else {
1686 error = QSystemError(errno, QSystemError::StandardLibraryError);
1687 return false;
1688 }
1689
1690 QString uniqueTrashBase = std::move(uniqueTrashedName);
1691 for (;;) {
1692 uniqueTrashedName = QString::asprintf("%ls-%llu", qUtf16Printable(uniqueTrashBase),
1693 counter++);
1694 if (op.tryCreateInfoFile(uniqueTrashedName, error))
1695 break;
1696 if (error.errorCode != EEXIST)
1697 return false;
1698 };
1699 }
1700
1701 QByteArray info =
1702 "[Trash Info]\n"
1703 "Path=" + QUrl::toPercentEncoding(source.filePath().mid(op.volumePrefixLength), "/") + "\n"
1704 "DeletionDate=" + QDateTime::currentDateTime().toString(Qt::ISODate).toUtf8()
1705 + "\n";
1706 if (QT_WRITE(op.infoFileFd, info.data(), info.size()) < 0) {
1707 error = QSystemError(errno, QSystemError::StandardLibraryError);
1708 return false;
1709 }
1710
1711 /*
1712 If we've already linked the file-to-be-trashed into the trash
1713 directory, we know it's in the same mountpoint and we won't get ENOSPC
1714 renaming the temporary file to the target name either.
1715 */
1716 bool renamed;
1717 if (op.tempTrashFileName.isEmpty()) {
1718 /*
1719 We did not get a link (we're trying to trash a directory or on a
1720 filesystem that doesn't support hardlinking), so rename straight
1721 from the original name. We might fail to rename if source and target
1722 are on different file systems.
1723 */
1724 renamed = renameat(AT_FDCWD, source.nativeFilePath(), op.filesDirFd,
1725 QFile::encodeName(uniqueTrashedName)) == 0;
1726 } else {
1727 renamed = renameat(op.filesDirFd, op.tempTrashFileName, op.filesDirFd,
1728 QFile::encodeName(uniqueTrashedName)) == 0;
1729 if (renamed)
1730 removeFile(sourcePath, error); // success, delete the original file
1731 }
1732 if (!renamed) {
1733 error = QSystemError(errno, QSystemError::StandardLibraryError);
1734 return false;
1735 }
1736
1737 op.commit();
1738 newLocation = QFileSystemEntry(op.trashPath + "/files/"_L1 + uniqueTrashedName);
1739 return true;
1740}
1741#endif // !Q_OS_DARWIN && (!QT_BOOTSTRAPPED && AT_FDCWD && !Q_OS_ANDROID && QT_CONFIG(datestring))
1742
1743//static
1744bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1745{
1746 Q_UNUSED(source);
1747 Q_UNUSED(target);
1748 error = QSystemError(ENOSYS, QSystemError::StandardLibraryError); //Function not implemented
1749 return false;
1750}
1751
1752//static
1753bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1754{
1755 QFileSystemEntry::NativePath srcPath = source.nativeFilePath();
1756 QFileSystemEntry::NativePath tgtPath = target.nativeFilePath();
1757
1758 Q_CHECK_FILE_NAME(srcPath, false);
1759 Q_CHECK_FILE_NAME(tgtPath, false);
1760
1761#if defined(RENAME_NOREPLACE) && QT_CONFIG(renameat2)
1762 if (renameat2(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_NOREPLACE) == 0)
1763 return true;
1764
1765 // We can also get EINVAL for some non-local filesystems.
1766 if (errno != EINVAL) {
1767 error = QSystemError(errno, QSystemError::StandardLibraryError);
1768 return false;
1769 }
1770#endif
1771#if defined(Q_OS_DARWIN) && defined(RENAME_EXCL)
1772 if (renameatx_np(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_EXCL) == 0)
1773 return true;
1774 if (errno != ENOTSUP) {
1775 error = QSystemError(errno, QSystemError::StandardLibraryError);
1776 return false;
1777 }
1778#endif
1779
1780 if (SupportsHardlinking && ::link(srcPath, tgtPath) == 0) {
1781 if (::unlink(srcPath) == 0)
1782 return true;
1783
1784 // if we managed to link but can't unlink the source, it's likely
1785 // it's in a directory we don't have write access to; fail the
1786 // renaming instead
1787 int savedErrno = errno;
1788
1789 // this could fail too, but there's nothing we can do about it now
1790 ::unlink(tgtPath);
1791
1792 error = QSystemError(savedErrno, QSystemError::StandardLibraryError);
1793 return false;
1794 } else if (!SupportsHardlinking) {
1795 // man 2 link on Linux has:
1796 // EPERM The filesystem containing oldpath and newpath does not
1797 // support the creation of hard links.
1798 errno = EPERM;
1799 }
1800
1801 switch (errno) {
1802 case EACCES:
1803 case EEXIST:
1804 case ENAMETOOLONG:
1805 case ENOENT:
1806 case ENOTDIR:
1807 case EROFS:
1808 case EXDEV:
1809 // accept the error from link(2) (especially EEXIST) and don't retry
1810 break;
1811
1812 default:
1813 // fall back to rename()
1814 // ### Race condition. If a file is moved in after this, it /will/ be
1815 // overwritten.
1816 if (::rename(srcPath, tgtPath) == 0)
1817 return true;
1818 }
1819
1820 error = QSystemError(errno, QSystemError::StandardLibraryError);
1821 return false;
1822}
1823
1824//static
1825bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1826{
1827 Q_CHECK_FILE_NAME(source, false);
1828 Q_CHECK_FILE_NAME(target, false);
1829
1830 if (::rename(source.nativeFilePath().constData(), target.nativeFilePath().constData()) == 0)
1831 return true;
1832 error = QSystemError(errno, QSystemError::StandardLibraryError);
1833 return false;
1834}
1835
1836//static
1837bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError &error)
1838{
1839 Q_CHECK_FILE_NAME(entry, false);
1840 if (unlink(entry.nativeFilePath().constData()) == 0)
1841 return true;
1842 error = QSystemError(errno, QSystemError::StandardLibraryError);
1843 return false;
1844
1845}
1846
1847//static
1848bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry,
1849 QFile::Permissions permissions, QSystemError &error)
1850{
1851 Q_CHECK_FILE_NAME(entry, false);
1852
1853 mode_t mode = QtPrivate::toMode_t(permissions);
1854 bool success = ::chmod(entry.nativeFilePath().constData(), mode) == 0;
1855 if (!success)
1856 error = QSystemError(errno, QSystemError::StandardLibraryError);
1857 return success;
1858}
1859
1860//static
1861bool QFileSystemEngine::setPermissions(int fd, QFile::Permissions permissions, QSystemError &error)
1862{
1863 mode_t mode = QtPrivate::toMode_t(permissions);
1864 bool success = ::fchmod(fd, mode) == 0;
1865 if (!success)
1866 error = QSystemError(errno, QSystemError::StandardLibraryError);
1867 return success;
1868}
1869
1870//static
1871bool QFileSystemEngine::setFileTime(int fd, const QDateTime &newDate, QFile::FileTime time, QSystemError &error)
1872{
1873 if (!newDate.isValid()
1874 || time == QFile::FileBirthTime || time == QFile::FileMetadataChangeTime) {
1875 error = QSystemError(EINVAL, QSystemError::StandardLibraryError);
1876 return false;
1877 }
1878
1879#if QT_CONFIG(futimens)
1880 // UTIME_OMIT: leave file timestamp unchanged
1881 struct timespec ts[2] = {{0, UTIME_OMIT}, {0, UTIME_OMIT}};
1882
1883 if (time == QFile::FileAccessTime || time == QFile::FileModificationTime) {
1884 const int idx = time == QFile::FileAccessTime ? 0 : 1;
1885 const std::chrono::milliseconds msecs{newDate.toMSecsSinceEpoch()};
1886 ts[idx] = durationToTimespec(msecs);
1887 }
1888
1889 if (futimens(fd, ts) == -1) {
1890 error = QSystemError(errno, QSystemError::StandardLibraryError);
1891 return false;
1892 }
1893
1894 return true;
1895#else
1896 Q_UNUSED(fd);
1897 error = QSystemError(ENOSYS, QSystemError::StandardLibraryError);
1898 return false;
1899#endif
1900}
1901
1902QString QFileSystemEngine::homePath()
1903{
1904 QString home = qEnvironmentVariable("HOME");
1905 if (home.isEmpty())
1906 home = rootPath();
1907 return QDir::cleanPath(home);
1908}
1909
1910QString QFileSystemEngine::rootPath()
1911{
1912 return u"/"_s;
1913}
1914
1915static constexpr QLatin1StringView nativeTempPath() noexcept
1916{
1917 // _PATH_TMP usually ends in '/' and we don't want that
1918 QLatin1StringView temp = _PATH_TMP ""_L1;
1919 static_assert(_PATH_TMP[0] == '/', "_PATH_TMP needs to be absolute");
1920 static_assert(_PATH_TMP[1] != '\0', "Are you really sure _PATH_TMP should be the root dir??");
1921 if (temp.endsWith(u'/'))
1922 temp.chop(1);
1923 return temp;
1924}
1925
1926QString QFileSystemEngine::tempPath()
1927{
1928#ifdef QT_UNIX_TEMP_PATH_OVERRIDE
1929 return QT_UNIX_TEMP_PATH_OVERRIDE ""_L1;
1930#else
1931 QString temp = qEnvironmentVariable("TMPDIR");
1932# if defined(Q_OS_DARWIN) && !defined(QT_BOOTSTRAPPED)
1933 if (NSString *nsPath; temp.isEmpty() && (nsPath = NSTemporaryDirectory()))
1934 temp = QString::fromCFString((CFStringRef)nsPath);
1935# endif
1936 if (temp.isEmpty())
1937 return nativeTempPath();
1938
1939 // the environment variable may also end in '/'
1940 if (temp.size() > 1 && temp.endsWith(u'/'))
1941 temp.chop(1);
1942
1943 QFileSystemEntry e(temp, QFileSystemEntry::FromInternalPath{});
1944 return QFileSystemEngine::absoluteName(e).filePath();
1945#endif
1946}
1947
1948bool QFileSystemEngine::setCurrentPath(const QFileSystemEntry &path)
1949{
1950 int r;
1951 r = QT_CHDIR(path.nativeFilePath().constData());
1952 return r >= 0;
1953}
1954
1955QFileSystemEntry QFileSystemEngine::currentPath()
1956{
1957 QFileSystemEntry result;
1958#if defined(__GLIBC__) && !defined(PATH_MAX)
1959 char *currentName = ::get_current_dir_name();
1960 if (currentName) {
1961 result = QFileSystemEntry(QByteArray(currentName), QFileSystemEntry::FromNativePath());
1962 ::free(currentName);
1963 }
1964#else
1965 char currentName[PATH_MAX+1];
1966 if (::getcwd(currentName, PATH_MAX)) {
1967#if defined(Q_OS_VXWORKS) && defined(VXWORKS_VXSIM)
1968 QByteArray dir(currentName);
1969 if (dir.indexOf(':') < dir.indexOf('/'))
1970 dir.remove(0, dir.indexOf(':')+1);
1971
1972 qstrncpy(currentName, dir.constData(), PATH_MAX);
1973#endif
1974 result = QFileSystemEntry(QByteArray(currentName), QFileSystemEntry::FromNativePath());
1975 }
1976# if defined(QT_DEBUG)
1977 if (result.isEmpty())
1978 qWarning("QFileSystemEngine::currentPath: getcwd() failed");
1979# endif
1980#endif
1981 return result;
1982}
1983
1984bool QFileSystemEngine::isCaseSensitive(const QFileSystemEntry &entry, QFileSystemMetaData &metaData)
1985{
1986#if defined(Q_OS_DARWIN)
1987 if (!metaData.hasFlags(QFileSystemMetaData::CaseSensitive))
1988 fillMetaData(entry, metaData, QFileSystemMetaData::CaseSensitive);
1989 return metaData.entryFlags.testFlag(QFileSystemMetaData::CaseSensitive);
1990#else
1991 Q_UNUSED(entry);
1992 Q_UNUSED(metaData);
1993 // FIXME: This may not be accurate for all file systems (QTBUG-28246)
1994 return true;
1995#endif
1996}
1997
1998QT_END_NAMESPACE
#define __has_include(x)
#define Q_CHECK_FILE_NAME(name, result)
static int qt_lstatx(const char *, struct statx *)
#define _PATH_TMP
static int qt_fstatx(int, struct statx *)
static QFileSystemMetaData::MetaDataFlags flagsFromStMode(mode_t mode, quint64 attributes)
static QByteArray & removeTrailingSlashes(QByteArray &path)
static QSystemError createDirectoryWithParents(const QByteArray &path, mode_t mode)
static constexpr QLatin1StringView nativeTempPath() noexcept
static int qt_statx(const char *, struct statx *)