summaryrefslogtreecommitdiffstats
path: root/src/corelib/io/qtemporaryfile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/io/qtemporaryfile.cpp')
-rw-r--r--src/corelib/io/qtemporaryfile.cpp502
1 files changed, 332 insertions, 170 deletions
diff --git a/src/corelib/io/qtemporaryfile.cpp b/src/corelib/io/qtemporaryfile.cpp
index 8a99873fee..b8d3e859cf 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::global()->generate();
+ 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