From b81242d6b6f69b1325a1d2c9488fd1c4b47150d0 Mon Sep 17 00:00:00 2001 From: Marcus Holland-Moritz Date: Thu, 28 Dec 2023 16:02:17 +0100 Subject: [PATCH] refactor(mkdwarfs): abstract out file creation --- CMakeLists.txt | 1 + include/dwarfs/file_access.h | 49 +++++++++++++++ include/dwarfs/file_access_generic.h | 32 ++++++++++ include/dwarfs/iolayer.h | 2 + src/dwarfs/file_access_generic.cpp | 91 ++++++++++++++++++++++++++++ src/dwarfs/iolayer.cpp | 3 + src/mkdwarfs_main.cpp | 76 +++++++++++++++-------- 7 files changed, 228 insertions(+), 26 deletions(-) create mode 100644 include/dwarfs/file_access.h create mode 100644 include/dwarfs/file_access_generic.h create mode 100644 src/dwarfs/file_access_generic.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a47b656d..cb8f133c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -378,6 +378,7 @@ list( src/dwarfs/entry.cpp src/dwarfs/error.cpp src/dwarfs/features.cpp + src/dwarfs/file_access_generic.cpp src/dwarfs/file_scanner.cpp src/dwarfs/file_stat.cpp src/dwarfs/file_type.cpp diff --git a/include/dwarfs/file_access.h b/include/dwarfs/file_access.h new file mode 100644 index 00000000..521d73df --- /dev/null +++ b/include/dwarfs/file_access.h @@ -0,0 +1,49 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/** + * \author Marcus Holland-Moritz (github@mhxnet.de) + * \copyright Copyright (c) Marcus Holland-Moritz + * + * This file is part of dwarfs. + * + * dwarfs is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * dwarfs is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with dwarfs. If not, see . + */ + +#pragma once + +#include +#include +#include +#include + +namespace dwarfs { + +class output_stream { + public: + virtual ~output_stream() = default; + + virtual std::ostream& os() = 0; + virtual void close(std::error_code& ec) = 0; +}; + +class file_access { + public: + virtual ~file_access() = default; + + virtual bool exists(std::filesystem::path const& path) const = 0; + virtual std::unique_ptr + open_output_binary(std::filesystem::path const& path, + std::error_code& ec) const = 0; +}; + +} // namespace dwarfs diff --git a/include/dwarfs/file_access_generic.h b/include/dwarfs/file_access_generic.h new file mode 100644 index 00000000..d8cd6f6f --- /dev/null +++ b/include/dwarfs/file_access_generic.h @@ -0,0 +1,32 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/** + * \author Marcus Holland-Moritz (github@mhxnet.de) + * \copyright Copyright (c) Marcus Holland-Moritz + * + * This file is part of dwarfs. + * + * dwarfs is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * dwarfs is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with dwarfs. If not, see . + */ + +#pragma once + +#include + +namespace dwarfs { + +class file_access; + +std::unique_ptr create_file_access_generic(); + +} // namespace dwarfs diff --git a/include/dwarfs/iolayer.h b/include/dwarfs/iolayer.h index 938b9635..2806ad99 100644 --- a/include/dwarfs/iolayer.h +++ b/include/dwarfs/iolayer.h @@ -26,6 +26,7 @@ namespace dwarfs { +class file_access; class os_access; class terminal; @@ -34,6 +35,7 @@ struct iolayer { std::shared_ptr os; std::shared_ptr term; + std::shared_ptr file; std::istream& in; std::ostream& out; std::ostream& err; diff --git a/src/dwarfs/file_access_generic.cpp b/src/dwarfs/file_access_generic.cpp new file mode 100644 index 00000000..82301c0d --- /dev/null +++ b/src/dwarfs/file_access_generic.cpp @@ -0,0 +1,91 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/** + * \author Marcus Holland-Moritz (github@mhxnet.de) + * \copyright Copyright (c) Marcus Holland-Moritz + * + * This file is part of dwarfs. + * + * dwarfs is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * dwarfs is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with dwarfs. If not, see . + */ + +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +#include "dwarfs/file_access.h" +#include "dwarfs/file_access_generic.h" +#include "dwarfs/util.h" + +namespace dwarfs { + +namespace { + +void assign_error_code(std::error_code& ec) { +#ifdef _WIN32 + ec.assign(::GetLastError(), std::system_category()); +#else + ec.assign(errno, std::generic_category()); +#endif +} + +class file_output_stream : public output_stream { + public: + file_output_stream(std::filesystem::path const& path, std::error_code& ec) + : os_{path.string().c_str(), std::ios::binary | std::ios::trunc} { + if (os_.bad() || os_.fail() || !os_.is_open()) { + assign_error_code(ec); + } + } + + std::ostream& os() override { return os_; } + + void close(std::error_code& ec) override { + os_.close(); + if (os_.bad()) { + assign_error_code(ec); + } + } + + private: + std::ofstream os_; +}; + +class file_access_generic : public file_access { + public: + bool exists(std::filesystem::path const& path) const override { + return std::filesystem::exists(path); + } + + std::unique_ptr + open_output_binary(std::filesystem::path const& path, + std::error_code& ec) const override { + auto rv = std::make_unique(path, ec); + if (ec) { + rv.reset(); + } + return rv; + } +}; + +} // namespace + +std::unique_ptr create_file_access_generic() { + return std::make_unique(); +} + +} // namespace dwarfs diff --git a/src/dwarfs/iolayer.cpp b/src/dwarfs/iolayer.cpp index 7d55a0ea..f8e5493a 100644 --- a/src/dwarfs/iolayer.cpp +++ b/src/dwarfs/iolayer.cpp @@ -21,6 +21,8 @@ #include +#include "dwarfs/file_access.h" +#include "dwarfs/file_access_generic.h" #include "dwarfs/iolayer.h" #include "dwarfs/os_access_generic.h" #include "dwarfs/terminal.h" @@ -31,6 +33,7 @@ iolayer const& iolayer::system_default() { static iolayer const iol{ .os = std::make_shared(), .term = terminal::create(), + .file = create_file_access_generic(), .in = std::cin, .out = std::cout, .err = std::cerr, diff --git a/src/mkdwarfs_main.cpp b/src/mkdwarfs_main.cpp index d304a252..0f551631 100644 --- a/src/mkdwarfs_main.cpp +++ b/src/mkdwarfs_main.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #ifdef _WIN32 @@ -63,6 +64,7 @@ #include "dwarfs/console_writer.h" #include "dwarfs/entry.h" #include "dwarfs/error.h" +#include "dwarfs/file_access.h" #include "dwarfs/filesystem_block_category_resolver.h" #include "dwarfs/filesystem_v2.h" #include "dwarfs/filesystem_writer.h" @@ -74,6 +76,7 @@ #include "dwarfs/mmap.h" #include "dwarfs/options.h" #include "dwarfs/options_interface.h" +#include "dwarfs/overloaded.h" #include "dwarfs/program_options_helpers.h" #include "dwarfs/progress.h" #include "dwarfs/scanner.h" @@ -1035,33 +1038,37 @@ int mkdwarfs_main(int argc, sys_char** argv, iolayer const& iol) { std::filesystem::path output(output_str); - std::unique_ptr os; + std::variant, + std::ostringstream> + os; if (!options.debug_filter_function) { if (output != "-") { - if (std::filesystem::exists(output) && !force_overwrite) { + if (iol.file->exists(output) && !force_overwrite) { iol.err << "error: output file already exists, use --force to overwrite\n"; return 1; } - auto ofs = std::make_unique(output, std::ios::binary | - std::ios::trunc); + std::error_code ec; + auto stream = iol.file->open_output_binary(output, ec); - if (ofs->bad() || !ofs->is_open()) { + if (ec) { iol.err << "error: cannot open output file '" << output - << "': " << ::strerror(errno) << "\n"; + << "': " << ec.message() << "\n"; return 1; } - os = std::move(ofs); + assert(stream); + + os.emplace>(std::move(stream)); } else { #ifdef _WIN32 ::_setmode(::_fileno(stdout), _O_BINARY); #endif } } else { - os = std::make_unique(); + os.emplace(); } options.enable_history = !no_history; @@ -1173,9 +1180,18 @@ int mkdwarfs_main(int argc, sys_char** argv, iolayer const& iol) { std::unique_ptr fsw; try { + std::ostream& fsw_os = std::visit( + overloaded( + [&](std::monostate) -> std::ostream& { return iol.out; }, + [&](std::unique_ptr& os) -> std::ostream& { + return os->os(); + }, + [&](std::ostringstream& oss) -> std::ostream& { return oss; }), + os); + fsw = std::make_unique( - os ? *os : iol.out, lgr, wg_compress, prog, schema_bc, metadata_bc, - history_bc, fswopts, header_ifs.get()); + fsw_os, lgr, wg_compress, prog, schema_bc, metadata_bc, history_bc, + fswopts, header_ifs.get()); categorized_option compression_opt; contextual_option_parser cop("--compression", compression_opt, cp, @@ -1245,21 +1261,34 @@ int mkdwarfs_main(int argc, sys_char** argv, iolayer const& iol) { if (!options.debug_filter_function) { LOG_INFO << "compression CPU time: " << time_with_unit(wg_compress.get_cpu_time()); + } - if (os) { - if (auto ofs = dynamic_cast(os.get())) { - ofs->close(); - } + { + auto ec = std::visit( + overloaded([](std::monostate) -> int { return 0; }, + [&](std::unique_ptr& os) -> int { + std::error_code ec; + os->close(ec); + if (ec) { + LOG_ERROR << "failed to close output file '" << output + << "': " << ec.message(); + return 1; + } + os.reset(); + return 0; + }, + [](std::ostringstream& oss [[maybe_unused]]) -> int { + assert(oss.str().empty()); + return 0; + }), + os); - if (os->bad()) { - LOG_ERROR << "failed to close output file '" << output - << "': " << strerror(errno); - return 1; - } - - os.reset(); + if (ec != 0) { + return ec; } + } + if (!options.debug_filter_function) { std::ostringstream err; if (prog.errors) { @@ -1273,11 +1302,6 @@ int mkdwarfs_main(int argc, sys_char** argv, iolayer const& iol) { ti << "filesystem " << (recompress ? "rewritten " : "created ") << err.str(); - } else { - assert(os); - auto oss [[maybe_unused]] = dynamic_cast(os.get()); - assert(oss); - assert(oss->str().empty()); } return prog.errors > 0;