summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2021-10-11 23:11:03 +0200
committerGitHub <noreply@github.com>2021-10-11 23:11:03 +0200
commitde3ef2524e9f4ebcd2cee2c70daa78189dcfd339 (patch)
tree0ada892739627d41b53c60628f2d3381e490e4e4
parentMerge pull request #20776 from medhefgo/boot-timeout (diff)
parentupdate TODO (diff)
downloadsystemd-de3ef2524e9f4ebcd2cee2c70daa78189dcfd339.tar.xz
systemd-de3ef2524e9f4ebcd2cee2c70daa78189dcfd339.zip
Merge pull request #20968 from poettering/homed-pin
homed: pin+lock homes while logged in + keep trying to unmount on logging out + optionally drop caches on logging out
-rw-r--r--TODO4
-rw-r--r--man/homectl.xml11
-rw-r--r--src/basic/socket-util.c2
-rw-r--r--src/basic/socket-util.h3
-rw-r--r--src/basic/user-util.c11
-rw-r--r--src/basic/user-util.h2
-rw-r--r--src/home/homectl.c23
-rw-r--r--src/home/homed-home.c251
-rw-r--r--src/home/homed-home.h40
-rw-r--r--src/home/homed-manager.c152
-rw-r--r--src/home/homework-luks.c41
-rw-r--r--src/home/homework.c72
-rw-r--r--src/home/homework.h1
-rw-r--r--src/home/user-record-util.c2
-rw-r--r--src/journal/journald-stream.c8
-rw-r--r--src/libsystemd/sd-bus/bus-common-errors.c1
-rw-r--r--src/libsystemd/sd-bus/bus-common-errors.h1
-rw-r--r--src/libsystemd/sd-bus/sd-bus.c1
-rw-r--r--src/libsystemd/sd-login/sd-login.c2
-rw-r--r--src/shared/rm-rf.c3
-rw-r--r--src/shared/rm-rf.h1
-rw-r--r--src/shared/user-record-show.c3
-rw-r--r--src/shared/user-record.c21
-rw-r--r--src/shared/user-record.h2
-rw-r--r--src/shared/varlink.c7
25 files changed, 547 insertions, 118 deletions
diff --git a/TODO b/TODO
index f670370a8e..efef0a5a37 100644
--- a/TODO
+++ b/TODO
@@ -1248,10 +1248,6 @@ Features:
fallback logic to get a regular user created on uninitialized systems.
- store PKCS#11 + FIDO2 token info in LUKS2 header, compatible with
systemd-cryptsetup, so that it can unlock homed volumes
- - try to unmount in regular intervals when home dir was busy when we
- tried because idle.
- - keep an fd to the homedir open at all times, to keep the fs pinned
- (autofs and such) while user is logged in.
* add a new switch --auto-definitions=yes/no or so to systemd-repart. If
specified, synthesize a definition automatically if we can: enlarge last
diff --git a/man/homectl.xml b/man/homectl.xml
index 245ebcee00..c2b1ec6c9b 100644
--- a/man/homectl.xml
+++ b/man/homectl.xml
@@ -611,6 +611,17 @@
</varlistentry>
<varlistentry>
+ <term><option>--drop-caches=</option><replaceable>BOOL</replaceable></term>
+
+ <listitem><para>Automatically flush OS file system caches on logout. This is useful in combination
+ with the fscrypt storage backend to ensure the OS does not keep decrypted versions of the files and
+ directories in memory (and accessible) after logout. This option is also supported on other backends,
+ but should not bring any benefit there. Defaults to off, except if the selected storage backend is
+ fscrypt, where it defaults to on. Note that flushing OS caches will negatively influence performance
+ of the OS shortly after logout.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>--fs-type=</option><replaceable>TYPE</replaceable></term>
<listitem><para>When LUKS2 storage is used configures the file system type to use inside the home
diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c
index 1e66f8700b..94ae90929a 100644
--- a/src/basic/socket-util.c
+++ b/src/basic/socket-util.c
@@ -551,7 +551,7 @@ int getpeername_pretty(int fd, bool include_port, char **ret) {
return -errno;
if (sa.sa.sa_family == AF_UNIX) {
- struct ucred ucred = {};
+ struct ucred ucred = UCRED_INVALID;
/* UNIX connection sockets are anonymous, so let's use
* PID/UID as pretty credentials instead */
diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h
index cb4a92236f..c4fafa084b 100644
--- a/src/basic/socket-util.h
+++ b/src/basic/socket-util.h
@@ -327,3 +327,6 @@ static inline int socket_set_recvfragsize(int fd, int af, bool b) {
}
int socket_get_mtu(int fd, int af, size_t *ret);
+
+/* an initializer for struct ucred that initialized all fields to the invalid value appropriate for each */
+#define UCRED_INVALID { .pid = 0, .uid = UID_INVALID, .gid = GID_INVALID }
diff --git a/src/basic/user-util.c b/src/basic/user-util.c
index 385ec514b4..aee2d9dc59 100644
--- a/src/basic/user-util.c
+++ b/src/basic/user-util.c
@@ -1085,3 +1085,14 @@ int is_this_me(const char *username) {
return uid == getuid();
}
+
+const char *get_home_root(void) {
+ const char *e;
+
+ /* For debug purposes allow overriding where we look for home dirs */
+ e = secure_getenv("SYSTEMD_HOME_ROOT");
+ if (e && path_is_absolute(e) && path_is_normalized(e))
+ return e;
+
+ return "/home";
+}
diff --git a/src/basic/user-util.h b/src/basic/user-util.h
index fd00b47b76..ab1ce48b2d 100644
--- a/src/basic/user-util.h
+++ b/src/basic/user-util.h
@@ -112,6 +112,8 @@ bool is_nologin_shell(const char *shell);
int is_this_me(const char *username);
+const char *get_home_root(void);
+
/* A locked *and* invalid password for "struct spwd"'s .sp_pwdp and "struct passwd"'s .pw_passwd field */
#define PASSWORD_LOCKED_AND_INVALID "!*"
diff --git a/src/home/homectl.c b/src/home/homectl.c
index d5b699a242..3af1381eb4 100644
--- a/src/home/homectl.c
+++ b/src/home/homectl.c
@@ -2131,6 +2131,7 @@ static int help(int argc, char *argv[], void *userdata) {
" --storage=STORAGE Storage type to use (luks, fscrypt, directory,\n"
" subvolume, cifs)\n"
" --image-path=PATH Path to image file/directory\n"
+ " --drop-caches=BOOL Whether to automatically drop caches on logout\n"
"\n%4$sLUKS Storage User Record Properties:%5$s\n"
" --fs-type=TYPE File system type to use in case of luks\n"
" storage (btrfs, ext4, xfs)\n"
@@ -2245,6 +2246,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_RECOVERY_KEY,
ARG_AND_RESIZE,
ARG_AND_CHANGE_PASSWORD,
+ ARG_DROP_CACHES,
};
static const struct option options[] = {
@@ -2327,6 +2329,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "recovery-key", required_argument, NULL, ARG_RECOVERY_KEY },
{ "and-resize", required_argument, NULL, ARG_AND_RESIZE },
{ "and-change-password", required_argument, NULL, ARG_AND_CHANGE_PASSWORD },
+ { "drop-caches", required_argument, NULL, ARG_DROP_CACHES },
{}
};
@@ -3450,6 +3453,26 @@ static int parse_argv(int argc, char *argv[]) {
arg_and_change_password = true;
break;
+ case ARG_DROP_CACHES: {
+ bool drop_caches;
+
+ if (isempty(optarg)) {
+ r = drop_from_identity("dropCaches");
+ if (r < 0)
+ return r;
+ }
+
+ r = parse_boolean_argument("--drop-caches=", optarg, &drop_caches);
+ if (r < 0)
+ return r;
+
+ r = json_variant_set_field_boolean(&arg_identity_extra, "dropCaches", r);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set drop caches field: %m");
+
+ break;
+ }
+
case '?':
return -EINVAL;
diff --git a/src/home/homed-home.c b/src/home/homed-home.c
index bbdc6940f3..d8f192650e 100644
--- a/src/home/homed-home.c
+++ b/src/home/homed-home.c
@@ -38,6 +38,9 @@
#include "user-record.h"
#include "user-util.h"
+/* Retry to deactivate home directories again and again every 15s until it works */
+#define RETRY_DEACTIVATE_USEC (15U * USEC_PER_SEC)
+
#define HOME_USERS_MAX 500
#define PENDING_OPERATIONS_MAX 100
@@ -130,6 +133,8 @@ int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) {
.worker_stdout_fd = -1,
.sysfs = TAKE_PTR(ns),
.signed_locally = -1,
+ .pin_fd = -1,
+ .luks_lock_fd = -1,
};
r = hashmap_put(m->homes_by_name, home->user_name, home);
@@ -203,6 +208,11 @@ Home *home_free(Home *h) {
h->current_operation = operation_unref(h->current_operation);
+ safe_close(h->pin_fd);
+ safe_close(h->luks_lock_fd);
+
+ h->retry_deactivate_event_source = sd_event_source_disable_unref(h->retry_deactivate_event_source);
+
return mfree(h);
}
@@ -317,6 +327,141 @@ int home_unlink_record(Home *h) {
return 0;
}
+static void home_unpin(Home *h) {
+ assert(h);
+
+ if (h->pin_fd < 0)
+ return;
+
+ h->pin_fd = safe_close(h->pin_fd);
+ log_debug("Successfully closed pin fd on home for %s.", h->user_name);
+}
+
+static void home_pin(Home *h) {
+ const char *path;
+
+ assert(h);
+
+ if (h->pin_fd >= 0) /* Already pinned? */
+ return;
+
+ path = user_record_home_directory(h->record);
+ if (!path) {
+ log_warning("No home directory path to pin for %s, ignoring.", h->user_name);
+ return;
+ }
+
+ h->pin_fd = open(path, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
+ if (h->pin_fd < 0) {
+ log_warning_errno(errno, "Couldn't open home directory '%s' for pinning, ignoring: %m", path);
+ return;
+ }
+
+ log_debug("Successfully pinned home directory '%s'.", path);
+}
+
+static void home_update_pin_fd(Home *h, HomeState state) {
+ assert(h);
+
+ if (state < 0)
+ state = home_get_state(h);
+
+ return HOME_STATE_SHALL_PIN(state) ? home_pin(h) : home_unpin(h);
+}
+
+static void home_maybe_close_luks_lock_fd(Home *h, HomeState state) {
+ assert(h);
+
+ if (h->luks_lock_fd < 0)
+ return;
+
+ if (state < 0)
+ state = home_get_state(h);
+
+ /* Keep the lock as long as the home dir is active or has some operation going */
+ if (HOME_STATE_IS_EXECUTING_OPERATION(state) || HOME_STATE_IS_ACTIVE(state) || state == HOME_LOCKED)
+ return;
+
+ h->luks_lock_fd = safe_close(h->luks_lock_fd);
+ log_debug("Successfully closed LUKS backing file lock for %s.", h->user_name);
+}
+
+static void home_maybe_stop_retry_deactivate(Home *h, HomeState state) {
+ assert(h);
+
+ /* Free the deactivation retry event source if we won't need it anymore. Specifically, we'll free the
+ * event source whenever the home directory is already deactivated (and we thus where successful) or
+ * if we start executing an operation that indicates that the home directory is going to be used or
+ * operated on again. Also, if the home is referenced again stop the timer */
+
+ if (HOME_STATE_MAY_RETRY_DEACTIVATE(state) &&
+ !h->ref_event_source_dont_suspend &&
+ !h->ref_event_source_please_suspend)
+ return;
+
+ h->retry_deactivate_event_source = sd_event_source_disable_unref(h->retry_deactivate_event_source);
+}
+
+static int home_deactivate_internal(Home *h, bool force, sd_bus_error *error);
+static void home_start_retry_deactivate(Home *h);
+
+static int home_on_retry_deactivate(sd_event_source *s, uint64_t usec, void *userdata) {
+ Home *h = userdata;
+ HomeState state;
+
+ assert(s);
+ assert(h);
+
+ /* 15s after the last attempt to deactivate the home directory passed. Let's try it one more time. */
+
+ h->retry_deactivate_event_source = sd_event_source_disable_unref(h->retry_deactivate_event_source);
+
+ state = home_get_state(h);
+ if (!HOME_STATE_MAY_RETRY_DEACTIVATE(state))
+ return 0;
+
+ if (IN_SET(state, HOME_ACTIVE, HOME_LINGERING)) {
+ log_info("Again trying to deactivate home directory.");
+
+ /* If we are not executing any operation, let's start deactivating now. Note that this will
+ * restart our timer again, we are gonna be called again if this doesn't work. */
+ (void) home_deactivate_internal(h, /* force= */ false, NULL);
+ } else
+ /* if we are executing an operation (specifically, area already running a deactivation
+ * operation), then simply reque the timer, so that we retry again. */
+ home_start_retry_deactivate(h);
+
+ return 0;
+}
+
+static void home_start_retry_deactivate(Home *h) {
+ int r;
+
+ assert(h);
+ assert(h->manager);
+
+ /* Alrady allocated? */
+ if (h->retry_deactivate_event_source)
+ return;
+
+ /* If the home directory is being used now don't start the timer */
+ if (h->ref_event_source_dont_suspend || h->ref_event_source_please_suspend)
+ return;
+
+ r = sd_event_add_time_relative(
+ h->manager->event,
+ &h->retry_deactivate_event_source,
+ CLOCK_MONOTONIC,
+ RETRY_DEACTIVATE_USEC,
+ 1*USEC_PER_MINUTE,
+ home_on_retry_deactivate,
+ h);
+ if (r < 0)
+ return (void) log_warning_errno(r, "Failed to install retry-deactivate event source, ignoring: %m");
+
+ (void) sd_event_source_set_description(h->retry_deactivate_event_source, "retry-deactivate");
+}
+
static void home_set_state(Home *h, HomeState state) {
HomeState old_state, new_state;
@@ -331,6 +476,10 @@ static void home_set_state(Home *h, HomeState state) {
home_state_to_string(old_state),
home_state_to_string(new_state));
+ home_update_pin_fd(h, new_state);
+ home_maybe_close_luks_lock_fd(h, new_state);
+ home_maybe_stop_retry_deactivate(h, new_state);
+
if (HOME_STATE_IS_EXECUTING_OPERATION(old_state) && !HOME_STATE_IS_EXECUTING_OPERATION(new_state)) {
/* If we just finished executing some operation, process the queue of pending operations. And
* enqueue it for GC too. */
@@ -483,6 +632,8 @@ static int convert_worker_errno(Home *h, int e, sd_bus_error *error) {
return sd_bus_error_setf(error, BUS_ERROR_NO_DISK_SPACE, "Not enough disk space for home %s", h->user_name);
case -EKEYREVOKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_CANT_AUTHENTICATE, "Home %s has no password or other authentication mechanism defined.", h->user_name);
+ case -EADDRINUSE:
+ return sd_bus_error_setf(error, BUS_ERROR_HOME_IN_USE, "Home %s is currently being used elsewhere.", h->user_name);
}
return 0;
@@ -1029,6 +1180,12 @@ static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord
_exit(EXIT_FAILURE);
}
+ /* If we haven't locked the device yet, ask for a lock to be taken and be passed back to us via sd_notify(). */
+ if (setenv("SYSTEMD_LUKS_LOCK", one_zero(h->luks_lock_fd < 0), 1) < 0) {
+ log_error_errno(errno, "Failed to set $SYSTEMD_LUKS_LOCK: %m");
+ _exit(EXIT_FAILURE);
+ }
+
if (h->manager->default_storage >= 0)
if (setenv("SYSTEMD_HOME_DEFAULT_STORAGE", user_storage_to_string(h->manager->default_storage), 1) < 0) {
log_error_errno(errno, "Failed to set $SYSTEMD_HOME_DEFAULT_STORAGE: %m");
@@ -1149,6 +1306,7 @@ int home_fixate(Home *h, UserRecord *secret, sd_bus_error *error) {
case HOME_INACTIVE:
case HOME_DIRTY:
case HOME_ACTIVE:
+ case HOME_LINGERING:
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_ALREADY_FIXATED, "Home %s is already fixated.", h->user_name);
case HOME_UNFIXATED:
@@ -1190,6 +1348,11 @@ int home_activate(Home *h, UserRecord *secret, sd_bus_error *error) {
return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name);
case HOME_ACTIVE:
return sd_bus_error_setf(error, BUS_ERROR_HOME_ALREADY_ACTIVE, "Home %s is already active.", h->user_name);
+ case HOME_LINGERING:
+ /* If we are lingering, i.e. active but are supposed to be deactivated, then cancel this
+ * timer if the user explicitly asks us to be active */
+ h->retry_deactivate_event_source = sd_event_source_disable_unref(h->retry_deactivate_event_source);
+ return 0;
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
case HOME_INACTIVE:
@@ -1236,6 +1399,7 @@ int home_authenticate(Home *h, UserRecord *secret, sd_bus_error *error) {
case HOME_INACTIVE:
case HOME_DIRTY:
case HOME_ACTIVE:
+ case HOME_LINGERING:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
@@ -1245,7 +1409,7 @@ int home_authenticate(Home *h, UserRecord *secret, sd_bus_error *error) {
if (r < 0)
return r;
- return home_authenticate_internal(h, secret, state == HOME_ACTIVE ? HOME_AUTHENTICATING_WHILE_ACTIVE : HOME_AUTHENTICATING, error);
+ return home_authenticate_internal(h, secret, HOME_STATE_IS_ACTIVE(state) ? HOME_AUTHENTICATING_WHILE_ACTIVE : HOME_AUTHENTICATING, error);
}
static int home_deactivate_internal(Home *h, bool force, sd_bus_error *error) {
@@ -1253,12 +1417,22 @@ static int home_deactivate_internal(Home *h, bool force, sd_bus_error *error) {
assert(h);
+ home_unpin(h); /* unpin so that we can deactivate */
+
r = home_start_work(h, force ? "deactivate-force" : "deactivate", h->record, NULL);
if (r < 0)
- return r;
+ /* Operation failed before it even started, reacquire pin fd, if state still dictates so */
+ home_update_pin_fd(h, _HOME_STATE_INVALID);
+ else {
+ home_set_state(h, HOME_DEACTIVATING);
+ r = 0;
+ }
- home_set_state(h, HOME_DEACTIVATING);
- return 0;
+ /* Let's start a timer to retry deactivation in 15. We'll stop the timer once we manage to deactivate
+ * the home directory again, or we we start any other operation. */
+ home_start_retry_deactivate(h);
+
+ return r;
}
int home_deactivate(Home *h, bool force, sd_bus_error *error) {
@@ -1273,6 +1447,7 @@ int home_deactivate(Home *h, bool force, sd_bus_error *error) {
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
case HOME_ACTIVE:
+ case HOME_LINGERING:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
@@ -1309,6 +1484,7 @@ int home_create(Home *h, UserRecord *secret, sd_bus_error *error) {
case HOME_ABSENT:
break;
case HOME_ACTIVE:
+ case HOME_LINGERING:
case HOME_LOCKED:
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "Home %s is currently being used, or an operation on home %s is currently being executed.", h->user_name, h->user_name);
@@ -1347,6 +1523,7 @@ int home_remove(Home *h, sd_bus_error *error) {
case HOME_DIRTY:
break;
case HOME_ACTIVE:
+ case HOME_LINGERING:
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "Home %s is currently being used, or an operation on home %s is currently being executed.", h->user_name, h->user_name);
}
@@ -1485,6 +1662,7 @@ int home_update(Home *h, UserRecord *hr, sd_bus_error *error) {
case HOME_INACTIVE:
case HOME_DIRTY:
case HOME_ACTIVE:
+ case HOME_LINGERING:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
@@ -1498,7 +1676,7 @@ int home_update(Home *h, UserRecord *hr, sd_bus_error *error) {
if (r < 0)
return r;
- home_set_state(h, state == HOME_ACTIVE ? HOME_UPDATING_WHILE_ACTIVE : HOME_UPDATING);
+ home_set_state(h, HOME_STATE_IS_ACTIVE(state) ? HOME_UPDATING_WHILE_ACTIVE : HOME_UPDATING);
return 0;
}
@@ -1520,6 +1698,7 @@ int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, sd_bus_error *e
case HOME_INACTIVE:
case HOME_DIRTY:
case HOME_ACTIVE:
+ case HOME_LINGERING:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
@@ -1568,7 +1747,7 @@ int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, sd_bus_error *e
if (r < 0)
return r;
- home_set_state(h, state == HOME_ACTIVE ? HOME_RESIZING_WHILE_ACTIVE : HOME_RESIZING);
+ home_set_state(h, HOME_STATE_IS_ACTIVE(state) ? HOME_RESIZING_WHILE_ACTIVE : HOME_RESIZING);
return 0;
}
@@ -1616,6 +1795,7 @@ int home_passwd(Home *h,
case HOME_INACTIVE:
case HOME_DIRTY:
case HOME_ACTIVE:
+ case HOME_LINGERING:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
@@ -1681,7 +1861,7 @@ int home_passwd(Home *h,
if (r < 0)
return r;
- home_set_state(h, state == HOME_ACTIVE ? HOME_PASSWD_WHILE_ACTIVE : HOME_PASSWD);
+ home_set_state(h, HOME_STATE_IS_ACTIVE(state) ? HOME_PASSWD_WHILE_ACTIVE : HOME_PASSWD);
return 0;
}
@@ -1700,6 +1880,7 @@ int home_unregister(Home *h, sd_bus_error *error) {
case HOME_DIRTY:
break;
case HOME_ACTIVE:
+ case HOME_LINGERING:
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "Home %s is currently being used, or an operation on home %s is currently being executed.", h->user_name, h->user_name);
}
@@ -1727,6 +1908,7 @@ int home_lock(Home *h, sd_bus_error *error) {
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is already locked.", h->user_name);
case HOME_ACTIVE:
+ case HOME_LINGERING:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
@@ -1767,6 +1949,7 @@ int home_unlock(Home *h, UserRecord *secret, sd_bus_error *error) {
case HOME_ABSENT:
case HOME_INACTIVE:
case HOME_ACTIVE:
+ case HOME_LINGERING:
case HOME_DIRTY:
return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_LOCKED, "Home %s is not locked.", h->user_name);
case HOME_LOCKED:
@@ -1789,7 +1972,7 @@ HomeState home_get_state(Home *h) {
/* Otherwise, let's see if the home directory is mounted. If so, we assume for sure the home
* directory is active */
if (user_record_test_home_directory(h->record) == USER_TEST_MOUNTED)
- return HOME_ACTIVE;
+ return h->retry_deactivate_event_source ? HOME_LINGERING : HOME_ACTIVE;
/* And if we see the image being gone, we report this as absent */
r = user_record_test_image_path(h->record);
@@ -1802,28 +1985,49 @@ HomeState home_get_state(Home *h) {
return HOME_INACTIVE;
}
-void home_process_notify(Home *h, char **l) {
+void home_process_notify(Home *h, char **l, int fd) {
+ _cleanup_close_ int taken_fd = TAKE_FD(fd);
const char *e;
int error;
int r;
assert(h);
- e = strv_env_get(l, "ERRNO");
- if (!e) {
- log_debug("Got notify message lacking ERRNO= field, ignoring.");
+ e = strv_env_get(l, "SYSTEMD_LUKS_LOCK_FD");
+ if (e) {
+ r = parse_boolean(e);
+ if (r < 0)
+ return (void) log_debug_errno(r, "Failed to parse SYSTEMD_LUKS_LOCK_FD value: %m");
+ if (r > 0) {
+ if (taken_fd < 0)
+ return (void) log_debug("Got notify message with SYSTEMD_LUKS_LOCK_FD=1 but no fd passed, ignoring: %m");
+
+ safe_close(h->luks_lock_fd);
+ h->luks_lock_fd = TAKE_FD(taken_fd);
+
+ log_debug("Successfully acquired LUKS lock fd from worker.");
+
+ /* Immediately check if we actually want to keep it */
+ home_maybe_close_luks_lock_fd(h, _HOME_STATE_INVALID);
+ } else {
+ if (taken_fd >= 0)
+ return (void) log_debug("Got notify message with SYSTEMD_LUKS_LOCK_FD=0 but fd passed, ignoring: %m");
+
+ h->luks_lock_fd = safe_close(h->luks_lock_fd);
+ }
+
return;
}
+ e = strv_env_get(l, "ERRNO");
+ if (!e)
+ return (void) log_debug("Got notify message lacking both ERRNO= and SYSTEMD_LUKS_LOCK_FD= field, ignoring.");
+
r = safe_atoi(e, &error);
- if (r < 0) {
- log_debug_errno(r, "Failed to parse received error number, ignoring: %s", e);
- return;
- }
- if (error <= 0) {
- log_debug("Error number is out of range: %i", error);
- return;
- }
+ if (r < 0)
+ return (void) log_debug_errno(r, "Failed to parse received error number, ignoring: %s", e);
+ if (error <= 0)
+ return (void) log_debug("Error number is out of range: %i", error);
h->worker_error_code = error;
}
@@ -2372,6 +2576,7 @@ static int home_dispatch_acquire(Home *h, Operation *o) {
break;
case HOME_ACTIVE:
+ case HOME_LINGERING:
for_state = HOME_AUTHENTICATING_FOR_ACQUIRE;
call = home_authenticate_internal;
break;
@@ -2426,6 +2631,7 @@ static int home_dispatch_release(Home *h, Operation *o) {
break;
case HOME_ACTIVE:
+ case HOME_LINGERING:
r = home_deactivate_internal(h, false, &error);
break;
@@ -2470,6 +2676,7 @@ static int home_dispatch_lock_all(Home *h, Operation *o) {
break;
case HOME_ACTIVE:
+ case HOME_LINGERING:
log_info("Locking home %s.", h->user_name);
r = home_lock(h, &error);
break;
@@ -2514,6 +2721,7 @@ static int home_dispatch_deactivate_all(Home *h, Operation *o) {
break;
case HOME_ACTIVE:
+ case HOME_LINGERING:
log_info("Deactivating home %s.", h->user_name);
r = home_deactivate_internal(h, false, &error);
break;
@@ -2559,6 +2767,7 @@ static int home_dispatch_pipe_eof(Home *h, Operation *o) {
break;
case HOME_ACTIVE:
+ case HOME_LINGERING:
r = home_deactivate_internal(h, false, &error);
if (r < 0)
log_warning_errno(r, "Failed to deactivate %s, ignoring: %s", h->user_name, bus_error_message(&error, r));
@@ -2600,6 +2809,7 @@ static int home_dispatch_deactivate_force(Home *h, Operation *o) {
case HOME_ACTIVE:
case HOME_LOCKED:
+ case HOME_LINGERING:
r = home_deactivate_internal(h, true, &error);
if (r < 0)
log_warning_errno(r, "Failed to forcibly deactivate %s, ignoring: %s", h->user_name, bus_error_message(&error, r));
@@ -2820,6 +3030,7 @@ static const char* const home_state_table[_HOME_STATE_MAX] = {
[HOME_ACTIVATING_FOR_ACQUIRE] = "activating-for-acquire",
[HOME_DEACTIVATING] = "deactivating",
[HOME_ACTIVE] = "active",
+ [HOME_LINGERING] = "lingering",
[HOME_LOCKING] = "locking",
[HOME_LOCKED] = "locked",
[HOME_UNLOCKING] = "unlocking",
diff --git a/src/home/homed-home.h b/src/home/homed-home.h
index 7f531a234c..7cd8a9edb4 100644
--- a/src/home/homed-home.h
+++ b/src/home/homed-home.h
@@ -21,6 +21,7 @@ typedef enum HomeState {
HOME_ACTIVATING_FOR_ACQUIRE, /* activating because Acquire() was called */
HOME_DEACTIVATING,
HOME_ACTIVE, /* logged in right now */
+ HOME_LINGERING, /* not logged in anymore, but we didn't manage to deactivate (because some process keeps it busy?) but we'll keep trying */
HOME_LOCKING,
HOME_LOCKED,
HOME_UNLOCKING,
@@ -43,6 +44,7 @@ typedef enum HomeState {
static inline bool HOME_STATE_IS_ACTIVE(HomeState state) {
return IN_SET(state,
HOME_ACTIVE,
+ HOME_LINGERING,
HOME_UPDATING_WHILE_ACTIVE,
HOME_RESIZING_WHILE_ACTIVE,
HOME_PASSWD_WHILE_ACTIVE,
@@ -74,6 +76,33 @@ static inline bool HOME_STATE_IS_EXECUTING_OPERATION(HomeState state) {
HOME_AUTHENTICATING_FOR_ACQUIRE);
}
+static inline bool HOME_STATE_SHALL_PIN(HomeState state) {
+ /* Like HOME_STATE_IS_ACTIVE() – but HOME_LINGERING is missing! */
+ return IN_SET(state,
+ HOME_ACTIVE,
+ HOME_UPDATING_WHILE_ACTIVE,
+ HOME_RESIZING_WHILE_ACTIVE,
+ HOME_PASSWD_WHILE_ACTIVE,
+ HOME_AUTHENTICATING_WHILE_ACTIVE,
+ HOME_AUTHENTICATING_FOR_ACQUIRE);
+}
+
+static inline bool HOME_STATE_MAY_RETRY_DEACTIVATE(HomeState state) {
+ /* Indicates when to leave the deactivate retry timer active */
+ return IN_SET(state,
+ HOME_ACTIVE,
+ HOME_LINGERING,
+ HOME_DEACTIVATING,
+ HOME_LOCKING,
+ HOME_UNLOCKING,
+ HOME_UNLOCKING_FOR_ACQUIRE,
+ HOME_UPDATING_WHILE_ACTIVE,
+ HOME_RESIZING_WHILE_ACTIVE,
+ HOME_PASSWD_WHILE_ACTIVE,
+ HOME_AUTHENTICATING_WHILE_ACTIVE,
+ HOME_AUTHENTICATING_FOR_ACQUIRE);
+}
+
struct Home {
Manager *manager;
char *user_name;
@@ -126,6 +155,15 @@ struct Home {
/* Used to coalesce bus PropertiesChanged events */
sd_event_source *deferred_change_event_source;
+
+ /* An fd to the top-level home directory we keep while logged in, to keep the dir busy */
+ int pin_fd;
+
+ /* A time event used to repeatedly try to unmount home dir after use if it didn't work on first try */
+ sd_event_source *retry_deactivate_event_source;
+
+ /* An fd that locks the backing file of LUKS home dirs with a BSD lock. */
+ int luks_lock_fd;
};
int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret);
@@ -152,7 +190,7 @@ int home_unlock(Home *h, UserRecord *secret, sd_bus_error *error);
HomeState home_get_state(Home *h);
-void home_process_notify(Home *h, char **l);
+void home_process_notify(Home *h, char **l, int fd);
int home_killall(Home *h);
diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c
index 070fd97d69..38283ff1ed 100644
--- a/src/home/homed-manager.c
+++ b/src/home/homed-manager.c
@@ -83,35 +83,38 @@ static void manager_watch_home(Manager *m) {
m->inotify_event_source = sd_event_source_disable_unref(m->inotify_event_source);
m->scan_slash_home = false;
- if (statfs("/home/", &sfs) < 0) {
+ if (statfs(get_home_root(), &sfs) < 0) {
log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno,
- "Failed to statfs() /home/ directory, disabling automatic scanning.");
+ "Failed to statfs() %s directory, disabling automatic scanning.", get_home_root());
return;
}
if (is_network_fs(&sfs)) {
- log_info("/home/ is a network file system, disabling automatic scanning.");
+ log_info("%s is a network file system, disabling automatic scanning.", get_home_root());
return;
}
if (is_fs_type(&sfs, AUTOFS_SUPER_MAGIC)) {
- log_info("/home/ is on autofs, disabling automatic scanning.");
+ log_info("%s is on autofs, disabling automatic scanning.", get_home_root());
return;
}
m->scan_slash_home = true;
- r = sd_event_add_inotify(m->event, &m->inotify_event_source, "/home/",
+ r = sd_event_add_inotify(m->event, &m->inotify_event_source, get_home_root(),
IN_CREATE|IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF|IN_ONLYDIR|IN_MOVED_TO|IN_MOVED_FROM|IN_DELETE,
on_home_inotify, m);
if (r < 0)
log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to create inotify watch on /home/, ignoring.");
+ "Failed to create inotify watch on %s, ignoring.", get_home_root());
(void) sd_event_source_set_description(m->inotify_event_source, "home-inotify");
+
+ log_info("Watching %s.", get_home_root());
}
static int on_home_inotify(sd_event_source *s, const struct inotify_event *event, void *userdata) {
+ _cleanup_free_ char *j = NULL;
Manager *m = userdata;
const char *e, *n;
@@ -121,15 +124,15 @@ static int on_home_inotify(sd_event_source *s, const struct inotify_event *event
if ((event->mask & (IN_Q_OVERFLOW|IN_MOVE_SELF|IN_DELETE_SELF|IN_IGNORED|IN_UNMOUNT)) != 0) {
if (FLAGS_SET(event->mask, IN_Q_OVERFLOW))
- log_debug("/home/ inotify queue overflow, rescanning.");
+ log_debug("%s inotify queue overflow, rescanning.", get_home_root());
else if (FLAGS_SET(event->mask, IN_MOVE_SELF))
- log_info("/home/ moved or renamed, recreating watch and rescanning.");
+ log_info("%s moved or renamed, recreating watch and rescanning.", get_home_root());
else if (FLAGS_SET(event->mask, IN_DELETE_SELF))
- log_info("/home/ deleted, recreating watch and rescanning.");
+ log_info("%s deleted, recreating watch and rescanning.", get_home_root());
else if (FLAGS_SET(event->mask, IN_UNMOUNT))
- log_info("/home/ unmounted, recreating watch and rescanning.");
+ log_info("%s unmounted, recreating watch and rescanning.", get_home_root());
else if (FLAGS_SET(event->mask, IN_IGNORED))
- log_info("/home/ watch invalidated, recreating watch and rescanning.");
+ log_info("%s watch invalidated, recreating watch and rescanning.", get_home_root());
manager_watch_home(m);
(void) manager_gc_images(m);
@@ -150,15 +153,19 @@ static int on_home_inotify(sd_event_source *s, const struct inotify_event *event
if (!suitable_user_name(n))
return 0;
+ j = path_join(get_home_root(), event->name);
+ if (!j)
+ return log_oom();
+
if ((event->mask & (IN_CREATE|IN_CLOSE_WRITE|IN_MOVED_TO)) != 0) {
if (FLAGS_SET(event->mask, IN_CREATE))
- log_debug("/home/%s has been created, having a look.", event->name);
+ log_debug("%s has been created, having a look.", j);
else if (FLAGS_SET(event->mask, IN_CLOSE_WRITE))
- log_debug("/home/%s has been modified, having a look.", event->name);
+ log_debug("%s has been modified, having a look.", j);
else if (FLAGS_SET(event->mask, IN_MOVED_TO))
- log_debug("/home/%s has been moved in, having a look.", event->name);
+ log_debug("%s has been moved in, having a look.", j);
- (void) manager_assess_image(m, -1, "/home/", event->name);
+ (void) manager_assess_image(m, -1, get_home_root(), event->name);
(void) bus_manager_emit_auto_login_changed(m);
}
@@ -166,11 +173,11 @@ static int on_home_inotify(sd_event_source *s, const struct inotify_event *event
Home *h;
if (FLAGS_SET(event->mask, IN_DELETE))
- log_debug("/home/%s has been deleted, revalidating.", event->name);
+ log_debug("%s has been deleted, revalidating.", j);
else if (FLAGS_SET(event->mask, IN_CLOSE_WRITE))
- log_debug("/home/%s has been closed after writing, revalidating.", event->name);
+ log_debug("%s has been closed after writing, revalidating.", j);
else if (FLAGS_SET(event->mask, IN_MOVED_FROM))
- log_debug("/home/%s has been moved away, revalidating.", event->name);
+ log_debug("%s has been moved away, revalidating.", j);
h = hashmap_get(m->homes_by_name, n);
if (h) {
@@ -487,7 +494,7 @@ static int search_quota(uid_t uid, const char *exclude_quota_path) {
* comprehensive, but should cover most cases. Note that in an ideal world every user would be
* registered in NSS and avoid our own UID range, but for all other cases, it's a good idea to be
* paranoid and check quota if we can. */
- FOREACH_STRING(where, "/home/", "/tmp/", "/var/", "/var/mail/", "/var/tmp/", "/var/spool/") {
+ FOREACH_STRING(where, get_home_root(), "/tmp/", "/var/", "/var/mail/", "/var/tmp/", "/var/spool/") {
struct dqblk req;
struct stat st;
@@ -914,13 +921,13 @@ int manager_enumerate_images(Manager *m) {
if (!m->scan_slash_home)
return 0;
- d = opendir("/home/");
+ d = opendir(get_home_root());
if (!d)
return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno,
- "Failed to open /home/: %m");
+ "Failed to open %s: %m", get_home_root());
- FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read /home/ directory: %m"))
- (void) manager_assess_image(m, dirfd(d), "/home", de->d_name);
+ FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s directory: %m", get_home_root()))
+ (void) manager_assess_image(m, dirfd(d), get_home_root(), de->d_name);
return 0;
}
@@ -1010,13 +1017,25 @@ static int manager_bind_varlink(Manager *m) {
return 0;
}
-static ssize_t read_datagram(int fd, struct ucred *ret_sender, void **ret) {
+static ssize_t read_datagram(
+ int fd,
+ struct ucred *ret_sender,
+ void **ret,
+ int *ret_passed_fd) {
+
+ CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred)) + CMSG_SPACE(sizeof(int))) control;
_cleanup_free_ void *buffer = NULL;
+ _cleanup_close_ int passed_fd = -1;
+ struct ucred *sender = NULL;
+ struct cmsghdr *cmsg;
+ struct msghdr mh;
+ struct iovec iov;
ssize_t n, m;
assert(fd >= 0);
assert(ret_sender);
assert(ret);
+ assert(ret_passed_fd);
n = next_datagram_size_fd(fd);
if (n < 0)
@@ -1026,58 +1045,54 @@ static ssize_t read_datagram(int fd, struct ucred *ret_sender, void **ret) {
if (!buffer)
return -ENOMEM;
- if (ret_sender) {
- CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control;
- bool found_ucred = false;
- struct cmsghdr *cmsg;
- struct msghdr mh;
- struct iovec iov;
+ /* Pass one extra byte, as a size check */
+ iov = IOVEC_MAKE(buffer, n + 1);
- /* Pass one extra byte, as a size check */
- iov = IOVEC_MAKE(buffer, n + 1);
-
- mh = (struct msghdr) {
- .msg_iov = &iov,
- .msg_iovlen = 1,
- .msg_control = &control,
- .msg_controllen = sizeof(control),
- };
+ mh = (struct msghdr) {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
- m = recvmsg_safe(fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
- if (m < 0)
- return m;
+ m = recvmsg_safe(fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
+ if (m < 0)
+ return m;
+ /* Ensure the size matches what we determined before */
+ if (m != n) {
cmsg_close_all(&mh);
+ return -EMSGSIZE;
+ }
- /* Ensure the size matches what we determined before */
- if (m != n)
- return -EMSGSIZE;
+ CMSG_FOREACH(cmsg, &mh) {
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_CREDENTIALS &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
+ assert(!sender);
+ sender = (struct ucred*) CMSG_DATA(cmsg);
+ }
- CMSG_FOREACH(cmsg, &mh)
- if (cmsg->cmsg_level == SOL_SOCKET &&
- cmsg->cmsg_type == SCM_CREDENTIALS &&
- cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_RIGHTS) {
- memcpy(ret_sender, CMSG_DATA(cmsg), sizeof(struct ucred));
- found_ucred = true;
+ if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) {
+ cmsg_close_all(&mh);
+ return -EMSGSIZE;
}
- if (!found_ucred)
- *ret_sender = (struct ucred) {
- .pid = 0,
- .uid = UID_INVALID,
- .gid = GID_INVALID,
- };
- } else {
- m = recv(fd, buffer, n + 1, MSG_DONTWAIT);
- if (m < 0)
- return -errno;
-
- /* Ensure the size matches what we determined before */
- if (m != n)
- return -EMSGSIZE;
+ assert(passed_fd < 0);
+ passed_fd = *(int*) CMSG_DATA(cmsg);
+ }
}
+ if (sender)
+ *ret_sender = *sender;
+ else
+ *ret_sender = (struct ucred) UCRED_INVALID;
+
+ *ret_passed_fd = TAKE_FD(passed_fd);
+
/* For safety reasons: let's always NUL terminate. */
((char*) buffer)[n] = 0;
*ret = TAKE_PTR(buffer);
@@ -1088,7 +1103,8 @@ static ssize_t read_datagram(int fd, struct ucred *ret_sender, void **ret) {
static int on_notify_socket(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
_cleanup_strv_free_ char **l = NULL;
_cleanup_free_ void *datagram = NULL;
- struct ucred sender;
+ _cleanup_close_ int passed_fd = -1;
+ struct ucred sender = UCRED_INVALID;
Manager *m = userdata;
ssize_t n;
Home *h;
@@ -1096,7 +1112,7 @@ static int on_notify_socket(sd_event_source *s, int fd, uint32_t revents, void *
assert(s);
assert(m);
- n = read_datagram(fd, &sender, &datagram);
+ n = read_datagram(fd, &sender, &datagram, &passed_fd);
if (IN_SET(n, -EAGAIN, -EINTR))
return 0;
if (n < 0)
@@ -1117,7 +1133,7 @@ static int on_notify_socket(sd_event_source *s, int fd, uint32_t revents, void *
if (!l)
return log_oom();
- home_process_notify(h, l);
+ home_process_notify(h, l, TAKE_FD(passed_fd));
return 0;
}
diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c
index c9c2476ed6..30b63e3481 100644
--- a/src/home/homework-luks.c
+++ b/src/home/homework-luks.c
@@ -8,11 +8,14 @@
#include <sys/mount.h>
#include <sys/xattr.h>
+#include "sd-daemon.h"
+
#include "blkid-util.h"
#include "blockdev-util.h"
#include "btrfs-util.h"
#include "chattr-util.h"
#include "dm-util.h"
+#include "env-util.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
@@ -1042,6 +1045,40 @@ int run_fallocate_by_path(const char *backing_path) {
return run_fallocate(backing_fd, NULL);
}
+static int lock_image_fd(int image_fd, const char *ip) {
+ int r;
+
+ /* If the $SYSTEMD_LUKS_LOCK environment variable is set we'll take an exclusive BSD lock on the
+ * image file, and send it to our parent. homed will keep it open to ensure no other instance of
+ * homed (across the network or such) will also mount the file. */
+
+ r = getenv_bool("SYSTEMD_LUKS_LOCK");
+ if (r == -ENXIO)
+ return 0;
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse $SYSTEMD_LUKS_LOCK environment variable: %m");
+ if (r > 0) {
+ if (flock(image_fd, LOCK_EX|LOCK_NB) < 0) {
+
+ if (errno == EWOULDBLOCK)
+ log_error_errno(errno, "Image file '%s' already locked, can't use.", ip);
+ else
+ log_error_errno(errno, "Failed to lock image file '%s': %m", ip);
+
+ return errno != EWOULDBLOCK ? -errno : -EADDRINUSE; /* Make error recognizable */
+ }
+
+ log_info("Successfully locked image file '%s'.", ip);
+
+ /* Now send it to our parent to keep safe while the home dir is active */
+ r = sd_pid_notify_with_fds(0, false, "SYSTEMD_LUKS_LOCK_FD=1", &image_fd, 1);
+ if (r < 0)
+ log_warning_errno(r, "Failed to send LUKS lock fd to parent, ignoring: %m");
+ }
+
+ return 0;
+}
+
int home_prepare_luks(
UserRecord *h,
bool already_activated,
@@ -1176,6 +1213,10 @@ int home_prepare_luks(
S_ISDIR(st.st_mode) ? SYNTHETIC_ERRNO(EISDIR) : SYNTHETIC_ERRNO(EBADFD),
"Image file %s is not a regular file or block device: %m", ip);
+ r = lock_image_fd(image_fd, ip);
+ if (r < 0)
+ return r;
+
r = luks_validate(image_fd, user_record_user_name_and_realm(h), h->partition_uuid, &found_partition_uuid, &offset, &size);
if (r < 0)
return log_error_errno(r, "Failed to validate disk label: %m");
diff --git a/src/home/homework.c b/src/home/homework.c
index ee1d4068ba..ae3fffed02 100644
--- a/src/home/homework.c
+++ b/src/home/homework.c
@@ -28,6 +28,7 @@
#include "rm-rf.h"
#include "stat-util.h"
#include "strv.h"
+#include "sync-util.h"
#include "tmpfile-util.h"
#include "user-util.h"
#include "virt.h"
@@ -283,6 +284,20 @@ int user_record_authenticate(
return 0;
}
+static void drop_caches_now(void) {
+ int r;
+
+ /* Drop file system caches now. See https://www.kernel.org/doc/Documentation/sysctl/vm.txt for
+ * details. We write "2" into /proc/sys/vm/drop_caches to ensure dentries/inodes are flushed, but not
+ * more. */
+
+ r = write_string_file("/proc/sys/vm/drop_caches", "2\n", WRITE_STRING_FILE_DISABLE_BUFFER);
+ if (r < 0)
+ log_warning_errno(r, "Failed to drop caches, ignoring: %m");
+ else
+ log_debug("Dropped caches.");
+}
+
int home_setup_undo(HomeSetup *setup) {
int r = 0, q;
@@ -295,6 +310,9 @@ int home_setup_undo(HomeSetup *setup) {
r = q;
}
+ if (syncfs(setup->root_fd) < 0)
+ log_debug_errno(errno, "Failed to synchronize home directory, ignoring: %m");
+
setup->root_fd = safe_close(setup->root_fd);
}
@@ -345,6 +363,9 @@ int home_setup_undo(HomeSetup *setup) {
setup->volume_key = mfree(setup->volume_key);
setup->volume_key_size = 0;
+ if (setup->do_drop_caches)
+ drop_caches_now();
+
return r;
}
@@ -367,6 +388,9 @@ int home_prepare(
/* Makes a home directory accessible (through the root_fd file descriptor, not by path!). */
+ if (!already_activated) /* If we set up the directory, we should also drop caches once we are done */
+ setup->do_drop_caches = setup->do_drop_caches || user_record_drop_caches(h);
+
switch (user_record_storage(h)) {
case USER_LUKS:
@@ -827,6 +851,13 @@ static int home_deactivate(UserRecord *h, bool force) {
return r;
}
+ /* Sync explicitly, so that the drop caches logic below can work as documented */
+ r = syncfs_path(AT_FDCWD, user_record_home_directory(h));
+ if (r < 0)
+ log_debug_errno(r, "Failed to synchronize home directory, ignoring: %m");
+ else
+ log_info("Syncing completed.");
+
if (umount2(user_record_home_directory(h), UMOUNT_NOFOLLOW | (force ? MNT_FORCE|MNT_DETACH : 0)) < 0)
return log_error_errno(errno, "Failed to unmount %s: %m", user_record_home_directory(h));
@@ -846,6 +877,9 @@ static int home_deactivate(UserRecord *h, bool force) {
if (!done)
return log_error_errno(SYNTHETIC_ERRNO(ENOEXEC), "Home is not active.");
+ if (user_record_drop_caches(h))
+ drop_caches_now();
+
log_info("Everything completed.");
return 0;
}
@@ -1095,12 +1129,12 @@ static int determine_default_storage(UserStorage *ret) {
if (r < 0)
return log_error_errno(r, "Failed to determine whether we are in a container: %m");
if (r == 0) {
- r = path_is_encrypted("/home");
+ r = path_is_encrypted(get_home_root());
if (r > 0)
- log_info("/home is encrypted, not using '%s' storage, in order to avoid double encryption.", user_storage_to_string(USER_LUKS));
+ log_info("%s is encrypted, not using '%s' storage, in order to avoid double encryption.", get_home_root(), user_storage_to_string(USER_LUKS));
else {
if (r < 0)
- log_warning_errno(r, "Failed to determine if /home is encrypted, ignoring: %m");
+ log_warning_errno(r, "Failed to determine if %s is encrypted, ignoring: %m", get_home_root());
r = dlopen_cryptsetup();
if (r < 0)
@@ -1114,14 +1148,14 @@ static int determine_default_storage(UserStorage *ret) {
} else
log_info("Running in container, not using '%s' storage.", user_storage_to_string(USER_LUKS));
- r = path_is_fs_type("/home", BTRFS_SUPER_MAGIC);
+ r = path_is_fs_type(get_home_root(), BTRFS_SUPER_MAGIC);
if (r < 0)
- log_warning_errno(r, "Failed to determine file system of /home, ignoring: %m");
+ log_warning_errno(r, "Failed to determine file system of %s, ignoring: %m", get_home_root());
if (r > 0) {
- log_info("/home is on btrfs, using '%s' as storage.", user_storage_to_string(USER_SUBVOLUME));
+ log_info("%s is on btrfs, using '%s' as storage.", get_home_root(), user_storage_to_string(USER_SUBVOLUME));
*ret = USER_SUBVOLUME;
} else {
- log_info("/home is on simple file system, using '%s' as storage.", user_storage_to_string(USER_DIRECTORY));
+ log_info("%s is on simple file system, using '%s' as storage.", get_home_root(), user_storage_to_string(USER_DIRECTORY));
*ret = USER_DIRECTORY;
}
@@ -1268,9 +1302,21 @@ static int home_remove(UserRecord *h) {
if (unlink(ip) < 0) {
if (errno != ENOENT)
return log_error_errno(errno, "Failed to remove %s: %m", ip);
- } else
+ } else {
+ _cleanup_free_ char *parent = NULL;
+
deleted = true;
+ r = path_extract_directory(ip, &parent);
+ if (r < 0)
+ log_debug_errno(r, "Failed to determine parent directory of '%s': %m", ip);
+ else {
+ r = fsync_path_at(AT_FDCWD, parent);
+ if (r < 0)
+ log_debug_errno(r, "Failed to synchronize disk after deleting '%s', ignoring: %m", ip);
+ }
+ }
+
} else if (S_ISBLK(st.st_mode))
log_info("Not removing file system on block device %s.", ip);
else
@@ -1285,7 +1331,7 @@ static int home_remove(UserRecord *h) {
case USER_FSCRYPT:
assert(ip);
- r = rm_rf(ip, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
+ r = rm_rf(ip, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_SYNCFS);
if (r < 0) {
if (r != -ENOENT)
return log_warning_errno(r, "Failed to remove %s: %m", ip);
@@ -1316,9 +1362,12 @@ static int home_remove(UserRecord *h) {
deleted = true;
}
- if (deleted)
+ if (deleted) {
+ if (user_record_drop_caches(h))
+ drop_caches_now();
+
log_info("Everything completed.");
- else
+ } else
return log_notice_errno(SYNTHETIC_ERRNO(EALREADY),
"Nothing to remove.");
@@ -1706,6 +1755,7 @@ static int run(int argc, char *argv[]) {
* ENOEXEC → file system is currently not active
* ENOSPC → not enough disk space for operation
* EKEYREVOKED → user record has not suitable hashed password or pkcs#11 entry, we cannot authenticate
+ * EADDRINUSE → home image is already used elsewhere (lock taken)
*/
if (streq(argv[1], "activate"))
diff --git a/src/home/homework.h b/src/home/homework.h
index fb53fd49b0..f20a23a918 100644
--- a/src/home/homework.h
+++ b/src/home/homework.h
@@ -32,6 +32,7 @@ typedef struct HomeSetup {
bool do_offline_fitrim;
bool do_offline_fallocate;
bool do_mark_clean;
+ bool do_drop_caches;
uint64_t partition_offset;
uint64_t partition_size;
diff --git a/src/home/user-record-util.c b/src/home/user-record-util.c
index bc0c4171b4..464f1dbb7a 100644
--- a/src/home/user-record-util.c
+++ b/src/home/user-record-util.c
@@ -75,7 +75,7 @@ int user_record_synthesize(
if (!ip)
return -ENOMEM;
- hd = path_join("/home/", user_name);
+ hd = path_join(get_home_root(), user_name);
if (!hd)
return -ENOMEM;
diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c
index 0a90091a86..cbff5036a4 100644
--- a/src/journal/journald-stream.c
+++ b/src/journal/journald-stream.c
@@ -36,6 +36,7 @@
#include "syslog-util.h"
#include "tmpfile-util.h"
#include "unit-name.h"
+#include "user-util.h"
#define STDOUT_STREAMS_MAX 4096
@@ -663,6 +664,7 @@ int stdout_stream_install(Server *s, int fd, StdoutStream **ret) {
*stream = (StdoutStream) {
.fd = -1,
.priority = LOG_INFO,
+ .ucred = UCRED_INVALID,
};
xsprintf(stream->id_field, "_STREAM_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id));
@@ -727,9 +729,9 @@ static int stdout_stream_new(sd_event_source *es, int listen_fd, uint32_t revent
}
if (s->n_stdout_streams >= STDOUT_STREAMS_MAX) {
- struct ucred u;
+ struct ucred u = UCRED_INVALID;
- r = getpeercred(fd, &u);
+ (void) getpeercred(fd, &u);
/* By closing fd here we make sure that the client won't wait too long for journald to
* gather all the data it adds to the error message to find out that the connection has
@@ -737,7 +739,7 @@ static int stdout_stream_new(sd_event_source *es, int listen_fd, uint32_t revent
*/
fd = safe_close(fd);
- server_driver_message(s, r < 0 ? 0 : u.pid, NULL, LOG_MESSAGE("Too many stdout streams, refusing connection."), NULL);
+ server_driver_message(s, u.pid, NULL, LOG_MESSAGE("Too many stdout streams, refusing connection."), NULL);
return 0;
}
diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c
index 43f9a7bdaa..61c5509e1c 100644
--- a/src/libsystemd/sd-bus/bus-common-errors.c
+++ b/src/libsystemd/sd-bus/bus-common-errors.c
@@ -142,6 +142,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
SD_BUS_ERROR_MAP(BUS_ERROR_TOO_MANY_OPERATIONS, ENOBUFS),
SD_BUS_ERROR_MAP(BUS_ERROR_AUTHENTICATION_LIMIT_HIT, ETOOMANYREFS),
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_CANT_AUTHENTICATE, EKEYREVOKED),
+ SD_BUS_ERROR_MAP(BUS_ERROR_HOME_IN_USE, EADDRINUSE),
SD_BUS_ERROR_MAP_END
};
diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h
index fd8f0c240e..348cd5094b 100644
--- a/src/libsystemd/sd-bus/bus-common-errors.h
+++ b/src/libsystemd/sd-bus/bus-common-errors.h
@@ -126,5 +126,6 @@
#define BUS_ERROR_TOO_MANY_OPERATIONS "org.freedesktop.home1.TooManyOperations"
#define BUS_ERROR_AUTHENTICATION_LIMIT_HIT "org.freedesktop.home1.AuthenticationLimitHit"
#define BUS_ERROR_HOME_CANT_AUTHENTICATE "org.freedesktop.home1.HomeCantAuthenticate"
+#define BUS_ERROR_HOME_IN_USE "org.freedesktop.home1.HomeInUse"
BUS_ERROR_MAP_ELF_USE(bus_common_errors);
diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c
index 80f2bdd87f..d8a7c81764 100644
--- a/src/libsystemd/sd-bus/sd-bus.c
+++ b/src/libsystemd/sd-bus/sd-bus.c
@@ -249,6 +249,7 @@ _public_ int sd_bus_new(sd_bus **ret) {
.original_pid = getpid_cached(),
.n_groups = SIZE_MAX,
.close_on_exit = true,
+ .ucred = UCRED_INVALID,
};
/* We guarantee that wqueue always has space for at least one entry */
diff --git a/src/libsystemd/sd-login/sd-login.c b/src/libsystemd/sd-login/sd-login.c
index d127443c4c..4a35e61425 100644
--- a/src/libsystemd/sd-login/sd-login.c
+++ b/src/libsystemd/sd-login/sd-login.c
@@ -136,7 +136,7 @@ _public_ int sd_pid_get_cgroup(pid_t pid, char **cgroup) {
}
_public_ int sd_peer_get_session(int fd, char **session) {
- struct ucred ucred = {};
+ struct ucred ucred = UCRED_INVALID;
int r;
assert_return(fd >= 0, -EBADF);
diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c
index d9cd8fb2b2..a8693660fa 100644
--- a/src/shared/rm-rf.c
+++ b/src/shared/rm-rf.c
@@ -250,6 +250,9 @@ int rm_rf_children(
ret = r;
}
+ if (FLAGS_SET(flags, REMOVE_SYNCFS) && syncfs(dirfd(d)) < 0 && ret >= 0)
+ ret = -errno;
+
return ret;
}
diff --git a/src/shared/rm-rf.h b/src/shared/rm-rf.h
index 577a2795e0..24fd9a2aa2 100644
--- a/src/shared/rm-rf.h
+++ b/src/shared/rm-rf.h
@@ -14,6 +14,7 @@ typedef enum RemoveFlags {
REMOVE_MISSING_OK = 1 << 4, /* If the top-level directory is missing, ignore the ENOENT for it */
REMOVE_CHMOD = 1 << 5, /* chmod() for write access if we cannot delete or access something */
REMOVE_CHMOD_RESTORE = 1 << 6, /* Restore the old mode before returning */
+ REMOVE_SYNCFS = 1 << 7, /* syncfs() the root of the specified directory after removing everything in it */
} RemoveFlags;
int unlinkat_harder(int dfd, const char *filename, int unlink_flags, RemoveFlags remove_flags);
diff --git a/src/shared/user-record-show.c b/src/shared/user-record-show.c
index 29aa5c0c7c..dee6c4e5ee 100644
--- a/src/shared/user-record-show.c
+++ b/src/shared/user-record-show.c
@@ -435,6 +435,9 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
if (hr->password_change_now >= 0)
printf("Pas. Ch. Now: %s\n", yes_no(hr->password_change_now));
+ if (hr->drop_caches >= 0 || user_record_drop_caches(hr))
+ printf(" Drop Caches: %s\n", yes_no(user_record_drop_caches(hr)));
+
if (!strv_isempty(hr->ssh_authorized_keys))
printf("SSH Pub. Key: %zu\n", strv_length(hr->ssh_authorized_keys));
diff --git a/src/shared/user-record.c b/src/shared/user-record.c
index f4e509e13e..9b2029bfcf 100644
--- a/src/shared/user-record.c
+++ b/src/shared/user-record.c
@@ -202,6 +202,7 @@ UserRecord* user_record_new(void) {
.pkcs11_protected_authentication_path_permitted = -1,
.fido2_user_presence_permitted = -1,
.fido2_user_verification_permitted = -1,
+ .drop_caches = -1,
};
return h;
@@ -1284,6 +1285,7 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
{ "luksPbkdfTimeCostUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 },
{ "luksPbkdfMemoryCost", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 },
{ "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 },
+ { "dropCaches", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, drop_caches), 0 },
{ "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 },
{ "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
{ "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 },
@@ -1406,11 +1408,11 @@ int user_record_build_image_path(UserStorage storage, const char *user_name_and_
return 0;
}
- z = strjoin("/home/", user_name_and_realm, suffix);
+ z = strjoin(get_home_root(), "/", user_name_and_realm, suffix);
if (!z)
return -ENOMEM;
- *ret = z;
+ *ret = path_simplify(z);
return 1;
}
@@ -1435,7 +1437,7 @@ static int user_record_augment(UserRecord *h, JsonDispatchFlags json_flags) {
return 0;
if (!h->home_directory && !h->home_directory_auto) {
- h->home_directory_auto = path_join("/home/", h->user_name);
+ h->home_directory_auto = path_join(get_home_root(), h->user_name);
if (!h->home_directory_auto)
return json_log_oom(h->json, json_flags);
}
@@ -1620,7 +1622,7 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
{ "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 },
{ "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
{ "luksDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_discard), 0 },
- { "luksOfflineDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_offline_discard), 0 },
+ { "luksOfflineDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_offline_discard), 0 },
{ "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
{ "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
{ "luksVolumeKeySize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_volume_key_size), 0 },
@@ -1629,6 +1631,7 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
{ "luksPbkdfTimeCostUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 },
{ "luksPbkdfMemoryCost", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 },
{ "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 },
+ { "dropCaches", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, drop_caches), 0 },
{ "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE },
{ "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 },
{ "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
@@ -2021,6 +2024,16 @@ bool user_record_can_authenticate(UserRecord *h) {
return !strv_isempty(h->hashed_password);
}
+bool user_record_drop_caches(UserRecord *h) {
+ assert(h);
+
+ if (h->drop_caches >= 0)
+ return h->drop_caches;
+
+ /* By default drop caches on fscrypt, not otherwise. */
+ return user_record_storage(h) == USER_FSCRYPT;
+}
+
uint64_t user_record_ratelimit_next_try(UserRecord *h) {
assert(h);
diff --git a/src/shared/user-record.h b/src/shared/user-record.h
index fa58dfdb6e..975e3e175b 100644
--- a/src/shared/user-record.h
+++ b/src/shared/user-record.h
@@ -353,6 +353,7 @@ typedef struct UserRecord {
int removable;
int enforce_password_policy;
int auto_login;
+ int drop_caches;
uint64_t stop_delay_usec; /* How long to leave systemd --user around on log-out */
int kill_processes; /* Whether to kill user processes forcibly on log-out */
@@ -419,6 +420,7 @@ int user_record_removable(UserRecord *h);
usec_t user_record_ratelimit_interval_usec(UserRecord *h);
uint64_t user_record_ratelimit_burst(UserRecord *h);
bool user_record_can_authenticate(UserRecord *h);
+bool user_record_drop_caches(UserRecord *h);
int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret);
diff --git a/src/shared/varlink.c b/src/shared/varlink.c
index 07a1b96f60..c34a08cf57 100644
--- a/src/shared/varlink.c
+++ b/src/shared/varlink.c
@@ -258,8 +258,7 @@ static int varlink_new(Varlink **ret) {
.state = _VARLINK_STATE_INVALID,
- .ucred.uid = UID_INVALID,
- .ucred.gid = GID_INVALID,
+ .ucred = UCRED_INVALID,
.timestamp = USEC_INFINITY,
.timeout = VARLINK_DEFAULT_TIMEOUT_USEC
@@ -2077,7 +2076,7 @@ static int validate_connection(VarlinkServer *server, const struct ucred *ucred)
return 1;
}
-static int count_connection(VarlinkServer *server, struct ucred *ucred) {
+static int count_connection(VarlinkServer *server, const struct ucred *ucred) {
unsigned c;
int r;
@@ -2106,8 +2105,8 @@ static int count_connection(VarlinkServer *server, struct ucred *ucred) {
int varlink_server_add_connection(VarlinkServer *server, int fd, Varlink **ret) {
_cleanup_(varlink_unrefp) Varlink *v = NULL;
+ struct ucred ucred = UCRED_INVALID;
bool ucred_acquired;
- struct ucred ucred;
int r;
assert_return(server, -EINVAL);