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

KDECore

  • sources
  • kde-4.14
  • kdelibs
  • 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 needFinalize;
52  bool directWriteFallback;
53 
54  Private()
55  : error(QFile::NoError),
56  needFinalize(false),
57  directWriteFallback(false)
58  {
59  }
60 };
61 
62 KSaveFile::KSaveFile()
63  : d(new Private())
64 {
65 }
66 
67 class KComponentData;
68 KSaveFile::KSaveFile(const QString &filename, const KComponentData & /*TODO REMOVE*/)
69  : d(new Private())
70 {
71  KSaveFile::setFileName(filename);
72 }
73 
74 KSaveFile::~KSaveFile()
75 {
76  finalize();
77 
78  delete d;
79 }
80 
81 bool KSaveFile::open(OpenMode flags)
82 {
83  if (isOpen()) {
84  return false;
85  }
86  d->needFinalize = false;
87  if ( d->realFileName.isNull() ) {
88  d->error=QFile::OpenError;
89  d->errorString=i18n("No target filename has been given.");
90  return false;
91  }
92 
93  if ( !d->tempFileName.isNull() ) {
94 #if 0 // do not set an error here, this open() fails, but the file itself is without errors
95  d->error=QFile::OpenError;
96  d->errorString=i18n("Already opened.");
97 #endif
98  return false;
99  }
100 
101  //Create our temporary file
102  QTemporaryFile tempFile;
103  tempFile.setAutoRemove(false);
104  tempFile.setFileTemplate(d->realFileName + QLatin1String("XXXXXX.new"));
105  if (!tempFile.open()) {
106 #ifdef Q_OS_UNIX
107  if (d->directWriteFallback && errno == EACCES) {
108  QFile::setFileName(d->realFileName);
109  if (QFile::open(flags)) {
110  d->tempFileName.clear();
111  d->error = QFile::NoError;
112  d->needFinalize = true;
113  return true;
114  }
115  }
116 #endif
117 
118  // we only check here if the directory can be written to
119  // the actual filename isn't written to, but replaced later
120  // with the contents of our tempfile
121  const QFileInfo fileInfo(d->realFileName);
122  QDir parentDir = fileInfo.dir();
123  if (!QFileInfo(parentDir.absolutePath()).isWritable()) {
124  d->error=QFile::PermissionsError;
125  d->errorString=i18n("Insufficient permissions in target directory.");
126  return false;
127  }
128  d->error=QFile::OpenError;
129  d->errorString=i18n("Unable to open temporary file. Error was: %1.", tempFile.error());
130  return false;
131  }
132 
133  // if we're overwriting an existing file, ensure temp file's
134  // permissions are the same as existing file so the existing
135  // file's permissions are preserved. this will succeed completely
136  // only if we are the same owner and group - or allmighty root.
137  QFileInfo fi ( d->realFileName );
138  if (fi.exists()) {
139  //Qt apparently has no way to change owner/group of file :(
140  if (fchown(tempFile.handle(), fi.ownerId(), fi.groupId())) {
141  // failed to set user and group => try to restore group only.
142  fchown(tempFile.handle(), -1, fi.groupId());
143  }
144 
145  tempFile.setPermissions(fi.permissions());
146  }
147  else {
148  mode_t umsk = KGlobal::umask();
149  fchmod(tempFile.handle(), 0666&(~umsk));
150  }
151 
152  //Open oursleves with the temporary file
153  QFile::setFileName(tempFile.fileName());
154  if (!QFile::open(flags)) {
155  tempFile.setAutoRemove(true);
156  return false;
157  }
158 
159  d->tempFileName = tempFile.fileName();
160  d->error=QFile::NoError;
161  d->errorString.clear();
162  d->needFinalize = true;
163  return true;
164 }
165 
166 void KSaveFile::setFileName(const QString &filename)
167 {
168  d->realFileName = filename;
169 
170  // make absolute if needed
171  if ( QDir::isRelativePath( filename ) ) {
172  d->realFileName = QDir::current().absoluteFilePath( filename );
173  }
174 
175  // follow symbolic link, if any
176  d->realFileName = KStandardDirs::realFilePath( d->realFileName );
177 
178  return;
179 }
180 
181 QFile::FileError KSaveFile::error() const
182 {
183  if ( d->error != QFile::NoError ) {
184  return d->error;
185  } else {
186  return QFile::error();
187  }
188 }
189 
190 QString KSaveFile::errorString() const
191 {
192  if ( !d->errorString.isEmpty() ) {
193  return d->errorString;
194  } else {
195  return QFile::errorString();
196  }
197 }
198 
199 QString KSaveFile::fileName() const
200 {
201  return d->realFileName;
202 }
203 
204 void KSaveFile::abort()
205 {
206  close();
207  if (!d->tempFileName.isEmpty()) {
208  QFile::remove(d->tempFileName); //non-static QFile::remove() does not work.
209  d->needFinalize = false;
210  }
211 }
212 
213 #ifdef HAVE_FDATASYNC
214 # define FDATASYNC fdatasync
215 #else
216 # define FDATASYNC fsync
217 #endif
218 
219 bool KSaveFile::finalize()
220 {
221  if (!d->needFinalize) {
222  return false;
223  }
224 
225  bool success = false;
226 #ifdef Q_OS_UNIX
227  static int extraSync = -1;
228  if (extraSync < 0)
229  extraSync = getenv("KDE_EXTRA_FSYNC") != 0 ? 1 : 0;
230  if (extraSync) {
231  if (flush()) {
232  forever {
233  if (!FDATASYNC(handle()))
234  break;
235  if (errno != EINTR) {
236  d->error = QFile::WriteError;
237  d->errorString = i18n("Synchronization to disk failed");
238  break;
239  }
240  }
241  }
242  }
243 #endif
244 
245  close();
246 
247  if (!d->tempFileName.isEmpty()) {
248  if (error() != NoError) {
249  QFile::remove(d->tempFileName);
250  }
251  //Qt does not allow us to atomically overwrite an existing file,
252  //so if the target file already exists, there is no way to change it
253  //to the temp file without creating a small race condition. So we use
254  //the standard rename call instead, which will do the copy without the
255  //race condition.
256  else if (0 == KDE::rename(d->tempFileName,d->realFileName)) {
257  d->error=QFile::NoError;
258  d->errorString.clear();
259  success = true;
260  } else {
261  d->error=QFile::OpenError;
262  d->errorString=i18n("Error during rename.");
263  QFile::remove(d->tempFileName);
264  }
265  } else { // direct overwrite
266  success = true;
267  }
268  d->needFinalize = false;
269 
270  return success;
271 }
272 
273 #undef FDATASYNC
274 
275 void KSaveFile::setDirectWriteFallback(bool enabled)
276 {
277  d->directWriteFallback = enabled;
278 }
279 
280 bool KSaveFile::directWriteFallback() const
281 {
282  return d->directWriteFallback;
283 }
284 
285 bool KSaveFile::backupFile( const QString& qFilename, const QString& backupDir )
286 {
287  // get backup type from config, by default use "simple"
288  // get extension from config, by default use "~"
289  // get max number of backups from config, by default set to 10
290 
291  KConfigGroup g(KGlobal::config(), "Backups"); // look in the Backups section
292  QString type = g.readEntry( "Type", "simple" );
293  QString extension = g.readEntry( "Extension", "~" );
294  QString message = g.readEntry( "Message", "Automated KDE Commit" );
295  int maxnum = g.readEntry( "MaxBackups", 10 );
296  if ( type.toLower() == QLatin1String("numbered") ) {
297  return( numberedBackupFile( qFilename, backupDir, extension, maxnum ) );
298  } else if ( type.toLower() == QLatin1String("rcs") ) {
299  return( rcsBackupFile( qFilename, backupDir, message ) );
300  } else {
301  return( simpleBackupFile( qFilename, backupDir, extension ) );
302  }
303 }
304 
305 bool KSaveFile::simpleBackupFile( const QString& qFilename,
306  const QString& backupDir,
307  const QString& backupExtension )
308 {
309  QString backupFileName = qFilename + backupExtension;
310 
311  if ( !backupDir.isEmpty() ) {
312  QFileInfo fileInfo ( qFilename );
313  backupFileName = backupDir + QLatin1Char('/') + fileInfo.fileName() + backupExtension;
314  }
315 
316 // kDebug(180) << "KSaveFile copying " << qFilename << " to " << backupFileName;
317  QFile::remove(backupFileName);
318  return QFile::copy(qFilename, backupFileName);
319 }
320 
321 bool KSaveFile::rcsBackupFile( const QString& qFilename,
322  const QString& backupDir,
323  const QString& backupMessage )
324 {
325  QFileInfo fileInfo ( qFilename );
326 
327  QString qBackupFilename;
328  if ( backupDir.isEmpty() ) {
329  qBackupFilename = qFilename;
330  } else {
331  qBackupFilename = backupDir + fileInfo.fileName();
332  }
333  qBackupFilename += QString::fromLatin1( ",v" );
334 
335  // If backupDir is specified, copy qFilename to the
336  // backupDir and perform the commit there, unlinking
337  // backupDir/qFilename when finished.
338  if ( !backupDir.isEmpty() )
339  {
340  if ( !QFile::copy(qFilename, backupDir + fileInfo.fileName()) ) {
341  return false;
342  }
343  fileInfo.setFile(backupDir + QLatin1Char('/') + fileInfo.fileName());
344  }
345 
346  QString cipath = KStandardDirs::findExe(QString::fromLatin1("ci"));
347  QString copath = KStandardDirs::findExe(QString::fromLatin1("co"));
348  QString rcspath = KStandardDirs::findExe(QString::fromLatin1("rcs"));
349  if ( cipath.isEmpty() || copath.isEmpty() || rcspath.isEmpty() )
350  return false;
351 
352  // Check in the file unlocked with 'ci'
353  QProcess ci;
354  if ( !backupDir.isEmpty() )
355  ci.setWorkingDirectory( backupDir );
356  ci.start( cipath, QStringList() << QString::fromLatin1("-u") << fileInfo.filePath() );
357  if ( !ci.waitForStarted() )
358  return false;
359  ci.write( backupMessage.toLatin1() );
360  ci.write(".");
361  ci.closeWriteChannel();
362  if( !ci.waitForFinished() )
363  return false;
364 
365  // Use 'rcs' to unset strict locking
366  QProcess rcs;
367  if ( !backupDir.isEmpty() )
368  rcs.setWorkingDirectory( backupDir );
369  rcs.start( rcspath, QStringList() << QString::fromLatin1("-U") << qBackupFilename );
370  if ( !rcs.waitForFinished() )
371  return false;
372 
373  // Use 'co' to checkout the current revision and restore permissions
374  QProcess co;
375  if ( !backupDir.isEmpty() )
376  co.setWorkingDirectory( backupDir );
377  co.start( copath, QStringList() << qBackupFilename );
378  if ( !co.waitForFinished() )
379  return false;
380 
381  if ( !backupDir.isEmpty() ) {
382  return QFile::remove( fileInfo.filePath() );
383  } else {
384  return true;
385  }
386 }
387 
388 bool KSaveFile::numberedBackupFile( const QString& qFilename,
389  const QString& backupDir,
390  const QString& backupExtension,
391  const uint maxBackups )
392 {
393  QFileInfo fileInfo ( qFilename );
394 
395  // The backup file name template.
396  QString sTemplate;
397  if ( backupDir.isEmpty() ) {
398  sTemplate = qFilename + QLatin1String(".%1") + backupExtension;
399  } else {
400  sTemplate = backupDir + QLatin1Char('/') + fileInfo.fileName() + QLatin1String(".%1") + backupExtension;
401  }
402 
403  // First, search backupDir for numbered backup files to remove.
404  // Remove all with number 'maxBackups' and greater.
405  QDir d = backupDir.isEmpty() ? fileInfo.dir() : backupDir;
406  d.setFilter( QDir::Files | QDir::Hidden | QDir::NoSymLinks );
407  const QStringList nameFilters = QStringList( fileInfo.fileName() + QLatin1String(".*") + backupExtension );
408  d.setNameFilters( nameFilters );
409  d.setSorting( QDir::Name );
410 
411  uint maxBackupFound = 0;
412  foreach ( const QFileInfo &fi, d.entryInfoList() ) {
413  if ( fi.fileName().endsWith( backupExtension ) ) {
414  // sTemp holds the file name, without the ending backupExtension
415  QString sTemp = fi.fileName();
416  sTemp.truncate( fi.fileName().length()-backupExtension.length() );
417  // compute the backup number
418  int idex = sTemp.lastIndexOf( QLatin1Char('.') );
419  if ( idex > 0 ) {
420  bool ok;
421  uint num = sTemp.mid( idex+1 ).toUInt( &ok );
422  if ( ok ) {
423  if ( num >= maxBackups ) {
424  QFile::remove( fi.filePath() );
425  } else {
426  maxBackupFound = qMax( maxBackupFound, num );
427  }
428  }
429  }
430  }
431  }
432 
433  // Next, rename max-1 to max, max-2 to max-1, etc.
434  QString to=sTemplate.arg( maxBackupFound+1 );
435  for ( int i=maxBackupFound; i>0; i-- ) {
436  QString from = sTemplate.arg( i );
437 // kDebug(180) << "KSaveFile renaming " << from << " to " << to;
438  QFile::rename( from, to );
439  to = from;
440  }
441 
442  // Finally create most recent backup by copying the file to backup number 1.
443 // kDebug(180) << "KSaveFile copying " << qFilename << " to " << sTemplate.arg(1);
444  return QFile::copy(qFilename, sTemplate.arg(1));
445 }
446 
KMessage::message
void message(KMessage::MessageType messageType, const QString &text, const QString &caption=QString())
Display a long message of a certain type.
Definition: kmessage.cpp:92
i18n
QString i18n(const char *text)
Returns a localized version of a string.
Definition: klocalizedstring.h:630
QFile::flush
bool flush()
KSaveFile::setDirectWriteFallback
void setDirectWriteFallback(bool enabled)
Allows writing over the existing file if necessary.
Definition: ksavefile.cpp:275
QString::truncate
void truncate(int position)
QIODevice::isWritable
bool isWritable() const
KSaveFile::~KSaveFile
virtual ~KSaveFile()
Destructor.
Definition: ksavefile.cpp:74
KSaveFile::error
QFile::FileError error() const
Returns the last error that occurred.
Definition: ksavefile.cpp:181
QDir::setNameFilters
void setNameFilters(const QStringList &nameFilters)
KStandardDirs::realFilePath
static QString realFilePath(const QString &filename)
Expands all symbolic links and resolves references to '/.
Definition: kstandarddirs.cpp:973
QFile::remove
bool remove()
KSaveFile::directWriteFallback
bool directWriteFallback() const
Returns true if the fallback solution for saving files in read-only directories is enabled...
Definition: ksavefile.cpp:280
QIODevice::errorString
QString errorString() const
FDATASYNC
#define FDATASYNC
Definition: ksavefile.cpp:216
QFileInfo::permissions
QFile::Permissions permissions() const
QFileInfo::setFile
void setFile(const QString &file)
QFile::handle
int handle() const
kconfig.h
ksavefile.h
QFile::rename
bool rename(const QString &newName)
KSaveFile::open
virtual bool open(OpenMode flags=QIODevice::ReadWrite)
Open the save file.
Definition: ksavefile.cpp:81
KDE::rename
int rename(const QString &in, const QString &out)
Definition: kde_file_win.cpp:163
QFile::setFileName
void setFileName(const QString &name)
QFile::setPermissions
bool setPermissions(QFlags< QFile::Permission > permissions)
KSaveFile::abort
void abort()
Discard changes without affecting the target file.
Definition: ksavefile.cpp:204
klocale.h
QFile::error
FileError error() const
QDir::setSorting
void setSorting(QFlags< QDir::SortFlag > sort)
KSaveFile::KSaveFile
KSaveFile()
Default constructor.
Definition: ksavefile.cpp:62
QFile
QFile::copy
bool copy(const QString &newName)
KGlobal::config
KSharedConfigPtr config()
Returns the general config object.
Definition: kglobal.cpp:139
QString::lastIndexOf
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const
QFileInfo::filePath
QString filePath() const
KSaveFile::fileName
QString fileName() const
Returns the name of the target file.
Definition: ksavefile.cpp:199
QDir::setFilter
void setFilter(QFlags< QDir::Filter > filters)
KSaveFile::errorString
QString errorString() const
Returns a human-readable description of the last error.
Definition: ksavefile.cpp:190
QFileInfo::fileName
QString fileName() const
QProcess
QTemporaryFile::setAutoRemove
void setAutoRemove(bool b)
KGlobal::umask
mode_t umask()
Returns the umask of the process.
Definition: kglobal.cpp:224
QDir::entryInfoList
QFileInfoList entryInfoList(QFlags< QDir::Filter > filters, QFlags< QDir::SortFlag > sort) const
QString::isEmpty
bool isEmpty() const
QTemporaryFile::setFileTemplate
void setFileTemplate(const QString &name)
QString::endsWith
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const
QIODevice::isOpen
bool isOpen() const
QFileInfo::dir
QDir dir() const
QString
QFile::open
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
QFileInfo::groupId
uint groupId() const
QStringList
QFileInfo
QString::toLower
QString toLower() const
QFileInfo::exists
bool exists() const
QProcess::waitForStarted
bool waitForStarted(int msecs)
KSaveFile::finalize
bool finalize()
Finalize changes to the file.
Definition: ksavefile.cpp:219
QLatin1Char
QProcess::setWorkingDirectory
void setWorkingDirectory(const QString &dir)
QDir::isRelativePath
bool isRelativePath(const QString &path)
QFile::close
virtual void close()
QTemporaryFile::fileName
QString fileName() const
QDir
KConfigGroup
A class for one specific group in a KConfig object.
Definition: kconfiggroup.h:53
QString::toLatin1
QByteArray toLatin1() const
QString::mid
QString mid(int position, int n) const
KSaveFile::rcsBackupFile
static bool rcsBackupFile(const QString &filename, const QString &backupDir=QString(), const QString &backupMessage=QString())
Static method to create an rcs backup file for a given filename.
Definition: ksavefile.cpp:321
QLatin1String
QDir::absolutePath
QString absolutePath() const
kstandarddirs.h
KStandardDirs::findExe
static QString findExe(const QString &appname, const QString &pathstr=QString(), SearchOptions options=NoSearchOptions)
Finds the executable in the system path.
Definition: kstandarddirs.cpp:1328
KSaveFile::setFileName
void setFileName(const QString &filename)
Set the target filename for the save file.
Definition: ksavefile.cpp:166
QFileInfo::ownerId
uint ownerId() const
KShell::NoError
Success.
Definition: kshell.h:89
QDir::absoluteFilePath
QString absoluteFilePath(const QString &fileName) const
QString::length
int length() const
QIODevice::write
qint64 write(const char *data, qint64 maxSize)
QString::fromLatin1
QString fromLatin1(const char *str, int size)
KSaveFile::numberedBackupFile
static bool numberedBackupFile(const QString &filename, const QString &backupDir=QString(), const QString &backupExtension=QString::fromLatin1("~"), const uint maxBackups=10)
Static method to create a backup file for a given filename.
Definition: ksavefile.cpp:388
QTemporaryFile
QDir::current
QDir current()
KSaveFile::backupFile
static bool backupFile(const QString &filename, const QString &backupDir=QString())
Static method to create a backup file before saving.
Definition: ksavefile.cpp:285
QProcess::closeWriteChannel
void closeWriteChannel()
QTemporaryFile::open
bool open()
QString::arg
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
KSaveFile::simpleBackupFile
static bool simpleBackupFile(const QString &filename, const QString &backupDir=QString(), const QString &backupExtension=QLatin1String("~"))
Static method to create a backup file for a given filename.
Definition: ksavefile.cpp:305
KConfigGroup::readEntry
T readEntry(const QString &key, const T &aDefault) const
Reads the value of an entry specified by pKey in the current group.
Definition: kconfiggroup.h:248
KComponentData
Per component data.
Definition: kcomponentdata.h:46
QProcess::start
void start(const QString &program, const QStringList &arguments, QFlags< QIODevice::OpenModeFlag > mode)
kconfiggroup.h
QString::toUInt
uint toUInt(bool *ok, int base) const
QProcess::waitForFinished
bool waitForFinished(int msecs)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Jun 22 2020 13:22:11 by doxygen 1.8.7 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 API Reference

Skip menu "kdelibs 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

Search



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