core: add WorkingDirectory to Unit.StartTransient
This commit adds setting the WorkingDirectory to the `io.systemd.Unit.StartTransient` varlink call. This is a first step towards more complete StartTransient in varlink. The goal is to be as close as possible to the D-Bus parameters. The exception is WorkingDirectory which is an object here so that we avoid the `-` prefixes and use a more type-safe approach by making it an explicit `missingOK` parameter. The key names stay the same as the D-Bus properties (PascalCase). If there are no equivalent D-Bus properties the native varlink convention of camelCase is used.
This commit is contained in:
+119
-7
@@ -702,6 +702,18 @@ 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);
|
||||
|
||||
typedef struct TransientWorkingDirectory {
|
||||
const char *path;
|
||||
bool home;
|
||||
bool missing_ok;
|
||||
} TransientWorkingDirectory;
|
||||
|
||||
typedef struct TransientExecContextParameters {
|
||||
bool present;
|
||||
bool working_directory_set;
|
||||
TransientWorkingDirectory working_directory;
|
||||
} TransientExecContextParameters;
|
||||
|
||||
typedef struct TransientServiceParameters {
|
||||
bool present;
|
||||
ServiceType type;
|
||||
@@ -753,7 +765,9 @@ static int dispatch_transient_exec_command(const char *name, sd_json_variant *va
|
||||
typedef struct StartTransientContextParameters {
|
||||
const char *id;
|
||||
const char *description;
|
||||
TransientExecContextParameters exec;
|
||||
TransientServiceParameters service;
|
||||
const char *bad_exec_field; /* Set by inner Exec dispatcher to the unknown sub-property name */
|
||||
} StartTransientContextParameters;
|
||||
|
||||
static void start_transient_context_parameters_done(StartTransientContextParameters *p) {
|
||||
@@ -766,12 +780,49 @@ typedef struct StartTransientParameters {
|
||||
JobMode mode;
|
||||
int notify_job_changes;
|
||||
int notify_unit_changes;
|
||||
const char *unsupported_property; /* For error reporting on unknown context fields */
|
||||
char *unsupported_property; /* For error reporting on unknown context fields */
|
||||
} StartTransientParameters;
|
||||
|
||||
static void start_transient_parameters_done(StartTransientParameters *p) {
|
||||
assert(p);
|
||||
start_transient_context_parameters_done(&p->context);
|
||||
free(p->unsupported_property);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
r = sd_json_dispatch_const_string(name, variant, flags, s);
|
||||
if (r >= 0 && isempty(*s))
|
||||
*s = NULL;
|
||||
return r;
|
||||
}
|
||||
|
||||
static int dispatch_transient_working_directory(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
||||
/* No equivalent D-Bus properties, so use varlink camelCase */
|
||||
static const sd_json_dispatch_field dispatch[] = {
|
||||
{ "path", SD_JSON_VARIANT_STRING, dispatch_const_string_empty_as_null, offsetof(TransientWorkingDirectory, path), 0 },
|
||||
{ "home", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(TransientWorkingDirectory, home), 0 },
|
||||
{ "missingOK", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(TransientWorkingDirectory, missing_ok), 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
TransientExecContextParameters *p = ASSERT_PTR(userdata);
|
||||
p->working_directory_set = true;
|
||||
return sd_json_dispatch(variant, dispatch, flags, &p->working_directory);
|
||||
}
|
||||
|
||||
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[] = {
|
||||
{ "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_service(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
||||
@@ -789,9 +840,10 @@ static int dispatch_transient_service(const char *name, sd_json_variant *variant
|
||||
|
||||
static int dispatch_transient_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
||||
static const sd_json_dispatch_field context_dispatch[] = {
|
||||
{ "ID", SD_JSON_VARIANT_STRING, json_dispatch_const_unit_name, offsetof(StartTransientContextParameters, id), SD_JSON_MANDATORY },
|
||||
{ "Description", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(StartTransientContextParameters, description), 0 },
|
||||
{ "Service", SD_JSON_VARIANT_OBJECT, dispatch_transient_service, 0, 0 },
|
||||
{ "ID", SD_JSON_VARIANT_STRING, json_dispatch_const_unit_name, offsetof(StartTransientContextParameters, id), SD_JSON_MANDATORY },
|
||||
{ "Description", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(StartTransientContextParameters, description), 0 },
|
||||
{ "Exec", SD_JSON_VARIANT_OBJECT, dispatch_transient_exec_context, 0, 0 },
|
||||
{ "Service", SD_JSON_VARIANT_OBJECT, dispatch_transient_service, 0, 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -802,10 +854,18 @@ static int dispatch_transient_context(const char *name, sd_json_variant *variant
|
||||
/* Don't propagate the caller's flags (in particular SD_JSON_MANDATORY from the outer 'context'
|
||||
* field) into the nested dispatch, otherwise every inner field becomes mandatory. */
|
||||
r = sd_json_dispatch_full(variant, context_dispatch, /* bad= */ NULL, /* flags= */ 0, &p->context, &bad_field);
|
||||
if (r == -EADDRNOTAVAIL && !isempty(bad_field))
|
||||
if (r == -EADDRNOTAVAIL && !isempty(bad_field)) {
|
||||
/* A UnitContext field that exists in the schema but is not settable at creation time: stash
|
||||
* the name so the caller can map this to io.systemd.Unit.PropertyNotSupported. */
|
||||
p->unsupported_property = bad_field;
|
||||
* the name so the caller can map this to io.systemd.Unit.PropertyNotSupported. If the
|
||||
* unknown field lives inside the nested Exec object, compose a dotted name to identify the
|
||||
* 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
|
||||
p->unsupported_property = strdup(bad_field);
|
||||
if (!p->unsupported_property)
|
||||
return -ENOMEM;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -825,6 +885,49 @@ static int transient_unit_apply_properties(Unit *u, StartTransientContextParamet
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int transient_exec_context_apply_properties(Unit *u, ExecContext *c, TransientExecContextParameters *p) {
|
||||
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)
|
||||
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;
|
||||
}
|
||||
|
||||
static int transient_service_apply_properties(Service *s, TransientServiceParameters *sp) {
|
||||
Unit *u = UNIT(ASSERT_PTR(s));
|
||||
int r;
|
||||
@@ -978,6 +1081,15 @@ int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters
|
||||
if (r < 0)
|
||||
return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL);
|
||||
|
||||
/* Apply exec-specific properties from context.Exec */
|
||||
ExecContext *c = unit_get_exec_context(u);
|
||||
if (c) {
|
||||
r = transient_exec_context_apply_properties(u, c, &p.context.exec);
|
||||
if (r < 0)
|
||||
return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL);
|
||||
} 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);
|
||||
if (s) {
|
||||
|
||||
@@ -421,10 +421,12 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
|
||||
/* ExecContext */
|
||||
static SD_VARLINK_DEFINE_STRUCT_TYPE(
|
||||
WorkingDirectory,
|
||||
SD_VARLINK_FIELD_COMMENT("The path to the working directory"),
|
||||
SD_VARLINK_DEFINE_FIELD(path, SD_VARLINK_STRING, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("The path to the working directory. Mutually exclusive with 'home'"),
|
||||
SD_VARLINK_DEFINE_FIELD(path, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("If true, use the configured user's home directory as the working directory. Mutually exclusive with 'path'"),
|
||||
SD_VARLINK_DEFINE_FIELD(home, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Whether the path to the working directory is allowed to not exist"),
|
||||
SD_VARLINK_DEFINE_FIELD(missingOK, SD_VARLINK_BOOL, 0));
|
||||
SD_VARLINK_DEFINE_FIELD(missingOK, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE));
|
||||
|
||||
static SD_VARLINK_DEFINE_STRUCT_TYPE(
|
||||
PartitionMountOptions,
|
||||
@@ -1182,8 +1184,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
|
||||
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, Service) 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. */
|
||||
* Description, Service, and the Exec subset {WorkingDirectory}) 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"),
|
||||
|
||||
@@ -621,6 +621,27 @@ result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/true"]'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-noargs.service; do sleep 0.5; done'
|
||||
|
||||
# Exec.WorkingDirectory
|
||||
defer_transient_cleanup varlink-transient-exec.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-exec.service","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Exec.WorkingDirectory.path == "/tmp"'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-exec.service; do sleep 0.5; done'
|
||||
systemctl show -P WorkingDirectory varlink-transient-exec.service | grep '^/tmp$' >/dev/null
|
||||
|
||||
# WorkingDirectory with missingOK=true (path does not exist but unit still starts)
|
||||
defer_transient_cleanup varlink-transient-wd-missing.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-wd-missing.service","Exec":{"WorkingDirectory":{"path":"/nonexistent/path","missingOK":true}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-wd-missing.service; do sleep 0.5; done'
|
||||
|
||||
# WorkingDirectory with home=true, missingOK omitted (defaults to false)
|
||||
defer_transient_cleanup varlink-transient-wd-home.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-wd-home.service","Exec":{"WorkingDirectory":{"home":true}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}'
|
||||
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
|
||||
|
||||
# Error cases: verify specific varlink error types
|
||||
set +o pipefail
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
@@ -634,6 +655,17 @@ varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
defer_transient_cleanup varlink-transient-badpath.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-badpath.service","Service":{"Type":"simple","ExecStart":[{"path":""}]}}}' |& grep "io.systemd.Unit.BadUnitSetting"
|
||||
# Relative WorkingDirectory path is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-wd.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-bad-wd.service","Exec":{"WorkingDirectory":{"path":"relative/path","missingOK":false}},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.BadUnitSetting"
|
||||
# Exec on a unit type without an exec context (.target) 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"
|
||||
# Unknown field in Exec is rejected as PropertyNotSupported
|
||||
defer_transient_cleanup varlink-transient-unknown-exec.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-unknown-exec.service","Exec":{"RootDirectory":"/tmp"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.PropertyNotSupported"
|
||||
set -o pipefail
|
||||
|
||||
transient_cleanup
|
||||
|
||||
Reference in New Issue
Block a user