From 4c2f5842308e835beaff012aea1a77ebe0cf5ad8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 1 Apr 2019 18:48:20 +0200 Subject: [PATCH] core: hook up service unit type with the new clean operation The implementation is pretty straight-foward: when we get a request to clean some type of resources we fork off a process doing that, and while it is running we are in the "cleaning" state. --- src/basic/unit-def.c | 1 + src/basic/unit-def.h | 1 + src/core/execute.c | 54 +++++++++++ src/core/execute.h | 3 + src/core/load-fragment-gperf.gperf.m4 | 1 + src/core/service.c | 130 ++++++++++++++++++++++++-- src/core/service.h | 2 + 7 files changed, 183 insertions(+), 9 deletions(-) diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c index 3c0482366ef..0544fee9ecd 100644 --- a/src/basic/unit-def.c +++ b/src/basic/unit-def.c @@ -178,6 +178,7 @@ static const char* const service_state_table[_SERVICE_STATE_MAX] = { [SERVICE_FINAL_SIGKILL] = "final-sigkill", [SERVICE_FAILED] = "failed", [SERVICE_AUTO_RESTART] = "auto-restart", + [SERVICE_CLEANING] = "cleaning", }; DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState); diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h index b3e111da3f6..a7cdb97ad2b 100644 --- a/src/basic/unit-def.h +++ b/src/basic/unit-def.h @@ -117,6 +117,7 @@ typedef enum ServiceState { SERVICE_FINAL_SIGKILL, SERVICE_FAILED, SERVICE_AUTO_RESTART, + SERVICE_CLEANING, _SERVICE_STATE_MAX, _SERVICE_STATE_INVALID = -1 } ServiceState; diff --git a/src/core/execute.c b/src/core/execute.c index 1cbb2a83de4..623c86a68e0 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -4774,6 +4774,60 @@ void exec_context_revert_tty(ExecContext *c) { } } +int exec_context_get_clean_directories( + ExecContext *c, + char **prefix, + ExecCleanMask mask, + char ***ret) { + + _cleanup_strv_free_ char **l = NULL; + ExecDirectoryType t; + int r; + + assert(c); + assert(prefix); + assert(ret); + + for (t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) { + char **i; + + if (!FLAGS_SET(mask, 1U << t)) + continue; + + if (!prefix[t]) + continue; + + STRV_FOREACH(i, c->directories[t].paths) { + char *j; + + j = path_join(prefix[t], *i); + if (!j) + return -ENOMEM; + + r = strv_consume(&l, j); + if (r < 0) + return r; + } + } + + *ret = TAKE_PTR(l); + return 0; +} + +int exec_context_get_clean_mask(ExecContext *c, ExecCleanMask *ret) { + ExecCleanMask mask = 0; + + assert(c); + assert(ret); + + for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) + if (!strv_isempty(c->directories[t].paths)) + mask |= 1U << t; + + *ret = mask; + return 0; +} + void exec_status_start(ExecStatus *s, pid_t pid) { assert(s); diff --git a/src/core/execute.h b/src/core/execute.h index 1b4998a759b..91595c7f6bb 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -382,6 +382,9 @@ void exec_context_free_log_extra_fields(ExecContext *c); void exec_context_revert_tty(ExecContext *c); +int exec_context_get_clean_directories(ExecContext *c, char **prefix, ExecCleanMask mask, char ***ret); +int exec_context_get_clean_mask(ExecContext *c, ExecCleanMask *ret); + void exec_status_start(ExecStatus *s, pid_t pid); void exec_status_exit(ExecStatus *s, const ExecContext *context, pid_t pid, int code, int status); void exec_status_dump(const ExecStatus *s, FILE *f, const char *prefix); diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 19ee56662c5..76c50166b64 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -315,6 +315,7 @@ Service.TimeoutSec, config_parse_service_timeout, 0, Service.TimeoutStartSec, config_parse_service_timeout, 0, 0 Service.TimeoutStopSec, config_parse_sec_fix_0, 0, offsetof(Service, timeout_stop_usec) Service.TimeoutAbortSec, config_parse_service_timeout_abort, 0, 0 +Service.TimeoutCleanSec, config_parse_sec, 0, offsetof(Service, timeout_clean_usec) Service.RuntimeMaxSec, config_parse_sec, 0, offsetof(Service, runtime_max_usec) Service.WatchdogSec, config_parse_sec, 0, offsetof(Service, watchdog_usec) m4_dnl The following five only exist for compatibility, they moved into Unit, see above diff --git a/src/core/service.c b/src/core/service.c index a7031df48a6..30687a967a4 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -30,6 +30,7 @@ #include "parse-util.h" #include "path-util.h" #include "process-util.h" +#include "rm-rf.h" #include "serialize.h" #include "service.h" #include "signal-util.h" @@ -59,7 +60,8 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = { [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING, [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING, [SERVICE_FAILED] = UNIT_FAILED, - [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING + [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING, + [SERVICE_CLEANING] = UNIT_MAINTENANCE, }; /* For Type=idle we never want to delay any other jobs, hence we @@ -80,7 +82,8 @@ static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] = [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING, [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING, [SERVICE_FAILED] = UNIT_FAILED, - [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING + [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING, + [SERVICE_CLEANING] = UNIT_MAINTENANCE, }; static int service_dispatch_inotify_io(sd_event_source *source, int fd, uint32_t events, void *userdata); @@ -103,6 +106,7 @@ static void service_init(Unit *u) { s->timeout_abort_set = u->manager->default_timeout_abort_set; s->restart_usec = u->manager->default_restart_usec; s->runtime_max_usec = USEC_INFINITY; + s->timeout_clean_usec = USEC_INFINITY; s->type = _SERVICE_TYPE_INVALID; s->socket_fd = -1; s->stdin_fd = s->stdout_fd = s->stderr_fd = -1; @@ -787,8 +791,9 @@ static int service_load(Unit *u) { } static void service_dump(Unit *u, FILE *f, const char *prefix) { - char buf_restart[FORMAT_TIMESPAN_MAX], buf_start[FORMAT_TIMESPAN_MAX], buf_stop[FORMAT_TIMESPAN_MAX]; - char buf_runtime[FORMAT_TIMESPAN_MAX], buf_watchdog[FORMAT_TIMESPAN_MAX], buf_abort[FORMAT_TIMESPAN_MAX]; + char buf_restart[FORMAT_TIMESPAN_MAX], buf_start[FORMAT_TIMESPAN_MAX], buf_stop[FORMAT_TIMESPAN_MAX], + buf_runtime[FORMAT_TIMESPAN_MAX], buf_watchdog[FORMAT_TIMESPAN_MAX], buf_abort[FORMAT_TIMESPAN_MAX], + buf_clean[FORMAT_TIMESPAN_MAX]; ServiceExecCommand c; Service *s = SERVICE(u); const char *prefix2; @@ -802,6 +807,7 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { "%sService State: %s\n" "%sResult: %s\n" "%sReload Result: %s\n" + "%sClean Result: %s\n" "%sPermissionsStartOnly: %s\n" "%sRootDirectoryStartOnly: %s\n" "%sRemainAfterExit: %s\n" @@ -814,6 +820,7 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { prefix, service_state_to_string(s->state), prefix, service_result_to_string(s->result), prefix, service_result_to_string(s->reload_result), + prefix, service_result_to_string(s->clean_result), prefix, yes_no(s->permissions_start_only), prefix, yes_no(s->root_directory_start_only), prefix, yes_no(s->remain_after_exit), @@ -869,8 +876,10 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { prefix, format_timespan(buf_abort, sizeof(buf_abort), s->timeout_abort_usec, USEC_PER_SEC)); fprintf(f, + "%sTimeoutCleanSec: %s\n" "%sRuntimeMaxSec: %s\n" "%sWatchdogSec: %s\n", + prefix, format_timespan(buf_clean, sizeof(buf_clean), s->timeout_clean_usec, USEC_PER_SEC), prefix, format_timespan(buf_runtime, sizeof(buf_runtime), s->runtime_max_usec, USEC_PER_SEC), prefix, format_timespan(buf_watchdog, sizeof(buf_watchdog), s->watchdog_usec, USEC_PER_SEC)); @@ -1069,7 +1078,8 @@ static void service_set_state(Service *s, ServiceState state) { SERVICE_RELOAD, SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, - SERVICE_AUTO_RESTART)) + SERVICE_AUTO_RESTART, + SERVICE_CLEANING)) s->timer_event_source = sd_event_source_unref(s->timer_event_source); if (!IN_SET(state, @@ -1085,7 +1095,8 @@ static void service_set_state(Service *s, ServiceState state) { SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD, SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, + SERVICE_CLEANING)) { service_unwatch_control_pid(s); s->control_command = NULL; s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; @@ -1151,6 +1162,9 @@ static usec_t service_coldplug_timeout(Service *s) { case SERVICE_AUTO_RESTART: return usec_add(UNIT(s)->inactive_enter_timestamp.monotonic, s->restart_usec); + case SERVICE_CLEANING: + return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_clean_usec); + default: return USEC_INFINITY; } @@ -1188,13 +1202,14 @@ static int service_coldplug(Unit *u) { SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD, SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, + SERVICE_CLEANING)) { r = unit_watch_pid(UNIT(s), s->control_pid, false); if (r < 0) return r; } - if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART)) { + if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART, SERVICE_CLEANING)) { (void) unit_enqueue_rewatch_pids(u); (void) unit_setup_dynamic_creds(u); (void) unit_setup_exec_runtime(u); @@ -2368,7 +2383,7 @@ static int service_start(Unit *u) { * please! */ if (IN_SET(s->state, SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, SERVICE_CLEANING)) return -EAGAIN; /* Already on it! */ @@ -2455,6 +2470,12 @@ static int service_stop(Unit *u) { return 0; } + /* If we are currently cleaning, then abort it, brutally. */ + if (s->state == SERVICE_CLEANING) { + service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_SUCCESS); + return 0; + } + assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED)); service_enter_stop(s, SERVICE_SUCCESS); @@ -3563,6 +3584,14 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { service_enter_dead(s, f, true); break; + case SERVICE_CLEANING: + + if (s->clean_result == SERVICE_SUCCESS) + s->clean_result = f; + + service_enter_dead(s, SERVICE_SUCCESS, false); + break; + default: assert_not_reached("Uh, control process died at wrong time."); } @@ -3679,6 +3708,15 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us service_enter_restart(s); break; + case SERVICE_CLEANING: + log_unit_warning(UNIT(s), "Cleaning timed out. killing."); + + if (s->clean_result == SERVICE_SUCCESS) + s->clean_result = SERVICE_FAILURE_TIMEOUT; + + service_enter_signal(s, SERVICE_FINAL_SIGKILL, 0); + break; + default: assert_not_reached("Timeout at wrong time."); } @@ -4086,6 +4124,7 @@ static void service_reset_failed(Unit *u) { s->result = SERVICE_SUCCESS; s->reload_result = SERVICE_SUCCESS; + s->clean_result = SERVICE_SUCCESS; s->n_restarts = 0; s->flush_n_restarts = false; } @@ -4155,6 +4194,77 @@ static int service_exit_status(Unit *u) { return s->main_exec_status.status; } +static int service_clean(Unit *u, ExecCleanMask mask) { + _cleanup_strv_free_ char **l = NULL; + Service *s = SERVICE(u); + pid_t pid; + int r; + + assert(s); + assert(mask != 0); + + if (s->state != SERVICE_DEAD) + return -EBUSY; + + r = exec_context_get_clean_directories(&s->exec_context, u->manager->prefix, mask, &l); + if (r < 0) + return r; + + if (strv_isempty(l)) + return -EUNATCH; + + service_unwatch_control_pid(s); + s->clean_result = SERVICE_SUCCESS; + s->control_command = NULL; + s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; + + r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_clean_usec)); + if (r < 0) + goto fail; + + r = unit_fork_helper_process(UNIT(s), "(sd-rmrf)", &pid); + if (r < 0) + goto fail; + if (r == 0) { + int ret = EXIT_SUCCESS; + char **i; + + STRV_FOREACH(i, l) { + r = rm_rf(*i, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_MISSING_OK); + if (r < 0) { + log_error_errno(r, "Failed to remove '%s': %m", *i); + ret = EXIT_FAILURE; + } + } + + _exit(ret); + } + + r = unit_watch_pid(u, pid, true); + if (r < 0) + goto fail; + + s->control_pid = pid; + + service_set_state(s, SERVICE_CLEANING); + + return 0; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to initiate cleaning: %m"); + s->clean_result = SERVICE_FAILURE_RESOURCES; + s->timer_event_source = sd_event_source_unref(s->timer_event_source); + return r; +} + +static int service_can_clean(Unit *u, ExecCleanMask *ret) { + Service *s = SERVICE(u); + + assert(s); + + return exec_context_get_clean_mask(&s->exec_context, ret); +} + static const char* const service_restart_table[_SERVICE_RESTART_MAX] = { [SERVICE_RESTART_NO] = "no", [SERVICE_RESTART_ON_SUCCESS] = "on-success", @@ -4255,6 +4365,8 @@ const UnitVTable service_vtable = { .can_reload = service_can_reload, .kill = service_kill, + .clean = service_clean, + .can_clean = service_can_clean, .serialize = service_serialize, .deserialize_item = service_deserialize_item, diff --git a/src/core/service.h b/src/core/service.h index d6182dbaa02..de56728c224 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -99,6 +99,7 @@ struct Service { usec_t timeout_stop_usec; usec_t timeout_abort_usec; bool timeout_abort_set; + usec_t timeout_clean_usec; usec_t runtime_max_usec; dual_timestamp watchdog_timestamp; @@ -147,6 +148,7 @@ struct Service { /* If we shut down, remember why */ ServiceResult result; ServiceResult reload_result; + ServiceResult clean_result; bool main_pid_known:1; bool main_pid_alien:1;