1/****************************************************************************
2**
3** Copyright (C) 2012 David Faure <faure@kde.org>
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtCore module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qsavefile.h"
41
42#ifndef QT_NO_TEMPORARYFILE
43
44#include "qplatformdefs.h"
45#include "private/qsavefile_p.h"
46#include "qfileinfo.h"
47#include "qabstractfileengine_p.h"
48#include "qdebug.h"
49#include "qtemporaryfile.h"
50#include "private/qiodevice_p.h"
51#include "private/qtemporaryfile_p.h"
52#ifdef Q_OS_UNIX
53#include <errno.h>
54#endif
55
56QT_BEGIN_NAMESPACE
57
58QSaveFilePrivate::QSaveFilePrivate()
59 : writeError(QFileDevice::NoError),
60 useTemporaryFile(true),
61 directWriteFallback(false)
62{
63}
64
65QSaveFilePrivate::~QSaveFilePrivate()
66{
67}
68
69/*!
70 \class QSaveFile
71 \inmodule QtCore
72 \brief The QSaveFile class provides an interface for safely writing to files.
73
74 \ingroup io
75
76 \reentrant
77
78 \since 5.1
79
80 QSaveFile is an I/O device for writing text and binary files, without losing
81 existing data if the writing operation fails.
82
83 While writing, the contents will be written to a temporary file, and if
84 no error happened, commit() will move it to the final file. This ensures that
85 no data at the final file is lost in case an error happens while writing,
86 and no partially-written file is ever present at the final location. Always
87 use QSaveFile when saving entire documents to disk.
88
89 QSaveFile automatically detects errors while writing, such as the full partition
90 situation, where write() cannot write all the bytes. It will remember that
91 an error happened, and will discard the temporary file in commit().
92
93 Much like with QFile, the file is opened with open(). Data is usually read
94 and written using QDataStream or QTextStream, but you can also call the
95 QIODevice-inherited functions read(), readLine(), readAll(), write().
96
97 Unlike QFile, calling close() is not allowed. commit() replaces it. If commit()
98 was not called and the QSaveFile instance is destroyed, the temporary file is
99 discarded.
100
101 To abort saving due to an application error, call cancelWriting(), so that
102 even a call to commit() later on will not save.
103
104 \sa QTextStream, QDataStream, QFileInfo, QDir, QFile, QTemporaryFile
105*/
106
107#ifdef QT_NO_QOBJECT
108QSaveFile::QSaveFile(const QString &name)
109 : QFileDevice(*new QSaveFilePrivate)
110{
111 Q_D(QSaveFile);
112 d->fileName = name;
113}
114#else
115/*!
116 Constructs a new file object to represent the file with the given \a name.
117*/
118QSaveFile::QSaveFile(const QString &name)
119 : QFileDevice(*new QSaveFilePrivate, 0)
120{
121 Q_D(QSaveFile);
122 d->fileName = name;
123}
124
125/*!
126 Constructs a new file object with the given \a parent.
127*/
128QSaveFile::QSaveFile(QObject *parent)
129 : QFileDevice(*new QSaveFilePrivate, parent)
130{
131}
132/*!
133 Constructs a new file object with the given \a parent to represent the
134 file with the specified \a name.
135*/
136QSaveFile::QSaveFile(const QString &name, QObject *parent)
137 : QFileDevice(*new QSaveFilePrivate, parent)
138{
139 Q_D(QSaveFile);
140 d->fileName = name;
141}
142#endif
143
144/*!
145 Destroys the file object, discarding the saved contents unless commit() was called.
146*/
147QSaveFile::~QSaveFile()
148{
149 Q_D(QSaveFile);
150 QFileDevice::close();
151 if (d->fileEngine) {
152 d->fileEngine->remove();
153 d->fileEngine.reset();
154 }
155}
156
157/*!
158 Returns the name set by setFileName() or to the QSaveFile
159 constructor.
160
161 \sa setFileName()
162*/
163QString QSaveFile::fileName() const
164{
165 return d_func()->fileName;
166}
167
168/*!
169 Sets the \a name of the file. The name can have no path, a
170 relative path, or an absolute path.
171
172 \sa QFile::setFileName(), fileName()
173*/
174void QSaveFile::setFileName(const QString &name)
175{
176 d_func()->fileName = name;
177}
178
179/*!
180 Opens the file using OpenMode \a mode, returning true if successful;
181 otherwise false.
182
183 Important: the \a mode must include QIODevice::WriteOnly.
184 It may also have additional flags, such as QIODevice::Text and QIODevice::Unbuffered.
185
186 QIODevice::ReadWrite, QIODevice::Append, QIODevice::NewOnly and
187 QIODevice::ExistingOnly are not supported at the moment.
188
189 \sa QIODevice::OpenMode, setFileName()
190*/
191bool QSaveFile::open(OpenMode mode)
192{
193 Q_D(QSaveFile);
194 if (isOpen()) {
195 qWarning("QSaveFile::open: File (%ls) already open", qUtf16Printable(fileName()));
196 return false;
197 }
198 unsetError();
199 d->writeError = QFileDevice::NoError;
200 if ((mode & (ReadOnly | WriteOnly)) == 0) {
201 qWarning("QSaveFile::open: Open mode not specified");
202 return false;
203 }
204 // In the future we could implement ReadWrite by copying from the existing file to the temp file...
205 // The implications of NewOnly and ExistingOnly when used with QSaveFile need to be considered carefully...
206 if (mode & (ReadOnly | Append | NewOnly | ExistingOnly)) {
207 qWarning("QSaveFile::open: Unsupported open mode 0x%x", int(mode));
208 return false;
209 }
210
211 // check if existing file is writable
212 QFileInfo existingFile(d->fileName);
213 if (existingFile.exists() && !existingFile.isWritable()) {
214 d->setError(QFileDevice::WriteError, QSaveFile::tr("Existing file %1 is not writable").arg(d->fileName));
215 d->writeError = QFileDevice::WriteError;
216 return false;
217 }
218
219 if (existingFile.isDir()) {
220 d->setError(QFileDevice::WriteError, QSaveFile::tr("Filename refers to a directory"));
221 d->writeError = QFileDevice::WriteError;
222 return false;
223 }
224
225 // Resolve symlinks. Don't use QFileInfo::canonicalFilePath so it still give the expected
226 // target even if the file does not exist
227 d->finalFileName = d->fileName;
228 if (existingFile.isSymLink()) {
229 int maxDepth = 128;
230 while (--maxDepth && existingFile.isSymLink())
231 existingFile.setFile(existingFile.symLinkTarget());
232 if (maxDepth > 0)
233 d->finalFileName = existingFile.filePath();
234 }
235
236 auto openDirectly = [&]() {
237 d->fileEngine.reset(QAbstractFileEngine::create(d->finalFileName));
238 if (d->fileEngine->open(mode | QIODevice::Unbuffered)) {
239 d->useTemporaryFile = false;
240 QFileDevice::open(mode);
241 return true;
242 }
243 return false;
244 };
245
246#ifdef Q_OS_WIN
247 // check if it is an Alternate Data Stream
248 if (d->finalFileName == d->fileName && d->fileName.indexOf(QLatin1Char(':'), 2) > 1) {
249 // yes, we can't rename onto it...
250 if (d->directWriteFallback) {
251 if (openDirectly())
252 return true;
253 d->setError(d->fileEngine->error(), d->fileEngine->errorString());
254 d->fileEngine.reset();
255 } else {
256 QString msg =
257 QSaveFile::tr("QSaveFile cannot open '%1' without direct write fallback "
258 "enabled: path contains an Alternate Data Stream specifier")
259 .arg(QDir::toNativeSeparators(d->fileName));
260 d->setError(QFileDevice::OpenError, msg);
261 }
262 return false;
263 }
264#endif
265
266 d->fileEngine.reset(new QTemporaryFileEngine(&d->finalFileName, QTemporaryFileEngine::Win32NonShared));
267 // if the target file exists, we'll copy its permissions below,
268 // but until then, let's ensure the temporary file is not accessible
269 // to a third party
270 int perm = (existingFile.exists() ? 0600 : 0666);
271 static_cast<QTemporaryFileEngine *>(d->fileEngine.get())->initialize(d->finalFileName, perm);
272 // Same as in QFile: QIODevice provides the buffering, so there's no need to request it from the file engine.
273 if (!d->fileEngine->open(mode | QIODevice::Unbuffered)) {
274 QFileDevice::FileError err = d->fileEngine->error();
275#ifdef Q_OS_UNIX
276 if (d->directWriteFallback && err == QFileDevice::OpenError && errno == EACCES) {
277 if (openDirectly())
278 return true;
279 err = d->fileEngine->error();
280 }
281#endif
282 if (err == QFileDevice::UnspecifiedError)
283 err = QFileDevice::OpenError;
284 d->setError(err, d->fileEngine->errorString());
285 d->fileEngine.reset();
286 return false;
287 }
288
289 d->useTemporaryFile = true;
290 QFileDevice::open(mode);
291 if (existingFile.exists())
292 setPermissions(existingFile.permissions());
293 return true;
294}
295
296/*!
297 \reimp
298 This method has been made private so that it cannot be called, in order to prevent mistakes.
299 In order to finish writing the file, call commit().
300 If instead you want to abort writing, call cancelWriting().
301*/
302void QSaveFile::close()
303{
304 qFatal("QSaveFile::close called");
305}
306
307/*!
308 Commits the changes to disk, if all previous writes were successful.
309
310 It is mandatory to call this at the end of the saving operation, otherwise the file will be
311 discarded.
312
313 If an error happened during writing, deletes the temporary file and returns \c false.
314 Otherwise, renames it to the final fileName and returns \c true on success.
315 Finally, closes the device.
316
317 \sa cancelWriting()
318*/
319bool QSaveFile::commit()
320{
321 Q_D(QSaveFile);
322 if (!d->fileEngine)
323 return false;
324
325 if (!isOpen()) {
326 qWarning("QSaveFile::commit: File (%ls) is not open", qUtf16Printable(fileName()));
327 return false;
328 }
329 QFileDevice::close(); // calls flush()
330
331 const auto fe = std::move(d->fileEngine);
332
333 // Sync to disk if possible. Ignore errors (e.g. not supported).
334 fe->syncToDisk();
335
336 if (d->useTemporaryFile) {
337 if (d->writeError != QFileDevice::NoError) {
338 fe->remove();
339 d->writeError = QFileDevice::NoError;
340 return false;
341 }
342 // atomically replace old file with new file
343 // Can't use QFile::rename for that, must use the file engine directly
344 Q_ASSERT(fe);
345 if (!fe->renameOverwrite(d->finalFileName)) {
346 d->setError(fe->error(), fe->errorString());
347 fe->remove();
348 return false;
349 }
350 }
351 return true;
352}
353
354/*!
355 Cancels writing the new file.
356
357 If the application changes its mind while saving, it can call cancelWriting(),
358 which sets an error code so that commit() will discard the temporary file.
359
360 Alternatively, it can simply make sure not to call commit().
361
362 Further write operations are possible after calling this method, but none
363 of it will have any effect, the written file will be discarded.
364
365 This method has no effect when direct write fallback is used. This is the case
366 when saving over an existing file in a readonly directory: no temporary file can
367 be created, so the existing file is overwritten no matter what, and cancelWriting()
368 cannot do anything about that, the contents of the existing file will be lost.
369
370 \sa commit()
371*/
372void QSaveFile::cancelWriting()
373{
374 Q_D(QSaveFile);
375 if (!isOpen())
376 return;
377 d->setError(QFileDevice::WriteError, QSaveFile::tr("Writing canceled by application"));
378 d->writeError = QFileDevice::WriteError;
379}
380
381/*!
382 \reimp
383*/
384qint64 QSaveFile::writeData(const char *data, qint64 len)
385{
386 Q_D(QSaveFile);
387 if (d->writeError != QFileDevice::NoError)
388 return -1;
389
390 const qint64 ret = QFileDevice::writeData(data, len);
391
392 if (d->error != QFileDevice::NoError)
393 d->writeError = d->error;
394 return ret;
395}
396
397/*!
398 Allows writing over the existing file if necessary.
399
400 QSaveFile creates a temporary file in the same directory as the final
401 file and atomically renames it. However this is not possible if the
402 directory permissions do not allow creating new files.
403 In order to preserve atomicity guarantees, open() fails when it
404 cannot create the temporary file.
405
406 In order to allow users to edit files with write permissions in a
407 directory with restricted permissions, call setDirectWriteFallback() with
408 \a enabled set to true, and the following calls to open() will fallback to
409 opening the existing file directly and writing into it, without the use of
410 a temporary file.
411 This does not have atomicity guarantees, i.e. an application crash or
412 for instance a power failure could lead to a partially-written file on disk.
413 It also means cancelWriting() has no effect, in such a case.
414
415 Typically, to save documents edited by the user, call setDirectWriteFallback(true),
416 and to save application internal files (configuration files, data files, ...), keep
417 the default setting which ensures atomicity.
418
419 \sa directWriteFallback()
420*/
421void QSaveFile::setDirectWriteFallback(bool enabled)
422{
423 Q_D(QSaveFile);
424 d->directWriteFallback = enabled;
425}
426
427/*!
428 Returns \c true if the fallback solution for saving files in read-only
429 directories is enabled.
430
431 \sa setDirectWriteFallback()
432*/
433bool QSaveFile::directWriteFallback() const
434{
435 Q_D(const QSaveFile);
436 return d->directWriteFallback;
437}
438
439QT_END_NAMESPACE
440
441#ifndef QT_NO_QOBJECT
442#include "moc_qsavefile.cpp"
443#endif
444
445#endif // QT_NO_TEMPORARYFILE
446