Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qsavefile.cpp
Go to the documentation of this file.
1// Copyright (C) 2012 David Faure <faure@kde.org>
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qsavefile.h"
5
6#ifndef QT_NO_TEMPORARYFILE
7
8#include "qplatformdefs.h"
9#include "private/qsavefile_p.h"
10#include "qfileinfo.h"
12#include "qdebug.h"
13#include "qtemporaryfile.h"
14#include "private/qiodevice_p.h"
15#include "private/qtemporaryfile_p.h"
16#ifdef Q_OS_UNIX
17#include <errno.h>
18#endif
19
21
22using namespace Qt::StringLiterals;
23
25 : writeError(QFileDevice::NoError),
26 useTemporaryFile(true),
27 directWriteFallback(false)
28{
29}
30
32{
33}
34
73#ifdef QT_NO_QOBJECT
76{
77 Q_D(QSaveFile);
78 d->fileName = name;
79}
80#else
86{
87 Q_D(QSaveFile);
88 d->fileName = name;
89}
90
96{
97}
104{
105 Q_D(QSaveFile);
106 d->fileName = name;
107}
108#endif
109
114{
115 Q_D(QSaveFile);
117 if (d->fileEngine) {
118 d->fileEngine->remove();
119 d->fileEngine.reset();
120 }
121}
122
130{
131 return d_func()->fileName;
132}
133
141{
142 d_func()->fileName = name;
143}
144
157bool QSaveFile::open(OpenMode mode)
158{
159 Q_D(QSaveFile);
160 if (isOpen()) {
161 qWarning("QSaveFile::open: File (%ls) already open", qUtf16Printable(fileName()));
162 return false;
163 }
164 unsetError();
165 d->writeError = QFileDevice::NoError;
166 if ((mode & (ReadOnly | WriteOnly)) == 0) {
167 qWarning("QSaveFile::open: Open mode not specified");
168 return false;
169 }
170 // In the future we could implement ReadWrite by copying from the existing file to the temp file...
171 // The implications of NewOnly and ExistingOnly when used with QSaveFile need to be considered carefully...
172 if (mode & (ReadOnly | Append | NewOnly | ExistingOnly)) {
173 qWarning("QSaveFile::open: Unsupported open mode 0x%x", uint(mode.toInt()));
174 return false;
175 }
176
177 // check if existing file is writable
178 QFileInfo existingFile(d->fileName);
179 if (existingFile.exists() && !existingFile.isWritable()) {
180 d->setError(QFileDevice::WriteError, QSaveFile::tr("Existing file %1 is not writable").arg(d->fileName));
181 d->writeError = QFileDevice::WriteError;
182 return false;
183 }
184
185 if (existingFile.isDir()) {
186 d->setError(QFileDevice::WriteError, QSaveFile::tr("Filename refers to a directory"));
187 d->writeError = QFileDevice::WriteError;
188 return false;
189 }
190
191 // Resolve symlinks. Don't use QFileInfo::canonicalFilePath so it still give the expected
192 // target even if the file does not exist
193 d->finalFileName = d->fileName;
194 if (existingFile.isSymLink()) {
195 int maxDepth = 128;
196 while (--maxDepth && existingFile.isSymLink())
197 existingFile.setFile(existingFile.symLinkTarget());
198 if (maxDepth > 0)
199 d->finalFileName = existingFile.filePath();
200 }
201
202 auto openDirectly = [&]() {
203 d->fileEngine.reset(QAbstractFileEngine::create(d->finalFileName));
204 if (d->fileEngine->open(mode | QIODevice::Unbuffered)) {
205 d->useTemporaryFile = false;
207 return true;
208 }
209 return false;
210 };
211
212 bool requiresDirectWrite = false;
213#ifdef Q_OS_WIN
214 // check if it is an Alternate Data Stream
215 requiresDirectWrite = d->finalFileName == d->fileName && d->fileName.indexOf(u':', 2) > 1;
216#elif defined(Q_OS_ANDROID)
217 // check if it is a content:// URL
218 requiresDirectWrite = d->fileName.startsWith("content://"_L1);
219#endif
220 if (requiresDirectWrite) {
221 // yes, we can't rename onto it...
222 if (d->directWriteFallback) {
223 if (openDirectly())
224 return true;
225 d->setError(d->fileEngine->error(), d->fileEngine->errorString());
226 d->fileEngine.reset();
227 } else {
228 QString msg =
229 QSaveFile::tr("QSaveFile cannot open '%1' without direct write fallback enabled.")
230 .arg(QDir::toNativeSeparators(d->fileName));
231 d->setError(QFileDevice::OpenError, msg);
232 }
233 return false;
234 }
235
236 d->fileEngine.reset(new QTemporaryFileEngine(&d->finalFileName, QTemporaryFileEngine::Win32NonShared));
237 // if the target file exists, we'll copy its permissions below,
238 // but until then, let's ensure the temporary file is not accessible
239 // to a third party
240 int perm = (existingFile.exists() ? 0600 : 0666);
241 static_cast<QTemporaryFileEngine *>(d->fileEngine.get())->initialize(d->finalFileName, perm);
242 // Same as in QFile: QIODevice provides the buffering, so there's no need to request it from the file engine.
243 if (!d->fileEngine->open(mode | QIODevice::Unbuffered)) {
244 QFileDevice::FileError err = d->fileEngine->error();
245#ifdef Q_OS_UNIX
246 if (d->directWriteFallback && err == QFileDevice::OpenError && errno == EACCES) {
247 if (openDirectly())
248 return true;
249 err = d->fileEngine->error();
250 }
251#endif
254 d->setError(err, d->fileEngine->errorString());
255 d->fileEngine.reset();
256 return false;
257 }
258
259 d->useTemporaryFile = true;
261 if (existingFile.exists())
262 setPermissions(existingFile.permissions());
263 return true;
264}
265
273{
274 qFatal("QSaveFile::close called");
275}
276
290{
291 Q_D(QSaveFile);
292 if (!d->fileEngine)
293 return false;
294
295 if (!isOpen()) {
296 qWarning("QSaveFile::commit: File (%ls) is not open", qUtf16Printable(fileName()));
297 return false;
298 }
299 QFileDevice::close(); // calls flush()
300
301 const auto fe = std::move(d->fileEngine);
302
303 // Sync to disk if possible. Ignore errors (e.g. not supported).
304 fe->syncToDisk();
305
306 if (d->useTemporaryFile) {
307 if (d->writeError != QFileDevice::NoError) {
308 fe->remove();
309 d->writeError = QFileDevice::NoError;
310 return false;
311 }
312 // atomically replace old file with new file
313 // Can't use QFile::rename for that, must use the file engine directly
314 Q_ASSERT(fe);
315 if (!fe->renameOverwrite(d->finalFileName)) {
316 d->setError(fe->error(), fe->errorString());
317 fe->remove();
318 return false;
319 }
320 }
321 return true;
322}
323
343{
344 Q_D(QSaveFile);
345 if (!isOpen())
346 return;
347 d->setError(QFileDevice::WriteError, QSaveFile::tr("Writing canceled by application"));
348 d->writeError = QFileDevice::WriteError;
349}
350
355{
356 Q_D(QSaveFile);
357 if (d->writeError != QFileDevice::NoError)
358 return -1;
359
361
362 if (d->error != QFileDevice::NoError)
363 d->writeError = d->error;
364 return ret;
365}
366
392{
393 Q_D(QSaveFile);
394 d->directWriteFallback = enabled;
395}
396
404{
405 Q_D(const QSaveFile);
406 return d->directWriteFallback;
407}
408
410
411#ifndef QT_NO_QOBJECT
412#include "moc_qsavefile.cpp"
413#endif
414
415#endif // QT_NO_TEMPORARYFILE
static QAbstractFileEngine * create(const QString &fileName)
Creates and returns a QAbstractFileEngine suitable for processing fileName.
static QString toNativeSeparators(const QString &pathName)
Definition qdir.cpp:929
\inmodule QtCore
Definition qfiledevice.h:16
void unsetError()
Sets the file's error to QFileDevice::NoError.
qint64 writeData(const char *data, qint64 len) override
\reimp
FileError
This enum describes the errors that may be returned by the error() function.
Definition qfiledevice.h:23
virtual bool setPermissions(Permissions permissionSpec)
Sets the permissions for the file to the permissions specified.
void close() override
Calls QFileDevice::flush() and closes the file.
\inmodule QtCore \reentrant
Definition qfileinfo.h:22
bool isSymLink() const
Returns true if this object points to a symbolic link, shortcut, or alias; otherwise returns false.
QString symLinkTarget() const
void setFile(const QString &file)
Sets the file that the QFileInfo provides information about to file.
bool isDir() const
Returns true if this object points to a directory or to a symbolic link to a directory.
bool isWritable() const
Returns true if the user can write to the file; otherwise returns false.
QString filePath() const
Returns the file name, including the path (which may be absolute or relative).
bool exists() const
Returns true if the file exists; otherwise returns false.
QFile::Permissions permissions() const
Returns the complete OR-ed together combination of QFile::Permissions for the file.
virtual bool open(QIODeviceBase::OpenMode mode)
Opens the device and sets its OpenMode to mode.
bool isOpen() const
Returns true if the device is open; otherwise returns false.
\inmodule QtCore
Definition qobject.h:90
\inmodule QtCore
Definition qsavefile.h:24
QSaveFile(const QString &name)
Constructs a new file object to represent the file with the given name.
Definition qsavefile.cpp:84
void setDirectWriteFallback(bool enabled)
Allows writing over the existing file if necessary.
~QSaveFile()
Destroys the file object, discarding the saved contents unless commit() was called.
void setFileName(const QString &name)
Sets the name of the file.
QString fileName() const override
Returns the name set by setFileName() or to the QSaveFile constructor.
bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
qint64 writeData(const char *data, qint64 len) override
\reimp
bool commit()
Commits the changes to disk, if all previous writes were successful.
void cancelWriting()
Cancels writing the new file.
bool directWriteFallback() const
Returns true if the fallback solution for saving files in read-only directories is enabled.
void close() override
\reimp This method has been made private so that it cannot be called, in order to prevent mistakes.
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
QString arg(qlonglong a, int fieldwidth=0, int base=10, QChar fillChar=u' ') const
Definition qstring.cpp:8606
Combined button and popup list for selecting options.
static bool initialize()
Definition qctf.cpp:67
#define qWarning
Definition qlogging.h:162
#define qFatal
Definition qlogging.h:164
return ret
GLenum mode
GLenum GLenum GLsizei const GLuint GLboolean enabled
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint name
GLenum GLsizei len
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
SSL_CTX int(*) void arg)
#define qUtf16Printable(string)
Definition qstring.h:1403
@ NoError
Definition main.cpp:34
unsigned int uint
Definition qtypes.h:29
long long qint64
Definition qtypes.h:55
#define enabled
QObject::connect nullptr
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent