Merge pull request #4835 from poettering/unit-name-printf

Various specifier resolution fixes.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2016-12-10 01:29:52 -05:00 committed by GitHub
commit 1ac7a93574
8 changed files with 132 additions and 120 deletions

2
TODO
View File

@ -116,8 +116,6 @@ Features:
* journald: sigbus API via a signal-handler safe function that people may call
from the SIGBUS handler
* move specifier expansion from service_spawn() into load-fragment.c
* optionally, also require WATCHDOG=1 notifications during service start-up and shutdown
* resolved: when routing queries, make sure only look for the *longest* suffix...

View File

@ -1246,21 +1246,6 @@
<entry>This is either the unescaped instance name (if applicable) with <filename>/</filename> prepended (if applicable), or the unescaped prefix name prepended with <filename>/</filename>.</entry>
</row>
<row>
<entry><literal>%c</literal></entry>
<entry>Control group path of the unit</entry>
<entry>This path does not include the <filename>/sys/fs/cgroup/systemd/</filename> prefix.</entry>
</row>
<row>
<entry><literal>%r</literal></entry>
<entry>Control group path of the slice the unit is placed in</entry>
<entry>This usually maps to the parent control group path of <literal>%c</literal>.</entry>
</row>
<row>
<entry><literal>%R</literal></entry>
<entry>Root control group path below which slices and units are placed</entry>
<entry>For system instances, this resolves to <filename>/</filename>, except in containers, where this maps to the container's root control group path.</entry>
</row>
<row>
<entry><literal>%t</literal></entry>
<entry>Runtime directory</entry>
<entry>This is either <filename>/run</filename> (for the system manager) or the path <literal>$XDG_RUNTIME_DIR</literal> resolves to (for user managers).</entry>
@ -1314,13 +1299,6 @@
</tgroup>
</table>
<para>Please note that specifiers <literal>%U</literal>,
<literal>%h</literal>, <literal>%s</literal> are mostly useless
when systemd is running in system mode. PID 1 cannot query the
user account database for information, so the specifiers only work
as shortcuts for things which are already specified in a different
way in the unit file. They are fully functional when systemd is
running in <option>--user</option> mode.</para>
</refsect1>
<refsect1>

View File

