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