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