Commit Graph

221 Commits

Author SHA1 Message Date
Thiago Macieira c617cc9593 QFileSystemEngine::tempPath: bypass QDir and go straight to QFSEngine
Temporary paths coming from the environment must be real filesystem
things, never a Qt file engine, so we don't need to create QDir with its
QDirPrivate, in order to call QFileSystemEngine.

The replacing of canonicalPath() with QFSE::absoluteName() is fine
because canonicalizing *after* cleanPath() is the wrong thing. For
example, if you had:

  $ ln -s $HOME/tmp /tmp/symlink
  $ TMPDIR=/tmp/symlink/..
then
  cleanPath($TMPDIR) = /tmp
  absolute($TMPDIR) = /tmp    # QFSE::absoluteName calls cleanPath
  canonical($TMPDIR) = $HOME
  canonical(cleanPath($TMPDIR)) = /tmp

The lack of canonicalization now only affects when the final path is a
symlink. Doing so bought us little security if it is a symlink and it
could change, because the result is not cached and could change from
call to call. That changing is probably worse than any attack, because
you could end up with

  QDir::tempPath() != QDir::tempPath()

[ChangeLog][QtCore][QDir] tempPath() may now return a non-canonical
path. This means going up from it (cdUp()) may result in different paths
from string manipulation (adding "/..").

Pick-to: 6.9
Change-Id: Iddf6f46edf6f3b6c3222fffd1e1e5479f0be92a9
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
(cherry picked from commit 7bd7df5aa1)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
2025-06-04 19:19:11 +00:00
Even Oscar Andersen 6295e78ade wasm: fix warning in createDirectoryWithParents for "/"
Change-Id: I6cd17ef9dd7ba26cbb8969817f2bcf83cc0ec24d
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
2025-04-23 18:25:50 +02:00
Karim Pinter fd73b22089 Fix VxWorks POSIX access() behavior
On VxWorks POSIX access() returns always false if it is called on file
which is not on POSIX file system, like DOSFS for example. Qt for
VxWorks 5.15 had similar patch omitting all access() calls and returning
true. This fix takes it a step further and checks if the file is on
POSIX filesystem or if it is DOSFS with read-only setting or not,
in case omitting access() call and returning true. Failure became
visible using QTranslator::load() method on DOSFS mmc card.

Task-number: QTBUG-134627
Pick-to: 6.8 6.9
Change-Id: I9c257d3cba1a2b976f2775ad129aae0e09f68ffd
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2025-04-14 09:38:59 +03:00
Joerg Bornemann fc277e3ff6 Fix linker errors when building bootstrap lib on macOS
The bootstrap lib doesn't build qfilesystemengine_mac.mm, and that's
where QFileSystemEngine::supportsMoveFileToTrash and
QFileSystemEngine::moveFileToTrash are implemented.

Make sure that the preprocessor picks up the QT_BOOTSTRAPPED condition
first to define implementations for the two methods.

Pick-to: 6.9
Fixes: QTBUG-135650
Change-Id: Icf7995abfae368b9f5bd5c4ccfcf4c3c6664d519
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2025-04-09 13:15:44 +02:00
Thiago Macieira ffb81e6270 QFileSystemEngine/Unix: remove superfluous setting of knownFlagsMask
fillFromStatBuf() and fillFromStatxBuf() (through flagsFromStMode()) set
the PosixStatFlags, ExistsAttribute, and sometimes HiddenAttribute. It's
harmless to set the same bits again, but we can do slightly better.

Change-Id: I1def9449b4ba10fbcd49fffd0fe6fc8734d0b3bd
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
2025-03-27 18:50:59 -07:00
Thiago Macieira e86b970ea4 QFileSystemEngine/Unix: deduplicate the st_mode parsing
Both fillFromStatBuf() and fillFromStatxBuf() had copies of the parsing
of the st_mode / stx_mode field. This deduplicates them and sets up for
when/if other OSes get a statx() system call similar to Linux's.

