/* 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace dwarfs::reader::internal {
using namespace dwarfs::internal;
namespace fs = std::filesystem;
namespace {
using ::apache::thrift::frozen::MappedFrozen;
void check_schema(std::span data) {
using namespace ::apache::thrift;
frozen::schema::Schema schema;
size_t schemaSize = CompactSerializer::deserialize(data, schema);
// std::cerr << debugString(schema) << '\n';
if (schemaSize != data.size()) {
DWARFS_THROW(runtime_error, "invalid schema size");
}
if (schema.layouts()->count(*schema.rootLayout()) == 0) {
DWARFS_THROW(runtime_error, "invalid rootLayout in schema");
}
for (auto const& kvl : *schema.layouts()) {
auto const& layout = kvl.second;
if (kvl.first >= static_cast(schema.layouts()->size())) {
DWARFS_THROW(runtime_error, "invalid layout key in schema");
}
if (*layout.size() < 0) {
DWARFS_THROW(runtime_error, "negative size in schema");
}
if (*layout.bits() < 0) {
DWARFS_THROW(runtime_error, "negative bits in schema");
}
for (auto const& kvf : *layout.fields()) {
auto const& field = kvf.second;
if (schema.layouts()->count(*field.layoutId()) == 0) {
DWARFS_THROW(runtime_error, "invalid layoutId in field");
}
}
}
}
template
MappedFrozen
map_frozen(std::span schema, std::span data) {
using namespace ::apache::thrift::frozen;
check_schema(schema);
auto layout = std::make_unique>();
folly::ByteRange tmp(schema.data(), schema.size());
deserializeRootLayout(tmp, *layout);
MappedFrozen ret(layout->view({data.data(), 0}));
ret.hold(std::move(layout));
return ret;
}
MappedFrozen
check_frozen(MappedFrozen meta) {
if (meta.features()) {
auto unsupported = feature_set::get_unsupported(meta.features()->thaw());
if (!unsupported.empty()) {
DWARFS_THROW(runtime_error,
fmt::format("file system uses the following features "
"unsupported by this build: {}",
boost::join(unsupported, ", ")));
}
}
return meta;
}
global_metadata::Meta const&
check_metadata_consistency(logger& lgr, global_metadata::Meta const& meta,
bool force_consistency_check) {
if (force_consistency_check) {
global_metadata::check_consistency(lgr, meta);
}
return meta;
}
void analyze_frozen(std::ostream& os,
MappedFrozen const& meta,
size_t total_size, fsinfo_options const& opts) {
using namespace ::apache::thrift::frozen;
null_logger lgr;
auto layout = meta.findFirstOfType<
std::unique_ptr>>();
auto& l = *layout;
std::vector> usage;
#if FMT_VERSION >= 70000
#define DWARFS_FMT_L "L"
#else
#define DWARFS_FMT_L "n"
#endif
auto fmt_size = [&](auto const& name, size_t count, size_t size) {
return fmt::format("{0:>14" DWARFS_FMT_L "} {1:.<20}{2:.>16" DWARFS_FMT_L
"} bytes {3:5.1f}% {4:5.1f} bytes/item\n",
count, name, size, 100.0 * size / total_size,
count > 0 ? static_cast(size) / count : 0.0);
};
auto fmt_detail = [&](auto const& name, size_t count, size_t size,
std::string num) {
return fmt::format(
" {0:<20}{1:>16" DWARFS_FMT_L "} bytes {2:>6} "
"{3:5.1f} bytes/item\n",
name, size, num, count > 0 ? static_cast(size) / count : 0.0);
};
auto fmt_detail_pct = [&](auto const& name, size_t count, size_t size) {
return fmt_detail(name, count, size,
fmt::format("{0:5.1f}%", 100.0 * size / total_size));
};
auto add_size = [&](auto const& name, size_t count, size_t size) {
usage.emplace_back(size, fmt_size(name, count, size));
};
auto list_size = [&](auto const& list, auto const& field) {
return (list.size() * field.layout.itemField.layout.bits + 7) / 8;
};
auto add_list_size = [&](auto const& name, auto const& list,
auto const& field) {
add_size(name, list.size(), list_size(list, field));
};
auto add_string_list_size = [&](auto const& name, auto const& list,
auto const& field) {
auto count = list.size();
if (count > 0) {
auto index_size = list_size(list, field);
auto data_size = list.back().end() - list.front().begin();
auto size = index_size + data_size;
auto fmt = fmt_size(name, count, size) +
fmt_detail_pct("|- data", count, data_size) +
fmt_detail_pct("'- index", count, index_size);
usage.emplace_back(size, fmt);
}
};
auto add_string_table_size = [&](auto const& name, auto const& table,
auto const& field) {
if (auto data_size = table.buffer().size(); data_size > 0) {
auto dict_size =
table.symtab() ? table.symtab()->size() : static_cast(0);
auto index_size = list_size(table.index(), field.layout.indexField);
auto size = index_size + data_size + dict_size;
auto count = table.index().size() - (table.packed_index() ? 0 : 1);
auto fmt = fmt_size(name, count, size) +
fmt_detail_pct("|- data", count, data_size);
if (table.symtab()) {
string_table st(lgr, "tmp", table);
auto unpacked_size = st.unpacked_size();
fmt += fmt_detail(
"|- unpacked", count, unpacked_size,
fmt::format("{0:5.2f}x",
static_cast(unpacked_size) / data_size));
fmt += fmt_detail_pct("|- dict", count, dict_size);
}
fmt += fmt_detail_pct("'- index", count, index_size);
usage.emplace_back(size, fmt);
}
};
#define META_LIST_SIZE(x) add_list_size(#x, meta.x(), l->x##Field)
#define META_STRING_LIST_SIZE(x) add_string_list_size(#x, meta.x(), l->x##Field)
#define META_OPT_LIST_SIZE(x) \
do { \
if (auto list = meta.x()) { \
add_list_size(#x, *list, l->x##Field.layout.valueField); \
} \
} while (0)
#define META_OPT_STRING_LIST_SIZE(x) \
do { \
if (auto list = meta.x()) { \
add_string_list_size(#x, *list, l->x##Field.layout.valueField); \
} \
} while (0)
#define META_OPT_STRING_SET_SIZE(x) META_OPT_STRING_LIST_SIZE(x)
#define META_OPT_STRING_TABLE_SIZE(x) \
do { \
if (auto table = meta.x()) { \
add_string_table_size(#x, *table, l->x##Field.layout.valueField); \
} \
} while (0)
META_LIST_SIZE(chunks);
META_LIST_SIZE(directories);
META_LIST_SIZE(inodes);
META_LIST_SIZE(chunk_table);
if (!meta.entry_table_v2_2().empty()) {
// deprecated, so only list if non-empty
META_LIST_SIZE(entry_table_v2_2);
}
META_LIST_SIZE(symlink_table);
META_LIST_SIZE(uids);
META_LIST_SIZE(gids);
META_LIST_SIZE(modes);
META_OPT_LIST_SIZE(devices);
META_OPT_LIST_SIZE(dir_entries);
META_OPT_LIST_SIZE(shared_files_table);
META_OPT_STRING_TABLE_SIZE(compact_names);
META_OPT_STRING_TABLE_SIZE(compact_symlinks);
META_STRING_LIST_SIZE(names);
META_STRING_LIST_SIZE(symlinks);
META_OPT_STRING_SET_SIZE(features);
META_OPT_STRING_LIST_SIZE(category_names);
META_OPT_LIST_SIZE(block_categories);
#undef META_LIST_SIZE
#undef META_OPT_STRING_SET_SIZE
#undef META_OPT_STRING_LIST_SIZE
#undef META_STRING_LIST_SIZE
#undef META_OPT_LIST_SIZE
#undef META_OPT_STRING_TABLE_SIZE
std::sort(usage.begin(), usage.end(), [](auto const& a, auto const& b) {
return a.first > b.first || (a.first == b.first && a.second < b.second);
});
os << "metadata memory usage:\n";
os << fmt::format(" {0:.<20}{1:.>16" DWARFS_FMT_L
"} bytes {2:6.1f} bytes/inode\n",
"total metadata", total_size,
static_cast(total_size) / meta.inodes().size());
#undef DWARFS_FMT_L
for (auto const& u : usage) {
os << u.second;
}
if (opts.features.has(fsinfo_feature::frozen_layout)) {
l->print(os, 0);
os << '\n';
}
}
template
void parse_metadata_options(
MappedFrozen const& meta, Function&& func) {
if (auto opt = meta.options()) {
func("mtime_only", opt->mtime_only());
func("packed_chunk_table", opt->packed_chunk_table());
func("packed_directories", opt->packed_directories());
func("packed_shared_files_table", opt->packed_shared_files_table());
}
if (auto names = meta.compact_names()) {
func("packed_names", static_cast(names->symtab()));
func("packed_names_index", names->packed_index());
}
if (auto symlinks = meta.compact_symlinks()) {
func("packed_symlinks", static_cast(symlinks->symtab()));
func("packed_symlinks_index", symlinks->packed_index());
}
}
struct category_info {
size_t count{0};
std::optional compressed_size;
std::optional uncompressed_size;
bool uncompressed_size_is_estimate{false};
};
std::map
get_category_info(MappedFrozen const& meta,
filesystem_info const* fsinfo) {
std::map catinfo;
if (auto blockcat = meta.block_categories()) {
for (auto [block, category] : ranges::views::enumerate(blockcat.value())) {
auto& ci = catinfo[category];
++ci.count;
if (fsinfo) {
if (!ci.compressed_size) {
ci.compressed_size = 0;
ci.uncompressed_size = 0;
}
*ci.compressed_size += fsinfo->compressed_block_sizes.at(block);
if (auto size = fsinfo->uncompressed_block_sizes.at(block)) {
*ci.uncompressed_size += *size;
} else {
ci.uncompressed_size_is_estimate = true;
}
}
}
}
return catinfo;
}
const uint16_t READ_ONLY_MASK = ~uint16_t(
fs::perms::owner_write | fs::perms::group_write | fs::perms::others_write);
} // namespace
template
class metadata_ final : public metadata_v2::impl {
public:
metadata_(logger& lgr, std::span schema,
std::span data, metadata_options const& options,
int inode_offset, bool force_consistency_check,
std::shared_ptr perfmon [[maybe_unused]])
: data_(data)
, meta_(
check_frozen(map_frozen(schema, data_)))
, global_(lgr, check_metadata_consistency(lgr, meta_,
options.check_consistency ||
force_consistency_check))
, root_(internal::dir_entry_view_impl::from_dir_entry_index(0, global_))
, LOG_PROXY_INIT(lgr)
, inode_offset_(inode_offset)
, symlink_inode_offset_(find_inode_offset(inode_rank::INO_LNK))
, file_inode_offset_(find_inode_offset(inode_rank::INO_REG))
, dev_inode_offset_(find_inode_offset(inode_rank::INO_DEV))
, inode_count_(meta_.dir_entries() ? meta_.inodes().size()
: meta_.entry_table_v2_2().size())
, nlinks_(build_nlinks(options))
, chunk_table_(unpack_chunk_table())
, shared_files_(decompress_shared_files())
, unique_files_(dev_inode_offset_ - file_inode_offset_ -
(shared_files_.empty()
? meta_.shared_files_table()
? meta_.shared_files_table()->size()
: 0
: shared_files_.size()))
, options_(options)
, symlinks_(meta_.compact_symlinks()
? string_table(lgr, "symlinks", *meta_.compact_symlinks())
: string_table(meta_.symlinks()))
// clang-format off
PERFMON_CLS_PROXY_INIT(perfmon, "metadata_v2")
PERFMON_CLS_TIMER_INIT(find)
PERFMON_CLS_TIMER_INIT(getattr)
PERFMON_CLS_TIMER_INIT(getattr_opts)
PERFMON_CLS_TIMER_INIT(readdir)
PERFMON_CLS_TIMER_INIT(reg_file_size)
PERFMON_CLS_TIMER_INIT(unpack_metadata) // clang-format on
{
if (static_cast(meta_.directories().size() - 1) !=
symlink_inode_offset_) {
DWARFS_THROW(
runtime_error,
fmt::format("metadata inconsistency: number of directories ({}) does "
"not match link index ({})",
meta_.directories().size() - 1, symlink_inode_offset_));
}
if (static_cast(meta_.symlink_table().size()) !=
(file_inode_offset_ - symlink_inode_offset_)) {
DWARFS_THROW(
runtime_error,
fmt::format(
"metadata inconsistency: number of symlinks ({}) does not match "
"chunk/symlink table delta ({} - {} = {})",
meta_.symlink_table().size(), file_inode_offset_,
symlink_inode_offset_,
file_inode_offset_ - symlink_inode_offset_));
}
if (!meta_.shared_files_table()) {
if (static_cast(meta_.chunk_table().size() - 1) !=
(dev_inode_offset_ - file_inode_offset_)) {
DWARFS_THROW(
runtime_error,
fmt::format(
"metadata inconsistency: number of files ({}) does not match "
"device/chunk index delta ({} - {} = {})",
meta_.chunk_table().size() - 1, dev_inode_offset_,
file_inode_offset_, dev_inode_offset_ - file_inode_offset_));
}
}
if (auto devs = meta_.devices()) {
int other_offset = find_inode_offset(inode_rank::INO_OTH);
if (static_cast(devs->size()) !=
(other_offset - dev_inode_offset_)) {
DWARFS_THROW(
runtime_error,
fmt::format("metadata inconsistency: number of devices ({}) does "
"not match other/device index delta ({} - {} = {})",
devs->size(), other_offset, dev_inode_offset_,
other_offset - dev_inode_offset_));
}
}
}
void check_consistency() const override;
void dump(std::ostream& os, fsinfo_options const& opts,
filesystem_info const* fsinfo,
std::function const& icb)
const override;
nlohmann::json info_as_json(fsinfo_options const& opts,
filesystem_info const* fsinfo) const override;
nlohmann::json as_json() const override;
std::string serialize_as_json(bool simple) const override;
size_t size() const override { return data_.size(); }
void walk(std::function const& func) const override {
walk_tree([&](uint32_t self_index, uint32_t parent_index) {
walk_call(func, self_index, parent_index);
});
}
void walk_data_order(
std::function const& func) const override {
walk_data_order_impl(func);
}
std::optional find(const char* path) const override;
std::optional find(int inode) const override;
std::optional find(int inode, const char* name) const override;
file_stat getattr(inode_view iv, std::error_code& ec) const override;
file_stat getattr(inode_view iv, getattr_options const& opts,
std::error_code& ec) const override;
std::optional opendir(inode_view iv) const override;
std::optional>
readdir(directory_view dir, size_t offset) const override;
size_t dirsize(directory_view dir) const override {
return 2 + dir.entry_count(); // adds '.' and '..', which we fake in ;-)
}
void access(inode_view iv, int mode, file_stat::uid_type uid,
file_stat::gid_type gid, std::error_code& ec) const override;
int open(inode_view iv, std::error_code& ec) const override;
std::string readlink(inode_view iv, readlink_mode mode,
std::error_code& ec) const override;
void statvfs(vfs_stat* stbuf) const override;
chunk_range get_chunks(int inode, std::error_code& ec) const override;
size_t block_size() const override { return meta_.block_size(); }
bool has_symlinks() const override { return !meta_.symlink_table().empty(); }
nlohmann::json get_inode_info(inode_view iv) const override;
std::optional
get_block_category(size_t block_number) const override;
std::vector get_all_block_categories() const override;
std::vector get_all_uids() const override;
std::vector get_all_gids() const override;
private:
template
using set_type = folly::F14ValueSet;
thrift::metadata::metadata unpack_metadata() const;
file_stat getattr_impl(inode_view iv, getattr_options const& opts) const;
inode_view make_inode_view(uint32_t inode) const {
// TODO: move compatibility details to metadata_types
uint32_t index =
meta_.dir_entries() ? inode : meta_.entry_table_v2_2()[inode];
return inode_view{std::make_shared(
meta_.inodes()[index], inode, meta_)};
}
dir_entry_view
make_dir_entry_view(uint32_t self_index, uint32_t parent_index) const {
return dir_entry_view{dir_entry_view_impl::from_dir_entry_index(
self_index, parent_index, global_)};
}
// This represents the order in which inodes are stored in inodes
// (or entry_table_v2_2 for older file systems)
enum class inode_rank {
INO_DIR,
INO_LNK,
INO_REG,
INO_DEV,
INO_OTH,
};
// TODO: merge with mode_rank in metadata_types
static inode_rank get_inode_rank(uint16_t mode) {
switch (posix_file_type::from_mode(mode)) {
case posix_file_type::directory:
return inode_rank::INO_DIR;
case posix_file_type::symlink:
return inode_rank::INO_LNK;
case posix_file_type::regular:
return inode_rank::INO_REG;
case posix_file_type::block:
case posix_file_type::character:
return inode_rank::INO_DEV;
case posix_file_type::socket:
case posix_file_type::fifo:
return inode_rank::INO_OTH;
default:
DWARFS_THROW(runtime_error,
fmt::format("unknown file type: {:#06x}", mode));
}
}
size_t find_inode_offset(inode_rank rank) const {
if (meta_.dir_entries()) {
auto range = boost::irange(size_t(0), meta_.inodes().size());
auto it = std::lower_bound(
range.begin(), range.end(), rank, [&](auto inode, inode_rank r) {
auto mode = meta_.modes()[meta_.inodes()[inode].mode_index()];
return get_inode_rank(mode) < r;
});
return *it;
} else {
auto range = boost::irange(size_t(0), meta_.entry_table_v2_2().size());
auto it = std::lower_bound(range.begin(), range.end(), rank,
[&](auto inode, inode_rank r) {
auto iv = make_inode_view(inode);
return get_inode_rank(iv.mode()) < r;
});
return *it;
}
}
directory_view make_directory_view(inode_view iv) const {
// TODO: revisit: is this the way to do it?
DWARFS_CHECK(iv.is_directory(), "not a directory");
return directory_view(iv.inode_num(), global_);
}
void analyze_chunks(std::ostream& os) const;
// TODO: see if we really need to pass the extra dir_entry_view in
// addition to directory_view
void dump(std::ostream& os, const std::string& indent, dir_entry_view entry,
fsinfo_options const& opts,
std::function const& icb) const;
void dump(std::ostream& os, const std::string& indent, directory_view dir,
dir_entry_view entry, fsinfo_options const& opts,
std::function const& icb) const;
nlohmann::json as_json(dir_entry_view entry) const;
nlohmann::json as_json(directory_view dir, dir_entry_view entry) const;
std::optional
find(directory_view dir, std::string_view name) const;
uint32_t chunk_table_lookup(uint32_t ino) const {
return chunk_table_.empty() ? meta_.chunk_table()[ino] : chunk_table_[ino];
}
int file_inode_to_chunk_index(int inode) const {
inode -= file_inode_offset_;
if (inode >= unique_files_) {
inode -= unique_files_;
if (!shared_files_.empty()) {
if (inode < static_cast(shared_files_.size())) {
inode = shared_files_[inode] + unique_files_;
}
} else if (auto sfp = meta_.shared_files_table()) {
if (inode < static_cast(sfp->size())) {
inode = (*sfp)[inode] + unique_files_;
}
}
}
return inode;
}
chunk_range get_chunk_range(int inode, std::error_code& ec) const {
inode = file_inode_to_chunk_index(inode);
if (inode >= 0 &&
(inode + 1) < static_cast(meta_.chunk_table().size())) {
ec.clear();
uint32_t begin = chunk_table_lookup(inode);
uint32_t end = chunk_table_lookup(inode + 1);
return chunk_range(meta_, begin, end);
}
ec = make_error_code(std::errc::invalid_argument);
return {};
}
size_t reg_file_size(inode_view iv) const {
PERFMON_CLS_SCOPED_SECTION(reg_file_size)
std::error_code ec;
auto cr = get_chunk_range(iv.inode_num(), ec);
DWARFS_CHECK(!ec, fmt::format("get_chunk_range({}): {}", iv.inode_num(),
ec.message()));
return std::accumulate(
cr.begin(), cr.end(), static_cast(0),
[](size_t s, chunk_view cv) { return s + cv.size(); });
}
size_t file_size(inode_view iv, uint16_t mode) const {
switch (posix_file_type::from_mode(mode)) {
case posix_file_type::regular:
return reg_file_size(iv);
case posix_file_type::symlink:
return link_value(iv).size();
default:
return 0;
}
}
// TODO: cleanup the walk logic
void walk_call(std::function const& func,
uint32_t self_index, uint32_t parent_index) const {
func(make_dir_entry_view(self_index, parent_index));
}
template
void walk(uint32_t self_index, uint32_t parent_index, set_type& seen,
T&& func) const;
template
void walk_tree(T&& func) const {
set_type seen;
walk(0, 0, seen, std::forward(func));
}
void
walk_data_order_impl(std::function const& func) const;
std::optional get_entry(int inode) const {
inode -= inode_offset_;
std::optional rv;
if (inode >= 0 && inode < inode_count_) {
rv = make_inode_view(inode);
}
return rv;
}
std::string
link_value(inode_view iv, readlink_mode mode = readlink_mode::raw) const {
std::string rv =
symlinks_[meta_
.symlink_table()[iv.inode_num() - symlink_inode_offset_]];
if (mode != readlink_mode::raw) {
char meta_preferred = '/';
if (auto ps = meta_.preferred_path_separator()) {
meta_preferred = static_cast(*ps);
}
char host_preferred =
static_cast(std::filesystem::path::preferred_separator);
if (mode == readlink_mode::posix) {
host_preferred = '/';
}
if (meta_preferred != host_preferred) {
std::replace(rv.begin(), rv.end(), meta_preferred, host_preferred);
}
}
return rv;
}
uint64_t get_device_id(int inode) const {
if (auto devs = meta_.devices()) {
return (*devs)[inode - dev_inode_offset_];
}
LOG_ERROR << "get_device_id() called, but no devices in file system";
return 0;
}
std::vector unpack_chunk_table() const {
std::vector chunk_table;
if (auto opts = meta_.options(); opts and opts->packed_chunk_table()) {
auto ti = LOG_TIMED_DEBUG;
chunk_table.resize(meta_.chunk_table().size());
std::partial_sum(meta_.chunk_table().begin(), meta_.chunk_table().end(),
chunk_table.begin());
ti << "unpacked chunk table ("
<< size_with_unit(sizeof(chunk_table.front()) * chunk_table.capacity())
<< ")";
}
return chunk_table;
}
std::vector decompress_shared_files() const {
std::vector decompressed;
if (auto opts = meta_.options();
opts and opts->packed_shared_files_table()) {
if (auto sfp = meta_.shared_files_table(); sfp and !sfp->empty()) {
auto ti = LOG_TIMED_DEBUG;
auto size = std::accumulate(sfp->begin(), sfp->end(), 2 * sfp->size());
decompressed.reserve(size);
uint32_t index = 0;
for (auto c : *sfp) {
decompressed.insert(decompressed.end(), c + 2, index++);
}
DWARFS_CHECK(decompressed.size() == size,
"unexpected decompressed shared files count");
ti << "decompressed shared files table ("
<< size_with_unit(sizeof(decompressed.front()) *
decompressed.capacity())
<< ")";
}
}
return decompressed;
}
std::vector build_nlinks(metadata_options const& options) const {
std::vector nlinks;
if (options.enable_nlink) {
auto ti = LOG_TIMED_DEBUG;
nlinks.resize(dev_inode_offset_ - file_inode_offset_);
if (auto de = meta_.dir_entries()) {
for (auto e : *de) {
int index = int(e.inode_num()) - file_inode_offset_;
if (index >= 0 && index < int(nlinks.size())) {
++nlinks[index];
}
}
} else {
for (auto e : meta_.inodes()) {
int index = int(e.inode_v2_2()) - file_inode_offset_;
if (index >= 0 && index < int(nlinks.size())) {
++nlinks[index];
}
}
}
ti << "built hardlink table ("
<< size_with_unit(sizeof(nlinks.front()) * nlinks.capacity()) << ")";
}
return nlinks;
}
std::span data_;
MappedFrozen meta_;
const global_metadata global_;
dir_entry_view root_;
LOG_PROXY_DECL(LoggerPolicy);
const int inode_offset_;
const int symlink_inode_offset_;
const int file_inode_offset_;
const int dev_inode_offset_;
const int inode_count_;
const std::vector nlinks_;
const std::vector chunk_table_;
const std::vector shared_files_;
const int unique_files_;
const metadata_options options_;
const string_table symlinks_;
PERFMON_CLS_PROXY_DECL
PERFMON_CLS_TIMER_DECL(find)
PERFMON_CLS_TIMER_DECL(getattr)
PERFMON_CLS_TIMER_DECL(getattr_opts)
PERFMON_CLS_TIMER_DECL(readdir)
PERFMON_CLS_TIMER_DECL(reg_file_size)
PERFMON_CLS_TIMER_DECL(unpack_metadata)
};
template
void metadata_::analyze_chunks(std::ostream& os) const {
folly::Histogram block_refs{1, 0, 1024};
folly::Histogram chunk_count{1, 0, 65536};
size_t mergeable_chunks{0};
for (size_t i = 1; i < meta_.chunk_table().size(); ++i) {
uint32_t beg = chunk_table_lookup(i - 1);
uint32_t end = chunk_table_lookup(i);
uint32_t num = end - beg;
assert(beg <= end);
if (num > 1) {
std::unordered_set blocks;
for (uint32_t k = beg; k < end; ++k) {
auto chk = meta_.chunks()[k];
blocks.emplace(chk.block());
if (k > beg) {
auto prev = meta_.chunks()[k - 1];
if (prev.block() == chk.block()) {
if (prev.offset() + prev.size() == chk.offset()) {
++mergeable_chunks;
}
}
}
}
block_refs.addValue(blocks.size());
} else {
block_refs.addValue(num);
}
chunk_count.addValue(num);
}
{
auto pct = [&](double p) { return block_refs.getPercentileEstimate(p); };
os << "single file block refs p50: " << pct(0.5) << ", p75: " << pct(0.75)
<< ", p90: " << pct(0.9) << ", p95: " << pct(0.95)
<< ", p99: " << pct(0.99) << ", p99.9: " << pct(0.999) << "\n";
}
{
auto pct = [&](double p) { return chunk_count.getPercentileEstimate(p); };
os << "single file chunk count p50: " << pct(0.5) << ", p75: " << pct(0.75)
<< ", p90: " << pct(0.9) << ", p95: " << pct(0.95)
<< ", p99: " << pct(0.99) << ", p99.9: " << pct(0.999) << "\n";
}
// TODO: we can remove this once we have no more mergeable chunks :-)
os << "mergeable chunks: " << mergeable_chunks << "/" << meta_.chunks().size()
<< "\n";
}
template
void metadata_::check_consistency() const {
global_.check_consistency(LOG_GET_LOGGER);
}
template
void metadata_::dump(
std::ostream& os, const std::string& indent, dir_entry_view entry,
fsinfo_options const& opts,
std::function const& icb) const {
auto iv = entry.inode();
auto mode = iv.mode();
auto inode = iv.inode_num();
os << indent << " " << file_stat::mode_string(mode);
if (inode > 0) {
os << " " << entry.name();
}
switch (posix_file_type::from_mode(mode)) {
case posix_file_type::regular: {
std::error_code ec;
auto cr = get_chunk_range(inode, ec);
DWARFS_CHECK(!ec,
fmt::format("get_chunk_range({}): {}", inode, ec.message()));
os << " [" << cr.begin_ << ", " << cr.end_ << "]";
os << " " << file_size(iv, mode) << "\n";
if (opts.features.has(fsinfo_feature::chunk_details)) {
icb(indent + " ", inode);
}
} break;
case posix_file_type::directory:
dump(os, indent + " ", make_directory_view(iv), entry, opts, icb);
break;
case posix_file_type::symlink:
os << " -> " << link_value(iv) << "\n";
break;
case posix_file_type::block:
os << " (block device: " << get_device_id(inode) << ")\n";
break;
case posix_file_type::character:
os << " (char device: " << get_device_id(inode) << ")\n";
break;
case posix_file_type::fifo:
os << " (named pipe)\n";
break;
case posix_file_type::socket:
os << " (socket)\n";
break;
}
}
template
nlohmann::json
metadata_::info_as_json(fsinfo_options const& opts,
filesystem_info const* fsinfo) const {
nlohmann::json info;
vfs_stat stbuf;
statvfs(&stbuf);
if (auto version = meta_.dwarfs_version()) {
info["created_by"] = version.value();
}
if (auto ts = meta_.create_timestamp()) {
info["created_on"] =
fmt::format("{:%Y-%m-%dT%H:%M:%S}", fmt::localtime(ts.value()));
}
if (opts.features.has(fsinfo_feature::metadata_summary)) {
info["block_size"] = meta_.block_size();
if (fsinfo) {
info["block_count"] = fsinfo->block_count;
}
info["inode_count"] = stbuf.files;
if (auto ps = meta_.preferred_path_separator()) {
info["preferred_path_separator"] = std::string(1, static_cast(*ps));
}
info["original_filesystem_size"] = stbuf.blocks;
if (fsinfo) {
info["compressed_block_size"] = fsinfo->compressed_block_size;
if (!fsinfo->uncompressed_block_size_is_estimate) {
info["uncompressed_block_size"] = fsinfo->uncompressed_block_size;
}
info["compressed_metadata_size"] = fsinfo->compressed_metadata_size;
if (!fsinfo->uncompressed_metadata_size_is_estimate) {
info["uncompressed_metadata_size"] = fsinfo->uncompressed_metadata_size;
}
}
if (auto opt = meta_.options()) {
nlohmann::json options;
parse_metadata_options(meta_, [&](auto const& name, bool value) {
if (value) {
options.push_back(name);
}
});
info["options"] = std::move(options);
if (auto res = opt->time_resolution_sec()) {
info["time_resolution"] = *res;
}
}
if (meta_.block_categories()) {
auto catnames = *meta_.category_names();
auto catinfo = get_category_info(meta_, fsinfo);
nlohmann::json& categories = info["categories"];
for (auto const& [category, ci] : catinfo) {
std::string name{catnames[category]};
categories[name] = {
{"block_count", ci.count},
};
if (ci.compressed_size) {
categories[name]["compressed_size"] = ci.compressed_size.value();
}
if (ci.uncompressed_size && !ci.uncompressed_size_is_estimate) {
categories[name]["uncompressed_size"] = ci.uncompressed_size.value();
}
}
}
}
if (opts.features.has(fsinfo_feature::metadata_details)) {
nlohmann::json meta;
meta["symlink_inode_offset"] = symlink_inode_offset_;
meta["file_inode_offset"] = file_inode_offset_;
meta["dev_inode_offset"] = dev_inode_offset_;
meta["chunks"] = meta_.chunks().size();
meta["directories"] = meta_.directories().size();
meta["inodes"] = meta_.inodes().size();
meta["chunk_table"] = meta_.chunk_table().size();
meta["entry_table_v2_2"] = meta_.entry_table_v2_2().size();
meta["symlink_table"] = meta_.symlink_table().size();
meta["uids"] = meta_.uids().size();
meta["gids"] = meta_.gids().size();
meta["modes"] = meta_.modes().size();
meta["names"] = meta_.names().size();
meta["symlinks"] = meta_.symlinks().size();
if (auto dev = meta_.devices()) {
meta["devices"] = dev->size();
}
if (auto de = meta_.dir_entries()) {
meta["dir_entries"] = de->size();
}
if (auto sfp = meta_.shared_files_table()) {
if (meta_.options()->packed_shared_files_table()) {
meta["packed_shared_files_table"] = sfp->size();
meta["unpacked_shared_files_table"] = shared_files_.size();
} else {
meta["shared_files_table"] = sfp->size();
}
meta["unique_files"] = unique_files_;
}
info["meta"] = std::move(meta);
}
if (opts.features.has(fsinfo_feature::directory_tree)) {
info["root"] = as_json(root_);
}
return info;
}
// TODO: can we move this to dir_entry_view?
template
void metadata_::dump(
std::ostream& os, const std::string& indent, directory_view dir,
dir_entry_view entry, fsinfo_options const& opts,
std::function const& icb) const {
auto count = dir.entry_count();
auto first = dir.first_entry();
os << " (" << count << " entries, parent=" << dir.parent_entry() << ")\n";
for (size_t i = 0; i < count; ++i) {
dump(os, indent, make_dir_entry_view(first + i, entry.self_index()), opts,
icb);
}
}
template
void metadata_::dump(
std::ostream& os, fsinfo_options const& opts, filesystem_info const* fsinfo,
std::function const& icb) const {
vfs_stat stbuf;
statvfs(&stbuf);
if (auto version = meta_.dwarfs_version()) {
os << "created by: " << *version << "\n";
}
if (auto ts = meta_.create_timestamp()) {
time_t tp = *ts;
std::string str(32, '\0');
str.resize(
std::strftime(str.data(), str.size(), "%F %T", std::localtime(&tp)));
os << "created on: " << str << "\n";
}
if (opts.features.has(fsinfo_feature::metadata_summary)) {
os << "block size: " << size_with_unit(stbuf.bsize) << "\n";
if (fsinfo) {
os << "block count: " << fsinfo->block_count << "\n";
}
os << "inode count: " << stbuf.files << "\n";
if (auto ps = meta_.preferred_path_separator()) {
os << "preferred path separator: " << static_cast(*ps) << "\n";
}
os << "original filesystem size: " << size_with_unit(stbuf.blocks) << "\n";
if (fsinfo) {
os << "compressed block size: "
<< size_with_unit(fsinfo->compressed_block_size);
if (!fsinfo->uncompressed_block_size_is_estimate) {
os << fmt::format(" ({0:.2f}%)",
(100.0 * fsinfo->compressed_block_size) /
fsinfo->uncompressed_block_size);
}
os << "\n";
os << "uncompressed block size: ";
if (fsinfo->uncompressed_block_size_is_estimate) {
os << "(at least) ";
}
os << size_with_unit(fsinfo->uncompressed_block_size) << "\n";
os << "compressed metadata size: "
<< size_with_unit(fsinfo->compressed_metadata_size);
if (!fsinfo->uncompressed_metadata_size_is_estimate) {
os << fmt::format(" ({0:.2f}%)",
(100.0 * fsinfo->compressed_metadata_size) /
fsinfo->uncompressed_metadata_size);
}
os << "\n";
os << "uncompressed metadata size: ";
if (fsinfo->uncompressed_metadata_size_is_estimate) {
os << "(at least) ";
}
os << size_with_unit(fsinfo->uncompressed_metadata_size) << "\n";
}
if (auto opt = meta_.options()) {
std::vector options;
parse_metadata_options(meta_, [&](auto const& name, bool value) {
if (value) {
options.push_back(name);
}
});
os << "options: " << boost::join(options, "\n ") << "\n";
if (auto res = opt->time_resolution_sec()) {
os << "time resolution: " << *res << " seconds\n";
}
}
if (meta_.block_categories()) {
auto catnames = *meta_.category_names();
auto catinfo = get_category_info(meta_, fsinfo);
os << "categories:\n";
for (auto const& [category, ci] : catinfo) {
os << " " << catnames[category] << ": " << ci.count << " blocks";
if (ci.compressed_size) {
if (ci.uncompressed_size_is_estimate ||
ci.uncompressed_size.value() != ci.compressed_size.value()) {
os << ", " << size_with_unit(ci.compressed_size.value())
<< " compressed";
}
if (!ci.uncompressed_size_is_estimate) {
os << ", " << size_with_unit(ci.uncompressed_size.value())
<< " uncompressed";
if (ci.uncompressed_size.value() != ci.compressed_size.value()) {
os << fmt::format(" ({0:.2f}%)",
(100.0 * ci.compressed_size.value()) /
ci.uncompressed_size.value());
}
}
}
os << "\n";
}
}
}
if (opts.features.has(fsinfo_feature::frozen_analysis)) {
analyze_frozen(os, meta_, data_.size(), opts);
}
if (opts.features.has(fsinfo_feature::metadata_details)) {
os << "symlink_inode_offset: " << symlink_inode_offset_ << "\n";
os << "file_inode_offset: " << file_inode_offset_ << "\n";
os << "dev_inode_offset: " << dev_inode_offset_ << "\n";
os << "chunks: " << meta_.chunks().size() << "\n";
os << "directories: " << meta_.directories().size() << "\n";
os << "inodes: " << meta_.inodes().size() << "\n";
os << "chunk_table: " << meta_.chunk_table().size() << "\n";
os << "entry_table_v2_2: " << meta_.entry_table_v2_2().size() << "\n";
os << "symlink_table: " << meta_.symlink_table().size() << "\n";
os << "uids: " << meta_.uids().size() << "\n";
os << "gids: " << meta_.gids().size() << "\n";
os << "modes: " << meta_.modes().size() << "\n";
os << "names: " << meta_.names().size() << "\n";
os << "symlinks: " << meta_.symlinks().size() << "\n";
if (auto dev = meta_.devices()) {
os << "devices: " << dev->size() << "\n";
}
if (auto de = meta_.dir_entries()) {
os << "dir_entries: " << de->size() << "\n";
}
if (auto sfp = meta_.shared_files_table()) {
if (meta_.options()->packed_shared_files_table()) {
os << "packed shared_files_table: " << sfp->size() << "\n";
os << "unpacked shared_files_table: " << shared_files_.size() << "\n";
} else {
os << "shared_files_table: " << sfp->size() << "\n";
}
os << "unique files: " << unique_files_ << "\n";
}
analyze_chunks(os);
}
if (opts.features.has(fsinfo_feature::metadata_full_dump)) {
os << ::apache::thrift::debugString(meta_.thaw()) << '\n';
}
if (opts.features.has(fsinfo_feature::directory_tree)) {
dump(os, "", root_, opts, icb);
}
}
template
nlohmann::json metadata_::as_json(directory_view dir,
dir_entry_view entry) const {
nlohmann::json arr = nlohmann::json::array();
auto count = dir.entry_count();
auto first = dir.first_entry();
for (size_t i = 0; i < count; ++i) {
arr.push_back(as_json(make_dir_entry_view(first + i, entry.self_index())));
}
return arr;
}
template
nlohmann::json metadata_::as_json(dir_entry_view entry) const {
nlohmann::json obj;
auto iv = entry.inode();
auto mode = iv.mode();
auto inode = iv.inode_num();
obj["mode"] = mode;
obj["modestring"] = file_stat::mode_string(mode);
obj["inode"] = inode;
if (inode > 0) {
obj["name"] = std::string(entry.name());
}
switch (posix_file_type::from_mode(mode)) {
case posix_file_type::regular:
obj["type"] = "file";
obj["size"] = file_size(iv, mode);
break;
case posix_file_type::directory:
obj["type"] = "directory";
obj["inodes"] = as_json(make_directory_view(iv), entry);
break;
case posix_file_type::symlink:
obj["type"] = "link";
obj["target"] = link_value(iv);
break;
case posix_file_type::block:
obj["type"] = "blockdev";
obj["device_id"] = get_device_id(inode);
break;
case posix_file_type::character:
obj["type"] = "chardev";
obj["device_id"] = get_device_id(inode);
break;
case posix_file_type::fifo:
obj["type"] = "fifo";
break;
case posix_file_type::socket:
obj["type"] = "socket";
break;
}
return obj;
}
template
nlohmann::json metadata_::as_json() const {
vfs_stat stbuf;
statvfs(&stbuf);
nlohmann::json obj{
{"statvfs",
{{"f_bsize", stbuf.bsize},
{"f_files", stbuf.files},
{"f_blocks", stbuf.blocks}}},
{"root", as_json(root_)},
};
return obj;
}
template
thrift::metadata::metadata metadata_::unpack_metadata() const {
PERFMON_CLS_SCOPED_SECTION(unpack_metadata)
auto meta = meta_.thaw();
if (auto opts = meta.options()) {
if (opts->packed_chunk_table().value()) {
meta.chunk_table() = chunk_table_;
}
if (opts->packed_directories().value()) {
meta.directories() = global_.directories();
}
if (opts->packed_shared_files_table().value()) {
meta.shared_files_table() = shared_files_;
}
if (auto const& names = global_.names(); names.is_packed()) {
meta.names() = names.unpack();
meta.compact_names().reset();
}
if (symlinks_.is_packed()) {
meta.symlinks() = symlinks_.unpack();
meta.compact_symlinks().reset();
}
opts->packed_chunk_table() = false;
opts->packed_directories() = false;
opts->packed_shared_files_table() = false;
}
return meta;
}
template
std::string metadata_::serialize_as_json(bool simple) const {
std::string json;
if (simple) {
apache::thrift::SimpleJSONSerializer serializer;
serializer.serialize(unpack_metadata(), &json);
} else {
apache::thrift::JSONSerializer serializer;
serializer.serialize(unpack_metadata(), &json);
}
return json;
}
template
template
void metadata_::walk(uint32_t self_index, uint32_t parent_index,
set_type& seen, T&& func) const {
func(self_index, parent_index);
auto entry = make_dir_entry_view(self_index, parent_index);
auto iv = entry.inode();
if (iv.is_directory()) {
auto inode = iv.inode_num();
if (!seen.emplace(inode).second) {
DWARFS_THROW(runtime_error, "cycle detected during directory walk");
}
auto dir = make_directory_view(iv);
for (auto cur_index : dir.entry_range()) {
walk(cur_index, self_index, seen, func);
}
seen.erase(inode);
}
}
template
void metadata_::walk_data_order_impl(
std::function const& func) const {
std::vector> entries;
if (auto dep = meta_.dir_entries()) {
entries.reserve(dep->size());
} else {
entries.reserve(meta_.inodes().size());
}
{
auto td = LOG_TIMED_DEBUG;
walk_tree([&](uint32_t self_index, uint32_t parent_index) {
entries.emplace_back(self_index, parent_index);
});
if (auto dep = meta_.dir_entries()) {
// 1. partition non-files / files
auto mid =
std::stable_partition(entries.begin(), entries.end(),
[de = *dep, beg = file_inode_offset_,
end = dev_inode_offset_](auto const& e) {
int ino = de[e.first].inode_num();
return ino < beg or ino >= end;
});
// 2. order files by chunk block number
// 2a. build mapping inode -> first chunk block
std::vector first_chunk_block;
{
auto td2 = LOG_TIMED_DEBUG;
first_chunk_block.resize(dep->size());
for (size_t ix = 0; ix < first_chunk_block.size(); ++ix) {
int ino = (*dep)[ix].inode_num();
if (ino >= file_inode_offset_ and ino < dev_inode_offset_) {
ino = file_inode_to_chunk_index(ino);
if (auto beg = chunk_table_lookup(ino);
beg != chunk_table_lookup(ino + 1)) {
first_chunk_block[ix] = meta_.chunks()[beg].block();
}
}
}
td2 << "prepare first chunk block vector";
}
// 2b. sort second partition accordingly
{
auto td2 = LOG_TIMED_DEBUG;
std::stable_sort(mid, entries.end(),
[&first_chunk_block](auto const& a, auto const& b) {
return first_chunk_block[a.first] <
first_chunk_block[b.first];
});
td2 << "final sort of " << std::distance(mid, entries.end())
<< " file entries";
}
} else {
std::sort(entries.begin(), entries.end(),
[this](auto const& a, auto const& b) {
return meta_.inodes()[a.first].inode_v2_2() <
meta_.inodes()[b.first].inode_v2_2();
});
}
td << "ordered " << entries.size() << " entries by file data order";
}
for (auto [self_index, parent_index] : entries) {
walk_call(func, self_index, parent_index);
}
}
template
std::optional
metadata_::find(directory_view dir, std::string_view name) const {
PERFMON_CLS_SCOPED_SECTION(find)
auto range = dir.entry_range();
auto it = std::lower_bound(
range.begin(), range.end(), name, [&](auto ix, std::string_view name) {
return internal::dir_entry_view_impl::name(ix, global_) < name;
});
std::optional rv;
if (it != range.end()) {
if (internal::dir_entry_view_impl::name(*it, global_) == name) {
rv = inode_view{internal::dir_entry_view_impl::inode(*it, global_)};
}
}
return rv;
}
template
std::optional
metadata_::find(const char* path) const {
while (*path == '/') {
++path;
}
std::optional iv = root_.inode();
while (*path) {
const char* next = ::strchr(path, '/');
size_t clen = next ? next - path : ::strlen(path); // Flawfinder: ignore
if (!iv->is_directory()) {
return std::nullopt;
}
iv = find(make_directory_view(*iv), std::string_view(path, clen));
if (!iv) {
break;
}
path = next ? next + 1 : path + clen;
}
return iv;
}
template
std::optional metadata_::find(int inode) const {
return get_entry(inode);
}
template
std::optional
metadata_::find(int inode, const char* name) const {
auto iv = get_entry(inode);
if (iv) {
if (!iv->is_directory()) {
return std::nullopt;
}
iv = find(make_directory_view(*iv), std::string_view(name));
}
return iv;
}
template
file_stat
metadata_::getattr_impl(inode_view iv,
getattr_options const& opts) const {
file_stat stbuf;
stbuf.set_dev(0); // TODO: should we make this configurable?
auto mode = iv.mode();
auto timebase = meta_.timestamp_base();
auto inode = iv.inode_num();
bool mtime_only = meta_.options() && meta_.options()->mtime_only();
uint32_t resolution = 1;
if (meta_.options()) {
if (auto res = meta_.options()->time_resolution_sec()) {
resolution = *res;
assert(resolution > 0);
}
}
if (options_.readonly) {
mode &= READ_ONLY_MASK;
}
stbuf.set_mode(mode);
if (!opts.no_size) {
stbuf.set_size(stbuf.is_directory() ? make_directory_view(iv).entry_count()
: file_size(iv, mode));
stbuf.set_blocks((stbuf.size_unchecked() + 511) / 512);
}
stbuf.set_ino(inode + inode_offset_);
stbuf.set_blksize(options_.block_size);
stbuf.set_uid(iv.getuid());
stbuf.set_gid(iv.getgid());
stbuf.set_mtime(resolution * (timebase + iv.raw().mtime_offset()));
if (mtime_only) {
stbuf.set_atime(stbuf.mtime_unchecked());
stbuf.set_ctime(stbuf.mtime_unchecked());
} else {
stbuf.set_atime(resolution * (timebase + iv.raw().atime_offset()));
stbuf.set_ctime(resolution * (timebase + iv.raw().ctime_offset()));
}
stbuf.set_nlink(options_.enable_nlink && stbuf.is_regular_file()
? DWARFS_NOTHROW(nlinks_.at(inode - file_inode_offset_))
: 1);
stbuf.set_rdev(stbuf.is_device() ? get_device_id(inode) : 0);
return stbuf;
}
template
file_stat
metadata_::getattr(inode_view iv, std::error_code& /*ec*/) const {
PERFMON_CLS_SCOPED_SECTION(getattr)
return getattr_impl(iv, {});
}
template
file_stat
metadata_::getattr(inode_view iv, getattr_options const& opts,
std::error_code& /*ec*/) const {
PERFMON_CLS_SCOPED_SECTION(getattr_opts)
return getattr_impl(iv, opts);
}
template
std::optional
metadata_::opendir(inode_view iv) const {
std::optional rv;
if (iv.is_directory()) {
rv = make_directory_view(iv);
}
return rv;
}
template
std::optional>
metadata_::readdir(directory_view dir, size_t offset) const {
PERFMON_CLS_SCOPED_SECTION(readdir)
switch (offset) {
case 0:
return std::pair(make_inode_view(dir.inode()), ".");
case 1:
return std::pair(make_inode_view(dir.parent_inode()), "..");
default:
offset -= 2;
if (offset >= dir.entry_count()) {
break;
}
auto index = dir.first_entry() + offset;
auto inode =
inode_view{internal::dir_entry_view_impl::inode(index, global_)};
return std::pair(inode,
internal::dir_entry_view_impl::name(index, global_));
}
return std::nullopt;
}
template
void metadata_::access(inode_view iv, int mode,
file_stat::uid_type uid,
file_stat::gid_type gid,
std::error_code& ec) const {
LOG_DEBUG << fmt::format("access([{}, {:o}, {}, {}], {:o}, {}, {})",
iv.inode_num(), iv.mode(), iv.getuid(), iv.getgid(),
mode, uid, gid);
if (mode == F_OK) {
// easy; we're only interested in the file's existance
ec.clear();
return;
}
int access_mode = 0;
auto set_xok = [&access_mode]() {
#ifdef _WIN32
access_mode |= 1; // Windows has no notion of X_OK
#else
access_mode |= X_OK;
#endif
};
if (uid == 0) {
access_mode = R_OK | W_OK;
if (iv.mode() & uint16_t(fs::perms::owner_exec | fs::perms::group_exec |
fs::perms::others_exec)) {
set_xok();
}
} else {
auto test = [e_mode = iv.mode(), &access_mode, &set_xok,
readonly = options_.readonly](fs::perms r_bit, fs::perms w_bit,
fs::perms x_bit) {
if (e_mode & uint16_t(r_bit)) {
access_mode |= R_OK;
}
if (e_mode & uint16_t(w_bit)) {
if (!readonly) {
access_mode |= W_OK;
}
}
if (e_mode & uint16_t(x_bit)) {
set_xok();
}
};
// Let's build the inode's access mask
test(fs::perms::others_read, fs::perms::others_write,
fs::perms::others_exec);
if (iv.getgid() == gid) {
test(fs::perms::group_read, fs::perms::group_write,
fs::perms::group_exec);
}
if (iv.getuid() == uid) {
test(fs::perms::owner_read, fs::perms::owner_write,
fs::perms::owner_exec);
}
}
if ((access_mode & mode) == mode) {
ec.clear();
} else {
ec = std::make_error_code(std::errc::permission_denied);
}
}
template
int metadata_::open(inode_view iv, std::error_code& ec) const {
if (iv.is_regular_file()) {
ec.clear();
return iv.inode_num();
}
ec = std::make_error_code(std::errc::invalid_argument);
return 0;
}
template
std::string metadata_::readlink(inode_view iv, readlink_mode mode,
std::error_code& ec) const {
if (iv.is_symlink()) {
ec.clear();
return link_value(iv, mode);
}
ec = std::make_error_code(std::errc::invalid_argument);
return {};
}
template
void metadata_::statvfs(vfs_stat* stbuf) const {
::memset(stbuf, 0, sizeof(*stbuf));
// Make sure bsize and frsize are the same, as doing otherwise can confuse
// some applications (such as `duf`).
stbuf->bsize = 1UL;
stbuf->frsize = 1UL;
stbuf->blocks = meta_.total_fs_size();
if (!options_.enable_nlink) {
if (auto ths = meta_.total_hardlink_size()) {
stbuf->blocks += *ths;
}
}
stbuf->files = inode_count_;
stbuf->readonly = true;
stbuf->namemax = PATH_MAX;
}
template
chunk_range
metadata_::get_chunks(int inode, std::error_code& ec) const {
return get_chunk_range(inode - inode_offset_, ec);
}
template
nlohmann::json metadata_::get_inode_info(inode_view iv) const {
nlohmann::json obj;
if (iv.is_regular_file()) {
std::error_code ec;
auto chunk_range = get_chunk_range(iv.inode_num(), ec);
DWARFS_CHECK(!ec, fmt::format("get_chunk_range({}): {}", iv.inode_num(),
ec.message()));
for (auto const& chunk : chunk_range) {
nlohmann::json& chk = obj["chunks"].emplace_back();
chk["block"] = chunk.block();
chk["offset"] = chunk.offset();
chk["size"] = chunk.size();
if (auto catname = get_block_category(chunk.block())) {
chk["category"] = catname.value();
}
}
}
obj["mode"] = iv.mode();
obj["modestring"] = file_stat::mode_string(iv.mode());
obj["uid"] = iv.getuid();
obj["gid"] = iv.getgid();
return obj;
}
template
std::optional
metadata_::get_block_category(size_t block_number) const {
if (auto catnames = meta_.category_names()) {
if (auto categories = meta_.block_categories()) {
return std::string(catnames.value()[categories.value()[block_number]]);
}
}
return std::nullopt;
}
template
std::vector
metadata_::get_all_block_categories() const {
std::vector rv;
if (auto catnames = meta_.category_names()) {
rv.reserve(catnames.value().size());
for (auto const& name : catnames.value()) {
rv.emplace_back(name);
}
}
return rv;
}
template
std::vector metadata_::get_all_uids() const {
std::vector rv;
rv.resize(meta_.uids().size());
std::copy(meta_.uids().begin(), meta_.uids().end(), rv.begin());
return rv;
}
template
std::vector metadata_::get_all_gids() const {
std::vector rv;
rv.resize(meta_.gids().size());
std::copy(meta_.gids().begin(), meta_.gids().end(), rv.begin());
return rv;
}
metadata_v2::metadata_v2(logger& lgr, std::span schema,
std::span data,
metadata_options const& options, int inode_offset,
bool force_consistency_check,
std::shared_ptr perfmon)
: impl_(make_unique_logging_object(
lgr, schema, data, options, inode_offset, force_consistency_check,
std::move(perfmon))) {}
} // namespace dwarfs::reader::internal