diff --git a/doc/dwarfs.md b/doc/dwarfs.md index b56c9acd9..698ba111e 100644 --- a/doc/dwarfs.md +++ b/doc/dwarfs.md @@ -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 diff --git a/include/dwarfs/reader/metadata_options.h b/include/dwarfs/reader/metadata_options.h index 2e269e048..5bb42fd9a 100644 --- a/include/dwarfs/reader/metadata_options.h +++ b/include/dwarfs/reader/metadata_options.h @@ -21,6 +21,11 @@ #pragma once +#include +#include + +#include + 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 fs_uid{}; + std::optional fs_gid{}; }; } // namespace dwarfs::reader diff --git a/src/reader/internal/metadata_v2.cpp b/src/reader/internal/metadata_v2.cpp index 247ccea88..dec8ddd98 100644 --- a/src/reader/internal/metadata_v2.cpp +++ b/src/reader/internal/metadata_v2.cpp @@ -1800,8 +1800,8 @@ metadata_::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) { diff --git a/test/dwarfs_test.cpp b/test/dwarfs_test.cpp index a96fe2e98..c1d8de6c1 100644 --- a/test/dwarfs_test.cpp +++ b/test/dwarfs_test.cpp @@ -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(); + + 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(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}; diff --git a/test/manpage_test.cpp b/test/manpage_test.cpp index 5e390cf30..e21912546 100644 --- a/test/manpage_test.cpp +++ b/test/manpage_test.cpp @@ -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"; diff --git a/test/tools_test.cpp b/test/tools_test.cpp index b56ee2f73..2a0e4c872 100644 --- a/test/tools_test.cpp +++ b/test/tools_test.cpp @@ -37,6 +37,7 @@ #ifndef _WIN32 #include #include +#include #include #ifdef __APPLE__ #include @@ -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"); diff --git a/tools/src/dwarfs_main.cpp b/tools/src/dwarfs_main.cpp index d5bb83ff4..219ba0f15 100644 --- a/tools/src/dwarfs_main.cpp +++ b/tools/src/dwarfs_main.cpp @@ -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 fs_uid; + std::optional 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(opts.decompress_ratio_str) : 0.8; +#ifndef _WIN32 + if (opts.uid_str) { + opts.fs_uid = to(opts.uid_str); + } + + if (opts.gid_str) { + opts.fs_gid = to(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()) {