]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
homed: retry deactivation every 15s until successful
authorLennart Poettering <lennart@poettering.net>
Mon, 30 Aug 2021 11:23:20 +0000 (13:23 +0200)
committerLennart Poettering <lennart@poettering.net>
Mon, 11 Oct 2021 14:00:34 +0000 (16:00 +0200)
Fixes: #17445
src/home/homed-home.c
src/home/homed-home.h

index 41f8ea1299ffc72cd80d0d878a1327bb6a2b3d45..0260b8041ca710043759c5c36e6d5a760bd40b02 100644 (file)
@@ -38,6 +38,9 @@
 #include "user-record.h"
 #include "user-util.h"
 
+/* Retry to deactivate home directories again and again every 15s until it works */
+#define RETRY_DEACTIVATE_USEC (15U * USEC_PER_SEC)
+
 #define HOME_USERS_MAX 500
 #define PENDING_OPERATIONS_MAX 100
 
@@ -206,6 +209,8 @@ Home *home_free(Home *h) {
 
         safe_close(h->pin_fd);
 
+        h->retry_deactivate_event_source = sd_event_source_disable_unref(h->retry_deactivate_event_source);
+
         return mfree(h);
 }
 
@@ -359,7 +364,83 @@ static void home_update_pin_fd(Home *h, HomeState state) {
         if (state < 0)
                 state = home_get_state(h);
 
-        return HOME_STATE_IS_ACTIVE(state) ? home_pin(h) : home_unpin(h);
+        return HOME_STATE_SHALL_PIN(state) ? home_pin(h) : home_unpin(h);
+}
+
+static void home_maybe_stop_retry_deactivate(Home *h, HomeState state) {
+        assert(h);
+
+        /* Free the deactivation retry event source if we won't need it anymore. Specifically, we'll free the
+         * event source whenever the home directory is already deactivated (and we thus where successful) or
+         * if we start executing an operation that indicates that the home directory is going to be used or
+         * operated on again. Also, if the home is referenced again stop the timer */
+
+        if (HOME_STATE_MAY_RETRY_DEACTIVATE(state) &&
+            !h->ref_event_source_dont_suspend &&
+            !h->ref_event_source_please_suspend)
+                return;
+
+        h->retry_deactivate_event_source = sd_event_source_disable_unref(h->retry_deactivate_event_source);
+}
+
+static int home_deactivate_internal(Home *h, bool force, sd_bus_error *error);
+static void home_start_retry_deactivate(Home *h);
+
+static int home_on_retry_deactivate(sd_event_source *s, uint64_t usec, void *userdata) {
+        Home *h = userdata;
+        HomeState state;
+
+        assert(s);
+        assert(h);
+
+        /* 15s after the last attempt to deactivate the home directory passed. Let's try it one more time. */
+
+        h->retry_deactivate_event_source = sd_event_source_disable_unref(h->retry_deactivate_event_source);
+
+        state = home_get_state(h);
+        if (!HOME_STATE_MAY_RETRY_DEACTIVATE(state))
+                return 0;
+
+        if (IN_SET(state, HOME_ACTIVE, HOME_LINGERING)) {
+                log_info("Again trying to deactivate home directory.");
+
+                /* If we are not executing any operation, let's start deactivating now. Note that this will
+                 * restart our timer again, we are gonna be called again if this doesn't work. */
+                (void) home_deactivate_internal(h, /* force= */ false, NULL);
+        } else
+                /* if we are executing an operation (specifically, area already running a deactivation
+                 * operation), then simply reque the timer, so that we retry again. */
+                home_start_retry_deactivate(h);
+
+        return 0;
+}
+
+static void home_start_retry_deactivate(Home *h) {
+        int r;
+
+        assert(h);
+        assert(h->manager);
+
+        /* Alrady allocated? */
+        if (h->retry_deactivate_event_source)
+                return;
+
+        /* If the home directory is being used now don't start the timer */
+        if (h->ref_event_source_dont_suspend || h->ref_event_source_please_suspend)
+                return;
+
+        r = sd_event_add_time_relative(
+                        h->manager->event,
+                        &h->retry_deactivate_event_source,
+                        CLOCK_MONOTONIC,
+                        RETRY_DEACTIVATE_USEC,
+                        1*USEC_PER_MINUTE,
+                        home_on_retry_deactivate,
+                        h);
+        if (r < 0)
+                return (void) log_warning_errno(r, "Failed to install retry-deactivate event source, ignoring: %m");
+
+        (void) sd_event_source_set_description(h->retry_deactivate_event_source, "retry-deactivate");
 }
 
 static void home_set_state(Home *h, HomeState state) {
@@ -377,6 +458,7 @@ static void home_set_state(Home *h, HomeState state) {
                  home_state_to_string(new_state));
 
         home_update_pin_fd(h, new_state);
+        home_maybe_stop_retry_deactivate(h, new_state);
 
         if (HOME_STATE_IS_EXECUTING_OPERATION(old_state) && !HOME_STATE_IS_EXECUTING_OPERATION(new_state)) {
                 /* If we just finished executing some operation, process the queue of pending operations. And
@@ -1196,6 +1278,7 @@ int home_fixate(Home *h, UserRecord *secret, sd_bus_error *error) {
         case HOME_INACTIVE:
         case HOME_DIRTY:
         case HOME_ACTIVE:
+        case HOME_LINGERING:
         case HOME_LOCKED:
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_ALREADY_FIXATED, "Home %s is already fixated.", h->user_name);
         case HOME_UNFIXATED:
@@ -1237,6 +1320,11 @@ int home_activate(Home *h, UserRecord *secret, sd_bus_error *error) {
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name);
         case HOME_ACTIVE:
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_ALREADY_ACTIVE, "Home %s is already active.", h->user_name);
+        case HOME_LINGERING:
+                /* If we are lingering, i.e. active but are supposed to be deactivated, then cancel this
+                 * timer if the user explicitly asks us to be active */
+                h->retry_deactivate_event_source = sd_event_source_disable_unref(h->retry_deactivate_event_source);
+                return 0;
         case HOME_LOCKED:
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
         case HOME_INACTIVE:
@@ -1283,6 +1371,7 @@ int home_authenticate(Home *h, UserRecord *secret, sd_bus_error *error) {
         case HOME_INACTIVE:
         case HOME_DIRTY:
         case HOME_ACTIVE:
+        case HOME_LINGERING:
                 break;
         default:
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
@@ -1292,7 +1381,7 @@ int home_authenticate(Home *h, UserRecord *secret, sd_bus_error *error) {
         if (r < 0)
                 return r;
 
-        return home_authenticate_internal(h, secret, state == HOME_ACTIVE ? HOME_AUTHENTICATING_WHILE_ACTIVE : HOME_AUTHENTICATING, error);
+        return home_authenticate_internal(h, secret, HOME_STATE_IS_ACTIVE(state) ? HOME_AUTHENTICATING_WHILE_ACTIVE : HOME_AUTHENTICATING, error);
 }
 
 static int home_deactivate_internal(Home *h, bool force, sd_bus_error *error) {
@@ -1303,14 +1392,19 @@ static int home_deactivate_internal(Home *h, bool force, sd_bus_error *error) {
         home_unpin(h); /* unpin so that we can deactivate */
 
         r = home_start_work(h, force ? "deactivate-force" : "deactivate", h->record, NULL);
-        if (r < 0) {
+        if (r < 0)
                 /* Operation failed before it even started, reacquire pin fd, if state still dictates so */
                 home_update_pin_fd(h, _HOME_STATE_INVALID);
-                return r;
+        else {
+                home_set_state(h, HOME_DEACTIVATING);
+                r = 0;
         }
 
-        home_set_state(h, HOME_DEACTIVATING);
-        return 0;
+        /* Let's start a timer to retry deactivation in 15. We'll stop the timer once we manage to deactivate
+         * the home directory again, or we we start any other operation. */
+        home_start_retry_deactivate(h);
+
+        return r;
 }
 
 int home_deactivate(Home *h, bool force, sd_bus_error *error) {
@@ -1325,6 +1419,7 @@ int home_deactivate(Home *h, bool force, sd_bus_error *error) {
         case HOME_LOCKED:
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
         case HOME_ACTIVE:
+        case HOME_LINGERING:
                 break;
         default:
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
@@ -1361,6 +1456,7 @@ int home_create(Home *h, UserRecord *secret, sd_bus_error *error) {
         case HOME_ABSENT:
                 break;
         case HOME_ACTIVE:
+        case HOME_LINGERING:
         case HOME_LOCKED:
         default:
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "Home %s is currently being used, or an operation on home %s is currently being executed.", h->user_name, h->user_name);
@@ -1399,6 +1495,7 @@ int home_remove(Home *h, sd_bus_error *error) {
         case HOME_DIRTY:
                 break;
         case HOME_ACTIVE:
+        case HOME_LINGERING:
         default:
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "Home %s is currently being used, or an operation on home %s is currently being executed.", h->user_name, h->user_name);
         }
@@ -1537,6 +1634,7 @@ int home_update(Home *h, UserRecord *hr, sd_bus_error *error) {
         case HOME_INACTIVE:
         case HOME_DIRTY:
         case HOME_ACTIVE:
+        case HOME_LINGERING:
                 break;
         default:
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
@@ -1550,7 +1648,7 @@ int home_update(Home *h, UserRecord *hr, sd_bus_error *error) {
         if (r < 0)
                 return r;
 
-        home_set_state(h, state == HOME_ACTIVE ? HOME_UPDATING_WHILE_ACTIVE : HOME_UPDATING);
+        home_set_state(h, HOME_STATE_IS_ACTIVE(state) ? HOME_UPDATING_WHILE_ACTIVE : HOME_UPDATING);
         return 0;
 }
 
@@ -1572,6 +1670,7 @@ int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, sd_bus_error *e
         case HOME_INACTIVE:
         case HOME_DIRTY:
         case HOME_ACTIVE:
+        case HOME_LINGERING:
                 break;
         default:
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
@@ -1620,7 +1719,7 @@ int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, sd_bus_error *e
         if (r < 0)
                 return r;
 
-        home_set_state(h, state == HOME_ACTIVE ? HOME_RESIZING_WHILE_ACTIVE : HOME_RESIZING);
+        home_set_state(h, HOME_STATE_IS_ACTIVE(state) ? HOME_RESIZING_WHILE_ACTIVE : HOME_RESIZING);
         return 0;
 }
 
@@ -1668,6 +1767,7 @@ int home_passwd(Home *h,
         case HOME_INACTIVE:
         case HOME_DIRTY:
         case HOME_ACTIVE:
+        case HOME_LINGERING:
                 break;
         default:
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
@@ -1733,7 +1833,7 @@ int home_passwd(Home *h,
         if (r < 0)
                 return r;
 
-        home_set_state(h, state == HOME_ACTIVE ? HOME_PASSWD_WHILE_ACTIVE : HOME_PASSWD);
+        home_set_state(h, HOME_STATE_IS_ACTIVE(state) ? HOME_PASSWD_WHILE_ACTIVE : HOME_PASSWD);
         return 0;
 }
 
@@ -1752,6 +1852,7 @@ int home_unregister(Home *h, sd_bus_error *error) {
         case HOME_DIRTY:
                 break;
         case HOME_ACTIVE:
+        case HOME_LINGERING:
         default:
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "Home %s is currently being used, or an operation on home %s is currently being executed.", h->user_name, h->user_name);
         }
@@ -1779,6 +1880,7 @@ int home_lock(Home *h, sd_bus_error *error) {
         case HOME_LOCKED:
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is already locked.", h->user_name);
         case HOME_ACTIVE:
+        case HOME_LINGERING:
                 break;
         default:
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
@@ -1819,6 +1921,7 @@ int home_unlock(Home *h, UserRecord *secret, sd_bus_error *error) {
         case HOME_ABSENT:
         case HOME_INACTIVE:
         case HOME_ACTIVE:
+        case HOME_LINGERING:
         case HOME_DIRTY:
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_LOCKED, "Home %s is not locked.", h->user_name);
         case HOME_LOCKED:
@@ -1841,7 +1944,7 @@ HomeState home_get_state(Home *h) {
         /* Otherwise, let's see if the home directory is mounted. If so, we assume for sure the home
          * directory is active */
         if (user_record_test_home_directory(h->record) == USER_TEST_MOUNTED)
-                return HOME_ACTIVE;
+                return h->retry_deactivate_event_source ? HOME_LINGERING : HOME_ACTIVE;
 
         /* And if we see the image being gone, we report this as absent */
         r = user_record_test_image_path(h->record);
@@ -2424,6 +2527,7 @@ static int home_dispatch_acquire(Home *h, Operation *o) {
                 break;
 
         case HOME_ACTIVE:
+        case HOME_LINGERING:
                 for_state = HOME_AUTHENTICATING_FOR_ACQUIRE;
                 call = home_authenticate_internal;
                 break;
@@ -2478,6 +2582,7 @@ static int home_dispatch_release(Home *h, Operation *o) {
                         break;
 
                 case HOME_ACTIVE:
+                case HOME_LINGERING:
                         r = home_deactivate_internal(h, false, &error);
                         break;
 
@@ -2522,6 +2627,7 @@ static int home_dispatch_lock_all(Home *h, Operation *o) {
                 break;
 
         case HOME_ACTIVE:
+        case HOME_LINGERING:
                 log_info("Locking home %s.", h->user_name);
                 r = home_lock(h, &error);
                 break;
@@ -2566,6 +2672,7 @@ static int home_dispatch_deactivate_all(Home *h, Operation *o) {
                 break;
 
         case HOME_ACTIVE:
+        case HOME_LINGERING:
                 log_info("Deactivating home %s.", h->user_name);
                 r = home_deactivate_internal(h, false, &error);
                 break;
@@ -2611,6 +2718,7 @@ static int home_dispatch_pipe_eof(Home *h, Operation *o) {
                 break;
 
         case HOME_ACTIVE:
+        case HOME_LINGERING:
                 r = home_deactivate_internal(h, false, &error);
                 if (r < 0)
                         log_warning_errno(r, "Failed to deactivate %s, ignoring: %s", h->user_name, bus_error_message(&error, r));
@@ -2652,6 +2760,7 @@ static int home_dispatch_deactivate_force(Home *h, Operation *o) {
 
         case HOME_ACTIVE:
         case HOME_LOCKED:
+        case HOME_LINGERING:
                 r = home_deactivate_internal(h, true, &error);
                 if (r < 0)
                         log_warning_errno(r, "Failed to forcibly deactivate %s, ignoring: %s", h->user_name, bus_error_message(&error, r));
@@ -2872,6 +2981,7 @@ static const char* const home_state_table[_HOME_STATE_MAX] = {
         [HOME_ACTIVATING_FOR_ACQUIRE]      = "activating-for-acquire",
         [HOME_DEACTIVATING]                = "deactivating",
         [HOME_ACTIVE]                      = "active",
+        [HOME_LINGERING]                   = "lingering",
         [HOME_LOCKING]                     = "locking",
         [HOME_LOCKED]                      = "locked",
         [HOME_UNLOCKING]                   = "unlocking",
index cd3b01ba72a29f9d219e6b2409d13132db3e2db7..e5a70e409236965b97cc42e2258f3da4d20e08d0 100644 (file)
@@ -21,6 +21,7 @@ typedef enum HomeState {
         HOME_ACTIVATING_FOR_ACQUIRE,  /* activating because Acquire() was called */
         HOME_DEACTIVATING,
         HOME_ACTIVE,                  /* logged in right now */
+        HOME_LINGERING,               /* not logged in anymore, but we didn't manage to deactivate (because some process keeps it busy?) but we'll keep trying */
         HOME_LOCKING,
         HOME_LOCKED,
         HOME_UNLOCKING,
@@ -43,6 +44,7 @@ typedef enum HomeState {
 static inline bool HOME_STATE_IS_ACTIVE(HomeState state) {
         return IN_SET(state,
                       HOME_ACTIVE,
+                      HOME_LINGERING,
                       HOME_UPDATING_WHILE_ACTIVE,
                       HOME_RESIZING_WHILE_ACTIVE,
                       HOME_PASSWD_WHILE_ACTIVE,
@@ -74,6 +76,33 @@ static inline bool HOME_STATE_IS_EXECUTING_OPERATION(HomeState state) {
                       HOME_AUTHENTICATING_FOR_ACQUIRE);
 }
 
+static inline bool HOME_STATE_SHALL_PIN(HomeState state) {
+        /* Like HOME_STATE_IS_ACTIVE() – but HOME_LINGERING is missing! */
+        return IN_SET(state,
+                      HOME_ACTIVE,
+                      HOME_UPDATING_WHILE_ACTIVE,
+                      HOME_RESIZING_WHILE_ACTIVE,
+                      HOME_PASSWD_WHILE_ACTIVE,
+                      HOME_AUTHENTICATING_WHILE_ACTIVE,
+                      HOME_AUTHENTICATING_FOR_ACQUIRE);
+}
+
+static inline bool HOME_STATE_MAY_RETRY_DEACTIVATE(HomeState state) {
+        /* Indicates when to leave the deactivate retry timer active */
+        return IN_SET(state,
+                      HOME_ACTIVE,
+                      HOME_LINGERING,
+                      HOME_DEACTIVATING,
+                      HOME_LOCKING,
+                      HOME_UNLOCKING,
+                      HOME_UNLOCKING_FOR_ACQUIRE,
+                      HOME_UPDATING_WHILE_ACTIVE,
+                      HOME_RESIZING_WHILE_ACTIVE,
+                      HOME_PASSWD_WHILE_ACTIVE,
+                      HOME_AUTHENTICATING_WHILE_ACTIVE,
+                      HOME_AUTHENTICATING_FOR_ACQUIRE);
+}
+
 struct Home {
         Manager *manager;
         char *user_name;
@@ -129,6 +158,9 @@ struct Home {
 
         /* An fd to the top-level home directory we keep while logged in, to keep the dir busy */
         int pin_fd;
+
+        /* A time event used to repeatedly try to unmount home dir after use if it didn't work on first try */
+        sd_event_source *retry_deactivate_event_source;
 };
 
 int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret);