Compare commits

...

9 Commits

Author SHA1 Message Date
Adrian Vovk d3e031264d
Merge 3e2b6fd389 into 9bf6ffe166 2024-11-22 11:11:05 -03:00
Luca Boccassi 9bf6ffe166
man: split cryptenroll man page into sections (#35297) 2024-11-22 12:01:07 +00:00
Lennart Poettering cc6baba720 cryptenroll: it's called PKCS#11, not PKCS11
In the --help text we really should use the official spelling, just like
in the man page.
2024-11-22 10:42:37 +01:00
Lennart Poettering 3ae48d071c man: add enrollment type sections to cryptenroll man page
We have the same sections in the --help text, hence we even more so
should have them in the man page.
2024-11-22 10:42:37 +01:00
Antonio Alvarez Feijoo 2ccacdd57c bash-completion: add --list-devices to systemd-cryptenroll
And also use it to list suitable block devices.
2024-11-22 10:38:19 +01:00
Yu Watanabe d99198819c core/service: service_add_fd_store() consumes passed fd
Without this change, the fd is closed twice on failure.

Fixes a bug introduced by dff9808a62.

Fixes #35288.
2024-11-22 04:15:51 +01:00
Adrian Vovk 3e2b6fd389
sysupdate: Add --stream flag for major version bumps
Basically, distros that maintain more than one release stream (i.e.
multiple stable versions, a beta channel, etc) can put the others'
transfer definitions into /usr/lib/sysupdate@$STREAM.d/, and the admin
can pass --stream= on the CLI to switch to this new stream.

Part of https://github.com/systemd/systemd/issues/33345

SQUASHME: s/--next/--streams=<stream>/
2024-10-30 22:00:30 -04:00
Adrian Vovk 6f6d29290e
Warn admins about danger of overriding sysupdate.d
Overriding settings of systemd-sysupdate is quite dangerous - OS vendor
might make a change that is incompatible with the settings in /etc, and
unlike most other config incompatibilities this has the potential to
accidentally wipe the wrong partition during an update. Not good. So
let's warn admins.
2024-10-30 21:54:49 -04:00
Adrian Vovk 19bac76b1a
sysupdate: Document components more visibly
Right now components aren't particularly well documented. Let's make
the feature a bit more visible.
2024-10-30 21:54:48 -04:00
8 changed files with 343 additions and 58 deletions

View File

@ -265,32 +265,11 @@
</refsect1>
<refsect1>
<title>Options</title>
<title>Unlocking</title>
<para>The following options are understood:</para>
<para>The following options are understood that may be used to unlock the device in preparation of the enrollment operations:</para>
<variablelist>
<varlistentry>
<term><option>--password</option></term>
<listitem><para>Enroll a regular password/passphrase. This command is mostly equivalent to
<command>cryptsetup luksAddKey</command>, however may be combined with
<option>--wipe-slot=</option> in one call, see below.</para>
<xi:include href="version-info.xml" xpointer="v248"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--recovery-key</option></term>
<listitem><para>Enroll a recovery key. Recovery keys are mostly identical to passphrases, but are
computer-generated instead of being chosen by a human, and thus have a guaranteed high entropy. The
key uses a character set that is easy to type in, and may be scanned off screen via a QR code.
</para>
<xi:include href="version-info.xml" xpointer="v248"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--unlock-key-file=<replaceable>PATH</replaceable></option></term>
@ -328,7 +307,45 @@
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Simple Enrollment</title>
<para>The following options are understood that may be used to enroll simple user input based
unlocking:</para>
<variablelist>
<varlistentry>
<term><option>--password</option></term>
<listitem><para>Enroll a regular password/passphrase. This command is mostly equivalent to
<command>cryptsetup luksAddKey</command>, however may be combined with
<option>--wipe-slot=</option> in one call, see below.</para>
<xi:include href="version-info.xml" xpointer="v248"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--recovery-key</option></term>
<listitem><para>Enroll a recovery key. Recovery keys are mostly identical to passphrases, but are
computer-generated instead of being chosen by a human, and thus have a guaranteed high entropy. The
key uses a character set that is easy to type in, and may be scanned off screen via a QR code.
</para>
<xi:include href="version-info.xml" xpointer="v248"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>PKCS#11 Enrollment</title>
<para>The following option is understood that may be used to enroll PKCS#11 tokens:</para>
<variablelist>
<varlistentry>
<term><option>--pkcs11-token-uri=<replaceable>URI</replaceable></option></term>
@ -361,7 +378,15 @@
<xi:include href="version-info.xml" xpointer="v248"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>FIDO2 Enrollment</title>
<para>The following options are understood that may be used to enroll PKCS#11 tokens:</para>
<variablelist>
<varlistentry>
<term><option>--fido2-credential-algorithm=<replaceable>STRING</replaceable></option></term>
<listitem><para>Specify COSE algorithm used in credential generation. The default value is
@ -461,7 +486,15 @@
<xi:include href="version-info.xml" xpointer="v249"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>TPM2 Enrollment</title>
<para>The following options are understood that may be used to enroll TPM2 devices:</para>
<variablelist>
<varlistentry>
<term><option>--tpm2-device=<replaceable>PATH</replaceable></option></term>
@ -636,7 +669,15 @@
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Other Options</title>
<para>The following additional options are understood:</para>
<variablelist>
<varlistentry>
<term><option>--wipe-slot=<replaceable>SLOT<optional>,SLOT...</optional></replaceable></option></term>

View File

@ -190,6 +190,17 @@
<xi:include href="version-info.xml" xpointer="v251"/></listitem>
</varlistentry>
<varlistentry>
<term><option>streams</option></term>
<listitem><para>Lists streams that can be updated. This enumerates the
<filename>/var/cache/systemd/sysupdate@*.d/</filename> and <filename>/usr/lib/sysupdate@*.d/</filename>
directories that contain transfer definitions. This command is useful to list possible parameters
for <option>--stream=</option> (see below).</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
@ -225,7 +236,8 @@
updated together in a synchronous fashion. Simply define multiple transfer files within the same
<filename>sysupdate.d/</filename> directory for these cases.</para>
<para>This option may not be combined with <option>--definitions=</option>.</para>
<para>This option may not be combined with <option>--definitions=</option> or
<option>--stream=</option>.</para>
<xi:include href="version-info.xml" xpointer="v251"/></listitem>
</varlistentry>
@ -237,11 +249,29 @@
are read from this directory instead of <filename>/usr/lib/sysupdate.d/*.conf</filename>,
<filename>/etc/sysupdate.d/*.conf</filename>, and <filename>/run/sysupdate.d/*.conf</filename>.</para>
<para>This option may not be combined with <option>--component=</option>.</para>
<para>This option may not be combined with <option>--component=</option> or
<option>--stream=</option>.</para>
<xi:include href="version-info.xml" xpointer="v251"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--stream=</option></term>
<listitem><para>Selects the update stream to use. Takes a stream name as argument. This alters
the search logic for transfer definitions to look in
<filename>/usr/lib/sysupdate@<replaceable>stream</replaceable>.d/</filename> and
<filename>/var/cache/systemd/sysupdate@<replaceable>stream</replaceable>.d/</filename> instead of
<filename>/usr/lib/sysupdate.d</filename>.
Note that administrator-controlled directories (i.e. <filename>/etc/sysupdate.d/</filename>, etc) are
still loaded as usual.</para>
<para>This option may not be combined with <option>--definitions=</option> or
<option>--component=</option>.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--root=</option></term>

View File

@ -65,6 +65,52 @@
<citerefentry><refentrytitle>sysupdate.features</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
</para>
<para>Sometimes, distributions need to update certain parts of themselves independently from the normal
update cycle.
For example, the <ulink url="https://github.com/rhboot/shim">UEFI Shim loader</ulink> (necessary for
UEFI Secure Boot support in many cases) has its own release cycle, requires code signatures from a
third-party, and in general is not tied to a distribution's update cycle.
Support for this scenario is provided by "components", which allow distributions to define transfer
definitions that receive updates independently from the base OS.
Components are defined in <filename>/usr/lib/sysupdate.<replaceable>component</replaceable>.d/</filename>,
and have corresponding override directories for administrators (i.e.
<filename>/etc/sysupdate.<replaceable>component</replaceable>.d/</filename>, etc).</para>
<para>Some distributions may wish to maintain multiple update "streams" at a time, for example to offer
a beta/nightly update channel, or to distribute security updates to multiple major versions at a time.
Users of such distributions may wish to remain on their current stream, and switch streams at some future
point in time.
A distribution with multiple update streams should ship the transfer definitions for each stream in the
<filename>/usr/lib/sysupdate@<replaceable>stream</replaceable>.d/</filename> or
<filename>/var/cache/systemd/sysupdate@<replaceable>stream</replaceable>.d/</filename> directories.
For example, a distribution with multiple stable branches can ship the next major release's transfer
definitions in the current release's
<filename>/usr/lib/sysupdate@foobarOS-<replaceable>next</replaceable>.d/</filename> directory, and users
can switch to it by updating the <literal>foobarOS-<replaceable>next</replaceable></literal> stream.
How exactly these stream definition directories are delivered is up to distributions: they can stabilize
transfer definitions a version in advance and ship the stream definitions from day one, or they can ship
these files as part of a regular security patch that users will install anyway, or they can use a
component as described above to update the stream definitions under <filename>/var/cache/</filename>
independently from the host system.
Note that the presence of a stream definition directory does not imply the availability of an upgrade on
that stream; it just defines where to look and if an update is found on the remote how to install it.
Also note that the normal administrator override files (i.e. transfer definitions, feature definitions,
or drop-ins found in <filname>/etc/sysupdate.d/</filname>, <filename>/run/sysupdate.d/</filename>, etc)
are applied over top of the definitions found in the stream definition directory.
This is done because a the stream definition directory turns into the normal definition directory
(<filename>/usr/lib/sysupdate.d/</filename>) when that stream is switched to.</para>
<para>System Administrators must take <emphasis>extreme</emphasis> care when overriding any transfer or
optional feature definitions, other than to turn on or off features!
As with any configuration defined in <filename>/usr</filename> and overridden in
<filename>/etc</filename>, an update to the host system can break the administrator overrides.
However, <command>systemd-sysupdate</command> is uniquely destructive: a broken configuration could
prevent the system from updating (best case), or completely destroy an installation by wiping the wrong
partition.
Distributions must take care to avoid breaking systems where overrides exist only to turn on or off
optional features; supporting (or choosing not to) everything else is up to distribution policy.
<emphasis>You have been warned.</emphasis></para>
<para>Each <filename>*.transfer</filename> file contains three sections: [Transfer], [Source] and [Target].</para>
</refsect1>

View File

@ -38,19 +38,12 @@ __get_tpm2_devices() {
done
}
__get_block_devices() {
local i
for i in /dev/*; do
[ -b "$i" ] && printf '%s\n' "$i"
done
}
_systemd_cryptenroll() {
local comps
local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword
local -A OPTS=(
[STANDALONE]='-h --help --version
--password --recovery-key'
--password --recovery-key --list-devices'
[ARG]='--unlock-key-file
--unlock-fido2-device
--unlock-tpm2-device
@ -116,7 +109,7 @@ _systemd_cryptenroll() {
return 0
fi
comps=$(__get_block_devices)
comps=$(systemd-cryptenroll --list-devices)
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
return 0
}

View File

@ -64,12 +64,18 @@
"/usr/local/lib/" n "\0" \
"/usr/lib/" n "\0"
#define CONF_PATHS(n) \
#define CONF_PATHS_ADMIN(n) \
"/etc/" n, \
"/run/" n, \
"/run/" n
#define CONF_PATHS_SYSTEM(n) \
"/usr/local/lib/" n, \
"/usr/lib/" n
#define CONF_PATHS(n) \
CONF_PATHS_ADMIN(n), \
CONF_PATHS_SYSTEM(n)
#define CONF_PATHS_STRV(n) \
STRV_MAKE(CONF_PATHS(n))

View File

@ -3426,14 +3426,12 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value,
return 0;
}
r = service_add_fd_store(s, fd, fdn, do_poll);
r = service_add_fd_store(s, TAKE_FD(fd), fdn, do_poll);
if (r < 0) {
log_unit_debug_errno(u, r,
"Failed to store deserialized fd '%s', ignoring: %m", fdn);
return 0;
}
TAKE_FD(fd);
} else if (streq(key, "extra-fd")) {
_cleanup_free_ char *fdv = NULL, *fdn = NULL;
_cleanup_close_ int fd = -EBADF;

View File

@ -193,7 +193,7 @@ static int help(void) {
"\n%3$sSimple Enrollment:%4$s\n"
" --password Enroll a user-supplied password\n"
" --recovery-key Enroll a recovery key\n"
"\n%3$sPKCS11 Enrollment:%4$s\n"
"\n%3$sPKCS#11 Enrollment:%4$s\n"
" --pkcs11-token-uri=URI\n"
" Specify PKCS#11 security token URI\n"
"\n%3$sFIDO2 Enrollment:%4$s\n"

View File

@ -47,6 +47,7 @@ char *arg_root = NULL;
static char *arg_image = NULL;
static bool arg_reboot = false;
static char *arg_component = NULL;
static char *arg_stream = NULL;
static int arg_verify = -1;
static ImagePolicy *arg_image_policy = NULL;
static bool arg_offline = false;
@ -56,6 +57,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_component, freep);
STATIC_DESTRUCTOR_REGISTER(arg_stream, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
STATIC_DESTRUCTOR_REGISTER(arg_transfer_source, freep);
@ -180,7 +182,54 @@ static int context_read_definitions(Context *c, const char* node) {
if (arg_definitions)
dirs = strv_new(arg_definitions);
else if (arg_component) {
else if (arg_stream) {
/* Ultimately we end up with a search path along the lines of: /etc/sysupdate.d/,
* /run/sysupdate.d/, /var/cache/systemd/sysupdate@<stream>.d, /usr/lib/sysupdate@<stream>.d.
* This is very unusual! It seems wrong! But this is the correct behavior. When a
* `systemd-sysupdate --stream=` update is completed, /usr/lib/sysupdate@<stream>.d (or its
* /var/cache alternative) turns into /usr/lib/sysupdate.d, but the admin overrides remain
* untouched. So if we did this any differently, we'd end up in a situation where the admin's
* settings are ignored when first installing a major upgrade but then suddenly considered
* again once the update is completed. In my opinion, that behavior would be more unexpected
* and dangerous than what is implemented here!
*
* Is this a big and surprising footgun for the admin? Yes. But frankly, so is overriding
* anything relating to sysupdate. If an admin has overrides that do anything other than
* turning on/off optional features, they've already aimed a ballistic missile at their
* installation. It'll detonate either immediately when trying to switch streams (as
* implemented now), or when updating to the first patch of the new stream (the alternative);
* the installation is doomed either way. And failing immediately during a major OS upgrade
* seems a lot more preferable, and something that admins will be more prepared for, than a
* subsequent security patch suddenly bricking installations. */
char **admin = STRV_MAKE(CONF_PATHS_ADMIN("sysupdate.d"));
char **system = STRV_MAKE("/var/cache/systemd/", CONF_PATHS_SYSTEM(""));
size_t i = 0;
dirs = new0(char*, strv_length(admin) + strv_length(system) + 1);
if (!dirs)
return log_oom();
STRV_FOREACH(dir, admin) {
char *d;
d = strdup(*dir);
if (!d)
return log_oom();
dirs[i++] = d;
}
STRV_FOREACH(dir, system) {
char *j;
j = strjoin(*dir, "sysupdate@", arg_stream, ".d");
if (!j)
return log_oom();
dirs[i++] = j;
}
} else if (arg_component) {
char **l = CONF_PATHS_STRV("");
size_t i = 0;
@ -247,6 +296,11 @@ static int context_read_definitions(Context *c, const char* node) {
"No transfer definitions for component '%s' found.",
arg_component);
if (arg_stream)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
"No transfer definitions for stream '%s' found.",
arg_stream);
return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
"No transfer definitions found.");
}
@ -1532,22 +1586,45 @@ static int component_name_valid(const char *c) {
return filename_is_valid(j);
}
static int verb_components(int argc, char **argv, void *userdata) {
static int stream_name_valid(const char *s) {
_cleanup_free_ char *j = NULL;
/* See if the specified string enclosed in the directory prefix+suffix would be a valid file name */
if (isempty(s))
return false;
if (string_has_cc(s, NULL))
return false;
if (!utf8_is_valid(s))
return false;
j = strjoin("sysupdate@", s, ".d");
if (!j)
return -ENOMEM;
return filename_is_valid(j);
}
static int walk_search_paths(char **paths, bool component, char ***ret, bool *ret_has_default) {
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
_cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
_cleanup_set_free_ Set *names = NULL;
_cleanup_free_ char **z = NULL; /* We use simple free() rather than strv_free() here, since set_free() will free the strings for us */
char **l = CONF_PATHS_STRV("");
bool has_default_component = false;
_cleanup_free_ char **names_strv = NULL; /* free() b/c the set still owns the values */
_cleanup_strv_free_ char **names_dup = NULL;
bool has_default = false;
int r;
assert(argc <= 1);
assert(paths);
assert(ret);
assert(ret_has_default);
r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
if (r < 0)
return r;
STRV_FOREACH(i, l) {
STRV_FOREACH(i, paths) {
_cleanup_closedir_ DIR *d = NULL;
_cleanup_free_ char *p = NULL;
@ -1577,11 +1654,11 @@ static int verb_components(int argc, char **argv, void *userdata) {
continue;
if (streq(de->d_name, "sysupdate.d")) {
has_default_component = true;
has_default = true;
continue;
}
e = startswith(de->d_name, "sysupdate.");
e = startswith(de->d_name, component ? "sysupdate." : "sysupdate@");
if (!e)
continue;
@ -1593,26 +1670,51 @@ static int verb_components(int argc, char **argv, void *userdata) {
if (!n)
return log_oom();
r = component_name_valid(n);
if (component)
r = component_name_valid(n);
else
r = stream_name_valid(n);
if (r < 0)
return log_error_errno(r, "Unable to validate component name: %m");
return log_error_errno(r, "Unable to validate %s name: %m",
component ? "component" : "stream");
if (r == 0)
continue;
r = set_ensure_consume(&names, &string_hash_ops_free, TAKE_PTR(n));
if (r < 0 && r != -EEXIST)
return log_error_errno(r, "Failed to add component to set: %m");
return log_error_errno(r, "Failed to add %s to set: %m",
component ? "component" : "stream");
}
}
z = set_get_strv(names);
if (!z)
names_strv = set_get_strv(names);
if (!names_strv)
return log_oom();
strv_sort(z);
names_dup = strv_copy(names_strv);
if (!names_dup)
return log_oom();
strv_sort(names_dup);
*ret = TAKE_PTR(names_dup);
*ret_has_default = has_default;
return 0;
}
static int verb_components(int argc, char **argv, void *userdata) {
_cleanup_strv_free_ char **names = NULL;
bool has_default_component = false;
int r;
assert(argc <= 1);
r = walk_search_paths(CONF_PATHS_STRV(""), true, &names, &has_default_component);
if (r < 0)
return r;
if (!sd_json_format_enabled(arg_json_format_flags)) {
if (!has_default_component && set_isempty(names)) {
if (!has_default_component && strv_isempty(names)) {
log_info("No components defined.");
return 0;
}
@ -1621,13 +1723,53 @@ static int verb_components(int argc, char **argv, void *userdata) {
printf("%s<default>%s\n",
ansi_highlight(), ansi_normal());
STRV_FOREACH(i, z)
STRV_FOREACH(i, names)
puts(*i);
} else {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_BOOLEAN("default", has_default_component),
SD_JSON_BUILD_PAIR_STRV("components", z));
SD_JSON_BUILD_PAIR_STRV("components", names));
if (r < 0)
return log_error_errno(r, "Failed to create JSON: %m");
r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
if (r < 0)
return log_error_errno(r, "Failed to print JSON: %m");
}
return 0;
}
static int verb_streams(int argc, char **argv, void *userdata) {
char **dirs = STRV_MAKE("/var/cache/systemd/", CONF_PATHS_SYSTEM(""));
_cleanup_strv_free_ char **names = NULL;
bool has_default_stream = false;
int r;
assert(argc <= 1);
r = walk_search_paths(dirs, false, &names, &has_default_stream);
if (r < 0)
return r;
if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
if (!has_default_stream && strv_isempty(names)) {
log_info("No streams defined.");
return 0;
}
if (has_default_stream)
printf("%s<default>%s\n",
ansi_highlight(), ansi_normal());
STRV_FOREACH(i, names)
puts(*i);
} else {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_BOOLEAN("default", has_default_stream),
SD_JSON_BUILD_PAIR_STRV("streams", names));
if (r < 0)
return log_error_errno(r, "Failed to create JSON: %m");
@ -1659,11 +1801,13 @@ static int verb_help(int argc, char **argv, void *userdata) {
" currently booted\n"
" reboot Reboot if a newer version is installed than booted\n"
" components Show list of components\n"
" streams Show list of streams\n"
" -h --help Show this help\n"
" --version Show package version\n"
"\n%3$sOptions:%4$s\n"
" -C --component=NAME Select component to update\n"
" --definitions=DIR Find transfer definitions in specified directory\n"
" --stream=STREAM Select stream to switch to\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --image=PATH Operate on disk image as filesystem root\n"
" --image-policy=POLICY\n"
@ -1698,6 +1842,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_NO_LEGEND,
ARG_SYNC,
ARG_DEFINITIONS,
ARG_STREAM,
ARG_JSON,
ARG_ROOT,
ARG_IMAGE,
@ -1714,6 +1859,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "definitions", required_argument, NULL, ARG_DEFINITIONS },
{ "stream", required_argument, NULL, ARG_STREAM },
{ "instances-max", required_argument, NULL, 'm' },
{ "sync", required_argument, NULL, ARG_SYNC },
{ "json", required_argument, NULL, ARG_JSON },
@ -1770,6 +1916,24 @@ static int parse_argv(int argc, char *argv[]) {
return r;
break;
case ARG_STREAM:
if (isempty(optarg)) {
arg_stream = mfree(arg_stream);
break;
}
r = stream_name_valid(optarg);
if (r < 0)
return log_error_errno(r, "Failed to determine if stream name is valid: %m");
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Stream name invalid: %s", optarg);
r = free_and_strdup_warn(&arg_stream, optarg);
if (r < 0)
return r;
break;
case ARG_JSON:
r = parse_json_argument(optarg, &arg_json_format_flags);
if (r <= 0)
@ -1856,6 +2020,12 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_definitions && arg_component)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --component= switches may not be combined.");
if (arg_definitions && arg_stream)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --stream= switches may not be combined.");
if (arg_component && arg_stream)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --component= and --stream= switches may not be combined.");
return 1;
}
@ -1864,6 +2034,7 @@ static int sysupdate_main(int argc, char *argv[]) {
static const Verb verbs[] = {
{ "list", VERB_ANY, 2, VERB_DEFAULT, verb_list },
{ "components", VERB_ANY, 1, 0, verb_components },
{ "streams", VERB_ANY, 1, 0, verb_streams },
{ "features", VERB_ANY, 2, 0, verb_features },
{ "check-new", VERB_ANY, 1, 0, verb_check_new },
{ "update", VERB_ANY, 2, 0, verb_update },