• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.10.2 API Reference
  • KDE Home
  • Contact Us
 

KDECore

  • kdecore
  • io
ksavefile.cpp
Go to the documentation of this file.
1 /* kate: tab-indents off; replace-tabs on; tab-width 4; remove-trailing-space on; encoding utf-8;*/
2 /*
3  This file is part of the KDE libraries
4  Copyright 1999 Waldo Bastian <bastian@kde.org>
5  Copyright 2006 Allen Winter <winter@kde.org>
6  Copyright 2006 Gregory S. Hayes <syncomm@kde.org>
7  Copyright 2006 Jaison Lee <lee.jaison@gmail.com>
8 
9  This library is free software; you can redistribute it and/or
10  modify it under the terms of the GNU Library General Public
11  License version 2 as published by the Free Software Foundation.
12 
13  This library is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  Library General Public License for more details.
17 
18  You should have received a copy of the GNU Library General Public License
19  along with this library; see the file COPYING.LIB. If not, write to
20  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  Boston, MA 02110-1301, USA.
22 */
23 
24 #include "ksavefile.h"
25 
26 #include <config.h>
27 
28 #include <QDir>
29 #include <QProcess>
30 #include <QTemporaryFile>
31 
32 #include <kde_file.h>
33 #include <klocale.h>
34 #include <kstandarddirs.h>
35 
36 // Only used by the backup file feature:
37 #include <kconfig.h>
38 #include <kconfiggroup.h>
39 
40 #include <stdlib.h>
41 #include <errno.h>
42 
43 class KSaveFile::Private
44 {
45 public:
46  QString realFileName; //The name of the end-result file
47  QString tempFileName; //The name of the temp file we are using
48 
49  QFile::FileError error;
50  QString errorString;
51  bool wasFinalized;
52 
53  Private()
54  {
55  error = QFile::NoError;
56  wasFinalized = false;
57  }
58 };
59 
60 KSaveFile::KSaveFile()
61  : d(new Private())
62 {
63 }
64 
65 class KComponentData;
66 KSaveFile::KSaveFile(const QString &filename, const KComponentData & /*TODO REMOVE*/)
67  : d(new Private())
68 {
69  KSaveFile::setFileName(filename);
70 }
71 
72 KSaveFile::~KSaveFile()
73 {
74  if (!d->wasFinalized)
75  finalize();
76 
77  delete d;
78 }
79 
80 bool KSaveFile::open(OpenMode flags)
81 {
82  if ( d->realFileName.isNull() ) {
83  d->error=QFile::OpenError;
84  d->errorString=i18n("No target filename has been given.");
85  return false;
86  }
87 
88  if ( !d->tempFileName.isNull() ) {
89 #if 0 // do not set an error here, this open() fails, but the file itself is without errors
90  d->error=QFile::OpenError;
91  d->errorString=i18n("Already opened.");
92 #endif
93  return false;
94  }
95 
96  // we only check here if the directory can be written to
97  // the actual filename isn't written to, but replaced later
98  // with the contents of our tempfile
99  if (!KStandardDirs::checkAccess(d->realFileName, W_OK)) {
100  d->error=QFile::PermissionsError;
101  d->errorString=i18n("Insufficient permissions in target directory.");
102  return false;
103  }
104 
105  //Create our temporary file
106  QTemporaryFile tempFile;
107  tempFile.setAutoRemove(false);
108  tempFile.setFileTemplate(d->realFileName + QLatin1String("XXXXXX.new"));
109  if (!tempFile.open()) {
110  d->error=QFile::OpenError;
111  d->errorString=i18n("Unable to open temporary file.");
112  return false;
113  }
114 
115  // if we're overwriting an existing file, ensure temp file's
116  // permissions are the same as existing file so the existing
117  // file's permissions are preserved. this will succeed completely
118  // only if we are the same owner and group - or allmighty root.
119  QFileInfo fi ( d->realFileName );
120  if (fi.exists()) {
121  //Qt apparently has no way to change owner/group of file :(
122  if (fchown(tempFile.handle(), fi.ownerId(), fi.groupId())) {
123  // failed to set user and group => try to restore group only.
124  fchown(tempFile.handle(), -1, fi.groupId());
125  }
126 
127  tempFile.setPermissions(fi.permissions());
128  }
129  else {
130  mode_t umsk = KGlobal::umask();
131  fchmod(tempFile.handle(), 0666&(~umsk));
132  }
133 
134  //Open oursleves with the temporary file
135  QFile::setFileName(tempFile.fileName());
136  if (!QFile::open(flags)) {
137  tempFile.setAutoRemove(true);
138  return false;
139  }
140 
141  d->tempFileName = tempFile.fileName();
142  d->error=QFile::NoError;
143  d->errorString.clear();
144  return true;
145 }
146 
147 void KSaveFile::setFileName(const QString &filename)
148 {
149  d->realFileName = filename;
150 
151  // make absolute if needed
152  if ( QDir::isRelativePath( filename ) ) {
153  d->realFileName = QDir::current().absoluteFilePath( filename );
154  }
155 
156  // follow symbolic link, if any
157  d->realFileName = KStandardDirs::realFilePath( d->realFileName );
158 
159  return;
160 }
161 
162 QFile::FileError KSaveFile::error() const
163 {
164  if ( d->error != QFile::NoError ) {
165  return d->error;
166  } else {
167  return QFile::error();
168  }
169 }
170 
171 QString KSaveFile::errorString() const
172 {
173  if ( !d->errorString.isEmpty() ) {
174  return d->errorString;
175  } else {
176  return QFile::errorString();
177  }
178 }
179 
180 QString KSaveFile::fileName() const
181 {
182  return d->realFileName;
183 }
184 
185 void KSaveFile::abort()
186 {
187  close();
188  QFile::remove(d->tempFileName); //non-static QFile::remove() does not work.
189  d->wasFinalized = true;
190 }
191 
192 #ifdef HAVE_FDATASYNC
193 # define FDATASYNC fdatasync
194 #else
195 # define FDATASYNC fsync
196 #endif
197 
198 bool KSaveFile::finalize()
199 {
200  bool success = false;
201 
202  if ( !d->wasFinalized ) {
203 
204 #ifdef Q_OS_UNIX
205  static int extraSync = -1;
206  if (extraSync < 0)
207  extraSync = getenv("KDE_EXTRA_FSYNC") != 0 ? 1 : 0;
208  if (extraSync) {
209  if (flush()) {
210  forever {
211  if (!FDATASYNC(handle()))
212  break;
213  if (errno != EINTR) {
214  d->error = QFile::WriteError;
215  d->errorString = i18n("Synchronization to disk failed");
216  break;
217  }
218  }
219  }
220  }
221 #endif
222 
223  close();
224 
225  if( error() != NoError ) {
226  QFile::remove(d->tempFileName);
227  }
228  //Qt does not allow us to atomically overwrite an existing file,
229  //so if the target file already exists, there is no way to change it
230  //to the temp file without creating a small race condition. So we use
231  //the standard rename call instead, which will do the copy without the
232  //race condition.
233  else if (0 == KDE::rename(d->tempFileName,d->realFileName)) {
234  d->error=QFile::NoError;
235  d->errorString.clear();
236  success = true;
237  } else {
238  d->error=QFile::OpenError;
239  d->errorString=i18n("Error during rename.");
240  QFile::remove(d->tempFileName);
241  }
242 
243  d->wasFinalized = true;
244  }
245 
246  return success;
247 }
248 
249 #undef FDATASYNC
250 
251 bool KSaveFile::backupFile( const QString& qFilename, const QString& backupDir )
252 {
253  // get backup type from config, by default use "simple"
254  // get extension from config, by default use "~"
255  // get max number of backups from config, by default set to 10
256 
257  KConfigGroup g(KGlobal::config(), "Backups"); // look in the Backups section
258  QString type = g.readEntry( "Type", "simple" );
259  QString extension = g.readEntry( "Extension", "~" );
260  QString message = g.readEntry( "Message", "Automated KDE Commit" );
261  int maxnum = g.readEntry( "MaxBackups", 10 );
262  if ( type.toLower() == QLatin1String("numbered") ) {
263  return( numberedBackupFile( qFilename, backupDir, extension, maxnum ) );
264  } else if ( type.toLower() == QLatin1String("rcs") ) {
265  return( rcsBackupFile( qFilename, backupDir, message ) );
266  } else {
267  return( simpleBackupFile( qFilename, backupDir, extension ) );
268  }
269 }
270 
271 bool KSaveFile::simpleBackupFile( const QString& qFilename,
272  const QString& backupDir,
273  const QString& backupExtension )
274 {
275  QString backupFileName = qFilename + backupExtension;
276 
277  if ( !backupDir.isEmpty() ) {
278  QFileInfo fileInfo ( qFilename );
279  backupFileName = backupDir + QLatin1Char('/') + fileInfo.fileName() + backupExtension;
280  }
281 
282 // kDebug(180) << "KSaveFile copying " << qFilename << " to " << backupFileName;
283  QFile::remove(backupFileName);
284  return QFile::copy(qFilename, backupFileName);
285 }
286 
287 bool KSaveFile::rcsBackupFile( const QString& qFilename,
288  const QString& backupDir,
289  const QString& backupMessage )
290 {
291  QFileInfo fileInfo ( qFilename );
292 
293  QString qBackupFilename;
294  if ( backupDir.isEmpty() ) {
295  qBackupFilename = qFilename;
296  } else {
297  qBackupFilename = backupDir + fileInfo.fileName();
298  }
299  qBackupFilename += QString::fromLatin1( ",v" );
300 
301  // If backupDir is specified, copy qFilename to the
302  // backupDir and perform the commit there, unlinking
303  // backupDir/qFilename when finished.
304  if ( !backupDir.isEmpty() )
305  {
306  if ( !QFile::copy(qFilename, backupDir + fileInfo.fileName()) ) {
307  return false;
308  }
309  fileInfo.setFile(backupDir + QLatin1Char('/') + fileInfo.fileName());
310  }
311 
312  QString cipath = KStandardDirs::findExe(QString::fromLatin1("ci"));
313  QString copath = KStandardDirs::findExe(QString::fromLatin1("co"));
314  QString rcspath = KStandardDirs::findExe(QString::fromLatin1("rcs"));
315  if ( cipath.isEmpty() || copath.isEmpty() || rcspath.isEmpty() )
316  return false;
317 
318  // Check in the file unlocked with 'ci'
319  QProcess ci;
320  if ( !backupDir.isEmpty() )
321  ci.setWorkingDirectory( backupDir );
322  ci.start( cipath, QStringList() << QString::fromLatin1("-u") << fileInfo.filePath() );
323  if ( !ci.waitForStarted() )
324  return false;
325  ci.write( backupMessage.toLatin1() );
326  ci.write(".");
327  ci.closeWriteChannel();
328  if( !ci.waitForFinished() )
329  return false;
330 
331  // Use 'rcs' to unset strict locking
332  QProcess rcs;
333  if ( !backupDir.isEmpty() )
334  rcs.setWorkingDirectory( backupDir );
335  rcs.start( rcspath, QStringList() << QString::fromLatin1("-U") << qBackupFilename );
336  if ( !rcs.waitForFinished() )
337  return false;
338 
339  // Use 'co' to checkout the current revision and restore permissions
340  QProcess co;
341  if ( !backupDir.isEmpty() )
342  co.setWorkingDirectory( backupDir );
343  co.start( copath, QStringList() << qBackupFilename );
344  if ( !co.waitForFinished() )
345  return false;
346 
347  if ( !backupDir.isEmpty() ) {
348  return QFile::remove( fileInfo.filePath() );
349  } else {
350  return true;
351  }
352 }
353 
354 bool KSaveFile::numberedBackupFile( const QString& qFilename,
355  const QString& backupDir,
356  const QString& backupExtension,
357  const uint maxBackups )
358 {
359  QFileInfo fileInfo ( qFilename );
360 
361  // The backup file name template.
362  QString sTemplate;
363  if ( backupDir.isEmpty() ) {
364  sTemplate = qFilename + QLatin1String(".%1") + backupExtension;
365  } else {
366  sTemplate = backupDir + QLatin1Char('/') + fileInfo.fileName() + QLatin1String(".%1") + backupExtension;
367  }
368 
369  // First, search backupDir for numbered backup files to remove.
370  // Remove all with number 'maxBackups' and greater.
371  QDir d = backupDir.isEmpty() ? fileInfo.dir() : backupDir;
372  d.setFilter( QDir::Files | QDir::Hidden | QDir::NoSymLinks );
373  const QStringList nameFilters = QStringList( fileInfo.fileName() + QLatin1String(".*") + backupExtension );
374  d.setNameFilters( nameFilters );
375  d.setSorting( QDir::Name );
376 
377  uint maxBackupFound = 0;
378  foreach ( const QFileInfo &fi, d.entryInfoList() ) {
379  if ( fi.fileName().endsWith( backupExtension ) ) {
380  // sTemp holds the file name, without the ending backupExtension
381  QString sTemp = fi.fileName();
382  sTemp.truncate( fi.fileName().length()-backupExtension.length() );
383  // compute the backup number
384  int idex = sTemp.lastIndexOf( QLatin1Char('.') );
385  if ( idex > 0 ) {
386  bool ok;
387  uint num = sTemp.mid( idex+1 ).toUInt( &ok );
388  if ( ok ) {
389  if ( num >= maxBackups ) {
390  QFile::remove( fi.filePath() );
391  } else {
392  maxBackupFound = qMax( maxBackupFound, num );
393  }
394  }
395  }
396  }
397  }
398 
399  // Next, rename max-1 to max, max-2 to max-1, etc.
400  QString to=sTemplate.arg( maxBackupFound+1 );
401  for ( int i=maxBackupFound; i>0; i-- ) {
402  QString from = sTemplate.arg( i );
403 // kDebug(180) << "KSaveFile renaming " << from << " to " << to;
404  QFile::rename( from, to );
405  to = from;
406  }
407 
408  // Finally create most recent backup by copying the file to backup number 1.
409 // kDebug(180) << "KSaveFile copying " << qFilename << " to " << sTemplate.arg(1);
410  return QFile::copy(qFilename, sTemplate.arg(1));
411 }
412 
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Tue Apr 16 2013 20:55:42 by doxygen 1.8.3.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KDECore

Skip menu "KDECore"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Modules
  • Related Pages

kdelibs-4.10.2 API Reference

Skip menu "kdelibs-4.10.2 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal