Skip to content

Commit

Permalink
Merge branch 'hotfix/v4.0.7'
Browse files Browse the repository at this point in the history
  • Loading branch information
rikyoz committed May 16, 2024
2 parents d41afbc + 31b3809 commit 69c0244
Show file tree
Hide file tree
Showing 15 changed files with 226 additions and 72 deletions.
29 changes: 19 additions & 10 deletions .github/workflows/ctest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
fail-fast: false

matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
os: [ubuntu-latest, windows-latest, macos-13]
build_type: [Debug, Release]
c_compiler: [gcc, clang, cl]
bit7z_auto_format: [OFF, ON]
Expand All @@ -39,7 +39,7 @@ jobs:
c_compiler: clang
cpp_compiler: clang++
use_system_7zip: OFF
- os: macos-latest
- os: macos-13
c_compiler: clang
cpp_compiler: clang++
use_system_7zip: OFF
Expand All @@ -63,21 +63,29 @@ jobs:
bit7z_use_system_codepage: ON
- os: ubuntu-latest
bit7z_path_sanitization: ON
- os: macos-latest
- os: macos-13
c_compiler: cl
- os: macos-latest
- os: macos-13
c_compiler: gcc
- os: macos-latest
- os: macos-13
bit7z_use_native_string: ON
- os: macos-latest
- os: macos-13
bit7z_auto_prefix_long_paths: ON
- os: macos-latest
- os: macos-13
bit7z_use_system_codepage: ON
- os: macos-latest
- os: macos-13
bit7z_path_sanitization: ON

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Fix kernel mmap rnd bits
shell: bash
if: ${{ runner.os == 'Linux' && matrix.build_type == 'Debug' }}
# Asan in llvm 14 provided in ubuntu 22.04 is incompatible with
# high-entropy ASLR in much newer kernels that GitHub runners are
# using leading to random crashes: https://reviews.llvm.org/D148280
run: sudo sysctl vm.mmap_rnd_bits=28

- name: Set reusable strings
# Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file.
Expand Down Expand Up @@ -121,9 +129,10 @@ jobs:
- name: Build 7z.so for tests (macOS)
shell: bash
if: matrix.os == 'macos-latest'
if: matrix.os == 'macos-13'
run: |
git clone --depth 1 https://github.com/rikyoz/7-Zip ${{ github.workspace }}/../7-zip
sed -i '' 's/-Wno-poison-system-directories/-Wno-poison-system-directories -Wno-declaration-after-statement\r/' ${{ github.workspace }}/../7-zip/CPP/7zip/warn_clang_mac.mak
cd ${{ github.workspace }}/../7-zip/CPP/7zip/Bundles/Format7zF/
make -j -f ../../cmpl_mac_x64.mak
cp b/m_x64/7z.so ${{ github.workspace }}/bin/x64/7z.so
Expand Down
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

cmake_minimum_required( VERSION 3.11 )
cmake_minimum_required( VERSION 3.14 )

