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