Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qtemporaryfile.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2017 Intel Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qtemporaryfile.h"
6
7#include "qplatformdefs.h"
8#include "qrandom.h"
9#include "private/qtemporaryfile_p.h"
10#include "private/qfile_p.h"
11#include "private/qsystemerror_p.h"
12
13#if !defined(Q_OS_WIN)
14#include "private/qcore_unix_p.h" // overrides QT_OPEN
15#include <errno.h>
16#endif
17
18#if defined(QT_BUILD_CORE_LIB)
19#include "qcoreapplication.h"
20#else
21#define tr(X) QString::fromLatin1(X)
22#endif
23
25
26using namespace Qt::StringLiterals;
27
28#if defined(Q_OS_WIN)
29typedef ushort Char;
30
31static inline Char Latin1Char(char ch)
32{
33 return ushort(uchar(ch));
34}
35
37
38#else // POSIX
39typedef char Char;
40typedef char Latin1Char;
41typedef int NativeFileHandle;
42#endif
43
45{
46 // Ensure there is a placeholder mask
47 QString qfilename = QDir::fromNativeSeparators(templateName);
48 qsizetype phPos = qfilename.size();
49 qsizetype phLength = 0;
50
51 while (phPos != 0) {
52 --phPos;
53
54 if (qfilename[phPos] == u'X') {
55 ++phLength;
56 continue;
57 }
58
59 if (phLength >= 6
60 || qfilename[phPos] == u'/') {
61 ++phPos;
62 break;
63 }
64
65 // start over
66 phLength = 0;
67 }
68
69 if (phLength < 6)
70 qfilename.append(".XXXXXX"_L1);
71
72 // "Nativify" :-)
76
77 // Find mask in native path
78 phPos = filename.size();
79 phLength = 0;
80 while (phPos != 0) {
81 --phPos;
82
83 if (filename[phPos] == Latin1Char('X')) {
84 ++phLength;
85 continue;
86 }
87
88 if (phLength >= 6) {
89 ++phPos;
90 break;
91 }
92
93 // start over
94 phLength = 0;
95 }
96
97 Q_ASSERT(phLength >= 6);
98 path = filename;
99 pos = phPos;
100 length = phLength;
101}
102
110{
111 Q_ASSERT(length != 0);
112 Q_ASSERT(pos < path.size());
113 Q_ASSERT(length <= path.size() - pos);
114
115 Char *const placeholderStart = (Char *)path.data() + pos;
116 Char *const placeholderEnd = placeholderStart + length;
117
118 // Replace placeholder with random chars.
119 {
120 // Since our dictionary is 26+26 characters, it would seem we only need
121 // a random number from 0 to 63 to select a character. However, due to
122 // the limited range, that would mean 12 (64-52) characters have double
123 // the probability of the others: 1 in 32 instead of 1 in 64.
124 //
125 // To overcome this limitation, we use more bits per character. With 10
126 // bits, there are 16 characters with probability 19/1024 and the rest
127 // at 20/1024 (i.e, less than .1% difference). This allows us to do 3
128 // characters per 32-bit random number, which is also half the typical
129 // placeholder length.
130 enum { BitsPerCharacter = 10 };
131
132 Char *rIter = placeholderEnd;
133 while (rIter != placeholderStart) {
135 auto applyOne = [&]() {
136 quint32 v = rnd & ((1 << BitsPerCharacter) - 1);
137 rnd >>= BitsPerCharacter;
138 char ch = char((26 + 26) * v / (1 << BitsPerCharacter));
139 if (ch < 26)
140 *--rIter = Latin1Char(ch + 'A');
141 else
142 *--rIter = Latin1Char(ch - 26 + 'a');
143 };
144
145 applyOne();
146 if (rIter == placeholderStart)
147 break;
148
149 applyOne();
150 if (rIter == placeholderStart)
151 break;
152
153 applyOne();
154 }
155 }
156
157 return path;
158}
159
160#ifndef QT_NO_TEMPORARYFILE
161
177{
178 const int maxAttempts = 16;
179 for (int attempt = 0; attempt < maxAttempts; ++attempt) {
180 // Atomically create file and obtain handle
182
183#if defined(Q_OS_WIN)
184 Q_UNUSED(mode);
185 const DWORD shareMode = (flags & QTemporaryFileEngine::Win32NonShared)
186 ? 0u : (FILE_SHARE_READ | FILE_SHARE_WRITE);
187
188 file = CreateFile((const wchar_t *)path.constData(),
189 GENERIC_READ | GENERIC_WRITE,
190 shareMode, NULL, CREATE_NEW,
191 FILE_ATTRIBUTE_NORMAL, NULL);
192
193 if (file != INVALID_HANDLE_VALUE)
194 return true;
195
196 DWORD err = GetLastError();
197 if (err == ERROR_ACCESS_DENIED) {
198 WIN32_FILE_ATTRIBUTE_DATA attributes;
199 if (!GetFileAttributesEx((const wchar_t *)path.constData(),
200 GetFileExInfoStandard, &attributes)
201 || attributes.dwFileAttributes == INVALID_FILE_ATTRIBUTES) {
202 // Potential write error (read-only parent directory, etc.).
204 return false;
205 } // else file already exists as a directory.
206 } else if (err != ERROR_FILE_EXISTS) {
208 return false;
209 }
210#else // POSIX
212 file = QT_OPEN(path.constData(),
213 QT_OPEN_CREAT | QT_OPEN_EXCL | QT_OPEN_RDWR | QT_OPEN_LARGEFILE,
214 static_cast<mode_t>(mode));
215
216 if (file != -1)
217 return true;
218
219 int err = errno;
220 if (err != EEXIST) {
222 return false;
223 }
224#endif
225 }
226
227 return false;
228}
229
231 Success = 0,
234};
235
238{
239#ifdef LINUX_UNNAMED_TMPFILE
240 // first, check if we have /proc, otherwise can't make the file exist later
241 // (no error message set, as caller will try regular temporary file)
242 if (!qt_haveLinuxProcfs())
244
245 const char *p = ".";
246 QByteArray::size_type lastSlash = tfn.path.lastIndexOf('/');
247 if (lastSlash >= 0) {
248 if (lastSlash == 0)
249 lastSlash = 1;
250 tfn.path[lastSlash] = '\0';
251 p = tfn.path.data();
252 }
253
254 file = QT_OPEN(p, O_TMPFILE | QT_OPEN_RDWR | QT_OPEN_LARGEFILE,
255 static_cast<mode_t>(mode));
256 if (file != -1)
258
259 if (errno == EOPNOTSUPP || errno == EISDIR) {
260 // fs or kernel doesn't support O_TMPFILE, so
261 // put the slash back so we may try a regular file
262 if (lastSlash != -1)
263 tfn.path[lastSlash] = '/';
265 }
266
267 // real error
270#else
271 Q_UNUSED(file);
272 Q_UNUSED(tfn);
273 Q_UNUSED(mode);
276#endif
277}
278
279//************* QTemporaryFileEngine
281{
282 Q_D(QFSFileEngine);
283 d->unmapAll();
285}
286
288{
289 Q_D(const QFSFileEngine);
290
291 if (!((nullptr == d->fh) && (-1 == d->fd)
292#if defined Q_OS_WIN
293 && (INVALID_HANDLE_VALUE == d->fileHandle)
294#endif
295 ))
296 return true;
297
298 return false;
299
300}
301
303{
304 // Really close the file, so we don't leak
307}
308
309bool QTemporaryFileEngine::open(QIODevice::OpenMode openMode,
310 std::optional<QFile::Permissions> permissions)
311{
312 Q_D(QFSFileEngine);
314
315 openMode |= QIODevice::ReadWrite;
316
318 return QFSFileEngine::open(openMode, permissions);
319
321
323#if defined(Q_OS_WIN)
324 NativeFileHandle &file = d->fileHandle;
325#else // POSIX
326 NativeFileHandle &file = d->fd;
327#endif
328
331 unnamedFile = true;
332 d->fileEntry.clear();
333 } else if (st == CreateUnnamedFileStatus::NotSupported &&
335 filePathIsTemplate = false;
336 unnamedFile = false;
338 } else {
339 setError(QFile::OpenError, error.toString());
340 return false;
341 }
342
343#if !defined(Q_OS_WIN)
344 d->closeFileHandle = true;
345#endif
346
347 d->openMode = openMode;
348 d->lastFlushFailed = false;
349 d->tried_stat = 0;
350
351 return true;
352}
353
355{
356 Q_D(QFSFileEngine);
357 // Since the QTemporaryFileEngine::close() does not really close the file,
358 // we must explicitly call QFSFileEngine::close() before we remove it.
359 d->unmapAll();
361 if (isUnnamedFile())
362 return true;
364 d->fileEntry.clear();
365 // If a QTemporaryFile is constructed using a template file path, the path
366 // is generated in QTemporaryFileEngine::open() and then filePathIsTemplate
367 // is set to false. If remove() and then open() are called on the same
368 // QTemporaryFile, the path is not regenerated. Here we ensure that if the
369 // file path was generated, it will be generated again in the scenario above.
371 return true;
372 }
373 return false;
374}
375
377{
378 if (isUnnamedFile()) {
379 bool ok = materializeUnnamedFile(newName, DontOverwrite);
381 return ok;
382 }
384 return QFSFileEngine::rename(newName);
385}
386
388{
389 if (isUnnamedFile()) {
390 bool ok = materializeUnnamedFile(newName, Overwrite);
392 return ok;
393 }
395 return QFSFileEngine::renameOverwrite(newName);
396}
397
399{
400 // Don't close the file, just seek to the front.
401 seek(0);
403 return true;
404}
405
407{
408 if (isUnnamedFile()) {
410 // we know our file isn't (won't be) a symlink
411 return QString();
412 }
413
414 // for all other cases, materialize the file
416 }
418}
419
421{
423
424#ifdef LINUX_UNNAMED_TMPFILE
425 Q_D(QFSFileEngine);
426 const QByteArray src = "/proc/self/fd/" + QByteArray::number(d->fd);
427 auto materializeAt = [=](const QFileSystemEntry &dst) {
428 return ::linkat(AT_FDCWD, src, AT_FDCWD, dst.nativeFilePath(), AT_SYMLINK_FOLLOW) == 0;
429 };
430#else
431 auto materializeAt = [](const QFileSystemEntry &) { return false; };
432#endif
433
434 auto success = [this](const QFileSystemEntry &entry) {
435 filePathIsTemplate = false;
436 unnamedFile = false;
437 d_func()->fileEntry = entry;
438 return true;
439 };
440
441 auto materializeAsTemplate = [=](const QString &newName) {
442 QTemporaryFileName tfn(newName);
443 static const int maxAttempts = 16;
444 for (int attempt = 0; attempt < maxAttempts; ++attempt) {
445 tfn.generateNext();
447 if (materializeAt(entry))
448 return success(entry);
449 }
450 return false;
451 };
452
453 if (mode == NameIsTemplate) {
454 if (materializeAsTemplate(newName))
455 return true;
456 } else {
457 // Use linkat to materialize the file
458 QFileSystemEntry dst(newName);
459 if (materializeAt(dst))
460 return success(dst);
461
462 if (errno == EEXIST && mode == Overwrite) {
463 // retry by first creating a temporary file in the right dir
464 if (!materializeAsTemplate(templateName))
465 return false;
466
467 // then rename the materialized file to target (same as renameOverwrite)
469 return QFSFileEngine::renameOverwrite(newName);
470 }
471 }
472
473 // failed
475 return false;
476}
477
479{
480#ifdef LINUX_UNNAMED_TMPFILE
481 if (unnamedFile) {
482 Q_ASSERT(d_func()->fileEntry.isEmpty());
484 }
485 return unnamedFile;
486#else
487 return false;
488#endif
489}
490
491//************* QTemporaryFilePrivate
492
494{
495}
496
498 : templateName(templateNameIn)
499{
500}
501
503{
504}
505
507{
508 if (!fileEngine) {
511 }
512 return fileEngine.get();
513}
514
516{
517 if (!fileEngine)
518 return;
519
520 QTemporaryFileEngine *tef = static_cast<QTemporaryFileEngine *>(fileEngine.get());
521 if (fileName.isEmpty())
522 tef->initialize(templateName, 0600);
523 else
524 tef->initialize(fileName, 0600, false);
525}
526
528{
529#ifdef LINUX_UNNAMED_TMPFILE
530 if (!fileName.isEmpty() || !fileEngine)
531 return;
532
533 auto *tef = static_cast<QTemporaryFileEngine *>(fileEngine.get());
535#endif
536}
537
539{
540 QString baseName;
541#if defined(QT_BUILD_CORE_LIB)
543 if (baseName.isEmpty())
544#endif
545 baseName = "qt_temp"_L1;
546
547 return QDir::tempPath() + u'/' + baseName + ".XXXXXX"_L1;
548}
549
550//************* QTemporaryFile
551
608#ifdef QT_NO_QOBJECT
611{
612}
613
614QTemporaryFile::QTemporaryFile(const QString &templateName)
615 : QFile(*new QTemporaryFilePrivate(templateName))
616{
617}
618
619#else
630{
631}
632
652 : QTemporaryFile(templateName, nullptr)
653{
654}
655
666{
667}
668
688 : QFile(*new QTemporaryFilePrivate(templateName), parent)
689{
690}
691#endif
692
701{
702 Q_D(QTemporaryFile);
703 close();
704 if (!d->fileName.isEmpty() && d->autoRemove)
705 remove();
706}
707
732{
733 Q_D(const QTemporaryFile);
734 return d->autoRemove;
735}
736
756{
757 Q_D(QTemporaryFile);
758 d->autoRemove = b;
759}
760
771{
772 Q_D(const QTemporaryFile);
773 auto tef = static_cast<QTemporaryFileEngine *>(d->fileEngine.get());
774 if (tef && tef->isReallyOpen())
776
777 if (d->fileName.isEmpty())
778 return QString();
779 return d->engine()->fileName(QAbstractFileEngine::DefaultName);
780}
781
789{
790 Q_D(const QTemporaryFile);
791 return d->templateName;
792}
793
810{
811 Q_D(QTemporaryFile);
812 d->templateName = name;
813}
814
833bool QTemporaryFile::rename(const QString &newName)
834{
835 Q_D(QTemporaryFile);
836 auto tef = static_cast<QTemporaryFileEngine *>(d->fileEngine.get());
837 if (!tef || !tef->isReallyOpen() || !tef->filePathWasTemplate)
838 return QFile::rename(newName);
839
840 unsetError();
841 close();
842 if (error() == QFile::NoError) {
843 if (tef->rename(newName)) {
844 unsetError();
845 // engine was able to handle the new name so we just reset it
846 tef->setFileName(newName);
847 d->fileName = newName;
848 return true;
849 }
850
851 d->setError(QFile::RenameError, tef->errorString());
852 }
853 return false;
854}
855
879{
880 if (QAbstractFileEngine *engine = file.d_func()->engine()) {
882 return nullptr; // native already
883 //cache
884 bool wasOpen = file.isOpen();
885 qint64 old_off = 0;
886 if (wasOpen)
887 old_off = file.pos();
888 else if (!file.open(QIODevice::ReadOnly))
889 return nullptr;
890 //dump data
892 if (ret->open()) {
893 file.seek(0);
894 char buffer[1024];
895 while (true) {
896 qint64 len = file.read(buffer, 1024);
897 if (len < 1)
898 break;
899 ret->write(buffer, len);
900 }
901 ret->seek(0);
902 } else {
903 delete ret;
904 ret = nullptr;
905 }
906 //restore
907 if (wasOpen)
908 file.seek(old_off);
909 else
910 file.close();
911 //done
912 return ret;
913 }
914 return nullptr;
915}
916
925{
926 Q_D(QTemporaryFile);
927 auto tef = static_cast<QTemporaryFileEngine *>(d->fileEngine.get());
928 if (tef && tef->isReallyOpen()) {
930 return true;
931 }
932
933 // reset the engine state so it creates a new, unique file name from the template;
934 // equivalent to:
935 // delete d->fileEngine;
936 // d->fileEngine = 0;
937 // d->engine();
938 d->resetFileEngine();
939
940 if (QFile::open(flags)) {
941 tef = static_cast<QTemporaryFileEngine *>(d->fileEngine.get());
942 if (tef->isUnnamedFile())
943 d->fileName.clear();
944 else
945 d->fileName = tef->fileName(QAbstractFileEngine::DefaultName);
946 return true;
947 }
948 return false;
949}
950
951#endif // QT_NO_TEMPORARYFILE
952
954
955#ifndef QT_NO_QOBJECT
956#include "moc_qtemporaryfile.cpp"
957#endif
\inmodule QtCore \reentrant
void setError(QFile::FileError error, const QString &str)
Sets the error type to error, and the error string to errorString.
FileName
These values are used to request a file name in a particular format.
QFile::FileError error() const
Returns the QFile::FileError that resulted from the last failed operation.
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:534
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:474
qsizetype size_type
Definition qbytearray.h:444
static QByteArray number(int, int base=10)
Returns a byte-array representing the whole number n as text.
qsizetype lastIndexOf(char c, qsizetype from=-1) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
QString applicationName
the name of this application
static QString fromNativeSeparators(const QString &pathName)
Definition qdir.cpp:962
static QString tempPath()
Returns the absolute canonical path of the system's temporary directory.
Definition qdir.cpp:2130
\inmodule QtCore
QString fileName(FileName file) const override
\reimp
bool rename(const QString &newName) override
\reimp
bool renameOverwrite(const QString &newName) override
\reimp
bool close() override
\reimp
bool open(QIODevice::OpenMode openMode, std::optional< QFile::Permissions > permissions) override
\reimp
void setFileName(const QString &file) override
\reimp
bool seek(qint64) override
\reimp
bool remove() override
\reimp
std::unique_ptr< QAbstractFileEngine > fileEngine
qint64 pos() const override
\reimp
bool seek(qint64 offset) override
For random-access devices, this function sets the current position to pos, returning true on success,...
void unsetError()
Sets the file's error to QFileDevice::NoError.
FileError error() const
Returns the file error status.
void close() override
Calls QFileDevice::flush() and closes the file.
QString fileName
Definition qfile_p.h:39
static QFileSystemEntry absoluteName(const QFileSystemEntry &entry)
Q_AUTOTEST_EXPORT NativePath nativeFilePath() const
\inmodule QtCore
Definition qfile.h:93
bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:881
bool remove()
Removes the file specified by fileName().
Definition qfile.cpp:419
bool rename(const QString &newName)
Renames the file currently specified by fileName() to newName.
Definition qfile.cpp:533
void setOpenMode(QIODeviceBase::OpenMode openMode)
Sets the OpenMode of the device to openMode.
bool isOpen() const
Returns true if the device is open; otherwise returns false.
qint64 read(char *data, qint64 maxlen)
Reads at most maxSize bytes from the device into data, and returns the number of bytes read.
\inmodule QtCore
Definition qobject.h:90
quint32 generate()
Generates a 32-bit random quantity and returns it.
Definition qrandom.h:48
static Q_DECL_CONST_FUNCTION QRandomGenerator * global()
\threadsafe
Definition qrandom.h:275
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
qsizetype size() const
Returns the number of characters in this string.
Definition qstring.h:182
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
QString & append(QChar c)
Definition qstring.cpp:3227
const QString & templateName
bool rename(const QString &newName) override
\reimp
bool remove() override
\reimp
QString fileName(FileName file) const override
\reimp
bool close() override
\reimp
bool open(QIODevice::OpenMode flags, std::optional< QFile::Permissions > permissions) override
\reimp
bool renameOverwrite(const QString &newName) override
\reimp
bool materializeUnnamedFile(const QString &newName, MaterializationMode mode)
bool isUnnamedFile() const override final
void initialize(const QString &file, quint32 mode, bool nameIsTemplate=true)
void setFileName(const QString &file) override
\reimp
static QString defaultTemplateName()
QAbstractFileEngine * engine() const override
\inmodule QtCore \reentrant
~QTemporaryFile()
Destroys the temporary file object, the file is automatically closed if necessary and if in auto remo...
QTemporaryFile()
Constructs a QTemporaryFile using as file template the application name returned by QCoreApplication:...
QString fileName() const override
Returns the complete unique filename backing the QTemporaryFile object.
static QTemporaryFile * createNativeFile(const QString &fileName)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void setFileTemplate(const QString &name)
Sets the static portion of the file name to name.
bool open()
A QTemporaryFile will always be opened in QIODevice::ReadWrite mode, this allows easy access to the d...
QString fileTemplate() const
Returns the set file template.
bool autoRemove() const
Returns true if the QTemporaryFile is in auto remove mode.
bool rename(const QString &newName)
Renames the current temporary file to newName and returns true if it succeeded.
void setAutoRemove(bool b)
Sets the QTemporaryFile into auto-remove mode if b is true.
Combined button and popup list for selecting options.
void * HANDLE
#define QT_OPEN
bool qt_haveLinuxProcfs()
DBusConnection const char DBusError * error
#define INVALID_FILE_ATTRIBUTES
return ret
GLboolean GLboolean GLboolean b
GLsizei const GLfloat * v
[13]
GLenum mode
GLenum GLuint GLenum GLsizei length
GLenum src
GLenum GLuint buffer
GLenum GLenum dst
GLbitfield flags
GLuint name
GLuint entry
GLenum GLsizei len
GLsizei const GLchar *const * path
GLfloat GLfloat p
[1]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static CreateUnnamedFileStatus createUnnamedFile(NativeFileHandle &file, QTemporaryFileName &tfn, quint32 mode, QSystemError *error)
char Char
int NativeFileHandle
char Latin1Char
static bool createFileFromTemplate(NativeFileHandle &file, QTemporaryFileName &templ, quint32 mode, int flags, QSystemError &error)
CreateUnnamedFileStatus
#define Q_UNUSED(x)
unsigned int quint32
Definition qtypes.h:45
unsigned char uchar
Definition qtypes.h:27
ptrdiff_t qsizetype
Definition qtypes.h:70
long long qint64
Definition qtypes.h:55
unsigned short ushort
Definition qtypes.h:28
QFile file
[0]
QObject::connect nullptr
char * toString(const MyType &t)
[31]
QJSEngine engine
[0]
QTemporaryFileName(const QString &templateName)
QFileSystemEntry::NativePath generateNext()
QFileSystemEntry::NativePath path
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent