QSaveFile: follow symbolic links
[ChangeLog][QtCore][QSaveFile] Now follows symbolic links while writing to a link instead of replacing the link with the contents. Change-Id: I5afd519cb9f96ae68fa4c23c33a18de75671a301 Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@digia.com> Reviewed-by: David Faure <david.faure@kdab.com>
This commit is contained in:
parent
e747324f68
commit
7e5e7eeaa1
@ -205,14 +205,26 @@ bool QSaveFile::open(OpenMode mode)
|
|||||||
d->writeError = QFileDevice::WriteError;
|
d->writeError = QFileDevice::WriteError;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
d->fileEngine = new QTemporaryFileEngine(d->fileName);
|
|
||||||
|
// Resolve symlinks. Don't use QFileInfo::canonicalFilePath so it still give the expected
|
||||||
|
// target even if the file does not exist
|
||||||
|
d->finalFileName = d->fileName;
|
||||||
|
if (existingFile.isSymLink()) {
|
||||||
|
int maxDepth = 128;
|
||||||
|
while (--maxDepth && existingFile.isSymLink())
|
||||||
|
existingFile.setFile(existingFile.symLinkTarget());
|
||||||
|
if (maxDepth > 0)
|
||||||
|
d->finalFileName = existingFile.filePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
d->fileEngine = new QTemporaryFileEngine(d->finalFileName);
|
||||||
// Same as in QFile: QIODevice provides the buffering, so there's no need to request it from the file engine.
|
// Same as in QFile: QIODevice provides the buffering, so there's no need to request it from the file engine.
|
||||||
if (!d->fileEngine->open(mode | QIODevice::Unbuffered)) {
|
if (!d->fileEngine->open(mode | QIODevice::Unbuffered)) {
|
||||||
QFileDevice::FileError err = d->fileEngine->error();
|
QFileDevice::FileError err = d->fileEngine->error();
|
||||||
#ifdef Q_OS_UNIX
|
#ifdef Q_OS_UNIX
|
||||||
if (d->directWriteFallback && err == QFileDevice::OpenError && errno == EACCES) {
|
if (d->directWriteFallback && err == QFileDevice::OpenError && errno == EACCES) {
|
||||||
delete d->fileEngine;
|
delete d->fileEngine;
|
||||||
d->fileEngine = QAbstractFileEngine::create(d->fileName);
|
d->fileEngine = QAbstractFileEngine::create(d->finalFileName);
|
||||||
if (d->fileEngine->open(mode | QIODevice::Unbuffered)) {
|
if (d->fileEngine->open(mode | QIODevice::Unbuffered)) {
|
||||||
d->useTemporaryFile = false;
|
d->useTemporaryFile = false;
|
||||||
QFileDevice::open(mode);
|
QFileDevice::open(mode);
|
||||||
@ -285,7 +297,7 @@ bool QSaveFile::commit()
|
|||||||
// atomically replace old file with new file
|
// atomically replace old file with new file
|
||||||
// Can't use QFile::rename for that, must use the file engine directly
|
// Can't use QFile::rename for that, must use the file engine directly
|
||||||
Q_ASSERT(d->fileEngine);
|
Q_ASSERT(d->fileEngine);
|
||||||
if (!d->fileEngine->renameOverwrite(d->fileName)) {
|
if (!d->fileEngine->renameOverwrite(d->finalFileName)) {
|
||||||
d->setError(d->fileEngine->error(), d->fileEngine->errorString());
|
d->setError(d->fileEngine->error(), d->fileEngine->errorString());
|
||||||
d->fileEngine->remove();
|
d->fileEngine->remove();
|
||||||
delete d->fileEngine;
|
delete d->fileEngine;
|
||||||
|
@ -70,6 +70,7 @@ protected:
|
|||||||
~QSaveFilePrivate();
|
~QSaveFilePrivate();
|
||||||
|
|
||||||
QString fileName;
|
QString fileName;
|
||||||
|
QString finalFileName; // fileName with symbolic links resolved
|
||||||
|
|
||||||
QFileDevice::FileError writeError;
|
QFileDevice::FileError writeError;
|
||||||
|
|
||||||
|
@ -93,6 +93,7 @@ private slots:
|
|||||||
void transactionalWriteNoPermissionsOnFile();
|
void transactionalWriteNoPermissionsOnFile();
|
||||||
void transactionalWriteCanceled();
|
void transactionalWriteCanceled();
|
||||||
void transactionalWriteErrorRenaming();
|
void transactionalWriteErrorRenaming();
|
||||||
|
void symlink();
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline QByteArray msgCannotOpen(const QFileDevice &f)
|
static inline QByteArray msgCannotOpen(const QFileDevice &f)
|
||||||
@ -340,5 +341,118 @@ void tst_QSaveFile::transactionalWriteErrorRenaming()
|
|||||||
QCOMPARE(file.error(), QFile::RenameError);
|
QCOMPARE(file.error(), QFile::RenameError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_QSaveFile::symlink()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_UNIX
|
||||||
|
QByteArray someData = "some data";
|
||||||
|
QTemporaryDir dir;
|
||||||
|
QVERIFY(dir.isValid());
|
||||||
|
|
||||||
|
const QString targetFile = dir.path() + QLatin1String("/outfile");
|
||||||
|
const QString linkFile = dir.path() + QLatin1String("/linkfile");
|
||||||
|
{
|
||||||
|
QFile file(targetFile);
|
||||||
|
QVERIFY2(file.open(QIODevice::WriteOnly), msgCannotOpen(file).constData());
|
||||||
|
QCOMPARE(file.write("Hello"), Q_INT64_C(5));
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVERIFY(QFile::link(targetFile, linkFile));
|
||||||
|
|
||||||
|
QString canonical = QFileInfo(linkFile).canonicalFilePath();
|
||||||
|
QCOMPARE(canonical, QFileInfo(targetFile).canonicalFilePath());
|
||||||
|
|
||||||
|
// Try saving into it
|
||||||
|
{
|
||||||
|
QSaveFile saveFile(linkFile);
|
||||||
|
QVERIFY(saveFile.open(QIODevice::WriteOnly));
|
||||||
|
QCOMPARE(saveFile.write(someData), someData.size());
|
||||||
|
saveFile.commit();
|
||||||
|
|
||||||
|
//Check that the linkFile is still a link and still has the same canonical path
|
||||||
|
QFileInfo info(linkFile);
|
||||||
|
QVERIFY(info.isSymLink());
|
||||||
|
QCOMPARE(QFileInfo(linkFile).canonicalFilePath(), canonical);
|
||||||
|
|
||||||
|
QFile file(targetFile);
|
||||||
|
QVERIFY2(file.open(QIODevice::ReadOnly), msgCannotOpen(file).constData());
|
||||||
|
QCOMPARE(file.readAll(), someData);
|
||||||
|
file.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save into a symbolic link that point to a removed file
|
||||||
|
someData = "more stuff";
|
||||||
|
{
|
||||||
|
QSaveFile saveFile(linkFile);
|
||||||
|
QVERIFY(saveFile.open(QIODevice::WriteOnly));
|
||||||
|
QCOMPARE(saveFile.write(someData), someData.size());
|
||||||
|
saveFile.commit();
|
||||||
|
|
||||||
|
QFileInfo info(linkFile);
|
||||||
|
QVERIFY(info.isSymLink());
|
||||||
|
QCOMPARE(QFileInfo(linkFile).canonicalFilePath(), canonical);
|
||||||
|
|
||||||
|
QFile file(targetFile);
|
||||||
|
QVERIFY2(file.open(QIODevice::ReadOnly), msgCannotOpen(file).constData());
|
||||||
|
QCOMPARE(file.readAll(), someData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// link to a link in another directory
|
||||||
|
QTemporaryDir dir2;
|
||||||
|
QVERIFY(dir2.isValid());
|
||||||
|
|
||||||
|
const QString linkFile2 = dir2.path() + QLatin1String("/linkfile");
|
||||||
|
QVERIFY(QFile::link(linkFile, linkFile2));
|
||||||
|
QCOMPARE(QFileInfo(linkFile2).canonicalFilePath(), canonical);
|
||||||
|
|
||||||
|
|
||||||
|
someData = "hello everyone";
|
||||||
|
|
||||||
|
{
|
||||||
|
QSaveFile saveFile(linkFile2);
|
||||||
|
QVERIFY(saveFile.open(QIODevice::WriteOnly));
|
||||||
|
QCOMPARE(saveFile.write(someData), someData.size());
|
||||||
|
saveFile.commit();
|
||||||
|
|
||||||
|
QFile file(targetFile);
|
||||||
|
QVERIFY2(file.open(QIODevice::ReadOnly), msgCannotOpen(file).constData());
|
||||||
|
QCOMPARE(file.readAll(), someData);
|
||||||
|
}
|
||||||
|
|
||||||
|
//cyclic link
|
||||||
|
const QString cyclicLink = dir.path() + QLatin1String("/cyclic");
|
||||||
|
QVERIFY(QFile::link(cyclicLink, cyclicLink));
|
||||||
|
{
|
||||||
|
QSaveFile saveFile(cyclicLink);
|
||||||
|
QVERIFY(saveFile.open(QIODevice::WriteOnly));
|
||||||
|
QCOMPARE(saveFile.write(someData), someData.size());
|
||||||
|
saveFile.commit();
|
||||||
|
|
||||||
|
QFile file(cyclicLink);
|
||||||
|
QVERIFY2(file.open(QIODevice::ReadOnly), msgCannotOpen(file).constData());
|
||||||
|
QCOMPARE(file.readAll(), someData);
|
||||||
|
}
|
||||||
|
|
||||||
|
//cyclic link2
|
||||||
|
QVERIFY(QFile::link(cyclicLink + QLatin1Char('1'), cyclicLink + QLatin1Char('2')));
|
||||||
|
QVERIFY(QFile::link(cyclicLink + QLatin1Char('2'), cyclicLink + QLatin1Char('1')));
|
||||||
|
|
||||||
|
{
|
||||||
|
QSaveFile saveFile(cyclicLink + QLatin1Char('1'));
|
||||||
|
QVERIFY(saveFile.open(QIODevice::WriteOnly));
|
||||||
|
QCOMPARE(saveFile.write(someData), someData.size());
|
||||||
|
saveFile.commit();
|
||||||
|
|
||||||
|
// the explicit file becomes a file instead of a link
|
||||||
|
QVERIFY(!QFileInfo(cyclicLink + QLatin1Char('1')).isSymLink());
|
||||||
|
QVERIFY(QFileInfo(cyclicLink + QLatin1Char('2')).isSymLink());
|
||||||
|
|
||||||
|
QFile file(cyclicLink + QLatin1Char('1'));
|
||||||
|
QVERIFY2(file.open(QIODevice::ReadOnly), msgCannotOpen(file).constData());
|
||||||
|
QCOMPARE(file.readAll(), someData);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
QTEST_MAIN(tst_QSaveFile)
|
QTEST_MAIN(tst_QSaveFile)
|
||||||
#include "tst_qsavefile.moc"
|
#include "tst_qsavefile.moc"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user