diff options
Diffstat (limited to 'src/corelib/io')
32 files changed, 1276 insertions, 402 deletions
diff --git a/src/corelib/io/io.pri b/src/corelib/io/io.pri index fe81689932..a33ffe75f2 100644 --- a/src/corelib/io/io.pri +++ b/src/corelib/io/io.pri @@ -136,6 +136,7 @@ qtConfig(settings) { } else: darwin:!nacl { SOURCES += io/qsettings_mac.cpp } + wasm : SOURCES += io/qsettings_wasm.cpp } win32 { @@ -182,7 +183,9 @@ win32 { SOURCES += io/qstorageinfo_mac.cpp qtConfig(processenvironment): \ OBJECTIVE_SOURCES += io/qprocess_darwin.mm - OBJECTIVE_SOURCES += io/qstandardpaths_mac.mm + OBJECTIVE_SOURCES += \ + io/qstandardpaths_mac.mm \ + io/qfilesystemengine_mac.mm osx { LIBS += -framework DiskArbitration -framework IOKit } else { diff --git a/src/corelib/io/qabstractfileengine.cpp b/src/corelib/io/qabstractfileengine.cpp index 8a1679c5af..070139b608 100644 --- a/src/corelib/io/qabstractfileengine.cpp +++ b/src/corelib/io/qabstractfileengine.cpp @@ -658,7 +658,7 @@ QStringList QAbstractFileEngine::entryList(QDir::Filters filters, const QStringL QAbstractFileEngine::FileFlags QAbstractFileEngine::fileFlags(FileFlags type) const { Q_UNUSED(type); - return nullptr; + return {}; } /*! diff --git a/src/corelib/io/qbuffer.cpp b/src/corelib/io/qbuffer.cpp index 8e980733de..595fcd2724 100644 --- a/src/corelib/io/qbuffer.cpp +++ b/src/corelib/io/qbuffer.cpp @@ -227,7 +227,7 @@ QBuffer::~QBuffer() \snippet buffer/buffer.cpp 4 - If \a byteArray is 0, the buffer creates its own internal + If \a byteArray is \nullptr, the buffer creates its own internal QByteArray to work on. This byte array is initially empty. \sa buffer(), setData(), open() diff --git a/src/corelib/io/qdiriterator.cpp b/src/corelib/io/qdiriterator.cpp index ce436b06e3..1cf6b1be08 100644 --- a/src/corelib/io/qdiriterator.cpp +++ b/src/corelib/io/qdiriterator.cpp @@ -181,7 +181,7 @@ QDirIteratorPrivate::QDirIteratorPrivate(const QFileSystemEntry &entry, const QS #elif QT_CONFIG(regularexpression) nameRegExps.reserve(nameFilters.size()); for (const auto &filter : nameFilters) { - QString re = QRegularExpression::anchoredPattern(QRegularExpression::wildcardToRegularExpression(filter)); + QString re = QRegularExpression::wildcardToRegularExpression(filter); nameRegExps.append( QRegularExpression(re, (filters & QDir::CaseSensitive) ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption)); } diff --git a/src/corelib/io/qfile.cpp b/src/corelib/io/qfile.cpp index 95f03ef816..0cdc5bd6d3 100644 --- a/src/corelib/io/qfile.cpp +++ b/src/corelib/io/qfile.cpp @@ -251,7 +251,7 @@ QFile::QFile(QFilePrivate &dd) Constructs a QFile object. */ QFile::QFile() - : QFileDevice(*new QFilePrivate, 0) + : QFileDevice(*new QFilePrivate, nullptr) { } /*! @@ -265,7 +265,7 @@ QFile::QFile(QObject *parent) Constructs a new file object to represent the file with the given \a name. */ QFile::QFile(const QString &name) - : QFileDevice(*new QFilePrivate, 0) + : QFileDevice(*new QFilePrivate, nullptr) { Q_D(QFile); d->fileName = name; @@ -552,6 +552,66 @@ QFile::remove(const QString &fileName) } /*! + \since 5.15 + + Moves the file specified by fileName() to the trash. Returns \c true if successful, + and sets the fileName() to the path at which the file can be found within the trash; + otherwise returns \c false. + + \note On systems where the system API doesn't report the location of the file in the + trash, fileName() will be set to the null string once the file has been moved. On + systems that don't have a trash can, this function always returns false. +*/ +bool +QFile::moveToTrash() +{ + Q_D(QFile); + if (d->fileName.isEmpty() && + !static_cast<QFSFileEngine *>(d->engine())->isUnnamedFile()) { + qWarning("QFile::remove: Empty or null file name"); + return false; + } + unsetError(); + close(); + if (error() == QFile::NoError) { + QFileSystemEntry fileEntry(d->fileName); + QFileSystemEntry trashEntry; + QSystemError error; + if (QFileSystemEngine::moveFileToTrash(fileEntry, trashEntry, error)) { + setFileName(trashEntry.filePath()); + unsetError(); + return true; + } + d->setError(QFile::RenameError, error.toString()); + } + return false; +} + +/*! + \since 5.15 + \overload + + Moves the file specified by fileName() to the trash. Returns \c true if successful, + and sets \a pathInTrash (if provided) to the path at which the file can be found within + the trash; otherwise returns \c false. + + \note On systems where the system API doesn't report the path of the file in the + trash, \a pathInTrash will be set to the null string once the file has been moved. + On systems that don't have a trash can, this function always returns false. +*/ +bool +QFile::moveToTrash(const QString &fileName, QString *pathInTrash) +{ + QFile file(fileName); + if (file.moveToTrash()) { + if (pathInTrash) + *pathInTrash = file.fileName(); + return true; + } + return false; +} + +/*! Renames the file currently specified by fileName() to \a newName. Returns \c true if successful; otherwise returns \c false. diff --git a/src/corelib/io/qfile.h b/src/corelib/io/qfile.h index 2099b2852f..917fec4e1a 100644 --- a/src/corelib/io/qfile.h +++ b/src/corelib/io/qfile.h @@ -125,6 +125,9 @@ public: bool remove(); static bool remove(const QString &fileName); + bool moveToTrash(); + static bool moveToTrash(const QString &fileName, QString *pathInTrash = nullptr); + bool rename(const QString &newName); static bool rename(const QString &oldName, const QString &newName); diff --git a/src/corelib/io/qfiledevice.cpp b/src/corelib/io/qfiledevice.cpp index ee619d99cc..b0aba3193c 100644 --- a/src/corelib/io/qfiledevice.cpp +++ b/src/corelib/io/qfiledevice.cpp @@ -202,7 +202,7 @@ QFileDevice::QFileDevice(QFileDevicePrivate &dd) \internal */ QFileDevice::QFileDevice() - : QIODevice(*new QFileDevicePrivate, 0) + : QIODevice(*new QFileDevicePrivate, nullptr) { } /*! diff --git a/src/corelib/io/qfileinfo.cpp b/src/corelib/io/qfileinfo.cpp index 89834de29f..64b1557231 100644 --- a/src/corelib/io/qfileinfo.cpp +++ b/src/corelib/io/qfileinfo.cpp @@ -134,7 +134,7 @@ uint QFileInfoPrivate::getFileFlags(QAbstractFileEngine::FileFlags request) cons // extra syscall. Bundle detecton on Mac can be slow, expecially on network // paths, so we separate out that as well. - QAbstractFileEngine::FileFlags req = nullptr; + QAbstractFileEngine::FileFlags req; uint cachedFlags = 0; if (request & (QAbstractFileEngine::FlagsMask | QAbstractFileEngine::TypesMask)) { @@ -1145,6 +1145,27 @@ bool QFileInfo::isShortcut() const [d]() { return d->getFileFlags(QAbstractFileEngine::LinkType); }); } + +/*! + \since 5.15 + + Returns \c true if the object points to a junction; + otherwise returns \c false. + + Junctions only exist on Windows' NTFS file system, and are typically + created by the \c{mklink} command. They can be thought of as symlinks for + directories, and can only be created for absolute paths on the local + volume. +*/ +bool QFileInfo::isJunction() const +{ + Q_D(const QFileInfo); + return d->checkAttribute<bool>( + QFileSystemMetaData::LegacyLinkType, + [d]() { return d->metaData.isJunction(); }, + [d]() { return d->getFileFlags(QAbstractFileEngine::LinkType); }); +} + /*! Returns \c true if the object points to a directory or to a symbolic link to a directory, and that directory is the root directory; otherwise diff --git a/src/corelib/io/qfileinfo.h b/src/corelib/io/qfileinfo.h index 3ac028085a..7c7ff56ae4 100644 --- a/src/corelib/io/qfileinfo.h +++ b/src/corelib/io/qfileinfo.h @@ -113,6 +113,7 @@ public: bool isSymLink() const; bool isSymbolicLink() const; bool isShortcut() const; + bool isJunction() const; bool isRoot() const; bool isBundle() const; diff --git a/src/corelib/io/qfilesystemengine_mac.mm b/src/corelib/io/qfilesystemengine_mac.mm new file mode 100644 index 0000000000..258ae2e8e0 --- /dev/null +++ b/src/corelib/io/qfilesystemengine_mac.mm @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qfilesystemengine_p.h" +#include "qfile.h" +#include "qurl.h" + +#include <QtCore/private/qcore_mac_p.h> +#include <CoreFoundation/CoreFoundation.h> + +QT_BEGIN_NAMESPACE + +/* + This implementation does not enable the "put back" option in Finder + for the trashed object. The only way to get this is to use Finder automation, + which would query the user for permission to access Finder using a modal, + blocking dialog - which we definitely can't have in a console application. + + Using Finder would also play the trash sound, which we don't want either in + such a core API; applications that want that can play the sound themselves. +*/ +//static +bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &source, + QFileSystemEntry &newLocation, QSystemError &error) +{ +#ifdef Q_OS_MACOS // desktop macOS has a trash can + QMacAutoReleasePool pool; + + QFileInfo info(source.filePath()); + NSString *filepath = info.filePath().toNSString(); + NSURL *fileurl = [NSURL fileURLWithPath:filepath isDirectory:info.isDir()]; + NSURL *resultingUrl = nil; + NSError *nserror = nil; + NSFileManager *fm = [NSFileManager defaultManager]; + if ([fm trashItemAtURL:fileurl resultingItemURL:&resultingUrl error:&nserror] != YES) { + error = QSystemError(nserror.code, QSystemError::NativeError); + return false; + } + newLocation = QFileSystemEntry(QUrl::fromNSURL(resultingUrl).path()); + return true; +#else // watch, tv, iOS don't have a trash can + return false; +#endif +} + +QT_END_NAMESPACE diff --git a/src/corelib/io/qfilesystemengine_p.h b/src/corelib/io/qfilesystemengine_p.h index ecfdc03743..555483a972 100644 --- a/src/corelib/io/qfilesystemengine_p.h +++ b/src/corelib/io/qfilesystemengine_p.h @@ -88,7 +88,7 @@ inline bool qIsFilenameBroken(const QFileSystemEntry &entry) Q_RETURN_ON_INVALID_FILENAME("Broken filename passed to function", (result)); \ } while (false) -class QFileSystemEngine +class Q_AUTOTEST_EXPORT QFileSystemEngine { public: static bool isCaseSensitive() @@ -155,6 +155,7 @@ public: static bool createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error); static bool copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error); + static bool moveFileToTrash(const QFileSystemEntry &source, QFileSystemEntry &newLocation, QSystemError &error); static bool renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error); static bool renameOverwriteFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error); static bool removeFile(const QFileSystemEntry &entry, QSystemError &error); 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) { diff --git a/src/corelib/io/qfilesystemengine_win.cpp b/src/corelib/io/qfilesystemengine_win.cpp index ae29190848..36d43e9cb7 100644 --- a/src/corelib/io/qfilesystemengine_win.cpp +++ b/src/corelib/io/qfilesystemengine_win.cpp @@ -41,6 +41,7 @@ #include "qoperatingsystemversion.h" #include "qplatformdefs.h" #include "qsysinfo.h" +#include "qscopeguard.h" #include "private/qabstractfileengine_p.h" #include "private/qfsfileengine_p.h" #include <private/qsystemlibrary_p.h> @@ -59,6 +60,8 @@ #include <objbase.h> #ifndef Q_OS_WINRT # include <shlobj.h> +# include <shobjidl.h> +# include <shellapi.h> # include <lm.h> # include <accctrl.h> #endif @@ -422,6 +425,104 @@ static inline bool getFindData(QString path, WIN32_FIND_DATA &findData) return false; } +#if defined(__IFileOperation_INTERFACE_DEFINED__) +class FileOperationProgressSink : public IFileOperationProgressSink +{ +public: + FileOperationProgressSink() + : ref(1) + {} + virtual ~FileOperationProgressSink() {} + + ULONG STDMETHODCALLTYPE AddRef() + { + return ++ref; + } + ULONG STDMETHODCALLTYPE Release() + { + if (--ref == 0) { + delete this; + return 0; + } + return ref; + } + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **ppvObject) + { + if (!ppvObject) + return E_POINTER; + + *ppvObject = nullptr; + + if (iid == __uuidof(IUnknown)) { + *ppvObject = static_cast<IUnknown*>(this); + } else if (iid == __uuidof(IFileOperationProgressSink)) { + *ppvObject = static_cast<IFileOperationProgressSink*>(this); + } + + if (*ppvObject) { + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; + } + + HRESULT STDMETHODCALLTYPE StartOperations() + { return S_OK; } + HRESULT STDMETHODCALLTYPE FinishOperations(HRESULT) + { return S_OK; } + HRESULT STDMETHODCALLTYPE PreRenameItem(DWORD, IShellItem *, LPCWSTR) + { return S_OK; } + HRESULT STDMETHODCALLTYPE PostRenameItem(DWORD, IShellItem *, LPCWSTR, HRESULT, IShellItem *) + { return S_OK; } + HRESULT STDMETHODCALLTYPE PreMoveItem(DWORD, IShellItem *, IShellItem *, LPCWSTR) + { return S_OK; } + HRESULT STDMETHODCALLTYPE PostMoveItem(DWORD, IShellItem *, IShellItem *, LPCWSTR, HRESULT, + IShellItem *) + { return S_OK; } + HRESULT STDMETHODCALLTYPE PreCopyItem(DWORD, IShellItem *, IShellItem *, LPCWSTR ) + { return S_OK; } + HRESULT STDMETHODCALLTYPE PostCopyItem(DWORD, IShellItem *, IShellItem *, LPCWSTR, HRESULT, + IShellItem *) + { return S_OK; } + HRESULT STDMETHODCALLTYPE PreDeleteItem(DWORD dwFlags, IShellItem *) + { + // stop the operation if the file will be deleted rather than trashed + return (dwFlags & TSF_DELETE_RECYCLE_IF_POSSIBLE) ? S_OK : E_FAIL; + } + HRESULT STDMETHODCALLTYPE PostDeleteItem(DWORD /* dwFlags */, IShellItem * /* psiItem */, + HRESULT /* hrDelete */, IShellItem *psiNewlyCreated) + { + if (psiNewlyCreated) { + wchar_t *pszName = nullptr; + psiNewlyCreated->GetDisplayName(SIGDN_FILESYSPATH, &pszName); + if (pszName) { + targetPath = QString::fromWCharArray(pszName); + CoTaskMemFree(pszName); + } + } + return S_OK; + } + HRESULT STDMETHODCALLTYPE PreNewItem(DWORD, IShellItem *, LPCWSTR) + { return S_OK; } + HRESULT STDMETHODCALLTYPE PostNewItem(DWORD, IShellItem *, LPCWSTR, LPCWSTR, DWORD, HRESULT, + IShellItem *) + { return S_OK; } + HRESULT STDMETHODCALLTYPE UpdateProgress(UINT,UINT) + { return S_OK; } + HRESULT STDMETHODCALLTYPE ResetTimer() + { return S_OK; } + HRESULT STDMETHODCALLTYPE PauseTimer() + { return S_OK; } + HRESULT STDMETHODCALLTYPE ResumeTimer() + { return S_OK; } + + QString targetPath; +private: + ULONG ref; +}; +#endif + bool QFileSystemEngine::uncListSharesOnServer(const QString &server, QStringList *list) { DWORD res = ERROR_NOT_SUPPORTED; @@ -1123,67 +1224,62 @@ static bool isDirPath(const QString &dirPath, bool *existed) return fileAttrib & FILE_ATTRIBUTE_DIRECTORY; } +// NOTE: if \a shouldMkdirFirst is false, we assume the caller did try to mkdir +// before calling this function. +static bool createDirectoryWithParents(const QString &nativeName, bool shouldMkdirFirst = true) +{ + const auto isUNCRoot = [](const QString &nativeName) { + return nativeName.startsWith(QLatin1String("\\\\")) && nativeName.count(QDir::separator()) <= 3; + }; + const auto isDriveName = [](const QString &nativeName) { + return nativeName.size() == 2 && nativeName.at(1) == QLatin1Char(':'); + }; + const auto isDir = [](const QString &nativeName) { + bool exists = false; + return isDirPath(nativeName, &exists) && exists; + }; + // Do not try to mkdir a UNC root path or a drive letter. + if (isUNCRoot(nativeName) || isDriveName(nativeName)) + return false; + + if (shouldMkdirFirst) { + if (mkDir(nativeName)) + return true; + } + + const int backSlash = nativeName.lastIndexOf(QDir::separator()); + if (backSlash < 1) + return false; + + const QString parentNativeName = nativeName.left(backSlash); + if (!createDirectoryWithParents(parentNativeName)) + return false; + + // try again + if (mkDir(nativeName)) + return true; + return isDir(nativeName); +} + //static bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents) { QString dirName = entry.filePath(); Q_CHECK_FILE_NAME(dirName, false); - if (createParents) { - dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName)); - // We spefically search for / so \ would break it.. - int oldslash = -1; - if (dirName.startsWith(QLatin1String("\\\\"))) { - // Don't try to create the root path of a UNC path; - // CreateDirectory() will just return ERROR_INVALID_NAME. - for (int i = 0; i < dirName.size(); ++i) { - if (dirName.at(i) != QDir::separator()) { - oldslash = i; - break; - } - } - if (oldslash != -1) - oldslash = dirName.indexOf(QDir::separator(), oldslash); - } else if (dirName.size() > 2 - && dirName.at(1) == QLatin1Char(':')) { - // Don't try to call mkdir with just a drive letter - oldslash = 2; - } - for (int slash=0; slash != -1; oldslash = slash) { - slash = dirName.indexOf(QDir::separator(), oldslash+1); - if (slash == -1) { - if (oldslash == dirName.length()) - break; - slash = dirName.length(); - } - if (slash) { - DWORD lastError; - QString chunk = dirName.left(slash); - if (!mkDir(chunk, &lastError)) { - if (lastError == ERROR_ALREADY_EXISTS || lastError == ERROR_ACCESS_DENIED) { - bool existed = false; - if (isDirPath(chunk, &existed) && existed) - continue; -#ifdef Q_OS_WINRT - static QThreadStorage<QString> dataLocation; - if (!dataLocation.hasLocalData()) - dataLocation.setLocalData(QDir::toNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation))); - static QThreadStorage<QString> tempLocation; - if (!tempLocation.hasLocalData()) - tempLocation.setLocalData(QDir::toNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::TempLocation))); - // We try to create something outside the sandbox, which is forbidden - // However we could still try to pass into the sandbox - if (dataLocation.localData().startsWith(chunk) || tempLocation.localData().startsWith(chunk)) - continue; -#endif - } - return false; - } - } - } + dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName)); + + // try to mkdir this directory + DWORD lastError; + if (mkDir(dirName, &lastError)) return true; - } - return mkDir(entry.filePath()); + // mkpath should return true, if the directory already exists, mkdir false. + if (!createParents) + return false; + if (lastError == ERROR_ALREADY_EXISTS) + return isDirPath(dirName, nullptr); + + return createDirectoryWithParents(dirName, false); } //static @@ -1436,6 +1532,103 @@ bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError & return ret; } +/* + If possible, we use the IFileOperation implementation, which allows us to determine + the location of the object in the trash. + If not (likely on mingw), we fall back to the old API, which won't allow us to know + that. +*/ +//static +bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &source, + QFileSystemEntry &newLocation, QSystemError &error) +{ +#ifndef Q_OS_WINRT + // we need the "display name" of the file, so can't use nativeFilePath + const QString sourcePath = QDir::toNativeSeparators(source.filePath()); + + /* + Windows 7 insists on showing confirmation dialogs and ignores the respective + flags set on IFileOperation. Fall back to SHFileOperation, even if it doesn't + give us the new location of the file. + */ + if (QOperatingSystemVersion::current() > QOperatingSystemVersion::Windows7) { +# if defined(__IFileOperation_INTERFACE_DEFINED__) + CoInitialize(NULL); + IFileOperation *pfo = nullptr; + IShellItem *deleteItem = nullptr; + FileOperationProgressSink *sink = nullptr; + HRESULT hres = E_FAIL; + + auto coUninitialize = qScopeGuard([&](){ + if (sink) + sink->Release(); + if (deleteItem) + deleteItem->Release(); + if (pfo) + pfo->Release(); + CoUninitialize(); + if (!SUCCEEDED(hres)) + error = QSystemError(hres, QSystemError::NativeError); + }); + + hres = CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pfo)); + if (!pfo) + return false; + pfo->SetOperationFlags(FOF_ALLOWUNDO | FOFX_RECYCLEONDELETE | FOF_NOCONFIRMATION + | FOF_SILENT | FOF_NOERRORUI); + hres = SHCreateItemFromParsingName(reinterpret_cast<const wchar_t*>(sourcePath.utf16()), + nullptr, IID_PPV_ARGS(&deleteItem)); + if (!deleteItem) + return false; + sink = new FileOperationProgressSink; + hres = pfo->DeleteItem(deleteItem, static_cast<IFileOperationProgressSink*>(sink)); + if (!SUCCEEDED(hres)) + return false; + hres = pfo->PerformOperations(); + if (!SUCCEEDED(hres)) + return false; + newLocation = QFileSystemEntry(sink->targetPath); + +# endif // no IFileOperation in SDK (mingw, likely) - fall back to SHFileOperation + } else { + // double null termination needed, so can't use QString::utf16 + QVarLengthArray<wchar_t, MAX_PATH + 1> winFile(sourcePath.length() + 2); + sourcePath.toWCharArray(winFile.data()); + winFile[sourcePath.length()] = wchar_t{}; + winFile[sourcePath.length() + 1] = wchar_t{}; + + SHFILEOPSTRUCTW operation; + operation.hwnd = nullptr; + operation.wFunc = FO_DELETE; + operation.pFrom = winFile.constData(); + operation.pTo = nullptr; + operation.fFlags = FOF_ALLOWUNDO | FOF_NO_UI; + operation.fAnyOperationsAborted = FALSE; + operation.hNameMappings = nullptr; + operation.lpszProgressTitle = nullptr; + + int result = SHFileOperation(&operation); + if (result != 0) { + error = QSystemError(result, QSystemError::NativeError); + return false; + } + /* + This implementation doesn't let us know where the file ended up, even if + we would specify FOF_WANTMAPPINGHANDLE | FOF_RENAMEONCOLLISION, as + FOF_RENAMEONCOLLISION has no effect unless files are moved, copied, or renamed. + */ + Q_UNUSED(newLocation); + } + return true; + +#else // Q_OS_WINRT + Q_UNUSED(source); + Q_UNUSED(newLocation); + Q_UNUSED(error); + return false; +#endif +} + //static bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry, QFile::Permissions permissions, QSystemError &error, QFileSystemMetaData *data) diff --git a/src/corelib/io/qfilesystemmetadata_p.h b/src/corelib/io/qfilesystemmetadata_p.h index 81f4b3ba13..3154658e5c 100644 --- a/src/corelib/io/qfilesystemmetadata_p.h +++ b/src/corelib/io/qfilesystemmetadata_p.h @@ -76,8 +76,7 @@ class Q_AUTOTEST_EXPORT QFileSystemMetaData { public: QFileSystemMetaData() - : knownFlagsMask(nullptr), - size_(-1) + : size_(-1) { } @@ -111,8 +110,10 @@ public: AliasType = 0x0, #endif #if defined(Q_OS_WIN) + JunctionType = 0x04000000, WinLnkType = 0x08000000, // Note: Uses the same position for AliasType on Mac #else + JunctionType = 0x0, WinLnkType = 0x0, #endif SequentialType = 0x00800000, // Note: overlaps with QAbstractFileEngine::RootFlag @@ -184,7 +185,7 @@ public: void clear() { - knownFlagsMask = nullptr; + knownFlagsMask = {}; } void clearFlags(MetaDataFlags flags = AllMetaDataFlags) @@ -205,8 +206,10 @@ public: bool wasDeleted() const { return (entryFlags & WasDeletedAttribute); } #if defined(Q_OS_WIN) bool isLnkFile() const { return (entryFlags & WinLnkType); } + bool isJunction() const { return (entryFlags & JunctionType); } #else bool isLnkFile() const { return false; } + bool isJunction() const { return false; } #endif qint64 size() const { return size_; } @@ -356,9 +359,15 @@ inline void QFileSystemMetaData::fillFromFindData(WIN32_FIND_DATA &findData, boo if (setLinkType) { knownFlagsMask |= LinkType; entryFlags &= ~LinkType; - if ((fileAttribute_ & FILE_ATTRIBUTE_REPARSE_POINT) - && (findData.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) { - entryFlags |= LinkType; + if (fileAttribute_ & FILE_ATTRIBUTE_REPARSE_POINT) { + if (findData.dwReserved0 == IO_REPARSE_TAG_SYMLINK) { + entryFlags |= LinkType; +#if defined(IO_REPARSE_TAG_MOUNT_POINT) + } else if ((fileAttribute_ & FILE_ATTRIBUTE_DIRECTORY) + && (findData.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT)) { + entryFlags |= JunctionType; +#endif + } } } } diff --git a/src/corelib/io/qfilesystemwatcher.cpp b/src/corelib/io/qfilesystemwatcher.cpp index 54460aff77..86c8963cb6 100644 --- a/src/corelib/io/qfilesystemwatcher.cpp +++ b/src/corelib/io/qfilesystemwatcher.cpp @@ -88,7 +88,7 @@ QFileSystemWatcherEngine *QFileSystemWatcherPrivate::createNativeEngine(QObject } QFileSystemWatcherPrivate::QFileSystemWatcherPrivate() - : native(0), poller(0) + : native(nullptr), poller(nullptr) { } diff --git a/src/corelib/io/qfilesystemwatcher_inotify.cpp b/src/corelib/io/qfilesystemwatcher_inotify.cpp index ca1f6cc359..888af998a5 100644 --- a/src/corelib/io/qfilesystemwatcher_inotify.cpp +++ b/src/corelib/io/qfilesystemwatcher_inotify.cpp @@ -242,7 +242,7 @@ QInotifyFileSystemWatcherEngine *QInotifyFileSystemWatcherEngine::create(QObject if (fd == -1) { fd = inotify_init(); if (fd == -1) - return 0; + return nullptr; } return new QInotifyFileSystemWatcherEngine(fd, parent); } diff --git a/src/corelib/io/qfsfileengine.cpp b/src/corelib/io/qfsfileengine.cpp index 0d73839f8d..3042eac2f0 100644 --- a/src/corelib/io/qfsfileengine.cpp +++ b/src/corelib/io/qfsfileengine.cpp @@ -911,14 +911,7 @@ bool QFSFileEngine::supportsExtension(Extension extension) const } /*! \fn bool QFSFileEngine::caseSensitive() const - Returns \c true for Windows, false for Unix. -*/ - -/*! \fn bool QFSFileEngine::copy(const QString ©Name) - - For Windows or Apple platforms, copy the file to file \a copyName. - - Not implemented for other Unix platforms. + Returns \c false for Windows, true for Unix. */ /*! \fn QString QFSFileEngine::currentPath(const QString &fileName) @@ -950,11 +943,34 @@ bool QFSFileEngine::supportsExtension(Extension extension) const \reimp */ -/*! \fn QString QFSFileEngine::homePath() +/*! Returns the home path of the current user. \sa rootPath() */ +QString QFSFileEngine::homePath() +{ + return QFileSystemEngine::homePath(); +} + +/*! + Returns the root path. + + \sa homePath() +*/ +QString QFSFileEngine::rootPath() +{ + return QFileSystemEngine::rootPath(); +} + +/*! + Returns the temporary path (i.e., a path in which it is safe + to store temporary files). +*/ +QString QFSFileEngine::tempPath() +{ + return QFileSystemEngine::tempPath(); +} /*! \fn bool QFSFileEngine::isRelativePath() const \reimp @@ -968,9 +984,6 @@ bool QFSFileEngine::supportsExtension(Extension extension) const true if successful; otherwise returns \c false. */ -/*! \fn bool QFSFileEngine::mkdir(const QString &name, bool createParentDirectories) const - \reimp -*/ /*! \fn uint QFSFileEngine::ownerId(QAbstractFileEngine::FileOwner own) const In Unix, if stat() is successful, the \c uid is returned if @@ -984,35 +997,87 @@ bool QFSFileEngine::supportsExtension(Extension extension) const \reimp */ -/*! \fn bool QFSFileEngine::remove() - \reimp +/*! + For Windows or Apple platforms, copy the file to file \a copyName. + + Not implemented for other Unix platforms. */ +bool QFSFileEngine::copy(const QString ©Name) +{ + Q_D(QFSFileEngine); + QSystemError error; + bool ret = QFileSystemEngine::copyFile(d->fileEntry, QFileSystemEntry(copyName), error); + if (!ret) + setError(QFile::CopyError, error.toString()); + return ret; +} -/*! \fn bool QFSFileEngine::rename(const QString &newName) +/*! \reimp */ +bool QFSFileEngine::remove() +{ + Q_D(QFSFileEngine); + QSystemError error; + bool ret = QFileSystemEngine::removeFile(d->fileEntry, error); + d->metaData.clear(); + if (!ret) + setError(QFile::RemoveError, error.toString()); + return ret; +} - -/*! \fn bool QFSFileEngine::renameOverwrite(const QString &newName) +/*! \reimp */ - -/*! \fn bool QFSFileEngine::rmdir(const QString &name, bool recurseParentDirectories) const +bool QFSFileEngine::rename(const QString &newName) +{ + Q_D(QFSFileEngine); + QSystemError error; + bool ret = QFileSystemEngine::renameFile(d->fileEntry, QFileSystemEntry(newName), error); + if (!ret) + setError(QFile::RenameError, error.toString()); + return ret; +} +/*! \reimp */ +bool QFSFileEngine::renameOverwrite(const QString &newName) +{ + Q_D(QFSFileEngine); + QSystemError error; + bool ret = QFileSystemEngine::renameOverwriteFile(d->fileEntry, QFileSystemEntry(newName), error); + if (!ret) + setError(QFile::RenameError, error.toString()); + return ret; +} -/*! \fn QString QFSFileEngine::rootPath() - Returns the root path. +/*! + \reimp +*/ +bool QFSFileEngine::mkdir(const QString &name, bool createParentDirectories) const +{ + return QFileSystemEngine::createDirectory(QFileSystemEntry(name), createParentDirectories); +} - \sa homePath() +/*! + \reimp */ +bool QFSFileEngine::rmdir(const QString &name, bool recurseParentDirectories) const +{ + return QFileSystemEngine::removeDirectory(QFileSystemEntry(name), recurseParentDirectories); +} -/*! \fn bool QFSFileEngine::setCurrentPath(const QString &path) + +/*! Sets the current path (e.g., for QDir), to \a path. Returns \c true if the new path exists; otherwise this function does nothing, and returns \c false. \sa currentPath() */ +bool QFSFileEngine::setCurrentPath(const QString &path) +{ + return QFileSystemEngine::setCurrentPath(QFileSystemEntry(path)); +} /*! \fn bool QFSFileEngine::setPermissions(uint perms) \reimp @@ -1022,11 +1087,6 @@ bool QFSFileEngine::supportsExtension(Extension extension) const \reimp */ -/*! \fn QString QFSFileEngine::tempPath() - Returns the temporary path (i.e., a path in which it is safe - to store temporary files). -*/ - /*! \fn QAbstractFileEngine::FileFlags QFSFileEnginePrivate::getPermissions(QAbstractFileEngine::FileFlags type) const \internal */ diff --git a/src/corelib/io/qfsfileengine_unix.cpp b/src/corelib/io/qfsfileengine_unix.cpp index d4983c72af..4610e9306c 100644 --- a/src/corelib/io/qfsfileengine_unix.cpp +++ b/src/corelib/io/qfsfileengine_unix.cpp @@ -304,54 +304,6 @@ bool QFSFileEnginePrivate::nativeIsSequential() const return isSequentialFdFh(); } -bool QFSFileEngine::remove() -{ - Q_D(QFSFileEngine); - QSystemError error; - bool ret = QFileSystemEngine::removeFile(d->fileEntry, error); - d->metaData.clear(); - if (!ret) { - setError(QFile::RemoveError, error.toString()); - } - return ret; -} - -bool QFSFileEngine::copy(const QString &newName) -{ - Q_D(QFSFileEngine); - QSystemError error; - bool ret = QFileSystemEngine::copyFile(d->fileEntry, QFileSystemEntry(newName), error); - if (!ret) { - setError(QFile::CopyError, error.toString()); - } - return ret; -} - -bool QFSFileEngine::renameOverwrite(const QString &newName) -{ - Q_D(QFSFileEngine); - QSystemError error; - bool ret = QFileSystemEngine::renameOverwriteFile(d->fileEntry, QFileSystemEntry(newName), error); - - if (!ret) - setError(QFile::RenameError, error.toString()); - - return ret; -} - -bool QFSFileEngine::rename(const QString &newName) -{ - Q_D(QFSFileEngine); - QSystemError error; - bool ret = QFileSystemEngine::renameFile(d->fileEntry, QFileSystemEntry(newName), error); - - if (!ret) { - setError(QFile::RenameError, error.toString()); - } - - return ret; -} - bool QFSFileEngine::link(const QString &newName) { Q_D(QFSFileEngine); @@ -368,45 +320,16 @@ qint64 QFSFileEnginePrivate::nativeSize() const return sizeFdFh(); } -bool QFSFileEngine::mkdir(const QString &name, bool createParentDirectories) const -{ - return QFileSystemEngine::createDirectory(QFileSystemEntry(name), createParentDirectories); -} - -bool QFSFileEngine::rmdir(const QString &name, bool recurseParentDirectories) const -{ - return QFileSystemEngine::removeDirectory(QFileSystemEntry(name), recurseParentDirectories); -} - bool QFSFileEngine::caseSensitive() const { return true; } -bool QFSFileEngine::setCurrentPath(const QString &path) -{ - return QFileSystemEngine::setCurrentPath(QFileSystemEntry(path)); -} - QString QFSFileEngine::currentPath(const QString &) { return QFileSystemEngine::currentPath().filePath(); } -QString QFSFileEngine::homePath() -{ - return QFileSystemEngine::homePath(); -} - -QString QFSFileEngine::rootPath() -{ - return QFileSystemEngine::rootPath(); -} - -QString QFSFileEngine::tempPath() -{ - return QFileSystemEngine::tempPath(); -} QFileInfoList QFSFileEngine::drives() { diff --git a/src/corelib/io/qfsfileengine_win.cpp b/src/corelib/io/qfsfileengine_win.cpp index 19cc3e6402..dd4882a2bc 100644 --- a/src/corelib/io/qfsfileengine_win.cpp +++ b/src/corelib/io/qfsfileengine_win.cpp @@ -443,66 +443,11 @@ bool QFSFileEnginePrivate::nativeIsSequential() const #endif } -bool QFSFileEngine::remove() -{ - Q_D(QFSFileEngine); - QSystemError error; - bool ret = QFileSystemEngine::removeFile(d->fileEntry, error); - if (!ret) - setError(QFile::RemoveError, error.toString()); - return ret; -} - -bool QFSFileEngine::copy(const QString ©Name) -{ - Q_D(QFSFileEngine); - QSystemError error; - bool ret = QFileSystemEngine::copyFile(d->fileEntry, QFileSystemEntry(copyName), error); - if (!ret) - setError(QFile::CopyError, error.toString()); - return ret; -} - -bool QFSFileEngine::rename(const QString &newName) -{ - Q_D(QFSFileEngine); - QSystemError error; - bool ret = QFileSystemEngine::renameFile(d->fileEntry, QFileSystemEntry(newName), error); - if (!ret) - setError(QFile::RenameError, error.toString()); - return ret; -} - -bool QFSFileEngine::renameOverwrite(const QString &newName) -{ - Q_D(QFSFileEngine); - QSystemError error; - bool ret = QFileSystemEngine::renameOverwriteFile(d->fileEntry, QFileSystemEntry(newName), error); - if (!ret) - setError(QFile::RenameError, error.toString()); - return ret; -} - -bool QFSFileEngine::mkdir(const QString &name, bool createParentDirectories) const -{ - return QFileSystemEngine::createDirectory(QFileSystemEntry(name), createParentDirectories); -} - -bool QFSFileEngine::rmdir(const QString &name, bool recurseParentDirectories) const -{ - return QFileSystemEngine::removeDirectory(QFileSystemEntry(name), recurseParentDirectories); -} - bool QFSFileEngine::caseSensitive() const { return false; } -bool QFSFileEngine::setCurrentPath(const QString &path) -{ - return QFileSystemEngine::setCurrentPath(QFileSystemEntry(path)); -} - QString QFSFileEngine::currentPath(const QString &fileName) { #if !defined(Q_OS_WINRT) @@ -530,21 +475,6 @@ QString QFSFileEngine::currentPath(const QString &fileName) #endif // Q_OS_WINRT } -QString QFSFileEngine::homePath() -{ - return QFileSystemEngine::homePath(); -} - -QString QFSFileEngine::rootPath() -{ - return QFileSystemEngine::rootPath(); -} - -QString QFSFileEngine::tempPath() -{ - return QFileSystemEngine::tempPath(); -} - #if !defined(Q_OS_WINRT) // cf QStorageInfo::isReady static inline bool isDriveReady(const wchar_t *path) @@ -661,14 +591,14 @@ QAbstractFileEngine::FileFlags QFSFileEngine::fileFlags(QAbstractFileEngine::Fil if (type & Refresh) d->metaData.clear(); - QAbstractFileEngine::FileFlags ret = 0; + QAbstractFileEngine::FileFlags ret; if (type & FlagsMask) ret |= LocalDiskFlag; bool exists; { - QFileSystemMetaData::MetaDataFlags queryFlags = 0; + QFileSystemMetaData::MetaDataFlags queryFlags; queryFlags |= QFileSystemMetaData::MetaDataFlags(uint(type)) & QFileSystemMetaData::Permissions; diff --git a/src/corelib/io/qiodevice.cpp b/src/corelib/io/qiodevice.cpp index 076b2394ba..cc1d110252 100644 --- a/src/corelib/io/qiodevice.cpp +++ b/src/corelib/io/qiodevice.cpp @@ -461,7 +461,7 @@ QIODevice::QIODevice(QIODevicePrivate &dd) */ QIODevice::QIODevice() - : QObject(*new QIODevicePrivate, 0) + : QObject(*new QIODevicePrivate, nullptr) { #if defined QIODEVICE_DEBUG QFile *file = qobject_cast<QFile *>(this); @@ -1829,7 +1829,7 @@ QByteArray QIODevicePrivate::peek(qint64 maxSize) /*! \fn bool QIODevice::getChar(char *c) Reads one character from the device and stores it in \a c. If \a c - is 0, the character is discarded. Returns \c true on success; + is \nullptr, the character is discarded. Returns \c true on success; otherwise returns \c false. \sa read(), putChar(), ungetChar() diff --git a/src/corelib/io/qnoncontiguousbytedevice.cpp b/src/corelib/io/qnoncontiguousbytedevice.cpp index d1806aa12b..df0197e8eb 100644 --- a/src/corelib/io/qnoncontiguousbytedevice.cpp +++ b/src/corelib/io/qnoncontiguousbytedevice.cpp @@ -127,7 +127,7 @@ QT_BEGIN_NAMESPACE \internal */ -QNonContiguousByteDevice::QNonContiguousByteDevice() : QObject((QObject*)0) +QNonContiguousByteDevice::QNonContiguousByteDevice() : QObject((QObject*)nullptr) { } @@ -188,7 +188,7 @@ const char* QNonContiguousByteDeviceByteArrayImpl::readPointer(qint64 maximumLen { if (atEnd()) { len = -1; - return 0; + return nullptr; } if (maximumLength != -1) @@ -241,7 +241,7 @@ const char* QNonContiguousByteDeviceRingBufferImpl::readPointer(qint64 maximumLe { if (atEnd()) { len = -1; - return 0; + return nullptr; } const char *returnValue = ringBuffer->readPointerAtPosition(currentPosition, len); @@ -282,7 +282,7 @@ qint64 QNonContiguousByteDeviceRingBufferImpl::size() const QNonContiguousByteDeviceIoDeviceImpl::QNonContiguousByteDeviceIoDeviceImpl(QIODevice *d) : QNonContiguousByteDevice(), - currentReadBuffer(0), currentReadBufferSize(16*1024), + currentReadBuffer(nullptr), currentReadBufferSize(16*1024), currentReadBufferAmount(0), currentReadBufferPosition(0), totalAdvancements(0), eof(false) { @@ -301,10 +301,10 @@ const char* QNonContiguousByteDeviceIoDeviceImpl::readPointer(qint64 maximumLeng { if (eof == true) { len = -1; - return 0; + return nullptr; } - if (currentReadBuffer == 0) + if (currentReadBuffer == nullptr) currentReadBuffer = new QByteArray(currentReadBufferSize, '\0'); // lazy alloc if (maximumLength == -1) @@ -323,7 +323,7 @@ const char* QNonContiguousByteDeviceIoDeviceImpl::readPointer(qint64 maximumLeng // size was unknown before, emit a readProgress with the final size if (size() == -1) emit readProgress(totalAdvancements, totalAdvancements); - return 0; + return nullptr; } currentReadBufferAmount = haveRead; @@ -349,7 +349,7 @@ bool QNonContiguousByteDeviceIoDeviceImpl::advanceReadPointer(qint64 amount) if (currentReadBufferPosition > currentReadBufferAmount) { qint64 i = currentReadBufferPosition - currentReadBufferAmount; while (i > 0) { - if (device->getChar(0) == false) { + if (device->getChar(nullptr) == false) { emit readProgress(totalAdvancements - i, size()); return false; // ### FIXME handle eof } @@ -377,7 +377,7 @@ bool QNonContiguousByteDeviceIoDeviceImpl::reset() totalAdvancements = 0; //reset the progress counter if (currentReadBuffer) { delete currentReadBuffer; - currentReadBuffer = 0; + currentReadBuffer = nullptr; } currentReadBufferAmount = 0; currentReadBufferPosition = 0; @@ -405,7 +405,7 @@ qint64 QNonContiguousByteDeviceIoDeviceImpl::pos() const return device->pos(); } -QByteDeviceWrappingIoDevice::QByteDeviceWrappingIoDevice(QNonContiguousByteDevice *bd) : QIODevice((QObject*)0) +QByteDeviceWrappingIoDevice::QByteDeviceWrappingIoDevice(QNonContiguousByteDevice *bd) : QIODevice((QObject*)nullptr) { byteDevice = bd; connect(bd, SIGNAL(readyRead()), SIGNAL(readyRead())); diff --git a/src/corelib/io/qprocess.cpp b/src/corelib/io/qprocess.cpp index 49e0847d44..aedcae2cdc 100644 --- a/src/corelib/io/qprocess.cpp +++ b/src/corelib/io/qprocess.cpp @@ -216,7 +216,7 @@ void QProcessEnvironmentPrivate::insert(const QProcessEnvironmentPrivate &other) environment variables to be removed. */ QProcessEnvironment::QProcessEnvironment() - : d(0) + : d(nullptr) { } @@ -436,18 +436,18 @@ void QProcessPrivate::Channel::clear() case PipeSource: Q_ASSERT(process); process->stdinChannel.type = Normal; - process->stdinChannel.process = 0; + process->stdinChannel.process = nullptr; break; case PipeSink: Q_ASSERT(process); process->stdoutChannel.type = Normal; - process->stdoutChannel.process = 0; + process->stdoutChannel.process = nullptr; break; } type = Normal; file.clear(); - process = 0; + process = nullptr; } /*! @@ -869,8 +869,8 @@ QProcessPrivate::QProcessPrivate() sequenceNumber = 0; exitCode = 0; exitStatus = QProcess::NormalExit; - startupSocketNotifier = 0; - deathNotifier = 0; + startupSocketNotifier = nullptr; + deathNotifier = nullptr; childStartedPipe[0] = INVALID_Q_PIPE; childStartedPipe[1] = INVALID_Q_PIPE; forkfd = -1; @@ -924,23 +924,23 @@ void QProcessPrivate::cleanup() if (stdoutChannel.notifier) { delete stdoutChannel.notifier; - stdoutChannel.notifier = 0; + stdoutChannel.notifier = nullptr; } if (stderrChannel.notifier) { delete stderrChannel.notifier; - stderrChannel.notifier = 0; + stderrChannel.notifier = nullptr; } if (stdinChannel.notifier) { delete stdinChannel.notifier; - stdinChannel.notifier = 0; + stdinChannel.notifier = nullptr; } if (startupSocketNotifier) { delete startupSocketNotifier; - startupSocketNotifier = 0; + startupSocketNotifier = nullptr; } if (deathNotifier) { delete deathNotifier; - deathNotifier = 0; + deathNotifier = nullptr; } closeChannel(&stdoutChannel); closeChannel(&stderrChannel); @@ -1229,7 +1229,7 @@ void QProcessPrivate::closeWriteChannel() #endif if (stdinChannel.notifier) { delete stdinChannel.notifier; - stdinChannel.notifier = 0; + stdinChannel.notifier = nullptr; } #ifdef Q_OS_WIN // ### Find a better fix, feeding the process little by little @@ -2617,7 +2617,7 @@ QT_END_INCLUDE_NAMESPACE QStringList QProcess::systemEnvironment() { QStringList tmp; - char *entry = 0; + char *entry = nullptr; int count = 0; while ((entry = environ[count++])) tmp << QString::fromLocal8Bit(entry); diff --git a/src/corelib/io/qprocess_unix.cpp b/src/corelib/io/qprocess_unix.cpp index 0c80daa024..2186f23ab6 100644 --- a/src/corelib/io/qprocess_unix.cpp +++ b/src/corelib/io/qprocess_unix.cpp @@ -246,7 +246,7 @@ bool QProcessPrivate::openChannel(Channel &channel) return false; // create the socket notifiers - if (threadData->hasEventDispatcher()) { + if (threadData.loadRelaxed()->hasEventDispatcher()) { if (&channel == &stdinChannel) { channel.notifier = new QSocketNotifier(channel.pipe[1], QSocketNotifier::Write, q); @@ -338,11 +338,11 @@ static char **_q_dupEnvironment(const QProcessEnvironmentPrivate::Map &environme { *envc = 0; if (environment.isEmpty()) - return 0; + return nullptr; char **envp = new char *[environment.count() + 2]; - envp[environment.count()] = 0; - envp[environment.count() + 1] = 0; + envp[environment.count()] = nullptr; + envp[environment.count() + 1] = nullptr; auto it = environment.constBegin(); const auto end = environment.constEnd(); @@ -377,7 +377,7 @@ void QProcessPrivate::startProcess() return; } - if (threadData->hasEventDispatcher()) { + if (threadData.loadRelaxed()->hasEventDispatcher()) { startupSocketNotifier = new QSocketNotifier(childStartedPipe[0], QSocketNotifier::Read, q); QObject::connect(startupSocketNotifier, SIGNAL(activated(int)), @@ -390,7 +390,7 @@ void QProcessPrivate::startProcess() // Create argument list with right number of elements, and set the final // one to 0. char **argv = new char *[arguments.count() + 2]; - argv[arguments.count() + 1] = 0; + argv[arguments.count() + 1] = nullptr; // Encode the program name. QByteArray encodedProgramName = QFile::encodeName(program); @@ -437,13 +437,13 @@ void QProcessPrivate::startProcess() // Duplicate the environment. int envc = 0; - char **envp = 0; + char **envp = nullptr; if (environment.d.constData()) { envp = _q_dupEnvironment(environment.d.constData()->vars, &envc); } // Encode the working directory if it's non-empty, otherwise just pass 0. - const char *workingDirPtr = 0; + const char *workingDirPtr = nullptr; QByteArray encodedWorkingDirectory; if (!workingDirectory.isEmpty()) { encodedWorkingDirectory = QFile::encodeName(workingDirectory); @@ -451,8 +451,13 @@ void QProcessPrivate::startProcess() } // Start the process manager, and fork off the child process. + // ### Qt6: revisit whether the change in behavior due to not using fork() + // is acceptable for derived classes. + int ffdflags = FFD_CLOEXEC; + if (typeid(*q) != typeid(QProcess)) + ffdflags |= FFD_USE_FORK; pid_t childPid; - forkfd = ::forkfd(FFD_CLOEXEC, &childPid); + forkfd = ::forkfd(ffdflags , &childPid); int lastForkErrno = errno; if (forkfd != FFD_CHILD_PROCESS) { // Parent process. @@ -517,7 +522,7 @@ void QProcessPrivate::startProcess() if (stderrChannel.pipe[0] != -1) ::fcntl(stderrChannel.pipe[0], F_SETFL, ::fcntl(stderrChannel.pipe[0], F_GETFL) | O_NONBLOCK); - if (threadData->eventDispatcher.loadAcquire()) { + if (threadData.loadRelaxed()->eventDispatcher.loadAcquire()) { deathNotifier = new QSocketNotifier(forkfd, QSocketNotifier::Read, q); QObject::connect(deathNotifier, SIGNAL(activated(int)), q, SLOT(_q_processDied())); @@ -596,7 +601,7 @@ bool QProcessPrivate::processStarted(QString *errorMessage) if (startupSocketNotifier) { startupSocketNotifier->setEnabled(false); startupSocketNotifier->deleteLater(); - startupSocketNotifier = 0; + startupSocketNotifier = nullptr; } qt_safe_close(childStartedPipe[0]); childStartedPipe[0] = -1; @@ -889,7 +894,7 @@ bool QProcessPrivate::waitForDeadChild() crashed = info.code != CLD_EXITED; delete deathNotifier; - deathNotifier = 0; + deathNotifier = nullptr; EINTR_LOOP(ret, forkfd_close(forkfd)); forkfd = -1; // Child is dead, don't try to kill it anymore @@ -935,7 +940,7 @@ bool QProcessPrivate::startDetached(qint64 *pid) struct sigaction noaction; memset(&noaction, 0, sizeof(noaction)); noaction.sa_handler = SIG_IGN; - ::sigaction(SIGPIPE, &noaction, 0); + ::sigaction(SIGPIPE, &noaction, nullptr); ::setsid(); @@ -964,7 +969,7 @@ bool QProcessPrivate::startDetached(qint64 *pid) char **argv = new char *[arguments.size() + 2]; for (int i = 0; i < arguments.size(); ++i) argv[i + 1] = ::strdup(QFile::encodeName(arguments.at(i)).constData()); - argv[arguments.size() + 1] = 0; + argv[arguments.size() + 1] = nullptr; // Duplicate the environment. int envc = 0; @@ -991,7 +996,7 @@ bool QProcessPrivate::startDetached(qint64 *pid) struct sigaction noaction; memset(&noaction, 0, sizeof(noaction)); noaction.sa_handler = SIG_IGN; - ::sigaction(SIGPIPE, &noaction, 0); + ::sigaction(SIGPIPE, &noaction, nullptr); // '\1' means execv failed char c = '\1'; @@ -1002,7 +1007,7 @@ bool QProcessPrivate::startDetached(qint64 *pid) struct sigaction noaction; memset(&noaction, 0, sizeof(noaction)); noaction.sa_handler = SIG_IGN; - ::sigaction(SIGPIPE, &noaction, 0); + ::sigaction(SIGPIPE, &noaction, nullptr); // '\2' means internal error char c = '\2'; diff --git a/src/corelib/io/qprocess_win.cpp b/src/corelib/io/qprocess_win.cpp index 3ba86063e3..1527cf93ed 100644 --- a/src/corelib/io/qprocess_win.cpp +++ b/src/corelib/io/qprocess_win.cpp @@ -49,7 +49,6 @@ #include <qelapsedtimer.h> #include <qfileinfo.h> #include <qrandom.h> -#include <qregexp.h> #include <qwineventnotifier.h> #include <private/qsystemlibrary_p.h> #include <private/qthread_p.h> @@ -398,7 +397,17 @@ static QString qt_create_commandline(const QString &program, const QStringList & for (int i=0; i<arguments.size(); ++i) { QString tmp = arguments.at(i); // Quotes are escaped and their preceding backslashes are doubled. - tmp.replace(QRegExp(QLatin1String("(\\\\*)\"")), QLatin1String("\\1\\1\\\"")); + int index = tmp.indexOf(QLatin1Char('"')); + while (index >= 0) { + // Escape quote + tmp.insert(index++, QLatin1Char('\\')); + // Double preceding backslashes (ignoring the one we just inserted) + for (int i = index - 2 ; i >= 0 && tmp.at(i) == QLatin1Char('\\') ; --i) { + tmp.insert(i, QLatin1Char('\\')); + index++; + } + index = tmp.indexOf(QLatin1Char('"'), index + 1); + } if (tmp.isEmpty() || tmp.contains(QLatin1Char(' ')) || tmp.contains(QLatin1Char('\t'))) { // The argument must not end with a \ since this would be interpreted // as escaping the quote -- rather put the \ behind the quote: e.g. @@ -590,7 +599,7 @@ void QProcessPrivate::startProcess() if (!pid) return; - if (threadData->hasEventDispatcher()) { + if (threadData.loadRelaxed()->hasEventDispatcher()) { processFinishedNotifier = new QWinEventNotifier(pid->hProcess, q); QObject::connect(processFinishedNotifier, SIGNAL(activated(HANDLE)), q, SLOT(_q_processDied())); processFinishedNotifier->setEnabled(true); diff --git a/src/corelib/io/qresource.cpp b/src/corelib/io/qresource.cpp index a494daa29f..225ee0a769 100644 --- a/src/corelib/io/qresource.cpp +++ b/src/corelib/io/qresource.cpp @@ -1,7 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2018 The Qt Company Ltd. -** Copyright (C) 2019 Intel Corporation. +** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2020 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -60,14 +60,14 @@ #include "private/qtools_p.h" #include "private/qsystemerror_p.h" +#ifndef QT_NO_COMPRESS +# include <zconf.h> +# include <zlib.h> +#endif #if QT_CONFIG(zstd) # include <zstd.h> #endif -#ifdef Q_OS_UNIX -# include "private/qcore_unix_p.h" -#endif - #if defined(Q_OS_UNIX) && !defined(Q_OS_NACL) && !defined(Q_OS_INTEGRITY) # define QT_USE_MMAP # include <sys/mman.h> @@ -296,6 +296,8 @@ public: void ensureInitialized() const; void ensureChildren() const; + qint64 uncompressedSize() const Q_DECL_PURE_FUNCTION; + qsizetype decompress(char *buffer, qsizetype bufferSize) const; bool load(const QString &file); void clear(); @@ -440,6 +442,78 @@ QResourcePrivate::ensureChildren() const } } +qint64 QResourcePrivate::uncompressedSize() const +{ + switch (compressionAlgo) { + case QResource::NoCompression: + return size; + + case QResource::ZlibCompression: +#ifndef QT_NO_COMPRESS + if (size_t(size) >= sizeof(quint32)) + return qFromBigEndian<quint32>(data); +#else + Q_ASSERT(!"QResource: Qt built without support for Zlib compression"); + Q_UNREACHABLE(); +#endif + break; + + case QResource::ZstdCompression: { +#if QT_CONFIG(zstd) + size_t n = ZSTD_getFrameContentSize(data, size); + return ZSTD_isError(n) ? -1 : qint64(n); +#else + // This should not happen because we've refused to load such resource + Q_ASSERT(!"QResource: Qt built without support for Zstd compression"); + Q_UNREACHABLE(); +#endif + } + + } + return -1; +} + +qsizetype QResourcePrivate::decompress(char *buffer, qsizetype bufferSize) const +{ + Q_ASSERT(data); + + switch (compressionAlgo) { + case QResource::NoCompression: + Q_UNREACHABLE(); + break; + + case QResource::ZlibCompression: { +#ifndef QT_NO_COMPRESS + uLong len = uLong(bufferSize); + int res = ::uncompress(reinterpret_cast<Bytef *>(buffer), &len, + data + sizeof(quint32), uLong(size - sizeof(quint32))); + if (res != Z_OK) { + qWarning("QResource: error decompressing zlib content (%d)", res); + return -1; + } + return len; +#else + Q_UNREACHABLE(); +#endif + } + + case QResource::ZstdCompression: { +#if QT_CONFIG(zstd) + size_t usize = ZSTD_decompress(buffer, bufferSize, data, size); + if (ZSTD_isError(usize)) { + qWarning("QResource: error decompressing zstd content: %s", ZSTD_getErrorName(usize)); + return -1; + } + return usize; +#else + Q_UNREACHABLE(); +#endif + } + } + + return -1; +} + /*! Constructs a QResource pointing to \a file. \a locale is used to load a specific localization of a resource data. @@ -600,9 +674,12 @@ QResource::Compression QResource::compressionAlgorithm() const } /*! - Returns the size of the data backing the resource. + Returns the size of the stored data backing the resource. - \sa data(), isFile() + If the resource is compressed, this function returns the size of the + compressed data. See uncompressedSize() for the uncompressed size. + + \sa data(), uncompressedSize(), isFile() */ qint64 QResource::size() const @@ -613,12 +690,29 @@ qint64 QResource::size() const } /*! - Returns direct access to a read only segment of data that this resource - represents. If the resource is compressed the data returned is compressed - and the appropriate library functions must be used to access the data. If - the resource is a directory \nullptr is returned. + \since 5.15 + + Returns the size of the data in this resource. If the data was not + compressed, this function returns the same as size(). If it was, then this + function extracts the size of the original uncompressed data from the + stored stream. + + \sa size(), uncompressedData(), isFile() +*/ +qint64 QResource::uncompressedSize() const +{ + Q_D(const QResource); + d->ensureInitialized(); + return d->uncompressedSize(); +} + +/*! + Returns direct access to a segment of read-only data, that this resource + represents. If the resource is compressed, the data returned is also + compressed. The caller must then decompress the data or use + uncompressedData(). If the resource is a directory, \c nullptr is returned. - \sa size(), compressionAlgorithm(), isFile() + \sa uncompressedData(), size(), isFile() */ const uchar *QResource::data() const @@ -629,6 +723,42 @@ const uchar *QResource::data() const } /*! + \since 5.15 + + Returns the resource data, decompressing it first, if the data was stored + compressed. If the resource is a directory or an error occurs while + decompressing, a null QByteArray is returned. + + \note If the data was compressed, this function will decompress every time + it is called. The result is not cached between calls. + + \sa uncompressedData(), size(), isCompressed(), isFile() +*/ + +QByteArray QResource::uncompressedData() const +{ + Q_D(const QResource); + qint64 n = uncompressedSize(); + if (n < 0) + return QByteArray(); + if (n > std::numeric_limits<QByteArray::size_type>::max()) { + qWarning("QResource: compressed content does not fit into a QByteArray; use QFile instead"); + return QByteArray(); + } + if (d->compressionAlgo == NoCompression) + return QByteArray::fromRawData(reinterpret_cast<const char *>(d->data), n); + + // decompress + QByteArray result(n, Qt::Uninitialized); + n = d->decompress(result.data(), n); + if (n < 0) + result.clear(); + else + result.truncate(n); + return result; +} + +/*! \since 5.8 Returns the date and time when the file was last modified before @@ -1451,13 +1581,7 @@ bool QResourceFileEngine::link(const QString &) qint64 QResourceFileEngine::size() const { Q_D(const QResourceFileEngine); - if (!d->resource.isValid()) - return 0; - if (d->resource.compressionAlgorithm() != QResource::NoCompression) { - d->uncompress(); - return d->uncompressed.size(); - } - return d->resource.size(); + return d->resource.isValid() ? d->resource.uncompressedSize() : 0; } qint64 QResourceFileEngine::pos() const @@ -1494,7 +1618,7 @@ bool QResourceFileEngine::isSequential() const QAbstractFileEngine::FileFlags QResourceFileEngine::fileFlags(QAbstractFileEngine::FileFlags type) const { Q_D(const QResourceFileEngine); - QAbstractFileEngine::FileFlags ret = 0; + QAbstractFileEngine::FileFlags ret; if(!d->resource.isValid()) return ret; @@ -1586,7 +1710,7 @@ QAbstractFileEngine::Iterator *QResourceFileEngine::beginEntryList(QDir::Filters */ QAbstractFileEngine::Iterator *QResourceFileEngine::endEntryList() { - return 0; + return nullptr; } bool QResourceFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) @@ -1596,7 +1720,7 @@ bool QResourceFileEngine::extension(Extension extension, const ExtensionOption * const MapExtensionOption *options = (const MapExtensionOption*)(option); MapExtensionReturn *returnValue = static_cast<MapExtensionReturn*>(output); returnValue->address = d->map(options->offset, options->size, options->flags); - return (returnValue->address != 0); + return (returnValue->address != nullptr); } if (extension == UnMapExtension) { const UnMapExtensionOption *options = (const UnMapExtensionOption*)option; @@ -1615,22 +1739,21 @@ uchar *QResourceFileEnginePrivate::map(qint64 offset, qint64 size, QFile::Memory Q_Q(QResourceFileEngine); Q_UNUSED(flags); - qint64 max = resource.size(); - if (resource.compressionAlgorithm() != QResource::NoCompression) { - uncompress(); - max = uncompressed.size(); - } - + qint64 max = resource.uncompressedSize(); qint64 end; if (offset < 0 || size <= 0 || !resource.isValid() || add_overflow(offset, size, &end) || end > max) { q->setError(QFile::UnspecifiedError, QString()); - return 0; + return nullptr; } const uchar *address = resource.data(); - if (resource.compressionAlgorithm() != QResource::NoCompression) + if (resource.compressionAlgorithm() != QResource::NoCompression) { + uncompress(); + if (uncompressed.isNull()) + return nullptr; address = reinterpret_cast<const uchar *>(uncompressed.constData()); + } return const_cast<uchar *>(address) + offset; } @@ -1643,41 +1766,10 @@ bool QResourceFileEnginePrivate::unmap(uchar *ptr) void QResourceFileEnginePrivate::uncompress() const { - if (uncompressed.isEmpty() && resource.size()) { - quint64 size; - switch (resource.compressionAlgorithm()) { - case QResource::NoCompression: - return; // nothing to do - - case QResource::ZlibCompression: -#ifndef QT_NO_COMPRESS - uncompressed = qUncompress(resource.data(), resource.size()); -#else - Q_ASSERT(!"QResourceFileEngine::open: Qt built without support for Zlib compression"); -#endif - break; - - case QResource::ZstdCompression: -#if QT_CONFIG(zstd) - size = ZSTD_getFrameContentSize(resource.data(), resource.size()); - if (!ZSTD_isError(size)) { - if (size >= MaxAllocSize) { - qWarning("QResourceFileEngine::open: content bigger than memory (size %lld)", size); - } else { - uncompressed = QByteArray(size, Qt::Uninitialized); - size = ZSTD_decompress(const_cast<char *>(uncompressed.data()), size, - resource.data(), resource.size()); - } - } - if (ZSTD_isError(size)) - qWarning("QResourceFileEngine::open: error decoding: %s", ZSTD_getErrorName(size)); -#else - Q_UNUSED(size); - Q_ASSERT(!"QResourceFileEngine::open: Qt built without support for Zstd compression"); -#endif - break; - } - } + if (resource.compressionAlgorithm() == QResource::NoCompression + || !uncompressed.isEmpty() || resource.size() == 0) + return; // nothing to do + uncompressed = resource.uncompressedData(); } #endif // !defined(QT_BOOTSTRAPPED) diff --git a/src/corelib/io/qresource.h b/src/corelib/io/qresource.h index 5ee8d5d266..52b0d74d29 100644 --- a/src/corelib/io/qresource.h +++ b/src/corelib/io/qresource.h @@ -1,6 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2019 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -75,6 +76,8 @@ public: Compression compressionAlgorithm() const; qint64 size() const; const uchar *data() const; + qint64 uncompressedSize() const; + QByteArray uncompressedData() const; QDateTime lastModified() const; #if QT_DEPRECATED_SINCE(5, 13) diff --git a/src/corelib/io/qsavefile.cpp b/src/corelib/io/qsavefile.cpp index 0a884a7df9..067ccda3df 100644 --- a/src/corelib/io/qsavefile.cpp +++ b/src/corelib/io/qsavefile.cpp @@ -116,7 +116,7 @@ QSaveFile::QSaveFile(const QString &name) Constructs a new file object to represent the file with the given \a name. */ QSaveFile::QSaveFile(const QString &name) - : QFileDevice(*new QSaveFilePrivate, 0) + : QFileDevice(*new QSaveFilePrivate, nullptr) { Q_D(QSaveFile); d->fileName = name; diff --git a/src/corelib/io/qsettings.cpp b/src/corelib/io/qsettings.cpp index fc7122d904..b191397e7e 100644 --- a/src/corelib/io/qsettings.cpp +++ b/src/corelib/io/qsettings.cpp @@ -76,10 +76,6 @@ # include <ioLib.h> #endif -#ifdef Q_OS_WASM -#include <emscripten.h> -#endif - #include <algorithm> #include <stdlib.h> @@ -210,7 +206,7 @@ QConfFile *QConfFile::fromName(const QString &fileName, bool _userPerms) ConfFileHash *usedHash = usedHashFunc(); ConfFileCache *unusedCache = unusedCacheFunc(); - QConfFile *confFile = 0; + QConfFile *confFile = nullptr; const auto locker = qt_scoped_lock(settingsGlobalMutex); if (!(confFile = usedHash->value(absPath))) { @@ -234,7 +230,7 @@ void QConfFile::clearCache() // QSettingsPrivate QSettingsPrivate::QSettingsPrivate(QSettings::Format format) - : format(format), scope(QSettings::UserScope /* nothing better to put */), iniCodec(0), fallbacks(true), + : format(format), scope(QSettings::UserScope /* nothing better to put */), iniCodec(nullptr), fallbacks(true), pendingChanges(false), status(QSettings::NoError) { } @@ -242,7 +238,7 @@ QSettingsPrivate::QSettingsPrivate(QSettings::Format format) QSettingsPrivate::QSettingsPrivate(QSettings::Format format, QSettings::Scope scope, const QString &organization, const QString &application) : format(format), scope(scope), organizationName(organization), applicationName(application), - iniCodec(0), fallbacks(true), pendingChanges(false), status(QSettings::NoError) + iniCodec(nullptr), fallbacks(true), pendingChanges(false), status(QSettings::NoError) { } @@ -295,7 +291,7 @@ after_loop: // see also qsettings_win.cpp, qsettings_winrt.cpp and qsettings_mac.cpp -#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) +#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) && !defined(Q_OS_WASM) QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format, QSettings::Scope scope, const QString &organization, const QString &application) { @@ -400,12 +396,12 @@ QString QSettingsPrivate::variantToString(const QVariant &v) { QString result; - switch (v.type()) { - case QVariant::Invalid: + switch (v.userType()) { + case QMetaType::UnknownType: result = QLatin1String("@Invalid()"); break; - case QVariant::ByteArray: { + case QMetaType::QByteArray: { QByteArray a = v.toByteArray(); result = QLatin1String("@ByteArray(") + QLatin1String(a.constData(), a.size()) @@ -413,14 +409,14 @@ QString QSettingsPrivate::variantToString(const QVariant &v) break; } - case QVariant::String: - case QVariant::LongLong: - case QVariant::ULongLong: - case QVariant::Int: - case QVariant::UInt: - case QVariant::Bool: - case QVariant::Double: - case QVariant::KeySequence: { + case QMetaType::QString: + case QMetaType::LongLong: + case QMetaType::ULongLong: + case QMetaType::Int: + case QMetaType::UInt: + case QMetaType::Bool: + case QMetaType::Double: + case QMetaType::QKeySequence: { result = v.toString(); if (result.contains(QChar::Null)) result = QLatin1String("@String(") + result + QLatin1Char(')'); @@ -429,17 +425,17 @@ QString QSettingsPrivate::variantToString(const QVariant &v) break; } #ifndef QT_NO_GEOM_VARIANT - case QVariant::Rect: { + case QMetaType::QRect: { QRect r = qvariant_cast<QRect>(v); result = QString::asprintf("@Rect(%d %d %d %d)", r.x(), r.y(), r.width(), r.height()); break; } - case QVariant::Size: { + case QMetaType::QSize: { QSize s = qvariant_cast<QSize>(v); result = QString::asprintf("@Size(%d %d)", s.width(), s.height()); break; } - case QVariant::Point: { + case QMetaType::QPoint: { QPoint p = qvariant_cast<QPoint>(v); result = QString::asprintf("@Point(%d %d)", p.x(), p.y()); break; @@ -450,7 +446,7 @@ QString QSettingsPrivate::variantToString(const QVariant &v) #ifndef QT_NO_DATASTREAM QDataStream::Version version; const char *typeSpec; - if (v.type() == QVariant::DateTime) { + if (v.userType() == QMetaType::QDateTime) { version = QDataStream::Qt_5_6; typeSpec = "@DateTime("; } else { @@ -928,8 +924,8 @@ QStringList QSettingsPrivate::splitArgs(const QString &s, int idx) void QConfFileSettingsPrivate::initFormat() { extension = (format == QSettings::NativeFormat) ? QLatin1String(".conf") : QLatin1String(".ini"); - readFunc = 0; - writeFunc = 0; + readFunc = nullptr; + writeFunc = nullptr; #if defined(Q_OS_MAC) caseSensitivity = (format == QSettings::NativeFormat) ? Qt::CaseSensitive : IniCaseSensitivity; #else @@ -1185,7 +1181,9 @@ QConfFileSettingsPrivate::QConfFileSettingsPrivate(QSettings::Format format, confFiles.append(QConfFile::fromName(systemPath.path + orgFile, false)); } +#ifndef Q_OS_WASM // wasm needs to delay access until after file sync initAccess(); +#endif } QConfFileSettingsPrivate::QConfFileSettingsPrivate(const QString &fileName, @@ -1548,13 +1546,6 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile) perms |= QFile::ReadGroup | QFile::ReadOther; QFile(confFile->name).setPermissions(perms); } -#ifdef Q_OS_WASM - EM_ASM( - // Sync sandbox filesystem to persistent database filesystem. See QTBUG-70002 - FS.syncfs(false, function(err) { - }); - ); -#endif } else { setStatus(QSettings::AccessError); } @@ -1897,8 +1888,8 @@ bool QConfFileSettingsPrivate::writeIniFile(QIODevice &device, const ParsedSetti QVariant(QString("foo")).toList() returns an empty list, not a list containing "foo". */ - if (value.type() == QVariant::StringList - || (value.type() == QVariant::List && value.toList().size() != 1)) { + if (value.userType() == QMetaType::QStringList + || (value.userType() == QMetaType::QVariantList && value.toList().size() != 1)) { iniEscapedStringList(variantListToStringList(value.toList()), block, iniCodec); } else { iniEscapedString(variantToString(value), block, iniCodec); @@ -3347,7 +3338,7 @@ bool QSettings::contains(const QString &key) const { Q_D(const QSettings); QString k = d->actualKey(key); - return d->get(k, 0); + return d->get(k, nullptr); } /*! diff --git a/src/corelib/io/qsettings_p.h b/src/corelib/io/qsettings_p.h index d18c96a06c..c30f099a72 100644 --- a/src/corelib/io/qsettings_p.h +++ b/src/corelib/io/qsettings_p.h @@ -57,6 +57,10 @@ #include "QtCore/qiodevice.h" #include "QtCore/qstack.h" #include "QtCore/qstringlist.h" + +#include <QtCore/qvariant.h> +#include "qsettings.h" + #ifndef QT_NO_QOBJECT #include "private/qobject_p.h" #endif @@ -253,6 +257,10 @@ protected: mutable QSettings::Status status; }; +#ifdef Q_OS_WASM +class QWasmSettingsPrivate; +#endif + class QConfFileSettingsPrivate : public QSettingsPrivate { public: @@ -281,7 +289,7 @@ public: private: void initFormat(); - void initAccess(); + virtual void initAccess(); void syncConfFile(QConfFile *confFile); bool writeIniFile(QIODevice &device, const ParsedSettingsMap &map); #ifdef Q_OS_MAC @@ -297,6 +305,9 @@ private: QString extension; Qt::CaseSensitivity caseSensitivity; int nextPosition; +#ifdef Q_OS_WASM + friend class QWasmSettingsPrivate; +#endif }; QT_END_NAMESPACE diff --git a/src/corelib/io/qsettings_wasm.cpp b/src/corelib/io/qsettings_wasm.cpp new file mode 100644 index 0000000000..8d8f4b505c --- /dev/null +++ b/src/corelib/io/qsettings_wasm.cpp @@ -0,0 +1,259 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsettings.h" +#ifndef QT_NO_SETTINGS + +#include "qsettings_p.h" +#ifndef QT_NO_QOBJECT +#include "qcoreapplication.h" +#include <QFile> +#endif // QT_NO_QOBJECT +#include <QDebug> + +#include <QFileInfo> +#include <QDir> +#include <emscripten.h> + +QT_BEGIN_NAMESPACE + +static bool isReadReady = false; + +class QWasmSettingsPrivate : public QConfFileSettingsPrivate +{ +public: + QWasmSettingsPrivate(QSettings::Scope scope, const QString &organization, + const QString &application); + ~QWasmSettingsPrivate(); + + bool get(const QString &key, QVariant *value) const override; + QStringList children(const QString &prefix, ChildSpec spec) const override; + void clear() override; + void sync() override; + void flush() override; + bool isWritable() const override; + + void syncToLocal(const char *data, int size); + void loadLocal(const QByteArray &filename); + void setReady(); + void initAccess() override; + +private: + QString databaseName; + QString id; +}; + +static void QWasmSettingsPrivate_onLoad(void *userData, void *dataPtr, int size) +{ + QWasmSettingsPrivate *wasm = reinterpret_cast<QWasmSettingsPrivate *>(userData); + + QFile file(wasm->fileName()); + QFileInfo fileInfo(wasm->fileName()); + QDir dir(fileInfo.path()); + if (!dir.exists()) + dir.mkpath(fileInfo.path()); + + if (file.open(QFile::WriteOnly)) { + file.write(reinterpret_cast<char *>(dataPtr), size); + file.close(); + wasm->setReady(); + } +} + +static void QWasmSettingsPrivate_onError(void *userData) +{ + QWasmSettingsPrivate *wasm = reinterpret_cast<QWasmSettingsPrivate *>(userData); + if (wasm) + wasm->setStatus(QSettings::AccessError); +} + +static void QWasmSettingsPrivate_onStore(void *userData) +{ + QWasmSettingsPrivate *wasm = reinterpret_cast<QWasmSettingsPrivate *>(userData); + if (wasm) + wasm->setStatus(QSettings::NoError); +} + +static void QWasmSettingsPrivate_onCheck(void *userData, int exists) +{ + QWasmSettingsPrivate *wasm = reinterpret_cast<QWasmSettingsPrivate *>(userData); + if (wasm) { + if (exists) + wasm->loadLocal(wasm->fileName().toLocal8Bit()); + else + wasm->setReady(); + } +} + +QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format, + QSettings::Scope scope, + const QString &organization, + const QString &application) +{ + Q_UNUSED(format) + if (organization == QLatin1String("Qt")) + { + QString organizationDomain = QCoreApplication::organizationDomain(); + QString applicationName = QCoreApplication::applicationName(); + + QSettingsPrivate *newSettings; + newSettings = new QWasmSettingsPrivate(scope, organizationDomain, applicationName); + + newSettings->beginGroupOrArray(QSettingsGroup(normalizedKey(organization))); + if (!application.isEmpty()) + newSettings->beginGroupOrArray(QSettingsGroup(normalizedKey(application))); + + return newSettings; + } + return new QWasmSettingsPrivate(scope, organization, application); +} + +QWasmSettingsPrivate::QWasmSettingsPrivate(QSettings::Scope scope, const QString &organization, + const QString &application) + : QConfFileSettingsPrivate(QSettings::NativeFormat, scope, organization, application) +{ + setStatus(QSettings::AccessError); // access error until sandbox gets loaded + databaseName = organization; + id = application; + + emscripten_idb_async_exists("/home/web_user", + fileName().toLocal8Bit(), + reinterpret_cast<void*>(this), + QWasmSettingsPrivate_onCheck, + QWasmSettingsPrivate_onError); +} + +QWasmSettingsPrivate::~QWasmSettingsPrivate() +{ +} + + void QWasmSettingsPrivate::initAccess() +{ + if (isReadReady) + QConfFileSettingsPrivate::initAccess(); +} + +bool QWasmSettingsPrivate::get(const QString &key, QVariant *value) const +{ + if (isReadReady) + return QConfFileSettingsPrivate::get(key, value); + + return false; +} + +QStringList QWasmSettingsPrivate::children(const QString &prefix, ChildSpec spec) const +{ + return QConfFileSettingsPrivate::children(prefix, spec); +} + +void QWasmSettingsPrivate::clear() +{ + QConfFileSettingsPrivate::clear(); + emscripten_idb_async_delete("/home/web_user", + fileName().toLocal8Bit(), + reinterpret_cast<void*>(this), + QWasmSettingsPrivate_onStore, + QWasmSettingsPrivate_onError); +} + +void QWasmSettingsPrivate::sync() +{ + QConfFileSettingsPrivate::sync(); + + QFile file(fileName()); + if (file.open(QFile::ReadOnly)) { + QByteArray dataPointer = file.readAll(); + + emscripten_idb_async_store("/home/web_user", + fileName().toLocal8Bit(), + reinterpret_cast<void *>(dataPointer.data()), + dataPointer.length(), + reinterpret_cast<void*>(this), + QWasmSettingsPrivate_onStore, + QWasmSettingsPrivate_onError); + } +} + +void QWasmSettingsPrivate::flush() +{ + sync(); +} + +bool QWasmSettingsPrivate::isWritable() const +{ + return isReadReady && QConfFileSettingsPrivate::isWritable(); +} + +void QWasmSettingsPrivate::syncToLocal(const char *data, int size) +{ + QFile file(fileName()); + + if (file.open(QFile::WriteOnly)) { + file.write(data, size + 1); + QByteArray data = file.readAll(); + + emscripten_idb_async_store("/home/web_user", + fileName().toLocal8Bit(), + reinterpret_cast<void *>(data.data()), + data.length(), + reinterpret_cast<void*>(this), + QWasmSettingsPrivate_onStore, + QWasmSettingsPrivate_onError); + setReady(); + } +} + +void QWasmSettingsPrivate::loadLocal(const QByteArray &filename) +{ + emscripten_idb_async_load("/home/web_user", + filename.data(), + reinterpret_cast<void*>(this), + QWasmSettingsPrivate_onLoad, + QWasmSettingsPrivate_onError); +} + +void QWasmSettingsPrivate::setReady() +{ + isReadReady = true; + setStatus(QSettings::NoError); + QConfFileSettingsPrivate::initAccess(); +} + +QT_END_NAMESPACE +#endif // QT_NO_SETTINGS diff --git a/src/corelib/io/qurl.cpp b/src/corelib/io/qurl.cpp index 878e007fb0..def4a1095f 100644 --- a/src/corelib/io/qurl.cpp +++ b/src/corelib/io/qurl.cpp @@ -415,7 +415,7 @@ #include "qhash.h" #include "qdir.h" // for QDir::fromNativeSeparators #include "qdatastream.h" -#if QT_CONFIG(topleveldomain) +#if QT_CONFIG(topleveldomain) // ### Qt6: Remove section #include "qtldurl_p.h" #endif #include "private/qipaddress_p.h" @@ -823,7 +823,7 @@ recodeFromUser(const QString &input, const ushort *actions, int from, int to) QString output; const QChar *begin = input.constData() + from; const QChar *end = input.constData() + to; - if (qt_urlRecode(output, begin, end, nullptr, actions)) + if (qt_urlRecode(output, begin, end, {}, actions)) return output; return input.mid(from, to - from); @@ -3149,10 +3149,13 @@ bool QUrl::hasFragment() const return d->hasFragment(); } +#if QT_DEPRECATED_SINCE(5, 15) #if QT_CONFIG(topleveldomain) /*! \since 4.8 + \deprecated + Returns the TLD (Top-Level Domain) of the URL, (e.g. .co.uk, .net). Note that the return value is prefixed with a '.' unless the URL does not contain a valid TLD, in which case the function returns @@ -3185,7 +3188,7 @@ QString QUrl::topLevelDomain(ComponentFormattingOptions options) const return tld; } #endif - +#endif // QT_DEPRECATED_SINCE(5, 15) /*! Returns the result of the merge of this URL with \a relative. This URL is used as a base to convert \a relative to an absolute URL. diff --git a/src/corelib/io/qurl.h b/src/corelib/io/qurl.h index 94269e4369..eb7fb8087c 100644 --- a/src/corelib/io/qurl.h +++ b/src/corelib/io/qurl.h @@ -233,9 +233,11 @@ public: void setHost(const QString &host, ParsingMode mode = DecodedMode); QString host(ComponentFormattingOptions = FullyDecoded) const; +#if QT_DEPRECATED_SINCE(5, 15) #if QT_CONFIG(topleveldomain) - QString topLevelDomain(ComponentFormattingOptions options = FullyDecoded) const; + QT_DEPRECATED QString topLevelDomain(ComponentFormattingOptions options = FullyDecoded) const; #endif +#endif // QT_DEPRECATED_SINCE(5, 15) void setPort(int port); int port(int defaultPort = -1) const; |