static int home_truncate(
UserRecord *h,
int fd,
- const char *path,
uint64_t size) {
bool trunc;
assert(h);
assert(fd >= 0);
- assert(path);
trunc = user_record_luks_discard(h);
if (!trunc) {
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(
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;
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,
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,
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);
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);