diff --git a/man/systemd-homed.service.xml b/man/systemd-homed.service.xml
index a4561776c9f..42e5780cccc 100644
--- a/man/systemd-homed.service.xml
+++ b/man/systemd-homed.service.xml
@@ -46,6 +46,10 @@
url="https://systemd.io/USER_GROUP_API">User/Group Record Lookup API via Varlink, and thus may be
browsed with
userdbctl1.
+
+ systemd-homed.service also manages blob directories for each home directory
+ it manages. See User Record Blob Directories
+ for more details.
diff --git a/src/home/home-util.c b/src/home/home-util.c
index c777d7b0eb6..9c9c0ff78aa 100644
--- a/src/home/home-util.c
+++ b/src/home/home-util.c
@@ -137,3 +137,7 @@ int bus_message_append_secret(sd_bus_message *m, UserRecord *secret) {
const char *home_record_dir(void) {
return secure_getenv("SYSTEMD_HOME_RECORD_DIR") ?: "/var/lib/systemd/home/";
}
+
+const char *home_system_blob_dir(void) {
+ return secure_getenv("SYSTEMD_HOME_SYSTEM_BLOB_DIR") ?: "/var/cache/systemd/home/";
+}
diff --git a/src/home/home-util.h b/src/home/home-util.h
index 36b301d2360..ead8f5637ee 100644
--- a/src/home/home-util.h
+++ b/src/home/home-util.h
@@ -35,3 +35,4 @@ int bus_message_append_secret(sd_bus_message *m, UserRecord *secret);
#define HOME_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE)
const char *home_record_dir(void);
+const char *home_system_blob_dir(void);
diff --git a/src/home/homed-home.c b/src/home/homed-home.c
index e1422475dce..7bb078ee2c8 100644
--- a/src/home/homed-home.c
+++ b/src/home/homed-home.c
@@ -33,6 +33,7 @@
#include "process-util.h"
#include "quota-util.h"
#include "resize-fs.h"
+#include "rm-rf.h"
#include "set.h"
#include "signal-util.h"
#include "stat-util.h"
@@ -96,7 +97,7 @@ static int suitable_home_record(UserRecord *hr) {
int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) {
_cleanup_(home_freep) Home *home = NULL;
- _cleanup_free_ char *nm = NULL, *ns = NULL;
+ _cleanup_free_ char *nm = NULL, *ns = NULL, *blob = NULL;
int r;
assert(m);
@@ -162,6 +163,13 @@ int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) {
if (r < 0)
return r;
+ blob = path_join(home_system_blob_dir(), hr->user_name);
+ if (!blob)
+ return -ENOMEM;
+ r = mkdir_safe(blob, 0755, 0, 0, MKDIR_IGNORE_EXISTING);
+ if (r < 0)
+ log_warning_errno(r, "Failed to create blob dir for user '%s': %m", home->user_name);
+
(void) bus_manager_emit_auto_login_changed(m);
(void) bus_home_emit_change(home);
(void) manager_schedule_rebalance(m, /* immediately= */ false);
@@ -323,7 +331,9 @@ int home_save_record(Home *h) {
}
int home_unlink_record(Home *h) {
+ _cleanup_free_ char *blob = NULL;
const char *fn;
+ int r;
assert(h);
@@ -335,6 +345,13 @@ int home_unlink_record(Home *h) {
if (unlink(fn) < 0 && errno != ENOENT)
return -errno;
+ blob = path_join(home_system_blob_dir(), h->user_name);
+ if (!blob)
+ return -ENOMEM;
+ r = rm_rf(blob, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_MISSING_OK);
+ if (r < 0)
+ return r;
+
return 0;
}
diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c
index 810ecb23fcf..2e07e90f9c7 100644
--- a/src/home/homed-manager.c
+++ b/src/home/homed-manager.c
@@ -42,6 +42,7 @@
#include "quota-util.h"
#include "random-util.h"
#include "resize-fs.h"
+#include "rm-rf.h"
#include "socket-util.h"
#include "sort-util.h"
#include "stat-util.h"
@@ -79,6 +80,7 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(homes_by_sysfs_hash_ops, char, pat
static int on_home_inotify(sd_event_source *s, const struct inotify_event *event, void *userdata);
static int manager_gc_images(Manager *m);
+static int manager_gc_blob(Manager *m);
static int manager_enumerate_images(Manager *m);
static int manager_assess_image(Manager *m, int dir_fd, const char *dir_path, const char *dentry_name);
static void manager_revalidate_image(Manager *m, Home *h);
@@ -1627,6 +1629,9 @@ int manager_startup(Manager *m) {
/* Let's clean up home directories whose devices got removed while we were not running */
(void) manager_enqueue_gc(m, NULL);
+ /* Let's clean up blob directories for home dirs that no longer exist */
+ (void) manager_gc_blob(m);
+
return 0;
}
@@ -1715,6 +1720,29 @@ int manager_gc_images(Manager *m) {
return 0;
}
+static int manager_gc_blob(Manager *m) {
+ _cleanup_closedir_ DIR *d = NULL;
+ int r;
+
+ assert(m);
+
+ d = opendir(home_system_blob_dir());
+ if (!d) {
+ if (errno == ENOENT)
+ return 0;
+ return log_error_errno(errno, "Failed to open %s: %m", home_system_blob_dir());
+ }
+
+ FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read system blob directory: %m"))
+ if (!hashmap_contains(m->homes_by_name, de->d_name)) {
+ r = rm_rf_at(dirfd(d), de->d_name, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
+ if (r < 0)
+ log_warning_errno(r, "Failed to delete blob dir for missing user '%s', ignoring: %m", de->d_name);
+ }
+
+ return 0;
+}
+
static int on_deferred_rescan(sd_event_source *s, void *userdata) {
Manager *m = ASSERT_PTR(userdata);
diff --git a/src/home/user-record-util.c b/src/home/user-record-util.c
index 8620371ac6a..b18a77d7407 100644
--- a/src/home/user-record-util.c
+++ b/src/home/user-record-util.c
@@ -282,7 +282,7 @@ int user_record_add_binding(
gid_t gid) {
_cleanup_(json_variant_unrefp) JsonVariant *new_binding_entry = NULL, *binding = NULL;
- _cleanup_free_ char *ip = NULL, *hd = NULL, *ip_auto = NULL, *lc = NULL, *lcm = NULL, *fst = NULL;
+ _cleanup_free_ char *blob = NULL, *ip = NULL, *hd = NULL, *ip_auto = NULL, *lc = NULL, *lcm = NULL, *fst = NULL;
sd_id128_t mid;
int r;
@@ -291,6 +291,10 @@ int user_record_add_binding(
if (!h->json)
return -EUNATCH;
+ blob = path_join(home_system_blob_dir(), h->user_name);
+ if (!blob)
+ return -ENOMEM;
+
r = sd_id128_get_machine(&mid);
if (r < 0)
return r;
@@ -331,6 +335,7 @@ int user_record_add_binding(
r = json_build(&new_binding_entry,
JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("blobDirectory", JSON_BUILD_STRING(blob)),
JSON_BUILD_PAIR_CONDITION(!!image_path, "imagePath", JSON_BUILD_STRING(image_path)),
JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(partition_uuid), "partitionUuid", JSON_BUILD_STRING(SD_ID128_TO_UUID_STRING(partition_uuid))),
JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(luks_uuid), "luksUuid", JSON_BUILD_STRING(SD_ID128_TO_UUID_STRING(luks_uuid))),
@@ -370,6 +375,8 @@ int user_record_add_binding(
if (r < 0)
return r;
+ free_and_replace(h->blob_directory, blob);
+
if (storage >= 0)
h->storage = storage;
@@ -1383,6 +1390,12 @@ int user_record_is_supported(UserRecord *hr, sd_bus_error *error) {
if (hr->service && !streq(hr->service, "io.systemd.Home"))
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Not accepted with service not matching io.systemd.Home.");
+ if (hr->blob_directory) {
+ /* This function is always called w/o binding section, so if hr->blob_dir is set then the caller set it themselves */
+ assert((hr->mask & USER_RECORD_BINDING) == 0);
+ return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot manage custom blob directories.");
+ }
+
return 0;
}
diff --git a/units/systemd-homed.service.in b/units/systemd-homed.service.in
index ee72c75ac96..ff4f429498c 100644
--- a/units/systemd-homed.service.in
+++ b/units/systemd-homed.service.in
@@ -30,6 +30,7 @@ RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_ALG AF_INET AF_INET6
RestrictNamespaces=mnt user
RestrictRealtime=yes
StateDirectory=systemd/home
+CacheDirectory=systemd/home
SystemCallArchitectures=native
SystemCallErrorNumber=EPERM
SystemCallFilter=@system-service @mount quotactl