/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#include <getopt.h>
#include <locale.h>
#include <net/if.h>
#include <sys/socket.h>
#include "format-ifname.h"
#include "format-table.h"
#include "format-util.h"
+#include "help-util.h"
#include "hostname-util.h"
#include "import-util.h"
#include "in-addr-util.h"
#include "machine-util.h"
#include "main-func.h"
#include "nulstr-util.h"
+#include "options.h"
#include "osc-context.h"
#include "output-mode.h"
#include "pager.h"
return 0;
}
+VERB_GROUP("Machine Commands");
+
+VERB_DEFAULT_NOARG(verb_list_machines, "list", "List running VMs and containers");
static int verb_list_machines(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;
return r;
}
+VERB(verb_show_machine, "status", "NAME…", 2, VERB_ANY, 0,
+ "Show VM/container details");
+VERB(verb_show_machine, "show", "[NAME…]", VERB_ANY, VERB_ANY, 0,
+ "Show properties of one or more VMs/containers");
static int verb_show_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
bool properties, new_line = false;
return 1;
}
+VERB(verb_start_machine, "start", "NAME…", 2, VERB_ANY, 0,
+ "Start container as a service");
static int verb_start_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
return 0;
}
+VERB(verb_login_machine, "login", "[NAME]", VERB_ANY, 2, 0,
+ "Get a login prompt in a container or on the local host");
static int verb_login_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
return process_forward(event, slot, master, PTY_FORWARD_IGNORE_VHANGUP, machine);
}
+VERB(verb_shell_machine, "shell", "[[USER@]NAME [COMMAND…]]", VERB_ANY, VERB_ANY, 0,
+ "Invoke a shell (or other command) in a container or on the local host");
static int verb_shell_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
static int verb_poweroff_machine(int argc, char *argv[], uintptr_t data, void *userdata);
+VERB(verb_enable_machine, "enable", "NAME…", 2, VERB_ANY, 0,
+ "Enable automatic container start at boot");
+VERB(verb_enable_machine, "disable", "NAME…", 2, VERB_ANY, 0,
+ "Disable automatic container start at boot");
static int verb_enable_machine(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;
return 0;
}
+VERB(verb_poweroff_machine, "poweroff", "NAME…", 2, VERB_ANY, 0,
+ "Power off one or more machines");
+VERB(verb_poweroff_machine, "stop", "NAME…", 2, VERB_ANY, 0,
+ /* help= */ NULL); /* Convenience alias */
static int verb_poweroff_machine(int argc, char *argv[], uintptr_t data, void *userdata) {
sd_bus *bus = ASSERT_PTR(userdata);
int r;
return 0;
}
+VERB(verb_reboot_machine, "reboot", "NAME…", 2, VERB_ANY, 0,
+ "Reboot one or more machines");
+VERB(verb_reboot_machine, "restart", "NAME…", 2, VERB_ANY, 0,
+ /* help= */ NULL); /* Convenience alias */
static int verb_reboot_machine(int argc, char *argv[], uintptr_t data, void *userdata) {
sd_bus *bus = ASSERT_PTR(userdata);
int r;
return 0;
}
+VERB(verb_pause, "pause", "NAME…", 2, VERB_ANY, 0,
+ "Pause one or more machines");
static int verb_pause(int argc, char *argv[], uintptr_t _data, void *userdata) {
return verb_vm_control(argc, argv, "io.systemd.MachineInstance.Pause");
}
+VERB(verb_resume, "resume", "NAME…", 2, VERB_ANY, 0,
+ "Resume one or more paused machines");
static int verb_resume(int argc, char *argv[], uintptr_t _data, void *userdata) {
return verb_vm_control(argc, argv, "io.systemd.MachineInstance.Resume");
}
+VERB(verb_terminate_machine, "terminate", "NAME…", 2, VERB_ANY, 0,
+ "Terminate one or more machines");
static int verb_terminate_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
sd_bus *bus = ASSERT_PTR(userdata);
int r;
return 0;
}
+VERB(verb_kill_machine, "kill", "NAME…", 2, VERB_ANY, 0,
+ "Send signal to processes of a machine");
static int verb_kill_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus *bus = ASSERT_PTR(userdata);
return copy_from ? "CopyFromMachine" : "CopyToMachine";
}
+VERB(verb_copy_files, "copy-to", "NAME PATH [PATH]", 3, 4, 0,
+ "Copy files from the host to a container");
+VERB(verb_copy_files, "copy-from", "NAME PATH [PATH]", 3, 4, 0,
+ "Copy files from a container to the host");
static int verb_copy_files(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 *m = NULL;
return 0;
}
+VERB(verb_bind_mount, "bind", "NAME PATH [PATH]", 3, 4, 0,
+ "Bind mount a path from the host into a container");
static int verb_bind_mount(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus *bus = ASSERT_PTR(userdata);
return 0;
}
+VERB_GROUP("Image Commands");
+
+VERB(verb_list_images, "list-images", /* argspec= */ NULL, VERB_ANY, 1, 0,
+ "Show available container and VM images");
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;
return r;
}
+VERB(verb_show_image, "image-status", "[NAME…]", VERB_ANY, VERB_ANY, 0,
+ "Show image details");
+VERB(verb_show_image, "show-image", "[NAME…]", VERB_ANY, VERB_ANY, 0,
+ "Show properties of image");
static int verb_show_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
bool properties, new_line = false;
return -ENOENT;
}
+VERB(verb_edit_settings, "edit", "NAME|FILE…", 2, VERB_ANY, 0,
+ "Edit settings of one or more VMs/containers");
static int verb_edit_settings(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(edit_file_context_done) EditFileContext context = {};
int r;
return do_edit_files_and_install(&context);
}
+VERB(verb_cat_settings, "cat", "NAME|FILE…", 2, VERB_ANY, 0,
+ "Show settings of one or more VMs/containers");
static int verb_cat_settings(int argc, char *argv[], uintptr_t _data, void *userdata) {
int r = 0;
return r;
}
+VERB(verb_clone_image, "clone", "NAME NAME", 3, 3, 0,
+ "Clone an image");
static int verb_clone_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_message_unrefp) sd_bus_message *m = NULL;
return 0;
}
+VERB(verb_rename_image, "rename", "NAME NAME", 3, 3, 0,
+ "Rename an image");
static int verb_rename_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus *bus = ASSERT_PTR(userdata);
return 0;
}
+VERB(verb_read_only_image, "read-only", "NAME [BOOL]", 2, 3, 0,
+ "Mark or unmark 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;
sd_bus *bus = ASSERT_PTR(userdata);
return 0;
}
+VERB(verb_remove_image, "remove", "NAME…", 2, VERB_ANY, 0,
+ "Remove an image");
static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
sd_bus *bus = ASSERT_PTR(userdata);
int r;
return 0;
}
+VERB(verb_set_limit, "set-limit", "[NAME] BYTES", 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;
sd_bus *bus = userdata;
return 0;
}
+VERB(verb_clean_images, "clean", /* argspec= */ NULL, VERB_ANY, 1, 0,
+ "Remove hidden (or all) images");
static int verb_clean_images(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;
return 0;
}
+VERB(verb_bind_volume, "bind-volume", "MACHINE PROVIDER:VOLUME[:…]", 3, 3, 0,
+ "Attach a volume to a running machine");
static int verb_bind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) {
int r;
return 0;
}
+VERB(verb_unbind_volume, "unbind-volume", "MACHINE PROVIDER:VOLUME", 3, 3, 0,
+ "Detach a volume from a running machine");
static int verb_unbind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) {
int r;
return 0;
}
-static int chainload_importctl(int argc, char *argv[]) {
+static int chainload_importctl(char **args) {
int r;
if (!arg_quiet)
- log_notice("The 'machinectl %1$s' command has been replaced by 'importctl -m %1$s'. Redirecting invocation.", argv[optind]);
+ log_notice("The 'machinectl %1$s' command has been replaced by 'importctl -m %1$s'. "
+ "Redirecting invocation.", args[0]);
_cleanup_strv_free_ char **c =
strv_new("importctl", "--class=machine");
if (strv_extend_many(&c, "--format", arg_format) < 0)
return log_oom();
- if (strv_extend_strv(&c, argv + optind, /* filter_duplicates= */ false) < 0)
+ if (strv_extend_strv(&c, args, /* filter_duplicates= */ false) < 0)
return log_oom();
if (DEBUG_LOGGING) {
}
static int help(void) {
- _cleanup_free_ char *link = NULL;
+ static const char* const vgroups[] = {
+ "Machine Commands",
+ "Image Commands",
+ };
+
+ Table *vtables[ELEMENTSOF(vgroups)] = {};
+ CLEANUP_ELEMENTS(vtables, table_unref_array_clear);
+ _cleanup_(table_unrefp) Table *options = NULL;
int r;
- pager_open(arg_pager_flags);
+ for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) {
+ r = verbs_get_help_table_group(vgroups[i], &vtables[i]);
+ if (r < 0)
+ return r;
+ }
- r = terminal_urlify_man("machinectl", "1", &link);
+ r = option_parser_get_help_table(&options);
if (r < 0)
- return log_oom();
+ return r;
- printf("%1$s [OPTIONS...] COMMAND ...\n\n"
- "%5$sSend control commands to or query the virtual machine and container%6$s\n"
- "%5$sregistration manager.%6$s\n"
- "\n%3$sMachine Commands:%4$s\n"
- " list List running VMs and containers\n"
- " status NAME... Show VM/container details\n"
- " show [NAME...] Show properties of one or more VMs/containers\n"
- " start NAME... Start container as a service\n"
- " login [NAME] Get a login prompt in a container or on the\n"
- " local host\n"
- " shell [[USER@]NAME [COMMAND...]]\n"
- " Invoke a shell (or other command) in a container\n"
- " or on the local host\n"
- " enable NAME... Enable automatic container start at boot\n"
- " disable NAME... Disable automatic container start at boot\n"
- " poweroff NAME... Power off one or more machines\n"
- " reboot NAME... Reboot one or more machines\n"
- " pause NAME... Pause one or more machines\n"
- " resume NAME... Resume one or more paused machines\n"
- " terminate NAME... Terminate one or more machines\n"
- " kill NAME... Send signal to processes of a machine\n"
- " copy-to NAME PATH [PATH] Copy files from the host to a container\n"
- " copy-from NAME PATH [PATH] Copy files from a container to the host\n"
- " bind NAME PATH [PATH] Bind mount a path from the host into a container\n"
- "\n%3$sImage Commands:%4$s\n"
- " list-images Show available container and VM images\n"
- " image-status [NAME...] Show image details\n"
- " show-image [NAME...] Show properties of image\n"
- " edit NAME|FILE... Edit settings of one or more VMs/containers\n"
- " cat NAME|FILE... Show settings of one or more VMs/containers\n"
- " clone NAME NAME Clone an image\n"
- " rename NAME NAME Rename an image\n"
- " read-only NAME [BOOL] Mark or unmark image read-only\n"
- " remove NAME... Remove an image\n"
- " set-limit [NAME] BYTES Set image or pool size limit (disk quota)\n"
- " clean Remove hidden (or all) images\n"
- "\n%3$sOptions:%4$s\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"
- " --system Connect to system machine manager\n"
- " --user Connect to user machine manager\n"
- " -p --property=NAME Show only properties by this name\n"
- " --value When showing properties, only print the value\n"
- " -P NAME Equivalent to --value --property=NAME\n"
- " -q --quiet Suppress output\n"
- " -a --all Show all properties, including empty ones\n"
- " -l --full Do not ellipsize output\n"
- " --kill-whom=WHOM Whom to send signal to\n"
- " -s --signal=SIGNAL Which signal to send\n"
- " --uid=USER Specify user ID to invoke shell as\n"
- " -E --setenv=VAR[=VALUE] Add an environment variable for shell\n"
- " --read-only Create read-only bind mount or clone\n"
- " --mkdir Create directory before bind mounting, if missing\n"
- " -n --lines=INTEGER Number of journal entries to show\n"
- " --max-addresses=INTEGER Number of internet addresses to show at most\n"
- " -o --output=STRING Change journal output mode (short, short-precise,\n"
- " short-iso, short-iso-precise, short-full,\n"
- " short-monotonic, short-unix, short-delta,\n"
- " json, json-pretty, json-sse, json-seq, cat,\n"
- " verbose, export, with-unit)\n"
- " --force Replace target file when copying, if necessary\n"
- " --now Start or power off container after enabling or\n"
- " disabling it\n"
- " --runner=RUNNER Select between nspawn and vmspawn as the runner\n"
- " -V Short for --runner=vmspawn\n"
- "\nSee the %2$s for details.\n",
- program_invocation_short_name,
- link,
- ansi_underline(),
- ansi_normal(),
+ assert_cc(ELEMENTSOF(vtables) == 2);
+ (void) table_sync_column_widths(0, options, vtables[0], vtables[1]);
+
+ pager_open(arg_pager_flags);
+
+ help_cmdline("[OPTIONS…] COMMAND …");
+
+ printf("\n%1$s%2$sSend control commands to or query the virtual machine and container%3$s\n"
+ "%1$s%2$sregistration manager.%3$s\n",
ansi_highlight(),
+ ansi_add_italics(),
ansi_normal());
- return 0;
-}
+ for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) {
+ help_section(vgroups[i]);
+ r = table_print_or_warn(vtables[i]);
+ if (r < 0)
+ return r;
+ }
-static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) {
- return help();
-}
+ help_section("Options");
+ r = table_print_or_warn(options);
+ if (r < 0)
+ return r;
-static int parse_argv(int argc, char *argv[]) {
-
- enum {
- ARG_VERSION = 0x100,
- ARG_NO_PAGER,
- ARG_NO_LEGEND,
- ARG_VALUE,
- ARG_KILL_WHOM,
- ARG_READ_ONLY,
- ARG_MKDIR,
- ARG_NO_ASK_PASSWORD,
- ARG_VERIFY,
- ARG_RUNNER,
- ARG_NOW,
- ARG_FORCE,
- ARG_FORMAT,
- ARG_UID,
- ARG_MAX_ADDRESSES,
- ARG_SYSTEM,
- ARG_USER,
- };
+ help_man_page_reference("machinectl", "1");
- static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, ARG_VERSION },
- { "property", required_argument, NULL, 'p' },
- { "value", no_argument, NULL, ARG_VALUE },
- { "all", no_argument, NULL, 'a' },
- { "full", no_argument, NULL, 'l' },
- { "no-pager", no_argument, NULL, ARG_NO_PAGER },
- { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
- { "kill-whom", required_argument, NULL, ARG_KILL_WHOM },
- { "signal", required_argument, NULL, 's' },
- { "host", required_argument, NULL, 'H' },
- { "machine", required_argument, NULL, 'M' },
- { "read-only", no_argument, NULL, ARG_READ_ONLY },
- { "mkdir", no_argument, NULL, ARG_MKDIR },
- { "quiet", no_argument, NULL, 'q' },
- { "lines", required_argument, NULL, 'n' },
- { "output", required_argument, NULL, 'o' },
- { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
- { "verify", required_argument, NULL, ARG_VERIFY },
- { "runner", required_argument, NULL, ARG_RUNNER },
- { "now", no_argument, NULL, ARG_NOW },
- { "force", no_argument, NULL, ARG_FORCE },
- { "format", required_argument, NULL, ARG_FORMAT },
- { "uid", required_argument, NULL, ARG_UID },
- { "setenv", required_argument, NULL, 'E' },
- { "max-addresses", required_argument, NULL, ARG_MAX_ADDRESSES },
- { "user", no_argument, NULL, ARG_USER },
- { "system", no_argument, NULL, ARG_SYSTEM },
- {}
- };
+ return 0;
+}
- bool reorder = false;
- int c, r, shell = -1;
+VERB_COMMON_HELP_HIDDEN(help);
+static int parse_argv(int argc, char *argv[], char ***ret_args) {
assert(argc >= 0);
assert(argv);
+ assert(ret_args);
+
+ /* For "shell" we don't want option reordering; options specified after the command should be passed
+ * to the program to execute, and not processed by us. For other verbs, we consume all options as
+ * usual. To make this work, start with OPTION_PARSER_RETURN_POSITIONAL_ARGS and switch to either
+ * OPTION_PARSER_STOP_AT_FIRST_NONOPTION or OPTION_PARSER_NORMAL after we've seen the verb and one
+ * more argument after that. */
+ OptionParser opts = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS };
+ _cleanup_strv_free_ char **args = NULL;
+ int r;
- /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
- * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
- optind = 0;
-
- for (;;) {
- static const char option_string[] = "-hp:P:als:H:M:qn:o:E:V";
-
- c = getopt_long(argc, argv, option_string + reorder, options, NULL);
- if (c < 0)
- break;
-
+ FOREACH_OPTION_OR_RETURN(c, &opts)
switch (c) {
- case 1: /* getopt_long() returns 1 if "-" was the first character of the option string, and a
- * non-option argument was discovered. */
-
- assert(!reorder);
-
- /* We generally are fine with the fact that getopt_long() reorders the command line, and looks
- * for switches after the main verb. However, for "shell" we really don't want that, since we
- * want that switches specified after the machine name are passed to the program to execute,
- * and not processed by us. To make this possible, we'll first invoke getopt_long() with
- * reordering disabled (i.e. with the "-" prefix in the option string), looking for the first
- * non-option parameter. If it's the verb "shell" we remember its position and continue
- * processing options. In this case, as soon as we hit the next non-option argument we found
- * the machine name, and stop further processing. If the first non-option argument is any other
- * verb than "shell" we switch to normal reordering mode and continue processing arguments
- * normally. */
-
- if (shell >= 0) {
- /* If we already found the "shell" verb on the command line, and now found the next
- * non-option argument, then this is the machine name and we should stop processing
- * further arguments. */
- optind--; /* don't process this argument, go one step back */
- goto done;
- }
- if (streq(optarg, "shell"))
- /* Remember the position of the "shell" verb, and continue processing normally. */
- shell = optind - 1;
- else {
- int saved_optind;
-
- /* OK, this is some other verb. In this case, turn on reordering again, and continue
- * processing normally. */
- reorder = true;
-
- /* We changed the option string. getopt_long() only looks at it again if we invoke it
- * at least once with a reset option index. Hence, let's reset the option index here,
- * then invoke getopt_long() again (ignoring what it has to say, after all we most
- * likely already processed it), and the bump the option index so that we read the
- * intended argument again. */
- saved_optind = optind;
- optind = 0;
- (void) getopt_long(argc, argv, option_string + reorder, options, NULL);
- optind = saved_optind - 1; /* go one step back, process this argument again */
- }
+ OPTION_POSITIONAL:
+ assert(opts.mode == OPTION_PARSER_RETURN_POSITIONAL_ARGS);
+
+ if (!args && !streq(opts.arg, "shell"))
+ opts.mode = OPTION_PARSER_NORMAL;
+ else if (args)
+ opts.mode = OPTION_PARSER_STOP_AT_FIRST_NONOPTION;
+ r = strv_extend(&args, opts.arg);
+ if (r < 0)
+ return log_oom();
break;
- case 'h':
+ OPTION_COMMON_HELP:
return help();
- case ARG_VERSION:
+ OPTION_COMMON_VERSION:
return version();
- case 'H':
+ OPTION_COMMON_HOST:
arg_transport = BUS_TRANSPORT_REMOTE;
- arg_host = optarg;
+ arg_host = opts.arg;
break;
- case 'M':
+ OPTION_COMMON_MACHINE:
arg_transport = BUS_TRANSPORT_MACHINE;
- arg_host = optarg;
+ arg_host = opts.arg;
break;
- case ARG_SYSTEM:
+ OPTION_LONG("system", NULL, "Connect to system machine manager"):
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
break;
- case ARG_USER:
+ OPTION_LONG("user", NULL, "Connect to user machine manager"):
arg_runtime_scope = RUNTIME_SCOPE_USER;
break;
- case 'p':
- case 'P':
- r = strv_extend(&arg_property, optarg);
+ OPTION_LONG("value", NULL, "When showing properties, only print the value"):
+ SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true);
+ break;
+
+ OPTION('p', "property", "NAME", "Show only properties by this name"): {}
+ OPTION_SHORT('P', "NAME", "Equivalent to --value --property=NAME"):
+ r = strv_extend(&arg_property, opts.arg);
if (r < 0)
return log_oom();
/* If the user asked for a particular property, show it to them, even if empty. */
SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true);
- if (c == 'p')
- break;
- _fallthrough_;
+ if (opts.opt->short_code == 'P')
+ SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true);
- case ARG_VALUE:
- SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true);
break;
- case 'a':
+ OPTION('a', "all", NULL, "Show all properties, including empty ones"):
SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true);
arg_all = true;
break;
- case 'l':
+ OPTION('l', "full", NULL, "Do not ellipsize output"):
arg_full = true;
break;
- case ARG_KILL_WHOM:
- arg_kill_whom = optarg;
+ OPTION_LONG("kill-whom", "WHOM", "Whom to send signal to"):
+ arg_kill_whom = opts.arg;
break;
- case 's':
- r = parse_signal_argument(optarg, &arg_signal);
+ OPTION('s', "signal", "SIGNAL", "Which signal to send"):
+ r = parse_signal_argument(opts.arg, &arg_signal);
if (r <= 0)
return r;
break;
- case ARG_UID:
- arg_uid = optarg;
+ OPTION_LONG("uid", "USER", "Specify user ID to invoke shell as"):
+ arg_uid = opts.arg;
break;
- case 'E':
- r = strv_env_replace_strdup_passthrough(&arg_setenv, optarg);
+ OPTION('E', "setenv", "VAR[=VALUE]", "Add an environment variable for shell"):
+ r = strv_env_replace_strdup_passthrough(&arg_setenv, opts.arg);
if (r < 0)
- return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg);
+ return log_error_errno(r, "Cannot assign environment variable %s: %m", opts.arg);
break;
- case ARG_READ_ONLY:
+ OPTION_LONG("read-only", NULL, "Create read-only bind mount or clone"):
arg_read_only = true;
break;
- case ARG_MKDIR:
+ OPTION_LONG("mkdir", NULL, "Create directory before bind mounting, if missing"):
arg_mkdir = true;
break;
- case 'n':
- if (safe_atou(optarg, &arg_lines) < 0)
+ OPTION('n', "lines", "INTEGER", "Number of journal entries to show"):
+ if (safe_atou(opts.arg, &arg_lines) < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Failed to parse lines '%s'", optarg);
+ "Failed to parse lines '%s'", opts.arg);
break;
- case ARG_MAX_ADDRESSES:
- if (streq(optarg, "all"))
+ OPTION_LONG("max-addresses", "INTEGER",
+ "Number of internet addresses to show at most"):
+ if (streq(opts.arg, "all"))
arg_max_addresses = UINT_MAX;
- else if (safe_atou(optarg, &arg_max_addresses) < 0)
+ else if (safe_atou(opts.arg, &arg_max_addresses) < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Invalid number of addresses: %s", optarg);
+ "Invalid number of addresses: %s", opts.arg);
break;
- case 'o':
- if (streq(optarg, "help"))
+ OPTION('o', "output", "STRING",
+ "Change journal output mode (short, short-precise, short-iso, "
+ "short-iso-precise, short-full, short-monotonic, short-unix, short-delta, "
+ "json, json-pretty, json-sse, json-seq, cat, verbose, export, with-unit)"):
+ if (streq(opts.arg, "help"))
return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX);
- r = output_mode_from_string(optarg);
+ r = output_mode_from_string(opts.arg);
if (r < 0)
- return log_error_errno(r, "Unknown output '%s'.", optarg);
+ return log_error_errno(r, "Unknown output '%s'.", opts.arg);
arg_output = r;
if (OUTPUT_MODE_IS_JSON(arg_output))
arg_legend = false;
break;
- case ARG_FORCE:
+ OPTION_LONG("force", NULL, "Replace target file when copying, if necessary"):
arg_force = true;
break;
- case ARG_NOW:
+ OPTION_LONG("now", NULL,
+ "Start or power off container after enabling or disabling it"):
arg_now = true;
break;
- case ARG_RUNNER:
- r = machine_runner_from_string(optarg);
+ OPTION_LONG("runner", "RUNNER",
+ "Select between nspawn and vmspawn as the runner"):
+ r = machine_runner_from_string(opts.arg);
if (r < 0)
- return log_error_errno(r, "Failed to parse --runner= setting: %s", optarg);
+ return log_error_errno(r, "Failed to parse --runner= setting: %s", opts.arg);
arg_runner = r;
break;
- case 'V':
+ OPTION_SHORT('V', NULL, "Short for --runner=vmspawn"):
arg_runner = RUNNER_VMSPAWN;
break;
- case ARG_VERIFY:
- if (streq(optarg, "help"))
+ /* Hidden options below */
+
+ OPTION_LONG("verify", "MODE", /* help= */ NULL):
+ if (streq(opts.arg, "help"))
return DUMP_STRING_TABLE(import_verify, ImportVerify, _IMPORT_VERIFY_MAX);
- r = import_verify_from_string(optarg);
+ r = import_verify_from_string(opts.arg);
if (r < 0)
- return log_error_errno(r, "Failed to parse --verify= setting: %s", optarg);
+ return log_error_errno(r, "Failed to parse --verify= setting: %s", opts.arg);
arg_verify = r;
break;
- case ARG_FORMAT:
- if (!STR_IN_SET(optarg, "uncompressed", "xz", "gzip", "bzip2", "zstd"))
+ OPTION_LONG("format", "FORMAT", /* help= */ NULL):
+ if (!STR_IN_SET(opts.arg, "uncompressed", "xz", "gzip", "bzip2", "zstd"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Unknown format: %s", optarg);
+ "Unknown format: %s", opts.arg);
- arg_format = optarg;
+ arg_format = opts.arg;
break;
- 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 'q':
+ OPTION('q', "quiet", NULL, "Suppress output"):
arg_quiet = true;
break;
-
- case '?':
- return -EINVAL;
-
- default:
- assert_not_reached();
}
- }
-
-done:
- if (shell >= 0) {
- char *t;
-
- /* We found the "shell" verb while processing the argument list. Since we turned off reordering of the
- * argument list initially let's readjust it now, and move the "shell" verb to the back. */
-
- optind -= 1; /* place the option index where the "shell" verb will be placed */
- t = argv[shell];
- for (int i = shell; i < optind; i++)
- argv[i] = argv[i+1];
- argv[optind] = t;
- }
+ /* We gathered some positional args in 'args' ourselves. Append the remaining ones. */
+ if (strv_extend_strv(&args, option_parser_get_args(&opts), /* filter_duplicates= */ false) < 0)
+ return log_oom();
+ *ret_args = TAKE_PTR(args);
return 1;
}
-static int machinectl_main(int argc, char *argv[], sd_bus *bus) {
-
- static const Verb verbs[] = {
- { "help", VERB_ANY, VERB_ANY, 0, verb_help },
- { "list", VERB_ANY, 1, VERB_DEFAULT, verb_list_machines },
- { "list-images", VERB_ANY, 1, 0, verb_list_images },
- { "status", 2, VERB_ANY, 0, verb_show_machine },
- { "image-status", VERB_ANY, VERB_ANY, 0, verb_show_image },
- { "show", VERB_ANY, VERB_ANY, 0, verb_show_machine },
- { "show-image", VERB_ANY, VERB_ANY, 0, verb_show_image },
- { "terminate", 2, VERB_ANY, 0, verb_terminate_machine },
- { "reboot", 2, VERB_ANY, 0, verb_reboot_machine },
- { "restart", 2, VERB_ANY, 0, verb_reboot_machine }, /* Convenience alias */
- { "poweroff", 2, VERB_ANY, 0, verb_poweroff_machine },
- { "stop", 2, VERB_ANY, 0, verb_poweroff_machine }, /* Convenience alias */
- { "kill", 2, VERB_ANY, 0, verb_kill_machine },
- { "pause", 2, VERB_ANY, 0, verb_pause },
- { "resume", 2, VERB_ANY, 0, verb_resume },
- { "login", VERB_ANY, 2, 0, verb_login_machine },
- { "shell", VERB_ANY, VERB_ANY, 0, verb_shell_machine },
- { "bind", 3, 4, 0, verb_bind_mount },
- { "bind-volume", 3, 3, 0, verb_bind_volume },
- { "unbind-volume", 3, 3, 0, verb_unbind_volume },
- { "edit", 2, VERB_ANY, 0, verb_edit_settings },
- { "cat", 2, VERB_ANY, 0, verb_cat_settings },
- { "copy-to", 3, 4, 0, verb_copy_files },
- { "copy-from", 3, 4, 0, verb_copy_files },
- { "remove", 2, VERB_ANY, 0, verb_remove_image },
- { "rename", 3, 3, 0, verb_rename_image },
- { "clone", 3, 3, 0, verb_clone_image },
- { "read-only", 2, 3, 0, verb_read_only_image },
- { "start", 2, VERB_ANY, 0, verb_start_machine },
- { "enable", 2, VERB_ANY, 0, verb_enable_machine },
- { "disable", 2, VERB_ANY, 0, verb_enable_machine },
- { "set-limit", 2, 3, 0, verb_set_limit },
- { "clean", VERB_ANY, 1, 0, verb_clean_images },
- {}
- };
-
- return dispatch_verb(argc, argv, verbs, bus);
-}
-
static int run(int argc, char *argv[]) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_strv_free_ char **args = NULL;
int r;
setlocale(LC_ALL, "");
log_setup();
- r = parse_argv(argc, argv);
+ r = parse_argv(argc, argv, &args);
if (r <= 0)
return r;
journal_browse_prepare();
- if (STRPTR_IN_SET(argv[optind],
- "import-tar", "import-raw", "import-fs",
- "export-tar", "export-raw",
- "pull-tar", "pull-raw",
- "list-transfers", "cancel-transfer"))
- return chainload_importctl(argc, argv);
+ if (args && STR_IN_SET(args[0],
+ "import-tar", "import-raw", "import-fs",
+ "export-tar", "export-raw",
+ "pull-tar", "pull-raw",
+ "list-transfers", "cancel-transfer"))
+ return chainload_importctl(args);
r = bus_connect_transport(arg_transport, arg_host, arg_runtime_scope, &bus);
if (r < 0)
(void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password);
- return machinectl_main(argc, argv, bus);
+ return dispatch_verb_with_args(args, bus);
}
DEFINE_MAIN_FUNCTION(run);