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

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