/* 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
#include
#include
#include
#include
#include
#include
#if FMT_VERSION >= 110000
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace dwarfs::tool {
namespace po = boost::program_options;
namespace {
void do_list_files(reader::filesystem_v2& fs, iolayer const& iol,
bool verbose) {
auto max_width = [](auto const& vec) {
auto max = std::max_element(vec.begin(), vec.end());
return std::to_string(*max).size();
};
auto const uid_width = max_width(fs.get_all_uids());
auto const gid_width = max_width(fs.get_all_gids());
size_t inode_size_width{0};
if (verbose) {
file_stat::off_type max_inode_size{0};
fs.walk([&](auto const& de) {
auto st = fs.getattr(de.inode());
max_inode_size = std::max(max_inode_size, st.size());
});
inode_size_width = fmt::format("{:L}", max_inode_size).size();
}
fs.walk([&](auto const& de) {
auto name = de.unix_path();
utf8_sanitize(name);
if (verbose) {
auto iv = de.inode();
if (iv.is_symlink()) {
auto target = fs.readlink(iv);
utf8_sanitize(target);
name += " -> " + target;
}
auto st = fs.getattr(iv);
iol.out << fmt::format(
"{3} {4:{0}}/{5:{1}} {6:{2}L} {7:%Y-%m-%d %H:%M} {8}\n", uid_width,
gid_width, inode_size_width, iv.mode_string(), iv.getuid(),
iv.getgid(), st.size(), fmt::localtime(st.mtime()), name);
} else if (!name.empty()) {
iol.out << name << "\n";
}
});
}
void do_checksum(logger& lgr, reader::filesystem_v2& fs, iolayer const& iol,
std::string const& algo, size_t num_workers) {
LOG_PROXY(debug_logger_policy, lgr);
thread_pool pool{lgr, *iol.os, "checksum", num_workers};
std::mutex mx;
fs.walk_data_order([&](auto const& de) {
auto iv = de.inode();
if (iv.is_regular_file()) {
std::error_code ec;
auto ranges = fs.readv(iv.inode_num(), ec);
if (ec) {
LOG_ERROR << "failed to read inode " << iv.inode_num() << ": "
<< ec.message();
return;
}
pool.add_job(
[&, de, iv,
ranges = std::make_shared(std::move(ranges))]() {
checksum cs(algo);
for (auto& fut : *ranges) {
try {
auto range = fut.get();
cs.update(range.data(), range.size());
} catch (std::exception const& e) {
LOG_ERROR << "error reading data from inode " << iv.inode_num()
<< ": " << e.what();
return;
}
}
auto output =
fmt::format("{} {}\n", cs.hexdigest(), de.unix_path());
{
std::lock_guard lock(mx);
iol.out << output;
}
});
}
});
pool.wait();
}
} // namespace
int dwarfsck_main(int argc, sys_char** argv, iolayer const& iol) {
const size_t num_cpu = std::max(hardware_concurrency(), 1u);
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: {})",
reader::fsinfo_features::max_level(),
fmt::join(reader::fsinfo_features::all().to_string_views(), ", "));
auto const detail_default{reader::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;
std::string detail;
bool quiet{false};
bool verbose{false};
bool output_json{false};
bool check_integrity{false};
bool no_check{false};
bool print_header{false};
bool list_files{false};
// clang-format off
po::options_description opts("Command line options");
opts.add_options()
("input,i",
po_sys_value(&input),
"input filesystem")
("detail,d",
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")
("verbose,v",
po::value(&verbose)->zero_tokens(),
"produce verbose output")
("image-offset,O",
po::value(&image_offset)->default_value("auto"),
"filesystem image offset in bytes")
("print-header,H",
po::value(&print_header)->zero_tokens(),
"print filesystem header to stdout and exit")
("list,l",
po::value(&list_files)->zero_tokens(),
"list all files and exit")
("checksum",
po::value(&checksum_algo),
checksum_desc.c_str())
("num-workers,n",
po::value(&num_workers)->default_value(num_cpu),
"number of reader worker threads")
("check-integrity",
po::value(&check_integrity)->zero_tokens(),
"check integrity of each block")
("no-check",
po::value(&no_check)->zero_tokens(),
"don't even verify block checksums")
("json,j",
po::value(&output_json)->zero_tokens(),
"print information in JSON format")
("export-metadata",
po_sys_value(&export_metadata),
"export raw metadata as JSON to file")
;
// clang-format on
tool::add_common_options(opts, logopts);
po::positional_options_description pos;
pos.add("input", -1);
po::variables_map vm;
try {
po::store(po::basic_command_line_parser(argc, argv)
.options(opts)
.positional(pos)
.run(),
vm);
po::notify(vm);
} catch (po::error const& e) {
iol.err << "error: " << e.what() << "\n";
return 1;
}
#ifdef DWARFS_BUILTIN_MANPAGE
if (vm.count("man")) {
tool::show_manpage(tool::manpage::get_dwarfsck_manpage(), iol);
return 0;
}
#endif
auto constexpr usage = "Usage: dwarfsck [OPTIONS...]\n";
if (vm.count("help") or !vm.count("input")) {
iol.out << tool::tool_header("dwarfsck")
<< library_dependencies::common_as_string() << "\n\n"
<< usage << "\n"
<< opts << "\n";
return 0;
}
try {
stream_logger lgr(iol.term, iol.err, logopts);
LOG_PROXY(debug_logger_policy, lgr);
if (no_check && check_integrity) {
LOG_WARN << "--no-check and --check-integrity are mutually exclusive";
return 1;
}
if (vm.count("checksum") && !checksum::is_available(checksum_algo)) {
LOG_WARN << "checksum algorithm not available: " << checksum_algo;
return 1;
}
if (print_header &&
(output_json || !export_metadata.empty() || check_integrity ||
list_files || !checksum_algo.empty())) {
LOG_WARN << "--print-header is mutually exclusive with --json, "
"--export-metadata, --check-integrity, --list and --checksum";
return 1;
}
reader::filesystem_options fsopts;
fsopts.metadata.enable_nlink = true;
fsopts.metadata.check_consistency = check_integrity;
fsopts.image_offset = reader::parse_image_offset(image_offset);
auto input_path = iol.os->canonical(input);
std::shared_ptr mm = iol.os->map_file(input_path);
if (print_header) {
if (auto hdr = reader::filesystem_v2::header(mm, fsopts.image_offset)) {
ensure_binary_mode(iol.out);
iol.out.write(reinterpret_cast(hdr->data()), hdr->size());
if (iol.out.bad() || iol.out.fail()) {
LOG_ERROR << "error writing header";
return 1;
}
} else {
LOG_WARN << "filesystem does not contain a header";
return 2;
}
} else {
reader::filesystem_v2 fs(lgr, *iol.os, mm, fsopts);
if (!export_metadata.empty()) {
std::error_code ec;
auto of = iol.file->open_output(iol.os->canonical(export_metadata), ec);
if (ec) {
LOG_ERROR << "failed to open metadata output file: " << ec.message();
return 1;
}
auto json = fs.serialize_metadata_as_json(false);
of->os().write(json.data(), json.size());
of->close(ec);
if (ec) {
LOG_ERROR << "failed to close metadata output file: " << ec.message();
return 1;
}
} else {
auto level = check_integrity ? reader::filesystem_check_level::FULL
: reader::filesystem_check_level::CHECKSUM;
auto errors = no_check ? 0 : fs.check(level, num_workers);
if (!quiet && !list_files && checksum_algo.empty()) {
reader::fsinfo_options opts;
opts.block_access = no_check
? reader::block_access_level::no_verify
: reader::block_access_level::unrestricted;
auto numeric_detail = try_to(detail);
opts.features =
numeric_detail.has_value()
? reader::fsinfo_features::for_level(*numeric_detail)
: reader::fsinfo_features::parse(detail);
if (output_json) {
iol.out << fs.info_as_json(opts) << "\n";
} else {
fs.dump(iol.out, opts);
}
}
if (list_files) {
do_list_files(fs, iol, verbose);
}
if (!checksum_algo.empty()) {
do_checksum(lgr, fs, iol, checksum_algo, num_workers);
}
if (errors > 0) {
return 1;
}
}
}
} catch (std::exception const& e) {
iol.err << exception_str(e) << "\n";
return 1;
}
return 0;
}
} // namespace dwarfs::tool