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/
This commit is contained in:
Lennart Poettering 2023-03-03 18:26:45 +01:00
parent 76511c1bd3
commit 9e61ed1115
3 changed files with 360 additions and 0 deletions

View File

@ -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')

9
src/vpick/meson.build Normal file
View File

@ -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'),
},
]

350
src/vpick/vpick-tool.c Normal file
View File

@ -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);