feat(dwarfsck): select detail features by name

This commit is contained in:
Marcus Holland-Moritz 2024-08-01 11:22:14 +02:00
parent 248a16c7bb
commit 4f95d66d57
6 changed files with 170 additions and 10 deletions

View File

@ -937,6 +937,7 @@ if(WITH_TESTS)
integral_value_parser_test integral_value_parser_test
lazy_value_test lazy_value_test
metadata_requirements_test metadata_requirements_test
options_test
pcm_sample_transformer_test pcm_sample_transformer_test
pcmaudio_categorizer_test pcmaudio_categorizer_test
speedometer_test speedometer_test

View File

@ -18,9 +18,9 @@ with a non-zero exit code.
Path to the filesystem image. Path to the filesystem image.
- `-d`, `--detail=`*value*: - `-d`, `--detail=`*value*:
Level of filesystem information detail. The default is 2. Higher values Level of filesystem information detail. This can be a numeric level
mean more output. Values larger than 6 will currently not provide any between 0 and 6, or a comma-separated list of feature names. The
further detail. default corresponds to a level of 2.
- `-q`, `--quiet`: - `-q`, `--quiet`:
Don't produce any output unless there is an error. Don't produce any output unless there is an error.

View File

@ -110,7 +110,12 @@ class fsinfo_features {
static constexpr fsinfo_features all() { return fsinfo_features().set_all(); } static constexpr fsinfo_features all() { return fsinfo_features().set_all(); }
static int max_level();
static fsinfo_features for_level(int level); static fsinfo_features for_level(int level);
static fsinfo_features parse(std::string_view str);
std::string to_string() const;
std::vector<std::string_view> to_string_views() const;
constexpr bool has(fsinfo_feature f) const { constexpr bool has(fsinfo_feature f) const {
return features_ & (1 << static_cast<size_t>(f)); return features_ & (1 << static_cast<size_t>(f));
@ -155,10 +160,11 @@ class fsinfo_features {
using feature_type = uint64_t; using feature_type = uint64_t;
static constexpr size_t max_feature_bits{ static constexpr size_t max_feature_bits{
std::numeric_limits<feature_type>::digits}; std::numeric_limits<feature_type>::digits};
static constexpr size_t num_feature_bits{
static_cast<size_t>(fsinfo_feature::num_fsinfo_feature_bits)};
static_assert(num_feature_bits <= max_feature_bits);
feature_type features_{0}; feature_type features_{0};
static_assert(static_cast<size_t>(fsinfo_feature::num_fsinfo_feature_bits) <=
max_feature_bits);
}; };
struct fsinfo_options { struct fsinfo_options {

View File

@ -19,11 +19,15 @@
* along with dwarfs. If not, see <https://www.gnu.org/licenses/>. * along with dwarfs. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include <algorithm>
#include <array>
#include <ostream> #include <ostream>
#include <string> #include <string>
#include <fmt/format.h> #include <fmt/format.h>
#include <range/v3/view/split.hpp>
#include <dwarfs/error.h> #include <dwarfs/error.h>
#include <dwarfs/options.h> #include <dwarfs/options.h>
@ -46,9 +50,36 @@ constexpr std::array level_features{
{fsinfo_feature::directory_tree, fsinfo_feature::frozen_layout}), {fsinfo_feature::directory_tree, fsinfo_feature::frozen_layout}),
/* 5 */ fsinfo_features({fsinfo_feature::chunk_details}), /* 5 */ fsinfo_features({fsinfo_feature::chunk_details}),
/* 6 */ fsinfo_features({fsinfo_feature::metadata_full_dump}), /* 6 */ fsinfo_features({fsinfo_feature::metadata_full_dump}),
/* 7 */ fsinfo_features({}),
}; };
constexpr std::array<std::pair<fsinfo_feature, std::string_view>,
static_cast<int>(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<fsinfo_feature>(i)) {
return false;
}
}
return true;
}
static_assert(fsinfo_feature_names_in_order());
} // namespace } // namespace
std::ostream& operator<<(std::ostream& os, file_order_mode mode) { 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)); DWARFS_THROW(runtime_error, fmt::format("invalid lock mode: {}", mode));
} }
int fsinfo_features::max_level() {
return static_cast<int>(level_features.size()) - 1;
}
fsinfo_features fsinfo_features::for_level(int level) { fsinfo_features fsinfo_features::for_level(int level) {
fsinfo_features features; fsinfo_features features;
@ -109,4 +144,51 @@ fsinfo_features fsinfo_features::for_level(int level) {
return features; 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<fsinfo_feature>(bit))) {
if (!result.empty()) {
result += ',';
}
result += fsinfo_feature_names[bit].second;
}
}
return result;
}
std::vector<std::string_view> fsinfo_features::to_string_views() const {
std::vector<std::string_view> result;
for (size_t bit = 0; bit < num_feature_bits; ++bit) {
if (has(static_cast<fsinfo_feature>(bit))) {
result.push_back(fsinfo_feature_names[bit].second);
}
}
return result;
}
} // namespace dwarfs } // namespace dwarfs

