This commit uses the abstractions added in the previous commit to
add a bunch more properties to the io.systemd.StartTransient()
to showcase how straightforward this is now.
New helpers for tristate bools and an init helper are added. A
dedicated dispatcher for LogLevelMax parses the string-form name
("info", "debug" etc.) declared in the varlink IDL.
The new properties are: DynamicUser, IgnoreSIGPIPE, LockPersonality,
MemoryDenyWriteExecute, NoNewPrivileges, OOMScoreAdjust, RemoveIPC,
RestrictRealtime, RestrictSUIDSGID, RootEphemeral, UMask.
The remaining ProtectKernel*, Private*, ProtectClock properties are
declared as STRING in the varlink IDL (matching the modern *Ex/enum
form) so a bool dispatcher does not pass schema validation. Those
need a string-parsing dispatcher and will be added in a follow-up.
This brings us closer to parity with the D-Bus code (still a long
way to go though).
1668 lines
77 KiB
C
1668 lines
77 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include "sd-bus.h"
|
|
#include "sd-json.h"
|
|
|
|
#include "bitfield.h"
|
|
#include "bus-polkit.h"
|
|
#include "cgroup.h"
|
|
#include "condition.h"
|
|
#include "dbus-job.h"
|
|
#include "execute.h"
|
|
#include "format-util.h"
|
|
#include "install.h"
|
|
#include "iovec-util.h"
|
|
#include "job.h"
|
|
#include "json-util.h"
|
|
#include "locale-util.h"
|
|
#include "manager.h"
|
|
#include "path-util.h"
|
|
#include "pidref.h"
|
|
#include "process-util.h"
|
|
#include "selinux-access.h"
|
|
#include "service.h"
|
|
#include "set.h"
|
|
#include "strv.h"
|
|
#include "unit-name.h"
|
|
#include "unit.h"
|
|
#include "user-util.h"
|
|
#include "varlink-automount.h"
|
|
#include "varlink-cgroup.h"
|
|
#include "varlink-common.h"
|
|
#include "varlink-execute.h"
|
|
#include "varlink-job.h"
|
|
#include "varlink-kill.h"
|
|
#include "varlink-mount.h"
|
|
#include "varlink-path.h"
|
|
#include "varlink-scope.h"
|
|
#include "varlink-service.h"
|
|
#include "varlink-socket.h"
|
|
#include "varlink-swap.h"
|
|
#include "varlink-timer.h"
|
|
#include "varlink-unit.h"
|
|
#include "varlink-util.h"
|
|
|
|
#define JSON_BUILD_EMERGENCY_ACTION_NON_EMPTY(name, value) \
|
|
SD_JSON_BUILD_PAIR_CONDITION(value > EMERGENCY_ACTION_NONE, name, JSON_BUILD_STRING_UNDERSCORIFY(emergency_action_to_string(value)))
|
|
|
|
static int unit_dependencies_build_json(sd_json_variant **ret, const char *name, void *userdata) {
|
|
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
|
Unit *u = ASSERT_PTR(userdata);
|
|
UnitDependency d;
|
|
int r;
|
|
|
|
assert(ret);
|
|
assert(name);
|
|
|
|
d = unit_dependency_from_string(name);
|
|
if (d < 0)
|
|
return log_debug_errno(d, "Failed to get unit dependency for '%s': %m", name);
|
|
|
|
void *value;
|
|
Unit *other;
|
|
HASHMAP_FOREACH_KEY(value, other, unit_get_dependencies(u, d)) {
|
|
r = sd_json_variant_append_arrayb(&v, SD_JSON_BUILD_STRING(other->id));
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
*ret = TAKE_PTR(v);
|
|
return 0;
|
|
}
|
|
|
|
static int unit_mounts_for_build_json(sd_json_variant **ret, const char *name, void *userdata) {
|
|
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
|
Hashmap **mounts_for = userdata;
|
|
UnitMountDependencyType d;
|
|
const char *p;
|
|
void *value;
|
|
int r;
|
|
|
|
assert(ret);
|
|
assert(name);
|
|
|
|
if (!mounts_for) {
|
|
*ret = NULL;
|
|
return 0;
|
|
}
|
|
|
|
d = unit_mount_dependency_type_from_string(name);
|
|
if (d < 0)
|
|
return log_debug_errno(d, "Failed to get unit mount dependency for '%s': %m", name);
|
|
|
|
HASHMAP_FOREACH_KEY(value, p, mounts_for[d]) {
|
|
r = sd_json_variant_append_arrayb(&v, SD_JSON_BUILD_STRING(p));
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
*ret = TAKE_PTR(v);
|
|
return 0;
|
|
}
|
|
|
|
static int unit_conditions_build_json(sd_json_variant **ret, const char *name, void *userdata) {
|
|
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
|
bool do_asserts = streq(name, "Asserts");
|
|
Condition *list = userdata;
|
|
int r;
|
|
|
|
assert(ret);
|
|
|
|
LIST_FOREACH(conditions, c, list) {
|
|
r = sd_json_variant_append_arraybo(
|
|
&v,
|
|
SD_JSON_BUILD_PAIR_STRING("type", do_asserts ? assert_type_to_string(c->type)
|
|
: condition_type_to_string(c->type)),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("trigger", c->trigger),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("negate", c->negate),
|
|
JSON_BUILD_PAIR_STRING_NON_EMPTY("parameter", c->parameter));
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
*ret = TAKE_PTR(v);
|
|
return 0;
|
|
}
|
|
|
|
static int unit_context_build_json(sd_json_variant **ret, const char *name, void *userdata) {
|
|
Unit *u = ASSERT_PTR(userdata);
|
|
|
|
/* The main principle behind context/runtime split is the following:
|
|
* If it make sense to place a property into a config/unit file it belongs to Context.
|
|
* Otherwise it's a 'Runtime'. */
|
|
|
|
/* TODO missing callbacks */
|
|
static const sd_json_build_callback_t unit_type_callbacks[_UNIT_TYPE_MAX] = {
|
|
[UNIT_AUTOMOUNT] = automount_context_build_json,
|
|
[UNIT_MOUNT] = mount_context_build_json,
|
|
[UNIT_PATH] = path_context_build_json,
|
|
[UNIT_SCOPE] = scope_context_build_json,
|
|
[UNIT_SERVICE] = service_context_build_json,
|
|
[UNIT_SOCKET] = socket_context_build_json,
|
|
[UNIT_SWAP] = swap_context_build_json,
|
|
[UNIT_TIMER] = timer_context_build_json,
|
|
};
|
|
|
|
return sd_json_buildo(
|
|
ASSERT_PTR(ret),
|
|
SD_JSON_BUILD_PAIR_STRING("Type", unit_type_to_string(u->type)),
|
|
SD_JSON_BUILD_PAIR_STRING("ID", u->id),
|
|
SD_JSON_BUILD_PAIR_CONDITION(!set_isempty(u->aliases), "Names", JSON_BUILD_STRING_SET(u->aliases)),
|
|
|
|
/* [Unit] Section Options
|
|
* https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#%5BUnit%5D%20Section%20Options */
|
|
JSON_BUILD_PAIR_STRING_NON_EMPTY("Description", u->description),
|
|
JSON_BUILD_PAIR_STRV_NON_EMPTY("Documentation", u->documentation),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("Wants", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("WantedBy", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("Requires", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("RequiredBy", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("Requisite", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("RequisiteOf", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("BindsTo", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("BoundBy", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("PartOf", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("ConsistsOf", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("Upholds", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("UpheldBy", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("Conflicts", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("ConflictedBy", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("Before", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("After", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("OnFailure", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("OnFailureOf", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("OnSuccess", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("OnSuccessOf", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("PropagatesReloadTo", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("ReloadPropagatedFrom", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("PropagatesStopTo", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("StopPropagatedFrom", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("JoinsNamespaceOf", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("RequiresMountsFor", unit_mounts_for_build_json, &u->mounts_for),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("WantsMountsFor", unit_mounts_for_build_json, &u->mounts_for),
|
|
JSON_BUILD_PAIR_ENUM("OnSuccessJobMode", job_mode_to_string(u->on_success_job_mode)),
|
|
JSON_BUILD_PAIR_ENUM("OnFailureJobMode", job_mode_to_string(u->on_failure_job_mode)),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("IgnoreOnIsolate", u->ignore_on_isolate),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("StopWhenUnneeded", u->stop_when_unneeded),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("RefuseManualStart", u->refuse_manual_start),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("RefuseManualStop", u->refuse_manual_stop),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("AllowIsolate", u->allow_isolate),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("DefaultDependencies", u->default_dependencies),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("SurviveFinalKillSignal", u->survive_final_kill_signal),
|
|
JSON_BUILD_PAIR_ENUM("CollectMode", collect_mode_to_string(u->collect_mode)),
|
|
JSON_BUILD_EMERGENCY_ACTION_NON_EMPTY("FailureAction", u->failure_action),
|
|
JSON_BUILD_EMERGENCY_ACTION_NON_EMPTY("SuccessAction", u->success_action),
|
|
JSON_BUILD_PAIR_INTEGER_NON_NEGATIVE("FailureActionExitStatus", u->failure_action_exit_status),
|
|
JSON_BUILD_PAIR_INTEGER_NON_NEGATIVE("SuccessActionExitStatus", u->success_action_exit_status),
|
|
JSON_BUILD_PAIR_FINITE_USEC("JobTimeoutUSec", u->job_timeout),
|
|
JSON_BUILD_PAIR_FINITE_USEC("JobRunningTimeoutUSec", u->job_running_timeout),
|
|
JSON_BUILD_EMERGENCY_ACTION_NON_EMPTY("JobTimeoutAction", u->job_timeout_action),
|
|
JSON_BUILD_PAIR_STRING_NON_EMPTY("JobTimeoutRebootArgument", u->job_timeout_reboot_arg),
|
|
JSON_BUILD_PAIR_RATELIMIT_ENABLED("StartLimit", &u->start_ratelimit),
|
|
JSON_BUILD_EMERGENCY_ACTION_NON_EMPTY("StartLimitAction", u->start_limit_action),
|
|
JSON_BUILD_PAIR_STRING_NON_EMPTY("RebootArgument", u->reboot_arg),
|
|
|
|
/* Conditions and Asserts
|
|
* https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#Conditions%20and%20Asserts */
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("Conditions", unit_conditions_build_json, u->conditions),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("Asserts", unit_conditions_build_json, u->asserts),
|
|
|
|
/* Others */
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("Triggers", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("TriggeredBy", unit_dependencies_build_json, u),
|
|
JSON_BUILD_PAIR_STRING_NON_EMPTY("AccessSELinuxContext", u->access_selinux_context),
|
|
JSON_BUILD_PAIR_STRING_NON_EMPTY("FragmentPath", u->fragment_path),
|
|
JSON_BUILD_PAIR_STRING_NON_EMPTY("SourcePath", u->source_path),
|
|
JSON_BUILD_PAIR_STRV_NON_EMPTY("DropInPaths", u->dropin_paths),
|
|
JSON_BUILD_PAIR_STRING_NON_EMPTY("UnitFilePreset", preset_action_past_tense_to_string(unit_get_unit_file_preset(u))),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("Transient", u->transient),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("Perpetual", u->perpetual),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("DebugInvocation", u->debug_invocation),
|
|
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("CGroup", unit_cgroup_context_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("Exec", unit_exec_context_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("Kill", unit_kill_context_build_json, unit_get_kill_context(u)),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL(unit_type_to_capitalized_string(u->type), unit_type_callbacks[u->type], u));
|
|
}
|
|
|
|
static int can_clean_build_json(sd_json_variant **ret, const char *name, void *userdata) {
|
|
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
|
Unit *u = ASSERT_PTR(userdata);
|
|
ExecCleanMask mask;
|
|
int r;
|
|
|
|
assert(ret);
|
|
|
|
r = unit_can_clean(u, &mask);
|
|
if (r < 0)
|
|
return log_debug_errno(r, "Failed to check if unit can be cleaned: %m");
|
|
|
|
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
|
|
if (!BIT_SET(mask, t))
|
|
continue;
|
|
|
|
r = sd_json_variant_append_arrayb(&v, SD_JSON_BUILD_STRING(exec_resource_type_to_string(t)));
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
if (FLAGS_SET(mask, EXEC_CLEAN_FDSTORE)) {
|
|
r = sd_json_variant_append_arrayb(&v, JSON_BUILD_CONST_STRING("fdstore"));
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
*ret = TAKE_PTR(v);
|
|
return 0;
|
|
}
|
|
|
|
static int markers_build_json(sd_json_variant **ret, const char *name, void *userdata) {
|
|
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
|
unsigned *markers = ASSERT_PTR(userdata);
|
|
int r;
|
|
|
|
assert(ret);
|
|
|
|
BIT_FOREACH(m, *markers) {
|
|
r = sd_json_variant_append_arrayb(&v, SD_JSON_BUILD_STRING(unit_marker_to_string(m)));
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
*ret = TAKE_PTR(v);
|
|
return 0;
|
|
}
|
|
|
|
static int activation_details_build_json(sd_json_variant **ret, const char *name, void *userdata) {
|
|
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
|
const ActivationDetails *activation_details = userdata;
|
|
_cleanup_strv_free_ char **pairs = NULL;
|
|
int r;
|
|
|
|
assert(ret);
|
|
|
|
/* activation_details_append_pair() gracefully takes activation_details==NULL */
|
|
r = activation_details_append_pair(activation_details, &pairs);
|
|
if (r < 0)
|
|
return log_debug_errno(r, "Failed to get activation details: %m");
|
|
|
|
STRV_FOREACH_PAIR(key, value, pairs) {
|
|
r = sd_json_variant_append_arraybo(&v,
|
|
SD_JSON_BUILD_PAIR_STRING("type", *key),
|
|
SD_JSON_BUILD_PAIR_STRING("name", *value));
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
*ret = TAKE_PTR(v);
|
|
return 0;
|
|
}
|
|
|
|
static int unit_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) {
|
|
Unit *u = ASSERT_PTR(userdata);
|
|
Unit *f = unit_following(u);
|
|
|
|
/* TODO missing callbacks */
|
|
static const sd_json_build_callback_t unit_type_callbacks[_UNIT_TYPE_MAX] = {
|
|
[UNIT_AUTOMOUNT] = automount_runtime_build_json,
|
|
[UNIT_MOUNT] = mount_runtime_build_json,
|
|
[UNIT_PATH] = path_runtime_build_json,
|
|
[UNIT_SCOPE] = scope_runtime_build_json,
|
|
[UNIT_SERVICE] = service_runtime_build_json,
|
|
[UNIT_SOCKET] = socket_runtime_build_json,
|
|
[UNIT_SWAP] = swap_runtime_build_json,
|
|
[UNIT_TIMER] = timer_runtime_build_json,
|
|
};
|
|
|
|
return sd_json_buildo(
|
|
ASSERT_PTR(ret),
|
|
JSON_BUILD_PAIR_STRING_NON_EMPTY("Following", f ? f->id : NULL),
|
|
SD_JSON_BUILD_PAIR_STRING("LoadState", unit_load_state_to_string(u->load_state)),
|
|
SD_JSON_BUILD_PAIR_STRING("ActiveState", unit_active_state_to_string(unit_active_state(u))),
|
|
SD_JSON_BUILD_PAIR_STRING("FreezerState", freezer_state_to_string(u->freezer_state)),
|
|
SD_JSON_BUILD_PAIR_STRING("SubState", unit_sub_state_to_string(u)),
|
|
JSON_BUILD_PAIR_STRING_NON_EMPTY("UnitFileState", unit_file_state_to_string(unit_get_unit_file_state(u))),
|
|
JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("StateChangeTimestamp", &u->state_change_timestamp),
|
|
JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("ActiveEnterTimestamp", &u->active_enter_timestamp),
|
|
JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("ActiveExitTimestamp", &u->active_exit_timestamp),
|
|
JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("InactiveEnterTimestamp", &u->inactive_enter_timestamp),
|
|
JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("InactiveExitTimestamp", &u->inactive_exit_timestamp),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("CanStart", unit_can_start_refuse_manual(u)),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("CanStop", unit_can_stop_refuse_manual(u)),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("CanReload", unit_can_reload(u)),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("CanIsolate", unit_can_isolate_refuse_manual(u)),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("CanClean", can_clean_build_json, u),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("CanFreeze", unit_can_freeze(u)),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("CanLiveMount", unit_can_live_mount(u, /* reterr_error= */ NULL) >= 0),
|
|
JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("JobId", u->job ? u->job->id : 0),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("NeedDaemonReload", unit_need_daemon_reload(u)),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("ConditionResult", u->condition_result),
|
|
SD_JSON_BUILD_PAIR_BOOLEAN("AssertResult", u->assert_result),
|
|
JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("ConditionTimestamp", &u->condition_timestamp),
|
|
JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("AssertTimestamp", &u->assert_timestamp),
|
|
SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(u->invocation_id), "InvocationID", SD_JSON_BUILD_UUID(u->invocation_id)),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("Markers", markers_build_json, &u->markers),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("ActivationDetails", activation_details_build_json, u->activation_details),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL("CGroup", unit_cgroup_runtime_build_json, u),
|
|
JSON_BUILD_PAIR_CALLBACK_NON_NULL(unit_type_to_capitalized_string(u->type), unit_type_callbacks[u->type], u));
|
|
}
|
|
|
|
static int list_unit_one(sd_varlink *link, Unit *unit) {
|
|
assert(link);
|
|
assert(unit);
|
|
|
|
return sd_varlink_replybo(link,
|
|
SD_JSON_BUILD_PAIR_CALLBACK("context", unit_context_build_json, unit),
|
|
SD_JSON_BUILD_PAIR_CALLBACK("runtime", unit_runtime_build_json, unit));
|
|
}
|
|
|
|
static int list_unit_one_with_selinux_access_check(sd_varlink *link, Unit *unit) {
|
|
int r;
|
|
|
|
assert(link);
|
|
assert(unit);
|
|
|
|
r = mac_selinux_unit_access_check_varlink(unit, link, "status");
|
|
if (r < 0)
|
|
/* If mac_selinux_unit_access_check_varlink() returned a error,
|
|
* it means that SELinux enforce is on. It also does all the logging(). */
|
|
return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL);
|
|
|
|
return list_unit_one(link, unit);
|
|
}
|
|
|
|
static int lookup_unit_by_pidref(sd_varlink *link, Manager *manager, PidRef *pidref, Unit **ret_unit) {
|
|
_cleanup_(pidref_done) PidRef peer = PIDREF_NULL;
|
|
Unit *unit;
|
|
int r;
|
|
|
|
assert(link);
|
|
assert(manager);
|
|
assert(ret_unit);
|
|
|
|
if (pidref_is_automatic(pidref)) {
|
|
r = varlink_get_peer_pidref(link, &peer);
|
|
if (r < 0)
|
|
return log_debug_errno(r, "Failed to get peer pidref: %m");
|
|
|
|
pidref = &peer;
|
|
} else if (!pidref_is_set(pidref))
|
|
return -EINVAL;
|
|
|
|
unit = manager_get_unit_by_pidref(manager, pidref);
|
|
if (!unit)
|
|
return -ESRCH;
|
|
|
|
*ret_unit = unit;
|
|
return 0;
|
|
}
|
|
|
|
static int load_unit_and_check(sd_varlink *link, Manager *manager, const char *name, Unit **ret_unit) {
|
|
Unit *unit;
|
|
int r;
|
|
|
|
assert(link);
|
|
assert(manager);
|
|
assert(name);
|
|
assert(ret_unit);
|
|
|
|
r = manager_load_unit(manager, name, /* path= */ NULL, /* e= */ NULL, &unit);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
/* manager_load_unit() will create an object regardless of whether the unit actually exists, so
|
|
* check the state and refuse if it's not in a good state. */
|
|
if (IN_SET(unit->load_state, UNIT_NOT_FOUND, UNIT_STUB, UNIT_MERGED))
|
|
return sd_varlink_error(link, "io.systemd.Unit.NoSuchUnit", NULL);
|
|
if (unit->load_state == UNIT_BAD_SETTING)
|
|
return sd_varlink_error(link, "io.systemd.Unit.UnitError", NULL);
|
|
if (unit->load_state == UNIT_ERROR)
|
|
return sd_varlink_errorbo(
|
|
link,
|
|
SD_VARLINK_ERROR_SYSTEM,
|
|
SD_JSON_BUILD_PAIR_STRING("origin", "linux"),
|
|
SD_JSON_BUILD_PAIR_INTEGER("errno", unit->load_error),
|
|
JSON_BUILD_PAIR_STRING_NON_EMPTY("errnoName", "io.systemd.Unit.UnitError"));
|
|
if (unit->load_state == UNIT_MASKED)
|
|
return sd_varlink_error(link, "io.systemd.Unit.UnitMasked", NULL);
|
|
assert(UNIT_IS_LOAD_COMPLETE(unit->load_state));
|
|
|
|
*ret_unit = unit;
|
|
return 0;
|
|
}
|
|
|
|
typedef struct UnitLookupParameters {
|
|
const char *name, *cgroup;
|
|
PidRef pidref;
|
|
sd_id128_t invocation_id;
|
|
} UnitLookupParameters;
|
|
|
|
static void unit_lookup_parameters_done(UnitLookupParameters *p) {
|
|
assert(p);
|
|
pidref_done(&p->pidref);
|
|
}
|
|
|
|
static int varlink_error_conflict_lookup_parameters(sd_varlink *v, const UnitLookupParameters *p) {
|
|
log_debug("Unit lookup by parameters name='%s' pid='"PID_FMT"' cgroup='%s' invocationID='%s' resulted in multiple different units.",
|
|
strnull(p->name),
|
|
pidref_is_automatic(&p->pidref) ? 0 : pidref_is_set(&p->pidref) ? p->pidref.pid : (pid_t) -1,
|
|
strnull(p->cgroup),
|
|
sd_id128_is_null(p->invocation_id) ? "" : SD_ID128_TO_UUID_STRING(p->invocation_id));
|
|
|
|
return varlink_error_no_such_unit(v, /* name= */ NULL);
|
|
}
|
|
|
|
static int lookup_unit_by_parameters(
|
|
sd_varlink *link,
|
|
Manager *manager,
|
|
UnitLookupParameters *p,
|
|
Unit **ret) {
|
|
|
|
/* The function can return ret_unit=NULL if no lookup parameters provided */
|
|
Unit *unit = NULL;
|
|
int r;
|
|
|
|
assert(link);
|
|
assert(manager);
|
|
assert(p);
|
|
assert(ret);
|
|
|
|
if (p->name) {
|
|
r = load_unit_and_check(link, manager, p->name, &unit);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
if (pidref_is_set_or_automatic(&p->pidref)) {
|
|
Unit *pid_unit;
|
|
|
|
r = lookup_unit_by_pidref(link, manager, &p->pidref, &pid_unit);
|
|
if (r == -EINVAL)
|
|
return sd_varlink_error_invalid_parameter_name(link, "pid");
|
|
if (r == -ESRCH)
|
|
return varlink_error_no_such_unit(link, "pid");
|
|
if (r < 0)
|
|
return r;
|
|
if (unit && pid_unit != unit)
|
|
return varlink_error_conflict_lookup_parameters(link, p);
|
|
|
|
unit = pid_unit;
|
|
}
|
|
|
|
if (p->cgroup) {
|
|
if (!path_is_absolute(p->cgroup) || !path_is_normalized(p->cgroup))
|
|
return sd_varlink_error_invalid_parameter_name(link, "cgroup");
|
|
|
|
Unit *cgroup_unit = manager_get_unit_by_cgroup(manager, p->cgroup);
|
|
if (!cgroup_unit)
|
|
return varlink_error_no_such_unit(link, "cgroup");
|
|
if (unit && cgroup_unit != unit)
|
|
return varlink_error_conflict_lookup_parameters(link, p);
|
|
|
|
unit = cgroup_unit;
|
|
}
|
|
|
|
if (!sd_id128_is_null(p->invocation_id)) {
|
|
Unit *id128_unit = hashmap_get(manager->units_by_invocation_id, &p->invocation_id);
|
|
if (!id128_unit)
|
|
return varlink_error_no_such_unit(link, "invocationID");
|
|
if (unit && id128_unit != unit)
|
|
return varlink_error_conflict_lookup_parameters(link, p);
|
|
|
|
unit = id128_unit;
|
|
}
|
|
|
|
*ret = unit;
|
|
return !!unit;
|
|
}
|
|
|
|
int vl_method_list_units(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
|
static const sd_json_dispatch_field dispatch_table[] = {
|
|
{ "name", SD_JSON_VARIANT_STRING, json_dispatch_const_unit_name, offsetof(UnitLookupParameters, name), 0 /* allows UNIT_NAME_PLAIN | UNIT_NAME_INSTANCE */ },
|
|
{ "pid", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_pidref, offsetof(UnitLookupParameters, pidref), SD_JSON_RELAX /* allows PID_AUTOMATIC */ },
|
|
{ "cgroup", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(UnitLookupParameters, cgroup), SD_JSON_STRICT /* require normalized path */ },
|
|
{ "invocationID", SD_JSON_VARIANT_STRING, sd_json_dispatch_id128, offsetof(UnitLookupParameters, invocation_id), 0 },
|
|
{}
|
|
};
|
|
|
|
Manager *manager = ASSERT_PTR(userdata);
|
|
_cleanup_(unit_lookup_parameters_done) UnitLookupParameters p = {
|
|
.pidref = PIDREF_NULL,
|
|
};
|
|
Unit *unit;
|
|
const char *k;
|
|
int r;
|
|
|
|
assert(link);
|
|
assert(parameters);
|
|
|
|
r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
|
|
if (r != 0)
|
|
return r;
|
|
|
|
r = lookup_unit_by_parameters(link, manager, &p, &unit);
|
|
if (r < 0)
|
|
return r;
|
|
if (r > 0)
|
|
return list_unit_one_with_selinux_access_check(link, unit);
|
|
|
|
if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE))
|
|
return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL);
|
|
|
|
r = sd_varlink_set_sentinel(link, VARLINK_ERROR_UNIT_NO_SUCH_UNIT);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
HASHMAP_FOREACH_KEY(unit, k, manager->units) {
|
|
/* ignore aliases */
|
|
if (k != unit->id)
|
|
continue;
|
|
|
|
r = mac_selinux_unit_access_check_varlink(unit, link, "status");
|
|
if (r < 0)
|
|
continue; /* silently skip units the caller is not allowed to see */
|
|
|
|
r = list_unit_one(link, unit);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int varlink_unit_queue_job_one(
|
|
Unit *u,
|
|
JobType type,
|
|
JobMode mode,
|
|
bool reload_if_possible,
|
|
uint32_t *ret_job_id,
|
|
Job **ret_job,
|
|
sd_bus_error *reterr_bus_error) {
|
|
|
|
int r;
|
|
|
|
assert(u);
|
|
|
|
r = unit_queue_job_check_and_mangle_type(u, &type, reload_if_possible, reterr_bus_error);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
Job *j;
|
|
r = manager_add_job(u->manager, type, u, mode, reterr_bus_error, &j);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
/* Before we send the method reply, force out the announcement JobNew for this job */
|
|
bus_job_send_pending_change_signal(j, /* including_new= */ true);
|
|
|
|
if (ret_job_id)
|
|
*ret_job_id = j->id;
|
|
if (ret_job)
|
|
*ret_job = j;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int varlink_error_no_such_unit(sd_varlink *v, const char *name) {
|
|
return sd_varlink_errorbo(
|
|
ASSERT_PTR(v),
|
|
VARLINK_ERROR_UNIT_NO_SUCH_UNIT,
|
|
JSON_BUILD_PAIR_STRING_NON_EMPTY("parameter", name));
|
|
}
|
|
|
|
void varlink_unit_send_change_signal(Unit *u) {
|
|
assert(u);
|
|
|
|
if (!u->varlink_unit_change)
|
|
return;
|
|
|
|
(void) sd_varlink_notifybo(
|
|
u->varlink_unit_change,
|
|
SD_JSON_BUILD_PAIR_CALLBACK("runtime", unit_runtime_build_json, u));
|
|
}
|
|
|
|
void varlink_job_send_change_signal(Job *j) {
|
|
assert(j);
|
|
|
|
if (!j->varlink || !j->varlink_notify_job_changes)
|
|
return;
|
|
|
|
(void) sd_varlink_notifybo(
|
|
j->varlink,
|
|
SD_JSON_BUILD_PAIR_CALLBACK("job", job_build_json, j));
|
|
}
|
|
|
|
void varlink_job_send_removed_signal(Job *j) {
|
|
assert(j);
|
|
|
|
if (!j->varlink)
|
|
return;
|
|
|
|
/* Send the final reply, which completes the method call */
|
|
(void) sd_varlink_replybo(
|
|
j->varlink,
|
|
SD_JSON_BUILD_PAIR_CALLBACK("context", unit_context_build_json, j->unit),
|
|
SD_JSON_BUILD_PAIR_CALLBACK("runtime", unit_runtime_build_json, j->unit),
|
|
SD_JSON_BUILD_PAIR_CALLBACK("job", job_build_json, j));
|
|
|
|
j->varlink = sd_varlink_unref(j->varlink);
|
|
j->unit->varlink_unit_change = sd_varlink_unref(j->unit->varlink_unit_change);
|
|
}
|
|
|
|
typedef struct TransientExecCommandItem {
|
|
const char *path;
|
|
char **arguments;
|
|
} TransientExecCommandItem;
|
|
|
|
static void transient_exec_command_item_done(TransientExecCommandItem *i) {
|
|
assert(i);
|
|
strv_free(i->arguments);
|
|
}
|
|
|
|
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 TransientSetCredential {
|
|
const char *id;
|
|
struct iovec value;
|
|
} TransientSetCredential;
|
|
|
|
typedef struct TransientExecContextParameters {
|
|
bool present;
|
|
bool working_directory_set;
|
|
TransientWorkingDirectory working_directory;
|
|
bool environment_set;
|
|
char **environment;
|
|
|
|
bool set_credentials_set;
|
|
TransientSetCredential *set_credentials;
|
|
size_t n_set_credentials;
|
|
|
|
bool set_credentials_encrypted_set;
|
|
TransientSetCredential *set_credentials_encrypted;
|
|
size_t n_set_credentials_encrypted;
|
|
|
|
const char *user;
|
|
const char *group;
|
|
char **supplementary_groups;
|
|
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) {
|
|
FOREACH_ARRAY(item, items, n)
|
|
iovec_done_erase(&item->value);
|
|
free(items);
|
|
}
|
|
|
|
static void transient_exec_context_parameters_done(TransientExecContextParameters *p) {
|
|
assert(p);
|
|
strv_free(p->environment);
|
|
strv_free(p->supplementary_groups);
|
|
transient_set_credential_array_free(p->set_credentials, p->n_set_credentials);
|
|
transient_set_credential_array_free(p->set_credentials_encrypted, p->n_set_credentials_encrypted);
|
|
}
|
|
|
|
typedef struct TransientServiceParameters {
|
|
bool present;
|
|
ServiceType type;
|
|
TransientExecCommandItem *exec_start;
|
|
size_t n_exec_start;
|
|
int remain_after_exit;
|
|
} TransientServiceParameters;
|
|
|
|
static void transient_service_parameters_done(TransientServiceParameters *p) {
|
|
assert(p);
|
|
FOREACH_ARRAY(i, p->exec_start, p->n_exec_start)
|
|
transient_exec_command_item_done(i);
|
|
free(p->exec_start);
|
|
}
|
|
|
|
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 },
|
|
{}
|
|
};
|
|
|
|
TransientServiceParameters *p = ASSERT_PTR(userdata);
|
|
size_t n;
|
|
int r;
|
|
|
|
if (!sd_json_variant_is_array(variant))
|
|
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Expected JSON array for ExecStart.");
|
|
|
|
n = sd_json_variant_elements(variant);
|
|
if (n == 0)
|
|
return 0;
|
|
|
|
p->exec_start = new0(TransientExecCommandItem, n);
|
|
if (!p->exec_start)
|
|
return -ENOMEM;
|
|
p->n_exec_start = n;
|
|
|
|
for (size_t i = 0; i < n; i++) {
|
|
sd_json_variant *element = sd_json_variant_by_index(variant, i);
|
|
|
|
r = sd_json_dispatch(element, exec_command_dispatch, /* flags= */ 0, &p->exec_start[i]);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
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 */
|
|
const char *bad_service_field;
|
|
} StartTransientContextParameters;
|
|
|
|
static void start_transient_context_parameters_done(StartTransientContextParameters *p) {
|
|
assert(p);
|
|
transient_exec_context_parameters_done(&p->exec);
|
|
transient_service_parameters_done(&p->service);
|
|
}
|
|
|
|
typedef struct StartTransientParameters {
|
|
StartTransientContextParameters context;
|
|
JobMode mode;
|
|
int notify_job_changes;
|
|
int notify_unit_changes;
|
|
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);
|
|
}
|
|
|
|
/* 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,
|
|
TransientSetCredential **ret_items,
|
|
size_t *ret_n) {
|
|
|
|
static const sd_json_dispatch_field item_dispatch[] = {
|
|
{ "id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(TransientSetCredential, id), SD_JSON_MANDATORY },
|
|
{ "value", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(TransientSetCredential, value), SD_JSON_MANDATORY },
|
|
{}
|
|
};
|
|
|
|
TransientSetCredential *items = NULL;
|
|
size_t n = 0;
|
|
int r;
|
|
|
|
assert(ret_items);
|
|
assert(ret_n);
|
|
|
|
CLEANUP_ARRAY(items, n, transient_set_credential_array_free);
|
|
|
|
size_t n_items = sd_json_variant_elements(variant);
|
|
if (n_items == 0) {
|
|
*ret_items = NULL;
|
|
*ret_n = 0;
|
|
return 0;
|
|
}
|
|
|
|
items = new0(TransientSetCredential, n_items);
|
|
if (!items)
|
|
return -ENOMEM;
|
|
|
|
for (; n < n_items; n++) {
|
|
r = sd_json_dispatch(sd_json_variant_by_index(variant, n), item_dispatch, /* flags= */ 0, &items[n]);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
*ret_n = n;
|
|
*ret_items = TAKE_PTR(items);
|
|
return 0;
|
|
}
|
|
|
|
static int dispatch_transient_set_credential(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
|
TransientExecContextParameters *p = ASSERT_PTR(userdata);
|
|
p->set_credentials_set = true;
|
|
return dispatch_transient_set_credential_array(variant, &p->set_credentials, &p->n_set_credentials);
|
|
}
|
|
|
|
static int dispatch_transient_set_credential_encrypted(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
|
TransientExecContextParameters *p = ASSERT_PTR(userdata);
|
|
p->set_credentials_encrypted_set = true;
|
|
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);
|
|
|
|
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 },
|
|
{ "ExecStart", SD_JSON_VARIANT_ARRAY, dispatch_transient_exec_command, 0, 0 },
|
|
{ "RemainAfterExit", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(TransientServiceParameters, remain_after_exit), 0 },
|
|
{}
|
|
};
|
|
|
|
StartTransientContextParameters *p = ASSERT_PTR(userdata);
|
|
p->service.present = true;
|
|
return sd_json_dispatch_full(variant, service_dispatch, /* bad= */ NULL, /* flags= */ 0, &p->service, &p->bad_service_field);
|
|
}
|
|
|
|
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 },
|
|
{ "Exec", SD_JSON_VARIANT_OBJECT, dispatch_transient_exec_context, 0, 0 },
|
|
{ "Service", SD_JSON_VARIANT_OBJECT, dispatch_transient_service, 0, 0 },
|
|
{}
|
|
};
|
|
|
|
StartTransientParameters *p = ASSERT_PTR(userdata);
|
|
const char *bad_field = NULL;
|
|
int r;
|
|
|
|
/* 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)) {
|
|
/* 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. 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 if (streq(bad_field, "Service") && !isempty(p->context.bad_service_field))
|
|
p->unsupported_property = strjoin("Service.", p->context.bad_service_field);
|
|
else
|
|
p->unsupported_property = strdup(bad_field);
|
|
if (!p->unsupported_property)
|
|
return -ENOMEM;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static int transient_unit_apply_properties(Unit *u, StartTransientContextParameters *p) {
|
|
int r;
|
|
|
|
assert(u);
|
|
assert(p);
|
|
|
|
if (p->description) {
|
|
r = unit_set_description(u, p->description);
|
|
if (r < 0)
|
|
return r;
|
|
unit_write_settingf(u, UNIT_RUNTIME|UNIT_ESCAPE_SPECIFIERS, "Description", "Description=%s", p->description);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int transient_apply_set_credentials(
|
|
Unit *u,
|
|
ExecContext *c,
|
|
const TransientSetCredential *items,
|
|
size_t n_items,
|
|
bool encrypted) {
|
|
|
|
int r;
|
|
|
|
assert(u);
|
|
assert(c);
|
|
|
|
FOREACH_ARRAY(item, items, n_items) {
|
|
const char *err = NULL;
|
|
|
|
r = exec_context_apply_set_credential(u, c, item->id, item->value.iov_base, item->value.iov_len,
|
|
encrypted, UNIT_RUNTIME|UNIT_PRIVATE, &err);
|
|
if (r == -EINVAL)
|
|
return log_debug_errno(r, "%s: %s", err, item->id);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* Some fields (like tristate) must be initialized as their "undefined" is eg. "-1" */
|
|
transient_exec_context_parameters_init(&p->exec);
|
|
|
|
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);
|
|
|
|
FOREACH_ELEMENT(prop, exec_properties) {
|
|
r = prop->apply(u, c, p);
|
|
if (r < 0) {
|
|
if (reterr_field)
|
|
*reterr_field = r == -EINVAL ? prop->err_field : NULL;
|
|
return r;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int transient_service_apply_properties(Service *s, TransientServiceParameters *sp, const char **reterr_field) {
|
|
Unit *u = UNIT(ASSERT_PTR(s));
|
|
int r;
|
|
|
|
assert(sp);
|
|
|
|
if (sp->type >= 0) {
|
|
s->type = sp->type;
|
|
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "Type", "Type=%s", service_type_to_string(sp->type));
|
|
}
|
|
|
|
if (sp->remain_after_exit >= 0) {
|
|
s->remain_after_exit = sp->remain_after_exit;
|
|
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "RemainAfterExit", "RemainAfterExit=%s", yes_no(sp->remain_after_exit));
|
|
}
|
|
|
|
FOREACH_ARRAY(item, sp->exec_start, sp->n_exec_start) {
|
|
_cleanup_(exec_command_freep) ExecCommand *c = NULL;
|
|
_cleanup_strv_free_ char **argv = NULL;
|
|
|
|
if (!filename_or_absolute_path_is_valid(item->path)) {
|
|
if (reterr_field)
|
|
*reterr_field = "Service.ExecStart";
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid ExecStart path: %s", item->path);
|
|
}
|
|
|
|
if (!strv_isempty(item->arguments)) {
|
|
argv = strv_copy(item->arguments);
|
|
if (!argv)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
c = new0(ExecCommand, 1);
|
|
if (!c)
|
|
return -ENOMEM;
|
|
|
|
r = path_simplify_alloc(item->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)) {
|
|
r = strv_extend(&argv, c->path);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
c->argv = TAKE_PTR(argv);
|
|
|
|
exec_command_append_list(&s->exec_command[SERVICE_EXEC_START], TAKE_PTR(c));
|
|
}
|
|
|
|
/* 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;
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
|
static const sd_json_dispatch_field dispatch_table[] = {
|
|
{ "context", SD_JSON_VARIANT_OBJECT, dispatch_transient_context, 0, SD_JSON_MANDATORY },
|
|
{ "mode", SD_JSON_VARIANT_STRING, dispatch_job_mode, offsetof(StartTransientParameters, mode), 0 },
|
|
{ "notifyJobChanges", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(StartTransientParameters, notify_job_changes), 0 },
|
|
{ "notifyUnitChanges", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(StartTransientParameters, notify_unit_changes), 0 },
|
|
{}
|
|
};
|
|
|
|
_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,
|
|
};
|
|
Manager *manager = ASSERT_PTR(userdata);
|
|
const char *bad_field = NULL;
|
|
Unit *u;
|
|
int r;
|
|
|
|
assert(link);
|
|
assert(parameters);
|
|
|
|
r = mac_selinux_access_check_varlink(link, "start");
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sd_json_dispatch_full(parameters, dispatch_table, /* bad= */ NULL, /* flags= */ 0, &p, &bad_field);
|
|
if (r < 0) {
|
|
/* An unknown field in 'context' maps to PropertyNotSupported (the field is defined in the
|
|
* UnitContext schema but cannot be set at creation time). Anything else is a bad parameter. */
|
|
if (streq_ptr(bad_field, "context") && r == -EADDRNOTAVAIL && p.unsupported_property)
|
|
return sd_varlink_errorbo(
|
|
link,
|
|
"io.systemd.Unit.PropertyNotSupported",
|
|
SD_JSON_BUILD_PAIR_STRING("property", p.unsupported_property));
|
|
if (bad_field)
|
|
return sd_varlink_error_invalid_parameter_name(link, bad_field);
|
|
return r;
|
|
}
|
|
|
|
/* Pre-check unit type early and return targeted varlink error as manager_setup_transient_unit() the
|
|
* too generic SD_BUS_ERROR_INVALID_ARGS. */
|
|
UnitType t = unit_name_to_type(p.context.id);
|
|
if (t < 0)
|
|
return sd_varlink_error_invalid_parameter_name(link, "context");
|
|
if (!unit_vtable[t]->can_transient)
|
|
return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL);
|
|
|
|
r = varlink_verify_polkit_async(
|
|
link,
|
|
manager->system_bus,
|
|
"org.freedesktop.systemd1.manage-units",
|
|
(const char**) STRV_MAKE(
|
|
"unit", p.context.id,
|
|
"verb", "start",
|
|
"polkit.message", N_("Authentication is required to start transient unit '$(unit)'."),
|
|
"polkit.gettext_domain", GETTEXT_PACKAGE),
|
|
&manager->polkit_registry);
|
|
if (r <= 0)
|
|
return r;
|
|
|
|
r = manager_setup_transient_unit(manager, p.context.id, &u, &bus_error);
|
|
if (r < 0)
|
|
return varlink_reply_bus_error(link, r, &bus_error);
|
|
|
|
/* Apply unit-level properties from context */
|
|
r = transient_unit_apply_properties(u, &p.context);
|
|
if (r == -EINVAL)
|
|
return sd_varlink_error_invalid_parameter_name(link, "context");
|
|
if (r < 0)
|
|
return sd_varlink_error_errno(link, r);
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* Apply service-specific properties from context.Service */
|
|
Service *s = SERVICE(u);
|
|
if (s) {
|
|
bad_field = NULL;
|
|
r = transient_service_apply_properties(s, &p.context.service, &bad_field);
|
|
if (r == -EINVAL)
|
|
return sd_varlink_error_invalid_parameter_name(link, bad_field ?: "Service");
|
|
if (r < 0)
|
|
return sd_varlink_error_errno(link, r);
|
|
} else if (p.context.service.present)
|
|
return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL);
|
|
|
|
unit_add_to_load_queue(u);
|
|
manager_dispatch_load_queue(manager);
|
|
|
|
if (u->load_state == UNIT_BAD_SETTING)
|
|
return sd_varlink_error_invalid_parameter_name(link, "context");
|
|
if (!UNIT_IS_LOAD_COMPLETE(u->load_state))
|
|
return sd_varlink_error(link, VARLINK_ERROR_UNIT_NO_SUCH_UNIT, NULL);
|
|
|
|
Job *j;
|
|
r = varlink_unit_queue_job_one(
|
|
u,
|
|
JOB_START,
|
|
p.mode,
|
|
/* reload_if_possible= */ false,
|
|
/* ret_job_id= */ NULL,
|
|
&j,
|
|
&bus_error);
|
|
if (r < 0)
|
|
return varlink_reply_bus_error(link, r, &bus_error);
|
|
|
|
bool notify_job = p.notify_job_changes > 0;
|
|
bool notify_unit = p.notify_unit_changes > 0;
|
|
|
|
/* Non-streaming, or fire-and-forget (no notification flags set): return full unit context
|
|
* and runtime, plus the job object so the caller can correlate with later state. */
|
|
if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE) || (!notify_job && !notify_unit))
|
|
return sd_varlink_replybo(
|
|
link,
|
|
SD_JSON_BUILD_PAIR_CALLBACK("context", unit_context_build_json, u),
|
|
SD_JSON_BUILD_PAIR_CALLBACK("runtime", unit_runtime_build_json, u),
|
|
SD_JSON_BUILD_PAIR_CALLBACK("job", job_build_json, j));
|
|
|
|
/* Streaming: always attach to the job for the final reply, and optionally to the unit for state
|
|
* change notifications. j->varlink owns the stream lifetime, u->varlink_unit_change is just a flag
|
|
* to also send unit state notifications along the way. */
|
|
assert(!j->varlink);
|
|
j->varlink = sd_varlink_ref(link);
|
|
j->varlink_notify_job_changes = notify_job;
|
|
if (notify_unit) {
|
|
assert(!u->varlink_unit_change);
|
|
u->varlink_unit_change = sd_varlink_ref(link);
|
|
}
|
|
|
|
/* Send initial job state notification if requested. Unit state change notifications are not sent
|
|
* here; they will arrive via varlink_unit_send_change_signal() when the unit actually transitions,
|
|
* matching D-Bus PropertiesChanged behavior. */
|
|
if (notify_job)
|
|
return sd_varlink_notifybo(
|
|
link,
|
|
SD_JSON_BUILD_PAIR_CALLBACK("job", job_build_json, j));
|
|
|
|
return 0;
|
|
}
|
|
|
|
typedef struct UnitSetPropertiesParameters {
|
|
const char *unsupported_property; /* For error reporting */
|
|
const char *name;
|
|
bool runtime;
|
|
|
|
bool markers_found;
|
|
unsigned markers, markers_mask;
|
|
} UnitSetPropertiesParameters;
|
|
|
|
static int parse_unit_markers(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
|
UnitSetPropertiesParameters *p = ASSERT_PTR(userdata);
|
|
bool some_plus_minus = false, some_absolute = false;
|
|
unsigned settings = 0, mask = 0;
|
|
sd_json_variant *e;
|
|
int r;
|
|
|
|
assert(variant);
|
|
|
|
JSON_VARIANT_ARRAY_FOREACH(e, variant) {
|
|
if (!sd_json_variant_is_string(e))
|
|
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Marker is not an array of strings.");
|
|
|
|
const char *word = sd_json_variant_string(e);
|
|
|
|
r = parse_unit_marker(word, &settings, &mask);
|
|
if (r < 0)
|
|
return json_log(variant, flags, r, "Failed to parse marker '%s'.", word);
|
|
if (r > 0)
|
|
some_plus_minus = true;
|
|
else
|
|
some_absolute = true;
|
|
}
|
|
|
|
if (some_plus_minus && some_absolute)
|
|
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Absolute and non-absolute markers in the same setting.");
|
|
|
|
if (some_absolute || sd_json_variant_elements(variant) == 0)
|
|
mask = UINT_MAX;
|
|
|
|
p->markers = settings;
|
|
p->markers_mask = mask;
|
|
p->markers_found = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int unit_dispatch_properties(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
|
static const sd_json_dispatch_field dispatch_table[] = {
|
|
{ "Markers", SD_JSON_VARIANT_ARRAY, parse_unit_markers, 0, 0 },
|
|
{}
|
|
};
|
|
UnitSetPropertiesParameters *p = ASSERT_PTR(userdata);
|
|
const char *bad_field = NULL;
|
|
int r;
|
|
|
|
r = sd_json_dispatch_full(variant, dispatch_table, /* bad= */ NULL, flags, userdata, &bad_field);
|
|
if (r == -EADDRNOTAVAIL && !isempty(bad_field))
|
|
/* When properties contains a valid field, but that we don't currently support, make sure to
|
|
* return the offending property, rather than generic invalid argument. */
|
|
p->unsupported_property = bad_field;
|
|
return r;
|
|
}
|
|
|
|
int vl_method_set_unit_properties(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
|
static const sd_json_dispatch_field dispatch_table[] = {
|
|
{ "name", SD_JSON_VARIANT_STRING, json_dispatch_const_unit_name, offsetof(UnitSetPropertiesParameters, name), SD_JSON_MANDATORY },
|
|
{ "runtime", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(UnitSetPropertiesParameters, runtime), SD_JSON_MANDATORY },
|
|
{ "properties", SD_JSON_VARIANT_OBJECT, unit_dispatch_properties, 0, SD_JSON_MANDATORY },
|
|
{}
|
|
};
|
|
|
|
UnitSetPropertiesParameters p = {};
|
|
Manager *manager = ASSERT_PTR(userdata);
|
|
const char *bad_field = NULL;
|
|
Unit *unit;
|
|
int r;
|
|
|
|
assert(link);
|
|
assert(parameters);
|
|
|
|
r = sd_json_dispatch_full(parameters, dispatch_table, /* bad= */ NULL, /* flags= */ 0, &p, &bad_field);
|
|
if (r < 0) {
|
|
/* When properties contains a valid field, but that we don't currently support, make sure to
|
|
* return a specific error, rather than generic invalid argument. */
|
|
if (streq_ptr(bad_field, "properties") && r == -EADDRNOTAVAIL)
|
|
return sd_varlink_errorbo(
|
|
link,
|
|
"io.systemd.Unit.PropertyNotSupported",
|
|
SD_JSON_BUILD_PAIR_CONDITION(!!p.unsupported_property, "property", SD_JSON_BUILD_STRING(p.unsupported_property)));
|
|
if (bad_field)
|
|
return sd_varlink_error_invalid_parameter_name(link, bad_field);
|
|
return r;
|
|
}
|
|
|
|
r = load_unit_and_check(link, manager, p.name, &unit);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = mac_selinux_unit_access_check_varlink(unit, link, "start");
|
|
if (r < 0)
|
|
return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL);
|
|
|
|
r = varlink_verify_polkit_async(
|
|
link,
|
|
manager->system_bus,
|
|
"org.freedesktop.systemd1.manage-units",
|
|
/* details= */ NULL,
|
|
&manager->polkit_registry);
|
|
if (r <= 0)
|
|
return r;
|
|
|
|
if (p.markers_found)
|
|
unit->markers = unit_normalize_markers((unit->markers & ~p.markers_mask), p.markers);
|
|
|
|
return sd_varlink_reply(link, NULL);
|
|
}
|