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 
56 QT_BEGIN_NAMESPACE
57 
QSaveFilePrivate()58 QSaveFilePrivate::QSaveFilePrivate()
59     : writeError(QFileDevice::NoError),
60       useTemporaryFile(true),
61       directWriteFallback(false)
62 {
63 }
64 
~QSaveFilePrivate()65 QSaveFilePrivate::~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
QSaveFile(const QString & name)108 QSaveFile::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 */
QSaveFile(const QString & name)118 QSaveFile::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 */
QSaveFile(QObject * parent)128 QSaveFile::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 */
QSaveFile(const QString & name,QObject * parent)136 QSaveFile::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 */
~QSaveFile()147 QSaveFile::~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 */
fileName() const163 QString 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 */
setFileName(const QString & name)174 void 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 */
open(OpenMode mode)191 bool 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     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(d->fileEngine->error(), d->fileEngine->errorString());
260             d->fileEngine.reset();
261         } else {
262             QString msg =
263                     QSaveFile::tr("QSaveFile cannot open '%1' without direct write fallback enabled.")
264                      .arg(QDir::toNativeSeparators(d->fileName));
265             d->setError(QFileDevice::OpenError, msg);
266         }
267         return false;
268     }
269 
270     d->fileEngine.reset(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(d->finalFileName, 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(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, 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 */
close()306 void QSaveFile::close()
307 {
308     qFatal("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 */
commit()323 bool QSaveFile::commit()
324 {
325     Q_D(QSaveFile);
326     if (!d->fileEngine)
327         return false;
328 
329     if (!isOpen()) {
330         qWarning("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(d->finalFileName)) {
350             d->setError(fe->error(), 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 */
cancelWriting()376 void QSaveFile::cancelWriting()
377 {
378     Q_D(QSaveFile);
379     if (!isOpen())
380         return;
381     d->setError(QFileDevice::WriteError, QSaveFile::tr("Writing canceled by application"));
382     d->writeError = QFileDevice::WriteError;
383 }
384 
385 /*!
386   \reimp
387 */
writeData(const char * data,qint64 len)388 qint64 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 */
setDirectWriteFallback(bool enabled)425 void 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 */
directWriteFallback() const437 bool QSaveFile::directWriteFallback() const
438 {
439     Q_D(const QSaveFile);
440     return d->directWriteFallback;
441 }
442 
443 QT_END_NAMESPACE
444 
445 #ifndef QT_NO_QOBJECT
446 #include "moc_qsavefile.cpp"
447 #endif
448 
449 #endif // QT_NO_TEMPORARYFILE
450