diff --git a/src/bin/zfs-tpm-list.cpp b/src/bin/zfs-tpm-list.cpp new file mode 100644 index 0000000..aa421d8 --- /dev/null +++ b/src/bin/zfs-tpm-list.cpp @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: MIT */ + + +#include "../main.hpp" +#include "../parse.hpp" +#include "../zfs.hpp" + +#include <algorithm> +#include <sys/mman.h> + + +#define TZPFMS_BACKEND_MAX_LEN 16 + + +/// zfs(8) uses struct zprop_get_cbdata_t, which is powerful, but inscrutable; we have a fixed format, which makes this easier +struct output_line { + char name[ZFS_MAX_DATASET_NAME_LEN + 1]; + char backend[TZPFMS_BACKEND_MAX_LEN + 1]; + bool key_available : 1; + bool coherent : 1; +}; + + +int main(int argc, char ** argv) { + bool human = true; + bool print_nontzpfms = false; + size_t maxdepth = MAXDEPTH_UNSET; + return do_bare_main( + argc, argv, "Hrd:a", "[-H] [-r|-d max] [-a]", + [&](auto arg) { + switch(arg) { + case 'H': + human = false; + break; + case 'r': + maxdepth = SIZE_MAX; + break; + case 'd': + if(parse_int(optarg, maxdepth)) { + fprintf(stderr, "%s is not an integer\n", optarg); + return __LINE__; + } + break; + case 'a': + print_nontzpfms = true; + break; + } + return 0; + }, + [&](auto libz) { + output_line * lines{}; + size_t lines_len{}; + quickscope_wrapper lines_deleter{[&] { free(lines); }}; + + + TRY_MAIN(for_all_datasets(libz, argv + optind, maxdepth, [&](auto dataset) { + boolean_t dataset_is_root; + TRY("get encryption root", zfs_crypto_get_encryption_root(dataset, &dataset_is_root, nullptr)); + if(!dataset_is_root) + return 0; + + char *backend{}, *handle{}; + TRY_MAIN(lookup_userprop(dataset, PROPNAME_BACKEND, backend)); + TRY_MAIN(lookup_userprop(dataset, PROPNAME_KEY, handle)); + + ++lines_len; + lines = TRY_PTR("allocate line buffer", reinterpret_cast<output_line *>(realloc(lines, sizeof(output_line) * lines_len))); + + auto & cur_line = lines[lines_len - 1]; + strncpy(cur_line.name, zfs_get_name(dataset), ZFS_MAX_DATASET_NAME_LEN); + strncpy(cur_line.backend, (backend && strlen(backend) <= TZPFMS_BACKEND_MAX_LEN) ? backend : "\0", TZPFMS_BACKEND_MAX_LEN); + // Tristate available/unavailable/none, but it's gonna be either available or unavailable on envryption roots, so + cur_line.key_available = zfs_prop_get_int(dataset, ZFS_PROP_KEYSTATUS) == ZFS_KEYSTATUS_AVAILABLE; + cur_line.coherent = !!backend == !!handle; + + return 0; + })); + + auto max_name_len = 0u; + auto max_backend_len = 0u; + auto max_key_available_len = 0u; + auto max_coherent_len = 0u; + auto separator = "\t"; + if(human) { + max_name_len = strlen("NAME"); + max_backend_len = strlen("BACK-END"); + max_key_available_len = strlen("KEYSTATUS"); + max_coherent_len = strlen("COHERENT"); + separator = " "; + + for(auto cur = lines; cur != lines + lines_len; ++cur) + if(print_nontzpfms || cur->backend[0] != '\0') { + max_name_len = std::max(max_name_len, strlen(cur->name)); + max_backend_len = std::max(max_backend_len, (cur->backend[0] != '\0') ? strlen(cur->backend) : strlen("-")); + max_key_available_len = std::max(max_key_available_len, cur->key_available ? strlen("available") : strlen("unavailable")); + } + } + + auto println = [&](auto name, auto backend, auto key_available, auto coherent) { + printf("%-*s%s%-*s%s%-*s%s%-*s\n", // + max_name_len, name, separator, // + max_backend_len, backend, separator, // + max_key_available_len, key_available, separator, // + max_coherent_len, coherent); + }; + if(human) + println("NAME", "BACK-END", "KEYSTATUS", "COHERENT"); + for(auto cur = lines; cur != lines + lines_len; ++cur) + if(print_nontzpfms || cur->backend[0] != '\0') + println(cur->name, (cur->backend[0] != '\0') ? cur->backend : "-", cur->key_available ? "available" : "unavailable", cur->coherent ? "yes" : "no"); + + return 0; + }); +} diff --git a/src/main.hpp b/src/main.hpp index 79100e9..da7d05f 100644 --- a/src/main.hpp +++ b/src/main.hpp @@ -7,6 +7,7 @@ #include "common.hpp" #include <libzfs.h> #include <stdlib.h> +#include <type_traits> #include <unistd.h> @@ -19,7 +20,7 @@ template <class G, class M> -int do_main(int argc, char ** argv, const char * getoptions, const char * usage, G && getoptfn, M && main) { +int do_bare_main(int argc, char ** argv, const char * getoptions, const char * usage, G && getoptfn, M && main) { const auto libz = TRY_PTR("initialise libzfs", libzfs_init()); quickscope_wrapper libz_deleter{[=] { libzfs_fini(libz); }}; @@ -40,35 +41,49 @@ int do_main(int argc, char ** argv, const char * getoptions, const char * usage, printf("tzpfms version %s\n", TZPFMS_VERSION); return 0; default: - getoptfn(opt); + if constexpr(std::is_same_v<std::invoke_result_t<G, decltype(opt)>, void>) + getoptfn(opt); + else { + if(auto err = getoptfn(opt)) { + fprintf(stderr, "Usage: %s [-hV] %s%s<dataset>\n", argv[0], usage, strlen(usage) ? " " : ""); + return err; + } + } } - if(optind >= argc) { - fprintf(stderr, - "No dataset to act on?\n" - "Usage: %s [-hV] %s%s<dataset>\n", - argv[0], usage, strlen(usage) ? " " : ""); - return __LINE__; - } - auto dataset = TRY_PTR(nullptr, zfs_open(libz, argv[optind], ZFS_TYPE_FILESYSTEM)); - quickscope_wrapper dataset_deleter{[&] { zfs_close(dataset); }}; - - { - char encryption_root[MAXNAMELEN]; - boolean_t dataset_is_root; - TRY("get encryption root", zfs_crypto_get_encryption_root(dataset, &dataset_is_root, encryption_root)); - - if(!dataset_is_root && !strlen(encryption_root)) { - fprintf(stderr, "Dataset %s not encrypted?\n", zfs_get_name(dataset)); - return __LINE__; - } else if(!dataset_is_root) { - printf("Using dataset %s's encryption root %s instead.\n", zfs_get_name(dataset), encryption_root); - // TODO: disallow maybe? or require force option? - zfs_close(dataset); - dataset = TRY_PTR(nullptr, zfs_open(libz, encryption_root, ZFS_TYPE_FILESYSTEM)); - } - } - - - return main(dataset); + return main(libz); +} + +template <class G, class M> +int do_main(int argc, char ** argv, const char * getoptions, const char * usage, G && getoptfn, M && main) { + return do_bare_main(argc, argv, getoptions, usage, getoptfn, [&](auto libz) { + if(optind >= argc) { + fprintf(stderr, + "No dataset to act on?\n" + "Usage: %s [-hV] %s%s<dataset>\n", + argv[0], usage, strlen(usage) ? " " : ""); + return __LINE__; + } + auto dataset = TRY_PTR(nullptr, zfs_open(libz, argv[optind], ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME)); + quickscope_wrapper dataset_deleter{[&] { zfs_close(dataset); }}; + + { + char encryption_root[MAXNAMELEN]; + boolean_t dataset_is_root; + TRY("get encryption root", zfs_crypto_get_encryption_root(dataset, &dataset_is_root, encryption_root)); + + if(!dataset_is_root && !strlen(encryption_root)) { + fprintf(stderr, "Dataset %s not encrypted?\n", zfs_get_name(dataset)); + return __LINE__; + } else if(!dataset_is_root) { + printf("Using dataset %s's encryption root %s instead.\n", zfs_get_name(dataset), encryption_root); + // TODO: disallow maybe? or require force option? + zfs_close(dataset); + dataset = TRY_PTR(nullptr, zfs_open(libz, encryption_root, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME)); + } + } + + + return main(dataset); + }); } diff --git a/src/parse.hpp b/src/parse.hpp index 7eee9e1..64ab701 100644 --- a/src/parse.hpp +++ b/src/parse.hpp @@ -11,7 +11,7 @@ template <class T> int parse_int(const char * what, T & out) { - int base = 0; + int base = 10; if(!strncmp(what, "0x", 2) || !strncmp(what, "0X", 2)) { base = 16; what += 2; diff --git a/src/zfs.hpp b/src/zfs.hpp index 54d105c..08b7bab 100644 --- a/src/zfs.hpp +++ b/src/zfs.hpp @@ -16,6 +16,8 @@ #define PROPNAME_BACKEND "xyz.nabijaczleweli:tzpfms.backend" #define PROPNAME_KEY "xyz.nabijaczleweli:tzpfms.key" +#define MAXDEPTH_UNSET (SIZE_MAX - 1) + /// Mimic libzfs error output #define REQUIRE_KEY_LOADED(dataset) \ @@ -76,3 +78,49 @@ int verify_backend(zfs_handle_t * on, const char * this_backend, F && func) { return 0; } + + +template <class F> +struct for_all_datasets_iterator_data { + F & func; + zfs_iter_f iterator; + size_t depth; +}; + +/// Iterate over datasets like zfs(8) list +template <class F> +int for_all_datasets(libzfs_handle_t * libz, char ** datasets, size_t maxdepth, F && func) { + auto iterator = [](zfs_handle_t * dataset, void * dat_p) { + auto dat = reinterpret_cast<for_all_datasets_iterator_data<F> *>(dat_p); + TRY_MAIN(dat->func(dataset)); + + if(dat->depth) { + for_all_datasets_iterator_data<F> ndat{dat->func, dat->iterator, dat->depth - 1}; + return zfs_iter_filesystems(dataset, dat->iterator, &ndat); + } else + return 0; + }; + + if(!*datasets) { + for_all_datasets_iterator_data<F> dat{func, iterator, (maxdepth == MAXDEPTH_UNSET) ? SIZE_MAX : maxdepth}; + switch(auto err = zfs_iter_root(libz, dat.iterator, &dat)) { + case -1: // zfs_iter_filesystems() only bubbles errors from callback, but zfs_iter_root() might produce new ones and return -1 + TRY("iterate root datasets", err); + __builtin_unreachable(); + case 0: + return 0; + default: + return err; + } + } else { + for_all_datasets_iterator_data<F> dat{func, iterator, (maxdepth == MAXDEPTH_UNSET) ? 0 : maxdepth}; + for(; *datasets; ++datasets) { + auto dataset = zfs_open(libz, *datasets, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME); + if(!dataset) + continue; // error printed by libzfs; mirror zfs(8) list behaviour here and continue despite any errors + + TRY_MAIN(dat.iterator(dataset, &dat)); + } + return 0; + } +}