Skip to content

Commit 82060b9

Browse files
committed
feat: allow setting image size in FUSE driver (fixes gh mhx#239)
1 parent daaa4e3 commit 82060b9

File tree

7 files changed

+123
-24
lines changed

7 files changed

+123
-24
lines changed

doc/dwarfs.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ options:
6666
This is only useful for images that have some header located
6767
before the actual filesystem data.
6868

69+
- `-o imagesize=`*value*:
70+
Explicitly set the size of the filesystem image in bytes,
71+
starting from the offset. This can be used in cases where
72+
the image is embedded in a larger file.
73+
6974
- `-o mlock=none`|`try`|`must`:
7075
Set this to `try` or `must` instead of the default `none` to
7176
try or require `mlock()`ing of the file system metadata into

include/dwarfs/reader/filesystem_options.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
#pragma once
2323

24+
#include <limits>
25+
2426
#include <dwarfs/reader/block_cache_options.h>
2527
#include <dwarfs/reader/inode_reader_options.h>
2628
#include <dwarfs/reader/metadata_options.h>
@@ -34,6 +36,7 @@ struct filesystem_options {
3436

3537
mlock_mode lock_mode{mlock_mode::NONE};
3638
file_off_t image_offset{0};
39+
file_off_t image_size{std::numeric_limits<file_off_t>::max()};
3740
block_cache_options block_cache{};
3841
metadata_options metadata{};
3942
inode_reader_options inode_reader{};

include/dwarfs/reader/internal/filesystem_parser.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ class filesystem_parser {
4545
public:
4646
static file_off_t find_image_offset(mmif& mm, file_off_t image_offset);
4747

48-
explicit filesystem_parser(std::shared_ptr<mmif> mm,
49-
file_off_t image_offset = 0);
48+
explicit filesystem_parser(
49+
std::shared_ptr<mmif> mm, file_off_t image_offset = 0,
50+
file_off_t image_size = std::numeric_limits<file_off_t>::max());
5051

5152
std::optional<dwarfs::internal::fs_section> next_section();
5253

@@ -74,6 +75,7 @@ class filesystem_parser {
7475

7576
std::shared_ptr<mmif> mm_;
7677
file_off_t const image_offset_{0};
78+
file_off_t const image_size_{std::numeric_limits<file_off_t>::max()};
7779
file_off_t offset_{0};
7880
int version_{0};
7981
uint8_t major_{0};

src/reader/filesystem_v2.cpp

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,8 @@ class filesystem_ final : public filesystem_v2::impl {
308308
return meta_.get_all_gids();
309309
}
310310
std::shared_ptr<filesystem_parser> get_parser() const override {
311-
return std::make_unique<filesystem_parser>(mm_, image_offset_);
311+
return std::make_unique<filesystem_parser>(mm_, image_offset_,
312+
options_.image_size);
312313
}
313314
std::optional<std::string>
314315
get_block_category(size_t block_no) const override {
@@ -341,6 +342,7 @@ class filesystem_ final : public filesystem_v2::impl {
341342
mutable std::unique_ptr<filesystem_info const> fsinfo_;
342343
history history_;
343344
file_off_t const image_offset_;
345+
filesystem_options const options_;
344346
PERFMON_CLS_PROXY_DECL
345347
PERFMON_CLS_TIMER_DECL(find_path)
346348
PERFMON_CLS_TIMER_DECL(find_inode)
@@ -380,7 +382,7 @@ filesystem_<LoggerPolicy>::get_info(fsinfo_options const& opts) const {
380382
std::lock_guard lock(mx_);
381383

382384
if (!fsinfo_ || opts.block_access > fsinfo_block_access_level_) {
383-
filesystem_parser parser(mm_, image_offset_);
385+
filesystem_parser parser(mm_, image_offset_, options_.image_size);
384386
filesystem_info info;
385387

386388
parser.rewind();
@@ -429,14 +431,15 @@ filesystem_<LoggerPolicy>::get_info(fsinfo_options const& opts) const {
429431
template <typename LoggerPolicy>
430432
filesystem_<LoggerPolicy>::filesystem_(
431433
logger& lgr, os_access const& os, std::shared_ptr<mmif> mm,
432-
const filesystem_options& options,
434+
filesystem_options const& options,
433435
std::shared_ptr<performance_monitor const> perfmon)
434436
: LOG_PROXY_INIT(lgr)
435437
, os_{os}
436438
, mm_{std::move(mm)}
437439
, history_({.with_timestamps = true})
438-
, image_offset_{filesystem_parser::find_image_offset(
439-
*mm_, options.image_offset)} // clang-format off
440+
, image_offset_{filesystem_parser::find_image_offset(*mm_,
441+
options.image_offset)}
442+
, options_{options} // clang-format off
440443
PERFMON_CLS_PROXY_INIT(perfmon, "filesystem_v2")
441444
PERFMON_CLS_TIMER_INIT(find_path)
442445
PERFMON_CLS_TIMER_INIT(find_inode)
@@ -465,7 +468,7 @@ filesystem_<LoggerPolicy>::filesystem_(
465468
PERFMON_CLS_TIMER_INIT(readv_future_ec) // clang-format on
466469
{
467470
block_cache cache(lgr, os_, mm_, options.block_cache, perfmon);
468-
filesystem_parser parser(mm_, image_offset_);
471+
filesystem_parser parser(mm_, image_offset_, options.image_size);
469472

470473
if (parser.has_index()) {
471474
LOG_DEBUG << "found valid section index";
@@ -531,7 +534,7 @@ filesystem_<LoggerPolicy>::filesystem_(
531534
template <typename LoggerPolicy>
532535
int filesystem_<LoggerPolicy>::check(filesystem_check_level level,
533536
size_t num_threads) const {
534-
filesystem_parser parser(mm_, image_offset_);
537+
filesystem_parser parser(mm_, image_offset_, options_.image_size);
535538

536539
worker_group wg(LOG_GET_LOGGER, os_, "fscheck", num_threads);
537540
std::vector<std::future<fs_section>> sections;
@@ -593,7 +596,7 @@ int filesystem_<LoggerPolicy>::check(filesystem_check_level level,
593596
template <typename LoggerPolicy>
594597
void filesystem_<LoggerPolicy>::dump(std::ostream& os,
595598
fsinfo_options const& opts) const {
596-
filesystem_parser parser(mm_, image_offset_);
599+
filesystem_parser parser(mm_, image_offset_, options_.image_size);
597600

598601
if (opts.features.has(fsinfo_feature::version)) {
599602
os << "DwarFS version " << parser.version();
@@ -662,7 +665,7 @@ std::string filesystem_<LoggerPolicy>::dump(fsinfo_options const& opts) const {
662665
template <typename LoggerPolicy>
663666
nlohmann::json
664667
filesystem_<LoggerPolicy>::info_as_json(fsinfo_options const& opts) const {
665-
filesystem_parser parser(mm_, image_offset_);
668+
filesystem_parser parser(mm_, image_offset_, options_.image_size);
666669

667670
auto info = nlohmann::json::object();
668671

src/reader/internal/filesystem_parser.cpp

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,13 @@ filesystem_parser::find_image_offset(mmif& mm, file_off_t image_offset) {
109109
}
110110

111111
filesystem_parser::filesystem_parser(std::shared_ptr<mmif> mm,
112-
file_off_t image_offset)
112+
file_off_t image_offset,
113+
file_off_t image_size)
113114
: mm_{std::move(mm)}
114-
, image_offset_{find_image_offset(*mm_, image_offset)} {
115-
if (mm_->size() < image_offset_ + sizeof(file_header)) {
115+
, image_offset_{find_image_offset(*mm_, image_offset)}
116+
, image_size_{
117+
std::min<file_off_t>(image_size, mm_->size() - image_offset_)} {
118+
if (image_size_ < static_cast<file_off_t>(sizeof(file_header))) {
116119
DWARFS_THROW(runtime_error, "file too small");
117120
}
118121

@@ -143,7 +146,7 @@ filesystem_parser::filesystem_parser(std::shared_ptr<mmif> mm,
143146

144147
std::optional<fs_section> filesystem_parser::next_section() {
145148
if (index_.empty()) {
146-
if (offset_ < static_cast<file_off_t>(mm_->size())) {
149+
if (offset_ < image_offset_ + image_size_) {
147150
auto section = fs_section(*mm_, offset_, version_);
148151
offset_ = section.end();
149152
return section;
@@ -154,7 +157,7 @@ std::optional<fs_section> filesystem_parser::next_section() {
154157
uint64_t offset = id & section_offset_mask;
155158
uint64_t next_offset = offset_ < static_cast<file_off_t>(index_.size())
156159
? index_[offset_] & section_offset_mask
157-
: mm_->size() - image_offset_;
160+
: image_size_;
158161
return fs_section(mm_, static_cast<section_type>(id >> 48),
159162
image_offset_ + offset, next_offset - offset, version_);
160163
}
@@ -189,7 +192,9 @@ bool filesystem_parser::has_checksums() const { return version_ >= 2; }
189192

190193
bool filesystem_parser::has_index() const { return !index_.empty(); }
191194

192-
size_t filesystem_parser::filesystem_size() const { return mm_->size(); }
195+
size_t filesystem_parser::filesystem_size() const {
196+
return image_offset_ + image_size_;
197+
}
193198

194199
std::span<uint8_t const>
195200
filesystem_parser::section_data(fs_section const& s) const {
@@ -199,14 +204,15 @@ filesystem_parser::section_data(fs_section const& s) const {
199204
void filesystem_parser::find_index() {
200205
uint64_t index_pos;
201206

202-
::memcpy(&index_pos, mm_->as<void>(mm_->size() - sizeof(uint64_t)),
207+
::memcpy(&index_pos,
208+
mm_->as<void>(image_offset_ + image_size_ - sizeof(uint64_t)),
203209
sizeof(uint64_t));
204210

205211
if ((index_pos >> 48) == static_cast<uint16_t>(section_type::SECTION_INDEX)) {
206212
index_pos &= section_offset_mask;
207213
index_pos += image_offset_;
208214

209-
if (index_pos < mm_->size()) {
215+
if (index_pos < static_cast<uint64_t>(image_offset_ + image_size_)) {
210216
auto section = fs_section(*mm_, index_pos, version_);
211217

212218
if (section.check_fast(*mm_)) {

test/dwarfs_test.cpp

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
#include <dwarfs/vfs_stat.h>
5151
#include <dwarfs/writer/entry_factory.h>
5252
#include <dwarfs/writer/filesystem_writer.h>
53+
#include <dwarfs/writer/filesystem_writer_options.h>
5354
#include <dwarfs/writer/filter_debug.h>
5455
#include <dwarfs/writer/fragment_order_options.h>
5556
#include <dwarfs/writer/rule_based_entry_filter.h>
@@ -81,6 +82,8 @@ build_dwarfs(logger& lgr, std::shared_ptr<test::os_access_mock> input,
8182
std::string const& compression,
8283
writer::segmenter::config const& cfg = writer::segmenter::config(),
8384
writer::scanner_options const& options = writer::scanner_options(),
85+
writer::filesystem_writer_options const& writer_opts =
86+
writer::filesystem_writer_options(),
8487
writer::writer_progress* prog = nullptr,
8588
std::shared_ptr<test::filter_transformer_data> ftd = nullptr,
8689
std::optional<std::span<std::filesystem::path const>> input_list =
@@ -120,7 +123,7 @@ build_dwarfs(logger& lgr, std::shared_ptr<test::os_access_mock> input,
120123
std::ostringstream oss;
121124

122125
block_compressor bc(compression);
123-
writer::filesystem_writer fsw(oss, lgr, pool, *prog);
126+
writer::filesystem_writer fsw(oss, lgr, pool, *prog, writer_opts);
124127
fsw.add_default_compressor(bc);
125128

126129
s.scan(fsw, std::filesystem::path("/"), *prog, input_list);
@@ -187,7 +190,7 @@ void basic_end_to_end_test(
187190
auto ftd = std::make_shared<test::filter_transformer_data>();
188191

189192
auto fsimage =
190-
build_dwarfs(lgr, input, compressor, cfg, options, &wprog, ftd);
193+
build_dwarfs(lgr, input, compressor, cfg, options, {}, &wprog, ftd);
191194

192195
EXPECT_EQ(14, ftd->filter_calls.size());
193196
EXPECT_EQ(15, ftd->transform_calls.size());
@@ -1046,7 +1049,7 @@ TEST_P(filter_test, filesystem) {
10461049
writer::scanner_options options;
10471050
options.remove_empty_dirs = true;
10481051

1049-
auto fsimage = build_dwarfs(lgr, input, "null", cfg, options, nullptr,
1052+
auto fsimage = build_dwarfs(lgr, input, "null", cfg, options, {}, nullptr,
10501053
nullptr, std::nullopt, std::move(rbf));
10511054

10521055
auto mm = std::make_shared<test::mmap_mock>(std::move(fsimage));
@@ -1136,8 +1139,8 @@ TEST(file_scanner, input_list) {
11361139
"foo.pl",
11371140
};
11381141

1139-
auto fsimage = build_dwarfs(lgr, input, "null", bmcfg, opts, nullptr, nullptr,
1140-
input_list);
1142+
auto fsimage = build_dwarfs(lgr, input, "null", bmcfg, opts, {}, nullptr,
1143+
nullptr, input_list);
11411144

11421145
auto mm = std::make_shared<test::mmap_mock>(std::move(fsimage));
11431146

@@ -1926,3 +1929,73 @@ TEST(filesystem, inode_size_cache) {
19261929
EXPECT_EQ(st.size(), size);
19271930
}
19281931
}
1932+
1933+
TEST(filesystem, multi_image) {
1934+
test::test_logger lgr;
1935+
std::string data("header");
1936+
std::vector<std::pair<file_off_t, file_off_t>> images;
1937+
1938+
for (std::string str : {"foo", "bar", "baz"}) {
1939+
auto input = std::make_shared<test::os_access_mock>();
1940+
input->add_dir("");
1941+
input->add_file(str, str);
1942+
auto img = build_dwarfs(lgr, input, "null", {}, {},
1943+
{.no_section_index = str == "bar"});
1944+
images.emplace_back(data.size(), img.size());
1945+
data += img;
1946+
data += "filler";
1947+
}
1948+
1949+
auto mm = std::make_shared<test::mmap_mock>(std::move(data));
1950+
auto os = std::make_shared<test::os_access_mock>();
1951+
1952+
std::vector<reader::filesystem_v2> fss;
1953+
1954+
for (size_t i = 0; i < images.size(); ++i) {
1955+
fss.emplace_back(
1956+
lgr, *os, mm,
1957+
reader::filesystem_options{.image_offset = images[i].first,
1958+
.image_size = images[i].second});
1959+
}
1960+
1961+
ASSERT_EQ(3, fss.size());
1962+
1963+
{
1964+
auto& fs = fss[0];
1965+
auto foo = fs.find("/foo");
1966+
auto bar = fs.find("/bar");
1967+
auto baz = fs.find("/baz");
1968+
1969+
ASSERT_TRUE(foo);
1970+
EXPECT_FALSE(bar);
1971+
EXPECT_FALSE(baz);
1972+
1973+
EXPECT_EQ("foo", fs.read_string(fs.open(foo->inode())));
1974+
}
1975+
1976+
{
1977+
auto& fs = fss[1];
1978+
auto foo = fs.find("/foo");
1979+
auto bar = fs.find("/bar");
1980+
auto baz = fs.find("/baz");
1981+
1982+
EXPECT_FALSE(foo);
1983+
ASSERT_TRUE(bar);
1984+
EXPECT_FALSE(baz);
1985+
1986+
EXPECT_EQ("bar", fs.read_string(fs.open(bar->inode())));
1987+
}
1988+
1989+
{
1990+
auto& fs = fss[2];
1991+
auto foo = fs.find("/foo");
1992+
auto bar = fs.find("/bar");
1993+
auto baz = fs.find("/baz");
1994+
1995+
EXPECT_FALSE(foo);
1996+
EXPECT_FALSE(bar);
1997+
ASSERT_TRUE(baz);
1998+
1999+
EXPECT_EQ("baz", fs.read_string(fs.open(baz->inode())));
2000+
}
2001+
}

tools/src/dwarfs_main.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ struct options {
164164
char const* mlock_str{nullptr}; // TODO: const?? -> use string?
165165
char const* decompress_ratio_str{nullptr}; // TODO: const?? -> use string?
166166
char const* image_offset_str{nullptr}; // TODO: const?? -> use string?
167+
char const* image_size_str{nullptr}; // TODO: const?? -> use string?
167168
char const* cache_tidy_strategy_str{nullptr}; // TODO: const?? -> use string?
168169
char const* cache_tidy_interval_str{nullptr}; // TODO: const?? -> use string?
169170
char const* cache_tidy_max_age_str{nullptr}; // TODO: const?? -> use string?
@@ -238,6 +239,7 @@ constexpr struct ::fuse_opt dwarfs_opts[] = {
238239
DWARFS_OPT("mlock=%s", mlock_str, 0),
239240
DWARFS_OPT("decratio=%s", decompress_ratio_str, 0),
240241
DWARFS_OPT("offset=%s", image_offset_str, 0),
242+
DWARFS_OPT("imagesize=%s", image_size_str, 0),
241243
DWARFS_OPT("tidy_strategy=%s", cache_tidy_strategy_str, 0),
242244
DWARFS_OPT("tidy_interval=%s", cache_tidy_interval_str, 0),
243245
DWARFS_OPT("tidy_max_age=%s", cache_tidy_max_age_str, 0),
@@ -1203,6 +1205,7 @@ void usage(std::ostream& os, std::filesystem::path const& progname) {
12031205
<< " -o mlock=NAME mlock mode: (none), try, must\n"
12041206
<< " -o decratio=NUM ratio for full decompression (0.8)\n"
12051207
<< " -o offset=NUM|auto filesystem image offset in bytes (0)\n"
1208+
<< " -o imagesize=NUM filesystem image size in bytes\n"
12061209
<< " -o enable_nlink show correct hardlink numbers\n"
12071210
<< " -o readonly show read-only file system\n"
12081211
<< " -o (no_)cache_image (don't) keep image in kernel cache\n"
@@ -1451,6 +1454,10 @@ void load_filesystem(dwarfs_userdata& userdata) {
14511454
fsopts.image_offset = reader::parse_image_offset(opts.image_offset_str);
14521455
}
14531456

1457+
if (opts.image_size_str) {
1458+
fsopts.image_size = to<file_off_t>(opts.image_size_str);
1459+
}
1460+
14541461
std::unordered_set<std::string> perfmon_enabled;
14551462
std::optional<std::filesystem::path> perfmon_trace_file;
14561463
#if DWARFS_PERFMON_ENABLED

0 commit comments

Comments
 (0)