/* 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 #ifdef _WIN32 #include #else #include #include #include #include #endif #include #include #include #include #include #include #include #include "dwarfs/file_stat.h" #include "test_helpers.h" namespace { namespace bp = boost::process; namespace fs = std::filesystem; auto test_dir = fs::path(TEST_DATA_DIR).make_preferred(); auto test_data_tar = test_dir / "data.tar"; auto test_data_dwarfs = test_dir / "data.dwarfs"; #ifdef _WIN32 #define EXE_EXT ".exe" #else #define EXE_EXT "" #endif auto tools_dir = fs::path(TOOLS_BIN_DIR).make_preferred(); auto mkdwarfs_bin = tools_dir / "mkdwarfs" EXE_EXT; auto fuse3_bin = tools_dir / "dwarfs" EXE_EXT; auto fuse2_bin = tools_dir / "dwarfs2" EXE_EXT; auto dwarfsextract_bin = tools_dir / "dwarfsextract" EXE_EXT; auto dwarfsck_bin = tools_dir / "dwarfsck" EXE_EXT; auto universal_bin = tools_dir / "universal" / "dwarfs-universal" EXE_EXT; #ifndef _WIN32 pid_t get_dwarfs_pid(fs::path const& path) { std::array attr_buf; auto attr_len = ::getxattr(path.c_str(), "user.dwarfs.driver.pid", attr_buf.data(), attr_buf.size()); if (attr_len < 0) { throw std::runtime_error("could not read pid from xattr"); } return folly::to(std::string_view(attr_buf.data(), attr_len)); } #endif bool wait_until_file_ready(fs::path const& path, std::chrono::milliseconds timeout) { auto end = std::chrono::steady_clock::now() + timeout; std::error_code ec; while (!fs::exists(path, ec)) { #ifdef _WIN32 if (ec && ec.value() != ERROR_OPERATION_ABORTED) { #else if (ec) { #endif std::cerr << "*** exists: " << ec.message() << "\n"; } std::this_thread::sleep_for(std::chrono::milliseconds(1)); if (std::chrono::steady_clock::now() >= end) { return false; } } return true; } bool read_file(fs::path const& path, std::string& out) { std::ifstream ifs(path, std::ios::binary); if (!ifs.is_open()) { return false; } std::stringstream tmp; tmp << ifs.rdbuf(); out = tmp.str(); return true; } struct compare_directories_result { std::set mismatched; std::set directories; std::set symlinks; std::set regular_files; }; std::ostream& operator<<(std::ostream& os, compare_directories_result const& cdr) { for (auto const& m : cdr.mismatched) { os << "*** mismatched: " << m << "\n"; } for (auto const& m : cdr.regular_files) { os << "*** regular: " << m << "\n"; } for (auto const& m : cdr.directories) { os << "*** directory: " << m << "\n"; } for (auto const& m : cdr.symlinks) { os << "*** symlink: " << m << "\n"; } return os; } template void find_all(fs::path const& root, T const& func) { std::deque q; q.push_back(root); while (!q.empty()) { auto p = q.front(); q.pop_front(); for (auto const& e : fs::directory_iterator(p)) { func(e); if (e.symlink_status().type() == fs::file_type::directory) { q.push_back(e.path()); } } } } bool compare_directories(fs::path const& p1, fs::path const& p2, compare_directories_result* res = nullptr) { std::map m1, m2; std::set s1, s2; find_all(p1, [&](auto const& e) { auto rp = e.path().lexically_relative(p1); m1.emplace(rp, e); s1.insert(rp); }); find_all(p2, [&](auto const& e) { auto rp = e.path().lexically_relative(p2); m2.emplace(rp, e); s2.insert(rp); }); if (res) { res->mismatched.clear(); res->directories.clear(); res->symlinks.clear(); res->regular_files.clear(); } bool rv = true; std::set common; std::set_intersection(s1.begin(), s1.end(), s2.begin(), s2.end(), std::inserter(common, common.end())); if (s1.size() != common.size() || s2.size() != common.size()) { if (res) { std::set_symmetric_difference( s1.begin(), s1.end(), s2.begin(), s2.end(), std::inserter(res->mismatched, res->mismatched.end())); } rv = false; } for (auto const& p : common) { auto const& e1 = m1[p]; auto const& e2 = m2[p]; if (e1.symlink_status().type() != e2.symlink_status().type() || (e1.symlink_status().type() != fs::file_type::directory && e1.file_size() != e2.file_size())) { if (res) { res->mismatched.insert(p); } rv = false; continue; } switch (e1.symlink_status().type()) { case fs::file_type::regular: { std::string c1, c2; if (!read_file(e1.path(), c1) || !read_file(e2.path(), c2) || c1 != c2) { if (res) { res->mismatched.insert(p); } rv = false; } } if (res) { res->regular_files.insert(p); } break; case fs::file_type::directory: if (res) { res->directories.insert(p); } break; case fs::file_type::symlink: if (fs::read_symlink(e1.path()) != fs::read_symlink(e2.path())) { if (res) { res->mismatched.insert(p); } rv = false; } if (res) { res->symlinks.insert(p); } break; default: break; } } return rv; } #ifdef _WIN32 struct new_process_group : public ::boost::process::detail::handler_base { template void on_setup(WindowsExecutor& e [[maybe_unused]]) const { e.creation_flags |= CREATE_NEW_PROCESS_GROUP; } }; #endif class subprocess { public: template subprocess(std::filesystem::path const& prog, Args&&... args) : prog_{prog} { (append_arg(cmdline_, std::forward(args)), ...); try { // std::cout << "running: " << cmdline() << "\n"; c_ = bp::child(prog.string(), bp::args(cmdline_), bp::std_in.close(), bp::std_out > out_, bp::std_err > err_, ios_ #ifdef _WIN32 , new_process_group() #endif ); } catch (...) { std::cerr << "failed to create subprocess: " << cmdline() << "\n"; throw; } } std::string cmdline() const { std::string cmd = prog_.string(); if (!cmdline_.empty()) { cmd += ' '; cmd += folly::join(' ', cmdline_); } return cmd; } void run() { ios_.run(); c_.wait(); outs_ = out_.get(); errs_ = err_.get(); } void run_background() { if (pt_) { throw std::runtime_error("already running in background"); } pt_ = std::make_unique([this] { run(); }); } void wait() { if (!pt_) { throw std::runtime_error("no process running in background"); } pt_->join(); pt_.reset(); } void interrupt() { #ifdef _WIN32 ::GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid()); #else ::kill(pid(), SIGINT); #endif } std::string const& out() const { return outs_; } std::string const& err() const { return errs_; } bp::pid_t pid() const { return c_.id(); } int exit_code() const { return c_.exit_code(); } template static std::tuple run(Args&&... args) { auto p = subprocess(std::forward(args)...); p.run(); return {p.out(), p.err(), p.exit_code()}; } template static std::optional check_run(Args&&... args) { auto const [out, err, ec] = run(std::forward(args)...); if (ec != 0) { std::cerr << "stdout:\n" << out << "\nstderr:\n" << err << "\n"; return std::nullopt; } return out; } private: static void append_arg(std::vector& args, fs::path const& arg) { args.emplace_back(arg.string()); } static void append_arg(std::vector& args, std::vector const& more) { args.insert(args.end(), more.begin(), more.end()); } template static void append_arg(std::vector& args, T const& arg) { args.emplace_back(arg); } bp::child c_; boost::asio::io_service ios_; std::future out_; std::future err_; std::string outs_; std::string errs_; std::unique_ptr pt_; std::filesystem::path const prog_; std::vector cmdline_; }; #ifndef _WIN32 class process_guard { public: process_guard() = default; explicit process_guard(pid_t pid) : pid_{pid} { auto proc_dir = fs::path("/proc") / folly::to(pid); proc_dir_fd_ = ::open(proc_dir.c_str(), O_DIRECTORY); if (proc_dir_fd_ < 0) { throw std::runtime_error("could not open " + proc_dir.string()); } } ~process_guard() { if (proc_dir_fd_ >= 0) { ::close(proc_dir_fd_); } } bool check_exit(std::chrono::milliseconds timeout) { auto end = std::chrono::steady_clock::now() + timeout; while (::faccessat(proc_dir_fd_, "fd", F_OK, 0) == 0) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); if (std::chrono::steady_clock::now() >= end) { ::kill(pid_, SIGTERM); return false; } } return true; } private: pid_t pid_{-1}; int proc_dir_fd_{-1}; }; #endif class driver_runner { public: struct foreground_t {}; static constexpr foreground_t foreground = foreground_t(); driver_runner() = default; template driver_runner(fs::path const& driver, bool tool_arg, fs::path const& image, fs::path const& mountpoint, Args&&... args) : mountpoint_{mountpoint} { setup_mountpoint(mountpoint); #ifdef _WIN32 process_ = std::make_unique(driver, make_tool_arg(tool_arg), image, mountpoint, std::forward(args)...); process_->run_background(); wait_until_file_ready(mountpoint, std::chrono::seconds(5)); #else if (!subprocess::check_run(driver, make_tool_arg(tool_arg), image, mountpoint, std::forward(args)...)) { throw std::runtime_error("error running " + driver.string()); } dwarfs_guard_ = process_guard(get_dwarfs_pid(mountpoint)); #endif } template driver_runner(foreground_t, fs::path const& driver, bool tool_arg, fs::path const& image, fs::path const& mountpoint, Args&&... args) : mountpoint_{mountpoint} { setup_mountpoint(mountpoint); process_ = std::make_unique(driver, make_tool_arg(tool_arg), image, mountpoint, #ifndef _WIN32 "-f", #endif std::forward(args)...); process_->run_background(); #ifndef _WIN32 dwarfs_guard_ = process_guard(process_->pid()); #endif } bool unmount() { if (!mountpoint_.empty()) { #ifndef _WIN32 if (process_) { #endif process_->interrupt(); process_->wait(); auto ec = process_->exit_code(); bool is_expected_exit_code = ec == 0 #ifndef _WIN32 || ec == SIGINT #endif ; if (!is_expected_exit_code) { std::cerr << "driver failed to unmount:\nout:\n" << process_->out() << "err:\n" << process_->err() << "exit code: " << ec << "\n"; } process_.reset(); mountpoint_.clear(); return is_expected_exit_code; #ifndef _WIN32 } else { subprocess::check_run(find_fusermount(), "-u", mountpoint_); mountpoint_.clear(); return dwarfs_guard_.check_exit(std::chrono::seconds(5)); } #endif } return false; } std::string cmdline() const { std::string rv; if (process_) { rv = process_->cmdline(); } return rv; } ~driver_runner() { if (!mountpoint_.empty()) { if (!unmount()) { ::abort(); } } } static std::vector make_tool_arg(bool tool_arg) { std::vector rv; if (tool_arg) { rv.push_back("--tool=dwarfs"); } return rv; } private: #ifndef _WIN32 static fs::path find_fusermount() { auto fusermount_bin = dwarfs::test::find_binary("fusermount"); if (!fusermount_bin) { fusermount_bin = dwarfs::test::find_binary("fusermount3"); } if (!fusermount_bin) { throw std::runtime_error("no fusermount binary found"); } return *fusermount_bin; } #endif static void setup_mountpoint(fs::path const& mp) { if (fs::exists(mp)) { fs::remove(mp); } #ifndef _WIN32 fs::create_directory(mp); #endif } fs::path mountpoint_; std::unique_ptr process_; #ifndef _WIN32 process_guard dwarfs_guard_; #endif }; bool check_readonly(fs::path const& p, bool readonly = false) { auto st = fs::status(p); bool is_writable = (st.permissions() & fs::perms::owner_write) != fs::perms::none; if (is_writable == readonly) { std::cerr << "readonly=" << readonly << ", st_mode=" << fmt::format("{0:o}", uint16_t(st.permissions())) << "\n"; return false; } #ifndef _WIN32 if (::access(p.string().c_str(), W_OK) == 0) { // access(W_OK) should never succeed ::perror("access"); return false; } #endif return true; } size_t num_hardlinks(fs::path const& p) { #ifdef _WIN32 auto stat = dwarfs::make_file_stat(p); return stat.nlink; #else return fs::hard_link_count(p); #endif } enum class binary_mode { standalone, universal_tool, universal_symlink, }; std::vector tools_test_modes{ binary_mode::standalone, binary_mode::universal_tool, binary_mode::universal_symlink, }; class tools_test : public ::testing::TestWithParam {}; } // namespace TEST_P(tools_test, end_to_end) { auto mode = GetParam(); std::chrono::seconds const timeout{5}; folly::test::TemporaryDirectory tempdir("dwarfs"); auto td = fs::path(tempdir.path().string()); auto image = td / "test.dwarfs"; auto image_hdr = td / "test_hdr.dwarfs"; auto fsdata_dir = td / "fsdata"; auto header_data = fsdata_dir / "format.sh"; auto universal_symlink_dwarfs_bin = td / "dwarfs" EXE_EXT; auto universal_symlink_mkdwarfs_bin = td / "mkdwarfs" EXE_EXT; auto universal_symlink_dwarfsck_bin = td / "dwarfsck" EXE_EXT; auto universal_symlink_dwarfsextract_bin = td / "dwarfsextract" EXE_EXT; std::vector mkdwarfs_tool_arg; std::vector dwarfsck_tool_arg; std::vector dwarfsextract_tool_arg; fs::path const* mkdwarfs_test_bin = &mkdwarfs_bin; fs::path const* dwarfsck_test_bin = &dwarfsck_bin; fs::path const* dwarfsextract_test_bin = &dwarfsextract_bin; if (mode == binary_mode::universal_symlink) { fs::create_symlink(universal_bin, universal_symlink_dwarfs_bin); fs::create_symlink(universal_bin, universal_symlink_mkdwarfs_bin); fs::create_symlink(universal_bin, universal_symlink_dwarfsck_bin); fs::create_symlink(universal_bin, universal_symlink_dwarfsextract_bin); } if (mode == binary_mode::universal_tool) { mkdwarfs_test_bin = &universal_bin; dwarfsck_test_bin = &universal_bin; dwarfsextract_test_bin = &universal_bin; mkdwarfs_tool_arg.push_back("--tool=mkdwarfs"); dwarfsck_tool_arg.push_back("--tool=dwarfsck"); dwarfsextract_tool_arg.push_back("--tool=dwarfsextract"); } ASSERT_TRUE(fs::create_directory(fsdata_dir)); ASSERT_TRUE(subprocess::check_run(*dwarfsextract_test_bin, dwarfsextract_tool_arg, "-i", test_data_dwarfs, "-o", fsdata_dir)); EXPECT_EQ(num_hardlinks(fsdata_dir / "format.sh"), 3); EXPECT_TRUE(fs::is_symlink(fsdata_dir / "foobar")); EXPECT_EQ(fs::read_symlink(fsdata_dir / "foobar"), fs::path("foo") / "bar"); auto unicode_symlink_name = u8"יוניקוד"; auto unicode_symlink = fsdata_dir / unicode_symlink_name; auto unicode_symlink_target = fs::path("unicode") / u8"我爱你" / u8"☀️ Sun" / u8"Γειά σας" / u8"مرحبًا" / u8"⚽️" / u8"Карибського"; std::string unicode_file_contents; EXPECT_TRUE(fs::is_symlink(unicode_symlink)); EXPECT_EQ(fs::read_symlink(unicode_symlink), unicode_symlink_target); EXPECT_TRUE(read_file(unicode_symlink, unicode_file_contents)); EXPECT_EQ(unicode_file_contents, "unicode\n"); EXPECT_TRUE( read_file(fsdata_dir / unicode_symlink_target, unicode_file_contents)); EXPECT_EQ(unicode_file_contents, "unicode\n"); ASSERT_TRUE(subprocess::check_run(*mkdwarfs_test_bin, mkdwarfs_tool_arg, "-i", fsdata_dir, "-o", image, "--no-progress")); ASSERT_TRUE(fs::exists(image)); ASSERT_GT(fs::file_size(image), 1000); ASSERT_TRUE(subprocess::check_run( *mkdwarfs_test_bin, mkdwarfs_tool_arg, "-i", image, "-o", image_hdr, "--no-progress", "--recompress=none", "--header", header_data)); ASSERT_TRUE(fs::exists(image_hdr)); ASSERT_GT(fs::file_size(image_hdr), 1000); auto mountpoint = td / "mnt"; auto extracted = td / "extracted"; auto untared = td / "untared"; std::vector drivers; switch (mode) { case binary_mode::standalone: drivers.push_back(fuse3_bin); if (fs::exists(fuse2_bin)) { drivers.push_back(fuse2_bin); } break; case binary_mode::universal_tool: drivers.push_back(universal_bin); break; case binary_mode::universal_symlink: drivers.push_back(universal_symlink_dwarfs_bin); break; } std::vector all_options{ "-s", #ifndef _WIN32 "-oenable_nlink", "-oreadonly", #endif "-omlock=try", "-ono_cache_image", "-ocache_files", }; unicode_symlink = mountpoint / unicode_symlink_name; for (auto const& driver : drivers) { { driver_runner runner(driver_runner::foreground, driver, mode == binary_mode::universal_tool, image, mountpoint); ASSERT_TRUE(wait_until_file_ready(mountpoint / "format.sh", timeout)) << runner.cmdline(); compare_directories_result cdr; ASSERT_TRUE(compare_directories(fsdata_dir, mountpoint, &cdr)) << runner.cmdline() << ": " << cdr; EXPECT_EQ(cdr.regular_files.size(), 26) << runner.cmdline() << ": " << cdr; EXPECT_EQ(cdr.directories.size(), 19) << runner.cmdline() << ": " << cdr; EXPECT_EQ(cdr.symlinks.size(), 2) << runner.cmdline() << ": " << cdr; EXPECT_EQ(1, num_hardlinks(mountpoint / "format.sh")) << runner.cmdline(); EXPECT_TRUE(fs::is_symlink(unicode_symlink)) << runner.cmdline(); EXPECT_EQ(fs::read_symlink(unicode_symlink), unicode_symlink_target) << runner.cmdline(); EXPECT_TRUE(read_file(unicode_symlink, unicode_file_contents)) << runner.cmdline(); EXPECT_EQ(unicode_file_contents, "unicode\n") << runner.cmdline(); EXPECT_TRUE( read_file(mountpoint / unicode_symlink_target, unicode_file_contents)) << runner.cmdline(); EXPECT_EQ(unicode_file_contents, "unicode\n") << runner.cmdline(); EXPECT_TRUE(runner.unmount()) << runner.cmdline(); } { auto const [out, err, ec] = subprocess::run( driver, driver_runner::make_tool_arg(mode == binary_mode::universal_tool), image_hdr, mountpoint); EXPECT_NE(0, ec) << driver << "\n" << "stdout:\n" << out << "\nstderr:\n" << err; } unsigned const combinations = 1 << all_options.size(); for (unsigned bitmask = 0; bitmask < combinations; ++bitmask) { std::vector args; #ifndef _WIN32 bool enable_nlink{false}; bool readonly{false}; for (size_t i = 0; i < all_options.size(); ++i) { if ((1 << i) & bitmask) { auto const& opt = all_options[i]; if (opt == "-oreadonly") { readonly = true; } if (opt == "-oenable_nlink") { enable_nlink = true; } args.push_back(opt); } } #endif { driver_runner runner(driver, mode == binary_mode::universal_tool, image, mountpoint, args); ASSERT_TRUE(wait_until_file_ready(mountpoint / "format.sh", timeout)) << runner.cmdline(); EXPECT_TRUE(fs::is_symlink(mountpoint / "foobar")) << runner.cmdline(); EXPECT_EQ(fs::read_symlink(mountpoint / "foobar"), fs::path("foo") / "bar") << runner.cmdline(); compare_directories_result cdr; ASSERT_TRUE(compare_directories(fsdata_dir, mountpoint, &cdr)) << runner.cmdline() << ": " << cdr; EXPECT_EQ(cdr.regular_files.size(), 26) << runner.cmdline() << ": " << cdr; EXPECT_EQ(cdr.directories.size(), 19) << runner.cmdline() << ": " << cdr; EXPECT_EQ(cdr.symlinks.size(), 2) << runner.cmdline() << ": " << cdr; #ifndef _WIN32 // TODO: https://github.com/winfsp/winfsp/issues/511 EXPECT_EQ(enable_nlink ? 3 : 1, num_hardlinks(mountpoint / "format.sh")) << runner.cmdline(); // This doesn't really work on Windows (yet) EXPECT_TRUE(check_readonly(mountpoint / "format.sh", readonly)) << runner.cmdline(); #endif } args.push_back("-ooffset=auto"); { driver_runner runner(driver, mode == binary_mode::universal_tool, image_hdr, mountpoint, args); ASSERT_TRUE(wait_until_file_ready(mountpoint / "format.sh", timeout)) << runner.cmdline(); EXPECT_TRUE(fs::is_symlink(mountpoint / "foobar")) << runner.cmdline(); EXPECT_EQ(fs::read_symlink(mountpoint / "foobar"), fs::path("foo") / "bar") << runner.cmdline(); compare_directories_result cdr; ASSERT_TRUE(compare_directories(fsdata_dir, mountpoint, &cdr)) << runner.cmdline() << ": " << cdr; EXPECT_EQ(cdr.regular_files.size(), 26) << runner.cmdline() << ": " << cdr; EXPECT_EQ(cdr.directories.size(), 19) << runner.cmdline() << ": " << cdr; EXPECT_EQ(cdr.symlinks.size(), 2) << runner.cmdline() << ": " << cdr; #ifndef _WIN32 // TODO: https://github.com/winfsp/winfsp/issues/511 EXPECT_EQ(enable_nlink ? 3 : 1, num_hardlinks(mountpoint / "format.sh")) << runner.cmdline(); // This doesn't really work on Windows (yet) EXPECT_TRUE(check_readonly(mountpoint / "format.sh", readonly)) << runner.cmdline(); #endif } } } auto meta_export = td / "test.meta"; ASSERT_TRUE( subprocess::check_run(*dwarfsck_test_bin, dwarfsck_tool_arg, image)); ASSERT_TRUE(subprocess::check_run(*dwarfsck_test_bin, dwarfsck_tool_arg, image, "--check-integrity")); ASSERT_TRUE(subprocess::check_run(*dwarfsck_test_bin, dwarfsck_tool_arg, image, "--export-metadata", meta_export)); { std::string header; EXPECT_TRUE(read_file(header_data, header)); auto output = subprocess::check_run(*dwarfsck_test_bin, dwarfsck_tool_arg, image_hdr, "-H"); ASSERT_TRUE(output); EXPECT_EQ(header, *output); } EXPECT_GT(fs::file_size(meta_export), 1000); ASSERT_TRUE(fs::create_directory(extracted)); ASSERT_TRUE(subprocess::check_run(*dwarfsextract_test_bin, dwarfsextract_tool_arg, "-i", image, "-o", extracted)); EXPECT_EQ(3, num_hardlinks(extracted / "format.sh")); EXPECT_TRUE(fs::is_symlink(extracted / "foobar")); EXPECT_EQ(fs::read_symlink(extracted / "foobar"), fs::path("foo") / "bar"); compare_directories_result cdr; ASSERT_TRUE(compare_directories(fsdata_dir, extracted, &cdr)) << cdr; EXPECT_EQ(cdr.regular_files.size(), 26) << cdr; EXPECT_EQ(cdr.directories.size(), 19) << cdr; EXPECT_EQ(cdr.symlinks.size(), 2) << cdr; } #ifdef _WIN32 #define EXPECT_EC_UNIX_WIN(ec, unix, windows) \ EXPECT_TRUE(ec) << runner.cmdline(); \ EXPECT_EQ(std::system_category(), (ec).category()) << runner.cmdline(); \ EXPECT_EQ(windows, (ec).value()) << runner.cmdline() << ": " << (ec).message() #else #define EXPECT_EC_UNIX_WIN(ec, unix, windows) \ EXPECT_TRUE(ec) << runner.cmdline(); \ EXPECT_EQ(std::generic_category(), (ec).category()) << runner.cmdline(); \ EXPECT_EQ(unix, (ec).value()) << runner.cmdline() << ": " << (ec).message() #endif TEST_P(tools_test, mutating_ops) { auto mode = GetParam(); std::chrono::seconds const timeout{5}; folly::test::TemporaryDirectory tempdir("dwarfs"); auto td = fs::path(tempdir.path().string()); auto mountpoint = td / "mnt"; auto file = mountpoint / "bench.sh"; auto empty_dir = mountpoint / "empty"; auto non_empty_dir = mountpoint / "foo"; auto name_inside_fs = mountpoint / "some_random_name"; auto name_outside_fs = td / "some_random_name"; auto universal_symlink_dwarfs_bin = td / "dwarfs" EXE_EXT; if (mode == binary_mode::universal_symlink) { fs::create_symlink(universal_bin, universal_symlink_dwarfs_bin); } std::vector drivers; switch (mode) { case binary_mode::standalone: drivers.push_back(fuse3_bin); if (fs::exists(fuse2_bin)) { drivers.push_back(fuse2_bin); } break; case binary_mode::universal_tool: drivers.push_back(universal_bin); break; case binary_mode::universal_symlink: drivers.push_back(universal_symlink_dwarfs_bin); break; } for (auto const& driver : drivers) { driver_runner runner(driver_runner::foreground, driver, mode == binary_mode::universal_tool, test_data_dwarfs, mountpoint); ASSERT_TRUE(wait_until_file_ready(mountpoint / "format.sh", timeout)) << runner.cmdline(); // remove (unlink) { std::error_code ec; EXPECT_FALSE(fs::remove(file, ec)) << runner.cmdline(); EXPECT_EC_UNIX_WIN(ec, ENOSYS, ERROR_ACCESS_DENIED); } { std::error_code ec; EXPECT_FALSE(fs::remove(empty_dir, ec)) << runner.cmdline(); EXPECT_EC_UNIX_WIN(ec, ENOSYS, ERROR_ACCESS_DENIED); } { std::error_code ec; EXPECT_FALSE(fs::remove(non_empty_dir, ec)) << runner.cmdline(); EXPECT_EC_UNIX_WIN(ec, ENOSYS, ERROR_ACCESS_DENIED); } { std::error_code ec; EXPECT_EQ(static_cast(-1), fs::remove_all(non_empty_dir, ec)) << runner.cmdline(); EXPECT_EC_UNIX_WIN(ec, ENOSYS, ERROR_ACCESS_DENIED); } // rename { std::error_code ec; fs::rename(file, name_inside_fs, ec); EXPECT_EC_UNIX_WIN(ec, ENOSYS, ERROR_ACCESS_DENIED); } { std::error_code ec; fs::rename(file, name_outside_fs, ec); EXPECT_EC_UNIX_WIN(ec, EXDEV, ERROR_ACCESS_DENIED); } { std::error_code ec; fs::rename(empty_dir, name_inside_fs, ec); EXPECT_EC_UNIX_WIN(ec, ENOSYS, ERROR_ACCESS_DENIED); } { std::error_code ec; fs::rename(empty_dir, name_outside_fs, ec); EXPECT_EC_UNIX_WIN(ec, EXDEV, ERROR_ACCESS_DENIED); } // hard link { std::error_code ec; fs::create_hard_link(file, name_inside_fs, ec); EXPECT_EC_UNIX_WIN(ec, ENOSYS, ERROR_ACCESS_DENIED); } { std::error_code ec; fs::create_hard_link(file, name_outside_fs, ec); EXPECT_EC_UNIX_WIN(ec, EXDEV, ERROR_ACCESS_DENIED); } // symbolic link { std::error_code ec; fs::create_symlink(file, name_inside_fs, ec); EXPECT_EC_UNIX_WIN(ec, ENOSYS, ERROR_ACCESS_DENIED); } { std::error_code ec; fs::create_symlink(file, name_outside_fs, ec); EXPECT_FALSE(ec) << runner.cmdline(); // this actually works :) EXPECT_TRUE(fs::remove(name_outside_fs, ec)) << runner.cmdline(); } { std::error_code ec; fs::create_directory_symlink(empty_dir, name_inside_fs, ec); EXPECT_EC_UNIX_WIN(ec, ENOSYS, ERROR_ACCESS_DENIED); } { std::error_code ec; fs::create_directory_symlink(empty_dir, name_outside_fs, ec); EXPECT_FALSE(ec) << runner.cmdline(); // this actually works :) EXPECT_TRUE(fs::remove(name_outside_fs, ec)) << runner.cmdline(); } // truncate { std::error_code ec; fs::resize_file(file, 1, ec); EXPECT_EC_UNIX_WIN(ec, ENOSYS, ERROR_ACCESS_DENIED); } // create directory { std::error_code ec; fs::create_directory(name_inside_fs, ec); EXPECT_EC_UNIX_WIN(ec, ENOSYS, ERROR_ACCESS_DENIED); } EXPECT_TRUE(runner.unmount()) << runner.cmdline(); } } INSTANTIATE_TEST_SUITE_P(dwarfs, tools_test, ::testing::ValuesIn(tools_test_modes));