Add QIODevice::NewOnly and QIODevice::ExistingOnly OpenMode flags

When QFile::open is called with the NewOnly flag, the call will
fail if the file already exists. As usual, if the file does not exist,
it will be created. Like QTemporaryFile, there is a guarantee from
the operating system that you are not accidentally creating a new file
on top of an older file. When QFile::open is called with the
ExistingOnly flag, the call will fail if the file does not exist. The
ExistingOnly flag only provides new functionality when used with the
WriteOnly flag. For ReadOnly it provides no change in functionality,
as ReadOnly by itself already never creates.

Task-number: QTBUG-52244
Change-Id: I8e3206728f245f95172c225bf297023fb078fc6d
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
d3fault 2017-11-24 16:07:53 -07:00
parent 0aecac1dcf
commit 1a4cc8d57b
14 changed files with 138 additions and 36 deletions

View File

@ -139,6 +139,7 @@
#define QT_OPEN_CREAT O_CREAT
#define QT_OPEN_TRUNC O_TRUNC
#define QT_OPEN_APPEND O_APPEND
#define QT_OPEN_EXCL O_EXCL
// Directory iteration
#define QT_DIR DIR

View File

@ -139,6 +139,7 @@
#define QT_OPEN_CREAT O_CREAT
#define QT_OPEN_TRUNC O_TRUNC
#define QT_OPEN_APPEND O_APPEND
#define QT_OPEN_EXCL O_EXCL
// Directory iteration
#define QT_DIR DIR

View File

@ -115,6 +115,7 @@
#define QT_OPEN_CREAT O_CREAT
#define QT_OPEN_TRUNC O_TRUNC
#define QT_OPEN_APPEND O_APPEND
#define QT_OPEN_EXCL O_EXCL
// Directory iteration
#define QT_DIR DIR

View File

@ -128,6 +128,7 @@
#define QT_OPEN_CREAT O_CREAT
#define QT_OPEN_TRUNC O_TRUNC
#define QT_OPEN_APPEND O_APPEND
#define QT_OPEN_EXCL O_EXCL
// Posix extensions to C89
#define QT_FILENO fileno

View File

@ -893,9 +893,9 @@ bool QFile::open(OpenMode mode)
qWarning("QFile::open: File (%s) already open", qPrintable(fileName()));
return false;
}
if (mode & Append)
// Either Append or NewOnly implies WriteOnly
if (mode & (Append | NewOnly))
mode |= WriteOnly;
unsetError();
if ((mode & (ReadOnly | WriteOnly)) == 0) {
qWarning("QIODevice::open: File access not specified");
@ -965,7 +965,8 @@ bool QFile::open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
qWarning("QFile::open: File (%s) already open", qPrintable(fileName()));
return false;
}
if (mode & Append)
// Either Append or NewOnly implies WriteOnly
if (mode & (Append | NewOnly))
mode |= WriteOnly;
unsetError();
if ((mode & (ReadOnly | WriteOnly)) == 0) {
@ -1023,7 +1024,8 @@ bool QFile::open(int fd, OpenMode mode, FileHandleFlags handleFlags)
qWarning("QFile::open: File (%s) already open", qPrintable(fileName()));
return false;
}
if (mode & Append)
// Either Append or NewOnly implies WriteOnly
if (mode & (Append | NewOnly))
mode |= WriteOnly;
unsetError();
if ((mode & (ReadOnly | WriteOnly)) == 0) {

View File

@ -164,6 +164,35 @@ QFSFileEngine::QFSFileEngine(QFSFileEnginePrivate &dd)
{
}
/*!
\internal
*/
bool QFSFileEngine::processOpenModeFlags(QIODevice::OpenMode *mode)
{
QIODevice::OpenMode &openMode = *mode;
if ((openMode & QFile::NewOnly) && (openMode & QFile::ExistingOnly)) {
qWarning("NewOnly and ExistingOnly are mutually exclusive");
setError(QFile::OpenError, QLatin1String("NewOnly and ExistingOnly are mutually exclusive"));
return false;
}
if ((openMode & QFile::ExistingOnly) && !(openMode & (QFile::ReadOnly | QFile::WriteOnly))) {
qWarning("ExistingOnly must be specified alongside ReadOnly, WriteOnly, or ReadWrite");
setError(QFile::OpenError, QLatin1String("ExistingOnly must be specified alongside ReadOnly, WriteOnly, or ReadWrite"));
return false;
}
// Either Append or NewOnly implies WriteOnly
if (openMode & (QFile::Append | QFile::NewOnly))
openMode |= QFile::WriteOnly;
// WriteOnly implies Truncate when ReadOnly, Append, and NewOnly are not set.
if ((openMode & QFile::WriteOnly) && !(openMode & (QFile::ReadOnly | QFile::Append | QFile::NewOnly)))
openMode |= QFile::Truncate;
return true;
}
/*!
Destructs the QFSFileEngine.
*/
@ -205,13 +234,8 @@ bool QFSFileEngine::open(QIODevice::OpenMode openMode)
return false;
}
// Append implies WriteOnly.
if (openMode & QFile::Append)
openMode |= QFile::WriteOnly;
// WriteOnly implies Truncate if neither ReadOnly nor Append are sent.
if ((openMode & QFile::WriteOnly) && !(openMode & (QFile::ReadOnly | QFile::Append)))
openMode |= QFile::Truncate;
if (!processOpenModeFlags(&openMode))
return false;
d->openMode = openMode;
d->lastFlushFailed = false;
@ -238,13 +262,8 @@ bool QFSFileEngine::open(QIODevice::OpenMode openMode, FILE *fh, QFile::FileHand
Q_D(QFSFileEngine);
// Append implies WriteOnly.
if (openMode & QFile::Append)
openMode |= QFile::WriteOnly;
// WriteOnly implies Truncate if neither ReadOnly nor Append are sent.
if ((openMode & QFile::WriteOnly) && !(openMode & (QFile::ReadOnly | QFile::Append)))
openMode |= QFile::Truncate;
if (!processOpenModeFlags(&openMode))
return false;
d->openMode = openMode;
d->lastFlushFailed = false;
@ -302,13 +321,8 @@ bool QFSFileEngine::open(QIODevice::OpenMode openMode, int fd, QFile::FileHandle
{
Q_D(QFSFileEngine);
// Append implies WriteOnly.
if (openMode & QFile::Append)
openMode |= QFile::WriteOnly;
// WriteOnly implies Truncate if neither ReadOnly nor Append are sent.
if ((openMode & QFile::WriteOnly) && !(openMode & (QFile::ReadOnly | QFile::Append)))
openMode |= QFile::Truncate;
if (!processOpenModeFlags(&openMode))
return false;
d->openMode = openMode;
d->lastFlushFailed = false;

View File

@ -131,6 +131,9 @@ public:
protected:
QFSFileEngine(QFSFileEnginePrivate &dd);
private:
inline bool processOpenModeFlags(QIODevice::OpenMode *mode);
};
class Q_AUTOTEST_EXPORT QFSFileEnginePrivate : public QAbstractFileEnginePrivate
@ -219,6 +222,12 @@ public:
int sysOpen(const QString &, int flags);
#endif
static bool openModeCanCreate(QIODevice::OpenMode openMode)
{
// WriteOnly can create, but only when ExistingOnly isn't specified.
// ReadOnly by itself never creates.
return (openMode & QFile::WriteOnly) && !(openMode & QFile::ExistingOnly);
}
protected:
QFSFileEnginePrivate();

View File

@ -74,11 +74,13 @@ static inline int openModeToOpenFlags(QIODevice::OpenMode mode)
oflags |= QT_OPEN_LARGEFILE;
#endif
if ((mode & QFile::ReadWrite) == QFile::ReadWrite) {
oflags = QT_OPEN_RDWR | QT_OPEN_CREAT;
} else if (mode & QFile::WriteOnly) {
oflags = QT_OPEN_WRONLY | QT_OPEN_CREAT;
}
if ((mode & QFile::ReadWrite) == QFile::ReadWrite)
oflags = QT_OPEN_RDWR;
else if (mode & QFile::WriteOnly)
oflags = QT_OPEN_WRONLY;
if (QFSFileEnginePrivate::openModeCanCreate(mode))
oflags |= QT_OPEN_CREAT;
if (mode & QFile::Append) {
oflags |= QT_OPEN_APPEND;
@ -87,6 +89,9 @@ static inline int openModeToOpenFlags(QIODevice::OpenMode mode)
oflags |= QT_OPEN_TRUNC;
}
if (mode & QFile::NewOnly)
oflags |= QT_OPEN_EXCL;
return oflags;
}

View File

@ -117,9 +117,12 @@ bool QFSFileEnginePrivate::nativeOpen(QIODevice::OpenMode openMode)
if (openMode & QIODevice::WriteOnly)
accessRights |= GENERIC_WRITE;
// WriteOnly can create files, ReadOnly cannot.
DWORD creationDisp = (openMode & QIODevice::WriteOnly) ? OPEN_ALWAYS : OPEN_EXISTING;
DWORD creationDisp = (openMode & QIODevice::NewOnly)
? CREATE_NEW
: openModeCanCreate(openMode)
? OPEN_ALWAYS
: OPEN_EXISTING;
// Create the file handle.
#ifndef Q_OS_WINRT
SECURITY_ATTRIBUTES securityAtts = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE };

View File

