~nabijaczleweli/tzpfms

4da1dbf845dc6bc4a2856fd30ada19d8ee0277f2 — наб 26 days ago b5cd910
Add zfs-tpm-list
4 files changed, 204 insertions(+), 27 deletions(-)

A src/bin/zfs-tpm-list.cpp
M src/main.hpp
M src/parse.hpp
M src/zfs.hpp
A src/bin/zfs-tpm-list.cpp => src/bin/zfs-tpm-list.cpp +114 -0
@@ 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;
	    });
}

M src/main.hpp => src/main.hpp +41 -26
@@ 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 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__;
		} 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));
		}
	}
		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);
		return main(dataset);
	});
}

M src/parse.hpp => src/parse.hpp +1 -1
@@ 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;

M src/zfs.hpp => src/zfs.hpp +48 -0
@@ 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;
	}
}