X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=src%2Fjob.c;h=bae6ab9f32f5c4fd3d569e09f7da22a67adf6755;hb=b8b5e648cacc8d73c55fdffbb3466ecd8146131a;hp=0a9ce153f697a9521b5b53efd32d51ee19d9b1f8;hpb=9aab5a731db87f96101c2eb5d2b15ac45c3a33c3;p=elogind.git diff --git a/src/job.c b/src/job.c index 0a9ce153f..bae6ab9f3 100644 --- a/src/job.c +++ b/src/job.c @@ -1,4 +1,4 @@ -/*-*- Mode: C; c-basic-offset: 8 -*-*/ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ /*** This file is part of systemd. @@ -21,6 +21,8 @@ #include #include +#include +#include #include "set.h" #include "unit.h" @@ -46,6 +48,8 @@ Job* job_new(Manager *m, JobType type, Unit *unit) { j->type = type; j->unit = unit; + j->timer_watch.type = WATCH_INVALID; + /* We don't link it here, that's what job_dependency() is for */ return j; @@ -56,10 +60,10 @@ void job_free(Job *j) { /* Detach from next 'bigger' objects */ if (j->installed) { - bus_job_send_removed_signal(j, !j->failed); + bus_job_send_removed_signal(j); - if (j->unit->meta.job == j) { - j->unit->meta.job = NULL; + if (j->unit->job == j) { + j->unit->job = NULL; unit_add_to_gc_queue(j->unit); } @@ -76,11 +80,20 @@ void job_free(Job *j) { if (j->in_dbus_queue) LIST_REMOVE(Job, dbus_queue, j->manager->dbus_job_queue, j); + if (j->timer_watch.type != WATCH_INVALID) { + assert(j->timer_watch.type == WATCH_JOB_TIMER); + assert(j->timer_watch.data.job == j); + assert(j->timer_watch.fd >= 0); + + assert_se(epoll_ctl(j->manager->epoll_fd, EPOLL_CTL_DEL, j->timer_watch.fd, NULL) >= 0); + close_nointr_nofail(j->timer_watch.fd); + } + free(j->bus_client); free(j); } -JobDependency* job_dependency_new(Job *subject, Job *object, bool matters) { +JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts) { JobDependency *l; assert(object); @@ -88,7 +101,7 @@ JobDependency* job_dependency_new(Job *subject, Job *object, bool matters) { /* Adds a new job link, which encodes that the 'subject' job * needs the 'object' job in some way. If 'subject' is NULL * this means the 'anchor' job (i.e. the one the user - * explcitily asked for) is the requester. */ + * explicitly asked for) is the requester. */ if (!(l = new0(JobDependency, 1))) return NULL; @@ -96,6 +109,7 @@ JobDependency* job_dependency_new(Job *subject, Job *object, bool matters) { l->subject = subject; l->object = object; l->matters = matters; + l->conflicts = conflicts; if (subject) LIST_PREPEND(JobDependency, subject, subject->subject_list, l); @@ -120,30 +134,6 @@ void job_dependency_free(JobDependency *l) { free(l); } -void job_dependency_delete(Job *subject, Job *object, bool *matters) { - JobDependency *l; - - assert(object); - - LIST_FOREACH(object, l, object->object_list) { - assert(l->object == object); - - if (l->subject == subject) - break; - } - - if (!l) { - if (matters) - *matters = false; - return; - } - - if (matters) - *matters = l->matters; - - job_dependency_free(l); -} - void job_dump(Job *j, FILE*f, const char *prefix) { assert(j); assert(f); @@ -157,7 +147,7 @@ void job_dump(Job *j, FILE*f, const char *prefix) { "%s\tState: %s\n" "%s\tForced: %s\n", prefix, j->id, - prefix, j->unit->meta.id, job_type_to_string(j->type), + prefix, j->unit->id, job_type_to_string(j->type), prefix, job_state_to_string(j->state), prefix, yes_no(j->override)); } @@ -174,100 +164,47 @@ bool job_is_anchor(Job *j) { return false; } -static bool types_match(JobType a, JobType b, JobType c, JobType d) { - return - (a == c && b == d) || - (a == d && b == c); -} - -int job_type_merge(JobType *a, JobType b) { - if (*a == b) - return 0; - - /* Merging is associative! a merged with b merged with c is - * the same as a merged with c merged with b. */ - - /* Mergeability is transitive! if a can be merged with b and b - * with c then a also with c */ - - /* Also, if a merged with b cannot be merged with c, then - * either a or b cannot be merged with c either */ - - if (types_match(*a, b, JOB_START, JOB_VERIFY_ACTIVE)) - *a = JOB_START; - else if (types_match(*a, b, JOB_START, JOB_RELOAD) || - types_match(*a, b, JOB_START, JOB_RELOAD_OR_START) || - types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_RELOAD_OR_START) || - types_match(*a, b, JOB_RELOAD, JOB_RELOAD_OR_START)) - *a = JOB_RELOAD_OR_START; - else if (types_match(*a, b, JOB_START, JOB_RESTART) || - types_match(*a, b, JOB_START, JOB_TRY_RESTART) || - types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_RESTART) || - types_match(*a, b, JOB_RELOAD, JOB_RESTART) || - types_match(*a, b, JOB_RELOAD_OR_START, JOB_RESTART) || - types_match(*a, b, JOB_RELOAD_OR_START, JOB_TRY_RESTART) || - types_match(*a, b, JOB_RESTART, JOB_TRY_RESTART)) - *a = JOB_RESTART; - else if (types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_RELOAD)) - *a = JOB_RELOAD; - else if (types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_TRY_RESTART) || - types_match(*a, b, JOB_RELOAD, JOB_TRY_RESTART)) - *a = JOB_TRY_RESTART; - else - return -EEXIST; - - return 0; -} - -bool job_type_is_mergeable(JobType a, JobType b) { - return job_type_merge(&a, b) >= 0; -} - -bool job_type_is_superset(JobType a, JobType b) { +/* + * Merging is commutative, so imagine the matrix as symmetric. We store only + * its lower triangle to avoid duplication. We don't store the main diagonal, + * because A merged with A is simply A. + * + * Merging is associative! A merged with B merged with C is the same as + * A merged with C merged with B. + * + * Mergeability is transitive! If A can be merged with B and B with C then + * A also with C. + * + * Also, if A merged with B cannot be merged with C, then either A or B cannot + * be merged with C either. + */ +static const JobType job_merging_table[] = { +/* What \ With * JOB_START JOB_VERIFY_ACTIVE JOB_STOP JOB_RELOAD JOB_RELOAD_OR_START JOB_RESTART JOB_TRY_RESTART */ +/************************************************************************************************************************************/ +/*JOB_START */ +/*JOB_VERIFY_ACTIVE */ JOB_START, +/*JOB_STOP */ -1, -1, +/*JOB_RELOAD */ JOB_RELOAD_OR_START, JOB_RELOAD, -1, +/*JOB_RELOAD_OR_START*/ JOB_RELOAD_OR_START, JOB_RELOAD_OR_START, -1, JOB_RELOAD_OR_START, +/*JOB_RESTART */ JOB_RESTART, JOB_RESTART, -1, JOB_RESTART, JOB_RESTART, +/*JOB_TRY_RESTART */ JOB_RESTART, JOB_TRY_RESTART, -1, JOB_TRY_RESTART, JOB_RESTART, JOB_RESTART, +}; - /* Checks whether operation a is a "superset" of b in its - * actions */ +JobType job_type_lookup_merge(JobType a, JobType b) { + assert_cc(ELEMENTSOF(job_merging_table) == _JOB_TYPE_MAX * (_JOB_TYPE_MAX - 1) / 2); + assert(a >= 0 && a < _JOB_TYPE_MAX); + assert(b >= 0 && b < _JOB_TYPE_MAX); if (a == b) - return true; - - switch (a) { - case JOB_START: - return b == JOB_VERIFY_ACTIVE; - - case JOB_RELOAD: - return - b == JOB_VERIFY_ACTIVE; - - case JOB_RELOAD_OR_START: - return - b == JOB_RELOAD || - b == JOB_START || - b == JOB_VERIFY_ACTIVE; - - case JOB_RESTART: - return - b == JOB_START || - b == JOB_VERIFY_ACTIVE || - b == JOB_RELOAD || - b == JOB_RELOAD_OR_START || - b == JOB_TRY_RESTART; - - case JOB_TRY_RESTART: - return - b == JOB_VERIFY_ACTIVE || - b == JOB_RELOAD; - default: - return false; + return a; + if (a < b) { + JobType tmp = a; + a = b; + b = tmp; } -} -bool job_type_is_conflicting(JobType a, JobType b) { - assert(a >= 0 && a < _JOB_TYPE_MAX); - assert(b >= 0 && b < _JOB_TYPE_MAX); - - return (a == JOB_STOP) != (b == JOB_STOP); + return job_merging_table[(a - 1) * a / 2 + b]; } bool job_type_is_redundant(JobType a, UnitActiveState b) { @@ -281,7 +218,7 @@ bool job_type_is_redundant(JobType a, UnitActiveState b) { case JOB_STOP: return b == UNIT_INACTIVE || - b == UNIT_MAINTENANCE; + b == UNIT_FAILED; case JOB_VERIFY_ACTIVE: return @@ -319,8 +256,12 @@ bool job_is_runnable(Job *j) { /* Checks whether there is any job running for the units this * job needs to be running after (in the case of a 'positive' - * job type) or before (in the case of a 'negative' job type - * . */ + * job type) or before (in the case of a 'negative' job + * type. */ + + /* First check if there is an override */ + if (j->ignore_order) + return true; if (j->type == JOB_START || j->type == JOB_VERIFY_ACTIVE || @@ -332,19 +273,19 @@ bool job_is_runnable(Job *j) { * dependencies, regardless whether they are * starting or stopping something. */ - SET_FOREACH(other, j->unit->meta.dependencies[UNIT_AFTER], i) - if (other->meta.job) + SET_FOREACH(other, j->unit->dependencies[UNIT_AFTER], i) + if (other->job) return false; } /* Also, if something else is being stopped and we should * change state after it, then lets wait. */ - SET_FOREACH(other, j->unit->meta.dependencies[UNIT_BEFORE], i) - if (other->meta.job && - (other->meta.job->type == JOB_STOP || - other->meta.job->type == JOB_RESTART || - other->meta.job->type == JOB_TRY_RESTART)) + SET_FOREACH(other, j->unit->dependencies[UNIT_BEFORE], i) + if (other->job && + (other->job->type == JOB_STOP || + other->job->type == JOB_RESTART || + other->job->type == JOB_TRY_RESTART)) return false; /* This means that for a service a and a service b where b @@ -361,8 +302,18 @@ bool job_is_runnable(Job *j) { return true; } +static void job_change_type(Job *j, JobType newtype) { + log_debug("Converting job %s/%s -> %s/%s", + j->unit->id, job_type_to_string(j->type), + j->unit->id, job_type_to_string(newtype)); + + j->type = newtype; +} + int job_run_and_invalidate(Job *j) { int r; + uint32_t id; + Manager *m; assert(j); assert(j->installed); @@ -381,10 +332,29 @@ int job_run_and_invalidate(Job *j) { j->state = JOB_RUNNING; job_add_to_dbus_queue(j); + /* While we execute this operation the job might go away (for + * example: because it is replaced by a new, conflicting + * job.) To make sure we don't access a freed job later on we + * store the id here, so that we can verify the job is still + * valid. */ + id = j->id; + m = j->manager; + switch (j->type) { + case JOB_RELOAD_OR_START: + if (unit_active_state(j->unit) == UNIT_ACTIVE) { + job_change_type(j, JOB_RELOAD); + r = unit_reload(j->unit); + break; + } + job_change_type(j, JOB_START); + /* fall through */ + case JOB_START: r = unit_start(j->unit); + + /* If this unit cannot be started, then simply wait */ if (r == -EBADR) r = 0; break; @@ -400,134 +370,249 @@ int job_run_and_invalidate(Job *j) { break; } + case JOB_TRY_RESTART: + if (UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(j->unit))) { + r = -ENOEXEC; + break; + } + job_change_type(j, JOB_RESTART); + /* fall through */ + case JOB_STOP: + case JOB_RESTART: r = unit_stop(j->unit); + + /* If this unit cannot stopped, then simply wait. */ + if (r == -EBADR) + r = 0; break; case JOB_RELOAD: r = unit_reload(j->unit); break; - case JOB_RELOAD_OR_START: - if (unit_active_state(j->unit) == UNIT_ACTIVE) - r = unit_reload(j->unit); - else - r = unit_start(j->unit); + default: + assert_not_reached("Unknown job type"); + } + + if ((j = manager_get_job(m, id))) { + if (r == -EALREADY) + r = job_finish_and_invalidate(j, JOB_DONE); + else if (r == -ENOEXEC) + r = job_finish_and_invalidate(j, JOB_SKIPPED); + else if (r == -EAGAIN) + j->state = JOB_WAITING; + else if (r < 0) + r = job_finish_and_invalidate(j, JOB_FAILED); + } + + return r; +} + +static void job_print_status_message(Unit *u, JobType t, JobResult result) { + assert(u); + + if (t == JOB_START) { + + switch (result) { + + case JOB_DONE: + unit_status_printf(u, ANSI_HIGHLIGHT_GREEN_ON " OK " ANSI_HIGHLIGHT_OFF, "Started %s", unit_description(u)); break; - case JOB_RESTART: { - UnitActiveState t = unit_active_state(j->unit); - if (t == UNIT_INACTIVE || t == UNIT_MAINTENANCE || t == UNIT_ACTIVATING) { - j->type = JOB_START; - r = unit_start(j->unit); - } else - r = unit_stop(j->unit); + case JOB_FAILED: + unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON "FAILED" ANSI_HIGHLIGHT_OFF, "Failed to start %s", unit_description(u)); + unit_status_printf(u, NULL, "See 'systemctl status %s' for details.", u->id); break; - } - case JOB_TRY_RESTART: { - UnitActiveState t = unit_active_state(j->unit); - if (t == UNIT_INACTIVE || t == UNIT_MAINTENANCE || t == UNIT_DEACTIVATING) - r = -ENOEXEC; - else if (t == UNIT_ACTIVATING) { - j->type = JOB_START; - r = unit_start(j->unit); - } else - r = unit_stop(j->unit); + case JOB_DEPENDENCY: + unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " ABORT" ANSI_HIGHLIGHT_OFF, "Dependency failed. Aborted start of %s", unit_description(u)); + break; + + case JOB_TIMEOUT: + unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, "Timed out starting %s", unit_description(u)); break; - } default: - assert_not_reached("Unknown job type"); - } + ; + } - if (r == -EALREADY) - r = job_finish_and_invalidate(j, true); - else if (r == -EAGAIN) { - j->state = JOB_WAITING; - return -EAGAIN; - } else if (r < 0) - r = job_finish_and_invalidate(j, false); + } else if (t == JOB_STOP) { - return r; + switch (result) { + + case JOB_TIMEOUT: + unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, "Timed out stopping %s", unit_description(u)); + break; + + case JOB_DONE: + case JOB_FAILED: + unit_status_printf(u, ANSI_HIGHLIGHT_GREEN_ON " OK " ANSI_HIGHLIGHT_OFF, "Stopped %s", unit_description(u)); + break; + + default: + ; + } + } } -int job_finish_and_invalidate(Job *j, bool success) { +int job_finish_and_invalidate(Job *j, JobResult result) { Unit *u; Unit *other; JobType t; Iterator i; + bool recursed = false; assert(j); assert(j->installed); - log_debug("Job %s/%s finished, success=%s", j->unit->meta.id, job_type_to_string(j->type), yes_no(success)); job_add_to_dbus_queue(j); /* Patch restart jobs so that they become normal start jobs */ - if (success && (j->type == JOB_RESTART || j->type == JOB_TRY_RESTART)) { + if (result == JOB_DONE && j->type == JOB_RESTART) { - log_debug("Converting job %s/%s -> %s/%s", - j->unit->meta.id, job_type_to_string(j->type), - j->unit->meta.id, job_type_to_string(JOB_START)); - - j->state = JOB_RUNNING; - j->type = JOB_START; + job_change_type(j, JOB_START); + j->state = JOB_WAITING; job_add_to_run_queue(j); - return 0; + + u = j->unit; + goto finish; } - j->failed = !success; + j->result = result; + + log_debug("Job %s/%s finished, result=%s", j->unit->id, job_type_to_string(j->type), job_result_to_string(result)); + + if (result == JOB_FAILED) + j->manager->n_failed_jobs ++; + u = j->unit; t = j->type; job_free(j); - if (!success) - unit_status_printf(u, "Starting %s " ANSI_HIGHLIGHT_ON "failed" ANSI_HIGHLIGHT_OFF ".\n", unit_description(u)); + job_print_status_message(u, t, result); /* Fail depending jobs on failure */ - if (!success) { + if (result != JOB_DONE) { if (t == JOB_START || t == JOB_VERIFY_ACTIVE || t == JOB_RELOAD_OR_START) { - SET_FOREACH(other, u->meta.dependencies[UNIT_REQUIRED_BY], i) - if (other->meta.job && - (other->meta.job->type == JOB_START || - other->meta.job->type == JOB_VERIFY_ACTIVE || - other->meta.job->type == JOB_RELOAD_OR_START)) - job_finish_and_invalidate(other->meta.job, false); - - SET_FOREACH(other, u->meta.dependencies[UNIT_REQUIRED_BY_OVERRIDABLE], i) - if (other->meta.job && - !other->meta.job->override && - (other->meta.job->type == JOB_START || - other->meta.job->type == JOB_VERIFY_ACTIVE || - other->meta.job->type == JOB_RELOAD_OR_START)) - job_finish_and_invalidate(other->meta.job, false); + SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY], i) + if (other->job && + (other->job->type == JOB_START || + other->job->type == JOB_VERIFY_ACTIVE || + other->job->type == JOB_RELOAD_OR_START)) { + job_finish_and_invalidate(other->job, JOB_DEPENDENCY); + recursed = true; + } + + SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i) + if (other->job && + (other->job->type == JOB_START || + other->job->type == JOB_VERIFY_ACTIVE || + other->job->type == JOB_RELOAD_OR_START)) { + job_finish_and_invalidate(other->job, JOB_DEPENDENCY); + recursed = true; + } + + SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY_OVERRIDABLE], i) + if (other->job && + !other->job->override && + (other->job->type == JOB_START || + other->job->type == JOB_VERIFY_ACTIVE || + other->job->type == JOB_RELOAD_OR_START)) { + job_finish_and_invalidate(other->job, JOB_DEPENDENCY); + recursed = true; + } } else if (t == JOB_STOP) { - SET_FOREACH(other, u->meta.dependencies[UNIT_CONFLICTS], i) - if (other->meta.job && - (other->meta.job->type == JOB_START || - other->meta.job->type == JOB_VERIFY_ACTIVE || - other->meta.job->type == JOB_RELOAD_OR_START)) - job_finish_and_invalidate(other->meta.job, false); + SET_FOREACH(other, u->dependencies[UNIT_CONFLICTED_BY], i) + if (other->job && + (other->job->type == JOB_START || + other->job->type == JOB_VERIFY_ACTIVE || + other->job->type == JOB_RELOAD_OR_START)) { + job_finish_and_invalidate(other->job, JOB_DEPENDENCY); + recursed = true; + } } } + /* Trigger OnFailure dependencies that are not generated by + * the unit itself. We don't tread JOB_CANCELED as failure in + * this context. And JOB_FAILURE is already handled by the + * unit itself. */ + if (result == JOB_TIMEOUT || result == JOB_DEPENDENCY) { + log_notice("Job %s/%s failed with result '%s'.", + u->id, + job_type_to_string(t), + job_result_to_string(result)); + + unit_trigger_on_failure(u); + } + +finish: /* Try to start the next jobs that can be started */ - SET_FOREACH(other, u->meta.dependencies[UNIT_AFTER], i) - if (other->meta.job) - job_add_to_run_queue(other->meta.job); - SET_FOREACH(other, u->meta.dependencies[UNIT_BEFORE], i) - if (other->meta.job) - job_add_to_run_queue(other->meta.job); + SET_FOREACH(other, u->dependencies[UNIT_AFTER], i) + if (other->job) + job_add_to_run_queue(other->job); + SET_FOREACH(other, u->dependencies[UNIT_BEFORE], i) + if (other->job) + job_add_to_run_queue(other->job); + + manager_check_finished(u->manager); + + return recursed; +} + +int job_start_timer(Job *j) { + struct itimerspec its; + struct epoll_event ev; + int fd, r; + assert(j); + + if (j->unit->job_timeout <= 0 || + j->timer_watch.type == WATCH_JOB_TIMER) + return 0; + + assert(j->timer_watch.type == WATCH_INVALID); + + if ((fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) { + r = -errno; + goto fail; + } + + zero(its); + timespec_store(&its.it_value, j->unit->job_timeout); + + if (timerfd_settime(fd, 0, &its, NULL) < 0) { + r = -errno; + goto fail; + } + + zero(ev); + ev.data.ptr = &j->timer_watch; + ev.events = EPOLLIN; + + if (epoll_ctl(j->manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) { + r = -errno; + goto fail; + } + + j->timer_watch.type = WATCH_JOB_TIMER; + j->timer_watch.fd = fd; + j->timer_watch.data.job = j; return 0; + +fail: + if (fd >= 0) + close_nointr_nofail(fd); + + return r; } void job_add_to_run_queue(Job *j) { @@ -567,6 +652,14 @@ char *job_dbus_path(Job *j) { return p; } +void job_timer_event(Job *j, uint64_t n_elapsed, Watch *w) { + assert(j); + assert(w == &j->timer_watch); + + log_warning("Job %s/%s timed out.", j->unit->id, job_type_to_string(j->type)); + job_finish_and_invalidate(j, JOB_TIMEOUT); +} + static const char* const job_state_table[_JOB_STATE_MAX] = { [JOB_WAITING] = "waiting", [JOB_RUNNING] = "running" @@ -589,7 +682,20 @@ DEFINE_STRING_TABLE_LOOKUP(job_type, JobType); static const char* const job_mode_table[_JOB_MODE_MAX] = { [JOB_FAIL] = "fail", [JOB_REPLACE] = "replace", - [JOB_ISOLATE] = "isolate" + [JOB_ISOLATE] = "isolate", + [JOB_IGNORE_DEPENDENCIES] = "ignore-dependencies", + [JOB_IGNORE_REQUIREMENTS] = "ignore-requirements" }; DEFINE_STRING_TABLE_LOOKUP(job_mode, JobMode); + +static const char* const job_result_table[_JOB_RESULT_MAX] = { + [JOB_DONE] = "done", + [JOB_CANCELED] = "canceled", + [JOB_TIMEOUT] = "timeout", + [JOB_FAILED] = "failed", + [JOB_DEPENDENCY] = "dependency", + [JOB_SKIPPED] = "skipped" +}; + +DEFINE_STRING_TABLE_LOOKUP(job_result, JobResult);