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