feat: allow uid/gid override in filesystem and FUSE driver (see gh #244)

This commit is contained in:
Marcus Holland-Moritz 2024-11-17 17:41:58 +01:00
parent 2ff7602192
commit 3a895769de
7 changed files with 126 additions and 2 deletions

View File

@ -47,6 +47,14 @@ options:
If you have a lot of CPUs, increasing this number can help
speed up access to files in the filesystem.
- `-o uid=`*num*:
Override the user ID for the whole file system. This option
is not supported on Windows.
- `-o gid=`*num*:
Override the group ID for the whole file system. This option
is not supported on Windows.
- `-o decratio=`*value*:
The ratio over which a block is fully decompressed. Blocks
are only decompressed partially, so each block has to carry

View File

@ -21,6 +21,11 @@
#pragma once
#include <cstddef>
#include <optional>
#include <dwarfs/file_stat.h>
namespace dwarfs::reader {
struct metadata_options {
@ -28,6 +33,8 @@ struct metadata_options {
bool readonly{false};
bool check_consistency{false};
size_t block_size{512};
std::optional<file_stat::uid_type> fs_uid{};
std::optional<file_stat::gid_type> fs_gid{};
};
} // namespace dwarfs::reader

View File

@ -1800,8 +1800,8 @@ metadata_<LoggerPolicy>::getattr_impl(inode_view iv,
stbuf.set_ino(inode + inode_offset_);
stbuf.set_blksize(options_.block_size);
stbuf.set_uid(iv.getuid());
stbuf.set_gid(iv.getgid());
stbuf.set_uid(options_.fs_uid.value_or(iv.getuid()));
stbuf.set_gid(options_.fs_gid.value_or(iv.getgid()));
stbuf.set_mtime(resolution * (timebase + ivr.mtime_offset()));
if (mtime_only) {

View File

@ -1229,6 +1229,63 @@ TEST(filesystem, uid_gid_count) {
EXPECT_EQ(349999, st99999.gid());
}
TEST(filesystem, uid_gid_override) {
test::test_logger lgr;
auto input = std::make_shared<test::os_access_mock>();
input->add("", {1, 040755, 1, 0, 0, 10, 42, 0, 0, 0});
input->add("foo16.txt", {2, 0100755, 1, 60000, 65535, 5, 42, 0, 0, 0},
"hello");
input->add("foo32.txt", {3, 0100755, 1, 65536, 4294967295, 5, 42, 0, 0, 0},
"world");
auto fsimage = build_dwarfs(lgr, input, "null");
auto mm = std::make_shared<test::mmap_mock>(std::move(fsimage));
{
reader::filesystem_options opts{.metadata = {
.fs_uid = 99999,
.fs_gid = 80000,
}};
reader::filesystem_v2 fs(lgr, *input, mm, opts);
auto dev16 = fs.find("/foo16.txt");
auto dev32 = fs.find("/foo32.txt");
ASSERT_TRUE(dev16);
ASSERT_TRUE(dev32);
auto st16 = fs.getattr(dev16->inode());
auto st32 = fs.getattr(dev32->inode());
EXPECT_EQ(99999, st16.uid());
EXPECT_EQ(80000, st16.gid());
EXPECT_EQ(99999, st32.uid());
EXPECT_EQ(80000, st32.gid());
}
{
reader::filesystem_v2 fs(lgr, *input, mm);
auto dev16 = fs.find("/foo16.txt");
auto dev32 = fs.find("/foo32.txt");
ASSERT_TRUE(dev16);
ASSERT_TRUE(dev32);
auto st16 = fs.getattr(dev16->inode());
auto st32 = fs.getattr(dev32->inode());
EXPECT_EQ(60000, st16.uid());
EXPECT_EQ(65535, st16.gid());
EXPECT_EQ(65536, st32.uid());
EXPECT_EQ(4294967295, st32.gid());
}
}
TEST(section_index_regression, github183) {
static constexpr uint64_t section_offset_mask{(UINT64_C(1) << 48) - 1};

View File

@ -169,6 +169,10 @@ TEST_P(manpage_coverage_test, options) {
if (tool.is_fuse) {
man_opts.erase("allow_root");
man_opts.erase("allow_other");
#ifdef _WIN32
man_opts.erase("uid");
man_opts.erase("gid");
#endif
} else {
EXPECT_TRUE(help_opts.contains("help"))
<< tool_name << " missing help option";

View File

@ -37,6 +37,7 @@
#ifndef _WIN32
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#ifdef __APPLE__
#include <sys/mount.h>
@ -1055,6 +1056,7 @@ TEST_P(tools_test, end_to_end) {
#ifndef _WIN32
"-oenable_nlink",
"-oreadonly",
"-ouid=2345,gid=3456",
#endif
};
@ -1075,6 +1077,7 @@ TEST_P(tools_test, end_to_end) {
#ifndef _WIN32
bool enable_nlink{false};
bool readonly{false};
bool uid_gid_override{false};
#endif
for (size_t i = 0; i < all_options.size(); ++i) {
@ -1087,6 +1090,9 @@ TEST_P(tools_test, end_to_end) {
if (opt == "-oenable_nlink") {
enable_nlink = true;
}
if (opt.find("-ouid=") != std::string::npos) {
uid_gid_override = true;
}
#endif
args.push_back(opt);
}
@ -1120,6 +1126,18 @@ TEST_P(tools_test, end_to_end) {
// This doesn't really work on Windows (yet)
EXPECT_TRUE(check_readonly(mountpoint / "format.sh", readonly))
<< runner.cmdline();
if (uid_gid_override) {
struct ::stat st;
ASSERT_EQ(0, ::lstat(mountpoint.string().c_str(), &st))
<< runner.cmdline();
EXPECT_EQ(st.st_uid, 2345) << runner.cmdline();
EXPECT_EQ(st.st_gid, 3456) << runner.cmdline();
ASSERT_EQ(0,
::lstat((mountpoint / "format.sh").string().c_str(), &st))
<< runner.cmdline();
EXPECT_EQ(st.st_uid, 2345) << runner.cmdline();
EXPECT_EQ(st.st_gid, 3456) << runner.cmdline();
}
#endif
auto perfmon =
dwarfs::getxattr(mountpoint, "user.dwarfs.driver.perfmon");

View File

@ -169,6 +169,10 @@ struct options {
char const* cache_tidy_interval_str{nullptr}; // TODO: const?? -> use string?
char const* cache_tidy_max_age_str{nullptr}; // TODO: const?? -> use string?
char const* seq_detector_thresh_str{nullptr}; // TODO: const?? -> use string?
#ifndef _WIN32
char const* uid_str{nullptr}; // TODO: const?? -> use string?
char const* gid_str{nullptr}; // TODO: const?? -> use string?
#endif
#if DWARFS_PERFMON_ENABLED
char const* perfmon_enabled_str{nullptr}; // TODO: const?? -> use string?
char const* perfmon_trace_file_str{nullptr}; // TODO: const?? -> use string?
@ -189,6 +193,10 @@ struct options {
std::chrono::milliseconds block_cache_tidy_interval{std::chrono::minutes(5)};
std::chrono::milliseconds block_cache_tidy_max_age{std::chrono::minutes{10}};
size_t seq_detector_threshold{kDefaultSeqDetectorThreshold};
#ifndef _WIN32
std::optional<file_stat::uid_type> fs_uid;
std::optional<file_stat::gid_type> fs_gid;
#endif
bool is_help{false};
#ifdef DWARFS_BUILTIN_MANPAGE
bool is_man{false};
@ -236,6 +244,10 @@ constexpr struct ::fuse_opt dwarfs_opts[] = {
DWARFS_OPT("readahead=%s", readahead_str, 0),
DWARFS_OPT("debuglevel=%s", debuglevel_str, 0),
DWARFS_OPT("workers=%s", workers_str, 0),
#ifndef _WIN32
DWARFS_OPT("uid=%s", uid_str, 0),
DWARFS_OPT("gid=%s", gid_str, 0),
#endif
DWARFS_OPT("mlock=%s", mlock_str, 0),
DWARFS_OPT("decratio=%s", decompress_ratio_str, 0),
DWARFS_OPT("offset=%s", image_offset_str, 0),
@ -1202,6 +1214,10 @@ void usage(std::ostream& os, std::filesystem::path const& progname) {
<< " -o blocksize=SIZE set file I/O block size (512K)\n"
<< " -o readahead=SIZE set readahead size (0)\n"
<< " -o workers=NUM number of worker threads (2)\n"
#ifndef _WIN32
<< " -o uid=NUM override user ID for file system\n"
<< " -o gid=NUM override group ID for file system\n"
#endif
<< " -o mlock=NAME mlock mode: (none), try, must\n"
<< " -o decratio=NUM ratio for full decompression (0.8)\n"
<< " -o offset=NUM|auto filesystem image offset in bytes (0)\n"
@ -1449,6 +1465,10 @@ void load_filesystem(dwarfs_userdata& userdata) {
fsopts.metadata.enable_nlink = bool(opts.enable_nlink);
fsopts.metadata.readonly = bool(opts.readonly);
fsopts.metadata.block_size = opts.blocksize;
#ifndef _WIN32
fsopts.metadata.fs_uid = opts.fs_uid;
fsopts.metadata.fs_gid = opts.fs_gid;
#endif
fsopts.inode_offset = inode_offset;
if (opts.image_offset_str) {
@ -1603,6 +1623,16 @@ int dwarfs_main(int argc, sys_char** argv, iolayer const& iol) {
opts.decompress_ratio =
opts.decompress_ratio_str ? to<double>(opts.decompress_ratio_str) : 0.8;
#ifndef _WIN32
if (opts.uid_str) {
opts.fs_uid = to<file_stat::uid_type>(opts.uid_str);
}
if (opts.gid_str) {
opts.fs_gid = to<file_stat::gid_type>(opts.gid_str);
}
#endif
if (opts.cache_tidy_strategy_str) {
if (auto it = cache_tidy_strategy_map.find(opts.cache_tidy_strategy_str);
it != cache_tidy_strategy_map.end()) {