diff --git a/CMakeLists.txt b/CMakeLists.txt index 34c071c3..29828b44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,114 +1,109 @@ cmake_minimum_required (VERSION 3.0) # KDE Application Version, managed by release script set (KDE_APPLICATIONS_VERSION_MAJOR "20") set (KDE_APPLICATIONS_VERSION_MINOR "03") set (KDE_APPLICATIONS_VERSION_MICRO "70") set (KDE_APPLICATIONS_VERSION "${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}.${KDE_APPLICATIONS_VERSION_MICRO}") project(ark VERSION ${KDE_APPLICATIONS_VERSION}) set(QT_MIN_VERSION 5.8.0) set(KF5_MIN_VERSION 5.44.0) find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(GenerateExportHeader) include(FeatureSummary) include(ECMQtDeclareLoggingCategory) include(ECMSetupVersion) add_definitions(-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) ecm_setup_version(${KDE_APPLICATIONS_VERSION} VARIABLE_PREFIX ARK VERSION_HEADER "ark_version.h") ecm_setup_version(PROJECT VARIABLE_PREFIX KERFUFFLE) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Concurrent Core Gui Widgets) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Archive Config Crash DBusAddons DocTools I18n IconThemes ItemModels KIO Service Parts Pty WidgetsAddons) find_package(Qt5Test ${QT_MIN_VERSION} CONFIG QUIET) set_package_properties(Qt5Test PROPERTIES PURPOSE "Required for tests" TYPE OPTIONAL) if(NOT Qt5Test_FOUND) set(BUILD_TESTING OFF CACHE BOOL "Build the testing tree.") endif() -find_package(LibArchive 3.2.0 REQUIRED) +find_package(LibArchive 3.3.3 REQUIRED) set_package_properties(LibArchive PROPERTIES URL "https://www.libarchive.org/" DESCRIPTION "A library for dealing with a wide variety of archive file formats" PURPOSE "Required for among others tar, tar.gz, tar.bz2 formats in Ark.") find_package(LibZip 1.3.0) set_package_properties(LibZip PROPERTIES URL "https://nih.at/libzip/" DESCRIPTION "A library for handling zip archives" PURPOSE "Optional for zip archives.") -find_package(SharedMimeInfo QUIET) -set_package_properties(SharedMimeInfo PROPERTIES - TYPE OPTIONAL - PURPOSE "Required for archive formats without an official mimetype.") - option(WITH_TEST_COVERAGE "Build with test coverage support" OFF) if (WITH_TEST_COVERAGE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") endif (WITH_TEST_COVERAGE) set(SUPPORTED_ARK_MIMETYPES "") add_definitions(-DTRANSLATION_DOMAIN="ark") # Until kf5 5.56 kconfig had some header which used Q_FOREACH # Q_FOREACH will be deprecated/removed in qt6. With QT_NO_FOREACH we prepare the migration to qt6. if (KF5Config_VERSION VERSION_GREATER "5.56.0") add_definitions(-DQT_NO_FOREACH) endif() add_subdirectory(plugins) add_subdirectory(kerfuffle) add_subdirectory(part) add_subdirectory(app) add_subdirectory(doc) if(BUILD_TESTING) add_subdirectory(autotests) endif() ki18n_install(po) kdoctools_install(po) if (NOT ECM_VERSION VERSION_LESS "5.59.0") install(FILES ark.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) else() install(FILES ark.categories DESTINATION ${KDE_INSTALL_CONFDIR}) endif() feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/kerfuffle/CMakeLists.txt b/kerfuffle/CMakeLists.txt index ba13e8e9..154ab8d1 100644 --- a/kerfuffle/CMakeLists.txt +++ b/kerfuffle/CMakeLists.txt @@ -1,82 +1,77 @@ ########### next target ############### set(kerfuffle_SRCS archiveformat.cpp archive_kerfuffle.cpp archiveinterface.cpp extractionsettingspage.cpp generalsettingspage.cpp previewsettingspage.cpp settingsdialog.cpp settingspage.cpp jobs.cpp adddialog.cpp compressionoptionswidget.cpp createdialog.cpp extractiondialog.cpp propertiesdialog.cpp queries.cpp addtoarchive.cpp cliinterface.cpp cliproperties.cpp mimetypes.cpp plugin.cpp pluginmanager.cpp pluginsettingspage.cpp archiveentry.cpp options.cpp ) kconfig_add_kcfg_files(kerfuffle_SRCS settings.kcfgc GENERATE_MOC) ki18n_wrap_ui(kerfuffle_SRCS createdialog.ui extractiondialog.ui extractionsettingspage.ui generalsettingspage.ui pluginsettingspage.ui previewsettingspage.ui propertiesdialog.ui compressionoptionswidget.ui ) ecm_qt_declare_logging_category(kerfuffle_SRCS HEADER ark_debug.h IDENTIFIER ARK CATEGORY_NAME ark.kerfuffle) add_library(kerfuffle SHARED ${kerfuffle_SRCS}) generate_export_header(kerfuffle BASE_NAME kerfuffle) if (APPLE) target_compile_definitions(kerfuffle PRIVATE -DDEPENDENCY_TOOL="otool") target_compile_definitions(kerfuffle PRIVATE -DDEPENDENCY_TOOL_ARGS="-L") else() target_compile_definitions(kerfuffle PRIVATE -DDEPENDENCY_TOOL="ldd") endif() target_link_libraries(kerfuffle PUBLIC KF5::IconThemes KF5::Pty KF5::Service KF5::I18n KF5::WidgetsAddons PRIVATE Qt5::Concurrent KF5::KIOCore KF5::KIOWidgets KF5::KIOFileWidgets ) set_target_properties(kerfuffle PROPERTIES VERSION ${KERFUFFLE_VERSION_STRING} SOVERSION ${KERFUFFLE_SOVERSION}) install(TARGETS kerfuffle ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP) install(FILES kerfufflePlugin.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR}) install(FILES ark.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) - -install(FILES mime/kerfuffle.xml DESTINATION ${KDE_INSTALL_MIMEDIR}) -if(SharedMimeInfo_FOUND) - update_xdg_mimetypes(${KDE_INSTALL_MIMEDIR}) -endif() diff --git a/kerfuffle/mime/kerfuffle.xml b/kerfuffle/mime/kerfuffle.xml deleted file mode 100644 index 931aa4d0..00000000 --- a/kerfuffle/mime/kerfuffle.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - Tar archive (zstd-compressed) - - - diff --git a/plugins/libarchive/CMakeLists.txt b/plugins/libarchive/CMakeLists.txt index 4d22d718..97ed40a5 100644 --- a/plugins/libarchive/CMakeLists.txt +++ b/plugins/libarchive/CMakeLists.txt @@ -1,109 +1,93 @@ include_directories(${LibArchive_INCLUDE_DIRS}) -if(NOT LibArchive_VERSION VERSION_LESS "3.3.3") - set(ENABLE_ZSTD_SUPPORT ON BOOL) -endif() - ########### next target ############### set(SUPPORTED_LIBARCHIVE_READWRITE_MIMETYPES "application/x-tar;application/x-compressed-tar;application/x-bzip-compressed-tar;application/x-tarz;application/x-xz-compressed-tar;") set(SUPPORTED_LIBARCHIVE_READWRITE_MIMETYPES "${SUPPORTED_LIBARCHIVE_READWRITE_MIMETYPES}application/x-lzma-compressed-tar;application/x-lzip-compressed-tar;application/x-tzo;application/x-lrzip-compressed-tar;application/x-lz4-compressed-tar;") 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;application/x-iso9660-appimage;application/x-archive;") - -if(ENABLE_ZSTD_SUPPORT) - set(SUPPORTED_LIBARCHIVE_READWRITE_MIMETYPES "${SUPPORTED_LIBARCHIVE_READWRITE_MIMETYPES}application/x-zstd-compressed-tar;") -endif() +set(SUPPORTED_LIBARCHIVE_READWRITE_MIMETYPES "${SUPPORTED_LIBARCHIVE_READWRITE_MIMETYPES}application/x-zstd-compressed-tar;") set(INSTALLED_LIBARCHIVE_PLUGINS "") set(kerfuffle_libarchive_readonly_SRCS libarchiveplugin.cpp readonlylibarchiveplugin.cpp ark_debug.cpp) set(kerfuffle_libarchive_readwrite_SRCS libarchiveplugin.cpp readwritelibarchiveplugin.cpp ark_debug.cpp) set(kerfuffle_libarchive_SRCS ${kerfuffle_libarchive_readonly_SRCS} readwritelibarchiveplugin.cpp) ecm_qt_declare_logging_category(kerfuffle_libarchive_SRCS HEADER ark_debug.h IDENTIFIER ARK CATEGORY_NAME ark.libarchive) # NOTE: the first double-quotes of the first mime and the last # double-quotes of the last mime must NOT be escaped. set(SUPPORTED_READONLY_MIMETYPES "application/x-deb\", \"application/x-cd-image\", \"application/x-bcpio\", \"application/x-cpio\", \"application/x-cpio-compressed\", \"application/x-sv4cpio\", \"application/x-sv4crc\", \"application/x-rpm\", \"application/x-source-rpm\", \"application/vnd.debian.binary-package\", \"application/vnd.ms-cab-compressed\", \"application/x-xar\", \"application/x-iso9660-appimage\", \"application/x-archive") # NOTE: the first double-quotes of the first mime and the last # double-quotes of the last mime must NOT be escaped. set(SUPPORTED_READWRITE_MIMETYPES "application/x-tar\", \"application/x-compressed-tar\", \"application/x-bzip-compressed-tar\", \"application/x-tarz\", \"application/x-xz-compressed-tar\", \"application/x-lzma-compressed-tar\", \"application/x-lzip-compressed-tar\", \"application/x-tzo\", \"application/x-lrzip-compressed-tar\", - \"application/x-lz4-compressed-tar") - -if(ENABLE_ZSTD_SUPPORT) - set(SUPPORTED_READWRITE_MIMETYPES - "${SUPPORTED_READWRITE_MIMETYPES}\", - \"application/x-zstd-compressed-tar") -endif() + \"application/x-lz4-compressed-tar\", + \"application/x-zstd-compressed-tar") configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/kerfuffle_libarchive_readonly.json.cmake ${CMAKE_CURRENT_BINARY_DIR}/kerfuffle_libarchive_readonly.json) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/kerfuffle_libarchive.json.cmake ${CMAKE_CURRENT_BINARY_DIR}/kerfuffle_libarchive.json) kerfuffle_add_plugin(kerfuffle_libarchive_readonly ${kerfuffle_libarchive_readonly_SRCS}) kerfuffle_add_plugin(kerfuffle_libarchive ${kerfuffle_libarchive_readwrite_SRCS}) -if(ENABLE_ZSTD_SUPPORT) - target_compile_definitions(kerfuffle_libarchive PRIVATE -DHAVE_ZSTD_SUPPORT) -endif() - target_link_libraries(kerfuffle_libarchive_readonly ${LibArchive_LIBRARIES}) target_link_libraries(kerfuffle_libarchive ${LibArchive_LIBRARIES}) set(INSTALLED_LIBARCHIVE_PLUGINS "${INSTALLED_LIBARCHIVE_PLUGINS}kerfuffle_libarchive_readonly;") set(INSTALLED_LIBARCHIVE_PLUGINS "${INSTALLED_LIBARCHIVE_PLUGINS}kerfuffle_libarchive;") set(SUPPORTED_ARK_MIMETYPES "${SUPPORTED_ARK_MIMETYPES}${SUPPORTED_LIBARCHIVE_READWRITE_MIMETYPES}${SUPPORTED_LIBARCHIVE_READONLY_MIMETYPES}" PARENT_SCOPE) set(INSTALLED_KERFUFFLE_PLUGINS "${INSTALLED_KERFUFFLE_PLUGINS}${INSTALLED_LIBARCHIVE_PLUGINS}" PARENT_SCOPE) find_program(LRZIP lrzip) if(LRZIP) message(STATUS "Found lrzip executable: ${LRZIP}") else() message(WARNING "Could not find the lrzip executable. Ark requires lrzip to handle the tar.lrz archive format.") endif() find_program(LZOP lzop) if(LZOP) message(STATUS "Found lzop executable: ${LZOP}") else() message(WARNING "Could not find the lzop executable. Ark requires lzop to handle the tar.lzo archive format if libarchive >= 3.3 has been compiled without liblzo2 support.") endif() find_program(ZSTD zstd) if(ZSTD) message(STATUS "Found zstd executable: ${ZSTD}") else() message(WARNING "Could not find the zstd executable. Ark requires zstd to handle the tar.zst archive format if libarchive >= 3.3.3 has been compiled without libzstd support.") endif() diff --git a/plugins/libarchive/readwritelibarchiveplugin.cpp b/plugins/libarchive/readwritelibarchiveplugin.cpp index fb821c84..37025947 100644 --- a/plugins/libarchive/readwritelibarchiveplugin.cpp +++ b/plugins/libarchive/readwritelibarchiveplugin.cpp @@ -1,558 +1,554 @@ /* * Copyright (c) 2007 Henrique Pinto * Copyright (c) 2008-2009 Harald Hvaal * Copyright (c) 2010 Raphael Kubo da Costa * Copyright (c) 2016 Vladyslav Batyrenko * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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 "readwritelibarchiveplugin.h" #include "ark_debug.h" #include #include #include #include #include #include K_PLUGIN_CLASS_WITH_JSON(ReadWriteLibarchivePlugin, "kerfuffle_libarchive.json") ReadWriteLibarchivePlugin::ReadWriteLibarchivePlugin(QObject *parent, const QVariantList &args) : LibarchivePlugin(parent, args) { qCDebug(ARK) << "Loaded libarchive read-write plugin"; } ReadWriteLibarchivePlugin::~ReadWriteLibarchivePlugin() { } bool ReadWriteLibarchivePlugin::addFiles(const QVector &files, const Archive::Entry *destination, const CompressionOptions &options, uint numberOfEntriesToAdd) { qCDebug(ARK) << "Adding" << files.size() << "entries with CompressionOptions" << options; const bool creatingNewFile = !QFileInfo::exists(filename()); const uint totalCount = m_numberOfEntries + numberOfEntriesToAdd; m_writtenFiles.clear(); if (!creatingNewFile && !initializeReader()) { return false; } if (!initializeWriter(creatingNewFile, options)) { return false; } // First write the new files. qCDebug(ARK) << "Writing new entries"; uint addedEntries = 0; // Recreate destination directory structure. const QString destinationPath = (destination == nullptr) ? QString() : destination->fullPath(); for (Archive::Entry *selectedFile : files) { if (QThread::currentThread()->isInterruptionRequested()) { break; } if (!writeFile(selectedFile->fullPath(), destinationPath)) { finish(false); return false; } addedEntries++; emit progress(float(addedEntries)/float(totalCount)); // For directories, write all subfiles/folders. const QString &fullPath = selectedFile->fullPath(); if (QFileInfo(fullPath).isDir()) { QDirIterator it(fullPath, QDir::AllEntries | QDir::Readable | QDir::Hidden | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); while (!QThread::currentThread()->isInterruptionRequested() && it.hasNext()) { QString path = it.next(); if ((it.fileName() == QLatin1String("..")) || (it.fileName() == QLatin1Char('.'))) { continue; } const bool isRealDir = it.fileInfo().isDir() && !it.fileInfo().isSymLink(); if (isRealDir) { path.append(QLatin1Char('/')); } if (!writeFile(path, destinationPath)) { finish(false); return false; } addedEntries++; emit progress(float(addedEntries)/float(totalCount)); } } } qCDebug(ARK) << "Added" << addedEntries << "new entries to archive"; bool isSuccessful = true; // If we have old archive entries. if (!creatingNewFile) { qCDebug(ARK) << "Copying any old entries"; m_filesPaths = m_writtenFiles; isSuccessful = processOldEntries(addedEntries, Add, totalCount); if (isSuccessful) { qCDebug(ARK) << "Added" << addedEntries << "old entries to archive"; } else { qCDebug(ARK) << "Adding entries failed"; } } finish(isSuccessful); return isSuccessful; } bool ReadWriteLibarchivePlugin::moveFiles(const QVector &files, Archive::Entry *destination, const CompressionOptions &options) { Q_UNUSED(options); qCDebug(ARK) << "Moving" << files.size() << "entries"; if (!initializeReader()) { return false; } if (!initializeWriter()) { return false; } // Copy old elements from previous archive to new archive. uint movedEntries = 0; m_filesPaths = entryFullPaths(files); m_entriesWithoutChildren = entriesWithoutChildren(files).count(); m_destination = destination; const bool isSuccessful = processOldEntries(movedEntries, Move, m_numberOfEntries); if (isSuccessful) { qCDebug(ARK) << "Moved" << movedEntries << "entries within archive"; } else { qCDebug(ARK) << "Moving entries failed"; } finish(isSuccessful); return isSuccessful; } bool ReadWriteLibarchivePlugin::copyFiles(const QVector &files, Archive::Entry *destination, const CompressionOptions &options) { Q_UNUSED(options); qCDebug(ARK) << "Copying" << files.size() << "entries"; if (!initializeReader()) { return false; } if (!initializeWriter()) { return false; } // Copy old elements from previous archive to new archive. uint copiedEntries = 0; m_filesPaths = entryFullPaths(files); m_destination = destination; const bool isSuccessful = processOldEntries(copiedEntries, Copy, m_numberOfEntries); if (isSuccessful) { qCDebug(ARK) << "Copied" << copiedEntries << "entries within archive"; } else { qCDebug(ARK) << "Copying entries failed"; } finish(isSuccessful); return isSuccessful; } bool ReadWriteLibarchivePlugin::deleteFiles(const QVector &files) { qCDebug(ARK) << "Deleting" << files.size() << "entries"; if (!initializeReader()) { return false; } if (!initializeWriter()) { return false; } // Copy old elements from previous archive to new archive. uint deletedEntries = 0; m_filesPaths = entryFullPaths(files); const bool isSuccessful = processOldEntries(deletedEntries, Delete, m_numberOfEntries); if (isSuccessful) { qCDebug(ARK) << "Removed" << deletedEntries << "entries from archive"; } else { qCDebug(ARK) << "Removing entries failed"; } finish(isSuccessful); return isSuccessful; } bool ReadWriteLibarchivePlugin::initializeWriter(const bool creatingNewFile, const CompressionOptions &options) { m_tempFile.setFileName(filename()); if (!m_tempFile.open(QIODevice::WriteOnly | QIODevice::Unbuffered)) { emit error(i18nc("@info", "Failed to create a temporary file for writing data.")); return false; } m_archiveWriter.reset(archive_write_new()); if (!(m_archiveWriter.data())) { emit error(i18n("The archive writer could not be initialized.")); return false; } // pax_restricted is the libarchive default, let's go with that. archive_write_set_format_pax_restricted(m_archiveWriter.data()); if (creatingNewFile) { if (!initializeNewFileWriterFilters(options)) { return false; } } else { if (!initializeWriterFilters()) { return false; } } if (archive_write_open_fd(m_archiveWriter.data(), m_tempFile.handle()) != ARCHIVE_OK) { emit error(i18nc("@info", "Could not open the archive for writing entries.")); return false; } return true; } bool ReadWriteLibarchivePlugin::initializeWriterFilters() { int ret; bool requiresExecutable = false; switch (archive_filter_code(m_archiveReader.data(), 0)) { case ARCHIVE_FILTER_GZIP: ret = archive_write_add_filter_gzip(m_archiveWriter.data()); break; case ARCHIVE_FILTER_BZIP2: ret = archive_write_add_filter_bzip2(m_archiveWriter.data()); break; case ARCHIVE_FILTER_XZ: ret = archive_write_add_filter_xz(m_archiveWriter.data()); break; case ARCHIVE_FILTER_LZMA: ret = archive_write_add_filter_lzma(m_archiveWriter.data()); break; case ARCHIVE_FILTER_COMPRESS: ret = archive_write_add_filter_compress(m_archiveWriter.data()); break; case ARCHIVE_FILTER_LZIP: ret = archive_write_add_filter_lzip(m_archiveWriter.data()); break; case ARCHIVE_FILTER_LZOP: ret = archive_write_add_filter_lzop(m_archiveWriter.data()); break; case ARCHIVE_FILTER_LRZIP: ret = archive_write_add_filter_lrzip(m_archiveWriter.data()); requiresExecutable = true; break; case ARCHIVE_FILTER_LZ4: ret = archive_write_add_filter_lz4(m_archiveWriter.data()); break; -#ifdef HAVE_ZSTD_SUPPORT case ARCHIVE_FILTER_ZSTD: ret = archive_write_add_filter_zstd(m_archiveWriter.data()); break; -#endif case ARCHIVE_FILTER_NONE: ret = archive_write_add_filter_none(m_archiveWriter.data()); break; default: emit error(i18n("The compression type '%1' is not supported by Ark.", QLatin1String(archive_filter_name(m_archiveReader.data(), 0)))); return false; } // Libarchive emits a warning for lrzip due to using external executable. if ((requiresExecutable && ret != ARCHIVE_WARN) || (!requiresExecutable && ret != ARCHIVE_OK)) { qCWarning(ARK) << "Failed to set compression method:" << archive_error_string(m_archiveWriter.data()); emit error(i18nc("@info", "Could not set the compression method.")); return false; } return true; } bool ReadWriteLibarchivePlugin::initializeNewFileWriterFilters(const CompressionOptions &options) { int ret; bool requiresExecutable = false; if (filename().right(2).toUpper() == QLatin1String("GZ")) { qCDebug(ARK) << "Detected gzip compression for new file"; ret = archive_write_add_filter_gzip(m_archiveWriter.data()); } else if (filename().right(3).toUpper() == QLatin1String("BZ2")) { qCDebug(ARK) << "Detected bzip2 compression for new file"; ret = archive_write_add_filter_bzip2(m_archiveWriter.data()); } else if (filename().right(2).toUpper() == QLatin1String("XZ")) { qCDebug(ARK) << "Detected xz compression for new file"; ret = archive_write_add_filter_xz(m_archiveWriter.data()); } else if (filename().right(4).toUpper() == QLatin1String("LZMA")) { qCDebug(ARK) << "Detected lzma compression for new file"; ret = archive_write_add_filter_lzma(m_archiveWriter.data()); } else if (filename().right(2).toUpper() == QLatin1String(".Z")) { qCDebug(ARK) << "Detected compress (.Z) compression for new file"; ret = archive_write_add_filter_compress(m_archiveWriter.data()); } else if (filename().right(2).toUpper() == QLatin1String("LZ")) { qCDebug(ARK) << "Detected lzip compression for new file"; ret = archive_write_add_filter_lzip(m_archiveWriter.data()); } else if (filename().right(3).toUpper() == QLatin1String("LZO")) { qCDebug(ARK) << "Detected lzop compression for new file"; ret = archive_write_add_filter_lzop(m_archiveWriter.data()); } else if (filename().right(3).toUpper() == QLatin1String("LRZ")) { qCDebug(ARK) << "Detected lrzip compression for new file"; ret = archive_write_add_filter_lrzip(m_archiveWriter.data()); requiresExecutable = true; } else if (filename().right(3).toUpper() == QLatin1String("LZ4")) { qCDebug(ARK) << "Detected lz4 compression for new file"; ret = archive_write_add_filter_lz4(m_archiveWriter.data()); -#ifdef HAVE_ZSTD_SUPPORT } else if (filename().right(3).toUpper() == QLatin1String("ZST")) { qCDebug(ARK) << "Detected zstd compression for new file"; ret = archive_write_add_filter_zstd(m_archiveWriter.data()); -#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(m_archiveWriter.data()); } else { qCDebug(ARK) << "Falling back to gzip"; ret = archive_write_add_filter_gzip(m_archiveWriter.data()); } // Libarchive emits a warning for lrzip due to using external executable. if ((requiresExecutable && ret != ARCHIVE_WARN) || (!requiresExecutable && ret != ARCHIVE_OK)) { qCWarning(ARK) << "Failed to set compression method:" << archive_error_string(m_archiveWriter.data()); emit error(i18nc("@info", "Could not set the compression method.")); return false; } // Set compression level if passed in CompressionOptions. if (options.isCompressionLevelSet()) { qCDebug(ARK) << "Using compression level:" << options.compressionLevel(); ret = archive_write_set_filter_option(m_archiveWriter.data(), nullptr, "compression-level", QString::number(options.compressionLevel()).toUtf8().constData()); if (ret != ARCHIVE_OK) { qCWarning(ARK) << "Failed to set compression level" << archive_error_string(m_archiveWriter.data()); emit error(i18nc("@info", "Could not set the compression level.")); return false; } } return true; } void ReadWriteLibarchivePlugin::finish(const bool isSuccessful) { if (!isSuccessful || QThread::currentThread()->isInterruptionRequested()) { archive_write_fail(m_archiveWriter.data()); m_tempFile.cancelWriting(); } else { // archive_write_close() needs to be called before calling QSaveFile::commit(), // otherwise the latter will close() the file descriptor m_archiveWriter is still working on. // TODO: We need to abstract this code better so that we only deal with one // object that manages both QSaveFile and ArchiveWriter. archive_write_close(m_archiveWriter.data()); m_tempFile.commit(); } } bool ReadWriteLibarchivePlugin::processOldEntries(uint &entriesCounter, OperationMode mode, uint totalCount) { const uint newEntries = entriesCounter; entriesCounter = 0; uint iteratedEntries = 0; // Create a map that contains old path as key and new path as value. QMap pathMap; if (mode == Move || mode == Copy) { m_filesPaths.sort(); QStringList resultList = entryPathsFromDestination(m_filesPaths, m_destination, m_entriesWithoutChildren); const int listSize = m_filesPaths.count(); Q_ASSERT(listSize == resultList.count()); for (int i = 0; i < listSize; ++i) { pathMap.insert(m_filesPaths.at(i), resultList.at(i)); } } struct archive_entry *entry; while (!QThread::currentThread()->isInterruptionRequested() && archive_read_next_header(m_archiveReader.data(), &entry) == ARCHIVE_OK) { const QString file = QFile::decodeName(archive_entry_pathname(entry)); if (mode == Move || mode == Copy) { const QString newPathname = pathMap.value(file); if (!newPathname.isEmpty()) { if (mode == Copy) { // Write the old entry. if (!writeEntry(entry)) { return false; } } else { emit entryRemoved(file); } entriesCounter++; iteratedEntries--; // Change entry path. archive_entry_set_pathname(entry, newPathname.toUtf8().constData()); emitEntryFromArchiveEntry(entry); } } else if (m_filesPaths.contains(file)) { archive_read_data_skip(m_archiveReader.data()); switch (mode) { case Delete: entriesCounter++; emit entryRemoved(file); emit progress(float(newEntries + entriesCounter + iteratedEntries)/float(totalCount)); break; case Add: qCDebug(ARK) << file << "is already present in the new archive, skipping."; // When overwriting entries, we need to decrement the counter manually, // because entry was emitted. m_numberOfEntries--; break; default: qCDebug(ARK) << "Mode" << mode << "is not considered for processing old libarchive entries"; Q_ASSERT(false); } continue; } // Write old entries. if (writeEntry(entry)) { if (mode == Add) { entriesCounter++; } else if (mode == Move || mode == Copy) { iteratedEntries++; } else if (mode == Delete) { iteratedEntries++; } } else { return false; } emit progress(float(newEntries + entriesCounter + iteratedEntries)/float(totalCount)); } return !QThread::currentThread()->isInterruptionRequested(); } bool ReadWriteLibarchivePlugin::writeEntry(struct archive_entry *entry) { const int returnCode = archive_write_header(m_archiveWriter.data(), entry); switch (returnCode) { case ARCHIVE_OK: // If the whole archive is extracted and the total filesize is // available, we use partial progress. copyData(QLatin1String(archive_entry_pathname(entry)), m_archiveReader.data(), m_archiveWriter.data(), false); break; case ARCHIVE_FAILED: case ARCHIVE_FATAL: qCCritical(ARK) << "archive_write_header() has returned" << returnCode << "with errno" << archive_errno(m_archiveWriter.data()); emit error(i18nc("@info", "Could not compress entry, operation aborted.")); return false; default: qCDebug(ARK) << "archive_writer_header() has returned" << returnCode << "which will be ignored."; break; } return true; } // TODO: if we merge this with copyData(), we can pass more data // such as an fd to archive_read_disk_entry_from_file() bool ReadWriteLibarchivePlugin::writeFile(const QString &relativeName, const QString &destination) { const QString absoluteFilename = QFileInfo(relativeName).absoluteFilePath(); const QString destinationFilename = destination + relativeName; // #253059: Even if we use archive_read_disk_entry_from_file, // libarchive may have been compiled without HAVE_LSTAT, // or something may have caused it to follow symlinks, in // which case stat() will be called. To avoid this, we // call lstat() ourselves. struct stat st; lstat(QFile::encodeName(absoluteFilename).constData(), &st); // krazy:exclude=syscalls struct archive_entry *entry = archive_entry_new(); archive_entry_set_pathname(entry, QFile::encodeName(destinationFilename).constData()); archive_entry_copy_sourcepath(entry, QFile::encodeName(absoluteFilename).constData()); archive_read_disk_entry_from_file(m_archiveReadDisk.data(), entry, -1, &st); const auto returnCode = archive_write_header(m_archiveWriter.data(), entry); if (returnCode == ARCHIVE_OK) { // If the whole archive is extracted and the total filesize is // available, we use partial progress. copyData(absoluteFilename, m_archiveWriter.data(), false); } else { qCCritical(ARK) << "Writing header failed with error code " << returnCode; qCCritical(ARK) << "Error while writing..." << archive_error_string(m_archiveWriter.data()) << "(error no =" << archive_errno(m_archiveWriter.data()) << ')'; emit error(i18nc("@info Error in a message box", "Could not compress entry.")); archive_entry_free(entry); return false; } if (QThread::currentThread()->isInterruptionRequested()) { archive_entry_free(entry); return false; } m_writtenFiles.push_back(destinationFilename); emitEntryFromArchiveEntry(entry); archive_entry_free(entry); return true; } #include "readwritelibarchiveplugin.moc"