From f639f60ed5e1541cb3bee20bd1d1ed6fad8fd104 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 30 Oct 2021 18:05:52 +0200 Subject: [PATCH 1/4] homed: allow querying disk free status separetely from generating JSON from it We later want to query per-home free status for implementing automatic grow/shrink of home directories, hence let's separate the JSON generation from the disk free status determination. --- src/home/homed-home.c | 134 +++++++++++++++++++++++++++++------------- src/home/homed-home.h | 2 + 2 files changed, 94 insertions(+), 42 deletions(-) diff --git a/src/home/homed-home.c b/src/home/homed-home.c index 10bb96a797..4f00c64fc9 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -2116,10 +2116,6 @@ static int home_get_disk_status_luks( int r; assert(h); - assert(ret_disk_size); - assert(ret_disk_usage); - assert(ret_disk_free); - assert(ret_disk_ceiling); if (state != HOME_ABSENT) { const char *ip; @@ -2209,11 +2205,16 @@ finish: if (disk_floor == UINT64_MAX) disk_floor = minimal_size_by_fs_name(user_record_file_system_type(h->record)); - *ret_disk_size = disk_size; - *ret_disk_usage = disk_usage; - *ret_disk_free = disk_free; - *ret_disk_ceiling = disk_ceiling; - *ret_disk_floor = disk_floor; + if (ret_disk_size) + *ret_disk_size = disk_size; + if (ret_disk_usage) + *ret_disk_usage = disk_usage; + if (ret_disk_free) + *ret_disk_free = disk_free; + if (ret_disk_ceiling) + *ret_disk_ceiling = disk_ceiling; + if (ret_disk_floor) + *ret_disk_floor = disk_floor; return 0; } @@ -2234,11 +2235,7 @@ static int home_get_disk_status_directory( const char *path = NULL; int r; - assert(ret_disk_size); - assert(ret_disk_usage); - assert(ret_disk_free); - assert(ret_disk_ceiling); - assert(ret_disk_floor); + assert(h); if (HOME_STATE_IS_ACTIVE(state)) path = user_record_home_directory(h->record); @@ -2349,15 +2346,82 @@ static int home_get_disk_status_directory( } finish: - *ret_disk_size = disk_size; - *ret_disk_usage = disk_usage; - *ret_disk_free = disk_free; - *ret_disk_ceiling = disk_ceiling; - *ret_disk_floor = disk_floor; + if (ret_disk_size) + *ret_disk_size = disk_size; + if (ret_disk_usage) + *ret_disk_usage = disk_usage; + if (ret_disk_free) + *ret_disk_free = disk_free; + if (ret_disk_ceiling) + *ret_disk_ceiling = disk_ceiling; + if (ret_disk_floor) + *ret_disk_floor = disk_floor; return 0; } +static int home_get_disk_status_internal( + Home *h, + HomeState state, + uint64_t *ret_disk_size, + uint64_t *ret_disk_usage, + uint64_t *ret_disk_free, + uint64_t *ret_disk_ceiling, + uint64_t *ret_disk_floor) { + + assert(h); + assert(h->record); + + switch (h->record->storage) { + + case USER_LUKS: + return home_get_disk_status_luks(h, state, ret_disk_size, ret_disk_usage, ret_disk_free, ret_disk_ceiling, ret_disk_floor); + + case USER_CLASSIC: + case USER_DIRECTORY: + case USER_SUBVOLUME: + case USER_FSCRYPT: + case USER_CIFS: + return home_get_disk_status_directory(h, state, ret_disk_size, ret_disk_usage, ret_disk_free, ret_disk_ceiling, ret_disk_floor); + + default: + /* don't know */ + + if (ret_disk_size) + *ret_disk_size = UINT64_MAX; + if (ret_disk_usage) + *ret_disk_usage = UINT64_MAX; + if (ret_disk_free) + *ret_disk_free = UINT64_MAX; + if (ret_disk_ceiling) + *ret_disk_ceiling = UINT64_MAX; + if (ret_disk_floor) + *ret_disk_floor = UINT64_MAX; + + return 0; + } +} + +int home_get_disk_status( + Home *h, + uint64_t *ret_disk_size, + uint64_t *ret_disk_usage, + uint64_t *ret_disk_free, + uint64_t *ret_disk_ceiling, + uint64_t *ret_disk_floor) { + + assert(h); + + return home_get_disk_status_internal( + h, + home_get_state(h), + ret_disk_size, + ret_disk_usage, + ret_disk_free, + ret_disk_ceiling, + ret_disk_floor); +} + int home_augment_status( Home *h, UserRecordLoadFlags flags, @@ -2382,29 +2446,15 @@ int home_augment_status( state = home_get_state(h); - switch (h->record->storage) { - - case USER_LUKS: - r = home_get_disk_status_luks(h, state, &disk_size, &disk_usage, &disk_free, &disk_ceiling, &disk_floor); - if (r < 0) - return r; - - break; - - case USER_CLASSIC: - case USER_DIRECTORY: - case USER_SUBVOLUME: - case USER_FSCRYPT: - case USER_CIFS: - r = home_get_disk_status_directory(h, state, &disk_size, &disk_usage, &disk_free, &disk_ceiling, &disk_floor); - if (r < 0) - return r; - - break; - - default: - ; /* unset */ - } + r = home_get_disk_status_internal( + h, state, + &disk_size, + &disk_usage, + &disk_free, + &disk_ceiling, + &disk_floor); + if (r < 0) + return r; if (disk_floor == UINT64_MAX || (disk_usage != UINT64_MAX && disk_floor < disk_usage)) disk_floor = disk_usage; diff --git a/src/home/homed-home.h b/src/home/homed-home.h index 7cd8a9edb4..c5654140d0 100644 --- a/src/home/homed-home.h +++ b/src/home/homed-home.h @@ -190,6 +190,8 @@ int home_unlock(Home *h, UserRecord *secret, sd_bus_error *error); HomeState home_get_state(Home *h); +int home_get_disk_status(Home *h, uint64_t *ret_disk_size,uint64_t *ret_disk_usage, uint64_t *ret_disk_free, uint64_t *ret_disk_ceiling, uint64_t *ret_disk_floor); + void home_process_notify(Home *h, char **l, int fd); int home_killall(Home *h); From b0a7fb152a9b46d7c1b021a38d3e2dedc74d3e49 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 5 Nov 2021 16:34:12 +0100 Subject: [PATCH 2/4] homed: include actual fs type + access mode as part of "status" section of user record So far we have two properties for the intended fstype + access mode of home dirs, but they might differ from what is actually used (because the user record changed from the home dir, after it was created, or vice versa). Let's hence add these props also to the "status" section of user record, which report the status quo. That way we can always show the correct, current settings. --- src/home/homed-home.c | 91 ++++++++++++++++++++++++++++++++++------ src/home/homed-home.h | 3 +- src/shared/user-record.c | 32 +++++++------- 3 files changed, 97 insertions(+), 29 deletions(-) diff --git a/src/home/homed-home.c b/src/home/homed-home.c index 4f00c64fc9..2b4e5dde8f 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -17,9 +17,11 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "fs-util.h" #include "home-util.h" #include "homed-home-bus.h" #include "homed-home.h" +#include "missing_magic.h" #include "missing_syscall.h" #include "mkdir.h" #include "path-util.h" @@ -32,9 +34,9 @@ #include "stat-util.h" #include "string-table.h" #include "strv.h" +#include "user-record-pwquality.h" #include "user-record-sign.h" #include "user-record-util.h" -#include "user-record-pwquality.h" #include "user-record.h" #include "user-util.h" @@ -2105,13 +2107,17 @@ static int home_get_disk_status_luks( uint64_t *ret_disk_usage, uint64_t *ret_disk_free, uint64_t *ret_disk_ceiling, - uint64_t *ret_disk_floor) { + uint64_t *ret_disk_floor, + statfs_f_type_t *ret_fstype, + mode_t *ret_access_mode) { uint64_t disk_size = UINT64_MAX, disk_usage = UINT64_MAX, disk_free = UINT64_MAX, disk_ceiling = UINT64_MAX, disk_floor = UINT64_MAX, stat_used = UINT64_MAX, fs_size = UINT64_MAX, header_size = 0; - + mode_t access_mode = MODE_INVALID; + statfs_f_type_t fstype = 0; struct statfs sfs; + struct stat st; const char *hd; int r; @@ -2122,8 +2128,6 @@ static int home_get_disk_status_luks( ip = user_record_image_path(h->record); if (ip) { - struct stat st; - if (stat(ip, &st) < 0) log_debug_errno(errno, "Failed to stat() %s, ignoring: %m", ip); else if (S_ISREG(st.st_mode)) { @@ -2171,11 +2175,26 @@ static int home_get_disk_status_luks( if (!hd) goto finish; + if (stat(hd, &st) < 0) { + log_debug_errno(errno, "Failed to stat() %s, ignoring: %m", hd); + goto finish; + } + + r = stat_verify_directory(&st); + if (r < 0) { + log_debug_errno(r, "Home directory %s is not a directory, ignoring: %m", hd); + goto finish; + } + + access_mode = st.st_mode & 07777; + if (statfs(hd, &sfs) < 0) { log_debug_errno(errno, "Failed to statfs() %s, ignoring: %m", hd); goto finish; } + fstype = sfs.f_type; + disk_free = sfs.f_bsize * sfs.f_bavail; fs_size = sfs.f_bsize * sfs.f_blocks; if (disk_size != UINT64_MAX && disk_size > fs_size) @@ -2215,6 +2234,10 @@ finish: *ret_disk_ceiling = disk_ceiling; if (ret_disk_floor) *ret_disk_floor = disk_floor; + if (ret_fstype) + *ret_fstype = fstype; + if (ret_access_mode) + *ret_access_mode = access_mode; return 0; } @@ -2226,10 +2249,14 @@ static int home_get_disk_status_directory( uint64_t *ret_disk_usage, uint64_t *ret_disk_free, uint64_t *ret_disk_ceiling, - uint64_t *ret_disk_floor) { + uint64_t *ret_disk_floor, + statfs_f_type_t *ret_fstype, + mode_t *ret_access_mode) { uint64_t disk_size = UINT64_MAX, disk_usage = UINT64_MAX, disk_free = UINT64_MAX, disk_ceiling = UINT64_MAX, disk_floor = UINT64_MAX; + mode_t access_mode = MODE_INVALID; + statfs_f_type_t fstype = 0; struct statfs sfs; struct dqblk req; const char *path = NULL; @@ -2258,6 +2285,8 @@ static int home_get_disk_status_directory( /* We don't initialize disk_usage from statfs() data here, since the device is likely not used * by us alone, and disk_usage should only reflect our own use. */ + + fstype = sfs.f_type; } if (IN_SET(h->record->storage, USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME)) { @@ -2356,6 +2385,10 @@ finish: *ret_disk_ceiling = disk_ceiling; if (ret_disk_floor) *ret_disk_floor = disk_floor; + if (ret_fstype) + *ret_fstype = fstype; + if (ret_access_mode) + *ret_access_mode = access_mode; return 0; } @@ -2367,7 +2400,9 @@ static int home_get_disk_status_internal( uint64_t *ret_disk_usage, uint64_t *ret_disk_free, uint64_t *ret_disk_ceiling, - uint64_t *ret_disk_floor) { + uint64_t *ret_disk_floor, + statfs_f_type_t *ret_fstype, + mode_t *ret_access_mode) { assert(h); assert(h->record); @@ -2375,14 +2410,14 @@ static int home_get_disk_status_internal( switch (h->record->storage) { case USER_LUKS: - return home_get_disk_status_luks(h, state, ret_disk_size, ret_disk_usage, ret_disk_free, ret_disk_ceiling, ret_disk_floor); + return home_get_disk_status_luks(h, state, ret_disk_size, ret_disk_usage, ret_disk_free, ret_disk_ceiling, ret_disk_floor, ret_fstype, ret_access_mode); case USER_CLASSIC: case USER_DIRECTORY: case USER_SUBVOLUME: case USER_FSCRYPT: case USER_CIFS: - return home_get_disk_status_directory(h, state, ret_disk_size, ret_disk_usage, ret_disk_free, ret_disk_ceiling, ret_disk_floor); + return home_get_disk_status_directory(h, state, ret_disk_size, ret_disk_usage, ret_disk_free, ret_disk_ceiling, ret_disk_floor, ret_fstype, ret_access_mode); default: /* don't know */ @@ -2397,6 +2432,10 @@ static int home_get_disk_status_internal( *ret_disk_ceiling = UINT64_MAX; if (ret_disk_floor) *ret_disk_floor = UINT64_MAX; + if (ret_fstype) + *ret_fstype = 0; + if (ret_access_mode) + *ret_access_mode = MODE_INVALID; return 0; } @@ -2408,7 +2447,9 @@ int home_get_disk_status( uint64_t *ret_disk_usage, uint64_t *ret_disk_free, uint64_t *ret_disk_ceiling, - uint64_t *ret_disk_floor) { + uint64_t *ret_disk_floor, + statfs_f_type_t *ret_fstype, + mode_t *ret_access_mode) { assert(h); @@ -2419,7 +2460,22 @@ int home_get_disk_status( ret_disk_usage, ret_disk_free, ret_disk_ceiling, - ret_disk_floor); + ret_disk_floor, + ret_fstype, + ret_access_mode); +} + +static const char *fstype_magic_to_name(statfs_f_type_t magic) { + /* For now, let's only translate the magic values of the file systems we actually are able to manage */ + + if (F_TYPE_EQUAL(magic, EXT4_SUPER_MAGIC)) + return "ext4"; + if (F_TYPE_EQUAL(magic, XFS_SUPER_MAGIC)) + return "xfs"; + if (F_TYPE_EQUAL(magic, BTRFS_SUPER_MAGIC)) + return "btrfs"; + + return NULL; } int home_augment_status( @@ -2430,6 +2486,9 @@ int home_augment_status( uint64_t disk_size = UINT64_MAX, disk_usage = UINT64_MAX, disk_free = UINT64_MAX, disk_ceiling = UINT64_MAX, disk_floor = UINT64_MAX; _cleanup_(json_variant_unrefp) JsonVariant *j = NULL, *v = NULL, *m = NULL, *status = NULL; _cleanup_(user_record_unrefp) UserRecord *ur = NULL; + statfs_f_type_t magic; + const char *fstype; + mode_t access_mode; HomeState state; sd_id128_t id; int r; @@ -2452,10 +2511,14 @@ int home_augment_status( &disk_usage, &disk_free, &disk_ceiling, - &disk_floor); + &disk_floor, + &magic, + &access_mode); if (r < 0) return r; + fstype = fstype_magic_to_name(magic); + if (disk_floor == UINT64_MAX || (disk_usage != UINT64_MAX && disk_floor < disk_usage)) disk_floor = disk_usage; if (disk_floor == UINT64_MAX || disk_floor < USER_DISK_SIZE_MIN) @@ -2472,7 +2535,9 @@ int home_augment_status( JSON_BUILD_PAIR_CONDITION(disk_free != UINT64_MAX, "diskFree", JSON_BUILD_UNSIGNED(disk_free)), JSON_BUILD_PAIR_CONDITION(disk_ceiling != UINT64_MAX, "diskCeiling", JSON_BUILD_UNSIGNED(disk_ceiling)), JSON_BUILD_PAIR_CONDITION(disk_floor != UINT64_MAX, "diskFloor", JSON_BUILD_UNSIGNED(disk_floor)), - JSON_BUILD_PAIR_CONDITION(h->signed_locally >= 0, "signedLocally", JSON_BUILD_BOOLEAN(h->signed_locally)) + JSON_BUILD_PAIR_CONDITION(h->signed_locally >= 0, "signedLocally", JSON_BUILD_BOOLEAN(h->signed_locally)), + JSON_BUILD_PAIR_CONDITION(fstype, "fileSystemType", JSON_BUILD_STRING(fstype)), + JSON_BUILD_PAIR_CONDITION(access_mode != MODE_INVALID, "accessMode", JSON_BUILD_UNSIGNED(access_mode)) )); if (r < 0) return r; diff --git a/src/home/homed-home.h b/src/home/homed-home.h index c5654140d0..37c54b0557 100644 --- a/src/home/homed-home.h +++ b/src/home/homed-home.h @@ -7,6 +7,7 @@ typedef struct Home Home; #include "homed-operation.h" #include "list.h" #include "ordered-set.h" +#include "stat-util.h" #include "user-record.h" typedef enum HomeState { @@ -190,7 +191,7 @@ int home_unlock(Home *h, UserRecord *secret, sd_bus_error *error); HomeState home_get_state(Home *h); -int home_get_disk_status(Home *h, uint64_t *ret_disk_size,uint64_t *ret_disk_usage, uint64_t *ret_disk_free, uint64_t *ret_disk_ceiling, uint64_t *ret_disk_floor); +int home_get_disk_status(Home *h, uint64_t *ret_disk_size,uint64_t *ret_disk_usage, uint64_t *ret_disk_free, uint64_t *ret_disk_ceiling, uint64_t *ret_disk_floor, statfs_f_type_t *ret_fstype, mode_t *ret_access_mode); void home_process_notify(Home *h, char **l, int fd); diff --git a/src/shared/user-record.c b/src/shared/user-record.c index 49febade2a..be1c45ff7b 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -1354,21 +1354,23 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp static int dispatch_status(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { static const JsonDispatch status_dispatch_table[] = { - { "diskUsage", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_usage), 0 }, - { "diskFree", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_free), 0 }, - { "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 }, - { "diskCeiling", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_ceiling), 0 }, - { "diskFloor", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_floor), 0 }, - { "state", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, state), JSON_SAFE }, - { "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE }, - { "signedLocally", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, signed_locally), 0 }, - { "goodAuthenticationCounter", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, good_authentication_counter), 0 }, - { "badAuthenticationCounter", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, bad_authentication_counter), 0 }, - { "lastGoodAuthenticationUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_good_authentication_usec), 0 }, - { "lastBadAuthenticationUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_bad_authentication_usec), 0 }, - { "rateLimitBeginUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_begin_usec), 0 }, - { "rateLimitCount", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_count), 0 }, - { "removable", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, removable), 0 }, + { "diskUsage", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_usage), 0 }, + { "diskFree", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_free), 0 }, + { "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 }, + { "diskCeiling", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_ceiling), 0 }, + { "diskFloor", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_floor), 0 }, + { "state", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, state), JSON_SAFE }, + { "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE }, + { "signedLocally", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, signed_locally), 0 }, + { "goodAuthenticationCounter", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, good_authentication_counter), 0 }, + { "badAuthenticationCounter", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, bad_authentication_counter), 0 }, + { "lastGoodAuthenticationUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_good_authentication_usec), 0 }, + { "lastBadAuthenticationUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_bad_authentication_usec), 0 }, + { "rateLimitBeginUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_begin_usec), 0 }, + { "rateLimitCount", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_count), 0 }, + { "removable", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, removable), 0 }, + { "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 }, + { "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE }, {}, }; From 67a6d39953ebba2d5b32ae5d3f8ac88f67a51748 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 12 Nov 2021 18:04:03 +0100 Subject: [PATCH 3/4] doc: document the two new accessMode/fileSystemType fields --- docs/USER_RECORD.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/USER_RECORD.md b/docs/USER_RECORD.md index f82f1307cc..1fd6c74548 100644 --- a/docs/USER_RECORD.md +++ b/docs/USER_RECORD.md @@ -863,6 +863,11 @@ on removable media the delay is selected very low to minimize the chance the home directory remains in unclean state if the storage device is removed from the system by the user). +`accessMode` → The access mode currently in effect for the home directory iself. + +`fileSystemType` → The file system type backing the home directory: a short +string, such as "btrfs", "ext4", "xfs". + ## Fields in the `signature` section As mentioned, the `signature` section of the user record may contain one or From e0c311b1aa146852b992c1501d032a6908033c25 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 12 Nov 2021 18:03:45 +0100 Subject: [PATCH 4/4] update TODO --- TODO | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/TODO b/TODO index bec042ab9c..e06d9edc93 100644 --- a/TODO +++ b/TODO @@ -81,6 +81,10 @@ Janitorial Clean-ups: Features: +* extend src/basic/filesystems.[ch] so that it can be used to translate any fs + magic into a string. Then use that to replace fstype_magic_to_name() in homed + sources, and similar code. + * userdb: when synthesizing NSS records, pick "best" password from defined passwords, not just the first. i.e. if there are multiple defined, prefer unlocked over locked and prefer non-empty over empty. @@ -1290,7 +1294,6 @@ Features: - make size of $XDG_RUNTIME_DIR configurable in user record - query password from kernel keyring first - update even if record is "absent" - - add a "access mode" + "fstype" field to the "status" section of json identity records reflecting the actually used access mode and fstype, even on non-luks backends - move acct mgmt stuff from pam_systemd_home to pam_systemd? - when "homectl --pkcs11-token-uri=" is used, synthesize ssh-authorized-keys records for all keys we have private keys on the stick for - make slice for users configurable (requires logind rework)