summaryrefslogtreecommitdiffstats
path: root/installerbuilder/libinstaller/kdtools/KDToolsCore/kdsavefile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'installerbuilder/libinstaller/kdtools/KDToolsCore/kdsavefile.cpp')
-rw-r--r--installerbuilder/libinstaller/kdtools/KDToolsCore/kdsavefile.cpp476
1 files changed, 476 insertions, 0 deletions
diff --git a/installerbuilder/libinstaller/kdtools/KDToolsCore/kdsavefile.cpp b/installerbuilder/libinstaller/kdtools/KDToolsCore/kdsavefile.cpp
new file mode 100644
index 000000000..38a4b87bf
--- /dev/null
+++ b/installerbuilder/libinstaller/kdtools/KDToolsCore/kdsavefile.cpp
@@ -0,0 +1,476 @@
+/****************************************************************************
+** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved.
+**
+** This file is part of the KD Tools library.
+**
+** Licensees holding valid commercial KD Tools licenses may use this file in
+** accordance with the KD Tools Commercial License Agreement provided with
+** the Software.
+**
+**
+** This file may be distributed and/or modified under the terms of the
+** GNU Lesser General Public License version 2 and version 3 as published by the
+** Free Software Foundation and appearing in the file LICENSE.LGPL included.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+** Contact info@kdab.com if any conditions of this licensing are not
+** clear to you.
+**
+**********************************************************************/
+
+#include "kdsavefile.h"
+
+#include <QtCore/QDebug>
+#include <QtCore/QDir>
+#include <QtCore/QPointer>
+#include <QtCore/QTemporaryFile>
+
+#ifdef Q_OS_WIN
+# include <io.h>
+#endif
+#include <memory>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+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;
+ _wsopen_s( &fd, reinterpret_cast<const wchar_t*>( path.utf16() ), flags, _SH_DENYRW, mode );
+ 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;
+ 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 );
+ if ( !touchFile( path, p ) )
+ return 0;
+ std::auto_ptr<QFile> 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;
+ 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<QFile> tmpFile;
+};
+
+KDSaveFile::KDSaveFile( const QString& filename, QObject* parent ) : QIODevice( parent ), d( new Private( makeAbsolute( filename ), this ) ) {
+}
+
+KDSaveFile::~KDSaveFile() {
+}
+
+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;
+}
+
+/**
+ * TODO
+ *
+ *
+ */
+
+#ifdef KDTOOLSCORE_UNITTESTS
+
+#include <KDUnitTest/Test>
+
+KDAB_UNITTEST_SIMPLE( KDSaveFile, "kdcoretools" ) {
+ //TODO test contents (needs blocking and checked write() )
+ {
+ const QString testfile1 = QLatin1String("kdsavefile-test1");
+ QByteArray testData("lalalala");
+ KDSaveFile saveFile( testfile1 );
+ assertTrue( saveFile.open( QIODevice::WriteOnly ) );
+ saveFile.write( testData.constData(), testData.size() );
+ assertTrue( saveFile.commit() );
+ assertTrue( QFile::exists( testfile1 ) );
+ assertTrue( QFile::remove( testfile1 ) );
+ }
+ {
+ const QString testfile1 = QLatin1String("kdsavefile-test1");
+ QByteArray testData("lalalala");
+ KDSaveFile saveFile( testfile1 );
+ assertTrue( saveFile.open( QIODevice::WriteOnly ) );
+ saveFile.write( testData.constData(), testData.size() );
+ saveFile.close();
+ assertFalse( QFile::exists( testfile1 ) );
+ }
+ {
+ const QString testfile1 = QLatin1String("kdsavefile-test1");
+ QByteArray testData("lalalala");
+ KDSaveFile saveFile( testfile1 );
+ assertTrue( saveFile.open( QIODevice::WriteOnly ) );
+ saveFile.write( testData.constData(), testData.size() );
+ assertTrue( saveFile.commit() );
+ assertTrue( QFile::exists( testfile1 ) );
+
+ KDSaveFile sf2( testfile1 );
+ sf2.setBackupExtension( QLatin1String(".bak") );
+ assertTrue( sf2.open( QIODevice::WriteOnly ) );
+ sf2.write( testData.constData(), testData.size() );
+ sf2.commit(); //commit in backup mode (default)
+ const QString backup = testfile1 + sf2.backupExtension();
+ assertTrue( QFile::exists( backup ) );
+ assertTrue( QFile::remove( backup ) );
+
+ KDSaveFile sf3( testfile1 );
+ sf3.setBackupExtension( QLatin1String(".bak") );
+ assertTrue( sf3.open( QIODevice::WriteOnly ) );
+ sf3.write( testData.constData(), testData.size() );
+ sf3.commit( KDSaveFile::OverwriteExistingFile );
+ const QString backup2 = testfile1 + sf3.backupExtension();
+ assertFalse( QFile::exists( backup2 ) );
+
+ assertTrue( QFile::remove( testfile1 ) );
+ }
+ {
+ const QString testfile1 = QLatin1String("kdsavefile-test1");
+ KDSaveFile sf( testfile1 );
+ assertFalse( sf.open( QIODevice::ReadOnly ) );
+ assertFalse( sf.open( QIODevice::WriteOnly|QIODevice::Append ) );
+ assertTrue( sf.open( QIODevice::ReadWrite ) );
+ }
+}
+
+#endif // KDTOOLSCORE_UNITTESTS