feat(dwarfsextract): add --format-options

This allows users to pass custom libarchive options of the specific output
format. Eg. `mtree:sha256` or `zstd:compression-level=9`.
This commit is contained in:
oxalica 2025-05-25 20:47:40 -04:00 committed by Marcus Holland-Moritz
parent acf2df1a09
commit dd5a8e3ac0
6 changed files with 42 additions and 17 deletions

View File

@ -67,6 +67,12 @@ to disk:
if no output directory is specified). For a full list of supported formats, if no output directory is specified). For a full list of supported formats,
see libarchive-formats(5). see libarchive-formats(5).
- `--format-options=`*options*:
Comma-separated libarchive options for the specific output format.
The options are passed to libarchive. For a full list of options for each
output format, see archive_write_set_options(3).
- `--continue-on-error`: - `--continue-on-error`:
Try to continue with extraction even when errors are encountered. This Try to continue with extraction even when errors are encountered. This
only applies to errors when reading from the file system image. Errors only applies to errors when reading from the file system image. Errors

View File

@ -63,12 +63,14 @@ class filesystem_extractor {
static void add_library_dependencies(library_dependencies& deps); static void add_library_dependencies(library_dependencies& deps);
void void
open_archive(std::filesystem::path const& output, std::string const& format) { open_archive(std::filesystem::path const& output, std::string const& format,
impl_->open_archive(output, format); std::string const& format_options = "") {
impl_->open_archive(output, format, format_options);
} }
void open_stream(std::ostream& os, std::string const& format) { void open_stream(std::ostream& os, std::string const& format,
impl_->open_stream(os, format); std::string const& format_options = "") {
impl_->open_stream(os, format, format_options);
} }
void open_disk(std::filesystem::path const& output) { void open_disk(std::filesystem::path const& output) {
@ -94,9 +96,11 @@ class filesystem_extractor {
public: public:
virtual ~impl() = default; virtual ~impl() = default;
virtual void open_archive(std::filesystem::path const& output, virtual void
std::string const& format) = 0; open_archive(std::filesystem::path const& output, std::string const& format,
virtual void open_stream(std::ostream& os, std::string const& format) = 0; std::string const& format_options = "") = 0;
virtual void open_stream(std::ostream& os, std::string const& format,
std::string const& format_options) = 0;
virtual void open_disk(std::filesystem::path const& output) = 0; virtual void open_disk(std::filesystem::path const& output) = 0;
virtual void close() = 0; virtual void close() = 0;
virtual bool virtual bool

View File

@ -122,16 +122,20 @@ class filesystem_extractor_ final : public filesystem_extractor::impl {
} }
} }
void open_archive(std::filesystem::path const& output [[maybe_unused]], void
std::string const& format [[maybe_unused]]) override { open_archive(std::filesystem::path const& output [[maybe_unused]],
std::string const& format [[maybe_unused]],
std::string const& format_options [[maybe_unused]]) override {
#ifdef DWARFS_FILESYSTEM_EXTRACTOR_NO_OPEN_FORMAT #ifdef DWARFS_FILESYSTEM_EXTRACTOR_NO_OPEN_FORMAT
DWARFS_THROW(runtime_error, "open_archive() not supported in this build"); DWARFS_THROW(runtime_error, "open_archive() not supported in this build");
#else #else
LOG_DEBUG << "opening archive file in " << format << " format"; LOG_DEBUG << "opening archive file in " << format
<< " format with options '" << format_options << "'";
a_ = ::archive_write_new(); a_ = ::archive_write_new();
check_result(::archive_write_set_format_by_name(a_, format.c_str())); check_result(::archive_write_set_format_by_name(a_, format.c_str()));
check_result(::archive_write_set_options(a_, format_options.c_str()));
check_result(::archive_write_set_bytes_in_last_block(a_, 1)); check_result(::archive_write_set_bytes_in_last_block(a_, 1));
#ifdef _WIN32 #ifdef _WIN32
@ -144,8 +148,10 @@ class filesystem_extractor_ final : public filesystem_extractor::impl {
#endif #endif
} }
void open_stream(std::ostream& os [[maybe_unused]], void
std::string const& format [[maybe_unused]]) override { open_stream(std::ostream& os [[maybe_unused]],
std::string const& format [[maybe_unused]],
std::string const& format_options [[maybe_unused]]) override {
#ifdef DWARFS_FILESYSTEM_EXTRACTOR_NO_OPEN_FORMAT #ifdef DWARFS_FILESYSTEM_EXTRACTOR_NO_OPEN_FORMAT
DWARFS_THROW(runtime_error, "open_stream() not supported in this build"); DWARFS_THROW(runtime_error, "open_stream() not supported in this build");
#else #else
@ -162,11 +168,13 @@ class filesystem_extractor_ final : public filesystem_extractor::impl {
iot_ = std::make_unique<std::thread>( iot_ = std::make_unique<std::thread>(
[this, &os, fd = pipefd_[0]] { pump(os, fd); }); [this, &os, fd = pipefd_[0]] { pump(os, fd); });
LOG_DEBUG << "opening archive stream in " << format << " format"; LOG_DEBUG << "opening archive stream in " << format
<< " format with options '" << format_options << "'";
a_ = ::archive_write_new(); a_ = ::archive_write_new();
check_result(::archive_write_set_format_by_name(a_, format.c_str())); check_result(::archive_write_set_format_by_name(a_, format.c_str()));
check_result(::archive_write_set_options(a_, format_options.c_str()));
check_result(::archive_write_set_bytes_in_last_block(a_, 1)); check_result(::archive_write_set_bytes_in_last_block(a_, 1));
check_result(::archive_write_open_fd(a_, pipefd_[1])); check_result(::archive_write_open_fd(a_, pipefd_[1]));
#endif #endif

View File

@ -194,6 +194,7 @@ TEST_P(manpage_coverage_test, options) {
if (tool_name == "dwarfsextract") { if (tool_name == "dwarfsextract") {
#ifdef DWARFS_FILESYSTEM_EXTRACTOR_NO_OPEN_FORMAT #ifdef DWARFS_FILESYSTEM_EXTRACTOR_NO_OPEN_FORMAT
man_opts.erase("format"); man_opts.erase("format");
man_opts.erase("format-options");
#endif #endif
man_opts.erase("pattern"); man_opts.erase("pattern");
} }

View File

@ -2253,11 +2253,14 @@ INSTANTIATE_TEST_SUITE_P(dwarfs, mkdwarfs_progress_test,
#ifndef DWARFS_FILESYSTEM_EXTRACTOR_NO_OPEN_FORMAT #ifndef DWARFS_FILESYSTEM_EXTRACTOR_NO_OPEN_FORMAT
TEST(dwarfsextract_test, mtree) { TEST(dwarfsextract_test, mtree) {
auto t = dwarfsextract_tester::create_with_image(); auto t = dwarfsextract_tester::create_with_image();
ASSERT_EQ(0, t.run({"-i", "image.dwarfs", "-f", "mtree"})) << t.err(); ASSERT_EQ(0, t.run({"-i", "image.dwarfs", "-f", "mtree", "--format-options",
"mtree:sha256"}))
<< t.err();
auto out = t.out(); auto out = t.out();
EXPECT_TRUE(out.starts_with("#mtree")) << out; EXPECT_TRUE(out.starts_with("#mtree")) << out;
EXPECT_THAT(out, ::testing::HasSubstr("type=dir")); EXPECT_THAT(out, ::testing::HasSubstr("type=dir"));
EXPECT_THAT(out, ::testing::HasSubstr("type=file")); EXPECT_THAT(out, ::testing::HasSubstr("type=file"));
EXPECT_THAT(out, ::testing::HasSubstr("sha256digest="));
} }
TEST(dwarfsextract_test, patterns) { TEST(dwarfsextract_test, patterns) {

View File

@ -72,7 +72,7 @@ int dwarfsextract_main(int argc, sys_char** argv, iolayer const& iol) {
std::string cache_size_str, image_offset; std::string cache_size_str, image_offset;
logger_options logopts; logger_options logopts;
#ifndef DWARFS_FILESYSTEM_EXTRACTOR_NO_OPEN_FORMAT #ifndef DWARFS_FILESYSTEM_EXTRACTOR_NO_OPEN_FORMAT
std::string format; std::string format, format_options;
#endif #endif
#if DWARFS_PERFMON_ENABLED #if DWARFS_PERFMON_ENABLED
std::string perfmon_str; std::string perfmon_str;
@ -100,6 +100,9 @@ int dwarfsextract_main(int argc, sys_char** argv, iolayer const& iol) {
("format,f", ("format,f",
po::value<std::string>(&format), po::value<std::string>(&format),
"output format") "output format")
("format-options",
po::value<std::string>(&format_options),
"comma-separated libarchive options for the specific output format")
#endif #endif
("continue-on-error", ("continue-on-error",
po::value<bool>(&continue_on_error)->zero_tokens(), po::value<bool>(&continue_on_error)->zero_tokens(),
@ -228,9 +231,9 @@ int dwarfsextract_main(int argc, sys_char** argv, iolayer const& iol) {
} }
if (stream) { if (stream) {
fsx.open_stream(*stream, format); fsx.open_stream(*stream, format, format_options);
} else { } else {
fsx.open_archive(iol.os->canonical(output), format); fsx.open_archive(iol.os->canonical(output), format, format_options);
} }
} }
#endif #endif