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