@ -191,13 +191,13 @@ Unit.IgnoreOnIsolate, config_parse_bool, 0,
Unit.IgnoreOnSnapshot, config_parse_warn_compat, DISABLED_LEGACY, 0
Unit.JobTimeoutSec, config_parse_sec_fix_0, 0, offsetof(Unit, job_timeout)
Unit.JobTimeoutAction, config_parse_emergency_action, 0, offsetof(Unit, job_timeout_action)
Unit.JobTimeoutRebootArgument, config_parse_string, 0, offsetof(Unit, job_timeout_reboot_arg)
Unit.JobTimeoutRebootArgument, config_parse_unit_string_printf, 0, offsetof(Unit, job_timeout_reboot_arg)
Unit.StartLimitIntervalSec, config_parse_sec, 0, offsetof(Unit, start_limit.interval)
m4_dnl The following is a legacy alias name for compatibility
Unit.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_limit.interval)
Unit.StartLimitBurst, config_parse_unsigned, 0, offsetof(Unit, start_limit.burst)
Unit.StartLimitAction, config_parse_emergency_action, 0, offsetof(Unit, start_limit_action)
Unit.RebootArgument, config_parse_string, 0, offsetof(Unit, reboot_arg)
Unit.RebootArgument, config_parse_unit_string_printf, 0, offsetof(Unit, reboot_arg)
Unit.ConditionPathExists, config_parse_unit_condition_path, CONDITION_PATH_EXISTS, offsetof(Unit, conditions)
Unit.ConditionPathExistsGlob, config_parse_unit_condition_path, CONDITION_PATH_EXISTS_GLOB, offsetof(Unit, conditions)
Unit.ConditionPathIsDirectory, config_parse_unit_condition_path, CONDITION_PATH_IS_DIRECTORY, offsetof(Unit, conditions)
@ -254,7 +254,7 @@ m4_dnl The following three only exist for compatibility, they moved into Unit, s
Service.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_limit.interval)
Service.StartLimitBurst, config_parse_unsigned, 0, offsetof(Unit, start_limit.burst)
Service.StartLimitAction, config_parse_emergency_action, 0, offsetof(Unit, start_limit_action)
Service.RebootArgument, config_parse_string, 0, offsetof(Unit, reboot_arg)
Service.RebootArgument, config_parse_unit_path_printf, 0, offsetof(Unit, reboot_arg)
Service.FailureAction, config_parse_emergency_action, 0, offsetof(Service, emergency_action)
Service.Type, config_parse_service_type, 0, offsetof(Service, type)
Service.Restart, config_parse_service_restart, 0, offsetof(Service, restart)
@ -272,8 +272,8 @@ Service.FileDescriptorStoreMax, config_parse_unsigned, 0,
Service.NotifyAccess, config_parse_notify_access, 0, offsetof(Service, notify_access)
Service.Sockets, config_parse_service_sockets, 0, 0
Service.BusPolicy, config_parse_warn_compat, DISABLED_LEGACY, 0
Service.USBFunctionDescriptors, config_parse_path, 0, offsetof(Service, usb_function_descriptors)
Service.USBFunctionStrings, config_parse_path, 0, offsetof(Service, usb_function_strings)
Service.USBFunctionDescriptors, config_parse_unit_path_printf, 0, offsetof(Service, usb_function_descriptors)
Service.USBFunctionStrings, config_parse_unit_path_printf, 0, offsetof(Service, usb_function_strings)
EXEC_CONTEXT_CONFIG_ITEMS(Service)m4_dnl
CGROUP_CONTEXT_CONFIG_ITEMS(Service)m4_dnl
KILL_CONTEXT_CONFIG_ITEMS(Service)m4_dnl
@ -332,9 +332,9 @@ Socket.Service, config_parse_socket_service, 0,
Socket.TriggerLimitIntervalSec, config_parse_sec, 0, offsetof(Socket, trigger_limit.interval)
Socket.TriggerLimitBurst, config_parse_unsigned, 0, offsetof(Socket, trigger_limit.burst)
m4_ifdef(`HAVE_SMACK',
`Socket.SmackLabel, config_parse_string, 0, offsetof(Socket, smack)
Socket.SmackLabelIPIn, config_parse_string, 0, offsetof(Socket, smack_ip_in)
Socket.SmackLabelIPOut, config_parse_string, 0, offsetof(Socket, smack_ip_out)',
`Socket.SmackLabel, config_parse_unit_string_printf, 0, offsetof(Socket, smack)
Socket.SmackLabelIPIn, config_parse_unit_string_printf, 0, offsetof(Socket, smack_ip_in)
Socket.SmackLabelIPOut, config_parse_unit_string_printf, 0, offsetof(Socket, smack_ip_out)',
`Socket.SmackLabel, config_parse_warn_compat, DISABLED_CONFIGURATION, 0
Socket.SmackLabelIPIn, config_parse_warn_compat, DISABLED_CONFIGURATION, 0
Socket.SmackLabelIPOut, config_parse_warn_compat, DISABLED_CONFIGURATION, 0')
@ -354,9 +354,9 @@ BusName.AllowWorld, config_parse_bus_policy_world, 0,
BusName.SELinuxContext, config_parse_exec_selinux_context, 0, 0
BusName.AcceptFileDescriptors, config_parse_bool, 0, offsetof(BusName, accept_fd)
m4_dnl
Mount.What, config_parse_string, 0, offsetof(Mount, parameters_fragment.what)
Mount.What, config_parse_unit_string_printf, 0, offsetof(Mount, parameters_fragment.what)
Mount.Where, config_parse_path, 0, offsetof(Mount, where)
Mount.Options, config_parse_string, 0, offsetof(Mount, parameters_fragment.options)
Mount.Options, config_parse_unit_string_printf, 0, offsetof(Mount, parameters_fragment.options)
Mount.Type, config_parse_string, 0, offsetof(Mount, parameters_fragment.fstype)
Mount.TimeoutSec, config_parse_sec, 0, offsetof(Mount, timeout_usec)
Mount.DirectoryMode, config_parse_mode, 0, offsetof(Mount, directory_mode)
@ -373,7 +373,7 @@ Automount.TimeoutIdleSec, config_parse_sec, 0,
m4_dnl
Swap.What, config_parse_path, 0, offsetof(Swap, parameters_fragment.what)
Swap.Priority, config_parse_int, 0, offsetof(Swap, parameters_fragment.priority)
Swap.Options, config_parse_string, 0, offsetof(Swap, parameters_fragment.options)
Swap.Options, config_parse_unit_string_printf, 0, offsetof(Swap, parameters_fragment.options)
Swap.TimeoutSec, config_parse_sec, 0, offsetof(Swap, timeout_usec)
EXEC_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl
CGROUP_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl

View File

@ -579,6 +579,7 @@ int config_parse_exec(
void *userdata) {
ExecCommand **e = data;
Unit *u = userdata;
const char *p;
bool semicolon;
int r;
@ -604,7 +605,7 @@ int config_parse_exec(
_cleanup_free_ ExecCommand *nce = NULL;
_cleanup_strv_free_ char **n = NULL;
size_t nlen = 0, nbufsize = 0;
char *f;
const char *f;
int i;
semicolon = false;
@ -631,47 +632,47 @@ int config_parse_exec(
f++;
}
if (isempty(f)) {
r = unit_full_printf(u, f, &path);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", f);
return 0;
}
if (isempty(path)) {
/* First word is either "-" or "@" with no command. */
log_syntax(unit, LOG_ERR, filename, line, 0, "Empty path in command line, ignoring: \"%s\"", rvalue);
return 0;
}
if (!string_is_safe(f)) {
if (!string_is_safe(path)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Executable path contains special characters, ignoring: %s", rvalue);
return 0;
}
if (!path_is_absolute(f)) {
if (!path_is_absolute(path)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Executable path is not absolute, ignoring: %s", rvalue);
return 0;
}
if (endswith(f, "/")) {
if (endswith(path, "/")) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Executable path specifies a directory, ignoring: %s", rvalue);
return 0;
}
if (f == firstword) {
path = firstword;
firstword = NULL;
} else {
path = strdup(f);
if (!path)
return log_oom();
}
if (!separate_argv0) {
char *w = NULL;
if (!GREEDY_REALLOC(n, nbufsize, nlen + 2))
return log_oom();
f = strdup(path);
if (!f)
w = strdup(path);
if (!w)
return log_oom();
n[nlen++] = f;
n[nlen++] = w;
n[nlen] = NULL;
}
path_kill_slashes(path);
while (!isempty(p)) {
_cleanup_free_ char *word = NULL;
_cleanup_free_ char *word = NULL, *resolved = NULL;
/* Check explicitly for an unquoted semicolon as
* command separator token. */
@ -682,18 +683,21 @@ int config_parse_exec(
break;
}
/* Check for \; explicitly, to not confuse it with \\;
* or "\;" or "\\;" etc. extract_first_word would
* return the same for all of those. */
/* Check for \; explicitly, to not confuse it with \\; or "\;" or "\\;" etc.
* extract_first_word() would return the same for all of those. */
if (p[0] == '\\' && p[1] == ';' && (!p[2] || strchr(WHITESPACE, p[2]))) {
char *w;
p += 2;
p += strspn(p, WHITESPACE);
if (!GREEDY_REALLOC(n, nbufsize, nlen + 2))
return log_oom();
f = strdup(";");
if (!f)
w = strdup(";");
if (!w)
return log_oom();
n[nlen++] = f;
n[nlen++] = w;
n[nlen] = NULL;
continue;
}
@ -701,14 +705,20 @@ int config_parse_exec(
r = extract_first_word_and_warn(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE, unit, filename, line, rvalue);
if (r == 0)
break;
else if (r < 0)
if (r < 0)
return 0;
r = unit_full_printf(u, word, &resolved);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to resolve unit specifiers on %s, ignoring: %m", word);
return 0;
}
if (!GREEDY_REALLOC(n, nbufsize, nlen + 2))
return log_oom();
n[nlen++] = word;
n[nlen++] = resolved;
n[nlen] = NULL;
word = NULL;
resolved = NULL;
}
if (!n || !n[0]) {
@ -1326,7 +1336,7 @@ int config_parse_exec_selinux_context(
} else
ignore = false;
r = unit_name_printf(u, rvalue, &k);
r = unit_full_printf(u, rvalue, &k);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m");
return 0;
@ -1374,7 +1384,7 @@ int config_parse_exec_apparmor_profile(
} else
ignore = false;
r = unit_name_printf(u, rvalue, &k);
r = unit_full_printf(u, rvalue, &k);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m");
return 0;
@ -1422,7 +1432,7 @@ int config_parse_exec_smack_process_label(
} else
ignore = false;
r = unit_name_printf(u, rvalue, &k);
r = unit_full_printf(u, rvalue, &k);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m");
return 0;
@ -1689,7 +1699,7 @@ int config_parse_fdname(
return 0;
}
r = unit_name_printf(UNIT(s), rvalue, &p);
r = unit_full_printf(UNIT(s), rvalue, &p);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue);
return 0;
@ -2555,7 +2565,7 @@ int config_parse_unit_requires_mounts_for(
assert(data);
for (p = rvalue;; ) {
_cleanup_free_ char *word = NULL;
_cleanup_free_ char *word = NULL, *resolved = NULL;
r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
if (r == 0)
@ -2573,9 +2583,15 @@ int config_parse_unit_requires_mounts_for(
continue;
}
r = unit_require_mounts_for(u, word);
r = unit_full_printf(u, word, &resolved);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add required mount \"%s\", ignoring: %m", word);
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit name \"%s\", ignoring: %m", word);
continue;
}
r = unit_require_mounts_for(u, resolved);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add required mount \"%s\", ignoring: %m", resolved);
continue;
}
}
@ -3710,7 +3726,7 @@ int config_parse_runtime_directory(
return 0;
}
r = unit_name_printf(u, word, &k);
r = unit_full_printf(u, word, &k);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r,
"Failed to resolve specifiers in \"%s\", ignoring: %m", word);
@ -3812,8 +3828,8 @@ int config_parse_namespace_path_strv(
void *data,
void *userdata) {
Unit *u = userdata;
char*** sv = data;
const char *prev;
const char *cur;
int r;
@ -3828,10 +3844,10 @@ int config_parse_namespace_path_strv(
return 0;
}
prev = cur = rvalue;
cur = rvalue;
for (;;) {
_cleanup_free_ char *word = NULL;
int offset;
_cleanup_free_ char *word = NULL, *resolved = NULL, *joined = NULL;
bool ignore_enoent;
r = extract_first_word(&cur, &word, NULL, EXTRACT_QUOTES);
if (r == 0)
@ -3839,31 +3855,37 @@ int config_parse_namespace_path_strv(
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Trailing garbage, ignoring: %s", prev);
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract first word, ignoring: %s", rvalue);
return 0;
}
if (!utf8_is_valid(word)) {
log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, word);
prev = cur;
continue;
}
offset = word[0] == '-';
if (!path_is_absolute(word + offset)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute path, ignoring: %s", word);
prev = cur;
ignore_enoent = word[0] == '-';
r = unit_full_printf(u, word + ignore_enoent, &resolved);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers in %s: %m", word);
continue;
}
path_kill_slashes(word + offset);
if (!path_is_absolute(resolved)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute path, ignoring: %s", resolved);
continue;
}
r = strv_push(sv, word);
path_kill_slashes(resolved);
joined = strjoin(ignore_enoent ? "-" : "", resolved);
r = strv_push(sv, joined);
if (r < 0)
return log_oom();
prev = cur;
word = NULL;
joined = NULL;
}
return 0;

