]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
homed: add concept for "adopting" an existing homedir locally
authorLennart Poettering <lennart@poettering.net>
Thu, 20 Feb 2025 08:52:18 +0000 (09:52 +0100)
committerLennart Poettering <lennart@poettering.net>
Fri, 7 Mar 2025 17:14:25 +0000 (18:14 +0100)
Currently homed scans /home/ via inotify for new .home + .homedir/
popping up to register as local users. Let's also add an explicit way to
request this form of "adoption": a bus call that takes a path and that
makes a home dir activatable locally.

(Usecase: you cross boot between two systems – let's say your traditional
fedora and your ParticleOS – and want to use the same homedir from both:
simply mount the /home dir from the other somewhere, and then hit
"homectl adopt /somewhere/lennart.home" and you have the user locally
too).

man/homectl.xml
man/org.freedesktop.home1.xml
shell-completion/bash/homectl
src/home/homectl.c
src/home/homed-manager-bus.c
src/home/homed-manager.c
src/home/homed-manager.h
src/libsystemd/sd-bus/bus-common-errors.c
src/libsystemd/sd-bus/bus-common-errors.h

index 568f077c05075047a38ba9a240262ed2c62b22d1..610c69ccc12c161d041be885674c9e5e002018b1 100644 (file)
         <xi:include href="version-info.xml" xpointer="v245"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><command>adopt</command> <replaceable>PATH</replaceable> [<replaceable>PATH</replaceable>…]</term>
+
+        <listitem><para>Adopts one or more existing home directories on the local system. Takes one or more paths to
+        <filename>*.home</filename> LUKS home directories or <filename>*.homedir/</filename> standalone home
+        directories or subvolumes previously created by <filename>systemd-homed</filename> and makes them
+        available locally for login. The referenced files are not moved. This is an alternative for moving
+        such home directories into <filename>/home/</filename> (where they would be picked up
+        automatically).</para>
+
+        <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><command>remove</command> <replaceable>USER</replaceable></term>
 
