summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--meson.build1
-rw-r--r--src/vpick/meson.build9
-rw-r--r--src/vpick/vpick-tool.c350
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);