diff options
| -rw-r--r-- | liblfjail/Makefile | 1 | ||||
| -rw-r--r-- | liblfjail/config_store.cc | 4 | ||||
| -rw-r--r-- | liblfjail/context.cc | 17 | ||||
| -rw-r--r-- | liblfjail/context.hh | 6 | ||||
| -rw-r--r-- | liblfjail/fileutils.cc | 25 | ||||
| -rw-r--r-- | liblfjail/fileutils.hh | 40 | ||||
| -rw-r--r-- | liblfjail/jail.cc | 8 | ||||
| -rw-r--r-- | liblfjail/jail_zfs.cc | 103 | ||||
| -rw-r--r-- | liblfjail/words.hh | 94 | ||||
| -rw-r--r-- | liblfjail/zfs.cc | 220 | ||||
| -rw-r--r-- | liblfjail/zfs.hh | 299 | ||||
| -rw-r--r-- | tests/words.cc | 36 |
12 files changed, 620 insertions, 233 deletions
diff --git a/liblfjail/Makefile b/liblfjail/Makefile index 209f873..b3efd69 100644 --- a/liblfjail/Makefile +++ b/liblfjail/Makefile @@ -7,7 +7,6 @@ SRCS= argv.cc \ config_store.cc \ config_string.cc \ config_value.cc \ - context.cc \ exec.cc \ fd.cc \ fileutils.cc \ diff --git a/liblfjail/config_store.cc b/liblfjail/config_store.cc index ae873ea..84ef0ae 100644 --- a/liblfjail/config_store.cc +++ b/liblfjail/config_store.cc @@ -31,7 +31,7 @@ void store::init(context const &ctx) { std::string config_text; // Load the configuration text. - auto config_path = ctx.path("config.ucl"); + auto config_path = ctx.dbdir / "config.ucl"; try { read_file(config_path, std::back_inserter(config_text)); } catch (io_error const &exc) { @@ -140,7 +140,7 @@ void store::write_all(this store const &self, context const &ctx) { std::string ucl_text(ucl_c_text); // Write the object to a file. - auto config_path = ctx.path("config.ucl"); + auto config_path = ctx.dbdir / "config.ucl"; try { safe_write_file(config_path, ucl_text); diff --git a/liblfjail/context.cc b/liblfjail/context.cc deleted file mode 100644 index 8445c97..0000000 --- a/liblfjail/context.cc +++ /dev/null @@ -1,17 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include <format> - -#include "context.hh" - -namespace lfjail { - -auto context::path(this context const &self, std::string_view what) - -> std::string -{ - return std::format("{0}/{1}", self.dbdir, what); -} - -} // namespace lfjail diff --git a/liblfjail/context.hh b/liblfjail/context.hh index d7aca5e..5eaed3b 100644 --- a/liblfjail/context.hh +++ b/liblfjail/context.hh @@ -5,6 +5,7 @@ #ifndef LFJAIL_CONTEXT_HH #define LFJAIL_CONTEXT_HH +#include <filesystem> #include <string> namespace lfjail { @@ -14,13 +15,10 @@ namespace lfjail { */ struct context { // Path to the database directory (-d). - std::string dbdir = LFJAIL_DBDIR; + std::filesystem::path dbdir = LFJAIL_DBDIR; // Print more detailed messages (-v). bool verbose = false; - - // Return the path for a particular file, relative to dbdir. - auto path(this context const &, std::string_view what) -> std::string; }; } // namespace lfjail diff --git a/liblfjail/fileutils.cc b/liblfjail/fileutils.cc index b73e05f..c4d81ad 100644 --- a/liblfjail/fileutils.cc +++ b/liblfjail/fileutils.cc @@ -14,27 +14,18 @@ namespace lfjail { -void ensure_dir(context const &ctx, std::string_view dir) { - auto const cdir = std::string(dir); +void ensure_dir(context const &ctx, std::filesystem::path const &dir) { + std::error_code err; - struct ::stat st{}; - if (auto const err = ::stat(cdir.c_str(), &st); err == 0) { - if (!S_ISDIR(st.st_mode)) - throw generic_error("{}: already exists", dir); + if (std::filesystem::create_directories(dir)) { + if (ctx.verbose) + std::print(std::cerr, "{}: created {}\n", + getprogname(), dir); return; } - if (errno != ENOENT) - throw generic_error("{}: stat: {}", - dir, ::strerror(errno)); - - if (auto const err = ::mkdir(cdir.c_str(), 0700); err != 0) - throw generic_error("{}: mkdir: {}", - dir, ::strerror(errno)); - - if (ctx.verbose) - std::print(std::cerr, "{}: created {}\n", - getprogname(), dir); + if (err) + throw generic_error("{}: mkdir: {}", dir, err.message()); } } // namespace lfjail diff --git a/liblfjail/fileutils.hh b/liblfjail/fileutils.hh index 0824796..0f7c1a7 100644 --- a/liblfjail/fileutils.hh +++ b/liblfjail/fileutils.hh @@ -7,6 +7,8 @@ #include <array> #include <iterator> +#include <filesystem> +#include <format> #include <ranges> #include <span> #include <string> @@ -19,16 +21,37 @@ #include "io_error.hh" #include "guard.hh" +/* + * std::formatter for path was only added in C++26; LLVM 19 doesn't have it. + */ + +#ifndef __cpp_lib_format_path +template<> +struct std::formatter<std::filesystem::path, char> +{ + template<typename ParseContext> + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { + return ctx.begin(); + } + + template<typename FmtContext> + auto format(std::filesystem::path const &path, + FmtContext& ctx) const -> FmtContext::iterator { + return std::ranges::copy(path.native(), ctx.out()).out; + } +}; +#endif // !__cpp_lib_format_path + namespace lfjail { -void ensure_dir(context const &ctx, std::string_view dir); +void ensure_dir(context const &ctx, std::filesystem::path const &dir); /* * Load the contents of a file into an output iterator. Throws io_error * on failure. */ -void read_file(std::string filename, +void read_file(std::filesystem::path const &filename, std::output_iterator<char> auto &&iter) { constexpr std::size_t bufsize = 1024; @@ -54,7 +77,8 @@ void read_file(std::string filename, * Write the contents of a range to a file. Throws io_error on error. */ -void write_file(std::string filename, std::ranges::range auto &&range) { +void write_file(std::filesystem::path const &filename, + std::ranges::range auto &&range) { // Convert the range into an array. std::vector<char> chars; std::ranges::copy(range, std::back_inserter(chars)); @@ -78,11 +102,15 @@ void write_file(std::string filename, std::ranges::range auto &&range) { * renamed to the target filename. If an error occurs, the target file will * not be modified. */ -void safe_write_file(std::string filename, std::ranges::range auto &&range) { - auto tmpfile = filename + ".tmp"; +void safe_write_file(std::filesystem::path const &filename, + std::ranges::range auto &&range) { + auto tmpfile(filename); + tmpfile.remove_filename(); + tmpfile /= (filename.filename().native() + ".tmp"); + auto tmpfile_guard = guard([tmpfile] { ::unlink(tmpfile.c_str()); }); - write_file(tmpfile, range); + write_file(tmpfile, std::forward<decltype(range)>(range)); int err = ::rename(tmpfile.c_str(), filename.c_str()); if (err != 0) throw io_error(filename, errno); diff --git a/liblfjail/jail.cc b/liblfjail/jail.cc index cf5d600..ca94c18 100644 --- a/liblfjail/jail.cc +++ b/liblfjail/jail.cc @@ -14,16 +14,16 @@ namespace { using namespace lfjail; // Get the jails storage directory. -auto get_jails_dir(context const &ctx) -> std::string { - return ctx.path("jails"); +auto get_jails_dir(context const &ctx) -> std::filesystem::path { + return ctx.dbdir / "jails"; } // Get the config file for a jail. auto jail_config_file(context const &ctx, std::string_view jailname) - -> std::string + -> std::filesystem::path { - return std::format("{}/{}.ucl", get_jails_dir(ctx), jailname); + return get_jails_dir(ctx) / jailname; } // Make sure our jails config path exists. diff --git a/liblfjail/jail_zfs.cc b/liblfjail/jail_zfs.cc index b8d58cc..07b0169 100644 --- a/liblfjail/jail_zfs.cc +++ b/liblfjail/jail_zfs.cc @@ -10,6 +10,7 @@ #include "exec.hh" #include "generic_error.hh" #include "jail_zfs.hh" +#include "zfs.hh" using namespace lfjail; using namespace std::literals; @@ -29,111 +30,17 @@ config::string_option mountpoint( namespace { /* - * Test if the given ZFS dataset exists. - */ -auto dataset_exists(std::string const &/*ds*/) -> bool { - throw("not implemented"); -#if 0 - auto sout = std::string(); - auto serr = std::string(); - - auto const ret = lfjail::cexecl( - std::back_inserter(sout), - std::back_inserter(serr), - "/sbin/zfs", - "zfs", "get", "-H", "-ovalue", "name", ds); - - return (ret == 0); -#endif -} - -/* - * Create a new ZFS dataset. - */ -template<typename... Args> -void dataset_create(std::string const &/*ds*/, Args&& .../*args*/) { - throw("not implemented"); -#if 0 - std::vector<std::string> argv; - argv.emplace_back("zfs"); - argv.emplace_back("create"); - (argv.emplace_back(std::format("-o{}", std::forward<Args>(args))), ...); - argv.emplace_back(ds); - - auto sout = std::string(); - auto serr = std::string(); - - auto const ret = lfjail::cexec(std::back_inserter(sout), - std::back_inserter(serr), - "/sbin/zfs", argv); - - if (!serr.empty()) - throw generic_error("zfs create failed: {}", serr); - if (ret != 0) - throw generic_error("zfs create failed: {}", ret); -#endif -} - -/* - * Destroy a ZFS datset. - */ -void dataset_destroy(std::string const &/*ds*/) { - throw("not implemented"); -#if 0 - auto sout = std::string(); - auto serr = std::string(); - - int ret = lfjail::cexecl(std::back_inserter(sout), - std::back_inserter(serr), - "/sbin/zfs", - "zfs", "destroy", "-r", ds); - - if (!serr.empty()) - throw generic_error("zfs destroy failed: {}", serr); - if (ret != 0) - throw generic_error("zfs destroy failed: {}", ret); -#endif -} - -/* - * Return a ZFS properly for a fileystem. - */ -auto get_property(std::string const &/*fs*/, std::string const &/*prop*/) - -> std::string -{ - throw("not implemented"); -#if 0 - auto sout = std::string(); - auto serr = std::string(); - - int ret = lfjail::cexecl( - std::back_inserter(sout), - std::back_inserter(serr), - "/sbin/zfs", - "zfs", "get", "-H", "-ovalue", prop, fs); - - if (!serr.empty()) - throw generic_error("zfs get failed: {}", serr); - if (ret != 0) - throw generic_error("zfs get failed: {}", ret); - if (sout[sout.size() - 1] == '\n') - sout.resize(sout.size() - 1); - return sout; -#endif -} - -/* * Make sure our top-level ZFS dataset exists. */ void ensure_jroot(context const &) { auto jroot = lfjail::zfs::filesystem.string(); auto mntpt = lfjail::zfs::mountpoint.string(); - if (dataset_exists(jroot)) + if (zfs::dataset_exists(jroot)) return; auto mntptopt = std::format("mountpoint={}", mntpt); - dataset_create(jroot, mntptopt); + zfs::create_dataset(jroot, mntptopt); } } // anonymous namespace @@ -150,7 +57,7 @@ void create_for_jail(context const &ctx, jail const &jailconf) { ensure_jroot(ctx); auto const ds = filesystem.string() + "/" + jailconf.name; - dataset_create(ds); + zfs::create_dataset(ds); } void destroy_for_jail(context const &, jail const &jailconf) { @@ -165,7 +72,7 @@ auto jail_root(context const &ctx) -> std::string { if (fs.empty()) throw generic_error("zfs.filesystem not set"); - return get_property(fs, "mountpoing"); + return ""; // fs.mountpoint } } // namespace lfjail::zfs diff --git a/liblfjail/words.hh b/liblfjail/words.hh index a9f7114..4b1209b 100644 --- a/liblfjail/words.hh +++ b/liblfjail/words.hh @@ -17,15 +17,13 @@ namespace lfjail { * words: take a string-like object and split it into words using the given * predicate. Empty values are discarded (i.e., repeated separators are * ignored). - * - * words() returns std::strings, while wordsv() return std::string_views - * which refer to the original string. */ namespace detail { -template<typename R, typename Pred> -auto split(std::string_view text, Pred &&pred) -> std::generator<R> +template<std::ranges::range Range, + std::indirect_unary_predicate<std::ranges::iterator_t<Range>> Pred> +auto split(Range &&text, Pred pred) -> std::generator<std::string> { auto pos = std::ranges::begin(text); auto end = std::ranges::end(text); @@ -42,86 +40,62 @@ auto split(std::string_view text, Pred &&pred) -> std::generator<R> auto split_pos = std::find_if(pos, end, pred); // Yield this word. - co_yield R(pos, split_pos); + co_yield std::string(pos, split_pos); pos = split_pos; } } +template<typename Pred> +struct words_fn : std::ranges::range_adaptor_closure<words_fn<Pred>> { + words_fn(Pred p) : _pred(std::move(p)) {} -template<typename ValueType, - std::ranges::viewable_range Range, - std::indirect_unary_predicate<std::ranges::iterator_t<Range>> Pred> -requires (std::ranges::contiguous_range<Range>) -struct words_view : - std::ranges::view_interface<words_view<ValueType, Range, Pred>> -{ - words_view(auto &&base, auto &&pred) - : _base(std::forward<decltype(base)>(base)) - , _pred(std::forward<decltype(pred)>(pred)) - , _generator(detail::split<ValueType>( - std::string_view(_base), _pred)) - {} - - auto begin(this auto const &self) { - return self._generator.begin(); - } - - auto end(this auto const &self) { - return self._generator.end(); + template<std::ranges::range Range> + constexpr auto operator()(Range &&range) const { + return detail::split(std::forward<Range>(range), + std::move(_pred)); } private: - Range _base; Pred _pred; - - mutable std::generator<ValueType> _generator; -}; - -template<typename R> -struct words_impl : std::ranges::range_adaptor_closure<words_impl<R>> { - template<std::ranges::range Range, typename Pred> - constexpr auto operator()(Range &&range, Pred &&pred) const { - return words_view<R, Range, Pred>( - std::forward<decltype(range)>(range), - std::forward<decltype(pred)>(pred)); - } }; } // namespace detail template<std::ranges::range Range, std::indirect_unary_predicate<std::ranges::iterator_t<Range>> Pred> -auto words(Range &&range, Pred &&pred) - requires (std::ranges::borrowed_range<Range>) -{ - return detail::words_impl<std::string_view>{}( - std::forward<Range>(range), - std::forward<Pred>(pred)); -} - -template<std::ranges::range Range, - std::indirect_unary_predicate<std::ranges::iterator_t<Range>> Pred> -auto words(Range &&range, Pred &&pred) - requires (!std::ranges::borrowed_range<Range>) +auto words(Range &&range, Pred pred) { - return detail::words_impl<std::string>{}( - std::forward<Range>(range), - std::forward<Pred>(pred)); + return detail::split(std::forward<Range>(range), std::move(pred)); } -template<std::ranges::range Range> -auto words(Range &&range, std::ranges::range_value_t<Range> ch) -{ +template<std::ranges::viewable_range Range, std::integral Char> +auto words(Range &&range, Char ch) { return words(std::forward<Range>(range), [ch](auto c) { return c == ch; }); } -template<std::ranges::range Range> -auto words(Range &&range) -{ +template<std::ranges::viewable_range Range> +auto words(Range &&range) { return words(std::forward<Range>(range), is_c_space); } +template<typename Pred> +requires(!std::integral<Pred> && !std::ranges::range<Pred>) +auto words(Pred pred) { + return detail::words_fn(std::move(pred)); +} + +auto words() { + return words(is_c_space); +} + +template<std::integral Char> +auto words(Char ch) { + return words([ch](auto c) { return c == ch; }); +} + +//constexpr words_impl words{}; + } // namespace lfjail #endif // LIBLFJAIL_WORDS_HH diff --git a/liblfjail/zfs.cc b/liblfjail/zfs.cc index bc0caee..5eaf655 100644 --- a/liblfjail/zfs.cc +++ b/liblfjail/zfs.cc @@ -9,6 +9,7 @@ * on FreeBSD, e.g. https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=257222. */ +#include <functional> #include <unordered_map> #include <unistd.h> @@ -24,16 +25,12 @@ namespace lfjail::zfs { namespace { -// Hardcode the program locations so we don't rely on $PATH. -constexpr std::string path_zpool = "/sbin/zpool"; -constexpr std::string path_zfs = "/sbin/zfs"; - /* * Run a command and split each line of output into whitespace-separated * columns. */ auto columns(exec::executor auto &&executor) - -> std::generator<std::vector<std::string_view>> + -> std::generator<std::vector<std::string>> { std::string output; @@ -69,17 +66,146 @@ auto set_values(auto &&map, std::string command, auto &&...args) -> void } } +template<typename T> +struct set_simple; + +template<typename T> +set_simple(T *) -> set_simple<T>; + +template<> +struct set_simple<bool> { + set_simple(bool *valuep_) : valuep(valuep_) {} + + bool *valuep; + + auto operator() (std::string_view newvalue) -> void { + if (newvalue == "on") + *valuep = true; + else if (newvalue == "off") + *valuep = false; + else + throw zfs_error("unexpected boolean value: {}", + newvalue); + } +}; + +template<> +struct set_simple<std::string> { + set_simple(std::string *valuep_) : valuep(valuep_) {} + + std::string *valuep; + + auto operator() (std::string_view newvalue) -> void { + *valuep = newvalue; + } +}; + +template<> +struct set_simple<std::optional<std::string>> { + set_simple(std::optional<std::string> *valuep_) : valuep(valuep_) {} + + std::optional<std::string> *valuep; + + auto operator() (std::string_view newvalue) -> void { + if (newvalue != "-") + *valuep = newvalue; + } +}; + + +template<> +struct set_simple<std::uint64_t> { + set_simple(std::uint64_t *valuep_) : valuep(valuep_) {} + + std::uint64_t *valuep; + + auto operator() (std::string_view newvalue) -> void { + // XXX + std::string cvalue(newvalue); + *valuep = strtoull(cvalue.c_str(), NULL, 10); + } +}; + +template<> +struct set_simple<unsigned> { + set_simple(unsigned *valuep_) : valuep(valuep_) {} + + unsigned *valuep; + + auto operator() (std::string_view newvalue) -> void { + // XXX + std::string cvalue(newvalue); + *valuep = std::strtoul(cvalue.c_str(), NULL, 10); + } +}; + +template<> +struct set_simple<double> { + set_simple(double *valuep_) : valuep(valuep_) {} + + double *valuep; + + auto operator() (std::string_view newvalue) -> void { + // XXX + std::string cvalue(newvalue); + *valuep = std::atof(cvalue.c_str()); + } +}; + +template<typename Tag> +struct set_simple<enumeration<Tag>> { + set_simple(enumeration<Tag> *valuep_) : valuep(valuep_) {} + + enumeration<Tag> *valuep; + + auto operator() (std::string_view newvalue) -> void { + valuep->value = newvalue; + } +}; + } // anonymous namespace auto get_pool(std::string_view name) -> pool { auto ret = pool{}; - - auto handlers = std::unordered_map{ - std::pair{"name"sv, [&ret](auto &&v) { ret.name = v; }}, + ret.name = name; + + static const auto handlers = std::unordered_map<std::string_view, + std::function<void (std::string_view)>>{ + {"guid"sv, set_simple(&ret.guid)}, + {"load_guid"sv, set_simple(&ret.load_guid)}, + {"size"sv, set_simple(&ret.size)}, + {"free"sv, set_simple(&ret.free)}, + {"allocated"sv, set_simple(&ret.allocated)}, + {"altroot"sv, set_simple(&ret.altroot)}, + {"health"sv, set_simple(&ret.health)}, + {"bootfs"sv, set_simple(&ret.bootfs)}, + {"delegation"sv, set_simple(&ret.delegation)}, + {"autoreplace"sv, set_simple(&ret.autoreplace)}, + {"cachefile"sv, set_simple(&ret.cachefile)}, + {"failmode"sv, set_simple(&ret.failmode)}, + {"listsnapshots"sv, set_simple(&ret.listsnapshots)}, + {"autoexpand"sv, set_simple(&ret.autoexpand)}, + {"readonly"sv, set_simple(&ret.readonly)}, + {"ashift"sv, set_simple(&ret.ashift)}, + {"comment"sv, set_simple(&ret.comment)}, + {"expandsize"sv, set_simple(&ret.expandsize)}, + {"freeing"sv, set_simple(&ret.freeing)}, + {"fragmentation"sv, set_simple(&ret.fragmentation)}, + {"leaked"sv, set_simple(&ret.leaked)}, + {"multihost"sv, set_simple(&ret.multihost)}, + {"checkpoint"sv, set_simple(&ret.checkpoint)}, + {"autotrim"sv, set_simple(&ret.autotrim)}, + {"compatibility"sv, set_simple(&ret.compatibility)}, + {"bcloneused"sv, set_simple(&ret.bcloneused)}, + {"bclonesaved"sv, set_simple(&ret.bclonesaved)}, + {"bcloneratio"sv, set_simple(&ret.bcloneratio)}, + {"dedup_table_size"sv, set_simple(&ret.dedup_table_size)}, + {"dedupratio"sv, set_simple(&ret.dedupratio)}, + {"last_scrubbed_txg"sv, set_simple(&ret.last_scrubbed_txg)}, }; set_values(handlers, path_zpool, - "zpool", "get", "-H", "-oproperty,value", "all", name); + "zpool", "get", "-Hp", "-oproperty,value", "all", name); return ret; } @@ -94,13 +220,63 @@ auto get_pools() -> std::generator<pool> { auto get_dataset(std::string_view name) -> dataset { auto ret = dataset{}; - - auto handlers = std::unordered_map{ - std::pair{"name"sv, [&ret](auto &&v) { ret.name = v; }}, + ret.name = name; + + static const auto handlers = std::unordered_map<std::string_view, + std::function<void (std::string_view)>>{ + {"type"sv, set_simple(&ret.type)}, + {"creation"sv, set_simple(&ret.creation)}, + {"used"sv, set_simple(&ret.used)}, + {"available"sv, set_simple(&ret.available)}, + {"referenced"sv, set_simple(&ret.referenced)}, + {"compressratio"sv, set_simple(&ret.compressratio)}, + {"mounted"sv, set_simple(&ret.mounted)}, + {"quota"sv, set_simple(&ret.quota)}, + {"reservation"sv, set_simple(&ret.reservation)}, + {"recordsize"sv, set_simple(&ret.recordsize)}, + {"sharenfs"sv, set_simple(&ret.sharenfs)}, + {"checksum"sv, set_simple(&ret.checksum)}, + {"compression"sv, set_simple(&ret.compression)}, + {"atime"sv, set_simple(&ret.atime)}, + {"devices"sv, set_simple(&ret.devices)}, + {"exec"sv, set_simple(&ret.exec)}, + {"setuid"sv, set_simple(&ret.setuid)}, + {"readonly"sv, set_simple(&ret.readonly)}, + {"jailed"sv, set_simple(&ret.jailed)}, + {"snapdir"sv, set_simple(&ret.snapdir)}, + {"aclmode"sv, set_simple(&ret.aclmode)}, + {"aclinherit"sv, set_simple(&ret.aclinherit)}, + {"createtxg"sv, set_simple(&ret.createtxg)}, + {"canmount"sv, set_simple(&ret.canmount)}, + {"xattr"sv, set_simple(&ret.xattr)}, + {"copies"sv, set_simple(&ret.copies)}, + {"version"sv, set_simple(&ret.version)}, + {"utf8only"sv, set_simple(&ret.utf8only)}, + {"normalization"sv, set_simple(&ret.normalization)}, + {"vscan"sv, set_simple(&ret.vscan)}, + {"nbmand"sv, set_simple(&ret.nbmand)}, + {"sharesmb"sv, set_simple(&ret.sharesmb)}, + {"refquota"sv, set_simple(&ret.refquota)}, + {"refreservation"sv, set_simple(&ret.refreservation)}, + {"guid"sv, set_simple(&ret.guid)}, + {"primarycache"sv, set_simple(&ret.primarycache)}, + {"secondarycache"sv, set_simple(&ret.secondarycache)}, + {"usedbysnapshots"sv, set_simple(&ret.usedbysnapshots)}, + {"usedbydataset"sv, set_simple(&ret.usedbydataset)}, + {"usedbychildren"sv, set_simple(&ret.usedbychildren)}, + {"usedbyrefreservation"sv, + set_simple(&ret.usedbyrefreservation)}, + {"logbias"sv, set_simple(&ret.logbias)}, + {"objsetid"sv, set_simple(&ret.objsetid)}, + {"dedup"sv, set_simple(&ret.dedup)}, + {"mlslabel"sv, set_simple(&ret.mlslabel)}, + {"sync"sv, set_simple(&ret.sync)}, + {"dnodesize"sv, set_simple(&ret.dnodesize)}, + {"refcompressratio"sv, set_simple(&ret.refcompressratio)}, }; - set_values(handlers, path_zfs, "zfs", "get", "-H", - "-oproperty,value"sv, "all", name); + set_values(handlers, path_zfs, "zfs", "get", "-Hp", + "-oproperty,value", "all", name); return ret; } @@ -122,4 +298,20 @@ auto dataset_exists(std::string name) -> bool { return std::ranges::contains(get_dataset_names(), name); } +/* + * Create a new ZFS dataset. + */ +auto destroy_dataset(std::string const &dsname) -> void { + using namespace std::literals; + + try { + auto ret = spawn(exec::execl(path_zfs, + "zfs", "destroy", "-r", "--", dsname)).wait(); + if (!ret) + throw zfs_error("failed to destroy dataset"); + } catch (std::exception const &exc) { + throw zfs_error("failed to destroy dataset: {}", exc.what()); + } +} + } // namespace lfjail::zfs diff --git a/liblfjail/zfs.hh b/liblfjail/zfs.hh index 915e042..34ddb06 100644 --- a/liblfjail/zfs.hh +++ b/liblfjail/zfs.hh @@ -7,38 +7,236 @@ #include "generator.hh" #include "generic_error.hh" +#include "spawn.hh" namespace lfjail::zfs { +// Hardcode the program locations so we don't rely on $PATH. +constexpr std::string path_zpool = "/sbin/zpool"; +constexpr std::string path_zfs = "/sbin/zfs"; + /* * Exception thrown when a ZFS operation fails. */ -struct zfs_error final : generic_error { - zfs_error(std::string what_) - : generic_error("{}", std::move(what_)) +struct zfs_error : generic_error { + template<typename... Args> + zfs_error(std::format_string<Args...> fmt, Args &&...args) + : generic_error(fmt, std::forward<Args>(args)...) {} }; /* - * Pools. + * Data type for an enumeration value. */ -struct pool { - std::string name; +template<typename Tag> +struct enumeration_value final { + const std::string_view value; }; -/* Fetch details for a specific pool by name. */ -auto get_pool(std::string_view name) -> pool; +template<typename Tag> +struct enumeration final : Tag { + enumeration() = default; -/* Fetch details for all pools on the system. */ -auto get_pools() -> std::generator<pool>; + enumeration(enumeration_value<Tag> value_) + : value(value_.value) + {} + + enumeration(enumeration const &) = default; + enumeration(enumeration &&) noexcept = default; + + auto operator=(enumeration const &) -> enumeration& = default; + auto operator=(enumeration &&) noexcept -> enumeration& = default; + + auto operator=(enumeration_value<Tag> newvalue) -> enumeration & { + value = newvalue.value; + return *this; + } + + std::string_view value; +}; + +template<typename Tag> +auto operator==(enumeration<Tag> const &lhs, + enumeration_value<Tag> const &rhs) + -> bool +{ + return lhs.value == rhs.value; +} + +struct failmode { + enumeration_value<failmode> continue_{"continue"}; + enumeration_value<failmode> panic{"panic"}; + enumeration_value<failmode> wait{"wait"}; +}; + +using failmode_t = enumeration<failmode>; + +struct dataset_type { + enumeration_value<dataset_type> filesystem{"filesystem"}; + enumeration_value<dataset_type> volume{"volume"}; + enumeration_value<dataset_type> snapshot{"snapshot"}; + enumeration_value<dataset_type> bookmark{"bookmark"}; +}; + +using dataset_type_t = enumeration<dataset_type>; + +struct snapdir { + enumeration_value<snapdir> disabled{"disabled"}; + enumeration_value<snapdir> hidden{"hidden"}; + enumeration_value<snapdir> visible{"visible"}; +}; + +using snapdir_t = enumeration<snapdir>; + +struct aclmode { + enumeration_value<aclmode> discard{"discard"}; + enumeration_value<aclmode> groupmask{"groupmask"}; + enumeration_value<aclmode> passthrough{"passthrough"}; + enumeration_value<aclmode> restricted{"restricted"}; +}; + +using aclmode_t = enumeration<aclmode>; + +struct aclinherit { + enumeration_value<aclinherit> discard{"discard"}; + enumeration_value<aclinherit> noallow{"noallow"}; + enumeration_value<aclinherit> restricted{"restricted"}; + enumeration_value<aclinherit> passthrough{"passthrough"}; + enumeration_value<aclinherit> passthrough_x{"passthrough-x"}; +}; + +using aclinherit_t = enumeration<aclinherit>; + +struct canmount { + enumeration_value<canmount> on{"on"}; + enumeration_value<canmount> off{"off"}; + enumeration_value<canmount> noauto{"noauto"}; +}; + +using canmount_t = enumeration<canmount>; + +struct xattr { + enumeration_value<xattr> on{"on"}; + enumeration_value<xattr> off{"off"}; + enumeration_value<xattr> dir{"dir"}; + enumeration_value<xattr> sa{"dir"}; +}; + +using xattr_t = enumeration<xattr>; + +struct normalization { + enumeration_value<normalization> none{"none"}; + enumeration_value<normalization> formC{"formC"}; + enumeration_value<normalization> formD{"formD"}; + enumeration_value<normalization> formKC{"formKC"}; + enumeration_value<normalization> formKD{"formKD"}; +}; + +using normalization_t = enumeration<normalization>; + +struct casesensitivity { + enumeration_value<casesensitivity> sensitive{"sensitive"}; + enumeration_value<casesensitivity> insensitive{"insensitive"}; + enumeration_value<casesensitivity> mixed{"mixed"}; +}; + +using casesensitivity_t = enumeration<casesensitivity>; + +struct primarycache { + enumeration_value<primarycache> all{"all"}; + enumeration_value<primarycache> none{"none"}; + enumeration_value<primarycache> metadata{"metadata"}; +}; + +using primarycache_t = enumeration<primarycache>; + +struct secondarycache { + enumeration_value<secondarycache> all{"all"}; + enumeration_value<secondarycache> none{"none"}; + enumeration_value<secondarycache> metadata{"metadata"}; +}; + +using secondarycache_t = enumeration<secondarycache>; + +struct logbias { + enumeration_value<logbias> latency{"latency"}; + enumeration_value<logbias> throughput{"throughput"}; +}; + +using logbias_t = enumeration<logbias>; + +struct sync { + enumeration_value<sync> standard{"standard"}; + enumeration_value<sync> disabled{"disabled"}; + enumeration_value<sync> always{"always"}; +}; + +using sync_t = enumeration<sync>; /* * Datasets. */ struct dataset { + // Dataset identity. std::string name; + dataset_type_t type; + std::uint64_t guid = 0; + std::uint64_t objsetid = 0; + + // Bookkeeping data, read-only. + std::uint64_t creation = 0u; + std::uint64_t used = 0u; + std::uint64_t available = 0u; + std::uint64_t referenced = 0u; + std::uint64_t usedbysnapshots = 0u; + std::uint64_t usedbydataset = 0u; + std::uint64_t usedbychildren = 0u; + std::uint64_t usedbyrefreservation = 0u; + double compressratio = 0; + double refcompressratio = 0; + bool mounted = false; + std::uint64_t createtxg = 0; + + // Mount options + std::string mountpoint; // XXX - should have a way to handle none/legacy + canmount_t canmount; + + // Miscellaneous options + std::uint64_t quota = 0u; + std::uint64_t reservation = 0u; + std::uint64_t recordsize = 0u; + std::string sharenfs; + std::string sharesmb; + std::string checksum; + std::string compression; + bool atime = false; + bool devices = false; + bool exec = false; + bool setuid = false; + bool readonly = false; + std::string jailed; + snapdir_t snapdir; + aclmode_t aclmode; + aclinherit_t aclinherit; + xattr_t xattr; + unsigned copies = 0; + unsigned version = 0; + bool utf8only = false; + normalization_t normalization; + casesensitivity_t casesensitivity; + bool vscan = false; + bool nbmand = false; + std::uint64_t refquota = 0; + std::uint64_t refreservation = 0; + primarycache_t primarycache; + secondarycache_t secondarycache; + logbias_t logbias; + std::string dedup; + std::string mlslabel; + sync_t sync; + std::string dnodesize; }; /* Return true if a dataset by this name exists. */ @@ -53,6 +251,87 @@ auto get_dataset_names() -> std::generator<std::string>; /* Fetch details for all datasets on the system. */ auto get_datasets() -> std::generator<dataset>; +/* Destroy a dataset */ +auto dataset_destroy(std::string_view name) -> void; + +/* + * Create a new ZFS dataset. + */ +auto create_dataset(std::string const &dsname, auto &&...args) -> dataset { + using namespace std::literals; + + try { + auto ret = spawn(exec::execl(path_zfs, + "zfs", "create", ("-o"s + args)..., + "--", dsname)).wait(); + if (!ret) + throw zfs_error("failed to create dataset"); + + return get_dataset(dsname); + } catch (std::exception const &exc) { + throw zfs_error("failed to create dataset: {}", exc.what()); + } +} + +/* + * Pools. + */ + +struct pool { + // Pool identity + std::string name; + std::uint64_t guid = 0u; + std::uint64_t load_guid = 0u; + std::optional<std::string> comment; + + // Bookkeeping information, read-only + std::uint64_t size = 0u; + std::uint64_t free = 0u; + std::uint64_t allocated = 0u; + std::uint64_t expandsize = 0u; + std::uint64_t freeing = 0u; + std::uint64_t leaked = 0u; + unsigned capacity = 0; + unsigned fragmentation = 0; + std::uint64_t bcloneused = 0; + std::uint64_t bclonesaved = 0; + double bcloneratio = 0; + std::uint64_t dedup_table_size = 0; + double dedupratio = 0; + std::uint64_t last_scrubbed_txg = 0; + std::string health; + std::optional<std::string> checkpoint; + + // Paths + std::optional<std::string> bootfs; + std::optional<std::string> altroot; + std::optional<std::string> cachefile; + + // Miscellaneous options + bool delegation = false; + bool autoreplace = false; + bool listsnapshots = false; + bool autoexpand = false; + bool readonly = false; + bool multihost = false; + bool autotrim = false; + failmode_t failmode; + std::string compatibility; + + unsigned ashift = 0; + + // TODO - feature flags + + // Get the pool's root dataset. + auto dataset() -> struct dataset; +}; + +/* Fetch details for a specific pool by name. */ +auto get_pool(std::string_view name) -> pool; + +/* Fetch details for all pools on the system. */ +auto get_pools() -> std::generator<pool>; + } // namespace lfjail::zfs #endif // LFJAIL_ZFS_HH diff --git a/tests/words.cc b/tests/words.cc index ff1d854..bfe61b9 100644 --- a/tests/words.cc +++ b/tests/words.cc @@ -40,6 +40,39 @@ TEST_CASE(charsep) { ATF_REQUIRE_EQ("baz", vec[2]); } +TEST_CASE(as_view) { + auto vec = "foo bar baz"s + | words() + | std::ranges::to<std::vector>(); + + ATF_REQUIRE_EQ(vec.size(), 3); + ATF_REQUIRE_EQ("foo", vec[0]); + ATF_REQUIRE_EQ("bar", vec[1]); + ATF_REQUIRE_EQ("baz", vec[2]); +} + +TEST_CASE(as_view_char) { + auto vec = "foo*bar*baz"s + | words('*') + | std::ranges::to<std::vector>(); + + ATF_REQUIRE_EQ(vec.size(), 3); + ATF_REQUIRE_EQ("foo", vec[0]); + ATF_REQUIRE_EQ("bar", vec[1]); + ATF_REQUIRE_EQ("baz", vec[2]); +} + +TEST_CASE(as_view_pred) { + auto vec = "foo bar baz"s + | words(is_c_space) + | std::ranges::to<std::vector>(); + + ATF_REQUIRE_EQ(vec.size(), 3); + ATF_REQUIRE_EQ("foo", vec[0]); + ATF_REQUIRE_EQ("bar", vec[1]); + ATF_REQUIRE_EQ("baz", vec[2]); +} + TEST_CASE(empty) { auto vec = words(""s) | std::ranges::to<std::vector>(); @@ -107,6 +140,9 @@ TEST_CASE(trailing_whitespace) { ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, basic); ATF_ADD_TEST_CASE(tcs, charsep); + ATF_ADD_TEST_CASE(tcs, as_view); + ATF_ADD_TEST_CASE(tcs, as_view_pred); + ATF_ADD_TEST_CASE(tcs, as_view_char); ATF_ADD_TEST_CASE(tcs, empty); ATF_ADD_TEST_CASE(tcs, temporary); ATF_ADD_TEST_CASE(tcs, range_for); |