@ -324,6 +324,23 @@ QIODevicePrivate::~QIODevicePrivate()
terminators are translated to the local encoding, for
example '\\r\\n' for Win32.
\value Unbuffered Any buffer in the device is bypassed.
\value NewOnly Fail if the file to be opened already exists. Create and
open the file only if it does not exist. There is a
guarantee from the operating system that you are the only
one creating and opening the file. Note that this mode
implies WriteOnly, and combining it with ReadWrite is
allowed. This flag currently only affects QFile. Other
classes might use this flag in the future, but until then
using this flag with any classes other than QFile may
result in undefined behavior.
\value ExistingOnly Fail if the file to be opened does not exist. This flag
must be specified alongside ReadOnly, WriteOnly, or
ReadWrite. Note that using this flag with ReadOnly alone
is redundant, as ReadOnly already fails when the file does
not exist. This flag currently only affects QFile. Other
classes might use this flag in the future, but until then
using this flag with any classes other than QFile may
result in undefined behavior.
Certain flags, such as \c Unbuffered and \c Truncate, are
meaningless when used with some subclasses. Some of these

View File

@ -76,7 +76,9 @@ public:
Append = 0x0004,
Truncate = 0x0008,
Text = 0x0010,
Unbuffered = 0x0020
Unbuffered = 0x0020,
NewOnly = 0x0040,
ExistingOnly = 0x0080
};
Q_DECLARE_FLAGS(OpenMode, OpenModeFlag)

View File

@ -184,7 +184,8 @@ void QSaveFile::setFileName(const QString &name)
Important: the \a mode must include QIODevice::WriteOnly.
It may also have additional flags, such as QIODevice::Text and QIODevice::Unbuffered.
QIODevice::ReadWrite and QIODevice::Append are not supported at the moment.
QIODevice::ReadWrite, QIODevice::Append, QIODevice::NewOnly and
QIODevice::ExistingOnly are not supported at the moment.
\sa QIODevice::OpenMode, setFileName()
*/
@ -201,7 +202,8 @@ bool QSaveFile::open(OpenMode mode)
return false;
}
// In the future we could implement ReadWrite by copying from the existing file to the temp file...
if ((mode & ReadOnly) || (mode & Append)) {
// The implications of NewOnly and ExistingOnly when used with QSaveFile need to be considered carefully...
if (mode & (ReadOnly | Append | NewOnly | ExistingOnly)) {
qWarning("QSaveFile::open: Unsupported open mode 0x%x", int(mode));
return false;
}

View File

@ -248,7 +248,7 @@ static bool createFileFromTemplate(NativeFileHandle &file, QTemporaryFileName &t
}
#else // POSIX
file = QT_OPEN(path.constData(),
QT_OPEN_CREAT | O_EXCL | QT_OPEN_RDWR | QT_OPEN_LARGEFILE,
QT_OPEN_CREAT | QT_OPEN_EXCL | QT_OPEN_RDWR | QT_OPEN_LARGEFILE,
static_cast<mode_t>(mode));
if (file != -1)

View File

@ -168,6 +168,8 @@ private slots:
void getch();
void ungetChar();
void createFile();
void createFileNewOnly();
void openFileExistingOnly();
void append();
void permissions_data();
void permissions();
@ -1211,6 +1213,48 @@ void tst_QFile::createFile()
QVERIFY( QFile::exists( "createme.txt" ) );
}
void tst_QFile::createFileNewOnly()
{
QFile::remove("createme.txt");
QVERIFY(!QFile::exists("createme.txt"));
QFile f("createme.txt");
QVERIFY2(f.open(QIODevice::NewOnly), msgOpenFailed(f).constData());
f.close();
QVERIFY(QFile::exists("createme.txt"));
QVERIFY(!f.open(QIODevice::NewOnly));
QVERIFY(QFile::exists("createme.txt"));
QFile::remove("createme.txt");
}
void tst_QFile::openFileExistingOnly()
{
QFile::remove("dontcreateme.txt");
QVERIFY(!QFile::exists("dontcreateme.txt"));
QFile f("dontcreateme.txt");
QVERIFY(!f.open(QIODevice::ExistingOnly | QIODevice::ReadOnly));
QVERIFY(!f.open(QIODevice::ExistingOnly | QIODevice::WriteOnly));
QVERIFY(!f.open(QIODevice::ExistingOnly | QIODevice::ReadWrite));
QVERIFY(!f.open(QIODevice::ExistingOnly));
QVERIFY(!QFile::exists("dontcreateme.txt"));
QVERIFY2(f.open(QIODevice::NewOnly), msgOpenFailed(f).constData());
f.close();
QVERIFY(QFile::exists("dontcreateme.txt"));
QVERIFY2(f.open(QIODevice::ExistingOnly | QIODevice::ReadOnly), msgOpenFailed(f).constData());
f.close();
QVERIFY2(f.open(QIODevice::ExistingOnly | QIODevice::WriteOnly), msgOpenFailed(f).constData());
f.close();
QVERIFY2(f.open(QIODevice::ExistingOnly | QIODevice::ReadWrite), msgOpenFailed(f).constData());
f.close();
QVERIFY(!f.open(QIODevice::ExistingOnly));
QVERIFY(QFile::exists("dontcreateme.txt"));
QFile::remove("dontcreateme.txt");
}
void tst_QFile::append()
{
const QString name("appendme.txt");