Enable support for Qt 5.12 in installer framework

Workaround issues of IFW not being able to work on elevated mode
caused by changes in qtbase, namely when initializing socket
connection between remote installer client and server, and writing
maintenance tool binary.

Switch to using unbuffered mode for QFSFileEngine instances as
buffered mode support has been dropped. Fix calls to QFile::copy()
when running elevated installer process. Make minor modifications
for unit tests to pass. Explicitly fail and return when performing
CreateLocalRepositoryOperation on non-owned directory.

Task-number: QTIFW-1312
Change-Id: I3db72547ee95c87d8c02d27e5b31c7b30e793431
Reviewed-by: Katja Marttila <katja.marttila@qt.io>
This commit is contained in:
Arttu Tarkiainen 2019-07-10 17:06:00 +03:00
parent 8efb76dc0a
commit b3eaeb1782
11 changed files with 96 additions and 13 deletions

View File

@ -158,7 +158,7 @@ bool Resource::open()
if (isOpen()) if (isOpen())
return false; return false;
if (!m_file.open(QIODevice::ReadOnly)) { if (!m_file.open(QIODevice::ReadOnly | QIODevice::Unbuffered)) {
setErrorString(m_file.errorString()); setErrorString(m_file.errorString());
return false; return false;
} }

View File

@ -38,6 +38,7 @@
#include "lib7z_facade.h" #include "lib7z_facade.h"
#include "packagemanagercore.h" #include "packagemanagercore.h"
#include "productkeycheck.h" #include "productkeycheck.h"
#include "constants.h"
#include "updateoperations.h" #include "updateoperations.h"
@ -183,6 +184,23 @@ bool CreateLocalRepositoryOperation::performOperation()
} }
setValue(QLatin1String("createddir"), mkDirOp.value(QLatin1String("createddir"))); setValue(QLatin1String("createddir"), mkDirOp.value(QLatin1String("createddir")));
#if QT_VERSION >= QT_VERSION_CHECK(5,10,0)
// Internal changes to QTemporaryFile break copying Qt resources through
// QInstaller::RemoteFileEngine. We do not register resources to be handled by
// RemoteFileEngine, instead copying using 5.9 succeeded because QFile::copy()
// creates a QTemporaryFile object internally that is handled by the remote engine.
//
// This will not work with Qt 5.10 and above as QTemporaryFile introduced a new
// rename() implementation that explicitly uses its own QTemporaryFileEngine.
//
// Fail and return early if we are working on an elevated permission directory.
if (core && !core->directoryWritable(repoPath)) {
setError(UserDefinedError);
setErrorString(tr("Creating local repository into elevated permissions "
"directory: %1 is not supported.").arg(repoPath));
return false;
}
#endif
// copy the whole meta data into local repository // copy the whole meta data into local repository
CopyDirectoryOperation copyDirOp(core); CopyDirectoryOperation copyDirOp(core);
copyDirOp.setArguments(QStringList() << QLatin1String(":/metadata/") << repoPath); copyDirOp.setArguments(QStringList() << QLatin1String(":/metadata/") << repoPath);

View File

@ -1034,7 +1034,7 @@ void PackageManagerCorePrivate::writeMaintenanceToolBinary(QFile *const input, q
qDebug() << "Writing maintenance tool:" << maintenanceToolRenamedName; qDebug() << "Writing maintenance tool:" << maintenanceToolRenamedName;
ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("Writing maintenance tool.")); ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("Writing maintenance tool."));
QTemporaryFile out; QFile out(generateTemporaryFileName());
QInstaller::openForWrite(&out); // throws an exception in case of error QInstaller::openForWrite(&out); // throws an exception in case of error
if (!input->seek(0)) if (!input->seek(0))
@ -1052,7 +1052,7 @@ void PackageManagerCorePrivate::writeMaintenanceToolBinary(QFile *const input, q
#endif #endif
// It's a bit odd to have only the magic in the data file, but this simplifies // It's a bit odd to have only the magic in the data file, but this simplifies
// other code a lot (since installers don't have any appended data either) // other code a lot (since installers don't have any appended data either)
QTemporaryFile dataOut; QFile dataOut(generateTemporaryFileName());
QInstaller::openForWrite(&dataOut); QInstaller::openForWrite(&dataOut);
QInstaller::appendInt64(&dataOut, 0); // operations start QInstaller::appendInt64(&dataOut, 0); // operations start
QInstaller::appendInt64(&dataOut, 0); // operations end QInstaller::appendInt64(&dataOut, 0); // operations end
@ -1070,10 +1070,9 @@ void PackageManagerCorePrivate::writeMaintenanceToolBinary(QFile *const input, q
} }
if (!dataOut.rename(resourcePath.filePath(QLatin1String("installer.dat")))) { if (!dataOut.rename(resourcePath.filePath(QLatin1String("installer.dat")))) {
throw Error(tr("Cannot write maintenance tool data to %1: %2").arg(out.fileName(), throw Error(tr("Cannot write maintenance tool data to %1: %2").arg(dataOut.fileName(),
out.errorString())); dataOut.errorString()));
} }
dataOut.setAutoRemove(false);
dataOut.setPermissions(dataOut.permissions() | QFile::WriteUser | QFile::ReadGroup dataOut.setPermissions(dataOut.permissions() | QFile::WriteUser | QFile::ReadGroup
| QFile::ReadOther); | QFile::ReadOther);
} }
@ -1098,6 +1097,11 @@ void PackageManagerCorePrivate::writeMaintenanceToolBinary(QFile *const input, q
} else { } else {
qDebug() << "Failed to write permissions for maintenance tool."; qDebug() << "Failed to write permissions for maintenance tool.";
} }
if (out.exists() && !out.remove()) {
qWarning() << tr("Cannot remove temporary data file \"%1\": %2")
.arg(out.fileName(), out.errorString());
}
} }
void PackageManagerCorePrivate::writeMaintenanceToolBinaryData(QFileDevice *output, QFile *const input, void PackageManagerCorePrivate::writeMaintenanceToolBinaryData(QFileDevice *output, QFile *const input,
@ -1367,7 +1371,7 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper
m_core->setValue(QLatin1String("installedOperationAreSorted"), QLatin1String("true")); m_core->setValue(QLatin1String("installedOperationAreSorted"), QLatin1String("true"));
try { try {
QTemporaryFile file; QFile file(generateTemporaryFileName());
QInstaller::openForWrite(&file); QInstaller::openForWrite(&file);
writeMaintenanceToolBinaryData(&file, &input, performedOperations, layout); writeMaintenanceToolBinaryData(&file, &input, performedOperations, layout);
QInstaller::appendInt64(&file, BinaryContent::MagicCookieDat); QInstaller::appendInt64(&file, BinaryContent::MagicCookieDat);
@ -1382,7 +1386,6 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper
throw Error(tr("Cannot write maintenance tool binary data to %1: %2") throw Error(tr("Cannot write maintenance tool binary data to %1: %2")
.arg(file.fileName(), file.errorString())); .arg(file.fileName(), file.errorString()));
} }
file.setAutoRemove(false);
file.setPermissions(file.permissions() | QFile::WriteUser | QFile::ReadGroup file.setPermissions(file.permissions() | QFile::WriteUser | QFile::ReadGroup
| QFile::ReadOther); | QFile::ReadOther);
} catch (const Error &/*error*/) { } catch (const Error &/*error*/) {

View File

@ -29,6 +29,8 @@
#include "remoteclient.h" #include "remoteclient.h"
#include "remoteclient_p.h" #include "remoteclient_p.h"
#include <QDir>
namespace QInstaller { namespace QInstaller {
RemoteClient *RemoteClient::s_instance = nullptr; RemoteClient *RemoteClient::s_instance = nullptr;
@ -61,6 +63,18 @@ QString RemoteClient::authorizationKey() const
return d->m_key; return d->m_key;
} }
QString RemoteClient::socketPathName(const QString &socketName) const
{
QString socketPathName;
if (socketName.startsWith(QLatin1Char('/'))) {
socketPathName = socketName;
} else {
socketPathName = QDir::tempPath();
socketPathName += QLatin1Char('/') + socketName;
}
return socketPathName;
}
/*! /*!
Initializes the client with \a socketName, with the \a key the client Initializes the client with \a socketName, with the \a key the client
sends to authenticate with the server, \a mode and \a startAs. sends to authenticate with the server, \a mode and \a startAs.
@ -69,7 +83,17 @@ void RemoteClient::init(const QString &socketName, const QString &key, Protocol:
Protocol::StartAs startAs) Protocol::StartAs startAs)
{ {
Q_D(RemoteClient); Q_D(RemoteClient);
// Since Qt 5.12.0, we should determince the full socket path on Unix
// platforms before calling QLocalSocketPrivate::_q_connectToSocket().
// Otherwise the new canonical implementation of QDir::tempPath()
// presents unintended usage of RemoteFileEngine.
#if QT_VERSION >= QT_VERSION_CHECK(5,12,0) && defined(Q_OS_UNIX)
d->init(socketPathName(socketName), key, mode, startAs);
#else
d->init(socketName, key, mode, startAs); d->init(socketName, key, mode, startAs);
#endif
} }
void RemoteClient::setAuthorizationFallbackDisabled(bool disabled) void RemoteClient::setAuthorizationFallbackDisabled(bool disabled)

View File

@ -54,6 +54,7 @@ public:
QString socketName() const; QString socketName() const;
QString authorizationKey() const; QString authorizationKey() const;
QString socketPathName(const QString &socketName) const;
bool isActive() const; bool isActive() const;
void setActive(bool active); void setActive(bool active);

View File

@ -324,9 +324,9 @@ bool RemoteFileEngine::open(QIODevice::OpenMode mode)
{ {
if (connectToServer()) { if (connectToServer()) {
return callRemoteMethod<bool>(QString::fromLatin1(Protocol::QAbstractFileEngineOpen), return callRemoteMethod<bool>(QString::fromLatin1(Protocol::QAbstractFileEngineOpen),
static_cast<qint32>(mode)); static_cast<qint32>(mode | QIODevice::Unbuffered));
} }
return m_fileEngine.open(mode); return m_fileEngine.open(mode | QIODevice::Unbuffered);
} }
/*! /*!

View File

@ -93,7 +93,17 @@ void RemoteServer::start()
void RemoteServer::init(const QString &socketName, const QString &key, Protocol::Mode mode) void RemoteServer::init(const QString &socketName, const QString &key, Protocol::Mode mode)
{ {
Q_D(RemoteServer); Q_D(RemoteServer);
// Since Qt 5.12.0, we should determince the full socket path on Unix
// platforms before calling QLocalSocketPrivate::_q_connectToSocket().
// Otherwise the new canonical implementation of QDir::tempPath()
// presents unintended usage of RemoteFileEngine.
#if QT_VERSION >= QT_VERSION_CHECK(5,12,0) && defined(Q_OS_UNIX)
d->m_socketName = socketPathName(socketName);
#else
d->m_socketName = socketName; d->m_socketName = socketName;
#endif
d->m_key = key; d->m_key = key;
d->m_mode = mode; d->m_mode = mode;
} }
@ -116,6 +126,18 @@ QString RemoteServer::authorizationKey() const
return d->m_key; return d->m_key;
} }
QString RemoteServer::socketPathName(const QString &socketName) const
{
QString socketPathName;
if (socketName.startsWith(QLatin1Char('/'))) {
socketPathName = socketName;
} else {
socketPathName = QDir::tempPath();
socketPathName += QLatin1Char('/') + socketName;
}
return socketPathName;
}
/*! /*!
Restarts the watchdog that tries to kill the server. Restarts the watchdog that tries to kill the server.
*/ */

View File

@ -53,6 +53,7 @@ public:
QString socketName() const; QString socketName() const;
QString authorizationKey() const; QString authorizationKey() const;
QString socketPathName(const QString &socketName) const;
private slots: private slots:
void restartWatchdog(); void restartWatchdog();

View File

@ -382,7 +382,14 @@ void RemoteServerConnection::handleQFSFileEngine(QIODevice *socket, const QStrin
} else if (command == QLatin1String(Protocol::QAbstractFileEngineCopy)) { } else if (command == QLatin1String(Protocol::QAbstractFileEngineCopy)) {
QString newName; QString newName;
data >>newName; data >>newName;
#ifdef Q_OS_LINUX
// QFileSystemEngine::copyFile() is currently unimplemented on Linux,
// copy using QFile instead of directly with QFSFileEngine.
QFile file(m_engine->fileName(QAbstractFileEngine::AbsoluteName));
sendData(socket, file.copy(newName));
#else
sendData(socket, m_engine->copy(newName)); sendData(socket, m_engine->copy(newName));
#endif
} else if (command == QLatin1String(Protocol::QAbstractFileEngineEntryList)) { } else if (command == QLatin1String(Protocol::QAbstractFileEngineEntryList)) {
qint32 filters; qint32 filters;
QStringList filterNames; QStringList filterNames;

View File

@ -119,15 +119,18 @@ private slots:
QVERIFY(core.calculateComponentsToInstall()); QVERIFY(core.calculateComponentsToInstall());
{ {
QTemporaryFile dummy(testDirectory + QLatin1String("/dummy")); QFile dummy(testDirectory + QLatin1String("/dummy"));
dummy.open(); QVERIFY(dummy.open(QIODevice::ReadWrite));
core.runInstaller(); core.runInstaller();
QVERIFY(QDir(testDirectory).exists()); QVERIFY(QDir(testDirectory).exists());
QVERIFY(QFileInfo(dummy.fileName()).exists()); QVERIFY(QFileInfo(dummy.fileName()).exists());
dummy.close();
QVERIFY(dummy.remove());
} }
QDir().rmdir(testDirectory); QVERIFY(QDir().rmdir(testDirectory));
ProgressCoordinator::instance()->reset(); ProgressCoordinator::instance()->reset();
} }

View File

@ -280,7 +280,11 @@ private slots:
// ignore Output from script // ignore Output from script
setExpectedScriptOutput("function receive()"); setExpectedScriptOutput("function receive()");
#if QT_VERSION >= QT_VERSION_CHECK(5,12,0)
QTest::ignoreMessage(QtWarningMsg, "<Unknown File>:38: ReferenceError: foo is not defined");
#else
QTest::ignoreMessage(QtWarningMsg, ":38: ReferenceError: foo is not defined"); QTest::ignoreMessage(QtWarningMsg, ":38: ReferenceError: foo is not defined");
#endif
emiter.produceSignal(); emiter.produceSignal();
const QJSValue value = m_scriptEngine->evaluate(""); const QJSValue value = m_scriptEngine->evaluate("");