]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
homed: explicitly deactivate all home directories on shutdown
authorLennart Poettering <lennart@poettering.net>
Mon, 21 Sep 2020 16:25:46 +0000 (18:25 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 30 Sep 2020 12:37:52 +0000 (14:37 +0200)
Let's explicitly deactivate all home dirs on shutdown, in order to
properly synchronizing unmounting and avoiding blocking devices.

Previously, we'd rely on automatic deactivation when home directories
become unused. However, that scheme is asynchronous, and ongoing
deactviations might conflicts with attempts to unmount /home. Let's fix
that by providing an explicit service systemd-homed-activate.service
whose only job is to have a ExecStop= line that explicitly deactivates
all home directories on shutdown. This service can the be ordered after
home.mount and similar, ensuring that we'll first deactivate all homes
before deactivating /home itself during shutdown.

This is kept separate from systemd-homed.service so that it is possible
to restart systemd-homed.service without deactivating all home
directories.

Fixes: #16842
man/homectl.xml
man/org.freedesktop.home1.xml
src/home/homectl.c
src/home/homed-home.c
src/home/homed-manager-bus.c
src/home/homed-operation.h
units/meson.build
units/systemd-homed-activate.service [new file with mode: 0644]
units/systemd-homed.service.in

index 2ceb56e3f0d4e3d907f64cb476dec9682d30dc3e..23eaedd6c573e79c9d5826446b50ba6608fda416 100644 (file)
         their home directories are removed from memory.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><command>deactivate-all</command></term>
+
+        <listitem><para>Execute the <command>deactivate</command> command on all active home directories at
+        once. This operation is generally executed on system shut down (i.e. by <command>systemctl
+        poweroff</command> and related commands), to ensure all active user's home directories are fully
+        deactivated before <filename>/home/</filename> and related file systems are unmounted.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><command>with</command> <replaceable>USER</replaceable> <replaceable>COMMAND…</replaceable></term>
 
index 73f868248097ca9dfc1509659a983c857254ed57..8d3defbfe035c986412fc6ce4e5931e921ab010f 100644 (file)
@@ -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 {
 
     <variablelist class="dbus-method" generated="True" extra-ref="LockAllHomes()"/>
 
+    <variablelist class="dbus-method" generated="True" extra-ref="DeactivateAllHomes()"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="AutoLogin"/>
 
     <!--End of Autogenerated section-->
@@ -340,6 +343,9 @@ node /org/freedesktop/home1 {
       <para><function>LockAllHomes()</function> 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.</para>
+
+      <para><function>DeactivateAllHomes()</function> deactivates all home areas that are currently
+      active. This is usually invoked automatically shortly before system shutdown.</para>
     </refsect2>
 
     <refsect2>
index 35c98c9d6dd6a8e913af5d1e241488a71718839a..4629499504c22f5c78f1a283abda19946cf297a4 100644 (file)
@@ -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 },
                 {}
         };
 
index 328ec32fd368485fe4a66509b6ca2bb6a16485be..6d0f0fbd0e52b53d41365129851a3040c803ef00 100644 (file)
@@ -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,
                 };
index fa3acb5244a140667e340e39847efb1a94c03e3c..a599c582976c72e3b79011f24ffff5edd9c2c893 100644 (file)
@@ -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
 };
index 224de9185253eb81793450ff497ace920a4858c6..0771dc6be0e51e48cba01f2c065c482fa4f09381 100644 (file)
@@ -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. */
index 275daad3f4e8609d1c253590d7fd3ca544e2d9bf..08c39c99b343cc41f3944b32a59bab633d75b0dc 100644 (file)
@@ -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 (file)
index 0000000..3a5057d
--- /dev/null
@@ -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
index a14bb5b409db766cf995f0e76692ac5b57a82eb1..4b6a91c9846e130a3fcf01b06479e41346ebc396 100644 (file)
@@ -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