From e537352b9bfffe6f6286483bff2c7601c78407e3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 10 Apr 2010 17:53:17 +0200 Subject: [PATCH] mount: implement mounting properly This also includes code that writes utmp/wtmp records when applicable, making use the mount infrastructure to detct when those files are accessible. Finally, this also introduces a --dump-configuration-items switch. --- Makefile.am | 4 +- automount.c | 104 ++--- automount.h | 21 +- device.c | 29 +- load-fragment.c | 124 +++++- load-fragment.h | 4 +- main.c | 35 +- manager.c | 74 ++++ manager.h | 8 + mount.c | 1000 ++++++++++++++++++++++++++++++++++++++++++----- mount.h | 57 ++- service.c | 89 +++-- service.h | 12 +- socket.c | 102 +++-- socket.h | 12 +- target.c | 18 +- timer.c | 2 +- unit.c | 94 +++-- unit.h | 29 +- utmp-wtmp.c | 210 ++++++++++ utmp-wtmp.h | 33 ++ 21 files changed, 1676 insertions(+), 385 deletions(-) create mode 100644 utmp-wtmp.c create mode 100644 utmp-wtmp.h diff --git a/Makefile.am b/Makefile.am index 9d8bd9df3..ec90ebad5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -106,7 +106,9 @@ COMMON_SOURCES= \ mount-setup.c \ mount-setup.h \ hostname-setup.c \ - hostname-setup.h + hostname-setup.h \ + utmp-wtmp.c \ + utmp-wtmp.h systemd_SOURCES = \ $(COMMON_SOURCES) \ diff --git a/automount.c b/automount.c index c28472da7..c6ce25d8b 100644 --- a/automount.c +++ b/automount.c @@ -26,34 +26,44 @@ #include "load-fragment.h" #include "load-dropin.h" -static int automount_init(Unit *u, UnitLoadState *new_state) { - int r; - Automount *a = AUTOMOUNT(u); +static const UnitActiveState state_translation_table[_AUTOMOUNT_STATE_MAX] = { + [AUTOMOUNT_DEAD] = UNIT_INACTIVE, + [AUTOMOUNT_WAITING] = UNIT_ACTIVE, + [AUTOMOUNT_RUNNING] = UNIT_ACTIVE, + [AUTOMOUNT_MAINTAINANCE] = UNIT_INACTIVE, +}; - assert(a); +static const char* const state_string_table[_AUTOMOUNT_STATE_MAX] = { + [AUTOMOUNT_DEAD] = "dead", + [AUTOMOUNT_WAITING] = "waiting", + [AUTOMOUNT_RUNNING] = "running", + [AUTOMOUNT_MAINTAINANCE] = "maintainance" +}; - exec_context_init(&a->exec_context); +static void automount_init(Unit *u) { + Automount *a = AUTOMOUNT(u); - /* Load a .automount file */ - if ((r = unit_load_fragment(u, new_state)) < 0) - return r; + a->state = 0; + a->mount = NULL; +} - if (*new_state == UNIT_STUB) - *new_state = UNIT_LOADED; +static int automount_load(Unit *u) { + int r; + Automount *a = AUTOMOUNT(u); - /* Load drop-in directory data */ - if ((r = unit_load_dropin(unit_follow_merge(u))) < 0) - return r; + assert(u); + assert(u->meta.load_state == UNIT_STUB); - if (*new_state == UNIT_LOADED) { + /* Load a .automount file */ + if ((r = unit_load_fragment_and_dropin_optional(u)) < 0) + return r; - if ((r = unit_add_dependency(u, UNIT_BEFORE, UNIT(a->mount))) < 0) - return r; + if (u->meta.load_state == UNIT_LOADED) { - if ((r = unit_add_exec_dependencies(u, &a->exec_context)) < 0) + if ((r = unit_load_related_unit(u, ".mount", (Unit**) &a->mount)) < 0) return r; - if ((r = unit_add_default_cgroup(u)) < 0) + if ((r = unit_add_dependency(u, UNIT_BEFORE, UNIT(a->mount))) < 0) return r; } @@ -61,73 +71,35 @@ static int automount_init(Unit *u, UnitLoadState *new_state) { } static void automount_done(Unit *u) { - Automount *d = AUTOMOUNT(u); + Automount *a = AUTOMOUNT(u); + + assert(a); - assert(d); - free(d->path); + a->mount = NULL; } static void automount_dump(Unit *u, FILE *f, const char *prefix) { - - static const char* const state_table[_AUTOMOUNT_STATE_MAX] = { - [AUTOMOUNT_DEAD] = "dead", - [AUTOMOUNT_START_PRE] = "start-pre", - [AUTOMOUNT_START_POST] = "start-post", - [AUTOMOUNT_WAITING] = "waiting", - [AUTOMOUNT_RUNNING] = "running", - [AUTOMOUNT_STOP_PRE] = "stop-pre", - [AUTOMOUNT_STOP_POST] = "stop-post", - [AUTOMOUNT_MAINTAINANCE] = "maintainance" - }; - - static const char* const command_table[_AUTOMOUNT_EXEC_MAX] = { - [AUTOMOUNT_EXEC_START_PRE] = "StartPre", - [AUTOMOUNT_EXEC_START_POST] = "StartPost", - [AUTOMOUNT_EXEC_STOP_PRE] = "StopPre", - [AUTOMOUNT_EXEC_STOP_POST] = "StopPost" - }; - - AutomountExecCommand c; Automount *s = AUTOMOUNT(u); assert(s); fprintf(f, - "%sAutomount State: %s\n" - "%sPath: %s\n", - prefix, state_table[s->state], - prefix, s->path); - - exec_context_dump(&s->exec_context, f, prefix); - - for (c = 0; c < _AUTOMOUNT_EXEC_MAX; c++) { - ExecCommand *i; - - LIST_FOREACH(command, i, s->exec_command[c]) - fprintf(f, "%s%s: %s\n", prefix, command_table[c], i->path); - } + "%sAutomount State: %s\n", + prefix, state_string_table[s->state]); } static UnitActiveState automount_active_state(Unit *u) { - static const UnitActiveState table[_AUTOMOUNT_STATE_MAX] = { - [AUTOMOUNT_DEAD] = UNIT_INACTIVE, - [AUTOMOUNT_START_PRE] = UNIT_ACTIVATING, - [AUTOMOUNT_START_POST] = UNIT_ACTIVATING, - [AUTOMOUNT_WAITING] = UNIT_ACTIVE, - [AUTOMOUNT_RUNNING] = UNIT_ACTIVE, - [AUTOMOUNT_STOP_PRE] = UNIT_DEACTIVATING, - [AUTOMOUNT_STOP_POST] = UNIT_DEACTIVATING, - [AUTOMOUNT_MAINTAINANCE] = UNIT_INACTIVE, - }; - - return table[AUTOMOUNT(u)->state]; + return state_translation_table[AUTOMOUNT(u)->state]; } const UnitVTable automount_vtable = { .suffix = ".mount", + .no_alias = true, + .init = automount_init, + .load = automount_load, .done = automount_done, .dump = automount_dump, diff --git a/automount.h b/automount.h index e7d778c7b..daa3686fc 100644 --- a/automount.h +++ b/automount.h @@ -28,34 +28,17 @@ typedef struct Automount Automount; typedef enum AutomountState { AUTOMOUNT_DEAD, - AUTOMOUNT_START_PRE, - AUTOMOUNT_START_POST, AUTOMOUNT_WAITING, AUTOMOUNT_RUNNING, - AUTOMOUNT_STOP_PRE, - AUTOMOUNT_STOP_POST, AUTOMOUNT_MAINTAINANCE, - _AUTOMOUNT_STATE_MAX + _AUTOMOUNT_STATE_MAX, + _AUTOMOUNT_STATE_INVALID = -1 } AutomountState; -typedef enum AutomountExecCommand { - AUTOMOUNT_EXEC_START_PRE, - AUTOMOUNT_EXEC_START_POST, - AUTOMOUNT_EXEC_STOP_PRE, - AUTOMOUNT_EXEC_STOP_POST, - _AUTOMOUNT_EXEC_MAX -} AutomountExecCommand; - struct Automount { Meta meta; AutomountState state; - char *path; - - ExecCommand* exec_command[_AUTOMOUNT_EXEC_MAX]; - ExecContext exec_context; - - pid_t contol_pid; Mount *mount; }; diff --git a/device.c b/device.c index 39375a425..0a7c967fa 100644 --- a/device.c +++ b/device.c @@ -42,20 +42,29 @@ static void device_done(Unit *u) { Device *d = DEVICE(u); assert(d); + free(d->sysfs); + d->sysfs = NULL; +} + +static void device_init(Unit *u) { + Device *d = DEVICE(u); + + assert(d); + assert(u->meta.load_state == UNIT_STUB); + + d->state = 0; } static void device_set_state(Device *d, DeviceState state) { DeviceState old_state; assert(d); - if (state == d->state) - return; - old_state = d->state; d->state = state; - log_debug("%s changed %s → %s", unit_id(UNIT(d)), state_string_table[old_state], state_string_table[state]); + if (state != old_state) + log_debug("%s changed %s → %s", unit_id(UNIT(d)), state_string_table[old_state], state_string_table[state]); unit_notify(UNIT(d), state_translation_table[old_state], state_translation_table[state]); } @@ -231,15 +240,16 @@ static int device_process_new_device(Manager *m, struct udev_device *dev, bool u if ((r = device_add_escaped_name(u, sysfs, true)) < 0) goto fail; + unit_add_to_load_queue(u); + } else + delete = false; + + if (!(DEVICE(u)->sysfs)) if (!(DEVICE(u)->sysfs = strdup(sysfs))) { r = -ENOMEM; goto fail; } - unit_add_to_load_queue(u); - } else - delete = false; - if (dn) if ((r = device_add_escaped_name(u, dn, true)) < 0) goto fail; @@ -460,7 +470,8 @@ fail: const UnitVTable device_vtable = { .suffix = ".device", - .init = unit_load_fragment_and_dropin_optional, + .init = device_init, + .load = unit_load_fragment_and_dropin_optional, .done = device_done, .coldplug = device_coldplug, diff --git a/load-fragment.c b/load-fragment.c index 1a6ae98e3..d1b819933 100644 --- a/load-fragment.c +++ b/load-fragment.c @@ -56,6 +56,15 @@ static int config_parse_deps( assert(lvalue); assert(rvalue); + if (UNIT_VTABLE(u)->refuse_requires && + (d == UNIT_REQUIRES || + d == UNIT_SOFT_REQUIRES || + d == UNIT_REQUISITE || + d == UNIT_SOFT_REQUISITE)) { + log_error("[%s:%u] Dependency of type %s not acceptable for this unit type.", filename, line, lvalue); + return -EBADMSG; + } + FOREACH_WORD(w, l, rvalue, state) { char *t; int r; @@ -1116,7 +1125,80 @@ static int merge_by_names(Unit **u, Set *names, const char *id) { return 0; } -static int load_from_path(Unit *u, const char *path, UnitLoadState *new_state) { +static void dump_items(FILE *f, const ConfigItem *items) { + const ConfigItem *i; + const char *prev_section = NULL; + bool not_first = false; + + struct { + ConfigParserCallback callback; + const char *rvalue; + } table[] = { + { config_parse_int, "INTEGER" }, + { config_parse_unsigned, "UNSIGNED" }, + { config_parse_size, "SIZE" }, + { config_parse_bool, "BOOLEAN" }, + { config_parse_string, "STRING" }, + { config_parse_path, "PATH" }, + { config_parse_strv, "STRING [...]" }, + { config_parse_nice, "NICE" }, + { config_parse_oom_adjust, "OOMADJUST" }, + { config_parse_io_class, "IOCLASS" }, + { config_parse_io_priority, "IOPRIORITY" }, + { config_parse_cpu_sched_policy, "CPUSCHEDPOLICY" }, + { config_parse_cpu_sched_prio, "CPUSCHEDPRIO" }, + { config_parse_cpu_affinity, "CPUAFFINITY" }, + { config_parse_mode, "MODE" }, + { config_parse_output, "OUTPUT" }, + { config_parse_input, "INPUT" }, + { config_parse_facility, "FACILITY" }, + { config_parse_level, "LEVEL" }, + { config_parse_capabilities, "CAPABILITIES" }, + { config_parse_secure_bits, "SECUREBITS" }, + { config_parse_bounding_set, "BOUNDINGSET" }, + { config_parse_timer_slack_ns, "TIMERSLACK" }, + { config_parse_limit, "LIMIT" }, + { config_parse_cgroup, "CGROUP [...]" }, + { config_parse_deps, "UNIT [...]" }, + { config_parse_names, "UNIT [...]" }, + { config_parse_exec, "PATH [ARGUMENT [...]]" }, + { config_parse_service_type, "SERVICETYPE" }, + { config_parse_service_restart, "SERVICERESTART" }, + { config_parse_sysv_priority, "SYSVPRIORITY" }, + { config_parse_kill_mode, "KILLMODE" }, + { config_parse_listen, "SOCKET [...]" }, + { config_parse_socket_bind, "SOCKETBIND" }, + { config_parse_bindtodevice, "NETWORKINTERFACE" } + }; + + assert(f); + assert(items); + + for (i = items; i->lvalue; i++) { + unsigned j; + const char *rvalue = "OTHER"; + + if (!streq_ptr(i->section, prev_section)) { + if (!not_first) + not_first = true; + else + fputc('\n', f); + + fprintf(f, "[%s]\n", i->section); + prev_section = i->section; + } + + for (j = 0; j < ELEMENTSOF(table); j++) + if (i->parse == table[j].callback) { + rvalue = table[j].rvalue; + break; + } + + fprintf(f, "%s=%s\n", i->lvalue, rvalue); + } +} + +static int load_from_path(Unit *u, const char *path) { static const char* const section_table[_UNIT_TYPE_MAX] = { [UNIT_SERVICE] = "Service", @@ -1223,7 +1305,13 @@ static int load_from_path(Unit *u, const char *path, UnitLoadState *new_state) { { "KillMode", config_parse_kill_mode, &u->socket.kill_mode, "Socket" }, EXEC_CONTEXT_CONFIG_ITEMS(u->socket.exec_context, "Socket"), - EXEC_CONTEXT_CONFIG_ITEMS(u->automount.exec_context, "Automount"), + { "What", config_parse_string, &u->mount.parameters_fragment.what, "Mount" }, + { "Where", config_parse_path, &u->mount.where, "Mount" }, + { "Options", config_parse_string, &u->mount.parameters_fragment.options, "Mount" }, + { "Type", config_parse_string, &u->mount.parameters_fragment.fstype, "Mount" }, + { "TimeoutSec", config_parse_usec, &u->mount.timeout_usec, "Mount" }, + { "KillMode", config_parse_kill_mode, &u->mount.kill_mode, "Mount" }, + EXEC_CONTEXT_CONFIG_ITEMS(u->mount.exec_context, "Mount"), { NULL, NULL, NULL, NULL } }; @@ -1238,8 +1326,14 @@ static int load_from_path(Unit *u, const char *path, UnitLoadState *new_state) { char *filename = NULL, *id = NULL; Unit *merged; + if (!u) { + /* Dirty dirty hack. */ + dump_items((FILE*) path, items); + return 0; + } + assert(u); - assert(new_state); + assert(path); sections[0] = "Meta"; sections[1] = section_table[u->meta.type]; @@ -1306,7 +1400,7 @@ static int load_from_path(Unit *u, const char *path, UnitLoadState *new_state) { goto finish; if (merged != u) { - *new_state = UNIT_MERGED; + u->meta.load_state = UNIT_MERGED; r = 0; goto finish; } @@ -1319,7 +1413,7 @@ static int load_from_path(Unit *u, const char *path, UnitLoadState *new_state) { u->meta.fragment_path = filename; filename = NULL; - *new_state = UNIT_LOADED; + u->meta.load_state = UNIT_LOADED; r = 0; finish: @@ -1335,16 +1429,14 @@ finish: return r; } -int unit_load_fragment(Unit *u, UnitLoadState *new_state) { +int unit_load_fragment(Unit *u) { int r; assert(u); - assert(new_state); - assert(*new_state == UNIT_STUB); if (u->meta.fragment_path) { - if ((r = load_from_path(u, u->meta.fragment_path, new_state)) < 0) + if ((r = load_from_path(u, u->meta.fragment_path)) < 0) return r; } else { @@ -1353,23 +1445,29 @@ int unit_load_fragment(Unit *u, UnitLoadState *new_state) { /* Try to find the unit under its id */ if ((t = unit_id(u))) - if ((r = load_from_path(u, t, new_state)) < 0) + if ((r = load_from_path(u, t)) < 0) return r; /* Try to find an alias we can load this with */ - if (*new_state == UNIT_STUB) + if (u->meta.load_state == UNIT_STUB) SET_FOREACH(t, u->meta.names, i) { if (unit_id(u) == t) continue; - if ((r = load_from_path(u, t, new_state)) < 0) + if ((r = load_from_path(u, t)) < 0) return r; - if (*new_state != UNIT_STUB) + if (u->meta.load_state != UNIT_STUB) break; } } return 0; } + +void unit_dump_config_items(FILE *f) { + /* OK, this wins a prize for extreme ugliness. */ + + load_from_path(NULL, (const void*) f); +} diff --git a/load-fragment.h b/load-fragment.h index 709859522..beba87cb7 100644 --- a/load-fragment.h +++ b/load-fragment.h @@ -26,6 +26,8 @@ /* Read service data from .desktop file style configuration fragments */ -int unit_load_fragment(Unit *u, UnitLoadState *new_state); +int unit_load_fragment(Unit *u); + +void unit_dump_config_items(FILE *f); #endif diff --git a/main.c b/main.c index 139770ede..082ac4bb1 100644 --- a/main.c +++ b/main.c @@ -38,7 +38,8 @@ static enum { ACTION_RUN, ACTION_HELP, - ACTION_TEST + ACTION_TEST, + ACTION_DUMP_CONFIGURATION_ITEMS } action = ACTION_RUN; static char *default_unit = NULL; @@ -138,7 +139,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_LOG_TARGET, ARG_DEFAULT, ARG_RUNNING_AS, - ARG_TEST + ARG_TEST, + ARG_DUMP_CONFIGURATION_ITEMS }; static const struct option options[] = { @@ -148,6 +150,7 @@ static int parse_argv(int argc, char *argv[]) { { "running-as", required_argument, NULL, ARG_RUNNING_AS }, { "test", no_argument, NULL, ARG_TEST }, { "help", no_argument, NULL, 'h' }, + { "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS }, { NULL, 0, NULL, 0 } }; @@ -202,6 +205,10 @@ static int parse_argv(int argc, char *argv[]) { action = ACTION_TEST; break; + case ARG_DUMP_CONFIGURATION_ITEMS: + action = ACTION_DUMP_CONFIGURATION_ITEMS; + break; + case 'h': action = ACTION_HELP; break; @@ -220,12 +227,13 @@ static int parse_argv(int argc, char *argv[]) { static int help(void) { printf("%s [options]\n\n" - " -h --help Show this help\n" - " --default=UNIT Set default unit\n" - " --log-level=LEVEL Set log level\n" - " --log-target=TARGET Set log target (console, syslog, kmsg)\n" - " --running-as=AS Set running as (init, system, session)\n" - " --test Determine startup sequence, dump it and exit\n", + " -h --help Show this help\n" + " --default=UNIT Set default unit\n" + " --log-level=LEVEL Set log level\n" + " --log-target=TARGET Set log target (console, syslog, kmsg)\n" + " --running-as=AS Set running as (init, system, session)\n" + " --test Determine startup sequence, dump it and exit\n" + " --dump-configuration-items Dump understood unit configuration items\n", __progname); return 0; @@ -270,12 +278,18 @@ int main(int argc, char *argv[]) { if (action == ACTION_HELP) { retval = help(); goto finish; + } else if (action == ACTION_DUMP_CONFIGURATION_ITEMS) { + unit_dump_config_items(stdout); + retval = 0; + goto finish; } assert_se(action == ACTION_RUN || action == ACTION_TEST); /* Set up PATH unless it is already set */ - setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", false); + setenv("PATH", + "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + running_as == MANAGER_INIT); /* Move out of the way, so that we won't block unmounts */ assert_se(chdir("/") == 0); @@ -296,8 +310,7 @@ int main(int argc, char *argv[]) { log_debug("systemd running in %s mode.", manager_running_as_to_string(running_as)); if (running_as == MANAGER_INIT) - if (hostname_setup() < 0) - goto finish; + hostname_setup(); if ((r = manager_new(running_as, &m)) < 0) { log_error("Failed to allocate manager object: %s", strerror(-r)); diff --git a/manager.c b/manager.c index 9e4fbbbae..0a1080871 100644 --- a/manager.c +++ b/manager.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,7 @@ #include "ratelimit.h" #include "cgroup.h" #include "mount-setup.h" +#include "utmp-wtmp.h" static int manager_setup_signals(Manager *m) { sigset_t mask; @@ -291,6 +293,8 @@ int manager_new(ManagerRunningAs running_as, Manager **_m) { if (!(m = new0(Manager, 1))) return -ENOMEM; + m->boot_timestamp = now(CLOCK_REALTIME); + m->running_as = running_as; m->signal_watch.fd = m->mount_watch.fd = m->udev_watch.fd = m->epoll_fd = -1; m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */ @@ -428,6 +432,10 @@ int manager_coldplug(Manager *m) { return r; } + /* Now that the initial devices are available, let's see if we + * can write the utmp file */ + manager_write_utmp_reboot(m); + return 0; } @@ -1704,6 +1712,72 @@ int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j) { return 0; } +static bool manager_utmp_good(Manager *m) { + int r; + + assert(m); + + if ((r = mount_path_is_mounted(m, _PATH_UTMPX)) <= 0) { + + if (r < 0) + log_warning("Failed to determine whether " _PATH_UTMPX " is mounted: %s", strerror(-r)); + + return false; + } + + return true; +} + +void manager_write_utmp_reboot(Manager *m) { + int r; + + assert(m); + + if (m->utmp_reboot_written) + return; + + if (m->running_as != MANAGER_INIT) + return; + + if (!manager_utmp_good(m)) + return; + + if ((r = utmp_put_reboot(m->boot_timestamp)) < 0) { + + if (r != -ENOENT && r != -EROFS) + log_warning("Failed to write utmp/wtmp: %s", strerror(-r)); + + return; + } + + m->utmp_reboot_written = true; +} + +void manager_write_utmp_runlevel(Manager *m, Unit *u) { + int runlevel, r; + + assert(m); + assert(u); + + if (u->meta.type != UNIT_TARGET) + return; + + if (m->running_as != MANAGER_INIT) + return; + + if (!manager_utmp_good(m)) + return; + + if ((runlevel = target_get_runlevel(TARGET(u))) <= 0) + return; + + if ((r = utmp_put_runlevel(0, runlevel, 0)) < 0) { + + if (r != -ENOENT && r != -EROFS) + log_warning("Failed to write utmp/wtmp: %s", strerror(-r)); + } +} + static const char* const manager_running_as_table[_MANAGER_RUNNING_AS_MAX] = { [MANAGER_INIT] = "init", [MANAGER_SYSTEM] = "system", diff --git a/manager.h b/manager.h index 5b72defe0..3ae8126f9 100644 --- a/manager.h +++ b/manager.h @@ -145,6 +145,8 @@ struct Manager { bool request_api_bus_dispatch:1; bool request_system_bus_dispatch:1; + bool utmp_reboot_written:1; + Hashmap *watch_pids; /* pid => Unit object n:1 */ int epoll_fd; @@ -172,6 +174,8 @@ struct Manager { Hashmap *cgroup_bondings; /* path string => CGroupBonding object 1:n */ char *cgroup_controller; char *cgroup_hierarchy; + + usec_t boot_timestamp; }; int manager_new(ManagerRunningAs running_as, Manager **m); @@ -204,4 +208,8 @@ int manager_loop(Manager *m); const char *manager_running_as_to_string(ManagerRunningAs i); ManagerRunningAs manager_running_as_from_string(const char *s); +void manager_write_utmp_reboot(Manager *m); + +void manager_write_utmp_runlevel(Manager *m, Unit *t); + #endif diff --git a/mount.c b/mount.c index 325f2d51e..d52c11d3c 100644 --- a/mount.c +++ b/mount.c @@ -23,109 +23,133 @@ #include #include #include -#include +#include #include "unit.h" #include "mount.h" #include "load-fragment.h" #include "load-dropin.h" #include "log.h" +#include "strv.h" +#include "mount-setup.h" static const UnitActiveState state_translation_table[_MOUNT_STATE_MAX] = { [MOUNT_DEAD] = UNIT_INACTIVE, [MOUNT_MOUNTING] = UNIT_ACTIVATING, + [MOUNT_MOUNTING_DONE] = UNIT_ACTIVE, [MOUNT_MOUNTED] = UNIT_ACTIVE, + [MOUNT_REMOUNTING] = UNIT_ACTIVE_RELOADING, [MOUNT_UNMOUNTING] = UNIT_DEACTIVATING, + [MOUNT_MOUNTING_SIGTERM] = UNIT_DEACTIVATING, + [MOUNT_MOUNTING_SIGKILL] = UNIT_DEACTIVATING, + [MOUNT_REMOUNTING_SIGTERM] = UNIT_ACTIVE_RELOADING, + [MOUNT_REMOUNTING_SIGKILL] = UNIT_ACTIVE_RELOADING, + [MOUNT_UNMOUNTING_SIGTERM] = UNIT_DEACTIVATING, + [MOUNT_UNMOUNTING_SIGKILL] = UNIT_DEACTIVATING, [MOUNT_MAINTAINANCE] = UNIT_INACTIVE, }; static const char* const state_string_table[_MOUNT_STATE_MAX] = { [MOUNT_DEAD] = "dead", [MOUNT_MOUNTING] = "mounting", + [MOUNT_MOUNTING_DONE] = "mounting-done", [MOUNT_MOUNTED] = "mounted", + [MOUNT_REMOUNTING] = "remounting", [MOUNT_UNMOUNTING] = "unmounting", + [MOUNT_MOUNTING_SIGTERM] = "mounting-sigterm", + [MOUNT_MOUNTING_SIGKILL] = "mounting-sigkill", + [MOUNT_REMOUNTING_SIGTERM] = "remounting-sigterm", + [MOUNT_REMOUNTING_SIGKILL] = "remounting-sigkill", + [MOUNT_UNMOUNTING_SIGTERM] = "unmounting-sigterm", + [MOUNT_UNMOUNTING_SIGKILL] = "unmounting-sigkill", [MOUNT_MAINTAINANCE] = "maintainance" }; +static void mount_parameters_done(MountParameters *p) { + assert(p); + + free(p->what); + free(p->options); + free(p->fstype); + + p->what = p->options = p->fstype = NULL; +} + static void mount_done(Unit *u) { Mount *m = MOUNT(u); assert(m); - free(m->what); - free(m->where); -} -static void mount_set_state(Mount *m, MountState state) { - MountState old_state; - assert(m); + free(m->where); + m->where = NULL; - if (state == m->state) - return; + mount_parameters_done(&m->parameters_etc_fstab); + mount_parameters_done(&m->parameters_proc_self_mountinfo); + mount_parameters_done(&m->parameters_fragment); - old_state = m->state; - m->state = state; + exec_context_done(&m->exec_context); + exec_command_done_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX); + m->control_command = NULL; - log_debug("%s changed %s → %s", unit_id(UNIT(m)), state_string_table[old_state], state_string_table[state]); + if (m->control_pid > 0) { + unit_unwatch_pid(u, m->control_pid); + m->control_pid = 0; + } - unit_notify(UNIT(m), state_translation_table[old_state], state_translation_table[state]); + unit_unwatch_timer(u, &m->timer_watch); } -static int mount_coldplug(Unit *u) { +static void mount_init(Unit *u) { Mount *m = MOUNT(u); - assert(m); - assert(m->state == MOUNT_DEAD); + assert(u); + assert(u->meta.load_state == UNIT_STUB); - if (m->from_proc_self_mountinfo) - mount_set_state(m, MOUNT_MOUNTED); + m->state = 0; + m->from_etc_fstab = false; + m->from_proc_self_mountinfo = false; + m->from_fragment = false; - return 0; -} + m->is_mounted = false; + m->just_mounted = false; + m->just_changed = false; -static void mount_dump(Unit *u, FILE *f, const char *prefix) { - Mount *s = MOUNT(u); + m->timeout_usec = DEFAULT_TIMEOUT_USEC; - assert(s); + zero(m->exec_command); + exec_context_init(&m->exec_context); - fprintf(f, - "%sMount State: %s\n" - "%sWhere: %s\n" - "%sWhat: %s\n" - "%sFrom /etc/fstab: %s\n" - "%sFrom /proc/self/mountinfo: %s\n", - prefix, state_string_table[s->state], - prefix, s->where, - prefix, s->what, - prefix, yes_no(s->from_etc_fstab), - prefix, yes_no(s->from_proc_self_mountinfo)); -} - -static UnitActiveState mount_active_state(Unit *u) { - assert(u); - - return state_translation_table[MOUNT(u)->state]; -} + m->kill_mode = 0; -static void mount_shutdown(Manager *m) { - assert(m); + m->control_pid = 0; + m->failure = false; - if (m->proc_self_mountinfo) - fclose(m->proc_self_mountinfo); + m->timer_watch.type = WATCH_INVALID; } static int mount_add_node_links(Mount *m) { Unit *device; char *e; int r; + const char *what; assert(m); /* Adds in links to the device that this node is based on */ - if (!path_startswith(m->what, "/dev/")) + if (m->parameters_fragment.what) + what = m->parameters_fragment.what; + else if (m->parameters_etc_fstab.what) + what = m->parameters_etc_fstab.what; + else + /* We observe kernel mounts only while they are live, + * hence don't create any links for them */ + return 0; + + if (!path_startswith(what, "/dev/")) return 0; - if (!(e = unit_name_escape_path(m->what+1, ".device"))) + if (!(e = unit_name_escape_path(what+1, ".device"))) return -ENOMEM; r = manager_load_unit(UNIT(m)->meta.manager, e, &device); @@ -140,8 +164,10 @@ static int mount_add_node_links(Mount *m) { if ((r = unit_add_dependency(UNIT(m), UNIT_REQUIRES, device)) < 0) return r; - if ((r = unit_add_dependency(device, UNIT_WANTS, UNIT(m))) < 0) - return r; + if (UNIT(m)->meta.manager->running_as == MANAGER_INIT || + UNIT(m)->meta.manager->running_as == MANAGER_SYSTEM) + if ((r = unit_add_dependency(device, UNIT_WANTS, UNIT(m))) < 0) + return r; return 0; } @@ -150,6 +176,8 @@ static int mount_add_path_links(Mount *m) { Meta *other; int r; + assert(m); + /* Adds in link to other mount points, that might lie below or * above us in the hierarchy */ @@ -161,36 +189,663 @@ static int mount_add_path_links(Mount *m) { if (n == m) continue; + if (m->meta.load_state != UNIT_LOADED) + continue; + if (path_startswith(m->where, n->where)) { - if ((r = unit_add_dependency(UNIT(m), UNIT_AFTER, (Unit*) other)) < 0) + if ((r = unit_add_dependency(UNIT(m), UNIT_AFTER, UNIT(other))) < 0) return r; - if ((r = unit_add_dependency(UNIT(m), UNIT_REQUIRES, (Unit*) other)) < 0) - return r; + if (n->from_etc_fstab || n->from_fragment) + if ((r = unit_add_dependency(UNIT(m), UNIT_REQUIRES, UNIT(other))) < 0) + return r; - } else if (startswith(n->where, m->where)) { + } else if (path_startswith(n->where, m->where)) { - if ((r = unit_add_dependency(UNIT(m), UNIT_BEFORE, (Unit*) other)) < 0) + if ((r = unit_add_dependency(UNIT(m), UNIT_BEFORE, UNIT(other))) < 0) return r; - if ((r = unit_add_dependency((Unit*) other, UNIT_REQUIRES, UNIT(m))) < 0) - return r; + if (m->from_etc_fstab || m->from_fragment) + if ((r = unit_add_dependency(UNIT(other), UNIT_REQUIRES, UNIT(m))) < 0) + return r; } } return 0; } -static int mount_add_one(Manager *m, const char *what, const char *where, bool live, bool set_flags) { - char *e; +static bool mount_test_option(const char *haystack, const char *needle) { + struct mntent me; + + assert(needle); + + /* Like glibc's hasmntopt(), but works on a string, not a + * struct mntent */ + + if (!haystack) + return false; + + zero(me); + me.mnt_opts = (char*) haystack; + + return !!hasmntopt(&me, needle); +} + +static int mount_add_target_links(Mount *m) { + const char *target; + MountParameters *p; + Unit *u; + int r; + + assert(m); + + if (m->from_fragment) + p = &m->parameters_fragment; + else if (m->from_etc_fstab) + p = &m->parameters_etc_fstab; + else + return 0; + + if (!p->fstype) + return 0; + + if (mount_test_option(p->options, MNTOPT_NOAUTO)) + return 0; + + if (mount_test_option(p->options, "_netdev") || + fstype_is_network(p->fstype)) + target = SPECIAL_REMOTE_FS_TARGET; + else + target = SPECIAL_LOCAL_FS_TARGET; + + if ((r = manager_load_unit(UNIT(m)->meta.manager, target, &u)) < 0) + return r; + + if ((r = unit_add_dependency(u, UNIT_WANTS, UNIT(m))) < 0) + return r; + + return unit_add_dependency(UNIT(m), UNIT_BEFORE, u); +} + +static int mount_load(Unit *u) { + Mount *m = MOUNT(u); + int r; + + assert(u); + assert(u->meta.load_state == UNIT_STUB); + + if ((r = unit_load_fragment_and_dropin_optional(u)) < 0) + return r; + + /* This is a new unit? Then let's add in some extras */ + if (u->meta.load_state == UNIT_LOADED) { + + /* Minor validity checking */ + if ((m->parameters_fragment.options || m->parameters_fragment.fstype) && !m->parameters_fragment.what) + return -EBADMSG; + + if (m->parameters_fragment.what) + m->from_fragment = true; + + if ((r = mount_add_node_links(MOUNT(u))) < 0) + return r; + + if ((r = mount_add_path_links(MOUNT(u))) < 0) + return r; + + if ((r = mount_add_target_links(MOUNT(u))) < 0) + return r; + + if ((r = unit_add_default_cgroup(u)) < 0) + return r; + } + + return 0; +} + +static void mount_set_state(Mount *m, MountState state) { + MountState old_state; + assert(m); + + old_state = m->state; + m->state = state; + + if (state != MOUNT_MOUNTING && + state != MOUNT_MOUNTING_DONE && + state != MOUNT_REMOUNTING && + state != MOUNT_UNMOUNTING && + state != MOUNT_MOUNTING_SIGTERM && + state != MOUNT_MOUNTING_SIGKILL && + state != MOUNT_UNMOUNTING_SIGTERM && + state != MOUNT_UNMOUNTING_SIGKILL && + state != MOUNT_REMOUNTING_SIGTERM && + state != MOUNT_REMOUNTING_SIGKILL) { + unit_unwatch_timer(UNIT(m), &m->timer_watch); + + if (m->control_pid > 0) { + unit_unwatch_pid(UNIT(m), m->control_pid); + m->control_pid = 0; + } + + m->control_command = NULL; + } + + if (state != old_state) + log_debug("%s changed %s → %s", unit_id(UNIT(m)), state_string_table[old_state], state_string_table[state]); + + unit_notify(UNIT(m), state_translation_table[old_state], state_translation_table[state]); +} + +static int mount_coldplug(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + assert(m->state == MOUNT_DEAD); + + if (m->from_proc_self_mountinfo) + mount_set_state(m, MOUNT_MOUNTED); + + return 0; +} + +static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { + pid_t pid; + int r; + + assert(m); + assert(c); + assert(_pid); + + if ((r = unit_watch_timer(UNIT(m), m->timeout_usec, &m->timer_watch)) < 0) + goto fail; + + if ((r = exec_spawn(c, + &m->exec_context, + NULL, 0, + true, + true, + UNIT(m)->meta.cgroup_bondings, + &pid)) < 0) + goto fail; + + if ((r = unit_watch_pid(UNIT(m), pid)) < 0) + /* FIXME: we need to do something here */ + goto fail; + + *_pid = pid; + + return 0; + +fail: + unit_unwatch_timer(UNIT(m), &m->timer_watch); + + return r; +} + +static void mount_dump(Unit *u, FILE *f, const char *prefix) { + Mount *m = MOUNT(u); + MountParameters *p; + + assert(m); + assert(f); + + if (m->from_proc_self_mountinfo) + p = &m->parameters_proc_self_mountinfo; + else if (m->from_fragment) + p = &m->parameters_fragment; + else + p = &m->parameters_etc_fstab; + + fprintf(f, + "%sMount State: %s\n" + "%sWhere: %s\n" + "%sWhat: %s\n" + "%sFile System Type: %s\n" + "%sOptions: %s\n" + "%sFrom /etc/fstab: %s\n" + "%sFrom /proc/self/mountinfo: %s\n" + "%sFrom fragment: %s\n" + "%sKillMode: %s\n", + prefix, state_string_table[m->state], + prefix, m->where, + prefix, strna(p->what), + prefix, strna(p->fstype), + prefix, strna(p->options), + prefix, yes_no(m->from_etc_fstab), + prefix, yes_no(m->from_proc_self_mountinfo), + prefix, yes_no(m->from_fragment), + prefix, kill_mode_to_string(m->kill_mode)); + + if (m->control_pid > 0) + fprintf(f, + "%sControl PID: %llu\n", + prefix, (unsigned long long) m->control_pid); + + exec_context_dump(&m->exec_context, f, prefix); +} + +static void mount_enter_dead(Mount *m, bool success) { + assert(m); + + if (!success) + m->failure = true; + + mount_set_state(m, m->failure ? MOUNT_MAINTAINANCE : MOUNT_DEAD); +} + +static void mount_enter_signal(Mount *m, MountState state, bool success) { + int r; + + assert(m); + + if (!success) + m->failure = true; + + if (m->control_pid > 0) { + int sig; + bool sent = false; + + sig = (state == MOUNT_MOUNTING_SIGTERM || + state == MOUNT_UNMOUNTING_SIGTERM || + state == MOUNT_REMOUNTING_SIGTERM) ? SIGTERM : SIGKILL; + + if (m->kill_mode == KILL_CONTROL_GROUP) { + + if ((r = cgroup_bonding_kill_list(UNIT(m)->meta.cgroup_bondings, sig)) < 0) { + if (r != -EAGAIN && r != -ESRCH) + goto fail; + } else + sent = true; + } + + if (!sent) + if (kill(m->kill_mode == KILL_PROCESS ? m->control_pid : -m->control_pid, sig) < 0 && errno != ESRCH) { + r = -errno; + goto fail; + } + } + + mount_set_state(m, state); + + if (m->control_pid <= 0) + mount_enter_dead(m, true); + + return; + +fail: + log_warning("%s failed to kill processes: %s", unit_id(UNIT(m)), strerror(-r)); + mount_enter_dead(m, false); +} + +static void mount_enter_mounted(Mount *m, bool success) { + assert(m); + + if (!success) + m->failure = true; + + mount_set_state(m, MOUNT_MOUNTED); +} + +static void mount_enter_unmounting(Mount *m, bool success) { + ExecCommand *c; + int r; + + assert(m); + + if (!success) + m->failure = true; + + m->control_command = c = m->exec_command + MOUNT_EXEC_UNMOUNT; + + if ((r = exec_command_set( + c, + "/bin/umount", + m->where, + NULL)) < 0) + goto fail; + + if ((r = mount_spawn(m, c, &m->control_pid)) < 0) + goto fail; + + mount_set_state(m, MOUNT_UNMOUNTING); + + return; + +fail: + log_warning("%s failed to run umount exectuable: %s", unit_id(UNIT(m)), strerror(-r)); + mount_enter_mounted(m, false); +} + +static void mount_enter_mounting(Mount *m, bool success) { + ExecCommand *c; + int r; + + assert(m); + + if (!success) + m->failure = true; + + m->control_command = c = m->exec_command + MOUNT_EXEC_MOUNT; + + if (m->from_fragment) + r = exec_command_set( + c, + "/bin/mount", + m->parameters_fragment.what, + m->where, + "-t", m->parameters_fragment.fstype, + "-o", m->parameters_fragment.options, + NULL); + else if (m->from_etc_fstab) + r = exec_command_set( + c, + "/bin/mount", + m->where, + NULL); + else + r = -ENOENT; + + if (r < 0) + goto fail; + + if ((r = mount_spawn(m, c, &m->control_pid)) < 0) + goto fail; + + mount_set_state(m, MOUNT_MOUNTING); + + return; + +fail: + log_warning("%s failed to run mount exectuable: %s", unit_id(UNIT(m)), strerror(-r)); + mount_enter_dead(m, false); +} + +static void mount_enter_mounting_done(Mount *m, bool success) { + assert(m); + + if (!success) + m->failure = true; + + mount_set_state(m, MOUNT_MOUNTING_DONE); +} + +static void mount_enter_remounting(Mount *m, bool success) { + ExecCommand *c; + int r; + + assert(m); + + if (!success) + m->failure = true; + + m->control_command = c = m->exec_command + MOUNT_EXEC_REMOUNT; + + if (m->from_fragment) { + char *buf = NULL; + const char *o; + + if (m->parameters_fragment.options) { + if (!(buf = strappend("remount,", m->parameters_fragment.options))) { + r = -ENOMEM; + goto fail; + } + + o = buf; + } else + o = "remount"; + + r = exec_command_set( + c, + "/bin/mount", + m->parameters_fragment.what, + m->where, + "-t", m->parameters_fragment.fstype, + "-o", o, + NULL); + + free(buf); + } else if (m->from_etc_fstab) + r = exec_command_set( + c, + "/bin/mount", + m->where, + "-o", "remount", + NULL); + else + r = -ENOENT; + + if (r < 0) { + r = -ENOMEM; + goto fail; + } + + if ((r = mount_spawn(m, c, &m->control_pid)) < 0) + goto fail; + + mount_set_state(m, MOUNT_REMOUNTING); + + return; + +fail: + mount_enter_mounted(m, false); +} + +static int mount_start(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + /* We cannot fulfill this request right now, try again later + * please! */ + if (m->state == MOUNT_UNMOUNTING || + m->state == MOUNT_UNMOUNTING_SIGTERM || + m->state == MOUNT_UNMOUNTING_SIGKILL) + return -EAGAIN; + + /* Already on it! */ + if (m->state == MOUNT_MOUNTING || + m->state == MOUNT_MOUNTING_SIGTERM || + m->state == MOUNT_MOUNTING_SIGKILL) + return 0; + + assert(m->state == MOUNT_DEAD || m->state == MOUNT_MAINTAINANCE); + + m->failure = false; + + mount_enter_mounting(m, true); + return 0; +} + +static int mount_stop(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + /* Cann't do this right now. */ + if (m->state == MOUNT_MOUNTING || + m->state == MOUNT_MOUNTING_DONE || + m->state == MOUNT_MOUNTING_SIGTERM || + m->state == MOUNT_MOUNTING_SIGKILL || + m->state == MOUNT_REMOUNTING || + m->state == MOUNT_REMOUNTING_SIGTERM || + m->state == MOUNT_REMOUNTING_SIGKILL) + return -EAGAIN; + + /* Already on it */ + if (m->state == MOUNT_UNMOUNTING || + m->state == MOUNT_UNMOUNTING_SIGKILL || + m->state == MOUNT_UNMOUNTING_SIGTERM) + return 0; + + assert(m->state == MOUNT_MOUNTED); + + mount_enter_unmounting(m, true); + return 0; +} + +static int mount_reload(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + if (m->state == MOUNT_MOUNTING_DONE) + return -EAGAIN; + + assert(m->state == MOUNT_MOUNTED); + + mount_enter_remounting(m, true); + return 0; +} + +static UnitActiveState mount_active_state(Unit *u) { + assert(u); + + return state_translation_table[MOUNT(u)->state]; +} + +static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { + Mount *m = MOUNT(u); + bool success; + + assert(m); + assert(pid >= 0); + + success = code == CLD_EXITED && status == 0; + m->failure = m->failure || !success; + + assert(m->control_pid == pid); + assert(m->control_command); + + exec_status_fill(&m->control_command->exec_status, pid, code, status); + m->control_pid = 0; + + log_debug("%s control process exited, code=%s status=%i", unit_id(u), sigchld_code_to_string(code), status); + + /* Note that mount(8) returning and the kernel sending us a + * mount table change event might happen out-of-order. If an + * operation succeed we assume the kernel will follow soon too + * and already change into the resulting state. If it fails + * we check if the kernel still knows about the mount. and + * change state accordingly. */ + + switch (m->state) { + + case MOUNT_MOUNTING: + case MOUNT_MOUNTING_DONE: + case MOUNT_MOUNTING_SIGKILL: + case MOUNT_MOUNTING_SIGTERM: + case MOUNT_REMOUNTING: + case MOUNT_REMOUNTING_SIGKILL: + case MOUNT_REMOUNTING_SIGTERM: + + if (success && m->from_proc_self_mountinfo) + mount_enter_mounted(m, true); + else if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, false); + else + mount_enter_dead(m, false); + break; + + case MOUNT_UNMOUNTING: + case MOUNT_UNMOUNTING_SIGKILL: + case MOUNT_UNMOUNTING_SIGTERM: + + if (success) + mount_enter_dead(m, true); + else if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, false); + else + mount_enter_dead(m, false); + break; + + default: + assert_not_reached("Uh, control process died at wrong time."); + } +} + +static void mount_timer_event(Unit *u, uint64_t elapsed, Watch *w) { + Mount *m = MOUNT(u); + + assert(m); + assert(elapsed == 1); + assert(w == &m->timer_watch); + + switch (m->state) { + + case MOUNT_MOUNTING: + case MOUNT_MOUNTING_DONE: + log_warning("%s mounting timed out. Stopping.", unit_id(u)); + mount_enter_signal(m, MOUNT_MOUNTING_SIGTERM, false); + break; + + case MOUNT_REMOUNTING: + log_warning("%s remounting timed out. Stopping.", unit_id(u)); + mount_enter_signal(m, MOUNT_REMOUNTING_SIGTERM, false); + break; + + case MOUNT_UNMOUNTING: + log_warning("%s unmounting timed out. Stopping.", unit_id(u)); + mount_enter_signal(m, MOUNT_UNMOUNTING_SIGTERM, false); + break; + + case MOUNT_MOUNTING_SIGTERM: + log_warning("%s mounting timed out. Killing.", unit_id(u)); + mount_enter_signal(m, MOUNT_MOUNTING_SIGKILL, false); + break; + + case MOUNT_REMOUNTING_SIGTERM: + log_warning("%s remounting timed out. Killing.", unit_id(u)); + mount_enter_signal(m, MOUNT_REMOUNTING_SIGKILL, false); + break; + + case MOUNT_UNMOUNTING_SIGTERM: + log_warning("%s unmounting timed out. Killing.", unit_id(u)); + mount_enter_signal(m, MOUNT_UNMOUNTING_SIGKILL, false); + break; + + case MOUNT_MOUNTING_SIGKILL: + case MOUNT_REMOUNTING_SIGKILL: + case MOUNT_UNMOUNTING_SIGKILL: + log_warning("%s mount process still around after SIGKILL. Ignoring.", unit_id(u)); + + if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, false); + else + mount_enter_dead(m, false); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } +} + +static int mount_add_one( + Manager *m, + const char *what, + const char *where, + const char *options, + const char *fstype, + bool from_proc_self_mountinfo, + bool set_flags) { int r; Unit *u; bool delete; + char *e, *w = NULL, *o = NULL, *f = NULL; + MountParameters *mp; assert(m); assert(what); assert(where); + assert(options); + assert(fstype); + + assert(!set_flags || from_proc_self_mountinfo); + + /* Ignore API mount points. They should never be referenced in + * dependencies ever. */ + if (mount_point_is_api(where)) + return 0; /* probably some kind of swap, which we don't cover for now */ if (where[0] != '/') @@ -218,8 +873,7 @@ static int mount_add_one(Manager *m, const char *what, const char *where, bool l if (r < 0) goto fail; - if (!(MOUNT(u)->what = strdup(what)) || - !(MOUNT(u)->where = strdup(where))) { + if (!(MOUNT(u)->where = strdup(where))) { r = -ENOMEM; goto fail; } @@ -233,30 +887,48 @@ static int mount_add_one(Manager *m, const char *what, const char *where, bool l free(e); } - if (set_flags) - MOUNT(u)->still_exists = true; + if (!(w = strdup(what)) || + !(o = strdup(options)) || + !(f = strdup(fstype))) { + r = -ENOMEM; + goto fail; + } + + if (from_proc_self_mountinfo) { + mp = &MOUNT(u)->parameters_proc_self_mountinfo; + + if (set_flags) { + MOUNT(u)->is_mounted = true; + MOUNT(u)->just_mounted = !MOUNT(u)->from_proc_self_mountinfo; + MOUNT(u)->just_changed = !streq_ptr(MOUNT(u)->parameters_proc_self_mountinfo.options, o); + } - if (live) { - if (set_flags) - MOUNT(u)->just_created = !MOUNT(u)->from_proc_self_mountinfo; MOUNT(u)->from_proc_self_mountinfo = true; + } else { - if (set_flags) - MOUNT(u)->just_created = !MOUNT(u)->from_etc_fstab; + mp = &MOUNT(u)->parameters_etc_fstab; + MOUNT(u)->from_etc_fstab = true; } - if ((r = mount_add_node_links(MOUNT(u))) < 0) - goto fail; + free(mp->what); + mp->what = w; - if ((r = mount_add_path_links(MOUNT(u))) < 0) - goto fail; + free(mp->options); + mp->options = o; + + free(mp->fstype); + mp->fstype = f; unit_add_to_dbus_queue(u); return 0; fail: + free(w); + free(o); + free(f); + if (delete && u) unit_free(u); @@ -301,7 +973,7 @@ static char *fstab_node_to_udev_node(char *p) { return strdup(p); } -static int mount_load_etc_fstab(Manager *m, bool set_flags) { +static int mount_load_etc_fstab(Manager *m) { FILE *f; int r; struct mntent* me; @@ -332,7 +1004,7 @@ static int mount_load_etc_fstab(Manager *m, bool set_flags) { if (where[0] == '/') path_kill_slashes(where); - r = mount_add_one(m, what, where, false, set_flags); + r = mount_add_one(m, what, where, me->mnt_opts, me->mnt_type, false, false); free(what); free(where); @@ -349,6 +1021,7 @@ finish: static int mount_load_proc_self_mountinfo(Manager *m, bool set_flags) { int r; + char *device, *path, *options, *fstype, *d, *p; assert(m); @@ -356,7 +1029,8 @@ static int mount_load_proc_self_mountinfo(Manager *m, bool set_flags) { for (;;) { int k; - char *device, *path, *d, *p; + + device = path = options = fstype = d = p = NULL; if ((k = fscanf(m->proc_self_mountinfo, "%*s " /* (1) mount id */ @@ -364,44 +1038,59 @@ static int mount_load_proc_self_mountinfo(Manager *m, bool set_flags) { "%*s " /* (3) major:minor */ "%*s " /* (4) root */ "%ms " /* (5) mount point */ - "%*s" /* (6) mount options */ + "%ms" /* (6) mount options */ "%*[^-]" /* (7) optional fields */ "- " /* (8) seperator */ - "%*s " /* (9) file system type */ + "%ms " /* (9) file system type */ "%ms" /* (10) mount source */ "%*[^\n]", /* some rubbish at the end */ &path, - &device)) != 2) { + &options, + &fstype, + &device)) != 4) { if (k == EOF) break; - return -EBADMSG; + r = -EBADMSG; + goto finish; } - if (!(d = cunescape(device))) { - free(device); - free(path); - return -ENOMEM; + if (!(d = cunescape(device)) || + !(p = cunescape(path))) { + r = -ENOMEM; + goto finish; } - free(device); - if (!(p = cunescape(path))) { - free(d); - free(path); - return -ENOMEM; - } - free(path); + if ((r = mount_add_one(m, d, p, options, fstype, true, set_flags)) < 0) + goto finish; - r = mount_add_one(m, d, p, true, set_flags); + free(device); + free(path); + free(options); + free(fstype); free(d); free(p); - - if (r < 0) - return r; } - return 0; + r = 0; + +finish: + free(device); + free(path); + free(options); + free(fstype); + free(d); + free(p); + + return r; +} + +static void mount_shutdown(Manager *m) { + assert(m); + + if (m->proc_self_mountinfo) + fclose(m->proc_self_mountinfo); } static int mount_enumerate(Manager *m) { @@ -422,7 +1111,7 @@ static int mount_enumerate(Manager *m) { if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->mount_watch.fd, &ev) < 0) return -errno; - if ((r = mount_load_etc_fstab(m, false)) < 0) + if ((r = mount_load_etc_fstab(m)) < 0) goto fail; if ((r = mount_load_proc_self_mountinfo(m, false)) < 0) @@ -448,6 +1137,14 @@ void mount_fd_event(Manager *m, int events) { if ((r = mount_load_proc_self_mountinfo(m, true)) < 0) { log_error("Failed to reread /proc/self/mountinfo: %s", strerror(-errno)); + + /* Reset flags, just in case, for later calls */ + LIST_FOREACH(units_per_type, meta, m->units_per_type[UNIT_MOUNT]) { + Mount *mount = (Mount*) meta; + + mount->is_mounted = mount->just_mounted = mount->just_changed = false; + } + return; } @@ -456,30 +1153,127 @@ void mount_fd_event(Manager *m, int events) { LIST_FOREACH(units_per_type, meta, m->units_per_type[UNIT_MOUNT]) { Mount *mount = (Mount*) meta; - if (mount->just_created && mount->state == MOUNT_DEAD) - mount_set_state(mount, MOUNT_MOUNTED); - else if (!mount->still_exists && mount->state == MOUNT_MOUNTED) { - mount_set_state(mount, MOUNT_DEAD); + if (!mount->is_mounted) { + /* This has just been unmounted. */ + mount->from_proc_self_mountinfo = false; + + switch (mount->state) { + + case MOUNT_MOUNTED: + mount_enter_dead(mount, true); + break; + + default: + mount_set_state(mount, mount->state); + break; + + } + + } else if (mount->just_mounted || mount->just_changed) { + + /* New or changed entrymount */ + + switch (mount->state) { + + case MOUNT_DEAD: + case MOUNT_MAINTAINANCE: + mount_enter_mounted(mount, true); + break; + + case MOUNT_MOUNTING: + mount_enter_mounting_done(mount, true); + break; + + default: + /* Nothing really changed, but let's + * issue an notification call + * nonetheless, in case somebody is + * waiting for this. (e.g. file system + * ro/rw remounts.) */ + mount_set_state(mount, mount->state); + break; + } + } + + /* Reset the flags for later calls */ + mount->is_mounted = mount->just_mounted = mount->just_changed = false; + } +} + +int mount_path_is_mounted(Manager *m, const char* path) { + char *t; + int r; + + assert(m); + assert(path); + + if (path[0] != '/') + return 1; + + if (!(t = strdup(path))) + return -ENOMEM; + + path_kill_slashes(t); + + for (;;) { + char *e, *slash; + Unit *u; + + if (!(e = unit_name_escape_path(t+1, ".mount"))) { + r = -ENOMEM; + goto finish; + } + + u = manager_get_unit(m, e); + free(e); + + if (u && + (MOUNT(u)->from_etc_fstab || MOUNT(u)->from_fragment) && + MOUNT(u)->state != MOUNT_MOUNTED) { + r = 0; + goto finish; } - /* Clear the flags for later calls */ - mount->just_created = false; - mount->still_exists = false; + assert_se(slash = strrchr(t, '/')); + + if (slash == t) { + r = 1; + goto finish; + } + + *slash = 0; } + + r = 1; + +finish: + free(t); + return r; } const UnitVTable mount_vtable = { .suffix = ".mount", - .init = unit_load_fragment_and_dropin_optional, + .no_alias = true, + + .init = mount_init, + .load = mount_load, .done = mount_done, + .coldplug = mount_coldplug, .dump = mount_dump, + .start = mount_start, + .stop = mount_stop, + .reload = mount_reload, + .active_state = mount_active_state, + .sigchld_event = mount_sigchld_event, + .timer_event = mount_timer_event, + .enumerate = mount_enumerate, .shutdown = mount_shutdown }; diff --git a/mount.h b/mount.h index 60585defc..66992e10e 100644 --- a/mount.h +++ b/mount.h @@ -28,31 +28,76 @@ typedef struct Mount Mount; typedef enum MountState { MOUNT_DEAD, - MOUNT_MOUNTING, + MOUNT_MOUNTING, /* /bin/mount is running, but the mount is not done yet. */ + MOUNT_MOUNTING_DONE, /* /bin/mount is running, and the mount is done. */ MOUNT_MOUNTED, + MOUNT_REMOUNTING, MOUNT_UNMOUNTING, + MOUNT_MOUNTING_SIGTERM, + MOUNT_MOUNTING_SIGKILL, + MOUNT_REMOUNTING_SIGTERM, + MOUNT_REMOUNTING_SIGKILL, + MOUNT_UNMOUNTING_SIGTERM, + MOUNT_UNMOUNTING_SIGKILL, MOUNT_MAINTAINANCE, - _MOUNT_STATE_MAX + _MOUNT_STATE_MAX, + _MOUNT_STATE_INVALID = -1 } MountState; +typedef enum MountExecCommand { + MOUNT_EXEC_MOUNT, + MOUNT_EXEC_UNMOUNT, + MOUNT_EXEC_REMOUNT, + _MOUNT_EXEC_COMMAND_MAX, + _MOUNT_EXEC_COMMAND_INVALID = -1 +} MountExecCommand; + +typedef struct MountParameters { + char *what; + char *options; + char *fstype; +} MountParameters; + struct Mount { Meta meta; - MountState state; + char *where; - char *what, *where; + MountParameters parameters_etc_fstab; + MountParameters parameters_proc_self_mountinfo; + MountParameters parameters_fragment; bool from_etc_fstab:1; bool from_proc_self_mountinfo:1; + bool from_fragment:1; /* Used while looking for mount points that vanished or got * added from/to /proc/self/mountinfo */ - bool still_exists:1; - bool just_created:1; + bool is_mounted:1; + bool just_mounted:1; + bool just_changed:1; + + bool failure:1; + + usec_t timeout_usec; + + ExecCommand exec_command[_MOUNT_EXEC_COMMAND_MAX]; + ExecContext exec_context; + + MountState state; + + KillMode kill_mode; + + ExecCommand* control_command; + pid_t control_pid; + + Watch timer_watch; }; extern const UnitVTable mount_vtable; void mount_fd_event(Manager *m, int events); +int mount_path_is_mounted(Manager *m, const char* path); + #endif diff --git a/service.c b/service.c index e5a9658bc..73e733bd8 100644 --- a/service.c +++ b/service.c @@ -78,7 +78,7 @@ static void service_done(Unit *u) { s->sysv_runlevels = NULL; exec_context_done(&s->exec_context); - exec_command_free_array(s->exec_command, _SERVICE_EXEC_MAX); + exec_command_free_array(s->exec_command, _SERVICE_EXEC_COMMAND_MAX); s->control_command = NULL; /* This will leak a process, but at least no memory or any of @@ -280,7 +280,7 @@ static int priority_from_rcd(Service *s, const char *init_script) { return 0; } -static int service_load_sysv_path(Service *s, const char *path, UnitLoadState *new_state) { +static int service_load_sysv_path(Service *s, const char *path) { FILE *f; Unit *u; unsigned line = 0; @@ -294,7 +294,6 @@ static int service_load_sysv_path(Service *s, const char *path, UnitLoadState *n assert(s); assert(path); - assert(new_state); u = UNIT(s); @@ -607,7 +606,7 @@ static int service_load_sysv_path(Service *s, const char *path, UnitLoadState *n goto finish; } - *new_state = UNIT_LOADED; + u->meta.load_state = UNIT_LOADED; r = 0; finish: @@ -618,7 +617,7 @@ finish: return r; } -static int service_load_sysv_name(Service *s, const char *name, UnitLoadState *new_state) { +static int service_load_sysv_name(Service *s, const char *name) { char **p; assert(s); @@ -634,26 +633,25 @@ static int service_load_sysv_name(Service *s, const char *name, UnitLoadState *n assert(endswith(path, ".service")); path[strlen(path)-8] = 0; - r = service_load_sysv_path(s, path, new_state); + r = service_load_sysv_path(s, path); free(path); if (r < 0) return r; - if (*new_state != UNIT_STUB) + if ((UNIT(s)->meta.load_state != UNIT_STUB)) break; } return 0; } -static int service_load_sysv(Service *s, UnitLoadState *new_state) { +static int service_load_sysv(Service *s) { const char *t; Iterator i; int r; assert(s); - assert(new_state); /* Load service data from SysV init scripts, preferably with * LSB headers ... */ @@ -662,31 +660,29 @@ static int service_load_sysv(Service *s, UnitLoadState *new_state) { return 0; if ((t = unit_id(UNIT(s)))) - if ((r = service_load_sysv_name(s, t, new_state)) < 0) + if ((r = service_load_sysv_name(s, t)) < 0) return r; - if (*new_state == UNIT_STUB) + if (UNIT(s)->meta.load_state == UNIT_STUB) SET_FOREACH(t, UNIT(s)->meta.names, i) { - if ((r == service_load_sysv_name(s, t, new_state)) < 0) + if (t == unit_id(UNIT(s))) + continue; + + if ((r == service_load_sysv_name(s, t)) < 0) return r; - if (*new_state != UNIT_STUB) + if (UNIT(s)->meta.load_state != UNIT_STUB) break; } return 0; } -static int service_init(Unit *u, UnitLoadState *new_state) { - int r; +static void service_init(Unit *u) { Service *s = SERVICE(u); - assert(s); - assert(new_state); - assert(*new_state == UNIT_STUB); - - /* First, reset everything to the defaults, in case this is a - * reload */ + assert(u); + assert(u->meta.load_state == UNIT_STUB); s->type = 0; s->restart = 0; @@ -711,18 +707,25 @@ static int service_init(Unit *u, UnitLoadState *new_state) { s->failure = false; RATELIMIT_INIT(s->ratelimit, 10*USEC_PER_SEC, 5); +} + +static int service_load(Unit *u) { + int r; + Service *s = SERVICE(u); + + assert(s); /* Load a .service file */ - if ((r = unit_load_fragment(u, new_state)) < 0) + if ((r = unit_load_fragment(u)) < 0) return r; /* Load a classic init script as a fallback, if we couldn't find anything */ - if (*new_state == UNIT_STUB) - if ((r = service_load_sysv(s, new_state)) < 0) + if (u->meta.load_state == UNIT_STUB) + if ((r = service_load_sysv(s)) < 0) return r; /* Still nothing found? Then let's give up */ - if (*new_state == UNIT_STUB) + if (u->meta.load_state == UNIT_STUB) return -ENOENT; /* We were able to load something, then let's add in the @@ -731,7 +734,7 @@ static int service_init(Unit *u, UnitLoadState *new_state) { return r; /* This is a new unit? Then let's add in some extras */ - if (*new_state == UNIT_LOADED) { + if (u->meta.load_state == UNIT_LOADED) { if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0) return r; @@ -788,7 +791,7 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { exec_context_dump(&s->exec_context, f, prefix); - for (c = 0; c < _SERVICE_EXEC_MAX; c++) { + for (c = 0; c < _SERVICE_EXEC_COMMAND_MAX; c++) { if (!s->exec_command[c]) continue; @@ -904,7 +907,7 @@ fail: } -static int service_notify_sockets(Service *s) { +static int service_notify_sockets_dead(Service *s) { Iterator i; Set *set; Socket *sock; @@ -966,19 +969,14 @@ static void service_set_state(Service *s, ServiceState state) { state != SERVICE_STOP_SIGKILL && state != SERVICE_STOP_POST && state != SERVICE_FINAL_SIGTERM && - state != SERVICE_FINAL_SIGKILL) + state != SERVICE_FINAL_SIGKILL) { if (s->control_pid > 0) { unit_unwatch_pid(UNIT(s), s->control_pid); s->control_pid = 0; } - if (state != SERVICE_START_PRE && - state != SERVICE_START && - state != SERVICE_START_POST && - state != SERVICE_RELOAD && - state != SERVICE_STOP && - state != SERVICE_STOP_POST) s->control_command = NULL; + } if (state == SERVICE_DEAD || state == SERVICE_STOP || @@ -989,12 +987,10 @@ static void service_set_state(Service *s, ServiceState state) { state == SERVICE_FINAL_SIGKILL || state == SERVICE_MAINTAINANCE || state == SERVICE_AUTO_RESTART) - service_notify_sockets(s); + service_notify_sockets_dead(s); - if (old_state == state) - return; - - log_debug("%s changed %s → %s", unit_id(UNIT(s)), service_state_to_string(old_state), service_state_to_string(state)); + if (old_state != state) + log_debug("%s changed %s → %s", unit_id(UNIT(s)), service_state_to_string(old_state), service_state_to_string(state)); unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state]); } @@ -1489,12 +1485,22 @@ static int service_stop(Unit *u) { assert(s); + /* Cannot do this now */ if (s->state == SERVICE_START_PRE || s->state == SERVICE_START || s->state == SERVICE_START_POST || s->state == SERVICE_RELOAD) return -EAGAIN; + /* Already on it */ + if (s->state == SERVICE_STOP || + s->state == SERVICE_STOP_SIGTERM || + s->state == SERVICE_STOP_SIGKILL || + s->state == SERVICE_STOP_POST || + s->state == SERVICE_FINAL_SIGTERM || + s->state == SERVICE_FINAL_SIGKILL) + return 0; + if (s->state == SERVICE_AUTO_RESTART) { service_set_state(s, SERVICE_DEAD); return 0; @@ -1966,7 +1972,7 @@ static const char* const service_type_table[_SERVICE_TYPE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType); -static const char* const service_exec_command_table[_SERVICE_EXEC_MAX] = { +static const char* const service_exec_command_table[_SERVICE_EXEC_COMMAND_MAX] = { [SERVICE_EXEC_START_PRE] = "ExecStartPre", [SERVICE_EXEC_START] = "ExecStart", [SERVICE_EXEC_START_POST] = "ExecStartPost", @@ -1981,6 +1987,7 @@ const UnitVTable service_vtable = { .suffix = ".service", .init = service_init, + .load = service_load, .done = service_done, .dump = service_dump, diff --git a/service.h b/service.h index fa81e98a7..580cf1ea0 100644 --- a/service.h +++ b/service.h @@ -69,8 +69,8 @@ typedef enum ServiceExecCommand { SERVICE_EXEC_RELOAD, SERVICE_EXEC_STOP, SERVICE_EXEC_STOP_POST, - _SERVICE_EXEC_MAX, - _SERVICE_EXEC_INVALID = -1 + _SERVICE_EXEC_COMMAND_MAX, + _SERVICE_EXEC_COMMAND_INVALID = -1 } ServiceExecCommand; struct Service { @@ -85,7 +85,7 @@ struct Service { usec_t restart_usec; usec_t timeout_usec; - ExecCommand* exec_command[_SERVICE_EXEC_MAX]; + ExecCommand* exec_command[_SERVICE_EXEC_COMMAND_MAX]; ExecContext exec_context; bool permissions_start_only; @@ -102,16 +102,16 @@ struct Service { pid_t main_pid, control_pid; bool main_pid_known:1; - bool sysv_has_lsb:1; - bool failure:1; /* if we shut down, remember why */ - Watch timer_watch; + bool sysv_has_lsb:1; char *sysv_path; int sysv_start_priority; char *sysv_runlevels; RateLimit ratelimit; + + Watch timer_watch; }; extern const UnitVTable service_vtable; diff --git a/socket.c b/socket.c index 51bfc9a88..00994daa2 100644 --- a/socket.c +++ b/socket.c @@ -79,7 +79,7 @@ static void socket_done(Unit *u) { } exec_context_done(&s->exec_context); - exec_command_free_array(s->exec_command, _SOCKET_EXEC_MAX); + exec_command_free_array(s->exec_command, _SOCKET_EXEC_COMMAND_MAX); s->control_command = NULL; if (s->control_pid > 0) { @@ -90,17 +90,16 @@ static void socket_done(Unit *u) { s->service = NULL; free(s->bind_to_device); + s->bind_to_device = NULL; unit_unwatch_timer(u, &s->timer_watch); } -static int socket_init(Unit *u, UnitLoadState *new_state) { +static void socket_init(Unit *u) { Socket *s = SOCKET(u); - char *t; - int r; - /* First, reset everything to the defaults, in case this is a - * reload */ + assert(u); + assert(u->meta.load_state == UNIT_STUB); s->state = 0; s->timer_watch.type = WATCH_INVALID; @@ -112,27 +111,24 @@ static int socket_init(Unit *u, UnitLoadState *new_state) { s->kill_mode = 0; s->failure = false; s->control_pid = 0; + s->service = NULL; exec_context_init(&s->exec_context); +} - if ((r = unit_load_fragment(u, new_state)) < 0) - return r; +static int socket_load(Unit *u) { + Socket *s = SOCKET(u); + int r; - if (*new_state == UNIT_STUB) - return -ENOENT; + assert(u); + assert(u->meta.load_state == UNIT_STUB); - if ((r = unit_load_dropin(unit_follow_merge(u))) < 0) + if ((r = unit_load_fragment_and_dropin(u)) < 0) return r; /* This is a new unit? Then let's add in some extras */ - if (*new_state == UNIT_LOADED) { - - if (!(t = unit_name_change_suffix(unit_id(u), ".service"))) - return -ENOMEM; + if (u->meta.load_state == UNIT_LOADED) { - r = manager_load_unit(u->meta.manager, t, (Unit**) &s->service); - free(t); - - if (r < 0) + if ((r = unit_load_related_unit(u, ".service", (Unit**) &s->service))) return r; if ((r = unit_add_dependency(u, UNIT_BEFORE, UNIT(s->service))) < 0) @@ -163,7 +159,7 @@ static const char* listen_lookup(int type) { static void socket_dump(Unit *u, FILE *f, const char *prefix) { - static const char* const command_table[_SOCKET_EXEC_MAX] = { + static const char* const command_table[_SOCKET_EXEC_COMMAND_MAX] = { [SOCKET_EXEC_START_PRE] = "StartPre", [SOCKET_EXEC_START_POST] = "StartPost", [SOCKET_EXEC_STOP_PRE] = "StopPre", @@ -226,7 +222,7 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) { exec_context_dump(&s->exec_context, f, prefix); - for (c = 0; c < _SOCKET_EXEC_MAX; c++) { + for (c = 0; c < _SOCKET_EXEC_COMMAND_MAX; c++) { if (!s->exec_command[c]) continue; @@ -363,27 +359,16 @@ static void socket_set_state(Socket *s, SocketState state) { state != SOCKET_STOP_PRE_SIGKILL && state != SOCKET_STOP_POST && state != SOCKET_STOP_POST_SIGTERM && - state != SOCKET_STOP_POST_SIGKILL) + state != SOCKET_STOP_POST_SIGKILL) { unit_unwatch_timer(UNIT(s), &s->timer_watch); - if (state != SOCKET_START_PRE && - state != SOCKET_START_POST && - state != SOCKET_STOP_PRE && - state != SOCKET_STOP_PRE_SIGTERM && - state != SOCKET_STOP_PRE_SIGKILL && - state != SOCKET_STOP_POST && - state != SOCKET_STOP_POST_SIGTERM && - state != SOCKET_STOP_POST_SIGKILL) if (s->control_pid > 0) { unit_unwatch_pid(UNIT(s), s->control_pid); s->control_pid = 0; } - if (state != SOCKET_START_PRE && - state != SOCKET_START_POST && - state != SOCKET_STOP_PRE && - state != SOCKET_STOP_POST) s->control_command = NULL; + } if (state != SOCKET_START_POST && state != SOCKET_LISTENING && @@ -396,15 +381,13 @@ static void socket_set_state(Socket *s, SocketState state) { if (state != SOCKET_LISTENING) socket_unwatch_fds(s); - if (state == old_state) - return; - - log_debug("%s changed %s → %s", unit_id(UNIT(s)), state_string_table[old_state], state_string_table[state]); + if (state != old_state) + log_debug("%s changed %s → %s", unit_id(UNIT(s)), state_string_table[old_state], state_string_table[state]); unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state]); } -static int socket_spawn(Socket *s, ExecCommand *c, bool timeout, pid_t *_pid) { +static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { pid_t pid; int r; @@ -412,11 +395,8 @@ static int socket_spawn(Socket *s, ExecCommand *c, bool timeout, pid_t *_pid) { assert(c); assert(_pid); - if (timeout) { - if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) - goto fail; - } else - unit_unwatch_timer(UNIT(s), &s->timer_watch); + if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) + goto fail; if ((r = exec_spawn(c, &s->exec_context, @@ -436,8 +416,7 @@ static int socket_spawn(Socket *s, ExecCommand *c, bool timeout, pid_t *_pid) { return 0; fail: - if (timeout) - unit_unwatch_timer(UNIT(s), &s->timer_watch); + unit_unwatch_timer(UNIT(s), &s->timer_watch); return r; } @@ -459,7 +438,7 @@ static void socket_enter_stop_post(Socket *s, bool success) { s->failure = true; if ((s->control_command = s->exec_command[SOCKET_EXEC_STOP_POST])) - if ((r = socket_spawn(s, s->control_command, true, &s->control_pid)) < 0) + if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) goto fail; socket_set_state(s, SOCKET_STOP_POST); @@ -502,7 +481,6 @@ static void socket_enter_signal(Socket *s, SocketState state, bool success) { r = -errno; goto fail; } - } socket_set_state(s, state); @@ -529,7 +507,7 @@ static void socket_enter_stop_pre(Socket *s, bool success) { s->failure = true; if ((s->control_command = s->exec_command[SOCKET_EXEC_STOP_PRE])) - if ((r = socket_spawn(s, s->control_command, true, &s->control_pid)) < 0) + if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) goto fail; socket_set_state(s, SOCKET_STOP_PRE); @@ -570,7 +548,7 @@ static void socket_enter_start_post(Socket *s) { } if ((s->control_command = s->exec_command[SOCKET_EXEC_START_POST])) - if ((r = socket_spawn(s, s->control_command, true, &s->control_pid)) < 0) { + if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) { log_warning("%s failed to run start-post executable: %s", unit_id(UNIT(s)), strerror(-r)); goto fail; } @@ -591,7 +569,7 @@ static void socket_enter_start_pre(Socket *s) { assert(s); if ((s->control_command = s->exec_command[SOCKET_EXEC_START_PRE])) - if ((r = socket_spawn(s, s->control_command, true, &s->control_pid)) < 0) + if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) goto fail; socket_set_state(s, SOCKET_START_PRE); @@ -634,7 +612,7 @@ static void socket_run_next(Socket *s, bool success) { s->control_command = s->control_command->command_next; - if ((r = socket_spawn(s, s->control_command, true, &s->control_pid)) < 0) + if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) goto fail; return; @@ -671,6 +649,13 @@ static int socket_start(Unit *u) { if (s->service->meta.load_state != UNIT_LOADED) return -ENOENT; + /* If the service is alredy actvie we cannot start the + * socket */ + if (s->service->state != SERVICE_DEAD && + s->service->state != SERVICE_MAINTAINANCE && + s->service->state != SERVICE_AUTO_RESTART) + return -EBUSY; + assert(s->state == SOCKET_DEAD || s->state == SOCKET_MAINTAINANCE); s->failure = false; @@ -689,6 +674,15 @@ static int socket_stop(Unit *u) { s->state == SOCKET_START_POST) return -EAGAIN; + /* Already on it */ + if (s->state == SOCKET_STOP_PRE || + s->state == SOCKET_STOP_PRE_SIGTERM || + s->state == SOCKET_STOP_PRE_SIGKILL || + s->state == SOCKET_STOP_POST || + s->state == SOCKET_STOP_POST_SIGTERM || + s->state == SOCKET_STOP_POST_SIGTERM) + return 0; + assert(s->state == SOCKET_LISTENING || s->state == SOCKET_RUNNING); socket_enter_stop_pre(s, true); @@ -781,14 +775,13 @@ static void socket_timer_event(Unit *u, uint64_t elapsed, Watch *w) { assert(s); assert(elapsed == 1); - assert(w == &s->timer_watch); switch (s->state) { case SOCKET_START_PRE: case SOCKET_START_POST: - log_warning("%s operation timed out. Stopping.", unit_id(u)); + log_warning("%s starting timed out. Stopping.", unit_id(u)); socket_enter_stop_pre(s, false); break; @@ -874,6 +867,7 @@ const UnitVTable socket_vtable = { .suffix = ".socket", .init = socket_init, + .load = socket_load, .done = socket_done, .dump = socket_dump, diff --git a/socket.h b/socket.h index f821186ec..4e053c0a4 100644 --- a/socket.h +++ b/socket.h @@ -41,7 +41,8 @@ typedef enum SocketState { SOCKET_STOP_POST_SIGTERM, SOCKET_STOP_POST_SIGKILL, SOCKET_MAINTAINANCE, - _SOCKET_STATE_MAX + _SOCKET_STATE_MAX, + _SOCKET_STATE_INVALID = -1 } SocketState; typedef enum SocketExecCommand { @@ -49,12 +50,15 @@ typedef enum SocketExecCommand { SOCKET_EXEC_START_POST, SOCKET_EXEC_STOP_PRE, SOCKET_EXEC_STOP_POST, - _SOCKET_EXEC_MAX + _SOCKET_EXEC_COMMAND_MAX, + _SOCKET_EXEC_COMMAND_INVALID = -1 } SocketExecCommand; typedef enum SocketType { SOCKET_SOCKET, - SOCKET_FIFO + SOCKET_FIFO, + _SOCKET_FIFO_MAX, + _SOCKET_FIFO_INVALID = -1 } SocketType; typedef struct SocketPort SocketPort; @@ -82,7 +86,7 @@ struct Socket { usec_t timeout_usec; - ExecCommand* exec_command[_SOCKET_EXEC_MAX]; + ExecCommand* exec_command[_SOCKET_EXEC_COMMAND_MAX]; ExecContext exec_context; Service *service; diff --git a/target.c b/target.c index f34ba0c36..5c83dbaaf 100644 --- a/target.c +++ b/target.c @@ -37,6 +37,15 @@ static const char* const state_string_table[_TARGET_STATE_MAX] = { [TARGET_ACTIVE] = "active" }; +static void target_init(Unit *u) { + Target *t = TARGET(u); + + assert(t); + assert(u->meta.load_state == UNIT_STUB); + + t->state = 0; +} + static void target_dump(Unit *u, FILE *f, const char *prefix) { Target *t = TARGET(u); @@ -52,13 +61,11 @@ static void target_set_state(Target *t, TargetState state) { TargetState old_state; assert(t); - if (state == t->state) - return; - old_state = t->state; t->state = state; - log_debug("%s changed %s → %s", unit_id(UNIT(t)), state_string_table[old_state], state_string_table[state]); + if (state != old_state) + log_debug("%s changed %s → %s", unit_id(UNIT(t)), state_string_table[old_state], state_string_table[state]); unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state]); } @@ -121,7 +128,8 @@ int target_get_runlevel(Target *t) { const UnitVTable target_vtable = { .suffix = ".target", - .init = unit_load_fragment_and_dropin, + .init = target_init, + .load = unit_load_fragment_and_dropin, .dump = target_dump, diff --git a/timer.c b/timer.c index 89a8ab073..41aeb7f3a 100644 --- a/timer.c +++ b/timer.c @@ -44,7 +44,7 @@ static UnitActiveState timer_active_state(Unit *u) { const UnitVTable timer_vtable = { .suffix = ".timer", - .init = unit_load_fragment_and_dropin, + .load = unit_load_fragment_and_dropin, .done = timer_done, .active_state = timer_active_state diff --git a/unit.c b/unit.c index 6d6f56416..d1416e03e 100644 --- a/unit.c +++ b/unit.c @@ -155,6 +155,11 @@ int unit_add_name(Unit *u, const char *text) { if (u->meta.type != _UNIT_TYPE_INVALID && t != u->meta.type) return -EINVAL; + if (u->meta.type != _UNIT_TYPE_INVALID && + UNIT_VTABLE(u)->no_alias && + !set_isempty(u->meta.names)) + return -EEXIST; + if (!(s = strdup(text))) return -ENOMEM; @@ -173,10 +178,14 @@ int unit_add_name(Unit *u, const char *text) { return r; } - if (u->meta.type == _UNIT_TYPE_INVALID) + if (u->meta.type == _UNIT_TYPE_INVALID) { LIST_PREPEND(Meta, units_per_type, u->meta.manager->units_per_type[t], &u->meta); - u->meta.type = t; + u->meta.type = t; + + if (UNIT_VTABLE(u)->init) + UNIT_VTABLE(u)->init(u); + } if (!u->meta.id) u->meta.id = s; @@ -278,9 +287,6 @@ void unit_free(Unit *u) { bus_unit_send_removed_signal(u); /* Detach from next 'bigger' objects */ - - cgroup_bonding_free_list(u->meta.cgroup_bondings); - SET_FOREACH(t, u->meta.names, i) hashmap_remove_value(u->meta.manager->units, t, u); @@ -296,13 +302,15 @@ void unit_free(Unit *u) { if (u->meta.in_cleanup_queue) LIST_REMOVE(Meta, cleanup_queue, u->meta.manager->cleanup_queue, &u->meta); + /* Free data and next 'smaller' objects */ + if (u->meta.job) + job_free(u->meta.job); + if (u->meta.load_state != UNIT_STUB) if (UNIT_VTABLE(u)->done) UNIT_VTABLE(u)->done(u); - /* Free data and next 'smaller' objects */ - if (u->meta.job) - job_free(u->meta.job); + cgroup_bonding_free_list(u->meta.cgroup_bondings); for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) bidi_set_free(u, u->meta.dependencies[d]); @@ -401,9 +409,6 @@ int unit_merge(Unit *u, Unit *other) { if (other == u) return 0; - /* This merges 'other' into 'unit'. FIXME: This does not - * rollback on failure. */ - if (u->meta.type != u->meta.type) return -EINVAL; @@ -569,18 +574,16 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { } /* Common implementation for multiple backends */ -int unit_load_fragment_and_dropin(Unit *u, UnitLoadState *new_state) { +int unit_load_fragment_and_dropin(Unit *u) { int r; assert(u); - assert(new_state); - assert(*new_state == UNIT_STUB || *new_state == UNIT_LOADED); /* Load a .service file */ - if ((r = unit_load_fragment(u, new_state)) < 0) + if ((r = unit_load_fragment(u)) < 0) return r; - if (*new_state == UNIT_STUB) + if (u->meta.load_state == UNIT_STUB) return -ENOENT; /* Load drop-in directory data */ @@ -591,22 +594,20 @@ int unit_load_fragment_and_dropin(Unit *u, UnitLoadState *new_state) { } /* Common implementation for multiple backends */ -int unit_load_fragment_and_dropin_optional(Unit *u, UnitLoadState *new_state) { +int unit_load_fragment_and_dropin_optional(Unit *u) { int r; assert(u); - assert(new_state); - assert(*new_state == UNIT_STUB || *new_state == UNIT_LOADED); /* Same as unit_load_fragment_and_dropin(), but whether * something can be loaded or not doesn't matter. */ /* Load a .service file */ - if ((r = unit_load_fragment(u, new_state)) < 0) + if ((r = unit_load_fragment(u)) < 0) return r; - if (*new_state == UNIT_STUB) - *new_state = UNIT_LOADED; + if (u->meta.load_state == UNIT_STUB) + u->meta.load_state = UNIT_LOADED; /* Load drop-in directory data */ if ((r = unit_load_dropin(unit_follow_merge(u))) < 0) @@ -617,7 +618,6 @@ int unit_load_fragment_and_dropin_optional(Unit *u, UnitLoadState *new_state) { int unit_load(Unit *u) { int r; - UnitLoadState res; assert(u); @@ -626,21 +626,21 @@ int unit_load(Unit *u) { u->meta.in_load_queue = false; } + if (u->meta.type == _UNIT_TYPE_INVALID) + return -EINVAL; + if (u->meta.load_state != UNIT_STUB) return 0; - if (UNIT_VTABLE(u)->init) { - res = UNIT_STUB; - if ((r = UNIT_VTABLE(u)->init(u, &res)) < 0) + if (UNIT_VTABLE(u)->load) + if ((r = UNIT_VTABLE(u)->load(u)) < 0) goto fail; - } - if (res == UNIT_STUB) { + if (u->meta.load_state == UNIT_STUB) { r = -ENOENT; goto fail; } - u->meta.load_state = res; assert((u->meta.load_state != UNIT_MERGED) == !u->meta.merged_into); unit_add_to_dbus_queue(unit_follow_merge(u)); @@ -711,9 +711,6 @@ int unit_stop(Unit *u) { if (!UNIT_VTABLE(u)->stop) return -EBADR; - if (state == UNIT_DEACTIVATING) - return 0; - unit_add_to_dbus_queue(u); return UNIT_VTABLE(u)->stop(u); } @@ -940,25 +937,38 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) { retroactively_stop_dependencies(u); } - if (!UNIT_IS_ACTIVE_OR_RELOADING(os) && UNIT_IS_ACTIVE_OR_RELOADING(ns)) { - + /* Some names are special */ + if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) { if (unit_has_name(u, SPECIAL_DBUS_SERVICE)) { - log_info("D-Bus became available, trying to reconnect."); - /* The bus just got started, hence try to connect to it. */ + /* The bus just might have become available, + * hence try to connect to it, if we aren't + * yet connected. */ bus_init_system(u->meta.manager); bus_init_api(u->meta.manager); } - if (unit_has_name(u, SPECIAL_SYSLOG_SERVICE)) { - /* The syslog daemon just got started, hence try to connect to it. */ - log_info("Syslog became available, trying to reconnect."); + if (unit_has_name(u, SPECIAL_SYSLOG_SERVICE)) + /* The syslog daemon just might have become + * available, hence try to connect to it, if + * we aren't yet connected. */ log_open_syslog(); - } - } else if (UNIT_IS_ACTIVE_OR_RELOADING(os) && !UNIT_IS_ACTIVE_OR_RELOADING(ns)) { + if (u->meta.type == UNIT_MOUNT) + /* Another directory became available, let's + * check if that is enough to write our utmp + * entry. */ + manager_write_utmp_reboot(u->meta.manager); + + if (u->meta.type == UNIT_TARGET) + /* A target got activated, maybe this is a runlevel? */ + manager_write_utmp_runlevel(u->meta.manager, u); + + } else if (!UNIT_IS_ACTIVE_OR_RELOADING(ns)) { if (unit_has_name(u, SPECIAL_SYSLOG_SERVICE)) - /* The syslog daemon just got terminated, hence try to disconnect from it. */ + /* The syslog daemon might just have + * terminated, hence try to disconnect from + * it. */ log_close_syslog(); /* We don't care about D-Bus here, since we'll get an diff --git a/unit.h b/unit.h index bfd0907fc..4b732c0f0 100644 --- a/unit.h +++ b/unit.h @@ -196,8 +196,31 @@ union Unit { struct UnitVTable { const char *suffix; - int (*init)(Unit *u, UnitLoadState *new_state); + /* Can units of this type have multiple names? */ + bool no_alias:1; + + /* If true units of this types can never have "Requires" + * dependencies, because state changes can only be observed, + * not triggered */ + bool refuse_requires:1; + + /* This should reset all type-specific variables. This should + * not allocate memory, and is either called with 0 + * initialized data, or with data left from done() */ + void (*init)(Unit *u); + + /* Actually load data from disk. This may fail, and should set + * load_state to UNIT_LOADED, UNIT_MERGED or leave it at + * UNIT_STUB if no configuration could be found. */ + int (*load)(Unit *u); + + /* This should free all type-specific variables. It should be + * idempotent. There's no need to reset variables that deal + * with dynamic memory/resources. */ void (*done)(Unit *u); + + /* If a a lot of units got created via enumerate(), this is + * where to actually set the state and call unit_notify(). */ int (*coldplug)(Unit *u); void (*dump)(Unit *u, FILE *f, const char *prefix); @@ -285,8 +308,8 @@ int unit_merge_by_name(Unit *u, const char *other); Unit *unit_follow_merge(Unit *u); -int unit_load_fragment_and_dropin(Unit *u, UnitLoadState *new_state); -int unit_load_fragment_and_dropin_optional(Unit *u, UnitLoadState *new_state); +int unit_load_fragment_and_dropin(Unit *u); +int unit_load_fragment_and_dropin_optional(Unit *u); int unit_load(Unit *unit); const char* unit_id(Unit *u); diff --git a/utmp-wtmp.c b/utmp-wtmp.c new file mode 100644 index 000000000..1650d10eb --- /dev/null +++ b/utmp-wtmp.c @@ -0,0 +1,210 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "macro.h" +#include "utmp-wtmp.h" + +int utmp_get_runlevel(int *runlevel, int *previous) { + struct utmpx lookup, *found; + int r; + const char *e; + + assert(runlevel); + + /* If these values are set in the environment this takes + * precedence. Presumably, sysvinit does this to work around a + * race condition that would otherwise exist where we'd always + * go to disk and hence might read runlevel data that might be + * very new and does not apply to the current script being + * executed. */ + + if ((e = getenv("RUNLEVEL")) && e[0] > 0) { + *runlevel = e[0]; + + if (previous) { + /* $PREVLEVEL seems to be an Upstart thing */ + + if ((e = getenv("PREVLEVEL")) && e[0] > 0) + *previous = e[0]; + else + *previous = 0; + } + + return 0; + } + + if (utmpxname(_PATH_UTMPX) < 0) + return -errno; + + setutxent(); + + zero(lookup); + lookup.ut_type = RUN_LVL; + + if (!(found = getutxid(&lookup))) + r = -errno; + else { + int a, b; + + a = found->ut_pid & 0xFF; + b = (found->ut_pid >> 8) & 0xFF; + + if (a < 0 || b < 0) + r = -EIO; + else { + *runlevel = a; + + if (previous) + *previous = b; + r = 0; + } + } + + endutxent(); + + return r; +} + +static void init_entry(struct utmpx *store, usec_t timestamp) { + struct utsname uts; + + assert(store); + + zero(*store); + zero(uts); + + if (timestamp <= 0) + timestamp = now(CLOCK_REALTIME); + + store->ut_tv.tv_sec = timestamp / USEC_PER_SEC; + store->ut_tv.tv_usec = timestamp % USEC_PER_SEC; + + if (uname(&uts) >= 0) + strncpy(store->ut_host, uts.release, sizeof(store->ut_host)); + + strncpy(store->ut_line, "~", sizeof(store->ut_line)); /* or ~~ ? */ + strncpy(store->ut_id, "~~", sizeof(store->ut_id)); +} + +static int write_entry_utmp(const struct utmpx *store) { + int r; + + assert(store); + + /* utmp is similar to wtmp, but there is only one entry for + * each entry type resp. user; i.e. basically a key/value + * table. */ + + if (utmpxname(_PATH_UTMPX) < 0) + return -errno; + + setutxent(); + + if (!pututxline(store)) + r = -errno; + else + r = 0; + + endutxent(); + + return r; +} + +static int write_entry_wtmp(const struct utmpx *store) { + assert(store); + + /* wtmp is a simple append-only file where each entry is + simply appended to * the end; i.e. basically a log. */ + + errno = 0; + updwtmpx(_PATH_WTMPX, store); + return -errno; +} + +static int write_entry_both(const struct utmpx *store) { + int r, s; + + r = write_entry_utmp(store); + s = write_entry_wtmp(store); + + if (r >= 0) + r = s; + + /* If utmp/wtmp have been disabled, that's a good thing, hence + * ignore the errors */ + if (r == -ENOENT) + r = 0; + + return r; +} + +int utmp_put_shutdown(usec_t timestamp) { + struct utmpx store; + + init_entry(&store, timestamp); + + store.ut_type = RUN_LVL; + strncpy(store.ut_user, "shutdown", sizeof(store.ut_user)); + + return write_entry_both(&store); +} + +int utmp_put_reboot(usec_t timestamp) { + struct utmpx store; + + init_entry(&store, timestamp); + + store.ut_type = BOOT_TIME; + strncpy(store.ut_user, "reboot", sizeof(store.ut_type)); + + return write_entry_both(&store); +} + +int utmp_put_runlevel(usec_t timestamp, int runlevel, int previous) { + struct utmpx store; + int r; + + assert(runlevel > 0); + + if (previous <= 0) { + /* Find the old runlevel automatically */ + + if ((r = utmp_get_runlevel(&previous, NULL)) < 0) + return r; + + if (previous == runlevel) + return 0; + } + + init_entry(&store, timestamp); + + store.ut_type = RUN_LVL; + store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8); + strncpy(store.ut_user, "runlevel", sizeof(store.ut_user)); + + return write_entry_both(&store); +} diff --git a/utmp-wtmp.h b/utmp-wtmp.h new file mode 100644 index 000000000..34c3222c9 --- /dev/null +++ b/utmp-wtmp.h @@ -0,0 +1,33 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef fooutmpwtmphfoo +#define fooutmpwtmphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include "util.h" + +int utmp_get_runlevel(int *runlevel, int *previous); + +int utmp_put_shutdown(usec_t timestamp); +int utmp_put_reboot(usec_t timestamp); +int utmp_put_runlevel(usec_t timestamp, int runlevel, int previous); + +#endif -- 2.30.2