diff options
author | Lennart Poettering <lennart@poettering.net> | 2023-03-03 18:26:45 +0100 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2024-01-03 18:38:46 +0100 |
commit | 9e61ed11156d423dc171a3377119b39b1cc4a8d6 (patch) | |
tree | 8ad4ccecbc92d6c573d61e85ad093cef82af7c5c | |
parent | shared: add new "vpick" concept for ".v/" directories that contain versioned ... (diff) | |
download | systemd-9e61ed11156d423dc171a3377119b39b1cc4a8d6.tar.xz systemd-9e61ed11156d423dc171a3377119b39b1cc4a8d6.zip |
vpick: add new tool "systemd-vpick" which exposes vpick on the command line
Usecase:
$ du $(systemd-vpick /srv/myimages.v/foo___.raw)
In order to determine size of newest image in /srv/myimages.v/
-rw-r--r-- | meson.build | 1 | ||||
-rw-r--r-- | src/vpick/meson.build | 9 | ||||
-rw-r--r-- | src/vpick/vpick-tool.c | 350 |
3 files changed, 360 insertions, 0 deletions
diff --git a/meson.build b/meson.build index d1433e3dbc..d2d255391d 100644 --- a/meson.build +++ b/meson.build @@ -2230,6 +2230,7 @@ subdir('src/vconsole') subdir('src/veritysetup') subdir('src/vmspawn') subdir('src/volatile-root') +subdir('src/vpick') subdir('src/xdg-autostart-generator') subdir('src/systemd') diff --git a/src/vpick/meson.build b/src/vpick/meson.build new file mode 100644 index 0000000000..a8c14cb584 --- /dev/null +++ b/src/vpick/meson.build @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +executables += [ + executable_template + { + 'name' : 'systemd-vpick', + 'public' : true, + 'sources' : files('vpick-tool.c'), + }, +] diff --git a/src/vpick/vpick-tool.c b/src/vpick/vpick-tool.c new file mode 100644 index 0000000000..0ae8fe322d --- /dev/null +++ b/src/vpick/vpick-tool.c @@ -0,0 +1,350 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <getopt.h> + +#include "architecture.h" +#include "build.h" +#include "format-table.h" +#include "fs-util.h" +#include "main-func.h" +#include "path-util.h" +#include "parse-util.h" +#include "pretty-print.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "vpick.h" + +static char *arg_filter_basename = NULL; +static char *arg_filter_version = NULL; +static Architecture arg_filter_architecture = _ARCHITECTURE_INVALID; +static char *arg_filter_suffix = NULL; +static uint32_t arg_filter_type_mask = 0; +static enum { + PRINT_PATH, + PRINT_FILENAME, + PRINT_VERSION, + PRINT_TYPE, + PRINT_ARCHITECTURE, + PRINT_TRIES, + PRINT_ALL, + _PRINT_INVALID = -EINVAL, +} arg_print = _PRINT_INVALID; +static PickFlags arg_flags = PICK_ARCHITECTURE|PICK_TRIES; + +STATIC_DESTRUCTOR_REGISTER(arg_filter_basename, freep); +STATIC_DESTRUCTOR_REGISTER(arg_filter_version, freep); +STATIC_DESTRUCTOR_REGISTER(arg_filter_suffix, freep); + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-vpick", "1", &link); + if (r < 0) + return log_oom(); + + printf("%1$s [OPTIONS...] PATH...\n" + "\n%5$sPick entry from versioned directory.%6$s\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + "\n%3$sLookup Keys:%4$s\n" + " -B --basename=BASENAME\n" + " Look for specified basename\n" + " -V VERSION Look for specified version\n" + " -A ARCH Look for specified architecture\n" + " -S --suffix=SUFFIX Look for specified suffix\n" + " -t --type=TYPE Look for specified inode type\n" + "\n%3$sOutput:%4$s\n" + " -p --print=filename Print selected filename rather than path\n" + " -p --print=version Print selected version rather than path\n" + " -p --print=type Print selected inode type rather than path\n" + " -p --print=arch Print selected architecture rather than path\n" + " -p --print=tries Print selected tries left/tries done rather than path\n" + " -p --print=all Print all of the above\n" + " --resolve=yes Canonicalize the result path\n" + "\nSee the %2$s for details.\n", + program_invocation_short_name, + link, + ansi_underline(), ansi_normal(), + ansi_highlight(), ansi_normal()); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_RESOLVE, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "basename", required_argument, NULL, 'B' }, + { "suffix", required_argument, NULL, 'S' }, + { "type", required_argument, NULL, 't' }, + { "print", required_argument, NULL, 'p' }, + { "resolve", required_argument, NULL, ARG_RESOLVE }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hB:V:A:S:t:p:", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case 'B': + if (!filename_part_is_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid basename string: %s", optarg); + + r = free_and_strdup_warn(&arg_filter_basename, optarg); + if (r < 0) + return r; + + break; + + case 'V': + if (!version_is_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid version string: %s", optarg); + + r = free_and_strdup_warn(&arg_filter_version, optarg); + if (r < 0) + return r; + + break; + + case 'A': + if (streq(optarg, "native")) + arg_filter_architecture = native_architecture(); + else if (streq(optarg, "secondary")) { +#ifdef ARCHITECTURE_SECONDARY + arg_filter_architecture = ARCHITECTURE_SECONDARY; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Local architecture has no secondary architecture."); +#endif + } else if (streq(optarg, "uname")) + arg_filter_architecture = uname_architecture(); + else if (streq(optarg, "auto")) + arg_filter_architecture = _ARCHITECTURE_INVALID; + else { + arg_filter_architecture = architecture_from_string(optarg); + if (arg_filter_architecture < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown architecture: %s", optarg); + } + break; + + case 'S': + if (!filename_part_is_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid suffix string: %s", optarg); + + r = free_and_strdup_warn(&arg_filter_suffix, optarg); + if (r < 0) + return r; + + break; + + case 't': + if (isempty(optarg)) + arg_filter_type_mask = 0; + else { + mode_t m; + + m = inode_type_from_string(optarg); + if (m == MODE_INVALID) + return log_error_errno(m, "Unknown inode type: %s", optarg); + + arg_filter_type_mask |= UINT32_C(1) << IFTODT(m); + } + + break; + + case 'p': + if (streq(optarg, "path")) + arg_print = PRINT_PATH; + else if (streq(optarg, "filename")) + arg_print = PRINT_FILENAME; + else if (streq(optarg, "version")) + arg_print = PRINT_VERSION; + else if (streq(optarg, "type")) + arg_print = PRINT_TYPE; + else if (STR_IN_SET(optarg, "arch", "architecture")) + arg_print = PRINT_ARCHITECTURE; + else if (streq(optarg, "tries")) + arg_print = PRINT_TRIES; + else if (streq(optarg, "all")) + arg_print = PRINT_ALL; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown --print= argument: %s", optarg); + + break; + + case ARG_RESOLVE: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --resolve= value: %m"); + + SET_FLAG(arg_flags, PICK_RESOLVE, r); + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + } + + if (arg_print < 0) + arg_print = PRINT_PATH; + + return 1; +} + +static int run(int argc, char *argv[]) { + int r; + + log_show_color(true); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + if (optind >= argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path to resolve must be specified."); + + for (int i = optind; i < argc; i++) { + _cleanup_free_ char *p = NULL; + r = path_make_absolute_cwd(argv[i], &p); + if (r < 0) + return log_error_errno(r, "Failed to make path '%s' absolute: %m", argv[i]); + + _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; + r = path_pick(/* toplevel_path= */ NULL, + /* toplevel_fd= */ AT_FDCWD, + p, + &(PickFilter) { + .basename = arg_filter_basename, + .version = arg_filter_version, + .architecture = arg_filter_architecture, + .suffix = arg_filter_suffix, + .type_mask = arg_filter_type_mask, + }, + arg_flags, + &result); + if (r < 0) + return log_error_errno(r, "Failed to pick version for '%s': %m", p); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No matching version for '%s' found.", p); + + switch (arg_print) { + + case PRINT_PATH: + fputs(result.path, stdout); + if (result.st.st_mode != MODE_INVALID && S_ISDIR(result.st.st_mode) && !endswith(result.path, "/")) + fputc('/', stdout); + fputc('\n', stdout); + break; + + case PRINT_FILENAME: { + _cleanup_free_ char *fname = NULL; + + r = path_extract_filename(result.path, &fname); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from path '%s': %m", result.path); + + puts(fname); + break; + } + + case PRINT_VERSION: + if (!result.version) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No version information discovered."); + + puts(result.version); + break; + + case PRINT_TYPE: + if (result.st.st_mode == MODE_INVALID) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No inode type information discovered."); + + puts(inode_type_to_string(result.st.st_mode)); + break; + + case PRINT_ARCHITECTURE: + if (result.architecture < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No architecture information discovered."); + + puts(architecture_to_string(result.architecture)); + break; + + case PRINT_TRIES: + if (result.tries_left == UINT_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No tries left/tries done information discovered."); + + printf("+%u-%u", result.tries_left, result.tries_done); + break; + + case PRINT_ALL: { + _cleanup_(table_unrefp) Table *t = NULL; + + t = table_new_vertical(); + if (!t) + return log_oom(); + + table_set_ersatz_string(t, TABLE_ERSATZ_NA); + + r = table_add_many( + t, + TABLE_FIELD, "Path", + TABLE_PATH, result.path, + TABLE_FIELD, "Version", + TABLE_STRING, result.version, + TABLE_FIELD, "Type", + TABLE_STRING, result.st.st_mode == MODE_INVALID ? NULL : inode_type_to_string(result.st.st_mode), + TABLE_FIELD, "Architecture", + TABLE_STRING, result.architecture < 0 ? NULL : architecture_to_string(result.architecture)); + if (r < 0) + return table_log_add_error(r); + + if (result.tries_left != UINT_MAX) { + r = table_add_many( + t, + TABLE_FIELD, "Tries left", + TABLE_UINT, result.tries_left, + TABLE_FIELD, "Tries done", + TABLE_UINT, result.tries_done); + if (r < 0) + return table_log_add_error(r); + } + + r = table_print(t, stdout); + if (r < 0) + return table_log_print_error(r); + + break; + } + + default: + assert_not_reached(); + } + } + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); |