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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtbase/src/corelib/io/qfilesystemengine_unix.cpp