diff options
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/dbus-scope.c | 162 | ||||
-rw-r--r-- | src/core/dbus-scope.h | 33 | ||||
-rw-r--r-- | src/core/scope.c | 466 | ||||
-rw-r--r-- | src/core/scope.h | 69 | ||||
-rw-r--r-- | src/core/service.c | 5 | ||||
-rw-r--r-- | src/core/slice.c | 5 | ||||
-rw-r--r-- | src/core/unit.c | 3 | ||||
-rw-r--r-- | src/core/unit.h | 2 |
8 files changed, 740 insertions, 5 deletions
diff --git a/src/core/dbus-scope.c b/src/core/dbus-scope.c new file mode 100644 index 0000000000..1497b76761 --- /dev/null +++ b/src/core/dbus-scope.c @@ -0,0 +1,162 @@ +/*-*- 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 <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> + +#include "dbus-unit.h" +#include "dbus-common.h" +#include "dbus-cgroup.h" +#include "dbus-kill.h" +#include "selinux-access.h" +#include "dbus-scope.h" + +#define BUS_SCOPE_INTERFACE \ + " <interface name=\"org.freedesktop.systemd1.Scope\">\n" \ + " <property name=\"TimeoutStopUSec\" type=\"t\" access=\"read\"/>\n" \ + BUS_KILL_CONTEXT_INTERFACE \ + BUS_CGROUP_CONTEXT_INTERFACE \ + " <property name=\"Result\" type=\"s\" access=\"read\"/>\n" \ + " </interface>\n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "<node>\n" \ + BUS_UNIT_INTERFACE \ + BUS_SCOPE_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "</node>\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Scope\0" + +const char bus_scope_interface[] _introspect_("Scope") = BUS_SCOPE_INTERFACE; + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_scope_append_scope_result, scope_result, ScopeResult); + +static const BusProperty bus_scope_properties[] = { + { "TimeoutStopUSec", bus_property_append_usec, "t", offsetof(Scope, timeout_stop_usec) }, + { "Result", bus_scope_append_scope_result, "s", offsetof(Scope, result) }, + {} +}; + +DBusHandlerResult bus_scope_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Scope *s = SCOPE(u); + + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Scope", bus_scope_properties, s }, + { "org.freedesktop.systemd1.Scope", bus_cgroup_context_properties, &s->cgroup_context }, + { "org.freedesktop.systemd1.Scope", bus_kill_context_properties, &s->kill_context }, + {} + }; + + SELINUX_UNIT_ACCESS_CHECK(u, c, message, "status"); + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); +} + +static int bus_scope_set_transient_properties( + Scope *s, + const char *name, + DBusMessageIter *i, + UnitSetPropertiesMode mode, + DBusError *error) { + + int r; + + assert(name); + assert(s); + assert(i); + + if (streq(name, "PIDs")) { + DBusMessageIter sub; + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(i) != DBUS_TYPE_UINT32) + return -EINVAL; + + r = set_ensure_allocated(&s->pids, trivial_hash_func, trivial_compare_func); + if (r < 0) + return r; + + dbus_message_iter_recurse(i, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32) { + uint32_t pid; + + dbus_message_iter_get_basic(&sub, &pid); + + if (pid <= 1) + return -EINVAL; + + r = set_put(s->pids, LONG_TO_PTR(pid)); + if (r < 0 && r != -EEXIST) + return r; + + dbus_message_iter_next(&sub); + } + + if (set_size(s->pids) <= 0) + return -EINVAL; + + return 1; + } + + return 0; +} + +int bus_scope_set_property( + Unit *u, + const char *name, + DBusMessageIter *i, + UnitSetPropertiesMode mode, + DBusError *error) { + + Scope *s = SCOPE(u); + int r; + + assert(name); + assert(u); + assert(i); + + r = bus_cgroup_set_property(u, &s->cgroup_context, name, i, mode, error); + if (r != 0) + return r; + + if (u->load_state == UNIT_STUB) { + /* While we are created we still accept PIDs */ + + r = bus_scope_set_transient_properties(s, name, i, mode, error); + if (r != 0) + return r; + } + + return 0; +} + +int bus_scope_commit_properties(Unit *u) { + assert(u); + + unit_realize_cgroup(u); + return 0; +} diff --git a/src/core/dbus-scope.h b/src/core/dbus-scope.h new file mode 100644 index 0000000000..e6836f13f0 --- /dev/null +++ b/src/core/dbus-scope.h @@ -0,0 +1,33 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + 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 <http://www.gnu.org/licenses/>. +***/ + +#include <dbus/dbus.h> + +#include "unit.h" + +DBusHandlerResult bus_scope_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +int bus_scope_set_property(Unit *u, const char *name, DBusMessageIter *i, UnitSetPropertiesMode mode, DBusError *error); +int bus_scope_commit_properties(Unit *u); + +extern const char bus_scope_interface[]; diff --git a/src/core/scope.c b/src/core/scope.c new file mode 100644 index 0000000000..f88addadf3 --- /dev/null +++ b/src/core/scope.c @@ -0,0 +1,466 @@ +/*-*- 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 <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <signal.h> +#include <unistd.h> + +#include "unit.h" +#include "scope.h" +#include "load-fragment.h" +#include "log.h" +#include "dbus-scope.h" +#include "special.h" +#include "unit-name.h" +#include "load-dropin.h" + +static const UnitActiveState state_translation_table[_SCOPE_STATE_MAX] = { + [SCOPE_DEAD] = UNIT_INACTIVE, + [SCOPE_RUNNING] = UNIT_ACTIVE, + [SCOPE_STOP_SIGTERM] = UNIT_DEACTIVATING, + [SCOPE_STOP_SIGKILL] = UNIT_DEACTIVATING, + [SCOPE_FAILED] = UNIT_FAILED +}; + +static void scope_init(Unit *u) { + Scope *s = SCOPE(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + s->timeout_stop_usec = DEFAULT_TIMEOUT_USEC; + + watch_init(&s->timer_watch); + + cgroup_context_init(&s->cgroup_context); + kill_context_init(&s->kill_context); + + UNIT(s)->ignore_on_isolate = true; + UNIT(s)->ignore_on_snapshot = true; +} + +static void scope_done(Unit *u) { + Scope *s = SCOPE(u); + + assert(u); + + cgroup_context_done(&s->cgroup_context); + + set_free(s->pids); + s->pids = NULL; + + unit_unwatch_timer(u, &s->timer_watch); +} + +static void scope_set_state(Scope *s, ScopeState state) { + ScopeState old_state; + assert(s); + + old_state = s->state; + s->state = state; + + if (state != SCOPE_STOP_SIGTERM && + state != SCOPE_STOP_SIGKILL) + unit_unwatch_timer(UNIT(s), &s->timer_watch); + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(s)->id, + scope_state_to_string(old_state), + scope_state_to_string(state)); + + unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); +} + +static int scope_add_default_dependencies(Scope *s) { + int r; + + assert(s); + + /* Make sure scopes are unloaded on shutdown */ + r = unit_add_two_dependencies_by_name( + UNIT(s), + UNIT_BEFORE, UNIT_CONFLICTS, + SPECIAL_SHUTDOWN_TARGET, NULL, true); + if (r < 0) + return r; + + return 0; +} + +static int scope_verify(Scope *s) { + assert(s); + + if (UNIT(s)->load_state != UNIT_LOADED) + return 0; + + if (set_size(s->pids) <= 0) { + log_error_unit(UNIT(s)->id, "Scope %s has no PIDs. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + return 0; +} + +static int scope_load(Unit *u) { + Scope *s = SCOPE(u); + int r; + + assert(s); + assert(u->load_state == UNIT_STUB); + + if (!u->transient && UNIT(s)->manager->n_reloading <= 0) + return -ENOENT; + + u->load_state = UNIT_LOADED; + + r = unit_load_dropin(u); + if (r < 0) + return r; + + r = unit_add_default_slice(u); + if (r < 0) + return r; + + if (u->default_dependencies) { + r = scope_add_default_dependencies(s); + if (r < 0) + return r; + } + + return scope_verify(s); +} + +static int scope_coldplug(Unit *u) { + Scope *s = SCOPE(u); + int r; + + assert(s); + assert(s->state == SCOPE_DEAD); + + if (s->deserialized_state != s->state) { + + if ((s->deserialized_state == SCOPE_STOP_SIGKILL || s->deserialized_state == SCOPE_STOP_SIGTERM) + && s->timeout_stop_usec > 0) { + r = unit_watch_timer(UNIT(s), CLOCK_MONOTONIC, true, s->timeout_stop_usec, &s->timer_watch); + if (r < 0) + + return r; + } + + scope_set_state(s, s->deserialized_state); + } + + return 0; +} + +static void scope_dump(Unit *u, FILE *f, const char *prefix) { + Scope *s = SCOPE(u); + + assert(s); + assert(f); + + fprintf(f, + "%sScope State: %s\n" + "%sResult: %s\n", + prefix, scope_state_to_string(s->state), + prefix, scope_result_to_string(s->result)); + + cgroup_context_dump(&s->cgroup_context, f, prefix); + kill_context_dump(&s->kill_context, f, prefix); +} + +static void scope_enter_dead(Scope *s, ScopeResult f) { + assert(s); + + if (f != SCOPE_SUCCESS) + s->result = f; + + scope_set_state(s, s->result != SCOPE_SUCCESS ? SCOPE_FAILED : SCOPE_DEAD); +} + +static void scope_enter_signal(Scope *s, ScopeState state, ScopeResult f) { + int r; + + assert(s); + + if (f != SCOPE_SUCCESS) + s->result = f; + + r = unit_kill_context( + UNIT(s), + &s->kill_context, + state != SCOPE_STOP_SIGTERM, + -1, -1, false); + if (r < 0) + goto fail; + + if (r > 0) { + if (s->timeout_stop_usec > 0) { + r = unit_watch_timer(UNIT(s), CLOCK_MONOTONIC, true, s->timeout_stop_usec, &s->timer_watch); + if (r < 0) + goto fail; + } + + scope_set_state(s, state); + } else + scope_enter_dead(s, SCOPE_SUCCESS); + + return; + +fail: + log_warning_unit(UNIT(s)->id, + "%s failed to kill processes: %s", UNIT(s)->id, strerror(-r)); + + scope_enter_dead(s, SCOPE_FAILURE_RESOURCES); +} + +static int scope_start(Unit *u) { + Scope *s = SCOPE(u); + int r; + + assert(s); + + if (s->state == SCOPE_STOP_SIGTERM || + s->state == SCOPE_STOP_SIGKILL) + return -EAGAIN; + + assert(s->state == SCOPE_DEAD); + + if (!u->transient && UNIT(s)->manager->n_reloading <= 0) + return -ENOENT; + + r = unit_realize_cgroup(u); + if (r < 0) { + log_error("Failed to realize cgroup: %s", strerror(-r)); + return r; + } + + r = cg_attach_many_with_mask(u->cgroup_mask, u->cgroup_path, s->pids); + if (r < 0) + return r; + + set_free(s->pids); + s->pids = NULL; + + s->result = SCOPE_SUCCESS; + + scope_set_state(s, SCOPE_RUNNING); + return 0; +} + +static int scope_stop(Unit *u) { + Scope *s = SCOPE(u); + + assert(s); + assert(s->state == SCOPE_RUNNING); + + if (s->state == SCOPE_STOP_SIGTERM || + s->state == SCOPE_STOP_SIGKILL) + return 0; + + assert(s->state == SCOPE_RUNNING); + + scope_enter_signal(s, SCOPE_STOP_SIGTERM, SCOPE_SUCCESS); + return 0; +} + +static int scope_kill(Unit *u, KillWho who, int signo, DBusError *error) { + return unit_kill_common(u, who, signo, -1, -1, error); +} + +static int scope_serialize(Unit *u, FILE *f, FDSet *fds) { + Scope *s = SCOPE(u); + + assert(s); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", scope_state_to_string(s->state)); + return 0; +} + +static int scope_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Scope *s = SCOPE(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + ScopeState state; + + state = scope_state_from_string(value); + if (state < 0) + log_debug("Failed to parse state value %s", value); + else + s->deserialized_state = state; + + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +static bool scope_check_gc(Unit *u) { + Scope *s = SCOPE(u); + int r; + + assert(s); + + /* Never clean up scopes that still have a process around, + * even if the scope is formally dead. */ + + if (UNIT(s)->cgroup_path) { + r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, UNIT(s)->cgroup_path, true); + if (r <= 0) + return true; + } + + return false; +} + +static void scope_timer_event(Unit *u, uint64_t elapsed, Watch*w) { + Scope *s = SCOPE(u); + + assert(s); + assert(elapsed == 1); + assert(w == &s->timer_watch); + + switch (s->state) { + + case SCOPE_STOP_SIGTERM: + if (s->kill_context.send_sigkill) { + log_warning_unit(u->id, "%s stopping timed out. Killing.", u->id); + scope_enter_signal(s, SCOPE_STOP_SIGKILL, SCOPE_FAILURE_TIMEOUT); + } else { + log_warning_unit(u->id, "%s stopping timed out. Skipping SIGKILL.", u->id); + scope_enter_dead(s, SCOPE_FAILURE_TIMEOUT); + } + + break; + + case SCOPE_STOP_SIGKILL: + log_warning_unit(u->id, "%s still around after SIGKILL. Ignoring.", u->id); + scope_enter_dead(s, SCOPE_FAILURE_TIMEOUT); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } +} + +static void scope_notify_cgroup_empty_event(Unit *u) { + Scope *s = SCOPE(u); + assert(u); + + log_debug_unit(u->id, "%s: cgroup is empty", u->id); + + switch (s->state) { + + case SCOPE_RUNNING: + case SCOPE_STOP_SIGTERM: + case SCOPE_STOP_SIGKILL: + scope_enter_dead(s, SCOPE_SUCCESS); + + break; + + default: + ; + } +} + +_pure_ static UnitActiveState scope_active_state(Unit *u) { + assert(u); + + return state_translation_table[SCOPE(u)->state]; +} + +_pure_ static const char *scope_sub_state_to_string(Unit *u) { + assert(u); + + return scope_state_to_string(SCOPE(u)->state); +} + +static const char* const scope_state_table[_SCOPE_STATE_MAX] = { + [SCOPE_DEAD] = "dead", + [SCOPE_RUNNING] = "active", + [SCOPE_STOP_SIGTERM] = "stop-sigterm", + [SCOPE_STOP_SIGKILL] = "stop-sigkill", + [SCOPE_FAILED] = "failed", +}; + +DEFINE_STRING_TABLE_LOOKUP(scope_state, ScopeState); + +static const char* const scope_result_table[_SCOPE_RESULT_MAX] = { + [SCOPE_SUCCESS] = "success", + [SCOPE_FAILURE_RESOURCES] = "resources", + [SCOPE_FAILURE_TIMEOUT] = "timeout", +}; + +DEFINE_STRING_TABLE_LOOKUP(scope_result, ScopeResult); + +const UnitVTable scope_vtable = { + .object_size = sizeof(Scope), + .sections = + "Unit\0" + "Scope\0" + "Install\0", + + .private_section = "Scope", + .cgroup_context_offset = offsetof(Scope, cgroup_context), + + .no_alias = true, + .no_instances = true, + + .init = scope_init, + .load = scope_load, + .done = scope_done, + + .coldplug = scope_coldplug, + + .dump = scope_dump, + + .start = scope_start, + .stop = scope_stop, + + .kill = scope_kill, + + .serialize = scope_serialize, + .deserialize_item = scope_deserialize_item, + + .active_state = scope_active_state, + .sub_state_to_string = scope_sub_state_to_string, + + .check_gc = scope_check_gc, + + .timer_event = scope_timer_event, + + .notify_cgroup_empty = scope_notify_cgroup_empty_event, + + .bus_interface = "org.freedesktop.systemd1.Scope", + .bus_message_handler = bus_scope_message_handler, + .bus_set_property = bus_scope_set_property, + .bus_commit_properties = bus_scope_commit_properties, + + .can_transient = true +}; diff --git a/src/core/scope.h b/src/core/scope.h new file mode 100644 index 0000000000..2a3dcb73d7 --- /dev/null +++ b/src/core/scope.h @@ -0,0 +1,69 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + 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 <http://www.gnu.org/licenses/>. +***/ + +typedef struct Scope Scope; + +#include "unit.h" +#include "kill.h" + +typedef enum ScopeState { + SCOPE_DEAD, + SCOPE_RUNNING, + SCOPE_STOP_SIGTERM, + SCOPE_STOP_SIGKILL, + SCOPE_FAILED, + _SCOPE_STATE_MAX, + _SCOPE_STATE_INVALID = -1 +} ScopeState; + +typedef enum ScopeResult { + SCOPE_SUCCESS, + SCOPE_FAILURE_RESOURCES, + SCOPE_FAILURE_TIMEOUT, + _SCOPE_RESULT_MAX, + _SCOPE_RESULT_INVALID = -1 +} ScopeResult; + +struct Scope { + Unit meta; + + CGroupContext cgroup_context; + KillContext kill_context; + + ScopeState state, deserialized_state; + ScopeResult result; + + usec_t timeout_stop_usec; + + Set *pids; + + Watch timer_watch; +}; + +extern const UnitVTable scope_vtable; + +const char* scope_state_to_string(ScopeState i) _const_; +ScopeState scope_state_from_string(const char *s) _pure_; + +const char* scope_result_to_string(ScopeResult i) _const_; +ScopeResult scope_result_from_string(const char *s) _pure_; diff --git a/src/core/service.c b/src/core/service.c index 6f18cbf759..2bc0dc5877 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -1617,6 +1617,7 @@ static int service_coldplug(Unit *u) { s->deserialized_state == SERVICE_FINAL_SIGTERM || s->deserialized_state == SERVICE_FINAL_SIGKILL || s->deserialized_state == SERVICE_AUTO_RESTART) { + if (s->deserialized_state == SERVICE_AUTO_RESTART || s->timeout_start_usec > 0) { usec_t k; @@ -2144,7 +2145,6 @@ static void service_kill_control_processes(Service *s) { return; p = strappenda(UNIT(s)->cgroup_path, "/control"); - cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, p, SIGKILL, true, true, true, NULL); } @@ -3322,8 +3322,7 @@ static void service_notify_cgroup_empty_event(Unit *u) { assert(u); - log_debug_unit(u->id, - "%s: cgroup is empty", u->id); + log_debug_unit(u->id, "%s: cgroup is empty", u->id); switch (s->state) { diff --git a/src/core/slice.c b/src/core/slice.c index 557f829088..40d416e35e 100644 --- a/src/core/slice.c +++ b/src/core/slice.c @@ -103,7 +103,10 @@ static int slice_add_default_dependencies(Slice *s) { assert(s); /* Make sure slices are unloaded on shutdown */ - r = unit_add_dependency_by_name(UNIT(s), UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); + r = unit_add_two_dependencies_by_name( + UNIT(s), + UNIT_BEFORE, UNIT_CONFLICTS, + SPECIAL_SHUTDOWN_TARGET, NULL, true); if (r < 0) return r; diff --git a/src/core/unit.c b/src/core/unit.c index c6c9c18653..991111ab31 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -60,7 +60,8 @@ const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { [UNIT_SNAPSHOT] = &snapshot_vtable, [UNIT_SWAP] = &swap_vtable, [UNIT_PATH] = &path_vtable, - [UNIT_SLICE] = &slice_vtable + [UNIT_SLICE] = &slice_vtable, + [UNIT_SCOPE] = &scope_vtable }; Unit *unit_new(Manager *m, size_t size) { diff --git a/src/core/unit.h b/src/core/unit.h index 8a62787b38..ed4df18246 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -280,6 +280,7 @@ typedef enum UnitSetPropertiesMode { #include "swap.h" #include "path.h" #include "slice.h" +#include "scope.h" struct UnitVTable { /* How much memory does an object of this unit type need */ @@ -462,6 +463,7 @@ DEFINE_CAST(SNAPSHOT, Snapshot); DEFINE_CAST(SWAP, Swap); DEFINE_CAST(PATH, Path); DEFINE_CAST(SLICE, Slice); +DEFINE_CAST(SCOPE, Scope); Unit *unit_new(Manager *m, size_t size); void unit_free(Unit *u); |