#include <sys/signalfd.h>
#include <sys/wait.h>
#include <unistd.h>
+#include <utmpx.h>
#include <sys/poll.h>
+#include <sys/reboot.h>
+#include <sys/ioctl.h>
+#include <linux/kd.h>
+#include <libcgroup.h>
+#include <termios.h>
+#include <fcntl.h>
#include "manager.h"
#include "hashmap.h"
#include "log.h"
#include "util.h"
#include "ratelimit.h"
+#include "cgroup.h"
+#include "mount-setup.h"
+#include "utmp-wtmp.h"
+
+static int enable_special_signals(Manager *m) {
+ char fd;
+
+ assert(m);
+
+ /* Enable that we get SIGINT on control-alt-del */
+ if (reboot(RB_DISABLE_CAD) < 0)
+ log_warning("Failed to enable ctrl-alt-del handling: %m");
+
+ if ((fd = open_terminal("/dev/tty0", O_RDWR)) < 0)
+ log_warning("Failed to open /dev/tty0: %m");
+ else {
+ /* Enable that we get SIGWINCH on kbrequest */
+ if (ioctl(fd, KDSIGACCEPT, SIGWINCH) < 0)
+ log_warning("Failed to enable kbrequest handling: %s", strerror(errno));
+
+ close_nointr_nofail(fd);
+ }
+
+ return 0;
+}
static int manager_setup_signals(Manager *m) {
sigset_t mask;
struct epoll_event ev;
+ struct sigaction sa;
assert(m);
- assert_se(reset_all_signal_handlers() == 0);
+ /* We are not interested in SIGSTOP and friends. */
+ zero(sa);
+ sa.sa_handler = SIG_DFL;
+ sa.sa_flags = SA_NOCLDSTOP|SA_RESTART;
+ assert_se(sigaction(SIGCHLD, &sa, NULL) == 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(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, SIGPWR) == 0); /* Some kernel drivers and upsd send us this on power failure */
assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
m->signal_watch.type = WATCH_SIGNAL;
if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->signal_watch.fd, &ev) < 0)
return -errno;
+ if (m->running_as == MANAGER_INIT)
+ return enable_special_signals(m);
+
return 0;
}
static int manager_find_paths(Manager *m) {
const char *e;
char *t;
+
assert(m);
/* First priority is whatever has been passed to us via env
return -ENOMEM;
}
- /* FIXME: This should probably look for MANAGER_INIT, and exclude MANAGER_SYSTEM */
- if (m->running_as != MANAGER_SESSION) {
+ if (m->running_as == MANAGER_INIT) {
/* /etc/init.d/ compativility does not matter to users */
if ((e = getenv("SYSTEMD_SYSVINIT_PATH")))
NULL)))
return -ENOMEM;
}
+
+ if ((e = getenv("SYSTEMD_SYSVRCND_PATH")))
+ if (!(m->sysvrcnd_path = split_path_and_make_absolute(e)))
+ return -ENOMEM;
+
+ if (strv_isempty(m->sysvrcnd_path)) {
+ strv_free(m->sysvrcnd_path);
+
+ if (!(m->sysvrcnd_path = strv_new(
+ SYSTEM_SYSVRCND_PATH, /* /etc/rcN.d/ */
+ NULL)))
+ return -ENOMEM;
+ }
}
strv_uniq(m->unit_path);
strv_uniq(m->sysvinit_path);
+ strv_uniq(m->sysvrcnd_path);
assert(!strv_isempty(m->unit_path));
if (!(t = strv_join(m->unit_path, "\n\t")))
} else
log_debug("Ignoring SysV init scripts.");
+ if (!strv_isempty(m->sysvrcnd_path)) {
+
+ if (!(t = strv_join(m->sysvrcnd_path, "\n\t")))
+ return -ENOMEM;
+
+ log_debug("Looking for SysV rcN.d links in:\n\t%s", t);
+ free(t);
+ } else
+ log_debug("Ignoring SysV rcN.d links.");
+
return 0;
}
-Manager* manager_new(void) {
+int manager_new(ManagerRunningAs running_as, bool confirm_spawn, Manager **_m) {
Manager *m;
+ int r = -ENOMEM;
+
+ assert(_m);
+ assert(running_as >= 0);
+ assert(running_as < _MANAGER_RUNNING_AS_MAX);
if (!(m = new0(Manager, 1)))
- return NULL;
+ return -ENOMEM;
- if (getpid() == 1)
- m->running_as = MANAGER_INIT;
- else if (getuid() == 0)
- m->running_as = MANAGER_SYSTEM;
- else
- m->running_as = MANAGER_SESSION;
+ m->boot_timestamp = now(CLOCK_REALTIME);
- log_debug("systemd running in %s mode.", manager_running_as_to_string(m->running_as));
+ m->running_as = running_as;
+ m->confirm_spawn = confirm_spawn;
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 (manager_find_paths(m) < 0)
- goto fail;
-
if (!(m->units = hashmap_new(string_hash_func, string_compare_func)))
goto fail;
if (!(m->watch_pids = hashmap_new(trivial_hash_func, trivial_compare_func)))
goto fail;
+ if (!(m->cgroup_bondings = hashmap_new(string_hash_func, string_compare_func)))
+ goto fail;
+
if ((m->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
goto fail;
- if (manager_setup_signals(m) < 0)
+ if ((r = manager_find_paths(m)) < 0)
goto fail;
- /* FIXME: this should be called only when the D-Bus bus daemon is running */
- if (bus_init(m) < 0)
+ if ((r = manager_setup_signals(m)) < 0)
goto fail;
- return m;
+ if ((r = manager_setup_cgroup(m)) < 0)
+ goto fail;
+
+ /* Try to connect to the busses, if possible. */
+ if ((r = bus_init_system(m)) < 0 ||
+ (r = bus_init_api(m)) < 0)
+ goto fail;
+
+ *_m = m;
+ return 0;
fail:
manager_free(m);
- return NULL;
+ return r;
+}
+
+static unsigned manager_dispatch_cleanup_queue(Manager *m) {
+ Meta *meta;
+ unsigned n = 0;
+
+ assert(m);
+
+ while ((meta = m->cleanup_queue)) {
+ assert(meta->in_cleanup_queue);
+
+ unit_free(UNIT(meta));
+ n++;
+ }
+
+ return n;
}
void manager_free(Manager *m) {
while ((u = hashmap_first(m->units)))
unit_free(u);
+ manager_dispatch_cleanup_queue(m);
+
for (c = 0; c < _UNIT_TYPE_MAX; c++)
if (unit_vtable[c]->shutdown)
unit_vtable[c]->shutdown(m);
- bus_done(m);
+ manager_shutdown_cgroup(m);
+
+ bus_done_api(m);
+ bus_done_system(m);
hashmap_free(m->units);
hashmap_free(m->jobs);
strv_free(m->unit_path);
strv_free(m->sysvinit_path);
+ strv_free(m->sysvrcnd_path);
+
+ free(m->cgroup_controller);
+ free(m->cgroup_hierarchy);
+
+ assert(hashmap_isempty(m->cgroup_bondings));
+ hashmap_free(m->cgroup_bondings);
free(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;
}
-static void transaction_delete_job(Manager *m, Job *j) {
+static void transaction_delete_job(Manager *m, Job *j, bool delete_dependencies) {
assert(m);
assert(j);
/* Deletes one job from the transaction */
- manager_transaction_unlink_job(m, j);
+ manager_transaction_unlink_job(m, j, delete_dependencies);
if (!j->installed)
job_free(j);
* transaction */
while ((j = hashmap_get(m->transaction_jobs, u)))
- transaction_delete_job(m, j);
+ transaction_delete_job(m, j, true);
}
static void transaction_clean_dependencies(Manager *m) {
while ((j = hashmap_first(m->transaction_jobs)))
if (j->installed)
- transaction_delete_job(m, j);
+ transaction_delete_job(m, j, true);
else
job_free(j);
/* Kill the other job */
other->subject_list = NULL;
other->object_list = NULL;
- transaction_delete_job(m, other);
+ transaction_delete_job(m, other, true);
}
static int delete_one_unmergeable_job(Manager *m, Job *j) {
return -ENOEXEC;
/* Ok, we can drop one, so let's do so. */
- log_debug("Try to fix job merging by deleting job %s/%s", unit_id(d->unit), job_type_to_string(d->type));
- transaction_delete_job(m, d);
+ log_debug("Trying to fix job merging by deleting job %s/%s", unit_id(d->unit), job_type_to_string(d->type));
+ transaction_delete_job(m, d, true);
return 0;
}
return 0;
}
+static void transaction_drop_redundant(Manager *m) {
+ bool again;
+
+ assert(m);
+
+ /* Goes through the transaction and removes all jobs that are
+ * a noop */
+
+ do {
+ Job *j;
+ Iterator i;
+
+ again = false;
+
+ HASHMAP_FOREACH(j, m->transaction_jobs, i) {
+ bool changes_something = false;
+ Job *k;
+
+ LIST_FOREACH(transaction, k, j) {
+
+ if (!job_is_anchor(k) &&
+ job_type_is_redundant(k->type, unit_active_state(k->unit)))
+ continue;
+
+ changes_something = true;
+ break;
+ }
+
+ if (changes_something)
+ continue;
+
+ log_debug("Found redundant job %s/%s, dropping.", unit_id(j->unit), job_type_to_string(j->type));
+ transaction_delete_job(m, j, false);
+ again = true;
+ break;
+ }
+
+ } while (again);
+}
+
static bool unit_matters_to_anchor(Unit *u, Job *j) {
assert(u);
assert(!j->transaction_prev);
* 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));
+ log_debug("Found ordering 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));
+ log_debug("Walked on cycle path to %s/%s", unit_id(k->unit), job_type_to_string(k->type));
if (!k->installed &&
!unit_matters_to_anchor(k->unit, k)) {
continue;
log_debug("Garbage collecting job %s/%s", unit_id(j->unit), job_type_to_string(j->type));
- transaction_delete_job(m, j);
+ transaction_delete_job(m, j, true);
again = true;
break;
}
/* 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));
- transaction_delete_job(m, j);
+ transaction_delete_job(m, j, true);
again = true;
break;
}
* jobs if we don't have to. */
transaction_minimize_impact(m);
+ /* Third step: Drop redundant jobs */
+ transaction_drop_redundant(m);
+
for (;;) {
- /* Third step: Let's remove unneeded jobs that might
+ /* Fourth step: Let's remove unneeded jobs that might
* be lurking. */
transaction_collect_garbage(m);
- /* Fourth step: verify order makes sense and correct
+ /* Fifth step: verify order makes sense and correct
* cycles if necessary and possible */
if ((r = transaction_verify_order(m, &generation)) >= 0)
break;
}
for (;;) {
- /* Fifth step: let's drop unmergeable entries if
+ /* Sixth step: let's drop unmergeable entries if
* necessary and possible, merge entries we can
* merge */
if ((r = transaction_merge_jobs(m)) >= 0)
goto rollback;
}
- /* Sixth step: an entry got dropped, let's garbage
+ /* Seventh step: an entry got dropped, let's garbage
* collect its dependencies. */
transaction_collect_garbage(m);
* unmergeable entries ... */
}
- /* Seventh step: check whether we can actually apply this */
+ /* Eights step: Drop redundant jobs again, if the merging now allows us to drop more. */
+ transaction_drop_redundant(m);
+
+ /* Ninth step: check whether we can actually apply this */
if (mode == JOB_FAIL)
if ((r = transaction_is_destructive(m, mode)) < 0) {
log_debug("Requested transaction contradicts existing jobs: %s", strerror(-r));
goto rollback;
}
- /* Eights step: apply changes */
+ /* Tenth step: apply changes */
if ((r = transaction_apply(m, mode)) < 0) {
log_debug("Failed to apply transaction: %s", strerror(-r));
goto rollback;
if (is_new)
*is_new = true;
+ log_debug("Added job %s/%s to transaction.", unit_id(unit), job_type_to_string(type));
+
return j;
}
-void manager_transaction_unlink_job(Manager *m, Job *j) {
+void manager_transaction_unlink_job(Manager *m, Job *j, bool delete_dependencies) {
assert(m);
assert(j);
job_dependency_free(j->object_list);
- if (other) {
+ if (other && delete_dependencies) {
log_debug("Deleting job %s/%s as dependency of job %s/%s",
unit_id(other->unit), job_type_to_string(other->type),
unit_id(j->unit), job_type_to_string(j->type));
- transaction_delete_job(m, other);
+ transaction_delete_job(m, other, delete_dependencies);
}
}
}
if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, !force, force, NULL)) < 0 && r != -EBADR)
goto fail;
SET_FOREACH(dep, ret->unit->meta.dependencies[UNIT_WANTS], i)
- if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, false, force, NULL)) < 0 && r != -EBADR)
- goto fail;
+ if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, false, force, NULL)) < 0)
+ log_warning("Cannot add dependency job for unit %s, ignoring: %s", unit_id(dep), strerror(-r));
SET_FOREACH(dep, ret->unit->meta.dependencies[UNIT_REQUISITE], i)
if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, true, force, NULL)) < 0 && r != -EBADR)
goto fail;
return 0;
}
+int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, bool force, Job **_ret) {
+ Unit *unit;
+ int r;
+
+ assert(m);
+ assert(type < _JOB_TYPE_MAX);
+ assert(name);
+ assert(mode < _JOB_MODE_MAX);
+
+ if ((r = manager_load_unit(m, name, &unit)) < 0)
+ return r;
+
+ return manager_add_job(m, type, unit, mode, force, _ret);
+}
+
Job *manager_get_job(Manager *m, uint32_t id) {
assert(m);
manager_dispatch_load_queue(m);
- *_ret = ret;
+ *_ret = unit_follow_merge(ret);
return 0;
}
m->dispatching_dbus_queue = true;
while ((meta = m->dbus_unit_queue)) {
- Unit *u = (Unit*) meta;
- assert(u->meta.in_dbus_queue);
+ assert(meta->in_dbus_queue);
- bus_unit_send_change_signal(u);
+ bus_unit_send_change_signal(UNIT(meta));
n++;
}
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, WEXITED|WNOHANG) < 0) {
+
+ /* First we call waitd() for a PID and do not reap the
+ * zombie. That way we can still access /proc/$PID for
+ * it while it is a zombie. */
+ if (waitid(P_ALL, 0, &si, WEXITED|WNOHANG|WNOWAIT) < 0) {
if (errno == ECHILD)
break;
+ if (errno == EINTR)
+ continue;
+
return -errno;
}
- if (si.si_pid == 0)
+ if (si.si_pid <= 0)
break;
+ if (si.si_code == CLD_EXITED || si.si_code == CLD_KILLED || si.si_code == CLD_DUMPED) {
+ char *name = NULL;
+
+ get_process_name(si.si_pid, &name);
+ log_debug("Got SIGCHLD for process %llu (%s)", (unsigned long long) si.si_pid, strna(name));
+ free(name);
+ }
+
+ /* And now, we actually reap the zombie. */
+ if (waitid(P_PID, si.si_pid, &si, WEXITED) < 0) {
+ if (errno == EINTR)
+ continue;
+
+ return -errno;
+ }
+
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);
+ log_debug("Child %llu died (code=%s, status=%i/%s)",
+ (long long unsigned) si.si_pid,
+ sigchld_code_to_string(si.si_code),
+ si.si_status,
+ strna(si.si_code == CLD_EXITED ? exit_status_to_string(si.si_status) : strsignal(si.si_status)));
if (!(u = hashmap_remove(m->watch_pids, UINT32_TO_PTR(si.si_pid))))
continue;
+ log_debug("Child %llu belongs to %s", (long long unsigned) si.si_pid, unit_id(u));
+
UNIT_VTABLE(u)->sigchld_event(u, si.si_pid, si.si_code, si.si_status);
}
return 0;
}
+static void manager_start_target(Manager *m, const char *name) {
+ int r;
+
+ if ((r = manager_add_job_by_name(m, JOB_START, name, JOB_REPLACE, true, NULL)) < 0)
+ log_error("Failed to enqueue %s job: %s", name, strerror(-r));
+}
+
static int manager_process_signal_fd(Manager *m, bool *quit) {
ssize_t n;
struct signalfd_siginfo sfsi;
case SIGINT:
case SIGTERM:
- if (m->running_as != MANAGER_INIT) {
- *quit = true;
- return 0;
-
- } else {
- Unit *target;
- int r;
-
- if ((r = manager_load_unit(m, SPECIAL_CTRL_ALT_DEL_TARGET, &target)) < 0)
- log_error("Failed to load ctrl-alt-del target: %s", strerror(-r));
- else if ((r = manager_add_job(m, JOB_START, target, JOB_REPLACE, true, NULL)) < 0)
- log_error("Failed to enqueue ctrl-alt-del job: %s", strerror(-r));
-
+ if (m->running_as == MANAGER_INIT) {
+ manager_start_target(m, SPECIAL_CTRL_ALT_DEL_TARGET);
break;
}
+ *quit = true;
+ return 0;
+
case SIGWINCH:
- if (m->running_as == MANAGER_INIT) {
- Unit *target;
- int r;
+ if (m->running_as == MANAGER_INIT)
+ manager_start_target(m, SPECIAL_KBREQUEST_TARGET);
- if ((r = manager_load_unit(m, SPECIAL_KBREQUEST_TARGET, &target)) < 0)
- log_error("Failed to load kbrequest target: %s", strerror(-r));
- else if ((r = manager_add_job(m, JOB_START, target, JOB_REPLACE, true, NULL)) < 0)
- log_error("Failed to enqueue kbrequest job: %s", strerror(-r));
+ /* This is a nop on non-init */
+ break;
- break;
+ case SIGPWR:
+ if (m->running_as == MANAGER_INIT)
+ manager_start_target(m, SPECIAL_SIGPWR_TARGET);
+
+ /* This is a nop on non-init */
+ break;
+
+ case SIGUSR1:
+ manager_dump_units(m, stdout, "\t");
+ manager_dump_jobs(m, stdout, "\t");
+ break;
+
+ case SIGUSR2: {
+ Unit *u;
+
+ u = manager_get_unit(m, SPECIAL_DBUS_SERVICE);
+
+ if (!u || UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) {
+ log_info("Trying to reconnect to bus...");
+ bus_init_system(m);
+ bus_init_api(m);
}
- /* This is a nop on non-init systemd's */
+ if (!u || !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) {
+ log_info("Loading D-Bus service...");
+ manager_start_target(m, SPECIAL_DBUS_SERVICE);
+ }
break;
+ }
default:
log_info("Got unhandled signal <%s>.", strsignal(sfsi.ssi_signo));
assert(m);
- for (;;) {
+ do {
struct epoll_event event;
int n;
sleep(1);
}
+ if (manager_dispatch_cleanup_queue(m) > 0)
+ continue;
+
if (manager_dispatch_load_queue(m) > 0)
continue;
if ((r = process_event(m, &event, &quit)) < 0)
return r;
+ } while (!quit);
- if (quit)
- return 0;
- }
+ return 0;
}
int manager_get_unit_from_dbus_path(Manager *m, const char *s, Unit **_u) {
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",