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:
committed by
Yu Watanabe
parent
a9ff124143
commit
2ff5250ae7
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user