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