summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArttu Tarkiainen <arttu.tarkiainen@qt.io>2021-10-25 15:28:09 +0300
committerArttu Tarkiainen <arttu.tarkiainen@qt.io>2021-10-26 15:29:27 +0300
commit9cf92b4cd273be55c056253b8e4a3a57e13195a8 (patch)
treeff0d4b9ad85fb5b5dea96045fb57ecf40a3ce0b2
parent4066fbebcabbdf591c2fc525343f8f78486457d2 (diff)
Add support for seeking files handled with libarchive
This fixes losing executable bits of files in Zip archives when extracted. Zip archives store file metadata in two ways, partial metadata per-entry and full metadata at the end of archive. IFW's read implementation previously did only streaming without support for seeking archives, which is required to obtain full metadata - meaning the partial metadata was used instead by libarchive's Zip reader. The extracted entries between the two metadata types are not consistent. This change also enables usage of archive formats that cannot be accurately handled with a streaming model, like 7zip which needs to read key data from the end of the file before reading file data from the beginning. Task-number: QTIFW-2372 Change-Id: Ie4ed33040fc52de073546e46d9da726816f47a81 Reviewed-by: Katja Marttila <katja.marttila@qt.io>
-rw-r--r--src/libs/installer/libarchivearchive.cpp116
-rw-r--r--src/libs/installer/libarchivearchive.h11
-rw-r--r--src/libs/installer/libarchivewrapper_p.cpp47
-rw-r--r--src/libs/installer/libarchivewrapper_p.h3
-rw-r--r--src/libs/installer/protocol.h2
-rw-r--r--src/libs/installer/remoteserverconnection.cpp4
-rw-r--r--src/libs/installer/remoteserverconnection_p.h10
-rw-r--r--tests/auto/installer/libarchivearchive/tst_libarchivearchive.cpp12
8 files changed, 190 insertions, 15 deletions
diff --git a/src/libs/installer/libarchivearchive.cpp b/src/libs/installer/libarchivearchive.cpp
index ad5609490..0d2619c7d 100644
--- a/src/libs/installer/libarchivearchive.cpp
+++ b/src/libs/installer/libarchivearchive.cpp
@@ -32,6 +32,8 @@
#include "errors.h"
#include "globals.h"
+#include <stdio.h>
+
#include <QApplication>
#include <QFileInfo>
#include <QDir>
@@ -93,7 +95,11 @@ void ExtractWorker::extract(const QString &dirPath, const quint64 totalFiles)
foreach (const QString &directory, createdDirs)
emit currentEntryChanged(directory);
- int status = archive_read_open(reader.get(), this, nullptr, readCallback, nullptr);
+ archive_read_set_read_callback(reader.get(), readCallback);
+ archive_read_set_callback_data(reader.get(), this);
+ archive_read_set_seek_callback(reader.get(), seekCallback);
+
+ int status = archive_read_open1(reader.get());
if (status != ARCHIVE_OK) {
m_status = Failure;
emit finished(QLatin1String(archive_error_string(reader.get())));
@@ -142,6 +148,12 @@ void ExtractWorker::addDataBlock(const QByteArray buffer)
emit dataReadyForRead();
}
+void ExtractWorker::onFilePositionChanged(qint64 pos)
+{
+ m_lastPos = pos;
+ emit seekReady();
+}
+
void ExtractWorker::cancel()
{
m_status = Canceled;
@@ -177,6 +189,26 @@ ssize_t ExtractWorker::readCallback(archive *reader, void *caller, const void **
return buffer->size();
}
+la_int64_t ExtractWorker::seekCallback(archive *reader, void *caller, la_int64_t offset, int whence)
+{
+ Q_UNUSED(reader)
+
+ ExtractWorker *obj;
+ if (!(obj = static_cast<ExtractWorker *>(caller)))
+ return ARCHIVE_FATAL;
+
+ emit obj->seekRequested(static_cast<qint64>(offset), whence);
+
+ {
+ QEventLoop loop;
+ QTimer::singleShot(30000, &loop, &QEventLoop::quit);
+ connect(obj, &ExtractWorker::seekReady, &loop, &QEventLoop::quit);
+ loop.exec();
+ }
+
+ return static_cast<la_int64_t>(obj->m_lastPos);
+}
+
bool ExtractWorker::writeEntry(archive *reader, archive *writer, archive_entry *entry)
{
int status;
@@ -235,6 +267,13 @@ bool ExtractWorker::writeEntry(archive *reader, archive *writer, archive_entry *
*/
/*!
+ \fn QInstaller::LibArchiveArchive::seekRequested(qint64 offset, int whence)
+
+ Emitted when the worker object requires a seek to \a offset to continue extracting.
+ The \a whence value defines the starting position for \a offset.
+*/
+
+/*!
\fn QInstaller::LibArchiveArchive::workerFinished()
Emitted when the worker object finished extracting an archive.
@@ -267,6 +306,12 @@ bool ExtractWorker::writeEntry(archive *reader, archive *writer, archive_entry *
*/
/*!
+ \fn QInstaller::LibArchiveArchive::workerAboutToSetFilePosition(qint64 pos)
+
+ Emitted when the worker object is about to set new file position \a pos.
+*/
+
+/*!
Constructs an archive object representing an archive file
specified by \a filename with \a parent as the parent object.
*/
@@ -377,7 +422,7 @@ bool LibArchiveArchive::extract(const QString &dirPath, const quint64 totalFiles
foreach (const QString &directory, createdDirs)
emit currentEntryChanged(directory);
- int status = archive_read_open(reader.get(), m_data, nullptr, readCallback, nullptr);
+ int status = archiveReadOpenWithCallbacks(reader.get());
if (status != ARCHIVE_OK)
throw Error(QLatin1String(archive_error_string(reader.get())));
@@ -496,7 +541,7 @@ QVector<ArchiveEntry> LibArchiveArchive::list()
QVector<ArchiveEntry> entries;
try {
- int status = archive_read_open(reader.get(), m_data, nullptr, readCallback, nullptr);
+ int status = archiveReadOpenWithCallbacks(reader.get());
if (status != ARCHIVE_OK)
throw Error(QLatin1String(archive_error_string(reader.get())));
@@ -537,7 +582,7 @@ bool LibArchiveArchive::isSupported()
configureReader(reader.get());
try {
- const int status = archive_read_open(reader.get(), m_data, nullptr, readCallback, nullptr);
+ const int status = archiveReadOpenWithCallbacks(reader.get());
if (status != ARCHIVE_OK)
throw Error(QLatin1String(archive_error_string(reader.get())));
} catch (const Error &e) {
@@ -575,6 +620,14 @@ void LibArchiveArchive::workerSetDataAtEnd()
}
/*!
+ Signals the worker object that the client file position changed to \a pos.
+*/
+void LibArchiveArchive::workerSetFilePosition(qint64 pos)
+{
+ emit workerAboutToSetFilePosition(pos);
+}
+
+/*!
Cancels the extract in progress for the worker object.
*/
void LibArchiveArchive::workerCancel()
@@ -672,9 +725,11 @@ void LibArchiveArchive::initExtractWorker()
connect(this, &LibArchiveArchive::workerAboutToExtract, &m_worker, &ExtractWorker::extract);
connect(this, &LibArchiveArchive::workerAboutToAddDataBlock, &m_worker, &ExtractWorker::addDataBlock);
connect(this, &LibArchiveArchive::workerAboutToSetDataAtEnd, &m_worker, &ExtractWorker::dataAtEnd);
+ connect(this, &LibArchiveArchive::workerAboutToSetFilePosition, &m_worker, &ExtractWorker::onFilePositionChanged);
connect(this, &LibArchiveArchive::workerAboutToCancel, &m_worker, &ExtractWorker::cancel);
connect(&m_worker, &ExtractWorker::dataBlockRequested, this, &LibArchiveArchive::dataBlockRequested);
+ connect(&m_worker, &ExtractWorker::seekRequested, this, &LibArchiveArchive::seekRequested);
connect(&m_worker, &ExtractWorker::finished, this, &LibArchiveArchive::onWorkerFinished);
connect(&m_worker, &ExtractWorker::currentEntryChanged, this, &LibArchiveArchive::currentEntryChanged);
@@ -684,6 +739,20 @@ void LibArchiveArchive::initExtractWorker()
}
/*!
+ \internal
+
+ Sets default callbacks for archive \a reader and opens for reading.
+*/
+int LibArchiveArchive::archiveReadOpenWithCallbacks(archive *reader)
+{
+ archive_read_set_read_callback(reader, readCallback);
+ archive_read_set_callback_data(reader, m_data);
+ archive_read_set_seek_callback(reader, seekCallback);
+
+ return archive_read_open1(reader);
+}
+
+/*!
Writes the current \a entry header, then pulls data from the archive \a reader
and writes it to the \a writer handle.
*/
@@ -767,6 +836,43 @@ ssize_t LibArchiveArchive::readCallback(archive *reader, void *archiveData, cons
}
/*!
+ \internal
+
+ Seeks to specified \a offset in the file device in \a archiveData and returns the position.
+ Possible \a whence values are \c SEEK_SET, \c SEEK_CUR, and \c SEEK_END. Returns
+ \c ARCHIVE_FATAL if the seek fails.
+*/
+la_int64_t LibArchiveArchive::seekCallback(archive *reader, void *archiveData, la_int64_t offset, int whence)
+{
+ Q_UNUSED(reader)
+
+ ArchiveData *data;
+ if (!(data = static_cast<ArchiveData *>(archiveData)))
+ return ARCHIVE_FATAL;
+
+ if (!data->file.isOpen() || data->file.isSequential())
+ return ARCHIVE_FATAL;
+
+ switch (whence) {
+ case SEEK_SET: // moves file pointer position to the beginning of the file
+ if (!data->file.seek(offset))
+ return ARCHIVE_FATAL;
+ break;
+ case SEEK_CUR: // moves file pointer position to given location
+ if (!data->file.seek(data->file.pos() + offset))
+ return ARCHIVE_FATAL;
+ break;
+ case SEEK_END: // moves file pointer position to the end of file
+ if (!data->file.seek(data->file.size() + offset))
+ return ARCHIVE_FATAL;
+ break;
+ default:
+ return ARCHIVE_FATAL;
+ }
+ return data->file.pos();
+}
+
+/*!
Returns the \a path to a file or directory, without the Win32 namespace prefix.
On Unix platforms, the \a path is returned unaltered.
*/
@@ -794,7 +900,7 @@ quint64 LibArchiveArchive::totalFiles()
configureReader(reader.get());
try {
- int status = archive_read_open(reader.get(), m_data, nullptr, readCallback, nullptr);
+ int status = archiveReadOpenWithCallbacks(reader.get());
if (status != ARCHIVE_OK)
throw Error(QLatin1String(archive_error_string(reader.get())));
diff --git a/src/libs/installer/libarchivearchive.h b/src/libs/installer/libarchivearchive.h
index e0281e655..796069ed1 100644
--- a/src/libs/installer/libarchivearchive.h
+++ b/src/libs/installer/libarchivearchive.h
@@ -64,12 +64,15 @@ public:
public Q_SLOTS:
void extract(const QString &dirPath, const quint64 totalFiles);
void addDataBlock(const QByteArray buffer);
+ void onFilePositionChanged(qint64 pos);
void cancel();
Q_SIGNALS:
void dataBlockRequested();
void dataAtEnd();
void dataReadyForRead();
+ void seekRequested(qint64 offset, int whence);
+ void seekReady();
void finished(const QString &errorString = QString());
void currentEntryChanged(const QString &filename);
@@ -77,10 +80,12 @@ Q_SIGNALS:
private:
static ssize_t readCallback(archive *reader, void *caller, const void **buff);
+ static la_int64_t seekCallback(archive *reader, void *caller, la_int64_t offset, int whence);
bool writeEntry(archive *reader, archive *writer, archive_entry *entry);
private:
QByteArray m_buffer;
+ qint64 m_lastPos = 0;
Status m_status;
};
@@ -107,16 +112,19 @@ public:
void workerExtract(const QString &dirPath, const quint64 totalFiles);
void workerAddDataBlock(const QByteArray buffer);
void workerSetDataAtEnd();
+ void workerSetFilePosition(qint64 pos);
void workerCancel();
ExtractWorker::Status workerStatus() const;
Q_SIGNALS:
void dataBlockRequested();
+ void seekRequested(qint64 offset, int whence);
void workerFinished();
void workerAboutToExtract(const QString &dirPath, const quint64 totalFiles);
void workerAboutToAddDataBlock(const QByteArray buffer);
void workerAboutToSetDataAtEnd();
+ void workerAboutToSetFilePosition(qint64 pos);
void workerAboutToCancel();
public Q_SLOTS:
@@ -133,11 +141,14 @@ private:
void initExtractWorker();
+ int archiveReadOpenWithCallbacks(archive *reader);
bool writeEntry(archive *reader, archive *writer, archive_entry *entry);
static qint64 readData(QFile *file, char *data, qint64 maxSize);
static ssize_t readCallback(archive *reader, void *archiveData, const void **buff);
+ static la_int64_t seekCallback(archive *reader, void *archiveData, la_int64_t offset, int whence);
+
static QString pathWithoutNamespace(const QString &path);
quint64 totalFiles();
diff --git a/src/libs/installer/libarchivewrapper_p.cpp b/src/libs/installer/libarchivewrapper_p.cpp
index 5509812cf..942e948e8 100644
--- a/src/libs/installer/libarchivewrapper_p.cpp
+++ b/src/libs/installer/libarchivewrapper_p.cpp
@@ -247,6 +247,10 @@ void LibArchiveWrapperPrivate::processSignals()
emit completedChanged(completed, total);
} else if (name == QLatin1String(Protocol::AbstractArchiveSignalDataBlockRequested)) {
emit dataBlockRequested();
+ } else if (name == QLatin1String(Protocol::AbstractArchiveSignalSeekRequested)) {
+ const qint64 offset = receivedSignals.takeFirst().value<qint64>();
+ const int whence = receivedSignals.takeFirst().value<int>();
+ emit seekRequested(offset, whence);
} else if (name == QLatin1String(Protocol::AbstractArchiveSignalWorkerFinished)) {
emit remoteWorkerFinished();
}
@@ -294,6 +298,35 @@ void LibArchiveWrapperPrivate::onDataBlockRequested()
}
/*!
+ Seeks to specified \a offset in the underlying file device. Possible \a whence
+ values are \c SEEK_SET, \c SEEK_CUR, and \c SEEK_END.
+*/
+void LibArchiveWrapperPrivate::onSeekRequested(qint64 offset, int whence)
+{
+ QFile *const file = &m_archive.m_data->file;
+ if (!file->isOpen() || file->isSequential()) {
+ qCWarning(QInstaller::lcInstallerInstallLog) << file->errorString();
+ setClientFilePosition(ARCHIVE_FATAL);
+ return;
+ }
+ bool success = false;
+ switch (whence) {
+ case SEEK_SET: // moves file pointer position to the beginning of the file
+ success = file->seek(offset);
+ break;
+ case SEEK_CUR: // moves file pointer position to given location
+ success = file->seek(file->pos() + offset);
+ break;
+ case SEEK_END: // moves file pointer position to the end of file
+ success = file->seek(file->size() + offset);
+ break;
+ default:
+ break;
+ }
+ setClientFilePosition(success ? file->pos() : ARCHIVE_FATAL);
+}
+
+/*!
Starts the timer to process server-side signals and connects handler
signals for the matching signals of the wrapper object.
*/
@@ -310,6 +343,8 @@ void LibArchiveWrapperPrivate::init()
QObject::connect(this, &LibArchiveWrapperPrivate::dataBlockRequested,
this, &LibArchiveWrapperPrivate::onDataBlockRequested);
+ QObject::connect(this, &LibArchiveWrapperPrivate::seekRequested,
+ this, &LibArchiveWrapperPrivate::onSeekRequested);
}
/*!
@@ -338,6 +373,18 @@ void LibArchiveWrapperPrivate::setClientDataAtEnd()
}
/*!
+ Calls a remote method to set new file position \a pos.
+*/
+void LibArchiveWrapperPrivate::setClientFilePosition(qint64 pos)
+{
+ if (connectToServer()) {
+ m_lock.lockForWrite();
+ callRemoteMethod(QLatin1String(Protocol::AbstractArchiveSetFilePosition), pos, dummy);
+ m_lock.unlock();
+ }
+}
+
+/*!
Calls a remote method to retrieve and return the status of
the extract worker on a server process.
*/
diff --git a/src/libs/installer/libarchivewrapper_p.h b/src/libs/installer/libarchivewrapper_p.h
index ea8409da0..722c91f17 100644
--- a/src/libs/installer/libarchivewrapper_p.h
+++ b/src/libs/installer/libarchivewrapper_p.h
@@ -66,6 +66,7 @@ Q_SIGNALS:
void currentEntryChanged(const QString &filename);
void completedChanged(const quint64 completed, const quint64 total);
void dataBlockRequested();
+ void seekRequested(qint64 offset, int whence);
void remoteWorkerFinished();
public Q_SLOTS:
@@ -74,12 +75,14 @@ public Q_SLOTS:
private Q_SLOTS:
void processSignals();
void onDataBlockRequested();
+ void onSeekRequested(qint64 offset, int whence);
private:
void init();
void addDataBlock(const QByteArray &buffer);
void setClientDataAtEnd();
+ void setClientFilePosition(qint64 pos);
ExtractWorker::Status workerStatus() const;
private:
diff --git a/src/libs/installer/protocol.h b/src/libs/installer/protocol.h
index c7eb9a308..65241e00b 100644
--- a/src/libs/installer/protocol.h
+++ b/src/libs/installer/protocol.h
@@ -177,6 +177,7 @@ const char AbstractArchiveIsSupported[] = "AbstractArchive::isSupported";
const char AbstractArchiveSetCompressionLevel[] = "AbstractArchive::setCompressionLevel";
const char AbstractArchiveAddDataBlock[] = "AbstractArchive::addDataBlock";
const char AbstractArchiveSetClientDataAtEnd[] = "AbstractArchive::setClientDataAtEnd";
+const char AbstractArchiveSetFilePosition[] = "AbstractArchive::setFilePosition";
const char AbstractArchiveWorkerStatus[] = "AbstractArchive::workerStatus";
const char AbstractArchiveCancel[] = "AbstractArchive::cancel";
@@ -184,6 +185,7 @@ const char GetAbstractArchiveSignals[] = "GetAbstractArchiveSignals";
const char AbstractArchiveSignalCurrentEntryChanged[] = "AbstractArchive::currentEntryChanged";
const char AbstractArchiveSignalCompletedChanged[] = "AbstractArchive::completedChanged";
const char AbstractArchiveSignalDataBlockRequested[] = "AbstractArchive::dataBlockRequested";
+const char AbstractArchiveSignalSeekRequested[] = "AbstractArchive::seekRequested";
const char AbstractArchiveSignalWorkerFinished[] = "AbstractArchive::workerFinished";
} // namespace Protocol
diff --git a/src/libs/installer/remoteserverconnection.cpp b/src/libs/installer/remoteserverconnection.cpp
index 5d72f834d..9e141ea48 100644
--- a/src/libs/installer/remoteserverconnection.cpp
+++ b/src/libs/installer/remoteserverconnection.cpp
@@ -596,6 +596,10 @@ void RemoteServerConnection::handleArchive(QIODevice *socket, const QString &com
archive->workerAddDataBlock(buff);
} else if (command == QLatin1String(Protocol::AbstractArchiveSetClientDataAtEnd)) {
archive->workerSetDataAtEnd();
+ } else if (command == QLatin1String(Protocol::AbstractArchiveSetFilePosition)) {
+ qint64 pos;
+ data >> pos;
+ archive->workerSetFilePosition(pos);
} else if (command == QLatin1String(Protocol::AbstractArchiveWorkerStatus)) {
sendData(socket, static_cast<qint32>(archive->workerStatus()));
} else if (command == QLatin1String(Protocol::AbstractArchiveCancel)) {
diff --git a/src/libs/installer/remoteserverconnection_p.h b/src/libs/installer/remoteserverconnection_p.h
index dc6d794b6..977a64711 100644
--- a/src/libs/installer/remoteserverconnection_p.h
+++ b/src/libs/installer/remoteserverconnection_p.h
@@ -143,6 +143,8 @@ private:
this, &AbstractArchiveSignalReceiver::onCompletedChanged);
connect(archive, &LibArchiveArchive::dataBlockRequested,
this, &AbstractArchiveSignalReceiver::onDataBlockRequested);
+ connect(archive, &LibArchiveArchive::seekRequested,
+ this, &AbstractArchiveSignalReceiver::onSeekRequested);
connect(archive, &LibArchiveArchive::workerFinished,
this, &AbstractArchiveSignalReceiver::onWorkerFinished);
}
@@ -169,6 +171,14 @@ private Q_SLOTS:
m_receivedSignals.append(QLatin1String(Protocol::AbstractArchiveSignalDataBlockRequested));
}
+ void onSeekRequested(qint64 offset, int whence)
+ {
+ QMutexLocker _(&m_lock);
+ m_receivedSignals.append(QLatin1String(Protocol::AbstractArchiveSignalSeekRequested));
+ m_receivedSignals.append(offset);
+ m_receivedSignals.append(whence);
+ }
+
void onWorkerFinished()
{
QMutexLocker _(&m_lock);
diff --git a/tests/auto/installer/libarchivearchive/tst_libarchivearchive.cpp b/tests/auto/installer/libarchivearchive/tst_libarchivearchive.cpp
index f12de22a4..8034f9556 100644
--- a/tests/auto/installer/libarchivearchive/tst_libarchivearchive.cpp
+++ b/tests/auto/installer/libarchivearchive/tst_libarchivearchive.cpp
@@ -44,6 +44,7 @@ private slots:
void initTestCase()
{
m_file.path = "valid";
+ m_file.permissions_mode = 0666;
m_file.compressedSize = 0; // unused
m_file.uncompressedSize = 5242880;
m_file.isDirectory = false;
@@ -79,7 +80,7 @@ private slots:
QVector<ArchiveEntry> files = archive.list();
QCOMPARE(files.count(), 1);
- QVERIFY(entriesMatch(files.first(), m_file));
+ QCOMPARE(files.first(), m_file);
}
void testCreateArchive_data()
@@ -167,15 +168,6 @@ private:
QTest::newRow("xz compressed tar archive") << ".tar.xz";
}
- bool entriesMatch(const ArchiveEntry &lhs, const ArchiveEntry &rhs)
- {
- return lhs.path == rhs.path
- && lhs.utcTime == rhs.utcTime
- && lhs.isDirectory == rhs.isDirectory
- && lhs.compressedSize == rhs.compressedSize
- && lhs.uncompressedSize == rhs.uncompressedSize;
- }
-
QString tempSourceFile(const QByteArray &data, const QString &templateName = QString())
{
QTemporaryFile source;