X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=manager.c;h=b574c6380dad18b6886851b1732efe7186c2eedc;hp=682c7e7f26b451172563644618eff712ef9fe723;hb=0acb83e9e63206af9a2c6a35ca054453d235458a;hpb=0301abf48ed3be921c33d409c73b554435cf6378 diff --git a/manager.c b/manager.c index 682c7e7f2..b574c6380 100644 --- a/manager.c +++ b/manager.c @@ -15,16 +15,50 @@ #include "macro.h" #include "strv.h" #include "log.h" +#include "util.h" +#include "ratelimit.h" -Manager* manager_new(void) { - Manager *m; +static int manager_setup_signals(Manager *m) { sigset_t mask; struct epoll_event ev; + assert(m); + + assert_se(reset_all_signal_handlers() == 0); + + assert_se(sigemptyset(&mask) == 0); + assert_se(sigaddset(&mask, SIGCHLD) == 0); + assert_se(sigaddset(&mask, SIGINT) == 0); /* Kernel sends us this on control-alt-del */ + assert_se(sigaddset(&mask, SIGWINCH) == 0); /* Kernel sends us this on kbrequest (alt-arrowup) */ + assert_se(sigaddset(&mask, SIGTERM) == 0); + assert_se(sigaddset(&mask, SIGHUP) == 0); + assert_se(sigaddset(&mask, SIGUSR1) == 0); + assert_se(sigaddset(&mask, SIGUSR2) == 0); + assert_se(sigaddset(&mask, SIGPIPE) == 0); + assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); + + m->signal_watch.type = WATCH_SIGNAL; + if ((m->signal_watch.fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) + return -errno; + + zero(ev); + ev.events = EPOLLIN; + ev.data.ptr = &m->signal_watch; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->signal_watch.fd, &ev) < 0) + return -errno; + + return 0; +} + +Manager* manager_new(void) { + Manager *m; + if (!(m = new0(Manager, 1))) return NULL; - m->signal_fd = m->epoll_fd = -1; + 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 */ if (!(m->units = hashmap_new(string_hash_func, string_compare_func))) goto fail; @@ -41,18 +75,11 @@ Manager* manager_new(void) { if ((m->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) goto fail; - assert_se(sigemptyset(&mask) == 0); - assert_se(sigaddset(&mask, SIGCHLD) == 0); - assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); - - if ((m->signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) + if (manager_setup_signals(m) < 0) goto fail; - zero(ev); - ev.events = EPOLLIN; - ev.data.fd = m->signal_fd; - - if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->signal_fd, &ev) < 0) + /* FIXME: this should be called only when the D-Bus bus daemon is running */ + if (bus_init(m) < 0) goto fail; return m; @@ -63,6 +90,7 @@ fail: } void manager_free(Manager *m) { + UnitType c; Unit *u; Job *j; @@ -74,6 +102,12 @@ void manager_free(Manager *m) { while ((u = hashmap_first(m->units))) unit_free(u); + for (c = 0; c < _UNIT_TYPE_MAX; c++) + if (unit_vtable[c]->shutdown) + unit_vtable[c]->shutdown(m); + + bus_done(m); + hashmap_free(m->units); hashmap_free(m->jobs); hashmap_free(m->transaction_jobs); @@ -81,12 +115,45 @@ void manager_free(Manager *m) { if (m->epoll_fd >= 0) close_nointr(m->epoll_fd); - if (m->signal_fd >= 0) - close_nointr(m->signal_fd); + if (m->signal_watch.fd >= 0) + close_nointr(m->signal_watch.fd); free(m); } +int manager_coldplug(Manager *m) { + int r; + UnitType c; + Iterator i; + Unit *u; + char *k; + + assert(m); + + /* First, let's ask every type to load all units from + * disk/kernel that it might know */ + for (c = 0; c < _UNIT_TYPE_MAX; c++) + if (unit_vtable[c]->enumerate) + if ((r = unit_vtable[c]->enumerate(m)) < 0) + return r; + + manager_dispatch_load_queue(m); + + /* Then, let's set up their initial state. */ + HASHMAP_FOREACH_KEY(u, k, m->units, i) { + + /* ignore aliases */ + if (unit_id(u) != k) + continue; + + if (UNIT_VTABLE(u)->coldplug) + if ((r = UNIT_VTABLE(u)->coldplug(u)) < 0) + return r; + } + + return 0; +} + static void transaction_delete_job(Manager *m, Job *j) { assert(m); assert(j); @@ -109,6 +176,24 @@ static void transaction_delete_unit(Manager *m, Unit *u) { transaction_delete_job(m, j); } +static void transaction_clean_dependencies(Manager *m) { + Iterator i; + Job *j; + + assert(m); + + /* Drops all dependencies of all installed jobs */ + + HASHMAP_FOREACH(j, m->jobs, i) { + while (j->subject_list) + job_dependency_free(j->subject_list); + while (j->object_list) + job_dependency_free(j->object_list); + } + + assert(!m->transaction_anchor); +} + static void transaction_abort(Manager *m) { Job *j; @@ -121,7 +206,8 @@ static void transaction_abort(Manager *m) { job_free(j); assert(hashmap_isempty(m->transaction_jobs)); - assert(!m->transaction_anchor); + + transaction_clean_dependencies(m); } static void transaction_find_jobs_that_matter_to_anchor(Manager *m, Job *j, unsigned generation) { @@ -346,8 +432,12 @@ static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned * since smart how we are we stored our way back in * there. */ + log_debug("Found cycle on %s/%s", unit_id(j->unit), job_type_to_string(j->type)); + for (k = from; k; k = (k->generation == generation ? k->marker : NULL)) { + log_debug("Walked on cycle path to %s/%s", unit_id(j->unit), job_type_to_string(j->type)); + if (!k->installed && !unit_matters_to_anchor(k->unit, k)) { /* Ok, we can drop this one, so let's @@ -363,6 +453,8 @@ static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned break; } + log_debug("Unable to break cycle"); + return -ENOEXEC; } @@ -389,6 +481,10 @@ static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned return r; } + /* Ok, let's backtrack, and remember that this entry is not on + * our path anymore. */ + j->marker = NULL; + return 0; } @@ -475,6 +571,7 @@ static void transaction_minimize_impact(Manager *m) { HASHMAP_FOREACH(j, m->transaction_jobs, i) { LIST_FOREACH(transaction, j, j) { + bool stops_running_service, changes_existing_job; /* If it matters, we shouldn't drop it */ if (j->matters_to_anchor) @@ -483,12 +580,25 @@ static void transaction_minimize_impact(Manager *m) { /* Would this stop a running service? * Would this change an existing job? * If so, let's drop this entry */ - if ((j->type != JOB_STOP || UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(j->unit))) && - (!j->unit->meta.job || job_type_is_conflicting(j->type, j->unit->meta.job->state))) + + stops_running_service = + j->type == JOB_STOP && UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(j->unit)); + + changes_existing_job = + j->unit->meta.job && job_type_is_conflicting(j->type, j->unit->meta.job->state); + + if (!stops_running_service && !changes_existing_job) continue; + if (stops_running_service) + log_debug("%s/%s would stop a running service.", unit_id(j->unit), job_type_to_string(j->type)); + + if (changes_existing_job) + log_debug("%s/%s would change existing job.", unit_id(j->unit), job_type_to_string(j->type)); + /* Ok, let's get rid of this */ - log_debug("Deleting %s/%s to minimize impact", unit_id(j->unit), job_type_to_string(j->type)); + log_debug("Deleting %s/%s to minimize impact.", unit_id(j->unit), job_type_to_string(j->type)); + transaction_delete_job(m, j); again = true; break; @@ -536,13 +646,11 @@ static int transaction_apply(Manager *m, JobMode mode) { assert(!j->transaction_next); assert(!j->transaction_prev); - while (j->subject_list) - job_dependency_free(j->subject_list); - while (j->object_list) - job_dependency_free(j->object_list); + job_schedule_run(j); } - m->transaction_anchor = NULL; + /* As last step, kill all remaining job dependencies. */ + transaction_clean_dependencies(m); return 0; @@ -585,8 +693,10 @@ static int transaction_activate(Manager *m, JobMode mode) { if ((r = transaction_verify_order(m, &generation)) >= 0) break; - if (r != -EAGAIN) + if (r != -EAGAIN) { + log_debug("Requested transaction contains an unfixable cyclic ordering dependency: %s", strerror(-r)); goto rollback; + } /* Let's see if the resulting transaction ordering * graph is still cyclic... */ @@ -599,8 +709,10 @@ static int transaction_activate(Manager *m, JobMode mode) { if ((r = transaction_merge_jobs(m)) >= 0) break; - if (r != -EAGAIN) + if (r != -EAGAIN) { + log_debug("Requested transaction contains unmergable jobs: %s", strerror(-r)); goto rollback; + } /* Sixth step: an entry got dropped, let's garbage * collect its dependencies. */ @@ -612,12 +724,16 @@ static int transaction_activate(Manager *m, JobMode mode) { /* Seventh step: check whether we can actually apply this */ if (mode == JOB_FAIL) - if ((r = transaction_is_destructive(m, mode)) < 0) + if ((r = transaction_is_destructive(m, mode)) < 0) { + log_debug("Requested transaction contradicts existing jobs: %s", strerror(-r)); goto rollback; + } /* Eights step: apply changes */ - if ((r = transaction_apply(m, mode)) < 0) + if ((r = transaction_apply(m, mode)) < 0) { + log_debug("Failed to apply transaction: %s", strerror(-r)); goto rollback; + } assert(hashmap_isempty(m->transaction_jobs)); assert(!m->transaction_anchor); @@ -780,6 +896,8 @@ int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool for assert(unit); assert(mode < _JOB_MODE_MAX); + log_debug("Trying to enqueue job %s/%s", unit_id(unit), job_type_to_string(type)); + if ((r = transaction_add_job_and_dependencies(m, type, unit, NULL, true, force, &ret))) { transaction_abort(m); return r; @@ -788,6 +906,8 @@ int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool for if ((r = transaction_activate(m, mode)) < 0) return r; + log_debug("Enqueued job %s/%s", unit_id(unit), job_type_to_string(type)); + if (_ret) *_ret = ret; @@ -807,7 +927,7 @@ Unit *manager_get_unit(Manager *m, const char *name) { return hashmap_get(m->units, name); } -static void dispatch_load_queue(Manager *m) { +void manager_dispatch_load_queue(Manager *m) { Meta *meta; assert(m); @@ -865,7 +985,7 @@ int manager_load_unit(Manager *m, const char *path, Unit **_ret) { } unit_add_to_load_queue(ret); - dispatch_load_queue(m); + manager_dispatch_load_queue(m); *_ret = ret; return 0; @@ -927,13 +1047,20 @@ void manager_dispatch_run_queue(Manager *m) { static int manager_dispatch_sigchld(Manager *m) { assert(m); + log_debug("dispatching SIGCHLD"); + for (;;) { siginfo_t si; Unit *u; zero(si); - if (waitid(P_ALL, 0, &si, WNOHANG) < 0) + if (waitid(P_ALL, 0, &si, WEXITED|WNOHANG) < 0) { + + if (errno == ECHILD) + break; + return -errno; + } if (si.si_pid == 0) break; @@ -941,6 +1068,8 @@ static int manager_dispatch_sigchld(Manager *m) { if (si.si_code != CLD_EXITED && si.si_code != CLD_KILLED && si.si_code != CLD_DUMPED) continue; + log_debug("child %llu died (code=%s, status=%i)", (long long unsigned) si.si_pid, sigchld_code_to_string(si.si_code), si.si_status); + if (!(u = hashmap_remove(m->watch_pids, UINT32_TO_PTR(si.si_pid)))) continue; @@ -950,7 +1079,7 @@ static int manager_dispatch_sigchld(Manager *m) { return 0; } -static int manager_process_signal_fd(Manager *m) { +static int manager_process_signal_fd(Manager *m, bool *quit) { ssize_t n; struct signalfd_siginfo sfsi; bool sigchld = false; @@ -958,19 +1087,31 @@ static int manager_process_signal_fd(Manager *m) { assert(m); for (;;) { - if ((n = read(m->signal_fd, &sfsi, sizeof(sfsi))) != sizeof(sfsi)) { + if ((n = read(m->signal_watch.fd, &sfsi, sizeof(sfsi))) != sizeof(sfsi)) { if (n >= 0) return -EIO; if (errno == EAGAIN) - return 0; + break; return -errno; } - if (sfsi.ssi_signo == SIGCHLD) + switch (sfsi.ssi_signo) { + + case SIGCHLD: sigchld = true; + break; + + case SIGINT: + case SIGTERM: + *quit = true; + return 0; + + default: + log_info("Got unhandled signal <%s>.", strsignal(sfsi.ssi_signo)); + } } if (sigchld) @@ -979,56 +1120,71 @@ static int manager_process_signal_fd(Manager *m) { return 0; } -static int process_event(Manager *m, struct epoll_event *ev) { +static int process_event(Manager *m, struct epoll_event *ev, bool *quit) { int r; + Watch *w; assert(m); assert(ev); - switch (ev->data.u32) { + assert(w = ev->data.ptr); - case MANAGER_SIGNAL: - assert(ev->data.fd == m->signal_fd); + switch (w->type) { - /* An incoming signal? */ - if (ev->events != POLLIN) - return -EINVAL; + case WATCH_SIGNAL: - if ((r = manager_process_signal_fd(m)) < 0) - return -r; + /* An incoming signal? */ + if (ev->events != EPOLLIN) + return -EINVAL; - break; + if ((r = manager_process_signal_fd(m, quit)) < 0) + return r; - case MANAGER_FD: { - Unit *u; + break; - /* Some fd event, to be dispatched to the units */ - assert_se(u = ev->data.ptr); - UNIT_VTABLE(u)->fd_event(u, ev->data.fd, ev->events); - break; - } + case WATCH_FD: - case MANAGER_TIMER: { - Unit *u; - uint64_t v; - ssize_t k; + /* Some fd event, to be dispatched to the units */ + UNIT_VTABLE(w->data.unit)->fd_event(w->data.unit, w->fd, ev->events, w); + break; - /* Some timer event, to be dispatched to the units */ - if ((k = read(ev->data.fd, &v, sizeof(v))) != sizeof(v)) { + case WATCH_TIMER: { + uint64_t v; + ssize_t k; - if (k < 0 && (errno == EINTR || errno == EAGAIN)) - break; + /* Some timer event, to be dispatched to the units */ + if ((k = read(ev->data.fd, &v, sizeof(v))) != sizeof(v)) { - return k < 0 ? -errno : -EIO; - } + if (k < 0 && (errno == EINTR || errno == EAGAIN)) + break; - assert_se(u = ev->data.ptr); - UNIT_VTABLE(u)->timer_event(u, ev->data.fd, v); - break; + return k < 0 ? -errno : -EIO; } - default: - assert_not_reached("Unknown epoll event type."); + UNIT_VTABLE(w->data.unit)->timer_event(w->data.unit, v, w); + break; + } + + case WATCH_MOUNT: + /* Some mount table change, intended for the mount subsystem */ + mount_fd_event(m, ev->events); + break; + + case WATCH_UDEV: + /* Some notification from udev, intended for the device subsystem */ + device_fd_event(m, ev->events); + break; + + case WATCH_DBUS_WATCH: + bus_watch_event(m, w, ev->events); + break; + + case WATCH_DBUS_TIMEOUT: + bus_timeout_event(m, w, ev->events); + break; + + default: + assert_not_reached("Unknown epoll event type."); } return 0; @@ -1036,16 +1192,30 @@ static int process_event(Manager *m, struct epoll_event *ev) { int manager_loop(Manager *m) { int r; + bool quit = false; + + RATELIMIT_DEFINE(rl, 1*USEC_PER_SEC, 1000); assert(m); for (;;) { - struct epoll_event events[32]; - int n, i; + struct epoll_event event; + int n; + + if (!ratelimit_test(&rl)) { + /* Yay, something is going seriously wrong, pause a little */ + log_warning("Looping too fast. Throttling execution a little."); + sleep(1); + } manager_dispatch_run_queue(m); - if ((n = epoll_wait(m->epoll_fd, events, ELEMENTSOF(events), -1)) < 0) { + if (m->request_bus_dispatch) { + bus_dispatch(m); + continue; + } + + if ((n = epoll_wait(m->epoll_fd, &event, 1, -1)) < 0) { if (errno == -EINTR) continue; @@ -1053,8 +1223,60 @@ int manager_loop(Manager *m) { return -errno; } - for (i = 0; i < n; i++) - if ((r = process_event(m, events + i)) < 0) - return r; + assert(n == 1); + + if ((r = process_event(m, &event, &quit)) < 0) + return r; + + if (quit) + return 0; } } + +int manager_get_unit_from_dbus_path(Manager *m, const char *s, Unit **_u) { + char *n; + Unit *u; + + assert(m); + assert(s); + assert(_u); + + if (!startswith(s, "/org/freedesktop/systemd1/unit/")) + return -EINVAL; + + if (!(n = bus_path_unescape(s+31))) + return -ENOMEM; + + u = manager_get_unit(m, n); + free(n); + + if (!u) + return -ENOENT; + + *_u = u; + + return 0; +} + +int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j) { + Job *j; + unsigned id; + int r; + + assert(m); + assert(s); + assert(_j); + + if (!startswith(s, "/org/freedesktop/systemd1/job/")) + return -EINVAL; + + if ((r = safe_atou(s + 30, &id)) < 0) + return r; + + if (!(j = manager_get_job(m, id))) + return -ENOENT; + + *_j = j; + + return 0; +}