View File

@ -49,7 +49,6 @@
#include "string-util.h"
#include "strv.h"
#include "unit-name.h"
#include "unit-printf.h"
#include "unit.h"
#include "utf8.h"
#include "util.h"
@ -1205,7 +1204,7 @@ static int service_spawn(
ExecFlags flags,
pid_t *_pid) {
_cleanup_strv_free_ char **argv = NULL, **final_env = NULL, **our_env = NULL, **fd_names = NULL;
_cleanup_strv_free_ char **final_env = NULL, **our_env = NULL, **fd_names = NULL;
_cleanup_free_ int *fds = NULL;
unsigned n_fds = 0, n_env = 0;
const char *path;
@ -1263,10 +1262,6 @@ static int service_spawn(
if (r < 0)
return r;
r = unit_full_printf_strv(UNIT(s), c->argv, &argv);
if (r < 0)
return r;
our_env = new0(char*, 9);
if (!our_env)
return -ENOMEM;
@ -1349,7 +1344,7 @@ static int service_spawn(
} else
path = UNIT(s)->cgroup_path;
exec_params.argv = argv;
exec_params.argv = c->argv;
exec_params.environment = final_env;
exec_params.fds = fds;
exec_params.fd_names = fd_names;

View File

@ -54,7 +54,6 @@
#include "string-util.h"
#include "strv.h"
#include "unit-name.h"
#include "unit-printf.h"
#include "unit.h"
#include "user-util.h"
#include "in-addr-util.h"
@ -1740,7 +1739,6 @@ static int socket_coldplug(Unit *u) {
}
static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) {
_cleanup_free_ char **argv = NULL;
pid_t pid;
int r;
ExecParameters exec_params = {
@ -1772,11 +1770,7 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) {
if (r < 0)
return r;
r = unit_full_printf_strv(UNIT(s), c->argv, &argv);
if (r < 0)
return r;
exec_params.argv = argv;
exec_params.argv = c->argv;
exec_params.environment = UNIT(s)->manager->environment;
exec_params.confirm_spawn = manager_get_confirm_spawn(UNIT(s)->manager);
exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported;

View File

@ -78,12 +78,18 @@ static int specifier_filename(char specifier, void *data, void *userdata, char *
return unit_name_to_path(u->id, ret);
}
static void bad_specifier(Unit *u, char specifier) {
log_unit_warning(u, "Specifier '%%%c' used in unit configuration, which is deprecated. Please update your unit file, as it does not work as intended.", specifier);
}
static int specifier_cgroup(char specifier, void *data, void *userdata, char **ret) {
Unit *u = userdata;
char *n;
assert(u);
bad_specifier(u, specifier);
if (u->cgroup_path)
n = strdup(u->cgroup_path);
else
@ -101,6 +107,8 @@ static int specifier_cgroup_root(char specifier, void *data, void *userdata, cha
assert(u);
bad_specifier(u, specifier);
n = strdup(u->manager->cgroup_root);
if (!n)
return -ENOMEM;
@ -115,6 +123,8 @@ static int specifier_cgroup_slice(char specifier, void *data, void *userdata, ch
assert(u);
bad_specifier(u, specifier);
if (UNIT_ISSET(u->slice)) {
Unit *slice;
@ -194,13 +204,20 @@ static int specifier_user_shell(char specifier, void *data, void *userdata, char
int unit_name_printf(Unit *u, const char* format, char **ret) {
/*
* This will use the passed string as format string and
* replace the following specifiers:
* This will use the passed string as format string and replace the following specifiers (which should all be
* safe for inclusion in unit names):
*
* %n: the full id of the unit (foo@bar.waldo)
* %N: the id of the unit without the suffix (foo@bar)
* %p: the prefix (foo)
* %i: the instance (bar)
*
* %U: the UID of the running user
* %u: the username of the running user
*
* %m: the machine ID of the running system
* %H: the host name of the running system
* %b: the boot ID of the running system
*/
const Specifier table[] = {
@ -208,7 +225,14 @@ int unit_name_printf(Unit *u, const char* format, char **ret) {
{ 'N', specifier_prefix_and_instance, NULL },
{ 'p', specifier_prefix, NULL },
{ 'i', specifier_string, u->instance },
{ 0, NULL, NULL }
{ 'U', specifier_user_id, NULL },
{ 'u', specifier_user_name, NULL },
{ 'm', specifier_machine_id, NULL },
{ 'H', specifier_host_name, NULL },
{ 'b', specifier_boot_id, NULL },
{}
};
assert(u);
@ -220,22 +244,23 @@ int unit_name_printf(Unit *u, const char* format, char **ret) {
int unit_full_printf(Unit *u, const char *format, char **ret) {
/* This is similar to unit_name_printf() but also supports
* unescaping. Also, adds a couple of additional codes:
/* This is similar to unit_name_printf() but also supports unescaping. Also, adds a couple of additional codes
* (which are likely not suitable for unescaped inclusion in unit names):
*
* %f the instance if set, otherwise the id
* %c cgroup path of unit
* %r where units in this slice are placed in the cgroup tree
* %R the root of this systemd's instance tree
* %t the runtime directory to place sockets in (e.g. "/run" or $XDG_RUNTIME_DIR)
* %U the UID of the running user
* %u the username of the running user
* %h the homedir of the running user
* %s the shell of the running user
* %m the machine ID of the running system
* %H the host name of the running system
* %b the boot ID of the running system
* %v `uname -r` of the running system
* %f: the unescaped instance if set, otherwise the id unescaped as path
* %c: cgroup path of unit (deprecated)
* %r: where units in this slice are placed in the cgroup tree (deprecated)
* %R: the root of this systemd's instance tree (deprecated)
* %t: the runtime directory to place sockets in (e.g. "/run" or $XDG_RUNTIME_DIR)
*
* %h: the homedir of the running user
* %s: the shell of the running user
*
* %v: `uname -r` of the running system
*
* NOTICE: When you add new entries here, please be careful: specifiers which depend on settings of the unit
* file itself are broken by design, as they would resolve differently depending on whether they are used
* before or after the relevant configuration setting. Hence: don't add them.
*/
const Specifier table[] = {

View File

@ -409,8 +409,8 @@ static void test_exec_spec_interpolation(Manager *m) {
test(m, "exec-spec-interpolation.service", 0, CLD_EXITED);
}
static int run_tests(UnitFileScope scope, test_function_t *tests) {
test_function_t *test = NULL;
static int run_tests(UnitFileScope scope, const test_function_t *tests) {
const test_function_t *test = NULL;
Manager *m = NULL;
int r;
@ -433,7 +433,7 @@ static int run_tests(UnitFileScope scope, test_function_t *tests) {
}
int main(int argc, char *argv[]) {
test_function_t user_tests[] = {
static const test_function_t user_tests[] = {
test_exec_workingdirectory,
test_exec_personality,
test_exec_ignoresigpipe,
@ -464,7 +464,7 @@ int main(int argc, char *argv[]) {
test_exec_spec_interpolation,
NULL,
};
test_function_t system_tests[] = {
static const test_function_t system_tests[] = {
test_exec_systemcall_system_mode_with_user,
NULL,
};