From c2756a68401102786be343712c0c35acbd73d28d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 28 Jun 2013 04:12:58 +0200 Subject: [PATCH] core: add transient units Transient units can be created via the bus API. They are configured via the method call parameters rather than on-disk files. They are subject to normal GC. Transient units currently may only be created for services (however, we will extend this), and currently only ExecStart= and the cgroup parameters can be configured (also to be extended). Transient units require a unique name, that previously had no configuration file on disk. A tool systemd-run is added that makes use of this functionality to run arbitrary command lines as transient services: $ systemd-run /bin/ping www.heise.de Will cause systemd to create a new transient service and run ping in it. --- .gitignore | 1 + Makefile.am | 15 +++- TODO | 4 +- src/core/dbus-cgroup.c | 3 +- src/core/dbus-manager.c | 69 ++++++++++++++++- src/core/dbus-service.c | 128 ++++++++++++++++++++++++++++++++ src/core/dbus-unit.c | 18 ++++- src/core/dbus-unit.h | 3 +- src/core/job.c | 7 +- src/core/load-fragment.c | 4 +- src/core/main.c | 4 +- src/core/manager.c | 16 +++- src/core/mount.c | 2 +- src/core/service.c | 25 ++++--- src/core/transaction.c | 3 +- src/core/unit.c | 143 +++++++++++++++++++++++++++++------ src/core/unit.h | 8 ++ src/run/Makefile | 1 + src/run/run.c | 156 +++++++++++++++++++++++++++++++++++++++ src/shared/unit-name.c | 2 +- src/shared/unit-name.h | 1 + 21 files changed, 559 insertions(+), 54 deletions(-) create mode 120000 src/run/Makefile create mode 100644 src/run/run.c diff --git a/.gitignore b/.gitignore index 866d8eba0..53b1597f4 100644 --- a/.gitignore +++ b/.gitignore @@ -65,6 +65,7 @@ /systemd-remount-api-vfs /systemd-remount-fs /systemd-reply-password +/systemd-run /systemd-shutdown /systemd-shutdownd /systemd-sleep diff --git a/Makefile.am b/Makefile.am index c64934efa..3ab14753a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -290,7 +290,8 @@ bin_PROGRAMS = \ systemd-nspawn \ systemd-detect-virt \ systemd-delta \ - systemd-analyze + systemd-analyze \ + systemd-run dist_bin_SCRIPTS = \ src/kernel-install/kernel-install @@ -1715,6 +1716,18 @@ systemd_nspawn_LDADD = \ libsystemd-id128-internal.la \ libsystemd-bus.la +# ------------------------------------------------------------------------------ +systemd_run_SOURCES = \ + src/run/run.c + +systemd_run_LDADD = \ + libsystemd-label.la \ + libsystemd-capability.la \ + libsystemd-shared.la \ + libsystemd-daemon.la \ + libsystemd-id128-internal.la \ + libsystemd-bus.la + # ------------------------------------------------------------------------------ systemd_stdio_bridge_SOURCES = \ src/stdio-bridge/stdio-bridge.c diff --git a/TODO b/TODO index c72921507..055d9739d 100644 --- a/TODO +++ b/TODO @@ -36,7 +36,9 @@ Features: * when reloading configuration, apply new cgroup configuration -* implement system-wide DefaultCPUAccounting=1 switch (and similar for blockio, memory, fair scheduling?) +* implement system-wide DefaultCPUAccounting=1 switch (and similar for blockio, memory?) + +* implement per-slice CPUFairScheduling=1 switch * handle jointly mounted controllers correctly diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c index cf05f04ea..d1d35633c 100644 --- a/src/core/dbus-cgroup.c +++ b/src/core/dbus-cgroup.c @@ -323,8 +323,6 @@ int bus_cgroup_set_property( return -EINVAL; } - n++; - if (mode != UNIT_CHECK) { a = new0(CGroupDeviceAllow, 1); if (!a) @@ -343,6 +341,7 @@ int bus_cgroup_set_property( LIST_PREPEND(CGroupDeviceAllow, device_allow, c->device_allow, a); } + n++; dbus_message_iter_next(&sub); } diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index c51226590..fe2f74980 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -239,6 +239,13 @@ " \n" \ " \n" \ " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ " \n" #define BUS_MANAGER_INTERFACE_SIGNALS \ @@ -1758,7 +1765,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start"); - r = bus_unit_set_properties(u, &iter, runtime ? UNIT_RUNTIME : UNIT_PERSISTENT, &error); + r = bus_unit_set_properties(u, &iter, runtime ? UNIT_RUNTIME : UNIT_PERSISTENT, true, &error); if (r < 0) return bus_send_error_reply(connection, message, &error, r); @@ -1766,6 +1773,66 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, if (!reply) goto oom; + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "StartTransientUnit")) { + const char *name, *smode; + DBusMessageIter iter; + JobMode mode; + UnitType t; + Unit *u; + + if (!dbus_message_iter_init(message, &iter)) + goto oom; + + if (bus_iter_get_basic_and_next(&iter, DBUS_TYPE_STRING, &name, true) < 0 || + bus_iter_get_basic_and_next(&iter, DBUS_TYPE_STRING, &smode, true) < 0) + return bus_send_error_reply(connection, message, NULL, -EINVAL); + + t = unit_name_to_type(name); + if (t < 0) + return bus_send_error_reply(connection, message, NULL, -EINVAL); + if (!unit_vtable[t]->can_transient) { + dbus_set_error(&error, DBUS_ERROR_INVALID_ARGS, "Unit type %s does not support transient units.", unit_type_to_string(t)); + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + mode = job_mode_from_string(smode); + if (mode < 0) { + dbus_set_error(&error, BUS_ERROR_INVALID_JOB_MODE, "Job mode %s is invalid.", smode); + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + r = manager_load_unit(m, name, NULL, NULL, &u); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start"); + + if (u->load_state != UNIT_NOT_FOUND || set_size(u->dependencies[UNIT_REFERENCED_BY]) > 0) { + dbus_set_error(&error, BUS_ERROR_UNIT_EXISTS, "Unit %s already exists.", name); + return bus_send_error_reply(connection, message, &error, -EEXIST); + } + + /* OK, the unit failed to load and is unreferenced, + * now let's fill in the transient data instead */ + r = unit_make_transient(u); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + /* Set our properties */ + r = bus_unit_set_properties(u, &iter, UNIT_RUNTIME, false, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + /* And load this stub fully */ + r = unit_load(u); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + manager_dispatch_load_queue(m); + + /* Finally, start it */ + return bus_unit_queue_job(connection, message, u, JOB_START, mode, false); + } else { const BusBoundProperties bps[] = { { "org.freedesktop.systemd1.Manager", bus_systemd_properties, systemd_property_string }, diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c index 867ef7e54..c2031c3bf 100644 --- a/src/core/dbus-service.c +++ b/src/core/dbus-service.c @@ -21,6 +21,8 @@ #include +#include "strv.h" +#include "path-util.h" #include "dbus-unit.h" #include "dbus-execute.h" #include "dbus-kill.h" @@ -162,6 +164,124 @@ DBusHandlerResult bus_service_message_handler(Unit *u, DBusConnection *connectio return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps); } +static int bus_service_set_transient_properties( + Service *s, + const char *name, + DBusMessageIter *i, + UnitSetPropertiesMode mode, + DBusError *error) { + + int r; + + assert(name); + assert(s); + assert(i); + + if (streq(name, "ExecStart")) { + DBusMessageIter sub; + unsigned n = 0; + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(i) != DBUS_TYPE_STRUCT) + return -EINVAL; + + dbus_message_iter_recurse(i, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + _cleanup_strv_free_ char **argv = NULL; + DBusMessageIter sub2; + dbus_bool_t ignore; + const char *path; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &path, true) < 0) + return -EINVAL; + + if (!path_is_absolute(path)) { + dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, "Path %s is not absolute.", path); + return -EINVAL; + } + + r = bus_parse_strv_iter(&sub2, &argv); + if (r < 0) + return r; + + dbus_message_iter_next(&sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_BOOLEAN, &ignore, false) < 0) + return -EINVAL; + + if (mode != UNIT_CHECK) { + ExecCommand *c; + + c = new0(ExecCommand, 1); + if (!c) + return -ENOMEM; + + c->path = strdup(path); + if (!c->path) { + free(c); + return -ENOMEM; + } + + c->argv = argv; + argv = NULL; + + c->ignore = ignore; + + path_kill_slashes(c->path); + exec_command_append_list(&s->exec_command[SERVICE_EXEC_START], c); + } + + n++; + dbus_message_iter_next(&sub); + } + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *f = NULL; + ExecCommand *c; + size_t size = 0; + + if (n == 0) { + exec_command_free_list(s->exec_command[SERVICE_EXEC_START]); + s->exec_command[SERVICE_EXEC_START] = NULL; + } + + f = open_memstream(&buf, &size); + if (!f) + return -ENOMEM; + + fputs("ExecStart=\n", f); + + LIST_FOREACH(command, c, s->exec_command[SERVICE_EXEC_START]) { + char **a; + fputs("ExecStart=", f); + + if (c->ignore) + fputc('-', f); + + fputc('@', f); + fputs(c->path, f); + + STRV_FOREACH(a, c->argv) { + fputc(' ', f); + fputs(*a, f); + } + + fputc('\n', f); + } + + fflush(f); + unit_write_drop_in_private_section(UNIT(s), mode, "exec-start", buf); + } + + return 1; + } + + return 0; +} + int bus_service_set_property( Unit *u, const char *name, @@ -180,6 +300,14 @@ int bus_service_set_property( if (r != 0) return r; + if (u->transient && u->load_state == UNIT_STUB) { + /* This is a transient unit, let's load a little more */ + + r = bus_service_set_transient_properties(s, name, i, mode, error); + if (r != 0) + return r; + } + return 0; } diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index b3724b679..f51850575 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -415,7 +415,7 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start"); - r = bus_unit_set_properties(u, &iter, runtime ? UNIT_RUNTIME : UNIT_PERSISTENT, &error); + r = bus_unit_set_properties(u, &iter, runtime ? UNIT_RUNTIME : UNIT_PERSISTENT, true, &error); if (r < 0) return bus_send_error_reply(connection, message, &error, r); @@ -716,7 +716,7 @@ DBusHandlerResult bus_unit_queue_job( (type == JOB_START || type == JOB_RESTART || type == JOB_TRY_RESTART) ? "start" : type == JOB_STOP ? "stop" : "reload"); - if (type == JOB_STOP && u->load_state == UNIT_ERROR && unit_active_state(u) == UNIT_INACTIVE) { + if (type == JOB_STOP && (u->load_state == UNIT_NOT_FOUND || u->load_state == UNIT_ERROR) && unit_active_state(u) == UNIT_INACTIVE) { dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s not loaded.", u->id); return bus_send_error_reply(connection, message, &error, -EPERM); } @@ -764,7 +764,13 @@ oom: return DBUS_HANDLER_RESULT_NEED_MEMORY; } -int bus_unit_set_properties(Unit *u, DBusMessageIter *iter, UnitSetPropertiesMode mode, DBusError *error) { +int bus_unit_set_properties( + Unit *u, + DBusMessageIter *iter, + UnitSetPropertiesMode mode, + bool commit, + DBusError *error) { + bool for_real = false; DBusMessageIter sub; unsigned n = 0; @@ -773,6 +779,9 @@ int bus_unit_set_properties(Unit *u, DBusMessageIter *iter, UnitSetPropertiesMod assert(u); assert(iter); + if (u->transient) + mode &= UNIT_RUNTIME; + /* We iterate through the array twice. First run we just check * if all passed data is valid, second run actually applies * it. This is to implement transaction-like behaviour without @@ -826,7 +835,7 @@ int bus_unit_set_properties(Unit *u, DBusMessageIter *iter, UnitSetPropertiesMod n += for_real; } - if (n > 0 && UNIT_VTABLE(u)->bus_commit_properties) + if (commit && n > 0 && UNIT_VTABLE(u)->bus_commit_properties) UNIT_VTABLE(u)->bus_commit_properties(u); return n; @@ -896,5 +905,6 @@ const BusProperty bus_unit_properties[] = { { "ConditionResult", bus_property_append_bool, "b", offsetof(Unit, condition_result) }, { "LoadError", bus_unit_append_load_error, "(ss)", 0 }, { "ControlGroup", bus_property_append_string, "s", offsetof(Unit, cgroup_path), true }, + { "Transient", bus_property_append_bool, "b", offsetof(Unit, transient) }, { NULL, } }; diff --git a/src/core/dbus-unit.h b/src/core/dbus-unit.h index 1b42757b4..18f7c4f08 100644 --- a/src/core/dbus-unit.h +++ b/src/core/dbus-unit.h @@ -128,6 +128,7 @@ " \n" \ " \n" \ " \n" \ + " \n" \ " \n" #define BUS_UNIT_INTERFACES_LIST \ @@ -141,7 +142,7 @@ void bus_unit_send_removed_signal(Unit *u); DBusHandlerResult bus_unit_queue_job(DBusConnection *connection, DBusMessage *message, Unit *u, JobType type, JobMode mode, bool reload_if_possible); -int bus_unit_set_properties(Unit *u, DBusMessageIter *i, UnitSetPropertiesMode mode, DBusError *error); +int bus_unit_set_properties(Unit *u, DBusMessageIter *i, UnitSetPropertiesMode mode, bool commit, DBusError *error); extern const DBusObjectPathVTable bus_unit_vtable; diff --git a/src/core/job.c b/src/core/job.c index d304a16d0..85f77e8f0 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -1088,10 +1088,13 @@ void job_shutdown_magic(Job *j) { * asynchronous sync() would cause their exit to be * delayed. */ - if (!unit_has_name(j->unit, SPECIAL_SHUTDOWN_TARGET)) + if (j->type != JOB_START) return; - if (j->type != JOB_START) + if (j->unit->manager->running_as != SYSTEMD_SYSTEM) + return; + + if (!unit_has_name(j->unit, SPECIAL_SHUTDOWN_TARGET)) return; if (detect_container(NULL) > 0) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 57c8156fd..2b10d72ab 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -2439,14 +2439,14 @@ static int load_from_path(Unit *u, const char *path) { if (null_or_empty(&st)) u->load_state = UNIT_MASKED; else { + u->load_state = UNIT_LOADED; + /* Now, parse the file contents */ r = config_parse(u->id, filename, f, UNIT_VTABLE(u)->sections, config_item_perf_lookup, (void*) load_fragment_gperf_lookup, false, true, u); if (r < 0) goto finish; - - u->load_state = UNIT_LOADED; } free(u->fragment_path); diff --git a/src/core/main.c b/src/core/main.c index 3c6fccf52..102cc3b31 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -1666,7 +1666,7 @@ int main(int argc, char *argv[]) { if (r < 0) { log_error("Failed to load default target: %s", bus_error(&error, r)); dbus_error_free(&error); - } else if (target->load_state == UNIT_ERROR) + } else if (target->load_state == UNIT_ERROR || target->load_state == UNIT_NOT_FOUND) log_error("Failed to load default target: %s", strerror(-target->load_error)); else if (target->load_state == UNIT_MASKED) log_error("Default target masked."); @@ -1679,7 +1679,7 @@ int main(int argc, char *argv[]) { log_error("Failed to load rescue target: %s", bus_error(&error, r)); dbus_error_free(&error); goto finish; - } else if (target->load_state == UNIT_ERROR) { + } else if (target->load_state == UNIT_ERROR || target->load_state == UNIT_ERROR) { log_error("Failed to load rescue target: %s", strerror(-target->load_error)); goto finish; } else if (target->load_state == UNIT_MASKED) { diff --git a/src/core/manager.c b/src/core/manager.c index 6ba51a411..42c9bcd48 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -993,7 +993,13 @@ unsigned manager_dispatch_load_queue(Manager *m) { return n; } -int manager_load_unit_prepare(Manager *m, const char *name, const char *path, DBusError *e, Unit **_ret) { +int manager_load_unit_prepare( + Manager *m, + const char *name, + const char *path, + DBusError *e, + Unit **_ret) { + Unit *ret; UnitType t; int r; @@ -1053,7 +1059,13 @@ int manager_load_unit_prepare(Manager *m, const char *name, const char *path, DB return 0; } -int manager_load_unit(Manager *m, const char *name, const char *path, DBusError *e, Unit **_ret) { +int manager_load_unit( + Manager *m, + const char *name, + const char *path, + DBusError *e, + Unit **_ret) { + int r; assert(m); diff --git a/src/core/mount.c b/src/core/mount.c index c1af903b1..3cc3e65b2 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -1562,7 +1562,7 @@ static int mount_add_one( } } - if (u->load_state == UNIT_ERROR) { + if (u->load_state == UNIT_NOT_FOUND) { u->load_state = UNIT_LOADED; u->load_error = 0; diff --git a/src/core/service.c b/src/core/service.c index 1dcd5cf44..6f18cbf75 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -983,7 +983,7 @@ static int service_load_sysv_name(Service *s, const char *name) { assert(s); assert(name); - /* For SysV services we strip the *.sh suffixes. */ + /* For SysV services we strip the *.sh suffixes. */ if (endswith(name, ".sh.service")) return -ENOENT; @@ -1193,27 +1193,32 @@ static int service_load(Unit *u) { assert(s); /* Load a .service file */ - if ((r = unit_load_fragment(u)) < 0) + r = unit_load_fragment(u); + if (r < 0) return r; #ifdef HAVE_SYSV_COMPAT /* Load a classic init script as a fallback, if we couldn't find anything */ - if (u->load_state == UNIT_STUB) - if ((r = service_load_sysv(s)) < 0) + if (u->load_state == UNIT_STUB) { + r = service_load_sysv(s); + if (r < 0) return r; + } #endif /* Still nothing found? Then let's give up */ if (u->load_state == UNIT_STUB) return -ENOENT; - /* We were able to load something, then let's add in the - * dropin directories. */ - if ((r = unit_load_dropin(unit_follow_merge(u))) < 0) - return r; - /* This is a new unit? Then let's add in some extras */ if (u->load_state == UNIT_LOADED) { + + /* We were able to load something, then let's add in + * the dropin directories. */ + r = unit_load_dropin(u); + if (r < 0) + return r; + if (s->type == _SERVICE_TYPE_INVALID) s->type = s->bus_name ? SERVICE_DBUS : SERVICE_SIMPLE; @@ -3886,6 +3891,8 @@ const UnitVTable service_vtable = { .bus_set_property = bus_service_set_property, .bus_commit_properties = bus_service_commit_properties, + .can_transient = true, + #ifdef HAVE_SYSV_COMPAT .enumerate = service_enumerate, #endif diff --git a/src/core/transaction.c b/src/core/transaction.c index fa97b6975..5259a5b7c 100644 --- a/src/core/transaction.c +++ b/src/core/transaction.c @@ -851,12 +851,13 @@ int transaction_add_job_and_dependencies( if (unit->load_state != UNIT_LOADED && unit->load_state != UNIT_ERROR && + unit->load_state != UNIT_NOT_FOUND && unit->load_state != UNIT_MASKED) { dbus_set_error(e, BUS_ERROR_LOAD_FAILED, "Unit %s is not loaded properly.", unit->id); return -EINVAL; } - if (type != JOB_STOP && unit->load_state == UNIT_ERROR) { + if (type != JOB_STOP && (unit->load_state == UNIT_ERROR || unit->load_state == UNIT_NOT_FOUND)) { dbus_set_error(e, BUS_ERROR_LOAD_FAILED, "Unit %s failed to load: %s. " "See system logs and 'systemctl status %s' for details.", diff --git a/src/core/unit.c b/src/core/unit.c index afeb15c15..c6c9c1865 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -348,6 +348,29 @@ static void bidi_set_free(Unit *u, Set *s) { set_free(s); } +static void unit_remove_transient(Unit *u) { + char **i; + + assert(u); + + if (!u->transient) + return; + + if (u->fragment_path) + unlink(u->fragment_path); + + STRV_FOREACH(i, u->dropin_paths) { + _cleanup_free_ char *p = NULL; + int r; + + unlink(*i); + + r = path_get_parent(*i, &p); + if (r >= 0) + rmdir(p); + } +} + void unit_free(Unit *u) { UnitDependency d; Iterator i; @@ -355,6 +378,9 @@ void unit_free(Unit *u) { assert(u); + if (u->manager->n_reloading <= 0) + unit_remove_transient(u); + bus_unit_send_removed_signal(u); if (u->load_state != UNIT_STUB) @@ -524,7 +550,7 @@ int unit_merge(Unit *u, Unit *other) { return -EINVAL; if (other->load_state != UNIT_STUB && - other->load_state != UNIT_ERROR) + other->load_state != UNIT_NOT_FOUND) return -EEXIST; if (other->job) @@ -580,7 +606,8 @@ int unit_merge_by_name(Unit *u, const char *name) { name = s; } - if (!(other = manager_get_unit(u->manager, name))) + other = manager_get_unit(u->manager, name); + if (!other) r = unit_add_name(u, name); else r = unit_merge(u, other); @@ -673,6 +700,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { "%s\tInactive Enter Timestamp: %s\n" "%s\tGC Check Good: %s\n" "%s\tNeed Daemon Reload: %s\n" + "%s\tTransient: %s\n" "%s\tSlice: %s\n" "%s\tCGroup: %s\n" "%s\tCGroup realized: %s\n" @@ -688,6 +716,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { prefix, strna(format_timestamp(timestamp4, sizeof(timestamp4), u->inactive_enter_timestamp.realtime)), prefix, yes_no(unit_check_gc(u)), prefix, yes_no(unit_need_daemon_reload(u)), + prefix, yes_no(u->transient), prefix, strna(unit_slice_name(u)), prefix, strna(u->cgroup_path), prefix, yes_no(u->cgroup_realized), @@ -903,34 +932,38 @@ int unit_load(Unit *u) { if (u->load_state != UNIT_STUB) return 0; - if (UNIT_VTABLE(u)->load) - if ((r = UNIT_VTABLE(u)->load(u)) < 0) + if (UNIT_VTABLE(u)->load) { + r = UNIT_VTABLE(u)->load(u); + if (r < 0) goto fail; + } if (u->load_state == UNIT_STUB) { r = -ENOENT; goto fail; } - if (u->load_state == UNIT_LOADED && - u->default_dependencies) - if ((r = unit_add_default_dependencies(u)) < 0) - goto fail; - if (u->load_state == UNIT_LOADED) { + + if (u->default_dependencies) { + r = unit_add_default_dependencies(u); + if (r < 0) + goto fail; + } + r = unit_add_mount_links(u); if (r < 0) - return r; - } + goto fail; - if (u->on_failure_isolate && - set_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { + if (u->on_failure_isolate && + set_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { - log_error_unit(u->id, - "More than one OnFailure= dependencies specified for %s but OnFailureIsolate= enabled. Refusing.", u->id); + log_error_unit(u->id, + "More than one OnFailure= dependencies specified for %s but OnFailureIsolate= enabled. Refusing.", u->id); - r = -EINVAL; - goto fail; + r = -EINVAL; + goto fail; + } } assert((u->load_state != UNIT_MERGED) == !u->merged_into); @@ -941,7 +974,7 @@ int unit_load(Unit *u) { return 0; fail: - u->load_state = UNIT_ERROR; + u->load_state = u->load_state == UNIT_STUB ? UNIT_NOT_FOUND : UNIT_ERROR; u->load_error = r; unit_add_to_dbus_queue(u); unit_add_to_gc_queue(u); @@ -2117,6 +2150,11 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { if (dual_timestamp_is_set(&u->condition_timestamp)) unit_serialize_item(u, f, "condition-result", yes_no(u->condition_result)); + unit_serialize_item(u, f, "transient", yes_no(u->transient)); + + if (u->cgroup_path) + unit_serialize_item(u, f, "cgroup", u->cgroup_path); + /* End marker */ fputc('\n', f); return 0; @@ -2239,15 +2277,38 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { } else if (streq(l, "condition-result")) { int b; - if ((b = parse_boolean(v)) < 0) + b = parse_boolean(v); + if (b < 0) log_debug("Failed to parse condition result value %s", v); else u->condition_result = b; continue; + + } else if (streq(l, "transient")) { + int b; + + b = parse_boolean(v); + if (b < 0) + log_debug("Failed to parse transient bool %s", v); + else + u->transient = b; + + continue; + } else if (streq(l, "cgroup")) { + char *s; + + s = strdup(v); + if (!v) + return -ENOMEM; + + free(u->cgroup_path); + u->cgroup_path = s; + continue; } - if ((r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds)) < 0) + r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds); + if (r < 0) return r; } } @@ -2652,9 +2713,6 @@ static int drop_in_file(Unit *u, UnitSetPropertiesMode mode, const char *name, c assert(_q); assert(mode & (UNIT_PERSISTENT|UNIT_RUNTIME)); - if (u->manager->running_as == SYSTEMD_USER && !(mode & UNIT_PERSISTENT)) - return -ENOTSUP; - if (!filename_is_safe(name)) return -EINVAL; @@ -2668,7 +2726,7 @@ static int drop_in_file(Unit *u, UnitSetPropertiesMode mode, const char *name, c return -ENOENT; p = strjoin(c, "/", u->id, ".d", NULL); - } else if (mode & UNIT_PERSISTENT) + } else if (mode & UNIT_PERSISTENT) p = strjoin("/etc/systemd/system/", u->id, ".d", NULL); else p = strjoin("/run/systemd/system/", u->id, ".d", NULL); @@ -2741,6 +2799,43 @@ int unit_remove_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name) { return r; } +int unit_make_transient(Unit *u) { + int r; + + assert(u); + + u->load_state = UNIT_STUB; + u->load_error = 0; + u->transient = true; + + free(u->fragment_path); + u->fragment_path = NULL; + + if (u->manager->running_as == SYSTEMD_USER) { + _cleanup_free_ char *c = NULL; + + r = user_config_home(&c); + if (r < 0) + return r; + if (r == 0) + return -ENOENT; + + u->fragment_path = strjoin(c, "/", u->id, NULL); + if (!u->fragment_path) + return -ENOMEM; + + mkdir_p(c, 0755); + } else { + u->fragment_path = strappend("/run/systemd/system/", u->id); + if (!u->fragment_path) + return -ENOMEM; + + mkdir_p("/run/systemd/system", 0755); + } + + return write_string_file_atomic_label(u->fragment_path, "# Transient stub"); +} + int unit_kill_context( Unit *u, KillContext *c, diff --git a/src/core/unit.h b/src/core/unit.h index be6abaff9..8a62787b3 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -241,6 +241,9 @@ struct Unit { /* Did the last condition check succeed? */ bool condition_result; + /* Is this a transient unit? */ + bool transient; + bool in_load_queue:1; bool in_dbus_queue:1; bool in_cleanup_queue:1; @@ -425,6 +428,9 @@ struct UnitVTable { /* Exclude from automatic gc */ bool no_gc:1; + + /* True if transient units of this type are OK */ + bool can_transient:1; }; extern const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX]; @@ -595,6 +601,8 @@ int unit_remove_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name); int unit_kill_context(Unit *u, KillContext *c, bool sigkill, pid_t main_pid, pid_t control_pid, bool main_pid_alien); +int unit_make_transient(Unit *u); + const char *unit_active_state_to_string(UnitActiveState i) _const_; UnitActiveState unit_active_state_from_string(const char *s) _pure_; diff --git a/src/run/Makefile b/src/run/Makefile new file mode 120000 index 000000000..d0b0e8e00 --- /dev/null +++ b/src/run/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/run/run.c b/src/run/run.c new file mode 100644 index 000000000..9f8bda48a --- /dev/null +++ b/src/run/run.c @@ -0,0 +1,156 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +#include "sd-bus.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "strv.h" + +static int start_transient_service( + sd_bus *bus, + const char *name, + char **argv, + sd_bus_error *error) { + + _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL; + char **i; + int r; + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit", &m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "ss", name, "fail"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", "ExecStart"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'v', "a(sasb)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "(sasb)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'r', "sasb"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", argv[0]); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return r; + + STRV_FOREACH(i, argv) { + r = sd_bus_message_append(m, "s", *i); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "b", false); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + return sd_bus_send_with_reply_and_block(bus, m, 0, error, &reply); +} + +int main(int argc, char* argv[]) { + sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_bus_unref_ sd_bus *bus = NULL; + _cleanup_free_ char *name = NULL; + int r; + + log_parse_environment(); + log_open(); + + if (argc < 2) { + log_error("Missing command line."); + r = -EINVAL; + goto fail; + } + + r = sd_bus_open_user(&bus); + if (r < 0) { + log_error("Failed to create new bus: %s", strerror(-r)); + goto fail; + } + + if (asprintf(&name, "run-%lu.service", (unsigned long) getpid()) < 0) { + r = log_oom(); + goto fail; + } + + r = start_transient_service(bus, name, argv + 1, &error); + if (r < 0) { + log_error("Failed start transient service: %s", error.message); + sd_bus_error_free(&error); + goto fail; + } + +fail: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/shared/unit-name.c b/src/shared/unit-name.c index 0910e86f1..bf1ab794a 100644 --- a/src/shared/unit-name.c +++ b/src/shared/unit-name.c @@ -52,6 +52,7 @@ DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType); static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = { [UNIT_STUB] = "stub", [UNIT_LOADED] = "loaded", + [UNIT_NOT_FOUND] = "not-found", [UNIT_ERROR] = "error", [UNIT_MERGED] = "merged", [UNIT_MASKED] = "masked" @@ -403,7 +404,6 @@ char *unit_name_template(const char *f) { strcpy(mempcpy(r, f, a), e); return r; - } char *unit_name_from_path(const char *path, const char *suffix) { diff --git a/src/shared/unit-name.h b/src/shared/unit-name.h index baa487a81..922d75232 100644 --- a/src/shared/unit-name.h +++ b/src/shared/unit-name.h @@ -49,6 +49,7 @@ enum UnitType { enum UnitLoadState { UNIT_STUB = 0, UNIT_LOADED, + UNIT_NOT_FOUND, UNIT_ERROR, UNIT_MERGED, UNIT_MASKED, -- 2.30.2