diff --git a/man/homectl.xml b/man/homectl.xml index 2ceb56e3f0..23eaedd6c5 100644 --- a/man/homectl.xml +++ b/man/homectl.xml @@ -821,6 +821,15 @@ their home directories are removed from memory. + + deactivate-all + + Execute the deactivate command on all active home directories at + once. This operation is generally executed on system shut down (i.e. by systemctl + poweroff and related commands), to ensure all active user's home directories are fully + deactivated before /home/ and related file systems are unmounted. + + with USER COMMAND… diff --git a/man/org.freedesktop.home1.xml b/man/org.freedesktop.home1.xml index 73f8682480..8d3defbfe0 100644 --- a/man/org.freedesktop.home1.xml +++ b/man/org.freedesktop.home1.xml @@ -95,6 +95,7 @@ node /org/freedesktop/home1 { out h send_fd); ReleaseHome(in s user_name); LockAllHomes(); + DeactivateAllHomes(); properties: readonly a(sso) AutoLogin = [...]; }; @@ -156,6 +157,8 @@ node /org/freedesktop/home1 { + + @@ -340,6 +343,9 @@ node /org/freedesktop/home1 { LockAllHomes() locks all active home directories that only have references that opted into automatic suspending during system suspend. This is usually invoked automatically shortly before system suspend. + + DeactivateAllHomes() deactivates all home areas that are currently + active. This is usually invoked automatically shortly before system shutdown. diff --git a/src/home/homectl.c b/src/home/homectl.c index 35c98c9d6d..4629499504 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -1844,7 +1844,28 @@ static int lock_all_homes(int argc, char *argv[], void *userdata) { r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); if (r < 0) - return log_error_errno(r, "Failed to lock home: %s", bus_error_message(&error, r)); + return log_error_errno(r, "Failed to lock all homes: %s", bus_error_message(&error, r)); + + return 0; +} + +static int deactivate_all_homes(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + r = bus_message_new_method_call(bus, &m, bus_mgr, "DeactivateAllHomes"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); + if (r < 0) + return log_error_errno(r, "Failed to deactivate all homes: %s", bus_error_message(&error, r)); return 0; } @@ -1902,6 +1923,7 @@ static int help(int argc, char *argv[], void *userdata) { " lock USER… Temporarily lock an active home area\n" " unlock USER… Unlock a temporarily locked home area\n" " lock-all Lock all suitable home areas\n" + " deactivate-all Deactivate all active home areas\n" " with USER [COMMAND…] Run shell or command with access to a home area\n" "\n%4$sOptions:%5$s\n" " -h --help Show this help\n" @@ -3328,21 +3350,22 @@ static int redirect_bus_mgr(void) { static int run(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "list", VERB_ANY, 1, VERB_DEFAULT, list_homes }, - { "activate", 2, VERB_ANY, 0, activate_home }, - { "deactivate", 2, VERB_ANY, 0, deactivate_home }, - { "inspect", VERB_ANY, VERB_ANY, 0, inspect_home }, - { "authenticate", VERB_ANY, VERB_ANY, 0, authenticate_home }, - { "create", VERB_ANY, 2, 0, create_home }, - { "remove", 2, VERB_ANY, 0, remove_home }, - { "update", VERB_ANY, 2, 0, update_home }, - { "passwd", VERB_ANY, 2, 0, passwd_home }, - { "resize", 2, 3, 0, resize_home }, - { "lock", 2, VERB_ANY, 0, lock_home }, - { "unlock", 2, VERB_ANY, 0, unlock_home }, - { "with", 2, VERB_ANY, 0, with_home }, - { "lock-all", VERB_ANY, 1, 0, lock_all_homes }, + { "help", VERB_ANY, VERB_ANY, 0, help }, + { "list", VERB_ANY, 1, VERB_DEFAULT, list_homes }, + { "activate", 2, VERB_ANY, 0, activate_home }, + { "deactivate", 2, VERB_ANY, 0, deactivate_home }, + { "inspect", VERB_ANY, VERB_ANY, 0, inspect_home }, + { "authenticate", VERB_ANY, VERB_ANY, 0, authenticate_home }, + { "create", VERB_ANY, 2, 0, create_home }, + { "remove", 2, VERB_ANY, 0, remove_home }, + { "update", VERB_ANY, 2, 0, update_home }, + { "passwd", VERB_ANY, 2, 0, passwd_home }, + { "resize", 2, 3, 0, resize_home }, + { "lock", 2, VERB_ANY, 0, lock_home }, + { "unlock", 2, VERB_ANY, 0, unlock_home }, + { "with", 2, VERB_ANY, 0, with_home }, + { "lock-all", VERB_ANY, 1, 0, lock_all_homes }, + { "deactivate-all", VERB_ANY, 1, 0, deactivate_all_homes }, {} }; diff --git a/src/home/homed-home.c b/src/home/homed-home.c index 328ec32fd3..6d0f0fbd0e 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -2482,6 +2482,50 @@ static int home_dispatch_lock_all(Home *h, Operation *o) { return 1; } +static int home_dispatch_deactivate_all(Home *h, Operation *o) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(h); + assert(o); + assert(o->type == OPERATION_DEACTIVATE_ALL); + + switch (home_get_state(h)) { + + case HOME_UNFIXATED: + case HOME_ABSENT: + case HOME_INACTIVE: + case HOME_DIRTY: + log_info("Home %s is already deactivated.", h->user_name); + r = 1; /* done */ + break; + + case HOME_LOCKED: + log_info("Home %s is currently locked, not deactivating.", h->user_name); + r = 1; /* done */ + break; + + case HOME_ACTIVE: + log_info("Deactivating home %s.", h->user_name); + r = home_deactivate_internal(h, false, &error); + break; + + default: + /* All other cases means we are currently executing an operation, which means the job remains + * pending. */ + return 0; + } + + assert(!h->current_operation); + + if (r != 0) /* failure or completed */ + operation_result(o, r, &error); + else /* ongoing */ + h->current_operation = operation_ref(o); + + return 1; +} + static int home_dispatch_pipe_eof(Home *h, Operation *o) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -2579,6 +2623,7 @@ static int on_pending(sd_event_source *s, void *userdata) { [OPERATION_ACQUIRE] = home_dispatch_acquire, [OPERATION_RELEASE] = home_dispatch_release, [OPERATION_LOCK_ALL] = home_dispatch_lock_all, + [OPERATION_DEACTIVATE_ALL] = home_dispatch_deactivate_all, [OPERATION_PIPE_EOF] = home_dispatch_pipe_eof, [OPERATION_DEACTIVATE_FORCE] = home_dispatch_deactivate_force, }; diff --git a/src/home/homed-manager-bus.c b/src/home/homed-manager-bus.c index fa3acb5244..a599c58297 100644 --- a/src/home/homed-manager-bus.c +++ b/src/home/homed-manager-bus.c @@ -591,7 +591,45 @@ static int method_lock_all_homes(sd_bus_message *message, void *userdata, sd_bus } if (waiting) /* At least one lock operation was enqeued, let's leave here without a reply: it will - * be sent as soon as the last of the lock operations completed. */ + * be sent as soon as the last of the lock operations completed. */ + return 1; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_deactivate_all_homes(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(operation_unrefp) Operation *o = NULL; + bool waiting = false; + Manager *m = userdata; + Home *h; + int r; + + assert(m); + + /* This is called from systemd-homed-activate.service's ExecStop= command to ensure that all home + * directories are shutdown before the system goes down. Note that we don't do this from + * systemd-homed.service itself since we want to allow restarting of it without tearing down all home + * directories. */ + + HASHMAP_FOREACH(h, m->homes_by_name) { + + if (!o) { + o = operation_new(OPERATION_DEACTIVATE_ALL, message); + if (!o) + return -ENOMEM; + } + + log_info("Automatically deactivating home of user %s.", h->user_name); + + r = home_schedule_operation(h, o, error); + if (r < 0) + return r; + + waiting = true; + } + + if (waiting) /* At least one lock operation was enqeued, let's leave here without a reply: it will be + * sent as soon as the last of the deactivation operations completed. */ return 1; return sd_bus_reply_method_return(message, NULL); @@ -804,6 +842,7 @@ static const sd_bus_vtable manager_vtable[] = { /* An operation that acts on all homes that allow it */ SD_BUS_METHOD("LockAllHomes", NULL, NULL, method_lock_all_homes, 0), + SD_BUS_METHOD("DeactivateAllHomes", NULL, NULL, method_deactivate_all_homes, 0), SD_BUS_VTABLE_END }; diff --git a/src/home/homed-operation.h b/src/home/homed-operation.h index 224de91852..0771dc6be0 100644 --- a/src/home/homed-operation.h +++ b/src/home/homed-operation.h @@ -9,6 +9,7 @@ typedef enum OperationType { OPERATION_ACQUIRE, /* enqueued on AcquireHome() */ OPERATION_RELEASE, /* enqueued on ReleaseHome() */ OPERATION_LOCK_ALL, /* enqueued on LockAllHomes() */ + OPERATION_DEACTIVATE_ALL, /* enqueued on DeactivateAllHomes() */ OPERATION_PIPE_EOF, /* enqueued when we see EOF on the per-home reference pipes */ OPERATION_DEACTIVATE_FORCE, /* enqueued on hard $HOME unplug */ OPERATION_IMMEDIATE, /* this is never enqueued, it's just a marker we immediately started executing an operation without enqueuing anything first. */ diff --git a/units/meson.build b/units/meson.build index 275daad3f4..08c39c99b3 100644 --- a/units/meson.build +++ b/units/meson.build @@ -102,6 +102,7 @@ units = [ ['systemd-firstboot.service', 'ENABLE_FIRSTBOOT', 'sysinit.target.wants/'], ['systemd-halt.service', ''], + ['systemd-homed-activate.service', 'ENABLE_HOMED'], ['systemd-initctl.socket', 'HAVE_SYSV_COMPAT', 'sockets.target.wants/'], ['systemd-journal-catalog-update.service', '', diff --git a/units/systemd-homed-activate.service b/units/systemd-homed-activate.service new file mode 100644 index 0000000000..3a5057d3aa --- /dev/null +++ b/units/systemd-homed-activate.service @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: LGPL-2.1+ +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Home Area Activation +Documentation=man:systemd-homed.service(8) +After=home.mount systemd-homed.service +Before=systemd-user-sessions.service + +[Service] +ExecStop=homectl deactivate-all +RemainAfterExit=true +Type=oneshot + +[Install] +WantedBy=systemd-homed.service +Also=systemd-homed.service diff --git a/units/systemd-homed.service.in b/units/systemd-homed.service.in index a14bb5b409..4b6a91c984 100644 --- a/units/systemd-homed.service.in +++ b/units/systemd-homed.service.in @@ -39,4 +39,4 @@ SystemCallFilter=@system-service @mount [Install] WantedBy=multi-user.target Alias=dbus-org.freedesktop.home1.service -Also=systemd-userdbd.service +Also=systemd-homed-activate.service systemd-userdbd.service