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, nullptr)
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(msg: "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(msg: "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(msg: "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(err: QFileDevice::WriteError, errorString: QSaveFile::tr(s: "Existing file %1 is not writable").arg(a: d->fileName));
215 d->writeError = QFileDevice::WriteError;
216 return false;
217 }
218
219 if (existingFile.isDir()) {
220 d->setError(err: QFileDevice::WriteError, errorString: QSaveFile::tr(s: "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(p: QAbstractFileEngine::create(fileName: d->finalFileName));
238 if (d->fileEngine->open(openMode: mode | QIODevice::Unbuffered)) {
239 d->useTemporaryFile = false;
240 QFileDevice::open(mode);
241 return true;
242 }
243 return false;
244 };
245
246 bool requiresDirectWrite = false;
247#ifdef Q_OS_WIN
248 // check if it is an Alternate Data Stream
249 requiresDirectWrite = d->finalFileName == d->fileName && d->fileName.indexOf(QLatin1Char(':'), 2) > 1;
250#elif defined(Q_OS_ANDROID)
251 // check if it is a content:// URL
252 requiresDirectWrite = d->fileName.startsWith(QLatin1String("content://"));
253#endif
254 if (requiresDirectWrite) {
255 // yes, we can't rename onto it...
256 if (d->directWriteFallback) {
257 if (openDirectly())
258 return true;
259 d->setError(err: d->fileEngine->error(), errorString: d->fileEngine->errorString());
260 d->fileEngine.reset();
261 } else {
262 QString msg =
263 QSaveFile::tr(s: "QSaveFile cannot open '%1' without direct write fallback enabled.")
264 .arg(a: QDir::toNativeSeparators(pathName: d->fileName));
265 d->setError(err: QFileDevice::OpenError, errorString: msg);
266 }
267 return false;
268 }
269
270 d->fileEngine.reset(p: new QTemporaryFileEngine(&d->finalFileName, QTemporaryFileEngine::Win32NonShared));
271 // if the target file exists, we'll copy its permissions below,
272 // but until then, let's ensure the temporary file is not accessible
273 // to a third party
274 int perm = (existingFile.exists() ? 0600 : 0666);
275 static_cast<QTemporaryFileEngine *>(d->fileEngine.get())->initialize(file: d->finalFileName, mode: perm);
276 // Same as in QFile: QIODevice provides the buffering, so there's no need to request it from the file engine.
277 if (!d->fileEngine->open(openMode: mode | QIODevice::Unbuffered)) {
278 QFileDevice::FileError err = d->fileEngine->error();
279#ifdef Q_OS_UNIX
280 if (d->directWriteFallback && err == QFileDevice::OpenError && errno == EACCES) {
281 if (openDirectly())
282 return true;
283 err = d->fileEngine->error();
284 }
285#endif
286 if (err == QFileDevice::UnspecifiedError)
287 err = QFileDevice::OpenError;
288 d->setError(err, errorString: d->fileEngine->errorString());
289 d->fileEngine.reset();
290 return false;
291 }
292
293 d->useTemporaryFile = true;
294 QFileDevice::open(mode);
295 if (existingFile.exists())
296 setPermissions(existingFile.permissions());
297 return true;
298}
299
300/*!
301 \reimp
302 This method has been made private so that it cannot be called, in order to prevent mistakes.
303 In order to finish writing the file, call commit().
304 If instead you want to abort writing, call cancelWriting().
305*/
306void QSaveFile::close()
307{
308 qFatal(msg: "QSaveFile::close called");
309}
310
311/*!
312 Commits the changes to disk, if all previous writes were successful.
313
314 It is mandatory to call this at the end of the saving operation, otherwise the file will be
315 discarded.
316
317 If an error happened during writing, deletes the temporary file and returns \c false.
318 Otherwise, renames it to the final fileName and returns \c true on success.
319 Finally, closes the device.
320
321 \sa cancelWriting()
322*/
323bool QSaveFile::commit()
324{
325 Q_D(QSaveFile);
326 if (!d->fileEngine)
327 return false;
328
329 if (!isOpen()) {
330 qWarning(msg: "QSaveFile::commit: File (%ls) is not open", qUtf16Printable(fileName()));
331 return false;
332 }
333 QFileDevice::close(); // calls flush()
334
335 const auto fe = std::move(d->fileEngine);
336
337 // Sync to disk if possible. Ignore errors (e.g. not supported).
338 fe->syncToDisk();
339
340 if (d->useTemporaryFile) {
341 if (d->writeError != QFileDevice::NoError) {
342 fe->remove();
343 d->writeError = QFileDevice::NoError;
344 return false;
345 }
346 // atomically replace old file with new file
347 // Can't use QFile::rename for that, must use the file engine directly
348 Q_ASSERT(fe);
349 if (!fe->renameOverwrite(newName: d->finalFileName)) {
350 d->setError(err: fe->error(), errorString: fe->errorString());
351 fe->remove();
352 return false;
353 }
354 }
355 return true;
356}
357
358/*!
359 Cancels writing the new file.
360
361 If the application changes its mind while saving, it can call cancelWriting(),
362 which sets an error code so that commit() will discard the temporary file.
363
364 Alternatively, it can simply make sure not to call commit().
365
366 Further write operations are possible after calling this method, but none
367 of it will have any effect, the written file will be discarded.
368
369 This method has no effect when direct write fallback is used. This is the case
370 when saving over an existing file in a readonly directory: no temporary file can
371 be created, so the existing file is overwritten no matter what, and cancelWriting()
372 cannot do anything about that, the contents of the existing file will be lost.
373
374 \sa commit()
375*/
376void QSaveFile::cancelWriting()
377{
378 Q_D(QSaveFile);
379 if (!isOpen())
380 return;
381 d->setError(err: QFileDevice::WriteError, errorString: QSaveFile::tr(s: "Writing canceled by application"));
382 d->writeError = QFileDevice::WriteError;
383}
384
385/*!
386 \reimp
387*/
388qint64 QSaveFile::writeData(const char *data, qint64 len)
389{
390 Q_D(QSaveFile);
391 if (d->writeError != QFileDevice::NoError)
392 return -1;
393
394 const qint64 ret = QFileDevice::writeData(data, len);
395
396 if (d->error != QFileDevice::NoError)
397 d->writeError = d->error;
398 return ret;
399}
400
401/*!
402 Allows writing over the existing file if necessary.
403
404 QSaveFile creates a temporary file in the same directory as the final
405 file and atomically renames it. However this is not possible if the
406 directory permissions do not allow creating new files.
407 In order to preserve atomicity guarantees, open() fails when it
408 cannot create the temporary file.
409
410 In order to allow users to edit files with write permissions in a
411 directory with restricted permissions, call setDirectWriteFallback() with
412 \a enabled set to true, and the following calls to open() will fallback to
413 opening the existing file directly and writing into it, without the use of
414 a temporary file.
415 This does not have atomicity guarantees, i.e. an application crash or
416 for instance a power failure could lead to a partially-written file on disk.
417 It also means cancelWriting() has no effect, in such a case.
418
419 Typically, to save documents edited by the user, call setDirectWriteFallback(true),
420 and to save application internal files (configuration files, data files, ...), keep
421 the default setting which ensures atomicity.
422
423 \sa directWriteFallback()
424*/
425void QSaveFile::setDirectWriteFallback(bool enabled)
426{
427 Q_D(QSaveFile);
428 d->directWriteFallback = enabled;
429}
430
431/*!
432 Returns \c true if the fallback solution for saving files in read-only
433 directories is enabled.
434
435 \sa setDirectWriteFallback()
436*/
437bool QSaveFile::directWriteFallback() const
438{
439 Q_D(const QSaveFile);
440 return d->directWriteFallback;
441}
442
443QT_END_NAMESPACE
444
445#ifndef QT_NO_QOBJECT
446#include "moc_qsavefile.cpp"
447#endif
448
449#endif // QT_NO_TEMPORARYFILE
450

source code of qtbase/src/corelib/io/qsavefile.cpp