Support for "positional options" and another batch of conversions (#41651)
To make review more sensible, I split out the reordering into separate commits when there was a lot of reordering.
This commit is contained in:
@@ -114,22 +114,23 @@
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--xattr[=BOOL]</option></term>
|
||||
<term><option>-x</option></term>
|
||||
<term><option>--xattr=</option></term>
|
||||
|
||||
<listitem><para>Controls whether to include information about extended attributes of the listed
|
||||
control groups in the output. With the long option, expects a boolean value. Defaults to no.
|
||||
control groups in the output. With the long option only, optionally accepts a boolean value. Defaults
|
||||
to no.
|
||||
</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v250"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--cgroup-id[=BOOL]</option></term>
|
||||
<term><option>-c</option></term>
|
||||
<term><option>--cgroup-id=</option></term>
|
||||
|
||||
<listitem><para>Controls whether to include the numeric ID of the listed control groups in the
|
||||
output. With the long option, expects a boolean value. Defaults to no.</para>
|
||||
output. With the long option only, optionally accepts a boolean value. Defaults to no.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v250"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
+61
-83
@@ -1,6 +1,5 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "sd-bus.h"
|
||||
@@ -10,8 +9,10 @@
|
||||
#include "bus-util.h"
|
||||
#include "cgroup-show.h"
|
||||
#include "cgroup-util.h"
|
||||
#include "format-table.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "options.h"
|
||||
#include "output-mode.h"
|
||||
#include "pager.h"
|
||||
#include "parse-util.h"
|
||||
@@ -35,155 +36,132 @@ static char **arg_names = NULL;
|
||||
static int arg_full = -1;
|
||||
static const char* arg_machine = NULL;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_names, freep); /* don't free the strings */
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_names, strv_freep);
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
_cleanup_(table_unrefp) Table *options = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("systemd-cgls", "1", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%s [OPTIONS...] [CGROUP...]\n\n"
|
||||
"Recursively show control group contents.\n\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
" --no-pager Do not pipe output into a pager\n"
|
||||
" -a --all Show all groups, including empty\n"
|
||||
" -u --unit Show the subtrees of specified system units\n"
|
||||
" --user-unit Show the subtrees of specified user units\n"
|
||||
" -x --xattr=BOOL Show cgroup extended attributes\n"
|
||||
" -c --cgroup-id=BOOL Show cgroup ID\n"
|
||||
" -l --full Do not ellipsize output\n"
|
||||
" -k Include kernel threads in output\n"
|
||||
" -M --machine=NAME Show container NAME\n"
|
||||
"\nSee the %s for details.\n",
|
||||
program_invocation_short_name,
|
||||
link);
|
||||
r = option_parser_get_help_table(&options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("%s [OPTIONS...] [CGROUP...]\n\n"
|
||||
"%sRecursively show control group contents.%s\n\n",
|
||||
program_invocation_short_name,
|
||||
ansi_highlight(),
|
||||
ansi_normal());
|
||||
|
||||
r = table_print_or_warn(options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("\nSee the %s for details.\n", link);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
enum {
|
||||
ARG_NO_PAGER = 0x100,
|
||||
ARG_VERSION,
|
||||
ARG_USER_UNIT,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
|
||||
{ "all", no_argument, NULL, 'a' },
|
||||
{ "full", no_argument, NULL, 'l' },
|
||||
{ "machine", required_argument, NULL, 'M' },
|
||||
{ "unit", optional_argument, NULL, 'u' },
|
||||
{ "user-unit", optional_argument, NULL, ARG_USER_UNIT },
|
||||
{ "xattr", required_argument, NULL, 'x' },
|
||||
{ "cgroup-id", required_argument, NULL, 'c' },
|
||||
{}
|
||||
};
|
||||
|
||||
int c, r;
|
||||
|
||||
assert(argc >= 1);
|
||||
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, "-hkalM:u::xc", options, NULL)) >= 0)
|
||||
OptionParser state = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS };
|
||||
const char *arg;
|
||||
int r;
|
||||
|
||||
FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c)
|
||||
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 'a':
|
||||
OPTION('a', "all", NULL, "Show all groups, including empty"):
|
||||
arg_output_flags |= OUTPUT_SHOW_ALL;
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
OPTION_FULL(OPTION_OPTIONAL_ARG, 'u', "unit", "UNIT",
|
||||
"Show the subtrees of specified system units"):
|
||||
if (arg_show_unit == SHOW_UNIT_USER)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Cannot combine --unit with --user-unit.");
|
||||
|
||||
arg_show_unit = SHOW_UNIT_SYSTEM;
|
||||
if (strv_push(&arg_names, optarg) < 0) /* push optarg if not empty */
|
||||
if (strv_extend(&arg_names, arg) < 0) /* push arg if not empty */
|
||||
return log_oom();
|
||||
break;
|
||||
|
||||
case ARG_USER_UNIT:
|
||||
OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "user-unit", "UNIT",
|
||||
"Show the subtrees of specified user units"):
|
||||
if (arg_show_unit == SHOW_UNIT_SYSTEM)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Cannot combine --user-unit with --unit.");
|
||||
|
||||
arg_show_unit = SHOW_UNIT_USER;
|
||||
if (strv_push(&arg_names, optarg) < 0) /* push optarg if not empty */
|
||||
if (strv_extend(&arg_names, arg) < 0) /* push arg if not empty */
|
||||
return log_oom();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
/* positional argument */
|
||||
if (strv_push(&arg_names, optarg) < 0)
|
||||
return log_oom();
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
arg_full = true;
|
||||
break;
|
||||
|
||||
case 'k':
|
||||
arg_output_flags |= OUTPUT_KERNEL_THREADS;
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
arg_machine = optarg;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
if (optarg) {
|
||||
r = parse_boolean(optarg);
|
||||
OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "xattr", "BOOL",
|
||||
"Show cgroup extended attributes"): {}
|
||||
OPTION_SHORT('x', NULL, "Same as --xattr=true"):
|
||||
if (arg) {
|
||||
r = parse_boolean(arg);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --xattr= value: %s", optarg);
|
||||
return log_error_errno(r, "Failed to parse --xattr= value: %s", arg);
|
||||
} else
|
||||
r = true;
|
||||
|
||||
SET_FLAG(arg_output_flags, OUTPUT_CGROUP_XATTRS, r);
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
if (optarg) {
|
||||
r = parse_boolean(optarg);
|
||||
OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "cgroup-id", "BOOL",
|
||||
"Show cgroup ID"): {}
|
||||
OPTION_SHORT('c', NULL, "Same as --cgroup-id=true"):
|
||||
if (arg) {
|
||||
r = parse_boolean(arg);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --cgroup-id= value: %s", optarg);
|
||||
return log_error_errno(r, "Failed to parse --cgroup-id= value: %s", arg);
|
||||
} else
|
||||
r = true;
|
||||
|
||||
SET_FLAG(arg_output_flags, OUTPUT_CGROUP_ID, r);
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
OPTION('l', "full", NULL, "Do not ellipsize output"):
|
||||
arg_full = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
OPTION_SHORT('k', NULL, "Include kernel threads in output"):
|
||||
arg_output_flags |= OUTPUT_KERNEL_THREADS;
|
||||
break;
|
||||
|
||||
OPTION_COMMON_MACHINE:
|
||||
arg_machine = arg;
|
||||
break;
|
||||
|
||||
OPTION_POSITIONAL:
|
||||
if (strv_extend(&arg_names, arg) < 0) /* push arg */
|
||||
return log_oom();
|
||||
break;
|
||||
}
|
||||
|
||||
if (arg_machine && arg_show_unit != SHOW_UNIT_NONE)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Cannot combine --unit or --user-unit with --machine=.");
|
||||
|
||||
assert(option_parser_get_n_args(&state) == 0);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
+106
-146
@@ -1,6 +1,5 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -10,9 +9,11 @@
|
||||
#include "cgroup-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "format-table.h"
|
||||
#include "hashmap.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "options.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
@@ -72,13 +73,15 @@ typedef enum {
|
||||
_CPU_INVALID = -EINVAL,
|
||||
} CPUType;
|
||||
|
||||
static unsigned arg_depth = 3;
|
||||
#define DEFAULT_MAXIMUM_DEPTH 3
|
||||
|
||||
static unsigned arg_depth = DEFAULT_MAXIMUM_DEPTH;
|
||||
static unsigned arg_iterations = UINT_MAX;
|
||||
static bool arg_batch = false;
|
||||
static bool arg_raw = false;
|
||||
static usec_t arg_delay = 1*USEC_PER_SEC;
|
||||
static char* arg_machine = NULL;
|
||||
static char* arg_root = NULL;
|
||||
static const char *arg_machine = NULL;
|
||||
static const char *arg_root = NULL;
|
||||
static bool arg_recursive = true;
|
||||
static bool arg_recursive_unset = false;
|
||||
static PidsCount arg_count = COUNT_PIDS;
|
||||
@@ -687,194 +690,151 @@ static void display(Hashmap *a) {
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
_cleanup_(table_unrefp) Table *options = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("systemd-cgtop", "1", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
r = option_parser_get_help_table(&options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("%s [OPTIONS...] [CGROUP]\n\n"
|
||||
"Show top control groups by their resource usage.\n\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
|
||||
" --order=path|tasks|cpu|memory|io\n"
|
||||
" Order by specified property\n"
|
||||
" -p Same as --order=path, order by path\n"
|
||||
" -t Same as --order=tasks, order by number of\n"
|
||||
" tasks/processes\n"
|
||||
" -c Same as --order=cpu, order by CPU load\n"
|
||||
" -m Same as --order=memory, order by memory load\n"
|
||||
" -i Same as --order=io, order by IO load\n"
|
||||
" -r --raw Provide raw (not human-readable) numbers\n"
|
||||
" --cpu[=percentage]\n"
|
||||
" Show CPU usage as percentage (default)\n"
|
||||
" --cpu=time Show CPU usage as time\n"
|
||||
" -P Count userspace processes instead of tasks (excl. kernel)\n"
|
||||
" -k Count all processes instead of tasks (incl. kernel)\n"
|
||||
" --recursive=BOOL Sum up process count recursively\n"
|
||||
" -d --delay=DELAY Delay between updates\n"
|
||||
" -n --iterations=N Run for N iterations before exiting\n"
|
||||
" -1 Shortcut for --iterations=1\n"
|
||||
" -b --batch Run in batch mode, accepting no input\n"
|
||||
" --depth=DEPTH Maximum traversal depth (default: %u)\n"
|
||||
" -M --machine= Show container\n"
|
||||
"\nSee the %s for details.\n",
|
||||
"%sShow top control groups by their resource usage.%s\n\n",
|
||||
program_invocation_short_name,
|
||||
arg_depth,
|
||||
link);
|
||||
ansi_highlight(),
|
||||
ansi_normal());
|
||||
|
||||
r = table_print_or_warn(options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("\nSee the %s for details.\n", link);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
enum {
|
||||
ARG_VERSION = 0x100,
|
||||
ARG_DEPTH,
|
||||
ARG_CPU_TYPE,
|
||||
ARG_ORDER,
|
||||
ARG_RECURSIVE,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "delay", required_argument, NULL, 'd' },
|
||||
{ "iterations", required_argument, NULL, 'n' },
|
||||
{ "batch", no_argument, NULL, 'b' },
|
||||
{ "raw", no_argument, NULL, 'r' },
|
||||
{ "depth", required_argument, NULL, ARG_DEPTH },
|
||||
{ "cpu", optional_argument, NULL, ARG_CPU_TYPE },
|
||||
{ "order", required_argument, NULL, ARG_ORDER },
|
||||
{ "recursive", required_argument, NULL, ARG_RECURSIVE },
|
||||
{ "machine", required_argument, NULL, 'M' },
|
||||
{}
|
||||
};
|
||||
|
||||
int c, r;
|
||||
|
||||
assert(argc >= 1);
|
||||
assert(argv);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "hptcmin:brd:kPM:1", options, NULL)) >= 0)
|
||||
OptionParser state = { argc, argv };
|
||||
const char *arg;
|
||||
int r;
|
||||
|
||||
FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c)
|
||||
switch (c) {
|
||||
|
||||
case 'h':
|
||||
OPTION_COMMON_HELP:
|
||||
return help();
|
||||
|
||||
case ARG_VERSION:
|
||||
OPTION_COMMON_VERSION:
|
||||
return version();
|
||||
|
||||
case ARG_CPU_TYPE:
|
||||
if (optarg) {
|
||||
arg_cpu_type = cpu_type_from_string(optarg);
|
||||
if (arg_cpu_type < 0)
|
||||
return log_error_errno(arg_cpu_type,
|
||||
"Unknown argument to --cpu=: %s",
|
||||
optarg);
|
||||
} else
|
||||
arg_cpu_type = CPU_TIME;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_DEPTH:
|
||||
r = safe_atou(optarg, &arg_depth);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse depth parameter '%s': %m", optarg);
|
||||
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
r = parse_sec(optarg, &arg_delay);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse delay parameter '%s': %m", optarg);
|
||||
if (arg_delay <= 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Invalid delay parameter '%s'",
|
||||
optarg);
|
||||
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
r = safe_atou(optarg, &arg_iterations);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse iterations parameter '%s': %m", optarg);
|
||||
|
||||
break;
|
||||
|
||||
case '1':
|
||||
arg_iterations = 1;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
arg_batch = true;
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
arg_raw = true;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
arg_order = ORDER_PATH;
|
||||
break;
|
||||
|
||||
case 't':
|
||||
arg_order = ORDER_TASKS;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
arg_order = ORDER_CPU;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
arg_order = ORDER_MEMORY;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
arg_order = ORDER_IO;
|
||||
break;
|
||||
|
||||
case ARG_ORDER:
|
||||
arg_order = order_from_string(optarg);
|
||||
OPTION_LONG("order", "PROPERTY",
|
||||
"Order by specified property (path, tasks, cpu, memory, io)"):
|
||||
arg_order = order_from_string(arg);
|
||||
if (arg_order < 0)
|
||||
return log_error_errno(arg_order,
|
||||
"Invalid argument to --order=: %s",
|
||||
optarg);
|
||||
arg);
|
||||
break;
|
||||
|
||||
case 'k':
|
||||
arg_count = COUNT_ALL_PROCESSES;
|
||||
OPTION_SHORT('p', NULL, "Same as --order=path, order by path"):
|
||||
arg_order = ORDER_PATH;
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
OPTION_SHORT('t', NULL, "Same as --order=tasks, order by number of tasks/processes"):
|
||||
arg_order = ORDER_TASKS;
|
||||
break;
|
||||
|
||||
OPTION_SHORT('c', NULL, "Same as --order=cpu, order by CPU load"):
|
||||
arg_order = ORDER_CPU;
|
||||
break;
|
||||
|
||||
OPTION_SHORT('m', NULL, "Same as --order=memory, order by memory load"):
|
||||
arg_order = ORDER_MEMORY;
|
||||
break;
|
||||
|
||||
OPTION_SHORT('i', NULL, "Same as --order=io, order by IO load"):
|
||||
arg_order = ORDER_IO;
|
||||
break;
|
||||
|
||||
OPTION('r', "raw", NULL, "Provide raw (not human-readable) numbers"):
|
||||
arg_raw = true;
|
||||
break;
|
||||
|
||||
OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "cpu", "percentage|time",
|
||||
"Show CPU usage as percentage (default) or time"):
|
||||
if (arg) {
|
||||
arg_cpu_type = cpu_type_from_string(arg);
|
||||
if (arg_cpu_type < 0)
|
||||
return log_error_errno(arg_cpu_type,
|
||||
"Unknown argument to --cpu=: %s",
|
||||
arg);
|
||||
} else
|
||||
arg_cpu_type = CPU_TIME;
|
||||
break;
|
||||
|
||||
OPTION_SHORT('P', NULL, "Count userspace processes instead of tasks (excl. kernel)"):
|
||||
arg_count = COUNT_USERSPACE_PROCESSES;
|
||||
break;
|
||||
|
||||
case ARG_RECURSIVE:
|
||||
r = parse_boolean_argument("--recursive=", optarg, &arg_recursive);
|
||||
OPTION_SHORT('k', NULL, "Count all processes instead of tasks (incl. kernel)"):
|
||||
arg_count = COUNT_ALL_PROCESSES;
|
||||
break;
|
||||
|
||||
OPTION_LONG("recursive", "BOOL", "Sum up process count recursively"):
|
||||
r = parse_boolean_argument("--recursive=", arg, &arg_recursive);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
arg_recursive_unset = !r;
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
arg_machine = optarg;
|
||||
OPTION('d', "delay", "DELAY", "Delay between updates"):
|
||||
r = parse_sec(arg, &arg_delay);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse delay parameter '%s': %m", arg);
|
||||
if (arg_delay <= 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Invalid delay parameter '%s'",
|
||||
arg);
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
OPTION('n', "iterations", "N", "Run for N iterations before exiting"):
|
||||
r = safe_atou(arg, &arg_iterations);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse iterations parameter '%s': %m", arg);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
OPTION_SHORT('1', NULL, "Shortcut for --iterations=1"):
|
||||
arg_iterations = 1;
|
||||
break;
|
||||
|
||||
OPTION('b', "batch", NULL, "Run in batch mode, accepting no input"):
|
||||
arg_batch = true;
|
||||
break;
|
||||
|
||||
OPTION_LONG("depth", "DEPTH",
|
||||
"Maximum traversal depth (default: "STRINGIFY(DEFAULT_MAXIMUM_DEPTH)")"):
|
||||
r = safe_atou(arg, &arg_depth);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse depth parameter '%s': %m", arg);
|
||||
break;
|
||||
|
||||
OPTION_COMMON_MACHINE:
|
||||
arg_machine = arg;
|
||||
break;
|
||||
}
|
||||
|
||||
if (optind == argc - 1)
|
||||
arg_root = argv[optind];
|
||||
else if (optind < argc)
|
||||
size_t n_args = option_parser_get_n_args(&state);
|
||||
if (n_args > 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Too many arguments.");
|
||||
if (n_args == 1)
|
||||
arg_root = option_parser_get_args(&state)[0];
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
+133
-212
@@ -1,6 +1,5 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
#include <linux/magic.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -24,6 +23,7 @@
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "memory-util.h"
|
||||
#include "options.h"
|
||||
#include "pager.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
@@ -305,6 +305,8 @@ static int add_credentials_to_table(Table *t, bool encrypted) {
|
||||
return 1; /* Creds dir set */
|
||||
}
|
||||
|
||||
VERB(verb_list, "list", NULL, VERB_ANY, 1, VERB_DEFAULT,
|
||||
"Show list of passed credentials");
|
||||
static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(table_unrefp) Table *t = NULL;
|
||||
int r, q;
|
||||
@@ -465,6 +467,8 @@ static int write_blob(FILE *f, const void *data, size_t size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
VERB(verb_cat, "cat", "CREDENTIAL...", 2, VERB_ANY, 0,
|
||||
"Show contents of specified credentials");
|
||||
static int verb_cat(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
usec_t timestamp;
|
||||
int r, ret = 0;
|
||||
@@ -551,6 +555,8 @@ static int verb_cat(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
VERB(verb_encrypt, "encrypt", "INPUT OUTPUT", 3, 3, 0,
|
||||
"Encrypt plaintext credential file and write to ciphertext credential file");
|
||||
static int verb_encrypt(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(iovec_done_erase) struct iovec plaintext = {}, output = {};
|
||||
_cleanup_free_ char *base64_buf = NULL, *fname = NULL;
|
||||
@@ -659,6 +665,8 @@ static int verb_encrypt(int argc, char *argv[], uintptr_t _data, void *userdata)
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
VERB(verb_decrypt, "decrypt", "INPUT [OUTPUT]", 2, 3, 0,
|
||||
"Decrypt ciphertext credential file and write to plaintext credential file");
|
||||
static int verb_decrypt(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(iovec_done_erase) struct iovec input = {}, plaintext = {};
|
||||
_cleanup_free_ char *fname = NULL;
|
||||
@@ -741,6 +749,8 @@ static int verb_decrypt(int argc, char *argv[], uintptr_t _data, void *userdata)
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
VERB_NOARG(verb_setup, "setup",
|
||||
"Generate credentials host key, if not existing yet");
|
||||
static int verb_setup(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(iovec_done_erase) struct iovec host_key = {};
|
||||
int r;
|
||||
@@ -754,212 +764,169 @@ static int verb_setup(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/* For backward compatibility. Hidden from help. */
|
||||
VERB(verb_has_tpm2, "has-tpm2", NULL, VERB_ANY, 1, 0, /* help= */ NULL);
|
||||
static int verb_has_tpm2(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
if (!arg_quiet)
|
||||
log_notice("The 'systemd-creds %1$s' command has been replaced by 'systemd-analyze %1$s'. Redirecting invocation.", argv[optind]);
|
||||
log_notice("The 'systemd-creds %1$s' command has been replaced by 'systemd-analyze %1$s'. Redirecting invocation.", argv[0]);
|
||||
|
||||
return verb_has_tpm2_generic(arg_quiet);
|
||||
}
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
_cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("systemd-creds", "1", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%1$s [OPTIONS...] COMMAND ...\n"
|
||||
"\n%5$sDisplay and Process Credentials.%6$s\n"
|
||||
"\n%3$sCommands:%4$s\n"
|
||||
" list Show list of passed credentials\n"
|
||||
" cat CREDENTIAL... Show contents of specified credentials\n"
|
||||
" setup Generate credentials host key, if not existing yet\n"
|
||||
" encrypt INPUT OUTPUT Encrypt plaintext credential file and write to\n"
|
||||
" ciphertext credential file\n"
|
||||
" decrypt INPUT [OUTPUT] Decrypt ciphertext credential file and write to\n"
|
||||
" plaintext credential file\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"
|
||||
" --json=pretty|short|off\n"
|
||||
" Generate JSON output\n"
|
||||
" --system Show credentials passed to system\n"
|
||||
" --transcode=base64|unbase64|hex|unhex\n"
|
||||
" Transcode credential data\n"
|
||||
" --newline=auto|yes|no\n"
|
||||
" Suffix output with newline\n"
|
||||
" -p --pretty Output as SetCredentialEncrypted= line\n"
|
||||
" --name=NAME Override filename included in encrypted credential\n"
|
||||
" --timestamp=TIME Include specified timestamp in encrypted credential\n"
|
||||
" --not-after=TIME Include specified invalidation time in encrypted\n"
|
||||
" credential\n"
|
||||
" --with-key=host|tpm2|host+tpm2|null|auto|auto-initrd\n"
|
||||
" Which keys to encrypt with\n"
|
||||
" -H Shortcut for --with-key=host\n"
|
||||
" -T Shortcut for --with-key=tpm2\n"
|
||||
" --tpm2-device=PATH\n"
|
||||
" Pick TPM2 device\n"
|
||||
" --tpm2-pcrs=PCR1+PCR2+PCR3+…\n"
|
||||
" Specify TPM2 PCRs to seal against (fixed hash)\n"
|
||||
" --tpm2-public-key=PATH\n"
|
||||
" Specify PEM certificate to seal against\n"
|
||||
" --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n"
|
||||
" Specify TPM2 PCRs to seal against (public key)\n"
|
||||
" --tpm2-signature=PATH\n"
|
||||
" Specify signature for public key PCR policy\n"
|
||||
" --user Select user-scoped credential encryption\n"
|
||||
" --uid=UID Select user for scoped credentials\n"
|
||||
" --allow-null Allow decrypting credentials with null key\n"
|
||||
" --refuse-null Refuse decrypting credentials with null key\n"
|
||||
"\nSee the %2$s for details.\n",
|
||||
r = verbs_get_help_table(&verbs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = option_parser_get_help_table(&options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
(void) table_sync_column_widths(0, verbs, options);
|
||||
|
||||
printf("%s [OPTIONS...] COMMAND ...\n\n"
|
||||
"%sDisplay and Process Credentials.%s\n"
|
||||
"\n%sCommands:%s\n",
|
||||
program_invocation_short_name,
|
||||
link,
|
||||
ansi_underline(),
|
||||
ansi_normal(),
|
||||
ansi_highlight(),
|
||||
ansi_normal(),
|
||||
ansi_underline(),
|
||||
ansi_normal());
|
||||
|
||||
r = table_print_or_warn(verbs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("\n%sOptions:%s\n",
|
||||
ansi_underline(),
|
||||
ansi_normal());
|
||||
|
||||
r = table_print_or_warn(options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("\nSee the %s for details.\n", link);
|
||||
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_JSON,
|
||||
ARG_SYSTEM,
|
||||
ARG_TRANSCODE,
|
||||
ARG_NEWLINE,
|
||||
ARG_WITH_KEY,
|
||||
ARG_TPM2_DEVICE,
|
||||
ARG_TPM2_PCRS,
|
||||
ARG_TPM2_PUBLIC_KEY,
|
||||
ARG_TPM2_PUBLIC_KEY_PCRS,
|
||||
ARG_TPM2_SIGNATURE,
|
||||
ARG_NAME,
|
||||
ARG_TIMESTAMP,
|
||||
ARG_NOT_AFTER,
|
||||
ARG_USER,
|
||||
ARG_UID,
|
||||
ARG_ALLOW_NULL,
|
||||
ARG_REFUSE_NULL,
|
||||
ARG_NO_ASK_PASSWORD,
|
||||
};
|
||||
|
||||
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 },
|
||||
{ "json", required_argument, NULL, ARG_JSON },
|
||||
{ "system", no_argument, NULL, ARG_SYSTEM },
|
||||
{ "transcode", required_argument, NULL, ARG_TRANSCODE },
|
||||
{ "newline", required_argument, NULL, ARG_NEWLINE },
|
||||
{ "pretty", no_argument, NULL, 'p' },
|
||||
{ "with-key", required_argument, NULL, ARG_WITH_KEY },
|
||||
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
|
||||
{ "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
|
||||
{ "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY },
|
||||
{ "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS },
|
||||
{ "tpm2-signature", required_argument, NULL, ARG_TPM2_SIGNATURE },
|
||||
{ "name", required_argument, NULL, ARG_NAME },
|
||||
{ "timestamp", required_argument, NULL, ARG_TIMESTAMP },
|
||||
{ "not-after", required_argument, NULL, ARG_NOT_AFTER },
|
||||
{ "quiet", no_argument, NULL, 'q' },
|
||||
{ "user", no_argument, NULL, ARG_USER },
|
||||
{ "uid", required_argument, NULL, ARG_UID },
|
||||
{ "allow-null", no_argument, NULL, ARG_ALLOW_NULL },
|
||||
{ "refuse-null", no_argument, NULL, ARG_REFUSE_NULL },
|
||||
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
|
||||
{}
|
||||
};
|
||||
|
||||
int c, r;
|
||||
VERB_COMMON_HELP(help);
|
||||
|
||||
static int parse_argv(int argc, char *argv[], char ***ret_args) {
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "hHTpq", options, NULL)) >= 0) {
|
||||
OptionParser state = { argc, argv };
|
||||
const char *arg;
|
||||
int r;
|
||||
|
||||
FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c)
|
||||
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_JSON:
|
||||
r = parse_json_argument(optarg, &arg_json_format_flags);
|
||||
OPTION_COMMON_JSON:
|
||||
r = parse_json_argument(arg, &arg_json_format_flags);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_SYSTEM:
|
||||
OPTION_LONG("system", NULL, "Show credentials passed to system"):
|
||||
arg_system = true;
|
||||
break;
|
||||
|
||||
case ARG_TRANSCODE:
|
||||
if (streq(optarg, "help")) {
|
||||
OPTION_LONG("transcode", "METHOD",
|
||||
"Transcode credential data (base64, unbase64, hex, unhex)"):
|
||||
if (streq(arg, "help")) {
|
||||
if (arg_legend)
|
||||
puts("Supported transcode types:");
|
||||
|
||||
return DUMP_STRING_TABLE(transcode_mode, TranscodeMode, _TRANSCODE_MAX);
|
||||
}
|
||||
|
||||
if (parse_boolean(optarg) == 0) /* If specified as "false", turn transcoding off */
|
||||
if (parse_boolean(arg) == 0) /* If specified as "false", turn transcoding off */
|
||||
arg_transcode = TRANSCODE_OFF;
|
||||
else {
|
||||
TranscodeMode m;
|
||||
|
||||
m = transcode_mode_from_string(optarg);
|
||||
m = transcode_mode_from_string(arg);
|
||||
if (m < 0)
|
||||
return log_error_errno(m, "Failed to parse transcode mode: %m");
|
||||
|
||||
arg_transcode = m;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ARG_NEWLINE:
|
||||
r = parse_tristate_argument_with_auto("--newline=", optarg, &arg_newline);
|
||||
OPTION_LONG("newline", "auto|yes|no", "Suffix output with newline"):
|
||||
r = parse_tristate_argument_with_auto("--newline=", arg, &arg_newline);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
OPTION('p', "pretty", NULL, "Output as SetCredentialEncrypted= line"):
|
||||
arg_pretty = true;
|
||||
break;
|
||||
|
||||
case ARG_WITH_KEY:
|
||||
if (streq(optarg, "help")) {
|
||||
OPTION_LONG("name", "NAME",
|
||||
"Override filename included in encrypted credential"):
|
||||
if (isempty(arg)) {
|
||||
arg_name = NULL;
|
||||
arg_name_any = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!credential_name_valid(arg))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", arg);
|
||||
|
||||
arg_name = arg;
|
||||
arg_name_any = false;
|
||||
break;
|
||||
|
||||
OPTION_LONG("timestamp", "TIME",
|
||||
"Include specified timestamp in encrypted credential"):
|
||||
r = parse_timestamp(arg, &arg_timestamp);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse timestamp: %s", arg);
|
||||
break;
|
||||
|
||||
OPTION_LONG("not-after", "TIME",
|
||||
"Include specified invalidation time in encrypted credential"):
|
||||
r = parse_timestamp(arg, &arg_not_after);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --not-after= timestamp: %s", arg);
|
||||
break;
|
||||
|
||||
OPTION_LONG("with-key", "KEY",
|
||||
"Which keys to encrypt with (host, tpm2, host+tpm2, null, auto, auto-initrd)"):
|
||||
if (streq(arg, "help")) {
|
||||
if (arg_legend)
|
||||
puts("Supported key types:");
|
||||
return DUMP_STRING_TABLE(cred_key_type, CredKeyType, _CRED_KEY_TYPE_MAX);
|
||||
}
|
||||
|
||||
if (isempty(optarg))
|
||||
if (isempty(arg))
|
||||
arg_with_key = _CRED_AUTO;
|
||||
else {
|
||||
CredKeyType t = cred_key_type_from_string(optarg);
|
||||
CredKeyType t = cred_key_type_from_string(arg);
|
||||
if (t < 0)
|
||||
return log_error_errno(t, "Failed to parse key type: %m");
|
||||
|
||||
@@ -967,90 +934,63 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'H':
|
||||
OPTION_SHORT('H', NULL, "Shortcut for --with-key=host"):
|
||||
arg_with_key = CRED_AES256_GCM_BY_HOST;
|
||||
break;
|
||||
|
||||
case 'T':
|
||||
OPTION_SHORT('T', NULL, "Shortcut for --with-key=tpm2"):
|
||||
arg_with_key = _CRED_AUTO_TPM2;
|
||||
break;
|
||||
|
||||
case ARG_TPM2_DEVICE:
|
||||
if (streq(optarg, "list"))
|
||||
OPTION_LONG("tpm2-device", "PATH", "Pick TPM2 device"):
|
||||
if (streq(arg, "list"))
|
||||
return tpm2_list_devices(arg_legend, arg_quiet);
|
||||
|
||||
arg_tpm2_device = streq(optarg, "auto") ? NULL : optarg;
|
||||
arg_tpm2_device = streq(arg, "auto") ? NULL : arg;
|
||||
break;
|
||||
|
||||
case ARG_TPM2_PCRS: /* For fixed hash PCR policies only */
|
||||
r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_pcr_mask);
|
||||
OPTION_LONG("tpm2-pcrs", "PCR1+PCR2+PCR3+…",
|
||||
"Specify TPM2 PCRs to seal against (fixed hash)"):
|
||||
/* For fixed hash PCR policies only */
|
||||
r = tpm2_parse_pcr_argument_to_mask(arg, &arg_tpm2_pcr_mask);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_TPM2_PUBLIC_KEY:
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key);
|
||||
OPTION_LONG("tpm2-public-key", "PATH",
|
||||
"Specify PEM certificate to seal against"):
|
||||
r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_public_key);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_TPM2_PUBLIC_KEY_PCRS: /* For public key PCR policies only */
|
||||
r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask);
|
||||
OPTION_LONG("tpm2-public-key-pcrs", "PCR1+PCR2+…",
|
||||
"Specify TPM2 PCRs to seal against (public key)"):
|
||||
/* For public key PCR policies only */
|
||||
r = tpm2_parse_pcr_argument_to_mask(arg, &arg_tpm2_public_key_pcr_mask);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_TPM2_SIGNATURE:
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_signature);
|
||||
OPTION_LONG("tpm2-signature", "PATH",
|
||||
"Specify signature for public key PCR policy"):
|
||||
r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_signature);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_NAME:
|
||||
if (isempty(optarg)) {
|
||||
arg_name = NULL;
|
||||
arg_name_any = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!credential_name_valid(optarg))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", optarg);
|
||||
|
||||
arg_name = optarg;
|
||||
arg_name_any = false;
|
||||
break;
|
||||
|
||||
case ARG_TIMESTAMP:
|
||||
r = parse_timestamp(optarg, &arg_timestamp);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse timestamp: %s", optarg);
|
||||
|
||||
break;
|
||||
|
||||
case ARG_NOT_AFTER:
|
||||
r = parse_timestamp(optarg, &arg_not_after);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --not-after= timestamp: %s", optarg);
|
||||
|
||||
break;
|
||||
|
||||
case ARG_USER:
|
||||
OPTION_LONG("user", NULL, "Select user-scoped credential encryption"):
|
||||
if (!uid_is_valid(arg_uid))
|
||||
arg_uid = getuid();
|
||||
|
||||
break;
|
||||
|
||||
case ARG_UID:
|
||||
if (isempty(optarg))
|
||||
OPTION_LONG("uid", "UID", "Select user for scoped credentials"):
|
||||
if (isempty(arg))
|
||||
arg_uid = UID_INVALID;
|
||||
else if (streq(optarg, "self"))
|
||||
else if (streq(arg, "self"))
|
||||
arg_uid = getuid();
|
||||
else {
|
||||
const char *name = optarg;
|
||||
const char *name = arg;
|
||||
|
||||
r = get_user_creds(
|
||||
&name,
|
||||
@@ -1061,35 +1001,30 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
/* flags= */ 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to resolve user '%s': %s",
|
||||
optarg, STRERROR_USER(r));
|
||||
arg, STRERROR_USER(r));
|
||||
}
|
||||
break;
|
||||
|
||||
case ARG_ALLOW_NULL:
|
||||
OPTION_LONG("allow-null", NULL,
|
||||
"Allow decrypting credentials with null key"):
|
||||
arg_credential_flags &= ~CREDENTIAL_REFUSE_NULL;
|
||||
arg_credential_flags |= CREDENTIAL_ALLOW_NULL;
|
||||
break;
|
||||
|
||||
case ARG_REFUSE_NULL:
|
||||
OPTION_LONG("refuse-null", NULL,
|
||||
"Refuse decrypting credentials with null key"):
|
||||
arg_credential_flags |= CREDENTIAL_REFUSE_NULL;
|
||||
arg_credential_flags &= ~CREDENTIAL_ALLOW_NULL;
|
||||
break;
|
||||
|
||||
case ARG_NO_ASK_PASSWORD:
|
||||
OPTION_COMMON_NO_ASK_PASSWORD:
|
||||
arg_ask_password = false;
|
||||
break;
|
||||
|
||||
case 'q':
|
||||
OPTION('q', "quiet", NULL, "Suppress informational messages"):
|
||||
arg_quiet = true;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
SET_FLAG(arg_credential_flags, CREDENTIAL_IPC_ALLOW_INTERACTIVE, arg_ask_password);
|
||||
|
||||
@@ -1118,25 +1053,10 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
|
||||
arg_varlink = r;
|
||||
|
||||
*ret_args = option_parser_get_args(&state);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int creds_main(int argc, char *argv[]) {
|
||||
|
||||
static const Verb verbs[] = {
|
||||
{ "list", VERB_ANY, 1, VERB_DEFAULT, verb_list },
|
||||
{ "cat", 2, VERB_ANY, 0, verb_cat },
|
||||
{ "encrypt", 3, 3, 0, verb_encrypt },
|
||||
{ "decrypt", 2, 3, 0, verb_decrypt },
|
||||
{ "setup", VERB_ANY, 1, 0, verb_setup },
|
||||
{ "help", VERB_ANY, 1, 0, verb_help },
|
||||
{ "has-tpm2", VERB_ANY, 1, 0, verb_has_tpm2 }, /* for backward compatibility */
|
||||
{}
|
||||
};
|
||||
|
||||
return dispatch_verb(argc, argv, verbs, NULL);
|
||||
}
|
||||
|
||||
#define TIMESTAMP_FRESH_MAX (30*USEC_PER_SEC)
|
||||
|
||||
static bool timestamp_is_fresh(usec_t x) {
|
||||
@@ -1552,14 +1472,15 @@ static int run(int argc, char *argv[]) {
|
||||
|
||||
log_setup();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
char **args = NULL;
|
||||
r = parse_argv(argc, argv, &args);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
if (arg_varlink)
|
||||
return vl_server();
|
||||
|
||||
return creds_main(argc, argv);
|
||||
return dispatch_verb_with_args(args, NULL);
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
|
||||
|
||||
+146
-274
@@ -1,7 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sd-bus.h"
|
||||
@@ -24,6 +23,7 @@
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "format-table.h"
|
||||
#include "fs-util.h"
|
||||
#include "glyph-util.h"
|
||||
#include "hostname-util.h"
|
||||
@@ -38,9 +38,9 @@
|
||||
#include "main-func.h"
|
||||
#include "memory-util.h"
|
||||
#include "mount-util.h"
|
||||
#include "options.h"
|
||||
#include "os-util.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
#include "password-quality-util.h"
|
||||
#include "path-util.h"
|
||||
#include "plymouth-util.h"
|
||||
@@ -1240,380 +1240,252 @@ static int process_reset(int rfd) {
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
_cleanup_(table_unrefp) Table *options = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("systemd-firstboot", "1", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%1$s [OPTIONS...]\n"
|
||||
"\n%3$sConfigures basic settings of the system.%4$s\n\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\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"
|
||||
" --locale=LOCALE Set primary locale (LANG=)\n"
|
||||
" --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
|
||||
" --keymap=KEYMAP Set keymap\n"
|
||||
" --timezone=TIMEZONE Set timezone\n"
|
||||
" --hostname=NAME Set hostname\n"
|
||||
" --setup-machine-id Set a random machine ID\n"
|
||||
" --machine-id=ID Set specified machine ID\n"
|
||||
" --root-password=PASSWORD Set root password from plaintext password\n"
|
||||
" --root-password-file=FILE Set root password from file\n"
|
||||
" --root-password-hashed=HASH Set root password from hashed password\n"
|
||||
" --root-shell=SHELL Set root shell\n"
|
||||
" --kernel-command-line=CMDLINE\n"
|
||||
" Set kernel command line\n"
|
||||
" --prompt-locale Prompt the user for locale settings\n"
|
||||
" --prompt-keymap Prompt the user for keymap settings\n"
|
||||
" --prompt-keymap-auto Prompt the user for keymap settings if invoked\n"
|
||||
" on local console\n"
|
||||
" --prompt-timezone Prompt the user for timezone\n"
|
||||
" --prompt-hostname Prompt the user for hostname\n"
|
||||
" --prompt-root-password Prompt the user for root password\n"
|
||||
" --prompt-root-shell Prompt the user for root shell\n"
|
||||
" --prompt Prompt for all of the above\n"
|
||||
" --copy-locale Copy locale from host\n"
|
||||
" --copy-keymap Copy keymap from host\n"
|
||||
" --copy-timezone Copy timezone from host\n"
|
||||
" --copy-root-password Copy root password from host\n"
|
||||
" --copy-root-shell Copy root shell from host\n"
|
||||
" --copy Copy locale, keymap, timezone, root password\n"
|
||||
" --force Overwrite existing files\n"
|
||||
" --delete-root-password Delete root password\n"
|
||||
" --welcome=no Disable the welcome text\n"
|
||||
" --chrome=no Don't show color bar at top and bottom of\n"
|
||||
" terminal\n"
|
||||
" --mute-console=yes Tell kernel/PID 1 to not write to the console\n"
|
||||
" while running\n"
|
||||
" --reset Remove existing files\n"
|
||||
"\nSee the %2$s for details.\n",
|
||||
r = option_parser_get_help_table(&options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("%s [OPTIONS...]\n\n"
|
||||
"%sConfigures basic settings of the system.%s\n\n",
|
||||
program_invocation_short_name,
|
||||
link,
|
||||
ansi_highlight(),
|
||||
ansi_normal());
|
||||
|
||||
r = table_print_or_warn(options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("\nSee the %s for details.\n", link);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
enum {
|
||||
ARG_VERSION = 0x100,
|
||||
ARG_ROOT,
|
||||
ARG_IMAGE,
|
||||
ARG_IMAGE_POLICY,
|
||||
ARG_LOCALE,
|
||||
ARG_LOCALE_MESSAGES,
|
||||
ARG_KEYMAP,
|
||||
ARG_TIMEZONE,
|
||||
ARG_HOSTNAME,
|
||||
ARG_SETUP_MACHINE_ID,
|
||||
ARG_MACHINE_ID,
|
||||
ARG_ROOT_PASSWORD,
|
||||
ARG_ROOT_PASSWORD_FILE,
|
||||
ARG_ROOT_PASSWORD_HASHED,
|
||||
ARG_ROOT_SHELL,
|
||||
ARG_KERNEL_COMMAND_LINE,
|
||||
ARG_PROMPT,
|
||||
ARG_PROMPT_LOCALE,
|
||||
ARG_PROMPT_KEYMAP,
|
||||
ARG_PROMPT_KEYMAP_AUTO,
|
||||
ARG_PROMPT_TIMEZONE,
|
||||
ARG_PROMPT_HOSTNAME,
|
||||
ARG_PROMPT_ROOT_PASSWORD,
|
||||
ARG_PROMPT_ROOT_SHELL,
|
||||
ARG_COPY,
|
||||
ARG_COPY_LOCALE,
|
||||
ARG_COPY_KEYMAP,
|
||||
ARG_COPY_TIMEZONE,
|
||||
ARG_COPY_ROOT_PASSWORD,
|
||||
ARG_COPY_ROOT_SHELL,
|
||||
ARG_FORCE,
|
||||
ARG_DELETE_ROOT_PASSWORD,
|
||||
ARG_WELCOME,
|
||||
ARG_CHROME,
|
||||
ARG_RESET,
|
||||
ARG_MUTE_CONSOLE,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "root", required_argument, NULL, ARG_ROOT },
|
||||
{ "image", required_argument, NULL, ARG_IMAGE },
|
||||
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
|
||||
{ "locale", required_argument, NULL, ARG_LOCALE },
|
||||
{ "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES },
|
||||
{ "keymap", required_argument, NULL, ARG_KEYMAP },
|
||||
{ "timezone", required_argument, NULL, ARG_TIMEZONE },
|
||||
{ "hostname", required_argument, NULL, ARG_HOSTNAME },
|
||||
{ "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID },
|
||||
{ "machine-id", required_argument, NULL, ARG_MACHINE_ID },
|
||||
{ "root-password", required_argument, NULL, ARG_ROOT_PASSWORD },
|
||||
{ "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE },
|
||||
{ "root-password-hashed", required_argument, NULL, ARG_ROOT_PASSWORD_HASHED },
|
||||
{ "root-shell", required_argument, NULL, ARG_ROOT_SHELL },
|
||||
{ "kernel-command-line", required_argument, NULL, ARG_KERNEL_COMMAND_LINE },
|
||||
{ "prompt", no_argument, NULL, ARG_PROMPT },
|
||||
{ "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE },
|
||||
{ "prompt-keymap", no_argument, NULL, ARG_PROMPT_KEYMAP },
|
||||
{ "prompt-keymap-auto", no_argument, NULL, ARG_PROMPT_KEYMAP_AUTO },
|
||||
{ "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE },
|
||||
{ "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME },
|
||||
{ "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD },
|
||||
{ "prompt-root-shell", no_argument, NULL, ARG_PROMPT_ROOT_SHELL },
|
||||
{ "copy", no_argument, NULL, ARG_COPY },
|
||||
{ "copy-locale", no_argument, NULL, ARG_COPY_LOCALE },
|
||||
{ "copy-keymap", no_argument, NULL, ARG_COPY_KEYMAP },
|
||||
{ "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE },
|
||||
{ "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD },
|
||||
{ "copy-root-shell", no_argument, NULL, ARG_COPY_ROOT_SHELL },
|
||||
{ "force", no_argument, NULL, ARG_FORCE },
|
||||
{ "delete-root-password", no_argument, NULL, ARG_DELETE_ROOT_PASSWORD },
|
||||
{ "welcome", required_argument, NULL, ARG_WELCOME },
|
||||
{ "chrome", required_argument, NULL, ARG_CHROME },
|
||||
{ "reset", no_argument, NULL, ARG_RESET },
|
||||
{ "mute-console", required_argument, NULL, ARG_MUTE_CONSOLE },
|
||||
{}
|
||||
};
|
||||
|
||||
int r, c;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
|
||||
OptionParser state = { argc, argv };
|
||||
const char *arg;
|
||||
int r;
|
||||
|
||||
FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c)
|
||||
switch (c) {
|
||||
|
||||
case 'h':
|
||||
OPTION_COMMON_HELP:
|
||||
return help();
|
||||
|
||||
case ARG_VERSION:
|
||||
OPTION_COMMON_VERSION:
|
||||
return version();
|
||||
|
||||
case ARG_ROOT:
|
||||
r = parse_path_argument(optarg, true, &arg_root);
|
||||
OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"):
|
||||
r = parse_path_argument(arg, true, &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(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(arg, &arg_image_policy);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_LOCALE:
|
||||
r = free_and_strdup(&arg_locale, optarg);
|
||||
OPTION_LONG("locale", "LOCALE", "Set primary locale (LANG=)"):
|
||||
r = free_and_strdup_warn(&arg_locale, arg);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_LOCALE_MESSAGES:
|
||||
r = free_and_strdup(&arg_locale_messages, optarg);
|
||||
OPTION_LONG("locale-messages", "LOCALE", "Set message locale (LC_MESSAGES=)"):
|
||||
r = free_and_strdup_warn(&arg_locale_messages, arg);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_KEYMAP:
|
||||
if (!keymap_is_valid(optarg))
|
||||
OPTION_LONG("keymap", "KEYMAP", "Set keymap"):
|
||||
if (!keymap_is_valid(arg))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Keymap %s is not valid.", optarg);
|
||||
"Keymap %s is not valid.", arg);
|
||||
|
||||
r = free_and_strdup(&arg_keymap, optarg);
|
||||
r = free_and_strdup_warn(&arg_keymap, arg);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_TIMEZONE:
|
||||
if (!timezone_is_valid(optarg, LOG_ERR))
|
||||
OPTION_LONG("timezone", "TIMEZONE", "Set timezone"):
|
||||
if (!timezone_is_valid(arg, LOG_ERR))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Timezone %s is not valid.", optarg);
|
||||
"Timezone %s is not valid.", arg);
|
||||
|
||||
r = free_and_strdup(&arg_timezone, optarg);
|
||||
r = free_and_strdup_warn(&arg_timezone, arg);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_ROOT_PASSWORD:
|
||||
r = free_and_strdup(&arg_root_password, optarg);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
arg_root_password_is_hashed = false;
|
||||
break;
|
||||
|
||||
case ARG_ROOT_PASSWORD_FILE:
|
||||
arg_root_password = mfree(arg_root_password);
|
||||
|
||||
r = read_one_line_file(optarg, &arg_root_password);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read %s: %m", optarg);
|
||||
|
||||
arg_root_password_is_hashed = false;
|
||||
break;
|
||||
|
||||
case ARG_ROOT_PASSWORD_HASHED:
|
||||
r = free_and_strdup(&arg_root_password, optarg);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
arg_root_password_is_hashed = true;
|
||||
break;
|
||||
|
||||
case ARG_ROOT_SHELL:
|
||||
r = free_and_strdup(&arg_root_shell, optarg);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
break;
|
||||
|
||||
case ARG_HOSTNAME:
|
||||
if (!hostname_is_valid(optarg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK))
|
||||
OPTION_LONG("hostname", "NAME", "Set hostname"):
|
||||
if (!hostname_is_valid(arg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Host name %s is not valid.", optarg);
|
||||
"Host name %s is not valid.", arg);
|
||||
|
||||
r = free_and_strdup(&arg_hostname, optarg);
|
||||
r = free_and_strdup_warn(&arg_hostname, arg);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
return r;
|
||||
|
||||
hostname_cleanup(arg_hostname);
|
||||
break;
|
||||
|
||||
case ARG_SETUP_MACHINE_ID:
|
||||
OPTION_LONG("setup-machine-id", NULL, "Set a random machine ID"):
|
||||
r = sd_id128_randomize(&arg_machine_id);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate randomized machine ID: %m");
|
||||
|
||||
break;
|
||||
|
||||
case ARG_MACHINE_ID:
|
||||
r = sd_id128_from_string(optarg, &arg_machine_id);
|
||||
OPTION_LONG("machine-id", "ID", "Set specified machine ID"):
|
||||
r = sd_id128_from_string(arg, &arg_machine_id);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse machine id %s.", optarg);
|
||||
|
||||
return log_error_errno(r, "Failed to parse machine id %s.", arg);
|
||||
break;
|
||||
|
||||
case ARG_KERNEL_COMMAND_LINE:
|
||||
r = free_and_strdup(&arg_kernel_cmdline, optarg);
|
||||
OPTION_LONG("root-password", "PASSWORD", "Set root password from plaintext password"):
|
||||
r = free_and_strdup_warn(&arg_root_password, arg);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
return r;
|
||||
|
||||
arg_root_password_is_hashed = false;
|
||||
break;
|
||||
|
||||
case ARG_PROMPT:
|
||||
OPTION_LONG("root-password-file", "FILE", "Set root password from file"):
|
||||
arg_root_password = mfree(arg_root_password);
|
||||
|
||||
r = read_one_line_file(arg, &arg_root_password);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read %s: %m", arg);
|
||||
|
||||
arg_root_password_is_hashed = false;
|
||||
break;
|
||||
|
||||
OPTION_LONG("root-password-hashed", "HASH", "Set root password from hashed password"):
|
||||
r = free_and_strdup_warn(&arg_root_password, arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
arg_root_password_is_hashed = true;
|
||||
break;
|
||||
|
||||
OPTION_LONG("root-shell", "SHELL", "Set root shell"):
|
||||
r = free_and_strdup_warn(&arg_root_shell, arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
OPTION_LONG("kernel-command-line", "CMDLINE", "Set kernel command line"):
|
||||
r = free_and_strdup_warn(&arg_kernel_cmdline, arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
OPTION_LONG("prompt-locale", NULL, "Prompt the user for locale settings"):
|
||||
arg_prompt_locale = true;
|
||||
break;
|
||||
|
||||
OPTION_LONG("prompt-keymap", NULL, "Prompt the user for keymap settings"):
|
||||
arg_prompt_keymap = true;
|
||||
arg_prompt_keymap_auto = false;
|
||||
break;
|
||||
|
||||
OPTION_LONG("prompt-keymap-auto", NULL,
|
||||
"Prompt the user for keymap settings if invoked on local console"):
|
||||
arg_prompt_keymap_auto = true;
|
||||
break;
|
||||
|
||||
OPTION_LONG("prompt-timezone", NULL, "Prompt the user for timezone"):
|
||||
arg_prompt_timezone = true;
|
||||
break;
|
||||
|
||||
OPTION_LONG("prompt-hostname", NULL, "Prompt the user for hostname"):
|
||||
arg_prompt_hostname = true;
|
||||
break;
|
||||
|
||||
OPTION_LONG("prompt-root-password", NULL, "Prompt the user for root password"):
|
||||
arg_prompt_root_password = true;
|
||||
break;
|
||||
|
||||
OPTION_LONG("prompt-root-shell", NULL, "Prompt the user for root shell"):
|
||||
arg_prompt_root_shell = true;
|
||||
break;
|
||||
|
||||
OPTION_LONG("prompt", NULL, "Prompt for all of the above"):
|
||||
arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname =
|
||||
arg_prompt_root_password = arg_prompt_root_shell = true;
|
||||
arg_prompt_keymap_auto = false;
|
||||
break;
|
||||
|
||||
case ARG_PROMPT_LOCALE:
|
||||
arg_prompt_locale = true;
|
||||
OPTION_LONG("copy-locale", NULL, "Copy locale from host"):
|
||||
arg_copy_locale = true;
|
||||
break;
|
||||
|
||||
case ARG_PROMPT_KEYMAP:
|
||||
arg_prompt_keymap = true;
|
||||
arg_prompt_keymap_auto = false;
|
||||
OPTION_LONG("copy-keymap", NULL, "Copy keymap from host"):
|
||||
arg_copy_keymap = true;
|
||||
break;
|
||||
|
||||
case ARG_PROMPT_KEYMAP_AUTO:
|
||||
arg_prompt_keymap_auto = true;
|
||||
OPTION_LONG("copy-timezone", NULL, "Copy timezone from host"):
|
||||
arg_copy_timezone = true;
|
||||
break;
|
||||
|
||||
case ARG_PROMPT_TIMEZONE:
|
||||
arg_prompt_timezone = true;
|
||||
OPTION_LONG("copy-root-password", NULL, "Copy root password from host"):
|
||||
arg_copy_root_password = true;
|
||||
break;
|
||||
|
||||
case ARG_PROMPT_HOSTNAME:
|
||||
arg_prompt_hostname = true;
|
||||
OPTION_LONG("copy-root-shell", NULL, "Copy root shell from host"):
|
||||
arg_copy_root_shell = true;
|
||||
break;
|
||||
|
||||
case ARG_PROMPT_ROOT_PASSWORD:
|
||||
arg_prompt_root_password = true;
|
||||
break;
|
||||
|
||||
case ARG_PROMPT_ROOT_SHELL:
|
||||
arg_prompt_root_shell = true;
|
||||
break;
|
||||
|
||||
case ARG_COPY:
|
||||
OPTION_LONG("copy", NULL, "Copy all of the above"):
|
||||
arg_copy_locale = arg_copy_keymap = arg_copy_timezone = arg_copy_root_password =
|
||||
arg_copy_root_shell = true;
|
||||
break;
|
||||
|
||||
case ARG_COPY_LOCALE:
|
||||
arg_copy_locale = true;
|
||||
break;
|
||||
|
||||
case ARG_COPY_KEYMAP:
|
||||
arg_copy_keymap = true;
|
||||
break;
|
||||
|
||||
case ARG_COPY_TIMEZONE:
|
||||
arg_copy_timezone = true;
|
||||
break;
|
||||
|
||||
case ARG_COPY_ROOT_PASSWORD:
|
||||
arg_copy_root_password = true;
|
||||
break;
|
||||
|
||||
case ARG_COPY_ROOT_SHELL:
|
||||
arg_copy_root_shell = true;
|
||||
break;
|
||||
|
||||
case ARG_FORCE:
|
||||
OPTION_LONG("force", NULL, "Overwrite existing files"):
|
||||
arg_force = true;
|
||||
break;
|
||||
|
||||
case ARG_DELETE_ROOT_PASSWORD:
|
||||
OPTION_LONG("delete-root-password", NULL, "Delete root password"):
|
||||
arg_delete_root_password = true;
|
||||
break;
|
||||
|
||||
case ARG_WELCOME:
|
||||
r = parse_boolean(optarg);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --welcome= argument: %s", optarg);
|
||||
|
||||
arg_welcome = r;
|
||||
break;
|
||||
|
||||
case ARG_CHROME:
|
||||
r = parse_boolean_argument("--chrome=", optarg, &arg_chrome);
|
||||
OPTION_LONG("welcome", "BOOL", "Whether to show the welcome text"):
|
||||
r = parse_boolean_argument("--welcome=", arg, &arg_welcome);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_RESET:
|
||||
OPTION_LONG("chrome", "BOOL",
|
||||
"Whether to show a color bar at top and bottom of terminal"):
|
||||
r = parse_boolean_argument("--chrome=", arg, &arg_chrome);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
OPTION_LONG("mute-console", "BOOL",
|
||||
"Whether to disallow kernel/PID 1 writes to the console while running"):
|
||||
r = parse_boolean_argument("--mute-console=", arg, &arg_mute_console);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
OPTION_LONG("reset", NULL, "Remove existing files"):
|
||||
arg_reset = true;
|
||||
break;
|
||||
|
||||
case ARG_MUTE_CONSOLE:
|
||||
r = parse_boolean_argument("--mute-console=", optarg, &arg_mute_console);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
if (arg_delete_root_password && (arg_copy_root_password || arg_root_password || arg_prompt_root_password))
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
OptionParser state = { argc, argv, /* stop_at_first_nonoption= */ true };
|
||||
OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION };
|
||||
const char *arg;
|
||||
int r;
|
||||
|
||||
|
||||
+1
-1
@@ -593,7 +593,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
OptionParser state = { argc, argv, /* stop_at_first_nonoption= */ true };
|
||||
OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION };
|
||||
const Option *opt;
|
||||
const char *arg;
|
||||
|
||||
|
||||
+34
-7
@@ -22,6 +22,7 @@ static bool option_arg_required(const Option *opt) {
|
||||
static bool option_is_metadata(const Option *opt) {
|
||||
/* A metadata entry that is not a real option, like the group marker */
|
||||
return ASSERT_PTR(opt)->flags & (OPTION_GROUP_MARKER |
|
||||
OPTION_POSITIONAL_ENTRY |
|
||||
OPTION_HELP_ENTRY |
|
||||
OPTION_HELP_ENTRY_VERBATIM);
|
||||
}
|
||||
@@ -75,6 +76,8 @@ int option_parse(
|
||||
|
||||
/* Check and initialize */
|
||||
if (state->optind == 0) {
|
||||
assert(state->mode >= 0 && state->mode < _OPTION_PARSER_MODE_MAX);
|
||||
|
||||
if (state->argc < 1 || strv_isempty(state->argv))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "argv cannot be empty");
|
||||
|
||||
@@ -87,9 +90,10 @@ int option_parse(
|
||||
const char *optname = NULL, *optval = NULL;
|
||||
_cleanup_free_ char *_optname = NULL; /* allocated option name */
|
||||
bool separate_optval = false;
|
||||
bool handling_positional_arg = false;
|
||||
|
||||
if (state->short_option_offset == 0) {
|
||||
/* Skip over non-option parameters */
|
||||
/* Handle non-option parameters */
|
||||
for (;;) {
|
||||
if (state->optind == state->argc)
|
||||
return 0;
|
||||
@@ -109,19 +113,36 @@ int option_parse(
|
||||
/* Looks like we found an option parameter */
|
||||
break;
|
||||
|
||||
if (state->stop_at_first_nonoption) {
|
||||
if (state->mode == OPTION_PARSER_STOP_AT_FIRST_NONOPTION) {
|
||||
state->parsing_stopped = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (state->mode == OPTION_PARSER_RETURN_POSITIONAL_ARGS) {
|
||||
handling_positional_arg = true;
|
||||
optval = state->argv[state->optind];
|
||||
break;
|
||||
}
|
||||
|
||||
state->optind++;
|
||||
}
|
||||
|
||||
/* Find matching option entry.
|
||||
* First, figure out if we have a long option or a short option. */
|
||||
assert(state->argv[state->optind][0] == '-');
|
||||
assert(handling_positional_arg || state->argv[state->optind][0] == '-');
|
||||
|
||||
if (state->argv[state->optind][1] == '-') {
|
||||
if (handling_positional_arg)
|
||||
/* We are supposed to return the positional arg to be handled. */
|
||||
for (option = options;; option++) {
|
||||
/* If OPTION_PARSER_RETURN_POSITIONAL_ARGS is specified,
|
||||
* OPTION_POSITIONAL must be used. */
|
||||
assert(option < options_end);
|
||||
|
||||
if (FLAGS_SET(option->flags, OPTION_POSITIONAL_ENTRY))
|
||||
break;
|
||||
}
|
||||
|
||||
else if (state->argv[state->optind][1] == '-') {
|
||||
/* We have a long option. */
|
||||
char *eq = strchr(state->argv[state->optind], '=');
|
||||
if (eq) {
|
||||
@@ -204,11 +225,11 @@ int option_parse(
|
||||
|
||||
assert(option);
|
||||
|
||||
if (optval && !option_takes_arg(option))
|
||||
if (!handling_positional_arg && optval && !option_takes_arg(option))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"%s: option '%s' doesn't allow an argument",
|
||||
program_invocation_short_name, optname);
|
||||
if (!optval && option_arg_required(option)) {
|
||||
if (!handling_positional_arg && !optval && option_arg_required(option)) {
|
||||
if (!state->argv[state->optind + 1])
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"%s: option '%s' requires an argument",
|
||||
@@ -218,7 +239,13 @@ int option_parse(
|
||||
}
|
||||
|
||||
if (state->short_option_offset == 0) {
|
||||
/* We're done with this option. Adjust the array and position. */
|
||||
/* We're done with this parameter. Adjust the array and position. */
|
||||
if (handling_positional_arg) {
|
||||
/* Sanity check */
|
||||
assert(state->positional_offset == state->optind);
|
||||
assert(!separate_optval);
|
||||
}
|
||||
|
||||
shift_arg(state->argv, state->positional_offset++, state->optind++);
|
||||
if (separate_optval)
|
||||
shift_arg(state->argv, state->positional_offset++, state->optind++);
|
||||
|
||||
+22
-7
@@ -6,10 +6,11 @@
|
||||
|
||||
typedef enum OptionFlags {
|
||||
OPTION_OPTIONAL_ARG = 1U << 0, /* Same as optional_argument in getopt */
|
||||
OPTION_STOPS_PARSING = 1U << 1, /* This option acts like "--" */
|
||||
OPTION_GROUP_MARKER = 1U << 2, /* Fake option entry to separate groups */
|
||||
OPTION_HELP_ENTRY = 1U << 3, /* Fake option entry to insert an additional help line */
|
||||
OPTION_HELP_ENTRY_VERBATIM = 1U << 4, /* Same, but use the long_code in the first column as written */
|
||||
OPTION_POSITIONAL_ENTRY = 1U << 1, /* The "option" to handle positional arguments */
|
||||
OPTION_STOPS_PARSING = 1U << 2, /* This option acts like "--" */
|
||||
OPTION_GROUP_MARKER = 1U << 3, /* Fake option entry to separate groups */
|
||||
OPTION_HELP_ENTRY = 1U << 4, /* Fake option entry to insert an additional help line */
|
||||
OPTION_HELP_ENTRY_VERBATIM = 1U << 5, /* Same, but use the long_code in the first column as written */
|
||||
} OptionFlags;
|
||||
|
||||
typedef struct Option {
|
||||
@@ -50,6 +51,7 @@ typedef struct Option {
|
||||
#define OPTION_LONG_FLAGS(fl, lc, mv, h) OPTION_FULL(fl, /* sc= */ 0, lc, mv, h)
|
||||
#define OPTION_SHORT(sc, mv, h) OPTION(sc, /* lc= */ NULL, mv, h)
|
||||
#define OPTION_SHORT_FLAGS(fl, sc, mv, h) OPTION_FULL(fl, sc, /* lc= */ NULL, mv, h)
|
||||
#define OPTION_POSITIONAL OPTION_FULL(OPTION_POSITIONAL_ENTRY, /* sc= */ 0, "(positional)", /* mv= */ NULL, /* h= */ NULL)
|
||||
#define OPTION_HELP_VERBATIM(lc, h) OPTION_FULL(OPTION_HELP_ENTRY_VERBATIM, /* sc= */ 0, lc, /* mv= */ NULL, h)
|
||||
|
||||
#define OPTION_COMMON_HELP \
|
||||
@@ -92,13 +94,26 @@ typedef struct Option {
|
||||
extern const Option __start_SYSTEMD_OPTIONS[];
|
||||
extern const Option __stop_SYSTEMD_OPTIONS[];
|
||||
|
||||
typedef enum OptionParserMode {
|
||||
/* The default mode. This is the implicit default and doesn't have to be specified. */
|
||||
OPTION_PARSER_NORMAL = 0,
|
||||
|
||||
/* Same as "+…" for getopt_long — only parse options before the first positional argument. */
|
||||
OPTION_PARSER_STOP_AT_FIRST_NONOPTION,
|
||||
|
||||
/* Same as "-…" for getopt_long — return positional arguments as "options" to be handled by the
|
||||
* option handler specified with OPTION_POSITIONAL. */
|
||||
OPTION_PARSER_RETURN_POSITIONAL_ARGS,
|
||||
|
||||
_OPTION_PARSER_MODE_MAX,
|
||||
} OptionParserMode;
|
||||
|
||||
typedef struct OptionParser {
|
||||
/* Those three should stay first so that it's possible to initialize the struct as { argc, argv }
|
||||
* or { argc, argv, true/false }. */
|
||||
* or { argc, argv, mode }. */
|
||||
int argc; /* The original argc. */
|
||||
char **argv; /* The argv array, possibly reordered. */
|
||||
bool stop_at_first_nonoption; /* Same as "+…" for getopt_long — only parse options before the first
|
||||
* positional argument. */
|
||||
OptionParserMode mode;
|
||||
|
||||
bool parsing_stopped; /* We processed "--" or an option that terminates option parsing. */
|
||||
int optind; /* Position of the parameter being handled.
|
||||
|
||||
+130
-71
@@ -15,7 +15,7 @@ static void test_option_parse_one(
|
||||
const Option options[],
|
||||
const Entry *entries,
|
||||
char **remaining,
|
||||
bool stop_at_first_nonoption) {
|
||||
OptionParserMode mode) {
|
||||
|
||||
_cleanup_free_ char *joined = strv_join(argv, ", ");
|
||||
log_debug("/* %s(%s) */", __func__, joined);
|
||||
@@ -32,7 +32,7 @@ static void test_option_parse_one(
|
||||
for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++)
|
||||
n_entries++;
|
||||
|
||||
OptionParser state = { argc, argv, stop_at_first_nonoption };
|
||||
OptionParser state = { argc, argv, mode };
|
||||
const Option *opt;
|
||||
const char *arg;
|
||||
for (int c; (c = option_parse(options, options + n_options, &state, &opt, &arg)) != 0; ) {
|
||||
@@ -101,7 +101,7 @@ TEST(option_parse) {
|
||||
options,
|
||||
NULL,
|
||||
NULL,
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"string1",
|
||||
@@ -114,7 +114,7 @@ TEST(option_parse) {
|
||||
"string2",
|
||||
"string3",
|
||||
"string4"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"--",
|
||||
@@ -128,7 +128,7 @@ TEST(option_parse) {
|
||||
"--help",
|
||||
"-h",
|
||||
"string4"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"string1",
|
||||
@@ -142,7 +142,7 @@ TEST(option_parse) {
|
||||
"string2",
|
||||
"--",
|
||||
"string4"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"string1",
|
||||
@@ -156,7 +156,7 @@ TEST(option_parse) {
|
||||
"string2",
|
||||
"string3",
|
||||
"string4"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"--help"),
|
||||
@@ -166,7 +166,7 @@ TEST(option_parse) {
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"--help",
|
||||
@@ -179,7 +179,7 @@ TEST(option_parse) {
|
||||
},
|
||||
STRV_MAKE("string1",
|
||||
"--help"),
|
||||
true);
|
||||
OPTION_PARSER_STOP_AT_FIRST_NONOPTION);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"-h"),
|
||||
@@ -189,7 +189,7 @@ TEST(option_parse) {
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"--help",
|
||||
@@ -206,7 +206,7 @@ TEST(option_parse) {
|
||||
"string2",
|
||||
"string3",
|
||||
"string4"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"-h",
|
||||
@@ -223,7 +223,7 @@ TEST(option_parse) {
|
||||
"string2",
|
||||
"string3",
|
||||
"string4"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"string1",
|
||||
@@ -240,7 +240,7 @@ TEST(option_parse) {
|
||||
"string2",
|
||||
"string3",
|
||||
"string4"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"string1",
|
||||
@@ -257,7 +257,7 @@ TEST(option_parse) {
|
||||
"string2",
|
||||
"string3",
|
||||
"string4"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"string1",
|
||||
@@ -274,7 +274,7 @@ TEST(option_parse) {
|
||||
"string2",
|
||||
"string3",
|
||||
"string4"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"string1",
|
||||
@@ -291,7 +291,7 @@ TEST(option_parse) {
|
||||
"string2",
|
||||
"string3",
|
||||
"string4"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"--required1", "reqarg1"),
|
||||
@@ -301,7 +301,7 @@ TEST(option_parse) {
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"-r", "reqarg1"),
|
||||
@@ -311,7 +311,7 @@ TEST(option_parse) {
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"string1",
|
||||
@@ -324,7 +324,7 @@ TEST(option_parse) {
|
||||
},
|
||||
STRV_MAKE("string1",
|
||||
"string2"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"string1",
|
||||
@@ -335,7 +335,7 @@ TEST(option_parse) {
|
||||
STRV_MAKE("string1",
|
||||
"string2",
|
||||
"-r", "reqarg1"),
|
||||
true);
|
||||
OPTION_PARSER_STOP_AT_FIRST_NONOPTION);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"--optional1=optarg1"),
|
||||
@@ -345,7 +345,7 @@ TEST(option_parse) {
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
true);
|
||||
OPTION_PARSER_STOP_AT_FIRST_NONOPTION);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"--optional1", "string1"),
|
||||
@@ -355,7 +355,7 @@ TEST(option_parse) {
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("string1"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"-ooptarg1"),
|
||||
@@ -365,7 +365,7 @@ TEST(option_parse) {
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"-o", "string1"),
|
||||
@@ -375,7 +375,7 @@ TEST(option_parse) {
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("string1"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
"string1",
|
||||
@@ -445,7 +445,7 @@ TEST(option_parse) {
|
||||
"--help",
|
||||
"--required1",
|
||||
"--optional1"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
}
|
||||
|
||||
TEST(option_stops_parsing) {
|
||||
@@ -469,7 +469,7 @@ TEST(option_stops_parsing) {
|
||||
},
|
||||
STRV_MAKE("--help",
|
||||
"foo"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Options before --exec are still parsed */
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
@@ -485,7 +485,7 @@ TEST(option_stops_parsing) {
|
||||
},
|
||||
STRV_MAKE("--version",
|
||||
"bar"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* --exec with no trailing args */
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
@@ -496,7 +496,7 @@ TEST(option_stops_parsing) {
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* --exec after positional args */
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
@@ -513,7 +513,7 @@ TEST(option_stops_parsing) {
|
||||
"--help",
|
||||
"--required",
|
||||
"val"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* "--" after --exec: "--" is still consumed as end-of-options marker. This is needed for
|
||||
* backwards compatibility, systemd-dissect implemented this behaviour. But also, it makes
|
||||
@@ -529,7 +529,7 @@ TEST(option_stops_parsing) {
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("--help"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* "--" before --exec: "--" terminates first, --exec is positional */
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
@@ -540,7 +540,7 @@ TEST(option_stops_parsing) {
|
||||
NULL,
|
||||
STRV_MAKE("--exec",
|
||||
"--help"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Multiple options then --exec then more option-like args */
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
@@ -559,7 +559,7 @@ TEST(option_stops_parsing) {
|
||||
STRV_MAKE("-h",
|
||||
"--required",
|
||||
"val2"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
}
|
||||
|
||||
TEST(option_group_marker) {
|
||||
@@ -584,7 +584,7 @@ TEST(option_group_marker) {
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Check that group marker name is ignored */
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
@@ -597,7 +597,7 @@ TEST(option_group_marker) {
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Verify that the group marker is not mistaken for an option */
|
||||
test_option_invalid_one(STRV_MAKE("arg0",
|
||||
@@ -625,7 +625,7 @@ TEST(option_group_marker) {
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Partial match with multiple candidates */
|
||||
test_option_invalid_one(STRV_MAKE("arg0",
|
||||
@@ -649,7 +649,7 @@ TEST(option_optional_arg) {
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Long option without = does NOT consume the next arg */
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
@@ -660,7 +660,7 @@ TEST(option_optional_arg) {
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("foo.txt"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Short option with inline arg */
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
@@ -671,7 +671,7 @@ TEST(option_optional_arg) {
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Short option without inline arg does NOT consume the next arg */
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
@@ -682,7 +682,7 @@ TEST(option_optional_arg) {
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("foo.txt"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Optional arg option at end of argv */
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
@@ -693,7 +693,7 @@ TEST(option_optional_arg) {
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Mixed: optional arg with other options */
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
@@ -708,7 +708,7 @@ TEST(option_optional_arg) {
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Short combo: -ho (h then o with no arg) */
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
@@ -720,7 +720,7 @@ TEST(option_optional_arg) {
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("pos1"),
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Short combo: -hobar (h then o with inline arg "bar") */
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
@@ -732,7 +732,7 @@ TEST(option_optional_arg) {
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
}
|
||||
|
||||
/* Test the OPTION, OPTION_LONG, OPTION_SHORT, OPTION_FULL, OPTION_GROUP macros
|
||||
@@ -741,7 +741,8 @@ TEST(option_optional_arg) {
|
||||
static void test_macros_parse_one(
|
||||
char **argv,
|
||||
const Entry *entries,
|
||||
char **remaining) {
|
||||
char **remaining,
|
||||
OptionParserMode mode) {
|
||||
|
||||
_cleanup_free_ char *joined = strv_join(argv, ", ");
|
||||
log_debug("/* %s(%s) */", __func__, joined);
|
||||
@@ -755,7 +756,7 @@ static void test_macros_parse_one(
|
||||
for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++)
|
||||
n_entries++;
|
||||
|
||||
OptionParser state = { argc, argv };
|
||||
OptionParser state = { argc, argv, mode };
|
||||
const Option *opt;
|
||||
const char *arg;
|
||||
|
||||
@@ -807,6 +808,9 @@ static void test_macros_parse_one(
|
||||
OPTION_LONG("debug", NULL, "Enable debug mode"):
|
||||
break;
|
||||
|
||||
OPTION_POSITIONAL:
|
||||
break;
|
||||
|
||||
default:
|
||||
log_error("Unexpected option id: %d", c);
|
||||
ASSERT_TRUE(false);
|
||||
@@ -828,7 +832,8 @@ TEST(option_macros) {
|
||||
{ "help" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION: short form */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -837,7 +842,8 @@ TEST(option_macros) {
|
||||
{ "help" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_LONG: only accessible via long form */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -846,7 +852,8 @@ TEST(option_macros) {
|
||||
{ "version" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_SHORT: only accessible via short form */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -855,7 +862,8 @@ TEST(option_macros) {
|
||||
{ .short_code = 'v' },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION with required arg: long --required=ARG */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -864,7 +872,8 @@ TEST(option_macros) {
|
||||
{ "required", "val1" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION with required arg: long --required ARG */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -873,7 +882,8 @@ TEST(option_macros) {
|
||||
{ "required", "val1" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION with required arg: short -r ARG */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -882,7 +892,8 @@ TEST(option_macros) {
|
||||
{ "required", "val1" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION with required arg: short -rARG */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -891,7 +902,8 @@ TEST(option_macros) {
|
||||
{ "required", "val1" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_FULL with OPTION_OPTIONAL_ARG: long with = */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -900,7 +912,8 @@ TEST(option_macros) {
|
||||
{ "optional", "val1" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_FULL with OPTION_OPTIONAL_ARG: long without = doesn't consume next */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -909,7 +922,8 @@ TEST(option_macros) {
|
||||
{ "optional", NULL },
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("pos1"));
|
||||
STRV_MAKE("pos1"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_FULL with OPTION_OPTIONAL_ARG: short inline */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -918,7 +932,8 @@ TEST(option_macros) {
|
||||
{ "optional", "val1" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_FULL with OPTION_OPTIONAL_ARG: short without inline */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -927,7 +942,8 @@ TEST(option_macros) {
|
||||
{ "optional", NULL },
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("pos1"));
|
||||
STRV_MAKE("pos1"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_FULL with OPTION_STOPS_PARSING: stops further option parsing */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -939,7 +955,8 @@ TEST(option_macros) {
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("--help",
|
||||
"--version"));
|
||||
"--version"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_STOPS_PARSING: options before are still parsed */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -953,7 +970,8 @@ TEST(option_macros) {
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("-h",
|
||||
"--debug"));
|
||||
"--debug"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_STOPS_PARSING with "--": "--" after exec is still consumed */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -964,7 +982,8 @@ TEST(option_macros) {
|
||||
{ "exec" },
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("--help"));
|
||||
STRV_MAKE("--help"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_STOPS_PARSING with "--": "--" before exec takes precedence */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -975,7 +994,8 @@ TEST(option_macros) {
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("--exec",
|
||||
"--help"));
|
||||
"--help"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_GROUP: group marker is transparent to parsing, --debug in Advanced group works */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -984,7 +1004,8 @@ TEST(option_macros) {
|
||||
{ "debug" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Mixed: all macro types together */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -1010,7 +1031,8 @@ TEST(option_macros) {
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("pos1",
|
||||
"pos2"));
|
||||
"pos2"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Short option combos with macros: -hv (help + verbose) */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -1020,7 +1042,8 @@ TEST(option_macros) {
|
||||
{ .short_code = 'v' },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Short option combo with required arg: -hrval (help + required with arg "val") */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -1030,7 +1053,8 @@ TEST(option_macros) {
|
||||
{ "required", "val" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Short option combo with optional arg: -hoval (help + optional with arg "val") */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -1040,7 +1064,8 @@ TEST(option_macros) {
|
||||
{ "optional", "val" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_STOPS_PARSING then "--": "--" is still consumed after exec */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -1055,7 +1080,8 @@ TEST(option_macros) {
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("--version",
|
||||
"-h"));
|
||||
"-h"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_STOPS_PARSING then later "--": "--" is not consumed */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -1071,7 +1097,8 @@ TEST(option_macros) {
|
||||
},
|
||||
STRV_MAKE("--version",
|
||||
"--",
|
||||
"-h"));
|
||||
"-h"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_STOPS_PARSING then "--" twice: second "--" is not consumed */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -1088,7 +1115,39 @@ TEST(option_macros) {
|
||||
},
|
||||
STRV_MAKE("--",
|
||||
"--version",
|
||||
"-h"));
|
||||
"-h"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Basic OPTION_POSITIONAL use */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
"--help",
|
||||
"arg1",
|
||||
"--debug",
|
||||
"arg2"),
|
||||
(Entry[]) {
|
||||
{ "help" },
|
||||
{ "(positional)", "arg1" },
|
||||
{ "debug" },
|
||||
{ "(positional)", "arg2" },
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
OPTION_PARSER_RETURN_POSITIONAL_ARGS);
|
||||
|
||||
/* OPTION_POSITIONAL combined with OPTION_STOPS_PARSING */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
"--help",
|
||||
"arg1",
|
||||
"--exec",
|
||||
"arg2"),
|
||||
(Entry[]) {
|
||||
{ "help" },
|
||||
{ "(positional)", "arg1" },
|
||||
{ "exec" },
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("arg2"),
|
||||
OPTION_PARSER_RETURN_POSITIONAL_ARGS);
|
||||
}
|
||||
|
||||
/* Test the pattern used by nspawn's --user: an optional-arg option that also
|
||||
@@ -1110,7 +1169,7 @@ TEST(option_optional_arg_consume) {
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
/* stop_at_first_nonoption= */ false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* --user without arg: next arg is an option, so no consumption */
|
||||
test_option_parse_one(STRV_MAKE("arg0",
|
||||
@@ -1123,7 +1182,7 @@ TEST(option_optional_arg_consume) {
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
/* stop_at_first_nonoption= */ false);
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* --user without arg: next arg is positional (doesn't start with -).
|
||||
* The option parser returns NULL for the arg. The caller would then
|
||||
|
||||
@@ -326,7 +326,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
OptionParser state = { argc, argv, /* stop_at_first_nonoption= */ true };
|
||||
OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION };
|
||||
const Option *current;
|
||||
const char *arg;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user