View File

@ -37,6 +37,7 @@
#include <folly/portability/Unistd.h> #include <folly/portability/Unistd.h>
#include <dwarfs/checksum.h> #include <dwarfs/checksum.h>
#include <dwarfs/conv.h>
#include <dwarfs/error.h> #include <dwarfs/error.h>
#include <dwarfs/file_access.h> #include <dwarfs/file_access.h>
#include <dwarfs/filesystem_v2.h> #include <dwarfs/filesystem_v2.h>
@ -161,12 +162,16 @@ int dwarfsck_main(int argc, sys_char** argv, iolayer const& iol) {
auto algo_list = checksum::available_algorithms(); auto algo_list = checksum::available_algorithms();
auto checksum_desc = fmt::format("print checksums for all files ({})", auto checksum_desc = fmt::format("print checksums for all files ({})",
fmt::join(algo_list, ", ")); 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; sys_string input, export_metadata;
std::string image_offset, checksum_algo; std::string image_offset, checksum_algo;
logger_options logopts; logger_options logopts;
size_t num_workers; size_t num_workers;
int detail; std::string detail;
bool quiet{false}; bool quiet{false};
bool verbose{false}; bool verbose{false};
bool output_json{false}; bool output_json{false};
@ -182,8 +187,8 @@ int dwarfsck_main(int argc, sys_char** argv, iolayer const& iol) {
po_sys_value<sys_string>(&input), po_sys_value<sys_string>(&input),
"input filesystem") "input filesystem")
("detail,d", ("detail,d",
po::value<int>(&detail)->default_value(2), po::value<std::string>(&detail)->default_value(detail_default.c_str()),
"detail level") detail_desc.c_str())
("quiet,q", ("quiet,q",
po::value<bool>(&quiet)->zero_tokens(), po::value<bool>(&quiet)->zero_tokens(),
"don't print anything unless an error occurs") "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 opts.block_access = no_check ? block_access_level::no_verify
: block_access_level::unrestricted; : block_access_level::unrestricted;
opts.features = fsinfo_features::for_level(detail);
auto numeric_detail = tryTo<int>(detail);
opts.features = numeric_detail.has_value()
? fsinfo_features::for_level(*numeric_detail)
: fsinfo_features::parse(detail);
if (output_json) { if (output_json) {
iol.out << fs.info_as_json(opts) << "\n"; iol.out << fs.info_as_json(opts) << "\n";

62
test/options_test.cpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <dwarfs/options.h>
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<runtime_error>(
testing::HasSubstr("invalid feature: \"whatever\"")));
EXPECT_THAT([]() { fsinfo_features::parse("frozen_layout,history,x"); },
testing::ThrowsMessage<runtime_error>(
testing::HasSubstr("invalid feature: \"x\"")));
}