return TRUE;
}
+/* This is a roundabout but more trustworthy way of doing a space check than
+ * relying on statvfs's f_bfree when you know the size of the objects. */
+static gboolean
+dfd_fallocate_check (int dfd, __off_t len, gboolean *out_passed, GError **error)
+{
+ g_auto (GLnxTmpfile) tmpf = {
+ 0,
+ };
+ if (!glnx_open_tmpfile_linkable_at (dfd, ".", O_WRONLY | O_CLOEXEC, &tmpf, error))
+ return FALSE;
+
+ *out_passed = TRUE;
+ /* There's glnx_try_fallocate, but not with the same error semantics. */
+ if (TEMP_FAILURE_RETRY (fallocate (tmpf.fd, 0, 0, len)) < 0)
+ {
+ if (G_IN_SET (errno, ENOSYS, EOPNOTSUPP))
+ return TRUE;
+ else if (errno != ENOSPC)
+ return glnx_throw_errno_prefix (error, "fallocate");
+ *out_passed = FALSE;
+ }
+ return TRUE;
+}
+
/* Analyze /boot and figure out if the new deployments won't fit in the
* remaining space. If they won't, check if deleting the deployments that are
* getting rotated out (e.g. the current rollback) would free up sufficient
net_new_bootcsum_dirs_total_size += bootdir_size;
}
- /* get bootfs free space */
- struct statvfs stvfsbuf;
- if (TEMP_FAILURE_RETRY (fstatvfs (self->boot_fd, &stvfsbuf)) < 0)
- return glnx_throw_errno_prefix (error, "fstatvfs(boot)");
-
- guint64 available_size = stvfsbuf.f_bsize * stvfsbuf.f_bfree;
-
- /* does the bootfs have enough free space for net-new bootdirs? */
- if (net_new_bootcsum_dirs_total_size <= available_size)
- return TRUE; /* nothing to do! */
+ {
+ gboolean bootfs_has_space = FALSE;
+ if (!dfd_fallocate_check (self->boot_fd, net_new_bootcsum_dirs_total_size, &bootfs_has_space,
+ error))
+ return glnx_prefix_error (error, "Checking if bootfs has space");
+
+ /* does the bootfs have enough free space for temporarily holding both the new
+ * and old bootdirs? */
+ if (bootfs_has_space)
+ return TRUE; /* nothing to do! */
+ }
/* OK, we would fail if we tried to write the new bootdirs. Is it salvageable?
* First, calculate how much space we could save with the bootcsums scheduled
bootcsum_dirs_to_remove_total_size += GPOINTER_TO_UINT (sizep);
}
- if (net_new_bootcsum_dirs_total_size > (available_size + bootcsum_dirs_to_remove_total_size))
+ if (net_new_bootcsum_dirs_total_size > bootcsum_dirs_to_remove_total_size)
{
- /* Even if we auto-pruned, the new bootdirs wouldn't fit. Just let the
- * code continue and let it hit ENOSPC. */
- g_printerr ("Disabling auto-prune optimization; insufficient space left in bootfs\n");
- return TRUE;
+ /* Check whether if we did early prune, we'd have enough space to write
+ * the new bootcsum dirs. */
+ gboolean bootfs_has_space = FALSE;
+ if (!dfd_fallocate_check (
+ self->boot_fd, net_new_bootcsum_dirs_total_size - bootcsum_dirs_to_remove_total_size,
+ &bootfs_has_space, error))
+ return glnx_prefix_error (error, "Checking if bootfs has space");
+
+ if (!bootfs_has_space)
+ {
+ /* Even if we auto-pruned, the new bootdirs wouldn't fit. Just let the
+ * code continue and let it hit ENOSPC. */
+ g_printerr ("Disabling auto-prune optimization; insufficient space left in bootfs\n");
+ return TRUE;
+ }
}
g_printerr ("Insufficient space left in bootfs; updating bootloader in two steps\n");
cd /root
mkdir -p rootfs/usr/lib/modules/`uname -r`
cp /usr/lib/modules/`uname -r`/vmlinuz rootfs/usr/lib/modules/`uname -r`
-echo 1 >> rootfs/usr/lib/modules/`uname -r`/vmlinuz
+dd if=/dev/urandom of=rootfs/usr/lib/modules/`uname -r`/vmlinuz count=1 conv=notrunc status=none
ostree commit --base "${host_refspec}" -P --tree=dir=rootfs -b modkernel1
-echo 1 >> rootfs/usr/lib/modules/`uname -r`/vmlinuz
+dd if=/dev/urandom of=rootfs/usr/lib/modules/`uname -r`/vmlinuz count=1 conv=notrunc status=none
ostree commit --base "${host_refspec}" -P --tree=dir=rootfs -b modkernel2
assert_bootfs_has_n_bootcsum_dirs() {
}
consume_bootfs_space() {
- local free_blocks=$(stat --file-system /boot -c '%a')
- local block_size=$(stat --file-system /boot -c '%s')
+ local free_blocks block_size
+ free_blocks=${1:-$(stat --file-system /boot -c '%a')}
+ block_size=$(stat --file-system /boot -c '%s')
# leave 1 block free
unshare -m bash -c \
"mount -o rw,remount /boot && \
assert_bootfs_has_n_bootcsum_dirs 2
assert_not_streq "$bootloader_orig" "$(sha256sum /boot/loader/entries/*)"
+# This next test relies on the fact that FCOS currently uses ext4 for /boot.
+# If that ever changes, we can reprovision boot to be ext4.
+if [[ $(findmnt -no FSTYPE /boot) != ext4 ]]; then
+ assert_not_reached "/boot is not ext4"
+fi
+
+# Put modkernel2 in rollback position
+rpm-ostree rollback
+
+# Below, we test that a bootcsum dir sized below f_bfree but still large enough
+# to not actually fit (because some filesystems like ext4 include reserved
+# overhead in their f_bfree count for some reason) will still trigger the auto-
+# prune logic.
+
+unconsume_bootfs_space
+
+# Size the bigfile just right so that the kernel+initrd will be just at the max
+# limit according to f_bfree.
+unshare -m bash -c \
+ "mount -o rw,remount /boot && \
+ cp /usr/lib/modules/`uname -r`/{vmlinuz,initramfs.img} /boot"
+free_blocks=$(stat --file-system /boot -c '%f')
+unshare -m bash -c \
+ "mount -o rw,remount /boot && rm /boot/{vmlinuz,initramfs.img}"
+consume_bootfs_space "$((free_blocks))"
+
+rpm-ostree rebase :modkernel1
+if ostree admin finalize-staged |& tee out.txt; then
+ assert_not_reached "successfully wrote kernel without auto-pruning"
+fi
+assert_file_has_content out.txt "No space left on device"
+rm out.txt
+
+# now, try again but with auto-pruning enabled
+rpm-ostree rebase :modkernel1
+OSTREE_SYSROOT_OPTS=early-prune ostree admin finalize-staged |& tee out.txt
+assert_file_has_content out.txt "updating bootloader in two steps"
+rm out.txt
+
echo "ok bootfs auto-prune"