]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
logind: split %t directory creation to a helper unit
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Sat, 9 Dec 2017 18:30:17 +0000 (19:30 +0100)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 25 Apr 2018 14:20:28 +0000 (16:20 +0200)
Unfortunately this needs a new binary to do the mount because there's just
too many special steps to outsource this to systemd-mount:
- EPERM needs to be treated specially
- UserRuntimeDir= setting must be obeyed
- SELinux label must be adjusted

This allows user@.service to be started independently of logind.
So 'systemctl start user@nnn' will start the user manager for user nnn.
Logind will start it too when the user logs in, and will stop it (unless
lingering is enabled) when the user logs out.

Fixes #7339.

meson.build
src/login/logind-user.c
src/login/meson.build
src/login/user-runtime-dir.c [new file with mode: 0644]
units/meson.build
units/user-runtime-dir@.service.in [new file with mode: 0644]
units/user@.service.in

index 734a289755866514d92c4497250157400938b877..426bd32991c69262d67f8191c1ed3c7282968728 100644 (file)
@@ -1673,6 +1673,14 @@ if conf.get('ENABLE_LOGIND') == 1
         endif
 endif
 
+executable('systemd-user-runtime-dir',
+           user_runtime_dir_sources,
+           include_directories : includes,
+           link_with : [libshared, liblogind_core],
+           install_rpath : rootlibexecdir,
+           install : true,
+           install_dir : rootlibexecdir)
+
 if conf.get('HAVE_PAM') == 1
         executable('systemd-user-sessions',
                    'src/user-sessions/user-sessions.c',
index 01b602dbc49b997996d34a864c0b96153680eb4a..712067c52c74488812e89ba559adf9ff7b51ab88 100644 (file)
@@ -319,55 +319,6 @@ int user_load(User *u) {
         return r;
 }
 
-static int user_mkdir_runtime_path(User *u) {
-        int r;
-
-        assert(u);
-
-        r = mkdir_safe_label("/run/user", 0755, 0, 0, MKDIR_WARN_MODE);
-        if (r < 0)
-                return log_error_errno(r, "Failed to create /run/user: %m");
-
-        if (path_is_mount_point(u->runtime_path, NULL, 0) <= 0) {
-                _cleanup_free_ char *t = NULL;
-
-                r = asprintf(&t, "mode=0700,uid=" UID_FMT ",gid=" GID_FMT ",size=%zu%s",
-                             u->uid, u->gid, u->manager->runtime_dir_size,
-                             mac_smack_use() ? ",smackfsroot=*" : "");
-                if (r < 0)
-                        return log_oom();
-
-                (void) mkdir_label(u->runtime_path, 0700);
-
-                r = mount("tmpfs", u->runtime_path, "tmpfs", MS_NODEV|MS_NOSUID, t);
-                if (r < 0) {
-                        if (!IN_SET(errno, EPERM, EACCES)) {
-                                r = log_error_errno(errno, "Failed to mount per-user tmpfs directory %s: %m", u->runtime_path);
-                                goto fail;
-                        }
-
-                        log_debug_errno(errno, "Failed to mount per-user tmpfs directory %s, assuming containerized execution, ignoring: %m", u->runtime_path);
-
-                        r = chmod_and_chown(u->runtime_path, 0700, u->uid, u->gid);
-                        if (r < 0) {
-                                log_error_errno(r, "Failed to change runtime directory ownership and mode: %m");
-                                goto fail;
-                        }
-                }
-
-                r = label_fix(u->runtime_path, 0);
-                if (r < 0)
-                        log_warning_errno(r, "Failed to fix label of '%s', ignoring: %m", u->runtime_path);
-        }
-
-        return 0;
-
-fail:
-        /* Try to clean up, but ignore errors */
-        (void) rmdir(u->runtime_path);
-        return r;
-}
-
 static int user_start_service(User *u) {
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         char *job;
@@ -414,15 +365,9 @@ int user_start(User *u) {
          */
         u->stopping = false;
 
-        if (!u->started) {
+        if (!u->started)
                 log_debug("Starting services for new user %s.", u->name);
 
-                /* Make XDG_RUNTIME_DIR */
-                r = user_mkdir_runtime_path(u);
-                if (r < 0)
-                        return r;
-        }
-
         /* Save the user data so far, because pam_systemd will read the
          * XDG_RUNTIME_DIR out of it while starting up systemd --user.
          * We need to do user_save_internal() because we have not
@@ -483,29 +428,6 @@ static int user_stop_service(User *u) {
         return r;
 }
 
-static int user_remove_runtime_path(User *u) {
-        int r;
-
-        assert(u);
-
-        r = rm_rf(u->runtime_path, 0);
-        if (r < 0)
-                log_error_errno(r, "Failed to remove runtime directory %s (before unmounting): %m", u->runtime_path);
-
-        /* Ignore cases where the directory isn't mounted, as that's
-         * quite possible, if we lacked the permissions to mount
-         * something */
-        r = umount2(u->runtime_path, MNT_DETACH);
-        if (r < 0 && !IN_SET(errno, EINVAL, ENOENT))
-                log_error_errno(errno, "Failed to unmount user runtime directory %s: %m", u->runtime_path);
-
-        r = rm_rf(u->runtime_path, REMOVE_ROOT);
-        if (r < 0)
-                log_error_errno(r, "Failed to remove runtime directory %s (after unmounting): %m", u->runtime_path);
-
-        return r;
-}
-
 int user_stop(User *u, bool force) {
         Session *s;
         int r = 0, k;
@@ -555,11 +477,6 @@ int user_finalize(User *u) {
                         r = k;
         }
 
-        /* Kill XDG_RUNTIME_DIR */
-        k = user_remove_runtime_path(u);
-        if (k < 0)
-                r = k;
-
         /* Clean SysV + POSIX IPC objects, but only if this is not a system user. Background: in many setups cronjobs
          * are run in full PAM and thus logind sessions, even if the code run doesn't belong to actual users but to
          * system components. Since enable RemoveIPC= globally for all users, we need to be a bit careful with such
index a616d5b934d36b8a5cc4746cd0550fdb454322fe..1e2cf49c431d131e7cc8d14360cd7d0d0ea882d4 100644 (file)
@@ -58,6 +58,11 @@ loginctl_sources = files('''
         sysfs-show.c
 '''.split())
 
+user_runtime_dir_sources = files('''
+        user-runtime-dir.c
+        logind.h
+'''.split())
+
 if conf.get('ENABLE_LOGIND') == 1
         logind_conf = configure_file(
                 input : 'logind.conf.in',
diff --git a/src/login/user-runtime-dir.c b/src/login/user-runtime-dir.c
new file mode 100644 (file)
index 0000000..1bb26c9
--- /dev/null
@@ -0,0 +1,170 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <stdint.h>
+#include <sys/mount.h>
+
+#include "fs-util.h"
+#include "label.h"
+#include "logind.h"
+#include "mkdir.h"
+#include "mount-util.h"
+#include "path-util.h"
+#include "rm-rf.h"
+#include "smack-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "user-util.h"
+
+static int gather_configuration(size_t *runtime_dir_size) {
+        Manager m = {};
+        int r;
+
+        manager_reset_config(&m);
+
+        r = manager_parse_config_file(&m);
+        if (r < 0)
+                log_warning_errno(r, "Failed to parse logind.conf: %m");
+
+        *runtime_dir_size = m.runtime_dir_size;
+        return 0;
+}
+
+static int user_mkdir_runtime_path(const char *runtime_path, uid_t uid, gid_t gid, size_t runtime_dir_size) {
+        int r;
+
+        assert(runtime_path);
+        assert(path_is_absolute(runtime_path));
+        assert(uid_is_valid(uid));
+        assert(gid_is_valid(gid));
+
+        r = mkdir_safe_label("/run/user", 0755, 0, 0, MKDIR_WARN_MODE);
+        if (r < 0)
+                return log_error_errno(r, "Failed to create /run/user: %m");
+
+        if (path_is_mount_point(runtime_path, NULL, 0) >= 0)
+                log_debug("%s is already a mount point", runtime_path);
+        else {
+                char options[sizeof("mode=0700,uid=,gid=,size=,smackfsroot=*")
+                             + DECIMAL_STR_MAX(uid_t)
+                             + DECIMAL_STR_MAX(gid_t)
+                             + DECIMAL_STR_MAX(size_t)];
+
+                xsprintf(options,
+                         "mode=0700,uid=" UID_FMT ",gid=" GID_FMT ",size=%zu%s",
+                         uid, gid, runtime_dir_size,
+                         mac_smack_use() ? ",smackfsroot=*" : "");
+
+                (void) mkdir_label(runtime_path, 0700);
+
+                r = mount("tmpfs", runtime_path, "tmpfs", MS_NODEV|MS_NOSUID, options);
+                if (r < 0) {
+                        if (!IN_SET(errno, EPERM, EACCES)) {
+                                r = log_error_errno(errno, "Failed to mount per-user tmpfs directory %s: %m", runtime_path);
+                                goto fail;
+                        }
+
+                        log_debug_errno(errno, "Failed to mount per-user tmpfs directory %s.\n"
+                                        "Assuming containerized execution, ignoring: %m", runtime_path);
+
+                        r = chmod_and_chown(runtime_path, 0700, uid, gid);
+                        if (r < 0) {
+                                log_error_errno(r, "Failed to change ownership and mode of \"%s\": %m", runtime_path);
+                                goto fail;
+                        }
+                }
+
+                r = label_fix(runtime_path, 0);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to fix label of \"%s\", ignoring: %m", runtime_path);
+        }
+
+        return 0;
+
+fail:
+        /* Try to clean up, but ignore errors */
+        (void) rmdir(runtime_path);
+        return r;
+}
+
+static int user_remove_runtime_path(const char *runtime_path) {
+        int r;
+
+        assert(runtime_path);
+        assert(path_is_absolute(runtime_path));
+
+        r = rm_rf(runtime_path, 0);
+        if (r < 0)
+                log_error_errno(r, "Failed to remove runtime directory %s (before unmounting): %m", runtime_path);
+
+        /* Ignore cases where the directory isn't mounted, as that's
+         * quite possible, if we lacked the permissions to mount
+         * something */
+        r = umount2(runtime_path, MNT_DETACH);
+        if (r < 0 && !IN_SET(errno, EINVAL, ENOENT))
+                log_error_errno(errno, "Failed to unmount user runtime directory %s: %m", runtime_path);
+
+        r = rm_rf(runtime_path, REMOVE_ROOT);
+        if (r < 0)
+                log_error_errno(r, "Failed to remove runtime directory %s (after unmounting): %m", runtime_path);
+
+        return r;
+}
+
+static int do_mount(const char *runtime_path, uid_t uid, gid_t gid) {
+        size_t runtime_dir_size;
+
+        assert_se(gather_configuration(&runtime_dir_size) == 0);
+
+        log_debug("Will mount %s owned by "UID_FMT":"GID_FMT, runtime_path, uid, gid);
+        return user_mkdir_runtime_path(runtime_path, uid, gid, runtime_dir_size);
+}
+
+static int do_umount(const char *runtime_path) {
+        log_debug("Will remove %s", runtime_path);
+        return user_remove_runtime_path(runtime_path);
+}
+
+int main(int argc, char *argv[]) {
+        const char *user;
+        uid_t uid;
+        gid_t gid;
+        char runtime_path[sizeof("/run/user") + DECIMAL_STR_MAX(uid_t)];
+        int r;
+
+        log_parse_environment();
+        log_open();
+
+        if (argc != 3) {
+                log_error("This program takes two arguments.");
+                return EXIT_FAILURE;
+        }
+        if (!STR_IN_SET(argv[1], "start", "stop")) {
+                log_error("First argument must be either \"start\" or \"stop\".");
+                return EXIT_FAILURE;
+        }
+
+        umask(0022);
+
+        user = argv[2];
+        r = get_user_creds(&user, &uid, &gid, NULL, NULL);
+        if (r < 0) {
+                log_error_errno(r,
+                                r == -ESRCH ? "No such user \"%s\"" :
+                                r == -ENOMSG ? "UID \"%s\" is invalid or has an invalid main group"
+                                             : "Failed to look up user \"%s\": %m",
+                                user);
+                return EXIT_FAILURE;
+        }
+
+        xsprintf(runtime_path, "/run/user/" UID_FMT, uid);
+
+        if (streq(argv[1], "start"))
+                r = do_mount(runtime_path, uid, gid);
+        else if (streq(argv[1], "stop"))
+                r = do_umount(runtime_path);
+        else
+                assert_not_reached("Unknown verb!");
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
index 1c89f369570f4de76886a66a97763db41883624a..fed2f107534816a293e8f7d792b1ba50a3a7672d 100644 (file)
@@ -218,6 +218,7 @@ in_units = [
          'multi-user.target.wants/'],
         ['systemd-vconsole-setup.service',       'ENABLE_VCONSOLE'],
         ['systemd-volatile-root.service',        ''],
+        ['user-runtime-dir@.service',            ''],
         ['user@.service',                        ''],
 ]
 
diff --git a/units/user-runtime-dir@.service.in b/units/user-runtime-dir@.service.in
new file mode 100644 (file)
index 0000000..8c02bed
--- /dev/null
@@ -0,0 +1,17 @@
+#  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=/run/user/%i mount wrapper
+StopWhenUnneeded=yes
+
+[Service]
+ExecStart=@rootlibexecdir@/systemd-user-runtime-dir start %i
+ExecStop=@rootlibexecdir@/systemd-user-runtime-dir stop %i
+RemainAfterExit=true
index 372ffa56d36a0f36113bc1a03701ea8db259111f..b88108e1b75601ec302ed66ff3c98ff0853adbc8 100644 (file)
@@ -10,6 +10,8 @@
 [Unit]
 Description=User Manager for UID %i
 After=systemd-user-sessions.service
+After=user-runtime-dir@%i.service
+Requires=user-runtime-dir@%i.service
 
 [Service]
 User=%i