/**************************************************************************** ** ** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB) ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Installer Framework. ** ** $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 Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "kdsavefile.h" #include #include #include #include #ifdef Q_OS_WIN # include # include // Required for _SH_DENYRW #endif #include #include #include #include #ifdef Q_OS_LINUX #include #endif static int permissionsToMode(QFile::Permissions p, bool *ok) { Q_ASSERT(ok); int m = 0; #ifdef Q_OS_WIN //following qfsfileengine_win.cpp: QFSFileEngine::setPermissions() if (p & QFile::ReadOwner || p & QFile::ReadUser || p & QFile::ReadGroup || p & QFile::ReadOther) m |= _S_IREAD; if (p & QFile::WriteOwner || p & QFile::WriteUser || p & QFile::WriteGroup || p & QFile::WriteOther) m |= _S_IWRITE; *ok = m != 0; #else if (p & QFile::ReadUser) m |= S_IRUSR; if (p & QFile::WriteUser) m |= S_IWUSR; if (p & QFile::ExeUser) m |= S_IXUSR; if (p & QFile::ReadGroup) m |= S_IRGRP; if (p & QFile::WriteGroup) m |= S_IWGRP; if (p & QFile::ExeGroup) m |= S_IXGRP; if (p & QFile::ReadOther) m |= S_IROTH; if (p & QFile::WriteOther) m |= S_IWOTH; if (p & QFile::ExeOther) m |= S_IXOTH; *ok = true; #endif return m; } static bool sync(int fd) { #ifdef Q_OS_WIN return _commit(fd) == 0; #else return fsync(fd) == 0; #endif } static QString makeAbsolute(const QString &path) { if (QDir::isAbsolutePath(path)) return path; return QDir::currentPath() + QLatin1String("/") + path; } static int myOpen(const QString &path, int flags, int mode) { #ifdef Q_OS_WIN int fd; #ifdef Q_CC_MINGW fd = _wsopen(reinterpret_cast(path.utf16()), flags, _SH_DENYRW, mode); #else _wsopen_s(&fd, reinterpret_cast(path.utf16()), flags, _SH_DENYRW, mode); #endif return fd; #else return open(QFile::encodeName(path).constData(), flags, mode); #endif } static void myClose(int fd) { #ifdef Q_OS_WIN _close(fd); #else close(fd); #endif } static bool touchFile(const QString &path, QFile::Permissions p) { bool ok = false; const int mode = permissionsToMode(p, &ok); if (!ok) return false; const int fd = myOpen(QDir::toNativeSeparators(path), O_WRONLY|O_CREAT, mode); if (fd < 0) { QFile file(path); if (!file.open(QIODevice::WriteOnly)) return false; if (!file.setPermissions(p)) { QFile::remove(path); return false; } return true; } sync(fd); myClose(fd); return true; } static QFile *createFile(const QString &path, QIODevice::OpenMode m, QFile::Permissions p, bool *openOk) { Q_ASSERT(openOk); *openOk = false; if (!touchFile(path, p)) return 0; std::auto_ptr file(new QFile(path)); *openOk = file->open(m | QIODevice::Append); if (!*openOk) QFile::remove(path); // try to remove empty file return file.release(); } /*! Generates a temporary file name based on template \a path \internal */ static QString generateTempFileName(const QString &path) { const QString tmp = path + QLatin1String("tmp.dsfdf.%1"); //TODO: use random suffix int count = 1; while (QFile::exists(tmp.arg(count))) ++count; return tmp.arg(count); } /*! \class KDSaveFile KDSaveFile \ingroup core \brief Secure and robust writing to a file */ class KDSaveFile::Private { KDSaveFile *const q; public: explicit Private(const QString &fname, KDSaveFile *qq) : q(qq), #ifdef Q_OS_WIN backupExtension(QLatin1String(".bak")), #else backupExtension(QLatin1String("~")), #endif permissions(QFile::ReadUser|QFile::WriteUser), filename(fname), tmpFile() { //TODO respect umask instead of hardcoded default permissions } ~Private() { deleteTempFile(); } bool deleteTempFile() { if (!tmpFile) return true; const QString name = tmpFile->fileName(); delete tmpFile; //force a real close by deleting the object, before deleting the actual file. Needed on Windows QFile tmp(name); return tmp.remove(); } bool recreateTemporaryFile(QIODevice::OpenMode mode) { deleteTempFile(); bool ok = false; tmpFile = createFile(generateTempFileName(filename), mode, permissions, &ok); QObject::connect(tmpFile, SIGNAL(aboutToClose()), q, SIGNAL(aboutToClose())); QObject::connect(tmpFile, SIGNAL(bytesWritten(qint64)), q, SIGNAL(bytesWritten(qint64))); QObject::connect(tmpFile, SIGNAL(readChannelFinished()), q, SIGNAL(readChannelFinished())); QObject::connect(tmpFile, SIGNAL(readyRead()), q, SIGNAL(readyRead())); return ok; } QString generateBackupName() const { const QString bf = filename + backupExtension; if (!QFile::exists(bf)) return bf; int count = 1; while (QFile::exists(bf + QString::number(count))) ++count; return bf + QString::number(count); } void propagateErrors() { if (!tmpFile) return; q->setErrorString(tmpFile->errorString()); } QString backupExtension; QFile::Permissions permissions; QString filename; QPointer tmpFile; }; KDSaveFile::KDSaveFile(const QString &filename, QObject *parent) : QIODevice(parent), d( new Private(makeAbsolute(filename), this)) {} KDSaveFile::~KDSaveFile() { delete d; } void KDSaveFile::close() { d->deleteTempFile(); QIODevice::close(); } bool KDSaveFile::open(OpenMode mode) { setOpenMode(QIODevice::NotOpen); if (mode & QIODevice::Append) { setErrorString(tr("Append mode not supported.")); return false; } if ((mode & QIODevice::WriteOnly) == 0){ setErrorString(tr("Read-only access not supported.")); return false; } // for some reason this seems to be problematic... let's remove it for now, will break in commit anyhow, if it's serious //if ( QFile::exists( d->filename ) && !QFileInfo( d->filename ).isWritable() ) { // setErrorString( tr("The target file %1 exists and is not writable").arg( d->filename ) ); // return false; //} const bool opened = d->recreateTemporaryFile(mode); if (opened) { setOpenMode(mode); } //if target file already exists, apply permissions of existing file to temp file return opened; } bool KDSaveFile::atEnd() { return d->tmpFile ? d->tmpFile->atEnd() : QIODevice::atEnd(); } qint64 KDSaveFile::bytesAvailable() const { return d->tmpFile ? d->tmpFile->bytesAvailable() : QIODevice::bytesAvailable(); } qint64 KDSaveFile::bytesToWrite() const { return d->tmpFile ? d->tmpFile->bytesToWrite() : QIODevice::bytesToWrite(); } bool KDSaveFile::canReadLine() const { return d->tmpFile ? d->tmpFile->canReadLine() : QIODevice::canReadLine(); } bool KDSaveFile::isSequential() const { return d->tmpFile ? d->tmpFile->isSequential() : QIODevice::isSequential(); } qint64 KDSaveFile::pos() const { return d->tmpFile ? d->tmpFile->pos() : QIODevice::pos(); } bool KDSaveFile::reset() { return d->tmpFile ? d->tmpFile->reset() : QIODevice::reset(); } bool KDSaveFile::seek(qint64 pos) { const bool ret = d->tmpFile ? d->tmpFile->seek(pos) : QIODevice::seek(pos); if (!ret) d->propagateErrors(); return ret; } qint64 KDSaveFile::size() const { return d->tmpFile ? d->tmpFile->size() : QIODevice::size(); } bool KDSaveFile::waitForBytesWritten(int msecs) { return d->tmpFile ? d->tmpFile->waitForBytesWritten(msecs) : QIODevice::waitForBytesWritten(msecs); } bool KDSaveFile::waitForReadyRead(int msecs) { return d->tmpFile ? d->tmpFile->waitForReadyRead(msecs) : QIODevice::waitForReadyRead(msecs); } bool KDSaveFile::commit(KDSaveFile::CommitMode mode) { if (!d->tmpFile) return false; const QString tmpfname = d->tmpFile->fileName(); d->tmpFile->flush(); delete d->tmpFile; QFile orig(d->filename); QString backup; if (orig.exists()) { backup = d->generateBackupName(); if (!orig.rename(backup)) { setErrorString(tr("Could not backup existing file %1: %2").arg( d->filename, orig.errorString())); QFile tmp(tmpfname); if (!tmp.remove()) // TODO how to report this error? qWarning() << "Could not remove temp file" << tmpfname << tmp.errorString(); if (mode != OverwriteExistingFile) return false; } } QFile target(tmpfname); if (!target.rename(d->filename)) { setErrorString(target.errorString()); return false; } if (mode == OverwriteExistingFile) { QFile tmp(backup); const bool removed = !tmp.exists() || tmp.remove(backup); if (!removed) qWarning() << "Could not remove the backup: " << tmp.errorString(); } return true; } QString KDSaveFile::fileName() const { return d->filename; } void KDSaveFile::setFileName(const QString &filename) { const QString fn = makeAbsolute(filename); if (fn == d->filename) return; close(); delete d->tmpFile; d->filename = fn; } qint64 KDSaveFile::readData(char *data, qint64 maxSize) { if (!d->tmpFile) { setErrorString(tr("TODO")); return -1; } const qint64 ret = d->tmpFile->read(data, maxSize); d->propagateErrors(); return ret; } qint64 KDSaveFile::readLineData(char *data, qint64 maxSize) { if (!d->tmpFile) { setErrorString(tr("TODO")); return -1; } const qint64 ret = d->tmpFile->readLine(data, maxSize); d->propagateErrors(); return ret; } qint64 KDSaveFile::writeData(const char *data, qint64 maxSize) { if (!d->tmpFile) { setErrorString(tr("TODO")); return -1; } const qint64 ret = d->tmpFile->write(data, maxSize); d->propagateErrors(); return ret; } bool KDSaveFile::flush() { return d->tmpFile ? d->tmpFile->flush() : false; } bool KDSaveFile::resize(qint64 sz) { return d->tmpFile ? d->tmpFile->resize(sz) : false; } int KDSaveFile::handle() const { return d->tmpFile ? d->tmpFile->handle() : -1; } QFile::Permissions KDSaveFile::permissions() const { return d->tmpFile ? d->tmpFile->permissions() : d->permissions; } bool KDSaveFile::setPermissions(QFile::Permissions p) { d->permissions = p; if (d->tmpFile) return d->tmpFile->setPermissions(p); return false; } void KDSaveFile::setBackupExtension(const QString &ext) { d->backupExtension = ext; } QString KDSaveFile::backupExtension() const { return d->backupExtension; }