diff options
Diffstat (limited to 'src/corelib/io/qtemporaryfile.cpp')
-rw-r--r-- | src/corelib/io/qtemporaryfile.cpp | 713 |
1 files changed, 713 insertions, 0 deletions
diff --git a/src/corelib/io/qtemporaryfile.cpp b/src/corelib/io/qtemporaryfile.cpp new file mode 100644 index 0000000000..b5e5808145 --- /dev/null +++ b/src/corelib/io/qtemporaryfile.cpp @@ -0,0 +1,713 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtemporaryfile.h" + +#ifndef QT_NO_TEMPORARYFILE + +#include "qplatformdefs.h" +#include "qabstractfileengine.h" +#include "private/qfile_p.h" +#include "private/qabstractfileengine_p.h" +#include "private/qfsfileengine_p.h" + +#if !defined(Q_OS_WINCE) +# include <errno.h> +#endif + +#include <stdlib.h> +#include <time.h> +#include <ctype.h> + +#if defined(Q_OS_UNIX) +# include "private/qcore_unix_p.h" // overrides QT_OPEN +#endif + +#if defined(QT_BUILD_CORE_LIB) +#include "qcoreapplication.h" +#endif + +QT_BEGIN_NAMESPACE + +/* + * 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. + */ + +/*! + \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 placeholderStart and \a placeholderEnd delimit the sub-string that will + be randomized. + + 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. +*/ +static int createFileFromTemplate(char *const path, + char *const placeholderStart, char *const placeholderEnd) +{ + Q_ASSERT(placeholderEnd > placeholderStart); + + // Initialize placeholder with random chars + PID. + { + char *rIter = placeholderEnd; + +#if defined(QT_BUILD_CORE_LIB) + qint64 pid = QCoreApplication::applicationPid(); + do { + *--rIter = (pid % 10) + '0'; + pid /= 10; + } while (rIter != placeholderStart && pid != 0); +#endif + + while (rIter != placeholderStart) { + char ch = char((qrand() & 0xffff) % (26 + 26)); + if (ch < 26) + *--rIter = ch + 'A'; + else + *--rIter = ch - 26 + 'a'; + } + } + + for (;;) { + // Atomically create file and obtain handle +#ifndef Q_OS_WIN + { + int fd = QT_OPEN(path, QT_OPEN_CREAT | O_EXCL | QT_OPEN_RDWR | QT_OPEN_LARGEFILE, 0600); + if (fd != -1) + return fd; + if (errno != EEXIST) + return -1; + } +#else + if (!QFileInfo(QLatin1String(path)).exists()) + return 1; +#endif + + /* tricky little algorwwithm for backward compatibility */ + for (char *iter = placeholderStart;;) { + // Character progression: [0-9] => 'a' ... 'z' => 'A' .. 'Z' + // String progression: "ZZaiC" => "aabiC" + if (*iter == 'Z') { + *iter++ = 'a'; + if (iter == placeholderEnd) + return -1; + } else { + if (isdigit(*iter)) + *iter = 'a'; + else if (*iter == 'z') /* inc from z to A */ + *iter = 'A'; + else { + ++*iter; + } + break; + } + } + } + /*NOTREACHED*/ +} + +//************* QTemporaryFileEngine +class QTemporaryFileEngine : public QFSFileEngine +{ + Q_DECLARE_PRIVATE(QFSFileEngine) +public: + QTemporaryFileEngine(const QString &file, bool fileIsTemplate = true) + : QFSFileEngine(), filePathIsTemplate(fileIsTemplate) + { + Q_D(QFSFileEngine); + d->fileEntry = QFileSystemEntry(file); + + if (!filePathIsTemplate) + QFSFileEngine::setFileName(file); + } + + ~QTemporaryFileEngine(); + + bool isReallyOpen(); + void setFileName(const QString &file); + void setFileTemplate(const QString &fileTemplate); + + bool open(QIODevice::OpenMode flags); + bool remove(); + bool rename(const QString &newName); + bool close(); + + bool filePathIsTemplate; +}; + +QTemporaryFileEngine::~QTemporaryFileEngine() +{ + QFSFileEngine::close(); +} + +bool QTemporaryFileEngine::isReallyOpen() +{ + Q_D(QFSFileEngine); + + if (!((0 == d->fh) && (-1 == d->fd) +#if defined (Q_OS_SYMBIAN) + && (0 == d->symbianFile.SubSessionHandle()) +#endif +#if defined Q_OS_WIN + && (INVALID_HANDLE_VALUE == d->fileHandle) +#endif + )) + return true; + + return false; + +} + +void QTemporaryFileEngine::setFileName(const QString &file) +{ + // Really close the file, so we don't leak + QFSFileEngine::close(); + 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); + Q_ASSERT(!isReallyOpen()); + + openMode |= QIODevice::ReadWrite; + + if (!filePathIsTemplate) + return QFSFileEngine::open(openMode); + + QString qfilename = d->fileEntry.filePath(); + + // Find placeholder string. + uint phPos = qfilename.length(); + uint phLength = 0; + + while (phPos != 0) { + --phPos; + + if (qfilename[phPos] == QLatin1Char('X')) { + ++phLength; + continue; + } + + if (qfilename[phPos] == QLatin1Char('/') + || phLength >= 6) { + ++phPos; + break; + } + + phLength = 0; + } + + QStringRef prefix, suffix; + if (phLength < 6) { + qfilename += QLatin1Char('.'); + prefix = QStringRef(&qfilename); + phLength = 6; + } else { + prefix = qfilename.leftRef(phPos); + suffix = qfilename.midRef(phPos + phLength); + } + + QByteArray filename = prefix.toLocal8Bit(); + phPos = filename.length(); + if (suffix.isEmpty()) + filename.resize(phPos + phLength); + else + filename.insert(phPos + phLength, suffix.toLocal8Bit()); + + char *path = filename.data(); + +#ifndef Q_OS_WIN + int fd = createFileFromTemplate(path, path + phPos, path + phPos + phLength); + if (fd != -1) { + // First open the fd as an external file descriptor to + // initialize the engine properly. + if (QFSFileEngine::open(openMode, fd)) { + + // Allow the engine to close the handle even if it's "external". + d->closeFileHandle = true; + + // Restore the file names (open() resets them). + d->fileEntry = QFileSystemEntry(QString::fromLocal8Bit(path, filename.length())); //note that filename is NOT a native path + filePathIsTemplate = false; + return true; + } + + QT_CLOSE(fd); + } + setError(errno == EMFILE ? QFile::ResourceError : QFile::OpenError, qt_error_string(errno)); + return false; +#else + if (createFileFromTemplate(path, path + phPos, path + phPos + phLength) == -1) { + return false; + } + + QString template_ = d->fileEntry.filePath(); + d->fileEntry = QFileSystemEntry(QString::fromLocal8Bit(path, filename.length())); + + if (QFSFileEngine::open(openMode)) { + filePathIsTemplate = false; + return true; + } + + d->fileEntry = QFileSystemEntry(template_); + return false; +#endif +} + +bool QTemporaryFileEngine::remove() +{ + Q_D(QFSFileEngine); + // Since the QTemporaryFileEngine::close() does not really close the file, + // we must explicitly call QFSFileEngine::close() before we remove it. + QFSFileEngine::close(); + if (QFSFileEngine::remove()) { + d->fileEntry.clear(); + return true; + } + return false; +} + +bool QTemporaryFileEngine::rename(const QString &newName) +{ + QFSFileEngine::close(); + return QFSFileEngine::rename(newName); +} + +bool QTemporaryFileEngine::close() +{ + // Don't close the file, just seek to the front. + seek(0); + setError(QFile::UnspecifiedError, QString()); + return true; +} + +//************* QTemporaryFilePrivate +class QTemporaryFilePrivate : public QFilePrivate +{ + Q_DECLARE_PUBLIC(QTemporaryFile) + +protected: + QTemporaryFilePrivate(); + ~QTemporaryFilePrivate(); + + bool autoRemove; + QString templateName; +}; + +QTemporaryFilePrivate::QTemporaryFilePrivate() : autoRemove(true) +{ +} + +QTemporaryFilePrivate::~QTemporaryFilePrivate() +{ +} + +//************* QTemporaryFile + +/*! + \class QTemporaryFile + \reentrant + \brief The QTemporaryFile class is an I/O device that operates on temporary files. + + \ingroup io + + + QTemporaryFile is used to create unique temporary files safely. + The file itself is created by calling open(). The name of the + temporary file is guaranteed to be unique (i.e., you are + guaranteed to not overwrite an existing file), and the file will + subsequently be removed upon destruction of the QTemporaryFile + object. This is an important technique that avoids data + corruption for applications that store data in temporary files. + The file name is either auto-generated, or created based on a + template, which is passed to QTemporaryFile's constructor. + + Example: + + \snippet doc/src/snippets/code/src_corelib_io_qtemporaryfile.cpp 0 + + Reopening a QTemporaryFile after calling close() is safe. For as long as + the QTemporaryFile object itself is not destroyed, the unique temporary + file will exist and be kept open internally by QTemporaryFile. + + The file name of the temporary file can be found by calling fileName(). + Note that this is only defined after the file is first opened; the function + returns an empty string before this. + + A temporary file will have some static part of the name and some + part that is calculated to be unique. The default filename \c + qt_temp will be placed into the temporary path as returned by + QDir::tempPath(). If you specify your own filename, a relative + file path will not be placed in the temporary directory by + default, but be relative to the current working directory. + + Specified filenames can contain the following template \c XXXXXX + (six upper case "X" characters), which will be replaced by the + auto-generated portion of the filename. Note that the template is + case sensitive. If the template is not present in the filename, + QTemporaryFile appends the generated part to the filename given. + + \sa QDir::tempPath(), QFile +*/ + +#ifdef QT_NO_QOBJECT +QTemporaryFile::QTemporaryFile() + : QFile(*new QTemporaryFilePrivate) +{ + Q_D(QTemporaryFile); + d->templateName = QDir::tempPath() + QLatin1String("/qt_temp.XXXXXX"); +} + +QTemporaryFile::QTemporaryFile(const QString &templateName) + : QFile(*new QTemporaryFilePrivate) +{ + Q_D(QTemporaryFile); + d->templateName = templateName; +} + +#else +/*! + Constructs a QTemporaryFile in QDir::tempPath(), using the file template + "qt_temp.XXXXXX". The file is stored in the system's temporary directory. + + \sa setFileTemplate(), QDir::tempPath() +*/ +QTemporaryFile::QTemporaryFile() + : QFile(*new QTemporaryFilePrivate, 0) +{ + Q_D(QTemporaryFile); + d->templateName = QDir::tempPath() + QLatin1String("/qt_temp.XXXXXX"); +} + +/*! + Constructs a QTemporaryFile with a template filename of \a + templateName. Upon opening the temporary file this will be used to create + a unique filename. + + If the \a templateName does not contain XXXXXX it will automatically be + appended and used as the dynamic portion of the filename. + + If \a templateName is a relative path, the path will be relative to the + current working directory. You can use QDir::tempPath() to construct \a + templateName if you want use the system's temporary directory. + + \sa open(), fileTemplate() +*/ +QTemporaryFile::QTemporaryFile(const QString &templateName) + : QFile(*new QTemporaryFilePrivate, 0) +{ + Q_D(QTemporaryFile); + d->templateName = templateName; +} + +/*! + Constructs a QTemporaryFile (with the given \a parent) in + QDir::tempPath(), using the file template "qt_temp.XXXXXX". + + \sa setFileTemplate() +*/ +QTemporaryFile::QTemporaryFile(QObject *parent) + : QFile(*new QTemporaryFilePrivate, parent) +{ + Q_D(QTemporaryFile); + d->templateName = QDir::tempPath() + QLatin1String("/qt_temp.XXXXXX"); +} + +/*! + Constructs a QTemporaryFile with a template filename of \a + templateName and the specified \a parent. + Upon opening the temporary file this will be used to + create a unique filename. + + If the \a templateName does not contain XXXXXX it will automatically be + appended and used as the dynamic portion of the filename. + + If \a templateName is a relative path, the path will be relative to the + current working directory. You can use QDir::tempPath() to construct \a + templateName if you want use the system's temporary directory. + + \sa open(), fileTemplate() +*/ +QTemporaryFile::QTemporaryFile(const QString &templateName, QObject *parent) + : QFile(*new QTemporaryFilePrivate, parent) +{ + Q_D(QTemporaryFile); + d->templateName = templateName; +} +#endif + +/*! + Destroys the temporary file object, the file is automatically + closed if necessary and if in auto remove mode it will + automatically delete the file. + + \sa autoRemove() +*/ +QTemporaryFile::~QTemporaryFile() +{ + Q_D(QTemporaryFile); + close(); + if (!d->fileName.isEmpty() && d->autoRemove) + remove(); +} + +/*! + \fn bool QTemporaryFile::open() + + A QTemporaryFile will always be opened in QIODevice::ReadWrite mode, + this allows easy access to the data in the file. This function will + return true upon success and will set the fileName() to the unique + filename used. + + \sa fileName() +*/ + +/*! + Returns true if the QTemporaryFile is in auto remove + mode. Auto-remove mode will automatically delete the filename from + disk upon destruction. This makes it very easy to create your + QTemporaryFile object on the stack, fill it with data, read from + it, and finally on function return it will automatically clean up + after itself. + + Auto-remove is on by default. + + \sa setAutoRemove(), remove() +*/ +bool QTemporaryFile::autoRemove() const +{ + Q_D(const QTemporaryFile); + return d->autoRemove; +} + +/*! + Sets the QTemporaryFile into auto-remove mode if \a b is true. + + Auto-remove is on by default. + + \sa autoRemove(), remove() +*/ +void QTemporaryFile::setAutoRemove(bool b) +{ + Q_D(QTemporaryFile); + d->autoRemove = b; +} + +/*! + Returns the complete unique filename backing the QTemporaryFile + object. This string is null before the QTemporaryFile is opened, + afterwards it will contain the fileTemplate() plus + additional characters to make it unique. + + \sa fileTemplate() +*/ + +QString QTemporaryFile::fileName() const +{ + Q_D(const QTemporaryFile); + if(d->fileName.isEmpty()) + return QString(); + return fileEngine()->fileName(QAbstractFileEngine::DefaultName); +} + +/*! + Returns the set file template. The default file template will be + called qt_temp and be placed in QDir::tempPath(). + + \sa setFileTemplate() +*/ +QString QTemporaryFile::fileTemplate() const +{ + Q_D(const QTemporaryFile); + return d->templateName; +} + +/*! + Sets the static portion of the file name to \a name. If the file + template ends in XXXXXX that will automatically be replaced with + the unique part of the filename, otherwise a filename will be + determined automatically based on the static portion specified. + + If \a name contains a relative file path, the path will be relative to the + current working directory. You can use QDir::tempPath() to construct \a + name if you want use the system's temporary directory. + + \sa fileTemplate() +*/ +void QTemporaryFile::setFileTemplate(const QString &name) +{ + Q_D(QTemporaryFile); + d->templateName = name; + if (d->fileEngine) + static_cast<QTemporaryFileEngine*>(d->fileEngine)->setFileTemplate(name); +} + +/*! + \fn QTemporaryFile *QTemporaryFile::createLocalFile(const QString &fileName) + \overload + + Works on the given \a fileName rather than an existing QFile + object. +*/ + + +/*! + If \a file is not on a local disk, a temporary file is created + on a local disk, \a file is copied into the temporary local file, + and a pointer to the temporary local file is returned. If \a file + is already on a local disk, a copy is not created and 0 is returned. +*/ +QTemporaryFile *QTemporaryFile::createLocalFile(QFile &file) +{ + if (QAbstractFileEngine *engine = file.fileEngine()) { + if(engine->fileFlags(QAbstractFileEngine::FlagsMask) & QAbstractFileEngine::LocalDiskFlag) + return 0; //local already + //cache + bool wasOpen = file.isOpen(); + qint64 old_off = 0; + if(wasOpen) + old_off = file.pos(); + else + file.open(QIODevice::ReadOnly); + //dump data + QTemporaryFile *ret = new QTemporaryFile; + ret->open(); + file.seek(0); + char buffer[1024]; + while(true) { + qint64 len = file.read(buffer, 1024); + if(len < 1) + break; + ret->write(buffer, len); + } + ret->seek(0); + //restore + if(wasOpen) + file.seek(old_off); + else + file.close(); + //done + return ret; + } + return 0; +} + +/*! + \internal +*/ + +QAbstractFileEngine *QTemporaryFile::fileEngine() const +{ + Q_D(const QTemporaryFile); + if(!d->fileEngine) { + if (d->fileName.isEmpty()) + d->fileEngine = new QTemporaryFileEngine(d->templateName); + else + d->fileEngine = new QTemporaryFileEngine(d->fileName, false); + } + return d->fileEngine; +} + +/*! + \reimp + + Creates a unique file name for the temporary file, and opens it. You can + get the unique name later by calling fileName(). The file is guaranteed to + have been created by this function (i.e., it has never existed before). +*/ +bool QTemporaryFile::open(OpenMode flags) +{ + Q_D(QTemporaryFile); + if (!d->fileName.isEmpty()) { + if (static_cast<QTemporaryFileEngine*>(fileEngine())->isReallyOpen()) { + setOpenMode(flags); + return true; + } + } + + if (QFile::open(flags)) { + d->fileName = d->fileEngine->fileName(QAbstractFileEngine::DefaultName); + return true; + } + return false; +} + +QT_END_NAMESPACE + +#endif // QT_NO_TEMPORARYFILE + + |