Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qfilesystemengine_unix.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 Intel Corporation.
2// Copyright (C) 2016 The Qt Company Ltd.
3// Copyright (C) 2013 Samuel Gaist <samuel.gaist@edeltech.ch>
4// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5
6#include "qplatformdefs.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
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.
88#else
90#endif
91};
92
93#if defined(Q_OS_DARWIN)
94static inline bool hasResourcePropertyFlag(const QFileSystemMetaData &data,
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,
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
160namespace {
161namespace GetFileTimes {
162qint64 time_t_toMsecs(time_t t)
163{
164 using namespace std::chrono;
165 return milliseconds{seconds{t}}.count();
166}
167
168// fallback set
169[[maybe_unused]] qint64 atime(const QT_STATBUF &statBuffer, ulong)
170{
171 return time_t_toMsecs(statBuffer.st_atime);
172}
173[[maybe_unused]] qint64 birthtime(const QT_STATBUF &, ulong)
174{
175 return Q_INT64_C(0);
176}
177[[maybe_unused]] qint64 ctime(const QT_STATBUF &statBuffer, ulong)
178{
179 return time_t_toMsecs(statBuffer.st_ctime);
180}
181[[maybe_unused]] qint64 mtime(const QT_STATBUF &statBuffer, ulong)
182{
183 return time_t_toMsecs(statBuffer.st_mtime);
184}
185
186// T is either a stat.timespec or statx.statx_timestamp,
187// both have tv_sec and tv_nsec members
188template<typename T>
189qint64 timespecToMSecs(const T &spec)
190{
191 using namespace std::chrono;
192 const nanoseconds nsecs = seconds{spec.tv_sec} + nanoseconds{spec.tv_nsec};
193 return duration_cast<milliseconds>(nsecs).count();
194}
195
196// Xtim, POSIX.1-2008
197template <typename T>
198[[maybe_unused]] static typename std::enable_if<(&T::st_atim, true), qint64>::type
199atime(const T &statBuffer, int)
200{ return timespecToMSecs(statBuffer.st_atim); }
201
202template <typename T>
203[[maybe_unused]] static typename std::enable_if<(&T::st_birthtim, true), qint64>::type
204birthtime(const T &statBuffer, int)
205{ return timespecToMSecs(statBuffer.st_birthtim); }
206
207template <typename T>
208[[maybe_unused]] static typename std::enable_if<(&T::st_ctim, true), qint64>::type
209ctime(const T &statBuffer, int)
210{ return timespecToMSecs(statBuffer.st_ctim); }
211
212template <typename T>
213[[maybe_unused]] static typename std::enable_if<(&T::st_mtim, true), qint64>::type
214mtime(const T &statBuffer, int)
215{ return timespecToMSecs(statBuffer.st_mtim); }
216
217#ifndef st_mtimespec
218// Xtimespec
219template <typename T>
220[[maybe_unused]] static typename std::enable_if<(&T::st_atimespec, true), qint64>::type
221atime(const T &statBuffer, int)
222{ return timespecToMSecs(statBuffer.st_atimespec); }
223
224template <typename T>
225[[maybe_unused]] static typename std::enable_if<(&T::st_birthtimespec, true), qint64>::type
226birthtime(const T &statBuffer, int)
227{ return timespecToMSecs(statBuffer.st_birthtimespec); }
228
229template <typename T>
230[[maybe_unused]] static typename std::enable_if<(&T::st_ctimespec, true), qint64>::type
231ctime(const T &statBuffer, int)
232{ return timespecToMSecs(statBuffer.st_ctimespec); }
233
234template <typename T>
235[[maybe_unused]] static typename std::enable_if<(&T::st_mtimespec, true), qint64>::type
236mtime(const T &statBuffer, int)
237{ return timespecToMSecs(statBuffer.st_mtimespec); }
238#endif
239
240#if !defined(st_mtimensec) && !defined(__alpha__)
241// Xtimensec
242template <typename T>
243[[maybe_unused]] static typename std::enable_if<(&T::st_atimensec, true), qint64>::type
244atime(const T &statBuffer, int)
245{ return statBuffer.st_atime * Q_INT64_C(1000) + statBuffer.st_atimensec / 1000000; }
246
247template <typename T>
248[[maybe_unused]] static typename std::enable_if<(&T::st_birthtimensec, true), qint64>::type
249birthtime(const T &statBuffer, int)
250{ return statBuffer.st_birthtime * Q_INT64_C(1000) + statBuffer.st_birthtimensec / 1000000; }
251
252template <typename T>
253[[maybe_unused]] static typename std::enable_if<(&T::st_ctimensec, true), qint64>::type
254ctime(const T &statBuffer, int)
255{ return statBuffer.st_ctime * Q_INT64_C(1000) + statBuffer.st_ctimensec / 1000000; }
256
257template <typename T>
258[[maybe_unused]] static typename std::enable_if<(&T::st_mtimensec, true), qint64>::type
259mtime(const T &statBuffer, int)
260{ return statBuffer.st_mtime * Q_INT64_C(1000) + statBuffer.st_mtimensec / 1000000; }
261#endif
262} // namespace GetFileTimes
263} // unnamed namespace
264
265#ifdef STATX_BASIC_STATS
266static int qt_real_statx(int fd, const char *pathname, int flags, struct statx *statxBuffer)
267{
268 unsigned mask = STATX_BASIC_STATS | STATX_BTIME;
269 int ret = statx(fd, pathname, flags | AT_NO_AUTOMOUNT, mask, statxBuffer);
270 return ret == -1 ? -errno : 0;
271}
272
273static int qt_statx(const char *pathname, struct statx *statxBuffer)
274{
275 return qt_real_statx(AT_FDCWD, pathname, 0, statxBuffer);
276}
277
278static int qt_lstatx(const char *pathname, struct statx *statxBuffer)
279{
280 return qt_real_statx(AT_FDCWD, pathname, AT_SYMLINK_NOFOLLOW, statxBuffer);
281}
282
283static int qt_fstatx(int fd, struct statx *statxBuffer)
284{
285 return qt_real_statx(fd, "", AT_EMPTY_PATH, statxBuffer);
286}
287
288inline void QFileSystemMetaData::fillFromStatxBuf(const struct statx &statxBuffer)
289{
290 // Permissions
291 if (statxBuffer.stx_mode & S_IRUSR)
293 if (statxBuffer.stx_mode & S_IWUSR)
295 if (statxBuffer.stx_mode & S_IXUSR)
297
298 if (statxBuffer.stx_mode & S_IRGRP)
300 if (statxBuffer.stx_mode & S_IWGRP)
302 if (statxBuffer.stx_mode & S_IXGRP)
304
305 if (statxBuffer.stx_mode & S_IROTH)
307 if (statxBuffer.stx_mode & S_IWOTH)
309 if (statxBuffer.stx_mode & S_IXOTH)
311
312 // Type
313 if (S_ISLNK(statxBuffer.stx_mode))
314 entryFlags |= QFileSystemMetaData::LinkType;
315 if ((statxBuffer.stx_mode & S_IFMT) == S_IFREG)
316 entryFlags |= QFileSystemMetaData::FileType;
317 else if ((statxBuffer.stx_mode & S_IFMT) == S_IFDIR)
319 else if ((statxBuffer.stx_mode & S_IFMT) != S_IFBLK)
321
322 // Attributes
323 entryFlags |= QFileSystemMetaData::ExistsAttribute; // inode exists
324 if (statxBuffer.stx_nlink == 0)
326 size_ = qint64(statxBuffer.stx_size);
327
328 // Times
329 using namespace GetFileTimes;
330 accessTime_ = timespecToMSecs(statxBuffer.stx_atime);
331 metadataChangeTime_ = timespecToMSecs(statxBuffer.stx_ctime);
332 modificationTime_ = timespecToMSecs(statxBuffer.stx_mtime);
333 const bool birthMask = statxBuffer.stx_mask & STATX_BTIME;
334 birthTime_ = birthMask ? timespecToMSecs(statxBuffer.stx_btime) : 0;
335
336 userId_ = statxBuffer.stx_uid;
337 groupId_ = statxBuffer.stx_gid;
338}
339#else
340static int qt_statx(const char *, struct statx *)
341{ return -ENOSYS; }
342
343static int qt_lstatx(const char *, struct statx *)
344{ return -ENOSYS; }
345
346static int qt_fstatx(int, struct statx *)
347{ return -ENOSYS; }
348
349inline void QFileSystemMetaData::fillFromStatxBuf(const struct statx &)
350{ }
351#endif
352
353//static
355{
356 data.entryFlags &= ~QFileSystemMetaData::PosixStatFlags;
358
359 struct statx statxBuffer;
360
361 int ret = qt_fstatx(fd, &statxBuffer);
362 if (ret != -ENOSYS) {
363 if (ret == 0) {
364 data.fillFromStatxBuf(statxBuffer);
365 return true;
366 }
367 return false;
368 }
369
370 QT_STATBUF statBuffer;
371
372 if (QT_FSTAT(fd, &statBuffer) == 0) {
373 data.fillFromStatBuf(statBuffer);
374 return true;
375 }
376
377 return false;
378}
379
380#if defined(_DEXTRA_FIRST)
381static void fillStat64fromStat32(struct stat64 *statBuf64, const struct stat &statBuf32)
382{
383 statBuf64->st_mode = statBuf32.st_mode;
384 statBuf64->st_size = statBuf32.st_size;
385#if _POSIX_VERSION >= 200809L
386 statBuf64->st_ctim = statBuf32.st_ctim;
387 statBuf64->st_mtim = statBuf32.st_mtim;
388 statBuf64->st_atim = statBuf32.st_atim;
389#else
390 statBuf64->st_ctime = statBuf32.st_ctime;
391 statBuf64->st_mtime = statBuf32.st_mtime;
392 statBuf64->st_atime = statBuf32.st_atime;
393#endif
394 statBuf64->st_uid = statBuf32.st_uid;
395 statBuf64->st_gid = statBuf32.st_gid;
396}
397#endif
398
399void QFileSystemMetaData::fillFromStatBuf(const QT_STATBUF &statBuffer)
400{
401 // Permissions
402 if (statBuffer.st_mode & S_IRUSR)
404 if (statBuffer.st_mode & S_IWUSR)
406 if (statBuffer.st_mode & S_IXUSR)
408
409 if (statBuffer.st_mode & S_IRGRP)
411 if (statBuffer.st_mode & S_IWGRP)
413 if (statBuffer.st_mode & S_IXGRP)
415
416 if (statBuffer.st_mode & S_IROTH)
418 if (statBuffer.st_mode & S_IWOTH)
420 if (statBuffer.st_mode & S_IXOTH)
422
423 // Type
424 if ((statBuffer.st_mode & S_IFMT) == S_IFREG)
425 entryFlags |= QFileSystemMetaData::FileType;
426 else if ((statBuffer.st_mode & S_IFMT) == S_IFDIR)
428 else if ((statBuffer.st_mode & S_IFMT) != S_IFBLK)
430
431 // Attributes
432 entryFlags |= QFileSystemMetaData::ExistsAttribute; // inode exists
433 if (statBuffer.st_nlink == 0)
435 size_ = statBuffer.st_size;
436#ifdef UF_HIDDEN
437 if (statBuffer.st_flags & UF_HIDDEN) {
439 knownFlagsMask |= QFileSystemMetaData::HiddenAttribute;
440 }
441#endif
442
443 // Times
444 accessTime_ = GetFileTimes::atime(statBuffer, 0);
445 birthTime_ = GetFileTimes::birthtime(statBuffer, 0);
446 metadataChangeTime_ = GetFileTimes::ctime(statBuffer, 0);
447 modificationTime_ = GetFileTimes::mtime(statBuffer, 0);
448
449 userId_ = statBuffer.st_uid;
450 groupId_ = statBuffer.st_gid;
451}
452
453void QFileSystemMetaData::fillFromDirEnt(const QT_DIRENT &entry)
454{
455#if defined(_DEXTRA_FIRST)
456 knownFlagsMask = {};
457 entryFlags = {};
458 for (dirent_extra *extra = _DEXTRA_FIRST(&entry); _DEXTRA_VALID(extra, &entry);
459 extra = _DEXTRA_NEXT(extra)) {
460 if (extra->d_type == _DTYPE_STAT || extra->d_type == _DTYPE_LSTAT) {
461
462 const struct dirent_extra_stat * const extra_stat =
463 reinterpret_cast<struct dirent_extra_stat *>(extra);
464
465 // Remember whether this was a link or not, this saves an lstat() call later.
466 if (extra->d_type == _DTYPE_LSTAT) {
467 knownFlagsMask |= QFileSystemMetaData::LinkType;
468 if (S_ISLNK(extra_stat->d_stat.st_mode))
469 entryFlags |= QFileSystemMetaData::LinkType;
470 }
471
472 // For symlinks, the extra type _DTYPE_LSTAT doesn't work for filling out the meta data,
473 // as we need the stat() information there, not the lstat() information.
474 // In this case, don't use the extra information.
475 // Unfortunately, readdir() never seems to return extra info of type _DTYPE_STAT, so for
476 // symlinks, we always incur the cost of an extra stat() call later.
477 if (S_ISLNK(extra_stat->d_stat.st_mode) && extra->d_type == _DTYPE_LSTAT)
478 continue;
479
480#if defined(QT_USE_XOPEN_LFS_EXTENSIONS) && defined(QT_LARGEFILE_SUPPORT)
481 // Even with large file support, d_stat is always of type struct stat, not struct stat64,
482 // so it needs to be converted
483 struct stat64 statBuf;
484 fillStat64fromStat32(&statBuf, extra_stat->d_stat);
485 fillFromStatBuf(statBuf);
486#else
487 fillFromStatBuf(extra_stat->d_stat);
488#endif
489 knownFlagsMask |= QFileSystemMetaData::PosixStatFlags;
490 if (!S_ISLNK(extra_stat->d_stat.st_mode)) {
491 knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
493 }
494 }
495 }
496#elif defined(_DIRENT_HAVE_D_TYPE) || defined(Q_OS_BSD4)
497 // BSD4 includes OS X and iOS
498
499 // ### This will clear all entry flags and knownFlagsMask
500 switch (entry.d_type)
501 {
502 case DT_DIR:
503 knownFlagsMask = QFileSystemMetaData::LinkType
508
511
512 break;
513
514 case DT_BLK:
515 knownFlagsMask = QFileSystemMetaData::LinkType
522
524
525 break;
526
527 case DT_CHR:
528 case DT_FIFO:
529 case DT_SOCK:
530 // ### System attribute
531 knownFlagsMask = QFileSystemMetaData::LinkType
538
541
542 break;
543
544 case DT_LNK:
545 knownFlagsMask = QFileSystemMetaData::LinkType;
547 break;
548
549 case DT_REG:
550 knownFlagsMask = QFileSystemMetaData::LinkType
556
559
560 break;
561
562 case DT_UNKNOWN:
563 default:
564 clear();
565 }
566#else
568#endif
569}
570
571//static
573{
574 Q_CHECK_FILE_NAME(link, link);
575
577 if (s.size() > 0) {
578 QString ret;
581 if (data.isDirectory() && s[0] != '/') {
582 QDir parent(link.filePath());
583 parent.cdUp();
584 ret = parent.path();
585 if (!ret.isEmpty() && !ret.endsWith(u'/'))
586 ret += u'/';
587 }
589
590 if (!ret.startsWith(u'/'))
591 ret.prepend(absoluteName(link).path() + u'/');
593 if (ret.size() > 1 && ret.endsWith(u'/'))
594 ret.chop(1);
595 return QFileSystemEntry(ret);
596 }
597#if defined(Q_OS_DARWIN)
598 {
599 QCFString path = CFStringCreateWithFileSystemRepresentation(0,
601 if (!path)
602 return QFileSystemEntry();
603
604 QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, path, kCFURLPOSIXPathStyle,
606 if (!url)
607 return QFileSystemEntry();
608
609 QCFType<CFDataRef> bookmarkData = CFURLCreateBookmarkDataFromFile(0, url, NULL);
610 if (!bookmarkData)
611 return QFileSystemEntry();
612
613 QCFType<CFURLRef> resolvedUrl = CFURLCreateByResolvingBookmarkData(0,
614 bookmarkData,
615 (CFURLBookmarkResolutionOptions)(kCFBookmarkResolutionWithoutUIMask
616 | kCFBookmarkResolutionWithoutMountingMask), NULL, NULL, NULL, NULL);
617 if (!resolvedUrl)
618 return QFileSystemEntry();
619
620 QCFString cfstr(CFURLCopyFileSystemPath(resolvedUrl, kCFURLPOSIXPathStyle));
621 if (!cfstr)
622 return QFileSystemEntry();
623
624 return QFileSystemEntry(QString::fromCFString(cfstr));
625 }
626#endif
627 return QFileSystemEntry();
628}
629
630//static
639
640//static
642{
644
645#if !defined(Q_OS_DARWIN) && !defined(Q_OS_QNX) && !defined(Q_OS_ANDROID) && !defined(Q_OS_HAIKU) && _POSIX_VERSION < 200809L && !defined(Q_OS_VXWORKS)
646 // realpath(X,0) is not supported
647 Q_UNUSED(data);
648 return QFileSystemEntry(slowCanonicalized(absoluteName(entry).filePath()));
649#else
650# if defined(Q_OS_DARWIN) || defined(Q_OS_ANDROID) || _POSIX_VERSION < 200801L
651 // used to store the result of realpath in case where realpath cannot allocate itself
652 char stack_result[PATH_MAX + 1];
653#else
654 // enables unconditionally passing stack_result below
655 std::nullptr_t stack_result = nullptr;
656# endif
657 auto resolved_path_deleter = [&](char *ptr) {
658 // frees resolved_name if it was allocated by realpath
659# if defined(Q_OS_DARWIN) || defined(Q_OS_ANDROID) || _POSIX_VERSION < 200801L
660 // ptr is either null, or points to stack_result
661 Q_ASSERT(!ptr || ptr == stack_result);
662 return;
663#else
664 free(ptr);
665# endif
666 };
667 std::unique_ptr<char, decltype (resolved_path_deleter)> resolved_name {nullptr, resolved_path_deleter};
668# if defined(Q_OS_DARWIN) || defined(Q_OS_ANDROID)
669 // On some Android and macOS versions, realpath() will return a path even if
670 // it does not exist. To work around this, we check existence in advance.
673
674 if (!data.exists())
675 errno = ENOENT;
676 else
677 resolved_name.reset(realpath(entry.nativeFilePath().constData(), stack_result));
678# else
679 resolved_name.reset(realpath(entry.nativeFilePath().constData(), stack_result));
680# endif
681 if (resolved_name) {
686 } else if (errno == ENOENT || errno == ENOTDIR) { // file doesn't exist
689 return QFileSystemEntry();
690 }
691 return entry;
692#endif
693}
694
695//static
697{
699
700 if (entry.isAbsolute() && entry.isClean())
701 return entry;
702
703 QByteArray orig = entry.nativeFilePath();
705 if (orig.isEmpty() || !orig.startsWith('/')) {
707 result = cur.nativeFilePath();
708 }
709 if (!orig.isEmpty() && !(orig.size() == 1 && orig[0] == '.')) {
710 if (!result.isEmpty() && !result.endsWith('/'))
711 result.append('/');
712 result.append(orig);
713 }
714
715 if (result.size() == 1 && result[0] == '/')
717 const bool isDir = result.endsWith('/');
718
719 /* as long as QDir::cleanPath() operates on a QString we have to convert to a string here.
720 * ideally we never convert to a string since that loses information. Please fix after
721 * we get a QByteArray version of QDir::cleanPath()
722 */
724 QString stringVersion = QDir::cleanPath(resultingEntry.filePath());
725 if (isDir)
726 stringVersion.append(u'/');
727 return QFileSystemEntry(stringVersion);
728}
729
730//static
732{
734
735 QT_STATBUF statResult;
736 if (QT_STAT(entry.nativeFilePath().constData(), &statResult)) {
737 if (errno != ENOENT)
738 qErrnoWarning("stat() failed for '%s'", entry.nativeFilePath().constData());
739 return QByteArray();
740 }
741 QByteArray result = QByteArray::number(quint64(statResult.st_dev), 16);
742 result += ':';
743 result += QByteArray::number(quint64(statResult.st_ino));
744 return result;
745}
746
747//static
749{
750 QT_STATBUF statResult;
751 if (QT_FSTAT(fd, &statResult)) {
752 qErrnoWarning("fstat() failed for fd %d", fd);
753 return QByteArray();
754 }
755 QByteArray result = QByteArray::number(quint64(statResult.st_dev), 16);
756 result += ':';
757 result += QByteArray::number(quint64(statResult.st_ino));
758 return result;
759}
760
761//static
763{
764#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
765 long size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
766 if (size_max == -1)
767 size_max = 1024;
768 QVarLengthArray<char, 1024> buf(size_max);
769#endif
770
771#if !defined(Q_OS_INTEGRITY) && !defined(Q_OS_WASM)
772 struct passwd *pw = nullptr;
773#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) && !defined(Q_OS_VXWORKS)
774 struct passwd entry;
775 getpwuid_r(userId, &entry, buf.data(), buf.size(), &pw);
776#else
777 pw = getpwuid(userId);
778#endif
779 if (pw)
780 return QFile::decodeName(QByteArray(pw->pw_name));
781#else // Integrity || WASM
782 Q_UNUSED(userId);
783#endif
784 return QString();
785}
786
787//static
789{
790#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
791 long size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
792 if (size_max == -1)
793 size_max = 1024;
794 QVarLengthArray<char, 1024> buf(size_max);
795#endif
796
797#if !defined(Q_OS_INTEGRITY) && !defined(Q_OS_WASM)
798 struct group *gr = nullptr;
799#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))
800 size_max = sysconf(_SC_GETGR_R_SIZE_MAX);
801 if (size_max == -1)
802 size_max = 1024;
803 buf.resize(size_max);
804 struct group entry;
805 // Some large systems have more members than the POSIX max size
806 // Loop over by doubling the buffer size (upper limit 250k)
807 for (long size = size_max; size < 256000; size += size)
808 {
809 buf.resize(size);
810 // ERANGE indicates that the buffer was too small
811 if (!getgrgid_r(groupId, &entry, buf.data(), buf.size(), &gr)
812 || errno != ERANGE)
813 break;
814 }
815#else
816 gr = getgrgid(groupId);
817#endif
818 if (gr)
819 return QFile::decodeName(QByteArray(gr->gr_name));
820#else // Integrity || WASM || VxWorks
821 Q_UNUSED(groupId);
822#endif
823 return QString();
824}
825
826#if defined(Q_OS_DARWIN)
827//static
829{
830 QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, QCFString(entry.filePath()),
831 kCFURLPOSIXPathStyle, true);
832 if (QCFType<CFDictionaryRef> dict = CFBundleCopyInfoDictionaryForURL(url)) {
833 if (CFTypeRef name = (CFTypeRef)CFDictionaryGetValue(dict, kCFBundleNameKey)) {
834 if (CFGetTypeID(name) == CFStringGetTypeID())
835 return QString::fromCFString((CFStringRef)name);
836 }
837 }
838 return QString();
839}
840#endif
841
842//static
844 QFileSystemMetaData::MetaDataFlags what)
845{
846 Q_CHECK_FILE_NAME(entry, false);
847
848#if defined(Q_OS_DARWIN)
852 }
855#endif
856#ifdef UF_HIDDEN
858 // OS X >= 10.5: st_flags & UF_HIDDEN
860 }
861#endif // defined(Q_OS_DARWIN)
862
863 // if we're asking for any of the stat(2) flags, then we're getting them all
866
867 data.entryFlags &= ~what;
868
869 const QByteArray nativeFilePath = entry.nativeFilePath();
870 int entryErrno = 0; // innocent until proven otherwise
871
872 // first, we may try lstat(2). Possible outcomes:
873 // - success and is a symlink: filesystem entry exists, but we need stat(2)
874 // -> statResult = -1;
875 // - success and is not a symlink: filesystem entry exists and we're done
876 // -> statResult = 0
877 // - failure: really non-existent filesystem entry
878 // -> entryExists = false; statResult = 0;
879 // both stat(2) and lstat(2) may generate a number of different errno
880 // conditions, but of those, the only ones that could happen and the
881 // entry still exist are EACCES, EFAULT, ENOMEM and EOVERFLOW. If we get
882 // EACCES or ENOMEM, then we have no choice on how to proceed, so we may
883 // as well conclude it doesn't exist; EFAULT can't happen and EOVERFLOW
884 // shouldn't happen because we build in _LARGEFIE64.
885 union {
886 QT_STATBUF statBuffer;
887 struct statx statxBuffer;
888 };
889 int statResult = -1;
891 mode_t mode = 0;
892 statResult = qt_lstatx(nativeFilePath, &statxBuffer);
893 if (statResult == -ENOSYS) {
894 // use lstst(2)
895 statResult = QT_LSTAT(nativeFilePath, &statBuffer);
896 if (statResult == 0)
897 mode = statBuffer.st_mode;
898 } else if (statResult == 0) {
899 statResult = 1; // record it was statx(2) that succeeded
900 mode = statxBuffer.stx_mode;
901 }
902
903 if (statResult >= 0) {
904 if (S_ISLNK(mode)) {
905 // it's a symlink, we don't know if the file "exists"
907 statResult = -1; // force stat(2) below
908 } else {
909 // it's a reagular file and it exists
910 if (statResult)
911 data.fillFromStatxBuf(statxBuffer);
912 else
913 data.fillFromStatBuf(statBuffer);
917 }
918 } else {
919 // it doesn't exist
920 entryErrno = errno;
922 }
923
924 data.knownFlagsMask |= QFileSystemMetaData::LinkType;
925 }
926
927 // second, we try a regular stat(2)
928 if (statResult == -1 && (what & QFileSystemMetaData::PosixStatFlags)) {
929 if (entryErrno == 0 && statResult == -1) {
930 data.entryFlags &= ~QFileSystemMetaData::PosixStatFlags;
931 statResult = qt_statx(nativeFilePath, &statxBuffer);
932 if (statResult == -ENOSYS) {
933 // use stat(2)
934 statResult = QT_STAT(nativeFilePath, &statBuffer);
935 if (statResult == 0)
936 data.fillFromStatBuf(statBuffer);
937 } else if (statResult == 0) {
938 data.fillFromStatxBuf(statxBuffer);
939 }
940 }
941
942 if (statResult != 0) {
943 entryErrno = errno;
944 data.birthTime_ = 0;
945 data.metadataChangeTime_ = 0;
946 data.modificationTime_ = 0;
947 data.accessTime_ = 0;
948 data.size_ = 0;
949 data.userId_ = (uint) -2;
950 data.groupId_ = (uint) -2;
951 }
952
953 // reset the mask
956 }
957
958 // third, we try access(2)
960 // calculate user permissions
961 auto checkAccess = [&](QFileSystemMetaData::MetaDataFlag flag, int mode) {
962 if (entryErrno != 0 || (what & flag) == 0)
963 return;
964 if (QT_ACCESS(nativeFilePath, mode) == 0) {
965 // access ok (and file exists)
966 data.entryFlags |= flag | QFileSystemMetaData::ExistsAttribute;
967 } else if (errno != EACCES && errno != EROFS) {
968 entryErrno = errno;
969 }
970 };
971
975
976 // if we still haven't found out if the file exists, try F_OK
977 if (entryErrno == 0 && (data.entryFlags & QFileSystemMetaData::ExistsAttribute) == 0) {
978 if (QT_ACCESS(nativeFilePath, F_OK) == -1)
979 entryErrno = errno;
980 else
982 }
983
984 data.knownFlagsMask |= (what & QFileSystemMetaData::UserPermissions) |
986 }
987
988#if defined(Q_OS_DARWIN)
990 if (entryErrno == 0 && hasResourcePropertyFlag(data, entry, kCFURLIsAliasFileKey)) {
991 // kCFURLIsAliasFileKey includes symbolic links, so filter those out
992 if (!(data.entryFlags & QFileSystemMetaData::LinkType))
994 }
995 data.knownFlagsMask |= QFileSystemMetaData::AliasType;
996 }
997
999 if (entryErrno == 0 && isPackage(data, entry))
1001
1002 data.knownFlagsMask |= QFileSystemMetaData::BundleType;
1003 }
1004#endif
1005
1007 && !data.isHidden()) {
1008 QString fileName = entry.fileName();
1009 if (fileName.startsWith(u'.')
1010#if defined(Q_OS_DARWIN)
1011 || (entryErrno == 0 && hasResourcePropertyFlag(data, entry, kCFURLIsHiddenKey))
1012#endif
1013 )
1016 }
1017
1018 if (entryErrno != 0) {
1019 what &= ~QFileSystemMetaData::LinkType; // don't clear link: could be broken symlink
1020 data.clearFlags(what);
1021 return false;
1022 }
1023 return true;
1024}
1025
1026// static
1027bool QFileSystemEngine::cloneFile(int srcfd, int dstfd, const QFileSystemMetaData &knownData)
1028{
1029 QT_STATBUF statBuffer;
1030 if (knownData.hasFlags(QFileSystemMetaData::PosixStatFlags) &&
1031 knownData.isFile()) {
1032 statBuffer.st_mode = S_IFREG;
1033 } else if (knownData.hasFlags(QFileSystemMetaData::PosixStatFlags) &&
1034 knownData.isDirectory()) {
1035 return false; // fcopyfile(3) returns success on directories
1036 } else if (QT_FSTAT(srcfd, &statBuffer) == -1) {
1037 return false;
1038 } else if (!S_ISREG((statBuffer.st_mode))) {
1039 // not a regular file, let QFile do the copy
1040 return false;
1041 }
1042
1043#if defined(Q_OS_LINUX)
1044 // first, try FICLONE (only works on regular files and only on certain fs)
1045 if (::ioctl(dstfd, FICLONE, srcfd) == 0)
1046 return true;
1047
1048 // Second, try sendfile (it can send to some special types too).
1049 // sendfile(2) is limited in the kernel to 2G - 4k
1050 const size_t SendfileSize = 0x7ffff000;
1051
1052 ssize_t n = ::sendfile(dstfd, srcfd, nullptr, SendfileSize);
1053 if (n == -1) {
1054 // if we got an error here, give up and try at an upper layer
1055 return false;
1056 }
1057
1058 while (n) {
1059 n = ::sendfile(dstfd, srcfd, nullptr, SendfileSize);
1060 if (n == -1) {
1061 // uh oh, this is probably a real error (like ENOSPC), but we have
1062 // no way to notify QFile of partial success, so just erase any work
1063 // done (hopefully we won't get any errors, because there's nothing
1064 // we can do about them)
1065 n = ftruncate(dstfd, 0);
1066 n = lseek(srcfd, 0, SEEK_SET);
1067 n = lseek(dstfd, 0, SEEK_SET);
1068 return false;
1069 }
1070 }
1071
1072 return true;
1073#elif defined(Q_OS_DARWIN)
1074 // try fcopyfile
1075 return fcopyfile(srcfd, dstfd, nullptr, COPYFILE_DATA | COPYFILE_STAT) == 0;
1076#else
1077 Q_UNUSED(dstfd);
1078 return false;
1079#endif
1080}
1081
1082// Note: if \a shouldMkdirFirst is false, we assume the caller did try to mkdir
1083// before calling this function.
1084static bool createDirectoryWithParents(const QByteArray &nativeName, mode_t mode,
1085 bool shouldMkdirFirst = true)
1086{
1087 // helper function to check if a given path is a directory, since mkdir can
1088 // fail if the dir already exists (it may have been created by another
1089 // thread or another process)
1090 const auto isDir = [](const QByteArray &nativeName) {
1091 QT_STATBUF st;
1092 return QT_STAT(nativeName.constData(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR;
1093 };
1094
1095 if (shouldMkdirFirst && QT_MKDIR(nativeName, mode) == 0)
1096 return true;
1097 if (errno == EISDIR)
1098 return true;
1099 if (errno == EEXIST)
1100 return isDir(nativeName);
1101 if (errno != ENOENT)
1102 return false;
1103
1104 // mkdir failed because the parent dir doesn't exist, so try to create it
1105 qsizetype slash = nativeName.lastIndexOf('/');
1106 if (slash < 1)
1107 return false;
1108
1109 QByteArray parentNativeName = nativeName.left(slash);
1110 if (!createDirectoryWithParents(parentNativeName, mode))
1111 return false;
1112
1113 // try again
1114 if (QT_MKDIR(nativeName, mode) == 0)
1115 return true;
1116 return errno == EEXIST && isDir(nativeName);
1117}
1118
1119//static
1121 std::optional<QFile::Permissions> permissions)
1122{
1123 QByteArray dirName = entry.nativeFilePath();
1124 Q_CHECK_FILE_NAME(dirName, false);
1125
1126 // Darwin doesn't support trailing /'s, so remove for everyone
1127 while (dirName.size() > 1 && dirName.endsWith(u'/'))
1128 dirName.chop(1);
1129
1130 // try to mkdir this directory
1131 mode_t mode = permissions ? QtPrivate::toMode_t(*permissions) : 0777;
1132 if (QT_MKDIR(dirName, mode) == 0)
1133 return true;
1134 if (!createParents)
1135 return false;
1136
1137 return createDirectoryWithParents(dirName, mode, false);
1138}
1139
1140//static
1141bool QFileSystemEngine::removeDirectory(const QFileSystemEntry &entry, bool removeEmptyParents)
1142{
1143 Q_CHECK_FILE_NAME(entry, false);
1144
1145 if (removeEmptyParents) {
1146 QString dirName = QDir::cleanPath(entry.filePath());
1147 for (qsizetype oldslash = 0, slash=dirName.size(); slash > 0; oldslash = slash) {
1148 const QByteArray chunk = QFile::encodeName(dirName.left(slash));
1149 QT_STATBUF st;
1150 if (QT_STAT(chunk.constData(), &st) != -1) {
1151 if ((st.st_mode & S_IFMT) != S_IFDIR)
1152 return false;
1153 if (::rmdir(chunk.constData()) != 0)
1154 return oldslash != 0;
1155 } else {
1156 return false;
1157 }
1158 slash = dirName.lastIndexOf(QDir::separator(), oldslash-1);
1159 }
1160 return true;
1161 }
1162 return rmdir(QFile::encodeName(entry.filePath()).constData()) == 0;
1163}
1164
1165//static
1167{
1168 Q_CHECK_FILE_NAME(source, false);
1169 Q_CHECK_FILE_NAME(target, false);
1170
1171 if (::symlink(source.nativeFilePath().constData(), target.nativeFilePath().constData()) == 0)
1172 return true;
1174 return false;
1175}
1176
1177#ifdef Q_OS_DARWIN
1178// see qfilesystemengine_mac.mm
1179#elif defined(QT_BOOTSTRAPPED) || !defined(AT_FDCWD)
1180// bootstrapped tools don't need this, and we don't want QStorageInfo
1181//static
1184{
1186 return false;
1187}
1188#else
1189/*
1190 Implementing as per https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
1191*/
1192
1193namespace {
1194struct FreeDesktopTrashOperation
1195{
1196 /*
1197 "A trash directory contains two subdirectories, named info and files."
1198 */
1199 QString trashPath;
1200 int filesDirFd = -1;
1201 int infoDirFd = -1;
1202 qsizetype volumePrefixLength = 0;
1203
1204 // relative file paths to the filesDirFd and infoDirFd from above
1205 QByteArray tempTrashFileName;
1206 QByteArray infoFilePath;
1207
1208 int infoFileFd = -1; // if we've already opened it
1209 ~FreeDesktopTrashOperation()
1210 {
1211 close();
1212 }
1213
1214 constexpr bool isTrashDirOpen() const { return filesDirFd != -1 && infoDirFd != -1; }
1215
1216 void close()
1217 {
1218 int savedErrno = errno;
1219 if (infoFileFd != -1) {
1220 Q_ASSERT(infoDirFd != -1);
1221 Q_ASSERT(!infoFilePath.isEmpty());
1222 Q_ASSERT(!trashPath.isEmpty());
1223
1224 QT_CLOSE(infoFileFd);
1225 unlinkat(infoDirFd, infoFilePath, 0);
1226 infoFileFd = -1;
1227 }
1228 if (!tempTrashFileName.isEmpty()) {
1229 Q_ASSERT(filesDirFd != -1);
1230 unlinkat(filesDirFd, tempTrashFileName, 0);
1231 }
1232 if (filesDirFd >= 0)
1233 QT_CLOSE(filesDirFd);
1234 if (infoDirFd >= 0)
1235 QT_CLOSE(infoDirFd);
1236 filesDirFd = infoDirFd = -1;
1237 errno = savedErrno;
1238 }
1239
1240 bool tryCreateInfoFile(const QString &filePath, QSystemError &error)
1241 {
1242 QByteArray p = QFile::encodeName(filePath) + ".trashinfo";
1243 infoFileFd = qt_safe_openat(infoDirFd, p, QT_OPEN_RDWR | QT_OPEN_CREAT | QT_OPEN_EXCL, 0666);
1244 if (infoFileFd < 0) {
1246 return false;
1247 }
1248 infoFilePath = std::move(p);
1249 return true;
1250 }
1251
1252 void commit()
1253 {
1254 QT_CLOSE(infoFileFd);
1255 infoFileFd = -1;
1256 tempTrashFileName = {};
1257 }
1258
1259 // opens a directory and returns the file descriptor
1260 static int openDirFd(int dfd, const char *path, int mode = 0)
1261 {
1262 mode |= QT_OPEN_RDONLY | O_NOFOLLOW | O_DIRECTORY;
1263 return qt_safe_openat(dfd, path, mode);
1264 }
1265
1266 // opens an XDG Trash directory that is a subdirectory of dfd, creating if necessary
1267 static int openOrCreateDir(int dfd, const char *path)
1268 {
1269 // try to open it as a dir, first
1270 int fd = openDirFd(dfd, path);
1271 if (fd >= 0 || errno != ENOENT)
1272 return fd;
1273
1274 // try to mkdirat
1275 if (mkdirat(dfd, path, 0700) < 0)
1276 return -1;
1277
1278 // try to open it again
1279 return openDirFd(dfd, path);
1280 }
1281
1282 // opens or makes the XDG Trash hierarchy on parentfd (may be -1) called targetDir
1283 bool getTrashDir(int parentfd, QString targetDir, const QFileSystemEntry &source,
1285 {
1286 if (parentfd == AT_FDCWD)
1287 trashPath = targetDir;
1288 QByteArray nativePath = QFile::encodeName(targetDir);
1289
1290 // open the directory
1291 int trashfd = openOrCreateDir(parentfd, nativePath);
1292 if (trashfd < 0 && errno != ENOENT) {
1294 return false;
1295 }
1296
1297 // check if it is ours (even if we've just mkdirat'ed it)
1298 if (QT_STATBUF st; QT_FSTAT(trashfd, &st) < 0) {
1300 return false;
1301 } else if (st.st_uid != getuid()) {
1303 return false;
1304 }
1305
1306 filesDirFd = openOrCreateDir(trashfd, "files");
1307 if (filesDirFd >= 0) {
1308 // try to link our file-to-be-trashed here
1309 QTemporaryFileName tfn("XXXXXX"_L1);
1310 for (int i = 0; i < 16; ++i) {
1311 QByteArray attempt = tfn.generateNext();
1312 if (linkat(AT_FDCWD, source.nativeFilePath(), filesDirFd, attempt, 0) == 0) {
1313 tempTrashFileName = std::move(attempt);
1314 break;
1315 }
1316 if (errno != EEXIST)
1317 break;
1318 }
1319
1320 // man 2 link on Linux has:
1321 // EPERM The filesystem containing oldpath and newpath does not
1322 // support the creation of hard links.
1323 // EPERM oldpath is a directory.
1324 // EPERM oldpath is marked immutable or append‐only.
1325 // EMLINK The file referred to by oldpath already has the maximum
1326 // number of links to it.
1327 if (!tempTrashFileName.isEmpty() || errno == EPERM || errno == EMLINK)
1328 infoDirFd = openOrCreateDir(trashfd, "info");
1329 }
1331 if (infoDirFd < 0)
1332 close();
1333 QT_CLOSE(trashfd);
1334 return infoDirFd >= 0;
1335 }
1336
1337 bool openMountPointTrashLocation(const QFileSystemEntry &source,
1338 const QStorageInfo &sourceStorage, QSystemError &error)
1339 {
1340 /*
1341 Method 1:
1342 "An administrator can create an $topdir/.Trash directory. The permissions on this
1343 directories should permit all users who can trash files at all to write in it;
1344 and the “sticky bit” in the permissions must be set, if the file system supports
1345 it.
1346 When trashing a file from a non-home partition/device, an implementation
1347 (if it supports trashing in top directories) MUST check for the presence
1348 of $topdir/.Trash."
1349 */
1350
1351 const auto dotTrash = "/.Trash"_L1;
1352 const QString userID = QString::number(::getuid());
1353 QFileSystemEntry dotTrashDir(sourceStorage.rootPath() + dotTrash);
1354
1355 // we MUST check that the sticky bit is set, and that it is not a symlink
1356 int genericTrashFd = openDirFd(AT_FDCWD, dotTrashDir.nativeFilePath());
1357 QT_STATBUF st = {};
1358 if (genericTrashFd < 0 && errno != ENOENT && errno != EACCES) {
1359 // O_DIRECTORY + O_NOFOLLOW produces ENOTDIR on Linux
1360 if (QT_LSTAT(dotTrashDir.nativeFilePath(), &st) == 0 && S_ISLNK(st.st_mode)) {
1361 // we SHOULD report the failed check to the administrator
1362 qCritical("Warning: '%s' is a symlink to '%s'",
1363 dotTrashDir.nativeFilePath().constData(),
1364 qt_readlink(dotTrashDir.nativeFilePath()).constData());
1366 }
1367 } else if (genericTrashFd >= 0) {
1368 QT_FSTAT(genericTrashFd, &st);
1369 if ((st.st_mode & S_ISVTX) == 0) {
1370 // we SHOULD report the failed check to the administrator
1371 qCritical("Warning: '%s' doesn't have sticky bit set!",
1372 dotTrashDir.nativeFilePath().constData());
1374 } else {
1375 /*
1376 "If the directory exists and passes the checks, a subdirectory of the
1377 $topdir/.Trash directory is to be used as the user's trash directory
1378 for this partition/device. The name of this subdirectory is the numeric
1379 identifier of the current user ($topdir/.Trash/$uid).
1380 When trashing a file, if this directory does not exist for the current user,
1381 the implementation MUST immediately create it, without any warnings or
1382 delays for the user."
1383 */
1384 if (getTrashDir(genericTrashFd, userID, source, error)) {
1385 // recreate the resulting path
1386 trashPath = dotTrashDir.filePath() + u'/' + userID;
1387 }
1388 }
1389 QT_CLOSE(genericTrashFd);
1390 }
1391
1392 /*
1393 Method 2:
1394 "If an $topdir/.Trash directory is absent, an $topdir/.Trash-$uid directory is to be
1395 used as the user's trash directory for this device/partition. [...] When trashing a
1396 file, if an $topdir/.Trash-$uid directory does not exist, the implementation MUST
1397 immediately create it, without any warnings or delays for the user."
1398 */
1399 if (!isTrashDirOpen())
1400 getTrashDir(AT_FDCWD, sourceStorage.rootPath() + dotTrash + u'-' + userID, source, error);
1401
1402 if (isTrashDirOpen()) {
1403 volumePrefixLength = sourceStorage.rootPath().size();
1404 if (volumePrefixLength == 1)
1405 volumePrefixLength = 0; // isRoot
1406 else
1407 ++volumePrefixLength; // to include the slash
1408 }
1409 return isTrashDirOpen();
1410 }
1411
1412 bool openHomeTrashLocation(const QFileSystemEntry &source, QSystemError &error)
1413 {
1415 return getTrashDir(AT_FDCWD, topDir + "/Trash"_L1, source, error);
1416 }
1417
1418 bool findTrashFor(const QFileSystemEntry &source, QSystemError &error)
1419 {
1420 /*
1421 First, try the standard Trash in $XDG_DATA_DIRS:
1422 "Its name and location are $XDG_DATA_HOME/Trash"; $XDG_DATA_HOME is what
1423 QStandardPaths returns for GenericDataLocation. If that doesn't exist, then
1424 we are not running on a freedesktop.org-compliant environment, and give up.
1425 */
1426 if (openHomeTrashLocation(source, error))
1427 return true;
1428 if (error.errorCode != EXDEV)
1429 return false;
1430
1431 // didn't work, try to find the trash outside the home filesystem
1432 const QStorageInfo sourceStorage(source.filePath());
1433 if (!sourceStorage.isValid())
1434 return false;
1435 return openMountPointTrashLocation(source, sourceStorage, error);
1436 }
1437};
1438} // unnamed namespace
1439
1440//static
1442 QFileSystemEntry &newLocation, QSystemError &error)
1443{
1444 const QFileSystemEntry sourcePath = [&] {
1445 if (QString path = source.filePath(); path.size() > 1 && path.endsWith(u'/')) {
1446 path.chop(1);
1448 }
1449 return absoluteName(source);
1450 }();
1451 FreeDesktopTrashOperation op;
1452 if (!op.findTrashFor(sourcePath, error))
1453 return false;
1454
1455 /*
1456 "The $trash/files directory contains the files and directories that were trashed.
1457 The names of files in this directory are to be determined by the implementation;
1458 the only limitation is that they must be unique within the directory. Even if a
1459 file with the same name and location gets trashed many times, each subsequent
1460 trashing must not overwrite a previous copy."
1461
1462 We first try the unchanged base name, then try something different if it collides.
1463
1464 "The $trash/info directory contains an "information file" for every file and directory
1465 in $trash/files. This file MUST have exactly the same name as the file or directory in
1466 $trash/files, plus the extension ".trashinfo"
1467 [...]
1468 When trashing a file or directory, the implementation MUST create the corresponding
1469 file in $trash/info first. Moreover, it MUST try to do this in an atomic fashion,
1470 so that if two processes try to trash files with the same filename this will result
1471 in two different trash files. On Unix-like systems this is done by generating a
1472 filename, and then opening with O_EXCL. If that succeeds the creation was atomic
1473 (at least on the same machine), if it fails you need to pick another filename."
1474 */
1475 QString uniqueTrashedName = sourcePath.fileName();
1476 if (!op.tryCreateInfoFile(uniqueTrashedName, error) && error.errorCode == EEXIST) {
1477 // we'll use a counter, starting with the file's inode number to avoid
1478 // collisions
1480 if (QT_STATBUF st; Q_LIKELY(QT_STAT(source.nativeFilePath(), &st) == 0)) {
1481 counter = st.st_ino;
1482 } else {
1484 return false;
1485 }
1486
1487 QString uniqueTrashBase = std::move(uniqueTrashedName);
1488 for (;;) {
1489 uniqueTrashedName = QString::asprintf("%ls-%llu", qUtf16Printable(uniqueTrashBase),
1490 counter++);
1491 if (op.tryCreateInfoFile(uniqueTrashedName, error))
1492 break;
1493 if (error.errorCode != EEXIST)
1494 return false;
1495 };
1496 }
1497
1499 "[Trash Info]\n"
1500 "Path=" + QUrl::toPercentEncoding(source.filePath().mid(op.volumePrefixLength), "/") + "\n"
1501 "DeletionDate=" + QDateTime::currentDateTime().toString(Qt::ISODate).toUtf8()
1502 + "\n";
1503 if (QT_WRITE(op.infoFileFd, info.data(), info.size()) < 0) {
1505 return false;
1506 }
1507
1508 /*
1509 If we've already linked the file-to-be-trashed into the trash
1510 directory, we know it's in the same mountpoint and we won't get ENOSPC
1511 renaming the temporary file to the target name either.
1512 */
1513 bool renamed;
1514 if (op.tempTrashFileName.isEmpty()) {
1515 /*
1516 We did not get a link (we're trying to trash a directory or on a
1517 filesystem that doesn't support hardlinking), so rename straight
1518 from the original name. We might fail to rename if source and target
1519 are on different file systems.
1520 */
1521 renamed = renameat(AT_FDCWD, source.nativeFilePath(), op.filesDirFd,
1522 QFile::encodeName(uniqueTrashedName)) == 0;
1523 } else {
1524 renamed = renameat(op.filesDirFd, op.tempTrashFileName, op.filesDirFd,
1525 QFile::encodeName(uniqueTrashedName)) == 0;
1526 if (renamed)
1527 removeFile(source, error); // success, delete the original file
1528 }
1529 if (!renamed) {
1531 return false;
1532 }
1533
1534 op.commit();
1535 newLocation = QFileSystemEntry(op.trashPath + "/files/"_L1 + uniqueTrashedName);
1536 return true;
1537}
1538#endif // !Q_OS_DARWIN && !QT_BOOTSTRAPPED
1539
1540//static
1542{
1543#if defined(Q_OS_DARWIN)
1544 if (::clonefile(source.nativeFilePath().constData(),
1545 target.nativeFilePath().constData(), 0) == 0)
1546 return true;
1548 return false;
1549#else
1552 error = QSystemError(ENOSYS, QSystemError::StandardLibraryError); //Function not implemented
1553 return false;
1554#endif
1555}
1556
1557//static
1559{
1560 QFileSystemEntry::NativePath srcPath = source.nativeFilePath();
1561 QFileSystemEntry::NativePath tgtPath = target.nativeFilePath();
1562
1563 Q_CHECK_FILE_NAME(srcPath, false);
1564 Q_CHECK_FILE_NAME(tgtPath, false);
1565
1566#if defined(RENAME_NOREPLACE) && QT_CONFIG(renameat2)
1567 if (renameat2(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_NOREPLACE) == 0)
1568 return true;
1569
1570 // We can also get EINVAL for some non-local filesystems.
1571 if (errno != EINVAL) {
1573 return false;
1574 }
1575#endif
1576#if defined(Q_OS_DARWIN) && defined(RENAME_EXCL)
1577 if (renameatx_np(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_EXCL) == 0)
1578 return true;
1579 if (errno != ENOTSUP) {
1581 return false;
1582 }
1583#endif
1584
1585 if (SupportsHardlinking && ::link(srcPath, tgtPath) == 0) {
1586 if (::unlink(srcPath) == 0)
1587 return true;
1588
1589 // if we managed to link but can't unlink the source, it's likely
1590 // it's in a directory we don't have write access to; fail the
1591 // renaming instead
1592 int savedErrno = errno;
1593
1594 // this could fail too, but there's nothing we can do about it now
1595 ::unlink(tgtPath);
1596
1598 return false;
1599 } else if (!SupportsHardlinking) {
1600 // man 2 link on Linux has:
1601 // EPERM The filesystem containing oldpath and newpath does not
1602 // support the creation of hard links.
1603 errno = EPERM;
1604 }
1605
1606 switch (errno) {
1607 case EACCES:
1608 case EEXIST:
1609 case ENAMETOOLONG:
1610 case ENOENT:
1611 case ENOTDIR:
1612 case EROFS:
1613 case EXDEV:
1614 // accept the error from link(2) (especially EEXIST) and don't retry
1615 break;
1616
1617 default:
1618 // fall back to rename()
1619 // ### Race condition. If a file is moved in after this, it /will/ be
1620 // overwritten.
1621 if (::rename(srcPath, tgtPath) == 0)
1622 return true;
1623 }
1624
1626 return false;
1627}
1628
1629//static
1631{
1632 Q_CHECK_FILE_NAME(source, false);
1633 Q_CHECK_FILE_NAME(target, false);
1634
1635 if (::rename(source.nativeFilePath().constData(), target.nativeFilePath().constData()) == 0)
1636 return true;
1638 return false;
1639}
1640
1641//static
1643{
1644 Q_CHECK_FILE_NAME(entry, false);
1645 if (unlink(entry.nativeFilePath().constData()) == 0)
1646 return true;
1648 return false;
1649
1650}
1651
1652//static
1654{
1655 Q_CHECK_FILE_NAME(entry, false);
1656
1657 mode_t mode = QtPrivate::toMode_t(permissions);
1658 bool success = ::chmod(entry.nativeFilePath().constData(), mode) == 0;
1659 if (success && data) {
1660 data->entryFlags &= ~QFileSystemMetaData::Permissions;
1661 data->entryFlags |= QFileSystemMetaData::MetaDataFlag(uint(permissions.toInt()));
1662 data->knownFlagsMask |= QFileSystemMetaData::Permissions;
1663 }
1664 if (!success)
1666 return success;
1667}
1668
1669//static
1670bool QFileSystemEngine::setPermissions(int fd, QFile::Permissions permissions, QSystemError &error, QFileSystemMetaData *data)
1671{
1672 mode_t mode = QtPrivate::toMode_t(permissions);
1673
1674 bool success = ::fchmod(fd, mode) == 0;
1675 if (success && data) {
1676 data->entryFlags &= ~QFileSystemMetaData::Permissions;
1677 data->entryFlags |= QFileSystemMetaData::MetaDataFlag(uint(permissions.toInt()));
1678 data->knownFlagsMask |= QFileSystemMetaData::Permissions;
1679 }
1680 if (!success)
1682 return success;
1683}
1684
1685//static
1687{
1688 if (!newDate.isValid()
1691 return false;
1692 }
1693
1694#if QT_CONFIG(futimens)
1695 // UTIME_OMIT: leave file timestamp unchanged
1696 struct timespec ts[2] = {{0, UTIME_OMIT}, {0, UTIME_OMIT}};
1697
1699 const int idx = time == QFile::FileAccessTime ? 0 : 1;
1700 const std::chrono::milliseconds msecs{newDate.toMSecsSinceEpoch()};
1701 ts[idx] = durationToTimespec(msecs);
1702 }
1703
1704 if (futimens(fd, ts) == -1) {
1706 return false;
1707 }
1708
1709 return true;
1710#else
1711 Q_UNUSED(fd);
1713 return false;
1714#endif
1715}
1716
1718{
1719 QString home = QFile::decodeName(qgetenv("HOME"));
1720 if (home.isEmpty())
1721 home = rootPath();
1722 return QDir::cleanPath(home);
1723}
1724
1726{
1727 return u"/"_s;
1728}
1729
1731{
1732#ifdef QT_UNIX_TEMP_PATH_OVERRIDE
1733 return QT_UNIX_TEMP_PATH_OVERRIDE ""_L1;
1734#else
1735 QString temp = QFile::decodeName(qgetenv("TMPDIR"));
1736 if (temp.isEmpty()) {
1737 if (false) {
1738#if defined(Q_OS_DARWIN) && !defined(QT_BOOTSTRAPPED)
1739 } else if (NSString *nsPath = NSTemporaryDirectory()) {
1740 temp = QString::fromCFString((CFStringRef)nsPath);
1741#endif
1742 } else {
1743 temp = _PATH_TMP ""_L1;
1744 }
1745 }
1746 return QDir(QDir::cleanPath(temp)).canonicalPath();
1747#endif
1748}
1749
1751{
1752 int r;
1753 r = QT_CHDIR(path.nativeFilePath().constData());
1754 return r >= 0;
1755}
1756
1758{
1760#if defined(__GLIBC__) && !defined(PATH_MAX)
1761 char *currentName = ::get_current_dir_name();
1762 if (currentName) {
1764 ::free(currentName);
1765 }
1766#else
1767 char currentName[PATH_MAX+1];
1768 if (::getcwd(currentName, PATH_MAX)) {
1769#if defined(Q_OS_VXWORKS) && defined(VXWORKS_VXSIM)
1770 QByteArray dir(currentName);
1771 if (dir.indexOf(':') < dir.indexOf('/'))
1772 dir.remove(0, dir.indexOf(':')+1);
1773
1774 qstrncpy(currentName, dir.constData(), PATH_MAX);
1775#endif
1777 }
1778# if defined(QT_DEBUG)
1779 if (result.isEmpty())
1780 qWarning("QFileSystemEngine::currentPath: getcwd() failed");
1781# endif
1782#endif
1783 return result;
1784}
\inmodule QtCore
Definition qbytearray.h:57
bool endsWith(char c) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qbytearray.h:227
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
QByteArray left(qsizetype n) const &
Definition qbytearray.h:169
void chop(qsizetype n)
Removes n bytes from the end of the byte array.
bool startsWith(QByteArrayView bv) const
Definition qbytearray.h:223
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:107
static QByteArray number(int, int base=10)
Returns a byte-array representing the whole number n as text.
\inmodule QtCore\reentrant
Definition qdatetime.h:283
static QDateTime currentDateTime()
This is an overloaded member function, provided for convenience. It differs from the above function o...
\inmodule QtCore
Definition qdir.h:20
bool cdUp()
Changes directory by moving one directory up from the QDir's current directory.
Definition qdir.cpp:1042
QString path() const
Returns the path.
Definition qdir.cpp:653
QString canonicalPath() const
Returns the canonical path, i.e.
Definition qdir.cpp:692
static QChar separator()
Returns the native directory separator: "/" under Unix and "\\" under Windows.
Definition qdir.h:209
static QString cleanPath(const QString &path)
Returns path with directory separators normalized (that is, platform-native separators converted to "...
Definition qdir.cpp:2398
@ FileMetadataChangeTime
Definition qfiledevice.h:60
@ FileModificationTime
Definition qfiledevice.h:61
static QFileSystemEntry getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data)
static QFileSystemEntry canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data)
static QByteArray id(const QFileSystemEntry &entry)
static bool setCurrentPath(const QFileSystemEntry &entry)
static bool moveFileToTrash(const QFileSystemEntry &source, QFileSystemEntry &newLocation, QSystemError &error)
static QFileSystemEntry getRawLinkPath(const QFileSystemEntry &link, QFileSystemMetaData &data)
static bool copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
static bool renameOverwriteFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
static bool fillMetaData(const QFileSystemEntry &entry, QFileSystemMetaData &data, QFileSystemMetaData::MetaDataFlags what)
static bool createDirectory(const QFileSystemEntry &entry, bool createParents, std::optional< QFile::Permissions > permissions=std::nullopt)
static QString bundleName(const QFileSystemEntry &)
static bool createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
static bool setPermissions(const QFileSystemEntry &entry, QFile::Permissions permissions, QSystemError &error, QFileSystemMetaData *data=nullptr)
static bool renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
static QFileSystemEntry absoluteName(const QFileSystemEntry &entry)
static bool setFileTime(const QFileSystemEntry &entry, const QDateTime &newDate, QFile::FileTime whatTime, QSystemError &error)
static QString resolveUserName(const QFileSystemEntry &entry, QFileSystemMetaData &data)
static QString resolveGroupName(const QFileSystemEntry &entry, QFileSystemMetaData &data)
static bool removeFile(const QFileSystemEntry &entry, QSystemError &error)
static bool removeDirectory(const QFileSystemEntry &entry, bool removeEmptyParents)
static QFileSystemEntry currentPath()
Q_AUTOTEST_EXPORT NativePath nativeFilePath() const
Q_AUTOTEST_EXPORT QString filePath() const
Q_AUTOTEST_EXPORT QString fileName() const
void clearFlags(MetaDataFlags flags=AllMetaDataFlags)
static QByteArray encodeName(const QString &fileName)
Converts fileName to an 8-bit encoding that you can use in native APIs.
Definition qfile.h:158
static QString decodeName(const QByteArray &localFileName)
This does the reverse of QFile::encodeName() using localFileName.
Definition qfile.h:162
static QString writableLocation(StandardLocation type)
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
QString left(qsizetype n) const &
Definition qstring.h:363
qsizetype indexOf(QLatin1StringView s, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4517
qsizetype lastIndexOf(QChar c, Qt::CaseSensitivity cs=Qt::CaseSensitive) const noexcept
Definition qstring.h:296
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
const QChar * constData() const
Returns a pointer to the data stored in the QString.
Definition qstring.h:1246
qsizetype size() const noexcept
Returns the number of characters in this string.
Definition qstring.h:186
static QString number(int, int base=10)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:8084
QString & remove(qsizetype i, qsizetype len)
Removes n characters from the string, starting at the given position index, and returns a reference t...
Definition qstring.cpp:3466
static QString static QString asprintf(const char *format,...) Q_ATTRIBUTE_FORMAT_PRINTF(1
Definition qstring.cpp:7263
qsizetype length() const noexcept
Returns the number of characters in this string.
Definition qstring.h:191
static QByteArray toPercentEncoding(const QString &, const QByteArray &exclude=QByteArray(), const QByteArray &include=QByteArray())
Returns an encoded copy of input.
Definition qurl.cpp:3019
void qErrnoWarning(const char *msg,...)
Combined button and popup list for selecting options.
@ ISODate
Q_CORE_EXPORT char * qstrncpy(char *dst, const char *src, size_t len)
#define Q_LIKELY(x)
QByteArray qt_readlink(const char *path)
timespec durationToTimespec(std::chrono::nanoseconds timeout) noexcept
#define QT_CLOSE
#define QT_WRITE
DBusConnection const char DBusError * error
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define Q_CHECK_FILE_NAME(name, result)
static int qt_lstatx(const char *, struct statx *)
#define _PATH_TMP
static int qt_fstatx(int, struct statx *)
@ SupportsHardlinking
static bool createDirectoryWithParents(const QByteArray &nativeName, mode_t mode, bool shouldMkdirFirst=true)
static int qt_statx(const char *, struct statx *)
#define PATH_MAX
#define qCritical
Definition qlogging.h:167
#define qWarning
Definition qlogging.h:166
return ret
static ControlElement< T > * ptr(QWidget *widget)
GLenum mode
GLuint64 key
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLboolean r
[2]
GLint GLint GLint GLint GLsizei GLsizei GLsizei GLboolean commit
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint srcPath
GLenum type
GLboolean GLuint group
GLenum GLuint GLenum GLsizei const GLchar * buf
GLenum target
GLbitfield flags
GLuint64 GLenum GLint fd
GLuint name
GLint GLint GLint GLint GLint GLint GLint GLbitfield mask
GLfloat n
GLuint counter
GLsizei GLsizei GLchar * source
GLdouble s
[6]
Definition qopenglext.h:235
GLuint entry
GLdouble GLdouble t
Definition qopenglext.h:243
GLsizei const GLchar *const * path
GLuint64EXT * result
[6]
GLfloat GLfloat p
[1]
static QUrl resolvedUrl(const QUrl &url, const QQmlRefPointer< QQmlContextData > &context)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static QString canonicalPath(const QString &rootPath)
#define qUtf16Printable(string)
Definition qstring.h:1543
Q_CORE_EXPORT QByteArray qgetenv(const char *varName)
#define Q_UNUSED(x)
unsigned long ulong
Definition qtypes.h:35
quint64 qulonglong
Definition qtypes.h:64
unsigned long long quint64
Definition qtypes.h:61
ptrdiff_t qsizetype
Definition qtypes.h:165
unsigned int uint
Definition qtypes.h:34
long long qint64
Definition qtypes.h:60
#define Q_INT64_C(c)
Definition qtypes.h:57
QUrl url("example.com")
[constructor-url-reference]
QString dir
[11]
QItemEditorCreatorBase * creator
QHostInfo info
[0]