From: Lennart Poettering Date: Mon, 30 Aug 2021 11:23:20 +0000 (+0200) Subject: homed: retry deactivation every 15s until successful X-Git-Tag: v250-rc1~531^2~4 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=23cff6d4febea714b854996193f8ab93643ef5ec;p=thirdparty%2Fsystemd.git homed: retry deactivation every 15s until successful Fixes: #17445 --- diff --git a/src/home/homed-home.c b/src/home/homed-home.c index 41f8ea1299f..0260b8041ca 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -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", diff --git a/src/home/homed-home.h b/src/home/homed-home.h index cd3b01ba72a..e5a70e40923 100644 --- a/src/home/homed-home.h +++ b/src/home/homed-home.h @@ -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);