Commit 88ad5a0e authored by Ragnar Thomsen's avatar Ragnar Thomsen

Add support for LZ4-compressed tar archives

The recently released libarchive 3.2.0 adds support for LZ4-compression.
We utilize this to enable support for LZ4-compressed tarballs in
libarchiveplugin.

Libarchive requires the lz4 executable to be found in path.

cmake < 3.6 is not able to detect version number of libarchive 3.2 and
therefore LZ4-support will not be enabled for older cmake versions, even
though libarchive 3.2 is installed.

Differential Revision: D1821
parent 594af97a
......@@ -149,6 +149,17 @@ void ArchiveTest::testProperties_data()
qDebug() << "lrzip executable not found in path. Skipping lrzip test.";
}
// Only run test for lz4-compressed tar if lz4 executable is found in path.
if (!QStandardPaths::findExecutable(QStringLiteral("lz4")).isEmpty()) {
QTest::newRow("lz4-compressed tarball")
<< QFINDTESTDATA("data/simplearchive.tar.lz4")
<< QStringLiteral("simplearchive")
<< false << false << false << Archive::Unencrypted
<< QStringLiteral("simplearchive");
} else {
qDebug() << "lz4 executable not found in path. Skipping lz4 test.";
}
QTest::newRow("xar archive")
<< QFINDTESTDATA("data/simplearchive.xar")
<< QStringLiteral("simplearchive")
......@@ -461,6 +472,28 @@ void ArchiveTest::testExtraction_data()
qDebug() << "lrzip executable not found in path. Skipping lrzip test.";
}
// Only run test for lz4-compressed tar if lz4 executable is found in path.
if (!QStandardPaths::findExecutable(QStringLiteral("lz4")).isEmpty()) {
archivePath = QFINDTESTDATA("data/simplearchive.tar.lz4");
QTest::newRow("extract selected entries from a lz4-compressed tarball without path")
<< archivePath
<< QVariantList {
QVariant::fromValue(fileRootNodePair(QStringLiteral("file3.txt"), QString())),
QVariant::fromValue(fileRootNodePair(QStringLiteral("dir2/file22.txt"), QString()))
}
<< ExtractionOptions()
<< 2;
archivePath = QFINDTESTDATA("data/simplearchive.tar.lz4");
QTest::newRow("extract all entries from a lz4-compressed tarball with path")
<< archivePath
<< QVariantList()
<< optionsPreservePaths
<< 7;
} else {
qDebug() << "lz4 executable not found in path. Skipping lz4 test.";
}
archivePath = QFINDTESTDATA("data/simplearchive.xar");
QTest::newRow("extract selected entries from a xar archive without path")
<< archivePath
......
......@@ -56,6 +56,7 @@ void MimeTypeTest::testMimeTypeDetection_data()
const QString compressedLzipTarMime = QStringLiteral("application/x-lzip-compressed-tar");
const QString compressedLzopTarMime = QStringLiteral("application/x-tzo");
const QString compressedLrzipTarMime = QStringLiteral("application/x-lrzip-compressed-tar");
const QString compressedLz4TarMime = QStringLiteral("application/x-lz4-compressed-tar");
const QString isoMimeType = QStringLiteral("application/x-cd-image");
const QString debMimeType = QMimeDatabase().mimeTypeForFile(QStringLiteral("dummy.deb"), QMimeDatabase::MatchExtension).name();
const QString xarMimeType = QStringLiteral("application/x-xar");
......@@ -69,6 +70,7 @@ void MimeTypeTest::testMimeTypeDetection_data()
QTest::newRow("tar.lz") << QFINDTESTDATA("data/simplearchive.tar.lz") << compressedLzipTarMime;
QTest::newRow("tar.lzo") << QFINDTESTDATA("data/simplearchive.tar.lzo") << compressedLzopTarMime;
QTest::newRow("tar.lrz") << QFINDTESTDATA("data/simplearchive.tar.lrz") << compressedLrzipTarMime;
QTest::newRow("tar.lz4") << QFINDTESTDATA("data/simplearchive.tar.lz4") << compressedLz4TarMime;
QTest::newRow("deb") << QFINDTESTDATA("data/smallarchive.deb") << debMimeType;
QTest::newRow("xar") << QFINDTESTDATA("data/simplearchive.xar") << xarMimeType;
......
......@@ -13,4 +13,9 @@
<glob pattern="*.xar"/>
<glob pattern="*.pkg"/>
</mime-type>
<mime-type type="application/x-lz4-compressed-tar">
<comment>Tar archive (LZ4-compressed)</comment>
<comment xml:lang="en">Tar archive (LZ4-compressed)</comment>
<glob pattern="*.tar.lz4"/>
</mime-type>
</mime-info>
......@@ -47,13 +47,36 @@ QMimeType determineMimeType(const QString& filename)
// we cannot rely on it when the archive extension is wrong; we need to validate by hand.
if (fileinfo.completeSuffix().toLower().remove(QRegularExpression(QStringLiteral("[^a-z\\.]"))).contains(QStringLiteral("tar."))) {
inputFile.chop(fileinfo.completeSuffix().length());
QString cleanExtension(fileinfo.completeSuffix().toLower());
// tar.bz2 and tar.lz4 need special treatment since they contain numbers.
bool isBZ2 = false;
bool isLZ4 = false;
if (fileinfo.completeSuffix().toLower().contains(QStringLiteral("bz2"))) {
cleanExtension.remove(QStringLiteral("bz2"));
isBZ2 = true;
}
if (fileinfo.completeSuffix().toLower().contains(QStringLiteral("lz4"))) {
cleanExtension.remove(QStringLiteral("lz4"));
isLZ4 = true;
}
// We remove non-alpha chars from the filename extension, but not periods.
// If the filename is e.g. "foo.tar.gz.1", we get the "foo.tar.gz." string,
// so we need to manually drop the last period character from it.
QString cleanExtension = fileinfo.completeSuffix().toLower().remove(QRegularExpression(QStringLiteral("[^a-z\\.]")));
cleanExtension.remove(QRegularExpression(QStringLiteral("[^a-z\\.]")));
if (cleanExtension.endsWith(QLatin1Char('.'))) {
cleanExtension.chop(1);
}
// Re-add extension for tar.bz2 and tar.lz4.
if (isBZ2) {
cleanExtension.append(QStringLiteral(".bz2"));
}
if (isLZ4) {
cleanExtension.append(QStringLiteral(".lz4"));
}
inputFile += cleanExtension;
qCDebug(ARK) << "Validated filename of compressed tar" << filename << "into filename" << inputFile;
}
......@@ -83,7 +106,9 @@ QMimeType determineMimeType(const QString& filename)
(mimeFromExtension == db.mimeTypeForName(QStringLiteral("application/x-lzip-compressed-tar")) &&
mimeFromContent == db.mimeTypeForName(QStringLiteral("application/x-lzip"))) ||
(mimeFromExtension == db.mimeTypeForName(QStringLiteral("application/x-lrzip-compressed-tar")) &&
mimeFromContent == db.mimeTypeForName(QStringLiteral("application/x-lrzip")))) {
mimeFromContent == db.mimeTypeForName(QStringLiteral("application/x-lrzip"))) ||
(mimeFromExtension == db.mimeTypeForName(QStringLiteral("application/x-lz4-compressed-tar")) &&
mimeFromContent == db.mimeTypeForName(QStringLiteral("application/x-lz4")))) {
return mimeFromExtension;
}
......
......@@ -119,6 +119,11 @@ QStringList PluginManager::supportedMimeTypes() const
supported.remove(QStringLiteral("application/x-lrzip-compressed-tar"));
}
// Remove entry for lz4-compressed tar if lz4 executable not found in path.
if (QStandardPaths::findExecutable(QStringLiteral("lz4")).isEmpty()) {
supported.remove(QStringLiteral("application/x-lz4-compressed-tar"));
}
return sortByComment(supported);
}
......@@ -134,6 +139,11 @@ QStringList PluginManager::supportedWriteMimeTypes() const
supported.remove(QStringLiteral("application/x-lrzip-compressed-tar"));
}
// Remove entry for lz4-compressed tar if lz4 executable not found in path.
if (QStandardPaths::findExecutable(QStringLiteral("lz4")).isEmpty()) {
supported.remove(QStringLiteral("application/x-lz4-compressed-tar"));
}
return sortByComment(supported);
}
......
......@@ -6,6 +6,11 @@ set(SUPPORTED_LIBARCHIVE_READWRITE_MIMETYPES "${SUPPORTED_LIBARCHIVE_READWRITE_M
set(SUPPORTED_LIBARCHIVE_READONLY_MIMETYPES "application/vnd.debian.binary-package;application/x-deb;application/x-cd-image;application/x-bcpio;application/x-cpio;application/x-cpio-compressed;application/x-sv4cpio;application/x-sv4crc;")
set(SUPPORTED_LIBARCHIVE_READONLY_MIMETYPES "${SUPPORTED_LIBARCHIVE_READONLY_MIMETYPES}application/x-rpm;application/x-source-rpm;application/vnd.ms-cab-compressed;application/x-xar;")
if(LibArchive_VERSION VERSION_EQUAL "3.2.0" OR
LibArchive_VERSION VERSION_GREATER "3.2.0")
set(SUPPORTED_LIBARCHIVE_READWRITE_MIMETYPES "${SUPPORTED_LIBARCHIVE_READWRITE_MIMETYPES}application/x-lz4-compressed-tar;")
endif()
set(INSTALLED_LIBARCHIVE_PLUGINS "")
set(kerfuffle_libarchive_readonly_SRCS libarchiveplugin.cpp readonlylibarchiveplugin.cpp ark_debug.cpp)
......@@ -46,6 +51,13 @@ set(SUPPORTED_READWRITE_MIMETYPES
\"application/x-tzo\",
\"application/x-lrzip-compressed-tar")
if(LibArchive_VERSION VERSION_EQUAL "3.2.0" OR
LibArchive_VERSION VERSION_GREATER "3.2.0")
set(SUPPORTED_READWRITE_MIMETYPES
"${SUPPORTED_READWRITE_MIMETYPES}\",
\"application/x-lz4-compressed-tar")
endif()
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/kerfuffle_libarchive_readonly.json.cmake
${CMAKE_CURRENT_BINARY_DIR}/kerfuffle_libarchive_readonly.json)
......@@ -57,6 +69,11 @@ configure_file(
add_library(kerfuffle_libarchive_readonly MODULE ${kerfuffle_libarchive_readonly_SRCS})
add_library(kerfuffle_libarchive MODULE ${kerfuffle_libarchive_readwrite_SRCS})
if(LibArchive_VERSION VERSION_EQUAL "3.2.0" OR
LibArchive_VERSION VERSION_GREATER "3.2.0")
target_compile_definitions(kerfuffle_libarchive PRIVATE -DHAVE_LIBARCHIVE_3_2_0)
endif()
target_link_libraries(kerfuffle_libarchive_readonly KF5::KIOCore ${LibArchive_LIBRARIES} kerfuffle)
target_link_libraries(kerfuffle_libarchive KF5::KIOCore ${LibArchive_LIBRARIES} kerfuffle)
......
......@@ -98,7 +98,7 @@ bool ReadWriteLibarchivePlugin::addFiles(const QStringList& files, const Compres
archive_write_set_format_pax_restricted(arch_writer.data());
int ret;
bool isLrzip = false;
bool requiresExecutable = false;
if (creatingNewFile) {
if (filename().right(2).toUpper() == QLatin1String("GZ")) {
qCDebug(ARK) << "Detected gzip compression for new file";
......@@ -124,7 +124,13 @@ bool ReadWriteLibarchivePlugin::addFiles(const QStringList& files, const Compres
} else if (filename().right(3).toUpper() == QLatin1String("LRZ")) {
qCDebug(ARK) << "Detected lrzip compression for new file";
ret = archive_write_add_filter_lrzip(arch_writer.data());
isLrzip = true;
requiresExecutable = true;
#ifdef HAVE_LIBARCHIVE_3_2_0
} else if (filename().right(3).toUpper() == QLatin1String("LZ4")) {
qCDebug(ARK) << "Detected lz4 compression for new file";
ret = archive_write_add_filter_lz4(arch_writer.data());
requiresExecutable = true;
#endif
} else if (filename().right(3).toUpper() == QLatin1String("TAR")) {
qCDebug(ARK) << "Detected no compression for new file (pure tar)";
ret = archive_write_add_filter_none(arch_writer.data());
......@@ -134,8 +140,8 @@ bool ReadWriteLibarchivePlugin::addFiles(const QStringList& files, const Compres
}
// Libarchive emits a warning for lrzip due to using external executable.
if ((isLrzip && ret != ARCHIVE_WARN) ||
(!isLrzip && ret != ARCHIVE_OK)) {
if ((requiresExecutable && ret != ARCHIVE_WARN) ||
(!requiresExecutable && ret != ARCHIVE_OK)) {
emit error(xi18nc("@info", "Setting the compression method failed with the following error:<nl/><message>%1</message>",
QLatin1String(archive_error_string(arch_writer.data()))));
return false;
......@@ -178,8 +184,14 @@ bool ReadWriteLibarchivePlugin::addFiles(const QStringList& files, const Compres
break;
case ARCHIVE_FILTER_LRZIP:
ret = archive_write_add_filter_lrzip(arch_writer.data());
isLrzip = true;
requiresExecutable = true;
break;
#ifdef HAVE_LIBARCHIVE_3_2_0
case ARCHIVE_FILTER_LZ4:
ret = archive_write_add_filter_lz4(arch_writer.data());
requiresExecutable = true;
break;
#endif
case ARCHIVE_FILTER_NONE:
ret = archive_write_add_filter_none(arch_writer.data());
break;
......@@ -190,8 +202,8 @@ bool ReadWriteLibarchivePlugin::addFiles(const QStringList& files, const Compres
}
// Libarchive emits a warning for lrzip due to using external executable.
if ((isLrzip && ret != ARCHIVE_WARN) ||
(!isLrzip && ret != ARCHIVE_OK)) {
if ((requiresExecutable && ret != ARCHIVE_WARN) ||
(!requiresExecutable && ret != ARCHIVE_OK)) {
emit error(xi18nc("@info", "Setting the compression method failed with the following error:<nl/><message>%1</message>",
QLatin1String(archive_error_string(arch_writer.data()))));
return false;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment