core: create abstraction/more properties for the "Exec" part of Unit.StartTransient (#42360)
This is a bit of an RFC (but I hope I got it mostly right), @daandemeyer suggested in https://github.com/systemd/systemd/pull/42161#pullrequestreview-4336323314 to improve the abstractions around the Exec= in io.systemd.Unit.StartTransient as we will add a bunch more of those. So this PR adds first a better abstraction and then uses it. See the individual commits for details.
This commit is contained in:
+379
-168
@@ -691,7 +691,22 @@ typedef struct TransientExecContextParameters {
|
||||
const char *user;
|
||||
const char *group;
|
||||
char **supplementary_groups;
|
||||
int nice; /* INT_MAX means unset */
|
||||
bool nice_set;
|
||||
int nice;
|
||||
bool oom_score_adjust_set;
|
||||
int oom_score_adjust;
|
||||
bool umask_set;
|
||||
uint32_t umask;
|
||||
/* Tristate bools: -1 = absent, 0/1 = no/yes. */
|
||||
int dynamic_user;
|
||||
int ignore_sigpipe;
|
||||
int lock_personality;
|
||||
int memory_deny_write_execute;
|
||||
int no_new_privileges;
|
||||
int remove_ipc;
|
||||
int restrict_realtime;
|
||||
int restrict_suid_sgid;
|
||||
int root_ephemeral;
|
||||
} TransientExecContextParameters;
|
||||
|
||||
static void transient_set_credential_array_free(TransientSetCredential *items, size_t n) {
|
||||
@@ -723,6 +738,14 @@ static void transient_service_parameters_done(TransientServiceParameters *p) {
|
||||
free(p->exec_start);
|
||||
}
|
||||
|
||||
static void transient_service_parameters_init(TransientServiceParameters *p) {
|
||||
assert(p);
|
||||
*p = (TransientServiceParameters) {
|
||||
.type = _SERVICE_TYPE_INVALID,
|
||||
.remain_after_exit = -1,
|
||||
};
|
||||
}
|
||||
|
||||
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 },
|
||||
@@ -771,6 +794,15 @@ static void start_transient_context_parameters_done(StartTransientContextParamet
|
||||
transient_service_parameters_done(&p->service);
|
||||
}
|
||||
|
||||
static void transient_exec_context_parameters_init(TransientExecContextParameters *p);
|
||||
|
||||
static void start_transient_context_parameters_init(StartTransientContextParameters *p) {
|
||||
assert(p);
|
||||
*p = (StartTransientContextParameters) {};
|
||||
transient_exec_context_parameters_init(&p->exec);
|
||||
transient_service_parameters_init(&p->service);
|
||||
}
|
||||
|
||||
typedef struct StartTransientParameters {
|
||||
StartTransientContextParameters context;
|
||||
JobMode mode;
|
||||
@@ -785,6 +817,16 @@ static void start_transient_parameters_done(StartTransientParameters *p) {
|
||||
free(p->unsupported_property);
|
||||
}
|
||||
|
||||
static void start_transient_parameters_init(StartTransientParameters *p) {
|
||||
assert(p);
|
||||
*p = (StartTransientParameters) {
|
||||
.mode = JOB_REPLACE,
|
||||
.notify_job_changes = -1,
|
||||
.notify_unit_changes = -1,
|
||||
};
|
||||
start_transient_context_parameters_init(&p->context);
|
||||
}
|
||||
|
||||
static int dispatch_const_string_empty_as_null(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
||||
const char **s = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
@@ -809,11 +851,24 @@ static int dispatch_transient_working_directory(const char *name, sd_json_varian
|
||||
return sd_json_dispatch(variant, dispatch, flags, &p->working_directory);
|
||||
}
|
||||
|
||||
static int dispatch_transient_environment(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
||||
TransientExecContextParameters *p = ASSERT_PTR(userdata);
|
||||
p->environment_set = true;
|
||||
return sd_json_dispatch_strv(name, variant, flags, &p->environment);
|
||||
}
|
||||
/* Generate a parse wrapper that flips TransientExecContextParameters.<field>_set and delegates the
|
||||
* actual value parse to the named primitive dispatcher. For fields where the JSON value type alone
|
||||
* cannot distinguish "absent" from "explicitly set to the default" (int 0, empty strv, etc.). */
|
||||
#define DEFINE_TRANSIENT_EXEC_SETTABLE(field, primitive_dispatch) \
|
||||
static int dispatch_transient_##field( \
|
||||
const char *name, \
|
||||
sd_json_variant *variant, \
|
||||
sd_json_dispatch_flags_t flags, \
|
||||
void *userdata) { \
|
||||
TransientExecContextParameters *p = ASSERT_PTR(userdata); \
|
||||
p->field##_set = true; \
|
||||
return primitive_dispatch(name, variant, flags, &p->field); \
|
||||
}
|
||||
|
||||
DEFINE_TRANSIENT_EXEC_SETTABLE(environment, sd_json_dispatch_strv);
|
||||
DEFINE_TRANSIENT_EXEC_SETTABLE(nice, sd_json_dispatch_int32);
|
||||
DEFINE_TRANSIENT_EXEC_SETTABLE(oom_score_adjust, sd_json_dispatch_int32);
|
||||
DEFINE_TRANSIENT_EXEC_SETTABLE(umask, sd_json_dispatch_uint32);
|
||||
|
||||
static int dispatch_transient_set_credential_array(
|
||||
sd_json_variant *variant,
|
||||
@@ -869,24 +924,7 @@ static int dispatch_transient_set_credential_encrypted(const char *name, sd_json
|
||||
return dispatch_transient_set_credential_array(variant, &p->set_credentials_encrypted, &p->n_set_credentials_encrypted);
|
||||
}
|
||||
|
||||
static int dispatch_transient_exec_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
||||
/* Key names compatible with D-Bus property names. */
|
||||
static const sd_json_dispatch_field exec_dispatch[] = {
|
||||
{ "Environment", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_transient_environment, 0, 0 },
|
||||
{ "Group", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(TransientExecContextParameters, group), SD_JSON_RELAX },
|
||||
{ "Nice", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int32, offsetof(TransientExecContextParameters, nice), 0 },
|
||||
{ "SetCredential", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential, 0, 0 },
|
||||
{ "SetCredentialEncrypted", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential_encrypted, 0, 0 },
|
||||
{ "SupplementaryGroups", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(TransientExecContextParameters, supplementary_groups), 0 },
|
||||
{ "User", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(TransientExecContextParameters, user), SD_JSON_RELAX },
|
||||
{ "WorkingDirectory", SD_JSON_VARIANT_OBJECT, dispatch_transient_working_directory, 0, 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
StartTransientContextParameters *p = ASSERT_PTR(userdata);
|
||||
p->exec.present = true;
|
||||
return sd_json_dispatch_full(variant, exec_dispatch, /* bad= */ NULL, /* flags= */ 0, &p->exec, &p->bad_exec_field);
|
||||
}
|
||||
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_service(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
||||
static const sd_json_dispatch_field service_dispatch[] = {
|
||||
@@ -976,146 +1014,320 @@ static int transient_apply_set_credentials(
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int transient_exec_context_apply_properties(
|
||||
Unit *u,
|
||||
ExecContext *c,
|
||||
TransientExecContextParameters *p,
|
||||
const char **reterr_field) {
|
||||
static int apply_exec_environment(Unit *u, ExecContext *c, TransientExecContextParameters *p) {
|
||||
int r;
|
||||
|
||||
assert(p);
|
||||
|
||||
if (!p->environment_set)
|
||||
return 0;
|
||||
|
||||
r = exec_context_apply_environment(u, c, p->environment, UNIT_RUNTIME|UNIT_PRIVATE);
|
||||
if (IN_SET(r, -E2BIG, -EINVAL))
|
||||
/* Convert E2BIG into EINVAL so the central loop maps it to Exec.Environment. */
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
r == -E2BIG ? "Too many environment assignments." : "Invalid Environment list.");
|
||||
return r;
|
||||
}
|
||||
|
||||
static int apply_exec_nice(Unit *u, ExecContext *c, TransientExecContextParameters *p) {
|
||||
assert(p);
|
||||
|
||||
if (!p->nice_set)
|
||||
return 0;
|
||||
|
||||
if (!nice_is_valid(p->nice))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid Nice= value: %i", p->nice);
|
||||
|
||||
c->nice = p->nice;
|
||||
c->nice_set = true;
|
||||
|
||||
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "Nice", "Nice=%i", p->nice);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apply_exec_oom_score_adjust(Unit *u, ExecContext *c, TransientExecContextParameters *p) {
|
||||
if (!p->oom_score_adjust_set)
|
||||
return 0;
|
||||
|
||||
if (!oom_score_adjust_is_valid(p->oom_score_adjust))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Invalid OOMScoreAdjust= value: %i", p->oom_score_adjust);
|
||||
|
||||
c->oom_score_adjust = p->oom_score_adjust;
|
||||
c->oom_score_adjust_set = true;
|
||||
|
||||
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "OOMScoreAdjust",
|
||||
"OOMScoreAdjust=%i", p->oom_score_adjust);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apply_exec_set_credential(Unit *u, ExecContext *c, TransientExecContextParameters *p) {
|
||||
assert(p);
|
||||
|
||||
if (!p->set_credentials_set)
|
||||
return 0;
|
||||
return transient_apply_set_credentials(u, c, p->set_credentials, p->n_set_credentials, /* encrypted= */ false);
|
||||
}
|
||||
|
||||
static int apply_exec_set_credential_encrypted(Unit *u, ExecContext *c, TransientExecContextParameters *p) {
|
||||
assert(p);
|
||||
|
||||
if (!p->set_credentials_encrypted_set)
|
||||
return 0;
|
||||
return transient_apply_set_credentials(u, c, p->set_credentials_encrypted, p->n_set_credentials_encrypted, /* encrypted= */ true);
|
||||
}
|
||||
|
||||
static int apply_exec_supplementary_groups(Unit *u, ExecContext *c, TransientExecContextParameters *p) {
|
||||
_cleanup_free_ char *joined = NULL;
|
||||
int r;
|
||||
|
||||
assert(p);
|
||||
|
||||
if (!p->supplementary_groups)
|
||||
return 0;
|
||||
|
||||
STRV_FOREACH(g, p->supplementary_groups)
|
||||
if (!valid_user_group_name(*g, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Invalid supplementary group name: %s", *g);
|
||||
|
||||
r = strv_extend_strv(&c->supplementary_groups, p->supplementary_groups, /* filter_duplicates= */ true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
joined = strv_join(p->supplementary_groups, " ");
|
||||
if (!joined)
|
||||
return -ENOMEM;
|
||||
|
||||
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "SupplementaryGroups",
|
||||
"SupplementaryGroups=%s", joined);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apply_exec_umask(Unit *u, ExecContext *c, TransientExecContextParameters *p) {
|
||||
if (!p->umask_set)
|
||||
return 0;
|
||||
|
||||
/* mode_t bits beyond 07777 are reserved/meaningless; reject so a caller passing a stray
|
||||
* negative or out-of-range int fails clearly instead of having the kernel silently mask it. */
|
||||
if (p->umask > 07777)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Invalid UMask= value: %#" PRIo32, p->umask);
|
||||
|
||||
c->umask = (mode_t) p->umask;
|
||||
|
||||
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "UMask", "UMask=%04" PRIo32, p->umask);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apply_exec_working_directory(Unit *u, ExecContext *c, TransientExecContextParameters *p) {
|
||||
_cleanup_free_ char *simplified = NULL;
|
||||
TransientWorkingDirectory *wd = &p->working_directory;
|
||||
int r;
|
||||
|
||||
assert(p);
|
||||
|
||||
if (!p->working_directory_set)
|
||||
return 0;
|
||||
|
||||
if (wd->home && wd->path)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"WorkingDirectory: 'home' and 'path' are mutually exclusive");
|
||||
if (!wd->home && !wd->path)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"WorkingDirectory: must specify either 'home' or 'path'");
|
||||
|
||||
if (!wd->home) {
|
||||
if (!path_is_absolute(wd->path))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"WorkingDirectory: expects an absolute path");
|
||||
r = path_simplify_alloc(wd->path, &simplified);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (!path_is_normalized(simplified))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"WorkingDirectory: expects a normalized path");
|
||||
}
|
||||
|
||||
free_and_replace(c->working_directory, simplified);
|
||||
c->working_directory_home = wd->home;
|
||||
c->working_directory_missing_ok = wd->missing_ok;
|
||||
|
||||
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "WorkingDirectory",
|
||||
"WorkingDirectory=%s%s",
|
||||
c->working_directory_missing_ok ? "-" : "",
|
||||
c->working_directory_home ? "~" : strempty(c->working_directory));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Generate an apply fn for an exec-context string field: copy the parsed value to ExecContext and
|
||||
* write the corresponding "Name=value" line, skipping if the caller didn't set the field.
|
||||
* Used where the parsed value is NULL or already validated by the dispatch callback. */
|
||||
#define DEFINE_APPLY_EXEC_STRING(field, JsonName) \
|
||||
static int apply_exec_##field(Unit *u, ExecContext *c, TransientExecContextParameters *p) { \
|
||||
int r; \
|
||||
\
|
||||
assert(p); \
|
||||
\
|
||||
if (!p->field) \
|
||||
return 0; \
|
||||
\
|
||||
r = free_and_strdup(&c->field, p->field); \
|
||||
if (r < 0) \
|
||||
return r; \
|
||||
\
|
||||
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, JsonName, \
|
||||
JsonName "=%s", p->field); \
|
||||
return 0; \
|
||||
}
|
||||
|
||||
DEFINE_APPLY_EXEC_STRING(user, "User");
|
||||
DEFINE_APPLY_EXEC_STRING(group, "Group");
|
||||
|
||||
/* Generate an apply fn for an exec-context tristate bool: copy the parsed value to ExecContext and
|
||||
* write the corresponding "Name=yes/no" line, skipping if the caller didn't set the field.
|
||||
* Used for the sandbox bool batch where parse and apply are entirely mechanical. */
|
||||
#define DEFINE_APPLY_EXEC_TRISTATE_BOOL(field, JsonName) \
|
||||
static int apply_exec_##field(Unit *u, ExecContext *c, TransientExecContextParameters *p) { \
|
||||
assert(c); \
|
||||
assert(p); \
|
||||
if (p->field < 0) \
|
||||
return 0; \
|
||||
c->field = p->field; \
|
||||
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, JsonName, \
|
||||
JsonName "=%s", yes_no(p->field)); \
|
||||
return 0; \
|
||||
}
|
||||
|
||||
DEFINE_APPLY_EXEC_TRISTATE_BOOL(dynamic_user, "DynamicUser");
|
||||
DEFINE_APPLY_EXEC_TRISTATE_BOOL(ignore_sigpipe, "IgnoreSIGPIPE");
|
||||
DEFINE_APPLY_EXEC_TRISTATE_BOOL(lock_personality, "LockPersonality");
|
||||
DEFINE_APPLY_EXEC_TRISTATE_BOOL(memory_deny_write_execute, "MemoryDenyWriteExecute");
|
||||
DEFINE_APPLY_EXEC_TRISTATE_BOOL(no_new_privileges, "NoNewPrivileges");
|
||||
DEFINE_APPLY_EXEC_TRISTATE_BOOL(remove_ipc, "RemoveIPC");
|
||||
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");
|
||||
|
||||
/* Per-property descriptor for fields of the Exec context.
|
||||
*
|
||||
* json_name - JSON key inside the Exec object (e.g. "Nice").
|
||||
* err_field - Qualified field name used for varlink error replies (e.g. "Exec.Nice").
|
||||
* json_type - Expected JSON type for sd_json_dispatch_full().
|
||||
* dispatch - Parses the JSON value into TransientExecContextParameters.
|
||||
* parse_offset - offsetof() into TransientExecContextParameters; 0 if dispatch writes via &p directly.
|
||||
* json_flags - Per-field flags forwarded to sd_json_dispatch_full().
|
||||
* apply - Writes the parsed value to ExecContext. Returns 0 on success (whether or not
|
||||
* 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.
|
||||
*/
|
||||
typedef struct TransientExecProperty {
|
||||
const char *json_name;
|
||||
const char *err_field;
|
||||
sd_json_variant_type_t json_type;
|
||||
sd_json_dispatch_callback_t dispatch;
|
||||
size_t parse_offset;
|
||||
sd_json_dispatch_flags_t json_flags;
|
||||
int (*apply)(Unit *u, ExecContext *c, TransientExecContextParameters *p);
|
||||
bool tristate;
|
||||
} TransientExecProperty;
|
||||
|
||||
/* Property descriptors for the Exec object. Ordered to match src/shared/varlink-io.systemd.Unit.c so related
|
||||
* fields (e.g. User/Group) stay close. JSON keys match the D-Bus property names. */
|
||||
#define EXEC_PROPERTY(json, type, dispatch_fn, parse_offset, json_flags, apply_fn) \
|
||||
{ json, "Exec." json, type, dispatch_fn, parse_offset, json_flags, apply_fn }
|
||||
|
||||
/* Tristate-bool property: parsed via sd_json_dispatch_tristate into an int (-1 = absent).
|
||||
* Sets .tristate=true so transient_exec_context_parameters_init() seeds the int to -1 before
|
||||
* dispatch. */
|
||||
#define EXEC_PROPERTY_TRISTATE_BOOL(json, field) \
|
||||
{ json, "Exec." json, SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, \
|
||||
offsetof(TransientExecContextParameters, field), 0, apply_exec_##field, true }
|
||||
|
||||
/* String property: dispatched into a const char* field and applied via the matching
|
||||
* DEFINE_APPLY_EXEC_STRING() function. dispatch_fn/json_flags vary per field (e.g. User/Group use
|
||||
* json_dispatch_const_user_group_name with SD_JSON_RELAX). */
|
||||
#define EXEC_PROPERTY_STRING(json, field, dispatch_fn, json_flags) \
|
||||
{ json, "Exec." json, SD_JSON_VARIANT_STRING, dispatch_fn, \
|
||||
offsetof(TransientExecContextParameters, field), json_flags, apply_exec_##field }
|
||||
|
||||
static const TransientExecProperty exec_properties[] = {
|
||||
EXEC_PROPERTY ("WorkingDirectory", SD_JSON_VARIANT_OBJECT, dispatch_transient_working_directory, 0, 0, apply_exec_working_directory),
|
||||
EXEC_PROPERTY_TRISTATE_BOOL("RootEphemeral", root_ephemeral),
|
||||
EXEC_PROPERTY_STRING ("User", user, json_dispatch_const_user_group_name, SD_JSON_RELAX),
|
||||
EXEC_PROPERTY_STRING ("Group", group, json_dispatch_const_user_group_name, SD_JSON_RELAX),
|
||||
EXEC_PROPERTY_TRISTATE_BOOL("DynamicUser", dynamic_user),
|
||||
EXEC_PROPERTY ("SupplementaryGroups", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(TransientExecContextParameters, supplementary_groups), 0, apply_exec_supplementary_groups),
|
||||
EXEC_PROPERTY_TRISTATE_BOOL("NoNewPrivileges", no_new_privileges),
|
||||
EXEC_PROPERTY ("UMask", SD_JSON_VARIANT_INTEGER, dispatch_transient_umask, 0, 0, apply_exec_umask),
|
||||
EXEC_PROPERTY ("OOMScoreAdjust", SD_JSON_VARIANT_INTEGER, dispatch_transient_oom_score_adjust, 0, 0, apply_exec_oom_score_adjust),
|
||||
EXEC_PROPERTY_TRISTATE_BOOL("IgnoreSIGPIPE", ignore_sigpipe),
|
||||
EXEC_PROPERTY ("Nice", SD_JSON_VARIANT_INTEGER, dispatch_transient_nice, 0, 0, apply_exec_nice),
|
||||
EXEC_PROPERTY_TRISTATE_BOOL("LockPersonality", lock_personality),
|
||||
EXEC_PROPERTY_TRISTATE_BOOL("MemoryDenyWriteExecute", memory_deny_write_execute),
|
||||
EXEC_PROPERTY_TRISTATE_BOOL("RestrictRealtime", restrict_realtime),
|
||||
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 ("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),
|
||||
};
|
||||
#undef EXEC_PROPERTY
|
||||
#undef EXEC_PROPERTY_TRISTATE_BOOL
|
||||
#undef EXEC_PROPERTY_STRING
|
||||
|
||||
/* 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
|
||||
* 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);
|
||||
FOREACH_ELEMENT(prop, exec_properties)
|
||||
if (prop->tristate)
|
||||
*(int*) ((uint8_t*) p + prop->parse_offset) = -1;
|
||||
}
|
||||
|
||||
static int dispatch_transient_exec_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
||||
/* Build dispatch table only once, its constant. */
|
||||
static sd_json_dispatch_field exec_dispatch[ELEMENTSOF(exec_properties) + 1] = {};
|
||||
static bool exec_dispatch_set = false;
|
||||
|
||||
StartTransientContextParameters *p = ASSERT_PTR(userdata);
|
||||
|
||||
if (!exec_dispatch_set) {
|
||||
FOREACH_ELEMENT(prop, exec_properties)
|
||||
exec_dispatch[prop - exec_properties] = (sd_json_dispatch_field) {
|
||||
.name = prop->json_name,
|
||||
.type = prop->json_type,
|
||||
.callback = prop->dispatch,
|
||||
.offset = prop->parse_offset,
|
||||
.flags = prop->json_flags,
|
||||
};
|
||||
exec_dispatch_set = true;
|
||||
}
|
||||
|
||||
p->exec.present = true;
|
||||
return sd_json_dispatch_full(variant, exec_dispatch, /* bad= */ NULL, /* flags= */ 0, &p->exec, &p->bad_exec_field);
|
||||
}
|
||||
|
||||
static int transient_exec_context_apply_properties(Unit *u, ExecContext *c, TransientExecContextParameters *p, const char **reterr_field) {
|
||||
int r;
|
||||
|
||||
assert(u);
|
||||
assert(c);
|
||||
assert(p);
|
||||
|
||||
if (p->working_directory_set) {
|
||||
TransientWorkingDirectory *wd = &p->working_directory;
|
||||
_cleanup_free_ char *simplified = NULL;
|
||||
|
||||
if (wd->home && wd->path) {
|
||||
FOREACH_ELEMENT(prop, exec_properties) {
|
||||
r = prop->apply(u, c, p);
|
||||
if (r < 0) {
|
||||
if (reterr_field)
|
||||
*reterr_field = "Exec.WorkingDirectory";
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"WorkingDirectory: 'home' and 'path' are mutually exclusive");
|
||||
*reterr_field = r == -EINVAL ? prop->err_field : NULL;
|
||||
return r;
|
||||
}
|
||||
if (!wd->home && !wd->path) {
|
||||
if (reterr_field)
|
||||
*reterr_field = "Exec.WorkingDirectory";
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"WorkingDirectory: must specify either 'home' or 'path'");
|
||||
}
|
||||
|
||||
if (!wd->home) {
|
||||
if (!path_is_absolute(wd->path)) {
|
||||
if (reterr_field)
|
||||
*reterr_field = "Exec.WorkingDirectory";
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"WorkingDirectory: expects an absolute path");
|
||||
}
|
||||
r = path_simplify_alloc(wd->path, &simplified);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (!path_is_normalized(simplified)) {
|
||||
if (reterr_field)
|
||||
*reterr_field = "Exec.WorkingDirectory";
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"WorkingDirectory: expects a normalized path");
|
||||
}
|
||||
}
|
||||
|
||||
free_and_replace(c->working_directory, simplified);
|
||||
c->working_directory_home = wd->home;
|
||||
c->working_directory_missing_ok = wd->missing_ok;
|
||||
|
||||
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "WorkingDirectory",
|
||||
"WorkingDirectory=%s%s",
|
||||
c->working_directory_missing_ok ? "-" : "",
|
||||
c->working_directory_home ? "~" : strempty(c->working_directory));
|
||||
}
|
||||
|
||||
if (p->environment_set) {
|
||||
r = exec_context_apply_environment(u, c, p->environment, UNIT_RUNTIME|UNIT_PRIVATE);
|
||||
if (IN_SET(r, -E2BIG, -EINVAL)) {
|
||||
if (reterr_field)
|
||||
*reterr_field = "Exec.Environment";
|
||||
/* Convert E2BIG into EINVAL to ensure upper layers get the right error context */
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
r == -E2BIG ? "Too many environment assignments." : "Invalid Environment list.");
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (p->set_credentials_set) {
|
||||
r = transient_apply_set_credentials(u, c, p->set_credentials, p->n_set_credentials, /* encrypted= */ false);
|
||||
if (r == -EINVAL && reterr_field)
|
||||
*reterr_field = "Exec.SetCredential";
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (p->set_credentials_encrypted_set) {
|
||||
r = transient_apply_set_credentials(u, c, p->set_credentials_encrypted, p->n_set_credentials_encrypted, /* encrypted= */ true);
|
||||
if (r == -EINVAL && reterr_field)
|
||||
*reterr_field = "Exec.SetCredentialEncrypted";
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
/* User/Group names already validated by json_dispatch_const_user_group_name() in the dispatch table:
|
||||
* either NULL or a non-empty validated name. */
|
||||
if (p->user) {
|
||||
r = free_and_strdup(&c->user, p->user);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "User",
|
||||
"User=%s", p->user);
|
||||
}
|
||||
|
||||
if (p->group) {
|
||||
r = free_and_strdup(&c->group, p->group);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "Group",
|
||||
"Group=%s", p->group);
|
||||
}
|
||||
|
||||
if (p->supplementary_groups) {
|
||||
_cleanup_free_ char *joined = NULL;
|
||||
|
||||
STRV_FOREACH(g, p->supplementary_groups)
|
||||
if (!valid_user_group_name(*g, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)) {
|
||||
if (reterr_field)
|
||||
*reterr_field = "Exec.SupplementaryGroups";
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Invalid supplementary group name: %s", *g);
|
||||
}
|
||||
|
||||
r = strv_extend_strv(&c->supplementary_groups, p->supplementary_groups, /* filter_duplicates= */ true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
joined = strv_join(p->supplementary_groups, " ");
|
||||
if (!joined)
|
||||
return -ENOMEM;
|
||||
|
||||
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "SupplementaryGroups",
|
||||
"SupplementaryGroups=%s", joined);
|
||||
}
|
||||
|
||||
if (p->nice != INT_MAX) {
|
||||
if (!nice_is_valid(p->nice)) {
|
||||
if (reterr_field)
|
||||
*reterr_field = "Exec.Nice";
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid Nice= value: %i", p->nice);
|
||||
}
|
||||
|
||||
c->nice = p->nice;
|
||||
c->nice_set = true;
|
||||
|
||||
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "Nice", "Nice=%i", p->nice);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1214,14 +1426,7 @@ int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters
|
||||
};
|
||||
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error bus_error = SD_BUS_ERROR_NULL;
|
||||
_cleanup_(start_transient_parameters_done) StartTransientParameters p = {
|
||||
.mode = JOB_REPLACE,
|
||||
.notify_job_changes = -1,
|
||||
.notify_unit_changes = -1,
|
||||
.context.service.type = _SERVICE_TYPE_INVALID,
|
||||
.context.service.remain_after_exit = -1,
|
||||
.context.exec.nice = INT_MAX,
|
||||
};
|
||||
_cleanup_(start_transient_parameters_done) StartTransientParameters p = {};
|
||||
Manager *manager = ASSERT_PTR(userdata);
|
||||
const char *bad_field = NULL;
|
||||
Unit *u;
|
||||
@@ -1230,6 +1435,8 @@ int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters
|
||||
assert(link);
|
||||
assert(parameters);
|
||||
|
||||
start_transient_parameters_init(&p);
|
||||
|
||||
r = mac_selinux_access_check_varlink(link, "start");
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -1280,17 +1487,21 @@ int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters
|
||||
if (r < 0)
|
||||
return sd_varlink_error_errno(link, r);
|
||||
|
||||
/* Apply exec-specific properties from context.Exec */
|
||||
ExecContext *c = unit_get_exec_context(u);
|
||||
if (c) {
|
||||
/* Apply context.Exec only when an Exec object was actually sent. The tristate bool fields are
|
||||
* seeded to -1 ("absent") during Exec dispatch; without an Exec object they stay 0, which the
|
||||
* apply functions would misread as "explicitly false" and write out bogus FooBar=no lines. */
|
||||
if (p.context.exec.present) {
|
||||
ExecContext *c = unit_get_exec_context(u);
|
||||
if (!c)
|
||||
return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL);
|
||||
|
||||
bad_field = NULL;
|
||||
r = transient_exec_context_apply_properties(u, c, &p.context.exec, &bad_field);
|
||||
if (r == -EINVAL)
|
||||
return sd_varlink_error_invalid_parameter_name(link, bad_field ?: "Exec");
|
||||
if (r < 0)
|
||||
return sd_varlink_error_errno(link, r);
|
||||
} else if (p.context.exec.present)
|
||||
return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL);
|
||||
}
|
||||
|
||||
/* Apply service-specific properties from context.Service */
|
||||
Service *s = SERVICE(u);
|
||||
|
||||
@@ -239,6 +239,20 @@ systemctl show -P Group varlink-transient-ids.service | grep "^${NOBODY_GROUP}$"
|
||||
systemctl show -P SupplementaryGroups varlink-transient-ids.service | grep "${NOBODY_GROUP}" >/dev/null
|
||||
systemctl show -P Nice varlink-transient-ids.service | grep '^5$' >/dev/null
|
||||
|
||||
# Exec.OOMScoreAdjust, Exec.UMask, Exec.NoNewPrivileges, Exec.MemoryDenyWriteExecute
|
||||
# (int+validator, mode_t, two tristate-bool shapes)
|
||||
defer_transient_cleanup varlink-transient-procctl.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-procctl.service","Exec":{"OOMScoreAdjust":250,"UMask":18,"NoNewPrivileges":true,"MemoryDenyWriteExecute":true},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Exec.OOMScoreAdjust == 250'
|
||||
echo "$result" | jq -e '.context.Exec.UMask == 18'
|
||||
echo "$result" | jq -e '.context.Exec.NoNewPrivileges == true'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-procctl.service; do sleep 0.5; done'
|
||||
systemctl show -P OOMScoreAdjust varlink-transient-procctl.service | grep '^250$' >/dev/null
|
||||
systemctl show -P UMask varlink-transient-procctl.service | grep '^0022$' >/dev/null
|
||||
systemctl show -P NoNewPrivileges varlink-transient-procctl.service | grep '^yes$' >/dev/null
|
||||
systemctl show -P MemoryDenyWriteExecute varlink-transient-procctl.service | grep '^yes$' >/dev/null
|
||||
|
||||
# Error cases: verify specific varlink error types
|
||||
set +o pipefail
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
@@ -284,6 +298,11 @@ defer_transient_cleanup varlink-transient-bad-nice.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-nice.service","Exec":{"Nice":100},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.Nice"
|
||||
# Out-of-range OOMScoreAdjust= value is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-oom.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-oom.service","Exec":{"OOMScoreAdjust":9999},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.OOMScoreAdjust"
|
||||
# Invalid credential ID
|
||||
defer_transient_cleanup varlink-transient-bad-cred-id.service
|
||||
expect_invalid_parameter \
|
||||
|
||||
Reference in New Issue
Block a user