Pick-to: 6.9
Change-Id: I7c80d121c21b76d78b81fffd172d205287b595d9
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
2025-03-27 18:50:59 -07:00
Thiago Macieira 45886e6a81 QFileSystemEngine/Darwin: remove use of clonefile() on Apple systems
Because we called this before opening the target file, we did not check
that the destination file engine is the QFSFileEngine, and therefore we
may have passed a path that referred to a Qt resource path. We still do
that on Windows, but Qt resource paths start with a ':' character and
that is not allowed on Windows, so the Win32 CopyFile() function fails.

Moreover, this function *can* clone directory trees, which we don't want
to happen because it doesn't happen on other OSes. Instead, fall back to
the fcopyfile() call in cloneFile(). It will still do cloning where
permitted.

This was added in commit db0064b767,
before the fcopyfile() in 974b3adf8a,
though both were for Qt 5.10.

[ChangeLog][QtCore][QFile] Fixed a bug on Apple systems that would cause
copy() to copy a directory if the QFile pointed to a directory and the
destination was in the same volume. Now copy()'s behavior is the same as
in other OSes: directories are never copied.

[ChangeLog][QtCore][QFile] Fixed a bug on Apple systems that allowed
copy() to create a file with the name that referred to a Qt resource
path, if one tried to copy to that path. Qt resource paths can't be
modified using QFile.

