Convert more programs to option+verb macros (#41974)

Just a handful are left! This is one of the final batches.
This commit is contained in:
Daan De Meyer
2026-05-07 18:02:09 +02:00
committed by GitHub
4 changed files with 521 additions and 736 deletions
+85 -145
View File
@@ -1,7 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <fcntl.h>
#include <getopt.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
@@ -14,6 +13,7 @@
#include "sd-messages.h"
#include "alloc-util.h"
#include "ansi-color.h"
#include "build.h"
#include "bus-error.h"
#include "bus-locator.h"
@@ -29,6 +29,7 @@
#include "format-util.h"
#include "fs-util.h"
#include "glob-util.h"
#include "help-util.h"
#include "image-policy.h"
#include "io-util.h"
#include "journal-internal.h"
@@ -39,12 +40,12 @@
#include "loop-util.h"
#include "main-func.h"
#include "mount-util.h"
#include "options.h"
#include "pager.h"
#include "parse-argument.h"
#include "parse-util.h"
#include "path-util.h"
#include "pidref.h"
#include "pretty-print.h"
#include "process-util.h"
#include "signal-util.h"
#include "string-util.h"
@@ -177,224 +178,166 @@ static int acquire_journal(sd_journal **ret, char **matches) {
}
static int help(void) {
_cleanup_free_ char *link = NULL;
_cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL;
int r;
r = terminal_urlify_man("coredumpctl", "1", &link);
r = verbs_get_help_table(&verbs);
if (r < 0)
return log_oom();
return r;
printf("%1$s [OPTIONS...] COMMAND ...\n\n"
"%5$sList or retrieve coredumps from the journal.%6$s\n"
"\n%3$sCommands:%4$s\n"
" list [MATCHES...] List available coredumps (default)\n"
" info [MATCHES...] Show detailed information about one or more coredumps\n"
" dump [MATCHES...] Print first matching coredump to stdout\n"
" debug [MATCHES...] Start a debugger for the first matching coredump\n"
"\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
" --version Print version string\n"
" --no-pager Do not pipe output into a pager\n"
" --no-legend Do not print the column headers\n"
" --json=pretty|short|off Generate JSON output\n"
" --debugger=DEBUGGER Use the given debugger\n"
" -A --debugger-arguments=ARGS Pass the given arguments to the debugger\n"
" -n INT Show maximum number of rows\n"
" -1 Show information about most recent entry only\n"
" -S --since=DATE Only print coredumps since the date\n"
" -U --until=DATE Only print coredumps until the date\n"
" -r --reverse Show the newest entries first\n"
" -F --field=FIELD List all values a certain field takes\n"
" -o --output=FILE Write output to FILE\n"
" --file=PATH Use journal file\n"
" -D --directory=DIR Use journal files from directory\n\n"
" -q --quiet Do not show info messages and privilege warning\n"
" --all Look at all journal files instead of local ones\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --image=PATH Operate on disk image as filesystem root\n"
" --image-policy=POLICY Specify disk image dissection policy\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
ansi_underline(),
ansi_normal(),
ansi_highlight(),
ansi_normal());
r = option_parser_get_help_table(&options);
if (r < 0)
return r;
(void) table_sync_column_widths(0, verbs, options);
help_cmdline("[OPTIONS…] COMMAND …");
help_abstract("List or retrieve coredumps from the journal.");
help_section("Commands");
r = table_print_or_warn(verbs);
if (r < 0)
return r;
help_section("Options");
r = table_print_or_warn(options);
if (r < 0)
return r;
help_man_page_reference("coredumpctl", "1");
return 0;
}
static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) {
return help();
}
VERB_COMMON_HELP_HIDDEN(help);
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_NO_PAGER,
ARG_NO_LEGEND,
ARG_JSON,
ARG_DEBUGGER,
ARG_FILE,
ARG_ROOT,
ARG_IMAGE,
ARG_IMAGE_POLICY,
ARG_ALL,
};
int c, r;
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version" , no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "debugger", required_argument, NULL, ARG_DEBUGGER },
{ "debugger-arguments", required_argument, NULL, 'A' },
{ "output", required_argument, NULL, 'o' },
{ "field", required_argument, NULL, 'F' },
{ "file", required_argument, NULL, ARG_FILE },
{ "directory", required_argument, NULL, 'D' },
{ "reverse", no_argument, NULL, 'r' },
{ "since", required_argument, NULL, 'S' },
{ "until", required_argument, NULL, 'U' },
{ "quiet", no_argument, NULL, 'q' },
{ "json", required_argument, NULL, ARG_JSON },
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{ "all", no_argument, NULL, ARG_ALL },
{}
};
static int parse_argv(int argc, char *argv[], char ***remaining_args) {
int r;
assert(argc >= 0);
assert(argv);
assert(remaining_args);
while ((c = getopt_long(argc, argv, "hA:o:F:1D:rS:U:qn:", options, NULL)) >= 0)
OptionParser opts = { argc, argv };
FOREACH_OPTION_OR_RETURN(c, &opts)
switch (c) {
case 'h':
OPTION_COMMON_HELP:
return help();
case ARG_VERSION:
OPTION_COMMON_VERSION:
return version();
case ARG_NO_PAGER:
OPTION_COMMON_NO_PAGER:
arg_pager_flags |= PAGER_DISABLE;
break;
case ARG_NO_LEGEND:
OPTION_COMMON_NO_LEGEND:
arg_legend = false;
break;
case ARG_DEBUGGER:
arg_debugger = optarg;
OPTION_LONG("debugger", "DEBUGGER", "Use the given debugger"):
arg_debugger = opts.arg;
break;
case 'A': {
OPTION('A', "debugger-arguments", "…", "Pass the given arguments to the debugger"): {
_cleanup_strv_free_ char **l = NULL;
r = strv_split_full(&l, optarg, WHITESPACE, EXTRACT_UNQUOTE);
r = strv_split_full(&l, opts.arg, WHITESPACE, EXTRACT_UNQUOTE);
if (r < 0)
return log_error_errno(r, "Failed to parse debugger arguments '%s': %m", optarg);
return log_error_errno(r, "Failed to parse debugger arguments '%s': %m", opts.arg);
strv_free_and_replace(arg_debugger_args, l);
break;
}
case ARG_FILE:
r = glob_extend(&arg_file, optarg, GLOB_NOCHECK);
OPTION_LONG("file", "PATH", "Use journal file"):
r = glob_extend(&arg_file, opts.arg, GLOB_NOCHECK);
if (r < 0)
return log_error_errno(r, "Failed to add paths: %m");
break;
case 'o':
OPTION('o', "output", "FILE", "Write output to FILE"):
if (arg_output)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Cannot set output more than once.");
arg_output = optarg;
arg_output = opts.arg;
break;
case 'S':
r = parse_timestamp(optarg, &arg_since);
OPTION('S', "since", "DATE", "Only print coredumps since the date"):
r = parse_timestamp(opts.arg, &arg_since);
if (r < 0)
return log_error_errno(r, "Failed to parse timestamp '%s': %m", optarg);
return log_error_errno(r, "Failed to parse timestamp '%s': %m", opts.arg);
break;
case 'U':
r = parse_timestamp(optarg, &arg_until);
OPTION('U', "until", "DATE", "Only print coredumps until the date"):
r = parse_timestamp(opts.arg, &arg_until);
if (r < 0)
return log_error_errno(r, "Failed to parse timestamp '%s': %m", optarg);
return log_error_errno(r, "Failed to parse timestamp '%s': %m", opts.arg);
break;
case 'F':
OPTION('F', "field", "FIELD", "List all values a certain field takes"):
if (arg_field)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Cannot use --field/-F more than once.");
arg_field = optarg;
arg_field = opts.arg;
break;
case '1':
OPTION_SHORT('1', NULL, "Show information about most recent entry only"):
arg_rows_max = 1;
arg_reverse = true;
break;
case 'n': {
OPTION_SHORT('n', "INT", "Show at most this many rows"): {
unsigned n;
r = safe_atou(optarg, &n);
r = safe_atou(opts.arg, &n);
if (r < 0 || n < 1)
return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL),
"Invalid numeric parameter to -n: %s", optarg);
"Invalid numeric parameter to -n: %s", opts.arg);
arg_rows_max = n;
break;
}
case 'D':
arg_directory = optarg;
OPTION('D', "directory", "DIR", "Use journal files from directory"):
arg_directory = opts.arg;
break;
case ARG_ROOT:
r = parse_path_argument(optarg, false, &arg_root);
OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"):
r = parse_path_argument(opts.arg, false, &arg_root);
if (r < 0)
return r;
break;
case ARG_IMAGE:
r = parse_path_argument(optarg, false, &arg_image);
OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"):
r = parse_path_argument(opts.arg, false, &arg_image);
if (r < 0)
return r;
break;
case ARG_IMAGE_POLICY:
r = parse_image_policy_argument(optarg, &arg_image_policy);
OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"):
r = parse_image_policy_argument(opts.arg, &arg_image_policy);
if (r < 0)
return r;
break;
case 'r':
OPTION('r', "reverse", NULL, "Show the newest entries first"):
arg_reverse = true;
break;
case 'q':
OPTION('q', "quiet", NULL, "Do not show info messages and privilege warning"):
arg_quiet = true;
break;
case ARG_JSON:
r = parse_json_argument(optarg, &arg_json_format_flags);
OPTION_COMMON_JSON:
r = parse_json_argument(opts.arg, &arg_json_format_flags);
if (r <= 0)
return r;
break;
case ARG_ALL:
OPTION_LONG("all", NULL, "Look at all journal files instead of local ones"):
arg_all = true;
break;
case '?':
return -EINVAL;
default:
assert_not_reached();
}
if (arg_since != USEC_INFINITY && arg_until != USEC_INFINITY &&
@@ -405,6 +348,7 @@ static int parse_argv(int argc, char *argv[]) {
if ((!!arg_directory + !!arg_image + !!arg_root) > 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root=, --image= or -D/--directory=, the combination of these options is not supported.");
*remaining_args = option_parser_get_args(&opts);
return 1;
}
@@ -902,6 +846,10 @@ static int print_entry(
return print_info(stdout, j, n_found > 0);
}
VERB(verb_dump_list, "list", "[MATCHES…]", VERB_ANY, VERB_ANY, VERB_DEFAULT,
"List available coredumps");
VERB(verb_dump_list, "info", "[MATCHES…]", VERB_ANY, VERB_ANY, 0,
"Show detailed information about one or more coredumps");
static int verb_dump_list(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_journal_closep) sd_journal *j = NULL;
_cleanup_(table_unrefp) Table *t = NULL;
@@ -1146,6 +1094,8 @@ error:
return r;
}
VERB(verb_dump_core, "dump", "[MATCHES…]", VERB_ANY, VERB_ANY, 0,
"Print first matching coredump to stdout");
static int verb_dump_core(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_journal_closep) sd_journal *j = NULL;
_cleanup_fclose_ FILE *f = NULL;
@@ -1182,6 +1132,10 @@ static int verb_dump_core(int argc, char *argv[], uintptr_t _data, void *userdat
return 0;
}
VERB(verb_run_debug, "debug", "[MATCHES…]", VERB_ANY, VERB_ANY, 0,
"Start a debugger for the first matching coredump");
VERB(verb_run_debug, "gdb", "[MATCHES…]", VERB_ANY, VERB_ANY, 0,
/* help= */ NULL);
static int verb_run_debug(int argc, char *argv[], uintptr_t _data, void *userdata) {
static const struct sigaction sa = {
.sa_sigaction = sigterm_process_group_handler,
@@ -1372,30 +1326,16 @@ static int check_units_active(void) {
return c;
}
static int coredumpctl_main(int argc, char *argv[]) {
static const Verb verbs[] = {
{ "list", VERB_ANY, VERB_ANY, VERB_DEFAULT, verb_dump_list },
{ "info", VERB_ANY, VERB_ANY, 0, verb_dump_list },
{ "dump", VERB_ANY, VERB_ANY, 0, verb_dump_core },
{ "debug", VERB_ANY, VERB_ANY, 0, verb_run_debug },
{ "gdb", VERB_ANY, VERB_ANY, 0, verb_run_debug },
{ "help", VERB_ANY, 1, 0, verb_help },
{}
};
return dispatch_verb(argc, argv, verbs, NULL);
}
static int run(int argc, char *argv[]) {
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
_cleanup_(umount_and_freep) char *mounted_dir = NULL;
char **args = NULL;
int r, units_active;
setlocale(LC_ALL, "");
log_setup();
r = parse_argv(argc, argv);
r = parse_argv(argc, argv, &args);
if (r <= 0)
return r;
@@ -1425,7 +1365,7 @@ static int run(int argc, char *argv[]) {
return log_oom();
}
r = coredumpctl_main(argc, argv);
r = dispatch_verb_with_args(args, NULL);
if (units_active > 0)
printf("%s-- Notice: %d systemd-coredump@.service %s, output may be incomplete.%s\n",
+146 -220
View File
@@ -1,10 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <getopt.h>
#include "sd-bus.h"
#include "sd-device.h"
#include "ansi-color.h"
#include "argv-util.h"
#include "build.h"
#include "bus-error.h"
@@ -20,15 +19,16 @@
#include "format-table.h"
#include "format-util.h"
#include "fstab-util.h"
#include "help-util.h"
#include "libmount-util.h"
#include "main-func.h"
#include "mountpoint-util.h"
#include "options.h"
#include "pager.h"
#include "parse-argument.h"
#include "parse-util.h"
#include "path-util.h"
#include "polkit-agent.h"
#include "pretty-print.h"
#include "process-util.h"
#include "runtime-scope.h"
#include "stat-util.h"
@@ -108,296 +108,220 @@ static int parse_where(const char *input, char **ret_where) {
return 0;
}
static int help(void) {
_cleanup_free_ char *link = NULL;
static int help(char *argv[]) {
_cleanup_(table_unrefp) Table *options_common = NULL, *options_mount = NULL;
int r;
r = terminal_urlify_man("systemd-mount", "1", &link);
r = option_parser_get_help_table(&options_common);
if (r < 0)
return log_oom();
return r;
printf("systemd-mount [OPTIONS...] WHAT [WHERE]\n"
"systemd-mount [OPTIONS...] --tmpfs [NAME] WHERE\n"
"systemd-mount [OPTIONS...] --list\n"
"%1$s [OPTIONS...] %7$sWHAT|WHERE...\n"
"\n%5$sEstablish a mount or auto-mount point transiently.%6$s\n"
"\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --no-block Do not wait until operation finished\n"
" --no-pager Do not pipe output into a pager\n"
" --no-legend Do not show the headers\n"
" -l --full Do not ellipsize output\n"
" --no-ask-password Do not prompt for password\n"
" -q --quiet Suppress information messages during runtime\n"
" --json=pretty|short|off Generate JSON output\n"
" --user Run as user unit\n"
" -H --host=[USER@]HOST Operate on remote host\n"
" -M --machine=CONTAINER Operate on local container\n"
" --discover Discover mount device metadata\n"
" -t --type=TYPE File system type\n"
" -o --options=OPTIONS Mount options\n"
" --owner=USER Add uid= and gid= options for USER\n"
" --fsck=no Don't run file system check before mount\n"
" --description=TEXT Description for unit\n"
" -p --property=NAME=VALUE Set mount unit property\n"
" --automount=BOOL Create an automount point\n"
" -A Same as --automount=yes\n"
" --timeout-idle-sec=SEC Specify automount idle timeout\n"
" --automount-property=NAME=VALUE\n"
" Set automount unit property\n"
" --bind-device Bind automount unit to device\n"
" --list List mountable block devices\n"
" -u --umount Unmount mount points\n"
" -G --collect Unload unit after it stopped, even when failed\n"
" -T --tmpfs Create a new tmpfs on the mount point\n"
" --canonicalize=BOOL Controls whether to canonicalize path before\n"
" operation\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
ansi_underline(),
ansi_normal(),
ansi_highlight(),
ansi_normal(),
streq(program_invocation_short_name, "systemd-umount") ? "" : "--umount ");
if (invoked_as(argv, "systemd-umount")) {
help_cmdline("[OPTIONS…] WHAT|WHERE…");
help_abstract("Unmount one or more mount points.");
} else {
help_cmdline("[OPTIONS…] WHAT [WHERE]");
help_cmdline("[OPTIONS…] --tmpfs [NAME] WHERE");
help_cmdline("[OPTIONS…] --list");
help_cmdline("[OPTIONS…] --umount WHAT|WHERE…");
help_abstract("Establish a mount or auto-mount point.");
r = option_parser_get_help_table_group("Mount options", &options_mount);
if (r < 0)
return r;
(void) table_sync_column_widths(0, options_common, options_mount);
}
help_section("Options");
r = table_print_or_warn(options_common);
if (r < 0)
return r;
if (options_mount) {
r = table_print_or_warn(options_mount);
if (r < 0)
return r;
}
help_man_page_reference("systemd-mount", "1");
return 0;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_NO_BLOCK,
ARG_NO_PAGER,
ARG_NO_LEGEND,
ARG_NO_ASK_PASSWORD,
ARG_USER,
ARG_SYSTEM,
ARG_DISCOVER,
ARG_MOUNT_TYPE,
ARG_MOUNT_OPTIONS,
ARG_OWNER,
ARG_FSCK,
ARG_DESCRIPTION,
ARG_TIMEOUT_IDLE,
ARG_AUTOMOUNT,
ARG_AUTOMOUNT_PROPERTY,
ARG_BIND_DEVICE,
ARG_LIST,
ARG_JSON,
ARG_CANONICALIZE,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-block", no_argument, NULL, ARG_NO_BLOCK },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "full", no_argument, NULL, 'l' },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ "quiet", no_argument, NULL, 'q' },
{ "user", no_argument, NULL, ARG_USER },
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "host", required_argument, NULL, 'H' },
{ "machine", required_argument, NULL, 'M' },
{ "discover", no_argument, NULL, ARG_DISCOVER },
{ "type", required_argument, NULL, 't' },
{ "options", required_argument, NULL, 'o' },
{ "owner", required_argument, NULL, ARG_OWNER },
{ "fsck", required_argument, NULL, ARG_FSCK },
{ "description", required_argument, NULL, ARG_DESCRIPTION },
{ "property", required_argument, NULL, 'p' },
{ "automount", required_argument, NULL, ARG_AUTOMOUNT },
{ "timeout-idle-sec", required_argument, NULL, ARG_TIMEOUT_IDLE },
{ "automount-property", required_argument, NULL, ARG_AUTOMOUNT_PROPERTY },
{ "bind-device", no_argument, NULL, ARG_BIND_DEVICE },
{ "list", no_argument, NULL, ARG_LIST },
{ "umount", no_argument, NULL, 'u' },
{ "unmount", no_argument, NULL, 'u' }, /* Compat spelling */
{ "collect", no_argument, NULL, 'G' },
{ "tmpfs", no_argument, NULL, 'T' },
{ "json", required_argument, NULL, ARG_JSON },
{ "canonicalize", required_argument, NULL, ARG_CANONICALIZE },
{},
};
int r, c;
static int parse_argv(int argc, char *argv[], char ***remaining_args) {
int r;
assert(argc >= 0);
assert(argv);
assert(remaining_args);
if (invoked_as(argv, "systemd-umount"))
arg_action = ACTION_UMOUNT;
while ((c = getopt_long(argc, argv, "hqH:M:t:o:p:AuGlT", options, NULL)) >= 0)
OptionParser opts = { argc, argv };
FOREACH_OPTION_OR_RETURN(c, &opts)
switch (c) {
case 'h':
return help();
OPTION_COMMON_HELP:
return help(argv);
case ARG_VERSION:
OPTION_COMMON_VERSION:
return version();
case ARG_NO_BLOCK:
arg_no_block = true;
break;
case ARG_NO_PAGER:
arg_pager_flags |= PAGER_DISABLE;
break;
case ARG_NO_LEGEND:
arg_legend = false;
break;
case 'l':
arg_full = true;
break;
case ARG_NO_ASK_PASSWORD:
arg_ask_password = false;
break;
case 'q':
arg_quiet = true;
break;
case ARG_USER:
OPTION_LONG("user", NULL, "Run as user unit"):
arg_runtime_scope = RUNTIME_SCOPE_USER;
break;
case ARG_SYSTEM:
OPTION_LONG("system", NULL, /* help= */ NULL):
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
break;
case 'H':
OPTION_COMMON_HOST:
arg_transport = BUS_TRANSPORT_REMOTE;
arg_host = optarg;
arg_host = opts.arg;
break;
case 'M':
r = parse_machine_argument(optarg, &arg_host, &arg_transport);
OPTION_COMMON_MACHINE:
r = parse_machine_argument(opts.arg, &arg_host, &arg_transport);
if (r < 0)
return r;
break;
case ARG_DISCOVER:
OPTION_LONG("canonicalize", "BOOL",
"Whether to canonicalize path before operation"):
r = parse_boolean_argument("--canonicalize=", opts.arg, &arg_canonicalize);
if (r < 0)
return r;
break;
OPTION_LONG("no-block", NULL, "Do not wait until operation finished"):
arg_no_block = true;
break;
OPTION_COMMON_NO_PAGER:
arg_pager_flags |= PAGER_DISABLE;
break;
OPTION_COMMON_NO_LEGEND:
arg_legend = false;
break;
OPTION('l', "full", NULL, "Do not ellipsize output"):
arg_full = true;
break;
OPTION_COMMON_NO_ASK_PASSWORD:
arg_ask_password = false;
break;
OPTION('q', "quiet", NULL, "Suppress informational messages during runtime"):
arg_quiet = true;
break;
OPTION_COMMON_JSON:
r = parse_json_argument(opts.arg, &arg_json_format_flags);
if (r <= 0)
return r;
break;
OPTION_GROUP("Mount options"): {}
OPTION_LONG("discover", NULL, "Discover mount device metadata"):
arg_discover = true;
break;
case 't':
r = free_and_strdup_warn(&arg_mount_type, optarg);
OPTION('t', "type", "TYPE", "File system type"):
r = free_and_strdup_warn(&arg_mount_type, opts.arg);
if (r < 0)
return r;
break;
case 'o':
r = free_and_strdup_warn(&arg_mount_options, optarg);
OPTION('o', "options", "OPTIONS", "Mount options"):
r = free_and_strdup_warn(&arg_mount_options, opts.arg);
if (r < 0)
return r;
break;
case ARG_OWNER: {
const char *user = optarg;
OPTION_LONG("owner", "USER", "Add uid= and gid= options for USER"): {
const char *user = opts.arg;
r = get_user_creds(&user, &arg_uid, &arg_gid, NULL, NULL, 0);
if (r < 0)
return log_error_errno(r,
r == -EBADMSG ? "UID or GID of user %s are invalid."
: "Cannot use \"%s\" as owner: %m",
optarg);
opts.arg);
break;
}
case ARG_FSCK:
r = parse_boolean_argument("--fsck=", optarg, &arg_fsck);
OPTION_LONG("fsck", "BOOL", "Run a file system check before mount"):
r = parse_boolean_argument("--fsck=", opts.arg, &arg_fsck);
if (r < 0)
return r;
break;
case ARG_DESCRIPTION:
r = free_and_strdup_warn(&arg_description, optarg);
OPTION_LONG("description", "TEXT", "Description for unit"):
r = free_and_strdup_warn(&arg_description, opts.arg);
if (r < 0)
return r;
break;
case 'p':
if (strv_extend(&arg_property, optarg) < 0)
OPTION('p', "property", "NAME=VALUE", "Set mount unit property"):
if (strv_extend(&arg_property, opts.arg) < 0)
return log_oom();
break;
case 'A':
OPTION_SHORT('A', NULL, "Same as --automount=yes"):
arg_action = ACTION_AUTOMOUNT;
break;
case ARG_AUTOMOUNT:
r = parse_boolean_argument("--automount=", optarg, NULL);
OPTION_LONG("automount", "BOOL", "Create an automount point"):
r = parse_boolean_argument("--automount=", opts.arg, NULL);
if (r < 0)
return r;
arg_action = r ? ACTION_AUTOMOUNT : ACTION_MOUNT;
break;
case ARG_TIMEOUT_IDLE:
r = parse_sec(optarg, &arg_timeout_idle);
OPTION_LONG("timeout-idle-sec", "SEC", "Specify automount idle timeout"):
r = parse_sec(opts.arg, &arg_timeout_idle);
if (r < 0)
return log_error_errno(r, "Failed to parse timeout: %s", optarg);
return log_error_errno(r, "Failed to parse timeout: %s", opts.arg);
arg_timeout_idle_set = true;
break;
case ARG_AUTOMOUNT_PROPERTY:
if (strv_extend(&arg_automount_property, optarg) < 0)
OPTION_LONG("automount-property", "NAME=VALUE", "Set automount unit property"):
if (strv_extend(&arg_automount_property, opts.arg) < 0)
return log_oom();
break;
case ARG_BIND_DEVICE:
OPTION_LONG("bind-device", NULL, "Bind automount unit to device"):
arg_bind_device = true;
break;
case ARG_LIST:
OPTION_LONG("list", NULL, "List mountable block devices"):
arg_action = ACTION_LIST;
break;
case 'u':
OPTION('u', "umount", NULL, "Unmount mount points"): {}
OPTION_LONG("unmount", NULL, /* help= */ NULL): /* compat spelling */
arg_action = ACTION_UMOUNT;
break;
case 'G':
OPTION('G', "collect", NULL, "Unload unit after it stopped, even when failed"):
arg_aggressive_gc = true;
break;
case 'T':
OPTION('T', "tmpfs", NULL, "Create a new tmpfs on the mount point"):
arg_tmpfs = true;
break;
case ARG_JSON:
r = parse_json_argument(optarg, &arg_json_format_flags);
if (r <= 0)
return r;
break;
case ARG_CANONICALIZE:
r = parse_boolean_argument("--canonicalize=", optarg, &arg_canonicalize);
if (r < 0)
return r;
break;
case '?':
return -EINVAL;
default:
assert_not_reached();
}
char **args = option_parser_get_args(&opts);
size_t n_args = option_parser_get_n_args(&opts);
if (arg_runtime_scope == RUNTIME_SCOPE_USER) {
arg_ask_password = false;
@@ -407,7 +331,7 @@ static int parse_argv(int argc, char *argv[]) {
}
if (arg_action == ACTION_LIST) {
if (optind < argc)
if (n_args > 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Too many arguments.");
@@ -415,22 +339,22 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Listing devices only supported locally.");
} else if (arg_action == ACTION_UMOUNT) {
if (optind >= argc)
if (n_args == 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"At least one argument required.");
if (arg_transport != BUS_TRANSPORT_LOCAL || !arg_canonicalize)
for (int i = optind; i < argc; i++)
if (!path_is_absolute(argv[i]))
STRV_FOREACH(a, args)
if (!path_is_absolute(*a))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Path must be absolute when operating remotely or when canonicalization is turned off: %s",
argv[i]);
*a);
} else {
if (optind >= argc)
if (n_args == 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"At least one argument required.");
if (argc > optind+2)
if (n_args > 2)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"More than two arguments are not allowed.");
@@ -439,16 +363,16 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--discover cannot be used in conjunction with --tmpfs.");
if (argc <= optind+1) {
if (n_args == 1) {
arg_mount_what = strdup("tmpfs");
if (!arg_mount_what)
return log_oom();
r = parse_where(argv[optind], &arg_mount_where);
r = parse_where(args[0], &arg_mount_where);
if (r < 0)
return r;
} else {
arg_mount_what = strdup(argv[optind]);
arg_mount_what = strdup(args[0]);
if (!arg_mount_what)
return log_oom();
}
@@ -463,12 +387,12 @@ static int parse_argv(int argc, char *argv[]) {
arg_mount_type);
} else {
if (arg_mount_type && !fstype_is_blockdev_backed(arg_mount_type)) {
arg_mount_what = strdup(argv[optind]);
arg_mount_what = strdup(args[0]);
if (!arg_mount_what)
return log_oom();
} else {
_cleanup_free_ char *u = NULL;
const char *p = argv[optind];
const char *p = args[0];
if (arg_canonicalize) {
u = fstab_node_to_udev_node(p);
@@ -494,8 +418,8 @@ static int parse_argv(int argc, char *argv[]) {
}
}
if (argc > optind+1) {
r = parse_where(argv[optind+1], &arg_mount_where);
if (n_args >= 2) {
r = parse_where(args[1], &arg_mount_where);
if (r < 0)
return r;
} else if (!arg_tmpfs)
@@ -524,6 +448,7 @@ static int parse_argv(int argc, char *argv[]) {
}
}
*remaining_args = args;
return 1;
}
@@ -1077,18 +1002,18 @@ static int umount_loop(sd_bus *bus, const char *backing_file) {
return umount_by_device(bus, dev);
}
static int action_umount(sd_bus *bus, int argc, char **argv) {
static int action_umount(sd_bus *bus, char **args) {
int r, ret = 0;
assert(bus);
assert(argv);
assert(argc > optind);
assert(args);
assert(!strv_isempty(args));
if (arg_transport != BUS_TRANSPORT_LOCAL || !arg_canonicalize) {
for (int i = optind; i < argc; i++) {
STRV_FOREACH(arg, args) {
_cleanup_free_ char *p = NULL;
r = path_simplify_alloc(argv[i], &p);
r = path_simplify_alloc(*arg, &p);
if (r < 0)
return r;
@@ -1097,10 +1022,10 @@ static int action_umount(sd_bus *bus, int argc, char **argv) {
return ret;
}
for (int i = optind; i < argc; i++) {
STRV_FOREACH(arg, args) {
_cleanup_free_ char *u = NULL, *p = NULL;
u = fstab_node_to_udev_node(argv[i]);
u = fstab_node_to_udev_node(*arg);
if (!u)
return log_oom();
@@ -1113,7 +1038,7 @@ static int action_umount(sd_bus *bus, int argc, char **argv) {
struct stat st;
if (fstat(fd, &st) < 0)
return log_error_errno(errno, "Can't stat '%s' (from %s): %m", p, argv[i]);
return log_error_errno(errno, "Can't stat '%s' (from %s): %m", p, *arg);
r = is_mount_point_at(fd, /* path= */ NULL, /* flags= */ 0);
fd = safe_close(fd); /* before continuing make sure the dir is not keeping anything busy */
@@ -1135,7 +1060,7 @@ static int action_umount(sd_bus *bus, int argc, char **argv) {
else
r = log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Unknown file type for unmounting: %s (from %s)",
p, argv[i]);
p, *arg);
RET_GATHER(ret, r);
}
}
@@ -1552,11 +1477,12 @@ static int list_devices(void) {
static int run(int argc, char* argv[]) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
char **args = NULL;
int r;
log_setup();
r = parse_argv(argc, argv);
r = parse_argv(argc, argv, &args);
if (r <= 0)
return r;
@@ -1570,7 +1496,7 @@ static int run(int argc, char* argv[]) {
(void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password);
if (arg_action == ACTION_UMOUNT)
return action_umount(bus, argc, argv);
return action_umount(bus, args);
if ((!arg_mount_type || fstype_is_blockdev_backed(arg_mount_type))
&& !path_is_normalized(arg_mount_what))
+270 -331
View File
@@ -1,10 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <getopt.h>
#include "sd-bus.h"
#include "alloc-util.h"
#include "ansi-color.h"
#include "build.h"
#include "bus-error.h"
#include "bus-locator.h"
@@ -15,8 +14,11 @@
#include "env-file.h"
#include "format-table.h"
#include "fs-util.h"
#include "glyph-util.h"
#include "help-util.h"
#include "install.h"
#include "main-func.h"
#include "options.h"
#include "os-util.h"
#include "pager.h"
#include "parse-argument.h"
@@ -24,7 +26,6 @@
#include "path-util.h"
#include "polkit-agent.h"
#include "portable.h"
#include "pretty-print.h"
#include "string-util.h"
#include "strv.h"
#include "verbs.h"
@@ -155,8 +156,9 @@ static int extract_prefix(const char *path, char **ret) {
if (!name)
return -ENOMEM;
/* A slightly reduced version of what's permitted in unit names. With ':' and '\' are removed, as well as '_'
* which we use as delimiter for the second part of the image string, which we ignore for now. */
/* A slightly reduced version of what's permitted in unit names. With ':' and '\' are removed, as
* well as '_' which we use as delimiter for the second part of the image string, which we ignore for
* now. */
if (!in_charset(name, ALPHANUMERICAL "-."))
return -EINVAL;
@@ -171,9 +173,9 @@ static int determine_matches(const char *image, char **l, bool allow_any, char *
_cleanup_strv_free_ char **k = NULL;
int r;
/* Determine the matches to apply. If the list is empty we derive the match from the image name. If the list
* contains exactly the "-" we return a wildcard list (which is the empty list), but only if this is expressly
* permitted. */
/* Determine the matches to apply. If the list is empty we derive the match from the image name. If
* the list contains exactly the "-" we return a wildcard list (which is the empty list), but only if
* this is expressly permitted. */
if (strv_isempty(l)) {
char *prefix;
@@ -251,6 +253,82 @@ static int maybe_reload(sd_bus **bus) {
return bus_service_manager_reload(*bus);
}
VERB(verb_list_images, "list", NULL, VERB_ANY, 1, VERB_DEFAULT,
"List available portable service images (default)");
static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(table_unrefp) Table *table = NULL;
int r;
r = acquire_bus(&bus);
if (r < 0)
return r;
r = bus_call_method(bus, bus_portable_mgr, "ListImages", &error, &reply, NULL);
if (r < 0)
return log_error_errno(r, "Failed to list images: %s", bus_error_message(&error, r));
table = table_new("name", "type", "ro", "crtime", "mtime", "usage", "state");
if (!table)
return log_oom();
r = sd_bus_message_enter_container(reply, 'a', "(ssbtttso)");
if (r < 0)
return bus_log_parse_error(r);
for (;;) {
const char *name, *type, *state;
uint64_t crtime, mtime, usage;
int ro_int;
r = sd_bus_message_read(reply, "(ssbtttso)", &name, &type, &ro_int, &crtime, &mtime, &usage, &state, NULL);
if (r < 0)
return bus_log_parse_error(r);
if (r == 0)
break;
r = table_add_many(table,
TABLE_STRING, name,
TABLE_STRING, type,
TABLE_BOOLEAN, ro_int,
TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL,
TABLE_TIMESTAMP, crtime,
TABLE_TIMESTAMP, mtime,
TABLE_SIZE, usage,
TABLE_STRING, state,
TABLE_SET_COLOR, !streq(state, "detached") ? ansi_highlight_green() : NULL);
if (r < 0)
return table_log_add_error(r);
}
r = sd_bus_message_exit_container(reply);
if (r < 0)
return bus_log_parse_error(r);
if (!table_isempty(table)) {
r = table_set_sort(table, (size_t) 0);
if (r < 0)
return table_log_sort_error(r);
table_set_header(table, arg_legend);
r = table_print_or_warn(table);
if (r < 0)
return r;
}
if (arg_legend) {
if (table_isempty(table))
printf("No images.\n");
else
printf("\n%zu images listed.\n", table_get_rows(table) - 1);
}
return 0;
}
static int get_image_metadata(sd_bus *bus, const char *image, char **matches, sd_bus_message **reply) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
@@ -292,6 +370,8 @@ static int get_image_metadata(sd_bus *bus, const char *image, char **matches, sd
return 0;
}
VERB(verb_inspect_image, "inspect", "NAME|PATH [PREFIX…]", 2, VERB_ANY, 0,
"Show details of specified portable service image");
static int verb_inspect_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
@@ -956,14 +1036,14 @@ static int attach_reattach_image(int argc, char *argv[], const char *method) {
return 0;
}
VERB(verb_attach_image, "attach", "NAME|PATH [PREFIX…]", 2, VERB_ANY, 0,
"Attach the specified portable service image");
static int verb_attach_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) && !arg_force ? "AttachImage" : "AttachImageWithExtensions");
}
static int verb_reattach_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) && !arg_force ? "ReattachImage" : "ReattachImageWithExtensions");
}
VERB(verb_detach_image, "detach", "NAME|PATH [PREFIX…]", 2, VERB_ANY, 0,
"Detach the specified portable service image");
static int verb_detach_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
@@ -1018,168 +1098,14 @@ static int verb_detach_image(int argc, char *argv[], uintptr_t _data, void *user
return 0;
}
static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(table_unrefp) Table *table = NULL;
int r;
r = acquire_bus(&bus);
if (r < 0)
return r;
r = bus_call_method(bus, bus_portable_mgr, "ListImages", &error, &reply, NULL);
if (r < 0)
return log_error_errno(r, "Failed to list images: %s", bus_error_message(&error, r));
table = table_new("name", "type", "ro", "crtime", "mtime", "usage", "state");
if (!table)
return log_oom();
r = sd_bus_message_enter_container(reply, 'a', "(ssbtttso)");
if (r < 0)
return bus_log_parse_error(r);
for (;;) {
const char *name, *type, *state;
uint64_t crtime, mtime, usage;
int ro_int;
r = sd_bus_message_read(reply, "(ssbtttso)", &name, &type, &ro_int, &crtime, &mtime, &usage, &state, NULL);
if (r < 0)
return bus_log_parse_error(r);
if (r == 0)
break;
r = table_add_many(table,
TABLE_STRING, name,
TABLE_STRING, type,
TABLE_BOOLEAN, ro_int,
TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL,
TABLE_TIMESTAMP, crtime,
TABLE_TIMESTAMP, mtime,
TABLE_SIZE, usage,
TABLE_STRING, state,
TABLE_SET_COLOR, !streq(state, "detached") ? ansi_highlight_green() : NULL);
if (r < 0)
return table_log_add_error(r);
}
r = sd_bus_message_exit_container(reply);
if (r < 0)
return bus_log_parse_error(r);
if (!table_isempty(table)) {
r = table_set_sort(table, (size_t) 0);
if (r < 0)
return table_log_sort_error(r);
table_set_header(table, arg_legend);
r = table_print_or_warn(table);
if (r < 0)
return r;
}
if (arg_legend) {
if (table_isempty(table))
printf("No images.\n");
else
printf("\n%zu images listed.\n", table_get_rows(table) - 1);
}
return 0;
}
static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r, i;
r = acquire_bus(&bus);
if (r < 0)
return r;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
for (i = 1; i < argc; i++) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "RemoveImage");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(m, "s", argv[i]);
if (r < 0)
return bus_log_create_error(r);
/* This is a slow operation, hence turn off any method call timeouts */
r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL);
if (r < 0)
return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r));
}
return 0;
}
static int verb_read_only_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int b = true, r;
if (argc > 2) {
b = parse_boolean(argv[2]);
if (b < 0)
return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]);
}
r = acquire_bus(&bus);
if (r < 0)
return r;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
r = bus_call_method(bus, bus_portable_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b);
if (r < 0)
return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r));
return 0;
}
static int verb_set_limit(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
uint64_t limit;
int r;
r = acquire_bus(&bus);
if (r < 0)
return r;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
if (STR_IN_SET(argv[argc-1], "-", "none", "infinity"))
limit = UINT64_MAX;
else {
r = parse_size(argv[argc-1], 1024, &limit);
if (r < 0)
return log_error_errno(r, "Failed to parse size: %s", argv[argc-1]);
}
if (argc > 2)
/* With two arguments changes the quota limit of the specified image */
r = bus_call_method(bus, bus_portable_mgr, "SetImageLimit", &error, NULL, "st", argv[1], limit);
else
/* With one argument changes the pool quota limit */
r = bus_call_method(bus, bus_portable_mgr, "SetPoolLimit", &error, NULL, "t", limit);
if (r < 0)
return log_error_errno(r, "Could not set limit: %s", bus_error_message(&error, r));
return 0;
VERB(verb_reattach_image, "reattach", "NAME|PATH [PREFIX…]", 2, VERB_ANY, 0,
"Reattach the specified portable service image");
static int verb_reattach_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) && !arg_force ? "ReattachImage" : "ReattachImageWithExtensions");
}
VERB(verb_is_image_attached, "is-attached", "NAME|PATH", 2, 2, 0,
"Query if portable service image is attached");
static int verb_is_image_attached(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
@@ -1230,6 +1156,100 @@ static int verb_is_image_attached(int argc, char *argv[], uintptr_t _data, void
return streq(state, "detached");
}
VERB(verb_read_only_image, "read-only", "NAME|PATH [BOOL]", 2, 3, 0,
"Mark or unmark portable service image read-only");
static int verb_read_only_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int b = true, r;
if (argc > 2) {
b = parse_boolean(argv[2]);
if (b < 0)
return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]);
}
r = acquire_bus(&bus);
if (r < 0)
return r;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
r = bus_call_method(bus, bus_portable_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b);
if (r < 0)
return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r));
return 0;
}
VERB(verb_remove_image, "remove", "NAME|PATH…", 2, VERB_ANY, 0,
"Remove a portable service image");
static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r, i;
r = acquire_bus(&bus);
if (r < 0)
return r;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
for (i = 1; i < argc; i++) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "RemoveImage");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(m, "s", argv[i]);
if (r < 0)
return bus_log_create_error(r);
/* This is a slow operation, hence turn off any method call timeouts */
r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL);
if (r < 0)
return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r));
}
return 0;
}
VERB(verb_set_limit, "set-limit", "[NAME|PATH] LIMIT", 2, 3, 0,
"Set image or pool size limit (disk quota)");
static int verb_set_limit(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
uint64_t limit;
int r;
r = acquire_bus(&bus);
if (r < 0)
return r;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
if (STR_IN_SET(argv[argc-1], "-", "none", "infinity"))
limit = UINT64_MAX;
else {
r = parse_size(argv[argc-1], 1024, &limit);
if (r < 0)
return log_error_errno(r, "Failed to parse size: %s", argv[argc-1]);
}
if (argc > 2)
/* With two arguments changes the quota limit of the specified image */
r = bus_call_method(bus, bus_portable_mgr, "SetImageLimit", &error, NULL, "st", argv[1], limit);
else
/* With one argument changes the pool quota limit */
r = bus_call_method(bus, bus_portable_mgr, "SetPoolLimit", &error, NULL, "t", limit);
if (r < 0)
return log_error_errno(r, "Could not set limit: %s", bus_error_message(&error, r));
return 0;
}
static int dump_profiles(void) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
@@ -1256,176 +1276,104 @@ static int dump_profiles(void) {
}
static int help(void) {
_cleanup_free_ char *link = NULL;
_cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL;
int r;
pager_open(arg_pager_flags);
r = terminal_urlify_man("portablectl", "1", &link);
r = verbs_get_help_table(&verbs);
if (r < 0)
return log_oom();
return r;
printf("%s [OPTIONS...] COMMAND ...\n\n"
"%sAttach or detach portable services from the local system.%s\n"
"\nCommands:\n"
" list List available portable service images\n"
" attach NAME|PATH [PREFIX...]\n"
" Attach the specified portable service image\n"
" detach NAME|PATH [PREFIX...]\n"
" Detach the specified portable service image\n"
" reattach NAME|PATH [PREFIX...]\n"
" Reattach the specified portable service image\n"
" inspect NAME|PATH [PREFIX...]\n"
" Show details of specified portable service image\n"
" is-attached NAME|PATH Query if portable service image is attached\n"
" read-only NAME|PATH [BOOL] Mark or unmark portable service image read-only\n"
" remove NAME|PATH... Remove a portable service image\n"
" set-limit [NAME|PATH] Set image or pool size limit (disk quota)\n"
"\nOptions:\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --no-pager Do not pipe output into a pager\n"
" --no-legend Do not show the headers and footers\n"
" --no-ask-password Do not ask for system passwords\n"
" -H --host=[USER@]HOST Operate on remote host\n"
" -M --machine=CONTAINER Operate on local container\n"
" -q --quiet Suppress informational messages\n"
" -p --profile=PROFILE Pick security profile for portable service\n"
" --copy=copy|auto|symlink|mixed\n"
" Pick copying or symlinking of resources\n"
" --runtime Attach portable service until next reboot only\n"
" --no-reload Don't reload the system and service manager\n"
" --cat When inspecting include unit and os-release file\n"
" contents\n"
" --enable Immediately enable/disable the portable service\n"
" after attach/detach\n"
" --now Immediately start/stop the portable service after\n"
" attach/before detach\n"
" --no-block Don't block waiting for attach --now to complete\n"
" --extension=PATH Extend the image with an overlay\n"
" --force Skip 'already active' check when attaching or\n"
" detaching an image (with extensions)\n"
" --clean When detaching, also remove configuration, state,\n"
" cache, logs or runtime data of the portable\n"
" service(s)\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
ansi_normal(),
link);
r = option_parser_get_help_table(&options);
if (r < 0)
return r;
(void) table_sync_column_widths(0, verbs, options);
help_cmdline("[OPTIONS…] COMMAND …");
help_abstract("Attach or detach portable services in the local system.");
help_section("Commands");
r = table_print_or_warn(verbs);
if (r < 0)
return r;
help_section("Options");
r = table_print_or_warn(options);
if (r < 0)
return r;
help_man_page_reference("portablectl", "1");
return 0;
}
static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) {
return help();
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_NO_PAGER,
ARG_NO_LEGEND,
ARG_NO_ASK_PASSWORD,
ARG_COPY,
ARG_RUNTIME,
ARG_NO_RELOAD,
ARG_CAT,
ARG_ENABLE,
ARG_NOW,
ARG_NO_BLOCK,
ARG_EXTENSION,
ARG_FORCE,
ARG_CLEAN,
ARG_USER,
ARG_SYSTEM,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ "host", required_argument, NULL, 'H' },
{ "machine", required_argument, NULL, 'M' },
{ "quiet", no_argument, NULL, 'q' },
{ "profile", required_argument, NULL, 'p' },
{ "copy", required_argument, NULL, ARG_COPY },
{ "runtime", no_argument, NULL, ARG_RUNTIME },
{ "no-reload", no_argument, NULL, ARG_NO_RELOAD },
{ "cat", no_argument, NULL, ARG_CAT },
{ "enable", no_argument, NULL, ARG_ENABLE },
{ "now", no_argument, NULL, ARG_NOW },
{ "no-block", no_argument, NULL, ARG_NO_BLOCK },
{ "extension", required_argument, NULL, ARG_EXTENSION },
{ "force", no_argument, NULL, ARG_FORCE },
{ "clean", no_argument, NULL, ARG_CLEAN },
{ "user", no_argument, NULL, ARG_USER },
{ "system", no_argument, NULL, ARG_SYSTEM },
{}
};
int r, c;
VERB_COMMON_HELP_HIDDEN(help);
static int parse_argv(int argc, char *argv[], char ***remaining_args) {
assert(argc >= 0);
assert(argv);
assert(remaining_args);
while ((c = getopt_long(argc, argv, "hH:M:qp:", options, NULL)) >= 0)
OptionParser opts = { argc, argv };
int r;
FOREACH_OPTION_OR_RETURN(c, &opts)
switch (c) {
case 'h':
OPTION_COMMON_HELP:
return help();
case ARG_VERSION:
OPTION_COMMON_VERSION:
return version();
case ARG_NO_PAGER:
OPTION_COMMON_NO_PAGER:
arg_pager_flags |= PAGER_DISABLE;
break;
case ARG_NO_LEGEND:
OPTION_COMMON_NO_LEGEND:
arg_legend = false;
break;
case ARG_NO_ASK_PASSWORD:
OPTION_COMMON_NO_ASK_PASSWORD:
arg_ask_password = false;
break;
case 'H':
OPTION_COMMON_HOST:
arg_transport = BUS_TRANSPORT_REMOTE;
arg_host = optarg;
arg_host = opts.arg;
break;
case 'M':
r = parse_machine_argument(optarg, &arg_host, &arg_transport);
OPTION_COMMON_MACHINE:
r = parse_machine_argument(opts.arg, &arg_host, &arg_transport);
if (r < 0)
return r;
break;
case 'q':
OPTION('q', "quiet", NULL, "Suppress informational messages"):
arg_quiet = true;
break;
case 'p':
if (streq(optarg, "help"))
OPTION('p', "profile", "PROFILE",
"Pick security profile for portable service"):
if (streq(opts.arg, "help"))
return dump_profiles();
if (!filename_is_valid(optarg))
if (!filename_is_valid(opts.arg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Unit profile name not valid: %s", optarg);
"Unit profile name not valid: %s", opts.arg);
arg_profile = optarg;
arg_profile = opts.arg;
break;
case ARG_COPY:
if (streq(optarg, "auto"))
OPTION_LONG("copy", "MODE",
"Pick copying or symlinking of resources "
"(copy, auto, symlink, mixed)"):
if (streq(opts.arg, "auto"))
arg_copy_mode = NULL;
else if (STR_IN_SET(optarg, "copy", "symlink", "mixed"))
arg_copy_mode = optarg;
else if (streq(optarg, "help")) {
else if (STR_IN_SET(opts.arg, "copy", "symlink", "mixed"))
arg_copy_mode = opts.arg;
else if (streq(opts.arg, "help")) {
puts("auto\n"
"copy\n"
"symlink\n"
@@ -1433,90 +1381,81 @@ static int parse_argv(int argc, char *argv[]) {
return 0;
} else
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Failed to parse --copy= argument: %s", optarg);
"Failed to parse --copy= argument: %s", opts.arg);
break;
case ARG_RUNTIME:
OPTION_LONG("runtime", NULL,
"Attach portable service until next reboot only"):
arg_runtime = true;
break;
case ARG_NO_RELOAD:
OPTION_LONG("no-reload", NULL,
"Don't reload the system and service manager"):
arg_reload = false;
break;
case ARG_CAT:
OPTION_LONG("cat", NULL,
"When inspecting include unit and os-release file contents"):
arg_cat = true;
break;
case ARG_ENABLE:
OPTION_LONG("enable", NULL,
"Immediately enable/disable the portable service after attach/detach"):
arg_enable = true;
break;
case ARG_NOW:
OPTION_LONG("now", NULL,
"Immediately start/stop the portable service after attach/before detach"):
arg_now = true;
break;
case ARG_NO_BLOCK:
OPTION_LONG("no-block", NULL,
"Don't block waiting for attach --now to complete"):
arg_no_block = true;
break;
case ARG_EXTENSION:
r = strv_extend(&arg_extension_images, optarg);
OPTION_LONG("extension", "PATH",
"Extend the image with an overlay"):
r = strv_extend(&arg_extension_images, opts.arg);
if (r < 0)
return log_oom();
break;
case ARG_FORCE:
OPTION_LONG("force", NULL,
"Skip 'already active' check when attaching or detaching an image (with extensions)"):
arg_force = true;
break;
case ARG_CLEAN:
OPTION_LONG("clean", NULL,
"When detaching, also remove configuration, state, "
"cache, logs or runtime data of the portable service(s)"):
arg_clean = true;
break;
case ARG_USER:
OPTION_LONG("user", NULL, /* help= */ NULL):
arg_runtime_scope = RUNTIME_SCOPE_USER;
break;
case ARG_SYSTEM:
OPTION_LONG("system", NULL, /* help= */ NULL):
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
break;
case '?':
return -EINVAL;
default:
assert_not_reached();
}
*remaining_args = option_parser_get_args(&opts);
return 1;
}
static int run(int argc, char *argv[]) {
static const Verb verbs[] = {
{ "help", VERB_ANY, VERB_ANY, 0, verb_help },
{ "list", VERB_ANY, 1, VERB_DEFAULT, verb_list_images },
{ "attach", 2, VERB_ANY, 0, verb_attach_image },
{ "detach", 2, VERB_ANY, 0, verb_detach_image },
{ "inspect", 2, VERB_ANY, 0, verb_inspect_image },
{ "is-attached", 2, 2, 0, verb_is_image_attached },
{ "read-only", 2, 3, 0, verb_read_only_image },
{ "remove", 2, VERB_ANY, 0, verb_remove_image },
{ "set-limit", 3, 3, 0, verb_set_limit },
{ "reattach", 2, VERB_ANY, 0, verb_reattach_image },
{}
};
char **args = NULL;
int r;
log_setup();
r = parse_argv(argc, argv);
r = parse_argv(argc, argv, &args);
if (r <= 0)
return r;
return dispatch_verb(argc, argv, verbs, NULL);
return dispatch_verb_with_args(args, NULL);
}
DEFINE_MAIN_FUNCTION(run);
+20 -40
View File
@@ -1,15 +1,14 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <getopt.h>
#include "sd-varlink.h"
#include "alloc-util.h"
#include "ansi-color.h"
#include "build.h"
#include "format-table.h"
#include "help-util.h"
#include "log.h"
#include "main-func.h"
#include "pretty-print.h"
#include "options.h"
#include "report-cgroup.h"
#include "varlink-io.systemd.Metrics.h"
#include "varlink-util.h"
@@ -46,63 +45,44 @@ static int vl_server(void) {
}
static int help(void) {
_cleanup_free_ char *url = NULL;
_cleanup_(table_unrefp) Table *options = NULL;
int r;
r = terminal_urlify_man("systemd-report-cgroup", "8", &url);
r = option_parser_get_help_table(&options);
if (r < 0)
return log_oom();
return r;
printf("%s [OPTIONS...]\n"
"\n%sReport cgroup metrics.%s\n"
"\n%sOptions:%s\n"
" -h --help Show this help\n"
" --version Show package version\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
ansi_normal(),
ansi_underline(),
ansi_normal(),
url);
help_cmdline("[OPTIONS...]");
help_abstract("Report cgroup metrics.");
help_section("Options");
r = table_print_or_warn(options);
if (r < 0)
return r;
help_man_page_reference("systemd-report-cgroup", "8");
return 0;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{}
};
int c, r;
int r;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
OptionParser opts = { argc, argv };
FOREACH_OPTION_OR_RETURN(c, &opts)
switch (c) {
case 'h':
OPTION_COMMON_HELP:
return help();
case ARG_VERSION:
OPTION_COMMON_VERSION:
return version();
case '?':
return -EINVAL;
default:
assert_not_reached();
}
if (optind < argc)
if (option_parser_get_n_args(&opts) > 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"This program takes no arguments.");