index e0e9eef982b297d133e9304727693ea503499b1e..1d73e78d39060c2d7b14d4bb510e51290f48c851 100644 (file)
@@ -70,6 +70,8 @@ node /org/freedesktop/home1 {
       @org.freedesktop.systemd1.Privileged("true")
       DeactivateHome(in  s user_name);
       RegisterHome(in  s user_record);
+      AdoptHome(in  s image_path,
+                in  t flags);
       UnregisterHome(in  s user_name);
       CreateHome(in  s user_record);
       CreateHomeEx(in  s user_record,
@@ -160,6 +162,8 @@ node /org/freedesktop/home1 {
 
     <variablelist class="dbus-method" generated="True" extra-ref="RegisterHome()"/>
 
+    <variablelist class="dbus-method" generated="True" extra-ref="AdoptHome()"/>
+
     <variablelist class="dbus-method" generated="True" extra-ref="UnregisterHome()"/>
 
     <variablelist class="dbus-method" generated="True" extra-ref="CreateHome()"/>
@@ -274,6 +278,12 @@ node /org/freedesktop/home1 {
       is useful to register home directories locally that are not located where
       <filename>systemd-homed.service</filename> would find them automatically.</para>
 
+      <para><function>AdoptHome()</function> also registers a new home directory locally. It takes a path to
+      a home directory itself, and will register it locally. This only works for <filename>*.home</filename>
+      and <filename>*.homedir/</filename> home directories. This operation is done automatically for all such
+      home areas showing up in <filename>/home/</filename>, but may be requested explicitly with this call for
+      directories elsewhere. The <varname>flags</varname> must be set to zero, currently.</para>
+
       <para><function>UnregisterHome()</function> unregisters an existing home directory. It takes a user
       name as argument and undoes what <function>RegisterHome()</function> does. It does not attempt to
       remove the home directory itself, it just unregisters it with the local system. Note that if the home
@@ -633,9 +643,9 @@ node /org/freedesktop/home1/home {
       <title>The Manager Object</title>
       <para><function>ActivateHomeIfReferenced()</function>, <function>RefHomeUnrestricted()</function>,
       <function>CreateHomeEx()</function>, and <function>UpdateHomeEx()</function> were added in version 256.</para>
-      <para><function>ListSigningKeys()</function>, <function>GetSigningKey()</function>,
-      <function>AddSigningKey()</function>, and <function>RemoveSigningKey()</function> were added in version
-      258.</para>
+      <para><function>AdoptHome()</function>, <function>ListSigningKeys()</function>,
+      <function>GetSigningKey()</function>, <function>AddSigningKey()</function>, and
+      <function>RemoveSigningKey()</function> were added in version 258.</para>
     </refsect2>
     <refsect2>
       <title>Home Objects</title>
index 6219f255948ef967337482c04ee8fbe98a1840af..3bd29fc808ba435f21b94d1d0e982bd4cfffd485 100644 (file)
@@ -173,7 +173,7 @@ _homectl() {
     fi
 
     local -A VERBS=(
-        [STANDALONE]='list lock-all'
+        [STANDALONE]='list lock-all adopt'
         [CREATE]='create'
         [NAMES]='activate deactivate inspect authenticate remove lock unlock'
         [NAME]='update passwd'
index 22570819a47c72459c65c2da3b424fd4174b7e27..123fcd99985cd1e209dbde33f352a2585932ae03 100644 (file)
@@ -1564,6 +1564,38 @@ static int create_home(int argc, char *argv[], void *userdata) {
         return create_home_common(/* input= */ NULL, /* show_enforce_password_policy_hint= */ true);
 }
 
+static int verb_adopt_home(int argc, char *argv[], void *userdata) {
+        int r, ret = 0;
+
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+        STRV_FOREACH(i, strv_skip(argv, 1)) {
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+                r = bus_message_new_method_call(bus, &m, bus_mgr, "AdoptHome");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_append(m, "st", *i, UINT64_C(0));
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to adopt home: %s", bus_error_message(&error, r));
+                        if (ret == 0)
+                                ret = r;
+                }
+        }
+
+        return ret;
+}
+
 static int remove_home(int argc, char *argv[], void *userdata) {
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         int r, ret = 0;
@@ -2808,6 +2840,7 @@ static int help(int argc, char *argv[], void *userdata) {
                "  inspect USER…                Inspect a home area\n"
                "  authenticate USER…           Authenticate a home area\n"
                "  create USER                  Create a home area\n"
+               "  adopt PATH…                  Add an existing home area on this system\n"
                "  remove USER…                 Remove a home area\n"
                "  update USER                  Update a home area\n"
                "  passwd USER                  Change password of a home area\n"
@@ -5242,6 +5275,7 @@ static int run(int argc, char *argv[]) {
                 { "inspect",            VERB_ANY, VERB_ANY, 0,            inspect_home             },
                 { "authenticate",       VERB_ANY, VERB_ANY, 0,            authenticate_home        },
                 { "create",             VERB_ANY, 2,        0,            create_home              },
+                { "adopt",              VERB_ANY, VERB_ANY, 0,            verb_adopt_home          },
                 { "remove",             2,        VERB_ANY, 0,            remove_home              },
                 { "update",             VERB_ANY, 2,        0,            update_home              },
                 { "passwd",             VERB_ANY, 2,        0,            passwd_home              },
index 5966758a56181af494ad15c871fa16ceb466a0f3..a3fe2b6a69a3daf6d55ed770e4c12da72513c772 100644 (file)
@@ -519,6 +519,47 @@ static int method_register_home(
         return sd_bus_reply_method_return(message, NULL);
 }
 
+static int method_adopt_home(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        Manager *m = ASSERT_PTR(userdata);
+        int r;
+
+        assert(message);
+
+        const char *image_path = NULL;
+        uint64_t flags = 0;
+        r = sd_bus_message_read(message, "st", &image_path, &flags);
+        if (r < 0)
+                return r;
+
+        if (!path_is_absolute(image_path) || !path_is_safe(image_path))
+                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified path is not absolute or not valid: %s", image_path);
+        if (flags != 0)
+                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags field must be zero.");
+
+        r = bus_verify_polkit_async(
+                        message,
+                        "org.freedesktop.home1.create-home",
+                        /* details= */ NULL,
+                        &m->polkit_registry,
+                        error);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return 1; /* Will call us back */
+
+        r = manager_adopt_home(m, image_path);
+        if (r == -EMEDIUMTYPE)
+                return sd_bus_error_setf(error, BUS_ERROR_UNRECOGNIZED_HOME_FORMAT, "Unrecognized format of home directory: %s", image_path);
+        if (r < 0)
+                return r;
+
+        return sd_bus_reply_method_return(message, NULL);
+}
+
 static int method_unregister_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         return generic_home_method(userdata, message, bus_home_method_unregister, error);
 }
@@ -1091,6 +1132,11 @@ static const sd_bus_vtable manager_vtable[] = {
                                 SD_BUS_NO_RESULT,
                                 method_register_home,
                                 SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("AdoptHome",
+                                SD_BUS_ARGS("s", image_path, "t", flags),
+                                SD_BUS_NO_RESULT,
+                                method_adopt_home,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
 
         /* Remove the JSON record from homed, but don't remove actual $HOME  */
         SD_BUS_METHOD_WITH_ARGS("UnregisterHome",
index a4512b8d266ecfd56bbf0951833f62a04428f051..9198368cfd1af5d5251a2e7a4816c8ba4b278b19 100644 (file)
@@ -177,7 +177,7 @@ static int on_home_inotify(sd_event_source *s, const struct inotify_event *event
                 else if (FLAGS_SET(event->mask, IN_MOVED_TO))
                         log_debug("%s has been moved in, having a look.", j);
 
-                (void) manager_assess_image(m, -1, get_home_root(), event->name);
+                (void) manager_assess_image(m, /* dir_fd= */ -EBADF, get_home_root(), event->name);
                 (void) bus_manager_emit_auto_login_changed(m);
         }
 
@@ -841,6 +841,10 @@ static int manager_assess_image(
         assert(dir_path);
         assert(dentry_name);
 
+        /* Maybe registers the specified .home or .homedir as a home we manage. Returns:
+         *
+         * -EMEDIUMTYPE: Not a dir with .homedir suffix or a file with .home suffix */
+
         luks_suffix = endswith(dentry_name, ".home");
         if (luks_suffix)
                 directory_suffix = NULL;
@@ -849,7 +853,7 @@ static int manager_assess_image(
 
         /* Early filter out: by name */
         if (!luks_suffix && !directory_suffix)
-                return 0;
+                return -EMEDIUMTYPE;
 
         path = path_join(dir_path, dentry_name);
         if (!path)
@@ -868,7 +872,7 @@ static int manager_assess_image(
                 _cleanup_free_ char *n = NULL, *user_name = NULL, *realm = NULL;
 
                 if (!luks_suffix)
-                        return 0;
+                        return -EMEDIUMTYPE;
 
                 n = strndup(dentry_name, luks_suffix - dentry_name);
                 if (!n)
@@ -876,7 +880,7 @@ static int manager_assess_image(
 
                 r = split_user_name_realm(n, &user_name, &realm);
                 if (r == -EINVAL) /* Not the right format: ignore */
-                        return 0;
+                        return -EMEDIUMTYPE;
                 if (r < 0)
                         return log_error_errno(r, "Failed to split image name into user name/realm: %m");
 
@@ -889,7 +893,7 @@ static int manager_assess_image(
                 UserStorage storage;
 
                 if (!directory_suffix)
-                        return 0;
+                        return -EMEDIUMTYPE;
 
                 n = strndup(dentry_name, directory_suffix - dentry_name);
                 if (!n)
@@ -897,7 +901,7 @@ static int manager_assess_image(
 
                 r = split_user_name_realm(n, &user_name, &realm);
                 if (r == -EINVAL) /* Not the right format: ignore */
-                        return 0;
+                        return -EMEDIUMTYPE;
                 if (r < 0)
                         return log_error_errno(r, "Failed to split image name into user name/realm: %m");
 
@@ -939,7 +943,26 @@ static int manager_assess_image(
                 return manager_add_home_by_image(m, user_name, realm, path, NULL, storage, st.st_uid);
         }
 
-        return 0;
+        return -EMEDIUMTYPE;
+}
+
+int manager_adopt_home(Manager *m, const char *path) {
+        int r;
+
+        assert(m);
+        assert(path);
+
+        _cleanup_free_ char *fn = NULL;
+        r = path_extract_filename(path, &fn);
+        if (r < 0)
+                return r;
+
+        _cleanup_free_ char *dir = NULL;
+        r = path_extract_directory(path, &dir);
+        if (r < 0)
+                return r;
+
+        return manager_assess_image(m, /* dir_fd= */ -EBADF, dir, fn);
 }
 
 int manager_enumerate_images(Manager *m) {
index 8f2c3d2fd7fa442d8919c9f7e4db8c4c6dac5e5c..b780094d0d8c4cae35591f0765f387253ec98c9e 100644 (file)
@@ -87,6 +87,8 @@ int manager_reschedule_rebalance(Manager *m);
 
 int manager_verify_user_record(Manager *m, UserRecord *hr);
 
+int manager_adopt_home(Manager *m, const char *path);
+
 int manager_acquire_key_pair(Manager *m);
 int manager_sign_user_record(Manager *m, UserRecord *u, UserRecord **ret, sd_bus_error *error);
 
index c0f5aff5ea2de3dc962051ec4532235ead5fe324..c66fded7f1aa165e994666916e2f1853d597c0d7 100644 (file)
@@ -151,6 +151,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
         SD_BUS_ERROR_MAP(BUS_ERROR_REBALANCE_NOT_NEEDED,         EALREADY),
         SD_BUS_ERROR_MAP(BUS_ERROR_HOME_NOT_REFERENCED,          EBADR),
         SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_KEY,                  ENOKEY),
+        SD_BUS_ERROR_MAP(BUS_ERROR_UNRECOGNIZED_HOME_FORMAT,     EMEDIUMTYPE),
 
         SD_BUS_ERROR_MAP(BUS_ERROR_NO_UPDATE_CANDIDATE,          EALREADY),
 
index 6322d68ad98144ec51bf6746f45a7fb98206cad7..9148f0aba9cec09fe61dec2b9d08e01f430f13fc 100644 (file)
 #define BUS_ERROR_REBALANCE_NOT_NEEDED         "org.freedesktop.home1.RebalanceNotNeeded"
 #define BUS_ERROR_HOME_NOT_REFERENCED          "org.freedesktop.home1.HomeNotReferenced"
 #define BUS_ERROR_NO_SUCH_KEY                  "org.freedesktop.home1.NoSuchKey"
+#define BUS_ERROR_UNRECOGNIZED_HOME_FORMAT     "org.freedesktop.home1.UnrecognizedHomeFormat"
 
 #define BUS_ERROR_NO_UPDATE_CANDIDATE          "org.freedesktop.sysupdate1.NoCandidate"