core: open more fields up to StartTransientUnit via Varlink (#43516)
This is split out of #43058 but makes a ton of sense on its own.
This commit is contained in:
+5
-32
@@ -1634,14 +1634,6 @@ int bus_property_get_exec_ex_command_list(
|
||||
return sd_bus_message_close_container(reply);
|
||||
}
|
||||
|
||||
static char* exec_command_flags_to_exec_chars(ExecCommandFlags flags) {
|
||||
return strjoin(FLAGS_SET(flags, EXEC_COMMAND_IGNORE_FAILURE) ? "-" : "",
|
||||
FLAGS_SET(flags, EXEC_COMMAND_NO_ENV_EXPAND) ? ":" : "",
|
||||
FLAGS_SET(flags, EXEC_COMMAND_FULLY_PRIVILEGED) ? "+" : "",
|
||||
FLAGS_SET(flags, EXEC_COMMAND_NO_SETUID) ? "!" : "",
|
||||
FLAGS_SET(flags, EXEC_COMMAND_VIA_SHELL) ? "|" : "");
|
||||
}
|
||||
|
||||
int bus_set_transient_exec_command(
|
||||
Unit *u,
|
||||
const char *name,
|
||||
@@ -1767,32 +1759,13 @@ int bus_set_transient_exec_command(
|
||||
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);
|
||||
bool via_shell = FLAGS_SET(c->flags, EXEC_COMMAND_VIA_SHELL);
|
||||
_cleanup_free_ char *a = NULL;
|
||||
|
||||
exec_chars = exec_command_flags_to_exec_chars(c->flags);
|
||||
if (!exec_chars)
|
||||
return -ENOMEM;
|
||||
r = exec_command_to_setting(c, &a);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
a = unit_concat_strv(via_shell ? strv_skip(c->argv, 1) : c->argv, esc_flags);
|
||||
if (!a)
|
||||
return -ENOMEM;
|
||||
|
||||
if (via_shell || streq(c->path, c->argv[0]))
|
||||
fprintf(f, "%s=%s%s%s\n",
|
||||
written_name, exec_chars, via_shell && c->argv[0][0] == '-' ? "@" : "", a);
|
||||
else {
|
||||
_cleanup_free_ char *t = NULL;
|
||||
const char *p;
|
||||
|
||||
p = unit_escape_setting(c->path, esc_flags, &t);
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
fprintf(f, "%s=%s@%s %s\n", written_name, exec_chars, p, a);
|
||||
}
|
||||
fprintf(f, "%s=%s\n", written_name, a);
|
||||
}
|
||||
|
||||
r = memstream_finalize(&m, &buf, NULL);
|
||||
|
||||
@@ -459,7 +459,7 @@ static bool can_inherit_stderr_from_stdout(const ExecContext *context) {
|
||||
if (e == EXEC_OUTPUT_NAMED_FD)
|
||||
return false;
|
||||
|
||||
if (IN_SET(e, EXEC_OUTPUT_FILE, EXEC_OUTPUT_FILE_APPEND, EXEC_OUTPUT_FILE_TRUNCATE))
|
||||
if (exec_output_is_file(e))
|
||||
return path_equal(context->stdio_file[STDOUT_FILENO], context->stdio_file[STDERR_FILENO]);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -2317,6 +2317,56 @@ void exec_command_append_list(ExecCommand **l, ExecCommand *e) {
|
||||
*l = e;
|
||||
}
|
||||
|
||||
int exec_command_to_setting(const ExecCommand *c, char **ret) {
|
||||
_cleanup_free_ char *exec_chars = NULL, *a = NULL, *s = NULL;
|
||||
UnitWriteFlags esc_flags;
|
||||
bool via_shell;
|
||||
|
||||
assert(c);
|
||||
assert(c->path);
|
||||
assert(!strv_isempty(c->argv));
|
||||
assert(ret);
|
||||
|
||||
/* Formats the command the way it appears on the right-hand side of an ExecStart= (or similar)
|
||||
* assignment in a unit file, i.e. the prefix characters encoding the flags, followed by the
|
||||
* (escaped) command line. This is the inverse of the ExecStart= parser in load-fragment.c, and used
|
||||
* when writing transient unit files. */
|
||||
|
||||
esc_flags = UNIT_ESCAPE_SPECIFIERS |
|
||||
(FLAGS_SET(c->flags, EXEC_COMMAND_NO_ENV_EXPAND) ? UNIT_ESCAPE_EXEC_SYNTAX : UNIT_ESCAPE_EXEC_SYNTAX_ENV);
|
||||
via_shell = FLAGS_SET(c->flags, EXEC_COMMAND_VIA_SHELL);
|
||||
|
||||
exec_chars = exec_command_flags_to_exec_chars(c->flags);
|
||||
if (!exec_chars)
|
||||
return -ENOMEM;
|
||||
|
||||
/* For via-shell commands argv[0] is always "sh" or "-sh", which is implied by the "|" prefix
|
||||
* (plus "@" for the login shell case), hence skip it. */
|
||||
a = unit_concat_strv(via_shell ? strv_skip(c->argv, 1) : c->argv, esc_flags);
|
||||
if (!a)
|
||||
return -ENOMEM;
|
||||
|
||||
/* If argv[0] matches the path the path is implied, too. Note that we use streq() instead of
|
||||
* path_equal() here as argv[0] can be arbitrary and may not be a path. */
|
||||
if (via_shell || streq(c->path, c->argv[0]))
|
||||
s = strjoin(exec_chars, via_shell && c->argv[0][0] == '-' ? "@" : "", a);
|
||||
else {
|
||||
_cleanup_free_ char *t = NULL;
|
||||
const char *p;
|
||||
|
||||
p = unit_escape_setting(c->path, esc_flags, &t);
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
s = strjoin(exec_chars, "@", p, " ", a);
|
||||
}
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
|
||||
*ret = TAKE_PTR(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_command_set(ExecCommand *c, const char *path, ...) {
|
||||
va_list ap;
|
||||
char **l, *p;
|
||||
|
||||
@@ -513,6 +513,12 @@ static inline bool exec_input_is_inheritable(ExecInput i) {
|
||||
return exec_input_is_terminal(i) || IN_SET(i, EXEC_INPUT_SOCKET, EXEC_INPUT_NAMED_FD);
|
||||
}
|
||||
|
||||
static inline bool exec_output_is_file(ExecOutput o) {
|
||||
/* The output types that refer to a regular file (StandardOutput=file:…, append:…, truncate:…), i.e.
|
||||
* that require a companion path in ExecContext.stdio_file[] */
|
||||
return IN_SET(o, EXEC_OUTPUT_FILE, EXEC_OUTPUT_FILE_APPEND, EXEC_OUTPUT_FILE_TRUNCATE);
|
||||
}
|
||||
|
||||
int exec_spawn(
|
||||
Unit *unit,
|
||||
ExecCommand *command,
|
||||
@@ -534,6 +540,7 @@ void exec_command_reset_status_list_array(ExecCommand **c, size_t n);
|
||||
void exec_command_dump(ExecCommand *c, FILE *f, const char *prefix);
|
||||
void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix);
|
||||
void exec_command_append_list(ExecCommand **l, ExecCommand *e);
|
||||
int exec_command_to_setting(const ExecCommand *c, char **ret);
|
||||
int exec_command_set(ExecCommand *c, const char *path, ...) _sentinel_;
|
||||
int exec_command_append(ExecCommand *c, const char *path, ...) _sentinel_;
|
||||
|
||||
|
||||
@@ -6541,7 +6541,7 @@ int config_parse_output_restricted(
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (IN_SET(t, EXEC_OUTPUT_SOCKET, EXEC_OUTPUT_NAMED_FD, EXEC_OUTPUT_FILE, EXEC_OUTPUT_FILE_APPEND, EXEC_OUTPUT_FILE_TRUNCATE)) {
|
||||
if (IN_SET(t, EXEC_OUTPUT_SOCKET, EXEC_OUTPUT_NAMED_FD) || exec_output_is_file(t)) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, 0, "Standard output types socket, fd:, file:, append:, truncate: are not supported as defaults, ignoring: %s", rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
+200
-33
@@ -1,5 +1,7 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <paths.h>
|
||||
|
||||
#include "sd-bus.h"
|
||||
#include "sd-json.h"
|
||||
|
||||
@@ -654,6 +656,11 @@ void varlink_job_send_removed_signal(Job *j) {
|
||||
typedef struct TransientExecCommandItem {
|
||||
const char *path;
|
||||
char **arguments;
|
||||
bool ignore_failure;
|
||||
bool privileged;
|
||||
bool no_setuid;
|
||||
bool no_env_expand;
|
||||
bool via_shell;
|
||||
} TransientExecCommandItem;
|
||||
|
||||
static void transient_exec_command_item_done(TransientExecCommandItem *i) {
|
||||
@@ -664,6 +671,29 @@ static void transient_exec_command_item_done(TransientExecCommandItem *i) {
|
||||
static JSON_DISPATCH_ENUM_DEFINE(dispatch_service_type, ServiceType, service_type_from_string);
|
||||
static JSON_DISPATCH_ENUM_DEFINE(dispatch_job_mode, JobMode, job_mode_from_string);
|
||||
static JSON_DISPATCH_ENUM_DEFINE(dispatch_collect_mode, CollectMode, collect_mode_from_string);
|
||||
static JSON_DISPATCH_ENUM_DEFINE(dispatch_exec_input, ExecInput, exec_input_from_string);
|
||||
|
||||
static ExecOutput exec_output_from_string_harder(const char *s) {
|
||||
ExecOutput o;
|
||||
|
||||
/* Like exec_output_from_string(), but also accepts the spelling of the combined types used in the
|
||||
* Varlink IDL (and hence in our own JSON output), i.e. with "_" instead of "+", so that what we emit
|
||||
* can be fed back to us. */
|
||||
|
||||
o = exec_output_from_string(s);
|
||||
if (o >= 0)
|
||||
return o;
|
||||
|
||||
if (streq_ptr(s, "kmsg_console"))
|
||||
return EXEC_OUTPUT_KMSG_AND_CONSOLE;
|
||||
if (streq_ptr(s, "journal_console"))
|
||||
return EXEC_OUTPUT_JOURNAL_AND_CONSOLE;
|
||||
|
||||
return _EXEC_OUTPUT_INVALID;
|
||||
}
|
||||
|
||||
static JSON_DISPATCH_ENUM_DEFINE(dispatch_exec_output, ExecOutput, exec_output_from_string_harder);
|
||||
static JSON_DISPATCH_ENUM_DEFINE(dispatch_kill_mode, KillMode, kill_mode_from_string);
|
||||
|
||||
typedef struct TransientWorkingDirectory {
|
||||
const char *path;
|
||||
@@ -703,6 +733,7 @@ typedef struct TransientExecContextParameters {
|
||||
const char *root_image;
|
||||
const char *root_mstack;
|
||||
const char *root_verity;
|
||||
const char *tty_path;
|
||||
const char *user_namespace_path;
|
||||
bool nice_set;
|
||||
int nice;
|
||||
@@ -720,6 +751,10 @@ typedef struct TransientExecContextParameters {
|
||||
int restrict_realtime;
|
||||
int restrict_suid_sgid;
|
||||
int root_ephemeral;
|
||||
/* Enums: parsed via JSON_DISPATCH_ENUM_DEFINE dispatchers, negative = absent. */
|
||||
ExecInput std_input;
|
||||
ExecOutput std_output;
|
||||
ExecOutput std_error;
|
||||
} TransientExecContextParameters;
|
||||
|
||||
static void transient_set_credential_array_free(TransientSetCredential *items, size_t n) {
|
||||
@@ -736,6 +771,20 @@ static void transient_exec_context_parameters_done(TransientExecContextParameter
|
||||
transient_set_credential_array_free(p->set_credentials_encrypted, p->n_set_credentials_encrypted);
|
||||
}
|
||||
|
||||
typedef struct TransientKillContextParameters {
|
||||
bool present;
|
||||
KillMode kill_mode; /* negative = absent */
|
||||
int send_sighup; /* tristate: -1 = absent */
|
||||
} TransientKillContextParameters;
|
||||
|
||||
static void transient_kill_context_parameters_init(TransientKillContextParameters *p) {
|
||||
assert(p);
|
||||
*p = (TransientKillContextParameters) {
|
||||
.kill_mode = _KILL_MODE_INVALID,
|
||||
.send_sighup = -1,
|
||||
};
|
||||
}
|
||||
|
||||
typedef struct TransientServiceParameters {
|
||||
bool present;
|
||||
ServiceType type;
|
||||
@@ -778,8 +827,13 @@ static void transient_service_parameters_init(TransientServiceParameters *p) {
|
||||
|
||||
static int dispatch_transient_exec_command(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
||||
static const sd_json_dispatch_field exec_command_dispatch[] = {
|
||||
{ "path", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(TransientExecCommandItem, path), SD_JSON_MANDATORY },
|
||||
{ "arguments", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(TransientExecCommandItem, arguments), 0 },
|
||||
{ "path", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(TransientExecCommandItem, path), SD_JSON_MANDATORY },
|
||||
{ "arguments", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(TransientExecCommandItem, arguments), 0 },
|
||||
{ "ignoreFailure", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(TransientExecCommandItem, ignore_failure), 0 },
|
||||
{ "privileged", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(TransientExecCommandItem, privileged), 0 },
|
||||
{ "noSetuid", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(TransientExecCommandItem, no_setuid), 0 },
|
||||
{ "noEnvExpand", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(TransientExecCommandItem, no_env_expand), 0 },
|
||||
{ "viaShell", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(TransientExecCommandItem, via_shell), 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -814,8 +868,10 @@ typedef struct StartTransientContextParameters {
|
||||
const char *description;
|
||||
CollectMode collect_mode;
|
||||
TransientExecContextParameters exec;
|
||||
TransientKillContextParameters kill;
|
||||
TransientServiceParameters service;
|
||||
const char *bad_exec_field; /* Set by inner Exec dispatcher to the unknown sub-property name */
|
||||
const char *bad_kill_field;
|
||||
const char *bad_service_field;
|
||||
} StartTransientContextParameters;
|
||||
|
||||
@@ -833,6 +889,7 @@ static void start_transient_context_parameters_init(StartTransientContextParamet
|
||||
.collect_mode = _COLLECT_MODE_INVALID,
|
||||
};
|
||||
transient_exec_context_parameters_init(&p->exec);
|
||||
transient_kill_context_parameters_init(&p->kill);
|
||||
transient_service_parameters_init(&p->service);
|
||||
}
|
||||
|
||||
@@ -959,6 +1016,18 @@ static int dispatch_transient_set_credential_encrypted(const char *name, sd_json
|
||||
|
||||
static int dispatch_transient_exec_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
|
||||
|
||||
static int dispatch_transient_kill(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
||||
static const sd_json_dispatch_field kill_dispatch[] = {
|
||||
{ "KillMode", SD_JSON_VARIANT_STRING, dispatch_kill_mode, offsetof(TransientKillContextParameters, kill_mode), 0 },
|
||||
{ "SendSIGHUP", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(TransientKillContextParameters, send_sighup), 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
StartTransientContextParameters *p = ASSERT_PTR(userdata);
|
||||
p->kill.present = true;
|
||||
return sd_json_dispatch_full(variant, kill_dispatch, /* bad= */ NULL, /* flags= */ 0, &p->kill, &p->bad_kill_field);
|
||||
}
|
||||
|
||||
static int dispatch_transient_service(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
||||
static const sd_json_dispatch_field service_dispatch[] = {
|
||||
{ "Type", SD_JSON_VARIANT_STRING, dispatch_service_type, offsetof(TransientServiceParameters, type), 0 },
|
||||
@@ -981,6 +1050,7 @@ static int dispatch_transient_context(const char *name, sd_json_variant *variant
|
||||
{ "Description", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(StartTransientContextParameters, description), 0 },
|
||||
{ "CollectMode", SD_JSON_VARIANT_STRING, dispatch_collect_mode, offsetof(StartTransientContextParameters, collect_mode), 0 },
|
||||
{ "Exec", SD_JSON_VARIANT_OBJECT, dispatch_transient_exec_context, 0, 0 },
|
||||
{ "Kill", SD_JSON_VARIANT_OBJECT, dispatch_transient_kill, 0, 0 },
|
||||
{ "Service", SD_JSON_VARIANT_OBJECT, dispatch_transient_service, 0, 0 },
|
||||
{}
|
||||
};
|
||||
@@ -999,6 +1069,8 @@ static int dispatch_transient_context(const char *name, sd_json_variant *variant
|
||||
* actual sub-property. */
|
||||
if (streq(bad_field, "Exec") && !isempty(p->context.bad_exec_field))
|
||||
p->unsupported_property = strjoin("Exec.", p->context.bad_exec_field);
|
||||
else if (streq(bad_field, "Kill") && !isempty(p->context.bad_kill_field))
|
||||
p->unsupported_property = strjoin("Kill.", p->context.bad_kill_field);
|
||||
else if (streq(bad_field, "Service") && !isempty(p->context.bad_service_field))
|
||||
p->unsupported_property = strjoin("Service.", p->context.bad_service_field);
|
||||
else
|
||||
@@ -1030,6 +1102,24 @@ static int transient_unit_apply_properties(Unit *u, StartTransientContextParamet
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int transient_kill_context_apply_properties(Unit *u, KillContext *k, TransientKillContextParameters *p) {
|
||||
assert(u);
|
||||
assert(k);
|
||||
assert(p);
|
||||
|
||||
if (p->kill_mode >= 0) {
|
||||
k->kill_mode = p->kill_mode;
|
||||
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "KillMode", "KillMode=%s", kill_mode_to_string(p->kill_mode));
|
||||
}
|
||||
|
||||
if (p->send_sighup >= 0) {
|
||||
k->send_sighup = p->send_sighup;
|
||||
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "SendSIGHUP", "SendSIGHUP=%s", yes_no(p->send_sighup));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int transient_apply_set_credentials(
|
||||
Unit *u,
|
||||
ExecContext *c,
|
||||
@@ -1252,6 +1342,38 @@ DEFINE_APPLY_EXEC_TRISTATE_BOOL(restrict_realtime, "RestrictRealtime");
|
||||
DEFINE_APPLY_EXEC_TRISTATE_BOOL(restrict_suid_sgid, "RestrictSUIDSGID");
|
||||
DEFINE_APPLY_EXEC_TRISTATE_BOOL(root_ephemeral, "RootEphemeral");
|
||||
|
||||
/* The stdio types that require a companion setting: the file based ones need a path (StandardInputFile=,
|
||||
* StandardOutputFile=, …) and StandardInput=data needs the payload (StandardInputData=). The Varlink
|
||||
* interface has no way to pass either yet, and without them the executor cannot set them up (it would assert
|
||||
* on the missing path, and silently fall back to /dev/null on the missing payload), hence refuse them for
|
||||
* now. For the output types exec_output_is_file() expresses the same. */
|
||||
static bool exec_input_needs_companion(ExecInput i) {
|
||||
return IN_SET(i, EXEC_INPUT_FILE, EXEC_INPUT_DATA);
|
||||
}
|
||||
|
||||
/* Generate an apply fn for an exec-context enum field: copy the parsed value to ExecContext and
|
||||
* write the corresponding "Name=value" line. A negative parsed value means the field was absent, values
|
||||
* for which refused_fn() returns true are rejected as invalid.
|
||||
* Mirrors D-Bus BUS_DEFINE_SET_TRANSIENT_PARSE. */
|
||||
#define DEFINE_APPLY_EXEC_ENUM(field, JsonName, to_string_fn, refused_fn) \
|
||||
static int apply_exec_##field(Unit *u, ExecContext *c, TransientExecContextParameters *p) { \
|
||||
assert(c); \
|
||||
assert(p); \
|
||||
if (p->field < 0) \
|
||||
return 0; \
|
||||
if (refused_fn(p->field)) \
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), \
|
||||
JsonName "=%s is not supported via Varlink.", to_string_fn(p->field)); \
|
||||
c->field = p->field; \
|
||||
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, JsonName, \
|
||||
JsonName "=%s", to_string_fn(p->field)); \
|
||||
return 0; \
|
||||
}
|
||||
|
||||
DEFINE_APPLY_EXEC_ENUM(std_error, "StandardError", exec_output_to_string, exec_output_is_file);
|
||||
DEFINE_APPLY_EXEC_ENUM(std_input, "StandardInput", exec_input_to_string, exec_input_needs_companion);
|
||||
DEFINE_APPLY_EXEC_ENUM(std_output, "StandardOutput", exec_output_to_string, exec_output_is_file);
|
||||
|
||||
/* Generate an apply fn for an exec-context absolute-path string: validate via path_is_absolute(),
|
||||
* copy into ExecContext, write "Name=<path>". Mirrors D-Bus bus_set_transient_path. */
|
||||
#define DEFINE_APPLY_EXEC_ABSOLUTE_PATH(field, JsonName) \
|
||||
@@ -1278,6 +1400,7 @@ DEFINE_APPLY_EXEC_ABSOLUTE_PATH(root_directory, "RootDirectory");
|
||||
DEFINE_APPLY_EXEC_ABSOLUTE_PATH(root_image, "RootImage");
|
||||
DEFINE_APPLY_EXEC_ABSOLUTE_PATH(root_mstack, "RootMStack");
|
||||
DEFINE_APPLY_EXEC_ABSOLUTE_PATH(root_verity, "RootVerity");
|
||||
DEFINE_APPLY_EXEC_ABSOLUTE_PATH(tty_path, "TTYPath");
|
||||
DEFINE_APPLY_EXEC_ABSOLUTE_PATH(user_namespace_path, "UserNamespacePath");
|
||||
|
||||
/* RootHashPath and RootHashSignaturePath need custom apply fns: the corresponding unit-file
|
||||
@@ -1341,7 +1464,8 @@ static int apply_exec_root_hash_sig_path(Unit *u, ExecContext *c, TransientExecC
|
||||
* the property was set), <0 on error. -EINVAL is mapped to err_field by the
|
||||
* central loop.
|
||||
* tristate - If true, the int at parse_offset is initialized to -1 (absent sentinel) by
|
||||
* transient_exec_context_parameters_init(). Defaults to false.
|
||||
* transient_exec_context_parameters_init(). Used for tristate bools and enum
|
||||
* fields (both treat negative as absent). Defaults to false.
|
||||
*/
|
||||
typedef struct TransientExecProperty {
|
||||
const char *json_name;
|
||||
@@ -1378,6 +1502,13 @@ typedef struct TransientExecProperty {
|
||||
{ json, "Exec." json, SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, \
|
||||
offsetof(TransientExecContextParameters, field), 0, apply_exec_##field }
|
||||
|
||||
/* Enum property: parsed via a JSON_DISPATCH_ENUM_DEFINE dispatcher directly into the enum field at
|
||||
* parse_offset. Sets .tristate=true so the field is seeded to -1 (= absent) before dispatch; the
|
||||
* matching DEFINE_APPLY_EXEC_ENUM fn treats any negative value as unset. */
|
||||
#define EXEC_PROPERTY_ENUM(json, field, dispatch_fn) \
|
||||
{ json, "Exec." json, SD_JSON_VARIANT_STRING, dispatch_fn, \
|
||||
offsetof(TransientExecContextParameters, field), 0, apply_exec_##field, true }
|
||||
|
||||
static const TransientExecProperty exec_properties[] = {
|
||||
EXEC_PROPERTY ("WorkingDirectory", SD_JSON_VARIANT_OBJECT, dispatch_transient_working_directory, 0, 0, apply_exec_working_directory),
|
||||
EXEC_PROPERTY_ABSOLUTE_PATH("RootDirectory", root_directory),
|
||||
@@ -1405,6 +1536,10 @@ static const TransientExecProperty exec_properties[] = {
|
||||
EXEC_PROPERTY_TRISTATE_BOOL("RestrictSUIDSGID", restrict_suid_sgid),
|
||||
EXEC_PROPERTY_TRISTATE_BOOL("RemoveIPC", remove_ipc),
|
||||
EXEC_PROPERTY ("Environment", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_transient_environment, 0, 0, apply_exec_environment),
|
||||
EXEC_PROPERTY_ENUM ("StandardInput", std_input, dispatch_exec_input),
|
||||
EXEC_PROPERTY_ENUM ("StandardOutput", std_output, dispatch_exec_output),
|
||||
EXEC_PROPERTY_ENUM ("StandardError", std_error, dispatch_exec_output),
|
||||
EXEC_PROPERTY_ABSOLUTE_PATH("TTYPath", tty_path),
|
||||
EXEC_PROPERTY ("SetCredential", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential, 0, 0, apply_exec_set_credential),
|
||||
EXEC_PROPERTY ("SetCredentialEncrypted", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential_encrypted, 0, 0, apply_exec_set_credential_encrypted),
|
||||
};
|
||||
@@ -1412,14 +1547,21 @@ static const TransientExecProperty exec_properties[] = {
|
||||
#undef EXEC_PROPERTY_TRISTATE_BOOL
|
||||
#undef EXEC_PROPERTY_STRING
|
||||
#undef EXEC_PROPERTY_ABSOLUTE_PATH
|
||||
#undef EXEC_PROPERTY_ENUM
|
||||
|
||||
/* Tristate-bool fields default to -1 (= absent). Default-zeroed slots would look "explicitly false"
|
||||
* to the apply path. Initialize all tristate fields here so adding a new tristate property only
|
||||
/* Tristate-bool and enum fields default to -1 (= absent). Default-zeroed slots would look
|
||||
* "explicitly set" to the apply path. Initialize all tristate fields here so adding a new such property only
|
||||
* requires a new exec_properties[] row, never a fresh designated initializer at the call site.
|
||||
* The .tristate descriptor flag drives the loop -- mirrors the .cleanup hook used on the Service
|
||||
* side, and avoids fragile dispatch-function-pointer equality checks. */
|
||||
static void transient_exec_context_parameters_init(TransientExecContextParameters *p) {
|
||||
assert(p);
|
||||
|
||||
/* The enum fields are seeded through an int pointer below, hence make sure they really are that
|
||||
* wide (their dispatchers write them through their own type) */
|
||||
assert_cc(sizeof(ExecInput) == sizeof(int));
|
||||
assert_cc(sizeof(ExecOutput) == sizeof(int));
|
||||
|
||||
FOREACH_ELEMENT(prop, exec_properties)
|
||||
if (prop->tristate)
|
||||
*(int*) ((uint8_t*) p + prop->parse_offset) = -1;
|
||||
@@ -1565,11 +1707,24 @@ static int transient_service_apply_properties(Service *s, TransientServiceParame
|
||||
FOREACH_ARRAY(item, sp->exec_start, sp->n_exec_start) {
|
||||
_cleanup_(exec_command_freep) ExecCommand *c = NULL;
|
||||
_cleanup_strv_free_ char **argv = NULL;
|
||||
const char *path = item->path;
|
||||
|
||||
if (!filename_or_absolute_path_is_valid(item->path)) {
|
||||
if (item->privileged && item->no_setuid) {
|
||||
if (reterr_field)
|
||||
*reterr_field = "Service.ExecStart";
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid ExecStart path: %s", item->path);
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"ExecStart 'privileged' and 'noSetuid' flags may not be combined.");
|
||||
}
|
||||
|
||||
if (item->via_shell)
|
||||
/* Always normalize path (and argv[0] below) to "sh", matching the D-Bus and unit
|
||||
* file counterparts. The real shell of the target user is determined at execution
|
||||
* time. */
|
||||
path = _PATH_BSHELL;
|
||||
else if (!filename_or_absolute_path_is_valid(path)) {
|
||||
if (reterr_field)
|
||||
*reterr_field = "Service.ExecStart";
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid ExecStart path: %s", path);
|
||||
}
|
||||
|
||||
if (!strv_isempty(item->arguments)) {
|
||||
@@ -1582,13 +1737,29 @@ static int transient_service_apply_properties(Service *s, TransientServiceParame
|
||||
if (!c)
|
||||
return -ENOMEM;
|
||||
|
||||
r = path_simplify_alloc(item->path, &c->path);
|
||||
c->flags =
|
||||
(item->ignore_failure ? EXEC_COMMAND_IGNORE_FAILURE : 0) |
|
||||
(item->privileged ? EXEC_COMMAND_FULLY_PRIVILEGED : 0) |
|
||||
(item->no_setuid ? EXEC_COMMAND_NO_SETUID : 0) |
|
||||
(item->no_env_expand ? EXEC_COMMAND_NO_ENV_EXPAND : 0) |
|
||||
(item->via_shell ? EXEC_COMMAND_VIA_SHELL : 0);
|
||||
|
||||
r = path_simplify_alloc(path, &c->path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* If no arguments were provided, default argv[0] to the executable path.
|
||||
* Otherwise the caller is expected to include argv[0] in the arguments array. */
|
||||
if (strv_isempty(argv)) {
|
||||
if (item->via_shell) {
|
||||
/* Normalize argv[0] to "sh", preserving login shell semantics requested via a
|
||||
* leading dash. */
|
||||
if (strv_isempty(argv))
|
||||
r = strv_extend(&argv, "sh");
|
||||
else
|
||||
r = free_and_strdup(&argv[0], argv[0][0] == '-' ? "-sh" : "sh");
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else if (strv_isempty(argv)) {
|
||||
/* If no arguments were provided, default argv[0] to the executable path.
|
||||
* Otherwise the caller is expected to include argv[0] in the arguments array. */
|
||||
r = strv_extend(&argv, c->path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -1600,30 +1771,14 @@ static int transient_service_apply_properties(Service *s, TransientServiceParame
|
||||
}
|
||||
|
||||
/* Write ExecStart= lines to the transient file */
|
||||
if (sp->n_exec_start > 0) {
|
||||
UnitWriteFlags esc_flags = UNIT_ESCAPE_SPECIFIERS|UNIT_ESCAPE_EXEC_SYNTAX_ENV;
|
||||
LIST_FOREACH(command, c, s->exec_command[SERVICE_EXEC_START]) {
|
||||
_cleanup_free_ char *a = NULL;
|
||||
|
||||
LIST_FOREACH(command, c, s->exec_command[SERVICE_EXEC_START]) {
|
||||
_cleanup_free_ char *a = NULL;
|
||||
r = exec_command_to_setting(c, &a);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
a = unit_concat_strv(c->argv, esc_flags);
|
||||
if (!a)
|
||||
return -ENOMEM;
|
||||
|
||||
/* streq() instead path_equal() as argv[0] can be arbitrary and may not be a path */
|
||||
if (streq(c->path, c->argv[0]))
|
||||
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "ExecStart", "ExecStart=%s", a);
|
||||
else {
|
||||
_cleanup_free_ char *t = NULL;
|
||||
const char *p;
|
||||
|
||||
p = unit_escape_setting(c->path, esc_flags, &t);
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "ExecStart", "ExecStart=@%s %s", p, a);
|
||||
}
|
||||
}
|
||||
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "ExecStart", "ExecStart=%s", a);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1725,6 +1880,18 @@ int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters
|
||||
return sd_varlink_error_errno(link, r);
|
||||
}
|
||||
|
||||
/* Apply context.Kill only when a Kill object was actually sent, on unit types that have a kill
|
||||
* context. */
|
||||
if (p.context.kill.present) {
|
||||
KillContext *k = unit_get_kill_context(u);
|
||||
if (!k)
|
||||
return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL);
|
||||
|
||||
r = transient_kill_context_apply_properties(u, k, &p.context.kill);
|
||||
if (r < 0)
|
||||
return sd_varlink_error_errno(link, r);
|
||||
}
|
||||
|
||||
/* Apply service-specific properties from context.Service */
|
||||
Service *s = SERVICE(u);
|
||||
if (s) {
|
||||
|
||||
@@ -519,6 +519,16 @@ ExecCommandFlags exec_command_flags_from_string(const char *s) {
|
||||
return 1U << idx;
|
||||
}
|
||||
|
||||
char* exec_command_flags_to_exec_chars(ExecCommandFlags flags) {
|
||||
/* Returns the prefix characters that encode the flags in unit file ExecStart= lines, i.e. the
|
||||
* inverse of the prefix parsing in load-fragment.c */
|
||||
return strjoin(FLAGS_SET(flags, EXEC_COMMAND_IGNORE_FAILURE) ? "-" : "",
|
||||
FLAGS_SET(flags, EXEC_COMMAND_NO_ENV_EXPAND) ? ":" : "",
|
||||
FLAGS_SET(flags, EXEC_COMMAND_FULLY_PRIVILEGED) ? "+" : "",
|
||||
FLAGS_SET(flags, EXEC_COMMAND_NO_SETUID) ? "!" : "",
|
||||
FLAGS_SET(flags, EXEC_COMMAND_VIA_SHELL) ? "|" : "");
|
||||
}
|
||||
|
||||
int fexecve_or_execve(int executable_fd, const char *executable, char *const argv[], char *const envp[]) {
|
||||
/* Refuse invalid fds, regardless if fexecve() use is enabled or not */
|
||||
if (executable_fd < 0)
|
||||
|
||||
@@ -55,6 +55,7 @@ typedef enum ExecCommandFlags {
|
||||
|
||||
int exec_command_flags_from_strv(char * const *ex_opts, ExecCommandFlags *ret);
|
||||
int exec_command_flags_to_strv(ExecCommandFlags flags, char ***ret);
|
||||
char* exec_command_flags_to_exec_chars(ExecCommandFlags flags);
|
||||
|
||||
DECLARE_STRING_TABLE_LOOKUP(exec_command_flags, ExecCommandFlags);
|
||||
|
||||
|
||||
@@ -1569,11 +1569,11 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
|
||||
SD_VARLINK_FIELD_COMMENT("The parameter passed to the condition"),
|
||||
SD_VARLINK_DEFINE_FIELD(parameter, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
|
||||
|
||||
/* UnitContext is used both as input to StartTransient (subset settable at creation time: ID,
|
||||
* Description, CollectMode, Service, and the Exec subset {WorkingDirectory, Environment, SetCredential,
|
||||
* SetCredentialEncrypted}) and as output from List/StartTransient (full unit configuration). Fields
|
||||
* that are not settable at creation time are rejected with PropertyNotSupported when supplied as
|
||||
* input. */
|
||||
/* UnitContext is used both as input to StartTransient (subset settable at creation time: ID, Description,
|
||||
* CollectMode, Service, Kill (currently KillMode and SendSIGHUP only), and the Exec subset listed in
|
||||
* exec_properties[] in src/core/varlink-unit.c) and as output from List/StartTransient (full unit
|
||||
* configuration). Fields that are not settable at creation time are rejected with PropertyNotSupported when
|
||||
* supplied as input. */
|
||||
static SD_VARLINK_DEFINE_STRUCT_TYPE(
|
||||
UnitContext,
|
||||
SD_VARLINK_FIELD_COMMENT("The unit type"),
|
||||
|
||||
@@ -211,6 +211,99 @@ varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-wd-home.service; do sleep 0.5; done'
|
||||
systemctl show -P WorkingDirectory varlink-transient-wd-home.service | grep '^~$' >/dev/null
|
||||
|
||||
# Exec.StandardInput/StandardOutput/StandardError enum properties and Exec.TTYPath
|
||||
defer_transient_cleanup varlink-transient-stdio.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-stdio.service","Exec":{"StandardInput":"null","StandardOutput":"null","StandardError":"journal","TTYPath":"/dev/tty2"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Exec.StandardInput == "null"'
|
||||
echo "$result" | jq -e '.context.Exec.StandardOutput == "null"'
|
||||
echo "$result" | jq -e '.context.Exec.StandardError == "journal"'
|
||||
echo "$result" | jq -e '.context.Exec.TTYPath == "/dev/tty2"'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-stdio.service; do sleep 0.5; done'
|
||||
systemctl show -P StandardInput varlink-transient-stdio.service | grep '^null$' >/dev/null
|
||||
systemctl show -P StandardOutput varlink-transient-stdio.service | grep '^null$' >/dev/null
|
||||
systemctl show -P StandardError varlink-transient-stdio.service | grep '^journal$' >/dev/null
|
||||
systemctl show -P TTYPath varlink-transient-stdio.service | grep '^/dev/tty2$' >/dev/null
|
||||
# "null" is also the default for StandardInput=, hence additionally verify it was written explicitly
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-stdio.service)
|
||||
grep '^StandardInput=null$' "$fragment" >/dev/null
|
||||
|
||||
# The combined output types are spelled with a "+" in unit files, but with a "_" in the Varlink IDL (and
|
||||
# hence in our own replies). The latter spelling must be accepted, so that replies can be fed back in.
|
||||
defer_transient_cleanup varlink-transient-stdio-combined.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-stdio-combined.service","Exec":{"StandardOutput":"kmsg_console","StandardError":"journal_console"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Exec.StandardOutput == "kmsg_console"'
|
||||
echo "$result" | jq -e '.context.Exec.StandardError == "journal_console"'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-stdio-combined.service; do sleep 0.5; done'
|
||||
systemctl show -P StandardOutput varlink-transient-stdio-combined.service | grep '^kmsg+console$' >/dev/null
|
||||
systemctl show -P StandardError varlink-transient-stdio-combined.service | grep '^journal+console$' >/dev/null
|
||||
|
||||
# Kill.KillMode/Kill.SendSIGHUP
|
||||
defer_transient_cleanup varlink-transient-kill.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-kill.service","Kill":{"KillMode":"process","SendSIGHUP":true},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Kill.KillMode == "process"'
|
||||
echo "$result" | jq -e '.context.Kill.SendSIGHUP == true'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-kill.service; do sleep 0.5; done'
|
||||
systemctl show -P KillMode varlink-transient-kill.service | grep '^process$' >/dev/null
|
||||
systemctl show -P SendSIGHUP varlink-transient-kill.service | grep '^yes$' >/dev/null
|
||||
|
||||
# ExecStart execution flags, i.e. the Varlink counterparts of the "-", ":", "|", "+" and "!" prefixes of
|
||||
# unit file ExecStart= lines. First the ones observable from an unprivileged unit:
|
||||
# - ignoreFailure: a failing command does not fail the unit
|
||||
# - noEnvExpand: "${VAR}" in the arguments is passed as-is rather than expanded
|
||||
# - viaShell: the command line is handed to the user's shell via "-c" (argv[0] is normalized to "sh")
|
||||
rm -f /tmp/varlink-transient-execflags-*
|
||||
defer_transient_cleanup varlink-transient-execflags.service
|
||||
# shellcheck disable=SC2016
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-execflags.service","Exec":{"Environment":["FLAGTEST=expanded"]},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[
|
||||
{"path":"/bin/false","ignoreFailure":true},
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/tmp/varlink-transient-execflags-${FLAGTEST}"]},
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/tmp/varlink-transient-execflags-${FLAGTEST}"],"noEnvExpand":true},
|
||||
{"path":"/bin/sh","arguments":["sh","touch","/tmp/varlink-transient-execflags-shell"],"viaShell":true}]}}}')
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[0].ignoreFailure == true'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[1].noEnvExpand == false'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[2].noEnvExpand == true'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[3].viaShell == true'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[3].path == "/bin/sh"'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[3].arguments[0] == "sh"'
|
||||
# The unit only becomes active once all ExecStart= commands succeeded (or were allowed to fail)
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-execflags.service; do sleep 0.5; done'
|
||||
test -e /tmp/varlink-transient-execflags-expanded
|
||||
# shellcheck disable=SC2016
|
||||
test -e '/tmp/varlink-transient-execflags-${FLAGTEST}'
|
||||
test -e /tmp/varlink-transient-execflags-shell
|
||||
# The flags must show up as the matching prefix characters in the transient unit file
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-execflags.service)
|
||||
grep -E '^ExecStart=-"/bin/false"$' "$fragment" >/dev/null
|
||||
grep -E '^ExecStart=:' "$fragment" >/dev/null
|
||||
grep -E '^ExecStart=\|' "$fragment" >/dev/null
|
||||
rm -f /tmp/varlink-transient-execflags-*
|
||||
|
||||
# Now the privilege related ones, in a unit running as an unprivileged user:
|
||||
# - privileged/noSetuid: the command runs with full privileges, i.e. can write where the user cannot
|
||||
# - (neither): the command runs as the user, and fails to write there (tolerated via ignoreFailure)
|
||||
rm -f /run/varlink-transient-execflags-*
|
||||
defer_transient_cleanup varlink-transient-execflags-priv.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-execflags-priv.service","Exec":{"User":"nobody"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/run/varlink-transient-execflags-plain"],"ignoreFailure":true},
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/run/varlink-transient-execflags-privileged"],"privileged":true},
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/run/varlink-transient-execflags-nosetuid"],"noSetuid":true}]}}}')
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[0].privileged == false'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[1].privileged == true'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[2].noSetuid == true'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-execflags-priv.service; do sleep 0.5; done'
|
||||
test ! -e /run/varlink-transient-execflags-plain
|
||||
test -e /run/varlink-transient-execflags-privileged
|
||||
test -e /run/varlink-transient-execflags-nosetuid
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-execflags-priv.service)
|
||||
grep -E '^ExecStart=\+' "$fragment" >/dev/null
|
||||
grep -E '^ExecStart=!' "$fragment" >/dev/null
|
||||
rm -f /run/varlink-transient-execflags-*
|
||||
|
||||
# Exec.SetCredential: pass a credential and verify the running process can read it
|
||||
defer_transient_cleanup varlink-transient-cred.service
|
||||
CRED_VALUE_B64=$(printf 'secret-value' | base64 -w0)
|
||||
@@ -310,6 +403,11 @@ defer_transient_cleanup varlink-transient-badpath.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-badpath.service","Service":{"Type":"simple","ExecStart":[{"path":""}]}}}' \
|
||||
"Service.ExecStart"
|
||||
# The 'privileged' and 'noSetuid' execution flags are mutually exclusive
|
||||
defer_transient_cleanup varlink-transient-badflags.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-badflags.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true","privileged":true,"noSetuid":true}]}}}' \
|
||||
"Service.ExecStart"
|
||||
# Relative WorkingDirectory path is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-wd.service
|
||||
expect_invalid_parameter \
|
||||
@@ -340,6 +438,33 @@ defer_transient_cleanup varlink-transient-bad-rd.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-rd.service","Exec":{"RootDirectory":"relative/path"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.RootDirectory"
|
||||
# Note that an unparsable enum value (e.g. StandardOutput=bogus) is rejected by the Varlink IDL
|
||||
# validation layer before the JSON dispatch even runs, hence there's no point in testing pid1's
|
||||
# dispatch-level enum handling here — and doing so would trip post.sh's "didn't pass validation"
|
||||
# journal check, since the IDL layer logs about the rejection.
|
||||
# Relative TTYPath is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-ttypath.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-ttypath.service","Exec":{"TTYPath":"relative/tty"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.TTYPath"
|
||||
# The file based stdio types need a companion path, and StandardInput=data a companion payload, neither of
|
||||
# which can be passed yet, hence they are rejected
|
||||
for t in file append truncate; do
|
||||
defer_transient_cleanup "varlink-transient-bad-stdout-$t.service"
|
||||
expect_invalid_parameter \
|
||||
"{\"context\":{\"ID\":\"varlink-transient-bad-stdout-$t.service\",\"Exec\":{\"StandardOutput\":\"$t\"},\"Service\":{\"Type\":\"oneshot\",\"ExecStart\":[{\"path\":\"/bin/true\"}]}}}" \
|
||||
"Exec.StandardOutput"
|
||||
done
|
||||
defer_transient_cleanup varlink-transient-bad-stderr.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-stderr.service","Exec":{"StandardError":"file"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.StandardError"
|
||||
for t in file data; do
|
||||
defer_transient_cleanup "varlink-transient-bad-stdin-$t.service"
|
||||
expect_invalid_parameter \
|
||||
"{\"context\":{\"ID\":\"varlink-transient-bad-stdin-$t.service\",\"Exec\":{\"StandardInput\":\"$t\"},\"Service\":{\"Type\":\"oneshot\",\"ExecStart\":[{\"path\":\"/bin/true\"}]}}}" \
|
||||
"Exec.StandardInput"
|
||||
done
|
||||
# Invalid credential ID
|
||||
defer_transient_cleanup varlink-transient-bad-cred-id.service
|
||||
expect_invalid_parameter \
|
||||
@@ -353,6 +478,9 @@ expect_invalid_parameter \
|
||||
# Exec on a unit type without an exec context (.slice) is rejected
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-exec.slice","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false}}}}' |& grep "io.systemd.Unit.UnitTypeNotSupported"
|
||||
# Same for Kill on a unit type without a kill context
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-kill.slice","Kill":{"KillMode":"process"}}}' |& grep "io.systemd.Unit.UnitTypeNotSupported"
|
||||
# Unknown field in Exec is rejected as PropertyNotSupported
|
||||
defer_transient_cleanup varlink-transient-unknown-exec.service
|
||||
unsupported_exec=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
@@ -366,6 +494,12 @@ unsupported_service=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTra
|
||||
'{"context":{"ID":"varlink-transient-unknown-service.service","Service":{"Type":"oneshot","Restart":"always","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true)
|
||||
echo "$unsupported_service" | grep "io.systemd.Unit.PropertyNotSupported"
|
||||
echo "$unsupported_service" | grep "Service.Restart"
|
||||
# Same for a Kill field
|
||||
defer_transient_cleanup varlink-transient-unknown-kill.service
|
||||
unsupported_kill=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-unknown-kill.service","Kill":{"KillSignal":"SIGINT"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true)
|
||||
echo "$unsupported_kill" | grep "io.systemd.Unit.PropertyNotSupported"
|
||||
echo "$unsupported_kill" | grep "Kill.KillSignal"
|
||||
set -o pipefail
|
||||
|
||||
transient_cleanup
|
||||
|
||||
Reference in New Issue
Block a user