diff --git a/src/core/job.c b/src/core/job.c index 4581dd520ee..32f6fc1a50f 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -612,6 +612,8 @@ int job_run_and_invalidate(Job *j) { r = job_finish_and_invalidate(j, JOB_UNSUPPORTED, true, false); else if (r == -ENOLINK) r = job_finish_and_invalidate(j, JOB_DEPENDENCY, true, false); + else if (r == -ESTALE) + r = job_finish_and_invalidate(j, JOB_ONCE, true, false); else if (r == -EAGAIN) job_set_state(j, JOB_WAITING); else if (r < 0) @@ -631,6 +633,7 @@ _pure_ static const char *job_get_status_message_format(Unit *u, JobType t, JobR [JOB_ASSERT] = "Assertion failed for %s.", [JOB_UNSUPPORTED] = "Starting of %s not supported.", [JOB_COLLECTED] = "Unnecessary job for %s was removed.", + [JOB_ONCE] = "Unit %s has been started before and cannot be started again." }; static const char *const generic_finished_stop_job[_JOB_RESULT_MAX] = { [JOB_DONE] = "Stopped %s.", @@ -690,6 +693,7 @@ static const struct { [JOB_ASSERT] = { ANSI_HIGHLIGHT_YELLOW, "ASSERT" }, [JOB_UNSUPPORTED] = { ANSI_HIGHLIGHT_YELLOW, "UNSUPP" }, /* JOB_COLLECTED */ + [JOB_ONCE] = { ANSI_HIGHLIGHT_RED, " ONCE " }, }; static void job_print_status_message(Unit *u, JobType t, JobResult result) { @@ -747,6 +751,7 @@ static void job_log_status_message(Unit *u, JobType t, JobResult result) { [JOB_ASSERT] = LOG_WARNING, [JOB_UNSUPPORTED] = LOG_WARNING, [JOB_COLLECTED] = LOG_INFO, + [JOB_ONCE] = LOG_ERR, }; assert(u); @@ -1523,6 +1528,7 @@ static const char* const job_result_table[_JOB_RESULT_MAX] = { [JOB_ASSERT] = "assert", [JOB_UNSUPPORTED] = "unsupported", [JOB_COLLECTED] = "collected", + [JOB_ONCE] = "once", }; DEFINE_STRING_TABLE_LOOKUP(job_result, JobResult); diff --git a/src/core/job.h b/src/core/job.h index 87f8d63c82e..ccb8e1b6741 100644 --- a/src/core/job.h +++ b/src/core/job.h @@ -96,6 +96,7 @@ enum JobResult { JOB_ASSERT, /* Couldn't start a unit, because an assert didn't hold */ JOB_UNSUPPORTED, /* Couldn't start a unit, because the unit type is not supported on the system */ JOB_COLLECTED, /* Job was garbage collected, since nothing needed it anymore */ + JOB_ONCE, /* Unit was started before, and hence can't be started again */ _JOB_RESULT_MAX, _JOB_RESULT_INVALID = -1 }; diff --git a/src/core/scope.c b/src/core/scope.c index 6c8c5a57ba0..1469cebff92 100644 --- a/src/core/scope.c +++ b/src/core/scope.c @@ -587,6 +587,7 @@ const UnitVTable scope_vtable = { .can_transient = true, .can_delegate = true, + .once_only = true, .init = scope_init, .load = scope_load, diff --git a/src/core/unit.c b/src/core/unit.c index 4c7e2cb8ae5..09ed43a104c 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -1753,6 +1753,7 @@ static bool unit_verify_deps(Unit *u) { * -EINVAL: Unit not loaded * -EOPNOTSUPP: Unit type not supported * -ENOLINK: The necessary dependencies are not fulfilled. + * -ESTALE: This unit has been started before and can't be started a second time */ int unit_start(Unit *u) { UnitActiveState state; @@ -1772,6 +1773,10 @@ int unit_start(Unit *u) { if (u->load_state != UNIT_LOADED) return -EINVAL; + /* Refuse starting scope units more than once */ + if (UNIT_VTABLE(u)->once_only && dual_timestamp_is_set(&u->inactive_enter_timestamp)) + return -ESTALE; + /* If the conditions failed, don't do anything at all. If we * already are activating this call might still be useful to * speed up activation in case there is some hold-off time, @@ -1835,6 +1840,10 @@ bool unit_can_start(Unit *u) { if (!unit_supported(u)) return false; + /* Scope units may be started only once */ + if (UNIT_VTABLE(u)->once_only && dual_timestamp_is_set(&u->inactive_exit_timestamp)) + return false; + return !!UNIT_VTABLE(u)->start; } diff --git a/src/core/unit.h b/src/core/unit.h index 89e9faa021d..26194ef35a0 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -561,6 +561,9 @@ struct UnitVTable { /* True if cgroup delegation is permissible */ bool can_delegate:1; + /* True if units of this type shall be startable only once and then never again */ + bool once_only:1; + /* True if queued jobs of this type should be GC'ed if no other job needs them anymore */ bool gc_jobs:1; }; diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 1d3f991bef1..a3b9bee6ce7 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -1900,8 +1900,6 @@ finish: } static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const* extra_args) { - int r = 0; - assert(d->result); if (!quiet) { @@ -1919,6 +1917,8 @@ static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const* log_error("Operation on or unit type of %s not supported on this system.", strna(d->name)); else if (streq(d->result, "collected")) log_error("Queued job for %s was garbage collected.", strna(d->name)); + else if (streq(d->result, "once")) + log_error("Unit %s was started already once and can't be started again.", strna(d->name)); else if (!STR_IN_SET(d->result, "done", "skipped")) { if (d->name) { _cleanup_free_ char *result = NULL; @@ -1935,21 +1935,24 @@ static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const* } if (STR_IN_SET(d->result, "canceled", "collected")) - r = -ECANCELED; + return -ECANCELED; else if (streq(d->result, "timeout")) - r = -ETIME; + return -ETIME; else if (streq(d->result, "dependency")) - r = -EIO; + return -EIO; else if (streq(d->result, "invalid")) - r = -ENOEXEC; + return -ENOEXEC; else if (streq(d->result, "assert")) - r = -EPROTO; + return -EPROTO; else if (streq(d->result, "unsupported")) - r = -EOPNOTSUPP; - else if (!STR_IN_SET(d->result, "done", "skipped")) - r = -EIO; + return -EOPNOTSUPP; + else if (streq(d->result, "once")) + return -ESTALE; + else if (STR_IN_SET(d->result, "done", "skipped")) + return 0; - return r; + log_debug("Unexpected job result, assuming server side newer than us: %s", d->result); + return -EIO; } int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_args) {