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