Revision control
Copy as Markdown
// Copyright (C) 2018 Intel Corporation.
// Copyright (C) 2016 The Qt Company Ltd.
// Copyright (C) 2013 Samuel Gaist <samuel.gaist@edeltech.ch>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:critical reason:data-parser
#include "qplatformdefs.h"
#include "qfilesystemengine_p.h"
#include "qfile.h"
#include "qstorageinfo.h"
#include <QtCore/qoperatingsystemversion.h>
#include <QtCore/private/qcore_unix_p.h>
#include <QtCore/private/qfiledevice_p.h>
#include <QtCore/private/qfunctions_p.h>
#include <QtCore/qvarlengtharray.h>
#ifndef QT_BOOTSTRAPPED
# include <QtCore/qscopedvaluerollback.h>
# include <QtCore/qstandardpaths.h>
# include <QtCore/private/qtemporaryfile_p.h>
#endif // QT_BOOTSTRAPPED
#include <grp.h>
#include <pwd.h>
#include <stdlib.h> // for realpath()
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <chrono>
#include <string>
#include <utility>
#if __has_include(<paths.h>)
# include <paths.h>
#endif
#ifndef _PATH_TMP // from <paths.h>
# define _PATH_TMP "/tmp"
#endif
#if __has_include(<sys/disk.h>)
// BSDs (including Apple Darwin)
# include <sys/disk.h>
#endif
#if defined(F_KINFO)
# include <sys/user.h> // struct kinfo_file, for fcntl(F_KINFO) (FreeBSD)
#endif
#if defined(Q_OS_DARWIN)
# include <QtCore/private/qcore_mac_p.h>
# include <CoreFoundation/CFBundle.h>
# include <UniformTypeIdentifiers/UTType.h>
# include <UniformTypeIdentifiers/UTCoreTypes.h>
# include <Foundation/Foundation.h>
# include <copyfile.h>
#endif
#ifdef Q_OS_MACOS
#include <CoreServices/CoreServices.h>
#endif
#if defined(QT_PLATFORM_UIKIT)
#include <MobileCoreServices/MobileCoreServices.h>
#endif
#if defined(Q_OS_LINUX)
# include <sys/ioctl.h>
# include <sys/sendfile.h>
# include <linux/fs.h>
// in case linux/fs.h is too old and doesn't define it:
#ifndef FICLONE
# define FICLONE _IOW(0x94, 9, int)
#endif
#endif
#if defined(Q_OS_VXWORKS)
# include <sys/statfs.h>
# if __has_include(<dosFsLib.h>)
# include <dosFsLib.h>
# endif
#endif
#if defined(Q_OS_ANDROID)
// statx() is disabled on Android because quite a few systems
// come with sandboxes that kill applications that make system calls outside a
// whitelist and several Android vendors can't be bothered to update the list.
# undef STATX_BASIC_STATS
#endif
#ifndef STATX_ALL
struct statx { mode_t stx_mode; }; // dummy
#endif
#ifndef O_DIRECTORY
# define O_DIRECTORY 0
#endif
#ifndef O_NOFOLLOW
# define O_NOFOLLOW 0
#endif
#ifndef O_PATH
# define O_PATH 0
#endif
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
static QByteArray &removeTrailingSlashes(QByteArray &path)
{
// Darwin doesn't support trailing /'s, so remove for everyone
while (path.size() > 1 && path.endsWith('/'))
path.chop(1);
return path;
}
enum {
#if defined(Q_OS_ANDROID) || defined(Q_OS_HARMONY)
// On Android/OHOS, the link(2) system call has been observed to always fail
// with EACCES, regardless of whether there are permission problems or not.
SupportsHardlinking = false
#else
SupportsHardlinking = true
#endif
};
#if defined(Q_OS_DARWIN)
static inline bool hasResourcePropertyFlag(const QFileSystemMetaData &data,
const QFileSystemEntry &entry,
CFStringRef key, QCFType<CFURLRef> &url)
{
if (!url) {
QCFString path = CFStringCreateWithFileSystemRepresentation(0,
entry.nativeFilePath().constData());
if (!path)
return false;
url = CFURLCreateWithFileSystemPath(0, path, kCFURLPOSIXPathStyle,
data.hasFlags(QFileSystemMetaData::DirectoryType));
}
if (!url)
return false;
CFBooleanRef value;
if (CFURLCopyResourcePropertyForKey(url, key, &value, NULL)) {
if (value == kCFBooleanTrue)
return true;
}
return false;
}
static bool isPackage(const QFileSystemMetaData &data, const QFileSystemEntry &entry,
QCFType<CFURLRef> &cachedUrl)
{
if (!data.isDirectory())
return false;
QFileInfo info(entry.filePath());
QString suffix = info.suffix();
if (suffix.length() > 0) {
// First step: is it a bundle?
const auto *utType = [UTType typeWithFilenameExtension:suffix.toNSString()];
if ([utType conformsToType:UTTypeBundle])
return true;
// Second step: check if an application knows the package type
QCFType<CFStringRef> path = entry.filePath().toCFString();
QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, path, kCFURLPOSIXPathStyle, true);
UInt32 type, creator;
// Well created packages have the PkgInfo file
if (CFBundleGetPackageInfoInDirectory(url, &type, &creator))
return true;
#ifdef Q_OS_MACOS
// Find if an application other than Finder claims to know how to handle the package
QCFType<CFURLRef> application = LSCopyDefaultApplicationURLForURL(url,
kLSRolesEditor | kLSRolesViewer, nullptr);
if (application) {
QCFType<CFBundleRef> bundle = CFBundleCreate(kCFAllocatorDefault, application);
CFStringRef identifier = CFBundleGetIdentifier(bundle);
QString applicationId = QString::fromCFString(identifier);
if (applicationId != "com.apple.finder"_L1)
return true;
}
#endif
}
// Third step: check if the directory has the package bit set
return hasResourcePropertyFlag(data, entry, kCFURLIsPackageKey, cachedUrl);
}
#endif
#ifdef Q_OS_VXWORKS
static inline void forceRequestedPermissionsOnVxWorks(QByteArray dirName, mode_t mode)
{
if (mode == 0) {
chmod(dirName, 0);
}
}
#endif
namespace {
namespace GetFileTimes {
qint64 time_t_toMsecs(time_t t)
{
using namespace std::chrono;
return milliseconds{seconds{t}}.count();
}
// fallback set
[[maybe_unused]] qint64 atime(const QT_STATBUF &statBuffer, ulong)
{
return time_t_toMsecs(statBuffer.st_atime);
}
[[maybe_unused]] qint64 birthtime(const QT_STATBUF &, ulong)
{
return Q_INT64_C(0);
}
[[maybe_unused]] qint64 ctime(const QT_STATBUF &statBuffer, ulong)
{
return time_t_toMsecs(statBuffer.st_ctime);
}
[[maybe_unused]] qint64 mtime(const QT_STATBUF &statBuffer, ulong)
{
return time_t_toMsecs(statBuffer.st_mtime);
}
// T is either a stat.timespec or statx.statx_timestamp,
// both have tv_sec and tv_nsec members
template<typename T>
qint64 timespecToMSecs(const T &spec)
{
using namespace std::chrono;
const nanoseconds nsecs = seconds{spec.tv_sec} + nanoseconds{spec.tv_nsec};
return duration_cast<milliseconds>(nsecs).count();
}
// Xtim, POSIX.1-2008
template <typename T>
[[maybe_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>
[[maybe_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>
[[maybe_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>
[[maybe_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>
[[maybe_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>
[[maybe_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>
[[maybe_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>
[[maybe_unused]] static typename std::enable_if<(&T::st_mtimespec, true), qint64>::type
mtime(const T &statBuffer, int)
{ return timespecToMSecs(statBuffer.st_mtimespec); }
#endif
#if !defined(st_mtimensec) && !defined(__alpha__)
// Xtimensec
template <typename T>
[[maybe_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>
[[maybe_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>
[[maybe_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>
[[maybe_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; }
#endif
} // namespace GetFileTimes
} // unnamed namespace
// converts QT_STATBUF::st_mode to QFSMD
// the \a attributes parameter is OS-specific
static QFileSystemMetaData::MetaDataFlags
flagsFromStMode(mode_t mode, [[maybe_unused]] quint64 attributes)
{
// inode exists
QFileSystemMetaData::MetaDataFlags entryFlags = QFileSystemMetaData::ExistsAttribute;
if (mode & S_IRUSR)
entryFlags |= QFileSystemMetaData::OwnerReadPermission;
if (mode & S_IWUSR)
entryFlags |= QFileSystemMetaData::OwnerWritePermission;
if (mode & S_IXUSR)
entryFlags |= QFileSystemMetaData::OwnerExecutePermission;
if (mode & S_IRGRP)
entryFlags |= QFileSystemMetaData::GroupReadPermission;
if (mode & S_IWGRP)
entryFlags |= QFileSystemMetaData::GroupWritePermission;
if (mode & S_IXGRP)
entryFlags |= QFileSystemMetaData::GroupExecutePermission;
if (mode & S_IROTH)
entryFlags |= QFileSystemMetaData::OtherReadPermission;
if (mode & S_IWOTH)
entryFlags |= QFileSystemMetaData::OtherWritePermission;
if (mode & S_IXOTH)
entryFlags |= QFileSystemMetaData::OtherExecutePermission;
// Type
Q_ASSERT(!S_ISLNK(mode)); // can only happen with lstat()
if ((mode & S_IFMT) == S_IFREG)
entryFlags |= QFileSystemMetaData::FileType;
else if ((mode & S_IFMT) == S_IFDIR)
entryFlags |= QFileSystemMetaData::DirectoryType;
else if ((mode & S_IFMT) != S_IFBLK) // char devices, sockets, FIFOs
entryFlags |= QFileSystemMetaData::SequentialType;
// OS-specific flags
// Potential flags for the future:
// UF_APPEND and STATX_ATTR_APPEND
// UF_COMPRESSED and STATX_ATTR_COMPRESSED
// UF_IMMUTABLE and STATX_ATTR_IMMUTABLE
// UF_NODUMP and STATX_ATTR_NODUMP
#if defined(Q_OS_VXWORKS) && __has_include(<dosFsLib.h>)
if (attributes & DOS_ATTR_RDONLY) {
// on a DOS FS, stat() always returns 0777 bits set in st_mode
// when DOS FS is read only the write permissions are removed
entryFlags &= ~QFileSystemMetaData::OwnerWritePermission;
entryFlags &= ~QFileSystemMetaData::GroupWritePermission;
entryFlags &= ~QFileSystemMetaData::OtherWritePermission;
}
#endif
return entryFlags;
}
#ifdef STATX_BASIC_STATS
static int qt_real_statx(int fd, const char *pathname, int flags, struct statx *statxBuffer)
{
unsigned mask = STATX_BASIC_STATS | STATX_BTIME;
int ret = statx(fd, pathname, flags | AT_NO_AUTOMOUNT, mask, statxBuffer);
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
MetaDataFlags flags = flagsFromStMode(statxBuffer.stx_mode, statxBuffer.stx_attributes);
entryFlags |= flags;
knownFlagsMask |= flags | PosixStatFlags;
// Attributes
if (statxBuffer.stx_nlink == 0)
entryFlags |= QFileSystemMetaData::WasDeletedAttribute;
size_ = qint64(statxBuffer.stx_size);
// Times
using namespace GetFileTimes;
accessTime_ = timespecToMSecs(statxBuffer.stx_atime);
metadataChangeTime_ = timespecToMSecs(statxBuffer.stx_ctime);
modificationTime_ = timespecToMSecs(statxBuffer.stx_mtime);
const bool birthMask = statxBuffer.stx_mask & STATX_BTIME;
birthTime_ = birthMask ? timespecToMSecs(statxBuffer.stx_btime) : 0;
userId_ = statxBuffer.stx_uid;
groupId_ = statxBuffer.stx_gid;
}
#else
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
bool QFileSystemEngine::fillMetaData(int fd, QFileSystemMetaData &data)
{
auto getSizeForBlockDev = [&](mode_t st_mode) {
#ifdef BLKGETSIZE64
// Linux
if (quint64 sz; (st_mode & S_IFMT) == S_IFBLK && ioctl(fd, BLKGETSIZE64, &sz) == 0)
data.size_ = sz; // returns byte count
#elif defined(BLKGETSIZE)
// older Linux
if (ulong sz; (st_mode & S_IFMT) == S_IFBLK && ioctl(fd, BLKGETSIZE, &sz) == 0)
data.size_ = sz * 512; // returns 512-byte sector count
#elif defined(DKIOCGETBLOCKCOUNT)
// Apple Darwin
qint32 blksz;
if (quint64 count; (st_mode & S_IFMT) == S_IFBLK
&& ioctl(fd, DKIOCGETBLOCKCOUNT, &count) == 0
&& ioctl(fd, DKIOCGETBLOCKSIZE, &blksz) == 0)
data.size_ = count * blksz;
#elif defined(DIOCGMEDIASIZE)
// FreeBSD
// see Linux-compat implementation in
// S_IFCHR is correct: FreeBSD doesn't have block devices any more
if (QT_OFF_T sz; (st_mode & S_IFMT) == S_IFCHR && ioctl(fd, DIOCGMEDIASIZE, &sz) == 0)
data.size_ = sz; // returns byte count
#else
Q_UNUSED(st_mode);
#endif
};
data.entryFlags &= ~QFileSystemMetaData::PosixStatFlags;
data.knownFlagsMask |= QFileSystemMetaData::PosixStatFlags;
struct statx statxBuffer;
int ret = qt_fstatx(fd, &statxBuffer);
if (ret != -ENOSYS) {
if (ret == 0) {
data.fillFromStatxBuf(statxBuffer);
getSizeForBlockDev(statxBuffer.stx_mode);
return true;
}
return false;
}
QT_STATBUF statBuffer;
if (QT_FSTAT(fd, &statBuffer) == 0) {
data.fillFromStatBuf(statBuffer);
getSizeForBlockDev(statBuffer.st_mode);
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)
{
quint64 attributes = 0;
#if defined(UF_SETTABLE) // BSDs (incl. Darwin)
attributes = statBuffer.st_flags;
#elif defined(Q_OS_VXWORKS) && __has_include(<dosFsLib.h>)
attributes = statBuffer.st_attrib;
#endif
// Permissions
MetaDataFlags flags = flagsFromStMode(statBuffer.st_mode, attributes);
entryFlags |= flags;
knownFlagsMask |= flags | PosixStatFlags;
// Attributes
if (statBuffer.st_nlink == 0)
entryFlags |= QFileSystemMetaData::WasDeletedAttribute;
size_ = statBuffer.st_size;
// 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 = {};
entryFlags = {};
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;
}
}
}
#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
Q_UNUSED(entry);
#endif
}
//static
QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data)
{
Q_CHECK_FILE_NAME(link, link);
QByteArray s = qt_readlink(link.nativeFilePath().constData());
if (s.size() > 0) {
QString ret;
if (!data.hasFlags(QFileSystemMetaData::DirectoryType))
fillMetaData(link, data, QFileSystemMetaData::DirectoryType);
if (data.isDirectory() && s[0] != '/') {
QDir parent(link.filePath());
parent.cdUp();
ret = parent.path();
if (!ret.isEmpty() && !ret.endsWith(u'/'))
ret += u'/';
}
ret += QFile::decodeName(s);
if (!ret.startsWith(u'/'))
ret.prepend(absoluteName(link).path() + u'/');
ret = QDir::cleanPath(ret);
if (ret.size() > 1 && ret.endsWith(u'/'))
ret.chop(1);
return QFileSystemEntry(ret);
}
#if defined(Q_OS_DARWIN)
{
QCFString path = CFStringCreateWithFileSystemRepresentation(0,
QFile::encodeName(QDir::cleanPath(link.filePath())).data());
if (!path)
return QFileSystemEntry();
QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, path, kCFURLPOSIXPathStyle,
data.hasFlags(QFileSystemMetaData::DirectoryType));
if (!url)
return QFileSystemEntry();
QCFType<CFDataRef> bookmarkData = CFURLCreateBookmarkDataFromFile(0, url, NULL);
if (!bookmarkData)
return QFileSystemEntry();
QCFType<CFURLRef> resolvedUrl = CFURLCreateByResolvingBookmarkData(0,
bookmarkData,
(CFURLBookmarkResolutionOptions)(kCFBookmarkResolutionWithoutUIMask
| kCFBookmarkResolutionWithoutMountingMask), NULL, NULL, NULL, NULL);
if (!resolvedUrl)
return QFileSystemEntry();
QCFString cfstr(CFURLCopyFileSystemPath(resolvedUrl, kCFURLPOSIXPathStyle));
if (!cfstr)
return QFileSystemEntry();
return QFileSystemEntry(QString::fromCFString(cfstr));
}
#endif
return QFileSystemEntry();
}
//static
QFileSystemEntry QFileSystemEngine::getRawLinkPath(const QFileSystemEntry &link,
QFileSystemMetaData &data)
{
Q_UNUSED(data)
const QByteArray path = qt_readlink(link.nativeFilePath().constData());
const QString ret = QFile::decodeName(path);
return QFileSystemEntry(ret);
}
//static
QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data)
{
Q_CHECK_FILE_NAME(entry, entry);
// If we have OS support to getting the canonical path, let's use it,
// because the OS can resolve symlinks on its own without transitions
// between user mode and kernel mode, and it's atomic, not subject to
// symlinks changing while resolving.
#if defined(Q_OS_LINUX) || defined(Q_OS_ANDROID) || defined(F_KINFO) || defined(F_GETPATH)
auto pathForFd = [](int fd) -> QByteArray {
# if defined(Q_OS_LINUX) || defined(Q_OS_ANDROID)
return qt_readlink(QByteArray::number(fd).prepend("/proc/self/fd/"));
# elif defined(F_KINFO) // FreeBSD
struct kinfo_file kif;
kif.kf_structsize = sizeof(kif); // required before the call
if (fcntl(fd, F_KINFO, &kif) == 0)
return kif.kf_path;
# elif defined(F_GETPATH) // Darwin and NetBSD (>= 10)
char buf[PATH_MAX];
if (fcntl(fd, F_GETPATH, buf) == 0)
return buf;
# endif
return {};
};
if (int fd = QT_OPEN(entry.nativeFilePath(), O_RDONLY | O_PATH | O_NONBLOCK); fd >= 0) {
// if we opened it, then it exists
data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
data.entryFlags |= QFileSystemMetaData::ExistsAttribute;
QByteArray ret = pathForFd(fd);
QT_CLOSE(fd);
// Check we've got an absolute path. Ancient Linux replied with device
// ID and inode number instead of a path, while BSD name-cache lookups
// can yield an empty path on success.
if (ret.startsWith('/')) // absolute path
return QFileSystemEntry(ret, QFileSystemEntry::FromNativePath{});
errno = 0;
} else if (errno == ENOENT || errno == ENOTDIR) { // file doesn't exist
data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
data.entryFlags &= ~(QFileSystemMetaData::ExistsAttribute);
return {};
} else if constexpr (O_PATH != 0) {
// If we have O_PATH, realpath() below can't succeed; if we don't,
// we may have tried to open an unreadable file for reading and got
// an EACCES error.
return {};
}
# define REALPATH_FALLBACK_INLINE Q_NEVER_INLINE
#else
# define REALPATH_FALLBACK_INLINE inline
#endif
return canonicalNameViaRealpath(entry, data);
}
REALPATH_FALLBACK_INLINE // static
QFileSystemEntry QFileSystemEngine::canonicalNameViaRealpath(const QFileSystemEntry &entry,
QFileSystemMetaData &data)
{
char *resolved_name = nullptr;
#ifdef PATH_MAX
// use the stack to avoid the overhead of memory allocation
char stack_result[PATH_MAX + 1];
#else
// system with unlimited file paths -> must use heap
std::nullptr_t stack_result = nullptr;
auto freer = qScopeGuard([&] { free(resolved_name); });
#endif
# if defined(Q_OS_DARWIN) || defined(Q_OS_ANDROID)
// On some Android and macOS versions, realpath() will return a path even if
// it does not exist. To work around this, we check existence in advance.
if (!data.hasFlags(QFileSystemMetaData::ExistsAttribute))
fillMetaData(entry, data, QFileSystemMetaData::ExistsAttribute);
if (!data.exists())
errno = ENOENT;
else
resolved_name = realpath(entry.nativeFilePath().constData(), stack_result);
# else
resolved_name = realpath(entry.nativeFilePath().constData(), stack_result);
# endif
if (resolved_name) {
data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
data.entryFlags |= QFileSystemMetaData::ExistsAttribute;
return QFileSystemEntry(resolved_name, QFileSystemEntry::FromNativePath{});
} else if (errno == ENOENT || errno == ENOTDIR) { // file doesn't exist
data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
data.entryFlags &= ~(QFileSystemMetaData::ExistsAttribute);
}
return QFileSystemEntry();
}
#undef REALPATH_FALLBACK_INLINE
//static
QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry)
{
Q_CHECK_FILE_NAME(entry, entry);
if (entry.isAbsolute() && entry.isClean())
return entry;
QByteArray orig = entry.nativeFilePath();
QByteArray result;
if (orig.isEmpty() || !orig.startsWith('/')) {
QFileSystemEntry cur(currentPath());
result = cur.nativeFilePath();
}
if (!orig.isEmpty() && !(orig.size() == 1 && orig[0] == '.')) {
if (!result.isEmpty() && !result.endsWith('/'))
result.append('/');
result.append(orig);
}
if (result.size() == 1 && result[0] == '/')
return QFileSystemEntry(result, QFileSystemEntry::FromNativePath());
const bool isDir = result.endsWith('/');
/* as long as QDir::cleanPath() operates on a QString we have to convert to a string here.
* ideally we never convert to a string since that loses information. Please fix after
* we get a QByteArray version of QDir::cleanPath()
*/
QFileSystemEntry resultingEntry(result, QFileSystemEntry::FromNativePath());
QString stringVersion = QDir::cleanPath(resultingEntry.filePath());
if (isDir)
stringVersion.append(u'/');
return QFileSystemEntry(stringVersion);
}
QByteArray QFileSystemNativeId::toByteArray() const
{
if (!isValid())
return {};
char buffer[sizeof "0123456789abcdef:18446744073709551615"];
std::snprintf(buffer, sizeof(buffer), "%llx:%llu",
qulonglong(volumeId), qulonglong(fileId[0]));
return buffer;
}
static QFileSystemNativeId nativeIdFromStatBuf(const QT_STATBUF &statResult)
{
QFileSystemNativeId id;
id.volumeId = quint64(statResult.st_dev);
id.fileId[0] = quint64(statResult.st_ino);
return id;
}
//static
QFileSystemNativeId QFileSystemEngine::nativeId(const QFileSystemEntry &entry)
{
Q_CHECK_FILE_NAME(entry, QFileSystemNativeId());
QT_STATBUF statResult;
if (QT_STAT(entry.nativeFilePath().constData(), &statResult) != 0)
return {};
return nativeIdFromStatBuf(statResult);
}
//static
QFileSystemNativeId QFileSystemEngine::nativeId(int fd)
{
QT_STATBUF statResult;
if (QT_FSTAT(fd, &statResult) != 0) {
qErrnoWarning("fstat() failed for fd %d", fd);
return {};
}
return nativeIdFromStatBuf(statResult);
}
//static
QString QFileSystemEngine::resolveUserName(uint userId)
{
#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
long size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
if (size_max == -1)
size_max = 1024;
QVarLengthArray<char, 1024> buf(size_max);
#endif
#if !defined(Q_OS_INTEGRITY) && !defined(Q_OS_WASM)
struct passwd *pw = nullptr;
#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) && !defined(Q_OS_VXWORKS)
struct passwd entry;
getpwuid_r(userId, &entry, buf.data(), buf.size(), &pw);
#else
pw = getpwuid(userId);
#endif
if (pw)
return QFile::decodeName(QByteArray(pw->pw_name));
#else // Integrity || WASM
Q_UNUSED(userId);
#endif
return QString();
}
//static
QString QFileSystemEngine::resolveGroupName(uint groupId)
{
#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
long size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
if (size_max == -1)
size_max = 1024;
QVarLengthArray<char, 1024> buf(size_max);
#endif
#if !defined(Q_OS_INTEGRITY) && !defined(Q_OS_WASM)
struct group *gr = nullptr;
#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))
size_max = sysconf(_SC_GETGR_R_SIZE_MAX);
if (size_max == -1)
size_max = 1024;
buf.resize(size_max);
struct group entry;
// Some large systems have more members than the POSIX max size
// Loop over by doubling the buffer size (upper limit 250k)
for (long size = size_max; size < 256000; size += size)
{
buf.resize(size);
// ERANGE indicates that the buffer was too small
if (!getgrgid_r(groupId, &entry, buf.data(), buf.size(), &gr)
|| errno != ERANGE)
break;
}
#else
gr = getgrgid(groupId);
#endif
if (gr)
return QFile::decodeName(QByteArray(gr->gr_name));
#else // Integrity || WASM || VxWorks
Q_UNUSED(groupId);
#endif
return QString();
}
#if defined(Q_OS_DARWIN)
//static
QString QFileSystemEngine::bundleName(const QFileSystemEntry &entry)
{
QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, QCFString(entry.filePath()),
kCFURLPOSIXPathStyle, true);
if (QCFType<CFDictionaryRef> dict = CFBundleCopyInfoDictionaryForURL(url)) {
if (CFTypeRef name = (CFTypeRef)CFDictionaryGetValue(dict, kCFBundleNameKey)) {
if (CFGetTypeID(name) == CFStringGetTypeID())
return QString::fromCFString((CFStringRef)name);
}
}
return QString();
}
#endif
//static
bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemMetaData &data,
QFileSystemMetaData::MetaDataFlags what)
{
Q_CHECK_FILE_NAME(entry, false);
// Detection of WasDeletedAttribute is imperfect: in general, if we can
// successfully stat() or access() a file, it hasn't been deleted (though
// there are exceptions, like /proc/XXX/fd/ entries on Linux). So we have
// to restore this flag in case we fail to stat() anything.
auto hadBeenDeleted = data.entryFlags & QFileSystemMetaData::WasDeletedAttribute;
#if defined(Q_OS_DARWIN)
if (what & (QFileSystemMetaData::BundleType | QFileSystemMetaData::CaseSensitive)) {
if (!data.hasFlags(QFileSystemMetaData::DirectoryType))
what |= QFileSystemMetaData::DirectoryType;
}
if (what & QFileSystemMetaData::AliasType)
what |= QFileSystemMetaData::LinkType;
#endif // defined(Q_OS_DARWIN)
bool needLstat = what.testAnyFlag(QFileSystemMetaData::LinkType);
#ifdef UF_HIDDEN
if (what & QFileSystemMetaData::HiddenAttribute) {
// Some OSes (BSDs) have the ability to mark directory entries as
// hidden besides the usual Unix way of naming them with a leading dot.
// For those OSes, we must lstat() the entry itself so we can find
// out if a symlink is hidden or not.
needLstat = true;
}
#endif
// if we're asking for any of the stat(2) flags, then we're getting them all
if (what & QFileSystemMetaData::PosixStatFlags)
what |= QFileSystemMetaData::PosixStatFlags;
data.entryFlags &= ~what;
const QByteArray nativeFilePath = entry.nativeFilePath();
int entryErrno = 0; // innocent until proven otherwise
// 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 _LARGEFILE64.
union {
QT_STATBUF statBuffer;
struct statx statxBuffer;
};
int statResult = -1;
if (needLstat) {
mode_t mode = 0;
statResult = qt_lstatx(nativeFilePath, &statxBuffer);
if (statResult == -ENOSYS) {
// use lstat(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) {
#ifdef UF_HIDDEN
// currently only supported on systems with no statx() call
Q_ASSERT(statResult == 0);
if (statBuffer.st_flags & UF_HIDDEN)
data.entryFlags |= QFileSystemMetaData::HiddenAttribute;
data.knownFlagsMask |= QFileSystemMetaData::HiddenAttribute;
#endif
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 {
// it's a regular file and it exists
if (statResult)
data.fillFromStatxBuf(statxBuffer);
else
data.fillFromStatBuf(statBuffer);
}
} else {
// it doesn't exist
entryErrno = errno;
statResult = -1;
data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
}
data.knownFlagsMask |= QFileSystemMetaData::LinkType;
}
// second, we try a regular stat(2)
if (statResult == -1 && (what & QFileSystemMetaData::PosixStatFlags)) {
if (entryErrno == 0) {
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 (statResult != 0) {
entryErrno = errno;
data.birthTime_ = 0;
data.metadataChangeTime_ = 0;
data.modificationTime_ = 0;
data.accessTime_ = 0;
data.size_ = 0;
data.userId_ = (uint) -2;
data.groupId_ = (uint) -2;
// reset the mask
data.knownFlagsMask |= QFileSystemMetaData::PosixStatFlags
| QFileSystemMetaData::ExistsAttribute;
}
}
// third, we try access(2)
if (what & (QFileSystemMetaData::UserPermissions | QFileSystemMetaData::ExistsAttribute)) {
#if defined(Q_OS_VXWORKS)
struct statfs statBuf;
if (statfs(nativeFilePath, &statBuf) == 0) {
if (statBuf.f_type != NFSV2_MAGIC && statBuf.f_type != NFSV3_MAGIC &&
statBuf.f_type != HRFS_MAGIC) {
#if __has_include(<dosFsLib.h>)
if (data.entryFlags & QFileSystemMetaData::OwnerWritePermission) {
data.entryFlags |= QFileSystemMetaData::UserWritePermission;
}
if (data.entryFlags & QFileSystemMetaData::OwnerExecutePermission) {
data.entryFlags |= QFileSystemMetaData::UserExecutePermission;
}
#endif
data.entryFlags |= QFileSystemMetaData::UserReadPermission |
QFileSystemMetaData::ExistsAttribute;
return true;
}
}
#if defined(QT_DEBUG)
else {
//on VxWorks hostfs, used for debugging, failes on statfs and falsely reports
//WasDeleted
statResult = QT_STAT(nativeFilePath, &statBuffer);
if (statResult == 0) {
data.entryFlags |= QFileSystemMetaData::UserReadPermission |
QFileSystemMetaData::ExistsAttribute;
data.entryFlags &= ~QFileSystemMetaData::WasDeletedAttribute;
return true;
}
}
#endif
#endif
// calculate user permissions
auto checkAccess = [&](QFileSystemMetaData::MetaDataFlag flag, int mode) {
if (entryErrno != 0 || (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) {
entryErrno = errno;
}
};
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 (entryErrno == 0 && (data.entryFlags & QFileSystemMetaData::ExistsAttribute) == 0) {
if (QT_ACCESS(nativeFilePath, F_OK) == -1)
entryErrno = errno;
else
data.entryFlags |= QFileSystemMetaData::ExistsAttribute;
}
data.knownFlagsMask |= (what & QFileSystemMetaData::UserPermissions) |
QFileSystemMetaData::ExistsAttribute;
}
#if defined(Q_OS_DARWIN)
QCFType<CFURLRef> cachedUrl;
if (what & QFileSystemMetaData::AliasType) {
if (entryErrno == 0 && hasResourcePropertyFlag(data, entry, kCFURLIsAliasFileKey, cachedUrl)) {
// kCFURLIsAliasFileKey includes symbolic links, so filter those out
if (!(data.entryFlags & QFileSystemMetaData::LinkType))
data.entryFlags |= QFileSystemMetaData::AliasType;
}
data.knownFlagsMask |= QFileSystemMetaData::AliasType;
}
if (what & QFileSystemMetaData::BundleType) {
if (entryErrno == 0 && isPackage(data, entry, cachedUrl))
data.entryFlags |= QFileSystemMetaData::BundleType;
data.knownFlagsMask |= QFileSystemMetaData::BundleType;
}
if (what & QFileSystemMetaData::CaseSensitive) {
if (entryErrno == 0 && hasResourcePropertyFlag(data, entry,
kCFURLVolumeSupportsCaseSensitiveNamesKey, cachedUrl))
data.entryFlags |= QFileSystemMetaData::CaseSensitive;
data.knownFlagsMask |= QFileSystemMetaData::CaseSensitive;
}
#endif
if (what & QFileSystemMetaData::HiddenAttribute
&& !data.isHidden()) {
// reusing nativeFilePath from above instead of entry.fileName(), to
// avoid memory allocation for the QString result.
qsizetype lastSlash = nativeFilePath.size();
while (lastSlash && nativeFilePath.at(lastSlash - 1) == '/')
--lastSlash; // skip ending slashes
while (lastSlash && nativeFilePath.at(lastSlash - 1) != '/')
--lastSlash; // skip non-slashes
--lastSlash; // point to the slash or -1 if no slash
if (nativeFilePath.at(lastSlash + 1) == '.')
data.entryFlags |= QFileSystemMetaData::HiddenAttribute;
data.knownFlagsMask |= QFileSystemMetaData::HiddenAttribute;
}
if (entryErrno != 0) {
what &= ~QFileSystemMetaData::LinkType; // don't clear link: could be broken symlink
data.clearFlags(what);
// see comment at the top
data.entryFlags |= hadBeenDeleted;
data.knownFlagsMask |= hadBeenDeleted;
return false;
}
return true;
}
// static
auto QFileSystemEngine::cloneFile(int srcfd, int dstfd, const QFileSystemMetaData &knownData) -> TriStateResult
{
QT_STATBUF statBuffer;
if (knownData.hasFlags(QFileSystemMetaData::PosixStatFlags) &&
knownData.isFile()) {
statBuffer.st_mode = S_IFREG;
} else if (knownData.hasFlags(QFileSystemMetaData::PosixStatFlags) &&
knownData.isDirectory()) {
errno = EISDIR;
return TriStateResult::Failed; // fcopyfile(3) returns success on directories
} else if (QT_FSTAT(srcfd, &statBuffer) == -1) {
// errno was set
return TriStateResult::Failed;
} else if (!S_ISREG((statBuffer.st_mode))) {
// not a regular file, let QFile do the copy
return TriStateResult::NotSupported;
}
[[maybe_unused]] auto destinationIsEmpty = [dstfd]() {
QT_STATBUF statBuffer;
return QT_FSTAT(dstfd, &statBuffer) == 0 && statBuffer.st_size == 0;
};
Q_ASSERT(destinationIsEmpty());
#if defined(Q_OS_LINUX)
// first, try FICLONE (only works on regular files and only on certain fs)
if (::ioctl(dstfd, FICLONE, srcfd) == 0)
return TriStateResult::Success;
#elif defined(Q_OS_DARWIN)
// try fcopyfile
if (fcopyfile(srcfd, dstfd, nullptr, COPYFILE_DATA | COPYFILE_STAT) == 0)
return TriStateResult::Success;
switch (errno) {
case ENOTSUP:
case ENOMEM:
return TriStateResult::NotSupported; // let QFile try
}
return TriStateResult::Failed;
#endif
#if QT_CONFIG(copy_file_range)
// Second, try copy_file_range. Tested on Linux & FreeBSD: FreeBSD can copy
// across mountpoints, Linux currently (6.12) can only if the source and
// destination mountpoints are the same filesystem type.
QT_OFF_T srcoffset = 0;
ssize_t copied;
do {
copied = ::copy_file_range(srcfd, &srcoffset, dstfd, nullptr, SSIZE_MAX, 0);
} while (copied > 0 || (copied < 0 && errno == EINTR));
if (copied == 0)
return TriStateResult::Success; // EOF -> success
if (srcoffset) {
// some bytes were copied, so this is a real error (like ENOSPC).
copied = ftruncate(dstfd, 0);
return TriStateResult::Failed;
}
// We failed with no bytes copied, so is this a real filesystem failure
// that will remain with sendfile() or the copy pump? Or is it a
// copy_file_range() condition?
switch (errno) {
case EINVAL: // observed with some obscure filesystem combinations
case EXDEV: // Linux can't do xdev file copies (FreeBSD can)
case ENOSYS: // caused by some containers wrongly filtering the system call
break;
default:
return TriStateResult::Failed;
}
#endif
#if defined(Q_OS_LINUX)
// For Linux, try sendfile (it can send to some special types too).
// sendfile(2) is limited in the kernel to 2G - 4k
const size_t SendfileSize = 0x7ffff000;
ssize_t n = ::sendfile(dstfd, srcfd, nullptr, SendfileSize);
if (n == -1) {
switch (errno) {
case ENOSPC:
case EIO:
return TriStateResult::Failed;
}
// if we got an error here, give up and try at an upper layer
return TriStateResult::NotSupported;
}
while (n) {
n = ::sendfile(dstfd, srcfd, nullptr, SendfileSize);
if (n == -1) {
// uh oh, this is probably a real error (like ENOSPC)
n = ftruncate(dstfd, 0);
n = lseek(srcfd, 0, SEEK_SET);
n = lseek(dstfd, 0, SEEK_SET);
return TriStateResult::Failed;
}
}
return TriStateResult::Success;
#else
Q_UNUSED(dstfd);
return TriStateResult::NotSupported;
#endif
}
static QSystemError createDirectoryWithParents(const QByteArray &path, mode_t mode)
{
#ifdef Q_OS_WASM
if (path == "/")
return {};
#endif
auto tryMkDir = [&path, mode]() -> QSystemError {
if (QT_MKDIR(path, mode) == 0) {
#ifdef Q_OS_VXWORKS
forceRequestedPermissionsOnVxWorks(path, mode);
#endif
return {};
}
// On macOS with APFS mkdir sets errno to EISDIR, QTBUG-97110
if (errno == EISDIR)
return {};
if (errno == EEXIST || errno == EROFS) {
// ::mkdir() can fail if the dir already exists (it may have been
// created by another thread or another process)
QT_STATBUF st;
if (QT_STAT(path.constData(), &st) != 0)
return QSystemError::stdError(errno);
const bool isDir = (st.st_mode & S_IFMT) == S_IFDIR;
return isDir ? QSystemError{} : QSystemError::stdError(EEXIST);
}
return QSystemError::stdError(errno);
};
QSystemError result = tryMkDir();
if (result.ok())
return result;
// Only handle non-existing dir components in the path
if (result.errorCode != ENOENT)
return result;
qsizetype slash = path.lastIndexOf('/');
while (slash > 0 && path[slash - 1] == '/')
--slash;
if (slash < 1)
return result;
// mkdir failed because the parent dir doesn't exist, so try to create it
QByteArray parentPath = path.first(slash);
if (result = createDirectoryWithParents(parentPath, mode); !result.ok())
return result;
// try again
return tryMkDir();
}
bool QFileSystemEngine::mkpath(const QFileSystemEntry &entry,
std::optional<QFile::Permissions> permissions)
{
QByteArray path = entry.nativeFilePath();
Q_CHECK_FILE_NAME(path, false);
mode_t mode = permissions ? QtPrivate::toMode_t(*permissions) : 0777;
return createDirectoryWithParents(removeTrailingSlashes(path), mode).ok();
}
bool QFileSystemEngine::mkdir(const QFileSystemEntry &entry,
std::optional<QFile::Permissions> permissions)
{
QByteArray path = entry.nativeFilePath();
Q_CHECK_FILE_NAME(path, false);
mode_t mode = permissions ? QtPrivate::toMode_t(*permissions) : 0777;
auto result = QT_MKDIR(removeTrailingSlashes(path), mode) == 0;
#if defined(Q_OS_VXWORKS)
if (result)
forceRequestedPermissionsOnVxWorks(path, mode);
#endif
return result;
}
bool QFileSystemEngine::rmdir(const QFileSystemEntry &entry)
{
const QByteArray path = entry.nativeFilePath();
Q_CHECK_FILE_NAME(path, false);
return ::rmdir(path.constData()) == 0;
}
bool QFileSystemEngine::rmpath(const QFileSystemEntry &entry)
{
QByteArray path = QFile::encodeName(QDir::cleanPath(entry.filePath()));
Q_CHECK_FILE_NAME(path, false);
if (::rmdir(path.constData()) != 0)
return false; // Only return false if `entry` couldn't be deleted
const char sep = QDir::separator().toLatin1();
qsizetype slash = path.lastIndexOf(sep);
// `slash > 0` because truncate(0) would make `path` empty
for (; slash > 0; slash = path.lastIndexOf(sep)) {
path.truncate(slash);
if (::rmdir(path.constData()) != 0)
break;
}
return true;
}
//static
bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
{
Q_CHECK_FILE_NAME(source, false);
Q_CHECK_FILE_NAME(target, false);
if (::symlink(source.nativeFilePath().constData(), target.nativeFilePath().constData()) == 0)
return true;
error = QSystemError(errno, QSystemError::StandardLibraryError);
return false;
}
#if defined(QT_BOOTSTRAPPED) || !defined(AT_FDCWD) || defined(Q_OS_ANDROID) || !QT_CONFIG(datestring) || defined(Q_OS_VXWORKS)
// bootstrapped tools don't need this, and we don't want QStorageInfo
//static
bool QFileSystemEngine::supportsMoveFileToTrash()
{
return false;
}
//static
bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &, QFileSystemEntry &,
QSystemError &error)
{
error = QSystemError(ENOSYS, QSystemError::StandardLibraryError);
return false;
}
#elif defined(Q_OS_DARWIN)
// see qfilesystemengine_mac.mm
#elif defined(Q_OS_HARMONY)
// see qfilesystemengine_ohos.cpp
#else
/*
*/
//static
bool QFileSystemEngine::supportsMoveFileToTrash()
{
return true;
}
namespace {
struct FreeDesktopTrashOperation
{
/*
"A trash directory contains two subdirectories, named info and files."
*/
QString trashPath;
int filesDirFd = -1;
int infoDirFd = -1;
// relative file paths to the filesDirFd and infoDirFd from above
QByteArray tempTrashFileName;
QByteArray infoFilePath;
QByteArray encodedRelativeFilePath;
int infoFileFd = -1; // if we've already opened it
~FreeDesktopTrashOperation()
{
close();
}
constexpr bool isTrashDirOpen() const { return filesDirFd != -1 && infoDirFd != -1; }
void close()
{
int savedErrno = errno;
if (infoFileFd != -1) {
Q_ASSERT(infoDirFd != -1);
Q_ASSERT(!infoFilePath.isEmpty());
Q_ASSERT(!trashPath.isEmpty());
QT_CLOSE(infoFileFd);
unlinkat(infoDirFd, infoFilePath, 0);
infoFileFd = -1;
}
if (!tempTrashFileName.isEmpty()) {
Q_ASSERT(filesDirFd != -1);
unlinkat(filesDirFd, tempTrashFileName, 0);
}
if (filesDirFd >= 0)
QT_CLOSE(filesDirFd);
if (infoDirFd >= 0)
QT_CLOSE(infoDirFd);
filesDirFd = infoDirFd = -1;
errno = savedErrno;
}
bool tryCreateInfoFile(const QString &filePath, QSystemError &error)
{
QByteArray p = QFile::encodeName(filePath) + ".trashinfo";
infoFileFd = qt_safe_openat(infoDirFd, p, QT_OPEN_RDWR | QT_OPEN_CREAT | QT_OPEN_EXCL, 0666);
if (infoFileFd < 0) {
error = QSystemError(errno, QSystemError::StandardLibraryError);
return false;
}
infoFilePath = std::move(p);
return true;
}
void commit()
{
QT_CLOSE(infoFileFd);
infoFileFd = -1;
tempTrashFileName = {};
}
// opens a directory and returns the file descriptor
static int openDirFd(int dfd, const char *path, int mode)
{
mode |= QT_OPEN_RDONLY | O_DIRECTORY;
return qt_safe_openat(dfd, path, mode);
}
// opens an XDG Trash directory that is a subdirectory of dfd, creating if necessary
static int openOrCreateDir(int dfd, const char *path, int openmode = 0)
{
// try to open it as a dir, first
int fd = openDirFd(dfd, path, openmode);
if (fd >= 0 || errno != ENOENT)
return fd;
// try to mkdirat
if (mkdirat(dfd, path, 0700) < 0)
return -1;
// try to open it again
return openDirFd(dfd, path, openmode);
}
static QByteArray
getRelativeFilePathResolvingSymlinks(const QFileSystemEntry &source, const QString &volumeRoot)
{
// Walk the directory's ancestors from the shortest to the longest,
// following symlinks (as QT_STAT does), until we reach the volume's
// device. The shallowest ancestor already on that device is the point
// at which we cross onto the volume; everything below it is kept
// verbatim.
// We could also have scanned from longest until we step outside of the
// volume's device. The results would be the same, except for the case
// of symlink loops. There's no technical reason to choose one way or
// the other. Note that scanning from the root produces the same result
// as the startsWith() match from the caller, if it were removed.
QByteArray result;
QByteArray nativeVolumeRoot = QFile::encodeName(volumeRoot);
QT_STATBUF st;
if (QT_STAT(nativeVolumeRoot.constData(), &st) != 0)
return result;
const auto volumeDevice = st.st_dev;
// The directory part is absolute and already cleaned of "." and "..".
QByteArray dirPath = QFile::encodeName(source.path());
char *nativeDir = dirPath.data();
qsizetype crossingLength = dirPath.size(); // default: the whole directory
for (qsizetype pos = 1; pos <= dirPath.size(); ++pos) {
if (pos < dirPath.size() && nativeDir[pos] != '/')
continue;
// temporarily terminate the string at this ancestor and stat() it
QScopedValueRollback null(nativeDir[pos], '\0');
if (QT_STAT(nativeDir, &st) == 0 && st.st_dev == volumeDevice) {
crossingLength = pos;
break;
}
}
// Canonicalize just the crossing directory, to express it relative to
// the mount point; the components below it are kept verbatim.
QFileSystemMetaData md;
QFileSystemEntry dir(dirPath.first(crossingLength), QFileSystemEntry::FromNativePath{});
result = QFileSystemEngine::canonicalName(dir, md).nativeFilePath();
if (Q_UNLIKELY(!result.startsWith(nativeVolumeRoot))) {
// still didn't match, this probably means the volume root is not a
// canonical path
md.clear();
dir = QFileSystemEntry(volumeRoot, QFileSystemEntry::FromInternalPath());
nativeVolumeRoot = QFileSystemEngine::canonicalName(dir, md).nativeFilePath();
}
result.remove(0, nativeVolumeRoot.size());
// Append the remaining directory components and the file name verbatim.
result += dirPath.sliced(crossingLength);
result += '/';
result += QFile::encodeName(source.fileName());
Q_ASSERT(result.startsWith('/'));
result.remove(0, 1);
return result;
}
// returns the path to \a source relative to the \a storage's mount point
static QByteArray getRelativeFilePath(const QFileSystemEntry &source,
const QStorageInfo &storage)
{
QString relativeFilePath = source.filePath();
QString volumeRoot = storage.rootPath();
qsizetype volumePrefixLength = volumeRoot.size();
if (volumePrefixLength == 1) {
// root volume: keep relativeFilePath unchanged
return source.nativeFilePath();
}
if (Q_LIKELY(relativeFilePath.startsWith(volumeRoot))) {
// another volume: remove the prefix and the slash
relativeFilePath.remove(0, volumePrefixLength + 1);
return QFile::encodeName(relativeFilePath);
}
// Uh oh, we must have followed some symlink to get here. We want to
// resolve symlinks only as far as getting onto the volume's device, but
// keep any symlinks below that point intact.
return getRelativeFilePathResolvingSymlinks(source, volumeRoot);
}
// opens or makes the XDG Trash hierarchy on parentfd (may be -1) called targetDir
QSystemError getTrashDir(int parentfd, QString targetDir, const QFileSystemEntry &source,
int openmode)
{
if (parentfd == AT_FDCWD)
trashPath = targetDir;
QByteArray nativePath = QFile::encodeName(targetDir);
// open the directory
int trashfd = openOrCreateDir(parentfd, nativePath, openmode);
if (trashfd < 0 && errno != ENOENT)
return QSystemError::stdError(errno);
// check if it is ours (even if we've just mkdirat'ed it)
if (QT_STATBUF st; QT_FSTAT(trashfd, &st) < 0)
return QSystemError::stdError(errno);
else if (st.st_uid != getuid())
return QSystemError::stdError(errno);
filesDirFd = openOrCreateDir(trashfd, "files");
if (filesDirFd >= 0) {
// try to link our file-to-be-trashed here
QTemporaryFileName tfn("XXXXXX"_L1);
for (int i = 0; i < 16; ++i) {
QByteArray attempt = tfn.generateNext();
if (linkat(AT_FDCWD, source.nativeFilePath(), filesDirFd, attempt, 0) == 0) {
tempTrashFileName = std::move(attempt);
break;
}
if (errno != EEXIST)
break;
}
// man 2 link on Linux has:
// EPERM The filesystem containing oldpath and newpath does not
// support the creation of hard links.
// EPERM oldpath is a directory.
// EPERM oldpath is marked immutable or append‐only.
// EMLINK The file referred to by oldpath already has the maximum
// number of links to it.
if (!tempTrashFileName.isEmpty() || errno == EPERM || errno == EMLINK)
infoDirFd = openOrCreateDir(trashfd, "info");
}
if (infoDirFd < 0) {
int saved_errno = errno;
close();
QT_CLOSE(trashfd);
return QSystemError::stdError(saved_errno);
}
QT_CLOSE(trashfd);
return {};
}
QSystemError openMountPointTrashLocation(const QFileSystemEntry &source,
const QStorageInfo &sourceStorage)
{
/*
Method 1:
"An administrator can create an $topdir/.Trash directory. The permissions on this
directories should permit all users who can trash files at all to write in it;
and the “sticky bit” in the permissions must be set, if the file system supports
it.
When trashing a file from a non-home partition/device, an implementation
(if it supports trashing in top directories) MUST check for the presence
of $topdir/.Trash."
*/
QSystemError error;
const auto dotTrash = "/.Trash"_L1;
const QString userID = QString::number(::getuid());
QFileSystemEntry dotTrashDir(sourceStorage.rootPath() + dotTrash);
// we MUST check that the sticky bit is set, and that it is not a symlink
int genericTrashFd = openDirFd(AT_FDCWD, dotTrashDir.nativeFilePath(), O_NOFOLLOW);
QT_STATBUF st = {};
if (genericTrashFd < 0 && errno != ENOENT && errno != EACCES) {
// O_DIRECTORY + O_NOFOLLOW produces ENOTDIR on Linux
if (QT_LSTAT(dotTrashDir.nativeFilePath(), &st) == 0 && S_ISLNK(st.st_mode)) {
// we SHOULD report the failed check to the administrator
qCritical("Warning: '%s' is a symlink to '%s'",
dotTrashDir.nativeFilePath().constData(),
qt_readlink(dotTrashDir.nativeFilePath()).constData());
error = QSystemError(ELOOP, QSystemError::StandardLibraryError);
}
} else if (genericTrashFd >= 0) {
QT_FSTAT(genericTrashFd, &st);
if ((st.st_mode & S_ISVTX) == 0) {
// we SHOULD report the failed check to the administrator
qCritical("Warning: '%s' doesn't have sticky bit set!",
dotTrashDir.nativeFilePath().constData());
error = QSystemError(EPERM, QSystemError::StandardLibraryError);
} else {
/*
"If the directory exists and passes the checks, a subdirectory of the
$topdir/.Trash directory is to be used as the user's trash directory
for this partition/device. The name of this subdirectory is the numeric
identifier of the current user ($topdir/.Trash/$uid).
When trashing a file, if this directory does not exist for the current user,
the implementation MUST immediately create it, without any warnings or
delays for the user."
*/
if (error = getTrashDir(genericTrashFd, userID, source, O_NOFOLLOW); error.ok()) {
// recreate the resulting path
trashPath = dotTrashDir.filePath() + u'/' + userID;
}
}
QT_CLOSE(genericTrashFd);
}
/*
Method 2:
"If an $topdir/.Trash directory is absent, an $topdir/.Trash-$uid directory is to be
used as the user's trash directory for this device/partition. [...] When trashing a
file, if an $topdir/.Trash-$uid directory does not exist, the implementation MUST
immediately create it, without any warnings or delays for the user."
*/
if (!isTrashDirOpen())
error = getTrashDir(AT_FDCWD, sourceStorage.rootPath() + dotTrash + u'-' + userID, source,
O_NOFOLLOW);
if (!isTrashDirOpen())
return error;
QByteArray relativePath = getRelativeFilePath(source, sourceStorage);
if (relativePath.isEmpty())
return QSystemError::stdError(errno);
encodedRelativeFilePath = relativePath.toPercentEncoding("/");
return QSystemError();
}
QSystemError openHomeTrashLocation(const QFileSystemEntry &source)
{
QString topDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
int openmode = 0; // do allow following symlinks
QSystemError error = getTrashDir(AT_FDCWD, topDir + "/Trash"_L1, source, openmode);
if (!error.ok())
return error;
// for the "home trash", we use the full, absolute path name
encodedRelativeFilePath = QFile::encodeName(source.filePath()).toPercentEncoding("/");
return QSystemError();
}
QSystemError findTrashFor(const QFileSystemEntry &source)
{
/*
First, try the standard Trash in $XDG_DATA_DIRS:
"Its name and location are $XDG_DATA_HOME/Trash"; $XDG_DATA_HOME is what
QStandardPaths returns for GenericDataLocation. If that doesn't exist, then
we are not running on a freedesktop.org-compliant environment, and give up.
*/
if (QSystemError error = openHomeTrashLocation(source); error.ok())
return QSystemError();
else if (error.errorCode != EXDEV)
return error;
// didn't work, try to find the trash outside the home filesystem
// (use source.path() in case the entry itself is a symlink to another fs)
const QStorageInfo sourceStorage(source.path());
if (!sourceStorage.isValid())
return QSystemError::stdError(ENODEV);
return openMountPointTrashLocation(source, sourceStorage);
}
};
} // unnamed namespace
//static
bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &source,
QFileSystemEntry &newLocation, QSystemError &error)
{
const QFileSystemEntry sourcePath = [&] {
if (QString path = source.filePath(); path.size() > 1 && path.endsWith(u'/')) {
path.chop(1);
return absoluteName(QFileSystemEntry(path));
}
return absoluteName(source);
}();
FreeDesktopTrashOperation op;
if (error = op.findTrashFor(sourcePath); !error.ok())
return false;
/*
"The $trash/files directory contains the files and directories that were trashed.
The names of files in this directory are to be determined by the implementation;
the only limitation is that they must be unique within the directory. Even if a
file with the same name and location gets trashed many times, each subsequent
trashing must not overwrite a previous copy."
We first try the unchanged base name, then try something different if it collides.
"The $trash/info directory contains an "information file" for every file and directory
in $trash/files. This file MUST have exactly the same name as the file or directory in
$trash/files, plus the extension ".trashinfo"
[...]
When trashing a file or directory, the implementation MUST create the corresponding
file in $trash/info first. Moreover, it MUST try to do this in an atomic fashion,
so that if two processes try to trash files with the same filename this will result
in two different trash files. On Unix-like systems this is done by generating a
filename, and then opening with O_EXCL. If that succeeds the creation was atomic
(at least on the same machine), if it fails you need to pick another filename."
*/
QString uniqueTrashedName = sourcePath.fileName();
if (!op.tryCreateInfoFile(uniqueTrashedName, error) && error.errorCode == EEXIST) {
// we'll use a counter, starting with the file's inode number to avoid
// collisions
qulonglong counter;
if (QT_STATBUF st; Q_LIKELY(QT_STAT(source.nativeFilePath(), &st) == 0)) {
counter = st.st_ino;
} else {
error = QSystemError(errno, QSystemError::StandardLibraryError);
return false;
}
QString uniqueTrashBase = std::move(uniqueTrashedName);
for (;;) {
uniqueTrashedName = QString::asprintf("%ls-%llu", qUtf16Printable(uniqueTrashBase),
counter++);
if (op.tryCreateInfoFile(uniqueTrashedName, error))
break;
if (error.errorCode != EEXIST)
return false;
};
}
QByteArray info =
"[Trash Info]\n"
"Path=" + op.encodedRelativeFilePath + "\n"
"DeletionDate=" + QDateTime::currentDateTime().toString(Qt::ISODate).toUtf8()
+ "\n";
if (QT_WRITE(op.infoFileFd, info.data(), info.size()) < 0) {
error = QSystemError(errno, QSystemError::StandardLibraryError);
return false;
}
/*
If we've already linked the file-to-be-trashed into the trash
directory, we know it's in the same mountpoint and we won't get ENOSPC
renaming the temporary file to the target name either.
*/
bool renamed;
if (op.tempTrashFileName.isEmpty()) {
/*
We did not get a link (we're trying to trash a directory or on a
filesystem that doesn't support hardlinking), so rename straight
from the original name. We might fail to rename if source and target
are on different file systems.
*/
renamed = renameat(AT_FDCWD, source.nativeFilePath(), op.filesDirFd,
QFile::encodeName(uniqueTrashedName)) == 0;
} else {
renamed = renameat(op.filesDirFd, op.tempTrashFileName, op.filesDirFd,
QFile::encodeName(uniqueTrashedName)) == 0;
if (renamed)
removeFile(sourcePath, error); // success, delete the original file
}
if (!renamed) {
error = QSystemError(errno, QSystemError::StandardLibraryError);
return false;
}
op.commit();
newLocation = QFileSystemEntry(op.trashPath + "/files/"_L1 + uniqueTrashedName);
return true;
}
#endif // !Q_OS_DARWIN && (!QT_BOOTSTRAPPED && AT_FDCWD && !Q_OS_ANDROID && QT_CONFIG(datestring))
//static
#if !defined(AT_FDCWD) || defined(Q_OS_QNX) || defined(Q_OS_VXWORKS)
// The implementation below requires the POSIX at-file functions. Without them,
// it's no better than the full path name solution in
// QDir::removeRecursively().
//
// The code below causes some unrelated files to disappear on QNX (we run our
// unit tests as root). On VxWorks, something doesn't go right and we fail to
// delete everything.
bool QFileSystemEngine::supportsRmdirRecursively() noexcept
{
return false;
}
//static
bool QFileSystemEngine::rmdirRecursively(const QFileSystemEntry &, QSystemError &error)
{
error = QSystemError(ENOSYS, QSystemError::StandardLibraryError);
return false;
}
#else
//static
bool QFileSystemEngine::supportsRmdirRecursively() noexcept
{
return true;
}
//static
bool QFileSystemEngine::rmdirRecursively(const QFileSystemEntry &entry, QSystemError &error)
{
Q_CHECK_FILE_NAME(entry, false);
# if defined(Q_OS_ANDROID) || defined(Q_OS_BSD4) || defined(Q_OS_LINUX)
// POSIX.1 says:
// "if any attempt is made to [...] modify the state of the associated
// description, other than by means of closedir(), readdir(), readdir_r(),
// rewinddir(), or seekdir(), the behavior is undefined".
// However, on these OSes we can openat() subdirectories using an iterated
// file descriptor.
static constexpr bool NeedsSecondFileDescriptor = false;
# else
static constexpr bool NeedsSecondFileDescriptor = true;
# endif
struct DirInfo {
DIR *dir;
int dirfd;
int parentFd; // parent's fd for unlinkat(parentFd, name.c_str(), AT_REMOVEDIR)
std::string name; // our entry name within parent
};
auto openDirectory = [](int parentFd, std::string name) {
constexpr int OpenMode = QT_OPEN_RDONLY | O_DIRECTORY | O_NOFOLLOW;
DirInfo di = { nullptr, -1, parentFd, std::move(name) };
di.dirfd = qt_safe_openat(parentFd, di.name.c_str(), OpenMode);
if (di.dirfd < 0)
return di;
// fdopendir takes ownership of dirfd if it succeeds
di.dir = fdopendir(di.dirfd);
if (!di.dir) {
QT_CLOSE(di.dirfd);
return di;
}
if constexpr (NeedsSecondFileDescriptor) {
di.dirfd = qt_safe_openat(parentFd, di.name.c_str(), OpenMode);
if (Q_UNLIKELY(di.dirfd < 0))
closedir(std::exchange(di.dir, nullptr));
}
return di;
};
QVarLengthArray<DirInfo, 16> stack;
if (DirInfo di = openDirectory(AT_FDCWD, entry.nativeFilePath().toStdString()); di.dir) {
stack.emplace_back(std::move(di));
} else {
if (errno == ENOENT)
return true;
error = QSystemError(errno, QSystemError::StandardLibraryError);
return false;
}
while (!stack.isEmpty()) {
DirInfo &top = stack.last();
if (QT_DIRENT *de = QT_READDIR(top.dir)) {
if (de->d_name[0] == '.') {
if (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))
continue;
}
QFileSystemMetaData meta;
meta.fillFromDirEnt(*de);
// If the entry is not known to be a directory, attempt to unlink it.
if (!meta.hasFlags(QFileSystemMetaData::DirectoryType) || !meta.isDirectory()) {
if (unlinkat(top.dirfd, de->d_name, 0) == 0)
continue; // removed a non-directory
// Couldn't unlink: it might be a directory
}
// Entry is a directory - open it and push onto the stack.
if (DirInfo di = openDirectory(top.dirfd, de->d_name); di.dir) {
// Parent entry stays on the stack below the child.
stack.emplace_back(std::move(di));
}
// If we can't open child, it will remain, causing the parent
// unlinkat() to fail with ENOTEMPTY.
} else {
// End of this directory - all children have been processed.
closedir(top.dir);
if constexpr (NeedsSecondFileDescriptor)
QT_CLOSE(top.dirfd);
// Now erase this entry in the parent
int r = unlinkat(top.parentFd, top.name.c_str(), AT_REMOVEDIR);
if (r < 0 && top.parentFd == AT_FDCWD && errno != ENOENT)
error = QSystemError(errno, QSystemError::StandardLibraryError);
stack.removeLast();
}
}
// True iff the root unlinkat() succeeded. Any upstream failure surfaces as
// ENOTEMPTY there.
return error.ok();
}
#endif // AT_FDCWD
//static
bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
{
Q_UNUSED(source);
Q_UNUSED(target);
error = QSystemError(ENOSYS, QSystemError::StandardLibraryError); //Function not implemented
return false;
}
//static
bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
{
QFileSystemEntry::NativePath srcPath = source.nativeFilePath();
QFileSystemEntry::NativePath tgtPath = target.nativeFilePath();
Q_CHECK_FILE_NAME(srcPath, false);
Q_CHECK_FILE_NAME(tgtPath, false);
#if defined(RENAME_NOREPLACE) && QT_CONFIG(renameat2)
if (renameat2(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_NOREPLACE) == 0)
return true;
// We can also get EINVAL for some non-local filesystems.
if (errno != EINVAL) {
error = QSystemError(errno, QSystemError::StandardLibraryError);
return false;
}
#endif
#if defined(Q_OS_DARWIN) && defined(RENAME_EXCL)
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 (SupportsHardlinking && ::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;
} else if (!SupportsHardlinking) {
// Since hard linking is not supported, check for target existence
// manually, mimicking what link(2) would have reported via EEXIST.
// If the target does not exist, fall through to ::rename() below.
if (::access(tgtPath, F_OK) == 0) {
errno = EEXIST;
} else {
// man 2 link on Linux has:
// EPERM The filesystem containing oldpath and newpath does not
// support the creation of hard links.
errno = EPERM;
}
}
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)
{
Q_CHECK_FILE_NAME(source, false);
Q_CHECK_FILE_NAME(target, false);
if (::rename(source.nativeFilePath().constData(), target.nativeFilePath().constData()) == 0)
return true;
error = QSystemError(errno, QSystemError::StandardLibraryError);
return false;
}
//static
bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError &error)
{
Q_CHECK_FILE_NAME(entry, false);
if (unlink(entry.nativeFilePath().constData()) == 0)
return true;
error = QSystemError(errno, QSystemError::StandardLibraryError);
return false;
}
//static
bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry,
QFile::Permissions permissions, QSystemError &error)
{
Q_CHECK_FILE_NAME(entry, false);
mode_t mode = QtPrivate::toMode_t(permissions);
bool success = ::chmod(entry.nativeFilePath().constData(), mode) == 0;
if (!success)
error = QSystemError(errno, QSystemError::StandardLibraryError);
return success;
}
//static
bool QFileSystemEngine::setPermissions(int fd, QFile::Permissions permissions, QSystemError &error)
{
mode_t mode = QtPrivate::toMode_t(permissions);
bool success = ::fchmod(fd, mode) == 0;
if (!success)
error = QSystemError(errno, QSystemError::StandardLibraryError);
return success;
}
//static
bool QFileSystemEngine::setFileTime(int fd, const QDateTime &newDate, QFile::FileTime time, QSystemError &error)
{
if (!newDate.isValid()
|| time == QFile::FileBirthTime || time == QFile::FileMetadataChangeTime) {
error = QSystemError(EINVAL, QSystemError::StandardLibraryError);
return false;
}
#if QT_CONFIG(futimens)
// UTIME_OMIT: leave file timestamp unchanged
struct timespec ts[2] = {{0, UTIME_OMIT}, {0, UTIME_OMIT}};
if (time == QFile::FileAccessTime || time == QFile::FileModificationTime) {
const int idx = time == QFile::FileAccessTime ? 0 : 1;
const std::chrono::milliseconds msecs{newDate.toMSecsSinceEpoch()};
ts[idx] = durationToTimespec(msecs);
}
if (futimens(fd, ts) == -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 = qEnvironmentVariable("HOME");
if (home.isEmpty())
home = rootPath();
return QDir::cleanPath(home);
}
QString QFileSystemEngine::rootPath()
{
return u"/"_s;
}
static constexpr QLatin1StringView nativeTempPath() noexcept
{
// _PATH_TMP usually ends in '/' and we don't want that
QLatin1StringView temp = _PATH_TMP ""_L1;
static_assert(_PATH_TMP[0] == '/', "_PATH_TMP needs to be absolute");
static_assert(_PATH_TMP[1] != '\0', "Are you really sure _PATH_TMP should be the root dir??");
if (temp.endsWith(u'/'))
temp.chop(1);
return temp;
}
QString QFileSystemEngine::tempPath()
{
#ifdef QT_UNIX_TEMP_PATH_OVERRIDE
return QT_UNIX_TEMP_PATH_OVERRIDE ""_L1;
#else
QString temp = qEnvironmentVariable("TMPDIR");
# if defined(Q_OS_DARWIN) && !defined(QT_BOOTSTRAPPED)
if (NSString *nsPath; temp.isEmpty() && (nsPath = NSTemporaryDirectory()))
temp = QString::fromCFString((CFStringRef)nsPath);
# endif
if (temp.isEmpty())
return nativeTempPath();
// the environment variable may also end in '/'
if (temp.size() > 1 && temp.endsWith(u'/'))
temp.chop(1);
QFileSystemEntry e(temp, QFileSystemEntry::FromInternalPath{});
return QFileSystemEngine::absoluteName(e).filePath();
#endif
}
bool QFileSystemEngine::setCurrentPath(const QFileSystemEntry &path)
{
int r;
r = QT_CHDIR(path.nativeFilePath().constData());
return r >= 0;
}
QFileSystemEntry QFileSystemEngine::currentPath()
{
QFileSystemEntry result;
#if defined(__GLIBC__) && !defined(PATH_MAX)
char *currentName = ::get_current_dir_name();
if (currentName) {
result = QFileSystemEntry(QByteArray(currentName), QFileSystemEntry::FromNativePath());
::free(currentName);
}
#else
char currentName[PATH_MAX+1];
if (::getcwd(currentName, PATH_MAX)) {
#if defined(Q_OS_VXWORKS) && defined(VXWORKS_VXSIM)
QByteArray dir(currentName);
if (dir.indexOf(':') < dir.indexOf('/'))
dir.remove(0, dir.indexOf(':')+1);
qstrncpy(currentName, dir.constData(), PATH_MAX);
#endif
result = QFileSystemEntry(QByteArray(currentName), QFileSystemEntry::FromNativePath());
}
# if defined(QT_DEBUG)
if (result.isEmpty())
qWarning("QFileSystemEngine::currentPath: getcwd() failed");
# endif
#endif
return result;
}
bool QFileSystemEngine::isCaseSensitive(const QFileSystemEntry &entry, QFileSystemMetaData &metaData)
{
#if defined(Q_OS_DARWIN)
if (!metaData.hasFlags(QFileSystemMetaData::CaseSensitive))
fillMetaData(entry, metaData, QFileSystemMetaData::CaseSensitive);
return metaData.entryFlags.testFlag(QFileSystemMetaData::CaseSensitive);
#else
Q_UNUSED(entry);
Q_UNUSED(metaData);
// FIXME: This may not be accurate for all file systems (QTBUG-28246)
return true;
#endif
}
QT_END_NAMESPACE