]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: hook up service unit type with the new clean operation
authorLennart Poettering <lennart@poettering.net>
Mon, 1 Apr 2019 16:48:20 +0000 (18:48 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 11 Jul 2019 10:18:51 +0000 (12:18 +0200)
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
src/basic/unit-def.h
src/core/execute.c
src/core/execute.h
src/core/load-fragment-gperf.gperf.m4
src/core/service.c
src/core/service.h

index 3c0482366ef399a5e6cd39af7c4bafc7dca729ed..0544fee9ecd4823998e45875e33419940f450e8c 100644 (file)
@@ -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);
index b3e111da3f624cef8233948b107dd9853ce74dac..a7cdb97ad2bd8dc3d8a8694a54930cfa61eb0a07 100644 (file)
@@ -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;
index 1cbb2a83de45132a8d7a1fbeac2bfd19e0bbc879..623c86a68e0b6c6be4a233d8a105682d5a81b4f6 100644 (file)
@@ -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);
 
index 1b4998a759b2fc1cb3ee5b7aca88e83f1471ce21..91595c7f6bb8c8cde07cbffcf9cf2b0483e534a0 100644 (file)
@@ -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);
index 19ee56662c5626a69049e070dc4794ab703d91cf..76c50166b6479f0200675912997107f2ab4ad1d5 100644 (file)
@@ -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
index a7031df48a674086caab96835220896773af01e9..30687a967a4d094308822bf426225819286314b4 100644 (file)
@@ -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,
index d6182dbaa0295016aeff0cd169714ccedba7281e..de56728c224ab51f302e470a44c7ab850c6a6617 100644 (file)
@@ -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;