• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.14.38 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
43class KSaveFile::Private
44{
45public:
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
62KSaveFile::KSaveFile()
63 : d(new Private())
64{
65}
66
67class KComponentData;
68KSaveFile::KSaveFile(const QString &filename, const KComponentData & /*TODO REMOVE*/)
69 : d(new Private())
70{
71 KSaveFile::setFileName(filename);
72}
73
74KSaveFile::~KSaveFile()
75{
76 finalize();
77
78 delete d;
79}
80
81bool 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
166void 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
181QFile::FileError KSaveFile::error() const
182{
183 if ( d->error != QFile::NoError ) {
184 return d->error;
185 } else {
186 return QFile::error();
187 }
188}
189
190QString KSaveFile::errorString() const
191{
192 if ( !d->errorString.isEmpty() ) {
193 return d->errorString;
194 } else {
195 return QFile::errorString();
196 }
197}
198
199QString KSaveFile::fileName() const
200{
201 return d->realFileName;
202}
203
204void 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
219bool 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
275void KSaveFile::setDirectWriteFallback(bool enabled)
276{
277 d->directWriteFallback = enabled;
278}
279
280bool KSaveFile::directWriteFallback() const
281{
282 return d->directWriteFallback;
283}
284
285bool 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
305bool 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
321bool 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
388bool 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
KComponentData
Per component data.
Definition: kcomponentdata.h:47
KConfigGroup
A class for one specific group in a KConfig object.
Definition: kconfiggroup.h:54
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
KSaveFile::finalize
bool finalize()
Finalize changes to the file.
Definition: ksavefile.cpp:219
KSaveFile::~KSaveFile
virtual ~KSaveFile()
Destructor.
Definition: ksavefile.cpp:74
KSaveFile::KSaveFile
KSaveFile()
Default constructor.
Definition: ksavefile.cpp:62
KSaveFile::fileName
QString fileName() const
Returns the name of the target file.
Definition: ksavefile.cpp:199
KSaveFile::open
virtual bool open(OpenMode flags=QIODevice::ReadWrite)
Open the save file.
Definition: ksavefile.cpp:81
KSaveFile::error
QFile::FileError error() const
Returns the last error that occurred.
Definition: ksavefile.cpp:181
KSaveFile::directWriteFallback
bool directWriteFallback() const
Returns true if the fallback solution for saving files in read-only directories is enabled.
Definition: ksavefile.cpp:280
KSaveFile::errorString
QString errorString() const
Returns a human-readable description of the last error.
Definition: ksavefile.cpp:190
KSaveFile::setDirectWriteFallback
void setDirectWriteFallback(bool enabled)
Allows writing over the existing file if necessary.
Definition: ksavefile.cpp:275
KSaveFile::setFileName
void setFileName(const QString &filename)
Set the target filename for the save file.
Definition: ksavefile.cpp:166
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
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
KSaveFile::abort
void abort()
Discard changes without affecting the target file.
Definition: ksavefile.cpp:204
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
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
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:1334
KStandardDirs::realFilePath
static QString realFilePath(const QString &filename)
Expands all symbolic links and resolves references to '/.
Definition: kstandarddirs.cpp:973
QProcess
QStringList
QString
QTemporaryFile
kconfig.h
kconfiggroup.h
klocale.h
i18n
QString i18n(const char *text)
Returns a localized version of a string.
Definition: klocalizedstring.h:630
FDATASYNC
#define FDATASYNC
Definition: ksavefile.cpp:216
ksavefile.h
kstandarddirs.h
KDE::rename
int rename(const QString &in, const QString &out)
Definition: kde_file_win.cpp:163
KGlobal::umask
mode_t umask()
Returns the umask of the process.
Definition: kglobal.cpp:224
KGlobal::config
KSharedConfigPtr config()
Returns the general config object.
Definition: kglobal.cpp:139
KShell::NoError
@ NoError
Success.
Definition: kshell.h:89
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Feb 20 2023 00:00:00 by doxygen 1.9.6 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.14.38 API Reference

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