diff options
Diffstat (limited to 'src/corelib/io/qtemporaryfile.cpp')
-rw-r--r-- | src/corelib/io/qtemporaryfile.cpp | 502 |
1 files changed, 332 insertions, 170 deletions
diff --git a/src/corelib/io/qtemporaryfile.cpp b/src/corelib/io/qtemporaryfile.cpp index 8a99873fee..3ecc24a5db 100644 --- a/src/corelib/io/qtemporaryfile.cpp +++ b/src/corelib/io/qtemporaryfile.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2017 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -39,9 +40,8 @@ #include "qtemporaryfile.h" -#ifndef QT_NO_TEMPORARYFILE - #include "qplatformdefs.h" +#include "qrandom.h" #include "private/qtemporaryfile_p.h" #include "private/qfile_p.h" #include "private/qsystemerror_p.h" @@ -53,6 +53,8 @@ #if defined(QT_BUILD_CORE_LIB) #include "qcoreapplication.h" +#else +#define tr(X) QString::fromLatin1(X) #endif QT_BEGIN_NAMESPACE @@ -73,82 +75,145 @@ typedef char Latin1Char; typedef int NativeFileHandle; #endif -/* - * Copyright (c) 1987, 1993 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ +QTemporaryFileName::QTemporaryFileName(const QString &templateName) +{ + // Ensure there is a placeholder mask + QString qfilename = templateName; + uint phPos = qfilename.length(); + uint phLength = 0; + + while (phPos != 0) { + --phPos; + + if (qfilename[phPos] == QLatin1Char('X')) { + ++phLength; + continue; + } + + if (phLength >= 6 + || qfilename[phPos] == QLatin1Char('/')) { + ++phPos; + break; + } + + // start over + phLength = 0; + } + + if (phLength < 6) + qfilename.append(QLatin1String(".XXXXXX")); + + // "Nativify" :-) + QFileSystemEntry::NativePath filename = QFileSystemEngine::absoluteName( + QFileSystemEntry(qfilename, QFileSystemEntry::FromInternalPath())) + .nativeFilePath(); + + // Find mask in native path + phPos = filename.length(); + phLength = 0; + while (phPos != 0) { + --phPos; + + if (filename[phPos] == Latin1Char('X')) { + ++phLength; + continue; + } + + if (phLength >= 6) { + ++phPos; + break; + } + + // start over + phLength = 0; + } + + Q_ASSERT(phLength >= 6); + path = filename; + pos = phPos; + length = phLength; +} /*! \internal - Generates a unique file path and returns a native handle to the open file. - \a path is used as a template when generating unique paths, \a pos - identifies the position of the first character that will be replaced in the - template and \a length the number of characters that may be substituted. - \a mode specifies the file mode bits (not used on Windows). - - Returns an open handle to the newly created file if successful, an invalid - handle otherwise. In both cases, the string in \a path will be changed and - contain the generated path name. + Generates a unique file path from the template \a templ and returns it. + The path in \c templ.path is modified. */ -static bool createFileFromTemplate(NativeFileHandle &file, - QFileSystemEntry::NativePath &path, size_t pos, size_t length, quint32 mode, - QSystemError &error) +QFileSystemEntry::NativePath QTemporaryFileName::generateNext() { Q_ASSERT(length != 0); - Q_ASSERT(pos < size_t(path.length())); - Q_ASSERT(length <= size_t(path.length()) - pos); + Q_ASSERT(pos < path.length()); + Q_ASSERT(length <= path.length() - pos); Char *const placeholderStart = (Char *)path.data() + pos; Char *const placeholderEnd = placeholderStart + length; - // Initialize placeholder with random chars + PID. + // Replace placeholder with random chars. { + // Since our dictionary is 26+26 characters, it would seem we only need + // a random number from 0 to 63 to select a character. However, due to + // the limited range, that would mean 12 (64-52) characters have double + // the probability of the others: 1 in 32 instead of 1 in 64. + // + // To overcome this limitation, we use more bits per character. With 10 + // bits, there are 16 characters with probability 19/1024 and the rest + // at 20/1024 (i.e, less than .1% difference). This allows us to do 3 + // characters per 32-bit random number, which is also half the typical + // placeholder length. + enum { BitsPerCharacter = 10 }; + Char *rIter = placeholderEnd; + while (rIter != placeholderStart) { + quint32 rnd = QRandomGenerator::get32(); + auto applyOne = [&]() { + quint32 v = rnd & ((1 << BitsPerCharacter) - 1); + rnd >>= BitsPerCharacter; + char ch = char((26 + 26) * v / (1 << BitsPerCharacter)); + if (ch < 26) + *--rIter = Latin1Char(ch + 'A'); + else + *--rIter = Latin1Char(ch - 26 + 'a'); + }; + + applyOne(); + if (rIter == placeholderStart) + break; -#if defined(QT_BUILD_CORE_LIB) - quint64 pid = quint64(QCoreApplication::applicationPid()); - do { - *--rIter = Latin1Char((pid % 10) + '0'); - pid /= 10; - } while (rIter != placeholderStart && pid != 0); -#endif + applyOne(); + if (rIter == placeholderStart) + break; - while (rIter != placeholderStart) { - char ch = char((qrand() & 0xffff) % (26 + 26)); - if (ch < 26) - *--rIter = Latin1Char(ch + 'A'); - else - *--rIter = Latin1Char(ch - 26 + 'a'); + applyOne(); } } - for (;;) { + return path; +} + +#ifndef QT_NO_TEMPORARYFILE + +/*! + \internal + + Generates a unique file path from the template \a templ and creates a new + file based based on those parameters: the \c templ.length characters in \c + templ.path starting at \c templ.pos will be replacd by a random sequence of + characters. \a mode specifies the file mode bits (not used on Windows). + + Returns true on success and sets the file handle on \a file. On error, + returns false, sets an invalid handle on \a handle and sets the error + condition in \a error. In both cases, the string in \a templ will be + changed and contain the generated path name. +*/ +static bool createFileFromTemplate(NativeFileHandle &file, QTemporaryFileName &templ, + quint32 mode, QSystemError &error) +{ + const int maxAttempts = 16; + for (int attempt = 0; attempt < maxAttempts; ++attempt) { // Atomically create file and obtain handle + const QFileSystemEntry::NativePath &path = templ.generateNext(); + #if defined(Q_OS_WIN) Q_UNUSED(mode); @@ -195,45 +260,58 @@ static bool createFileFromTemplate(NativeFileHandle &file, return false; } #endif - - /* tricky little algorwwithm for backward compatibility */ - for (Char *iter = placeholderStart;;) { - // Character progression: [0-9] => 'a' ... 'z' => 'A' .. 'Z' - // String progression: "ZZaiC" => "aabiC" - switch (char(*iter)) { - case 'Z': - // Rollover, advance next character - *iter = Latin1Char('a'); - if (++iter == placeholderEnd) { - // Out of alternatives. Return file exists error, previously set. - error = QSystemError(err, QSystemError::NativeError); - return false; - } - - continue; - - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - *iter = Latin1Char('a'); - break; - - case 'z': - // increment 'z' to 'A' - *iter = Latin1Char('A'); - break; - - default: - ++*iter; - break; - } - break; - } } - Q_ASSERT(false); return false; } +enum class CreateUnnamedFileStatus { + Success = 0, + NotSupported, + OtherError +}; + +static CreateUnnamedFileStatus +createUnnamedFile(NativeFileHandle &file, QTemporaryFileName &tfn, quint32 mode, QSystemError *error) +{ +#ifdef LINUX_UNNAMED_TMPFILE + // first, check if we have /proc, otherwise can't make the file exist later + // (no error message set, as caller will try regular temporary file) + if (!qt_haveLinuxProcfs()) + return CreateUnnamedFileStatus::NotSupported; + + const char *p = "."; + int lastSlash = tfn.path.lastIndexOf('/'); + if (lastSlash != -1) { + tfn.path[lastSlash] = '\0'; + p = tfn.path.data(); + } + + file = QT_OPEN(p, O_TMPFILE | QT_OPEN_RDWR | QT_OPEN_LARGEFILE, + static_cast<mode_t>(mode)); + if (file != -1) + return CreateUnnamedFileStatus::Success; + + if (errno == EOPNOTSUPP || errno == EISDIR) { + // fs or kernel doesn't support O_TMPFILE, so + // put the slash back so we may try a regular file + if (lastSlash != -1) + tfn.path[lastSlash] = '/'; + return CreateUnnamedFileStatus::NotSupported; + } + + // real error + *error = QSystemError(errno, QSystemError::NativeError); + return CreateUnnamedFileStatus::OtherError; +#else + Q_UNUSED(file); + Q_UNUSED(tfn); + Q_UNUSED(mode); + Q_UNUSED(error); + return CreateUnnamedFileStatus::NotSupported; +#endif +} + //************* QTemporaryFileEngine QTemporaryFileEngine::~QTemporaryFileEngine() { @@ -264,13 +342,6 @@ void QTemporaryFileEngine::setFileName(const QString &file) QFSFileEngine::setFileName(file); } -void QTemporaryFileEngine::setFileTemplate(const QString &fileTemplate) -{ - Q_D(QFSFileEngine); - if (filePathIsTemplate) - d->fileEntry = QFileSystemEntry(fileTemplate); -} - bool QTemporaryFileEngine::open(QIODevice::OpenMode openMode) { Q_D(QFSFileEngine); @@ -281,59 +352,7 @@ bool QTemporaryFileEngine::open(QIODevice::OpenMode openMode) if (!filePathIsTemplate) return QFSFileEngine::open(openMode); - QString qfilename = d->fileEntry.filePath(); - - // Ensure there is a placeholder mask - uint phPos = qfilename.length(); - uint phLength = 0; - - while (phPos != 0) { - --phPos; - - if (qfilename[phPos] == QLatin1Char('X')) { - ++phLength; - continue; - } - - if (phLength >= 6 - || qfilename[phPos] == QLatin1Char('/')) { - ++phPos; - break; - } - - // start over - phLength = 0; - } - - if (phLength < 6) - qfilename.append(QLatin1String(".XXXXXX")); - - // "Nativify" :-) - QFileSystemEntry::NativePath filename = QFileSystemEngine::absoluteName( - QFileSystemEntry(qfilename, QFileSystemEntry::FromInternalPath())) - .nativeFilePath(); - - // Find mask in native path - phPos = filename.length(); - phLength = 0; - while (phPos != 0) { - --phPos; - - if (filename[phPos] == Latin1Char('X')) { - ++phLength; - continue; - } - - if (phLength >= 6) { - ++phPos; - break; - } - - // start over - phLength = 0; - } - - Q_ASSERT(phLength >= 6); + QTemporaryFileName tfn(templateName); QSystemError error; #if defined(Q_OS_WIN) @@ -342,19 +361,24 @@ bool QTemporaryFileEngine::open(QIODevice::OpenMode openMode) NativeFileHandle &file = d->fd; #endif - if (!createFileFromTemplate(file, filename, phPos, phLength, fileMode, error)) { + CreateUnnamedFileStatus st = createUnnamedFile(file, tfn, fileMode, &error); + if (st == CreateUnnamedFileStatus::Success) { + unnamedFile = true; + d->fileEntry.clear(); + } else if (st == CreateUnnamedFileStatus::NotSupported && + createFileFromTemplate(file, tfn, fileMode, error)) { + filePathIsTemplate = false; + unnamedFile = false; + d->fileEntry = QFileSystemEntry(tfn.path, QFileSystemEntry::FromNativePath()); + } else { setError(QFile::OpenError, error.toString()); return false; } - d->fileEntry = QFileSystemEntry(filename, QFileSystemEntry::FromNativePath()); - #if !defined(Q_OS_WIN) || defined(Q_OS_WINRT) d->closeFileHandle = true; #endif - filePathIsTemplate = false; - d->openMode = openMode; d->lastFlushFailed = false; d->tried_stat = 0; @@ -369,7 +393,7 @@ bool QTemporaryFileEngine::remove() // we must explicitly call QFSFileEngine::close() before we remove it. d->unmapAll(); QFSFileEngine::close(); - if (QFSFileEngine::remove()) { + if (isUnnamedFile() || QFSFileEngine::remove()) { d->fileEntry.clear(); // If a QTemporaryFile is constructed using a template file path, the path // is generated in QTemporaryFileEngine::open() and then filePathIsTemplate @@ -384,12 +408,22 @@ bool QTemporaryFileEngine::remove() bool QTemporaryFileEngine::rename(const QString &newName) { + if (isUnnamedFile()) { + bool ok = materializeUnnamedFile(newName, DontOverwrite); + QFSFileEngine::close(); + return ok; + } QFSFileEngine::close(); return QFSFileEngine::rename(newName); } bool QTemporaryFileEngine::renameOverwrite(const QString &newName) { + if (isUnnamedFile()) { + bool ok = materializeUnnamedFile(newName, Overwrite); + QFSFileEngine::close(); + return ok; + } QFSFileEngine::close(); return QFSFileEngine::renameOverwrite(newName); } @@ -402,6 +436,88 @@ bool QTemporaryFileEngine::close() return true; } +QString QTemporaryFileEngine::fileName(QAbstractFileEngine::FileName file) const +{ + if (isUnnamedFile()) { + if (file == LinkName) { + // we know our file isn't (won't be) a symlink + return QString(); + } + + // for all other cases, materialize the file + const_cast<QTemporaryFileEngine *>(this)->materializeUnnamedFile(templateName, NameIsTemplate); + } + return QFSFileEngine::fileName(file); +} + +bool QTemporaryFileEngine::materializeUnnamedFile(const QString &newName, QTemporaryFileEngine::MaterializationMode mode) +{ + Q_ASSERT(isUnnamedFile()); + +#ifdef LINUX_UNNAMED_TMPFILE + Q_D(QFSFileEngine); + const QByteArray src = "/proc/self/fd/" + QByteArray::number(d->fd); + auto materializeAt = [=](const QFileSystemEntry &dst) { + return ::linkat(AT_FDCWD, src, AT_FDCWD, dst.nativeFilePath(), AT_SYMLINK_FOLLOW) == 0; + }; +#else + auto materializeAt = [](const QFileSystemEntry &) { return false; }; +#endif + + auto success = [this](const QFileSystemEntry &entry) { + filePathIsTemplate = false; + unnamedFile = false; + d_func()->fileEntry = entry; + return true; + }; + + auto materializeAsTemplate = [=](const QString &newName) { + QTemporaryFileName tfn(newName); + static const int maxAttempts = 16; + for (int attempt = 0; attempt < maxAttempts; ++attempt) { + tfn.generateNext(); + QFileSystemEntry entry(tfn.path, QFileSystemEntry::FromNativePath()); + if (materializeAt(entry)) + return success(entry); + } + return false; + }; + + if (mode == NameIsTemplate) { + if (materializeAsTemplate(newName)) + return true; + } else { + // Use linkat to materialize the file + QFileSystemEntry dst(newName); + if (materializeAt(dst)) + return success(dst); + + if (errno == EEXIST && mode == Overwrite) { + // retry by first creating a temporary file in the right dir + if (!materializeAsTemplate(templateName)) + return false; + + // then rename the materialized file to target (same as renameOverwrite) + QFSFileEngine::close(); + return QFSFileEngine::renameOverwrite(newName); + } + } + + // failed + setError(QFile::RenameError, QSystemError(errno, QSystemError::NativeError).toString()); + return false; +} + +bool QTemporaryFileEngine::isUnnamedFile() const +{ +#ifdef LINUX_UNNAMED_TMPFILE + Q_ASSERT(unnamedFile == d_func()->fileEntry.isEmpty()); + return unnamedFile; +#else + return false; +#endif +} + //************* QTemporaryFilePrivate QTemporaryFilePrivate::QTemporaryFilePrivate() @@ -420,7 +536,7 @@ QTemporaryFilePrivate::~QTemporaryFilePrivate() QAbstractFileEngine *QTemporaryFilePrivate::engine() const { if (!fileEngine) { - fileEngine = new QTemporaryFileEngine; + fileEngine = new QTemporaryFileEngine(&templateName); resetFileEngine(); } return fileEngine; @@ -438,6 +554,17 @@ void QTemporaryFilePrivate::resetFileEngine() const tef->initialize(fileName, 0600, false); } +void QTemporaryFilePrivate::materializeUnnamedFile() +{ +#ifdef LINUX_UNNAMED_TMPFILE + if (!fileName.isEmpty() || !fileEngine) + return; + + auto *tef = static_cast<QTemporaryFileEngine *>(fileEngine); + fileName = tef->fileName(QAbstractFileEngine::DefaultName); +#endif +} + QString QTemporaryFilePrivate::defaultTemplateName() { QString baseName; @@ -646,6 +773,10 @@ void QTemporaryFile::setAutoRemove(bool b) QString QTemporaryFile::fileName() const { Q_D(const QTemporaryFile); + auto tef = static_cast<QTemporaryFileEngine *>(d->fileEngine); + if (tef && tef->isReallyOpen()) + const_cast<QTemporaryFilePrivate *>(d)->materializeUnnamedFile(); + if(d->fileName.isEmpty()) return QString(); return d->engine()->fileName(QAbstractFileEngine::DefaultName); @@ -679,8 +810,36 @@ void QTemporaryFile::setFileTemplate(const QString &name) { Q_D(QTemporaryFile); d->templateName = name; - if (d->fileEngine) - static_cast<QTemporaryFileEngine*>(d->fileEngine)->setFileTemplate(name); +} + +/*! + \internal + + This is just a simplified version of QFile::rename() because we know a few + extra details about what kind of file we have. The documentation is hidden + from the user because QFile::rename() should be enough. +*/ +bool QTemporaryFile::rename(const QString &newName) +{ + Q_D(QTemporaryFile); + auto tef = static_cast<QTemporaryFileEngine *>(d->fileEngine); + if (!tef || !tef->isReallyOpen() || !tef->filePathWasTemplate) + return QFile::rename(newName); + + unsetError(); + close(); + if (error() == QFile::NoError) { + if (tef->rename(newName)) { + unsetError(); + // engine was able to handle the new name so we just reset it + tef->setFileName(newName); + d->fileName = newName; + return true; + } + + d->setError(QFile::RenameError, tef->errorString()); + } + return false; } /*! @@ -771,11 +930,10 @@ QTemporaryFile *QTemporaryFile::createNativeFile(QFile &file) bool QTemporaryFile::open(OpenMode flags) { Q_D(QTemporaryFile); - if (!d->fileName.isEmpty()) { - if (static_cast<QTemporaryFileEngine*>(d->engine())->isReallyOpen()) { - setOpenMode(flags); - return true; - } + auto tef = static_cast<QTemporaryFileEngine *>(d->fileEngine); + if (tef && tef->isReallyOpen()) { + setOpenMode(flags); + return true; } // reset the engine state so it creates a new, unique file name from the template; @@ -786,16 +944,20 @@ bool QTemporaryFile::open(OpenMode flags) d->resetFileEngine(); if (QFile::open(flags)) { - d->fileName = d->fileEngine->fileName(QAbstractFileEngine::DefaultName); + tef = static_cast<QTemporaryFileEngine *>(d->fileEngine); + if (tef->isUnnamedFile()) + d->fileName.clear(); + else + d->fileName = tef->fileName(QAbstractFileEngine::DefaultName); return true; } return false; } -QT_END_NAMESPACE - #endif // QT_NO_TEMPORARYFILE +QT_END_NAMESPACE + #ifndef QT_NO_QOBJECT #include "moc_qtemporaryfile.cpp" #endif |