1
0
mirror of https://github.com/systemd/systemd synced 2024-07-09 04:26:06 +00:00

Merge pull request #27113 from keszybz/variable-expansion-rework

Rework serialization of command lines in pid1 and make run not expand variables
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2023-04-24 22:03:06 +02:00 committed by GitHub
commit 208a59c15f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 638 additions and 318 deletions

View File

@ -92,6 +92,11 @@
Consider using the <option>exec</option> service type (i.e. <option>--property=Type=exec</option>) to
ensure that <command>systemd-run</command> returns successfully only if the specified command line has
been successfully started.</para>
<para>After <command>systemd-run</command> passes the command to the service manager, the manager
performs variable expansion. This means that dollar characters (<literal>$</literal>) which should not be
expanded need to be escaped as <literal>$$</literal>. Expansion can also be disabled using
<varname>--expand-environment=no</varname>.</para>
</refsect1>
<refsect1>
@ -169,6 +174,25 @@
</listitem>
</varlistentry>
<varlistentry>
<term><option>--expand-environment=<replaceable>BOOL</replaceable></option></term>
<listitem><para>Expand environment variables in command arguments. If enabled (the default),
environment variables specified as <literal>${<replaceable>VARIABLE</replaceable>}</literal> will be
expanded in the same way as in commands specified via <varname>ExecStart=</varname> in units. With
<varname>--scope</varname>, this expansion is performed by <command>systemd-run</command> itself, and
in other cases by the service manager that spawns the command. Note that this is similar to, but not
the same as variable expansion in
<citerefentry project='man-pages'><refentrytitle>bash</refentrytitle><manvolnum>1</manvolnum></citerefentry>
and other shells.</para>
<para>See
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for a description of variable expansion. Disabling variable expansion is useful if the specified
command includes or may include a <literal>$</literal> sign.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-r</option></term>
<term><option>--remain-after-exit</option></term>
@ -528,12 +552,49 @@ There is a screen on:
<programlisting>$ loginctl enable-linger</programlisting>
</example>
<example>
<title>Variable expansion by the manager</title>
<programlisting>$ systemd-run -t echo "&lt;${INVOCATION_ID}>" '&lt;${INVOCATION_ID}>'
&lt;> &lt;5d0149bfa2c34b79bccb13074001eb20>
</programlisting>
<para>The first argument is expanded by the shell (double quotes), but the second one is not expanded
by the shell (single quotes). <command>echo</command> is called with [<literal>/usr/bin/echo</literal>,
<literal>[]</literal>, <literal>[${INVOCATION_ID}]</literal>] as the argument array, and then
<command>systemd</command> generates <varname>${INVOCATION_ID}</varname> and substitutes it in the
command-line. This substitution could not be done on the client side, because the target ID that will
be set for the service isn't known before the call is made.</para>
</example>
<example>
<title>Variable expansion and output redirection using a shell</title>
<para>Variable expansion by <command>systemd</command> can be disabled with
<varname>--expand-environment=no</varname>.</para>
<para>Disabling variable expansion can be useful if the command to execute contains dollar characters
and escaping them would be inconvenient. For example, when a shell is used:</para>
<programlisting>$ systemd-run --expand-environment=no -t bash \
-c 'echo $SHELL $$ >/dev/stdout'
/bin/bash 12345
</programlisting>
<para>The last argument is passed verbatim to the <command>bash</command> shell which is started by the
service unit. The shell expands <literal>$SHELL</literal> to the path of the shell, and
<literal>$$</literal> to its process number, and then those strings are passed to the
<command>echo</command> built-in and printed to standard output (which in this case is connected to the
calling terminal).</para>
</example>
<example>
<title>Return value</title>
<programlisting>$ systemd-run --user --wait true
$ systemd-run --user --wait -p SuccessExitStatus=11 bash -c 'exit 11'
$ systemd-run --user --wait -p SuccessExitStatus=SIGUSR1 bash -c 'kill -SIGUSR1 $$$$'</programlisting>
$ systemd-run --user --wait -p SuccessExitStatus=SIGUSR1 --expand-environment=no \
bash -c 'kill -SIGUSR1 $$'</programlisting>
<para>Those three invocations will succeed, i.e. terminate with an exit code of 0.</para>
</example>

View File

