2022-05-10 10:06:48 +00:00
|
|
|
|
// 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
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
|
|
|
|
#include "qplatformdefs.h"
|
|
|
|
|
#include "qfilesystemengine_p.h"
|
|
|
|
|
#include "qfile.h"
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
#include "qstorageinfo.h"
|
2022-12-27 02:57:06 +00:00
|
|
|
|
#include "qurl.h"
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
2017-02-24 23:06:21 +00:00
|
|
|
|
#include <QtCore/qoperatingsystemversion.h>
|
2017-06-30 20:30:52 +00:00
|
|
|
|
#include <QtCore/private/qcore_unix_p.h>
|
2021-10-26 12:52:36 +00:00
|
|
|
|
#include <QtCore/private/qfiledevice_p.h>
|
2023-07-24 07:07:42 +00:00
|
|
|
|
#include <QtCore/private/qfunctions_p.h>
|
2011-04-27 10:05:43 +00:00
|
|
|
|
#include <QtCore/qvarlengtharray.h>
|
2020-05-04 07:44:15 +00:00
|
|
|
|
#ifndef QT_BOOTSTRAPPED
|
|
|
|
|
# include <QtCore/qstandardpaths.h>
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
# include <QtCore/private/qtemporaryfile_p.h>
|
2020-05-04 07:44:15 +00:00
|
|
|
|
#endif // QT_BOOTSTRAPPED
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
2023-12-20 01:22:35 +00:00
|
|
|
|
#include <grp.h>
|
2017-09-18 08:47:46 +00:00
|
|
|
|
#include <pwd.h>
|
2011-04-27 10:05:43 +00:00
|
|
|
|
#include <stdlib.h> // for realpath()
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
|
2022-10-30 08:54:26 +00:00
|
|
|
|
#include <chrono>
|
2022-02-14 09:29:24 +00:00
|
|
|
|
#include <memory> // for std::unique_ptr
|
|
|
|
|
|
2019-12-08 22:47:10 +00:00
|
|
|
|
#if __has_include(<paths.h>)
|
2017-12-10 06:54:04 +00:00
|
|
|
|
# include <paths.h>
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef _PATH_TMP // from <paths.h>
|
|
|
|
|
# define _PATH_TMP "/tmp"
|
|
|
|
|
#endif
|
|
|
|
|
|
2024-08-06 21:14:49 +00:00
|
|
|
|
#if __has_include(<sys/disk.h>)
|
|
|
|
|
// BSDs (including Apple Darwin)
|
|
|
|
|
# include <sys/disk.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2023-03-17 16:53:51 +00:00
|
|
|
|
#if defined(Q_OS_DARWIN)
|
2011-04-27 10:05:43 +00:00
|
|
|
|
# include <QtCore/private/qcore_mac_p.h>
|
2013-09-18 20:36:51 +00:00
|
|
|
|
# include <CoreFoundation/CFBundle.h>
|
2024-02-12 20:11:34 +00:00
|
|
|
|
# include <UniformTypeIdentifiers/UTType.h>
|
|
|
|
|
# include <UniformTypeIdentifiers/UTCoreTypes.h>
|
|
|
|
|
# include <Foundation/Foundation.h>
|
|
|
|
|
# include <copyfile.h>
|
2011-04-27 10:05:43 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
2020-04-03 10:45:36 +00:00
|
|
|
|
#ifdef Q_OS_MACOS
|
2015-07-14 03:16:44 +00:00
|
|
|
|
#include <CoreServices/CoreServices.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2016-02-16 14:29:59 +00:00
|
|
|
|
#if defined(QT_PLATFORM_UIKIT)
|
2015-07-14 03:16:44 +00:00
|
|
|
|
#include <MobileCoreServices/MobileCoreServices.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2017-06-29 21:27:48 +00:00
|
|
|
|
#if defined(Q_OS_LINUX)
|
2017-07-16 08:19:23 +00:00
|
|
|
|
# include <sys/ioctl.h>
|
|
|
|
|
# include <sys/sendfile.h>
|
2017-06-29 21:27:48 +00:00
|
|
|
|
# include <linux/fs.h>
|
|
|
|
|
|
2017-07-16 08:19:23 +00:00
|
|
|
|
// in case linux/fs.h is too old and doesn't define it:
|
|
|
|
|
#ifndef FICLONE
|
|
|
|
|
# define FICLONE _IOW(0x94, 9, int)
|
|
|
|
|
#endif
|
2017-06-29 21:27:48 +00:00
|
|
|
|
#endif
|
2017-07-16 08:19:23 +00:00
|
|
|
|
|
2025-03-20 09:08:45 +00:00
|
|
|
|
#if defined(Q_OS_VXWORKS)
|
|
|
|
|
# include <sys/statfs.h>
|
|
|
|
|
# if __has_include(<dosFsLib.h>)
|
|
|
|
|
# include <dosFsLib.h>
|
|
|
|
|
# endif
|
|
|
|
|
#endif
|
|
|
|
|
|
2018-10-17 14:59:25 +00:00
|
|
|
|
#if defined(Q_OS_ANDROID)
|
|
|
|
|
// statx() is disabled on Android because quite a few systems
|
2018-01-07 21:32:35 +00:00
|
|
|
|
// 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.
|
2018-10-17 14:59:25 +00:00
|
|
|
|
# undef STATX_BASIC_STATS
|
2017-06-29 21:27:48 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
2018-01-07 21:32:35 +00:00
|
|
|
|
#ifndef STATX_ALL
|
|
|
|
|
struct statx { mode_t stx_mode; }; // dummy
|
2017-11-24 09:48:36 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
|
|
2022-03-09 15:34:49 +00:00
|
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
|
|
2024-09-25 18:07:46 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-02 03:38:20 +00:00
|
|
|
|
enum {
|
|
|
|
|
#ifdef Q_OS_ANDROID
|
|
|
|
|
// On Android, 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
|
|
|
|
|
};
|
|
|
|
|
|
2015-07-14 03:16:44 +00:00
|
|
|
|
#if defined(Q_OS_DARWIN)
|
|
|
|
|
static inline bool hasResourcePropertyFlag(const QFileSystemMetaData &data,
|
|
|
|
|
const QFileSystemEntry &entry,
|
|
|
|
|
CFStringRef key)
|
2011-04-27 10:05:43 +00:00
|
|
|
|
{
|
2015-07-14 03:16:44 +00:00
|
|
|
|
QCFString path = CFStringCreateWithFileSystemRepresentation(0,
|
|
|
|
|
entry.nativeFilePath().constData());
|
|
|
|
|
if (!path)
|
2011-04-27 10:05:43 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
2015-07-14 03:16:44 +00:00
|
|
|
|
QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, path, kCFURLPOSIXPathStyle,
|
|
|
|
|
data.hasFlags(QFileSystemMetaData::DirectoryType));
|
|
|
|
|
if (!url)
|
2011-04-27 10:05:43 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
2015-07-14 03:16:44 +00:00
|
|
|
|
CFBooleanRef value;
|
|
|
|
|
if (CFURLCopyResourcePropertyForKey(url, key, &value, NULL)) {
|
|
|
|
|
if (value == kCFBooleanTrue)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
2011-04-27 10:05:43 +00:00
|
|
|
|
}
|
2013-09-18 20:36:51 +00:00
|
|
|
|
|
|
|
|
|
static bool isPackage(const QFileSystemMetaData &data, const QFileSystemEntry &entry)
|
|
|
|
|
{
|
|
|
|
|
if (!data.isDirectory())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
QFileInfo info(entry.filePath());
|
|
|
|
|
QString suffix = info.suffix();
|
|
|
|
|
|
|
|
|
|
if (suffix.length() > 0) {
|
2024-02-12 20:11:34 +00:00
|
|
|
|
// First step: is it a bundle?
|
|
|
|
|
const auto *utType = [UTType typeWithFilenameExtension:suffix.toNSString()];
|
|
|
|
|
if ([utType conformsToType:UTTypeBundle])
|
2014-01-29 07:38:40 +00:00
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
// Second step: check if an application knows the package type
|
2016-10-03 17:34:21 +00:00
|
|
|
|
QCFType<CFStringRef> path = entry.filePath().toCFString();
|
2014-01-29 07:38:40 +00:00
|
|
|
|
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;
|
|
|
|
|
|
2017-01-05 22:14:41 +00:00
|
|
|
|
#ifdef Q_OS_MACOS
|
2014-01-29 07:38:40 +00:00
|
|
|
|
// Find if an application other than Finder claims to know how to handle the package
|
2017-01-05 22:14:41 +00:00
|
|
|
|
QCFType<CFURLRef> application = LSCopyDefaultApplicationURLForURL(url,
|
|
|
|
|
kLSRolesEditor | kLSRolesViewer, nullptr);
|
2013-09-18 20:36:51 +00:00
|
|
|
|
|
|
|
|
|
if (application) {
|
2014-01-28 11:53:10 +00:00
|
|
|
|
QCFType<CFBundleRef> bundle = CFBundleCreate(kCFAllocatorDefault, application);
|
|
|
|
|
CFStringRef identifier = CFBundleGetIdentifier(bundle);
|
2016-10-03 17:34:21 +00:00
|
|
|
|
QString applicationId = QString::fromCFString(identifier);
|
2022-03-09 15:34:49 +00:00
|
|
|
|
if (applicationId != "com.apple.finder"_L1)
|
2013-09-18 20:36:51 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
2015-07-14 03:16:44 +00:00
|
|
|
|
#endif
|
2013-09-18 20:36:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Third step: check if the directory has the package bit set
|
2015-07-14 03:16:44 +00:00
|
|
|
|
return hasResourcePropertyFlag(data, entry, kCFURLIsPackageKey);
|
2011-04-27 10:05:43 +00:00
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2024-06-11 14:13:07 +00:00
|
|
|
|
#ifdef Q_OS_VXWORKS
|
|
|
|
|
static inline void forceRequestedPermissionsOnVxWorks(QByteArray dirName, mode_t mode)
|
|
|
|
|
{
|
|
|
|
|
if (mode == 0) {
|
|
|
|
|
chmod(dirName, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2017-07-02 17:28:39 +00:00
|
|
|
|
namespace {
|
|
|
|
|
namespace GetFileTimes {
|
2022-10-30 08:54:26 +00:00
|
|
|
|
qint64 time_t_toMsecs(time_t t)
|
2017-07-02 18:46:43 +00:00
|
|
|
|
{
|
2022-10-30 08:54:26 +00:00
|
|
|
|
using namespace std::chrono;
|
|
|
|
|
return milliseconds{seconds{t}}.count();
|
2017-07-02 17:28:39 +00:00
|
|
|
|
}
|
2017-07-02 18:46:43 +00:00
|
|
|
|
|
|
|
|
|
// fallback set
|
2022-10-30 08:54:26 +00:00
|
|
|
|
[[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();
|
|
|
|
|
}
|
2017-07-02 18:46:43 +00:00
|
|
|
|
|
|
|
|
|
// Xtim, POSIX.1-2008
|
|
|
|
|
template <typename T>
|
2020-10-02 08:47:47 +00:00
|
|
|
|
[[maybe_unused]] static typename std::enable_if<(&T::st_atim, true), qint64>::type
|
2017-07-02 18:46:43 +00:00
|
|
|
|
atime(const T &statBuffer, int)
|
|
|
|
|
{ return timespecToMSecs(statBuffer.st_atim); }
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
2020-10-02 08:47:47 +00:00
|
|
|
|
[[maybe_unused]] static typename std::enable_if<(&T::st_birthtim, true), qint64>::type
|
2017-07-02 18:46:43 +00:00
|
|
|
|
birthtime(const T &statBuffer, int)
|
|
|
|
|
{ return timespecToMSecs(statBuffer.st_birthtim); }
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
2020-10-02 08:47:47 +00:00
|
|
|
|
[[maybe_unused]] static typename std::enable_if<(&T::st_ctim, true), qint64>::type
|
2017-07-02 18:46:43 +00:00
|
|
|
|
ctime(const T &statBuffer, int)
|
|
|
|
|
{ return timespecToMSecs(statBuffer.st_ctim); }
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
2020-10-02 08:47:47 +00:00
|
|
|
|
[[maybe_unused]] static typename std::enable_if<(&T::st_mtim, true), qint64>::type
|
2017-07-02 18:46:43 +00:00
|
|
|
|
mtime(const T &statBuffer, int)
|
|
|
|
|
{ return timespecToMSecs(statBuffer.st_mtim); }
|
|
|
|
|
|
|
|
|
|
#ifndef st_mtimespec
|
|
|
|
|
// Xtimespec
|
|
|
|
|
template <typename T>
|
2020-10-02 08:47:47 +00:00
|
|
|
|
[[maybe_unused]] static typename std::enable_if<(&T::st_atimespec, true), qint64>::type
|
2017-07-02 18:46:43 +00:00
|
|
|
|
atime(const T &statBuffer, int)
|
|
|
|
|
{ return timespecToMSecs(statBuffer.st_atimespec); }
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
2020-10-02 08:47:47 +00:00
|
|
|
|
[[maybe_unused]] static typename std::enable_if<(&T::st_birthtimespec, true), qint64>::type
|
2017-07-02 18:46:43 +00:00
|
|
|
|
birthtime(const T &statBuffer, int)
|
|
|
|
|
{ return timespecToMSecs(statBuffer.st_birthtimespec); }
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
2020-10-02 08:47:47 +00:00
|
|
|
|
[[maybe_unused]] static typename std::enable_if<(&T::st_ctimespec, true), qint64>::type
|
2017-07-02 18:46:43 +00:00
|
|
|
|
ctime(const T &statBuffer, int)
|
|
|
|
|
{ return timespecToMSecs(statBuffer.st_ctimespec); }
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
2020-10-02 08:47:47 +00:00
|
|
|
|
[[maybe_unused]] static typename std::enable_if<(&T::st_mtimespec, true), qint64>::type
|
2017-07-02 18:46:43 +00:00
|
|
|
|
mtime(const T &statBuffer, int)
|
|
|
|
|
{ return timespecToMSecs(statBuffer.st_mtimespec); }
|
2017-07-02 17:28:39 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
2018-11-11 20:44:27 +00:00
|
|
|
|
#if !defined(st_mtimensec) && !defined(__alpha__)
|
2017-07-02 18:46:43 +00:00
|
|
|
|
// Xtimensec
|
|
|
|
|
template <typename T>
|
2020-10-02 08:47:47 +00:00
|
|
|
|
[[maybe_unused]] static typename std::enable_if<(&T::st_atimensec, true), qint64>::type
|
2017-07-02 18:46:43 +00:00
|
|
|
|
atime(const T &statBuffer, int)
|
|
|
|
|
{ return statBuffer.st_atime * Q_INT64_C(1000) + statBuffer.st_atimensec / 1000000; }
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
2020-10-02 08:47:47 +00:00
|
|
|
|
[[maybe_unused]] static typename std::enable_if<(&T::st_birthtimensec, true), qint64>::type
|
2017-07-02 18:46:43 +00:00
|
|
|
|
birthtime(const T &statBuffer, int)
|
|
|
|
|
{ return statBuffer.st_birthtime * Q_INT64_C(1000) + statBuffer.st_birthtimensec / 1000000; }
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
2020-10-02 08:47:47 +00:00
|
|
|
|
[[maybe_unused]] static typename std::enable_if<(&T::st_ctimensec, true), qint64>::type
|
2017-07-02 18:46:43 +00:00
|
|
|
|
ctime(const T &statBuffer, int)
|
|
|
|
|
{ return statBuffer.st_ctime * Q_INT64_C(1000) + statBuffer.st_ctimensec / 1000000; }
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
2020-10-02 08:47:47 +00:00
|
|
|
|
[[maybe_unused]] static typename std::enable_if<(&T::st_mtimensec, true), qint64>::type
|
2017-07-02 18:46:43 +00:00
|
|
|
|
mtime(const T &statBuffer, int)
|
|
|
|
|
{ return statBuffer.st_mtime * Q_INT64_C(1000) + statBuffer.st_mtimensec / 1000000; }
|
2017-09-07 11:01:00 +00:00
|
|
|
|
#endif
|
|
|
|
|
} // namespace GetFileTimes
|
|
|
|
|
} // unnamed namespace
|
2017-07-02 18:46:43 +00:00
|
|
|
|
|
2025-03-24 18:22:53 +00:00
|
|
|
|
// 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
|
|
|
|
|
#ifdef UF_HIDDEN
|
|
|
|
|
if (attributes & UF_HIDDEN)
|
|
|
|
|
entryFlags |= QFileSystemMetaData::HiddenAttribute;
|
2025-03-20 09:08:45 +00:00
|
|
|
|
#elif 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;
|
|
|
|
|
}
|
2025-03-24 18:22:53 +00:00
|
|
|
|
#endif
|
|
|
|
|
return entryFlags;
|
|
|
|
|
}
|
|
|
|
|
|
Add support for statx(2) on Linux
This system call, new in Linux 4.11, gives us the file birth time. It's
also extensible, representing the fourth generation of stat(2) on Linux
(the original sys_stat(), sys_newstat(), sys_stat64() and now
sys_statx()), not to be confused with glibc's __xstat function, which
wraps a call to stat64. Anyway, the new one is designed to be extensible.
Now we get birth times on ext[34] on Linux too:
Name: .
Path: . (/home/tjmaciei/src/qt)
Size: 4096 Type: Directory
Attrs: readable writable executable hidden nativepath
Mode: drwxr-xr-x
Owner: tjmaciei (1000) Group: users (100)
Access: 2017-07-02T14:47:49.608
Birth: 2016-05-02T13:20:33.097
Change: 2017-07-01T13:37:08.737
Modified: 2017-07-01T13:37:08.737
It's not supported in any other filesystems I have (Linux sources show
xfs has the feature too). Even on ext4, it depends on whether the
filesystem was created with 256-byte inodes, which my /boot fs wasn't.
Change-Id: I8d96dea9955d4c749b99fffd14cda23ed60d5e72
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
2017-07-02 21:57:21 +00:00
|
|
|
|
#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;
|
2021-03-25 16:08:00 +00:00
|
|
|
|
int ret = statx(fd, pathname, flags | AT_NO_AUTOMOUNT, mask, statxBuffer);
|
Add support for statx(2) on Linux
This system call, new in Linux 4.11, gives us the file birth time. It's
also extensible, representing the fourth generation of stat(2) on Linux
(the original sys_stat(), sys_newstat(), sys_stat64() and now
sys_statx()), not to be confused with glibc's __xstat function, which
wraps a call to stat64. Anyway, the new one is designed to be extensible.
Now we get birth times on ext[34] on Linux too:
Name: .
Path: . (/home/tjmaciei/src/qt)
Size: 4096 Type: Directory
Attrs: readable writable executable hidden nativepath
Mode: drwxr-xr-x
Owner: tjmaciei (1000) Group: users (100)
Access: 2017-07-02T14:47:49.608
Birth: 2016-05-02T13:20:33.097
Change: 2017-07-01T13:37:08.737
Modified: 2017-07-01T13:37:08.737
It's not supported in any other filesystems I have (Linux sources show
xfs has the feature too). Even on ext4, it depends on whether the
filesystem was created with 256-byte inodes, which my /boot fs wasn't.
Change-Id: I8d96dea9955d4c749b99fffd14cda23ed60d5e72
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
2017-07-02 21:57:21 +00:00
|
|
|
|
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
|
2025-03-24 18:22:53 +00:00
|
|
|
|
MetaDataFlags flags = flagsFromStMode(statxBuffer.stx_mode, statxBuffer.stx_attributes);
|
|
|
|
|
entryFlags |= flags;
|
|
|
|
|
knownFlagsMask |= flags | PosixStatFlags;
|
Add support for statx(2) on Linux
This system call, new in Linux 4.11, gives us the file birth time. It's
also extensible, representing the fourth generation of stat(2) on Linux
(the original sys_stat(), sys_newstat(), sys_stat64() and now
sys_statx()), not to be confused with glibc's __xstat function, which
wraps a call to stat64. Anyway, the new one is designed to be extensible.
Now we get birth times on ext[34] on Linux too:
Name: .
Path: . (/home/tjmaciei/src/qt)
Size: 4096 Type: Directory
Attrs: readable writable executable hidden nativepath
Mode: drwxr-xr-x
Owner: tjmaciei (1000) Group: users (100)
Access: 2017-07-02T14:47:49.608
Birth: 2016-05-02T13:20:33.097
Change: 2017-07-01T13:37:08.737
Modified: 2017-07-01T13:37:08.737
It's not supported in any other filesystems I have (Linux sources show
xfs has the feature too). Even on ext4, it depends on whether the
filesystem was created with 256-byte inodes, which my /boot fs wasn't.
Change-Id: I8d96dea9955d4c749b99fffd14cda23ed60d5e72
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
2017-07-02 21:57:21 +00:00
|
|
|
|
|
|
|
|
|
// Attributes
|
|
|
|
|
if (statxBuffer.stx_nlink == 0)
|
|
|
|
|
entryFlags |= QFileSystemMetaData::WasDeletedAttribute;
|
|
|
|
|
size_ = qint64(statxBuffer.stx_size);
|
|
|
|
|
|
|
|
|
|
// Times
|
2022-10-30 08:54:26 +00:00
|
|
|
|
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;
|
Add support for statx(2) on Linux
This system call, new in Linux 4.11, gives us the file birth time. It's
also extensible, representing the fourth generation of stat(2) on Linux
(the original sys_stat(), sys_newstat(), sys_stat64() and now
sys_statx()), not to be confused with glibc's __xstat function, which
wraps a call to stat64. Anyway, the new one is designed to be extensible.
Now we get birth times on ext[34] on Linux too:
Name: .
Path: . (/home/tjmaciei/src/qt)
Size: 4096 Type: Directory
Attrs: readable writable executable hidden nativepath
Mode: drwxr-xr-x
Owner: tjmaciei (1000) Group: users (100)
Access: 2017-07-02T14:47:49.608
Birth: 2016-05-02T13:20:33.097
Change: 2017-07-01T13:37:08.737
Modified: 2017-07-01T13:37:08.737
It's not supported in any other filesystems I have (Linux sources show
xfs has the feature too). Even on ext4, it depends on whether the
filesystem was created with 256-byte inodes, which my /boot fs wasn't.
Change-Id: I8d96dea9955d4c749b99fffd14cda23ed60d5e72
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
2017-07-02 21:57:21 +00:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2017-07-02 18:20:54 +00:00
|
|
|
|
//static
|
|
|
|
|
bool QFileSystemEngine::fillMetaData(int fd, QFileSystemMetaData &data)
|
|
|
|
|
{
|
2024-08-06 21:14:49 +00:00
|
|
|
|
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
|
|
|
|
|
// http://fxr.watson.org/fxr/source/compat/linux/linux_ioctl.c?v=FREEBSD-13-STABLE#L282
|
|
|
|
|
// 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
|
|
|
|
|
};
|
2017-07-02 18:20:54 +00:00
|
|
|
|
data.entryFlags &= ~QFileSystemMetaData::PosixStatFlags;
|
|
|
|
|
data.knownFlagsMask |= QFileSystemMetaData::PosixStatFlags;
|
|
|
|
|
|
2021-10-08 07:47:46 +00:00
|
|
|
|
struct statx statxBuffer;
|
Add support for statx(2) on Linux
This system call, new in Linux 4.11, gives us the file birth time. It's
also extensible, representing the fourth generation of stat(2) on Linux
(the original sys_stat(), sys_newstat(), sys_stat64() and now
sys_statx()), not to be confused with glibc's __xstat function, which
wraps a call to stat64. Anyway, the new one is designed to be extensible.
Now we get birth times on ext[34] on Linux too:
Name: .
Path: . (/home/tjmaciei/src/qt)
Size: 4096 Type: Directory
Attrs: readable writable executable hidden nativepath
Mode: drwxr-xr-x
Owner: tjmaciei (1000) Group: users (100)
Access: 2017-07-02T14:47:49.608
Birth: 2016-05-02T13:20:33.097
Change: 2017-07-01T13:37:08.737
Modified: 2017-07-01T13:37:08.737
It's not supported in any other filesystems I have (Linux sources show
xfs has the feature too). Even on ext4, it depends on whether the
filesystem was created with 256-byte inodes, which my /boot fs wasn't.
Change-Id: I8d96dea9955d4c749b99fffd14cda23ed60d5e72
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
2017-07-02 21:57:21 +00:00
|
|
|
|
|
|
|
|
|
int ret = qt_fstatx(fd, &statxBuffer);
|
|
|
|
|
if (ret != -ENOSYS) {
|
2017-09-25 18:40:33 +00:00
|
|
|
|
if (ret == 0) {
|
|
|
|
|
data.fillFromStatxBuf(statxBuffer);
|
2024-08-06 21:14:49 +00:00
|
|
|
|
getSizeForBlockDev(statxBuffer.stx_mode);
|
2017-09-25 18:40:33 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
Add support for statx(2) on Linux
This system call, new in Linux 4.11, gives us the file birth time. It's
also extensible, representing the fourth generation of stat(2) on Linux
(the original sys_stat(), sys_newstat(), sys_stat64() and now
sys_statx()), not to be confused with glibc's __xstat function, which
wraps a call to stat64. Anyway, the new one is designed to be extensible.
Now we get birth times on ext[34] on Linux too:
Name: .
Path: . (/home/tjmaciei/src/qt)
Size: 4096 Type: Directory
Attrs: readable writable executable hidden nativepath
Mode: drwxr-xr-x
Owner: tjmaciei (1000) Group: users (100)
Access: 2017-07-02T14:47:49.608
Birth: 2016-05-02T13:20:33.097
Change: 2017-07-01T13:37:08.737
Modified: 2017-07-01T13:37:08.737
It's not supported in any other filesystems I have (Linux sources show
xfs has the feature too). Even on ext4, it depends on whether the
filesystem was created with 256-byte inodes, which my /boot fs wasn't.
Change-Id: I8d96dea9955d4c749b99fffd14cda23ed60d5e72
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
2017-07-02 21:57:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-08 07:47:46 +00:00
|
|
|
|
QT_STATBUF statBuffer;
|
|
|
|
|
|
2017-07-02 18:20:54 +00:00
|
|
|
|
if (QT_FSTAT(fd, &statBuffer) == 0) {
|
|
|
|
|
data.fillFromStatBuf(statBuffer);
|
2024-08-06 21:14:49 +00:00
|
|
|
|
getSizeForBlockDev(statBuffer.st_mode);
|
2017-07-02 18:20:54 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2025-03-24 18:22:53 +00:00
|
|
|
|
quint64 attributes = 0;
|
|
|
|
|
#if defined(UF_SETTABLE) // BSDs (incl. Darwin)
|
|
|
|
|
attributes = statBuffer.st_flags;
|
2025-03-20 09:08:45 +00:00
|
|
|
|
#elif defined(Q_OS_VXWORKS) && __has_include(<dosFsLib.h>)
|
|
|
|
|
attributes = statBuffer.st_attrib;
|
2025-03-24 18:22:53 +00:00
|
|
|
|
#endif
|
|
|
|
|
// Permissions
|
|
|
|
|
MetaDataFlags flags = flagsFromStMode(statBuffer.st_mode, attributes);
|
|
|
|
|
entryFlags |= flags;
|
|
|
|
|
knownFlagsMask |= flags | PosixStatFlags;
|
2017-07-02 18:20:54 +00:00
|
|
|
|
|
|
|
|
|
// Attributes
|
|
|
|
|
if (statBuffer.st_nlink == 0)
|
|
|
|
|
entryFlags |= QFileSystemMetaData::WasDeletedAttribute;
|
|
|
|
|
size_ = statBuffer.st_size;
|
|
|
|
|
|
|
|
|
|
// Times
|
2017-07-02 18:46:43 +00:00
|
|
|
|
accessTime_ = GetFileTimes::atime(statBuffer, 0);
|
|
|
|
|
birthTime_ = GetFileTimes::birthtime(statBuffer, 0);
|
|
|
|
|
metadataChangeTime_ = GetFileTimes::ctime(statBuffer, 0);
|
|
|
|
|
modificationTime_ = GetFileTimes::mtime(statBuffer, 0);
|
|
|
|
|
|
2017-07-02 18:20:54 +00:00
|
|
|
|
userId_ = statBuffer.st_uid;
|
|
|
|
|
groupId_ = statBuffer.st_gid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QFileSystemMetaData::fillFromDirEnt(const QT_DIRENT &entry)
|
|
|
|
|
{
|
|
|
|
|
#if defined(_DEXTRA_FIRST)
|
2020-09-11 20:55:05 +00:00
|
|
|
|
knownFlagsMask = {};
|
|
|
|
|
entryFlags = {};
|
2017-07-02 18:20:54 +00:00
|
|
|
|
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
|
2020-06-27 12:18:09 +00:00
|
|
|
|
Q_UNUSED(entry);
|
2017-07-02 18:20:54 +00:00
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
|
//static
|
|
|
|
|
QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data)
|
|
|
|
|
{
|
2019-09-04 13:26:10 +00:00
|
|
|
|
Q_CHECK_FILE_NAME(link, link);
|
2017-06-29 18:16:04 +00:00
|
|
|
|
|
2017-06-30 20:30:52 +00:00
|
|
|
|
QByteArray s = qt_readlink(link.nativeFilePath().constData());
|
Port from container.count()/length() to size()
This is semantic patch using ClangTidyTransformator:
auto QtContainerClass = expr(hasType(namedDecl(hasAnyName(<classes>)))).bind(o)
makeRule(cxxMemberCallExpr(on(QtContainerClass),
callee(cxxMethodDecl(hasAnyName({"count", "length"),
parameterCountIs(0))))),
changeTo(cat(access(o, cat("size"), "()"))),
cat("use 'size()' instead of 'count()/length()'"))
a.k.a qt-port-to-std-compatible-api with config Scope: 'Container'.
<classes> are:
// sequential:
"QByteArray",
"QList",
"QQueue",
"QStack",
"QString",
"QVarLengthArray",
"QVector",
// associative:
"QHash",
"QMultiHash",
"QMap",
"QMultiMap",
"QSet",
// Qt has no QMultiSet
Change-Id: Ibe8837be96e8d30d1846881ecd65180c1bc459af
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
2022-09-30 12:09:04 +00:00
|
|
|
|
if (s.size() > 0) {
|
2011-04-27 10:05:43 +00:00
|
|
|
|
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();
|
2022-03-09 15:34:49 +00:00
|
|
|
|
if (!ret.isEmpty() && !ret.endsWith(u'/'))
|
|
|
|
|
ret += u'/';
|
2011-04-27 10:05:43 +00:00
|
|
|
|
}
|
2017-06-30 20:30:52 +00:00
|
|
|
|
ret += QFile::decodeName(s);
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
2022-03-09 15:34:49 +00:00
|
|
|
|
if (!ret.startsWith(u'/'))
|
|
|
|
|
ret.prepend(absoluteName(link).path() + u'/');
|
2011-04-27 10:05:43 +00:00
|
|
|
|
ret = QDir::cleanPath(ret);
|
2022-03-09 15:34:49 +00:00
|
|
|
|
if (ret.size() > 1 && ret.endsWith(u'/'))
|
2011-04-27 10:05:43 +00:00
|
|
|
|
ret.chop(1);
|
|
|
|
|
return QFileSystemEntry(ret);
|
|
|
|
|
}
|
2015-07-14 03:16:44 +00:00
|
|
|
|
#if defined(Q_OS_DARWIN)
|
2011-04-27 10:05:43 +00:00
|
|
|
|
{
|
2015-07-14 03:16:44 +00:00
|
|
|
|
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();
|
|
|
|
|
|
2016-10-03 17:34:21 +00:00
|
|
|
|
return QFileSystemEntry(QString::fromCFString(cfstr));
|
2011-04-27 10:05:43 +00:00
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
return QFileSystemEntry();
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-16 05:17:39 +00:00
|
|
|
|
//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);
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
|
//static
|
|
|
|
|
QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data)
|
|
|
|
|
{
|
2019-09-04 13:26:10 +00:00
|
|
|
|
Q_CHECK_FILE_NAME(entry, entry);
|
2024-05-09 17:24:02 +00:00
|
|
|
|
char *resolved_name = nullptr;
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
2024-05-09 17:24:02 +00:00
|
|
|
|
#ifdef PATH_MAX
|
|
|
|
|
// use the stack to avoid the overhead of memory allocation
|
2022-02-14 09:29:24 +00:00
|
|
|
|
char stack_result[PATH_MAX + 1];
|
|
|
|
|
#else
|
2024-05-09 17:24:02 +00:00
|
|
|
|
// system with unlimited file paths -> must use heap
|
2022-02-14 09:29:24 +00:00
|
|
|
|
std::nullptr_t stack_result = nullptr;
|
2024-05-09 17:24:02 +00:00
|
|
|
|
auto freer = qScopeGuard([&] { free(resolved_name); });
|
|
|
|
|
#endif
|
|
|
|
|
|
2015-03-11 13:38:59 +00:00
|
|
|
|
# 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.
|
2015-01-08 09:29:12 +00:00
|
|
|
|
if (!data.hasFlags(QFileSystemMetaData::ExistsAttribute))
|
|
|
|
|
fillMetaData(entry, data, QFileSystemMetaData::ExistsAttribute);
|
|
|
|
|
|
2022-02-14 09:29:24 +00:00
|
|
|
|
if (!data.exists())
|
2015-01-08 09:29:12 +00:00
|
|
|
|
errno = ENOENT;
|
2022-02-14 09:29:24 +00:00
|
|
|
|
else
|
2024-05-09 17:24:02 +00:00
|
|
|
|
resolved_name = realpath(entry.nativeFilePath().constData(), stack_result);
|
2011-04-27 10:05:43 +00:00
|
|
|
|
# else
|
2024-05-09 17:24:02 +00:00
|
|
|
|
resolved_name = realpath(entry.nativeFilePath().constData(), stack_result);
|
2011-04-27 10:05:43 +00:00
|
|
|
|
# endif
|
2015-03-11 13:38:59 +00:00
|
|
|
|
if (resolved_name) {
|
2011-04-27 10:05:43 +00:00
|
|
|
|
data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
|
|
|
|
|
data.entryFlags |= QFileSystemMetaData::ExistsAttribute;
|
2024-05-09 17:44:35 +00:00
|
|
|
|
return QFileSystemEntry(resolved_name, QFileSystemEntry::FromNativePath{});
|
2018-07-26 16:16:48 +00:00
|
|
|
|
} else if (errno == ENOENT || errno == ENOTDIR) { // file doesn't exist
|
2011-04-27 10:05:43 +00:00
|
|
|
|
data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
|
|
|
|
|
data.entryFlags &= ~(QFileSystemMetaData::ExistsAttribute);
|
|
|
|
|
return QFileSystemEntry();
|
|
|
|
|
}
|
|
|
|
|
return entry;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//static
|
|
|
|
|
QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry)
|
|
|
|
|
{
|
2019-09-04 13:26:10 +00:00
|
|
|
|
Q_CHECK_FILE_NAME(entry, entry);
|
|
|
|
|
|
2011-09-02 17:40:45 +00:00
|
|
|
|
if (entry.isAbsolute() && entry.isClean())
|
2011-04-27 10:05:43 +00:00
|
|
|
|
return entry;
|
|
|
|
|
|
|
|
|
|
QByteArray orig = entry.nativeFilePath();
|
|
|
|
|
QByteArray result;
|
|
|
|
|
if (orig.isEmpty() || !orig.startsWith('/')) {
|
|
|
|
|
QFileSystemEntry cur(currentPath());
|
|
|
|
|
result = cur.nativeFilePath();
|
|
|
|
|
}
|
Port from container.count()/length() to size()
This is semantic patch using ClangTidyTransformator:
auto QtContainerClass = expr(hasType(namedDecl(hasAnyName(<classes>)))).bind(o)
makeRule(cxxMemberCallExpr(on(QtContainerClass),
callee(cxxMethodDecl(hasAnyName({"count", "length"),
parameterCountIs(0))))),
changeTo(cat(access(o, cat("size"), "()"))),
cat("use 'size()' instead of 'count()/length()'"))
a.k.a qt-port-to-std-compatible-api with config Scope: 'Container'.
<classes> are:
// sequential:
"QByteArray",
"QList",
"QQueue",
"QStack",
"QString",
"QVarLengthArray",
"QVector",
// associative:
"QHash",
"QMultiHash",
"QMap",
"QMultiMap",
"QSet",
// Qt has no QMultiSet
Change-Id: Ibe8837be96e8d30d1846881ecd65180c1bc459af
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
2022-09-30 12:09:04 +00:00
|
|
|
|
if (!orig.isEmpty() && !(orig.size() == 1 && orig[0] == '.')) {
|
2011-04-27 10:05:43 +00:00
|
|
|
|
if (!result.isEmpty() && !result.endsWith('/'))
|
|
|
|
|
result.append('/');
|
|
|
|
|
result.append(orig);
|
|
|
|
|
}
|
|
|
|
|
|
Port from container.count()/length() to size()
This is semantic patch using ClangTidyTransformator:
auto QtContainerClass = expr(hasType(namedDecl(hasAnyName(<classes>)))).bind(o)
makeRule(cxxMemberCallExpr(on(QtContainerClass),
callee(cxxMethodDecl(hasAnyName({"count", "length"),
parameterCountIs(0))))),
changeTo(cat(access(o, cat("size"), "()"))),
cat("use 'size()' instead of 'count()/length()'"))
a.k.a qt-port-to-std-compatible-api with config Scope: 'Container'.
<classes> are:
// sequential:
"QByteArray",
"QList",
"QQueue",
"QStack",
"QString",
"QVarLengthArray",
"QVector",
// associative:
"QHash",
"QMultiHash",
"QMap",
"QMultiMap",
"QSet",
// Qt has no QMultiSet
Change-Id: Ibe8837be96e8d30d1846881ecd65180c1bc459af
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
2022-09-30 12:09:04 +00:00
|
|
|
|
if (result.size() == 1 && result[0] == '/')
|
2011-04-27 10:05:43 +00:00
|
|
|
|
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)
|
2022-03-09 15:34:49 +00:00
|
|
|
|
stringVersion.append(u'/');
|
2011-04-27 10:05:43 +00:00
|
|
|
|
return QFileSystemEntry(stringVersion);
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-09 08:11:17 +00:00
|
|
|
|
//static
|
|
|
|
|
QByteArray QFileSystemEngine::id(const QFileSystemEntry &entry)
|
|
|
|
|
{
|
2019-09-04 13:26:10 +00:00
|
|
|
|
Q_CHECK_FILE_NAME(entry, QByteArray());
|
2017-06-29 18:16:04 +00:00
|
|
|
|
|
2017-06-29 18:17:23 +00:00
|
|
|
|
QT_STATBUF statResult;
|
|
|
|
|
if (QT_STAT(entry.nativeFilePath().constData(), &statResult)) {
|
2017-06-29 19:35:01 +00:00
|
|
|
|
if (errno != ENOENT)
|
|
|
|
|
qErrnoWarning("stat() failed for '%s'", entry.nativeFilePath().constData());
|
2013-01-09 08:11:17 +00:00
|
|
|
|
return QByteArray();
|
|
|
|
|
}
|
|
|
|
|
QByteArray result = QByteArray::number(quint64(statResult.st_dev), 16);
|
|
|
|
|
result += ':';
|
2013-01-15 19:14:31 +00:00
|
|
|
|
result += QByteArray::number(quint64(statResult.st_ino));
|
2013-01-09 08:11:17 +00:00
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-29 19:55:54 +00:00
|
|
|
|
//static
|
2019-04-04 19:58:29 +00:00
|
|
|
|
QByteArray QFileSystemEngine::id(int fd)
|
2017-06-29 19:55:54 +00:00
|
|
|
|
{
|
|
|
|
|
QT_STATBUF statResult;
|
2019-04-04 19:58:29 +00:00
|
|
|
|
if (QT_FSTAT(fd, &statResult)) {
|
|
|
|
|
qErrnoWarning("fstat() failed for fd %d", fd);
|
2017-06-29 19:55:54 +00:00
|
|
|
|
return QByteArray();
|
|
|
|
|
}
|
|
|
|
|
QByteArray result = QByteArray::number(quint64(statResult.st_dev), 16);
|
2013-01-09 08:11:17 +00:00
|
|
|
|
result += ':';
|
2013-01-15 19:14:31 +00:00
|
|
|
|
result += QByteArray::number(quint64(statResult.st_ino));
|
2013-01-09 08:11:17 +00:00
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
|
//static
|
|
|
|
|
QString QFileSystemEngine::resolveUserName(uint userId)
|
|
|
|
|
{
|
2016-12-29 16:11:24 +00:00
|
|
|
|
#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
|
2023-03-08 11:12:40 +00:00
|
|
|
|
long size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
|
2011-04-27 10:05:43 +00:00
|
|
|
|
if (size_max == -1)
|
|
|
|
|
size_max = 1024;
|
|
|
|
|
QVarLengthArray<char, 1024> buf(size_max);
|
|
|
|
|
#endif
|
|
|
|
|
|
2018-06-01 13:13:30 +00:00
|
|
|
|
#if !defined(Q_OS_INTEGRITY) && !defined(Q_OS_WASM)
|
2019-05-14 13:04:18 +00:00
|
|
|
|
struct passwd *pw = nullptr;
|
2016-12-29 16:11:24 +00:00
|
|
|
|
#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) && !defined(Q_OS_VXWORKS)
|
2011-04-27 10:05:43 +00:00
|
|
|
|
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));
|
2019-07-12 22:21:42 +00:00
|
|
|
|
#else // Integrity || WASM
|
|
|
|
|
Q_UNUSED(userId);
|
2015-10-27 01:06:47 +00:00
|
|
|
|
#endif
|
2011-04-27 10:05:43 +00:00
|
|
|
|
return QString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//static
|
|
|
|
|
QString QFileSystemEngine::resolveGroupName(uint groupId)
|
|
|
|
|
{
|
2016-12-29 16:11:24 +00:00
|
|
|
|
#if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
|
2023-03-08 11:12:40 +00:00
|
|
|
|
long size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
|
2011-04-27 10:05:43 +00:00
|
|
|
|
if (size_max == -1)
|
|
|
|
|
size_max = 1024;
|
|
|
|
|
QVarLengthArray<char, 1024> buf(size_max);
|
|
|
|
|
#endif
|
|
|
|
|
|
2023-12-20 01:22:35 +00:00
|
|
|
|
#if !defined(Q_OS_INTEGRITY) && !defined(Q_OS_WASM)
|
2019-05-14 13:04:18 +00:00
|
|
|
|
struct group *gr = nullptr;
|
2016-12-29 16:11:24 +00:00
|
|
|
|
#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))
|
2011-04-27 10:05:43 +00:00
|
|
|
|
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)
|
2023-03-08 11:12:40 +00:00
|
|
|
|
for (long size = size_max; size < 256000; size += size)
|
2011-04-27 10:05:43 +00:00
|
|
|
|
{
|
|
|
|
|
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));
|
2024-01-24 15:34:14 +00:00
|
|
|
|
#else // Integrity || WASM || VxWorks
|
2019-07-12 22:21:42 +00:00
|
|
|
|
Q_UNUSED(groupId);
|
2015-10-27 01:06:47 +00:00
|
|
|
|
#endif
|
2011-04-27 10:05:43 +00:00
|
|
|
|
return QString();
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-14 03:16:44 +00:00
|
|
|
|
#if defined(Q_OS_DARWIN)
|
2011-04-27 10:05:43 +00:00
|
|
|
|
//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())
|
2016-10-03 17:34:21 +00:00
|
|
|
|
return QString::fromCFString((CFStringRef)name);
|
2011-04-27 10:05:43 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return QString();
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
//static
|
|
|
|
|
bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemMetaData &data,
|
|
|
|
|
QFileSystemMetaData::MetaDataFlags what)
|
|
|
|
|
{
|
2019-09-04 13:26:10 +00:00
|
|
|
|
Q_CHECK_FILE_NAME(entry, false);
|
2017-06-29 18:16:04 +00:00
|
|
|
|
|
2015-07-14 03:16:44 +00:00
|
|
|
|
#if defined(Q_OS_DARWIN)
|
2024-09-19 20:29:01 +00:00
|
|
|
|
if (what & (QFileSystemMetaData::BundleType | QFileSystemMetaData::CaseSensitive)) {
|
2011-04-27 10:05:43 +00:00
|
|
|
|
if (!data.hasFlags(QFileSystemMetaData::DirectoryType))
|
|
|
|
|
what |= QFileSystemMetaData::DirectoryType;
|
|
|
|
|
}
|
2022-03-03 17:05:46 +00:00
|
|
|
|
if (what & QFileSystemMetaData::AliasType)
|
|
|
|
|
what |= QFileSystemMetaData::LinkType;
|
2017-07-26 21:01:45 +00:00
|
|
|
|
#endif
|
|
|
|
|
#ifdef UF_HIDDEN
|
2011-04-27 10:05:43 +00:00
|
|
|
|
if (what & QFileSystemMetaData::HiddenAttribute) {
|
2015-02-18 11:49:44 +00:00
|
|
|
|
// OS X >= 10.5: st_flags & UF_HIDDEN
|
2011-04-27 10:05:43 +00:00
|
|
|
|
what |= QFileSystemMetaData::PosixStatFlags;
|
|
|
|
|
}
|
2015-07-14 03:16:44 +00:00
|
|
|
|
#endif // defined(Q_OS_DARWIN)
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
2017-07-03 01:34:09 +00:00
|
|
|
|
// if we're asking for any of the stat(2) flags, then we're getting them all
|
2011-04-27 10:05:43 +00:00
|
|
|
|
if (what & QFileSystemMetaData::PosixStatFlags)
|
|
|
|
|
what |= QFileSystemMetaData::PosixStatFlags;
|
|
|
|
|
|
|
|
|
|
data.entryFlags &= ~what;
|
|
|
|
|
|
2017-07-02 22:45:02 +00:00
|
|
|
|
const QByteArray nativeFilePath = entry.nativeFilePath();
|
2017-11-19 19:26:33 +00:00
|
|
|
|
int entryErrno = 0; // innocent until proven otherwise
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
2017-07-03 01:34:09 +00:00
|
|
|
|
// first, we may try lstat(2). Possible outcomes:
|
|
|
|
|
// - success and is a symlink: filesystem entry exists, but we need stat(2)
|
Add support for statx(2) on Linux
This system call, new in Linux 4.11, gives us the file birth time. It's
also extensible, representing the fourth generation of stat(2) on Linux
(the original sys_stat(), sys_newstat(), sys_stat64() and now
sys_statx()), not to be confused with glibc's __xstat function, which
wraps a call to stat64. Anyway, the new one is designed to be extensible.
Now we get birth times on ext[34] on Linux too:
Name: .
Path: . (/home/tjmaciei/src/qt)
Size: 4096 Type: Directory
Attrs: readable writable executable hidden nativepath
Mode: drwxr-xr-x
Owner: tjmaciei (1000) Group: users (100)
Access: 2017-07-02T14:47:49.608
Birth: 2016-05-02T13:20:33.097
Change: 2017-07-01T13:37:08.737
Modified: 2017-07-01T13:37:08.737
It's not supported in any other filesystems I have (Linux sources show
xfs has the feature too). Even on ext4, it depends on whether the
filesystem was created with 256-byte inodes, which my /boot fs wasn't.
Change-Id: I8d96dea9955d4c749b99fffd14cda23ed60d5e72
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
2017-07-02 21:57:21 +00:00
|
|
|
|
// -> statResult = -1;
|
2017-07-03 01:34:09 +00:00
|
|
|
|
// - success and is not a symlink: filesystem entry exists and we're done
|
Add support for statx(2) on Linux
This system call, new in Linux 4.11, gives us the file birth time. It's
also extensible, representing the fourth generation of stat(2) on Linux
(the original sys_stat(), sys_newstat(), sys_stat64() and now
sys_statx()), not to be confused with glibc's __xstat function, which
wraps a call to stat64. Anyway, the new one is designed to be extensible.
Now we get birth times on ext[34] on Linux too:
Name: .
Path: . (/home/tjmaciei/src/qt)
Size: 4096 Type: Directory
Attrs: readable writable executable hidden nativepath
Mode: drwxr-xr-x
Owner: tjmaciei (1000) Group: users (100)
Access: 2017-07-02T14:47:49.608
Birth: 2016-05-02T13:20:33.097
Change: 2017-07-01T13:37:08.737
Modified: 2017-07-01T13:37:08.737
It's not supported in any other filesystems I have (Linux sources show
xfs has the feature too). Even on ext4, it depends on whether the
filesystem was created with 256-byte inodes, which my /boot fs wasn't.
Change-Id: I8d96dea9955d4c749b99fffd14cda23ed60d5e72
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
2017-07-02 21:57:21 +00:00
|
|
|
|
// -> statResult = 0
|
2017-07-03 01:34:09 +00:00
|
|
|
|
// - failure: really non-existent filesystem entry
|
Add support for statx(2) on Linux
This system call, new in Linux 4.11, gives us the file birth time. It's
also extensible, representing the fourth generation of stat(2) on Linux
(the original sys_stat(), sys_newstat(), sys_stat64() and now
sys_statx()), not to be confused with glibc's __xstat function, which
wraps a call to stat64. Anyway, the new one is designed to be extensible.
Now we get birth times on ext[34] on Linux too:
Name: .
Path: . (/home/tjmaciei/src/qt)
Size: 4096 Type: Directory
Attrs: readable writable executable hidden nativepath
Mode: drwxr-xr-x
Owner: tjmaciei (1000) Group: users (100)
Access: 2017-07-02T14:47:49.608
Birth: 2016-05-02T13:20:33.097
Change: 2017-07-01T13:37:08.737
Modified: 2017-07-01T13:37:08.737
It's not supported in any other filesystems I have (Linux sources show
xfs has the feature too). Even on ext4, it depends on whether the
filesystem was created with 256-byte inodes, which my /boot fs wasn't.
Change-Id: I8d96dea9955d4c749b99fffd14cda23ed60d5e72
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
2017-07-02 21:57:21 +00:00
|
|
|
|
// -> entryExists = false; statResult = 0;
|
2017-07-03 01:34:09 +00:00
|
|
|
|
// 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.
|
Add support for statx(2) on Linux
This system call, new in Linux 4.11, gives us the file birth time. It's
also extensible, representing the fourth generation of stat(2) on Linux
(the original sys_stat(), sys_newstat(), sys_stat64() and now
sys_statx()), not to be confused with glibc's __xstat function, which
wraps a call to stat64. Anyway, the new one is designed to be extensible.
Now we get birth times on ext[34] on Linux too:
Name: .
Path: . (/home/tjmaciei/src/qt)
Size: 4096 Type: Directory
Attrs: readable writable executable hidden nativepath
Mode: drwxr-xr-x
Owner: tjmaciei (1000) Group: users (100)
Access: 2017-07-02T14:47:49.608
Birth: 2016-05-02T13:20:33.097
Change: 2017-07-01T13:37:08.737
Modified: 2017-07-01T13:37:08.737
It's not supported in any other filesystems I have (Linux sources show
xfs has the feature too). Even on ext4, it depends on whether the
filesystem was created with 256-byte inodes, which my /boot fs wasn't.
Change-Id: I8d96dea9955d4c749b99fffd14cda23ed60d5e72
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
2017-07-02 21:57:21 +00:00
|
|
|
|
union {
|
|
|
|
|
QT_STATBUF statBuffer;
|
|
|
|
|
struct statx statxBuffer;
|
|
|
|
|
};
|
|
|
|
|
int statResult = -1;
|
2011-04-27 10:05:43 +00:00
|
|
|
|
if (what & QFileSystemMetaData::LinkType) {
|
Add support for statx(2) on Linux
This system call, new in Linux 4.11, gives us the file birth time. It's
also extensible, representing the fourth generation of stat(2) on Linux
(the original sys_stat(), sys_newstat(), sys_stat64() and now
sys_statx()), not to be confused with glibc's __xstat function, which
wraps a call to stat64. Anyway, the new one is designed to be extensible.
Now we get birth times on ext[34] on Linux too:
Name: .
Path: . (/home/tjmaciei/src/qt)
Size: 4096 Type: Directory
Attrs: readable writable executable hidden nativepath
Mode: drwxr-xr-x
Owner: tjmaciei (1000) Group: users (100)
Access: 2017-07-02T14:47:49.608
Birth: 2016-05-02T13:20:33.097
Change: 2017-07-01T13:37:08.737
Modified: 2017-07-01T13:37:08.737
It's not supported in any other filesystems I have (Linux sources show
xfs has the feature too). Even on ext4, it depends on whether the
filesystem was created with 256-byte inodes, which my /boot fs wasn't.
Change-Id: I8d96dea9955d4c749b99fffd14cda23ed60d5e72
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
2017-07-02 21:57:21 +00:00
|
|
|
|
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"
|
2011-04-27 10:05:43 +00:00
|
|
|
|
data.entryFlags |= QFileSystemMetaData::LinkType;
|
Add support for statx(2) on Linux
This system call, new in Linux 4.11, gives us the file birth time. It's
also extensible, representing the fourth generation of stat(2) on Linux
(the original sys_stat(), sys_newstat(), sys_stat64() and now
sys_statx()), not to be confused with glibc's __xstat function, which
wraps a call to stat64. Anyway, the new one is designed to be extensible.
Now we get birth times on ext[34] on Linux too:
Name: .
Path: . (/home/tjmaciei/src/qt)
Size: 4096 Type: Directory
Attrs: readable writable executable hidden nativepath
Mode: drwxr-xr-x
Owner: tjmaciei (1000) Group: users (100)
Access: 2017-07-02T14:47:49.608
Birth: 2016-05-02T13:20:33.097
Change: 2017-07-01T13:37:08.737
Modified: 2017-07-01T13:37:08.737
It's not supported in any other filesystems I have (Linux sources show
xfs has the feature too). Even on ext4, it depends on whether the
filesystem was created with 256-byte inodes, which my /boot fs wasn't.
Change-Id: I8d96dea9955d4c749b99fffd14cda23ed60d5e72
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
2017-07-02 21:57:21 +00:00
|
|
|
|
statResult = -1; // force stat(2) below
|
2011-04-27 10:05:43 +00:00
|
|
|
|
} else {
|
2017-07-03 01:34:09 +00:00
|
|
|
|
// it's a reagular file and it exists
|
Add support for statx(2) on Linux
This system call, new in Linux 4.11, gives us the file birth time. It's
also extensible, representing the fourth generation of stat(2) on Linux
(the original sys_stat(), sys_newstat(), sys_stat64() and now
sys_statx()), not to be confused with glibc's __xstat function, which
wraps a call to stat64. Anyway, the new one is designed to be extensible.
Now we get birth times on ext[34] on Linux too:
Name: .
Path: . (/home/tjmaciei/src/qt)
Size: 4096 Type: Directory
Attrs: readable writable executable hidden nativepath
Mode: drwxr-xr-x
Owner: tjmaciei (1000) Group: users (100)
Access: 2017-07-02T14:47:49.608
Birth: 2016-05-02T13:20:33.097
Change: 2017-07-01T13:37:08.737
Modified: 2017-07-01T13:37:08.737
It's not supported in any other filesystems I have (Linux sources show
xfs has the feature too). Even on ext4, it depends on whether the
filesystem was created with 256-byte inodes, which my /boot fs wasn't.
Change-Id: I8d96dea9955d4c749b99fffd14cda23ed60d5e72
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
2017-07-02 21:57:21 +00:00
|
|
|
|
if (statResult)
|
|
|
|
|
data.fillFromStatxBuf(statxBuffer);
|
|
|
|
|
else
|
|
|
|
|
data.fillFromStatBuf(statBuffer);
|
2011-04-27 10:05:43 +00:00
|
|
|
|
}
|
|
|
|
|
} else {
|
2017-07-03 01:34:09 +00:00
|
|
|
|
// it doesn't exist
|
2017-11-19 19:26:33 +00:00
|
|
|
|
entryErrno = errno;
|
2017-07-03 01:34:09 +00:00
|
|
|
|
data.knownFlagsMask |= QFileSystemMetaData::ExistsAttribute;
|
2011-04-27 10:05:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data.knownFlagsMask |= QFileSystemMetaData::LinkType;
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-03 01:34:09 +00:00
|
|
|
|
// second, we try a regular stat(2)
|
2017-11-19 19:26:33 +00:00
|
|
|
|
if (statResult == -1 && (what & QFileSystemMetaData::PosixStatFlags)) {
|
|
|
|
|
if (entryErrno == 0 && statResult == -1) {
|
2017-07-03 01:34:09 +00:00
|
|
|
|
data.entryFlags &= ~QFileSystemMetaData::PosixStatFlags;
|
Add support for statx(2) on Linux
This system call, new in Linux 4.11, gives us the file birth time. It's
also extensible, representing the fourth generation of stat(2) on Linux
(the original sys_stat(), sys_newstat(), sys_stat64() and now
sys_statx()), not to be confused with glibc's __xstat function, which
wraps a call to stat64. Anyway, the new one is designed to be extensible.
Now we get birth times on ext[34] on Linux too:
Name: .
Path: . (/home/tjmaciei/src/qt)
Size: 4096 Type: Directory
Attrs: readable writable executable hidden nativepath
Mode: drwxr-xr-x
Owner: tjmaciei (1000) Group: users (100)
Access: 2017-07-02T14:47:49.608
Birth: 2016-05-02T13:20:33.097
Change: 2017-07-01T13:37:08.737
Modified: 2017-07-01T13:37:08.737
It's not supported in any other filesystems I have (Linux sources show
xfs has the feature too). Even on ext4, it depends on whether the
filesystem was created with 256-byte inodes, which my /boot fs wasn't.
Change-Id: I8d96dea9955d4c749b99fffd14cda23ed60d5e72
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
2017-07-02 21:57:21 +00:00
|
|
|
|
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);
|
|
|
|
|
}
|
2017-07-03 01:34:09 +00:00
|
|
|
|
}
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
Add support for statx(2) on Linux
This system call, new in Linux 4.11, gives us the file birth time. It's
also extensible, representing the fourth generation of stat(2) on Linux
(the original sys_stat(), sys_newstat(), sys_stat64() and now
sys_statx()), not to be confused with glibc's __xstat function, which
wraps a call to stat64. Anyway, the new one is designed to be extensible.
Now we get birth times on ext[34] on Linux too:
Name: .
Path: . (/home/tjmaciei/src/qt)
Size: 4096 Type: Directory
Attrs: readable writable executable hidden nativepath
Mode: drwxr-xr-x
Owner: tjmaciei (1000) Group: users (100)
Access: 2017-07-02T14:47:49.608
Birth: 2016-05-02T13:20:33.097
Change: 2017-07-01T13:37:08.737
Modified: 2017-07-01T13:37:08.737
It's not supported in any other filesystems I have (Linux sources show
xfs has the feature too). Even on ext4, it depends on whether the
filesystem was created with 256-byte inodes, which my /boot fs wasn't.
Change-Id: I8d96dea9955d4c749b99fffd14cda23ed60d5e72
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
2017-07-02 21:57:21 +00:00
|
|
|
|
if (statResult != 0) {
|
2017-11-19 19:26:33 +00:00
|
|
|
|
entryErrno = errno;
|
2016-07-23 18:14:57 +00:00
|
|
|
|
data.birthTime_ = 0;
|
|
|
|
|
data.metadataChangeTime_ = 0;
|
2011-04-27 10:05:43 +00:00
|
|
|
|
data.modificationTime_ = 0;
|
|
|
|
|
data.accessTime_ = 0;
|
|
|
|
|
data.size_ = 0;
|
|
|
|
|
data.userId_ = (uint) -2;
|
|
|
|
|
data.groupId_ = (uint) -2;
|
|
|
|
|
|
2025-03-24 21:54:31 +00:00
|
|
|
|
// reset the mask
|
|
|
|
|
data.knownFlagsMask |= QFileSystemMetaData::PosixStatFlags
|
|
|
|
|
| QFileSystemMetaData::ExistsAttribute;
|
|
|
|
|
}
|
2011-04-27 10:05:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-03 01:34:09 +00:00
|
|
|
|
// third, we try access(2)
|
|
|
|
|
if (what & (QFileSystemMetaData::UserPermissions | QFileSystemMetaData::ExistsAttribute)) {
|
2025-03-20 09:08:45 +00:00
|
|
|
|
#if defined(Q_OS_VXWORKS)
|
|
|
|
|
// on VxWorks if the filesystem is not POSIX, access() always returns false, despite the
|
|
|
|
|
// file is readable
|
|
|
|
|
struct statfs statBuf;
|
|
|
|
|
if (statfs(nativeFilePath, &statBuf) != 0) {
|
|
|
|
|
what &= ~QFileSystemMetaData::LinkType; // don't clear link: could be broken symlink
|
|
|
|
|
data.clearFlags(what);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2017-07-03 01:34:09 +00:00
|
|
|
|
// calculate user permissions
|
|
|
|
|
auto checkAccess = [&](QFileSystemMetaData::MetaDataFlag flag, int mode) {
|
2017-11-19 19:26:33 +00:00
|
|
|
|
if (entryErrno != 0 || (what & flag) == 0)
|
2017-07-03 01:34:09 +00:00
|
|
|
|
return;
|
|
|
|
|
if (QT_ACCESS(nativeFilePath, mode) == 0) {
|
|
|
|
|
// access ok (and file exists)
|
|
|
|
|
data.entryFlags |= flag | QFileSystemMetaData::ExistsAttribute;
|
|
|
|
|
} else if (errno != EACCES && errno != EROFS) {
|
2017-11-19 19:26:33 +00:00
|
|
|
|
entryErrno = errno;
|
2017-07-03 01:34:09 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
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
|
2017-11-19 19:26:33 +00:00
|
|
|
|
if (entryErrno == 0 && (data.entryFlags & QFileSystemMetaData::ExistsAttribute) == 0) {
|
|
|
|
|
if (QT_ACCESS(nativeFilePath, F_OK) == -1)
|
|
|
|
|
entryErrno = errno;
|
|
|
|
|
else
|
2017-07-03 01:34:09 +00:00
|
|
|
|
data.entryFlags |= QFileSystemMetaData::ExistsAttribute;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data.knownFlagsMask |= (what & QFileSystemMetaData::UserPermissions) |
|
|
|
|
|
QFileSystemMetaData::ExistsAttribute;
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-14 03:16:44 +00:00
|
|
|
|
#if defined(Q_OS_DARWIN)
|
2017-07-03 01:34:09 +00:00
|
|
|
|
if (what & QFileSystemMetaData::AliasType) {
|
2022-03-03 17:05:46 +00:00
|
|
|
|
if (entryErrno == 0 && hasResourcePropertyFlag(data, entry, kCFURLIsAliasFileKey)) {
|
|
|
|
|
// kCFURLIsAliasFileKey includes symbolic links, so filter those out
|
|
|
|
|
if (!(data.entryFlags & QFileSystemMetaData::LinkType))
|
|
|
|
|
data.entryFlags |= QFileSystemMetaData::AliasType;
|
|
|
|
|
}
|
2011-04-27 10:05:43 +00:00
|
|
|
|
data.knownFlagsMask |= QFileSystemMetaData::AliasType;
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-03 01:34:09 +00:00
|
|
|
|
if (what & QFileSystemMetaData::BundleType) {
|
2017-11-19 19:26:33 +00:00
|
|
|
|
if (entryErrno == 0 && isPackage(data, entry))
|
2017-07-03 01:34:09 +00:00
|
|
|
|
data.entryFlags |= QFileSystemMetaData::BundleType;
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
2017-07-03 01:34:09 +00:00
|
|
|
|
data.knownFlagsMask |= QFileSystemMetaData::BundleType;
|
2011-04-27 10:05:43 +00:00
|
|
|
|
}
|
2024-09-19 20:29:01 +00:00
|
|
|
|
|
|
|
|
|
if (what & QFileSystemMetaData::CaseSensitive) {
|
|
|
|
|
if (entryErrno == 0 && hasResourcePropertyFlag(
|
|
|
|
|
data, entry, kCFURLVolumeSupportsCaseSensitiveNamesKey))
|
|
|
|
|
data.entryFlags |= QFileSystemMetaData::CaseSensitive;
|
|
|
|
|
data.knownFlagsMask |= QFileSystemMetaData::CaseSensitive;
|
|
|
|
|
}
|
2017-07-03 01:34:09 +00:00
|
|
|
|
#endif
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
|
|
|
|
if (what & QFileSystemMetaData::HiddenAttribute
|
|
|
|
|
&& !data.isHidden()) {
|
|
|
|
|
QString fileName = entry.fileName();
|
2022-03-09 15:34:49 +00:00
|
|
|
|
if (fileName.startsWith(u'.')
|
2015-07-14 03:16:44 +00:00
|
|
|
|
#if defined(Q_OS_DARWIN)
|
2017-11-19 19:26:33 +00:00
|
|
|
|
|| (entryErrno == 0 && hasResourcePropertyFlag(data, entry, kCFURLIsHiddenKey))
|
2015-07-14 03:16:44 +00:00
|
|
|
|
#endif
|
|
|
|
|
)
|
2011-04-27 10:05:43 +00:00
|
|
|
|
data.entryFlags |= QFileSystemMetaData::HiddenAttribute;
|
|
|
|
|
data.knownFlagsMask |= QFileSystemMetaData::HiddenAttribute;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-19 19:26:33 +00:00
|
|
|
|
if (entryErrno != 0) {
|
2017-07-03 01:34:09 +00:00
|
|
|
|
what &= ~QFileSystemMetaData::LinkType; // don't clear link: could be broken symlink
|
2012-06-05 12:50:13 +00:00
|
|
|
|
data.clearFlags(what);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-11-19 19:26:33 +00:00
|
|
|
|
return true;
|
2011-04-27 10:05:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-16 08:19:23 +00:00
|
|
|
|
// static
|
2025-01-21 18:39:07 +00:00
|
|
|
|
auto QFileSystemEngine::cloneFile(int srcfd, int dstfd, const QFileSystemMetaData &knownData) -> TriStateResult
|
2017-07-16 08:19:23 +00:00
|
|
|
|
{
|
2017-07-26 20:29:45 +00:00
|
|
|
|
QT_STATBUF statBuffer;
|
|
|
|
|
if (knownData.hasFlags(QFileSystemMetaData::PosixStatFlags) &&
|
|
|
|
|
knownData.isFile()) {
|
|
|
|
|
statBuffer.st_mode = S_IFREG;
|
|
|
|
|
} else if (knownData.hasFlags(QFileSystemMetaData::PosixStatFlags) &&
|
|
|
|
|
knownData.isDirectory()) {
|
2025-01-21 18:39:07 +00:00
|
|
|
|
errno = EISDIR;
|
|
|
|
|
return TriStateResult::Failed; // fcopyfile(3) returns success on directories
|
2017-07-25 22:50:24 +00:00
|
|
|
|
} else if (QT_FSTAT(srcfd, &statBuffer) == -1) {
|
2025-01-21 18:39:07 +00:00
|
|
|
|
// errno was set
|
|
|
|
|
return TriStateResult::Failed;
|
2017-07-25 22:50:24 +00:00
|
|
|
|
} else if (!S_ISREG((statBuffer.st_mode))) {
|
|
|
|
|
// not a regular file, let QFile do the copy
|
2025-01-21 18:39:07 +00:00
|
|
|
|
return TriStateResult::NotSupported;
|
2017-07-26 20:29:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-01-19 19:28:26 +00:00
|
|
|
|
[[maybe_unused]] auto destinationIsEmpty = [dstfd]() {
|
|
|
|
|
QT_STATBUF statBuffer;
|
|
|
|
|
return QT_FSTAT(dstfd, &statBuffer) == 0 && statBuffer.st_size == 0;
|
|
|
|
|
};
|
|
|
|
|
Q_ASSERT(destinationIsEmpty());
|
|
|
|
|
|
2017-07-16 08:19:23 +00:00
|
|
|
|
#if defined(Q_OS_LINUX)
|
2017-07-25 22:50:24 +00:00
|
|
|
|
// first, try FICLONE (only works on regular files and only on certain fs)
|
|
|
|
|
if (::ioctl(dstfd, FICLONE, srcfd) == 0)
|
2025-01-21 18:39:07 +00:00
|
|
|
|
return TriStateResult::Success;
|
2025-01-19 19:47:34 +00:00
|
|
|
|
#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
|
2017-07-25 22:50:24 +00:00
|
|
|
|
|
2025-01-19 19:47:34 +00:00
|
|
|
|
#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;
|
|
|
|
|
QT_OFF_T dstoffset = 0;
|
|
|
|
|
ssize_t copied;
|
|
|
|
|
do {
|
|
|
|
|
copied = ::copy_file_range(srcfd, &srcoffset, dstfd, &dstoffset, 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;
|
|
|
|
|
}
|
|
|
|
|
if (errno != EXDEV)
|
|
|
|
|
return TriStateResult::Failed;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if defined(Q_OS_LINUX)
|
|
|
|
|
// For Linux, try sendfile (it can send to some special types too).
|
2017-07-25 22:50:24 +00:00
|
|
|
|
// sendfile(2) is limited in the kernel to 2G - 4k
|
2018-07-11 21:06:55 +00:00
|
|
|
|
const size_t SendfileSize = 0x7ffff000;
|
2017-07-25 22:50:24 +00:00
|
|
|
|
|
2019-11-22 13:46:58 +00:00
|
|
|
|
ssize_t n = ::sendfile(dstfd, srcfd, nullptr, SendfileSize);
|
2017-07-25 22:50:24 +00:00
|
|
|
|
if (n == -1) {
|
QFileSystemEngine/Linux: detect sendfile() permanent errors
sendfile(2) isn't always able to send to all file types, so current code
only detected the ability to send by having sent something. This commit
tries a little harder to detect permanent data-send problems on the
first try. I'm unable to find a case where this fails on Linux, in
particular because we don't enter this block if the source file isn't
S_IFREG.
Now, on Linux, there's no file-copy pump:
openat(AT_FDCWD, "dummy.o", O_RDONLY|O_CLOEXEC) = 4
openat(AT_FDCWD, "/tmp/tmp", O_RDWR|O_CLOEXEC|O_TMPFILE, 0600) = 5
ioctl(5, BTRFS_IOC_CLONE or FICLONE, 4) = -1 EXDEV
sendfile(5, 4, NULL, 2147479552) = -1 ENOSPC
lseek(5, 0, SEEK_SET) = 0
close(5) = 0
write(2, "\"Could not copy to /tmp/tmp/f: No space left on device\"") = 56
close(4) = 0
Change-Id: I88ce23447b0d7341848cfffd2c469e15cb69f5a6
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2025-01-21 18:46:27 +00:00
|
|
|
|
switch (errno) {
|
|
|
|
|
case ENOSPC:
|
|
|
|
|
case EIO:
|
|
|
|
|
return TriStateResult::Failed;
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-25 22:50:24 +00:00
|
|
|
|
// if we got an error here, give up and try at an upper layer
|
2025-01-21 18:39:07 +00:00
|
|
|
|
return TriStateResult::NotSupported;
|
2017-07-25 22:50:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-07-11 21:06:55 +00:00
|
|
|
|
while (n) {
|
2019-11-22 13:46:58 +00:00
|
|
|
|
n = ::sendfile(dstfd, srcfd, nullptr, SendfileSize);
|
2018-07-11 21:06:55 +00:00
|
|
|
|
if (n == -1) {
|
2025-01-21 18:39:07 +00:00
|
|
|
|
// uh oh, this is probably a real error (like ENOSPC)
|
2017-07-25 22:50:24 +00:00
|
|
|
|
n = ftruncate(dstfd, 0);
|
|
|
|
|
n = lseek(srcfd, 0, SEEK_SET);
|
|
|
|
|
n = lseek(dstfd, 0, SEEK_SET);
|
2025-01-21 18:39:07 +00:00
|
|
|
|
return TriStateResult::Failed;
|
2017-07-25 22:50:24 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-21 18:39:07 +00:00
|
|
|
|
return TriStateResult::Success;
|
2017-07-16 08:19:23 +00:00
|
|
|
|
#else
|
|
|
|
|
Q_UNUSED(dstfd);
|
2025-01-21 18:39:07 +00:00
|
|
|
|
return TriStateResult::NotSupported;
|
2017-07-16 08:19:23 +00:00
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-25 18:07:46 +00:00
|
|
|
|
static QSystemError createDirectoryWithParents(const QByteArray &path, mode_t mode)
|
2017-01-24 20:17:12 +00:00
|
|
|
|
{
|
2024-09-20 06:13:00 +00:00
|
|
|
|
#ifdef Q_OS_WASM
|
2025-04-23 05:52:53 +00:00
|
|
|
|
if (path == "/")
|
2024-09-25 18:07:46 +00:00
|
|
|
|
return {};
|
2024-09-20 06:13:00 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
2024-09-25 18:07:46 +00:00
|
|
|
|
auto tryMkDir = [&path, mode]() -> QSystemError {
|
|
|
|
|
if (QT_MKDIR(path, mode) == 0) {
|
2024-06-11 14:13:07 +00:00
|
|
|
|
#ifdef Q_OS_VXWORKS
|
2024-09-25 18:07:46 +00:00
|
|
|
|
forceRequestedPermissionsOnVxWorks(path, mode);
|
2024-06-11 14:13:07 +00:00
|
|
|
|
#endif
|
2024-09-25 18:07:46 +00:00
|
|
|
|
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;
|
2017-01-24 20:17:12 +00:00
|
|
|
|
|
|
|
|
|
if (slash < 1)
|
2024-09-25 18:07:46 +00:00
|
|
|
|
return result;
|
2017-01-24 20:17:12 +00:00
|
|
|
|
|
2024-09-25 18:07:46 +00:00
|
|
|
|
// 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;
|
2017-01-24 20:17:12 +00:00
|
|
|
|
|
|
|
|
|
// try again
|
2024-09-25 18:07:46 +00:00
|
|
|
|
return tryMkDir();
|
2017-01-24 20:17:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-09-25 18:07:46 +00:00
|
|
|
|
bool QFileSystemEngine::mkpath(const QFileSystemEntry &entry,
|
|
|
|
|
std::optional<QFile::Permissions> permissions)
|
2011-04-27 10:05:43 +00:00
|
|
|
|
{
|
2024-09-25 18:07:46 +00:00
|
|
|
|
QByteArray path = entry.nativeFilePath();
|
|
|
|
|
Q_CHECK_FILE_NAME(path, false);
|
2017-01-24 20:17:12 +00:00
|
|
|
|
|
2021-11-10 15:52:07 +00:00
|
|
|
|
mode_t mode = permissions ? QtPrivate::toMode_t(*permissions) : 0777;
|
2024-09-25 18:07:46 +00:00
|
|
|
|
return createDirectoryWithParents(removeTrailingSlashes(path), mode).ok();
|
|
|
|
|
}
|
2017-01-24 20:17:12 +00:00
|
|
|
|
|
2024-09-25 18:07:46 +00:00
|
|
|
|
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;
|
2024-11-05 12:54:42 +00:00
|
|
|
|
auto result = QT_MKDIR(removeTrailingSlashes(path), mode) == 0;
|
|
|
|
|
#if defined(Q_OS_VXWORKS)
|
|
|
|
|
if (result)
|
|
|
|
|
forceRequestedPermissionsOnVxWorks(path, mode);
|
|
|
|
|
#endif
|
|
|
|
|
return result;
|
2011-04-27 10:05:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-03-11 18:42:51 +00:00
|
|
|
|
bool QFileSystemEngine::rmdir(const QFileSystemEntry &entry)
|
2011-04-27 10:05:43 +00:00
|
|
|
|
{
|
2024-03-11 18:42:51 +00:00
|
|
|
|
const QByteArray path = entry.nativeFilePath();
|
|
|
|
|
Q_CHECK_FILE_NAME(path, false);
|
|
|
|
|
return ::rmdir(path.constData()) == 0;
|
|
|
|
|
}
|
2017-06-29 18:16:04 +00:00
|
|
|
|
|
2024-03-11 18:42:51 +00:00
|
|
|
|
bool QFileSystemEngine::rmpath(const QFileSystemEntry &entry)
|
|
|
|
|
{
|
2024-06-10 16:04:19 +00:00
|
|
|
|
QByteArray path = QFile::encodeName(QDir::cleanPath(entry.filePath()));
|
2024-03-11 18:42:51 +00:00
|
|
|
|
Q_CHECK_FILE_NAME(path, false);
|
|
|
|
|
|
2024-06-10 16:04:19 +00:00
|
|
|
|
if (::rmdir(path.constData()) != 0)
|
|
|
|
|
return false; // Only return false if `entry` couldn't be deleted
|
2024-03-11 18:42:51 +00:00
|
|
|
|
|
2024-06-10 16:04:19 +00:00
|
|
|
|
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;
|
2011-04-27 10:05:43 +00:00
|
|
|
|
}
|
2024-03-11 18:42:51 +00:00
|
|
|
|
|
|
|
|
|
return true;
|
2011-04-27 10:05:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//static
|
|
|
|
|
bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error)
|
|
|
|
|
{
|
2019-09-04 13:26:10 +00:00
|
|
|
|
Q_CHECK_FILE_NAME(source, false);
|
|
|
|
|
Q_CHECK_FILE_NAME(target, false);
|
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
|
if (::symlink(source.nativeFilePath().constData(), target.nativeFilePath().constData()) == 0)
|
|
|
|
|
return true;
|
|
|
|
|
error = QSystemError(errno, QSystemError::StandardLibraryError);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-07 06:55:16 +00:00
|
|
|
|
#if defined(QT_BOOTSTRAPPED) || !defined(AT_FDCWD) || defined(Q_OS_ANDROID) || !QT_CONFIG(datestring) || defined(Q_OS_VXWORKS)
|
2023-09-21 22:00:35 +00:00
|
|
|
|
// bootstrapped tools don't need this, and we don't want QStorageInfo
|
2024-07-29 20:34:21 +00:00
|
|
|
|
//static
|
|
|
|
|
bool QFileSystemEngine::supportsMoveFileToTrash()
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-21 22:00:35 +00:00
|
|
|
|
//static
|
|
|
|
|
bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &, QFileSystemEntry &,
|
|
|
|
|
QSystemError &error)
|
|
|
|
|
{
|
|
|
|
|
error = QSystemError(ENOSYS, QSystemError::StandardLibraryError);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2025-04-07 06:55:16 +00:00
|
|
|
|
#elif defined(Q_OS_DARWIN)
|
|
|
|
|
// see qfilesystemengine_mac.mm
|
2023-09-21 22:00:35 +00:00
|
|
|
|
#else
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
/*
|
2025-01-17 09:59:02 +00:00
|
|
|
|
Implementing as per https://specifications.freedesktop.org/trash-spec/1.0/
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
*/
|
2024-07-29 20:34:21 +00:00
|
|
|
|
//static
|
|
|
|
|
bool QFileSystemEngine::supportsMoveFileToTrash()
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2020-02-04 13:47:28 +00:00
|
|
|
|
|
2023-09-21 05:51:45 +00:00
|
|
|
|
namespace {
|
|
|
|
|
struct FreeDesktopTrashOperation
|
|
|
|
|
{
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
/*
|
|
|
|
|
"A trash directory contains two subdirectories, named info and files."
|
|
|
|
|
*/
|
|
|
|
|
QString trashPath;
|
|
|
|
|
int filesDirFd = -1;
|
|
|
|
|
int infoDirFd = -1;
|
|
|
|
|
qsizetype volumePrefixLength = 0;
|
|
|
|
|
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
// relative file paths to the filesDirFd and infoDirFd from above
|
|
|
|
|
QByteArray tempTrashFileName;
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
QByteArray infoFilePath;
|
2023-09-21 05:51:45 +00:00
|
|
|
|
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
int infoFileFd = -1; // if we've already opened it
|
2023-09-21 05:51:45 +00:00
|
|
|
|
~FreeDesktopTrashOperation()
|
|
|
|
|
{
|
|
|
|
|
close();
|
|
|
|
|
}
|
|
|
|
|
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
constexpr bool isTrashDirOpen() const { return filesDirFd != -1 && infoDirFd != -1; }
|
|
|
|
|
|
2023-09-21 05:51:45 +00:00
|
|
|
|
void close()
|
|
|
|
|
{
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
int savedErrno = errno;
|
2023-09-21 05:51:45 +00:00
|
|
|
|
if (infoFileFd != -1) {
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
Q_ASSERT(infoDirFd != -1);
|
|
|
|
|
Q_ASSERT(!infoFilePath.isEmpty());
|
|
|
|
|
Q_ASSERT(!trashPath.isEmpty());
|
|
|
|
|
|
2023-09-21 05:51:45 +00:00
|
|
|
|
QT_CLOSE(infoFileFd);
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
unlinkat(infoDirFd, infoFilePath, 0);
|
|
|
|
|
infoFileFd = -1;
|
2023-09-21 05:51:45 +00:00
|
|
|
|
}
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
if (!tempTrashFileName.isEmpty()) {
|
|
|
|
|
Q_ASSERT(filesDirFd != -1);
|
|
|
|
|
unlinkat(filesDirFd, tempTrashFileName, 0);
|
|
|
|
|
}
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
if (filesDirFd >= 0)
|
|
|
|
|
QT_CLOSE(filesDirFd);
|
|
|
|
|
if (infoDirFd >= 0)
|
|
|
|
|
QT_CLOSE(infoDirFd);
|
|
|
|
|
filesDirFd = infoDirFd = -1;
|
|
|
|
|
errno = savedErrno;
|
2023-09-21 05:51:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool tryCreateInfoFile(const QString &filePath, QSystemError &error)
|
|
|
|
|
{
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
QByteArray p = QFile::encodeName(filePath) + ".trashinfo";
|
|
|
|
|
infoFileFd = qt_safe_openat(infoDirFd, p, QT_OPEN_RDWR | QT_OPEN_CREAT | QT_OPEN_EXCL, 0666);
|
2023-09-21 05:51:45 +00:00
|
|
|
|
if (infoFileFd < 0) {
|
|
|
|
|
error = QSystemError(errno, QSystemError::StandardLibraryError);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
infoFilePath = std::move(p);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void commit()
|
|
|
|
|
{
|
|
|
|
|
QT_CLOSE(infoFileFd);
|
|
|
|
|
infoFileFd = -1;
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
tempTrashFileName = {};
|
2023-09-21 05:51:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
// opens a directory and returns the file descriptor
|
|
|
|
|
static int openDirFd(int dfd, const char *path, int mode = 0)
|
|
|
|
|
{
|
|
|
|
|
mode |= QT_OPEN_RDONLY | O_NOFOLLOW | 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)
|
|
|
|
|
{
|
|
|
|
|
// try to open it as a dir, first
|
|
|
|
|
int fd = openDirFd(dfd, path);
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// opens or makes the XDG Trash hierarchy on parentfd (may be -1) called targetDir
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
bool getTrashDir(int parentfd, QString targetDir, const QFileSystemEntry &source,
|
|
|
|
|
QSystemError &error)
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
{
|
|
|
|
|
if (parentfd == AT_FDCWD)
|
|
|
|
|
trashPath = targetDir;
|
|
|
|
|
QByteArray nativePath = QFile::encodeName(targetDir);
|
|
|
|
|
|
|
|
|
|
// open the directory
|
|
|
|
|
int trashfd = openOrCreateDir(parentfd, nativePath);
|
|
|
|
|
if (trashfd < 0 && errno != ENOENT) {
|
|
|
|
|
error = QSystemError(errno, QSystemError::StandardLibraryError);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// check if it is ours (even if we've just mkdirat'ed it)
|
|
|
|
|
if (QT_STATBUF st; QT_FSTAT(trashfd, &st) < 0) {
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
error = QSystemError(errno, QSystemError::StandardLibraryError);
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
return false;
|
|
|
|
|
} else if (st.st_uid != getuid()) {
|
|
|
|
|
error = QSystemError(EPERM, QSystemError::StandardLibraryError);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
filesDirFd = openOrCreateDir(trashfd, "files");
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
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");
|
|
|
|
|
}
|
2023-09-21 01:49:59 +00:00
|
|
|
|
error = QSystemError(errno, QSystemError::StandardLibraryError);
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
if (infoDirFd < 0)
|
|
|
|
|
close();
|
|
|
|
|
QT_CLOSE(trashfd);
|
|
|
|
|
return infoDirFd >= 0;
|
|
|
|
|
}
|
2023-09-21 01:49:59 +00:00
|
|
|
|
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
bool openMountPointTrashLocation(const QFileSystemEntry &source,
|
|
|
|
|
const QStorageInfo &sourceStorage, QSystemError &error)
|
|
|
|
|
{
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
/*
|
|
|
|
|
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."
|
|
|
|
|
*/
|
|
|
|
|
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
const auto dotTrash = "/.Trash"_L1;
|
2023-09-21 01:31:38 +00:00
|
|
|
|
const QString userID = QString::number(::getuid());
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
QFileSystemEntry dotTrashDir(sourceStorage.rootPath() + dotTrash);
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
|
|
|
|
|
// we MUST check that the sticky bit is set, and that it is not a symlink
|
|
|
|
|
int genericTrashFd = openDirFd(AT_FDCWD, dotTrashDir.nativeFilePath());
|
|
|
|
|
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)) {
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
// we SHOULD report the failed check to the administrator
|
|
|
|
|
qCritical("Warning: '%s' is a symlink to '%s'",
|
2023-09-21 01:31:38 +00:00
|
|
|
|
dotTrashDir.nativeFilePath().constData(),
|
|
|
|
|
qt_readlink(dotTrashDir.nativeFilePath()).constData());
|
2023-09-21 01:49:59 +00:00
|
|
|
|
error = QSystemError(ELOOP, QSystemError::StandardLibraryError);
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
}
|
|
|
|
|
} else if (genericTrashFd >= 0) {
|
|
|
|
|
QT_FSTAT(genericTrashFd, &st);
|
|
|
|
|
if ((st.st_mode & S_ISVTX) == 0) {
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
// we SHOULD report the failed check to the administrator
|
|
|
|
|
qCritical("Warning: '%s' doesn't have sticky bit set!",
|
2023-09-21 01:31:38 +00:00
|
|
|
|
dotTrashDir.nativeFilePath().constData());
|
2023-09-21 01:49:59 +00:00
|
|
|
|
error = QSystemError(EPERM, QSystemError::StandardLibraryError);
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
} else {
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
/*
|
|
|
|
|
"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."
|
|
|
|
|
*/
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
if (getTrashDir(genericTrashFd, userID, source, error)) {
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
// recreate the resulting path
|
|
|
|
|
trashPath = dotTrashDir.filePath() + u'/' + userID;
|
|
|
|
|
}
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
}
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
QT_CLOSE(genericTrashFd);
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
}
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
/*
|
|
|
|
|
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."
|
|
|
|
|
*/
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
if (!isTrashDirOpen())
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
getTrashDir(AT_FDCWD, sourceStorage.rootPath() + dotTrash + u'-' + userID, source, error);
|
2023-09-21 01:05:15 +00:00
|
|
|
|
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
if (isTrashDirOpen()) {
|
|
|
|
|
volumePrefixLength = sourceStorage.rootPath().size();
|
|
|
|
|
if (volumePrefixLength == 1)
|
|
|
|
|
volumePrefixLength = 0; // isRoot
|
2023-09-21 01:05:15 +00:00
|
|
|
|
else
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
++volumePrefixLength; // to include the slash
|
2023-09-21 01:05:15 +00:00
|
|
|
|
}
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
return isTrashDirOpen();
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
}
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
bool openHomeTrashLocation(const QFileSystemEntry &source, QSystemError &error)
|
|
|
|
|
{
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
QString topDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
return getTrashDir(AT_FDCWD, topDir + "/Trash"_L1, source, error);
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
bool findTrashFor(const QFileSystemEntry &source, QSystemError &error)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
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 (openHomeTrashLocation(source, error))
|
|
|
|
|
return true;
|
|
|
|
|
if (error.errorCode != EXDEV)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// didn't work, try to find the trash outside the home filesystem
|
|
|
|
|
const QStorageInfo sourceStorage(source.filePath());
|
|
|
|
|
if (!sourceStorage.isValid())
|
|
|
|
|
return false;
|
|
|
|
|
return openMountPointTrashLocation(source, sourceStorage, error);
|
|
|
|
|
}
|
|
|
|
|
};
|
2023-09-21 05:51:45 +00:00
|
|
|
|
} // unnamed namespace
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
|
|
|
|
|
//static
|
|
|
|
|
bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &source,
|
|
|
|
|
QFileSystemEntry &newLocation, QSystemError &error)
|
|
|
|
|
{
|
2023-09-22 01:14:27 +00:00
|
|
|
|
const QFileSystemEntry sourcePath = [&] {
|
|
|
|
|
if (QString path = source.filePath(); path.size() > 1 && path.endsWith(u'/')) {
|
|
|
|
|
path.chop(1);
|
|
|
|
|
return absoluteName(QFileSystemEntry(path));
|
|
|
|
|
}
|
|
|
|
|
return absoluteName(source);
|
|
|
|
|
}();
|
2023-09-21 05:51:45 +00:00
|
|
|
|
FreeDesktopTrashOperation op;
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
if (!op.findTrashFor(sourcePath, error))
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
return false;
|
2023-09-21 05:51:45 +00:00
|
|
|
|
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
/*
|
|
|
|
|
"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."
|
2023-09-21 05:51:45 +00:00
|
|
|
|
|
|
|
|
|
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."
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
*/
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
QString uniqueTrashedName = sourcePath.fileName();
|
2023-09-21 05:51:45 +00:00
|
|
|
|
if (!op.tryCreateInfoFile(uniqueTrashedName, error) && error.errorCode == EEXIST) {
|
moveToTrash/Unix: use the file's inode number as collision avoidance
Instead of a sequential and thus predictable counter. This improves the
performance of when you keep creating and trashing the same file base
name. The previous algorithm would try all occurrences from 0 to however
many trashings have happened.
This could have been any random number, but the source file's inode is
"random" enough for us.
strace of the second file's trashing:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash/info/tst_qfile.moveToTrashOpenFile.vLwfNe.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = -1 EEXIST (File exists)
newfstatat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.vLwfNe", {st_mode=S_IFREG|0644, st_size=16, ...}, 0) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash/info/tst_qfile.moveToTrashOpenFile.vLwfNe-23527891.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 4
newfstatat(AT_FDCWD, "/etc/localtime", {st_mode=S_IFREG|0644, st_size=2852, ...}, 0) = 0
write(4, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat2(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.vLwfNe", AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash/files/tst_qfile.moveToTrashOpenFile.vLwfNe-23527891", RENAME_NOREPLACE) = 0
close(4) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d73459c2eb3d
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-21 06:48:49 +00:00
|
|
|
|
// 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++);
|
2023-09-21 05:51:45 +00:00
|
|
|
|
if (op.tryCreateInfoFile(uniqueTrashedName, error))
|
|
|
|
|
break;
|
|
|
|
|
if (error.errorCode != EEXIST)
|
|
|
|
|
return false;
|
moveToTrash/Unix: use the file's inode number as collision avoidance
Instead of a sequential and thus predictable counter. This improves the
performance of when you keep creating and trashing the same file base
name. The previous algorithm would try all occurrences from 0 to however
many trashings have happened.
This could have been any random number, but the source file's inode is
"random" enough for us.
strace of the second file's trashing:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash/info/tst_qfile.moveToTrashOpenFile.vLwfNe.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = -1 EEXIST (File exists)
newfstatat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.vLwfNe", {st_mode=S_IFREG|0644, st_size=16, ...}, 0) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash/info/tst_qfile.moveToTrashOpenFile.vLwfNe-23527891.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 4
newfstatat(AT_FDCWD, "/etc/localtime", {st_mode=S_IFREG|0644, st_size=2852, ...}, 0) = 0
write(4, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat2(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.vLwfNe", AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash/files/tst_qfile.moveToTrashOpenFile.vLwfNe-23527891", RENAME_NOREPLACE) = 0
close(4) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d73459c2eb3d
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-21 06:48:49 +00:00
|
|
|
|
};
|
2023-09-21 02:38:25 +00:00
|
|
|
|
}
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
|
2023-09-21 05:51:45 +00:00
|
|
|
|
QByteArray info =
|
|
|
|
|
"[Trash Info]\n"
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
"Path=" + QUrl::toPercentEncoding(source.filePath().mid(op.volumePrefixLength), "/") + "\n"
|
2023-09-21 05:51:45 +00:00
|
|
|
|
"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;
|
|
|
|
|
}
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
|
2020-01-31 11:54:17 +00:00
|
|
|
|
/*
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
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.
|
2020-01-31 11:54:17 +00:00
|
|
|
|
*/
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
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)
|
2024-06-24 20:40:25 +00:00
|
|
|
|
removeFile(sourcePath, error); // success, delete the original file
|
moveToTrash/Unix: use linkat() to check early for cross-device renames
This ensures that we will succeed in renaming files, because we already
have created a link to it in the right directory. With this, we can
remove the home filesystem check that was using QStorageInfo. The
majority of file deletions we expect applications to perform will use
this code path.
An additional benefit is that we ensure we can't get an ENOSPC when
renaming any more, because we already have the entry in the directory.
This needs a fallback to the existing mechanism for two cases:
* trashing full directories, because you can't hardlink them
* when operating on a volume that isn't a Unix filesystem (e.g., a FAT
filesystem on a removable device)
QTemporaryFileName required a small change to allow non-absolute paths.
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
linkat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK", 6, ".eRPdPI", 0) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.MuahmK.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
[../etc/localtime..]
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(6, ".eRPdPI", 6, "tst_qfile.moveToTrashOpenFile.MuahmK") = 0
unlink("/home/tjmaciei/tst_qfile.moveToTrashOpenFile.MuahmK") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1786d714fc24f161
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2023-09-25 02:48:47 +00:00
|
|
|
|
}
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
if (!renamed) {
|
|
|
|
|
error = QSystemError(errno, QSystemError::StandardLibraryError);
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
return false;
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
}
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
|
2023-09-21 05:51:45 +00:00
|
|
|
|
op.commit();
|
moveToTrash/Unix: refactor to use openat()/mkdirat()/renameat()
This ensures much better security against race conditions and attacks,
at the expense of a few more system calls.
On first run (when no trash dir is yet present):
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", 0700) = 0
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=0, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "files", 0700) = 0
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = -1 ENOENT (No such file or directory)
mkdirat(5, "info", 0700) = 0
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.fjYRxv.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.fjYRxv", 6, "tst_qfile.moveToTrashOpenFile.fjYRxv") = 0
close(5) = 0
close(6) = 0
close(7) = 0
On subsequent runs:
openat(AT_FDCWD, "/home/tjmaciei/.qttest/share/Trash", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 5
newfstatat(5, "", {st_mode=S_IFDIR|0700, st_size=18, ...}, AT_EMPTY_PATH) = 0
getuid() = 1000
openat(5, "files", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 6
openat(5, "info", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 7
close(5) = 0
openat(7, "tst_qfile.moveToTrashOpenFile.sPjrcA.trashinfo", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 5
openat(AT_FDCWD, "/usr/share/zoneinfo/UTC", O_RDONLY|O_CLOEXEC) = 8
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
newfstatat(8, "", {st_mode=S_IFREG|0644, st_size=114, ...}, AT_EMPTY_PATH) = 0
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 114
lseek(8, -60, SEEK_CUR) = 54
read(8, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 60
close(8) = 0
write(5, "[Trash Info]\nPath=/home/tjmaciei"..., 103) = 103
renameat(AT_FDCWD, "/home/tjmaciei/tst_qfile.moveToTrashOpenFile.sPjrcA", 6, "tst_qfile.moveToTrashOpenFile.sPjrcA") = 0
close(5) = 0
close(6) = 0
close(7) = 0
Change-Id: I9d43e5b91eb142d6945cfffd1787117927650dab
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-09-22 00:36:36 +00:00
|
|
|
|
newLocation = QFileSystemEntry(op.trashPath + "/files/"_L1 + uniqueTrashedName);
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
2024-10-24 19:43:19 +00:00
|
|
|
|
#endif // !Q_OS_DARWIN && (!QT_BOOTSTRAPPED && AT_FDCWD && !Q_OS_ANDROID && QT_CONFIG(datestring))
|
Implement moving of a single file system entry to the trash
This implements the operation for Windows, macOS, and Unix, for now
only as a private API (since QFileSystemEngine is private). This adds
the capability as a testable function; public API to be agreed on and
added in a separate commit.
The Unix implementation follows the freedesktop.org specification [1]
version 1.0.
[1] https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
On macOS and Windows, native APIs are used, with each having some
limitations:
* on macOS, the file in the trash won't have a "put back" option,
as we don't use Finder automation, for the reasons provided in the
comments
* on Windows, we might not be able to use the modern IFileOperation
API, e.g. if Qt is built with mingw which doesn't seem to provide
the interface definition; the fallback doesn't provide access to
the file name in the trash
The test case creates files and directories, and moves them to the
trash. As part of the cleanup routine, it deletes all file system
entries created. If run on Windows without IFileOperations support,
this will add a file in the trash for each test run, filling up
hard drive space.
Task-number: QTBUG-47703
Change-Id: I5f5f4e578be2f45d7da84f70a03acbe1a12a1231
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
2019-10-31 16:20:13 +00:00
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
|
//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)
|
2017-06-29 21:03:26 +00:00
|
|
|
|
{
|
|
|
|
|
QFileSystemEntry::NativePath srcPath = source.nativeFilePath();
|
|
|
|
|
QFileSystemEntry::NativePath tgtPath = target.nativeFilePath();
|
2019-09-04 13:26:10 +00:00
|
|
|
|
|
|
|
|
|
Q_CHECK_FILE_NAME(srcPath, false);
|
|
|
|
|
Q_CHECK_FILE_NAME(tgtPath, false);
|
2017-06-29 21:27:48 +00:00
|
|
|
|
|
2018-09-19 05:05:54 +00:00
|
|
|
|
#if defined(RENAME_NOREPLACE) && QT_CONFIG(renameat2)
|
2017-06-29 21:27:48 +00:00
|
|
|
|
if (renameat2(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_NOREPLACE) == 0)
|
|
|
|
|
return true;
|
|
|
|
|
|
2017-10-24 20:48:15 +00:00
|
|
|
|
// We can also get EINVAL for some non-local filesystems.
|
2018-09-19 05:05:54 +00:00
|
|
|
|
if (errno != EINVAL) {
|
2017-06-29 21:27:48 +00:00
|
|
|
|
error = QSystemError(errno, QSystemError::StandardLibraryError);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
#if defined(Q_OS_DARWIN) && defined(RENAME_EXCL)
|
2018-08-24 13:41:42 +00:00
|
|
|
|
if (renameatx_np(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_EXCL) == 0)
|
|
|
|
|
return true;
|
|
|
|
|
if (errno != ENOTSUP) {
|
|
|
|
|
error = QSystemError(errno, QSystemError::StandardLibraryError);
|
|
|
|
|
return false;
|
2017-06-29 21:27:48 +00:00
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2017-12-02 03:38:20 +00:00
|
|
|
|
if (SupportsHardlinking && ::link(srcPath, tgtPath) == 0) {
|
2017-06-29 21:03:26 +00:00
|
|
|
|
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;
|
2017-12-02 03:38:20 +00:00
|
|
|
|
} else if (!SupportsHardlinking) {
|
|
|
|
|
// man 2 link on Linux has:
|
|
|
|
|
// EPERM The filesystem containing oldpath and newpath does not
|
|
|
|
|
// support the creation of hard links.
|
|
|
|
|
errno = EPERM;
|
2017-06-29 21:03:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
2011-04-27 10:05:43 +00:00
|
|
|
|
{
|
2019-09-04 13:26:10 +00:00
|
|
|
|
Q_CHECK_FILE_NAME(source, false);
|
|
|
|
|
Q_CHECK_FILE_NAME(target, false);
|
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2019-09-04 13:26:10 +00:00
|
|
|
|
Q_CHECK_FILE_NAME(entry, false);
|
2011-04-27 10:05:43 +00:00
|
|
|
|
if (unlink(entry.nativeFilePath().constData()) == 0)
|
|
|
|
|
return true;
|
|
|
|
|
error = QSystemError(errno, QSystemError::StandardLibraryError);
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-29 17:38:13 +00:00
|
|
|
|
//static
|
2024-03-14 20:59:59 +00:00
|
|
|
|
bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry,
|
|
|
|
|
QFile::Permissions permissions, QSystemError &error)
|
2017-06-29 17:38:13 +00:00
|
|
|
|
{
|
2019-09-04 13:26:10 +00:00
|
|
|
|
Q_CHECK_FILE_NAME(entry, false);
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
2021-10-26 12:52:36 +00:00
|
|
|
|
mode_t mode = QtPrivate::toMode_t(permissions);
|
2011-04-27 10:05:43 +00:00
|
|
|
|
bool success = ::chmod(entry.nativeFilePath().constData(), mode) == 0;
|
2017-06-29 17:38:13 +00:00
|
|
|
|
if (!success)
|
|
|
|
|
error = QSystemError(errno, QSystemError::StandardLibraryError);
|
|
|
|
|
return success;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//static
|
2024-03-14 20:59:59 +00:00
|
|
|
|
bool QFileSystemEngine::setPermissions(int fd, QFile::Permissions permissions, QSystemError &error)
|
2017-06-29 17:38:13 +00:00
|
|
|
|
{
|
2021-10-26 12:52:36 +00:00
|
|
|
|
mode_t mode = QtPrivate::toMode_t(permissions);
|
2017-06-29 17:38:13 +00:00
|
|
|
|
bool success = ::fchmod(fd, mode) == 0;
|
2011-04-27 10:05:43 +00:00
|
|
|
|
if (!success)
|
|
|
|
|
error = QSystemError(errno, QSystemError::StandardLibraryError);
|
|
|
|
|
return success;
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-02 17:28:39 +00:00
|
|
|
|
//static
|
2024-02-11 22:31:27 +00:00
|
|
|
|
bool QFileSystemEngine::setFileTime(int fd, const QDateTime &newDate, QFile::FileTime time, QSystemError &error)
|
2017-07-02 17:28:39 +00:00
|
|
|
|
{
|
2024-02-11 22:31:27 +00:00
|
|
|
|
if (!newDate.isValid()
|
|
|
|
|
|| time == QFile::FileBirthTime || time == QFile::FileMetadataChangeTime) {
|
2017-07-02 17:28:39 +00:00
|
|
|
|
error = QSystemError(EINVAL, QSystemError::StandardLibraryError);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if QT_CONFIG(futimens)
|
2022-10-30 08:54:26 +00:00
|
|
|
|
// UTIME_OMIT: leave file timestamp unchanged
|
|
|
|
|
struct timespec ts[2] = {{0, UTIME_OMIT}, {0, UTIME_OMIT}};
|
|
|
|
|
|
2024-02-11 22:31:27 +00:00
|
|
|
|
if (time == QFile::FileAccessTime || time == QFile::FileModificationTime) {
|
|
|
|
|
const int idx = time == QFile::FileAccessTime ? 0 : 1;
|
2023-01-27 20:34:46 +00:00
|
|
|
|
const std::chrono::milliseconds msecs{newDate.toMSecsSinceEpoch()};
|
2023-02-24 17:00:17 +00:00
|
|
|
|
ts[idx] = durationToTimespec(msecs);
|
2017-07-02 17:28:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
|
QString QFileSystemEngine::homePath()
|
|
|
|
|
{
|
2024-12-31 19:26:39 +00:00
|
|
|
|
QString home = qEnvironmentVariable("HOME");
|
2013-01-02 15:43:39 +00:00
|
|
|
|
if (home.isEmpty())
|
2011-04-27 10:05:43 +00:00
|
|
|
|
home = rootPath();
|
|
|
|
|
return QDir::cleanPath(home);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString QFileSystemEngine::rootPath()
|
|
|
|
|
{
|
2022-03-18 09:31:16 +00:00
|
|
|
|
return u"/"_s;
|
2011-04-27 10:05:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-05-08 09:01:39 +00:00
|
|
|
|
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??");
|
|
|
|
|
return temp;
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
|
QString QFileSystemEngine::tempPath()
|
|
|
|
|
{
|
|
|
|
|
#ifdef QT_UNIX_TEMP_PATH_OVERRIDE
|
2022-03-09 20:26:01 +00:00
|
|
|
|
return QT_UNIX_TEMP_PATH_OVERRIDE ""_L1;
|
2011-04-27 10:05:43 +00:00
|
|
|
|
#else
|
2024-12-31 19:26:39 +00:00
|
|
|
|
QString temp = qEnvironmentVariable("TMPDIR");
|
2016-11-17 15:10:11 +00:00
|
|
|
|
if (temp.isEmpty()) {
|
2017-12-10 06:54:04 +00:00
|
|
|
|
if (false) {
|
2016-11-17 15:10:11 +00:00
|
|
|
|
#if defined(Q_OS_DARWIN) && !defined(QT_BOOTSTRAPPED)
|
2017-12-10 06:54:04 +00:00
|
|
|
|
} else if (NSString *nsPath = NSTemporaryDirectory()) {
|
2016-11-17 15:10:11 +00:00
|
|
|
|
temp = QString::fromCFString((CFStringRef)nsPath);
|
|
|
|
|
#endif
|
2017-12-10 06:54:04 +00:00
|
|
|
|
} else {
|
2025-05-08 09:01:39 +00:00
|
|
|
|
constexpr auto nativeTemp = nativeTempPath();
|
|
|
|
|
temp = nativeTemp;
|
2016-11-17 15:10:11 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2025-05-08 09:01:39 +00:00
|
|
|
|
|
|
|
|
|
// 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();
|
2011-04-27 10:05:43 +00:00
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QFileSystemEngine::setCurrentPath(const QFileSystemEntry &path)
|
|
|
|
|
{
|
|
|
|
|
int r;
|
2012-02-22 15:17:30 +00:00
|
|
|
|
r = QT_CHDIR(path.nativeFilePath().constData());
|
2011-04-27 10:05:43 +00:00
|
|
|
|
return r >= 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QFileSystemEntry QFileSystemEngine::currentPath()
|
|
|
|
|
{
|
|
|
|
|
QFileSystemEntry result;
|
|
|
|
|
#if defined(__GLIBC__) && !defined(PATH_MAX)
|
2014-12-08 11:00:04 +00:00
|
|
|
|
char *currentName = ::get_current_dir_name();
|
|
|
|
|
if (currentName) {
|
|
|
|
|
result = QFileSystemEntry(QByteArray(currentName), QFileSystemEntry::FromNativePath());
|
|
|
|
|
::free(currentName);
|
|
|
|
|
}
|
2011-04-27 10:05:43 +00:00
|
|
|
|
#else
|
2014-12-08 11:00:04 +00:00
|
|
|
|
char currentName[PATH_MAX+1];
|
|
|
|
|
if (::getcwd(currentName, PATH_MAX)) {
|
2013-01-03 13:50:45 +00:00
|
|
|
|
#if defined(Q_OS_VXWORKS) && defined(VXWORKS_VXSIM)
|
2014-12-08 11:00:04 +00:00
|
|
|
|
QByteArray dir(currentName);
|
|
|
|
|
if (dir.indexOf(':') < dir.indexOf('/'))
|
|
|
|
|
dir.remove(0, dir.indexOf(':')+1);
|
2013-01-03 13:50:45 +00:00
|
|
|
|
|
2014-12-08 11:00:04 +00:00
|
|
|
|
qstrncpy(currentName, dir.constData(), PATH_MAX);
|
2013-01-03 13:50:45 +00:00
|
|
|
|
#endif
|
2014-12-08 11:00:04 +00:00
|
|
|
|
result = QFileSystemEntry(QByteArray(currentName), QFileSystemEntry::FromNativePath());
|
|
|
|
|
}
|
2011-04-27 10:05:43 +00:00
|
|
|
|
# if defined(QT_DEBUG)
|
2014-12-08 11:00:04 +00:00
|
|
|
|
if (result.isEmpty())
|
|
|
|
|
qWarning("QFileSystemEngine::currentPath: getcwd() failed");
|
2011-04-27 10:05:43 +00:00
|
|
|
|
# endif
|
|
|
|
|
#endif
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2024-09-19 20:29:01 +00:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
|
QT_END_NAMESPACE
|