diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a9ba147..4184923c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -937,6 +937,7 @@ if(WITH_TESTS) integral_value_parser_test lazy_value_test metadata_requirements_test + options_test pcm_sample_transformer_test pcmaudio_categorizer_test speedometer_test diff --git a/doc/dwarfsck.md b/doc/dwarfsck.md index d7958687..47145d8b 100644 --- a/doc/dwarfsck.md +++ b/doc/dwarfsck.md @@ -18,9 +18,9 @@ with a non-zero exit code. Path to the filesystem image. - `-d`, `--detail=`*value*: - Level of filesystem information detail. The default is 2. Higher values - mean more output. Values larger than 6 will currently not provide any - further detail. + Level of filesystem information detail. This can be a numeric level + between 0 and 6, or a comma-separated list of feature names. The + default corresponds to a level of 2. - `-q`, `--quiet`: Don't produce any output unless there is an error. diff --git a/include/dwarfs/options.h b/include/dwarfs/options.h index c5f9ddf4..09560e6e 100644 --- a/include/dwarfs/options.h +++ b/include/dwarfs/options.h @@ -110,7 +110,12 @@ class fsinfo_features { static constexpr fsinfo_features all() { return fsinfo_features().set_all(); } + static int max_level(); static fsinfo_features for_level(int level); + static fsinfo_features parse(std::string_view str); + + std::string to_string() const; + std::vector to_string_views() const; constexpr bool has(fsinfo_feature f) const { return features_ & (1 << static_cast(f)); @@ -155,10 +160,11 @@ class fsinfo_features { using feature_type = uint64_t; static constexpr size_t max_feature_bits{ std::numeric_limits::digits}; + static constexpr size_t num_feature_bits{ + static_cast(fsinfo_feature::num_fsinfo_feature_bits)}; + static_assert(num_feature_bits <= max_feature_bits); feature_type features_{0}; - static_assert(static_cast(fsinfo_feature::num_fsinfo_feature_bits) <= - max_feature_bits); }; struct fsinfo_options { diff --git a/src/dwarfs/options.cpp b/src/dwarfs/options.cpp index ecd86c83..0bfb8f8e 100644 --- a/src/dwarfs/options.cpp +++ b/src/dwarfs/options.cpp @@ -19,11 +19,15 @@ * along with dwarfs. If not, see . */ +#include +#include #include #include #include +#include + #include #include @@ -46,9 +50,36 @@ constexpr std::array level_features{ {fsinfo_feature::directory_tree, fsinfo_feature::frozen_layout}), /* 5 */ fsinfo_features({fsinfo_feature::chunk_details}), /* 6 */ fsinfo_features({fsinfo_feature::metadata_full_dump}), - /* 7 */ fsinfo_features({}), }; +constexpr std::array, + static_cast(fsinfo_feature::num_fsinfo_feature_bits)> + fsinfo_feature_names{{ +#define FSINFO_FEATURE_PAIR_(f) {fsinfo_feature::f, #f} + FSINFO_FEATURE_PAIR_(version), + FSINFO_FEATURE_PAIR_(history), + FSINFO_FEATURE_PAIR_(metadata_summary), + FSINFO_FEATURE_PAIR_(metadata_details), + FSINFO_FEATURE_PAIR_(metadata_full_dump), + FSINFO_FEATURE_PAIR_(frozen_analysis), + FSINFO_FEATURE_PAIR_(frozen_layout), + FSINFO_FEATURE_PAIR_(directory_tree), + FSINFO_FEATURE_PAIR_(section_details), + FSINFO_FEATURE_PAIR_(chunk_details), +#undef FSINFO_FEATURE_PAIR_ + }}; + +constexpr bool fsinfo_feature_names_in_order() { + for (size_t i = 0; i < fsinfo_feature_names.size(); ++i) { + if (fsinfo_feature_names[i].first != static_cast(i)) { + return false; + } + } + return true; +} + +static_assert(fsinfo_feature_names_in_order()); + } // namespace std::ostream& operator<<(std::ostream& os, file_order_mode mode) { @@ -97,6 +128,10 @@ mlock_mode parse_mlock_mode(std::string_view mode) { DWARFS_THROW(runtime_error, fmt::format("invalid lock mode: {}", mode)); } +int fsinfo_features::max_level() { + return static_cast(level_features.size()) - 1; +} + fsinfo_features fsinfo_features::for_level(int level) { fsinfo_features features; @@ -109,4 +144,51 @@ fsinfo_features fsinfo_features::for_level(int level) { return features; } +fsinfo_features fsinfo_features::parse(std::string_view features) { + fsinfo_features result; + + for (auto const& f : features | ranges::views::split(',')) { + // TODO: This should be much simpler with C++23 + std::string_view fsv(&*f.begin(), ranges::distance(f)); + auto const it = + std::find_if(fsinfo_feature_names.begin(), fsinfo_feature_names.end(), + [&fsv](auto const& p) { return fsv == p.second; }); + + if (it == fsinfo_feature_names.end()) { + DWARFS_THROW(runtime_error, fmt::format("invalid feature: \"{}\"", fsv)); + } + + result |= it->first; + } + + return result; +} + +std::string fsinfo_features::to_string() const { + std::string result; + + for (size_t bit = 0; bit < num_feature_bits; ++bit) { + if (has(static_cast(bit))) { + if (!result.empty()) { + result += ','; + } + result += fsinfo_feature_names[bit].second; + } + } + + return result; +} + +std::vector fsinfo_features::to_string_views() const { + std::vector result; + + for (size_t bit = 0; bit < num_feature_bits; ++bit) { + if (has(static_cast(bit))) { + result.push_back(fsinfo_feature_names[bit].second); + } + } + + return result; +} + } // namespace dwarfs diff --git a/src/dwarfsck_main.cpp b/src/dwarfsck_main.cpp index 395985ff..50b679d3 100644 --- a/src/dwarfsck_main.cpp +++ b/src/dwarfsck_main.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -161,12 +162,16 @@ int dwarfsck_main(int argc, sys_char** argv, iolayer const& iol) { auto algo_list = checksum::available_algorithms(); auto checksum_desc = fmt::format("print checksums for all files ({})", fmt::join(algo_list, ", ")); + auto detail_desc = fmt::format( + "detail level (0-{}, or feature list: {})", fsinfo_features::max_level(), + fmt::join(fsinfo_features::all().to_string_views(), ", ")); + auto const detail_default{fsinfo_features::for_level(2).to_string()}; sys_string input, export_metadata; std::string image_offset, checksum_algo; logger_options logopts; size_t num_workers; - int detail; + std::string detail; bool quiet{false}; bool verbose{false}; bool output_json{false}; @@ -182,8 +187,8 @@ int dwarfsck_main(int argc, sys_char** argv, iolayer const& iol) { po_sys_value(&input), "input filesystem") ("detail,d", - po::value(&detail)->default_value(2), - "detail level") + po::value(&detail)->default_value(detail_default.c_str()), + detail_desc.c_str()) ("quiet,q", po::value(&quiet)->zero_tokens(), "don't print anything unless an error occurs") @@ -327,7 +332,11 @@ int dwarfsck_main(int argc, sys_char** argv, iolayer const& iol) { opts.block_access = no_check ? block_access_level::no_verify : block_access_level::unrestricted; - opts.features = fsinfo_features::for_level(detail); + + auto numeric_detail = tryTo(detail); + opts.features = numeric_detail.has_value() + ? fsinfo_features::for_level(*numeric_detail) + : fsinfo_features::parse(detail); if (output_json) { iol.out << fs.info_as_json(opts) << "\n"; diff --git a/test/options_test.cpp b/test/options_test.cpp new file mode 100644 index 00000000..65e67fef --- /dev/null +++ b/test/options_test.cpp @@ -0,0 +1,62 @@ +/* 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 + +using namespace dwarfs; + +TEST(options, fsinfo_features) { + fsinfo_features ff; + + EXPECT_EQ(ff.to_string(), ""); + + EXPECT_NO_THROW(ff |= fsinfo_features::parse("frozen_layout,history")); + + EXPECT_TRUE(ff.has(fsinfo_feature::history)); + EXPECT_TRUE(ff & fsinfo_feature::frozen_layout); + + EXPECT_FALSE(ff.has(fsinfo_feature::frozen_analysis)); + EXPECT_FALSE(ff & fsinfo_feature::version); + + EXPECT_EQ(ff.to_string(), "history,frozen_layout"); + + ff.clear(fsinfo_feature::history); + + EXPECT_FALSE(ff & fsinfo_feature::history); + EXPECT_TRUE(ff & fsinfo_feature::frozen_layout); + EXPECT_EQ(ff.to_string(), "frozen_layout"); + + ff.reset(); + + EXPECT_FALSE(ff & fsinfo_feature::frozen_layout); + EXPECT_EQ(ff.to_string(), ""); + + EXPECT_THAT([]() { fsinfo_features::parse("history,whatever"); }, + testing::ThrowsMessage( + testing::HasSubstr("invalid feature: \"whatever\""))); + + EXPECT_THAT([]() { fsinfo_features::parse("frozen_layout,history,x"); }, + testing::ThrowsMessage( + testing::HasSubstr("invalid feature: \"x\""))); +}