From 27abf0c49151e61e03ed3d98226005fb3e511cc2 Mon Sep 17 00:00:00 2001 From: Oz Date: Fri, 2 Aug 2024 21:03:10 +0200 Subject: [PATCH] Re-implement file streams using low-level IO posix functions --- CMakeLists.txt | 2 + cmake/CompilerOptions.cmake | 2 + src/bitarchiveitem.cpp | 7 +- src/internal/cfileinstream.cpp | 40 +++---- src/internal/cfileinstream.hpp | 30 ++++- src/internal/cfileoutstream.cpp | 51 +++------ src/internal/cfileoutstream.hpp | 33 ++++-- src/internal/cmultivolumeoutstream.cpp | 2 +- src/internal/cvolumeoutstream.cpp | 10 +- src/internal/cvolumeoutstream.hpp | 4 + src/internal/fileextractcallback.cpp | 4 - src/internal/filehandle.cpp | 145 +++++++++++++++++++++++++ src/internal/filehandle.hpp | 65 +++++++++++ 13 files changed, 311 insertions(+), 84 deletions(-) create mode 100644 src/internal/filehandle.cpp create mode 100644 src/internal/filehandle.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 160157c5..4f98f36d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,7 @@ set( HEADERS src/internal/extractcallback.hpp src/internal/failuresourcecategory.hpp src/internal/fileextractcallback.hpp + src/internal/filehandle.cpp src/internal/fixedbufferextractcallback.hpp src/internal/formatdetect.hpp src/internal/fsindexer.hpp @@ -155,6 +156,7 @@ set( SOURCES src/internal/extractcallback.cpp src/internal/failuresourcecategory.cpp src/internal/fileextractcallback.cpp + src/internal/filehandle.cpp src/internal/fixedbufferextractcallback.cpp src/internal/formatdetect.cpp src/internal/fsindexer.cpp diff --git a/cmake/CompilerOptions.cmake b/cmake/CompilerOptions.cmake index a25843ec..f935c068 100644 --- a/cmake/CompilerOptions.cmake +++ b/cmake/CompilerOptions.cmake @@ -5,6 +5,8 @@ # compiler-specific options if( MSVC ) + target_compile_definitions( ${LIB_TARGET} PRIVATE _CRT_DECLARE_NONSTDC_NAMES=0 ) + # setting a pdb file name for debug builds (otherwise it is not generated!) set_target_properties( ${LIB_TARGET} PROPERTIES COMPILE_PDB_NAME_DEBUG ${LIB_TARGET}${CMAKE_DEBUG_POSTFIX} ) diff --git a/src/bitarchiveitem.cpp b/src/bitarchiveitem.cpp index e18a6e2f..8052625d 100644 --- a/src/bitarchiveitem.cpp +++ b/src/bitarchiveitem.cpp @@ -128,9 +128,12 @@ auto BitArchiveItem::crc() const -> uint32_t { } // On MSVC, these macros are not defined, so we define them here. -#if !defined(S_ISLNK) && defined(S_IFMT) +#if !defined(S_ISLNK) #ifndef S_IFLNK -constexpr auto S_IFLNK = 0xA000; + constexpr auto S_IFLNK = 0xA000; +#endif +#ifndef S_IFMT + constexpr auto S_IFMT = 0xF000; #endif #define S_ISLNK( m ) (((m) & S_IFMT) == S_IFLNK) #endif diff --git a/src/internal/cfileinstream.cpp b/src/internal/cfileinstream.cpp index e1ae046b..2888c49a 100644 --- a/src/internal/cfileinstream.cpp +++ b/src/internal/cfileinstream.cpp @@ -12,34 +12,26 @@ #include "internal/cfileinstream.hpp" -#include "bitexception.hpp" -#include "internal/cstdinstream.hpp" -#include "internal/fs.hpp" -#include "internal/stringutil.hpp" +namespace bit7z { -#include +CFileInStream::CFileInStream( const fs::path& filePath ) : mFile{ filePath } {} -namespace bit7z { +COM_DECLSPEC_NOTHROW +STDMETHODIMP CFileInStream::Read( void* data, UInt32 size, UInt32* processedSize ) noexcept { + if ( processedSize != nullptr ) { + *processedSize = 0; + } -CFileInStream::CFileInStream( const fs::path& filePath ) : CStdInStream( mFileStream ) { - /* Disabling std::ifstream's buffering, as unbuffered IO gives better performance - * with the data block sizes read by 7-Zip. - * Note: we need to do this before and after opening the file (https://stackoverflow.com/a/59161297/3497024). */ - mFileStream.rdbuf()->pubsetbuf( nullptr, 0 ); - openFile( filePath ); - mFileStream.rdbuf()->pubsetbuf( nullptr, 0 ); + if ( size == 0 ) { + return S_OK; + } + + return mFile.read( data, size, processedSize ); } -void CFileInStream::openFile( const fs::path& filePath ) { - mFileStream.open( filePath, std::ios::in | std::ios::binary ); // flawfinder: ignore - if ( mFileStream.fail() ) { -#if defined( __MINGW32__ ) || defined( __MINGW64__ ) - std::error_code error{ errno, std::generic_category() }; - throw BitException( "Failed to open the archive file", error, path_to_tstring( filePath ) ); -#else - throw BitException( "Failed to open the archive file", last_error_code(), path_to_tstring( filePath ) ); -#endif - } +COM_DECLSPEC_NOTHROW +STDMETHODIMP CFileInStream::Seek( Int64 offset, UInt32 seekOrigin, UInt64* newPosition ) noexcept { + return mFile.seek( static_cast< SeekOrigin >( seekOrigin ), offset, newPosition ); } -} // namespace bit7z \ No newline at end of file +} // namespace bit7z diff --git a/src/internal/cfileinstream.hpp b/src/internal/cfileinstream.hpp index 25bf1485..9f2099c2 100644 --- a/src/internal/cfileinstream.hpp +++ b/src/internal/cfileinstream.hpp @@ -10,21 +10,41 @@ #ifndef CFILEINSTREAM_HPP #define CFILEINSTREAM_HPP -#include "internal/cstdinstream.hpp" +#include "bitexception.hpp" +#include "internal/com.hpp" +#include "internal/filehandle.hpp" #include "internal/fs.hpp" +#include "internal/guids.hpp" +#include "internal/macros.hpp" -#include +#include <7zip/IStream.h> namespace bit7z { -class CFileInStream : public CStdInStream { +class CFileInStream : public IInStream, public CMyUnknownImp { public: explicit CFileInStream( const fs::path& filePath ); - void openFile( const fs::path& filePath ); + CFileInStream( const CFileInStream& ) = delete; + + CFileInStream( CFileInStream&& ) = delete; + + auto operator=( const CFileInStream& ) -> CFileInStream& = delete; + + auto operator=( CFileInStream&& ) -> CFileInStream& = delete; + + MY_UNKNOWN_VIRTUAL_DESTRUCTOR( ~CFileInStream() ) = default; + + // IInStream + BIT7Z_STDMETHOD( Read, void* data, UInt32 size, UInt32* processedSize ); + + BIT7Z_STDMETHOD( Seek, Int64 offset, UInt32 seekOrigin, UInt64* newPosition ); + + // NOLINTNEXTLINE(modernize-use-noexcept, modernize-use-trailing-return-type, readability-identifier-length) + MY_UNKNOWN_IMP1( IInStream ) //-V2507 //-V2511 //-V835 //-V3504 private: - fs::ifstream mFileStream; + InputFile mFile; }; } // namespace bit7z diff --git a/src/internal/cfileoutstream.cpp b/src/internal/cfileoutstream.cpp index 76d07086..bd66fbdf 100644 --- a/src/internal/cfileoutstream.cpp +++ b/src/internal/cfileoutstream.cpp @@ -12,47 +12,30 @@ #include "internal/cfileoutstream.hpp" -#include "bitexception.hpp" -#include "internal/cstdoutstream.hpp" -#include "internal/stringutil.hpp" - -#include -#include -#include - namespace bit7z { -CFileOutStream::CFileOutStream( fs::path filePath, bool createAlways ) - : CStdOutStream( mFileStream ), mFilePath{ std::move( filePath ) } { - std::error_code error; - if ( !createAlways && fs::exists( mFilePath, error ) ) { - if ( !error ) { - // The call to fs::exists succeeded, but the filePath exists, and this is an error. - error = std::make_error_code( std::errc::file_exists ); - } - throw BitException( "Failed to create the output file", error, path_to_tstring( mFilePath ) ); +CFileOutStream::CFileOutStream( const fs::path& filePath, bool createAlways ) + : mFile( filePath, createAlways ), mFilePath{ filePath } {} + +COM_DECLSPEC_NOTHROW +STDMETHODIMP CFileOutStream::Write( const void* data, UInt32 size, UInt32* processedSize ) noexcept { + if ( processedSize != nullptr ) { + *processedSize = 0; } - /* Disabling std::ofstream's buffering, as unbuffered IO gives better performance - * with the data block sizes written by 7-Zip. - * Note: we need to do this before and after opening the file (https://stackoverflow.com/a/59161297/3497024). */ - mFileStream.rdbuf()->pubsetbuf( nullptr, 0 ); - mFileStream.open( mFilePath, std::ios::binary | std::ios::trunc ); // flawfinder: ignore - if ( mFileStream.fail() ) { -#if defined( __MINGW32__ ) || defined( __MINGW64__ ) - error = std::error_code{ errno, std::generic_category() }; - throw BitException( "Failed to open the output file", error, path_to_tstring( mFilePath ) ); -#else - throw BitException( "Failed to open the output file", last_error_code(), path_to_tstring( mFilePath ) ); -#endif + if ( size == 0 ) { + return S_OK; } - mFileStream.rdbuf()->pubsetbuf( nullptr, 0 ); + + return mFile.write( data, size, processedSize ); } -auto CFileOutStream::fail() const -> bool { - return mFileStream.fail(); +COM_DECLSPEC_NOTHROW +STDMETHODIMP CFileOutStream::Seek( Int64 offset, UInt32 seekOrigin, UInt64* newPosition ) noexcept { + return mFile.seek( static_cast< SeekOrigin >( seekOrigin ), offset, newPosition ); } + COM_DECLSPEC_NOTHROW STDMETHODIMP CFileOutStream::SetSize( UInt64 newSize ) noexcept { std::error_code error; @@ -60,8 +43,4 @@ STDMETHODIMP CFileOutStream::SetSize( UInt64 newSize ) noexcept { return error ? E_FAIL : S_OK; } -auto CFileOutStream::path() const -> const fs::path& { - return mFilePath; -} - } // namespace bit7z \ No newline at end of file diff --git a/src/internal/cfileoutstream.hpp b/src/internal/cfileoutstream.hpp index d2ec5279..a93caeac 100644 --- a/src/internal/cfileoutstream.hpp +++ b/src/internal/cfileoutstream.hpp @@ -10,28 +10,43 @@ #ifndef CFILEOUTSTREAM_HPP #define CFILEOUTSTREAM_HPP -#include "bitdefines.hpp" -#include "internal/cstdoutstream.hpp" -#include "internal/fs.hpp" +#include "bitexception.hpp" +#include "internal/com.hpp" +#include "internal/filehandle.hpp" +#include "internal/guids.hpp" #include "internal/macros.hpp" -#include +#include <7zip/IStream.h> namespace bit7z { -class CFileOutStream : public CStdOutStream { +class CFileOutStream : public IOutStream, public CMyUnknownImp { public: - explicit CFileOutStream( fs::path filePath, bool createAlways = false ); + explicit CFileOutStream( const fs::path& filePath, bool createAlways = false ); - BIT7Z_NODISCARD auto path() const -> const fs::path&; + CFileOutStream( const CFileOutStream& ) = delete; - BIT7Z_NODISCARD auto fail() const -> bool; + CFileOutStream( CFileOutStream&& ) = delete; + + auto operator=( const CFileOutStream& ) -> CFileOutStream& = delete; + + auto operator=( CFileOutStream&& ) -> CFileOutStream& = delete; + + MY_UNKNOWN_VIRTUAL_DESTRUCTOR( ~CFileOutStream() ) = default; + + // IOutStream + BIT7Z_STDMETHOD( Write, void const* data, UInt32 size, UInt32* processedSize ); + + BIT7Z_STDMETHOD( Seek, Int64 offset, UInt32 seekOrigin, UInt64* newPosition ); BIT7Z_STDMETHOD( SetSize, UInt64 newSize ); + // NOLINTNEXTLINE(modernize-use-noexcept, modernize-use-trailing-return-type, readability-identifier-length) + MY_UNKNOWN_IMP1( IOutStream ) //-V2507 //-V2511 //-V835 //-V3504 + private: + OutputFile mFile; fs::path mFilePath; - fs::ofstream mFileStream; }; } // namespace bit7z diff --git a/src/internal/cmultivolumeoutstream.cpp b/src/internal/cmultivolumeoutstream.cpp index 08b04773..48b7dc68 100644 --- a/src/internal/cmultivolumeoutstream.cpp +++ b/src/internal/cmultivolumeoutstream.cpp @@ -144,7 +144,7 @@ STDMETHODIMP CMultiVolumeOutStream::SetSize( UInt64 newSize ) noexcept { newSize -= volume->currentSize(); } while ( !mVolumes.empty() ) { - const fs::path volumePath = mVolumes.back()->path(); + const fs::path volumePath = mVolumes.back()->volumePath(); mVolumes.pop_back(); std::error_code error; fs::remove( volumePath, error ); diff --git a/src/internal/cvolumeoutstream.cpp b/src/internal/cvolumeoutstream.cpp index e283ad41..fbf61ccd 100644 --- a/src/internal/cvolumeoutstream.cpp +++ b/src/internal/cvolumeoutstream.cpp @@ -18,12 +18,12 @@ namespace bit7z { CVolumeOutStream::CVolumeOutStream( const fs::path& volumeName ) - : CFileOutStream( volumeName ), mCurrentOffset{ 0 }, mCurrentSize{ 0 } {} + : CFileOutStream( volumeName ), mCurrentOffset{ 0 }, mCurrentSize{ 0 }, mVolumePath{ volumeName } {} COM_DECLSPEC_NOTHROW STDMETHODIMP CVolumeOutStream::Seek( Int64 offset, UInt32 seekOrigin, UInt64* newPosition ) noexcept { UInt64 pos{}; - RINOK( CStdOutStream::Seek( offset, seekOrigin, &pos ) ) //-V3504 + RINOK( CFileOutStream::Seek( offset, seekOrigin, &pos ) ) //-V3504 mCurrentOffset = pos; if ( newPosition != nullptr ) { *newPosition = pos; @@ -38,7 +38,7 @@ STDMETHODIMP CVolumeOutStream::Write( const void* data, UInt32 size, UInt32* pro } UInt32 writtenSize{}; - RINOK( CStdOutStream::Write( data, size, &writtenSize ) ) //-V3504 + RINOK( CFileOutStream::Write( data, size, &writtenSize ) ) //-V3504 if ( writtenSize == 0 && size != 0 ) { return E_FAIL; @@ -70,4 +70,8 @@ void CVolumeOutStream::setCurrentSize( uint64_t currentSize ) { mCurrentSize = currentSize; } +auto CVolumeOutStream::volumePath() const -> const fs::path& { + return mVolumePath; +} + } // namespace bit7z \ No newline at end of file diff --git a/src/internal/cvolumeoutstream.hpp b/src/internal/cvolumeoutstream.hpp index 69a50a04..a8497d64 100644 --- a/src/internal/cvolumeoutstream.hpp +++ b/src/internal/cvolumeoutstream.hpp @@ -26,6 +26,8 @@ class CVolumeOutStream final : public CFileOutStream { BIT7Z_NODISCARD auto currentSize() const -> uint64_t; + BIT7Z_NODISCARD auto volumePath() const -> const fs::path&; + void setCurrentSize( uint64_t currentSize ); // IOutStream @@ -39,6 +41,8 @@ class CVolumeOutStream final : public CFileOutStream { uint64_t mCurrentOffset; uint64_t mCurrentSize; + + fs::path mVolumePath; }; } // namespace bit7z diff --git a/src/internal/fileextractcallback.cpp b/src/internal/fileextractcallback.cpp index 5bebf969..7c30178e 100644 --- a/src/internal/fileextractcallback.cpp +++ b/src/internal/fileextractcallback.cpp @@ -48,10 +48,6 @@ auto FileExtractCallback::finishOperation( OperationResult operationResult ) -> return result; } - if ( mFileOutStream->fail() ) { - return E_FAIL; - } - mFileOutStream.Release(); // We need to release the file to change its modified time. if ( extractMode() != ExtractMode::Extract ) { // No need to set attributes or modified time of the file. diff --git a/src/internal/filehandle.cpp b/src/internal/filehandle.cpp new file mode 100644 index 00000000..5d3c960e --- /dev/null +++ b/src/internal/filehandle.cpp @@ -0,0 +1,145 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +/* + * bit7z - A C++ static library to interface with the 7-zip shared libraries. + * Copyright (c) 2014-2024 Riccardo Ostani - All Rights Reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * 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/. + */ + +#include "filehandle.hpp" + +#include "bitexception.hpp" +#include "internal/stringutil.hpp" + +#include +#ifdef _WIN32 +#include + +#if !defined( __MINGW32__ ) && !defined( __MINGW64__ ) +constexpr auto close = &_close; +constexpr auto lseek64 = &_lseeki64; +constexpr auto write = &_write; +constexpr auto read = &_read; +#else +#include // For _SH_DENYNO +#endif +#else +#include +#endif + +namespace bit7z { + +inline auto errno_as_hresult() -> HRESULT { +#ifdef _WIN32 + return HRESULT_FROM_WIN32( _doserrno ); +#else + return HRESULT_FROM_WIN32( static_cast< DWORD >( errno ) ); +#endif +} + +FileHandle::FileHandle( const handle_t handle ) : mHandle{ handle } {} + +FileHandle::~FileHandle() { + close( mHandle ); +} + +auto FileHandle::seek( SeekOrigin origin, const Int64 distance, UInt64* newPosition ) const noexcept -> HRESULT { + const auto result = lseek64( mHandle, distance, static_cast< int >( origin ) ); + if ( result < 0 ) { + return errno_as_hresult(); + } + if ( newPosition != nullptr ) { + *newPosition = static_cast< UInt64 >( result ); + } + return S_OK; +} + +inline auto openOutputFile( const fs::path& filePath, const bool createAlways ) -> handle_t { + std::error_code error; + if ( !createAlways && fs::exists( filePath, error ) ) { + if ( !error ) { + // The call to fs::exists succeeded, but the filePath exists, and this is an error. + error = std::make_error_code( std::errc::file_exists ); + } + throw BitException( "Failed to create the output file", error, path_to_tstring( filePath ) ); + } + +#ifdef _WIN32 + handle_t handle = -1; + const errno_t result = _wsopen_s( &handle, + filePath.c_str(), + _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, + _SH_DENYNO, + _S_IREAD | _S_IWRITE ); + if ( result != 0 ) { + const auto errorValue = HRESULT_FROM_WIN32( _doserrno ); + throw BitException( "Could not open the input file", make_hresult_code( errorValue ) ); + } +#else + handle_t handle = open( filePath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR ); // NOLINT(*-vararg) + if ( handle < 0 ) { + throw BitException( "Could not open the output file", last_error_code() ); + } +#endif + return handle; +} + +OutputFile::OutputFile( const fs::path& filePath, const bool createAlways ) + : FileHandle{ openOutputFile( filePath, createAlways ) } {} + +auto OutputFile::write( const void* data, const UInt32 size, UInt32* processedSize ) const noexcept -> HRESULT { + const auto result = ::write( mHandle, data, size ); + if ( result < 0 ) { + if ( processedSize != nullptr ) { + *processedSize = 0; + } + return errno_as_hresult(); + } + if ( processedSize != nullptr ) { + *processedSize = static_cast< UInt32 >( result ); + } + return S_OK; +} + +inline auto openInputFile( const fs::path& filePath ) -> handle_t { +#ifdef _WIN32 + handle_t handle = -1; + const errno_t result = _wsopen_s( &handle, + filePath.c_str(), + _O_RDONLY | _O_BINARY, + _SH_DENYNO, + _S_IREAD ); + if ( result != 0 ) { + const auto error = HRESULT_FROM_WIN32( _doserrno ); + throw BitException( "Could not open the input file", make_hresult_code( error ) ); + } +#else + handle_t handle = open( filePath.c_str(), O_RDONLY ); // NOLINT(*-vararg) + if ( handle < 0 ) { + throw BitException( "Could not open the input file", last_error_code() ); + } +#endif + return handle; +} + +InputFile::InputFile( const fs::path& filePath ) : FileHandle{ openInputFile( filePath ) } {} + +auto InputFile::read( void* data, const UInt32 size, UInt32* processedSize ) const noexcept -> HRESULT { + const auto result = ::read( mHandle, data, size ); + if ( result < 0 ) { + if ( processedSize != nullptr ) { + *processedSize = 0; + } + return errno_as_hresult(); + } + if ( processedSize != nullptr ) { + *processedSize = static_cast< UInt32 >( result ); + } + return S_OK; +} + +} // namespace bit7z diff --git a/src/internal/filehandle.hpp b/src/internal/filehandle.hpp new file mode 100644 index 00000000..2a78d855 --- /dev/null +++ b/src/internal/filehandle.hpp @@ -0,0 +1,65 @@ +/* + * bit7z - A C++ static library to interface with the 7-zip shared libraries. + * Copyright (c) 2014-2024 Riccardo Ostani - All Rights Reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * 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/. + */ + +#ifndef FILEHANDLE_HPP +#define FILEHANDLE_HPP + +#include "bitwindows.hpp" // For HRESULT +#include "internal/fs.hpp" + +#include // For 7-Zip integer types + +#include +#include + +namespace bit7z { + +using handle_t = int; + +enum class SeekOrigin : std::uint8_t { + Begin = SEEK_SET, + CurrentPosition = SEEK_CUR, + End = SEEK_END +}; + +class FileHandle { + protected: + handle_t mHandle{ -1 }; + + explicit FileHandle( handle_t handle ); + + public: + explicit FileHandle( const FileHandle& ) = delete; + + explicit FileHandle( FileHandle&& ) = delete; + + auto operator=( const FileHandle& ) = delete; + + auto operator=( FileHandle&& ) = delete; + + ~FileHandle(); + + auto seek( SeekOrigin origin, Int64 distance, UInt64* newPosition ) const noexcept -> HRESULT; +}; + +struct OutputFile final : public FileHandle { + explicit OutputFile( const fs::path& filePath, bool createAlways ); + + auto write( const void* data, UInt32 size, UInt32* processedSize ) const noexcept -> HRESULT; +}; + +struct InputFile final : public FileHandle { + explicit InputFile( const fs::path& filePath ); + + auto read( void* data, UInt32 size, UInt32* processedSize ) const noexcept -> HRESULT; +}; + +} // namespace bit7z + +#endif //FILEHANDLE_HPP