homework: skip LUKS fallocate for metadata operations

UpdateHomeEx needs to set up the home temporarily so that it can
write the refreshed identity records. Inspect and password changes use
the same setup path for metadata-oriented work.

For LUKS homes with luksDiscard=no, home_setup_luks() fully
allocates the backing image while setting it up. If diskSize=max was
previously written to a sparse image, this can fail with ENOSPC before
the user can disable auto-resize mode again.

Add a setup flag that lets metadata operations skip the full-image
fallocate step. Real create, activate, and resize paths keep the
existing allocation behavior.

Before, reproduced on Ubuntu 26.04 with /home on tmpfs:

  homectl create reprohomed \
          --storage=luks \
          --fs-type=btrfs \
          --disk-size=300M \
          --auto-resize-mode=shrink-and-grow \
          --luks-discard=no
  homectl update reprohomed --disk-size=max
  dd if=/dev/zero of=/var/tmp/homed-repro/filler bs=1M count=500
  systemctl restart systemd-homed.service
  homectl update reprohomed --auto-resize-mode=off

  Operation on home reprohomed failed:
  Not enough disk space for home reprohomed

Fixes #34527

Follow-up for: ab3b6fcb40
This commit is contained in:
dongshengyuan
2026-08-27 04:54:38 +09:00
committed by Yu Watanabe
parent a9ff124143
commit 2ff5250ae7
4 changed files with 132 additions and 7 deletions
+5 -3
View File
@@ -1430,7 +1430,7 @@ int home_setup_luks(
if (run_mark_dirty(setup->image_fd, true) > 0)
setup->do_mark_clean = true;
if (!user_record_luks_discard(h)) {
if (!FLAGS_SET(flags, HOME_SETUP_LUKS_DONT_FALLOCATE) && !user_record_luks_discard(h)) {
r = run_fallocate(setup->image_fd, &st);
if (r < 0)
return r;
@@ -1505,7 +1505,9 @@ int home_setup_luks(
if (user_record_luks_discard(h))
(void) run_fitrim(setup->root_fd);
setup->do_offline_fallocate = !(setup->do_offline_fitrim = user_record_luks_offline_discard(h));
setup->do_offline_fitrim = user_record_luks_offline_discard(h);
setup->do_offline_fallocate = !setup->do_offline_fitrim;
setup->tolerate_offline_fallocate_enospc = FLAGS_SET(flags, HOME_SETUP_LUKS_DONT_FALLOCATE);
}
if (!sd_id128_is_null(found_partition_uuid))
@@ -1609,7 +1611,7 @@ int home_activate_luks(
r = home_setup_luks(
h,
0,
flags,
NULL,
setup,
cache,
+8 -4
View File
@@ -436,7 +436,10 @@ int home_setup_done(HomeSetup *setup) {
if (setup->image_fd >= 0) {
if (setup->do_offline_fallocate) {
q = run_fallocate(setup->image_fd, NULL);
if (q < 0)
if (setup->tolerate_offline_fallocate_enospc &&
ERRNO_IS_NEG_DISK_SPACE(q))
log_warning_errno(q, "Failed to allocate backing file, ignoring: %m");
else if (q < 0)
r = q;
}
@@ -468,6 +471,7 @@ int home_setup_done(HomeSetup *setup) {
setup->undo_dm = false;
setup->do_offline_fitrim = false;
setup->do_offline_fallocate = false;
setup->tolerate_offline_fallocate_enospc = false;
setup->do_mark_clean = false;
setup->dm_name = mfree(setup->dm_name);
@@ -1686,7 +1690,7 @@ static int home_update(UserRecord *h, Hashmap *blobs, UserRecord **ret) {
return user_record_clone(h, USER_RECORD_LOAD_MASK_SECRET|USER_RECORD_PERMISSIVE, ret);
}
r = home_setup(h, flags, &setup, &cache, &header_home);
r = home_setup(h, flags | HOME_SETUP_LUKS_DONT_FALLOCATE, &setup, &cache, &header_home);
if (r < 0)
return r;
@@ -1788,7 +1792,7 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
if (r < 0)
return r;
r = home_setup(h, flags, &setup, &cache, &header_home);
r = home_setup(h, flags | HOME_SETUP_LUKS_DONT_FALLOCATE, &setup, &cache, &header_home);
if (r < 0)
return r;
@@ -1866,7 +1870,7 @@ static int home_inspect(UserRecord *h, UserRecord **ret_home) {
if (r < 0)
return r;
r = home_setup(h, flags, &setup, &cache, &header_home);
r = home_setup(h, flags | HOME_SETUP_LUKS_DONT_FALLOCATE, &setup, &cache, &header_home);
if (r < 0)
return r;
+4
View File
@@ -48,6 +48,7 @@ typedef struct HomeSetup {
bool undo_mount:1; /* Whether to unmount /run/systemd/user-home-mount */
bool do_offline_fitrim:1;
bool do_offline_fallocate:1;
bool tolerate_offline_fallocate_enospc:1;
bool do_mark_clean:1;
bool do_drop_caches:1;
@@ -82,6 +83,9 @@ typedef enum HomeSetupFlags {
HOME_SETUP_RESIZE_DONT_GROW = 1 << 4, /* If the resize would grow, gracefully terminate operation */
HOME_SETUP_RESIZE_DONT_SHRINK = 1 << 5, /* If the resize would shrink, gracefully terminate operation */
HOME_SETUP_RESIZE_DONT_UNDO = 1 << 6, /* Leave loopback/DM device context open after successful operation */
/* LUKS backend: */
HOME_SETUP_LUKS_DONT_FALLOCATE = 1 << 7, /* Skip setup-time backing file allocation */
} HomeSetupFlags;
int home_setup_done(HomeSetup *setup);
+115
View File
@@ -40,6 +40,22 @@ wait_for_state() {
timeout 2m bash -c "until homectl inspect '${1:?}' | grep -F 'State: $2' >/dev/null; do sleep 2; done"
}
cleanup_update_auto_resize_mode_without_space() (
set +e
rm -f /tmp/no-space-update.create /home/no-space-update.filler /home/no-space-update.home
if homectl inspect no-space-update >/dev/null 2>&1; then
homectl deactivate no-space-update 2>/dev/null
wait_for_state no-space-update inactive 2>/dev/null
homectl remove no-space-update 2>/dev/null
fi
mount /home -o remount,size=290M || :
systemctl restart systemd-homed.service || :
return 0
)
FSTYPE="$(stat --file-system --format "%T" /)"
systemctl start systemd-homed.service systemd-userdbd.socket
@@ -337,6 +353,105 @@ testcase_recovery_key_file() (
rm -rf "$KEY_DIR"
)
testcase_update_auto_resize_mode_without_space() {
local blocks bsize create_output fill_mbytes fill_size filler home_size image
local fs_stat image_stat image_blocks image_size image_unallocated
create_output="/tmp/no-space-update.create"
filler="/home/no-space-update.filler"
home_size=512M
image="/home/no-space-update.home"
trap cleanup_update_auto_resize_mode_without_space RETURN ERR EXIT
mount /home -o remount,size="$home_size"
if ! NEWPASSWORD=xEhErW0ndafV4s \
homectl create no-space-update \
--storage=luks \
--disk-size=300M \
--auto-resize-mode=shrink-and-grow \
--luks-discard=no \
--luks-offline-discard=yes \
--image-path="$image" \
--luks-pbkdf-type=pbkdf2 \
--luks-pbkdf-time-cost=1ms \
--rate-limit-interval=1s \
--rate-limit-burst=1000 \
>"$create_output" 2>&1; then
if grep -F "System does not support selected storage backend" "$create_output" >/dev/null; then
cat "$create_output"
echo "LUKS storage backend not supported, skipping update without space test."
return 0
fi
cat "$create_output" >&2
return 1
fi
cat "$create_output"
PASSWORD=xEhErW0ndafV4s homectl update no-space-update --disk-size=max
wait_for_state no-space-update inactive
# Activation still fully allocates luksDiscard=no images while space is available.
PASSWORD=xEhErW0ndafV4s homectl activate no-space-update
wait_for_state no-space-update active
homectl deactivate no-space-update
wait_for_state no-space-update inactive
PASSWORD=xEhErW0ndafV4s homectl resize no-space-update 256M
wait_for_state no-space-update inactive
PASSWORD=xEhErW0ndafV4s homectl update no-space-update --disk-size=max
wait_for_state no-space-update inactive
image_stat="$(stat -c '%s %b' "$image")"
read -r image_size image_blocks <<<"$image_stat"
[[ "$image_size" =~ ^[0-9]+$ && "$image_blocks" =~ ^[0-9]+$ ]]
image_unallocated=$((image_size - image_blocks * 512))
fs_stat="$(stat --file-system --format "%a %S" /home)"
read -r blocks bsize <<<"$fs_stat"
[[ "$blocks" =~ ^[0-9]+$ && "$bsize" =~ ^[0-9]+$ ]]
fill_size=$((blocks * bsize - 128 * 1024 * 1024))
fill_mbytes=$((fill_size / 1024 / 1024))
if [[ "$fill_mbytes" -le 0 ]]; then
echo "Not enough free space to set up update without space test, skipping."
return 0
fi
# Leave enough room for the small identity update below, but not enough for the old full-image fallocate().
dd if=/dev/zero of="$filler" bs=1M count="$fill_mbytes"
fs_stat="$(stat --file-system --format "%a %S" /home)"
read -r blocks bsize <<<"$fs_stat"
[[ "$blocks" =~ ^[0-9]+$ && "$bsize" =~ ^[0-9]+$ ]]
if [[ "$image_unallocated" -le $((blocks * bsize)) ]]; then
printf 'Not enough backing space for the update test: '
printf 'image_unallocated=%s free_space=%s fill_mbytes=%s\n' \
"$image_unallocated" "$((blocks * bsize))" "$fill_mbytes"
return 0
fi
systemctl restart systemd-homed.service
wait_for_exist no-space-update
PASSWORD=xEhErW0ndafV4s homectl authenticate no-space-update
PASSWORD=xEhErW0ndafV4s homectl update no-space-update \
--luks-discard=yes \
--luks-offline-discard=no
PASSWORD=xEhErW0ndafV4s homectl update no-space-update \
--luks-discard=no \
--luks-offline-discard=yes
PASSWORD=xEhErW0ndafV4s NEWPASSWORD=yPN4N0fYNKUkOq homectl passwd no-space-update
PASSWORD=yPN4N0fYNKUkOq homectl update no-space-update --auto-resize-mode=off
homectl inspect no-space-update | grep -F "Auto Resize: off" >/dev/null
}
testcase_blob() {
# blob directory tests
# See docs/USER_RECORD_BLOB_DIRS.md