1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-22 06:50:18 +03:00

homework: also add logic for "maximizing" size of home

This commit is contained in:
Lennart Poettering 2021-10-29 12:21:41 +02:00
parent 34081f6be7
commit 2b02eb0591

View File

@ -1960,7 +1960,6 @@ static int calculate_disk_size(UserRecord *h, const char *parent_dir, uint64_t *
static int home_truncate(
UserRecord *h,
int fd,
const char *path,
uint64_t size) {
bool trunc;
@ -1968,7 +1967,6 @@ static int home_truncate(
assert(h);
assert(fd >= 0);
assert(path);
trunc = user_record_luks_discard(h);
if (!trunc) {
@ -1986,14 +1984,14 @@ static int home_truncate(
if (r < 0) {
if (ERRNO_IS_DISK_SPACE(errno)) {
log_error_errno(errno, "Not enough disk space to allocate home.");
log_debug_errno(errno, "Not enough disk space to allocate home of size %s.", FORMAT_BYTES(size));
return -ENOSPC; /* make recognizable */
}
return log_error_errno(errno, "Failed to truncate home image %s: %m", path);
return log_error_errno(errno, "Failed to truncate home image: %m");
}
return 0;
return !trunc; /* Return == 0 if we managed to truncate, > 0 if we managed to allocate */
}
int home_create_luks(
@ -2175,7 +2173,7 @@ int home_create_luks(
log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r,
"Failed to set file attributes on %s, ignoring: %m", setup->temporary_image_path);
r = home_truncate(h, setup->image_fd, setup->temporary_image_path, host_size);
r = home_truncate(h, setup->image_fd, host_size);
if (r < 0)
return r;
@ -2764,6 +2762,39 @@ static int get_smallest_fs_size(int fd, uint64_t *ret) {
return 0;
}
static int get_largest_image_size(int fd, const struct stat *st, uint64_t *ret) {
uint64_t used, avail, sum;
struct statfs sfs;
int r;
assert(fd >= 0);
assert(st);
assert(ret);
/* Determines the maximum file size we might be able to grow the image file referenced by the fd to. */
r = stat_verify_regular(st);
if (r < 0)
return log_error_errno(r, "Image file is not a regular file, refusing: %m");
if (syncfs(fd) < 0)
return log_error_errno(errno, "Failed to synchronize file system backing image file: %m");
if (fstatfs(fd, &sfs) < 0)
return log_error_errno(errno, "Failed to statfs() image file: %m");
used = (uint64_t) st->st_blocks * 512;
avail = (uint64_t) sfs.f_bsize * sfs.f_bavail;
if (avail > UINT64_MAX - used)
sum = UINT64_MAX;
else
sum = avail + used;
*ret = DISK_SIZE_ROUND_DOWN(MIN(sum, USER_DISK_SIZE_MAX));
return 0;
}
static int resize_fs_loop(
UserRecord *h,
HomeSetup *setup,
@ -2851,6 +2882,77 @@ static int resize_fs_loop(
return 0;
}
static int resize_image_loop(
UserRecord *h,
HomeSetup *setup,
uint64_t old_image_size,
uint64_t new_image_size,
uint64_t *ret_image_size) {
uint64_t current_image_size;
unsigned n_iterations = 0;
int r;
assert(h);
assert(setup);
assert(setup->image_fd >= 0);
/* A bisection loop trying to find the closest size to what the user asked for. (Well, we bisect like
* this only when we *grow* the image if we shrink the image then there's no need to bisect.) */
current_image_size = old_image_size;
for (uint64_t lower_boundary = old_image_size, upper_boundary = new_image_size, try_image_size = new_image_size;;) {
bool worked;
n_iterations++;
r = home_truncate(h, setup->image_fd, try_image_size);
if (r < 0) {
if (!ERRNO_IS_DISK_SPACE(r) || new_image_size < old_image_size) /* Not a disk space issue? Not trying to grow? */
return r;
log_debug_errno(r, "Growing from %s to %s didn't work, not enough space on backing disk.", FORMAT_BYTES(current_image_size), FORMAT_BYTES(try_image_size));
worked = false;
} else if (r > 0) { /* Success: allocation worked */
log_debug("Resizing from %s to %s via allocation worked successfully.", FORMAT_BYTES(current_image_size), FORMAT_BYTES(try_image_size));
current_image_size = try_image_size;
worked = true;
} else { /* Success, but through truncation, not allocation. */
log_debug("Resizing from %s to %s via truncation worked successfully.", FORMAT_BYTES(old_image_size), FORMAT_BYTES(try_image_size));
current_image_size = try_image_size;
break; /* there's no point in the bisection logic if this was plain truncation and
* not allocation, let's exit immediately. */
}
if (new_image_size < old_image_size) /* If we are shrinking we are done after one iteration */
break;
/* If we are growing then let's adjust our bisection boundaries and try again */
if (worked)
lower_boundary = MAX(lower_boundary, try_image_size);
else
upper_boundary = MIN(upper_boundary, try_image_size);
if (lower_boundary >= upper_boundary) {
log_debug("Image can't be grown further (range to try is empty).");
break;
}
try_image_size = DISK_SIZE_ROUND_DOWN(lower_boundary + (upper_boundary - lower_boundary) / 2);
if (try_image_size <= lower_boundary || try_image_size >= upper_boundary) {
log_debug("Image can't be grown further (remaining range to try too small).");
break;
}
}
log_debug("Bisection loop completed after %u iterations.", n_iterations);
if (ret_image_size)
*ret_image_size = current_image_size;
return 0;
}
int home_resize_luks(
UserRecord *h,
HomeSetupFlags flags,
@ -2988,11 +3090,17 @@ int home_resize_luks(
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Old partition doesn't fit in backing storage, refusing.");
if (S_ISREG(st.st_mode)) {
uint64_t partition_table_extra;
uint64_t partition_table_extra, largest_size;
partition_table_extra = old_image_size - setup->partition_size;
if (new_image_size <= partition_table_extra)
r = get_largest_image_size(setup->image_fd, &st, &largest_size);
if (r < 0)
return r;
if (new_image_size > largest_size)
new_image_size = largest_size;
if (new_image_size < partition_table_extra)
new_image_size = partition_table_extra;
new_partition_size = DISK_SIZE_ROUND_DOWN(new_image_size - partition_table_extra);
@ -3109,12 +3217,35 @@ int home_resize_luks(
if (new_fs_size > old_fs_size) { /* → Grow */
if (S_ISREG(st.st_mode)) {
uint64_t resized_image_size;
/* Grow file size */
r = home_truncate(h, image_fd, ip, new_image_size);
r = resize_image_loop(h, setup, old_image_size, new_image_size, &resized_image_size);
if (r < 0)
return r;
log_info("Growing of image file completed.");
if (resized_image_size == old_image_size) {
log_info("Couldn't change image size.");
return 0;
}
assert(resized_image_size > old_image_size);
log_info("Growing of image file from %s to %s completed.", FORMAT_BYTES(old_image_size), FORMAT_BYTES(resized_image_size));
if (resized_image_size < new_image_size) {
uint64_t sub;
/* If the growing we managed to do is smaller than what we wanted we need to
* adjust the partition/file system sizes we are going for, too */
sub = new_image_size - resized_image_size;
assert(new_partition_size >= sub);
new_partition_size -= sub;
assert(new_fs_size >= sub);
new_fs_size -= sub;
}
new_image_size = resized_image_size;
} else {
assert(S_ISBLK(st.st_mode));
assert(new_image_size == old_image_size);