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 case ENOSYS: // caused by some containers wrongly filtering the system call
1208 break;
1209
1210 default:
1211 return TriStateResult::Failed;
1212 }
1213#endif
1214
1215#if defined(Q_OS_LINUX)
1216 // For Linux, try sendfile (it can send to some special types too).
1217 // sendfile(2) is limited in the kernel to 2G - 4k
1218 const size_t SendfileSize = 0x7ffff000;
1219
1220 ssize_t n = ::sendfile(dstfd, srcfd, nullptr, SendfileSize);
1221 if (n == -1) {
1222 switch (errno) {
1223 case ENOSPC:
1224 case EIO:
1225 return TriStateResult::Failed;
1226 }
1227
1228 // if we got an error here, give up and try at an upper layer
1229 return TriStateResult::NotSupported;
1230 }
1231
1232 while (n) {
1233 n = ::sendfile(dstfd, srcfd, nullptr, SendfileSize);
1234 if (n == -1) {
1235 // uh oh, this is probably a real error (like ENOSPC)
1236 n = ftruncate(dstfd, 0);
1237 n = lseek(srcfd, 0, SEEK_SET);
1238 n = lseek(dstfd, 0, SEEK_SET);
1239 return TriStateResult::Failed;
1240 }
1241 }
1242
1243 return TriStateResult::Success;
1244#else
1245 Q_UNUSED(dstfd);
1246 return TriStateResult::NotSupported;
1247#endif
1248}
1249
1250static QSystemError createDirectoryWithParents(const QByteArray &path, mode_t mode)
1251{
1252#ifdef Q_OS_WASM
1253 if (path == "/")
1254 return {};
1255#endif
1256
1257 auto tryMkDir = [&path, mode]() -> QSystemError {
1258 if (QT_MKDIR(path, mode) == 0) {
1259#ifdef Q_OS_VXWORKS
1260 forceRequestedPermissionsOnVxWorks(path, mode);
1261#endif
1262 return {};
1263 }
1264 // On macOS with APFS mkdir sets errno to EISDIR, QTBUG-97110
1265 if (errno == EISDIR)
1266 return {};
1267 if (errno == EEXIST || errno == EROFS) {
1268 // ::mkdir() can fail if the dir already exists (it may have been
1269 // created by another thread or another process)
1270 QT_STATBUF st;
1271 if (QT_STAT(path.constData(), &st) != 0)
1272 return QSystemError::stdError(errno);
1273 const bool isDir = (st.st_mode & S_IFMT) == S_IFDIR;
1274 return isDir ? QSystemError{} : QSystemError::stdError(EEXIST);
1275 }
1276 return QSystemError::stdError(errno);
1277 };
1278
1279 QSystemError result = tryMkDir();
1280 if (result.ok())
1281 return result;
1282
1283 // Only handle non-existing dir components in the path
1284 if (result.errorCode != ENOENT)
1285 return result;
1286
1287 qsizetype slash = path.lastIndexOf('/');
1288 while (slash > 0 && path[slash - 1] == '/')
1289 --slash;
1290
1291 if (slash < 1)
1292 return result;
1293
1294 // mkdir failed because the parent dir doesn't exist, so try to create it
1295 QByteArray parentPath = path.first(slash);
1296 if (result = createDirectoryWithParents(parentPath, mode); !result.ok())
1297 return result;
1298
1299 // try again
1300 return tryMkDir();
1301}
1302
1303bool QFileSystemEngine::mkpath(const QFileSystemEntry &entry,
1304 std::optional<QFile::Permissions> permissions)
1305{
1306 QByteArray path = entry.nativeFilePath();
1307 Q_CHECK_FILE_NAME(path, false);
1308
1309 mode_t mode = permissions ? QtPrivate::toMode_t(*permissions) : 0777;
1310 return createDirectoryWithParents(removeTrailingSlashes(path), mode).ok();
1311}
1312
1313bool QFileSystemEngine::mkdir(const QFileSystemEntry &entry,
1314 std::optional<QFile::Permissions> permissions)
1315{
1316 QByteArray path = entry.nativeFilePath();
1317 Q_CHECK_FILE_NAME(path, false);
1318
1319 mode_t mode = permissions ? QtPrivate::toMode_t(*permissions) : 0777;
1320 auto result = QT_MKDIR(removeTrailingSlashes(path), mode) == 0;
1321#if defined(Q_OS_VXWORKS)
1322 if (result)
1323 forceRequestedPermissionsOnVxWorks(path, mode);
1324#endif
1325 return result;
1326}
1327
1328bool QFileSystemEngine::rmdir(const QFileSystemEntry &entry)
1329{
1330 const QByteArray path = entry.nativeFilePath();
1331 Q_CHECK_FILE_NAME(path, false);
1332 return ::rmdir(path.constData()) == 0;
1333}
1334
1335bool QFileSystemEngine::rmpath(const QFileSystemEntry &entry)
1336{
1337 QByteArray path = QFile::encodeName(QDir::cleanPath(entry.filePath()));
1338 Q_CHECK_FILE_NAME(path, false);
1339
1340 if (::rmdir(path.constData()) != 0)
1341 return false; // Only return false if `entry` couldn't be deleted
1342
1343 const char sep = QDir::separator().toLatin1();
1344 qsizetype slash = path.lastIndexOf(sep);
1345 // `slash > 0` because truncate(0) would make `path` empty
1346 for (; slash > 0; slash = path.lastIndexOf(sep)) {
1347 path.truncate(slash);
1348 if (::rmdir(path.constData()) != 0)
1349 break;
1350 }
1351
1352 return true;
1353}
1354
1355//static
1356bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1357{
1358 Q_CHECK_FILE_NAME(source, false);
1359 Q_CHECK_FILE_NAME(target, false);
1360
1361 if (::symlink(source.nativeFilePath().constData(), target.nativeFilePath().constData()) == 0)
1362 return true;
1363 error = QSystemError(errno, QSystemError::StandardLibraryError);
1364 return false;
1365}
1366
1367#if defined(QT_BOOTSTRAPPED) || !defined(AT_FDCWD) || defined(Q_OS_ANDROID) || !QT_CONFIG(datestring) || defined(Q_OS_VXWORKS)
1368// bootstrapped tools don't need this, and we don't want QStorageInfo
1369//static
1370bool QFileSystemEngine::supportsMoveFileToTrash()
1371{
1372 return false;
1373}
1374
1375//static
1376bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &, QFileSystemEntry &,
1377 QSystemError &error)
1378{
1379 error = QSystemError(ENOSYS, QSystemError::StandardLibraryError);
1380 return false;
1381}
1382#elif defined(Q_OS_DARWIN)
1383// see qfilesystemengine_mac.mm
1384#else
1385/*
1386 Implementing as per https://specifications.freedesktop.org/trash-spec/1.0/
1387*/
1388//static
1389bool QFileSystemEngine::supportsMoveFileToTrash()
1390{
1391 return true;
1392}
1393
1394namespace {
1395struct FreeDesktopTrashOperation
1396{
1397 /*
1398 "A trash directory contains two subdirectories, named info and files."
1399 */
1400 QString trashPath;
1401 int filesDirFd = -1;
1402 int infoDirFd = -1;
1403 qsizetype volumePrefixLength = 0;
1404
1405 // relative file paths to the filesDirFd and infoDirFd from above
1406 QByteArray tempTrashFileName;
1407 QByteArray infoFilePath;
1408
1409 int infoFileFd = -1; // if we've already opened it
1410 ~FreeDesktopTrashOperation()
1411 {
1412 close();
1413 }
1414
1415 constexpr bool isTrashDirOpen() const { return filesDirFd != -1 && infoDirFd != -1; }
1416
1417 void close()
1418 {
1419 int savedErrno = errno;
1420 if (infoFileFd != -1) {
1421 Q_ASSERT(infoDirFd != -1);
1422 Q_ASSERT(!infoFilePath.isEmpty());
1423 Q_ASSERT(!trashPath.isEmpty());
1424
1425 QT_CLOSE(infoFileFd);
1426 unlinkat(infoDirFd, infoFilePath, 0);
1427 infoFileFd = -1;
1428 }
1429 if (!tempTrashFileName.isEmpty()) {
1430 Q_ASSERT(filesDirFd != -1);
1431 unlinkat(filesDirFd, tempTrashFileName, 0);
1432 }
1433 if (filesDirFd >= 0)
1434 QT_CLOSE(filesDirFd);
1435 if (infoDirFd >= 0)
1436 QT_CLOSE(infoDirFd);
1437 filesDirFd = infoDirFd = -1;
1438 errno = savedErrno;
1439 }
1440
1441 bool tryCreateInfoFile(const QString &filePath, QSystemError &error)
1442 {
1443 QByteArray p = QFile::encodeName(filePath) + ".trashinfo";
1444 infoFileFd = qt_safe_openat(infoDirFd, p, QT_OPEN_RDWR | QT_OPEN_CREAT | QT_OPEN_EXCL, 0666);
1445 if (infoFileFd < 0) {
1446 error = QSystemError(errno, QSystemError::StandardLibraryError);
1447 return false;
1448 }
1449 infoFilePath = std::move(p);
1450 return true;
1451 }
1452
1453 void commit()
1454 {
1455 QT_CLOSE(infoFileFd);
1456 infoFileFd = -1;
1457 tempTrashFileName = {};
1458 }
1459
1460 // opens a directory and returns the file descriptor
1461 static int openDirFd(int dfd, const char *path, int mode)
1462 {
1463 mode |= QT_OPEN_RDONLY | O_DIRECTORY;
1464 return qt_safe_openat(dfd, path, mode);
1465 }
1466
1467 // opens an XDG Trash directory that is a subdirectory of dfd, creating if necessary
1468 static int openOrCreateDir(int dfd, const char *path, int openmode = 0)
1469 {
1470 // try to open it as a dir, first
1471 int fd = openDirFd(dfd, path, openmode);
1472 if (fd >= 0 || errno != ENOENT)
1473 return fd;
1474
1475 // try to mkdirat
1476 if (mkdirat(dfd, path, 0700) < 0)
1477 return -1;
1478
1479 // try to open it again
1480 return openDirFd(dfd, path, openmode);
1481 }
1482
1483 // opens or makes the XDG Trash hierarchy on parentfd (may be -1) called targetDir
1484 QSystemError getTrashDir(int parentfd, QString targetDir, const QFileSystemEntry &source,
1485 int openmode)
1486 {
1487 if (parentfd == AT_FDCWD)
1488 trashPath = targetDir;
1489 QByteArray nativePath = QFile::encodeName(targetDir);
1490
1491 // open the directory
1492 int trashfd = openOrCreateDir(parentfd, nativePath, openmode);
1493 if (trashfd < 0 && errno != ENOENT)
1494 return QSystemError::stdError(errno);
1495
1496 // check if it is ours (even if we've just mkdirat'ed it)
1497 if (QT_STATBUF st; QT_FSTAT(trashfd, &st) < 0)
1498 return QSystemError::stdError(errno);
1499 else if (st.st_uid != getuid())
1500 return QSystemError::stdError(errno);
1501
1502 filesDirFd = openOrCreateDir(trashfd, "files");
1503 if (filesDirFd >= 0) {
1504 // try to link our file-to-be-trashed here
1505 QTemporaryFileName tfn("XXXXXX"_L1);
1506 for (int i = 0; i < 16; ++i) {
1507 QByteArray attempt = tfn.generateNext();
1508 if (linkat(AT_FDCWD, source.nativeFilePath(), filesDirFd, attempt, 0) == 0) {
1509 tempTrashFileName = std::move(attempt);
1510 break;
1511 }
1512 if (errno != EEXIST)
1513 break;
1514 }
1515
1516 // man 2 link on Linux has:
1517 // EPERM The filesystem containing oldpath and newpath does not
1518 // support the creation of hard links.
1519 // EPERM oldpath is a directory.
1520 // EPERM oldpath is marked immutable or append‐only.
1521 // EMLINK The file referred to by oldpath already has the maximum
1522 // number of links to it.
1523 if (!tempTrashFileName.isEmpty() || errno == EPERM || errno == EMLINK)
1524 infoDirFd = openOrCreateDir(trashfd, "info");
1525 }
1526
1527 if (infoDirFd < 0) {
1528 int saved_errno = errno;
1529 close();
1530 QT_CLOSE(trashfd);
1531 return QSystemError::stdError(saved_errno);
1532 }
1533
1534 QT_CLOSE(trashfd);
1535 return {};
1536 }
1537
1538 QSystemError openMountPointTrashLocation(const QFileSystemEntry &source,
1539 const QStorageInfo &sourceStorage)
1540 {
1541 /*
1542 Method 1:
1543 "An administrator can create an $topdir/.Trash directory. The permissions on this
1544 directories should permit all users who can trash files at all to write in it;
1545 and the “sticky bit” in the permissions must be set, if the file system supports
1546 it.
1547 When trashing a file from a non-home partition/device, an implementation
1548 (if it supports trashing in top directories) MUST check for the presence
1549 of $topdir/.Trash."
1550 */
1551
1552 QSystemError error;
1553 const auto dotTrash = "/.Trash"_L1;
1554 const QString userID = QString::number(::getuid());
1555 QFileSystemEntry dotTrashDir(sourceStorage.rootPath() + dotTrash);
1556
1557 // we MUST check that the sticky bit is set, and that it is not a symlink
1558 int genericTrashFd = openDirFd(AT_FDCWD, dotTrashDir.nativeFilePath(), O_NOFOLLOW);
1559 QT_STATBUF st = {};
1560 if (genericTrashFd < 0 && errno != ENOENT && errno != EACCES) {
1561 // O_DIRECTORY + O_NOFOLLOW produces ENOTDIR on Linux
1562 if (QT_LSTAT(dotTrashDir.nativeFilePath(), &st) == 0 && S_ISLNK(st.st_mode)) {
1563 // we SHOULD report the failed check to the administrator
1564 qCritical("Warning: '%s' is a symlink to '%s'",
1565 dotTrashDir.nativeFilePath().constData(),
1566 qt_readlink(dotTrashDir.nativeFilePath()).constData());
1567 error = QSystemError(ELOOP, QSystemError::StandardLibraryError);
1568 }
1569 } else if (genericTrashFd >= 0) {
1570 QT_FSTAT(genericTrashFd, &st);
1571 if ((st.st_mode & S_ISVTX) == 0) {
1572 // we SHOULD report the failed check to the administrator
1573 qCritical("Warning: '%s' doesn't have sticky bit set!",
1574 dotTrashDir.nativeFilePath().constData());
1575 error = QSystemError(EPERM, QSystemError::StandardLibraryError);
1576 } else {
1577 /*
1578 "If the directory exists and passes the checks, a subdirectory of the
1579 $topdir/.Trash directory is to be used as the user's trash directory
1580 for this partition/device. The name of this subdirectory is the numeric
1581 identifier of the current user ($topdir/.Trash/$uid).
1582 When trashing a file, if this directory does not exist for the current user,
1583 the implementation MUST immediately create it, without any warnings or
1584 delays for the user."
1585 */
1586 if (error = getTrashDir(genericTrashFd, userID, source, O_NOFOLLOW); error.ok()) {
1587 // recreate the resulting path
1588 trashPath = dotTrashDir.filePath() + u'/' + userID;
1589 }
1590 }
1591 QT_CLOSE(genericTrashFd);
1592 }
1593
1594 /*
1595 Method 2:
1596 "If an $topdir/.Trash directory is absent, an $topdir/.Trash-$uid directory is to be
1597 used as the user's trash directory for this device/partition. [...] When trashing a
1598 file, if an $topdir/.Trash-$uid directory does not exist, the implementation MUST
1599 immediately create it, without any warnings or delays for the user."
1600 */
1601 if (!isTrashDirOpen())
1602 error = getTrashDir(AT_FDCWD, sourceStorage.rootPath() + dotTrash + u'-' + userID, source,
1603 O_NOFOLLOW);
1604
1605 if (isTrashDirOpen()) {
1606 volumePrefixLength = sourceStorage.rootPath().size();
1607 if (volumePrefixLength == 1)
1608 volumePrefixLength = 0; // isRoot
1609 else
1610 ++volumePrefixLength; // to include the slash
1611 }
1612 return isTrashDirOpen() ? QSystemError() : error;
1613 }
1614
1615 QSystemError openHomeTrashLocation(const QFileSystemEntry &source)
1616 {
1617 QString topDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
1618 int openmode = 0; // do allow following symlinks
1619 return getTrashDir(AT_FDCWD, topDir + "/Trash"_L1, source, openmode);
1620 }
1621
1622 QSystemError findTrashFor(const QFileSystemEntry &source)
1623 {
1624 /*
1625 First, try the standard Trash in $XDG_DATA_DIRS:
1626 "Its name and location are $XDG_DATA_HOME/Trash"; $XDG_DATA_HOME is what
1627 QStandardPaths returns for GenericDataLocation. If that doesn't exist, then
1628 we are not running on a freedesktop.org-compliant environment, and give up.
1629 */
1630 if (QSystemError error = openHomeTrashLocation(source); error.ok())
1631 return QSystemError();
1632 else if (error.errorCode != EXDEV)
1633 return error;
1634
1635 // didn't work, try to find the trash outside the home filesystem
1636 const QStorageInfo sourceStorage(source.filePath());
1637 if (!sourceStorage.isValid())
1638 return QSystemError::stdError(ENODEV);
1639 return openMountPointTrashLocation(source, sourceStorage);
1640 }
1641};
1642} // unnamed namespace
1643
1644//static
1645bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &source,
1646 QFileSystemEntry &newLocation, QSystemError &error)
1647{
1648 const QFileSystemEntry sourcePath = [&] {
1649 if (QString path = source.filePath(); path.size() > 1 && path.endsWith(u'/')) {
1650 path.chop(1);
1651 return absoluteName(QFileSystemEntry(path));
1652 }
1653 return absoluteName(source);
1654 }();
1655 FreeDesktopTrashOperation op;
1656 if (error = op.findTrashFor(sourcePath); !error.ok())
1657 return false;
1658
1659 /*
1660 "The $trash/files directory contains the files and directories that were trashed.
1661 The names of files in this directory are to be determined by the implementation;
1662 the only limitation is that they must be unique within the directory. Even if a
1663 file with the same name and location gets trashed many times, each subsequent
1664 trashing must not overwrite a previous copy."
1665
1666 We first try the unchanged base name, then try something different if it collides.
1667
1668 "The $trash/info directory contains an "information file" for every file and directory
1669 in $trash/files. This file MUST have exactly the same name as the file or directory in
1670 $trash/files, plus the extension ".trashinfo"
1671 [...]
1672 When trashing a file or directory, the implementation MUST create the corresponding
1673 file in $trash/info first. Moreover, it MUST try to do this in an atomic fashion,
1674 so that if two processes try to trash files with the same filename this will result
1675 in two different trash files. On Unix-like systems this is done by generating a
1676 filename, and then opening with O_EXCL. If that succeeds the creation was atomic
1677 (at least on the same machine), if it fails you need to pick another filename."
1678 */
1679 QString uniqueTrashedName = sourcePath.fileName();
1680 if (!op.tryCreateInfoFile(uniqueTrashedName, error) && error.errorCode == EEXIST) {
1681 // we'll use a counter, starting with the file's inode number to avoid
1682 // collisions
1683 qulonglong counter;
1684 if (QT_STATBUF st; Q_LIKELY(QT_STAT(source.nativeFilePath(), &st) == 0)) {
1685 counter = st.st_ino;
1686 } else {
1687 error = QSystemError(errno, QSystemError::StandardLibraryError);
1688 return false;
1689 }
1690
1691 QString uniqueTrashBase = std::move(uniqueTrashedName);
1692 for (;;) {
1693 uniqueTrashedName = QString::asprintf("%ls-%llu", qUtf16Printable(uniqueTrashBase),
1694 counter++);
1695 if (op.tryCreateInfoFile(uniqueTrashedName, error))
1696 break;
1697 if (error.errorCode != EEXIST)
1698 return false;
1699 };
1700 }
1701
1702 QByteArray info =
1703 "[Trash Info]\n"
1704 "Path=" + QUrl::toPercentEncoding(source.filePath().mid(op.volumePrefixLength), "/") + "\n"
1705 "DeletionDate=" + QDateTime::currentDateTime().toString(Qt::ISODate).toUtf8()
1706 + "\n";
1707 if (QT_WRITE(op.infoFileFd, info.data(), info.size()) < 0) {
1708 error = QSystemError(errno, QSystemError::StandardLibraryError);
1709 return false;
1710 }
1711
1712 /*
1713 If we've already linked the file-to-be-trashed into the trash
1714 directory, we know it's in the same mountpoint and we won't get ENOSPC
1715 renaming the temporary file to the target name either.
1716 */
1717 bool renamed;
1718 if (op.tempTrashFileName.isEmpty()) {
1719 /*
1720 We did not get a link (we're trying to trash a directory or on a
1721 filesystem that doesn't support hardlinking), so rename straight
1722 from the original name. We might fail to rename if source and target
1723 are on different file systems.
1724 */
1725 renamed = renameat(AT_FDCWD, source.nativeFilePath(), op.filesDirFd,
1726 QFile::encodeName(uniqueTrashedName)) == 0;
1727 } else {
1728 renamed = renameat(op.filesDirFd, op.tempTrashFileName, op.filesDirFd,
1729 QFile::encodeName(uniqueTrashedName)) == 0;
1730 if (renamed)
1731 removeFile(sourcePath, error); // success, delete the original file
1732 }
1733 if (!renamed) {
1734 error = QSystemError(errno, QSystemError::StandardLibraryError);
1735 return false;
1736 }
1737
1738 op.commit();
1739 newLocation = QFileSystemEntry(op.trashPath + "/files/"_L1 + uniqueTrashedName);
1740 return true;
1741}
1742#endif // !Q_OS_DARWIN && (!QT_BOOTSTRAPPED && AT_FDCWD && !Q_OS_ANDROID && QT_CONFIG(datestring))
1743
1744//static
1745bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1746{
1747 Q_UNUSED(source);
1748 Q_UNUSED(target);
1749 error = QSystemError(ENOSYS, QSystemError::StandardLibraryError); //Function not implemented
1750 return false;
1751}
1752
1753//static
1754bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1755{
1756 QFileSystemEntry::NativePath srcPath = source.nativeFilePath();
1757 QFileSystemEntry::NativePath tgtPath = target.nativeFilePath();
1758
1759 Q_CHECK_FILE_NAME(srcPath, false);
1760 Q_CHECK_FILE_NAME(tgtPath, false);
1761
1762#if defined(RENAME_NOREPLACE) && QT_CONFIG(renameat2)
1763 if (renameat2(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_NOREPLACE) == 0)
1764 return true;
1765
1766 // We can also get EINVAL for some non-local filesystems.
1767 if (errno != EINVAL) {
1768 error = QSystemError(errno, QSystemError::StandardLibraryError);
1769 return false;
1770 }
1771#endif
1772#if defined(Q_OS_DARWIN) && defined(RENAME_EXCL)
1773 if (renameatx_np(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_EXCL) == 0)
1774 return true;
1775 if (errno != ENOTSUP) {
1776 error = QSystemError(errno, QSystemError::StandardLibraryError);
1777 return false;
1778 }
1779#endif
1780
1781 if (SupportsHardlinking && ::link(srcPath, tgtPath) == 0) {
1782 if (::unlink(srcPath) == 0)
1783 return true;
1784
1785 // if we managed to link but can't unlink the source, it's likely
1786 // it's in a directory we don't have write access to; fail the
1787 // renaming instead
1788 int savedErrno = errno;
1789
1790 // this could fail too, but there's nothing we can do about it now
1791 ::unlink(tgtPath);
1792
1793 error = QSystemError(savedErrno, QSystemError::StandardLibraryError);
1794 return false;
1795 } else if (!SupportsHardlinking) {
1796 // man 2 link on Linux has:
1797 // EPERM The filesystem containing oldpath and newpath does not
1798 // support the creation of hard links.
1799 errno = EPERM;
1800 }
1801
1802 switch (errno) {
1803 case EACCES:
1804 case EEXIST:
1805 case ENAMETOOLONG:
1806 case ENOENT:
1807 case ENOTDIR:
1808 case EROFS:
1809 case EXDEV:
1810 // accept the error from link(2) (especially EEXIST) and don't retry
1811 break;
1812
1813 default:
1814 // fall back to rename()
1815 // ### Race condition. If a file is moved in after this, it /will/ be
1816 // overwritten.
1817 if (::rename(srcPath, tgtPath) == 0)
1818 return true;
1819 }
1820
1821 error = QSystemError(errno, QSystemError::StandardLibraryError);
1822 return false;
1823}
1824
1825//static
1826bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1827{
1828 Q_CHECK_FILE_NAME(source, false);
1829 Q_CHECK_FILE_NAME(target, false);
1830
1831 if (::rename(source.nativeFilePath().constData(), target.nativeFilePath().constData()) == 0)
1832 return true;
1833 error = QSystemError(errno, QSystemError::StandardLibraryError);
1834 return false;
1835}
1836
1837//static
1838bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError &error)
1839{
1840 Q_CHECK_FILE_NAME(entry, false);
1841 if (unlink(entry.nativeFilePath().constData()) == 0)
1842 return true;
1843 error = QSystemError(errno, QSystemError::StandardLibraryError);
1844 return false;
1845
1846}
1847
1848//static
1849bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry,
1850 QFile::Permissions permissions, QSystemError &error)
1851{
1852 Q_CHECK_FILE_NAME(entry, false);
1853
1854 mode_t mode = QtPrivate::toMode_t(permissions);
1855 bool success = ::chmod(entry.nativeFilePath().constData(), mode) == 0;
1856 if (!success)
1857 error = QSystemError(errno, QSystemError::StandardLibraryError);
1858 return success;
1859}
1860
1861//static
1862bool QFileSystemEngine::setPermissions(int fd, QFile::Permissions permissions, QSystemError &error)
1863{
1864 mode_t mode = QtPrivate::toMode_t(permissions);
1865 bool success = ::fchmod(fd, mode) == 0;
1866 if (!success)
1867 error = QSystemError(errno, QSystemError::StandardLibraryError);
1868 return success;
1869}
1870
1871//static
1872bool QFileSystemEngine::setFileTime(int fd, const QDateTime &newDate, QFile::FileTime time, QSystemError &error)
1873{
1874 if (!newDate.isValid()
1875 || time == QFile::FileBirthTime || time == QFile::FileMetadataChangeTime) {
1876 error = QSystemError(EINVAL, QSystemError::StandardLibraryError);
1877 return false;
1878 }
1879
1880#if QT_CONFIG(futimens)
1881 // UTIME_OMIT: leave file timestamp unchanged
1882 struct timespec ts[2] = {{0, UTIME_OMIT}, {0, UTIME_OMIT}};
1883
1884 if (time == QFile::FileAccessTime || time == QFile::FileModificationTime) {
1885 const int idx = time == QFile::FileAccessTime ? 0 : 1;
1886 const std::chrono::milliseconds msecs{newDate.toMSecsSinceEpoch()};
1887 ts[idx] = durationToTimespec(msecs);
1888 }
1889
1890 if (futimens(fd, ts) == -1) {
1891 error = QSystemError(errno, QSystemError::StandardLibraryError);
1892 return false;
1893 }
1894
1895 return true;
1896#else
1897 Q_UNUSED(fd);
1898 error = QSystemError(ENOSYS, QSystemError::StandardLibraryError);
1899 return false;
1900#endif
1901}
1902
1903QString QFileSystemEngine::homePath()
1904{
1905 QString home = qEnvironmentVariable("HOME");
1906 if (home.isEmpty())
1907 home = rootPath();
1908 return QDir::cleanPath(home);
1909}
1910
1911QString QFileSystemEngine::rootPath()
1912{
1913 return u"/"_s;
1914}
1915
1916static constexpr QLatin1StringView nativeTempPath() noexcept
1917{
1918 // _PATH_TMP usually ends in '/' and we don't want that
1919 QLatin1StringView temp = _PATH_TMP ""_L1;
1920 static_assert(_PATH_TMP[0] == '/', "_PATH_TMP needs to be absolute");
1921 static_assert(_PATH_TMP[1] != '\0', "Are you really sure _PATH_TMP should be the root dir??");
1922 if (temp.endsWith(u'/'))
1923 temp.chop(1);
1924 return temp;
1925}
1926
1927QString QFileSystemEngine::tempPath()
1928{
1929#ifdef QT_UNIX_TEMP_PATH_OVERRIDE
1930 return QT_UNIX_TEMP_PATH_OVERRIDE ""_L1;
1931#else
1932 QString temp = qEnvironmentVariable("TMPDIR");
1933# if defined(Q_OS_DARWIN) && !defined(QT_BOOTSTRAPPED)
1934 if (NSString *nsPath; temp.isEmpty() && (nsPath = NSTemporaryDirectory()))
1935 temp = QString::fromCFString((CFStringRef)nsPath);
1936# endif
1937 if (temp.isEmpty())
1938 return nativeTempPath();
1939
1940 // the environment variable may also end in '/'
1941 if (temp.size() > 1 && temp.endsWith(u'/'))
1942 temp.chop(1);
1943
1944 QFileSystemEntry e(temp, QFileSystemEntry::FromInternalPath{});
1945 return QFileSystemEngine::absoluteName(e).filePath();
1946#endif
1947}
1948
1949bool QFileSystemEngine::setCurrentPath(const QFileSystemEntry &path)
1950{
1951 int r;
1952 r = QT_CHDIR(path.nativeFilePath().constData());
1953 return r >= 0;
1954}
1955
1956QFileSystemEntry QFileSystemEngine::currentPath()
1957{
1958 QFileSystemEntry result;
1959#if defined(__GLIBC__) && !defined(PATH_MAX)
1960 char *currentName = ::get_current_dir_name();
1961 if (currentName) {
1962 result = QFileSystemEntry(QByteArray(currentName), QFileSystemEntry::FromNativePath());
1963 ::free(currentName);
1964 }
1965#else
1966 char currentName[PATH_MAX+1];
1967 if (::getcwd(currentName, PATH_MAX)) {
1968#if defined(Q_OS_VXWORKS) && defined(VXWORKS_VXSIM)
1969 QByteArray dir(currentName);
1970 if (dir.indexOf(':') < dir.indexOf('/'))
1971 dir.remove(0, dir.indexOf(':')+1);
1972
1973 qstrncpy(currentName, dir.constData(), PATH_MAX);
1974#endif
1975 result = QFileSystemEntry(QByteArray(currentName), QFileSystemEntry::FromNativePath());
1976 }
1977# if defined(QT_DEBUG)
1978 if (result.isEmpty())
1979 qWarning("QFileSystemEngine::currentPath: getcwd() failed");
1980# endif
1981#endif
1982 return result;
1983}
1984
1985bool QFileSystemEngine::isCaseSensitive(const QFileSystemEntry &entry, QFileSystemMetaData &metaData)
1986{
1987#if defined(Q_OS_DARWIN)
1988 if (!metaData.hasFlags(QFileSystemMetaData::CaseSensitive))
1989 fillMetaData(entry, metaData, QFileSystemMetaData::CaseSensitive);
1990 return metaData.entryFlags.testFlag(QFileSystemMetaData::CaseSensitive);
1991#else
1992 Q_UNUSED(entry);
1993 Q_UNUSED(metaData);
1994 // FIXME: This may not be accurate for all file systems (QTBUG-28246)
1995 return true;
1996#endif
1997}
1998
1999QT_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 *)