run: convert run0 option parser to macros
This exercises the new option namespace code.
--help output is generally the same, except for the formatting changes
related to use of the new output helpers and the common option macros.
--same-root-dir is now documented (followup for
475729b805).
Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+71
-156
@@ -1,7 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mount.h>
|
||||
@@ -182,51 +181,27 @@ static int help(void) {
|
||||
}
|
||||
|
||||
static int help_sudo_mode(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
_cleanup_(table_unrefp) Table *opts_table = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("run0", "1", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
/* NB: Let's not go overboard with short options: we try to keep a modicum of compatibility with
|
||||
* sudo's short switches, hence please do not introduce new short switches unless they have a roughly
|
||||
* equivalent purpose on sudo. Use long options for everything private to run0. */
|
||||
|
||||
printf("%s [OPTIONS...] COMMAND [ARGUMENTS...]\n"
|
||||
"\n%sElevate privileges interactively.%s\n\n"
|
||||
" -h --help Show this help\n"
|
||||
" -V --version Show package version\n"
|
||||
" --no-ask-password Do not prompt for password\n"
|
||||
" --machine=CONTAINER Operate on local container\n"
|
||||
" --unit=UNIT Run under the specified unit name\n"
|
||||
" --property=NAME=VALUE Set service or scope unit property\n"
|
||||
" --description=TEXT Description for unit\n"
|
||||
" --slice=SLICE Run in the specified slice\n"
|
||||
" --slice-inherit Inherit the slice\n"
|
||||
" -u --user=USER Run as system user\n"
|
||||
" -g --group=GROUP Run as system group\n"
|
||||
" --nice=NICE Nice level\n"
|
||||
" -D --chdir=PATH Set working directory\n"
|
||||
" --via-shell Invoke command via target user's login shell\n"
|
||||
" -i Shortcut for --via-shell --chdir='~'\n"
|
||||
" --setenv=NAME[=VALUE] Set environment variable\n"
|
||||
" --background=COLOR Set ANSI color for background\n"
|
||||
" --pty Request allocation of a pseudo TTY for stdio\n"
|
||||
" --pty-late Just like --pty, but leave TTY access to agents\n"
|
||||
" until unit is started up\n"
|
||||
" --pipe Request direct pipe for stdio\n"
|
||||
" --shell-prompt-prefix=PREFIX Set $SHELL_PROMPT_PREFIX\n"
|
||||
" --lightweight=BOOLEAN Control whether to register a session with service manager\n"
|
||||
" or without\n"
|
||||
" --area=AREA Home area to log into\n"
|
||||
" --empower Give privileges to selected or current user\n"
|
||||
"\nSee the %s for details.\n",
|
||||
program_invocation_short_name,
|
||||
ansi_highlight(),
|
||||
ansi_normal(),
|
||||
link);
|
||||
r = option_parser_get_help_table_full("run0", /* group= */ NULL, &opts_table);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
help_cmdline("[OPTIONS...] COMMAND [ARGUMENTS...]");
|
||||
help_abstract("Elevate privileges interactively.");
|
||||
|
||||
help_section("Options:");
|
||||
|
||||
r = table_print_or_warn(opts_table);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
help_man_page_reference("run0", "1");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -795,149 +770,96 @@ static Glyph pty_window_glyph(void) {
|
||||
}
|
||||
|
||||
static int parse_argv_sudo_mode(int argc, char *argv[]) {
|
||||
|
||||
enum {
|
||||
ARG_NO_ASK_PASSWORD = 0x100,
|
||||
ARG_MACHINE,
|
||||
ARG_UNIT,
|
||||
ARG_PROPERTY,
|
||||
ARG_DESCRIPTION,
|
||||
ARG_SLICE,
|
||||
ARG_SLICE_INHERIT,
|
||||
ARG_NICE,
|
||||
ARG_SETENV,
|
||||
ARG_BACKGROUND,
|
||||
ARG_PTY,
|
||||
ARG_PTY_LATE,
|
||||
ARG_PIPE,
|
||||
ARG_SHELL_PROMPT_PREFIX,
|
||||
ARG_LIGHTWEIGHT,
|
||||
ARG_AREA,
|
||||
ARG_VIA_SHELL,
|
||||
ARG_EMPOWER,
|
||||
ARG_SAME_ROOT_DIR,
|
||||
};
|
||||
int r;
|
||||
|
||||
/* If invoked as "run0" binary, let's expose a more sudo-like interface. We add various extensions
|
||||
* though (but limit the extension to long options). */
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, 'V' },
|
||||
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
|
||||
{ "machine", required_argument, NULL, ARG_MACHINE },
|
||||
{ "unit", required_argument, NULL, ARG_UNIT },
|
||||
{ "property", required_argument, NULL, ARG_PROPERTY },
|
||||
{ "description", required_argument, NULL, ARG_DESCRIPTION },
|
||||
{ "slice", required_argument, NULL, ARG_SLICE },
|
||||
{ "slice-inherit", no_argument, NULL, ARG_SLICE_INHERIT },
|
||||
{ "user", required_argument, NULL, 'u' },
|
||||
{ "group", required_argument, NULL, 'g' },
|
||||
{ "nice", required_argument, NULL, ARG_NICE },
|
||||
{ "chdir", required_argument, NULL, 'D' },
|
||||
{ "via-shell", no_argument, NULL, ARG_VIA_SHELL },
|
||||
{ "login", no_argument, NULL, 'i' }, /* compat with sudo, --via-shell + --chdir='~' */
|
||||
{ "setenv", required_argument, NULL, ARG_SETENV },
|
||||
{ "background", required_argument, NULL, ARG_BACKGROUND },
|
||||
{ "pty", no_argument, NULL, ARG_PTY },
|
||||
{ "pty-late", no_argument, NULL, ARG_PTY_LATE },
|
||||
{ "pipe", no_argument, NULL, ARG_PIPE },
|
||||
{ "shell-prompt-prefix", required_argument, NULL, ARG_SHELL_PROMPT_PREFIX },
|
||||
{ "lightweight", required_argument, NULL, ARG_LIGHTWEIGHT },
|
||||
{ "area", required_argument, NULL, ARG_AREA },
|
||||
{ "empower", no_argument, NULL, ARG_EMPOWER },
|
||||
{ "same-root-dir", no_argument, NULL, ARG_SAME_ROOT_DIR },
|
||||
{},
|
||||
};
|
||||
|
||||
int r, c;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
/* 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;
|
||||
while ((c = getopt_long(argc, argv, "+hVu:g:D:i", options, NULL)) >= 0)
|
||||
OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION, "run0" };
|
||||
|
||||
FOREACH_OPTION(c, &opts, /* on_error= */ return c)
|
||||
switch (c) {
|
||||
|
||||
case 'h':
|
||||
OPTION_NAMESPACE("run0"): {}
|
||||
|
||||
OPTION_COMMON_HELP:
|
||||
return help_sudo_mode();
|
||||
|
||||
case 'V':
|
||||
OPTION('V', "version", NULL, "Show package version"):
|
||||
return version();
|
||||
|
||||
case ARG_NO_ASK_PASSWORD:
|
||||
OPTION_COMMON_NO_ASK_PASSWORD:
|
||||
arg_ask_password = false;
|
||||
break;
|
||||
|
||||
case ARG_MACHINE:
|
||||
r = parse_machine_argument(optarg, &arg_host, &arg_transport);
|
||||
OPTION_LONG("machine", "CONTAINER", "Operate on local container"):
|
||||
r = parse_machine_argument(opts.arg, &arg_host, &arg_transport);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_UNIT:
|
||||
arg_unit = optarg;
|
||||
OPTION_LONG("unit", "UNIT", "Run under the specified unit name"):
|
||||
arg_unit = opts.arg;
|
||||
break;
|
||||
|
||||
case ARG_PROPERTY:
|
||||
if (strv_extend(&arg_property, optarg) < 0)
|
||||
OPTION_LONG("property", "NAME=VALUE", "Set service or scope unit property"):
|
||||
if (strv_extend(&arg_property, opts.arg) < 0)
|
||||
return log_oom();
|
||||
|
||||
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 ARG_SLICE:
|
||||
r = free_and_strdup_warn(&arg_slice, optarg);
|
||||
OPTION_LONG("slice", "SLICE", "Run in the specified slice"):
|
||||
r = free_and_strdup_warn(&arg_slice, opts.arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_SLICE_INHERIT:
|
||||
OPTION_LONG("slice-inherit", NULL, "Inherit the slice"):
|
||||
arg_slice_inherit = true;
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
r = free_and_strdup_warn(&arg_exec_user, optarg);
|
||||
OPTION('u', "user", "USER", "Run as system user"):
|
||||
r = free_and_strdup_warn(&arg_exec_user, opts.arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
arg_exec_group = optarg;
|
||||
OPTION('g', "group", "GROUP", "Run as system group"):
|
||||
arg_exec_group = opts.arg;
|
||||
break;
|
||||
|
||||
case ARG_NICE:
|
||||
r = parse_nice(optarg, &arg_nice);
|
||||
OPTION_LONG("nice", "NICE", "Nice level"):
|
||||
r = parse_nice(opts.arg, &arg_nice);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse nice value: %s", optarg);
|
||||
return log_error_errno(r, "Failed to parse nice value: %s", opts.arg);
|
||||
|
||||
arg_nice_set = true;
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
if (streq(optarg, "~"))
|
||||
r = free_and_strdup_warn(&arg_working_directory, optarg);
|
||||
OPTION('D', "chdir", "PATH", "Set working directory"):
|
||||
if (streq(opts.arg, "~"))
|
||||
r = free_and_strdup_warn(&arg_working_directory, opts.arg);
|
||||
else
|
||||
/* Root will be manually suppressed later. */
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_working_directory);
|
||||
r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_working_directory);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_VIA_SHELL:
|
||||
OPTION_LONG("via-shell", NULL, "Invoke command via target user's login shell"):
|
||||
arg_via_shell = true;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
OPTION_LONG("login", NULL, NULL): {} /* hidden compat alias for -i */
|
||||
OPTION_SHORT('i', NULL, "Shortcut for --via-shell --chdir='~'"):
|
||||
r = free_and_strdup_warn(&arg_working_directory, "~");
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -945,69 +867,61 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) {
|
||||
arg_via_shell = true;
|
||||
break;
|
||||
|
||||
case ARG_SETENV:
|
||||
r = strv_env_replace_strdup_passthrough(&arg_environment, optarg);
|
||||
OPTION_LONG("setenv", "NAME[=VALUE]", "Set environment variable"):
|
||||
r = strv_env_replace_strdup_passthrough(&arg_environment, 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_BACKGROUND:
|
||||
r = parse_background_argument(optarg, &arg_background);
|
||||
OPTION_LONG("background", "COLOR", "Set ANSI color for background"):
|
||||
r = parse_background_argument(opts.arg, &arg_background);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_PTY:
|
||||
case ARG_PTY_LATE:
|
||||
OPTION_LONG("pty", NULL, "Request allocation of a pseudo TTY for stdio"): {}
|
||||
OPTION_LONG("pty-late", NULL,
|
||||
"Just like --pty, but leave TTY access to agents until unit is started up"):
|
||||
arg_stdio |= ARG_STDIO_PTY;
|
||||
arg_pty_late = c == ARG_PTY_LATE;
|
||||
arg_pty_late = streq(opts.opt->long_code, "pty-late");
|
||||
break;
|
||||
|
||||
case ARG_PIPE:
|
||||
OPTION_LONG("pipe", NULL, "Request direct pipe for stdio"):
|
||||
arg_stdio |= ARG_STDIO_DIRECT;
|
||||
break;
|
||||
|
||||
case ARG_SHELL_PROMPT_PREFIX:
|
||||
r = free_and_strdup_warn(&arg_shell_prompt_prefix, optarg);
|
||||
OPTION_LONG("shell-prompt-prefix", "PREFIX", "Set $SHELL_PROMPT_PREFIX"):
|
||||
r = free_and_strdup_warn(&arg_shell_prompt_prefix, opts.arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_LIGHTWEIGHT:
|
||||
r = parse_tristate_argument_with_auto("--lightweight=", optarg, &arg_lightweight);
|
||||
OPTION_LONG("lightweight", "BOOLEAN",
|
||||
"Control whether to register a session with service manager or without"):
|
||||
r = parse_tristate_argument_with_auto("--lightweight=", opts.arg, &arg_lightweight);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_AREA:
|
||||
OPTION_LONG("area", "AREA", "Home area to log into"):
|
||||
/* We allow an empty --area= specification to allow logging into the primary home directory */
|
||||
if (!isempty(optarg) && !filename_is_valid(optarg))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid area name, refusing: %s", optarg);
|
||||
if (!isempty(opts.arg) && !filename_is_valid(opts.arg))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid area name, refusing: %s", opts.arg);
|
||||
|
||||
r = free_and_strdup_warn(&arg_area, optarg);
|
||||
r = free_and_strdup_warn(&arg_area, opts.arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_EMPOWER:
|
||||
OPTION_LONG("empower", NULL, "Give privileges to selected or current user"):
|
||||
arg_empower = true;
|
||||
break;
|
||||
|
||||
case ARG_SAME_ROOT_DIR:
|
||||
OPTION_LONG("same-root-dir", NULL, NULL): /* hidden */
|
||||
r = free_and_strdup_warn(&arg_root_directory, "/");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
if (!arg_working_directory) {
|
||||
@@ -1055,8 +969,9 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) {
|
||||
arg_send_sighup = true;
|
||||
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
if (argc > optind) {
|
||||
l = strv_copy(argv + optind);
|
||||
char **args = option_parser_get_args(&opts);
|
||||
if (!strv_isempty(args)) {
|
||||
l = strv_copy(args);
|
||||
if (!l)
|
||||
return log_oom();
|
||||
} else if (!arg_via_shell) {
|
||||
|
||||
Reference in New Issue
Block a user