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"
8#include "qfilesystemengine_p.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(c: '/'))
99 path.chop(n: 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
110 SupportsHardlinking = true
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(t: 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(t: statBuffer.st_ctime);
213}
214[[maybe_unused]] qint64 mtime(const QT_STATBUF &statBuffer, ulong)
215{
216 return time_t_toMsecs(t: 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>(d: 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
300static QFileSystemMetaData::MetaDataFlags
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(dirfd: fd, path: pathname, flags: flags | AT_NO_AUTOMOUNT, mask: mask, buf: 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, flags: 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, pathname: "", AT_EMPTY_PATH, statxBuffer);
376}
377
378inline void QFileSystemMetaData::fillFromStatxBuf(const struct statx &statxBuffer)
379{
380 // Permissions
381 MetaDataFlags flags = flagsFromStMode(mode: statxBuffer.stx_mode, attributes: 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(spec: statxBuffer.stx_atime);
393 metadataChangeTime_ = timespecToMSecs(spec: statxBuffer.stx_ctime);
394 modificationTime_ = timespecToMSecs(spec: statxBuffer.stx_mtime);
395 const bool birthMask = statxBuffer.stx_mask & STATX_BTIME;
396 birthTime_ = birthMask ? timespecToMSecs(spec: 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: 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: &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: fd, buf: &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(mode: 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(path: link.nativeFilePath().constData());
642 if (s.size() > 0) {
643 QString ret;
644 if (!data.hasFlags(flags: QFileSystemMetaData::DirectoryType))
645 fillMetaData(entry: link, data, what: 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(c: u'/'))
651 ret += u'/';
652 }
653 ret += QFile::decodeName(localFileName: s);
654
655 if (!ret.startsWith(c: u'/'))
656 ret.prepend(s: absoluteName(entry: link).path() + u'/');
657 ret = QDir::cleanPath(path: ret);
658 if (ret.size() > 1 && ret.endsWith(c: u'/'))
659 ret.chop(n: 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(path: link.nativeFilePath().constData());
701 const QString ret = QFile::decodeName(localFileName: 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(name: entry.nativeFilePath().constData(), resolved: 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(c: '/')) {
756 QFileSystemEntry cur(currentPath());
757 result = cur.nativeFilePath();
758 }
759 if (!orig.isEmpty() && !(orig.size() == 1 && orig[0] == '.')) {
760 if (!result.isEmpty() && !result.endsWith(c: '/'))
761 result.append(c: '/');
762 result.append(a: orig);
763 }
764
765 if (result.size() == 1 && result[0] == '/')
766 return QFileSystemEntry(result, QFileSystemEntry::FromNativePath());
767 const bool isDir = result.endsWith(c: '/');
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(path: resultingEntry.filePath());
775 if (isDir)
776 stringVersion.append(c: 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(file: entry.nativeFilePath().constData(), buf: &statResult)) {
787 if (errno != ENOENT)
788 qErrnoWarning(msg: "stat() failed for '%s'", entry.nativeFilePath().constData());
789 return QByteArray();
790 }
791 QByteArray result = QByteArray::number(quint64(statResult.st_dev), base: 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: fd, buf: &statResult)) {
802 qErrnoWarning(msg: "fstat() failed for fd %d", fd);
803 return QByteArray();
804 }
805 QByteArray result = QByteArray::number(quint64(statResult.st_dev), base: 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(uid: userId, resultbuf: &entry, buffer: buf.data(), buflen: buf.size(), result: &pw);
826#else
827 pw = getpwuid(userId);
828#endif
829 if (pw)
830 return QFile::decodeName(localFileName: 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(sz: 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(sz: size);
860 // ERANGE indicates that the buffer was too small
861 if (!getgrgid_r(gid: groupId, resultbuf: &entry, buffer: buf.data(), buflen: buf.size(), result: &gr)
862 || errno != ERANGE)
863 break;
864 }
865#else
866 gr = getgrgid(groupId);
867#endif
868 if (gr)
869 return QFile::decodeName(localFileName: 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(flag: 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 // defined(Q_OS_DARWIN)
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 _LARGEFIE64.
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(pathname: nativeFilePath, statxBuffer: &statxBuffer);
955 if (statResult == -ENOSYS) {
956 // use lstst(2)
957 statResult = QT_LSTAT(file: nativeFilePath, buf: &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 reagular file and it exists
980 if (statResult)
981 data.fillFromStatxBuf(statxBuffer: statxBuffer);
982 else
983 data.fillFromStatBuf(statBuffer: 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(pathname: nativeFilePath, statxBuffer: &statxBuffer);
1000 if (statResult == -ENOSYS) {
1001 // use stat(2)
1002 statResult = QT_STAT(file: nativeFilePath, buf: &statBuffer);
1003 if (statResult == 0)
1004 data.fillFromStatBuf(statBuffer: statBuffer);
1005 } else if (statResult == 0) {
1006 data.fillFromStatxBuf(statxBuffer: 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 // on VxWorks if the filesystem is not POSIX, access() always returns false, despite the
1030 // file is readable
1031 struct statfs statBuf;
1032 if (statfs(nativeFilePath, &statBuf) != 0) {
1033 what &= ~QFileSystemMetaData::LinkType; // don't clear link: could be broken symlink
1034 data.clearFlags(what);
1035 return false;
1036 }
1037 if (statBuf.f_type != NFSV2_MAGIC && statBuf.f_type != NFSV3_MAGIC &&
1038 statBuf.f_type != HRFS_MAGIC) {
1039#if __has_include(<dosFsLib.h>)
1040 if (data.entryFlags & QFileSystemMetaData::OwnerWritePermission) {
1041 data.entryFlags |= QFileSystemMetaData::UserWritePermission;
1042 }
1043 if (data.entryFlags & QFileSystemMetaData::OwnerExecutePermission) {
1044 data.entryFlags |= QFileSystemMetaData::UserExecutePermission;
1045 }
1046#endif
1047 data.entryFlags |= QFileSystemMetaData::UserReadPermission |
1048 QFileSystemMetaData::ExistsAttribute;
1049 return true;
1050 }
1051#endif
1052 // calculate user permissions
1053 auto checkAccess = [&](QFileSystemMetaData::MetaDataFlag flag, int mode) {
1054 if (entryErrno != 0 || (what & flag) == 0)
1055 return;
1056 if (QT_ACCESS(name: nativeFilePath, type: mode) == 0) {
1057 // access ok (and file exists)
1058 data.entryFlags |= flag | QFileSystemMetaData::ExistsAttribute;
1059 } else if (errno != EACCES && errno != EROFS) {
1060 entryErrno = errno;
1061 }
1062 };
1063 checkAccess(QFileSystemMetaData::UserReadPermission, R_OK);
1064 checkAccess(QFileSystemMetaData::UserWritePermission, W_OK);
1065 checkAccess(QFileSystemMetaData::UserExecutePermission, X_OK);
1066
1067 // if we still haven't found out if the file exists, try F_OK
1068 if (entryErrno == 0 && (data.entryFlags & QFileSystemMetaData::ExistsAttribute) == 0) {
1069 if (QT_ACCESS(name: nativeFilePath, F_OK) == -1)
1070 entryErrno = errno;
1071 else
1072 data.entryFlags |= QFileSystemMetaData::ExistsAttribute;
1073 }
1074
1075 data.knownFlagsMask |= (what & QFileSystemMetaData::UserPermissions) |
1076 QFileSystemMetaData::ExistsAttribute;
1077 }
1078
1079#if defined(Q_OS_DARWIN)
1080 QCFType<CFURLRef> cachedUrl;
1081 if (what & QFileSystemMetaData::AliasType) {
1082 if (entryErrno == 0 && hasResourcePropertyFlag(data, entry, kCFURLIsAliasFileKey, cachedUrl)) {
1083 // kCFURLIsAliasFileKey includes symbolic links, so filter those out
1084 if (!(data.entryFlags & QFileSystemMetaData::LinkType))
1085 data.entryFlags |= QFileSystemMetaData::AliasType;
1086 }
1087 data.knownFlagsMask |= QFileSystemMetaData::AliasType;
1088 }
1089
1090 if (what & QFileSystemMetaData::BundleType) {
1091 if (entryErrno == 0 && isPackage(data, entry, cachedUrl))
1092 data.entryFlags |= QFileSystemMetaData::BundleType;
1093
1094 data.knownFlagsMask |= QFileSystemMetaData::BundleType;
1095 }
1096
1097 if (what & QFileSystemMetaData::CaseSensitive) {
1098 if (entryErrno == 0 && hasResourcePropertyFlag(data, entry,
1099 kCFURLVolumeSupportsCaseSensitiveNamesKey, cachedUrl))
1100 data.entryFlags |= QFileSystemMetaData::CaseSensitive;
1101 data.knownFlagsMask |= QFileSystemMetaData::CaseSensitive;
1102 }
1103#endif
1104
1105 if (what & QFileSystemMetaData::HiddenAttribute
1106 && !data.isHidden()) {
1107 // reusing nativeFilePath from above instead of entry.fileName(), to
1108 // avoid memory allocation for the QString result.
1109 qsizetype lastSlash = nativeFilePath.size();
1110
1111 while (lastSlash && nativeFilePath.at(i: lastSlash - 1) == '/')
1112 --lastSlash; // skip ending slashes
1113 while (lastSlash && nativeFilePath.at(i: lastSlash - 1) != '/')
1114 --lastSlash; // skip non-slashes
1115 --lastSlash; // point to the slash or -1 if no slash
1116
1117 if (nativeFilePath.at(i: lastSlash + 1) == '.')
1118 data.entryFlags |= QFileSystemMetaData::HiddenAttribute;
1119 data.knownFlagsMask |= QFileSystemMetaData::HiddenAttribute;
1120 }
1121
1122 if (entryErrno != 0) {
1123 what &= ~QFileSystemMetaData::LinkType; // don't clear link: could be broken symlink
1124 data.clearFlags(flags: what);
1125
1126 // see comment at the top
1127 data.entryFlags |= hadBeenDeleted;
1128 data.knownFlagsMask |= hadBeenDeleted;
1129
1130 return false;
1131 }
1132 return true;
1133}
1134
1135// static
1136auto QFileSystemEngine::cloneFile(int srcfd, int dstfd, const QFileSystemMetaData &knownData) -> TriStateResult
1137{
1138 QT_STATBUF statBuffer;
1139 if (knownData.hasFlags(flags: QFileSystemMetaData::PosixStatFlags) &&
1140 knownData.isFile()) {
1141 statBuffer.st_mode = S_IFREG;
1142 } else if (knownData.hasFlags(flags: QFileSystemMetaData::PosixStatFlags) &&
1143 knownData.isDirectory()) {
1144 errno = EISDIR;
1145 return TriStateResult::Failed; // fcopyfile(3) returns success on directories
1146 } else if (QT_FSTAT(fd: srcfd, buf: &statBuffer) == -1) {
1147 // errno was set
1148 return TriStateResult::Failed;
1149 } else if (!S_ISREG((statBuffer.st_mode))) {
1150 // not a regular file, let QFile do the copy
1151 return TriStateResult::NotSupported;
1152 }
1153
1154 [[maybe_unused]] auto destinationIsEmpty = [dstfd]() {
1155 QT_STATBUF statBuffer;
1156 return QT_FSTAT(fd: dstfd, buf: &statBuffer) == 0 && statBuffer.st_size == 0;
1157 };
1158 Q_ASSERT(destinationIsEmpty());
1159
1160#if defined(Q_OS_LINUX)
1161 // first, try FICLONE (only works on regular files and only on certain fs)
1162 if (::ioctl(fd: dstfd, FICLONE, srcfd) == 0)
1163 return TriStateResult::Success;
1164#elif defined(Q_OS_DARWIN)
1165 // try fcopyfile
1166 if (fcopyfile(srcfd, dstfd, nullptr, COPYFILE_DATA | COPYFILE_STAT) == 0)
1167 return TriStateResult::Success;
1168 switch (errno) {
1169 case ENOTSUP:
1170 case ENOMEM:
1171 return TriStateResult::NotSupported; // let QFile try
1172 }
1173 return TriStateResult::Failed;
1174#endif
1175
1176#if QT_CONFIG(copy_file_range)
1177 // Second, try copy_file_range. Tested on Linux & FreeBSD: FreeBSD can copy
1178 // across mountpoints, Linux currently (6.12) can only if the source and
1179 // destination mountpoints are the same filesystem type.
1180 QT_OFF_T srcoffset = 0;
1181 ssize_t copied;
1182 do {
1183 copied = ::copy_file_range(infd: srcfd, pinoff: &srcoffset, outfd: dstfd, poutoff: nullptr, SSIZE_MAX, flags: 0);
1184 } while (copied > 0 || (copied < 0 && errno == EINTR));
1185 if (copied == 0)
1186 return TriStateResult::Success; // EOF -> success
1187 if (srcoffset) {
1188 // some bytes were copied, so this is a real error (like ENOSPC).
1189 copied = ftruncate(fd: dstfd, length: 0);
1190 return TriStateResult::Failed;
1191 }
1192 if (errno != EXDEV)
1193 return TriStateResult::Failed;
1194#endif
1195
1196#if defined(Q_OS_LINUX)
1197 // For Linux, try sendfile (it can send to some special types too).
1198 // sendfile(2) is limited in the kernel to 2G - 4k
1199 const size_t SendfileSize = 0x7ffff000;
1200
1201 ssize_t n = ::sendfile(out_fd: dstfd, in_fd: srcfd, offset: nullptr, count: SendfileSize);
1202 if (n == -1) {
1203 switch (errno) {
1204 case ENOSPC:
1205 case EIO:
1206 return TriStateResult::Failed;
1207 }
1208
1209 // if we got an error here, give up and try at an upper layer
1210 return TriStateResult::NotSupported;
1211 }
1212
1213 while (n) {
1214 n = ::sendfile(out_fd: dstfd, in_fd: srcfd, offset: nullptr, count: SendfileSize);
1215 if (n == -1) {
1216 // uh oh, this is probably a real error (like ENOSPC)
1217 n = ftruncate(fd: dstfd, length: 0);
1218 n = lseek(fd: srcfd, offset: 0, SEEK_SET);
1219 n = lseek(fd: dstfd, offset: 0, SEEK_SET);
1220 return TriStateResult::Failed;
1221 }
1222 }
1223
1224 return TriStateResult::Success;
1225#else
1226 Q_UNUSED(dstfd);
1227 return TriStateResult::NotSupported;
1228#endif
1229}
1230
1231static QSystemError createDirectoryWithParents(const QByteArray &path, mode_t mode)
1232{
1233#ifdef Q_OS_WASM
1234 if (path == "/")
1235 return {};
1236#endif
1237
1238 auto tryMkDir = [&path, mode]() -> QSystemError {
1239 if (QT_MKDIR(path: path, mode: mode) == 0) {
1240#ifdef Q_OS_VXWORKS
1241 forceRequestedPermissionsOnVxWorks(path, mode);
1242#endif
1243 return {};
1244 }
1245 // On macOS with APFS mkdir sets errno to EISDIR, QTBUG-97110
1246 if (errno == EISDIR)
1247 return {};
1248 if (errno == EEXIST || errno == EROFS) {
1249 // ::mkdir() can fail if the dir already exists (it may have been
1250 // created by another thread or another process)
1251 QT_STATBUF st;
1252 if (QT_STAT(file: path.constData(), buf: &st) != 0)
1253 return QSystemError::stdError(errno);
1254 const bool isDir = (st.st_mode & S_IFMT) == S_IFDIR;
1255 return isDir ? QSystemError{} : QSystemError::stdError(EEXIST);
1256 }
1257 return QSystemError::stdError(errno);
1258 };
1259
1260 QSystemError result = tryMkDir();
1261 if (result.ok())
1262 return result;
1263
1264 // Only handle non-existing dir components in the path
1265 if (result.errorCode != ENOENT)
1266 return result;
1267
1268 qsizetype slash = path.lastIndexOf(c: '/');
1269 while (slash > 0 && path[slash - 1] == '/')
1270 --slash;
1271
1272 if (slash < 1)
1273 return result;
1274
1275 // mkdir failed because the parent dir doesn't exist, so try to create it
1276 QByteArray parentPath = path.first(n: slash);
1277 if (result = createDirectoryWithParents(path: parentPath, mode); !result.ok())
1278 return result;
1279
1280 // try again
1281 return tryMkDir();
1282}
1283
1284bool QFileSystemEngine::mkpath(const QFileSystemEntry &entry,
1285 std::optional<QFile::Permissions> permissions)
1286{
1287 QByteArray path = entry.nativeFilePath();
1288 Q_CHECK_FILE_NAME(path, false);
1289
1290 mode_t mode = permissions ? QtPrivate::toMode_t(permissions: *permissions) : 0777;
1291 return createDirectoryWithParents(path: removeTrailingSlashes(path), mode).ok();
1292}
1293
1294bool QFileSystemEngine::mkdir(const QFileSystemEntry &entry,
1295 std::optional<QFile::Permissions> permissions)
1296{
1297 QByteArray path = entry.nativeFilePath();
1298 Q_CHECK_FILE_NAME(path, false);
1299
1300 mode_t mode = permissions ? QtPrivate::toMode_t(permissions: *permissions) : 0777;
1301 auto result = QT_MKDIR(path: removeTrailingSlashes(path), mode: mode) == 0;
1302#if defined(Q_OS_VXWORKS)
1303 if (result)
1304 forceRequestedPermissionsOnVxWorks(path, mode);
1305#endif
1306 return result;
1307}
1308
1309bool QFileSystemEngine::rmdir(const QFileSystemEntry &entry)
1310{
1311 const QByteArray path = entry.nativeFilePath();
1312 Q_CHECK_FILE_NAME(path, false);
1313 return ::rmdir(path: path.constData()) == 0;
1314}
1315
1316bool QFileSystemEngine::rmpath(const QFileSystemEntry &entry)
1317{
1318 QByteArray path = QFile::encodeName(fileName: QDir::cleanPath(path: entry.filePath()));
1319 Q_CHECK_FILE_NAME(path, false);
1320
1321 if (::rmdir(path: path.constData()) != 0)
1322 return false; // Only return false if `entry` couldn't be deleted
1323
1324 const char sep = QDir::separator().toLatin1();
1325 qsizetype slash = path.lastIndexOf(c: sep);
1326 // `slash > 0` because truncate(0) would make `path` empty
1327 for (; slash > 0; slash = path.lastIndexOf(c: sep)) {
1328 path.truncate(pos: slash);
1329 if (::rmdir(path: path.constData()) != 0)
1330 break;
1331 }
1332
1333 return true;
1334}
1335
1336//static
1337bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1338{
1339 Q_CHECK_FILE_NAME(source, false);
1340 Q_CHECK_FILE_NAME(target, false);
1341
1342 if (::symlink(from: source.nativeFilePath().constData(), to: target.nativeFilePath().constData()) == 0)
1343 return true;
1344 error = QSystemError(errno, QSystemError::StandardLibraryError);
1345 return false;
1346}
1347
1348#if defined(QT_BOOTSTRAPPED) || !defined(AT_FDCWD) || defined(Q_OS_ANDROID) || !QT_CONFIG(datestring) || defined(Q_OS_VXWORKS)
1349// bootstrapped tools don't need this, and we don't want QStorageInfo
1350//static
1351bool QFileSystemEngine::supportsMoveFileToTrash()
1352{
1353 return false;
1354}
1355
1356//static
1357bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &, QFileSystemEntry &,
1358 QSystemError &error)
1359{
1360 error = QSystemError(ENOSYS, QSystemError::StandardLibraryError);
1361 return false;
1362}
1363#elif defined(Q_OS_DARWIN)
1364// see qfilesystemengine_mac.mm
1365#else
1366/*
1367 Implementing as per https://specifications.freedesktop.org/trash-spec/1.0/
1368*/
1369//static
1370bool QFileSystemEngine::supportsMoveFileToTrash()
1371{
1372 return true;
1373}
1374
1375namespace {
1376struct FreeDesktopTrashOperation
1377{
1378 /*
1379 "A trash directory contains two subdirectories, named info and files."
1380 */
1381 QString trashPath;
1382 int filesDirFd = -1;
1383 int infoDirFd = -1;
1384 qsizetype volumePrefixLength = 0;
1385
1386 // relative file paths to the filesDirFd and infoDirFd from above
1387 QByteArray tempTrashFileName;
1388 QByteArray infoFilePath;
1389
1390 int infoFileFd = -1; // if we've already opened it
1391 ~FreeDesktopTrashOperation()
1392 {
1393 close();
1394 }
1395
1396 constexpr bool isTrashDirOpen() const { return filesDirFd != -1 && infoDirFd != -1; }
1397
1398 void close()
1399 {
1400 int savedErrno = errno;
1401 if (infoFileFd != -1) {
1402 Q_ASSERT(infoDirFd != -1);
1403 Q_ASSERT(!infoFilePath.isEmpty());
1404 Q_ASSERT(!trashPath.isEmpty());
1405
1406 QT_CLOSE(fd: infoFileFd);
1407 unlinkat(fd: infoDirFd, name: infoFilePath, flag: 0);
1408 infoFileFd = -1;
1409 }
1410 if (!tempTrashFileName.isEmpty()) {
1411 Q_ASSERT(filesDirFd != -1);
1412 unlinkat(fd: filesDirFd, name: tempTrashFileName, flag: 0);
1413 }
1414 if (filesDirFd >= 0)
1415 QT_CLOSE(fd: filesDirFd);
1416 if (infoDirFd >= 0)
1417 QT_CLOSE(fd: infoDirFd);
1418 filesDirFd = infoDirFd = -1;
1419 errno = savedErrno;
1420 }
1421
1422 bool tryCreateInfoFile(const QString &filePath, QSystemError &error)
1423 {
1424 QByteArray p = QFile::encodeName(fileName: filePath) + ".trashinfo";
1425 infoFileFd = qt_safe_openat(dfd: infoDirFd, pathname: p, QT_OPEN_RDWR | QT_OPEN_CREAT | QT_OPEN_EXCL, mode: 0666);
1426 if (infoFileFd < 0) {
1427 error = QSystemError(errno, QSystemError::StandardLibraryError);
1428 return false;
1429 }
1430 infoFilePath = std::move(p);
1431 return true;
1432 }
1433
1434 void commit()
1435 {
1436 QT_CLOSE(fd: infoFileFd);
1437 infoFileFd = -1;
1438 tempTrashFileName = {};
1439 }
1440
1441 // opens a directory and returns the file descriptor
1442 static int openDirFd(int dfd, const char *path, int mode)
1443 {
1444 mode |= QT_OPEN_RDONLY | O_DIRECTORY;
1445 return qt_safe_openat(dfd, pathname: path, flags: mode);
1446 }
1447
1448 // opens an XDG Trash directory that is a subdirectory of dfd, creating if necessary
1449 static int openOrCreateDir(int dfd, const char *path, int openmode = 0)
1450 {
1451 // try to open it as a dir, first
1452 int fd = openDirFd(dfd, path, mode: openmode);
1453 if (fd >= 0 || errno != ENOENT)
1454 return fd;
1455
1456 // try to mkdirat
1457 if (mkdirat(fd: dfd, path: path, mode: 0700) < 0)
1458 return -1;
1459
1460 // try to open it again
1461 return openDirFd(dfd, path, mode: openmode);
1462 }
1463
1464 // opens or makes the XDG Trash hierarchy on parentfd (may be -1) called targetDir
1465 QSystemError getTrashDir(int parentfd, QString targetDir, const QFileSystemEntry &source,
1466 int openmode)
1467 {
1468 if (parentfd == AT_FDCWD)
1469 trashPath = targetDir;
1470 QByteArray nativePath = QFile::encodeName(fileName: targetDir);
1471
1472 // open the directory
1473 int trashfd = openOrCreateDir(dfd: parentfd, path: nativePath, openmode);
1474 if (trashfd < 0 && errno != ENOENT)
1475 return QSystemError::stdError(errno);
1476
1477 // check if it is ours (even if we've just mkdirat'ed it)
1478 if (QT_STATBUF st; QT_FSTAT(fd: trashfd, buf: &st) < 0)
1479 return QSystemError::stdError(errno);
1480 else if (st.st_uid != getuid())
1481 return QSystemError::stdError(errno);
1482
1483 filesDirFd = openOrCreateDir(dfd: trashfd, path: "files");
1484 if (filesDirFd >= 0) {
1485 // try to link our file-to-be-trashed here
1486 QTemporaryFileName tfn("XXXXXX"_L1);
1487 for (int i = 0; i < 16; ++i) {
1488 QByteArray attempt = tfn.generateNext();
1489 if (linkat(AT_FDCWD, from: source.nativeFilePath(), tofd: filesDirFd, to: attempt, flags: 0) == 0) {
1490 tempTrashFileName = std::move(attempt);
1491 break;
1492 }
1493 if (errno != EEXIST)
1494 break;
1495 }
1496
1497 // man 2 link on Linux has:
1498 // EPERM The filesystem containing oldpath and newpath does not
1499 // support the creation of hard links.
1500 // EPERM oldpath is a directory.
1501 // EPERM oldpath is marked immutable or append‐only.
1502 // EMLINK The file referred to by oldpath already has the maximum
1503 // number of links to it.
1504 if (!tempTrashFileName.isEmpty() || errno == EPERM || errno == EMLINK)
1505 infoDirFd = openOrCreateDir(dfd: trashfd, path: "info");
1506 }
1507
1508 if (infoDirFd < 0) {
1509 int saved_errno = errno;
1510 close();
1511 QT_CLOSE(fd: trashfd);
1512 return QSystemError::stdError(error: saved_errno);
1513 }
1514
1515 QT_CLOSE(fd: trashfd);
1516 return {};
1517 }
1518
1519 QSystemError openMountPointTrashLocation(const QFileSystemEntry &source,
1520 const QStorageInfo &sourceStorage)
1521 {
1522 /*
1523 Method 1:
1524 "An administrator can create an $topdir/.Trash directory. The permissions on this
1525 directories should permit all users who can trash files at all to write in it;
1526 and the “sticky bit” in the permissions must be set, if the file system supports
1527 it.
1528 When trashing a file from a non-home partition/device, an implementation
1529 (if it supports trashing in top directories) MUST check for the presence
1530 of $topdir/.Trash."
1531 */
1532
1533 QSystemError error;
1534 const auto dotTrash = "/.Trash"_L1;
1535 const QString userID = QString::number(::getuid());
1536 QFileSystemEntry dotTrashDir(sourceStorage.rootPath() + dotTrash);
1537
1538 // we MUST check that the sticky bit is set, and that it is not a symlink
1539 int genericTrashFd = openDirFd(AT_FDCWD, path: dotTrashDir.nativeFilePath(), O_NOFOLLOW);
1540 QT_STATBUF st = {};
1541 if (genericTrashFd < 0 && errno != ENOENT && errno != EACCES) {
1542 // O_DIRECTORY + O_NOFOLLOW produces ENOTDIR on Linux
1543 if (QT_LSTAT(file: dotTrashDir.nativeFilePath(), buf: &st) == 0 && S_ISLNK(st.st_mode)) {
1544 // we SHOULD report the failed check to the administrator
1545 qCritical(msg: "Warning: '%s' is a symlink to '%s'",
1546 dotTrashDir.nativeFilePath().constData(),
1547 qt_readlink(path: dotTrashDir.nativeFilePath()).constData());
1548 error = QSystemError(ELOOP, QSystemError::StandardLibraryError);
1549 }
1550 } else if (genericTrashFd >= 0) {
1551 QT_FSTAT(fd: genericTrashFd, buf: &st);
1552 if ((st.st_mode & S_ISVTX) == 0) {
1553 // we SHOULD report the failed check to the administrator
1554 qCritical(msg: "Warning: '%s' doesn't have sticky bit set!",
1555 dotTrashDir.nativeFilePath().constData());
1556 error = QSystemError(EPERM, QSystemError::StandardLibraryError);
1557 } else {
1558 /*
1559 "If the directory exists and passes the checks, a subdirectory of the
1560 $topdir/.Trash directory is to be used as the user's trash directory
1561 for this partition/device. The name of this subdirectory is the numeric
1562 identifier of the current user ($topdir/.Trash/$uid).
1563 When trashing a file, if this directory does not exist for the current user,
1564 the implementation MUST immediately create it, without any warnings or
1565 delays for the user."
1566 */
1567 if (error = getTrashDir(parentfd: genericTrashFd, targetDir: userID, source, O_NOFOLLOW); error.ok()) {
1568 // recreate the resulting path
1569 trashPath = dotTrashDir.filePath() + u'/' + userID;
1570 }
1571 }
1572 QT_CLOSE(fd: genericTrashFd);
1573 }
1574
1575 /*
1576 Method 2:
1577 "If an $topdir/.Trash directory is absent, an $topdir/.Trash-$uid directory is to be
1578 used as the user's trash directory for this device/partition. [...] When trashing a
1579 file, if an $topdir/.Trash-$uid directory does not exist, the implementation MUST
1580 immediately create it, without any warnings or delays for the user."
1581 */
1582 if (!isTrashDirOpen())
1583 error = getTrashDir(AT_FDCWD, targetDir: sourceStorage.rootPath() + dotTrash + u'-' + userID, source,
1584 O_NOFOLLOW);
1585
1586 if (isTrashDirOpen()) {
1587 volumePrefixLength = sourceStorage.rootPath().size();
1588 if (volumePrefixLength == 1)
1589 volumePrefixLength = 0; // isRoot
1590 else
1591 ++volumePrefixLength; // to include the slash
1592 }
1593 return isTrashDirOpen() ? QSystemError() : error;
1594 }
1595
1596 QSystemError openHomeTrashLocation(const QFileSystemEntry &source)
1597 {
1598 QString topDir = QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation);
1599 int openmode = 0; // do allow following symlinks
1600 return getTrashDir(AT_FDCWD, targetDir: topDir + "/Trash"_L1, source, openmode);
1601 }
1602
1603 QSystemError findTrashFor(const QFileSystemEntry &source)
1604 {
1605 /*
1606 First, try the standard Trash in $XDG_DATA_DIRS:
1607 "Its name and location are $XDG_DATA_HOME/Trash"; $XDG_DATA_HOME is what
1608 QStandardPaths returns for GenericDataLocation. If that doesn't exist, then
1609 we are not running on a freedesktop.org-compliant environment, and give up.
1610 */
1611 if (QSystemError error = openHomeTrashLocation(source); error.ok())
1612 return QSystemError();
1613 else if (error.errorCode != EXDEV)
1614 return error;
1615
1616 // didn't work, try to find the trash outside the home filesystem
1617 const QStorageInfo sourceStorage(source.filePath());
1618 if (!sourceStorage.isValid())
1619 return QSystemError::stdError(ENODEV);
1620 return openMountPointTrashLocation(source, sourceStorage);
1621 }
1622};
1623} // unnamed namespace
1624
1625//static
1626bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &source,
1627 QFileSystemEntry &newLocation, QSystemError &error)
1628{
1629 const QFileSystemEntry sourcePath = [&] {
1630 if (QString path = source.filePath(); path.size() > 1 && path.endsWith(c: u'/')) {
1631 path.chop(n: 1);
1632 return absoluteName(entry: QFileSystemEntry(path));
1633 }
1634 return absoluteName(entry: source);
1635 }();
1636 FreeDesktopTrashOperation op;
1637 if (error = op.findTrashFor(source: sourcePath); !error.ok())
1638 return false;
1639
1640 /*
1641 "The $trash/files directory contains the files and directories that were trashed.
1642 The names of files in this directory are to be determined by the implementation;
1643 the only limitation is that they must be unique within the directory. Even if a
1644 file with the same name and location gets trashed many times, each subsequent
1645 trashing must not overwrite a previous copy."
1646
1647 We first try the unchanged base name, then try something different if it collides.
1648
1649 "The $trash/info directory contains an "information file" for every file and directory
1650 in $trash/files. This file MUST have exactly the same name as the file or directory in
1651 $trash/files, plus the extension ".trashinfo"
1652 [...]
1653 When trashing a file or directory, the implementation MUST create the corresponding
1654 file in $trash/info first. Moreover, it MUST try to do this in an atomic fashion,
1655 so that if two processes try to trash files with the same filename this will result
1656 in two different trash files. On Unix-like systems this is done by generating a
1657 filename, and then opening with O_EXCL. If that succeeds the creation was atomic
1658 (at least on the same machine), if it fails you need to pick another filename."
1659 */
1660 QString uniqueTrashedName = sourcePath.fileName();
1661 if (!op.tryCreateInfoFile(filePath: uniqueTrashedName, error) && error.errorCode == EEXIST) {
1662 // we'll use a counter, starting with the file's inode number to avoid
1663 // collisions
1664 qulonglong counter;
1665 if (QT_STATBUF st; Q_LIKELY(QT_STAT(source.nativeFilePath(), &st) == 0)) {
1666 counter = st.st_ino;
1667 } else {
1668 error = QSystemError(errno, QSystemError::StandardLibraryError);
1669 return false;
1670 }
1671
1672 QString uniqueTrashBase = std::move(uniqueTrashedName);
1673 for (;;) {
1674 uniqueTrashedName = QString::asprintf(format: "%ls-%llu", qUtf16Printable(uniqueTrashBase),
1675 counter++);
1676 if (op.tryCreateInfoFile(filePath: uniqueTrashedName, error))
1677 break;
1678 if (error.errorCode != EEXIST)
1679 return false;
1680 };
1681 }
1682
1683 QByteArray info =
1684 "[Trash Info]\n"
1685 "Path=" + QUrl::toPercentEncoding(source.filePath().mid(position: op.volumePrefixLength), exclude: "/") + "\n"
1686 "DeletionDate=" + QDateTime::currentDateTime().toString(format: Qt::ISODate).toUtf8()
1687 + "\n";
1688 if (QT_WRITE(fd: op.infoFileFd, data: info.data(), len: info.size()) < 0) {
1689 error = QSystemError(errno, QSystemError::StandardLibraryError);
1690 return false;
1691 }
1692
1693 /*
1694 If we've already linked the file-to-be-trashed into the trash
1695 directory, we know it's in the same mountpoint and we won't get ENOSPC
1696 renaming the temporary file to the target name either.
1697 */
1698 bool renamed;
1699 if (op.tempTrashFileName.isEmpty()) {
1700 /*
1701 We did not get a link (we're trying to trash a directory or on a
1702 filesystem that doesn't support hardlinking), so rename straight
1703 from the original name. We might fail to rename if source and target
1704 are on different file systems.
1705 */
1706 renamed = renameat(AT_FDCWD, old: source.nativeFilePath(), newfd: op.filesDirFd,
1707 new: QFile::encodeName(fileName: uniqueTrashedName)) == 0;
1708 } else {
1709 renamed = renameat(oldfd: op.filesDirFd, old: op.tempTrashFileName, newfd: op.filesDirFd,
1710 new: QFile::encodeName(fileName: uniqueTrashedName)) == 0;
1711 if (renamed)
1712 removeFile(entry: sourcePath, error); // success, delete the original file
1713 }
1714 if (!renamed) {
1715 error = QSystemError(errno, QSystemError::StandardLibraryError);
1716 return false;
1717 }
1718
1719 op.commit();
1720 newLocation = QFileSystemEntry(op.trashPath + "/files/"_L1 + uniqueTrashedName);
1721 return true;
1722}
1723#endif // !Q_OS_DARWIN && (!QT_BOOTSTRAPPED && AT_FDCWD && !Q_OS_ANDROID && QT_CONFIG(datestring))
1724
1725//static
1726bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1727{
1728 Q_UNUSED(source);
1729 Q_UNUSED(target);
1730 error = QSystemError(ENOSYS, QSystemError::StandardLibraryError); //Function not implemented
1731 return false;
1732}
1733
1734//static
1735bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1736{
1737 QFileSystemEntry::NativePath srcPath = source.nativeFilePath();
1738 QFileSystemEntry::NativePath tgtPath = target.nativeFilePath();
1739
1740 Q_CHECK_FILE_NAME(srcPath, false);
1741 Q_CHECK_FILE_NAME(tgtPath, false);
1742
1743#if defined(RENAME_NOREPLACE) && QT_CONFIG(renameat2)
1744 if (renameat2(AT_FDCWD, old: srcPath, AT_FDCWD, new: tgtPath, RENAME_NOREPLACE) == 0)
1745 return true;
1746
1747 // We can also get EINVAL for some non-local filesystems.
1748 if (errno != EINVAL) {
1749 error = QSystemError(errno, QSystemError::StandardLibraryError);
1750 return false;
1751 }
1752#endif
1753#if defined(Q_OS_DARWIN) && defined(RENAME_EXCL)
1754 if (renameatx_np(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_EXCL) == 0)
1755 return true;
1756 if (errno != ENOTSUP) {
1757 error = QSystemError(errno, QSystemError::StandardLibraryError);
1758 return false;
1759 }
1760#endif
1761
1762 if (SupportsHardlinking && ::link(from: srcPath, to: tgtPath) == 0) {
1763 if (::unlink(name: srcPath) == 0)
1764 return true;
1765
1766 // if we managed to link but can't unlink the source, it's likely
1767 // it's in a directory we don't have write access to; fail the
1768 // renaming instead
1769 int savedErrno = errno;
1770
1771 // this could fail too, but there's nothing we can do about it now
1772 ::unlink(name: tgtPath);
1773
1774 error = QSystemError(savedErrno, QSystemError::StandardLibraryError);
1775 return false;
1776 } else if (!SupportsHardlinking) {
1777 // man 2 link on Linux has:
1778 // EPERM The filesystem containing oldpath and newpath does not
1779 // support the creation of hard links.
1780 errno = EPERM;
1781 }
1782
1783 switch (errno) {
1784 case EACCES:
1785 case EEXIST:
1786 case ENAMETOOLONG:
1787 case ENOENT:
1788 case ENOTDIR:
1789 case EROFS:
1790 case EXDEV:
1791 // accept the error from link(2) (especially EEXIST) and don't retry
1792 break;
1793
1794 default:
1795 // fall back to rename()
1796 // ### Race condition. If a file is moved in after this, it /will/ be
1797 // overwritten.
1798 if (::rename(old: srcPath, new: tgtPath) == 0)
1799 return true;
1800 }
1801
1802 error = QSystemError(errno, QSystemError::StandardLibraryError);
1803 return false;
1804}
1805
1806//static
1807bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1808{
1809 Q_CHECK_FILE_NAME(source, false);
1810 Q_CHECK_FILE_NAME(target, false);
1811
1812 if (::rename(old: source.nativeFilePath().constData(), new: target.nativeFilePath().constData()) == 0)
1813 return true;
1814 error = QSystemError(errno, QSystemError::StandardLibraryError);
1815 return false;
1816}
1817
1818//static
1819bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError &error)
1820{
1821 Q_CHECK_FILE_NAME(entry, false);
1822 if (unlink(name: entry.nativeFilePath().constData()) == 0)
1823 return true;
1824 error = QSystemError(errno, QSystemError::StandardLibraryError);
1825 return false;
1826
1827}
1828
1829//static
1830bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry,
1831 QFile::Permissions permissions, QSystemError &error)
1832{
1833 Q_CHECK_FILE_NAME(entry, false);
1834
1835 mode_t mode = QtPrivate::toMode_t(permissions);
1836 bool success = ::chmod(file: entry.nativeFilePath().constData(), mode: mode) == 0;
1837 if (!success)
1838 error = QSystemError(errno, QSystemError::StandardLibraryError);
1839 return success;
1840}
1841
1842//static
1843bool QFileSystemEngine::setPermissions(int fd, QFile::Permissions permissions, QSystemError &error)
1844{
1845 mode_t mode = QtPrivate::toMode_t(permissions);
1846 bool success = ::fchmod(fd: fd, mode: mode) == 0;
1847 if (!success)
1848 error = QSystemError(errno, QSystemError::StandardLibraryError);
1849 return success;
1850}
1851
1852//static
1853bool QFileSystemEngine::setFileTime(int fd, const QDateTime &newDate, QFile::FileTime time, QSystemError &error)
1854{
1855 if (!newDate.isValid()
1856 || time == QFile::FileBirthTime || time == QFile::FileMetadataChangeTime) {
1857 error = QSystemError(EINVAL, QSystemError::StandardLibraryError);
1858 return false;
1859 }
1860
1861#if QT_CONFIG(futimens)
1862 // UTIME_OMIT: leave file timestamp unchanged
1863 struct timespec ts[2] = {{.tv_sec: 0, UTIME_OMIT}, {.tv_sec: 0, UTIME_OMIT}};
1864
1865 if (time == QFile::FileAccessTime || time == QFile::FileModificationTime) {
1866 const int idx = time == QFile::FileAccessTime ? 0 : 1;
1867 const std::chrono::milliseconds msecs{newDate.toMSecsSinceEpoch()};
1868 ts[idx] = durationToTimespec(timeout: msecs);
1869 }
1870
1871 if (futimens(fd: fd, times: ts) == -1) {
1872 error = QSystemError(errno, QSystemError::StandardLibraryError);
1873 return false;
1874 }
1875
1876 return true;
1877#else
1878 Q_UNUSED(fd);
1879 error = QSystemError(ENOSYS, QSystemError::StandardLibraryError);
1880 return false;
1881#endif
1882}
1883
1884QString QFileSystemEngine::homePath()
1885{
1886 QString home = qEnvironmentVariable(varName: "HOME");
1887 if (home.isEmpty())
1888 home = rootPath();
1889 return QDir::cleanPath(path: home);
1890}
1891
1892QString QFileSystemEngine::rootPath()
1893{
1894 return u"/"_s;
1895}
1896
1897static constexpr QLatin1StringView nativeTempPath() noexcept
1898{
1899 // _PATH_TMP usually ends in '/' and we don't want that
1900 QLatin1StringView temp = _PATH_TMP ""_L1;
1901 static_assert(_PATH_TMP[0] == '/', "_PATH_TMP needs to be absolute");
1902 static_assert(_PATH_TMP[1] != '\0', "Are you really sure _PATH_TMP should be the root dir??");
1903 if (temp.endsWith(c: u'/'))
1904 temp.chop(n: 1);
1905 return temp;
1906}
1907
1908QString QFileSystemEngine::tempPath()
1909{
1910#ifdef QT_UNIX_TEMP_PATH_OVERRIDE
1911 return QT_UNIX_TEMP_PATH_OVERRIDE ""_L1;
1912#else
1913 QString temp = qEnvironmentVariable(varName: "TMPDIR");
1914# if defined(Q_OS_DARWIN) && !defined(QT_BOOTSTRAPPED)
1915 if (NSString *nsPath; temp.isEmpty() && (nsPath = NSTemporaryDirectory()))
1916 temp = QString::fromCFString((CFStringRef)nsPath);
1917# endif
1918 if (temp.isEmpty())
1919 return nativeTempPath();
1920
1921 // the environment variable may also end in '/'
1922 if (temp.size() > 1 && temp.endsWith(c: u'/'))
1923 temp.chop(n: 1);
1924
1925 QFileSystemEntry e(temp, QFileSystemEntry::FromInternalPath{});
1926 return QFileSystemEngine::absoluteName(entry: e).filePath();
1927#endif
1928}
1929
1930bool QFileSystemEngine::setCurrentPath(const QFileSystemEntry &path)
1931{
1932 int r;
1933 r = QT_CHDIR(path: path.nativeFilePath().constData());
1934 return r >= 0;
1935}
1936
1937QFileSystemEntry QFileSystemEngine::currentPath()
1938{
1939 QFileSystemEntry result;
1940#if defined(__GLIBC__) && !defined(PATH_MAX)
1941 char *currentName = ::get_current_dir_name();
1942 if (currentName) {
1943 result = QFileSystemEntry(QByteArray(currentName), QFileSystemEntry::FromNativePath());
1944 ::free(currentName);
1945 }
1946#else
1947 char currentName[PATH_MAX+1];
1948 if (::getcwd(buf: currentName, PATH_MAX)) {
1949#if defined(Q_OS_VXWORKS) && defined(VXWORKS_VXSIM)
1950 QByteArray dir(currentName);
1951 if (dir.indexOf(':') < dir.indexOf('/'))
1952 dir.remove(0, dir.indexOf(':')+1);
1953
1954 qstrncpy(currentName, dir.constData(), PATH_MAX);
1955#endif
1956 result = QFileSystemEntry(QByteArray(currentName), QFileSystemEntry::FromNativePath());
1957 }
1958# if defined(QT_DEBUG)
1959 if (result.isEmpty())
1960 qWarning(msg: "QFileSystemEngine::currentPath: getcwd() failed");
1961# endif
1962#endif
1963 return result;
1964}
1965
1966bool QFileSystemEngine::isCaseSensitive(const QFileSystemEntry &entry, QFileSystemMetaData &metaData)
1967{
1968#if defined(Q_OS_DARWIN)
1969 if (!metaData.hasFlags(QFileSystemMetaData::CaseSensitive))
1970 fillMetaData(entry, metaData, QFileSystemMetaData::CaseSensitive);
1971 return metaData.entryFlags.testFlag(QFileSystemMetaData::CaseSensitive);
1972#else
1973 Q_UNUSED(entry);
1974 Q_UNUSED(metaData);
1975 // FIXME: This may not be accurate for all file systems (QTBUG-28246)
1976 return true;
1977#endif
1978}
1979
1980QT_END_NAMESPACE
1981

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