KDELibs4Support

ksavefile.cpp
1 /*
2  This file is part of the KDE libraries
3  Copyright 1999 Waldo Bastian <[email protected]>
4  Copyright 2006 Allen Winter <[email protected]>
5  Copyright 2006 Gregory S. Hayes <[email protected]>
6  Copyright 2006 Jaison Lee <[email protected]>
7 
8  This library is free software; you can redistribute it and/or
9  modify it under the terms of the GNU Library General Public
10  License version 2 as published by the Free Software Foundation.
11 
12  This library is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  Library General Public License for more details.
16 
17  You should have received a copy of the GNU Library General Public License
18  along with this library; see the file COPYING.LIB. If not, write to
19  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  Boston, MA 02110-1301, USA.
21 */
22 
23 #include "ksavefile.h"
24 #include <KLocalizedString>
25 #include <QDir>
26 #include <QTemporaryFile>
27 
28 #include <sys/types.h>
29 #include <sys/stat.h> // umask, fchmod
30 #include <unistd.h> // fchown, fdatasync
31 
32 #include <stdlib.h>
33 #include <errno.h>
34 
35 #include <config-kdelibs4support.h>
36 
37 #ifdef Q_OS_WIN
38 #include <kde_file_win.h>
39 #endif
40 
41 static int s_umask;
42 
43 // Read umask before any threads are created to avoid race conditions
44 static int kStoreUmask()
45 {
46  mode_t tmp = 0;
47  s_umask = umask(tmp);
48  return umask(s_umask);
49 }
50 
51 Q_CONSTRUCTOR_FUNCTION(kStoreUmask)
52 
53 class Q_DECL_HIDDEN KSaveFile::Private
54 {
55 public:
56  QString realFileName; //The name of the end-result file
57  QString tempFileName; //The name of the temp file we are using
58 
60  QString errorString;
61  bool needFinalize;
62  bool directWriteFallback;
63 
64  Private()
65  : error(QFile::NoError),
66  needFinalize(false),
67  directWriteFallback(false)
68  {
69  }
70 };
71 
73  : d(new Private())
74 {
75 }
76 
77 KSaveFile::KSaveFile(const QString &filename)
78  : d(new Private())
79 {
80  KSaveFile::setFileName(filename);
81 }
82 
84 {
85  finalize();
86 
87  delete d;
88 }
89 
91 {
92  if (isOpen()) {
93  return false;
94  }
95  d->needFinalize = false;
96 
97  if (d->realFileName.isEmpty()) {
98  d->error = QFile::OpenError;
99  d->errorString = i18n("No target filename has been given.");
100  return false;
101  }
102 
103  if (!d->tempFileName.isNull()) {
104 #if 0 // do not set an error here, this open() fails, but the file itself is without errors
105  d->error = QFile::OpenError;
106  d->errorString = i18n("Already opened.");
107 #endif
108  return false;
109  }
110 
111  //Create our temporary file
112  QTemporaryFile tempFile;
113  tempFile.setAutoRemove(false);
114  tempFile.setFileTemplate(d->realFileName + QLatin1String("XXXXXX.new"));
115  if (!tempFile.open()) {
116 #ifdef Q_OS_UNIX
117  if (d->directWriteFallback && errno == EACCES) {
118  QFile::setFileName(d->realFileName);
119  if (QFile::open(flags)) {
120  d->tempFileName.clear();
121  d->error = QFile::NoError;
122  d->needFinalize = true;
123  return true;
124  }
125  }
126 #endif
127 
128  // we only check here if the directory can be written to
129  // the actual filename isn't written to, but replaced later
130  // with the contents of our tempfile
131  const QFileInfo fileInfo(d->realFileName);
132  QDir parentDir = fileInfo.dir();
133  if (!QFileInfo(parentDir.absolutePath()).isWritable()) {
134  d->error = QFile::PermissionsError;
135  d->errorString = i18n("Insufficient permissions in target directory.");
136  return false;
137  }
138  d->error = QFile::OpenError;
139  d->errorString = i18n("Unable to open temporary file.");
140  return false;
141  }
142 
143  // if we're overwriting an existing file, ensure temp file's
144  // permissions are the same as existing file so the existing
145  // file's permissions are preserved. this will succeed completely
146  // only if we are the same owner and group - or allmighty root.
147  QFileInfo fi(d->realFileName);
148  if (fi.exists()) {
149  //Qt apparently has no way to change owner/group of file :(
150  if (fchown(tempFile.handle(), fi.ownerId(), fi.groupId())) {
151  // failed to set user and group => try to restore group only.
152  fchown(tempFile.handle(), -1, fi.groupId());
153  }
154 
155  tempFile.setPermissions(fi.permissions());
156  } else {
157  fchmod(tempFile.handle(), 0666 & (~s_umask));
158  }
159 
160  //Open oursleves with the temporary file
161  QFile::setFileName(tempFile.fileName());
162  if (!QFile::open(flags)) {
163  tempFile.setAutoRemove(true);
164  return false;
165  }
166 
167  d->tempFileName = tempFile.fileName();
168  d->error = QFile::NoError;
169  d->errorString.clear();
170  d->needFinalize = true;
171  return true;
172 }
173 
174 void KSaveFile::setFileName(const QString &filename)
175 {
176  d->realFileName = filename;
177 
178  // make absolute if needed
179  if (QDir::isRelativePath(filename)) {
180  d->realFileName = QDir::current().absoluteFilePath(filename);
181  }
182 
183  const QFileInfo fileInfo(d->realFileName);
184  QDir parentDir = fileInfo.dir();
185 
186  // follow symbolic link, if any
187  d->realFileName = parentDir.canonicalPath() + QLatin1Char('/') + fileInfo.fileName();
188 }
189 
191 {
192  if (d->error != QFile::NoError) {
193  return d->error;
194  } else {
195  return QFile::error();
196  }
197 }
198 
200 {
201  if (!d->errorString.isEmpty()) {
202  return d->errorString;
203  } else {
204  return QFile::errorString();
205  }
206 }
207 
209 {
210  return d->realFileName;
211 }
212 
214 {
215  close();
216  if (!d->tempFileName.isEmpty()) {
217  QFile::remove(d->tempFileName); //non-static QFile::remove() does not work.
218  d->needFinalize = false;
219  }
220 }
221 
222 #if HAVE_FDATASYNC
223 # define FDATASYNC fdatasync
224 #else
225 # define FDATASYNC fsync
226 #endif
227 
229 {
230  if (!d->needFinalize) {
231  return false;
232  }
233  bool success = false;
234 #ifdef Q_OS_UNIX
235  static int extraSync = -1;
236  if (extraSync < 0) {
237  extraSync = getenv("KDE_EXTRA_FSYNC") != nullptr ? 1 : 0;
238  }
239  if (extraSync) {
240  if (flush()) {
241  Q_FOREVER {
242  if (!FDATASYNC(handle()))
243  {
244  break;
245  }
246  if (errno != EINTR)
247  {
248  d->error = QFile::WriteError;
249  d->errorString = i18n("Synchronization to disk failed");
250  break;
251  }
252  }
253  }
254  }
255 #endif
256 
257  close();
258 
259  if (!d->tempFileName.isEmpty()) {
260  if (error() != NoError) {
261  QFile::remove(d->tempFileName);
262  }
263  //Qt does not allow us to atomically overwrite an existing file,
264  //so if the target file already exists, there is no way to change it
265  //to the temp file without creating a small race condition. So we use
266  //the standard rename call instead, which will do the copy without the
267  //race condition.
268 #ifdef Q_OS_WIN
269  else if (0 == kdewin32_rename(QFile::encodeName(d->tempFileName).constData(),
270  QFile::encodeName(d->realFileName).constData())) {
271 #else
272  else if (0 == ::rename(QFile::encodeName(d->tempFileName).constData(),
273  QFile::encodeName(d->realFileName).constData())) {
274 #endif
275  d->error = QFile::NoError;
276  d->errorString.clear();
277  success = true;
278  } else {
279  d->error = QFile::OpenError;
280  d->errorString = i18n("Error during rename.");
281  QFile::remove(d->tempFileName);
282  }
283  } else { // direct overwrite
284  success = true;
285  }
286  d->needFinalize = false;
287 
288  return success;
289 }
290 
291 #undef FDATASYNC
292 
294 {
295  d->directWriteFallback = enabled;
296 }
297 
299 {
300  return d->directWriteFallback;
301 }
302 
void setDirectWriteFallback(bool enabled)
Allows writing over the existing file if necessary.
Definition: ksavefile.cpp:293
bool isWritable() const const
virtual ~KSaveFile()
Destructor.
Definition: ksavefile.cpp:83
QFile::FileError error() const
Returns the last error that occurred.
Definition: ksavefile.cpp:190
bool flush()
bool remove()
bool directWriteFallback() const
Returns true if the fallback solution for saving files in read-only directories is enabled...
Definition: ksavefile.cpp:298
QString errorString() const const
QFile::Permissions permissions() const const
QString fileName() const override
Returns the name of the target file.
Definition: ksavefile.cpp:208
QFileDevice::FileError error() const const
bool rename(const QString &newName)
Class to allow for atomic file I/O, as well as utility functions.
Definition: ksavefile.h:98
void setFileName(const QString &name)
typedef OpenMode
int handle() const const
virtual bool setPermissions(QFileDevice::Permissions permissions) override
void abort()
Discard changes without affecting the target file.
Definition: ksavefile.cpp:213
KSaveFile()
Default constructor.
Definition: ksavefile.cpp:72
QString errorString() const
Returns a human-readable description of the last error.
Definition: ksavefile.cpp:199
QString fileName() const const
void setAutoRemove(bool b)
mode_t umask()
Returns the umask of the process.
Definition: kglobal.cpp:134
const char * constData() const const
void setFileTemplate(const QString &name)
void error(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Notify)
bool isOpen() const const
QDir dir() const const
virtual bool open(QIODevice::OpenMode mode) override
bool open(OpenMode flags=QIODevice::ReadWrite) override
Open the save file.
Definition: ksavefile.cpp:90
uint groupId() const const
bool exists() const const
bool finalize()
Finalize changes to the file.
Definition: ksavefile.cpp:228
bool isRelativePath(const QString &path)
virtual QString fileName() const const override
QString i18n(const char *text, const TYPE &arg...)
QString absolutePath() const const
virtual void close() override
void setFileName(const QString &filename)
Set the target filename for the save file.
Definition: ksavefile.cpp:174
uint ownerId() const const
QString absoluteFilePath(const QString &fileName) const const
QString canonicalPath() const const
QDir current()
QByteArray encodeName(const QString &fileName)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Sat Jul 11 2020 22:58:05 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.