@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "graphics.h"
#include "logarithm.h"
#include "proto/graphics-output.h"
#include "splash.h"
#include "unaligned-fundamental.h"
@ -141,14 +142,14 @@ static void read_channel_maks(
channel_shift[R] = __builtin_ctz(dib->channel_mask_r);
channel_shift[G] = __builtin_ctz(dib->channel_mask_g);
channel_shift[B] = __builtin_ctz(dib->channel_mask_b);
channel_scale[R] = 0xff / ((1 << __builtin_popcount(dib->channel_mask_r)) - 1);
channel_scale[G] = 0xff / ((1 << __builtin_popcount(dib->channel_mask_g)) - 1);
channel_scale[B] = 0xff / ((1 << __builtin_popcount(dib->channel_mask_b)) - 1);
channel_scale[R] = 0xff / ((1 << popcount(dib->channel_mask_r)) - 1);
channel_scale[G] = 0xff / ((1 << popcount(dib->channel_mask_g)) - 1);
channel_scale[B] = 0xff / ((1 << popcount(dib->channel_mask_b)) - 1);
if (dib->size >= SIZEOF_BMP_DIB_RGBA && dib->channel_mask_a != 0) {
channel_mask[A] = dib->channel_mask_a;
channel_shift[A] = __builtin_ctz(dib->channel_mask_a);
channel_scale[A] = 0xff / ((1 << __builtin_popcount(dib->channel_mask_a)) - 1);
channel_scale[A] = 0xff / ((1 << popcount(dib->channel_mask_a)) - 1);
} else {
channel_mask[A] = 0;
channel_shift[A] = 0;

View File

@ -1543,6 +1543,9 @@ int bus_set_transient_exec_command(
unsigned n = 0;
int r;
/* Drop Ex from the written setting. E.g. ExecStart=, not ExecStartEx=. */
const char *written_name = is_ex_prop ? strndupa(name, strlen(name) - 2) : name;
r = sd_bus_message_enter_container(message, 'a', is_ex_prop ? "(sasas)" : "(sasb)");
if (r < 0)
return r;
@ -1624,31 +1627,32 @@ int bus_set_transient_exec_command(
if (!f)
return -ENOMEM;
fprintf(f, "%s=\n", name);
fprintf(f, "%s=\n", written_name);
LIST_FOREACH(command, c, *exec_command) {
_cleanup_free_ char *a = NULL, *exec_chars = NULL;
UnitWriteFlags esc_flags = UNIT_ESCAPE_SPECIFIERS |
(FLAGS_SET(c->flags, EXEC_COMMAND_NO_ENV_EXPAND) ? UNIT_ESCAPE_EXEC_SYNTAX : UNIT_ESCAPE_EXEC_SYNTAX_ENV);
exec_chars = exec_command_flags_to_exec_chars(c->flags);
if (!exec_chars)
return -ENOMEM;
a = unit_concat_strv(c->argv, UNIT_ESCAPE_SPECIFIERS|UNIT_ESCAPE_EXEC_SYNTAX);
a = unit_concat_strv(c->argv, esc_flags);
if (!a)
return -ENOMEM;
if (streq_ptr(c->path, c->argv ? c->argv[0] : NULL))
fprintf(f, "%s=%s%s\n", name, exec_chars, a);
fprintf(f, "%s=%s%s\n", written_name, exec_chars, a);
else {
_cleanup_free_ char *t = NULL;
const char *p;
p = unit_escape_setting(c->path,
UNIT_ESCAPE_SPECIFIERS|UNIT_ESCAPE_EXEC_SYNTAX, &t);
p = unit_escape_setting(c->path, esc_flags, &t);
if (!p)
return -ENOMEM;
fprintf(f, "%s=%s@%s %s\n", name, exec_chars, p, a);
fprintf(f, "%s=%s@%s %s\n", written_name, exec_chars, p, a);
}
}
@ -1656,7 +1660,7 @@ int bus_set_transient_exec_command(
if (r < 0)
return r;
unit_write_setting(u, flags, name, buf);
unit_write_setting(u, flags, written_name, buf);
}
return 1;

View File

@ -670,7 +670,8 @@ static int bus_service_set_transient_property(
return bus_set_transient_exit_status(u, name, &s->success_status, message, flags, error);
ci = service_exec_command_from_string(name);
ci = (ci >= 0) ? ci : service_exec_ex_command_from_string(name);
if (ci < 0)
ci = service_exec_ex_command_from_string(name);
if (ci >= 0)
return bus_set_transient_exec_command(u, name, &s->exec_command[ci], message, flags, error);

View File

@ -36,6 +36,7 @@
#include "load-dropin.h"
#include "load-fragment.h"
#include "log.h"
#include "logarithm.h"
#include "macro.h"
#include "missing_audit.h"
#include "mkdir-label.h"
@ -4375,7 +4376,7 @@ static const char* unit_drop_in_dir(Unit *u, UnitWriteFlags flags) {
const char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf) {
assert(s);
assert(!FLAGS_SET(flags, UNIT_ESCAPE_EXEC_SYNTAX | UNIT_ESCAPE_C));
assert(popcount(flags & (UNIT_ESCAPE_EXEC_SYNTAX_ENV | UNIT_ESCAPE_EXEC_SYNTAX | UNIT_ESCAPE_C)) <= 1);
assert(buf);
_cleanup_free_ char *t = NULL;
@ -4395,10 +4396,19 @@ const char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf)
}
/* We either do C-escaping or shell-escaping, to additionally escape characters that we parse for
* ExecStart= and friends, i.e. '$' and ';' and quotes. */
* ExecStart= and friends, i.e. '$' and quotes. */
if (flags & UNIT_ESCAPE_EXEC_SYNTAX) {
char *t2 = shell_escape(s, "$;'\"");
if (flags & (UNIT_ESCAPE_EXEC_SYNTAX_ENV | UNIT_ESCAPE_EXEC_SYNTAX)) {
char *t2;
if (flags & UNIT_ESCAPE_EXEC_SYNTAX_ENV) {
t2 = strreplace(s, "$", "$$");
if (!t2)
return NULL;
free_and_replace(t, t2);
}
t2 = shell_escape(t ?: s, "\"");
if (!t2)
return NULL;
free_and_replace(t, t2);
@ -4406,7 +4416,9 @@ const char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf)
s = t;
} else if (flags & UNIT_ESCAPE_C) {
char *t2 = cescape(s);
char *t2;
t2 = cescape(s);
if (!t2)
return NULL;
free_and_replace(t, t2);

View File

@ -534,22 +534,25 @@ typedef struct UnitStatusMessageFormats {
/* Flags used when writing drop-in files or transient unit files */
typedef enum UnitWriteFlags {
/* Write a runtime unit file or drop-in (i.e. one below /run) */
UNIT_RUNTIME = 1 << 0,
UNIT_RUNTIME = 1 << 0,
/* Write a persistent drop-in (i.e. one below /etc) */
UNIT_PERSISTENT = 1 << 1,
UNIT_PERSISTENT = 1 << 1,
/* Place this item in the per-unit-type private section, instead of [Unit] */
UNIT_PRIVATE = 1 << 2,
UNIT_PRIVATE = 1 << 2,
/* Apply specifier escaping before writing */
UNIT_ESCAPE_SPECIFIERS = 1 << 3,
/* Apply specifier escaping */
UNIT_ESCAPE_SPECIFIERS = 1 << 3,
/* Escape elements of ExecStart= syntax before writing */
UNIT_ESCAPE_EXEC_SYNTAX = 1 << 4,
/* Escape elements of ExecStart= syntax, incl. prevention of variable expansion */
UNIT_ESCAPE_EXEC_SYNTAX_ENV = 1 << 4,
/* Escape elements of ExecStart=: syntax (no variable expansion) */
UNIT_ESCAPE_EXEC_SYNTAX = 1 << 5,
/* Apply C escaping before writing */
UNIT_ESCAPE_C = 1 << 5,
UNIT_ESCAPE_C = 1 << 6,
} UnitWriteFlags;
/* Returns true if neither persistent, nor runtime storage is requested, i.e. this is a check invocation only */

View File

@ -3,8 +3,6 @@
#include <stdint.h>
#include "macro.h"
/* Note: log2(0) == log2(1) == 0 here and below. */
#define CONST_LOG2ULL(x) ((x) > 1 ? (unsigned) __builtin_clzll(x) ^ 63U : 0)
@ -30,6 +28,14 @@ static inline unsigned u32ctz(uint32_t n) {
#endif
}
#define popcount(n) \
_Generic((n), \
unsigned char: __builtin_popcount(n), \
unsigned short: __builtin_popcount(n), \
unsigned: __builtin_popcount(n), \
unsigned long: __builtin_popcountl(n), \
unsigned long long: __builtin_popcountll(n))
#define CONST_LOG2U(x) ((x) > 1 ? __SIZEOF_INT__ * 8 - __builtin_clz(x) - 1 : 0)
#define NONCONST_LOG2U(x) ({ \
unsigned _x = (x); \

View File

@ -45,6 +45,7 @@ static const char *arg_unit = NULL;
static const char *arg_description = NULL;
static const char *arg_slice = NULL;
static bool arg_slice_inherit = false;
static bool arg_expand_environment = true;
static bool arg_send_sighup = false;
static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
static const char *arg_host = NULL;
@ -102,6 +103,7 @@ static int help(void) {
" --description=TEXT Description for unit\n"
" --slice=SLICE Run in the specified slice\n"
" --slice-inherit Inherit the slice\n"
" --expand-environment=BOOL Control expansion of environment variables\n"
" --no-block Do not wait until operation finished\n"
" -r --remain-after-exit Leave service around until explicitly stopped\n"
" --wait Wait until service stopped again\n"
@ -168,6 +170,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_DESCRIPTION,
ARG_SLICE,
ARG_SLICE_INHERIT,
ARG_EXPAND_ENVIRONMENT,
ARG_SEND_SIGHUP,
ARG_SERVICE_TYPE,
ARG_EXEC_USER,
@ -192,47 +195,48 @@ static int parse_argv(int argc, char *argv[]) {
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "user", no_argument, NULL, ARG_USER },
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "scope", no_argument, NULL, ARG_SCOPE },
{ "unit", required_argument, NULL, 'u' },
{ "description", required_argument, NULL, ARG_DESCRIPTION },
{ "slice", required_argument, NULL, ARG_SLICE },
{ "slice-inherit", no_argument, NULL, ARG_SLICE_INHERIT },
{ "remain-after-exit", no_argument, NULL, 'r' },
{ "send-sighup", no_argument, NULL, ARG_SEND_SIGHUP },
{ "host", required_argument, NULL, 'H' },
{ "machine", required_argument, NULL, 'M' },
{ "service-type", required_argument, NULL, ARG_SERVICE_TYPE },
{ "wait", no_argument, NULL, ARG_WAIT },
{ "uid", required_argument, NULL, ARG_EXEC_USER },
{ "gid", required_argument, NULL, ARG_EXEC_GROUP },
{ "nice", required_argument, NULL, ARG_NICE },
{ "setenv", required_argument, NULL, 'E' },
{ "property", required_argument, NULL, 'p' },
{ "tty", no_argument, NULL, 't' }, /* deprecated alias */
{ "pty", no_argument, NULL, 't' },
{ "pipe", no_argument, NULL, 'P' },
{ "quiet", no_argument, NULL, 'q' },
{ "on-active", required_argument, NULL, ARG_ON_ACTIVE },
{ "on-boot", required_argument, NULL, ARG_ON_BOOT },
{ "on-startup", required_argument, NULL, ARG_ON_STARTUP },
{ "on-unit-active", required_argument, NULL, ARG_ON_UNIT_ACTIVE },
{ "on-unit-inactive", required_argument, NULL, ARG_ON_UNIT_INACTIVE },
{ "on-calendar", required_argument, NULL, ARG_ON_CALENDAR },
{ "on-timezone-change",no_argument, NULL, ARG_ON_TIMEZONE_CHANGE},
{ "on-clock-change", no_argument, NULL, ARG_ON_CLOCK_CHANGE },
{ "timer-property", required_argument, NULL, ARG_TIMER_PROPERTY },
{ "path-property", required_argument, NULL, ARG_PATH_PROPERTY },
{ "socket-property", required_argument, NULL, ARG_SOCKET_PROPERTY },
{ "no-block", no_argument, NULL, ARG_NO_BLOCK },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ "collect", no_argument, NULL, 'G' },
{ "working-directory", required_argument, NULL, ARG_WORKING_DIRECTORY },
{ "same-dir", no_argument, NULL, 'd' },
{ "shell", no_argument, NULL, 'S' },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "user", no_argument, NULL, ARG_USER },
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "scope", no_argument, NULL, ARG_SCOPE },
{ "unit", required_argument, NULL, 'u' },
{ "description", required_argument, NULL, ARG_DESCRIPTION },
{ "slice", required_argument, NULL, ARG_SLICE },
{ "slice-inherit", no_argument, NULL, ARG_SLICE_INHERIT },
{ "remain-after-exit", no_argument, NULL, 'r' },
{ "expand-environment", required_argument, NULL, ARG_EXPAND_ENVIRONMENT },
{ "send-sighup", no_argument, NULL, ARG_SEND_SIGHUP },
{ "host", required_argument, NULL, 'H' },
{ "machine", required_argument, NULL, 'M' },
{ "service-type", required_argument, NULL, ARG_SERVICE_TYPE },
{ "wait", no_argument, NULL, ARG_WAIT },
{ "uid", required_argument, NULL, ARG_EXEC_USER },
{ "gid", required_argument, NULL, ARG_EXEC_GROUP },
{ "nice", required_argument, NULL, ARG_NICE },
{ "setenv", required_argument, NULL, 'E' },
{ "property", required_argument, NULL, 'p' },
{ "tty", no_argument, NULL, 't' }, /* deprecated alias */
{ "pty", no_argument, NULL, 't' },
{ "pipe", no_argument, NULL, 'P' },
{ "quiet", no_argument, NULL, 'q' },
{ "on-active", required_argument, NULL, ARG_ON_ACTIVE },
{ "on-boot", required_argument, NULL, ARG_ON_BOOT },
{ "on-startup", required_argument, NULL, ARG_ON_STARTUP },
{ "on-unit-active", required_argument, NULL, ARG_ON_UNIT_ACTIVE },
{ "on-unit-inactive", required_argument, NULL, ARG_ON_UNIT_INACTIVE },
{ "on-calendar", required_argument, NULL, ARG_ON_CALENDAR },
{ "on-timezone-change", no_argument, NULL, ARG_ON_TIMEZONE_CHANGE },
{ "on-clock-change", no_argument, NULL, ARG_ON_CLOCK_CHANGE },
{ "timer-property", required_argument, NULL, ARG_TIMER_PROPERTY },
{ "path-property", required_argument, NULL, ARG_PATH_PROPERTY },
{ "socket-property", required_argument, NULL, ARG_SOCKET_PROPERTY },
{ "no-block", no_argument, NULL, ARG_NO_BLOCK },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ "collect", no_argument, NULL, 'G' },
{ "working-directory", required_argument, NULL, ARG_WORKING_DIRECTORY },
{ "same-dir", no_argument, NULL, 'd' },
{ "shell", no_argument, NULL, 'S' },
{},
};
@ -287,6 +291,12 @@ static int parse_argv(int argc, char *argv[]) {
arg_slice_inherit = true;
break;
case ARG_EXPAND_ENVIRONMENT:
r = parse_boolean_argument("--expand-environment=", optarg, &arg_expand_environment);
if (r < 0)
return r;
break;
case ARG_SEND_SIGHUP:
arg_send_sighup = true;
break;
@ -719,6 +729,11 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p
bool send_term = false;
int r;
/* We disable environment expansion on the server side via ExecStartEx=:.
* ExecStartEx was added relatively recently (v243), and some bugs were fixed only later.
* So use that feature only if required. It will fail with older systemds. */
bool use_ex_prop = !arg_expand_environment;
assert(m);
r = transient_unit_set_properties(m, UNIT_SERVICE, arg_property);
@ -850,19 +865,23 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(m, "s", "ExecStart");
r = sd_bus_message_append(m, "s",
use_ex_prop ? "ExecStartEx" : "ExecStart");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_open_container(m, 'v', "a(sasb)");
r = sd_bus_message_open_container(m, 'v',
use_ex_prop ? "a(sasas)" : "a(sasb)");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_open_container(m, 'a', "(sasb)");
r = sd_bus_message_open_container(m, 'a',
use_ex_prop ? "(sasas)" : "(sasb)");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_open_container(m, 'r', "sasb");
r = sd_bus_message_open_container(m, 'r',
use_ex_prop ? "sasas" : "sasb");
if (r < 0)
return bus_log_create_error(r);
@ -874,7 +893,12 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(m, "b", false);
if (use_ex_prop)
r = sd_bus_message_append_strv(
m,
STRV_MAKE(arg_expand_environment ? NULL : "no-env-expand"));
else
r = sd_bus_message_append(m, "b", false);
if (r < 0)
return bus_log_create_error(r);
@ -1112,10 +1136,78 @@ static int pty_forward_handler(PTYForward *f, int rcode, void *userdata) {
return 0;
}
static int start_transient_service(
static int make_transient_service_unit(
sd_bus *bus,
int *retval) {
sd_bus_message **message,
const char *service,
const char *pty_path) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
int r;
assert(bus);
assert(message);
assert(service);
r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password);
if (r < 0)
return bus_log_create_error(r);
/* Name and mode */
r = sd_bus_message_append(m, "ss", service, "fail");
if (r < 0)
return bus_log_create_error(r);
/* Properties */
r = sd_bus_message_open_container(m, 'a', "(sv)");
if (r < 0)
return bus_log_create_error(r);
r = transient_service_set_properties(m, pty_path);
if (r < 0)
return r;
r = sd_bus_message_close_container(m);
if (r < 0)
return bus_log_create_error(r);
/* Auxiliary units */
r = sd_bus_message_append(m, "a(sa(sv))", 0);
if (r < 0)
return bus_log_create_error(r);
*message = TAKE_PTR(m);
return 0;
}
static int bus_call_with_hint(
sd_bus *bus,
sd_bus_message *message,
const char *name,
sd_bus_message **reply) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
r = sd_bus_call(bus, message, 0, &error, reply);
if (r < 0) {
log_error_errno(r, "Failed to start transient %s unit: %s", name, bus_error_message(&error, r));
if (!arg_expand_environment &&
sd_bus_error_has_names(&error,
SD_BUS_ERROR_UNKNOWN_PROPERTY,
SD_BUS_ERROR_PROPERTY_READ_ONLY))
log_notice_errno(r, "Hint: --expand-environment=no is not supported by old systemd");
}
return r;
}
static int start_transient_service(sd_bus *bus) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
@ -1124,7 +1216,6 @@ static int start_transient_service(
int r;
assert(bus);
assert(retval);
if (arg_stdio == ARG_STDIO_PTY) {
@ -1197,42 +1288,15 @@ static int start_transient_service(
return r;
}
r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password);
if (r < 0)
return bus_log_create_error(r);
/* Name and mode */
r = sd_bus_message_append(m, "ss", service, "fail");
if (r < 0)
return bus_log_create_error(r);
/* Properties */
r = sd_bus_message_open_container(m, 'a', "(sv)");
if (r < 0)
return bus_log_create_error(r);
r = transient_service_set_properties(m, pty_path);
r = make_transient_service_unit(bus, &m, service, pty_path);
if (r < 0)
return r;
r = sd_bus_message_close_container(m);
if (r < 0)
return bus_log_create_error(r);
/* Auxiliary units */
r = sd_bus_message_append(m, "a(sa(sv))", 0);
if (r < 0)
return bus_log_create_error(r);
polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
r = sd_bus_call(bus, m, 0, &error, &reply);
r = bus_call_with_hint(bus, m, "service", &reply);
if (r < 0)
return log_error_errno(r, "Failed to start transient service unit: %s", bus_error_message(&error, r));
return r;
if (w) {
const char *object;
@ -1363,16 +1427,15 @@ static int start_transient_service(
/* Try to propagate the service's return value. But if the service defines
* e.g. SuccessExitStatus, honour this, and return 0 to mean "success". */
if (streq_ptr(c.result, "success"))
*retval = 0;
else if (streq_ptr(c.result, "exit-code") && c.exit_status > 0)
*retval = c.exit_status;
else if (streq_ptr(c.result, "signal"))
*retval = EXIT_EXCEPTION;
else
*retval = EXIT_FAILURE;
return EXIT_SUCCESS;
if (streq_ptr(c.result, "exit-code") && c.exit_status > 0)
return c.exit_status;
if (streq_ptr(c.result, "signal"))
return EXIT_EXCEPTION;
return EXIT_FAILURE;
}
return 0;
return EXIT_SUCCESS;
}
static int acquire_invocation_id(sd_bus *bus, sd_id128_t *ret) {
@ -1411,7 +1474,7 @@ static int start_transient_scope(sd_bus *bus) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
_cleanup_strv_free_ char **env = NULL, **user_env = NULL;
_cleanup_strv_free_ char **env = NULL, **user_env = NULL, **expanded_cmdline = NULL;
_cleanup_free_ char *scope = NULL;
const char *object = NULL;
sd_id128_t invocation_id;
@ -1553,75 +1616,33 @@ static int start_transient_scope(sd_bus *bus) {
if (!arg_quiet)
log_info("Running scope as unit: %s", scope);
if (arg_expand_environment) {
expanded_cmdline = replace_env_argv(arg_cmdline, env);
if (!expanded_cmdline)
return log_oom();
arg_cmdline = expanded_cmdline;
}
execvpe(arg_cmdline[0], arg_cmdline, env);
return log_error_errno(errno, "Failed to execute: %m");
}
static int start_transient_trigger(
static int make_transient_trigger_unit(
sd_bus *bus,
const char *suffix) {
sd_bus_message **message,
const char *suffix,
const char *trigger,
const char *service) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
_cleanup_free_ char *trigger = NULL, *service = NULL;
const char *object = NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
int r;
assert(bus);
r = bus_wait_for_jobs_new(bus, &w);
if (r < 0)
return log_oom();
if (arg_unit) {
switch (unit_name_to_type(arg_unit)) {
case UNIT_SERVICE:
service = strdup(arg_unit);
if (!service)
return log_oom();
r = unit_name_change_suffix(service, suffix, &trigger);
if (r < 0)
return log_error_errno(r, "Failed to change unit suffix: %m");
break;
case UNIT_TIMER:
trigger = strdup(arg_unit);
if (!trigger)
return log_oom();
r = unit_name_change_suffix(trigger, ".service", &service);
if (r < 0)
return log_error_errno(r, "Failed to change unit suffix: %m");
break;
default:
r = unit_name_mangle_with_suffix(arg_unit, "as unit",
arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN,
".service", &service);
if (r < 0)
return log_error_errno(r, "Failed to mangle unit name: %m");
r = unit_name_mangle_with_suffix(arg_unit, "as trigger",
arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN,
suffix, &trigger);
if (r < 0)
return log_error_errno(r, "Failed to mangle unit name: %m");
break;
}
} else {
r = make_unit_name(bus, UNIT_SERVICE, &service);
if (r < 0)
return r;
r = unit_name_change_suffix(service, suffix, &trigger);
if (r < 0)
return log_error_errno(r, "Failed to change unit suffix: %m");
}
assert(message);
assert(suffix);
assert(trigger);
assert(service);
r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit");
if (r < 0)
@ -1690,11 +1711,81 @@ static int start_transient_trigger(
if (r < 0)
return bus_log_create_error(r);
*message = TAKE_PTR(m);
return 0;
}
static int start_transient_trigger(sd_bus *bus, const char *suffix) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
_cleanup_free_ char *trigger = NULL, *service = NULL;
const char *object = NULL;
int r;
assert(bus);
assert(suffix);
r = bus_wait_for_jobs_new(bus, &w);
if (r < 0)
return log_oom();
if (arg_unit) {
switch (unit_name_to_type(arg_unit)) {
case UNIT_SERVICE:
service = strdup(arg_unit);
if (!service)
return log_oom();
r = unit_name_change_suffix(service, suffix, &trigger);
if (r < 0)
return log_error_errno(r, "Failed to change unit suffix: %m");
break;
case UNIT_TIMER:
trigger = strdup(arg_unit);
if (!trigger)
return log_oom();
r = unit_name_change_suffix(trigger, ".service", &service);
if (r < 0)
return log_error_errno(r, "Failed to change unit suffix: %m");
break;
default:
r = unit_name_mangle_with_suffix(arg_unit, "as unit",
arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN,
".service", &service);
if (r < 0)
return log_error_errno(r, "Failed to mangle unit name: %m");
r = unit_name_mangle_with_suffix(arg_unit, "as trigger",
arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN,
suffix, &trigger);
if (r < 0)
return log_error_errno(r, "Failed to mangle unit name: %m");
break;
}
} else {
r = make_unit_name(bus, UNIT_SERVICE, &service);
if (r < 0)
return r;
r = unit_name_change_suffix(service, suffix, &trigger);
if (r < 0)
return log_error_errno(r, "Failed to change unit suffix: %m");
}
r = make_transient_trigger_unit(bus, &m, suffix, trigger, service);
if (r < 0)
return r;
polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
r = sd_bus_call(bus, m, 0, &error, &reply);
r = bus_call_with_hint(bus, m, suffix + 1, &reply);
if (r < 0)
return log_error_errno(r, "Failed to start transient %s unit: %s", suffix + 1, bus_error_message(&error, r));
return r;
r = sd_bus_message_read(reply, "o", &object);
if (r < 0)
@ -1710,7 +1801,7 @@ static int start_transient_trigger(
log_info("Will run service as unit: %s", service);
}
return 0;
return EXIT_SUCCESS;
}
static bool shall_make_executable_absolute(void) {
@ -1729,7 +1820,7 @@ static bool shall_make_executable_absolute(void) {
static int run(int argc, char* argv[]) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_free_ char *description = NULL;
int r, retval = EXIT_SUCCESS;
int r;
log_show_color(true);
log_parse_environment();
@ -1776,19 +1867,14 @@ static int run(int argc, char* argv[]) {
return bus_log_connect_error(r, arg_transport);
if (arg_scope)
r = start_transient_scope(bus);
else if (arg_path_property)
r = start_transient_trigger(bus, ".path");
else if (arg_socket_property)
r = start_transient_trigger(bus, ".socket");
else if (arg_with_timer)
r = start_transient_trigger(bus, ".timer");
else
r = start_transient_service(bus, &retval);
if (r < 0)
return r;
return retval;
return start_transient_scope(bus);
if (arg_path_property)
return start_transient_trigger(bus, ".path");
if (arg_socket_property)
return start_transient_trigger(bus, ".socket");
if (arg_with_timer)
return start_transient_trigger(bus, ".timer");
return start_transient_service(bus);
}
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);

View File

@ -14,6 +14,8 @@
#include "hexdecoct.h"
#include "hmac.h"
#include "lock-util.h"
#include "log.h"
#include "logarithm.h"
#include "memory-util.h"
#include "openssl-util.h"
#include "parse-util.h"
@ -774,7 +776,7 @@ size_t tpm2_tpms_pcr_selection_weight(const TPMS_PCR_SELECTION *s) {
uint32_t mask;
tpm2_tpms_pcr_selection_to_mask(s, &mask);
return (size_t)__builtin_popcount(mask);
return popcount(mask);
}
/* Utility functions for TPML_PCR_SELECTION. */

View File

@ -218,25 +218,6 @@ tests += [
'sources' : files('test-boot-timestamps.c'),
'condition' : 'ENABLE_EFI',
},
{
'sources' : files('test-bpf-devices.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : files('test-bpf-firewall.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : files('test-bpf-foreign-programs.c'),
'base' : test_core_base,
},
{
'sources' : files('test-bpf-lsm.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : files('test-btrfs.c'),
'type' : 'manual',
@ -252,27 +233,10 @@ tests += [
'sources' : files('test-capability.c'),
'dependencies' : libcap,
},
{
'sources' : files('test-cgroup-cpu.c'),
'base' : test_core_base,
},
{
'sources' : files('test-cgroup-mask.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : files('test-cgroup-unit-default.c'),
'base' : test_core_base,
},
{
'sources' : files('test-chase-manual.c'),
'type' : 'manual',
},
{
'sources' : files('test-chown-rec.c'),
'base' : test_core_base,
},
{
'sources' : files('test-compress-benchmark.c'),
'link_with' : [
@ -298,27 +262,12 @@ tests += [
'sources' : files('test-dlopen-so.c'),
'dependencies' : libp11kit_cflags
},
{
'sources' : files('test-emergency-action.c'),
'base' : test_core_base,
},
{
'sources' : files('test-engine.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : [
files('test-errno-list.c'),
generated_gperf_headers,
],
},
{
'sources' : files('test-execute.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
'timeout' : 360,
},
{
'sources' : files('test-fd-util.c'),
'dependencies' : libseccomp,
@ -331,11 +280,6 @@ tests += [
],
'timeout' : 180,
},
{
'sources' : files('test-install.c'),
'base' : test_core_base,
'type' : 'manual',
},
{
'sources' : [
files('test-ip-protocol-list.c'),
@ -346,11 +290,6 @@ tests += [
'sources' : files('test-ipcrm.c'),
'type' : 'unsafe',
},
{
'sources' : files('test-job-type.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : files('test-json.c'),
'dependencies' : libm,
@ -367,25 +306,10 @@ tests += [
threads,
],
},
{
'sources' : files('test-load-fragment.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : files('test-loop-block.c'),
'dependencies' : [threads, libblkid],
'base' : test_core_base,
'parallel' : false,
},
{
'sources' : files('test-loopback.c'),
'dependencies' : common_test_dependencies,
},
{
'sources' : files('test-manager.c'),
'base' : test_core_base,
},
{
'sources' : files('test-math-util.c'),
'dependencies' : libm,
@ -394,26 +318,12 @@ tests += [
'sources' : files('test-mempress.c'),
'dependencies' : threads,
},
{
'sources' : files('test-namespace.c'),
'dependencies' : [
libblkid,
threads,
],
'base' : test_core_base,
},
{
'sources' : files('test-netlink-manual.c'),
'dependencies' : libkmod,
'condition' : 'HAVE_KMOD',
'type' : 'manual',
},
{
'sources' : files('test-ns.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
'type' : 'manual',
},
{
'sources' : files('test-nscd-flush.c'),
'condition' : 'ENABLE_NSCD',
@ -440,12 +350,6 @@ tests += [
'sources' : files('test-parse-util.c'),
'dependencies' : libm,
},
{
'sources' : files('test-path.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
'timeout' : 120,
},
{
'sources' : files('test-process-util.c'),
'dependencies' : threads,
@ -464,11 +368,6 @@ tests += [
'condition' : 'ENABLE_BOOTLOADER',
'c_args' : '-I@0@'.format(efi_config_h_dir),
},
{
'sources' : files('test-sched-prio.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : files('test-seccomp.c'),
'dependencies' : libseccomp,
@ -526,6 +425,133 @@ tests += [
'includes' : udev_includes,
'type' : 'manual',
},
{
'sources' : files('test-utmp.c'),
'condition' : 'ENABLE_UTMP',
},
{
'sources' : files('test-varlink.c'),
'dependencies' : threads,
},
{
'sources' : files('test-watchdog.c'),
'type' : 'unsafe',
},
# Tests that link to libcore, i.e. tests for pid1 code.
{
'sources' : files('test-bpf-devices.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : files('test-bpf-firewall.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : files('test-bpf-foreign-programs.c'),
'base' : test_core_base,
},
{
'sources' : files('test-bpf-lsm.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : files('test-cgroup-cpu.c'),
'base' : test_core_base,
},
{
'sources' : files('test-cgroup-mask.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : files('test-cgroup-unit-default.c'),
'base' : test_core_base,
},
{
'sources' : files('test-chown-rec.c'),
'base' : test_core_base,
},
{
'sources' : files('test-core-unit.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : files('test-emergency-action.c'),
'base' : test_core_base,
},
{
'sources' : files('test-engine.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : files('test-execute.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
'timeout' : 360,
},
{
'sources' : files('test-install.c'),
'base' : test_core_base,
'type' : 'manual',
},
{
'sources' : files('test-job-type.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : files('test-load-fragment.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : files('test-loop-block.c'),
'dependencies' : [threads, libblkid],
'base' : test_core_base,
'parallel' : false,
},
{
'sources' : files('test-manager.c'),
'base' : test_core_base,
},
{
'sources' : files('test-namespace.c'),
'dependencies' : [
libblkid,
threads,
],
'base' : test_core_base,
},
{
'sources' : files('test-ns.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
'type' : 'manual',
},
{
'sources' : files('test-path.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
'timeout' : 120,
},
{
'sources' : files('test-sched-prio.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : files('test-socket-bind.c'),
'dependencies' : libdl,
'condition' : 'BPF_FRAMEWORK',
'base' : test_core_base,
},
{
'sources' : files('test-unit-name.c'),
'dependencies' : common_test_dependencies,
@ -536,30 +562,13 @@ tests += [
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : files('test-utmp.c'),
'condition' : 'ENABLE_UTMP',
},
{
'sources' : files('test-varlink.c'),
'dependencies' : threads,
},
{
'sources' : files('test-watch-pid.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
'sources' : files('test-watchdog.c'),
'type' : 'unsafe',
},
]
############################################################
# define some tests here, because the link_with deps were not defined earlier
tests += [
# Tests from other directories that have link_with deps that were not defined earlier
{
'sources' : files('../libsystemd/sd-bus/test-bus-error.c'),
'link_with' : [
@ -577,10 +586,4 @@ tests += [
'link_with' : libudev,
'dependencies' : threads,
},
{
'sources' : files('test-socket-bind.c'),
'dependencies' : libdl,
'condition' : 'BPF_FRAMEWORK',
'base' : test_core_base,
},
]

120
src/test/test-core-unit.c Normal file
View File

@ -0,0 +1,120 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "escape.h"
#include "tests.h"
#include "unit.h"
static void test_unit_escape_setting_one(
const char *s,
const char *expected_exec_env,
const char *expected_exec,
const char *expected_c) {
_cleanup_free_ char *a = NULL, *b, *c, *d,
*s_esc, *a_esc, *b_esc, *c_esc, *d_esc;
const char *t;
if (!expected_exec_env)
expected_exec_env = s;
if (!expected_exec)
expected_exec = expected_exec_env;
if (!expected_c)
expected_c = expected_exec;
assert_se(s_esc = cescape(s));
assert_se(t = unit_escape_setting(s, 0, &a));
assert_se(a_esc = cescape(t));
log_debug("%s: [%s] → [%s]", __func__, s_esc, a_esc);
assert_se(a == NULL);
assert_se(t == s);
assert_se(t = unit_escape_setting(s, UNIT_ESCAPE_EXEC_SYNTAX_ENV, &b));
assert_se(b_esc = cescape(t));
log_debug("%s: [%s] → [%s]", __func__, s_esc, b_esc);
assert_se(b == NULL || streq(b, t));
assert_se(streq(t, expected_exec_env));
assert_se(t = unit_escape_setting(s, UNIT_ESCAPE_EXEC_SYNTAX, &c));
assert_se(c_esc = cescape(t));
log_debug("%s: [%s] → [%s]", __func__, s_esc, c_esc);
assert_se(c == NULL || streq(c, t));
assert_se(streq(t, expected_exec));
assert_se(t = unit_escape_setting(s, UNIT_ESCAPE_C, &d));
assert_se(d_esc = cescape(t));
log_debug("%s: [%s] → [%s]", __func__, s_esc, d_esc);
assert_se(d == NULL || streq(d, t));
assert_se(streq(t, expected_c));
}
TEST(unit_escape_setting) {
test_unit_escape_setting_one("/sbin/sbash", NULL, NULL, NULL);
test_unit_escape_setting_one("$", "$$", "$", "$");
test_unit_escape_setting_one("$$", "$$$$", "$$", "$$");
test_unit_escape_setting_one("'", "'", NULL, "\\'");
test_unit_escape_setting_one("\"", "\\\"", NULL, NULL);
test_unit_escape_setting_one("\t", "\\t", NULL, NULL);
test_unit_escape_setting_one(" ", NULL, NULL, NULL);
test_unit_escape_setting_one("$;'\"\t\n", "$$;'\\\"\\t\\n", "$;'\\\"\\t\\n", "$;\\'\\\"\\t\\n");
}
static void test_unit_concat_strv_one(
char **s,
const char *expected_none,
const char *expected_exec_env,
const char *expected_exec,
const char *expected_c) {
_cleanup_free_ char *a, *b, *c, *d,
*s_ser, *s_esc, *a_esc, *b_esc, *c_esc, *d_esc;
assert_se(s_ser = strv_join(s, "_"));
assert_se(s_esc = cescape(s_ser));
if (!expected_exec_env)
expected_exec_env = expected_none;
if (!expected_exec)
expected_exec = expected_none;
if (!expected_c)
expected_c = expected_none;
assert_se(a = unit_concat_strv(s, 0));
assert_se(a_esc = cescape(a));
log_debug("%s: [%s] → [%s]", __func__, s_esc, a_esc);
assert_se(streq(a, expected_none));
assert_se(b = unit_concat_strv(s, UNIT_ESCAPE_EXEC_SYNTAX_ENV));
assert_se(b_esc = cescape(b));
log_debug("%s: [%s] → [%s]", __func__, s_esc, b_esc);
assert_se(streq(b, expected_exec_env));
assert_se(c = unit_concat_strv(s, UNIT_ESCAPE_EXEC_SYNTAX));
assert_se(c_esc = cescape(c));
log_debug("%s: [%s] → [%s]", __func__, s_esc, c_esc);
assert_se(streq(c, expected_exec));
assert_se(d = unit_concat_strv(s, UNIT_ESCAPE_C));
assert_se(d_esc = cescape(d));
log_debug("%s: [%s] → [%s]", __func__, s_esc, d_esc);
assert_se(streq(d, expected_c));
}
TEST(unit_concat_strv) {
test_unit_concat_strv_one(STRV_MAKE("a", "b", "c"),
"\"a\" \"b\" \"c\"",
NULL,
NULL,
NULL);
test_unit_concat_strv_one(STRV_MAKE("a", " ", "$", "$$", ""),
"\"a\" \" \" \"$\" \"$$\" \"\"",
"\"a\" \" \" \"$$\" \"$$$$\" \"\"",
NULL,
NULL);
test_unit_concat_strv_one(STRV_MAKE("\n", " ", "\t"),
"\"\n\" \" \" \"\t\"",
"\"\\n\" \" \" \"\\t\"",
"\"\\n\" \" \" \"\\t\"",
"\"\\n\" \" \" \"\\t\"");
}
DEFINE_TEST_MAIN(LOG_DEBUG);

View File

@ -71,4 +71,25 @@ TEST(log2i) {
assert_se(log2i(INT_MAX) == sizeof(int)*8-2);
}
TEST(popcount) {
uint16_t u16a = 0x0000;
uint16_t u16b = 0xFFFF;
uint32_t u32a = 0x00000010;
uint32_t u32b = 0xFFFFFFFF;
uint64_t u64a = 0x0000000000000010;
uint64_t u64b = 0x0100000000100010;
assert_se(popcount(u16a) == 0);
assert_se(popcount(u16b) == 16);
assert_se(popcount(u32a) == 1);
assert_se(popcount(u32b) == 32);
assert_se(popcount(u64a) == 1);
assert_se(popcount(u64b) == 3);
/* This would fail:
* error: _Generic selector of type int is not compatible with any association
* assert_se(popcount(0x10) == 1);
*/
}
DEFINE_TEST_MAIN(LOG_INFO);