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