project( bit7z
VERSION 4.0.6
VERSION 4.0.7
DESCRIPTION "A C++ static library offering a clean and simple interface to the 7-zip/p7zip shared libraries"
HOMEPAGE_URL "https://github.com/rikyoz/bit7z/" )
set( CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "ON" )
Expand Down
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,11 +286,11 @@ add_subdirectory( ${CMAKE_SOURCE_DIR}/third_party/bit7z )
target_link_libraries( ${YOUR_TARGET} PRIVATE bit7z )
```

The library is highly customizable: for a detailed list of the available build options, please refer to the [wiki](https://github.com/rikyoz/bit7z/wiki/Building-the-library).
The library is highly customizable: for a detailed list of the available build options, please refer to the [wiki](https://github.com/rikyoz/bit7z/wiki/Building-the-library#%EF%B8%8F-build-options).

### 📑 7-zip Version
### 📌 7-zip Version

While configuring bit7z via CMake, it automatically downloads the latest version of 7-zip currently supported by the library.
While configuring bit7z via CMake, it automatically downloads the latest version of 7-zip supported by the library.

Optionally, you can specify a different version of 7-zip via the CMake option `BIT7Z_7ZIP_VERSION` (e.g., `-DBIT7Z_7ZIP_VERSION="22.01"`).

Expand All @@ -300,16 +300,26 @@ Please note that, in general, it is best to use the same version of 7-zip of the

#### Using 7-zip v23.01 on Linux and macOS

By default, bit7z is compatible with the `7z.so` from 7-zip v23.01 and later.

If you plan to use the `7z.so` from p7zip or 7-zip v22.01 and earlier instead, you have two ways to make bit7z compatible:

+ Configure bit7z with the CMake option `-DBIT7Z_USE_LEGACY_IUNKNOWN=ON`; _or_
+ Configure bit7z for 7-zip v22.01 (i.e., `-DBIT7Z_7ZIP_VERSION="22.01"`).

<details>
<summary>Expand for more details!</summary>
<summary>Expand for more details</summary>

_On Linux and macOS_, 7-zip v23.01 introduced breaking changes to the IUnknown interface. If you build bit7z for such a version of 7-zip (the default), it will not support using the shared libraries from previous versions of 7-zip (or from p7zip). Conversely, bit7z made for earlier versions of 7-zip or for p7zip is incompatible with the shared libraries from 7-zip v23.01 and later.
_On Linux and macOS_, 7-zip v23.01 introduced breaking changes to the IUnknown interface.
As a result, if you build bit7z for such a version of 7-zip (the default), it will not support using the shared libraries from previous versions of 7-zip (or from p7zip).
Conversely, bit7z made for earlier versions of 7-zip or for p7zip is incompatible with the shared libraries from 7-zip v23.01 and later.

You can build the shared libraries of 7-zip v23.01 in a backward-compatible mode by defining the macro `Z7_USE_VIRTUAL_DESTRUCTOR_IN_IUNKNOWN`. If this is your case, you can build bit7z for v23.01 using the option `BIT7Z_USE_LEGACY_IUNKNOWN` (in this case, bit7z will be compatible also with previous versions of 7-zip/p7zip).
You can build the shared libraries of 7-zip v23.01 in a backward-compatible mode by defining the macro `Z7_USE_VIRTUAL_DESTRUCTOR_IN_IUNKNOWN`.
If this is your case, you'll need to enable the `BIT7Z_USE_LEGACY_IUNKNOWN` to make bit7z work (in this case, bit7z will be compatible also with previous versions of 7-zip/p7zip).

</details>

### 🌐 String Encoding
### 🗺️ String Encoding

By default, bit7z follows the [UTF-8 Everywhere Manifesto](http://utf8everywhere.org/) to simplify the use of the library within cross-platform projects.
In short, this means that:
Expand Down Expand Up @@ -374,6 +384,6 @@ Older versions (v3.x and earlier) of bit7z were released under the [GNU General

<br/>
<div align="center">
Copyright &copy; 2014 - 2023 Riccardo Ostani (<a href="https://github.com/rikyoz">@rikyoz</a>)
Copyright &copy; 2014 - 2024 Riccardo Ostani (<a href="https://github.com/rikyoz">@rikyoz</a>)
</div>
<br/>
2 changes: 1 addition & 1 deletion cmake/Dependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ endif()
if( NOT USE_STANDARD_FILESYSTEM OR NOT STANDARD_FILESYSTEM_COMPILES OR BIT7Z_BUILD_TESTS )
CPMAddPackage( NAME ghc_filesystem
GITHUB_REPOSITORY rikyoz/filesystem
GIT_TAG c0dcd0b090da7dffc74b124a6f164f54dbbb5ccb
GIT_TAG 983650f374699e3979f9cdefe13ddff60bd4ac68
DOWNLOAD_ONLY YES )
if( ghc_filesystem_ADDED )
message( STATUS "ghc::filesystem source code available at ${ghc_filesystem_SOURCE_DIR}" )
Expand Down
2 changes: 2 additions & 0 deletions include/bit7z/bit7z.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
#ifndef BIT7Z_HPP
#define BIT7Z_HPP

#include "bitarchiveeditor.hpp"
#include "bitarchivereader.hpp"
#include "bitarchivewriter.hpp"
#include "bitexception.hpp"
#include "bitfilecompressor.hpp"
#include "bitfileextractor.hpp"
Expand Down
39 changes: 34 additions & 5 deletions include/bit7z/bitarchiveeditor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ using std::vector;

using EditedItems = std::unordered_map< uint32_t, BitItemsVector::value_type >;

enum struct DeletePolicy : std::uint8_t {
ItemOnly,
RecurseDirs
};

/**
* @brief The BitArchiveEditor class allows creating new file archives or updating old ones.
* Update operations supported are the addition of new items,
Expand Down Expand Up @@ -132,18 +137,40 @@ class BIT7Z_MAYBE_UNUSED BitArchiveEditor final : public BitArchiveWriter {
void updateItem( const tstring& itemPath, istream& inStream );

/**
* @brief Marks the item at the given index as deleted.
* @brief Marks as deleted the item at the given index.
*
* @note By default, if the item is a folder, only its metadata is deleted, not the files within it.
* If instead the policy is set to DeletePolicy::RecurseDirs,
* then the items within the folder will also be deleted.
*
* @param index the index of the item to be deleted.
* @param index the index of the item to be deleted.
* @param policy the policy to be used when deleting items.
*
* @throws BitException if the index is invalid.
*/
void deleteItem( uint32_t index );
void deleteItem( uint32_t index, DeletePolicy policy = DeletePolicy::ItemOnly );

/**
* @brief Marks the item at the given path (in the archive) as deleted.
* @brief Marks as deleted the archive's item(s) with the specified path.
*
* @note By default, if the marked item is a folder, only its metadata will be deleted, not the files within it.
* To delete the folder contents as well, set the `policy` to `DeletePolicy::RecurseDirs`.
*
* @note The specified path must not begin with a path separator.
*
* @note A path with a trailing separator will _only_ be considered if
* the policy is DeletePolicy::RecurseDirs, and will only match folders;
* with DeletePolicy::ItemOnly, no item will match a path with a trailing separator.
*
* @note Generally, archives may contain multiple items with the same paths.
* If this is the case, all matching items will be marked as deleted according to the specified policy.
*
* @param itemPath the path (in the archive) of the item to be deleted.
* @param policy the policy to be used when deleting items.
*
* @throws BitException if the specified path is empty or invalid, or if no matching item could be found.
*/
void deleteItem( const tstring& itemPath );
void deleteItem( const tstring& itemPath, DeletePolicy policy = DeletePolicy::ItemOnly );

/**
* @brief Applies the requested changes (i.e., rename/update/delete operations) to the input archive.
Expand All @@ -164,6 +191,8 @@ class BIT7Z_MAYBE_UNUSED BitArchiveEditor final : public BitArchiveWriter {
auto hasNewData( uint32_t index ) const noexcept -> bool override;

auto hasNewProperties( uint32_t index ) const noexcept -> bool override;

void markItemAsDeleted( uint32_t index );
};

} // namespace bit7z
Expand Down
8 changes: 8 additions & 0 deletions include/bit7z/bitarchiveitem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ class BitArchiveItem : public BitGenericItem {
*/
BIT7Z_NODISCARD auto path() const -> tstring override;

/**
* @note Same as path(), but returning a native string (i.e., std::wstring on Windows, std::string elsewhere).
*
* @return the path of the item in the archive, if available or inferable from the name, or an empty string
* otherwise.
*/
BIT7Z_NODISCARD auto nativePath() const -> native_string;

/**
* @return the uncompressed size of the item.
*/
Expand Down
9 changes: 3 additions & 6 deletions include/bit7z/bitoutputarchive.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,10 @@ class BitOutputArchive {
* @brief Adds all the files inside the given directory path that match the given wildcard filter.
*
* @param inDir the directory where to search for files to be added to the output archive.
* @param filter (optional) the wildcard filter to be used for searching the files.
* @param recursive (optional) recursively search the files in the given directory
* and all of its subdirectories.
* @param filter the wildcard filter to be used for searching the files.
* @param recursive recursively search the files in the given directory and all of its subdirectories.
*/
void addFiles( const tstring& inDir,
const tstring& filter = BIT7Z_STRING( "*" ),
bool recursive = true );
void addFiles( const tstring& inDir, const tstring& filter, bool recursive );

/**
* @brief Adds all the files inside the given directory path that match the given wildcard filter.
Expand Down
98 changes: 82 additions & 16 deletions src/bitarchiveeditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,32 +90,98 @@ void BitArchiveEditor::updateItem( const tstring& itemPath, std::istream& inStre
mEditedItems[ findItem( itemPath ) ] = std::make_unique< StdInputItem >( inStream, itemPath ); //-V108
}

void BitArchiveEditor::deleteItem( uint32_t index ) {
void BitArchiveEditor::deleteItem( uint32_t index, DeletePolicy policy ) {
if ( index >= inputArchiveItemsCount() ) {
throw BitException( "Cannot delete item at index " + std::to_string( index ),
make_error_code( BitError::InvalidIndex ) );
}
mEditedItems.erase( index );
setDeletedIndex( index );

markItemAsDeleted( index );

const auto& deletedItem = inputArchive()->itemAt( index );
if ( !deletedItem.isDir() || policy == DeletePolicy::ItemOnly ) {
return;
}

const auto deletedPath = deletedItem.nativePath() + fs::path::preferred_separator;
if ( deletedPath.size() <= 1 ) { // The original path was empty
return;
}

for ( const auto& item: *inputArchive() ) {
if ( starts_with( item.nativePath(), deletedPath ) ) {
markItemAsDeleted( item.index() );
}
}
}

/**
* Determines if the given itemPath is within the specified base directory.
*
* @param itemPath The path to the item to be checked.
* @param isDir A boolean indicating whether the itemPath represents a directory.
* @param base The base directory against which the itemPath is checked.
*
* @return true if the itemPath is located within the base directory, false otherwise.
*/
inline auto isInsidePath( const fs::path& itemPath, bool isDir, const fs::path& base ) -> bool {
const auto relativePath = itemPath.lexically_relative( base ).native();
/* The relative path of the item with respect to the base directory can be:
* 1) A single dot component;
* 2) A path not starting with a dot-dot component (i.e., the item is inside the base path);
* 3) A path starting with a dot-dot component (i.e., the item is outside the base path).
*
* This function is called after having evaluated itemPath == base to false;
* also, 7-Zip reports folder paths without trailing separators.
* Hence, the first case can be restricted further to only the case
* where the base path has a trailing path separator: this means that if
* the relative path is a single dot, the item must be a folder to be considered inside the base path.
*
* The remaining cases 2 and 3 are checked as follows:
* - Either the relative path doesn't start with two dots (and then the item is inside the base path) or
* - It starts with two dots, but it is not a dot-dot component (e.g., a normal filename starting with two dots).
*/
return ( relativePath != BIT7Z_NATIVE_STRING( "." ) || isDir ) &&
( !starts_with( relativePath, BIT7Z_NATIVE_STRING( ".." ) ) ||
( relativePath.size() > 2 && !isPathSeparator( relativePath[ 2 ] ) ) );
}

void BitArchiveEditor::deleteItem( const tstring& itemPath ) {
auto res = std::find_if( inputArchive()->cbegin(), inputArchive()->cend(),
// Note: we don't use auto for the parameter type to support compilation with GCC 4.9
[ &, this ]( const BitArchiveItemOffset& archiveItem ) {
if ( archiveItem.path() == itemPath ) {
mEditedItems.erase( archiveItem.index() );
setDeletedIndex( archiveItem.index() );
return true;
}
return false;
} );
if ( res == inputArchive()->cend() ) {
throw BitException( "Could not mark the item as deleted",
void BitArchiveEditor::deleteItem( const tstring& itemPath, DeletePolicy policy ) {
// The path to be deleted must be relative to the root of the archive.
if ( itemPath.empty() || isPathSeparator( itemPath.front() ) ) {
throw BitException( "Could not mark any path as deleted",
std::make_error_code( std::errc::invalid_argument ), itemPath );
}

bool deleted = false;

// Normalized form of the path to be deleted inside the archive.
const auto deletedPath = tstring_to_path( itemPath ).lexically_normal();
for ( const auto& item: *inputArchive() ) {
const fs::path path = item.nativePath();

// The current item is marked as deleted if either:
// - it is lexicographically equivalent to the path to be deleted; or
// - we need to recursively delete directories,
// and the path of the item is (lexicographically) inside the path to be deleted.
if ( path == deletedPath ||
( policy == DeletePolicy::RecurseDirs && isInsidePath( path, item.isDir(), deletedPath ) ) ) {
markItemAsDeleted( item.index() );
deleted = true;
}
}

if ( !deleted ) {
throw BitException( "Could not mark any path as deleted",
std::make_error_code( std::errc::no_such_file_or_directory ), itemPath );
}
}

void BitArchiveEditor::markItemAsDeleted( uint32_t index ) {
mEditedItems.erase( index );
setDeletedIndex( index );
}

void BitArchiveEditor::setUpdateMode( UpdateMode mode ) {
if ( mode == UpdateMode::None ) {
throw BitException( "Cannot set update mode to UpdateMode::None",
Expand Down
9 changes: 9 additions & 0 deletions src/bitarchiveitem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ auto BitArchiveItem::path() const -> tstring {
return path.getString();
}

auto BitArchiveItem::nativePath() const -> native_string {
BitPropVariant path = itemProperty( BitProperty::Path );
if ( path.isEmpty() ) {
path = itemProperty( BitProperty::Name );
return path.isEmpty() ? native_string{} : path.getNativeString();
}
return path.getNativeString();
}

auto BitArchiveItem::size() const -> uint64_t {
const BitPropVariant size = itemProperty( BitProperty::Size );
return size.isEmpty() ? 0 : size.getUInt64();
Expand Down
Loading

0 comments on commit 69c0244

Please sign in to comment.