Commit 987b4830 authored by Adrián Chaves Fernández (Gallaecio)'s avatar Adrián Chaves Fernández (Gallaecio) Committed by Adrián Chaves (Gallaecio)
Browse files

Use KArchive instead of libarchive and our own object-oriented wrapper for it

parent ff67452a
......@@ -48,7 +48,7 @@ add_definitions(#-DQT_NO_CAST_TO_ASCII
)
find_package(Qt5 5.2.0 REQUIRED CONFIG COMPONENTS Core Test Network Concurrent DBus Widgets)
find_package(LibArchive REQUIRED)
find_package(KF5Archive REQUIRED)
find_package(Sqlite REQUIRED)
find_package(PolkitQt5-1 REQUIRED)
......
#
# Try to find libarchive.
#
# Once done, this will define:
#
# LIBARCHIVE_FOUND — System has libarchive.
# LIBARCHIVE_INCLUDE_DIR — libarchive include directory.
# LIBARCHIVE_LIBRARY — Libraries to be linked for libarchive.
#
# Copyright © 2006, Pino Toscano, <toscano.pino@tiscali.it>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the <organization> nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
include(CheckLibraryExists)
if (LIBARCHIVE_LIBRARY AND LIBARCHIVE_INCLUDE_DIR)
# In cache already.
set(LIBARCHIVE_FOUND TRUE)
else (LIBARCHIVE_LIBRARY AND LIBARCHIVE_INCLUDE_DIR)
find_path(LIBARCHIVE_INCLUDE_DIR archive.h
${GNUWIN32_DIR}/include
)
find_library(LIBARCHIVE_LIBRARY NAMES archive libarchive archive2 libarchive2
PATHS
${GNUWIN32_DIR}/lib
)
if (LIBARCHIVE_LIBRARY)
CHECK_LIBRARY_EXISTS(${LIBARCHIVE_LIBRARY} archive_write_set_compression_gzip "" HAVE_LIBARCHIVE_GZIP_SUPPORT)
endif (LIBARCHIVE_LIBRARY)
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibArchive DEFAULT_MSG LIBARCHIVE_INCLUDE_DIR LIBARCHIVE_LIBRARY HAVE_LIBARCHIVE_GZIP_SUPPORT)
# Ensure that they are cached.
set(LIBARCHIVE_INCLUDE_DIR ${LIBARCHIVE_INCLUDE_DIR} CACHE INTERNAL "libarchive include path")
set(LIBARCHIVE_LIBRARY ${LIBARCHIVE_LIBRARY} CACHE INTERNAL "Libraries needed to use libarchive")
endif (LIBARCHIVE_LIBRARY AND LIBARCHIVE_INCLUDE_DIR)
1. Introduction
An Object-oriented interface to libarchive
(http://code.google.com/p/libarchive/) is included in the library, to manage
accesses to package archives.
It is meant to be used by akabei so only the needed features were implemented.
2. Implementation details
Libarchive basically works this way: first you need to create an archive
structure for read or write accesses to the archive. Let's focus on read
accesses, which are used the most in our interface.
Using the archive structure, you can iterate over the archive files, obtaining
an archive_entry structure for each.
Given the current entry, you can read the contents, extract it, get information
about it, and some other operations.
All this is managed using states: while iterating, the archive structure changes
to reflect the current position in the archive. The archive_entry structures
work in a similar way. The immediate consequence is that you cannot directly
access to some archive entry (even if you know its name, or separately save the
archive_entry structure): the only way to access a given entry is to iterate over
all the entries, putting all the structures in the right state, until you arrive
to the desired one.
This creates efficiency issues: if an archive contains 1000 entries and I want
to extract only 10, my only choice is to call the extract() service 10 times;
but this function will iterate over a good part of the archive entries before
finding the one I requested, except for the cases in which I'm particularly
lucky to find all of my entries at the beginning.
Since scenarios similar to that depicted above aren't uncommon in akabei,
functors have been employed to circumvent the issue. Any operation that need to
access a subset of the archive entries is described by a functor class (see
archiveoperations.h) derived by the abstract class EntryHandlerFunctor. Each
call to operator() of the class performs the needed service on just one archive
entry (the ArchiveEntry passed as a parameter), and returns a boolean value to
indicate success or failure. In future implementations more return values could
be added, e.g. to decide whether a failure should stop the entire execution or
only the current entry will be skipped.
Other parameters needed for computation can be passed in advance using the class
constructor, while additional methods can be defined to get results, if present.
To start the execution, after defining the functor, developers need to call the
handleEntries service in ArchiveHandler, passing the functor object as the only
parameter. The service will perform a simple iteration over all the archive
entries, constructs the ArchiveEntry object for each, and call the given
functor.
Considering again the unefficient scenario above, in this case the developer
will define a functor that will extract only the 10 entries needed, using a
second version of the extract() function implemented just for these cases that
doesn't do a second useless iteration in the archive, and just ignore everything
else. Thus we accomplish the same thing with just one iteration in the archive.
\ No newline at end of file
......@@ -20,8 +20,6 @@ md5.c
AbstractSQLiteConnection.cpp
SQLiteConnection.cpp
PolKitSQLiteConnection.cpp
libarchive++/archivehandler.cpp
libarchive++/polkitarchivehandler.cpp
)
set(AKABEI_OPERATION_SRCS
......@@ -63,13 +61,6 @@ PolKitSQLiteConnection.h
md5.h
)
set(AKABEI_ARCHIVE_HDRS
libarchive++/archivehandler.h
libarchive++/archiveentry.h
libarchive++/archiveoperations.h
libarchive++/polkitarchivehandler.h
)
set(AKABEI_OPERATION_HDRS
operations/akabeiplainreinstalloperation.h
operations/akabeipolkitreinstalloperation.h
......@@ -103,14 +94,13 @@ set_target_properties(akabeicore PROPERTIES VERSION ${MAJOR_AKABEI_VERSION}.${MI
target_link_libraries(akabeicore Qt5::Core
Qt5::DBus
Qt5::Concurrent
${LIBARCHIVE_LIBRARY}
KF5::Archive
${SQLITE_LIBRARIES}
${POLKITQT-1_LIBRARIES})
install(TARGETS akabeicore DESTINATION ${LIB_INSTALL_DIR})
install(FILES ${AKABEI_CORE_HDRS} DESTINATION ${INCLUDES_INSTALL_DIR})
install(FILES ${AKABEI_OPERATION_HDRS} DESTINATION ${INCLUDES_INSTALL_DIR}/operations)
install(FILES ${AKABEI_ARCHIVE_HDRS} DESTINATION ${INCLUDES_INSTALL_DIR}/libarchive++)
add_subdirectory(benchmarks)
......
......@@ -20,11 +20,13 @@
#include <akabeigroup_p.h>
#include <akabeiquery.h>
#include <QCryptographicHash>
#include <QHash>
#include <QtConcurrentRun>
#include <QtConcurrentMap>
#include <QDir>
#include <QFutureWatcher>
#include <QQueue>
#include <QVariant>
#include <QTranslator>
#include <QCoreApplication>
......@@ -35,13 +37,14 @@
#include <memory>
#include <libarchive++/archivehandler.h>
#include <akabeihook.h>
#include "akabeilog.h"
#include <fcntl.h>
#include <polkit-qt5-1/polkitqt1-authority.h>
#include <KF5/KArchive/KTar>
Q_DECLARE_METATYPE(QUuid)
Q_DECLARE_METATYPE(QList< Akabei::Package* >)
Q_DECLARE_METATYPE(QList< Akabei::Group* >)
......@@ -754,122 +757,140 @@ Package* Backend::loadPackageFromFile(const QString& path)
std::unique_ptr<Package> retpackage(new Package);
retpackage->setPathToArchive(path);
try {
ArchiveHandler pkg(path);
/* If full is false, only read through the archive until we find our needed
* metadata. If it is true, read through the entire archive, which serves
* as a verfication of integrity and allows us to create the filelist. */
KTar pkg(path);
if (!pkg.open(QIODevice::ReadOnly)) {
return nullptr;
}
const KArchiveFile* pkginfoFile = pkg.directory()->file(
QStringLiteral(".PKGINFO"));
if (pkginfoFile == nullptr) {
return nullptr;
}
for (auto&& line : QString::fromUtf8(pkginfoFile->data()).split('\n')) {
if (!line.contains('=') || line.startsWith('#')) {
// Skip this line, not interesting
continue;
}
foreach (QString const& line, pkg.readTextFile(".PKGINFO").split('\n')) {
if (!line.contains('=') || line.startsWith('#')) {
// Skip this line, not interesting
continue;
}
// Separate the two strings
QStringList splitted = line.split('=');
QString key = splitted.first();
// There might be an equal, so
splitted.removeAt(0);
QString value = splitted.join(QChar('='));
// Just remove all spaces ignorantly here
key = key.remove(' ');
// Recheck if it's commented over
if (key.startsWith('#')) {
// Pass by
continue;
}
// Separate the two strings
QStringList splitted = line.split('=');
QString key = splitted.first();
// There might be an equal, so
splitted.removeAt(0);
QString value = splitted.join(QChar('='));
// Just remove all spaces ignorantly here
key = key.remove(' ');
// Recheck if it's commented over
if (key.startsWith('#')) {
// Pass by
// Strip whitespaces here instead
value = value.simplified();
// Ok, now let's analyze
if (key == QLatin1String("pkgname")) {
retpackage->d_func()->setName(value);
} else if (key == QLatin1String("pkgver")) {
retpackage->d_func()->setVersion(Package::Version(value.toUtf8()));
} else if (key == QLatin1String("pkgdesc")) {
retpackage->d_func()->setDescription(value);
} else if (key == QLatin1String("group")) {
retpackage->d_func()->addGroup(d->groupPool->group(value));
} else if (key == QLatin1String("url")) {
retpackage->d_func()->setUrl(QUrl(value));
} else if (key == QLatin1String("license")) {
retpackage->d_func()->addLicense(value);
} else if (key == QLatin1String("builddate")) {
retpackage->d_func()->setBuildDate(QDateTime::fromTime_t(value.toUInt()));
} else if (key == QLatin1String("packager")) {
retpackage->d_func()->setPackager(value);
} else if (key == QLatin1String("arch")) {
retpackage->d_func()->setArch(value);
} else if (key == QLatin1String("size")) {
retpackage->d_func()->setSize(value.toInt());
} else if (key == QLatin1String("depend")) {
retpackage->d_func()->addDependency(value);
} else if (key == QLatin1String("makedepend")) {
retpackage->d_func()->addMakeDependency(value);
} else if (key == QLatin1String("optdepend")) {
retpackage->d_func()->addOptDependency(value);
} else if (key == QLatin1String("conflict")) {
retpackage->d_func()->addConflict(value);
} else if (key == QLatin1String("replaces")) {
retpackage->d_func()->addReplaces(value);
} else if (key == QLatin1String("provides")) {
retpackage->d_func()->addProvider(value);
} else if (key == QLatin1String("hook")) {
retpackage->d_func()->addHook(value);
} else if (key == QLatin1String("backup")) {
// TODO: make the application know why the md5 function failed
const KArchiveFile* backupFile = pkg.directory()->file(value);
if (backupFile == nullptr) {
continue;
}
// Strip whitespaces here instead
value = value.simplified();
// Ok, now let's analyze
if (key == QLatin1String("pkgname")) {
retpackage->d_func()->setName(value);
} else if (key == QLatin1String("pkgver")) {
retpackage->d_func()->setVersion(Package::Version(value.toUtf8()));
} else if (key == QLatin1String("pkgdesc")) {
retpackage->d_func()->setDescription(value);
} else if (key == QLatin1String("group")) {
retpackage->d_func()->addGroup(d->groupPool->group(value));
} else if (key == QLatin1String("url")) {
retpackage->d_func()->setUrl(QUrl(value));
} else if (key == QLatin1String("license")) {
retpackage->d_func()->addLicense(value);
} else if (key == QLatin1String("builddate")) {
retpackage->d_func()->setBuildDate(QDateTime::fromTime_t(value.toUInt()));
} else if (key == QLatin1String("packager")) {
retpackage->d_func()->setPackager(value);
} else if (key == QLatin1String("arch")) {
retpackage->d_func()->setArch(value);
} else if (key == QLatin1String("size")) {
retpackage->d_func()->setSize(value.toInt());
} else if (key == QLatin1String("depend")) {
retpackage->d_func()->addDependency(value);
} else if (key == QLatin1String("makedepend")) {
retpackage->d_func()->addMakeDependency(value);
} else if (key == QLatin1String("optdepend")) {
retpackage->d_func()->addOptDependency(value);
} else if (key == QLatin1String("conflict")) {
retpackage->d_func()->addConflict(value);
} else if (key == QLatin1String("replaces")) {
retpackage->d_func()->addReplaces(value);
} else if (key == QLatin1String("provides")) {
retpackage->d_func()->addProvider(value);
} else if (key == QLatin1String("hook")) {
retpackage->d_func()->addHook(value);
} else if (key == QLatin1String("backup")) {
/*
* TODO: make the application know why the md5 function failed
*/
QString md5 = pkg.md5(value);
if (!md5.isEmpty()) {
retpackage->d_func()->addBackup(value, md5);
}
} else if (key == QLatin1String("ultimateowner")) {
retpackage->d_func()->addUltimatelyOwnedFile(value);
} else if (key == QLatin1String("makepkgopt")) {
// retpackage->d_func()->arch = value;
} else if (key == QLatin1String("screenshot")) {
retpackage->d_func()->setScreenshot(QUrl(value));
} else if (key == QLatin1String("mimetype")) {
retpackage->d_func()->addMimetype(value);
} else if (key == QLatin1String("hook")) {
retpackage->d_func()->addHook(value);
} else if (key == QLatin1String("epoch")) {
retpackage->d_func()->setEpoch(value.toInt());
} else if (key == QLatin1String("gitfolder")) {
retpackage->d_func()->setGitFolder(value);
} else if (key == QLatin1String("gitrepository")) {
retpackage->d_func()->setGitRepo(value);
} else if (key == QLatin1String("gitbranch")) {
retpackage->d_func()->setGitBranch(value);
} else if (key == QLatin1String("gitrepo")) { //This is legacy support
QStringList split = value.split(QStringLiteral("-"));
if (!split.isEmpty()) {
retpackage->d_func()->setGitRepo(split.takeFirst());
retpackage->d_func()->setGitBranch(split.isEmpty() ? QStringLiteral("master") : split.first());
}
QString md5(QCryptographicHash::hash(
backupFile->data(), QCryptographicHash::Md5));
if (!md5.isEmpty()) {
retpackage->d_func()->addBackup(value, md5);
}
} else if (key == QLatin1String("ultimateowner")) {
retpackage->d_func()->addUltimatelyOwnedFile(value);
} else if (key == QLatin1String("makepkgopt")) {
// retpackage->d_func()->arch = value;
} else if (key == QLatin1String("screenshot")) {
retpackage->d_func()->setScreenshot(QUrl(value));
} else if (key == QLatin1String("mimetype")) {
retpackage->d_func()->addMimetype(value);
} else if (key == QLatin1String("hook")) {
retpackage->d_func()->addHook(value);
} else if (key == QLatin1String("epoch")) {
retpackage->d_func()->setEpoch(value.toInt());
} else if (key == QLatin1String("gitfolder")) {
retpackage->d_func()->setGitFolder(value);
} else if (key == QLatin1String("gitrepository")) {
retpackage->d_func()->setGitRepo(value);
} else if (key == QLatin1String("gitbranch")) {
retpackage->d_func()->setGitBranch(value);
} else if (key == QLatin1String("gitrepo")) { //This is legacy support
QStringList split = value.split(QStringLiteral("-"));
if (!split.isEmpty()) {
retpackage->d_func()->setGitRepo(split.takeFirst());
retpackage->d_func()->setGitBranch(split.isEmpty() ? QStringLiteral("master") : split.first());
}
}
}
if (pkg.getEntries().contains(QStringLiteral(".INSTALL"))) {
retpackage->d_func()->setScriptlet(pkg.readTextFile(QStringLiteral(".INSTALL")));
}
const KArchiveFile* installFile = pkg.directory()->file(
QStringLiteral(".INSTALL"));
if (installFile != nullptr) {
retpackage->d_func()->setScriptlet(
QString::fromUtf8(installFile->data()));
}
int isize = 0;
foreach (QString const& entry, pkg.getEntries()) {
retpackage->d_func()->addFile(entry);
isize += pkg.getEntrySize(entry);
QQueue<QPair<const KArchiveDirectory*, QString>> dirQueue;
dirQueue.enqueue({pkg.directory(), QString()});
int isize = 0;
while (!dirQueue.isEmpty()) {
auto pair = dirQueue.dequeue();
for (auto&& entry : pair.first->entries()) {
const KArchiveEntry* archiveEntry = pkg.directory()->entry(entry);
QString entryPath(pair.second + entry + QStringLiteral("/"));
if (archiveEntry->isFile()) {
isize +=
dynamic_cast<const KArchiveFile*>(archiveEntry)->size();
} else {
dirQueue.enqueue({
dynamic_cast<const KArchiveDirectory*>(archiveEntry),
entryPath
});
}
retpackage->d_func()->addFile(entryPath);
}
retpackage->d_func()->setInstalledSize(isize);
} catch (ArchiveException& e) {
akabeiDebug() << "Error during access in archive: " << e.what();
return nullptr;
}
retpackage->d_func()->setInstalledSize(isize);
if (retpackage->name().isEmpty()) {
return nullptr;
......@@ -896,57 +917,59 @@ Delta* Backend::loadDeltaFromFile(const QString& path)
return nullptr;
}
ArchiveHandler delta(path);
KTar delta(path);
if (!delta.open(QIODevice::ReadOnly)) {
return nullptr;
}
std::unique_ptr<Delta> retdelta(new Delta(QString(), QByteArray(), QByteArray()));
retdelta->d_func()->pathToDelta = path;
try {
/* It can be only our delta */
foreach (QString const& entry, delta.getEntries()) {
if (entry != QLatin1String(".DELTAINFO") && entry != QLatin1String(".") && entry != path.split('/').last().remove(QStringLiteral(".tar.xz"))) {
return nullptr;
}
}
// Verify expected content
QString deltaArchiveFilePath(
path.split('/').last().remove(QStringLiteral(".tar.xz")));
const KArchiveFile* deltaArchiveFile =
delta.directory()->file(deltaArchiveFilePath);
if (deltaArchiveFile == nullptr) {
return nullptr;
}
const KArchiveFile* deltaInfoFile =
delta.directory()->file(QStringLiteral(".DELTAINFO"));
if (deltaInfoFile == nullptr) {
return nullptr;
}
/* If full is false, only read through the archive until we find our needed
* metadata. If it is true, read through the entire archive, which serves
* as a verfication of integrity and allows us to create the filelist. */
foreach (QString const& line, delta.readTextFile(".DELTAINFO").split('\n')) {
if (!line.contains('=') || line.startsWith('#')) {
// Skip this line, not interesting
continue;
}
for (auto&& line : QString::fromUtf8(deltaInfoFile->data()).split('\n')) {
if (!line.contains('=') || line.startsWith('#')) {
// Skip this line, not interesting
continue;
}
// Separate the two strings
QStringList splitted = line.split('=');
QString key = splitted.first();
// There might be an equal, so
splitted.removeAt(0);
QString value = splitted.join(QChar('='));
// Just remove all spaces ignorantly here
key = key.remove(' ');
// Recheck if it's commented over
if (key.startsWith('#')) {
// Pass by
}
// Separate the two strings
QStringList splitted = line.split('=');
QString key = splitted.first();
// There might be an equal, so
splitted.removeAt(0);
QString value = splitted.join(QChar('='));
// Just remove all spaces ignorantly here
key = key.remove(' ');
// Recheck if it's commented over
if (key.startsWith('#')) {
// Pass by
}
// Strip whitespaces here instead
value = value.simplified();
// Strip whitespaces here instead
value = value.simplified();
// Ok, now let's analyze
if (key == QLatin1String("pkgname")) {
retdelta->d_func()->name = value;
} else if (key == QLatin1String("verfrom")) {
retdelta->d_func()->vFrom = value.toUtf8();
} else if (key == QLatin1String("verto")) {
retdelta->d_func()->vTo = value.toUtf8();
}
// Ok, now let's analyze
if (key == QLatin1String("pkgname")) {
retdelta->d_func()->name = value;
} else if (key == QLatin1String("verfrom")) {
retdelta->d_func()->vFrom = value.toUtf8();
} else if (key == QLatin1String("verto")) {
retdelta->d_func()->vTo = value.toUtf8();
}
} catch (ArchiveException const& e) {
akabeiDebug() << "Error during access to archive: " << e.what();
return nullptr;
}
if (retdelta->targetName().isEmpty()) {
......
......@@ -17,7 +17,6 @@
#include <akabeihelpers.h>
#include <utime.h>
#include <libarchive++/archivehandler.h>
#include <SQLiteConnection.h>
#include "akabeiquery.h"
......
......@@ -13,11 +13,13 @@
#include <akabeidelta_p.h>
#include <akabeihelpers_p.h>
#include <akabeiconfig.h>
#include <libarchive++/archivehandler.h>
#include <QDir>
#include <QTemporaryDir>
#include <QTemporaryFile>
#include <KF5/KArchive/KTar>
namespace Akabei
{
Delta::Delta(const QString& targetName, const QByteArray& versionFrom, const QByteArray& versionTo)
......@@ -111,14 +113,25 @@ QString Delta::xdeltaApplyCommand(const QString& pathToDest) const
d->pathToDelta,
pathToDest);
} else {
ArchiveHandler handle(d->pathToDelta);
QString deltaPath = Akabei::Config::instance()->rootDir().absoluteFilePath("tmp/" + d->pathToDelta.split('/').last().remove(QStringLiteral(".tar.xz")));
handle.extract(d->pathToDelta.split('/').last().remove(QStringLiteral(".tar.xz")), deltaPath, true);
KTar archive(d->pathToDelta);
if (!archive.open(QIODevice::ReadOnly)) {
return QString();
}
QTemporaryDir dir;
if (!dir.isValid()) {
return QString();
}
dir.setAutoRemove(false);
QString deltaPath(d->pathToDelta.split('/').last().remove(
QStringLiteral(".tar.xz")));
const KArchiveFile* file = archive.directory()->file(deltaPath);
if (!file->copyTo(dir.path())) {
return QString();
}
retstring = QStringLiteral("xdelta3 -d -q -s %1 %2 %3").arg(
d->pathToOldFile,