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
788//static
789QByteArray QFileSystemEngine::id(const QFileSystemEntry &entry)
790{
791 Q_CHECK_FILE_NAME(entry, QByteArray());
792
793 QT_STATBUF statResult;
794 if (QT_STAT(entry.nativeFilePath().constData(), &statResult)) {
795 if (errno != ENOENT)
796 qErrnoWarning("stat() failed for '%s'", entry.nativeFilePath().constData());
797 return QByteArray();
798 }
799 QByteArray result = QByteArray::number(quint64(statResult.st_dev), 16);
800 result += ':';
801 result += QByteArray::number(quint64(statResult.st_ino));
802 return result;
803}
804
805//static
806QByteArray QFileSystemEngine::id(int fd)
807{
808 QT_STATBUF statResult;
809 if (QT_FSTAT(fd, &statResult)) {
810 qErrnoWarning("fstat() failed for fd %d", fd);
811 return QByteArray();
812 }
813 QByteArray result = QByteArray::number(quint64(statResult.st_dev), 16);
814 result += ':';
815 result += QByteArray::number(quint64(statResult.st_ino));
816 return result;
817}
818
819//static
820QString QFileSystemEngine::resolveUserName(uint userId)
821{
822#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
823 long size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
824 if (size_max == -1)
825 size_max = 1024;
826 QVarLengthArray<char, 1024> buf(size_max);
827#endif
828
829#if !defined(Q_OS_INTEGRITY) && !defined(Q_OS_WASM)
830 struct passwd *pw = nullptr;
831#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) && !defined(Q_OS_VXWORKS)
832 struct passwd entry;
833 getpwuid_r(userId, &entry, buf.data(), buf.size(), &pw);
834#else
835 pw = getpwuid(userId);
836#endif
837 if (pw)
838 return QFile::decodeName(QByteArray(pw->pw_name));
839#else // Integrity || WASM
840 Q_UNUSED(userId);
841#endif
842 return QString();
843}
844
845//static
846QString QFileSystemEngine::resolveGroupName(uint groupId)
847{
848#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
849 long size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
850 if (size_max == -1)
851 size_max = 1024;
852 QVarLengthArray<char, 1024> buf(size_max);
853#endif
854
855#if !defined(Q_OS_INTEGRITY) && !defined(Q_OS_WASM)
856 struct group *gr = nullptr;
857#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))
858 size_max = sysconf(_SC_GETGR_R_SIZE_MAX);
859 if (size_max == -1)
860 size_max = 1024;
861 buf.resize(size_max);
862 struct group entry;
863 // Some large systems have more members than the POSIX max size
864 // Loop over by doubling the buffer size (upper limit 250k)
865 for (long size = size_max; size < 256000; size += size)
866 {
867 buf.resize(size);
868 // ERANGE indicates that the buffer was too small
869 if (!getgrgid_r(groupId, &entry, buf.data(), buf.size(), &gr)
870 || errno != ERANGE)
871 break;
872 }
873#else
874 gr = getgrgid(groupId);
875#endif
876 if (gr)
877 return QFile::decodeName(QByteArray(gr->gr_name));
878#else // Integrity || WASM || VxWorks
879 Q_UNUSED(groupId);
880#endif
881 return QString();
882}
883
884#if defined(Q_OS_DARWIN)
885//static
886QString QFileSystemEngine::bundleName(const QFileSystemEntry &entry)
887{
888 QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, QCFString(entry.filePath()),
889 kCFURLPOSIXPathStyle, true);
890 if (QCFType<CFDictionaryRef> dict = CFBundleCopyInfoDictionaryForURL(url)) {
891 if (CFTypeRef name = (CFTypeRef)CFDictionaryGetValue(dict, kCFBundleNameKey)) {
892 if (CFGetTypeID(name) == CFStringGetTypeID())
893 return QString::fromCFString((CFStringRef)name);
894 }
895 }
896 return QString();
897}
898#endif
899
900//static
901bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemMetaData &data,
902 QFileSystemMetaData::MetaDataFlags what)
903{
904 Q_CHECK_FILE_NAME(entry, false);
905
906 // Detection of WasDeletedAttribute is imperfect: in general, if we can
907 // successfully stat() or access() a file, it hasn't been deleted (though
908 // there are exceptions, like /proc/XXX/fd/ entries on Linux). So we have
909 // to restore this flag in case we fail to stat() anything.
910 auto hadBeenDeleted = data.entryFlags & QFileSystemMetaData::WasDeletedAttribute;
911
912#if defined(Q_OS_DARWIN)
913 if (what & (QFileSystemMetaData::BundleType | QFileSystemMetaData::CaseSensitive)) {
914 if (!data.hasFlags(QFileSystemMetaData::DirectoryType))
915 what |= QFileSystemMetaData::DirectoryType;
916 }
917 if (what & QFileSystemMetaData::AliasType)
918 what |= QFileSystemMetaData::LinkType;
919#endif // defined(Q_OS_DARWIN)
920
921 bool needLstat = what.testAnyFlag(QFileSystemMetaData::LinkType);
922
923#ifdef UF_HIDDEN
924 if (what & QFileSystemMetaData::HiddenAttribute) {
925 // Some OSes (BSDs) have the ability to mark directory entries as
926 // hidden besides the usual Unix way of naming them with a leading dot.
927 // For those OSes, we must lstat() the entry itself so we can find
928 // out if a symlink is hidden or not.
929 needLstat = true;
930 }
931#endif
932
933 // if we're asking for any of the stat(2) flags, then we're getting them all
934 if (what & QFileSystemMetaData::PosixStatFlags)
935 what |= QFileSystemMetaData::PosixStatFlags;
936
937 data.entryFlags &= ~what;
938
939 const QByteArray nativeFilePath = entry.nativeFilePath();
940 int entryErrno = 0; // innocent until proven otherwise
941
942 // first, we may try lstat(2). Possible outcomes:
943 // - success and is a symlink: filesystem entry exists, but we need stat(2)
944 // -> statResult = -1;
945 // - success and is not a symlink: filesystem entry exists and we're done
946 // -> statResult = 0
947 // - failure: really non-existent filesystem entry
948 // -> entryExists = false; statResult = 0;
949 // both stat(2) and lstat(2) may generate a number of different errno
950 // conditions, but of those, the only ones that could happen and the
951 // entry still exist are EACCES, EFAULT, ENOMEM and EOVERFLOW. If we get
952 // EACCES or ENOMEM, then we have no choice on how to proceed, so we may
953 // as well conclude it doesn't exist; EFAULT can't happen and EOVERFLOW
954 // shouldn't happen because we build in _LARGEFILE64.
955 union {
956 QT_STATBUF statBuffer;
957 struct statx statxBuffer;
958 };
959 int statResult = -1;
960 if (needLstat) {
961 mode_t mode = 0;
962 statResult = qt_lstatx(nativeFilePath, &statxBuffer);
963 if (statResult == -ENOSYS) {
964 // use lstat(2)
965 statResult = QT_LSTAT(nativeFilePath, &statBuffer);
966 if (statResult == 0)
967 mode = statBuffer.st_mode;
968 } else if (statResult == 0) {
969 statResult = 1; // record it was statx(2) that succeeded
970 mode = statxBuffer.stx_mode;
971 }
972
973 if (statResult >= 0) {
974#ifdef UF_HIDDEN
975 // currently only supported on systems with no statx() call
976 Q_ASSERT(statResult == 0);
977 if (statBuffer.st_flags & UF_HIDDEN)
978 data.entryFlags |= QFileSystemMetaData::HiddenAttribute;
979 data.knownFlagsMask |= QFileSystemMetaData::HiddenAttribute;
980#endif
981
982 if (S_ISLNK(mode)) {
983 // it's a symlink, we don't know if the file "exists"
984 data.entryFlags |= QFileSystemMetaData::LinkType;
985 statResult = -1; // force stat(2) below
986 } else {
987 // it's a regular file and it exists
988 if (statResult)
989 data.fillFromStatxBuf(statxBuffer);
990 else
991 data.fillFromStatBuf(statBuffer);
992 }
993 } else {
994 // it doesn't exist
995 entryErrno = errno;
996 statResult = -1;
997 data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
998 }
999
1000 data.knownFlagsMask |= QFileSystemMetaData::LinkType;
1001 }
1002
1003 // second, we try a regular stat(2)
1004 if (statResult == -1 && (what & QFileSystemMetaData::PosixStatFlags)) {
1005 if (entryErrno == 0) {
1006 data.entryFlags &= ~QFileSystemMetaData::PosixStatFlags;
1007 statResult = qt_statx(nativeFilePath, &statxBuffer);
1008 if (statResult == -ENOSYS) {
1009 // use stat(2)
1010 statResult = QT_STAT(nativeFilePath, &statBuffer);
1011 if (statResult == 0)
1012 data.fillFromStatBuf(statBuffer);
1013 } else if (statResult == 0) {
1014 data.fillFromStatxBuf(statxBuffer);
1015 }
1016 }
1017
1018 if (statResult != 0) {
1019 entryErrno = errno;
1020 data.birthTime_ = 0;
1021 data.metadataChangeTime_ = 0;
1022 data.modificationTime_ = 0;
1023 data.accessTime_ = 0;
1024 data.size_ = 0;
1025 data.userId_ = (uint) -2;
1026 data.groupId_ = (uint) -2;
1027
1028 // reset the mask
1029 data.knownFlagsMask |= QFileSystemMetaData::PosixStatFlags
1030 | QFileSystemMetaData::ExistsAttribute;
1031 }
1032 }
1033
1034 // third, we try access(2)
1035 if (what & (QFileSystemMetaData::UserPermissions | QFileSystemMetaData::ExistsAttribute)) {
1036#if defined(Q_OS_VXWORKS)
1037 struct statfs statBuf;
1038 if (statfs(nativeFilePath, &statBuf) == 0) {
1039 if (statBuf.f_type != NFSV2_MAGIC && statBuf.f_type != NFSV3_MAGIC &&
1040 statBuf.f_type != HRFS_MAGIC) {
1041#if __has_include(<dosFsLib.h>)
1042 if (data.entryFlags & QFileSystemMetaData::OwnerWritePermission) {
1043 data.entryFlags |= QFileSystemMetaData::UserWritePermission;
1044 }
1045 if (data.entryFlags & QFileSystemMetaData::OwnerExecutePermission) {
1046 data.entryFlags |= QFileSystemMetaData::UserExecutePermission;
1047 }
1048#endif
1049 data.entryFlags |= QFileSystemMetaData::UserReadPermission |
1050 QFileSystemMetaData::ExistsAttribute;
1051 return true;
1052 }
1053 }
1054#if defined(QT_DEBUG)
1055 else {
1056 //on VxWorks hostfs, used for debugging, failes on statfs and falsely reports
1057 //WasDeleted
1058 statResult = QT_STAT(nativeFilePath, &statBuffer);
1059 if (statResult == 0) {
1060 data.entryFlags |= QFileSystemMetaData::UserReadPermission |
1061 QFileSystemMetaData::ExistsAttribute;
1062 data.entryFlags &= ~QFileSystemMetaData::WasDeletedAttribute;
1063 return true;
1064 }
1065 }
1066#endif
1067#endif
1068 // calculate user permissions
1069 auto checkAccess = [&](QFileSystemMetaData::MetaDataFlag flag, int mode) {
1070 if (entryErrno != 0 || (what & flag) == 0)
1071 return;
1072 if (QT_ACCESS(nativeFilePath, mode) == 0) {
1073 // access ok (and file exists)
1074 data.entryFlags |= flag | QFileSystemMetaData::ExistsAttribute;
1075 } else if (errno != EACCES && errno != EROFS) {
1076 entryErrno = errno;
1077 }
1078 };
1079 checkAccess(QFileSystemMetaData::UserReadPermission, R_OK);
1080 checkAccess(QFileSystemMetaData::UserWritePermission, W_OK);
1081 checkAccess(QFileSystemMetaData::UserExecutePermission, X_OK);
1082
1083 // if we still haven't found out if the file exists, try F_OK
1084 if (entryErrno == 0 && (data.entryFlags & QFileSystemMetaData::ExistsAttribute) == 0) {
1085 if (QT_ACCESS(nativeFilePath, F_OK) == -1)
1086 entryErrno = errno;
1087 else
1088 data.entryFlags |= QFileSystemMetaData::ExistsAttribute;
1089 }
1090
1091 data.knownFlagsMask |= (what & QFileSystemMetaData::UserPermissions) |
1092 QFileSystemMetaData::ExistsAttribute;
1093 }
1094
1095#if defined(Q_OS_DARWIN)
1096 QCFType<CFURLRef> cachedUrl;
1097 if (what & QFileSystemMetaData::AliasType) {
1098 if (entryErrno == 0 && hasResourcePropertyFlag(data, entry, kCFURLIsAliasFileKey, cachedUrl)) {
1099 // kCFURLIsAliasFileKey includes symbolic links, so filter those out
1100 if (!(data.entryFlags & QFileSystemMetaData::LinkType))
1101 data.entryFlags |= QFileSystemMetaData::AliasType;
1102 }
1103 data.knownFlagsMask |= QFileSystemMetaData::AliasType;
1104 }
1105
1106 if (what & QFileSystemMetaData::BundleType) {
1107 if (entryErrno == 0 && isPackage(data, entry, cachedUrl))
1108 data.entryFlags |= QFileSystemMetaData::BundleType;
1109
1110 data.knownFlagsMask |= QFileSystemMetaData::BundleType;
1111 }
1112
1113 if (what & QFileSystemMetaData::CaseSensitive) {
1114 if (entryErrno == 0 && hasResourcePropertyFlag(data, entry,
1115 kCFURLVolumeSupportsCaseSensitiveNamesKey, cachedUrl))
1116 data.entryFlags |= QFileSystemMetaData::CaseSensitive;
1117 data.knownFlagsMask |= QFileSystemMetaData::CaseSensitive;
1118 }
1119#endif
1120
1121 if (what & QFileSystemMetaData::HiddenAttribute
1122 && !data.isHidden()) {
1123 // reusing nativeFilePath from above instead of entry.fileName(), to
1124 // avoid memory allocation for the QString result.
1125 qsizetype lastSlash = nativeFilePath.size();
1126
1127 while (lastSlash && nativeFilePath.at(lastSlash - 1) == '/')
1128 --lastSlash; // skip ending slashes
1129 while (lastSlash && nativeFilePath.at(lastSlash - 1) != '/')
1130 --lastSlash; // skip non-slashes
1131 --lastSlash; // point to the slash or -1 if no slash
1132
1133 if (nativeFilePath.at(lastSlash + 1) == '.')
1134 data.entryFlags |= QFileSystemMetaData::HiddenAttribute;
1135 data.knownFlagsMask |= QFileSystemMetaData::HiddenAttribute;
1136 }
1137
1138 if (entryErrno != 0) {
1139 what &= ~QFileSystemMetaData::LinkType; // don't clear link: could be broken symlink
1140 data.clearFlags(what);
1141
1142 // see comment at the top
1143 data.entryFlags |= hadBeenDeleted;
1144 data.knownFlagsMask |= hadBeenDeleted;
1145
1146 return false;
1147 }
1148 return true;
1149}
1150
1151// static
1152auto QFileSystemEngine::cloneFile(int srcfd, int dstfd, const QFileSystemMetaData &knownData) -> TriStateResult
1153{
1154 QT_STATBUF statBuffer;
1155 if (knownData.hasFlags(QFileSystemMetaData::PosixStatFlags) &&
1156 knownData.isFile()) {
1157 statBuffer.st_mode = S_IFREG;
1158 } else if (knownData.hasFlags(QFileSystemMetaData::PosixStatFlags) &&
1159 knownData.isDirectory()) {
1160 errno = EISDIR;
1161 return TriStateResult::Failed; // fcopyfile(3) returns success on directories
1162 } else if (QT_FSTAT(srcfd, &statBuffer) == -1) {
1163 // errno was set
1164 return TriStateResult::Failed;
1165 } else if (!S_ISREG((statBuffer.st_mode))) {
1166 // not a regular file, let QFile do the copy
1167 return TriStateResult::NotSupported;
1168 }
1169
1170 [[maybe_unused]] auto destinationIsEmpty = [dstfd]() {
1171 QT_STATBUF statBuffer;
1172 return QT_FSTAT(dstfd, &statBuffer) == 0 && statBuffer.st_size == 0;
1173 };
1174 Q_ASSERT(destinationIsEmpty());
1175
1176#if defined(Q_OS_LINUX)
1177 // first, try FICLONE (only works on regular files and only on certain fs)
1178 if (::ioctl(dstfd, FICLONE, srcfd) == 0)
1179 return TriStateResult::Success;
1180#elif defined(Q_OS_DARWIN)
1181 // try fcopyfile
1182 if (fcopyfile(srcfd, dstfd, nullptr, COPYFILE_DATA | COPYFILE_STAT) == 0)
1183 return TriStateResult::Success;
1184 switch (errno) {
1185 case ENOTSUP:
1186 case ENOMEM:
1187 return TriStateResult::NotSupported; // let QFile try
1188 }
1189 return TriStateResult::Failed;
1190#endif
1191
1192#if QT_CONFIG(copy_file_range)
1193 // Second, try copy_file_range. Tested on Linux & FreeBSD: FreeBSD can copy
1194 // across mountpoints, Linux currently (6.12) can only if the source and
1195 // destination mountpoints are the same filesystem type.
1196 QT_OFF_T srcoffset = 0;
1197 ssize_t copied;
1198 do {
1199 copied = ::copy_file_range(srcfd, &srcoffset, dstfd, nullptr, SSIZE_MAX, 0);
1200 } while (copied > 0 || (copied < 0 && errno == EINTR));
1201 if (copied == 0)
1202 return TriStateResult::Success; // EOF -> success
1203 if (srcoffset) {
1204 // some bytes were copied, so this is a real error (like ENOSPC).
1205 copied = ftruncate(dstfd, 0);
1206 return TriStateResult::Failed;
1207 }
1208
1209 // We failed with no bytes copied, so is this a real filesystem failure
1210 // that will remain with sendfile() or the copy pump? Or is it a
1211 // copy_file_range() condition?
1212 switch (errno) {
1213 case EINVAL: // observed with some obscure filesystem combinations
1214 case EXDEV: // Linux can't do xdev file copies (FreeBSD can)
1215 case ENOSYS: // caused by some containers wrongly filtering the system call
1216 break;
1217
1218 default:
1219 return TriStateResult::Failed;
1220 }
1221#endif
1222
1223#if defined(Q_OS_LINUX)
1224 // For Linux, try sendfile (it can send to some special types too).
1225 // sendfile(2) is limited in the kernel to 2G - 4k
1226 const size_t SendfileSize = 0x7ffff000;
1227
1228 ssize_t n = ::sendfile(dstfd, srcfd, nullptr, SendfileSize);
1229 if (n == -1) {
1230 switch (errno) {
1231 case ENOSPC:
1232 case EIO:
1233 return TriStateResult::Failed;
1234 }
1235
1236 // if we got an error here, give up and try at an upper layer
1237 return TriStateResult::NotSupported;
1238 }
1239
1240 while (n) {
1241 n = ::sendfile(dstfd, srcfd, nullptr, SendfileSize);
1242 if (n == -1) {
1243 // uh oh, this is probably a real error (like ENOSPC)
1244 n = ftruncate(dstfd, 0);
1245 n = lseek(srcfd, 0, SEEK_SET);
1246 n = lseek(dstfd, 0, SEEK_SET);
1247 return TriStateResult::Failed;
1248 }
1249 }
1250
1251 return TriStateResult::Success;
1252#else
1253 Q_UNUSED(dstfd);
1254 return TriStateResult::NotSupported;
1255#endif
1256}
1257
1258static QSystemError createDirectoryWithParents(const QByteArray &path, mode_t mode)
1259{
1260#ifdef Q_OS_WASM
1261 if (path == "/")
1262 return {};
1263#endif
1264
1265 auto tryMkDir = [&path, mode]() -> QSystemError {
1266 if (QT_MKDIR(path, mode) == 0) {
1267#ifdef Q_OS_VXWORKS
1268 forceRequestedPermissionsOnVxWorks(path, mode);
1269#endif
1270 return {};
1271 }
1272 // On macOS with APFS mkdir sets errno to EISDIR, QTBUG-97110
1273 if (errno == EISDIR)
1274 return {};
1275 if (errno == EEXIST || errno == EROFS) {
1276 // ::mkdir() can fail if the dir already exists (it may have been
1277 // created by another thread or another process)
1278 QT_STATBUF st;
1279 if (QT_STAT(path.constData(), &st) != 0)
1280 return QSystemError::stdError(errno);
1281 const bool isDir = (st.st_mode & S_IFMT) == S_IFDIR;
1282 return isDir ? QSystemError{} : QSystemError::stdError(EEXIST);
1283 }
1284 return QSystemError::stdError(errno);
1285 };
1286
1287 QSystemError result = tryMkDir();
1288 if (result.ok())
1289 return result;
1290
1291 // Only handle non-existing dir components in the path
1292 if (result.errorCode != ENOENT)
1293 return result;
1294
1295 qsizetype slash = path.lastIndexOf('/');
1296 while (slash > 0 && path[slash - 1] == '/')
1297 --slash;
1298
1299 if (slash < 1)
1300 return result;
1301
1302 // mkdir failed because the parent dir doesn't exist, so try to create it
1303 QByteArray parentPath = path.first(slash);
1304 if (result = createDirectoryWithParents(parentPath, mode); !result.ok())
1305 return result;
1306
1307 // try again
1308 return tryMkDir();
1309}
1310
1311bool QFileSystemEngine::mkpath(const QFileSystemEntry &entry,
1312 std::optional<QFile::Permissions> permissions)
1313{
1314 QByteArray path = entry.nativeFilePath();
1315 Q_CHECK_FILE_NAME(path, false);
1316
1317 mode_t mode = permissions ? QtPrivate::toMode_t(*permissions) : 0777;
1318 return createDirectoryWithParents(removeTrailingSlashes(path), mode).ok();
1319}
1320
1321bool QFileSystemEngine::mkdir(const QFileSystemEntry &entry,
1322 std::optional<QFile::Permissions> permissions)
1323{
1324 QByteArray path = entry.nativeFilePath();
1325 Q_CHECK_FILE_NAME(path, false);
1326
1327 mode_t mode = permissions ? QtPrivate::toMode_t(*permissions) : 0777;
1328 auto result = QT_MKDIR(removeTrailingSlashes(path), mode) == 0;
1329#if defined(Q_OS_VXWORKS)
1330 if (result)
1331 forceRequestedPermissionsOnVxWorks(path, mode);
1332#endif
1333 return result;
1334}
1335
1336bool QFileSystemEngine::rmdir(const QFileSystemEntry &entry)
1337{
1338 const QByteArray path = entry.nativeFilePath();
1339 Q_CHECK_FILE_NAME(path, false);
1340 return ::rmdir(path.constData()) == 0;
1341}
1342
1343bool QFileSystemEngine::rmpath(const QFileSystemEntry &entry)
1344{
1345 QByteArray path = QFile::encodeName(QDir::cleanPath(entry.filePath()));
1346 Q_CHECK_FILE_NAME(path, false);
1347
1348 if (::rmdir(path.constData()) != 0)
1349 return false; // Only return false if `entry` couldn't be deleted
1350
1351 const char sep = QDir::separator().toLatin1();
1352 qsizetype slash = path.lastIndexOf(sep);
1353 // `slash > 0` because truncate(0) would make `path` empty
1354 for (; slash > 0; slash = path.lastIndexOf(sep)) {
1355 path.truncate(slash);
1356 if (::rmdir(path.constData()) != 0)
1357 break;
1358 }
1359
1360 return true;
1361}
1362
1363//static
1364bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1365{
1366 Q_CHECK_FILE_NAME(source, false);
1367 Q_CHECK_FILE_NAME(target, false);
1368
1369 if (::symlink(source.nativeFilePath().constData(), target.nativeFilePath().constData()) == 0)
1370 return true;
1371 error = QSystemError(errno, QSystemError::StandardLibraryError);
1372 return false;
1373}
1374
1375#if defined(QT_BOOTSTRAPPED) || !defined(AT_FDCWD) || defined(Q_OS_ANDROID) || !QT_CONFIG(datestring) || defined(Q_OS_VXWORKS)
1376// bootstrapped tools don't need this, and we don't want QStorageInfo
1377//static
1378bool QFileSystemEngine::supportsMoveFileToTrash()
1379{
1380 return false;
1381}
1382
1383//static
1384bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &, QFileSystemEntry &,
1385 QSystemError &error)
1386{
1387 error = QSystemError(ENOSYS, QSystemError::StandardLibraryError);
1388 return false;
1389}
1390#elif defined(Q_OS_DARWIN)
1391// see qfilesystemengine_mac.mm
1392#elif defined(Q_OS_HARMONY)
1393// see qfilesystemengine_ohos.cpp
1394#else
1395/*
1396 Implementing as per https://specifications.freedesktop.org/trash-spec/1.0/
1397*/
1398//static
1399bool QFileSystemEngine::supportsMoveFileToTrash()
1400{
1401 return true;
1402}
1403
1404namespace {
1405struct FreeDesktopTrashOperation
1406{
1407 /*
1408 "A trash directory contains two subdirectories, named info and files."
1409 */
1410 QString trashPath;
1411 int filesDirFd = -1;
1412 int infoDirFd = -1;
1413 qsizetype volumePrefixLength = 0;
1414
1415 // relative file paths to the filesDirFd and infoDirFd from above
1416 QByteArray tempTrashFileName;
1417 QByteArray infoFilePath;
1418
1419 int infoFileFd = -1; // if we've already opened it
1420 ~FreeDesktopTrashOperation()
1421 {
1422 close();
1423 }
1424
1425 constexpr bool isTrashDirOpen() const { return filesDirFd != -1 && infoDirFd != -1; }
1426
1427 void close()
1428 {
1429 int savedErrno = errno;
1430 if (infoFileFd != -1) {
1431 Q_ASSERT(infoDirFd != -1);
1432 Q_ASSERT(!infoFilePath.isEmpty());
1433 Q_ASSERT(!trashPath.isEmpty());
1434
1435 QT_CLOSE(infoFileFd);
1436 unlinkat(infoDirFd, infoFilePath, 0);
1437 infoFileFd = -1;
1438 }
1439 if (!tempTrashFileName.isEmpty()) {
1440 Q_ASSERT(filesDirFd != -1);
1441 unlinkat(filesDirFd, tempTrashFileName, 0);
1442 }
1443 if (filesDirFd >= 0)
1444 QT_CLOSE(filesDirFd);
1445 if (infoDirFd >= 0)
1446 QT_CLOSE(infoDirFd);
1447 filesDirFd = infoDirFd = -1;
1448 errno = savedErrno;
1449 }
1450
1451 bool tryCreateInfoFile(const QString &filePath, QSystemError &error)
1452 {
1453 QByteArray p = QFile::encodeName(filePath) + ".trashinfo";
1454 infoFileFd = qt_safe_openat(infoDirFd, p, QT_OPEN_RDWR | QT_OPEN_CREAT | QT_OPEN_EXCL, 0666);
1455 if (infoFileFd < 0) {
1456 error = QSystemError(errno, QSystemError::StandardLibraryError);
1457 return false;
1458 }
1459 infoFilePath = std::move(p);
1460 return true;
1461 }
1462
1463 void commit()
1464 {
1465 QT_CLOSE(infoFileFd);
1466 infoFileFd = -1;
1467 tempTrashFileName = {};
1468 }
1469
1470 // opens a directory and returns the file descriptor
1471 static int openDirFd(int dfd, const char *path, int mode)
1472 {
1473 mode |= QT_OPEN_RDONLY | O_DIRECTORY;
1474 return qt_safe_openat(dfd, path, mode);
1475 }
1476
1477 // opens an XDG Trash directory that is a subdirectory of dfd, creating if necessary
1478 static int openOrCreateDir(int dfd, const char *path, int openmode = 0)
1479 {
1480 // try to open it as a dir, first
1481 int fd = openDirFd(dfd, path, openmode);
1482 if (fd >= 0 || errno != ENOENT)
1483 return fd;
1484
1485 // try to mkdirat
1486 if (mkdirat(dfd, path, 0700) < 0)
1487 return -1;
1488
1489 // try to open it again
1490 return openDirFd(dfd, path, openmode);
1491 }
1492
1493 // opens or makes the XDG Trash hierarchy on parentfd (may be -1) called targetDir
1494 QSystemError getTrashDir(int parentfd, QString targetDir, const QFileSystemEntry &source,
1495 int openmode)
1496 {
1497 if (parentfd == AT_FDCWD)
1498 trashPath = targetDir;
1499 QByteArray nativePath = QFile::encodeName(targetDir);
1500
1501 // open the directory
1502 int trashfd = openOrCreateDir(parentfd, nativePath, openmode);
1503 if (trashfd < 0 && errno != ENOENT)
1504 return QSystemError::stdError(errno);
1505
1506 // check if it is ours (even if we've just mkdirat'ed it)
1507 if (QT_STATBUF st; QT_FSTAT(trashfd, &st) < 0)
1508 return QSystemError::stdError(errno);
1509 else if (st.st_uid != getuid())
1510 return QSystemError::stdError(errno);
1511
1512 filesDirFd = openOrCreateDir(trashfd, "files");
1513 if (filesDirFd >= 0) {
1514 // try to link our file-to-be-trashed here
1515 QTemporaryFileName tfn("XXXXXX"_L1);
1516 for (int i = 0; i < 16; ++i) {
1517 QByteArray attempt = tfn.generateNext();
1518 if (linkat(AT_FDCWD, source.nativeFilePath(), filesDirFd, attempt, 0) == 0) {
1519 tempTrashFileName = std::move(attempt);
1520 break;
1521 }
1522 if (errno != EEXIST)
1523 break;
1524 }
1525
1526 // man 2 link on Linux has:
1527 // EPERM The filesystem containing oldpath and newpath does not
1528 // support the creation of hard links.
1529 // EPERM oldpath is a directory.
1530 // EPERM oldpath is marked immutable or append‐only.
1531 // EMLINK The file referred to by oldpath already has the maximum
1532 // number of links to it.
1533 if (!tempTrashFileName.isEmpty() || errno == EPERM || errno == EMLINK)
1534 infoDirFd = openOrCreateDir(trashfd, "info");
1535 }
1536
1537 if (infoDirFd < 0) {
1538 int saved_errno = errno;
1539 close();
1540 QT_CLOSE(trashfd);
1541 return QSystemError::stdError(saved_errno);
1542 }
1543
1544 QT_CLOSE(trashfd);
1545 return {};
1546 }
1547
1548 QSystemError openMountPointTrashLocation(const QFileSystemEntry &source,
1549 const QStorageInfo &sourceStorage)
1550 {
1551 /*
1552 Method 1:
1553 "An administrator can create an $topdir/.Trash directory. The permissions on this
1554 directories should permit all users who can trash files at all to write in it;
1555 and the “sticky bit” in the permissions must be set, if the file system supports
1556 it.
1557 When trashing a file from a non-home partition/device, an implementation
1558 (if it supports trashing in top directories) MUST check for the presence
1559 of $topdir/.Trash."
1560 */
1561
1562 QSystemError error;
1563 const auto dotTrash = "/.Trash"_L1;
1564 const QString userID = QString::number(::getuid());
1565 QFileSystemEntry dotTrashDir(sourceStorage.rootPath() + dotTrash);
1566
1567 // we MUST check that the sticky bit is set, and that it is not a symlink
1568 int genericTrashFd = openDirFd(AT_FDCWD, dotTrashDir.nativeFilePath(), O_NOFOLLOW);
1569 QT_STATBUF st = {};
1570 if (genericTrashFd < 0 && errno != ENOENT && errno != EACCES) {
1571 // O_DIRECTORY + O_NOFOLLOW produces ENOTDIR on Linux
1572 if (QT_LSTAT(dotTrashDir.nativeFilePath(), &st) == 0 && S_ISLNK(st.st_mode)) {
1573 // we SHOULD report the failed check to the administrator
1574 qCritical("Warning: '%s' is a symlink to '%s'",
1575 dotTrashDir.nativeFilePath().constData(),
1576 qt_readlink(dotTrashDir.nativeFilePath()).constData());
1577 error = QSystemError(ELOOP, QSystemError::StandardLibraryError);
1578 }
1579 } else if (genericTrashFd >= 0) {
1580 QT_FSTAT(genericTrashFd, &st);
1581 if ((st.st_mode & S_ISVTX) == 0) {
1582 // we SHOULD report the failed check to the administrator
1583 qCritical("Warning: '%s' doesn't have sticky bit set!",
1584 dotTrashDir.nativeFilePath().constData());
1585 error = QSystemError(EPERM, QSystemError::StandardLibraryError);
1586 } else {
1587 /*
1588 "If the directory exists and passes the checks, a subdirectory of the
1589 $topdir/.Trash directory is to be used as the user's trash directory
1590 for this partition/device. The name of this subdirectory is the numeric
1591 identifier of the current user ($topdir/.Trash/$uid).
1592 When trashing a file, if this directory does not exist for the current user,
1593 the implementation MUST immediately create it, without any warnings or
1594 delays for the user."
1595 */
1596 if (error = getTrashDir(genericTrashFd, userID, source, O_NOFOLLOW); error.ok()) {
1597 // recreate the resulting path
1598 trashPath = dotTrashDir.filePath() + u'/' + userID;
1599 }
1600 }
1601 QT_CLOSE(genericTrashFd);
1602 }
1603
1604 /*
1605 Method 2:
1606 "If an $topdir/.Trash directory is absent, an $topdir/.Trash-$uid directory is to be
1607 used as the user's trash directory for this device/partition. [...] When trashing a
1608 file, if an $topdir/.Trash-$uid directory does not exist, the implementation MUST
1609 immediately create it, without any warnings or delays for the user."
1610 */
1611 if (!isTrashDirOpen())
1612 error = getTrashDir(AT_FDCWD, sourceStorage.rootPath() + dotTrash + u'-' + userID, source,
1613 O_NOFOLLOW);
1614
1615 if (isTrashDirOpen()) {
1616 volumePrefixLength = sourceStorage.rootPath().size();
1617 if (volumePrefixLength == 1)
1618 volumePrefixLength = 0; // isRoot
1619 else
1620 ++volumePrefixLength; // to include the slash
1621 }
1622 return isTrashDirOpen() ? QSystemError() : error;
1623 }
1624
1625 QSystemError openHomeTrashLocation(const QFileSystemEntry &source)
1626 {
1627 QString topDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
1628 int openmode = 0; // do allow following symlinks
1629 return getTrashDir(AT_FDCWD, topDir + "/Trash"_L1, source, openmode);
1630 }
1631
1632 QSystemError findTrashFor(const QFileSystemEntry &source)
1633 {
1634 /*
1635 First, try the standard Trash in $XDG_DATA_DIRS:
1636 "Its name and location are $XDG_DATA_HOME/Trash"; $XDG_DATA_HOME is what
1637 QStandardPaths returns for GenericDataLocation. If that doesn't exist, then
1638 we are not running on a freedesktop.org-compliant environment, and give up.
1639 */
1640 if (QSystemError error = openHomeTrashLocation(source); error.ok())
1641 return QSystemError();
1642 else if (error.errorCode != EXDEV)
1643 return error;
1644
1645 // didn't work, try to find the trash outside the home filesystem
1646 const QStorageInfo sourceStorage(source.filePath());
1647 if (!sourceStorage.isValid())
1648 return QSystemError::stdError(ENODEV);
1649 return openMountPointTrashLocation(source, sourceStorage);
1650 }
1651};
1652} // unnamed namespace
1653
1654//static
1655bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &source,
1656 QFileSystemEntry &newLocation, QSystemError &error)
1657{
1658 const QFileSystemEntry sourcePath = [&] {
1659 if (QString path = source.filePath(); path.size() > 1 && path.endsWith(u'/')) {
1660 path.chop(1);
1661 return absoluteName(QFileSystemEntry(path));
1662 }
1663 return absoluteName(source);
1664 }();
1665 FreeDesktopTrashOperation op;
1666 if (error = op.findTrashFor(sourcePath); !error.ok())
1667 return false;
1668
1669 /*
1670 "The $trash/files directory contains the files and directories that were trashed.
1671 The names of files in this directory are to be determined by the implementation;
1672 the only limitation is that they must be unique within the directory. Even if a
1673 file with the same name and location gets trashed many times, each subsequent
1674 trashing must not overwrite a previous copy."
1675
1676 We first try the unchanged base name, then try something different if it collides.
1677
1678 "The $trash/info directory contains an "information file" for every file and directory
1679 in $trash/files. This file MUST have exactly the same name as the file or directory in
1680 $trash/files, plus the extension ".trashinfo"
1681 [...]
1682 When trashing a file or directory, the implementation MUST create the corresponding
1683 file in $trash/info first. Moreover, it MUST try to do this in an atomic fashion,
1684 so that if two processes try to trash files with the same filename this will result
1685 in two different trash files. On Unix-like systems this is done by generating a
1686 filename, and then opening with O_EXCL. If that succeeds the creation was atomic
1687 (at least on the same machine), if it fails you need to pick another filename."
1688 */
1689 QString uniqueTrashedName = sourcePath.fileName();
1690 if (!op.tryCreateInfoFile(uniqueTrashedName, error) && error.errorCode == EEXIST) {
1691 // we'll use a counter, starting with the file's inode number to avoid
1692 // collisions
1693 qulonglong counter;
1694 if (QT_STATBUF st; Q_LIKELY(QT_STAT(source.nativeFilePath(), &st) == 0)) {
1695 counter = st.st_ino;
1696 } else {
1697 error = QSystemError(errno, QSystemError::StandardLibraryError);
1698 return false;
1699 }
1700
1701 QString uniqueTrashBase = std::move(uniqueTrashedName);
1702 for (;;) {
1703 uniqueTrashedName = QString::asprintf("%ls-%llu", qUtf16Printable(uniqueTrashBase),
1704 counter++);
1705 if (op.tryCreateInfoFile(uniqueTrashedName, error))
1706 break;
1707 if (error.errorCode != EEXIST)
1708 return false;
1709 };
1710 }
1711
1712 QByteArray info =
1713 "[Trash Info]\n"
1714 "Path=" + QUrl::toPercentEncoding(source.filePath().mid(op.volumePrefixLength), "/") + "\n"
1715 "DeletionDate=" + QDateTime::currentDateTime().toString(Qt::ISODate).toUtf8()
1716 + "\n";
1717 if (QT_WRITE(op.infoFileFd, info.data(), info.size()) < 0) {
1718 error = QSystemError(errno, QSystemError::StandardLibraryError);
1719 return false;
1720 }
1721
1722 /*
1723 If we've already linked the file-to-be-trashed into the trash
1724 directory, we know it's in the same mountpoint and we won't get ENOSPC
1725 renaming the temporary file to the target name either.
1726 */
1727 bool renamed;
1728 if (op.tempTrashFileName.isEmpty()) {
1729 /*
1730 We did not get a link (we're trying to trash a directory or on a
1731 filesystem that doesn't support hardlinking), so rename straight
1732 from the original name. We might fail to rename if source and target
1733 are on different file systems.
1734 */
1735 renamed = renameat(AT_FDCWD, source.nativeFilePath(), op.filesDirFd,
1736 QFile::encodeName(uniqueTrashedName)) == 0;
1737 } else {
1738 renamed = renameat(op.filesDirFd, op.tempTrashFileName, op.filesDirFd,
1739 QFile::encodeName(uniqueTrashedName)) == 0;
1740 if (renamed)
1741 removeFile(sourcePath, error); // success, delete the original file
1742 }
1743 if (!renamed) {
1744 error = QSystemError(errno, QSystemError::StandardLibraryError);
1745 return false;
1746 }
1747
1748 op.commit();
1749 newLocation = QFileSystemEntry(op.trashPath + "/files/"_L1 + uniqueTrashedName);
1750 return true;
1751}
1752#endif // !Q_OS_DARWIN && (!QT_BOOTSTRAPPED && AT_FDCWD && !Q_OS_ANDROID && QT_CONFIG(datestring))
1753
1754//static
1755#if !defined(AT_FDCWD) || defined(Q_OS_QNX) || defined(Q_OS_VXWORKS)
1756// The implementation below requires the POSIX at-file functions. Without them,
1757// it's no better than the full path name solution in
1758// QDir::removeRecursively().
1759//
1760// The code below causes some unrelated files to disappear on QNX (we run our
1761// unit tests as root). On VxWorks, something doesn't go right and we fail to
1762// delete everything.
1763bool QFileSystemEngine::supportsRmdirRecursively() noexcept
1764{
1765 return false;
1766}
1767
1768//static
1769bool QFileSystemEngine::rmdirRecursively(const QFileSystemEntry &, QSystemError &error)
1770{
1771 error = QSystemError(ENOSYS, QSystemError::StandardLibraryError);
1772 return false;
1773}
1774#else
1775
1776//static
1777bool QFileSystemEngine::supportsRmdirRecursively() noexcept
1778{
1779 return true;
1780}
1781
1782//static
1783bool QFileSystemEngine::rmdirRecursively(const QFileSystemEntry &entry, QSystemError &error)
1784{
1785 Q_CHECK_FILE_NAME(entry, false);
1786
1787# if defined(Q_OS_ANDROID) || defined(Q_OS_BSD4) || defined(Q_OS_LINUX)
1788 // POSIX.1 says:
1789 // "if any attempt is made to [...] modify the state of the associated
1790 // description, other than by means of closedir(), readdir(), readdir_r(),
1791 // rewinddir(), or seekdir(), the behavior is undefined".
1792 // However, on these OSes we can openat() subdirectories using an iterated
1793 // file descriptor.
1794 constexpr bool NeedsSecondFileDescriptor = false;
1795# else
1796 constexpr bool NeedsSecondFileDescriptor = true;
1797# endif
1798 struct DirInfo {
1799 DIR *dir;
1800 int dirfd;
1801 int parentFd; // parent's fd for unlinkat(parentFd, name.c_str(), AT_REMOVEDIR)
1802 std::string name; // our entry name within parent
1803 };
1804 auto openDirectory = [](int parentFd, std::string name) {
1805 constexpr int OpenMode = QT_OPEN_RDONLY | O_DIRECTORY | O_NOFOLLOW;
1806 DirInfo di = { nullptr, -1, parentFd, std::move(name) };
1807 di.dirfd = qt_safe_openat(parentFd, di.name.c_str(), OpenMode);
1808 if (di.dirfd < 0)
1809 return di;
1810
1811 // fdopendir takes ownership of dirfd if it succeeds
1812 di.dir = fdopendir(di.dirfd);
1813 if (!di.dir) {
1814 QT_CLOSE(di.dirfd);
1815 return di;
1816 }
1817
1818 if constexpr (NeedsSecondFileDescriptor) {
1819 di.dirfd = qt_safe_openat(parentFd, di.name.c_str(), OpenMode);
1820 if (Q_UNLIKELY(di.dirfd < 0))
1821 closedir(std::exchange(di.dir, nullptr));
1822 }
1823
1824 return di;
1825 };
1826
1827 QVarLengthArray<DirInfo, 16> stack;
1828 if (DirInfo di = openDirectory(AT_FDCWD, entry.nativeFilePath().toStdString()); di.dir) {
1829 stack.emplace_back(std::move(di));
1830 } else {
1831 if (errno == ENOENT)
1832 return true;
1833 error = QSystemError(errno, QSystemError::StandardLibraryError);
1834 return false;
1835 }
1836
1837 while (!stack.isEmpty()) {
1838 DirInfo &top = stack.last();
1839 if (QT_DIRENT *de = QT_READDIR(top.dir)) {
1840 if (de->d_name[0] == '.') {
1841 if (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))
1842 continue;
1843 }
1844
1845 QFileSystemMetaData meta;
1846 meta.fillFromDirEnt(*de);
1847
1848 // If the entry is not known to be a directory, attempt to unlink it.
1849 if (!meta.hasFlags(QFileSystemMetaData::DirectoryType) || !meta.isDirectory()) {
1850 if (unlinkat(top.dirfd, de->d_name, 0) == 0)
1851 continue; // removed a non-directory
1852 // Couldn't unlink: it might be a directory
1853 }
1854
1855 // Entry is a directory - open it and push onto the stack.
1856 if (DirInfo di = openDirectory(top.dirfd, de->d_name); di.dir) {
1857 // Parent entry stays on the stack below the child.
1858 stack.emplace_back(std::move(di));
1859 }
1860
1861 // If we can't open child, it will remain, causing the parent
1862 // unlinkat() to fail with ENOTEMPTY.
1863 } else {
1864 // End of this directory - all children have been processed.
1865 closedir(top.dir);
1866 if constexpr (NeedsSecondFileDescriptor)
1867 QT_CLOSE(top.dirfd);
1868
1869 // Now erase this entry in the parent
1870 int r = unlinkat(top.parentFd, top.name.c_str(), AT_REMOVEDIR);
1871 if (r < 0 && top.parentFd == AT_FDCWD && errno != ENOENT)
1872 error = QSystemError(errno, QSystemError::StandardLibraryError);
1873 stack.removeLast();
1874 }
1875 }
1876
1877 // True iff the root unlinkat() succeeded. Any upstream failure surfaces as
1878 // ENOTEMPTY there.
1879 return error.ok();
1880}
1881#endif // AT_FDCWD
1882
1883//static
1884bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1885{
1886 Q_UNUSED(source);
1887 Q_UNUSED(target);
1888 error = QSystemError(ENOSYS, QSystemError::StandardLibraryError); //Function not implemented
1889 return false;
1890}
1891
1892//static
1893bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1894{
1895 QFileSystemEntry::NativePath srcPath = source.nativeFilePath();
1896 QFileSystemEntry::NativePath tgtPath = target.nativeFilePath();
1897
1898 Q_CHECK_FILE_NAME(srcPath, false);
1899 Q_CHECK_FILE_NAME(tgtPath, false);
1900
1901#if defined(RENAME_NOREPLACE) && QT_CONFIG(renameat2)
1902 if (renameat2(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_NOREPLACE) == 0)
1903 return true;
1904
1905 // We can also get EINVAL for some non-local filesystems.
1906 if (errno != EINVAL) {
1907 error = QSystemError(errno, QSystemError::StandardLibraryError);
1908 return false;
1909 }
1910#endif
1911#if defined(Q_OS_DARWIN) && defined(RENAME_EXCL)
1912 if (renameatx_np(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_EXCL) == 0)
1913 return true;
1914 if (errno != ENOTSUP) {
1915 error = QSystemError(errno, QSystemError::StandardLibraryError);
1916 return false;
1917 }
1918#endif
1919
1920 if (SupportsHardlinking && ::link(srcPath, tgtPath) == 0) {
1921 if (::unlink(srcPath) == 0)
1922 return true;
1923
1924 // if we managed to link but can't unlink the source, it's likely
1925 // it's in a directory we don't have write access to; fail the
1926 // renaming instead
1927 int savedErrno = errno;
1928
1929 // this could fail too, but there's nothing we can do about it now
1930 ::unlink(tgtPath);
1931
1932 error = QSystemError(savedErrno, QSystemError::StandardLibraryError);
1933 return false;
1934 } else if (!SupportsHardlinking) {
1935 // Since hard linking is not supported, check for target existence
1936 // manually, mimicking what link(2) would have reported via EEXIST.
1937 // If the target does not exist, fall through to ::rename() below.
1938 if (::access(tgtPath, F_OK) == 0) {
1939 errno = EEXIST;
1940 } else {
1941 // man 2 link on Linux has:
1942 // EPERM The filesystem containing oldpath and newpath does not
1943 // support the creation of hard links.
1944 errno = EPERM;
1945 }
1946 }
1947
1948 switch (errno) {
1949 case EACCES:
1950 case EEXIST:
1951 case ENAMETOOLONG:
1952 case ENOENT:
1953 case ENOTDIR:
1954 case EROFS:
1955 case EXDEV:
1956 // accept the error from link(2) (especially EEXIST) and don't retry
1957 break;
1958
1959 default:
1960 // fall back to rename()
1961 // ### Race condition. If a file is moved in after this, it /will/ be
1962 // overwritten.
1963 if (::rename(srcPath, tgtPath) == 0)
1964 return true;
1965 }
1966
1967 error = QSystemError(errno, QSystemError::StandardLibraryError);
1968 return false;
1969}
1970
1971//static
1972bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
1973{
1974 Q_CHECK_FILE_NAME(source, false);
1975 Q_CHECK_FILE_NAME(target, false);
1976
1977 if (::rename(source.nativeFilePath().constData(), target.nativeFilePath().constData()) == 0)
1978 return true;
1979 error = QSystemError(errno, QSystemError::StandardLibraryError);
1980 return false;
1981}
1982
1983//static
1984bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError &error)
1985{
1986 Q_CHECK_FILE_NAME(entry, false);
1987 if (unlink(entry.nativeFilePath().constData()) == 0)
1988 return true;
1989 error = QSystemError(errno, QSystemError::StandardLibraryError);
1990 return false;
1991
1992}
1993
1994//static
1995bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry,
1996 QFile::Permissions permissions, QSystemError &error)
1997{
1998 Q_CHECK_FILE_NAME(entry, false);
1999
2000 mode_t mode = QtPrivate::toMode_t(permissions);
2001 bool success = ::chmod(entry.nativeFilePath().constData(), mode) == 0;
2002 if (!success)
2003 error = QSystemError(errno, QSystemError::StandardLibraryError);
2004 return success;
2005}
2006
2007//static
2008bool QFileSystemEngine::setPermissions(int fd, QFile::Permissions permissions, QSystemError &error)
2009{
2010 mode_t mode = QtPrivate::toMode_t(permissions);
2011 bool success = ::fchmod(fd, mode) == 0;
2012 if (!success)
2013 error = QSystemError(errno, QSystemError::StandardLibraryError);
2014 return success;
2015}
2016
2017//static
2018bool QFileSystemEngine::setFileTime(int fd, const QDateTime &newDate, QFile::FileTime time, QSystemError &error)
2019{
2020 if (!newDate.isValid()
2021 || time == QFile::FileBirthTime || time == QFile::FileMetadataChangeTime) {
2022 error = QSystemError(EINVAL, QSystemError::StandardLibraryError);
2023 return false;
2024 }
2025
2026#if QT_CONFIG(futimens)
2027 // UTIME_OMIT: leave file timestamp unchanged
2028 struct timespec ts[2] = {{0, UTIME_OMIT}, {0, UTIME_OMIT}};
2029
2030 if (time == QFile::FileAccessTime || time == QFile::FileModificationTime) {
2031 const int idx = time == QFile::FileAccessTime ? 0 : 1;
2032 const std::chrono::milliseconds msecs{newDate.toMSecsSinceEpoch()};
2033 ts[idx] = durationToTimespec(msecs);
2034 }
2035
2036 if (futimens(fd, ts) == -1) {
2037 error = QSystemError(errno, QSystemError::StandardLibraryError);
2038 return false;
2039 }
2040
2041 return true;
2042#else
2043 Q_UNUSED(fd);
2044 error = QSystemError(ENOSYS, QSystemError::StandardLibraryError);
2045 return false;
2046#endif
2047}
2048
2049QString QFileSystemEngine::homePath()
2050{
2051 QString home = qEnvironmentVariable("HOME");
2052 if (home.isEmpty())
2053 home = rootPath();
2054 return QDir::cleanPath(home);
2055}
2056
2057QString QFileSystemEngine::rootPath()
2058{
2059 return u"/"_s;
2060}
2061
2062static constexpr QLatin1StringView nativeTempPath() noexcept
2063{
2064 // _PATH_TMP usually ends in '/' and we don't want that
2065 QLatin1StringView temp = _PATH_TMP ""_L1;
2066 static_assert(_PATH_TMP[0] == '/', "_PATH_TMP needs to be absolute");
2067 static_assert(_PATH_TMP[1] != '\0', "Are you really sure _PATH_TMP should be the root dir??");
2068 if (temp.endsWith(u'/'))
2069 temp.chop(1);
2070 return temp;
2071}
2072
2073QString QFileSystemEngine::tempPath()
2074{
2075#ifdef QT_UNIX_TEMP_PATH_OVERRIDE
2076 return QT_UNIX_TEMP_PATH_OVERRIDE ""_L1;
2077#else
2078 QString temp = qEnvironmentVariable("TMPDIR");
2079# if defined(Q_OS_DARWIN) && !defined(QT_BOOTSTRAPPED)
2080 if (NSString *nsPath; temp.isEmpty() && (nsPath = NSTemporaryDirectory()))
2081 temp = QString::fromCFString((CFStringRef)nsPath);
2082# endif
2083 if (temp.isEmpty())
2084 return nativeTempPath();
2085
2086 // the environment variable may also end in '/'
2087 if (temp.size() > 1 && temp.endsWith(u'/'))
2088 temp.chop(1);
2089
2090 QFileSystemEntry e(temp, QFileSystemEntry::FromInternalPath{});
2091 return QFileSystemEngine::absoluteName(e).filePath();
2092#endif
2093}
2094
2095bool QFileSystemEngine::setCurrentPath(const QFileSystemEntry &path)
2096{
2097 int r;
2098 r = QT_CHDIR(path.nativeFilePath().constData());
2099 return r >= 0;
2100}
2101
2102QFileSystemEntry QFileSystemEngine::currentPath()
2103{
2104 QFileSystemEntry result;
2105#if defined(__GLIBC__) && !defined(PATH_MAX)
2106 char *currentName = ::get_current_dir_name();
2107 if (currentName) {
2108 result = QFileSystemEntry(QByteArray(currentName), QFileSystemEntry::FromNativePath());
2109 ::free(currentName);
2110 }
2111#else
2112 char currentName[PATH_MAX+1];
2113 if (::getcwd(currentName, PATH_MAX)) {
2114#if defined(Q_OS_VXWORKS) && defined(VXWORKS_VXSIM)
2115 QByteArray dir(currentName);
2116 if (dir.indexOf(':') < dir.indexOf('/'))
2117 dir.remove(0, dir.indexOf(':')+1);
2118
2119 qstrncpy(currentName, dir.constData(), PATH_MAX);
2120#endif
2121 result = QFileSystemEntry(QByteArray(currentName), QFileSystemEntry::FromNativePath());
2122 }
2123# if defined(QT_DEBUG)
2124 if (result.isEmpty())
2125 qWarning("QFileSystemEngine::currentPath: getcwd() failed");
2126# endif
2127#endif
2128 return result;
2129}
2130
2131bool QFileSystemEngine::isCaseSensitive(const QFileSystemEntry &entry, QFileSystemMetaData &metaData)
2132{
2133#if defined(Q_OS_DARWIN)
2134 if (!metaData.hasFlags(QFileSystemMetaData::CaseSensitive))
2135 fillMetaData(entry, metaData, QFileSystemMetaData::CaseSensitive);
2136 return metaData.entryFlags.testFlag(QFileSystemMetaData::CaseSensitive);
2137#else
2138 Q_UNUSED(entry);
2139 Q_UNUSED(metaData);
2140 // FIXME: This may not be accurate for all file systems (QTBUG-28246)
2141 return true;
2142#endif
2143}
2144
2145QT_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 int qt_statx(const char *, struct statx *)