diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index 191cefb2..afa88013 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -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] @@ -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 @@ -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. @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 0127820e..a3cc671f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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" ) diff --git a/README.md b/README.md index d5554a11..3e6d9fc7 100644 --- a/README.md +++ b/README.md @@ -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"`). @@ -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"`). +
- Expand for more details! + Expand for more details -_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).
-### 🌐 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: @@ -374,6 +384,6 @@ Older versions (v3.x and earlier) of bit7z were released under the [GNU General
-Copyright © 2014 - 2023 Riccardo Ostani (@rikyoz) +Copyright © 2014 - 2024 Riccardo Ostani (@rikyoz)

diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index 7fd8b764..49498016 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -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}" ) diff --git a/include/bit7z/bit7z.hpp b/include/bit7z/bit7z.hpp index fd103d3a..ac3afd8e 100644 --- a/include/bit7z/bit7z.hpp +++ b/include/bit7z/bit7z.hpp @@ -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" diff --git a/include/bit7z/bitarchiveeditor.hpp b/include/bit7z/bitarchiveeditor.hpp index 0d8da12a..0e47ddb4 100644 --- a/include/bit7z/bitarchiveeditor.hpp +++ b/include/bit7z/bitarchiveeditor.hpp @@ -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, @@ -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. @@ -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 diff --git a/include/bit7z/bitarchiveitem.hpp b/include/bit7z/bitarchiveitem.hpp index 671af111..1300d1a4 100644 --- a/include/bit7z/bitarchiveitem.hpp +++ b/include/bit7z/bitarchiveitem.hpp @@ -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. */ diff --git a/include/bit7z/bitoutputarchive.hpp b/include/bit7z/bitoutputarchive.hpp index 2d14bfb2..5c53cfbe 100644 --- a/include/bit7z/bitoutputarchive.hpp +++ b/include/bit7z/bitoutputarchive.hpp @@ -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. diff --git a/src/bitarchiveeditor.cpp b/src/bitarchiveeditor.cpp index 92880848..2bbe1f67 100644 --- a/src/bitarchiveeditor.cpp +++ b/src/bitarchiveeditor.cpp @@ -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", diff --git a/src/bitarchiveitem.cpp b/src/bitarchiveitem.cpp index aabdadd9..ae957f58 100644 --- a/src/bitarchiveitem.cpp +++ b/src/bitarchiveitem.cpp @@ -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(); diff --git a/src/bitarchivereader.cpp b/src/bitarchivereader.cpp index ac75fb78..74e1334b 100644 --- a/src/bitarchivereader.cpp +++ b/src/bitarchivereader.cpp @@ -49,9 +49,12 @@ auto BitArchiveReader::archiveProperties() const -> map< BitProperty, BitPropVar return result; } -auto BitArchiveReader::items() const -> vector< BitArchiveItemInfo > { - vector< BitArchiveItemInfo > result; - for ( uint32_t i = 0; i < itemsCount(); ++i ) { +auto BitArchiveReader::items() const -> std::vector< BitArchiveItemInfo > { + const auto count = itemsCount(); + + std::vector< BitArchiveItemInfo > result; + result.reserve( count ); + for ( uint32_t i = 0; i < count; ++i ) { BitArchiveItemInfo item( i ); for ( uint32_t j = kpidNoProperty; j <= kpidCopyLink; ++j ) { // We cast property twice (here and in archiveProperty), to make the code is easier to read. @@ -61,7 +64,7 @@ auto BitArchiveReader::items() const -> vector< BitArchiveItemInfo > { item.setProperty( property, propertyValue ); } } - result.push_back( item ); + result.push_back( std::move( item ) ); } return result; } diff --git a/src/bititemsvector.cpp b/src/bititemsvector.cpp index 6f58ed78..d98a3f5c 100644 --- a/src/bititemsvector.cpp +++ b/src/bititemsvector.cpp @@ -38,9 +38,10 @@ void BitItemsVector::indexDirectory( const fs::path& inDir, void BitItemsVector::indexPaths( const std::vector< tstring >& inPaths, IndexingOptions options ) { const auto symlinkPolicy = options.followSymlinks ? SymlinkPolicy::Follow : SymlinkPolicy::DoNotFollow; - for ( const auto& filePath : inPaths ) { + for ( const auto& inputPath : inPaths ) { + const auto filePath = tstring_to_path( inputPath ); const FilesystemItem item{ filePath, - options.retainFolderStructure ? tstring_to_path( filePath ) : fs::path{}, + options.retainFolderStructure ? filePath : fs::path{}, symlinkPolicy }; indexItem( item, options ); } diff --git a/src/internal/fsutil.cpp b/src/internal/fsutil.cpp index 6f8858ec..6b8c127d 100644 --- a/src/internal/fsutil.cpp +++ b/src/internal/fsutil.cpp @@ -303,10 +303,6 @@ auto fsutil::get_file_attributes_ex( const fs::path& filePath, constexpr auto kLongPathPrefix = BIT7Z_NATIVE_STRING( R"(\\?\)" ); -inline auto starts_with( const native_string& str, const native_string& prefix ) -> bool { - return str.size() >= prefix.size() && str.compare( 0, prefix.size(), prefix ) == 0; -} - auto fsutil::should_format_long_path( const fs::path& path ) -> bool { constexpr auto kMaxDosFilenameSize = 12; diff --git a/src/internal/stringutil.hpp b/src/internal/stringutil.hpp index 2ac190bb..21d39aae 100644 --- a/src/internal/stringutil.hpp +++ b/src/internal/stringutil.hpp @@ -74,6 +74,34 @@ inline auto path_to_wide_string( const fs::path& path ) -> std::wstring { #endif } +inline auto starts_with( const native_string& str, const native_string& prefix ) -> bool { + return str.rfind( prefix, 0 ) == 0; +} + +/** + * Checks if the given character is a valid path separator on the target platform. + * + * @note On Windows, both / and \ are considered as path separators. + * On other platforms, only / is considered a path separator. + * + * @param character the character value to be checked. + * + * @return true if the character is a valid path separator on the target platform, false otherwise. + */ +inline auto isPathSeparator( char character ) -> bool { +#ifdef _WIN32 + return character == '/' || character == '\\'; +#else + return character == '/'; +#endif +} + +#ifdef _WIN32 +inline auto isPathSeparator( wchar_t character ) -> bool { + return character == L'/' || character == L'\\'; +} +#endif + } // namespace bit7z #endif //STRINGUTIL_HPP diff --git a/tests/src/test_dateutil.cpp b/tests/src/test_dateutil.cpp index 1519fe7e..86e25753 100644 --- a/tests/src/test_dateutil.cpp +++ b/tests/src/test_dateutil.cpp @@ -10,7 +10,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -#if !defined(__GNUC__) || __GNUC__ >= 5 +#if !defined(__GNUC__) || __GNUC__ >= 5 || defined(__clang__) #include #include @@ -23,7 +23,7 @@ using namespace bit7z; struct DateConversionTest { const char* name; - std::time_t dateTime; + std::int64_t dateTime; // Using std::int64_t instead of std::time_t to use 64-bit time on x86 FILETIME fileTime; }; @@ -36,7 +36,6 @@ TEST_CASE( "fsutil: Date conversions", "[fsutil][date functions]" ) { DateConversionTest{ "1 January 1970, 00:00", 0, { 3577643008, 27111902 } } ); DYNAMIC_SECTION( "Date: " << testDate.name ) { - #ifndef _WIN32 SECTION( "From std::time_t to FILETIME" ) { auto output = time_to_FILETIME( testDate.dateTime ); @@ -45,20 +44,17 @@ TEST_CASE( "fsutil: Date conversions", "[fsutil][date functions]" ) { } #endif - SECTION( "From FILETIME...") { - std::time_t output{}; #ifndef _WIN32 - SECTION( "...to std::filesystem::file_time_type" ) { - auto result = FILETIME_to_file_time_type( testDate.fileTime ); - output = std::time_t{ duration_cast< seconds >( result.time_since_epoch() ).count() }; - } + SECTION( "From FILETIME to std::filesystem::file_time_type" ) { + auto result = FILETIME_to_file_time_type( testDate.fileTime ); + auto output = duration_cast< seconds >( result.time_since_epoch() ).count(); + REQUIRE( output == testDate.dateTime ); + } #endif - SECTION( "...to bit7z::time_type" ) { - auto result = FILETIME_to_time_type( testDate.fileTime ); - output = bit7z::time_type::clock::to_time_t( result ); - } - + SECTION( "From FILETIME to bit7z::time_type" ) { + auto result = FILETIME_to_time_type( testDate.fileTime ); + auto output = bit7z::time_type::clock::to_time_t( result ); REQUIRE( output == testDate.dateTime ); } }