diff options
Diffstat (limited to 'src/corelib/io/qfilesystemengine_unix.cpp')
-rw-r--r-- | src/corelib/io/qfilesystemengine_unix.cpp | 780 |
1 files changed, 707 insertions, 73 deletions
diff --git a/src/corelib/io/qfilesystemengine_unix.cpp b/src/corelib/io/qfilesystemengine_unix.cpp index 6640e2b7e7..3bb20a2e00 100644 --- a/src/corelib/io/qfilesystemengine_unix.cpp +++ b/src/corelib/io/qfilesystemengine_unix.cpp @@ -43,6 +43,8 @@ #include "qfilesystemengine_p.h" #include "qfile.h" +#include <QtCore/qoperatingsystemversion.h> +#include <QtCore/private/qcore_unix_p.h> #include <QtCore/qvarlengtharray.h> #include <stdlib.h> // for realpath() @@ -52,7 +54,6 @@ #include <stdio.h> #include <errno.h> - #if defined(Q_OS_MAC) # include <QtCore/private/qcore_mac_p.h> # include <CoreFoundation/CFBundle.h> @@ -67,12 +68,31 @@ #endif #if defined(Q_OS_DARWIN) +# if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(101200, 100000, 100000, 30000) +# include <sys/clonefile.h> +# endif // We cannot include <Foundation/Foundation.h> (it's an Objective-C header), but // we need these declarations: Q_FORWARD_DECLARE_OBJC_CLASS(NSString); extern "C" NSString *NSTemporaryDirectory(); #endif +#if defined(Q_OS_LINUX) +# include <sys/syscall.h> +# include <linux/fs.h> + +# if !QT_CONFIG(renameat2) && defined(SYS_renameat2) +static int renameat2(int oldfd, const char *oldpath, int newfd, const char *newpath, unsigned flags) +{ return syscall(SYS_renameat2, oldfd, oldpath, newfd, newpath, flags); } +# endif + +# if !QT_CONFIG(statx) && defined(SYS_statx) && QT_HAS_INCLUDE(<linux/stat.h>) +# include <linux/stat.h> +static int statx(int dirfd, const char *pathname, int flag, unsigned mask, struct statx *statxbuf) +{ return syscall(SYS_statx, dirfd, pathname, flag, mask, statxbuf); } +# endif +#endif + QT_BEGIN_NAMESPACE #if defined(Q_OS_DARWIN) @@ -143,33 +163,447 @@ static bool isPackage(const QFileSystemMetaData &data, const QFileSystemEntry &e } #endif +namespace { +namespace GetFileTimes { +#if !QT_CONFIG(futimens) && (QT_CONFIG(futimes)) +template <typename T> +static inline typename QtPrivate::QEnableIf<(&T::st_atim, &T::st_mtim, true)>::Type get(const T *p, struct timeval *access, struct timeval *modification) +{ + access->tv_sec = p->st_atim.tv_sec; + access->tv_usec = p->st_atim.tv_nsec / 1000; + + modification->tv_sec = p->st_mtim.tv_sec; + modification->tv_usec = p->st_mtim.tv_nsec / 1000; +} + +template <typename T> +static inline typename QtPrivate::QEnableIf<(&T::st_atimespec, &T::st_mtimespec, true)>::Type get(const T *p, struct timeval *access, struct timeval *modification) +{ + access->tv_sec = p->st_atimespec.tv_sec; + access->tv_usec = p->st_atimespec.tv_nsec / 1000; + + modification->tv_sec = p->st_mtimespec.tv_sec; + modification->tv_usec = p->st_mtimespec.tv_nsec / 1000; +} + +template <typename T> +static inline typename QtPrivate::QEnableIf<(&T::st_atimensec, &T::st_mtimensec, true)>::Type get(const T *p, struct timeval *access, struct timeval *modification) +{ + access->tv_sec = p->st_atime; + access->tv_usec = p->st_atimensec / 1000; + + modification->tv_sec = p->st_mtime; + modification->tv_usec = p->st_mtimensec / 1000; +} +#endif + +qint64 timespecToMSecs(const timespec &spec) +{ + return (qint64(spec.tv_sec) * 1000) + (spec.tv_nsec / 1000000); +} + +// fallback set +Q_DECL_UNUSED qint64 atime(const QT_STATBUF &statBuffer, ulong) { return qint64(statBuffer.st_atime) * 1000; } +Q_DECL_UNUSED qint64 birthtime(const QT_STATBUF &, ulong) { return Q_INT64_C(0); } +Q_DECL_UNUSED qint64 ctime(const QT_STATBUF &statBuffer, ulong) { return qint64(statBuffer.st_ctime) * 1000; } +Q_DECL_UNUSED qint64 mtime(const QT_STATBUF &statBuffer, ulong) { return qint64(statBuffer.st_mtime) * 1000; } + +// Xtim, POSIX.1-2008 +template <typename T> +Q_DECL_UNUSED static typename std::enable_if<(&T::st_atim, true), qint64>::type +atime(const T &statBuffer, int) +{ return timespecToMSecs(statBuffer.st_atim); } + +template <typename T> +Q_DECL_UNUSED static typename std::enable_if<(&T::st_birthtim, true), qint64>::type +birthtime(const T &statBuffer, int) +{ return timespecToMSecs(statBuffer.st_birthtim); } + +template <typename T> +Q_DECL_UNUSED static typename std::enable_if<(&T::st_ctim, true), qint64>::type +ctime(const T &statBuffer, int) +{ return timespecToMSecs(statBuffer.st_ctim); } + +template <typename T> +Q_DECL_UNUSED static typename std::enable_if<(&T::st_mtim, true), qint64>::type +mtime(const T &statBuffer, int) +{ return timespecToMSecs(statBuffer.st_mtim); } + +#ifndef st_mtimespec +// Xtimespec +template <typename T> +Q_DECL_UNUSED static typename std::enable_if<(&T::st_atimespec, true), qint64>::type +atime(const T &statBuffer, int) +{ return timespecToMSecs(statBuffer.st_atimespec); } + +template <typename T> +Q_DECL_UNUSED static typename std::enable_if<(&T::st_birthtimespec, true), qint64>::type +birthtime(const T &statBuffer, int) +{ return timespecToMSecs(statBuffer.st_birthtimespec); } + +template <typename T> +Q_DECL_UNUSED static typename std::enable_if<(&T::st_ctimespec, true), qint64>::type +ctime(const T &statBuffer, int) +{ return timespecToMSecs(statBuffer.st_ctimespec); } + +template <typename T> +Q_DECL_UNUSED static typename std::enable_if<(&T::st_mtimespec, true), qint64>::type +mtime(const T &statBuffer, int) +{ return timespecToMSecs(statBuffer.st_mtimespec); } +#endif + +// Xtimensec +template <typename T> +Q_DECL_UNUSED static typename std::enable_if<(&T::st_atimensec, true), qint64>::type +atime(const T &statBuffer, int) +{ return statBuffer.st_atime * Q_INT64_C(1000) + statBuffer.st_atimensec / 1000000; } + +template <typename T> +Q_DECL_UNUSED static typename std::enable_if<(&T::st_birthtimensec, true), qint64>::type +birthtime(const T &statBuffer, int) +{ return statBuffer.st_birthtime * Q_INT64_C(1000) + statBuffer.st_birthtimensec / 1000000; } + +template <typename T> +Q_DECL_UNUSED static typename std::enable_if<(&T::st_ctimensec, true), qint64>::type +ctime(const T &statBuffer, int) +{ return statBuffer.st_ctime * Q_INT64_C(1000) + statBuffer.st_ctimensec / 1000000; } + +template <typename T> +Q_DECL_UNUSED static typename std::enable_if<(&T::st_mtimensec, true), qint64>::type +mtime(const T &statBuffer, int) +{ return statBuffer.st_mtime * Q_INT64_C(1000) + statBuffer.st_mtimensec / 1000000; } +} +} + +#ifdef STATX_BASIC_STATS +static int qt_real_statx(int fd, const char *pathname, int flags, struct statx *statxBuffer) +{ +#ifdef Q_ATOMIC_INT8_IS_SUPPORTED + static QBasicAtomicInteger<qint8> statxTested = Q_BASIC_ATOMIC_INITIALIZER(0); +#else + static QBasicAtomicInt statxTested = Q_BASIC_ATOMIC_INITIALIZER(0); +#endif + + if (statxTested.load() == -1) + return -ENOSYS; + + unsigned mask = STATX_BASIC_STATS | STATX_BTIME; + int ret = statx(fd, pathname, flags, mask, statxBuffer); + if (ret == -1 && errno == ENOSYS) { + statxTested.store(-1); + return -ENOSYS; + } + statxTested.store(1); + return ret == -1 ? -errno : 0; +} + +static int qt_statx(const char *pathname, struct statx *statxBuffer) +{ + return qt_real_statx(AT_FDCWD, pathname, 0, statxBuffer); +} + +static int qt_lstatx(const char *pathname, struct statx *statxBuffer) +{ + return qt_real_statx(AT_FDCWD, pathname, AT_SYMLINK_NOFOLLOW, statxBuffer); +} + +static int qt_fstatx(int fd, struct statx *statxBuffer) +{ + return qt_real_statx(fd, "", AT_EMPTY_PATH, statxBuffer); +} + +inline void QFileSystemMetaData::fillFromStatxBuf(const struct statx &statxBuffer) +{ + // Permissions + if (statxBuffer.stx_mode & S_IRUSR) + entryFlags |= QFileSystemMetaData::OwnerReadPermission; + if (statxBuffer.stx_mode & S_IWUSR) + entryFlags |= QFileSystemMetaData::OwnerWritePermission; + if (statxBuffer.stx_mode & S_IXUSR) + entryFlags |= QFileSystemMetaData::OwnerExecutePermission; + + if (statxBuffer.stx_mode & S_IRGRP) + entryFlags |= QFileSystemMetaData::GroupReadPermission; + if (statxBuffer.stx_mode & S_IWGRP) + entryFlags |= QFileSystemMetaData::GroupWritePermission; + if (statxBuffer.stx_mode & S_IXGRP) + entryFlags |= QFileSystemMetaData::GroupExecutePermission; + + if (statxBuffer.stx_mode & S_IROTH) + entryFlags |= QFileSystemMetaData::OtherReadPermission; + if (statxBuffer.stx_mode & S_IWOTH) + entryFlags |= QFileSystemMetaData::OtherWritePermission; + if (statxBuffer.stx_mode & S_IXOTH) + entryFlags |= QFileSystemMetaData::OtherExecutePermission; + + // Type + if (S_ISLNK(statxBuffer.stx_mode)) + entryFlags |= QFileSystemMetaData::LinkType; + if ((statxBuffer.stx_mode & S_IFMT) == S_IFREG) + entryFlags |= QFileSystemMetaData::FileType; + else if ((statxBuffer.stx_mode & S_IFMT) == S_IFDIR) + entryFlags |= QFileSystemMetaData::DirectoryType; + else if ((statxBuffer.stx_mode & S_IFMT) != S_IFBLK) + entryFlags |= QFileSystemMetaData::SequentialType; + + // Attributes + entryFlags |= QFileSystemMetaData::ExistsAttribute; // inode exists + if (statxBuffer.stx_nlink == 0) + entryFlags |= QFileSystemMetaData::WasDeletedAttribute; + size_ = qint64(statxBuffer.stx_size); + + // Times + auto toMSecs = [](struct statx_timestamp ts) + { + return qint64(ts.tv_sec) * 1000 + (ts.tv_nsec / 1000000); + }; + accessTime_ = toMSecs(statxBuffer.stx_atime); + metadataChangeTime_ = toMSecs(statxBuffer.stx_ctime); + modificationTime_ = toMSecs(statxBuffer.stx_mtime); + if (statxBuffer.stx_mask & STATX_BTIME) + birthTime_ = toMSecs(statxBuffer.stx_btime); + else + birthTime_ = 0; + + userId_ = statxBuffer.stx_uid; + groupId_ = statxBuffer.stx_gid; +} +#else +struct statx { mode_t stx_mode; }; +static int qt_statx(const char *, struct statx *) +{ return -ENOSYS; } + +static int qt_lstatx(const char *, struct statx *) +{ return -ENOSYS; } + +static int qt_fstatx(int, struct statx *) +{ return -ENOSYS; } + +inline void QFileSystemMetaData::fillFromStatxBuf(const struct statx &) +{ } +#endif + //static -QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data) +bool QFileSystemEngine::fillMetaData(int fd, QFileSystemMetaData &data) { -#if defined(__GLIBC__) && !defined(PATH_MAX) -#define PATH_CHUNK_SIZE 256 - char *s = 0; - int len = -1; - int size = PATH_CHUNK_SIZE; - - while (1) { - s = (char *) ::realloc(s, size); - Q_CHECK_PTR(s); - len = ::readlink(link.nativeFilePath().constData(), s, size); - if (len < 0) { - ::free(s); - break; - } - if (len < size) { - break; + data.entryFlags &= ~QFileSystemMetaData::PosixStatFlags; + data.knownFlagsMask |= QFileSystemMetaData::PosixStatFlags; + + union { + struct statx statxBuffer; + QT_STATBUF statBuffer; + }; + + int ret = qt_fstatx(fd, &statxBuffer); + if (ret != -ENOSYS) { + data.fillFromStatxBuf(statxBuffer); + return ret == 0; + } + + if (QT_FSTAT(fd, &statBuffer) == 0) { + data.fillFromStatBuf(statBuffer); + return true; + } + + return false; +} + +#if defined(_DEXTRA_FIRST) +static void fillStat64fromStat32(struct stat64 *statBuf64, const struct stat &statBuf32) +{ + statBuf64->st_mode = statBuf32.st_mode; + statBuf64->st_size = statBuf32.st_size; +#if _POSIX_VERSION >= 200809L + statBuf64->st_ctim = statBuf32.st_ctim; + statBuf64->st_mtim = statBuf32.st_mtim; + statBuf64->st_atim = statBuf32.st_atim; +#else + statBuf64->st_ctime = statBuf32.st_ctime; + statBuf64->st_mtime = statBuf32.st_mtime; + statBuf64->st_atime = statBuf32.st_atime; +#endif + statBuf64->st_uid = statBuf32.st_uid; + statBuf64->st_gid = statBuf32.st_gid; +} +#endif + +void QFileSystemMetaData::fillFromStatBuf(const QT_STATBUF &statBuffer) +{ + // Permissions + if (statBuffer.st_mode & S_IRUSR) + entryFlags |= QFileSystemMetaData::OwnerReadPermission; + if (statBuffer.st_mode & S_IWUSR) + entryFlags |= QFileSystemMetaData::OwnerWritePermission; + if (statBuffer.st_mode & S_IXUSR) + entryFlags |= QFileSystemMetaData::OwnerExecutePermission; + + if (statBuffer.st_mode & S_IRGRP) + entryFlags |= QFileSystemMetaData::GroupReadPermission; + if (statBuffer.st_mode & S_IWGRP) + entryFlags |= QFileSystemMetaData::GroupWritePermission; + if (statBuffer.st_mode & S_IXGRP) + entryFlags |= QFileSystemMetaData::GroupExecutePermission; + + if (statBuffer.st_mode & S_IROTH) + entryFlags |= QFileSystemMetaData::OtherReadPermission; + if (statBuffer.st_mode & S_IWOTH) + entryFlags |= QFileSystemMetaData::OtherWritePermission; + if (statBuffer.st_mode & S_IXOTH) + entryFlags |= QFileSystemMetaData::OtherExecutePermission; + + // Type + if ((statBuffer.st_mode & S_IFMT) == S_IFREG) + entryFlags |= QFileSystemMetaData::FileType; + else if ((statBuffer.st_mode & S_IFMT) == S_IFDIR) + entryFlags |= QFileSystemMetaData::DirectoryType; + else if ((statBuffer.st_mode & S_IFMT) != S_IFBLK) + entryFlags |= QFileSystemMetaData::SequentialType; + + // Attributes + entryFlags |= QFileSystemMetaData::ExistsAttribute; // inode exists + if (statBuffer.st_nlink == 0) + entryFlags |= QFileSystemMetaData::WasDeletedAttribute; + size_ = statBuffer.st_size; +#ifdef UF_HIDDEN + if (statBuffer.st_flags & UF_HIDDEN) { + entryFlags |= QFileSystemMetaData::HiddenAttribute; + knownFlagsMask |= QFileSystemMetaData::HiddenAttribute; + } +#endif + + // Times + accessTime_ = GetFileTimes::atime(statBuffer, 0); + birthTime_ = GetFileTimes::birthtime(statBuffer, 0); + metadataChangeTime_ = GetFileTimes::ctime(statBuffer, 0); + modificationTime_ = GetFileTimes::mtime(statBuffer, 0); + + userId_ = statBuffer.st_uid; + groupId_ = statBuffer.st_gid; +} + +void QFileSystemMetaData::fillFromDirEnt(const QT_DIRENT &entry) +{ +#if defined(_DEXTRA_FIRST) + knownFlagsMask = 0; + entryFlags = 0; + for (dirent_extra *extra = _DEXTRA_FIRST(&entry); _DEXTRA_VALID(extra, &entry); + extra = _DEXTRA_NEXT(extra)) { + if (extra->d_type == _DTYPE_STAT || extra->d_type == _DTYPE_LSTAT) { + + const struct dirent_extra_stat * const extra_stat = + reinterpret_cast<struct dirent_extra_stat *>(extra); + + // Remember whether this was a link or not, this saves an lstat() call later. + if (extra->d_type == _DTYPE_LSTAT) { + knownFlagsMask |= QFileSystemMetaData::LinkType; + if (S_ISLNK(extra_stat->d_stat.st_mode)) + entryFlags |= QFileSystemMetaData::LinkType; + } + + // For symlinks, the extra type _DTYPE_LSTAT doesn't work for filling out the meta data, + // as we need the stat() information there, not the lstat() information. + // In this case, don't use the extra information. + // Unfortunately, readdir() never seems to return extra info of type _DTYPE_STAT, so for + // symlinks, we always incur the cost of an extra stat() call later. + if (S_ISLNK(extra_stat->d_stat.st_mode) && extra->d_type == _DTYPE_LSTAT) + continue; + +#if defined(QT_USE_XOPEN_LFS_EXTENSIONS) && defined(QT_LARGEFILE_SUPPORT) + // Even with large file support, d_stat is always of type struct stat, not struct stat64, + // so it needs to be converted + struct stat64 statBuf; + fillStat64fromStat32(&statBuf, extra_stat->d_stat); + fillFromStatBuf(statBuf); +#else + fillFromStatBuf(extra_stat->d_stat); +#endif + knownFlagsMask |= QFileSystemMetaData::PosixStatFlags; + if (!S_ISLNK(extra_stat->d_stat.st_mode)) { + knownFlagsMask |= QFileSystemMetaData::ExistsAttribute; + entryFlags |= QFileSystemMetaData::ExistsAttribute; + } } - size *= 2; + } +#elif defined(_DIRENT_HAVE_D_TYPE) || defined(Q_OS_BSD4) + // BSD4 includes OS X and iOS + + // ### This will clear all entry flags and knownFlagsMask + switch (entry.d_type) + { + case DT_DIR: + knownFlagsMask = QFileSystemMetaData::LinkType + | QFileSystemMetaData::FileType + | QFileSystemMetaData::DirectoryType + | QFileSystemMetaData::SequentialType + | QFileSystemMetaData::ExistsAttribute; + + entryFlags = QFileSystemMetaData::DirectoryType + | QFileSystemMetaData::ExistsAttribute; + + break; + + case DT_BLK: + knownFlagsMask = QFileSystemMetaData::LinkType + | QFileSystemMetaData::FileType + | QFileSystemMetaData::DirectoryType + | QFileSystemMetaData::BundleType + | QFileSystemMetaData::AliasType + | QFileSystemMetaData::SequentialType + | QFileSystemMetaData::ExistsAttribute; + + entryFlags = QFileSystemMetaData::ExistsAttribute; + + break; + + case DT_CHR: + case DT_FIFO: + case DT_SOCK: + // ### System attribute + knownFlagsMask = QFileSystemMetaData::LinkType + | QFileSystemMetaData::FileType + | QFileSystemMetaData::DirectoryType + | QFileSystemMetaData::BundleType + | QFileSystemMetaData::AliasType + | QFileSystemMetaData::SequentialType + | QFileSystemMetaData::ExistsAttribute; + + entryFlags = QFileSystemMetaData::SequentialType + | QFileSystemMetaData::ExistsAttribute; + + break; + + case DT_LNK: + knownFlagsMask = QFileSystemMetaData::LinkType; + entryFlags = QFileSystemMetaData::LinkType; + break; + + case DT_REG: + knownFlagsMask = QFileSystemMetaData::LinkType + | QFileSystemMetaData::FileType + | QFileSystemMetaData::DirectoryType + | QFileSystemMetaData::BundleType + | QFileSystemMetaData::SequentialType + | QFileSystemMetaData::ExistsAttribute; + + entryFlags = QFileSystemMetaData::FileType + | QFileSystemMetaData::ExistsAttribute; + + break; + + case DT_UNKNOWN: + default: + clear(); } #else - char s[PATH_MAX+1]; - int len = readlink(link.nativeFilePath().constData(), s, PATH_MAX); + Q_UNUSED(entry) #endif - if (len > 0) { +} + +//static +QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data) +{ + QByteArray s = qt_readlink(link.nativeFilePath().constData()); + if (s.length() > 0) { QString ret; if (!data.hasFlags(QFileSystemMetaData::DirectoryType)) fillMetaData(link, data, QFileSystemMetaData::DirectoryType); @@ -180,11 +614,7 @@ QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, if (!ret.isEmpty() && !ret.endsWith(QLatin1Char('/'))) ret += QLatin1Char('/'); } - s[len] = '\0'; - ret += QFile::decodeName(QByteArray(s)); -#if defined(__GLIBC__) && !defined(PATH_MAX) - ::free(s); -#endif + ret += QFile::decodeName(s); if (!ret.startsWith(QLatin1Char('/'))) { const QString linkFilePath = link.filePath(); @@ -448,51 +878,97 @@ bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemM if (!data.hasFlags(QFileSystemMetaData::DirectoryType)) what |= QFileSystemMetaData::DirectoryType; } +#endif +#ifdef UF_HIDDEN if (what & QFileSystemMetaData::HiddenAttribute) { // OS X >= 10.5: st_flags & UF_HIDDEN what |= QFileSystemMetaData::PosixStatFlags; } #endif // defined(Q_OS_DARWIN) + // if we're asking for any of the stat(2) flags, then we're getting them all if (what & QFileSystemMetaData::PosixStatFlags) what |= QFileSystemMetaData::PosixStatFlags; - if (what & QFileSystemMetaData::ExistsAttribute) { - // FIXME: Would other queries being performed provide this bit? - what |= QFileSystemMetaData::PosixStatFlags; - } - data.entryFlags &= ~what; const QByteArray nativeFilePath = entry.nativeFilePath(); bool entryExists = true; // innocent until proven otherwise - QT_STATBUF statBuffer; - bool statBufferValid = false; + // first, we may try lstat(2). Possible outcomes: + // - success and is a symlink: filesystem entry exists, but we need stat(2) + // -> statResult = -1; + // - success and is not a symlink: filesystem entry exists and we're done + // -> statResult = 0 + // - failure: really non-existent filesystem entry + // -> entryExists = false; statResult = 0; + // both stat(2) and lstat(2) may generate a number of different errno + // conditions, but of those, the only ones that could happen and the + // entry still exist are EACCES, EFAULT, ENOMEM and EOVERFLOW. If we get + // EACCES or ENOMEM, then we have no choice on how to proceed, so we may + // as well conclude it doesn't exist; EFAULT can't happen and EOVERFLOW + // shouldn't happen because we build in _LARGEFIE64. + union { + QT_STATBUF statBuffer; + struct statx statxBuffer; + }; + int statResult = -1; if (what & QFileSystemMetaData::LinkType) { - if (QT_LSTAT(nativeFilePath, &statBuffer) == 0) { - if (S_ISLNK(statBuffer.st_mode)) { + mode_t mode = 0; + statResult = qt_lstatx(nativeFilePath, &statxBuffer); + if (statResult == -ENOSYS) { + // use lstst(2) + statResult = QT_LSTAT(nativeFilePath, &statBuffer); + if (statResult == 0) + mode = statBuffer.st_mode; + } else if (statResult == 0) { + statResult = 1; // record it was statx(2) that succeeded + mode = statxBuffer.stx_mode; + } + + if (statResult >= 0) { + if (S_ISLNK(mode)) { + // it's a symlink, we don't know if the file "exists" data.entryFlags |= QFileSystemMetaData::LinkType; + statResult = -1; // force stat(2) below } else { - statBufferValid = true; - data.entryFlags &= ~QFileSystemMetaData::PosixStatFlags; + // it's a reagular file and it exists + if (statResult) + data.fillFromStatxBuf(statxBuffer); + else + data.fillFromStatBuf(statBuffer); + data.knownFlagsMask |= QFileSystemMetaData::PosixStatFlags + | QFileSystemMetaData::ExistsAttribute; + data.entryFlags |= QFileSystemMetaData::ExistsAttribute; } } else { + // it doesn't exist entryExists = false; + data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute; } data.knownFlagsMask |= QFileSystemMetaData::LinkType; } - if (statBufferValid || (what & QFileSystemMetaData::PosixStatFlags)) { - if (entryExists && !statBufferValid) - statBufferValid = (QT_STAT(nativeFilePath, &statBuffer) == 0); + // second, we try a regular stat(2) + if (statResult != 0 && (what & QFileSystemMetaData::PosixStatFlags)) { + if (entryExists && statResult == -1) { + data.entryFlags &= ~QFileSystemMetaData::PosixStatFlags; + statResult = qt_statx(nativeFilePath, &statxBuffer); + if (statResult == -ENOSYS) { + // use stat(2) + statResult = QT_STAT(nativeFilePath, &statBuffer); + if (statResult == 0) + data.fillFromStatBuf(statBuffer); + } else if (statResult == 0) { + data.fillFromStatxBuf(statxBuffer); + } + } - if (statBufferValid) - data.fillFromStatBuf(statBuffer); - else { + if (statResult != 0) { entryExists = false; - data.creationTime_ = 0; + data.birthTime_ = 0; + data.metadataChangeTime_ = 0; data.modificationTime_ = 0; data.accessTime_ = 0; data.size_ = 0; @@ -505,34 +981,49 @@ bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemM | QFileSystemMetaData::ExistsAttribute; } + // third, we try access(2) + if (what & (QFileSystemMetaData::UserPermissions | QFileSystemMetaData::ExistsAttribute)) { + // calculate user permissions + auto checkAccess = [&](QFileSystemMetaData::MetaDataFlag flag, int mode) { + if (!entryExists || (what & flag) == 0) + return; + if (QT_ACCESS(nativeFilePath, mode) == 0) { + // access ok (and file exists) + data.entryFlags |= flag | QFileSystemMetaData::ExistsAttribute; + } else if (errno != EACCES && errno != EROFS) { + entryExists = false; + } + }; + + checkAccess(QFileSystemMetaData::UserReadPermission, R_OK); + checkAccess(QFileSystemMetaData::UserWritePermission, W_OK); + checkAccess(QFileSystemMetaData::UserExecutePermission, X_OK); + + // if we still haven't found out if the file exists, try F_OK + if (entryExists && (data.entryFlags & QFileSystemMetaData::ExistsAttribute) == 0) { + entryExists = QT_ACCESS(nativeFilePath, F_OK) == 0; + if (entryExists) + data.entryFlags |= QFileSystemMetaData::ExistsAttribute; + } + + data.knownFlagsMask |= (what & QFileSystemMetaData::UserPermissions) | + QFileSystemMetaData::ExistsAttribute; + } + #if defined(Q_OS_DARWIN) - if (what & QFileSystemMetaData::AliasType) - { + if (what & QFileSystemMetaData::AliasType) { if (entryExists && hasResourcePropertyFlag(data, entry, kCFURLIsAliasFileKey)) data.entryFlags |= QFileSystemMetaData::AliasType; data.knownFlagsMask |= QFileSystemMetaData::AliasType; } -#endif - if (what & QFileSystemMetaData::UserPermissions) { - // calculate user permissions + if (what & QFileSystemMetaData::BundleType) { + if (entryExists && isPackage(data, entry)) + data.entryFlags |= QFileSystemMetaData::BundleType; - if (entryExists) { - if (what & QFileSystemMetaData::UserReadPermission) { - if (QT_ACCESS(nativeFilePath, R_OK) == 0) - data.entryFlags |= QFileSystemMetaData::UserReadPermission; - } - if (what & QFileSystemMetaData::UserWritePermission) { - if (QT_ACCESS(nativeFilePath, W_OK) == 0) - data.entryFlags |= QFileSystemMetaData::UserWritePermission; - } - if (what & QFileSystemMetaData::UserExecutePermission) { - if (QT_ACCESS(nativeFilePath, X_OK) == 0) - data.entryFlags |= QFileSystemMetaData::UserExecutePermission; - } - } - data.knownFlagsMask |= (what & QFileSystemMetaData::UserPermissions); + data.knownFlagsMask |= QFileSystemMetaData::BundleType; } +#endif if (what & QFileSystemMetaData::HiddenAttribute && !data.isHidden()) { @@ -546,15 +1037,8 @@ bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemM data.knownFlagsMask |= QFileSystemMetaData::HiddenAttribute; } -#if defined(Q_OS_DARWIN) - if (what & QFileSystemMetaData::BundleType) { - if (entryExists && isPackage(data, entry)) - data.entryFlags |= QFileSystemMetaData::BundleType; - - data.knownFlagsMask |= QFileSystemMetaData::BundleType; - } -#endif if (!entryExists) { + what &= ~QFileSystemMetaData::LinkType; // don't clear link: could be broken symlink data.clearFlags(what); return false; } @@ -649,8 +1133,22 @@ bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSy //static bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) { +#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(101200, 100000, 100000, 30000) + const auto current = QOperatingSystemVersion::current(); + if (current >= QOperatingSystemVersion::MacOSSierra || + current >= QOperatingSystemVersion(QOperatingSystemVersion::IOS, 10) || + current >= QOperatingSystemVersion(QOperatingSystemVersion::TvOS, 10) || + current >= QOperatingSystemVersion(QOperatingSystemVersion::WatchOS, 3)) { + if (::clonefile(source.nativeFilePath().constData(), + target.nativeFilePath().constData(), 0) == 0) + return true; + error = QSystemError(errno, QSystemError::StandardLibraryError); + return false; + } +#else Q_UNUSED(source); Q_UNUSED(target); +#endif error = QSystemError(ENOSYS, QSystemError::StandardLibraryError); //Function not implemented return false; } @@ -658,6 +1156,77 @@ bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSyst //static bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) { + QFileSystemEntry::NativePath srcPath = source.nativeFilePath(); + QFileSystemEntry::NativePath tgtPath = target.nativeFilePath(); + +#if defined(RENAME_NOREPLACE) && (QT_CONFIG(renameat2) || defined(SYS_renameat2)) + if (renameat2(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_NOREPLACE) == 0) + return true; + + // If we're using syscall(), check for ENOSYS; + // if renameat2 came from libc, we don't accept ENOSYS. + if (QT_CONFIG(renameat2) || errno != ENOSYS) { + error = QSystemError(errno, QSystemError::StandardLibraryError); + return false; + } +#endif +#if defined(Q_OS_DARWIN) && defined(RENAME_EXCL) + const auto current = QOperatingSystemVersion::current(); + if (current >= QOperatingSystemVersion::MacOSSierra || + current >= QOperatingSystemVersion(QOperatingSystemVersion::IOS, 10) || + current >= QOperatingSystemVersion(QOperatingSystemVersion::TvOS, 10) || + current >= QOperatingSystemVersion(QOperatingSystemVersion::WatchOS, 3)) { + if (renameatx_np(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_EXCL) == 0) + return true; + if (errno != ENOTSUP) { + error = QSystemError(errno, QSystemError::StandardLibraryError); + return false; + } + } +#endif + + if (::link(srcPath, tgtPath) == 0) { + if (::unlink(srcPath) == 0) + return true; + + // if we managed to link but can't unlink the source, it's likely + // it's in a directory we don't have write access to; fail the + // renaming instead + int savedErrno = errno; + + // this could fail too, but there's nothing we can do about it now + ::unlink(tgtPath); + + error = QSystemError(savedErrno, QSystemError::StandardLibraryError); + return false; + } + + switch (errno) { + case EACCES: + case EEXIST: + case ENAMETOOLONG: + case ENOENT: + case ENOTDIR: + case EROFS: + case EXDEV: + // accept the error from link(2) (especially EEXIST) and don't retry + break; + + default: + // fall back to rename() + // ### Race condition. If a file is moved in after this, it /will/ be + // overwritten. + if (::rename(srcPath, tgtPath) == 0) + return true; + } + + error = QSystemError(errno, QSystemError::StandardLibraryError); + return false; +} + +//static +bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) +{ if (::rename(source.nativeFilePath().constData(), target.nativeFilePath().constData()) == 0) return true; error = QSystemError(errno, QSystemError::StandardLibraryError); @@ -730,6 +1299,71 @@ bool QFileSystemEngine::setPermissions(int fd, QFile::Permissions permissions, Q return success; } +//static +bool QFileSystemEngine::setFileTime(int fd, const QDateTime &newDate, QAbstractFileEngine::FileTime time, QSystemError &error) +{ + if (!newDate.isValid() || time == QAbstractFileEngine::BirthTime || + time == QAbstractFileEngine::MetadataChangeTime) { + error = QSystemError(EINVAL, QSystemError::StandardLibraryError); + return false; + } + +#if QT_CONFIG(futimens) + struct timespec ts[2]; + + ts[0].tv_sec = ts[1].tv_sec = 0; + ts[0].tv_nsec = ts[1].tv_nsec = UTIME_OMIT; + + const qint64 msecs = newDate.toMSecsSinceEpoch(); + + if (time == QAbstractFileEngine::AccessTime) { + ts[0].tv_sec = msecs / 1000; + ts[0].tv_nsec = (msecs % 1000) * 1000000; + } else if (time == QAbstractFileEngine::ModificationTime) { + ts[1].tv_sec = msecs / 1000; + ts[1].tv_nsec = (msecs % 1000) * 1000000; + } + + if (futimens(fd, ts) == -1) { + error = QSystemError(errno, QSystemError::StandardLibraryError); + return false; + } + + return true; +#elif QT_CONFIG(futimes) + struct timeval tv[2]; + QT_STATBUF st; + + if (QT_FSTAT(fd, &st) == -1) { + error = QSystemError(errno, QSystemError::StandardLibraryError); + return false; + } + + GetFileTimes::get(&st, &tv[0], &tv[1]); + + const qint64 msecs = newDate.toMSecsSinceEpoch(); + + if (time == QAbstractFileEngine::AccessTime) { + tv[0].tv_sec = msecs / 1000; + tv[0].tv_usec = (msecs % 1000) * 1000; + } else if (time == QAbstractFileEngine::ModificationTime) { + tv[1].tv_sec = msecs / 1000; + tv[1].tv_usec = (msecs % 1000) * 1000; + } + + if (futimes(fd, tv) == -1) { + error = QSystemError(errno, QSystemError::StandardLibraryError); + return false; + } + + return true; +#else + Q_UNUSED(fd); + error = QSystemError(ENOSYS, QSystemError::StandardLibraryError); + return false; +#endif +} + QString QFileSystemEngine::homePath() { QString home = QFile::decodeName(qgetenv("HOME")); |