KDECore
ksavefile.cpp
Go to the documentation of this file.
00001 /* kate: tab-indents off; replace-tabs on; tab-width 4; remove-trailing-space on; encoding utf-8;*/ 00002 /* 00003 This file is part of the KDE libraries 00004 Copyright 1999 Waldo Bastian <bastian@kde.org> 00005 Copyright 2006 Allen Winter <winter@kde.org> 00006 Copyright 2006 Gregory S. Hayes <syncomm@kde.org> 00007 Copyright 2006 Jaison Lee <lee.jaison@gmail.com> 00008 00009 This library is free software; you can redistribute it and/or 00010 modify it under the terms of the GNU Library General Public 00011 License version 2 as published by the Free Software Foundation. 00012 00013 This library is distributed in the hope that it will be useful, 00014 but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 Library General Public License for more details. 00017 00018 You should have received a copy of the GNU Library General Public License 00019 along with this library; see the file COPYING.LIB. If not, write to 00020 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00021 Boston, MA 02110-1301, USA. 00022 */ 00023 00024 #include "ksavefile.h" 00025 00026 #include <config.h> 00027 00028 #include <QtCore/QDir> 00029 #include <QProcess> 00030 #include <QTemporaryFile> 00031 00032 #include <kconfig.h> 00033 #include <kde_file.h> 00034 #include <klocale.h> 00035 #include <kstandarddirs.h> 00036 #include <kconfiggroup.h> 00037 #include <kcomponentdata.h> 00038 00039 #include <stdlib.h> 00040 #include <errno.h> 00041 00042 class KSaveFile::Private 00043 { 00044 public: 00045 QString realFileName; //The name of the end-result file 00046 QString tempFileName; //The name of the temp file we are using 00047 00048 QFile::FileError error; 00049 QString errorString; 00050 bool wasFinalized; 00051 KComponentData componentData; 00052 00053 Private(const KComponentData &c) 00054 : componentData(c) 00055 { 00056 error = QFile::NoError; 00057 wasFinalized = false; 00058 } 00059 }; 00060 00061 KSaveFile::KSaveFile() 00062 : d(new Private(KGlobal::mainComponent())) 00063 { 00064 } 00065 00066 KSaveFile::KSaveFile(const QString &filename, const KComponentData &componentData) 00067 : d(new Private(componentData)) 00068 { 00069 KSaveFile::setFileName(filename); 00070 } 00071 00072 KSaveFile::~KSaveFile() 00073 { 00074 if (!d->wasFinalized) 00075 finalize(); 00076 00077 delete d; 00078 } 00079 00080 bool KSaveFile::open(OpenMode flags) 00081 { 00082 if ( d->realFileName.isNull() ) { 00083 d->error=QFile::OpenError; 00084 d->errorString=i18n("No target filename has been given."); 00085 return false; 00086 } 00087 00088 if ( !d->tempFileName.isNull() ) { 00089 #if 0 // do not set an error here, this open() fails, but the file itself is without errors 00090 d->error=QFile::OpenError; 00091 d->errorString=i18n("Already opened."); 00092 #endif 00093 return false; 00094 } 00095 00096 // we only check here if the directory can be written to 00097 // the actual filename isn't written to, but replaced later 00098 // with the contents of our tempfile 00099 if (!KStandardDirs::checkAccess(d->realFileName, W_OK)) { 00100 d->error=QFile::PermissionsError; 00101 d->errorString=i18n("Insufficient permissions in target directory."); 00102 return false; 00103 } 00104 00105 //Create our temporary file 00106 QTemporaryFile tempFile; 00107 tempFile.setAutoRemove(false); 00108 tempFile.setFileTemplate(d->realFileName + QLatin1String("XXXXXX.new")); 00109 if (!tempFile.open()) { 00110 d->error=QFile::OpenError; 00111 d->errorString=i18n("Unable to open temporary file."); 00112 return false; 00113 } 00114 00115 // if we're overwriting an existing file, ensure temp file's 00116 // permissions are the same as existing file so the existing 00117 // file's permissions are preserved. this will succeed completely 00118 // only if we are the same owner and group - or allmighty root. 00119 QFileInfo fi ( d->realFileName ); 00120 if (fi.exists()) { 00121 //Qt apparently has no way to change owner/group of file :( 00122 if (fchown(tempFile.handle(), fi.ownerId(), fi.groupId())) { 00123 // failed to set user and group => try to restore group only. 00124 fchown(tempFile.handle(), -1, fi.groupId()); 00125 } 00126 00127 tempFile.setPermissions(fi.permissions()); 00128 } 00129 else { 00130 mode_t umsk = KGlobal::umask(); 00131 fchmod(tempFile.handle(), 0666&(~umsk)); 00132 } 00133 00134 //Open oursleves with the temporary file 00135 QFile::setFileName(tempFile.fileName()); 00136 if (!QFile::open(flags)) { 00137 tempFile.setAutoRemove(true); 00138 return false; 00139 } 00140 00141 d->tempFileName = tempFile.fileName(); 00142 d->error=QFile::NoError; 00143 d->errorString.clear(); 00144 return true; 00145 } 00146 00147 void KSaveFile::setFileName(const QString &filename) 00148 { 00149 d->realFileName = filename; 00150 00151 // make absolute if needed 00152 if ( QDir::isRelativePath( filename ) ) { 00153 d->realFileName = QDir::current().absoluteFilePath( filename ); 00154 } 00155 00156 // follow symbolic link, if any 00157 d->realFileName = KStandardDirs::realFilePath( d->realFileName ); 00158 00159 return; 00160 } 00161 00162 QFile::FileError KSaveFile::error() const 00163 { 00164 if ( d->error != QFile::NoError ) { 00165 return d->error; 00166 } else { 00167 return QFile::error(); 00168 } 00169 } 00170 00171 QString KSaveFile::errorString() const 00172 { 00173 if ( !d->errorString.isEmpty() ) { 00174 return d->errorString; 00175 } else { 00176 return QFile::errorString(); 00177 } 00178 } 00179 00180 QString KSaveFile::fileName() const 00181 { 00182 return d->realFileName; 00183 } 00184 00185 void KSaveFile::abort() 00186 { 00187 close(); 00188 QFile::remove(d->tempFileName); //non-static QFile::remove() does not work. 00189 d->wasFinalized = true; 00190 } 00191 00192 #ifdef HAVE_FDATASYNC 00193 # define FDATASYNC fdatasync 00194 #else 00195 # define FDATASYNC fsync 00196 #endif 00197 00198 bool KSaveFile::finalize() 00199 { 00200 bool success = false; 00201 00202 if ( !d->wasFinalized ) { 00203 00204 #ifdef Q_OS_UNIX 00205 static int extraSync = -1; 00206 if (extraSync < 0) 00207 extraSync = getenv("KDE_EXTRA_FSYNC") != 0 ? 1 : 0; 00208 if (extraSync) { 00209 if (flush()) { 00210 forever { 00211 if (!FDATASYNC(handle())) 00212 break; 00213 if (errno != EINTR) { 00214 d->error = QFile::WriteError; 00215 d->errorString = i18n("Synchronization to disk failed"); 00216 break; 00217 } 00218 } 00219 } 00220 } 00221 #endif 00222 00223 close(); 00224 00225 if( error() != NoError ) { 00226 QFile::remove(d->tempFileName); 00227 } 00228 //Qt does not allow us to atomically overwrite an existing file, 00229 //so if the target file already exists, there is no way to change it 00230 //to the temp file without creating a small race condition. So we use 00231 //the standard rename call instead, which will do the copy without the 00232 //race condition. 00233 else if (0 == KDE::rename(d->tempFileName,d->realFileName)) { 00234 d->error=QFile::NoError; 00235 d->errorString.clear(); 00236 success = true; 00237 } else { 00238 d->error=QFile::OpenError; 00239 d->errorString=i18n("Error during rename."); 00240 QFile::remove(d->tempFileName); 00241 } 00242 00243 d->wasFinalized = true; 00244 } 00245 00246 return success; 00247 } 00248 00249 #undef FDATASYNC 00250 00251 bool KSaveFile::backupFile( const QString& qFilename, const QString& backupDir ) 00252 { 00253 // get backup type from config, by default use "simple" 00254 // get extension from config, by default use "~" 00255 // get max number of backups from config, by default set to 10 00256 00257 KConfigGroup g(KGlobal::config(), "Backups"); // look in the Backups section 00258 QString type = g.readEntry( "Type", "simple" ); 00259 QString extension = g.readEntry( "Extension", "~" ); 00260 QString message = g.readEntry( "Message", "Automated KDE Commit" ); 00261 int maxnum = g.readEntry( "MaxBackups", 10 ); 00262 if ( type.toLower() == QLatin1String("numbered") ) { 00263 return( numberedBackupFile( qFilename, backupDir, extension, maxnum ) ); 00264 } else if ( type.toLower() == QLatin1String("rcs") ) { 00265 return( rcsBackupFile( qFilename, backupDir, message ) ); 00266 } else { 00267 return( simpleBackupFile( qFilename, backupDir, extension ) ); 00268 } 00269 } 00270 00271 bool KSaveFile::simpleBackupFile( const QString& qFilename, 00272 const QString& backupDir, 00273 const QString& backupExtension ) 00274 { 00275 QString backupFileName = qFilename + backupExtension; 00276 00277 if ( !backupDir.isEmpty() ) { 00278 QFileInfo fileInfo ( qFilename ); 00279 backupFileName = backupDir + QLatin1Char('/') + fileInfo.fileName() + backupExtension; 00280 } 00281 00282 // kDebug(180) << "KSaveFile copying " << qFilename << " to " << backupFileName; 00283 QFile::remove(backupFileName); 00284 return QFile::copy(qFilename, backupFileName); 00285 } 00286 00287 bool KSaveFile::rcsBackupFile( const QString& qFilename, 00288 const QString& backupDir, 00289 const QString& backupMessage ) 00290 { 00291 QFileInfo fileInfo ( qFilename ); 00292 00293 QString qBackupFilename; 00294 if ( backupDir.isEmpty() ) { 00295 qBackupFilename = qFilename; 00296 } else { 00297 qBackupFilename = backupDir + fileInfo.fileName(); 00298 } 00299 qBackupFilename += QString::fromLatin1( ",v" ); 00300 00301 // If backupDir is specified, copy qFilename to the 00302 // backupDir and perform the commit there, unlinking 00303 // backupDir/qFilename when finished. 00304 if ( !backupDir.isEmpty() ) 00305 { 00306 if ( !QFile::copy(qFilename, backupDir + fileInfo.fileName()) ) { 00307 return false; 00308 } 00309 fileInfo.setFile(backupDir + QLatin1Char('/') + fileInfo.fileName()); 00310 } 00311 00312 QString cipath = KStandardDirs::findExe(QString::fromLatin1("ci")); 00313 QString copath = KStandardDirs::findExe(QString::fromLatin1("co")); 00314 QString rcspath = KStandardDirs::findExe(QString::fromLatin1("rcs")); 00315 if ( cipath.isEmpty() || copath.isEmpty() || rcspath.isEmpty() ) 00316 return false; 00317 00318 // Check in the file unlocked with 'ci' 00319 QProcess ci; 00320 if ( !backupDir.isEmpty() ) 00321 ci.setWorkingDirectory( backupDir ); 00322 ci.start( cipath, QStringList() << QString::fromLatin1("-u") << fileInfo.filePath() ); 00323 if ( !ci.waitForStarted() ) 00324 return false; 00325 ci.write( backupMessage.toLatin1() ); 00326 ci.write("."); 00327 ci.closeWriteChannel(); 00328 if( !ci.waitForFinished() ) 00329 return false; 00330 00331 // Use 'rcs' to unset strict locking 00332 QProcess rcs; 00333 if ( !backupDir.isEmpty() ) 00334 rcs.setWorkingDirectory( backupDir ); 00335 rcs.start( rcspath, QStringList() << QString::fromLatin1("-U") << qBackupFilename ); 00336 if ( !rcs.waitForFinished() ) 00337 return false; 00338 00339 // Use 'co' to checkout the current revision and restore permissions 00340 QProcess co; 00341 if ( !backupDir.isEmpty() ) 00342 co.setWorkingDirectory( backupDir ); 00343 co.start( copath, QStringList() << qBackupFilename ); 00344 if ( !co.waitForFinished() ) 00345 return false; 00346 00347 if ( !backupDir.isEmpty() ) { 00348 return QFile::remove( fileInfo.filePath() ); 00349 } else { 00350 return true; 00351 } 00352 } 00353 00354 bool KSaveFile::numberedBackupFile( const QString& qFilename, 00355 const QString& backupDir, 00356 const QString& backupExtension, 00357 const uint maxBackups ) 00358 { 00359 QFileInfo fileInfo ( qFilename ); 00360 00361 // The backup file name template. 00362 QString sTemplate; 00363 if ( backupDir.isEmpty() ) { 00364 sTemplate = qFilename + QLatin1String(".%1") + backupExtension; 00365 } else { 00366 sTemplate = backupDir + QLatin1Char('/') + fileInfo.fileName() + QLatin1String(".%1") + backupExtension; 00367 } 00368 00369 // First, search backupDir for numbered backup files to remove. 00370 // Remove all with number 'maxBackups' and greater. 00371 QDir d = backupDir.isEmpty() ? fileInfo.dir() : backupDir; 00372 d.setFilter( QDir::Files | QDir::Hidden | QDir::NoSymLinks ); 00373 const QStringList nameFilters = QStringList( fileInfo.fileName() + QLatin1String(".*") + backupExtension ); 00374 d.setNameFilters( nameFilters ); 00375 d.setSorting( QDir::Name ); 00376 00377 uint maxBackupFound = 0; 00378 foreach ( const QFileInfo &fi, d.entryInfoList() ) { 00379 if ( fi.fileName().endsWith( backupExtension ) ) { 00380 // sTemp holds the file name, without the ending backupExtension 00381 QString sTemp = fi.fileName(); 00382 sTemp.truncate( fi.fileName().length()-backupExtension.length() ); 00383 // compute the backup number 00384 int idex = sTemp.lastIndexOf( QLatin1Char('.') ); 00385 if ( idex > 0 ) { 00386 bool ok; 00387 uint num = sTemp.mid( idex+1 ).toUInt( &ok ); 00388 if ( ok ) { 00389 if ( num >= maxBackups ) { 00390 QFile::remove( fi.filePath() ); 00391 } else { 00392 maxBackupFound = qMax( maxBackupFound, num ); 00393 } 00394 } 00395 } 00396 } 00397 } 00398 00399 // Next, rename max-1 to max, max-2 to max-1, etc. 00400 QString to=sTemplate.arg( maxBackupFound+1 ); 00401 for ( int i=maxBackupFound; i>0; i-- ) { 00402 QString from = sTemplate.arg( i ); 00403 // kDebug(180) << "KSaveFile renaming " << from << " to " << to; 00404 QFile::rename( from, to ); 00405 to = from; 00406 } 00407 00408 // Finally create most recent backup by copying the file to backup number 1. 00409 // kDebug(180) << "KSaveFile copying " << qFilename << " to " << sTemplate.arg(1); 00410 return QFile::copy(qFilename, sTemplate.arg(1)); 00411 } 00412
KDE 4.6 API Reference