diff options
Diffstat (limited to 'src/corelib/io/qfilesystemengine_unix.cpp')
-rw-r--r-- | src/corelib/io/qfilesystemengine_unix.cpp | 216 |
1 files changed, 214 insertions, 2 deletions
diff --git a/src/corelib/io/qfilesystemengine_unix.cpp b/src/corelib/io/qfilesystemengine_unix.cpp index 38cb6a423d..eaf4e2d9af 100644 --- a/src/corelib/io/qfilesystemengine_unix.cpp +++ b/src/corelib/io/qfilesystemengine_unix.cpp @@ -42,6 +42,8 @@ #include "qplatformdefs.h" #include "qfilesystemengine_p.h" #include "qfile.h" +#include "qstorageinfo.h" +#include "qtextstream.h" #include <QtCore/qoperatingsystemversion.h> #include <QtCore/private/qcore_unix_p.h> @@ -1076,14 +1078,14 @@ bool QFileSystemEngine::cloneFile(int srcfd, int dstfd, const QFileSystemMetaDat // sendfile(2) is limited in the kernel to 2G - 4k const size_t SendfileSize = 0x7ffff000; - ssize_t n = ::sendfile(dstfd, srcfd, NULL, SendfileSize); + ssize_t n = ::sendfile(dstfd, srcfd, nullptr, SendfileSize); if (n == -1) { // if we got an error here, give up and try at an upper layer return false; } while (n) { - n = ::sendfile(dstfd, srcfd, NULL, SendfileSize); + n = ::sendfile(dstfd, srcfd, nullptr, SendfileSize); if (n == -1) { // uh oh, this is probably a real error (like ENOSPC), but we have // no way to notify QFile of partial success, so just erase any work @@ -1197,6 +1199,216 @@ bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSy return false; } +#ifndef Q_OS_DARWIN +/* + Implementing as per https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html +*/ + +// bootstrapped tools don't need this, and we don't want QStorageInfo +#ifndef QT_BOOTSTRAPPED +static QString freeDesktopTrashLocation(const QString &sourcePath) +{ + auto makeTrashDir = [](const QDir &topDir, const QString &trashDir) -> QString { + auto ownerPerms = QFileDevice::ReadOwner + | QFileDevice::WriteOwner + | QFileDevice::ExeOwner; + QString targetDir = topDir.filePath(trashDir); + if (topDir.mkdir(trashDir)) + QFile::setPermissions(targetDir, ownerPerms); + if (QFileInfo(targetDir).isDir()) + return targetDir; + return QString(); + }; + auto isSticky = [](const QFileInfo &fileInfo) -> bool { + struct stat st; + if (stat(QFile::encodeName(fileInfo.absoluteFilePath()).constData(), &st) == 0) + return st.st_mode & S_ISVTX; + + return false; + }; + + QString trash; + const QLatin1String dotTrash(".Trash"); + const QStorageInfo sourceStorage(sourcePath); + const QStorageInfo homeStorage(QDir::home()); + // We support trashing of files outside the users home partition + if (sourceStorage != homeStorage) { + QDir topDir(sourceStorage.rootPath()); + /* + 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." + */ + const QString userID = QString::number(::getuid()); + if (topDir.cd(dotTrash)) { + const QFileInfo trashInfo(topDir.path()); + + // we MUST check that the sticky bit is set, and that it is not a symlink + if (trashInfo.isSymLink()) { + // we SHOULD report the failed check to the administrator + qCritical("Warning: '%s' is a symlink to '%s'", + trashInfo.absoluteFilePath().toLocal8Bit().constData(), + trashInfo.symLinkTarget().toLatin1().constData()); + } else if (!isSticky(trashInfo)) { + // we SHOULD report the failed check to the administrator + qCritical("Warning: '%s' doesn't have sticky bit set!", + trashInfo.absoluteFilePath().toLocal8Bit().constData()); + } else if (trashInfo.isDir()) { + /* + "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." + */ + trash = makeTrashDir(topDir, userID); + } + } + /* + Method 2: + "If an $topdir/.Trash directory is absent, an $topdir/.Trash-$uid directory is to be + used as the user's trash directory for this device/partition. [...] When trashing a + file, if an $topdir/.Trash-$uid directory does not exist, the implementation MUST + immediately create it, without any warnings or delays for the user." + */ + if (trash.isEmpty()) { + topDir = QDir(sourceStorage.rootPath()); + const QString userTrashDir = dotTrash + QLatin1Char('-') + userID; + trash = makeTrashDir(topDir, userTrashDir); + } + } + /* + "If both (1) and (2) fail [...], the implementation MUST either trash the + file into the user's “home trash” or refuse to trash it." + + We trash the file into the user's home trash. + */ + if (trash.isEmpty()) { + QDir topDir = QDir::home(); + trash = makeTrashDir(topDir, dotTrash); + if (!QFileInfo(trash).isDir()) { + qWarning("Unable to establish trash directory %s in %s", + dotTrash.latin1(), topDir.path().toLocal8Bit().constData()); + } + } + + return trash; +} +#endif // QT_BOOTSTRAPPED + +//static +bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &source, + QFileSystemEntry &newLocation, QSystemError &error) +{ +#ifdef QT_BOOTSTRAPPED + Q_UNUSED(source); + Q_UNUSED(newLocation); + error = QSystemError(ENOSYS, QSystemError::StandardLibraryError); + return false; +#else + const QFileInfo sourceInfo(source.filePath()); + if (!sourceInfo.exists()) { + error = QSystemError(ENOENT, QSystemError::StandardLibraryError); + return false; + } + const QString sourcePath = sourceInfo.absoluteFilePath(); + + QDir trashDir(freeDesktopTrashLocation(sourcePath)); + if (!trashDir.exists()) + return false; + /* + "A trash directory contains two subdirectories, named info and files." + */ + const QLatin1String filesDir("files"); + const QLatin1String infoDir("info"); + trashDir.mkdir(filesDir); + int savedErrno = errno; + trashDir.mkdir(infoDir); + if (!savedErrno) + savedErrno = errno; + if (!trashDir.exists(filesDir) || !trashDir.exists(infoDir)) { + error = QSystemError(savedErrno, QSystemError::StandardLibraryError); + return false; + } + /* + "The $trash/files directory contains the files and directories that were trashed. + The names of files in this directory are to be determined by the implementation; + the only limitation is that they must be unique within the directory. Even if a + file with the same name and location gets trashed many times, each subsequent + trashing must not overwrite a previous copy." + */ + const QString trashedName = sourceInfo.isDir() + ? QDir(sourcePath).dirName() + : sourceInfo.fileName(); + QString uniqueTrashedName = QLatin1Char('/') + trashedName; + QString infoFileName; + int counter = 0; + QFile infoFile; + auto makeUniqueTrashedName = [trashedName, &counter]() -> QString { + ++counter; + return QString(QLatin1String("/%1-%2")) + .arg(trashedName) + .arg(counter, 4, 10, QLatin1Char('0')); + }; + do { + while (QFile::exists(trashDir.filePath(filesDir) + uniqueTrashedName)) + uniqueTrashedName = makeUniqueTrashedName(); + /* + "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." + */ + infoFileName = trashDir.filePath(infoDir) + + uniqueTrashedName + QLatin1String(".trashinfo"); + infoFile.setFileName(infoFileName); + if (!infoFile.open(QIODevice::NewOnly | QIODevice::WriteOnly | QIODevice::Text)) + uniqueTrashedName = makeUniqueTrashedName(); + } while (!infoFile.isOpen()); + + const QString targetPath = trashDir.filePath(filesDir) + uniqueTrashedName; + const QFileSystemEntry target(targetPath); + + /* + We might fail to rename if source and target are on different file systems. + In that case, we don't try further, i.e. copying and removing the original + is usually not what the user would expect to happen. + */ + if (!renameFile(source, target, error)) { + infoFile.close(); + infoFile.remove(); + return false; + } + + QTextStream out(&infoFile); +#if QT_CONFIG(textcodec) + out.setCodec("UTF-8"); +#endif + out << "[Trash Info]" << Qt::endl; + out << "Path=" << sourcePath << Qt::endl; + out << "DeletionDate=" + << QDateTime::currentDateTime().toString(QLatin1String("yyyy-MM-ddThh:mm:ss")) << Qt::endl; + infoFile.close(); + + newLocation = QFileSystemEntry(targetPath); + return true; +#endif // QT_BOOTSTRAPPED +} +#endif // Q_OS_DARWIN + //static bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) { |