Pick-to: 6.9
Change-Id: I996368f4c656ff10ff5efffd5b47c6ddde34fd10
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
2025-01-27 18:41:32 -08:00
Thiago Macieira 9b006eb91a QFileSystemEngine/Unix: use copy_file_range(2) in cloneFile()
For remote filesystems on Linux, this can invoke a server-side copy to
avoid network traffic. For local filesystems, Linux can use an optimized
FS-specific call (which may be the same operation as the FICLONE, but
I'm not sure) or splice the data on its own.

Tested with:
 - FreeBSD ufs
 - FreeBSD ufs-to-tmpfs
 - FreeBSD tmpfs (tested ENOSPC with it)
 - Linux btrfs
 - Linux ext4
 - Linux tmpfs (tested ENOSPC with it)

Change-Id: I3ae11d555882bdbb0487fffd81cb5568171cee3f
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2025-01-27 12:11:23 -08:00
Thiago Macieira fe75526542 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-27 12:11:23 -08:00
Thiago Macieira be2df3c6e0 QFileSystemEngine: let cloneFile() inform QFile of permanent errors
The Unix implementation in QFileSystemEngine::cloneFile() was unable to
tell the upper layer whether the failure was permanent (like ENOSPC and
EIO) or whether the fast copy attempt wasn't possible. This resulted in
QFile always retrying even after ENOSPC conditions.

This commit resolves that.

Change-Id: I38a830a99a0d38bcb51efffdf34bb7fead639496
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2025-01-27 12:11:23 -08:00
Kai Köhne ccc99bcf8b Doc: Update external link to freedesktop.org trash specification
Old link gives a 404

Pick-to: 6.9
Change-Id: I3e528a29a6ed2d0d97c3fcf1cf11c441a2882415
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Jaishree Vyas <jaishree.vyas@qt.io>
2025-01-27 18:44:23 +01:00
Thiago Macieira 40eea11807 QFile/Unix: ensure the destination of copy() is empty before cloneFile()
This is potentially a bug in the !QT_CONFIG(temporaryfile) case in which
we wouldn't truncate the file after we had copied it.

Change-Id: Ieea7fb3ca9981e555140fffd2300fcebcdd800b5
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
2025-01-24 16:26:34 -08:00
Thiago Macieira db34e27f7f Replace qgetenv() calls converted to QString with qEnvironmentVariable()
It's slightly more efficient.

Pick-to: 6.9
Change-Id: Id5ac04fc27eee108c8e5fffd786c3d5f793a0a9d
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2025-01-01 14:23:05 -03:00
Ahmad Samir b6ab002b3b QFileSystemEngine: remove QFSMetaData* parameter from setPermissions()
Nothing passes a QFileSystemMetaData* to any of those methods.

Add QFileSystemMetaData::setPermissions(), which contains the code that
used to be in QFileSystemEngine::setPermissions(); callers can use it
directly to set the permissions on a QFileSystemMetaData object, after
the QFileSystemEngine::setPermissions() call succeeds.

Change-Id: I9f3415e969680f3b7039a7a8982032349e0133e1
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
2024-11-13 08:12:00 +03:00
Jarno Lämsä 039b0c6b9b Don't support moving files to trash on VxWorks
On VxWorks 24.03, the `AT_FDCWD` wasn't defined, which happened to
branch to the correct branch. VxWorsk 24.09 TP does define the
`AT_FDCWD`, which causes the preprocessor directives to branch to a
branch where moving files to trash would be supported.

In a sidenote, VxWorks doesn't define `renameat`, `O_DIRECTORY` nor
`O_NOFOLLOW`.

Task-number: QTBUG-130629
Pick-to: 6.8
Change-Id: I2b850813aeff6f925ab91932efd7aeb13f753f69
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2024-11-08 15:45:05 +02:00
Michał Łoś 2cf12a6bae Fix directory permissions not set correctly on VxWorks
In commit e275db9d88, changes were made to
handling directory creation in qfilesystem_unix.cpp. These changes
missed VxWorks-specific case where its system `mkdir` function treats
permissions of `000` as `default permissions`. This is causing failure
of `tst_QDir::mkdirWithPermissions(0000)` test.

To fix this, use `forceRequestedPermissionsOnVxWorks` function for
VxWorks in `QFileSystemEngine::mkdir`.

Fixes: QTBUG-130737
Task-number: QTBUG-115777
Change-Id: I16fc36448693050c7c92096d804a1caeb7e65115
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2024-11-05 19:53:02 +01:00
Jari Helaakoski 3ee98d6adf Fix -no-feature-datestring and -no-feature-xmlstreamreader
Task-number: QTBUG-112830
Change-Id: I25dad19dee98d64eb5c226cbcc2b628f2a371ea4
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2024-10-28 11:48:28 +03:00
Ahmad Samir e275db9d88 QFileSystemEngine: split dir creation into mkdir/mkpath
Split the logic of createDirectory() into mkdir and mkpath.
This matches QDir::mkdir()/mkpath().

"mkdir()" won't confuse the compiler about which method to call,
because libc's mkdir() is either used via QT_MKDIR which expands to
"::mkdir" or directly as ::mdkir(), whereas the static
QFileSystemEngine::mkdir() is always called with full scope.

Change-Id: I31b67727cce23f1bc560432d40231a24dc560d5b
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2024-10-25 22:52:38 +03:00
Ahmad Samir 42bde675de QFileSystemEngine/Unix: refactor rmpath()
Convert the path to QByteArray once up top, instead of using
QFile::encodeName() multiple times in the for-loop, and then use the
same QByteArray and truncate() it in each iteration.

Extend tst_qdir unittests.

Change-Id: I0d879e7f429db25879859acab074c2c197f41f6c
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2024-10-10 19:03:43 +03:00
Ahmad Samir ead72a1155 QFileSystemEngine: add rmpath()
removeDirectory() acts in two mode, rmdir (one entry) and rmpath (the
entry and all empty parent directories). This is irregular behavior, and
API with a very specific use-case (in unittests, where you create a dir
tree and want to cleanup after the test finishes).

So, split the code into rmdir() and rmpath(), which matches
QDir::rmdir() and QDir::rmpath().

On Unix, a further optimization pointed out by Thiago in review, remove
the stat() call, ::rmdir() will fail with ENOTDIR if we try to remove
anything that isn't a dir.

Change-Id: I1bbb6e6c1ce49ba6d73d3c510b449223498612fb
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2024-10-10 03:10:24 +03:00
Tor Arne Vestbø 3d08816f4c Darwin: Teach QFileSystemEngine how to resolve case-sensitivity
Both APFS and HFS+ can be both case-sensitive and case-insensitive
(the default), and the mounted file system may be any other file
system than these two as well, so hard-coding to case-sensitive
is not sufficient.

Pick-to: 6.8
Task-number: QTBUG-28246
Task-number: QTBUG-31103
Change-Id: Ibdb902df3f169b016a519f67ad5a79e6afb6aae3
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2024-10-02 11:41:14 +00:00
Thiago Macieira 3b9f5c82f5 QFileSystemEngine/Unix: implement getting the size of block devices
Implemented for Linux, macOS, and FreeBSD. This works only on open files
because of the need to ioctl().

Before:
"/dev/system/stuff2" : 0

After:
"/dev/system/stuff2" : 68719476736              [Linux]
"/dev/ada1" : 42949672960                       [FreeBSD]
"/dev/disk0" : 500277792768                     [macOS]
"/dev/disk2" : 39306240                         [macOS]

With:
    if (f.open(QIODevice::ReadOnly | QIODevice::Unbuffered))
        qDebug() << f.fileName() << ':' << f.size();

[ChangeLog][QtCore][QFile] For open block devices on Unix systems,
size() now returns the size of the underlying device. Previously, it
would always return 0.

Change-Id: I8a96935cf6c742259c9dfffd17e9402bdbd6b963
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2024-09-25 11:15:32 -07:00
Even Oscar Andersen 0568511e84 wasm: Make sure QDir::mkpath("/") works on webassembly
As described in bug QTBUG-127767 mkdir("/") returns ENOSPC
on webassembly, and the code is not prepared to handle that.
We solve it by explicitly checking for "/" which shall
always be present.

Fixes: QTBUG-127767
Change-Id: Icf86f0b681ac3bd8ddc97c1a4168c2faf02aa4a1
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2024-09-25 20:15:31 +02:00
Thiago Macieira c41b6b7986 QFile::moveFileToTrash: explicitly disable Android support
This code has been enabled but didn't work: we kept getting permission
errors on use. So let's save some library size.

Pick-to: 6.8
Change-Id: Ifb754f0e28774c20aa7cfffd17e7549e7624e42a
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2024-08-07 18:35:48 -07:00
Thiago Macieira 17d1d577c9 QFile: add supportsMoveToTrash()
[ChangeLog][QtCore][QFile] Added supportsMoveToTrash() to check if Qt
supports moving files to trash in the current OS.

Fixes: QTBUG-127580
Change-Id: Ifb754f0e28774c20aa7cfffd17e6c951ffd9d9ff
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2024-08-08 01:35:47 +00:00
Thiago Macieira 31cf699e69 QFileSystemEngine::canonicalName: skip QDir::cleanPath()
realpath() is supposed to return the canonicalized absolute path, so
there should be nothing left to clean.

Pick-to: 6.8
Change-Id: Ie30a3caf09ef4176bb36fffd17cde30c7538dba9
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2024-07-10 05:55:19 -07:00
Thiago Macieira 3302b0cdc1 QFile::moveToTrash/Unix: ensure we try to remove the proper source
We already had code to strip ending slashes, which makes sense if trying
to trash directories. In the case of symlinks to directories, that also
changes from trashing the directory to trashing the symlink. But had to
removeFile() on the same modified path.

Fixes: QTBUG-126621
Pick-to: 6.7 6.8
Change-Id: I46feca3a447244a8ba19fffd17dc0b56f2b1c68c
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
2024-06-25 08:12:39 -07:00
Łukasz Matysiak 90e79aea8e Force requested permissions when calling mkdir on VxWorks
Calling mkdir with mode == 0 works just fine on Linux - it creates a
directory and the permissions are set to 0.
On VxWorks, calling mkdir with mode == 0 uses the default mode set in
the system.
This leads to a failing test (tst_QDir::mkdirWithPermissions(0000)) and
potential confusion when the same code does not behave in the same way
when called on Linux and VxWorks.

To keep the same interface between unix-like systems, explicitly set the
permissions to 0 when on VxWorks.

Pick-to: 6.7 6.8
Task-number: QTBUG-115777
Change-Id: I75e429c086500cb7c19f9bb14693bb4266e18046
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Reviewed-by: Michał Łoś <michal.los@siili.com>
2024-06-18 17:55:33 +02:00
Łukasz Matysiak 8915ae3a75 Make createDirectoryWithParents return true for existing dirs on read only fs
When the filesystem is mounted in a read only mode, Linux returns true
on an attempt to create a dir that already exists on that fs.
However not every platform behaves that way.

VxWorks is not a fully unix-like system.
It is possible to enable a component that provides a virtual root file
system (VRFS) so that devices and paths can be managed using "/" as a root.
The root itself is not an actual path that can be used like on other systems.
It is not possible to store files directly in "/".

On Linux, mkdir on "/" returns EEXIST.
On VxWorks, it returns EROFS (read only file system).

That leads to a failing test (tst_QDir::makedirReturnCode).
It also leads to a broken contract, since the doc for QDir::mkpath states that:
`If the path already exists when this function is called, it will return
true.`
The doc for createDirectoryWithParents has no such comment and it is
used by other functions that also do not promise such things, but the
implementation behaves that way anyway: when errno == EEXIST -> return
true if the target path is an existing directory.

Since the existing unix implementation already returns true for existing dirs
(without checking if it was called in the context of `mkpath` or any other
function), fix the problem by checking if errno == EROFS after a call
to mkdir and then checking if the target path already exists and is a directory

Pick-to: 6.7 6.8
Task-number: QTBUG-115777
Change-Id: I849bca56618bf675933cccc5a9d5313e0014628b
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Reviewed-by: Karim Pinter <karim.pinter@qt.io>
Reviewed-by: Jarno Lämsä <jarno.lamsa@qt.io>
2024-06-16 23:56:59 +02:00
Thiago Macieira e32009fc0d QFileSystemEngine::canonicalName: don't use malloc()'ing realpath()
It is available in POSIX.1-2008 but is not worth it. The Linux man page
says it only works up to PATH_MAX anyway and the POSIX documentation
says it's UB if PATH_MAX isn't defined.

This cleans up the source code out of these messy #if and should be
faster, because we avoid a heap allocation (stack is always faster).

Instead, we only relegate the heap version to the case where PATH_MAX
isn't defined (i.e., GNU HURD), because in that case the realpath()
function can't be used with a stack allocation.

Pick-to: 6.7
Change-Id: Ie30a3caf09ef4176bb36fffd17cde1ed5c5dad59
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2024-05-24 21:34:27 +00:00
Łukasz Matysiak f097cbd9bf Make QFileSystemEngine::canonicalName use the realpath function on VxWorks
Despite realpath being available on VxWorks (when the INCLUDE_IO_REALPATH
component is used in the VIP), canonicalName doesn't use it, because
the system reports _POSIX_VERSION as 200112.

Fix the problem by adding an additional condition so that VxWorks
correctly uses realpath.

Pick-to: 6.7
Task-number: QTBUG-115777
Change-Id: I734f525e870f93a7ec955d379dcc2137b591e171
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2024-05-10 10:15:42 +02:00
Ahmad Samir fd295f4bf6 QAbstractFileEngine: remove member FileTime and use QFile::FileTime
This is probably a remnant from when QAbstractFileEngine was public API
since it's been changed to private API, just use QFile::FileTime.

Pick-to: 6.7
Change-Id: I60d3d4ff811f95434b81d5ca115f5d43cfff8b15
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2024-03-21 19:05:02 +02:00
Tor Arne Vestbø 2a2a301557 Apple: Fix a few deprecation warnings after bumping deployment targets
- kIOMasterPortDefault -> kIOMainPortDefault
 - Use UTType instead of Carbon Core functions/constants
 - NSWorkspace iconForFileType -> iconForContentType
 - Removed obsoleted kUTTypeInkText pasteboard type

There are still a few more, but these will be fixed in follow ups.

Change-Id: Ibbca226d578b4ba64bd9c8c5d0addc1870114a20
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
2024-02-19 18:03:11 +01:00
Krzysztof Sommerfeld 21196d26d8 Make use of methods defined in utils unix layer
Some unix-like concepts are supported by VxWorks VSB layer - UTILS_UNIX.
One of such methods is getgrgid(). Include it in
`qfilesystemengine_unix.cpp`, so that we don't need to exclude VxWorks from code that uses it anymore.

Task-number: QTBUG-115777
Change-Id: I72b301647bfdb208cb6859bb0f9994e3537fc345
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2024-02-06 15:23:42 +00:00
Ahmad Samir 6d9185c0e6 QFileSystemEngine: use nativeFilePath()
Instead of filePath() then converting to QByteArray.

Change-Id: I6f656774979bedde5c657613303518750ab06855
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2024-02-01 23:42:56 +02:00
Marcin Zdunek 1d4ab5c135 Remove qfunctions_vxworks files and usage as it is no longer needed
Task-number: QTBUG-115777
Change-Id: I0d803ac7ce067737b79a39a267a2b0eb509ae0b8
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2024-01-25 12:21:41 +01:00
Thiago Macieira d5393a936d 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-10-26 11:36:46 -07:00
Thiago Macieira c94bed69b7 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-10-17 19:08:25 -07:00
Thiago Macieira 25b1990784 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-10-17 19:08:25 -07:00
Thiago Macieira 77c661b275 moveToTrash/Unix: use lower-level API to write the info file
So we can more easily get any errors from attempting to write the file.
It is possible to get them with QFile, by either doing .flush() or using
QIODevice::Unbuffered, but using the C API is a definite sure way. Plus,
since this is QFileSystemEngine, this avoids the possibility that QFile
may choose to use a different file engine than the native one, for some
reason. And it reduces overhead.

This allows us to more easily detect why the file creation failed and
therefore stop looping if the error wasn't EEXIST. That will avoid an
infinite loop in case the necessary directories exist but aren't
writable.

It's also moved above the renaming, such that the failure to populate
the info file prevents the renaming too. Both operations can have the
same likely errors, ENOSPC and EIO. The likelihood of EIO is very low,
for both; but for ENOSPC it's far more likely for writing the
file. Avoiding the ENOSPC error for the renaming is handled in a later
commit.

Change-Id: I9d43e5b91eb142d6945cfffd1786d417142ac728
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-10-17 19:08:25 -07:00
Thiago Macieira 6359e8b8bd moveToTrash/Unix: avoid creating too many QStorageInfo
QStorageInfo is great, but rather expensive, so this introduces a faster
check by stat()ing the source file and $HOME, to see if they are the
same device, saving us two or three QStorageInfo constructions. That is
a necessary condition: if they aren't the same device, we know rename()
into $HOME/.local/share/Trash will fail.

But it's not a sufficient condition: they need to be the same mount
point and that's something only QStorageInfo will give us. Strictly
speaking, the only way to be sure that you can rename() into the trash
path is to, well, attempt it (as usual, something for a later commit).

Change-Id: I9d43e5b91eb142d6945cfffd1786c474cac25083
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-10-17 19:08:25 -07:00
Thiago Macieira de24134aa7 moveToTrash/Unix: avoid TOCTOU in creating the unique file name
This is not a security issue because we still use QIODevice::NewOnly
(O_EXCL) and loop again. But because we do so, we don't need to check
for existence with QFile::exists() in the first place.

Pick-to: 6.6
Change-Id: I9d43e5b91eb142d6945cfffd1786c98a39781517
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
2023-10-17 19:08:25 -07:00
Thiago Macieira 1c5575f194 moveToTrash/Unix: trust freeDesktopTrashLocation() to find the directory
Make it receive the QSystemError so it can set the error condition
properly in case the suitable location for this input file can't be
found. This also includes the case when the input file does not exist in
the first place, which I moved into the function because upcoming
commits will imply this check anyway.

Change-Id: I9d43e5b91eb142d6945cfffd1786c6e59d3b0204
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
2023-10-17 19:08:24 -07:00
Thiago Macieira 61d99530c8 moveToTrash/Unix: avoid QFileInfo to get an absolute file name
We know what engine we're using, so don't go the long way around via
QFileInfo and QFSFileEngine to get back to QFileSystemEngine in order to
calculate an absolute and clean path.

Since we're doing that, we may as well use QFileSystemEntry's ability to
give us the file name portion of this absolute path without having to go
via QFileInfo and QDir again. We just need to make sure that a dir name
isn't ending in a slash: absoluteName() would remove that for us, but
only if the entry isn't already absolute and clean.

Change-Id: I9d43e5b91eb142d6945cfffd17871389d359e750
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
2023-10-06 18:43:30 -07:00
Thiago Macieira fa97531952 moveToTrash/Unix: reorganize the #ifdef
Change-Id: I9d43e5b91eb142d6945cfffd178708f58b71e7ef
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
2023-10-06 18:43:29 -07:00
Thiago Macieira 36a169e31e moveToTrash/Unix: use lstat() to confirm $root/.Trash is suitable
We can't use QFileSystemEngine::fillMetaData() because there's no bit in
QFileSystemMetaData to indicate the sticky flag, so we must make a at
least one stat() or lstat() call ourselves. Given that we need to know
if $root/.Trash is a symlink, that system call must be lstat(). And it
turns out that system call provides everything we need to confirm its
suitability.

This avoids QDir overhead just to manipulate strings.

Pick-to: 6.6
Change-Id: I9d43e5b91eb142d6945cfffd1786c5e54199ecb2
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
2023-09-23 16:16:09 -07:00
Thiago Macieira 3d027f8d95 moveToTrash/Unix: remove unnecessary targetPath variable
It was used twice, in both cases to create a QFileSystemEntry, so the
two results were equal. Therefore, just use the first result to create
the second.

Pick-to: 6.6
Change-Id: I9d43e5b91eb142d6945cfffd1786d45d20485f40
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
2023-09-23 08:18:31 -07:00
Thiago Macieira 6434b6ea48 moveToTrash/Unix: rename 'infoPath' variable to 'pathForInfo'
We have other variables whose name start with 'info' in this function,
so infoPath is misleading: it's not the path to infoFile and it isn't
related to the infoFileName. Instead, it's the path to the file being
trashed which will be saved in the info file.

Pick-to: 6.6
Change-Id: I9d43e5b91eb142d6945cfffd1786d358a0e02dfd
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
2023-09-23 08:18:30 -07:00
Thiago Macieira 6e4d9ff74d moveToTrash/Unix: use Qt::ISODate to format the current date
Instead of forcing QDateTime to parse our pattern.

Pick-to: 6.6
Change-Id: I9d43e5b91eb142d6945cfffd1786d094a123826a
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
2023-09-23 08:18:23 -07:00
Thiago Macieira a71f556830 moveToTrash/Unix: avoid mkdir/chmod race condition for the trash dir
QDir::mkdir() followed by QFile::setPermissions() is a race condition
because an attacker could enter the directory before we set the
permissions. QDir::mkdir() got an overload with the permissions in 6.3,
but I decided to go a level lower and use QFileSystemEngine directly
here.

Pick-to: 6.5 6.6
Change-Id: I9d43e5b91eb142d6945cfffd1786c338e21c129e
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
2023-09-23 08:18:22 -07:00