aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--liblfjail/Makefile1
-rw-r--r--liblfjail/config_store.cc4
-rw-r--r--liblfjail/context.cc17
-rw-r--r--liblfjail/context.hh6
-rw-r--r--liblfjail/fileutils.cc25
-rw-r--r--liblfjail/fileutils.hh40
-rw-r--r--liblfjail/jail.cc8
-rw-r--r--liblfjail/jail_zfs.cc103
-rw-r--r--liblfjail/words.hh94
-rw-r--r--liblfjail/zfs.cc220
-rw-r--r--liblfjail/zfs.hh299
-rw-r--r--tests/words.cc36
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);