From 3e93be27e3a316583d60d1f8a192016161f3f8a8 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 21 Dec 2025 16:45:39 -0800 Subject: [PATCH] chore: files --- CMakeLists.txt | 5 +- include/boost/capy/detail/except.hpp | 28 +- include/boost/capy/detail/file_posix.hpp | 116 ++++++ include/boost/capy/detail/file_stdio.hpp | 92 +++++ include/boost/capy/detail/file_win32.hpp | 106 +++++ include/boost/capy/error.hpp | 22 ++ include/boost/capy/file.hpp | 375 ++++++++++++++++++ include/boost/capy/file_mode.hpp | 98 +++++ src/detail/except.cpp | 74 +++- src/detail/file_posix.cpp | 358 +++++++++++++++++ src/detail/file_stdio.cpp | 357 +++++++++++++++++ src/detail/file_win32.cpp | 385 +++++++++++++++++++ src/detail/win32_unicode_path.hpp | 84 ++++ test/unit/CMakeLists.txt | 3 + test/unit/Jamfile | 10 + test/unit/detail/file_posix.cpp | 39 ++ test/unit/detail/file_stdio.cpp | 39 ++ test/unit/detail/file_win32.cpp | 39 ++ test/unit/file.cpp | 69 ++++ test/unit/file_mode.cpp | 11 + test/unit/file_test.hpp | 469 +++++++++++++++++++++++ 21 files changed, 2772 insertions(+), 7 deletions(-) create mode 100644 include/boost/capy/detail/file_posix.hpp create mode 100644 include/boost/capy/detail/file_stdio.hpp create mode 100644 include/boost/capy/detail/file_win32.hpp create mode 100644 include/boost/capy/error.hpp create mode 100644 include/boost/capy/file.hpp create mode 100644 include/boost/capy/file_mode.hpp create mode 100644 src/detail/file_posix.cpp create mode 100644 src/detail/file_stdio.cpp create mode 100644 src/detail/file_win32.cpp create mode 100644 src/detail/win32_unicode_path.hpp create mode 100644 test/unit/detail/file_posix.cpp create mode 100644 test/unit/detail/file_stdio.cpp create mode 100644 test/unit/detail/file_win32.cpp create mode 100644 test/unit/file.cpp create mode 100644 test/unit/file_mode.cpp create mode 100644 test/unit/file_test.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f75c90d..037d38b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,7 +67,7 @@ foreach (BOOST_CAPY_DEPENDENCY ${BOOST_CAPY_DEPENDENCIES}) endforeach () # Conditional dependencies if (BOOST_CAPY_BUILD_TESTS) - set(BOOST_CAPY_UNIT_TEST_LIBRARIES url) + set(BOOST_CAPY_UNIT_TEST_LIBRARIES filesystem url) endif () if (BOOST_CAPY_BUILD_EXAMPLES) # set(BOOST_CAPY_EXAMPLE_LIBRARIES url) @@ -140,6 +140,7 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/src PREFIX "src" FILES ${BOOST_CAP function(boost_capy_setup_properties target) target_compile_features(${target} PUBLIC cxx_constexpr) target_include_directories(${target} PUBLIC "${PROJECT_SOURCE_DIR}/include") + target_include_directories(${target} PRIVATE "${PROJECT_SOURCE_DIR}") target_link_libraries(${target} PUBLIC ${BOOST_CAPY_DEPENDENCIES}) target_compile_definitions(${target} PUBLIC BOOST_CAPY_NO_LIB) target_compile_definitions(${target} PRIVATE BOOST_CAPY_SOURCE) @@ -161,6 +162,8 @@ if (BOOST_CAPY_MRDOCS_BUILD) return() endif() +# Complete dependency list + add_library(boost_capy include/boost/capy.hpp build/Jamfile ${BOOST_CAPY_HEADERS} ${BOOST_CAPY_SOURCES}) add_library(Boost::capy ALIAS boost_capy) boost_capy_setup_properties(boost_capy) diff --git a/include/boost/capy/detail/except.hpp b/include/boost/capy/detail/except.hpp index fc15071..caaab06 100644 --- a/include/boost/capy/detail/except.hpp +++ b/include/boost/capy/detail/except.hpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace boost { namespace capy { @@ -21,12 +22,35 @@ namespace detail { BOOST_CAPY_DECL void BOOST_NORETURN throw_bad_typeid( source_location const& loc = BOOST_CURRENT_LOCATION); +BOOST_CAPY_DECL void BOOST_NORETURN throw_bad_alloc( + source_location const& loc = BOOST_CURRENT_LOCATION); + +BOOST_CAPY_DECL void BOOST_NORETURN throw_invalid_argument( + source_location const& loc = BOOST_CURRENT_LOCATION); + BOOST_CAPY_DECL void BOOST_NORETURN throw_invalid_argument( - core::string_view s = "invalid argument", + char const* what, + source_location const& loc = BOOST_CURRENT_LOCATION); + +BOOST_CAPY_DECL void BOOST_NORETURN throw_length_error( + source_location const& loc = BOOST_CURRENT_LOCATION); + +BOOST_CAPY_DECL void BOOST_NORETURN throw_length_error( + char const* what, source_location const& loc = BOOST_CURRENT_LOCATION); BOOST_CAPY_DECL void BOOST_NORETURN throw_logic_error( - core::string_view s = "logic error", + source_location const& loc = BOOST_CURRENT_LOCATION); + +BOOST_CAPY_DECL void BOOST_NORETURN throw_out_of_range( + source_location const& loc = BOOST_CURRENT_LOCATION); + +BOOST_CAPY_DECL void BOOST_NORETURN throw_runtime_error( + char const* what, + source_location const& loc = BOOST_CURRENT_LOCATION); + +BOOST_CAPY_DECL void BOOST_NORETURN throw_system_error( + system::error_code const& ec, source_location const& loc = BOOST_CURRENT_LOCATION); } // detail diff --git a/include/boost/capy/detail/file_posix.hpp b/include/boost/capy/detail/file_posix.hpp new file mode 100644 index 0000000..3394516 --- /dev/null +++ b/include/boost/capy/detail/file_posix.hpp @@ -0,0 +1,116 @@ +// +// Copyright (c) 2022 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_DETAIL_FILE_POSIX_HPP +#define BOOST_CAPY_DETAIL_FILE_POSIX_HPP + +#include + +#if ! defined(BOOST_CAPY_NO_POSIX_FILE) +# if ! defined(__APPLE__) && ! defined(__linux__) && ! defined(__FreeBSD__) && ! defined(__NetBSD__) +# define BOOST_CAPY_NO_POSIX_FILE +# endif +#endif + +#if ! defined(BOOST_CAPY_USE_POSIX_FILE) +# if ! defined(BOOST_CAPY_NO_POSIX_FILE) +# define BOOST_CAPY_USE_POSIX_FILE 1 +# else +# define BOOST_CAPY_USE_POSIX_FILE 0 +# endif +#endif + +#if BOOST_CAPY_USE_POSIX_FILE + +#include +#include +#include +#include + +namespace boost { +namespace capy { +namespace detail { + +// Implementation of File for POSIX systems. +class file_posix +{ + int fd_ = -1; + + BOOST_CAPY_DECL + static + int + native_close(int& fd); + +public: + using native_handle_type = int; + + BOOST_CAPY_DECL + ~file_posix(); + + file_posix() = default; + + BOOST_CAPY_DECL + file_posix(file_posix&& other) noexcept; + + BOOST_CAPY_DECL + file_posix& + operator=(file_posix&& other) noexcept; + + native_handle_type + native_handle() const + { + return fd_; + } + + BOOST_CAPY_DECL + void + native_handle(native_handle_type fd); + + bool + is_open() const + { + return fd_ != -1; + } + + BOOST_CAPY_DECL + void + close(system::error_code& ec); + + BOOST_CAPY_DECL + void + open(char const* path, file_mode mode, system::error_code& ec); + + BOOST_CAPY_DECL + std::uint64_t + size(system::error_code& ec) const; + + BOOST_CAPY_DECL + std::uint64_t + pos(system::error_code& ec) const; + + BOOST_CAPY_DECL + void + seek(std::uint64_t offset, system::error_code& ec); + + BOOST_CAPY_DECL + std::size_t + read(void* buffer, std::size_t n, system::error_code& ec); + + BOOST_CAPY_DECL + std::size_t + write(void const* buffer, std::size_t n, system::error_code& ec); +}; + +} // detail +} // capy +} // boost + +#endif + +#endif diff --git a/include/boost/capy/detail/file_stdio.hpp b/include/boost/capy/detail/file_stdio.hpp new file mode 100644 index 0000000..a03cf0b --- /dev/null +++ b/include/boost/capy/detail/file_stdio.hpp @@ -0,0 +1,92 @@ +// +// Copyright (c) 2022 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_DETAIL_FILE_STDIO_HPP +#define BOOST_CAPY_DETAIL_FILE_STDIO_HPP + +#include +#include +#include +#include +#include + +namespace boost { +namespace capy { +namespace detail { + +// Implementation of File which uses cstdio. +class file_stdio +{ + std::FILE* f_ = nullptr; + +public: + using native_handle_type = std::FILE*; + + BOOST_CAPY_DECL + ~file_stdio(); + + file_stdio() = default; + + BOOST_CAPY_DECL + file_stdio(file_stdio&& other) noexcept; + + BOOST_CAPY_DECL + file_stdio& + operator=(file_stdio&& other) noexcept; + + std::FILE* + native_handle() const + { + return f_; + } + + BOOST_CAPY_DECL + void + native_handle(std::FILE* f); + + bool + is_open() const + { + return f_ != nullptr; + } + + BOOST_CAPY_DECL + void + close(system::error_code& ec); + + BOOST_CAPY_DECL + void + open(char const* path, file_mode mode, system::error_code& ec); + + BOOST_CAPY_DECL + std::uint64_t + size(system::error_code& ec) const; + + BOOST_CAPY_DECL + std::uint64_t + pos(system::error_code& ec) const; + + BOOST_CAPY_DECL + void + seek(std::uint64_t offset, system::error_code& ec); + + BOOST_CAPY_DECL + std::size_t + read(void* buffer, std::size_t n, system::error_code& ec); + + BOOST_CAPY_DECL + std::size_t + write(void const* buffer, std::size_t n, system::error_code& ec); +}; + +} // detail +} // capy +} // boost + +#endif diff --git a/include/boost/capy/detail/file_win32.hpp b/include/boost/capy/detail/file_win32.hpp new file mode 100644 index 0000000..e1223e0 --- /dev/null +++ b/include/boost/capy/detail/file_win32.hpp @@ -0,0 +1,106 @@ +// +// Copyright (c) 2022 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_DETAIL_FILE_WIN32_HPP +#define BOOST_CAPY_DETAIL_FILE_WIN32_HPP + +#include + +#if ! defined(BOOST_CAPY_USE_WIN32_FILE) +# ifdef _WIN32 +# define BOOST_CAPY_USE_WIN32_FILE 1 +# else +# define BOOST_CAPY_USE_WIN32_FILE 0 +# endif +#endif + +#if BOOST_CAPY_USE_WIN32_FILE + +#include +#include +#include +#include + +namespace boost { +namespace capy { +namespace detail { + +// Implementation of File for Win32. +class file_win32 +{ + boost::winapi::HANDLE_ h_ = + boost::winapi::INVALID_HANDLE_VALUE_; + +public: + using native_handle_type = boost::winapi::HANDLE_; + + BOOST_CAPY_DECL + ~file_win32(); + + file_win32() = default; + + BOOST_CAPY_DECL + file_win32(file_win32&& other) noexcept; + + BOOST_CAPY_DECL + file_win32& + operator=(file_win32&& other) noexcept; + + native_handle_type + native_handle() + { + return h_; + } + + BOOST_CAPY_DECL + void + native_handle(native_handle_type h); + + bool + is_open() const + { + return h_ != boost::winapi::INVALID_HANDLE_VALUE_; + } + + BOOST_CAPY_DECL + void + close(system::error_code& ec); + + BOOST_CAPY_DECL + void + open(char const* path, file_mode mode, system::error_code& ec); + + BOOST_CAPY_DECL + std::uint64_t + size(system::error_code& ec) const; + + BOOST_CAPY_DECL + std::uint64_t + pos(system::error_code& ec) const; + + BOOST_CAPY_DECL + void + seek(std::uint64_t offset, system::error_code& ec); + + BOOST_CAPY_DECL + std::size_t + read(void* buffer, std::size_t n, system::error_code& ec); + + BOOST_CAPY_DECL + std::size_t + write(void const* buffer, std::size_t n, system::error_code& ec); +}; + +} // detail +} // capy +} // boost + +#endif + +#endif diff --git a/include/boost/capy/error.hpp b/include/boost/capy/error.hpp new file mode 100644 index 0000000..0885a1b --- /dev/null +++ b/include/boost/capy/error.hpp @@ -0,0 +1,22 @@ +// +// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_ERROR_HPP +#define BOOST_CAPY_ERROR_HPP + +#include +#include + +namespace boost { +namespace capy { + +} // capy +} // boost + +#endif diff --git a/include/boost/capy/file.hpp b/include/boost/capy/file.hpp new file mode 100644 index 0000000..e3838ce --- /dev/null +++ b/include/boost/capy/file.hpp @@ -0,0 +1,375 @@ +// +// Copyright (c) 2022 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2025 Mohammad Nejati +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_FILE_HPP +#define BOOST_CAPY_FILE_HPP + +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace capy { + +/** A platform-independent file stream. + + This class provides a portable interface for + reading from and writing to files. +*/ +class file +{ +#if BOOST_CAPY_USE_WIN32_FILE + using impl_type = detail::file_win32; +#elif BOOST_CAPY_USE_POSIX_FILE + using impl_type = detail::file_posix; +#else + using impl_type = detail::file_stdio; +#endif + + impl_type impl_; + +public: + /** The type of the underlying native file handle. + + This type is platform-specific. + */ + using native_handle_type = impl_type::native_handle_type; + + /** Constructor. + + There is no open file initially. + */ + file() = default; + + /** Constructor. + + Open a file at the given path with the specified mode. + + @par Exception Safety + Exception thrown if operation fails. + + @throw system_error + Operation fails. + + @param path The UTF-8 encoded path to the file. + + @param mode The file mode to use. + + @see + @ref file_mode, + @ref open. + */ + file(char const* path, file_mode mode) + { + open(path, mode); + } + + /** Constructor. + + The moved-from object behaves as if default-constructed. + */ + file(file&& other) noexcept = default; + + /** Assignment + + The moved-from object behaves as if default-constructed. + */ + file& + operator=( + file&& other) noexcept = default; + + /** Destructor + + If the file is open it is first closed. + */ + ~file() = default; + + /** Returns the native handle associated with the file. + */ + native_handle_type + native_handle() + { + return impl_.native_handle(); + } + + /** Set the native file handle. + + If the file is open it is first closed. + + @param h The native handle to assign. + */ + void + native_handle(native_handle_type h) + { + impl_.native_handle(h); + } + + /** Return true if the file is open. + */ + bool + is_open() const + { + return impl_.is_open(); + } + + /** Close the file if open. + + Note that, The descriptor is closed even if the function + reports an error. + + @param ec Set to the error, if any occurred. + */ + void + close(system::error_code& ec) + { + impl_.close(ec); + } + + /** Close the file if open. + + Note that, The descriptor is closed even if the function + reports an error. + + @par Exception Safety + Exception thrown if operation fails. + + @throw system_error + Operation fails. + */ + void + close() + { + system::error_code ec; + impl_.close(ec); + if(ec.failed()) + detail::throw_system_error(ec); + } + + /** Open a file at the given path with the specified mode. + + @param path The UTF-8 encoded path to the file. + + @param mode The file mode to use. + + @param ec Set to the error, if any occurred. + + @see + @ref file_mode. + */ + void + open(char const* path, file_mode mode, system::error_code& ec) + { + impl_.open(path, mode, ec); + } + + /** Open a file at the given path with the specified mode. + + @param path The UTF-8 encoded path to the file. + + @param mode The file mode to use. + + @par Exception Safety + Exception thrown if operation fails. + + @throw system_error + Operation fails. + + @see + @ref file_mode. + */ + void + open(char const* path, file_mode mode) + { + system::error_code ec; + impl_.open(path, mode, ec); + if(ec.failed()) + detail::throw_system_error(ec); + } + + /** Return the size of the open file in bytes. + + @param ec Set to the error, if any occurred. + */ + std::uint64_t + size(system::error_code& ec) const + { + return impl_.size(ec); + } + + /** Return the size of the open file in bytes. + + @par Exception Safety + Exception thrown if operation fails. + + @throw system_error + Operation fails. + */ + std::uint64_t + size() const + { + system::error_code ec; + auto r = impl_.size(ec); + if(ec.failed()) + detail::throw_system_error(ec); + return r; + } + + /** Return the current position in the file, in bytes from the beginning. + + @param ec Set to the error, if any occurred. + */ + std::uint64_t + pos(system::error_code& ec) const + { + return impl_.pos(ec); + } + + /** Return the current position in the file, in bytes from the beginning. + + @par Exception Safety + Exception thrown if operation fails. + + @throw system_error + Operation fails. + */ + std::uint64_t + pos() const + { + system::error_code ec; + auto r = impl_.pos(ec); + if(ec.failed()) + detail::throw_system_error(ec); + return r; + } + + /** Set the current position in the file. + + @param offset The byte offset from the beginning of the file. + + @param ec Set to the error, if any occurred. + */ + void + seek(std::uint64_t offset, system::error_code& ec) + { + impl_.seek(offset, ec); + } + + /** Set the current position in the file. + + @par Exception Safety + Exception thrown if operation fails. + + @throw system_error + Operation fails. + + @param offset The byte offset from the beginning of the file. + */ + void + seek(std::uint64_t offset) + { + system::error_code ec; + impl_.seek(offset, ec); + if(ec.failed()) + detail::throw_system_error(ec); + } + + /** Read data from the file. + + @return The number of bytes read. Returns + 0 on end-of-file or if an error occurs (in + which case @p ec is set). + + @param buffer The buffer to store the read data. + + @param n The number of bytes to read. + + @param ec Set to the error, if any occurred. + */ + std::size_t + read(void* buffer, std::size_t n, system::error_code& ec) + { + return impl_.read(buffer, n, ec); + } + + /** Read data from the file. + + @par Exception Safety + Exception thrown if operation fails. + + @throw system_error + Operation fails. + + @return The number of bytes read. Returns + 0 on end-of-file. + + @param buffer The buffer to store the read data. + + @param n The number of bytes to read. + */ + std::size_t + read(void* buffer, std::size_t n) + { + system::error_code ec; + auto r = impl_.read(buffer, n, ec); + if(ec.failed()) + detail::throw_system_error(ec); + return r; + } + + /** Write data to the file. + + @return The number of bytes written. + Returns 0 on error (in which case @p ec is + set). + + @param buffer The buffer containing the data to write. + + @param n The number of bytes to write. + + @param ec Set to the error, if any occurred. + */ + std::size_t + write(void const* buffer, std::size_t n, system::error_code& ec) + { + return impl_.write(buffer, n, ec); + } + + /** Write data to the file. + + @par Exception Safety + Exception thrown if operation fails. + + @throw system_error + Operation fails. + + @return The number of bytes written. + + @param buffer The buffer containing the data to write. + + @param n The number of bytes to write. + */ + std::size_t + write(void const* buffer, std::size_t n) + { + system::error_code ec; + auto r = impl_.write(buffer, n, ec); + if(ec.failed()) + detail::throw_system_error(ec); + return r; + } +}; + +} // capy +} // boost + +#endif diff --git a/include/boost/capy/file_mode.hpp b/include/boost/capy/file_mode.hpp new file mode 100644 index 0000000..1fe69c1 --- /dev/null +++ b/include/boost/capy/file_mode.hpp @@ -0,0 +1,98 @@ +// +// Copyright (c) 2022 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2025 Mohammad Nejati +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_FILE_MODE_HPP +#define BOOST_CAPY_FILE_MODE_HPP + +namespace boost { +namespace capy { + +/** File open modes + + These modes are used when opening files using + instances of the @ref file. + + @code + file_mode access sharing seeking file std mode + -------------------------------------------------------------------------------------- + read read-only shared random must exist "rb" + scan read-only shared sequential must exist "rbS" + write read/write exclusive random create/truncate "wb+" + write_new read/write exclusive random must not exist "wbx" + write_existing read/write exclusive random must exist "rb+" + append write-only exclusive sequential create/truncate "ab" + append_existing write-only exclusive sequential must exist "ab" + @endcode + + @see + @ref file. +*/ +enum class file_mode +{ + /** Random read-only access to an existing file. + */ + read, + + /** Sequential read-only access to an existing file. + */ + scan, + + /** Random reading and writing to a new or truncated file. + + This mode permits random-access reading and writing + for the specified file. If the file does not exist + prior to the function call, it is created with an + initial size of zero bytes. Otherwise if the file + already exists, the size is truncated to zero bytes. + */ + write, + + /** Random reading and writing to a new file only. + + This mode permits random-access reading and writing + for the specified file. The file will be created with + an initial size of zero bytes. If the file already exists + prior to the function call, an error is returned and + no file is opened. + */ + write_new, + + /** Random write-only access to existing file. + + If the file does not exist, an error is generated. + */ + write_existing, + + /** Appending to a new or truncated file. + + The current file position shall be set to the end of + the file prior to each write. + + @li If the file does not exist, it is created. + + @li If the file exists, it is truncated to + zero size upon opening. + */ + append, + + /** Appending to an existing file. + + The current file position shall be set to the end of + the file prior to each write. + + If the file does not exist, an error is generated. + */ + append_existing +}; + +} // capy +} // boost + +#endif diff --git a/src/detail/except.cpp b/src/detail/except.cpp index dbd2c72..a36f8a1 100644 --- a/src/detail/except.cpp +++ b/src/detail/except.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -24,20 +25,85 @@ throw_bad_typeid( throw_exception(std::bad_typeid(), loc); } +void +throw_bad_alloc( + source_location const& loc) +{ + throw_exception( + std::bad_alloc(), loc); +} + +void +throw_invalid_argument( + source_location const& loc) +{ + throw_exception( + std::invalid_argument( + "invalid argument"), + loc); +} + void throw_invalid_argument( - core::string_view s, + char const* what, + source_location const& loc) +{ + throw_exception( + std::invalid_argument(what), loc); +} + +void +throw_length_error( + source_location const& loc) +{ + throw_exception( + std::length_error( + "length error"), loc); +} + +void +throw_length_error( + char const* what, source_location const& loc) { - throw_exception(std::invalid_argument(s), loc); + throw_exception( + std::length_error(what), loc); } void throw_logic_error( - core::string_view s, source_location const& loc) { - throw_exception(std::logic_error(s), loc); + throw_exception( + std::logic_error( + "logic error"), + loc); +} + +void +throw_out_of_range( + source_location const& loc) +{ + throw_exception( + std::out_of_range("out of range"), loc); +} + +void +throw_runtime_error( + char const* what, + source_location const& loc) +{ + throw_exception( + std::runtime_error(what), loc); +} + +void +throw_system_error( + system::error_code const& ec, + source_location const& loc) +{ + throw_exception( + system::system_error(ec), loc); } } // detail diff --git a/src/detail/file_posix.cpp b/src/detail/file_posix.cpp new file mode 100644 index 0000000..f4300de --- /dev/null +++ b/src/detail/file_posix.cpp @@ -0,0 +1,358 @@ +// +// Copyright (c) 2022 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#include + +#if BOOST_CAPY_USE_POSIX_FILE + +#include +#include +#include +#include +#include +#include +#include +#include + +#if ! defined(BOOST_CAPY_NO_POSIX_FADVISE) +# if defined(__APPLE__) || (defined(__ANDROID__) && (__ANDROID_API__ < 21)) +# define BOOST_CAPY_NO_POSIX_FADVISE +# endif +#endif + +#if ! defined(BOOST_CAPY_USE_POSIX_FADVISE) +# if ! defined(BOOST_CAPY_NO_POSIX_FADVISE) +# define BOOST_CAPY_USE_POSIX_FADVISE 1 +# else +# define BOOST_CAPY_USE_POSIX_FADVISE 0 +# endif +#endif + +namespace boost { +namespace capy { +namespace detail { + +int +file_posix:: +native_close(native_handle_type& fd) +{ +/* https://github.com/boostorg/beast/issues/1445 + + This function is tuned for Linux / Mac OS: + + * only calls close() once + * returns the error directly to the caller + * does not loop on EINTR + + If this is incorrect for the platform, then the + caller will need to implement their own type + meeting the File requirements and use the correct + behavior. + + See: + http://man7.org/linux/man-pages/man2/close.2.html +*/ + int ev = 0; + if(fd != -1) + { + if(::close(fd) != 0) + ev = errno; + fd = -1; + } + return ev; +} + +file_posix:: +~file_posix() +{ + native_close(fd_); +} + +file_posix:: +file_posix( + file_posix&& other) noexcept + : fd_(boost::exchange(other.fd_, -1)) +{ +} + +file_posix& +file_posix:: +operator=( + file_posix&& other) noexcept +{ + if(&other == this) + return *this; + native_close(fd_); + fd_ = other.fd_; + other.fd_ = -1; + return *this; +} + +void +file_posix:: +native_handle(native_handle_type fd) +{ + native_close(fd_); + fd_ = fd; +} + +void +file_posix:: +close( + system::error_code& ec) +{ + auto const ev = native_close(fd_); + if(ev) + ec.assign(ev, + system::system_category()); + else + ec = {}; +} + +void +file_posix:: +open(char const* path, file_mode mode, system::error_code& ec) +{ + auto const ev = native_close(fd_); + if(ev) + ec.assign(ev, + system::system_category()); + else + ec = {}; + + int f = 0; +#if BOOST_CAPY_USE_POSIX_FADVISE + int advise = 0; +#endif + switch(mode) + { + default: + case file_mode::read: + f = O_RDONLY; + #if BOOST_CAPY_USE_POSIX_FADVISE + advise = POSIX_FADV_RANDOM; + #endif + break; + case file_mode::scan: + f = O_RDONLY; + #if BOOST_CAPY_USE_POSIX_FADVISE + advise = POSIX_FADV_SEQUENTIAL; + #endif + break; + + case file_mode::write: + f = O_RDWR | O_CREAT | O_TRUNC; + #if BOOST_CAPY_USE_POSIX_FADVISE + advise = POSIX_FADV_RANDOM; + #endif + break; + + case file_mode::write_new: + f = O_RDWR | O_CREAT | O_EXCL; + #if BOOST_CAPY_USE_POSIX_FADVISE + advise = POSIX_FADV_RANDOM; + #endif + break; + + case file_mode::write_existing: + f = O_RDWR | O_EXCL; + #if BOOST_CAPY_USE_POSIX_FADVISE + advise = POSIX_FADV_RANDOM; + #endif + break; + + case file_mode::append: + f = O_WRONLY | O_CREAT | O_APPEND; + #if BOOST_CAPY_USE_POSIX_FADVISE + advise = POSIX_FADV_SEQUENTIAL; + #endif + break; + + case file_mode::append_existing: + f = O_WRONLY | O_APPEND; + #if BOOST_CAPY_USE_POSIX_FADVISE + advise = POSIX_FADV_SEQUENTIAL; + #endif + break; + } + for(;;) + { + fd_ = ::open(path, f, 0644); + if(fd_ != -1) + break; + auto const ev = errno; + if(ev != EINTR) + { + ec.assign(ev, + system::system_category()); + return; + } + } +#if BOOST_CAPY_USE_POSIX_FADVISE + if(::posix_fadvise(fd_, 0, 0, advise)) + { + auto const ev = errno; + native_close(fd_); + ec.assign(ev, + system::system_category()); + return; + } +#endif + ec = {}; +} + +std::uint64_t +file_posix:: +size( + system::error_code& ec) const +{ + if(fd_ == -1) + { + ec = make_error_code( + system::errc::bad_file_descriptor); + return 0; + } + struct stat st; + if(::fstat(fd_, &st) != 0) + { + ec.assign(errno, + system::system_category()); + return 0; + } + ec = {}; + return st.st_size; +} + +std::uint64_t +file_posix:: +pos( + system::error_code& ec) const +{ + if(fd_ == -1) + { + ec = make_error_code( + system::errc::bad_file_descriptor); + return 0; + } + auto const result = ::lseek(fd_, 0, SEEK_CUR); + if(result == (::off_t)-1) + { + ec.assign(errno, + system::system_category()); + return 0; + } + ec = {}; + return result; +} + +void +file_posix:: +seek(std::uint64_t offset, + system::error_code& ec) +{ + if(fd_ == -1) + { + ec = make_error_code( + system::errc::bad_file_descriptor); + return; + } + auto const result = ::lseek(fd_, offset, SEEK_SET); + if(result == static_cast<::off_t>(-1)) + { + ec.assign(errno, + system::system_category()); + return; + } + ec = {}; +} + +std::size_t +file_posix:: +read(void* buffer, std::size_t n, + system::error_code& ec) +{ + if(fd_ == -1) + { + ec = make_error_code( + system::errc::bad_file_descriptor); + return 0; + } + std::size_t nread = 0; + while(n > 0) + { + // not required to define SSIZE_MAX so we avoid it + constexpr auto ssmax = + static_cast((std::numeric_limits< + decltype(::read(fd_, buffer, n))>::max)()); + auto const amount = (std::min)( + n, ssmax); + auto const result = ::read(fd_, buffer, amount); + if(result == -1) + { + auto const ev = errno; + if(ev == EINTR) + continue; + ec.assign(ev, + system::system_category()); + return nread; + } + if(result == 0) + { + // short read + return nread; + } + n -= result; + nread += result; + buffer = static_cast(buffer) + result; + } + return nread; +} + +std::size_t +file_posix:: +write(void const* buffer, std::size_t n, + system::error_code& ec) +{ + if(fd_ == -1) + { + ec = make_error_code( + system::errc::bad_file_descriptor); + return 0; + } + std::size_t nwritten = 0; + while(n > 0) + { + // not required to define SSIZE_MAX so we avoid it + constexpr auto ssmax = + static_cast((std::numeric_limits< + decltype(::write(fd_, buffer, n))>::max)()); + auto const amount = (std::min)( + n, ssmax); + auto const result = ::write(fd_, buffer, amount); + if(result == -1) + { + auto const ev = errno; + if(ev == EINTR) + continue; + ec.assign(ev, + system::system_category()); + return nwritten; + } + n -= result; + nwritten += result; + buffer = static_cast(buffer) + result; + } + return nwritten; +} + +} // detail +} // capy +} // boost + +#endif diff --git a/src/detail/file_stdio.cpp b/src/detail/file_stdio.cpp new file mode 100644 index 0000000..47fdaf6 --- /dev/null +++ b/src/detail/file_stdio.cpp @@ -0,0 +1,357 @@ +// +// Copyright (c) 2022 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#include "src/detail/win32_unicode_path.hpp" +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace capy { +namespace detail { + +file_stdio:: +~file_stdio() +{ + if(f_) + fclose(f_); +} + +file_stdio:: +file_stdio( + file_stdio&& other) noexcept + : f_(boost::exchange(other.f_, nullptr)) +{ +} + +file_stdio& +file_stdio:: +operator=( + file_stdio&& other) noexcept +{ + if(&other == this) + return *this; + if(f_) + fclose(f_); + f_ = other.f_; + other.f_ = nullptr; + return *this; +} + +void +file_stdio:: +native_handle(std::FILE* f) +{ + if(f_) + fclose(f_); + f_ = f; +} + +void +file_stdio:: +close( + system::error_code& ec) +{ + if(f_) + { + int failed = fclose(f_); + f_ = nullptr; + if(failed) + { + ec.assign(errno, + system::generic_category()); + return; + } + } + ec = {}; +} + +void +file_stdio:: +open(char const* path, file_mode mode, + system::error_code& ec) +{ + if(f_) + { + fclose(f_); + f_ = nullptr; + } + ec = {}; +#ifdef _WIN32 + boost::winapi::WCHAR_ const* s; + detail::win32_unicode_path unicode_path(path, ec); + if (ec) + return; +#else + char const* s; +#endif + switch(mode) + { + default: + case file_mode::read: + #ifdef _WIN32 + s = L"rb"; + #else + s = "rb"; + #endif + break; + + case file_mode::scan: + #ifdef _WIN32 + s = L"rbS"; + #else + s = "rb"; + #endif + break; + + case file_mode::write: + #ifdef _WIN32 + s = L"wb+"; + #else + s = "wb+"; + #endif + break; + + case file_mode::write_new: + { +#ifdef _WIN32 +# if (defined(BOOST_MSVC) && BOOST_MSVC >= 1910) || (defined(_MSVC_STL_VERSION) && _MSVC_STL_VERSION >= 141) + s = L"wbx"; +# else + std::FILE* f0; + auto const ev = ::_wfopen_s(&f0, unicode_path.c_str(), L"rb"); + if(! ev) + { + std::fclose(f0); + ec = make_error_code( + system::errc::file_exists); + return; + } + else if(ev != + system::errc::no_such_file_or_directory) + { + ec.assign(ev, + system::generic_category()); + return; + } + s = L"wb"; +# endif +#else + s = "wbx"; +#endif + break; + } + + case file_mode::write_existing: + #ifdef _WIN32 + s = L"rb+"; + #else + s = "rb+"; + #endif + break; + + case file_mode::append: + #ifdef _WIN32 + s = L"ab"; + #else + s = "ab"; + #endif + break; + + case file_mode::append_existing: + { +#ifdef _WIN32 + std::FILE* f0; + auto const ev = + ::_wfopen_s(&f0, unicode_path.c_str(), L"rb+"); + if(ev) + { + ec.assign(ev, + system::generic_category()); + return; + } +#else + auto const f0 = + std::fopen(path, "rb+"); + if(! f0) + { + ec.assign(errno, + system::generic_category()); + return; + } +#endif + std::fclose(f0); + #ifdef _WIN32 + s = L"ab"; + #else + s = "ab"; + #endif + break; + } + } + +#ifdef _WIN32 + auto const ev = ::_wfopen_s( + &f_, unicode_path.c_str(), s); + if(ev) + { + f_ = nullptr; + ec.assign(ev, + system::generic_category()); + return; + } +#else + f_ = std::fopen(path, s); + if(! f_) + { + ec.assign(errno, + system::generic_category()); + return; + } +#endif +} + +std::uint64_t +file_stdio:: +size( + system::error_code& ec) const +{ + if(! f_) + { + ec = make_error_code( + system::errc::bad_file_descriptor); + return 0; + } + long pos = std::ftell(f_); + if(pos == -1L) + { + ec.assign(errno, + system::generic_category()); + return 0; + } + int result = std::fseek(f_, 0, SEEK_END); + if(result != 0) + { + ec.assign(errno, + system::generic_category()); + return 0; + } + long size = std::ftell(f_); + if(size == -1L) + { + ec.assign(errno, + system::generic_category()); + std::fseek(f_, pos, SEEK_SET); + return 0; + } + result = std::fseek(f_, pos, SEEK_SET); + if(result != 0) + ec.assign(errno, + system::generic_category()); + else + ec = {}; + return size; +} + +std::uint64_t +file_stdio:: +pos( + system::error_code& ec) const +{ + if(! f_) + { + ec = make_error_code( + system::errc::bad_file_descriptor); + return 0; + } + long pos = std::ftell(f_); + if(pos == -1L) + { + ec.assign(errno, + system::generic_category()); + return 0; + } + ec = {}; + return pos; +} + +void +file_stdio:: +seek(std::uint64_t offset, + system::error_code& ec) +{ + if(! f_) + { + ec = make_error_code( + system::errc::bad_file_descriptor); + return; + } + if(offset > static_cast((std::numeric_limits::max)())) + { + ec = make_error_code( + system::errc::invalid_seek); + return; + } + int result = std::fseek(f_, + static_cast(offset), SEEK_SET); + if(result != 0) + ec.assign(errno, + system::generic_category()); + else + ec = {}; +} + +std::size_t +file_stdio:: +read(void* buffer, std::size_t n, + system::error_code& ec) +{ + if(! f_) + { + ec = make_error_code( + system::errc::bad_file_descriptor); + return 0; + } + auto nread = std::fread(buffer, 1, n, f_); + if(std::ferror(f_)) + { + ec.assign(errno, + system::generic_category()); + return 0; + } + return nread; +} + +std::size_t +file_stdio:: +write(void const* buffer, std::size_t n, + system::error_code& ec) +{ + if(! f_) + { + ec = make_error_code( + system::errc::bad_file_descriptor); + return 0; + } + auto nwritten = std::fwrite(buffer, 1, n, f_); + if(std::ferror(f_)) + { + ec.assign(errno, + system::generic_category()); + return 0; + } + return nwritten; +} + +} // detail +} // capy +} // boost diff --git a/src/detail/file_win32.cpp b/src/detail/file_win32.cpp new file mode 100644 index 0000000..b1ee6c7 --- /dev/null +++ b/src/detail/file_win32.cpp @@ -0,0 +1,385 @@ +// +// Copyright (c) 2022 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#include + +#if BOOST_CAPY_USE_WIN32_FILE + +#include "src/detail/win32_unicode_path.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace capy { +namespace detail { + +// VFALCO Can't seem to get boost/detail/winapi to work with +// this so use the non-Ex version for now. +BOOST_CAPY_DECL +winapi::BOOL_ +set_file_pointer_ex( + winapi::HANDLE_ hFile, + winapi::LARGE_INTEGER_ lpDistanceToMove, + winapi::PLARGE_INTEGER_ lpNewFilePointer, + winapi::DWORD_ dwMoveMethod) +{ + auto dwHighPart = lpDistanceToMove.u.HighPart; + auto dwLowPart = winapi::SetFilePointer( + hFile, + lpDistanceToMove.u.LowPart, + &dwHighPart, + dwMoveMethod); + if(dwLowPart == winapi::INVALID_SET_FILE_POINTER_) + return 0; + if(lpNewFilePointer) + { + lpNewFilePointer->u.LowPart = dwLowPart; + lpNewFilePointer->u.HighPart = dwHighPart; + } + return 1; +} + +file_win32:: +~file_win32() +{ + if(h_ != winapi::INVALID_HANDLE_VALUE_) + winapi::CloseHandle(h_); +} + +file_win32:: +file_win32( + file_win32&& other) noexcept + : h_(boost::exchange(other.h_, + winapi::INVALID_HANDLE_VALUE_)) +{ +} + +file_win32& +file_win32:: +operator=( + file_win32&& other) noexcept +{ + if(&other == this) + return *this; + if(h_) + winapi::CloseHandle(h_); + h_ = other.h_; + other.h_ = winapi::INVALID_HANDLE_VALUE_; + return *this; +} + +void +file_win32:: +native_handle(native_handle_type h) +{ + if(h_ != winapi::INVALID_HANDLE_VALUE_) + winapi::CloseHandle(h_); + h_ = h; +} + +void +file_win32:: +close( + system::error_code& ec) +{ + if(h_ != winapi::INVALID_HANDLE_VALUE_) + { + if(! winapi::CloseHandle(h_)) + ec.assign( + winapi::GetLastError(), + system::system_category()); + else + ec = {}; + h_ = winapi::INVALID_HANDLE_VALUE_; + } + else + { + ec = {}; + } +} + +void +file_win32:: +open(char const* path, file_mode mode, + system::error_code& ec) +{ + if(h_ != winapi::INVALID_HANDLE_VALUE_) + { + winapi::CloseHandle(h_); + h_ = winapi::INVALID_HANDLE_VALUE_; + } + winapi::DWORD_ share_mode = 0; + winapi::DWORD_ desired_access = 0; + winapi::DWORD_ creation_disposition = 0; + winapi::DWORD_ flags_and_attributes = 0; +/* + | When the file... + This argument: | Exists Does not exist + -------------------------+------------------------------------------------------ + CREATE_ALWAYS | Truncates Creates + CREATE_NEW +-----------+ Fails Creates + OPEN_ALWAYS ===| does this |===> Opens Creates + OPEN_EXISTING +-----------+ Opens Fails + TRUNCATE_EXISTING | Truncates Fails +*/ + switch(mode) + { + default: + case file_mode::read: + desired_access = winapi::GENERIC_READ_; + share_mode = winapi::FILE_SHARE_READ_; + creation_disposition = winapi::OPEN_EXISTING_; + flags_and_attributes = 0x10000000; // FILE_FLAG_RANDOM_ACCESS + break; + + case file_mode::scan: + desired_access = winapi::GENERIC_READ_; + share_mode = winapi::FILE_SHARE_READ_; + creation_disposition = winapi::OPEN_EXISTING_; + flags_and_attributes = 0x08000000; // FILE_FLAG_SEQUENTIAL_SCAN + break; + + case file_mode::write: + desired_access = winapi::GENERIC_READ_ | + winapi::GENERIC_WRITE_; + creation_disposition = winapi::CREATE_ALWAYS_; + flags_and_attributes = 0x10000000; // FILE_FLAG_RANDOM_ACCESS + break; + + case file_mode::write_new: + desired_access = winapi::GENERIC_READ_ | + winapi::GENERIC_WRITE_; + creation_disposition = winapi::CREATE_NEW_; + flags_and_attributes = 0x10000000; // FILE_FLAG_RANDOM_ACCESS + break; + + case file_mode::write_existing: + desired_access = winapi::GENERIC_READ_ | + winapi::GENERIC_WRITE_; + creation_disposition = winapi::OPEN_EXISTING_; + flags_and_attributes = 0x10000000; // FILE_FLAG_RANDOM_ACCESS + break; + + case file_mode::append: + desired_access = winapi::GENERIC_READ_ | + winapi::GENERIC_WRITE_; + + creation_disposition = winapi::OPEN_ALWAYS_; + flags_and_attributes = 0x08000000; // FILE_FLAG_SEQUENTIAL_SCAN + break; + + case file_mode::append_existing: + desired_access = winapi::GENERIC_READ_ | + winapi::GENERIC_WRITE_; + creation_disposition = winapi::OPEN_EXISTING_; + flags_and_attributes = 0x08000000; // FILE_FLAG_SEQUENTIAL_SCAN + break; + } + + detail::win32_unicode_path unicode_path(path, ec); + if (ec) + return; + h_ = ::CreateFileW( + unicode_path.c_str(), + desired_access, + share_mode, + NULL, + creation_disposition, + flags_and_attributes, + NULL); + if (h_ == winapi::INVALID_HANDLE_VALUE_) + { + ec.assign(winapi::GetLastError(), + system::system_category()); + return; + } + if (mode == file_mode::append || + mode == file_mode::append_existing) + { + winapi::LARGE_INTEGER_ in; + in.QuadPart = 0; + if (!detail::set_file_pointer_ex(h_, in, 0, + winapi::FILE_END_)) + { + ec.assign(winapi::GetLastError(), + system::system_category()); + winapi::CloseHandle(h_); + h_ = winapi::INVALID_HANDLE_VALUE_; + return; + } + } + ec = {}; +} + +std::uint64_t +file_win32:: +size( + system::error_code& ec) const +{ + if(h_ == winapi::INVALID_HANDLE_VALUE_) + { + ec = make_error_code( + system::errc::bad_file_descriptor); + return 0; + } + winapi::LARGE_INTEGER_ fileSize; + if(! winapi::GetFileSizeEx(h_, &fileSize)) + { + ec.assign(winapi::GetLastError(), + system::system_category()); + return 0; + } + ec = {}; + return fileSize.QuadPart; +} + +std::uint64_t +file_win32:: +pos( + system::error_code& ec) const +{ + if(h_ == winapi::INVALID_HANDLE_VALUE_) + { + ec = make_error_code( + system::errc::bad_file_descriptor); + return 0; + } + winapi::LARGE_INTEGER_ in; + winapi::LARGE_INTEGER_ out; + in.QuadPart = 0; + if(! detail::set_file_pointer_ex(h_, in, &out, + winapi::FILE_CURRENT_)) + { + ec.assign(winapi::GetLastError(), + system::system_category()); + return 0; + } + ec = {}; + return out.QuadPart; +} + +void +file_win32:: +seek(std::uint64_t offset, + system::error_code& ec) +{ + if(h_ == winapi::INVALID_HANDLE_VALUE_) + { + ec = make_error_code( + system::errc::bad_file_descriptor); + return; + } + winapi::LARGE_INTEGER_ in; + in.QuadPart = offset; + if(! detail::set_file_pointer_ex(h_, in, 0, + winapi::FILE_BEGIN_)) + { + ec.assign(winapi::GetLastError(), + system::system_category()); + return; + } + ec = {}; +} + +std::size_t +file_win32:: +read(void* buffer, std::size_t n, + system::error_code& ec) +{ + if(h_ == winapi::INVALID_HANDLE_VALUE_) + { + ec = make_error_code( + system::errc::bad_file_descriptor); + return 0; + } + std::size_t nread = 0; + while(n > 0) + { + winapi::DWORD_ amount; + if(n > (std::numeric_limits< + winapi::DWORD_>::max)()) + amount = (std::numeric_limits< + winapi::DWORD_>::max)(); + else + amount = static_cast< + winapi::DWORD_>(n); + winapi::DWORD_ bytesRead; + if(! ::ReadFile(h_, buffer, amount, &bytesRead, 0)) + { + auto const dwError = winapi::GetLastError(); + if(dwError != winapi::ERROR_HANDLE_EOF_) + ec.assign(dwError, + system::system_category()); + else + ec = {}; + return nread; + } + if(bytesRead == 0) + return nread; + n -= bytesRead; + nread += bytesRead; + buffer = static_cast(buffer) + bytesRead; + } + ec = {}; + return nread; +} + +std::size_t +file_win32:: +write(void const* buffer, std::size_t n, + system::error_code& ec) +{ + if(h_ == winapi::INVALID_HANDLE_VALUE_) + { + ec = make_error_code( + system::errc::bad_file_descriptor); + return 0; + } + std::size_t nwritten = 0; + while(n > 0) + { + winapi::DWORD_ amount; + if(n > (std::numeric_limits< + winapi::DWORD_>::max)()) + amount = (std::numeric_limits< + winapi::DWORD_>::max)(); + else + amount = static_cast< + winapi::DWORD_>(n); + winapi::DWORD_ bytesWritten; + if(! ::WriteFile(h_, buffer, amount, &bytesWritten, 0)) + { + auto const dwError = winapi::GetLastError(); + if(dwError != winapi::ERROR_HANDLE_EOF_) + ec.assign(dwError, + system::system_category()); + else + ec = {}; + return nwritten; + } + if(bytesWritten == 0) + return nwritten; + n -= bytesWritten; + nwritten += bytesWritten; + buffer = static_cast(buffer) + bytesWritten; + } + ec = {}; + return nwritten; +} + +} // detail +} // capy +} // boost + +#endif diff --git a/src/detail/win32_unicode_path.hpp b/src/detail/win32_unicode_path.hpp new file mode 100644 index 0000000..4d4fe17 --- /dev/null +++ b/src/detail/win32_unicode_path.hpp @@ -0,0 +1,84 @@ +// +// Copyright (c) 2019 Mika Fischer (mika.fischer@zoopnet.de) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_CAPY_DETAIL_WIN32_UNICODE_PATH_HPP +#define BOOST_CAPY_DETAIL_WIN32_UNICODE_PATH_HPP + +#ifdef _WIN32 +#include +//#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace capy { +namespace detail { + +class win32_unicode_path +{ + using WCHAR_ = boost::winapi::WCHAR_; + +public: + win32_unicode_path(const char* utf8_path, system::error_code& ec) { + int ret = mb2wide(utf8_path, static_buf_.data(), + static_buf_.size()); + if (ret == 0) + { + int sz = mb2wide(utf8_path, nullptr, 0); + if (sz == 0) + { + ec.assign(winapi::GetLastError(), + system::system_category()); + return; + } + dynamic_buf_.resize(sz); + int ret2 = mb2wide(utf8_path, + dynamic_buf_.data(), + dynamic_buf_.size()); + if (ret2 == 0) + { + ec.assign(winapi::GetLastError(), + system::system_category()); + return; + } + } + } + + WCHAR_ const* c_str() const noexcept + { + return dynamic_buf_.empty() + ? static_buf_.data() + : dynamic_buf_.data(); + } + +private: + int mb2wide(const char* utf8_path, WCHAR_* buf, size_t sz) + { + return winapi::MultiByteToWideChar( + winapi::CP_UTF8_, + winapi::MB_ERR_INVALID_CHARS_, + utf8_path, -1, + buf, static_cast(sz)); + } + + std::array static_buf_; + std::vector dynamic_buf_; +}; + +} // detail +} // capy +} // boost + +#endif + +#endif diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 792fc39..144198e 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -20,8 +20,11 @@ add_executable(boost_capy_tests ${PFILES}) target_link_libraries( boost_capy_tests PRIVATE boost_url_test_suite_with_main + Boost::filesystem Boost::capy) +target_include_directories(boost_capy_tests PRIVATE . ../../) + if (TARGET Boost::capy_zlib) target_link_libraries(boost_capy_tests PRIVATE Boost::capy_zlib) endif () diff --git a/test/unit/Jamfile b/test/unit/Jamfile index afd92f8..a2dbbc9 100644 --- a/test/unit/Jamfile +++ b/test/unit/Jamfile @@ -19,6 +19,7 @@ project ../../../url/extra/test_suite/test_main.cpp ../../../url/extra/test_suite/test_suite.cpp . + ../.. ../../../url/extra/test_suite extra on @@ -29,3 +30,12 @@ for local f in [ glob-tree-ex . : *.cpp : ] { run $(f) ; } + +for local f in [ glob-tree-ex . : file*.cpp ] +{ + run $(f) + : requirements + /boost/filesystem//boost_filesystem + off norecover:static + ; +} diff --git a/test/unit/detail/file_posix.cpp b/test/unit/detail/file_posix.cpp new file mode 100644 index 0000000..9149393 --- /dev/null +++ b/test/unit/detail/file_posix.cpp @@ -0,0 +1,39 @@ +// +// Copyright (c) 2022 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +// Test that header file is self-contained. +#include + +#if BOOST_CAPY_USE_POSIX_FILE + +#include "file_test.hpp" + +namespace boost { +namespace capy { +namespace detail { + +class file_posix_test +{ +public: + void + run() + { + test_file(); + } +}; + +TEST_SUITE( + file_posix_test, + "boost.capy.detail.file_posix"); + +} // detail +} // capy +} // boost + +#endif diff --git a/test/unit/detail/file_stdio.cpp b/test/unit/detail/file_stdio.cpp new file mode 100644 index 0000000..a734d47 --- /dev/null +++ b/test/unit/detail/file_stdio.cpp @@ -0,0 +1,39 @@ +// +// Copyright (c) 2022 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +// Test that header file is self-contained. +#include + +#include "../file_test.hpp" + +namespace boost { +namespace capy { +namespace detail { + +class file_stdio_test +{ +public: + void + run() + { +#ifdef _WIN32 + test_file(); +#else + test_file(); +#endif + } +}; + +TEST_SUITE( + file_stdio_test, + "boost.capy.detail.file_stdio"); + +} // detail +} // capy +} // boost diff --git a/test/unit/detail/file_win32.cpp b/test/unit/detail/file_win32.cpp new file mode 100644 index 0000000..840a40a --- /dev/null +++ b/test/unit/detail/file_win32.cpp @@ -0,0 +1,39 @@ +// +// Copyright (c) 2022 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +// Test that header file is self-contained. +#include + +#if BOOST_CAPY_USE_WIN32_FILE + +#include "test/unit/file_test.hpp" + +namespace boost { +namespace capy { +namespace detail { + +class file_win32_test +{ +public: + void + run() + { + test_file(); + } +}; + +TEST_SUITE( + file_win32_test, + "boost.capy.detail.file_win32"); + +} // detail +} // capy +} // boost + +#endif // BOOST_CAPY_USE_WIN32_FILE diff --git a/test/unit/file.cpp b/test/unit/file.cpp new file mode 100644 index 0000000..bab9801 --- /dev/null +++ b/test/unit/file.cpp @@ -0,0 +1,69 @@ +// +// Copyright (c) 2022 Vinnie Falco (vinnie dot falco at gmail dot com) +// Copyright (c) 2025 Mohammad Nejati +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +// Test that header file is self-contained. +#include + +#include +#include "file_test.hpp" + +namespace boost { +namespace capy { + +struct file_test +{ + void + testThrowingOverloads() + { + // constructor + BOOST_TEST_THROWS( + file("missing.txt", file_mode::scan), + system::system_error); + + file f; + char buf[1]; + + BOOST_TEST_THROWS( + f.open("missing.txt", file_mode::scan), + system::system_error); + // BOOST_TEST_THROWS( + // f.close(), + // system::system_error); + BOOST_TEST_THROWS( + f.size(), + system::system_error); + BOOST_TEST_THROWS( + f.pos(), + system::system_error); + BOOST_TEST_THROWS( + f.seek(1), + system::system_error); + BOOST_TEST_THROWS( + f.read(buf, 1), + system::system_error); + BOOST_TEST_THROWS( + f.write(buf, 1), + system::system_error); + } + + void + run() + { + test_file(); + testThrowingOverloads(); + } +}; + +TEST_SUITE( + file_test, + "boost.capy.file"); + +} // capy +} // boost diff --git a/test/unit/file_mode.cpp b/test/unit/file_mode.cpp new file mode 100644 index 0000000..ad9c2d2 --- /dev/null +++ b/test/unit/file_mode.cpp @@ -0,0 +1,11 @@ +// +// Copyright (c) 2022 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +// Test that header file is self-contained. +#include diff --git a/test/unit/file_test.hpp b/test/unit/file_test.hpp new file mode 100644 index 0000000..ec592ae --- /dev/null +++ b/test/unit/file_test.hpp @@ -0,0 +1,469 @@ +// +// Copyright (c) 2022 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_HTTP_PROTO_FILE_TEST_HPP +#define BOOST_HTTP_PROTO_FILE_TEST_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "test_suite.hpp" + +#if defined(BOOST_GCC) && BOOST_GCC >= 130000 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wself-move" +#endif + +namespace boost { +namespace capy { + +template +void +test_file() +{ + BOOST_STATIC_ASSERT( + ! std::is_copy_constructible::value); + BOOST_STATIC_ASSERT( + ! std::is_copy_assignable::value); + + namespace fs = boost::filesystem; + +#ifdef _WIN32 + static + constexpr + boost::winapi::WCHAR_ + unicode_suffix[] = { + 0xd83e, 0xdd84, 0x0000 }; // UTF-16-LE unicorn +#else + static + constexpr + char + unicode_suffix[] = { + '\xf0', '\x9f', '\xa6', '\x84', '\x00' }; // UTF-8 unicorn +#endif + + class temp_path + { + fs::path path_; + std::vector utf8_str_; + + public: + temp_path() + : path_(fs::unique_path()) + { + if (append_unicode_suffix) + path_ += unicode_suffix; +#ifdef _WIN32 + constexpr auto cp = boost::winapi::CP_UTF8_; + constexpr auto flags = boost::winapi::WC_ERR_INVALID_CHARS_; + auto sz = boost::winapi::WideCharToMultiByte( + cp, flags, path_.c_str(), -1, nullptr, 0, + nullptr, nullptr); + BOOST_TEST(sz != 0); + utf8_str_.resize(sz); + auto ret = boost::winapi::WideCharToMultiByte( + cp, flags, path_.c_str(), -1, + utf8_str_.data(), sz, + nullptr, nullptr); + BOOST_TEST(ret == sz); +#endif + } + + operator fs::path const&() + { + return path_; + } + + operator char const*() + { +#ifdef _WIN32 + return utf8_str_.data(); +#else + return path_.c_str(); +#endif + } + }; + + auto const create = + [](fs::path const& path, std::string const& data = "") + { + BOOST_TEST(! fs::exists(path)); + std::ofstream out(path.c_str()); + BOOST_TEST(out.is_open()); + if (data.size()) + out.write(data.c_str(), data.size()); + }; + + auto const remove = + [](fs::path const& path) + { + fs::remove(path); + BOOST_TEST(! fs::exists(path)); + }; + + auto const consume_file = + [](fs::path const& path) + { + // no exceptions - failure will result in an empty string + std::ifstream in(path.c_str()); + noskipws(in); + auto s = std::string( + std::istream_iterator(in), + std::istream_iterator()); + in.close(); + return s; + }; + + temp_path path; + + // bad file descriptor + { + File f; + char buf[1]; + BOOST_TEST(! f.is_open()); + BOOST_TEST(! fs::exists(path)); + { + system::error_code ec; + f.size(ec); + BOOST_TEST(ec == + system::errc::bad_file_descriptor); + } + { + system::error_code ec; + f.pos(ec); + BOOST_TEST(ec == + system::errc::bad_file_descriptor); + } + { + system::error_code ec; + f.seek(0, ec); + BOOST_TEST(ec == + system::errc::bad_file_descriptor); + } + { + system::error_code ec; + f.read(buf, 0, ec); + BOOST_TEST(ec == + system::errc::bad_file_descriptor); + } + { + system::error_code ec; + f.write(buf, 0, ec); + BOOST_TEST(ec == + system::errc::bad_file_descriptor); + } + } + + // file_mode::read + { + { + File f; + system::error_code ec; + create(path); + f.open(path, file_mode::read, ec); + BOOST_TEST(! ec); + } + remove(path); + } + + // file_mode::scan + { + { + File f; + system::error_code ec; + create(path); + f.open(path, file_mode::scan, ec); + BOOST_TEST(! ec); + } + remove(path); + } + + // file_mode::write + { + { + File f; + system::error_code ec; + BOOST_TEST(! fs::exists(path)); + f.open(path, file_mode::write, ec); + BOOST_TEST(! ec); + BOOST_TEST(fs::exists(path)); + } + { + File f; + system::error_code ec; + BOOST_TEST(fs::exists(path)); + f.open(path, file_mode::write, ec); + BOOST_TEST(! ec); + BOOST_TEST(fs::exists(path)); + } + remove(path); + } + + // file_mode::write_new + { + { + File f; + system::error_code ec; + BOOST_TEST(! fs::exists(path)); + f.open(path, file_mode::write_new, ec); + BOOST_TEST(! ec); + BOOST_TEST(fs::exists(path)); + } + { + File f; + system::error_code ec; + BOOST_TEST(fs::exists(path)); + f.open(path, file_mode::write_new, ec); + BOOST_TEST(ec); + } + remove(path); + } + + // file_mode::write_existing + { + { + File f; + system::error_code ec; + BOOST_TEST(! fs::exists(path)); + f.open(path, file_mode::write_existing, ec); + BOOST_TEST(ec); + BOOST_TEST(! fs::exists(path)); + } + { + File f; + system::error_code ec; + create(path); + BOOST_TEST(fs::exists(path)); + f.open(path, file_mode::write_existing, ec); + BOOST_TEST(! ec); + } + remove(path); + } + + // file_mode::append + { + { + File f; + system::error_code ec; + BOOST_TEST(! fs::exists(path)); + f.open(path, file_mode::append, ec); + BOOST_TEST(! ec); + BOOST_TEST(fs::exists(path)); + static const std::string extra = "the"; + f.write(extra.c_str(), extra.size(), ec); + BOOST_TEST(!ec); + f.close(ec); + auto s = consume_file(path); + BOOST_TEST(s == "the"); + } + + { + File f; + system::error_code ec; + BOOST_TEST(fs::exists(path)); + f.open(path, file_mode::append, ec); + BOOST_TEST(! ec); + BOOST_TEST(fs::exists(path)); + static const std::string extra = " cat"; + f.write(extra.c_str(), extra.size(), ec); + BOOST_TEST(!ec); + f.close(ec); + auto s = consume_file(path); + BOOST_TEST_EQ(s, "the cat"); + } + remove(path); + } + + // file_mode::append_existing + { + { + File f; + system::error_code ec; + BOOST_TEST(! fs::exists(path)); + f.open(path, file_mode::append_existing, ec); + BOOST_TEST(ec); + BOOST_TEST(! fs::exists(path)); + } + remove(path); + { + File f; + system::error_code ec; + create(path, "the cat"); + f.open(path, file_mode::append_existing, ec); + BOOST_TEST(! ec); + static std::string const extra = " sat"; + f.write(extra.c_str(), extra.size(), ec); + BOOST_TEST(!ec); + f.close(ec); + BOOST_TEST(!ec); + auto s = consume_file(path); + BOOST_TEST_EQ(s, "the cat sat"); + } + remove(path); + } + + // special members + { + { + File f1; + system::error_code ec; + f1.open(path, file_mode::write, ec); + BOOST_TEST(! ec); + BOOST_TEST(f1.is_open()); + + // move constructor + File f2(std::move(f1)); + BOOST_TEST(! f1.is_open()); + BOOST_TEST(f2.is_open()); + + // move assignment + File f3; + f3 = std::move(f2); + BOOST_TEST(! f2.is_open()); + BOOST_TEST(f3.is_open()); + } + remove(path); + } + + // re-open + { + { + File f; + system::error_code ec; + f.open(path, file_mode::write, ec); + BOOST_TEST(! ec); + f.open(path, file_mode::write, ec); + BOOST_TEST(! ec); + } + remove(path); + } + + // re-assign + { + temp_path path2; + { + system::error_code ec; + + File f1; + f1.open(path, file_mode::write, ec); + BOOST_TEST(! ec); + + File f2; + f2.open(path2, file_mode::write, ec); + BOOST_TEST(! ec); + + f2 = std::move(f1); + BOOST_TEST(! f1.is_open()); + BOOST_TEST(f2.is_open()); + } + remove(path); + remove(path2); + } + + // self-move + { + { + File f; + system::error_code ec; + f.open(path, file_mode::write, ec); + BOOST_TEST(! ec); + auto& f_(f); + f_ = std::move(f); + BOOST_TEST(f.is_open()); + } + remove(path); + } + + // native_handle + { + { + File f; + auto none = f.native_handle(); + system::error_code ec; + f.open(path, file_mode::write, ec); + BOOST_TEST(! ec); + auto fd = f.native_handle(); + BOOST_TEST(fd != none); + f.native_handle(none); + BOOST_TEST(! f.is_open()); + } + remove(path); + } + + // read and write + { + core::string_view const s = + "Hello, world!"; + + // write + { + File f; + system::error_code ec; + f.open(path, file_mode::write, ec); + BOOST_TEST(! ec); + + f.write(s.data(), s.size(), ec); + BOOST_TEST(! ec); + + auto size = f.size(ec); + BOOST_TEST(! ec); + BOOST_TEST(size == s.size()); + + auto pos = f.pos(ec); + BOOST_TEST(! ec); + BOOST_TEST(pos == size); + + f.close(ec); + BOOST_TEST(! ec); + } + + // read + { + File f; + system::error_code ec; + f.open(path, file_mode::read, ec); + BOOST_TEST(! ec); + + std::string buf; + buf.resize(s.size()); + f.read(&buf[0], buf.size(), ec); + BOOST_TEST(! ec); + BOOST_TEST(buf == s); + + f.seek(1, ec); + BOOST_TEST(! ec); + buf.resize(3); + f.read(&buf[0], buf.size(), ec); + BOOST_TEST(! ec); + BOOST_TEST(buf == "ell"); + + auto pos = f.pos(ec); + BOOST_TEST(! ec); + BOOST_TEST(pos == 4); + } + remove(path); + } + + BOOST_TEST(! fs::exists(path)); +} + +} // capy +} // boost + +#endif