Rework treecompose kernel processing

Prep for changing `boot_location: new` to use `/usr/lib/ostree-boot`
and `/usr/lib/modules`.  Rework our kernel postprocessing
so that we unify the `boot_location` handling with initramfs generation.

Instead of doing the initramfs first in postprocessing, we do it nearly last,
after e.g. `etc` is renamed to `usr/etc`. This has some consequences, such as
the fact that `run_bwrap_mutably()` is now called in both situations. In
general, our handling of `etc` is inconsistent, although understandably so.

As part of this, I finally got around to implementing the bit from
https://github.com/systemd/systemd/pull/4174 however suboptimal it is; need the
unified core so we can cleanly ignore the posttrans like we do others.  We
intentionally keep the file around in the generated tree so that installing a
kernel RPM per client doesn't try to do any of this either.

This all gets folded together so that the logic for handling the bootloader gets
simpler - in the Fedora case, we now know to find kernels in `/usr/lib/modules`
and can ignore `/boot`.

Closes: #959
Approved by: jlebon
This commit is contained in:
Colin Walters 2017-08-25 21:06:09 -04:00 committed by Atomic Bot
parent 93d3fc6617
commit f113fc5e27
8 changed files with 400 additions and 185 deletions

View File

@ -32,7 +32,7 @@ It supports the following parameters:
possible values: possible values:
* "both": the default, kernel data goes in /boot and /usr/lib/ostree-boot * "both": the default, kernel data goes in /boot and /usr/lib/ostree-boot
* "legacy": Now an alias for "both"; historically meant just "boot" * "legacy": Now an alias for "both"; historically meant just "boot"
* "new": kernel data goes in /usr/lib/ostree-boot * "new": kernel data goes in /usr/lib/ostree-boot and /usr/lib/modules
* `etc-group-members`: Array of strings, optional: Unix groups in this * `etc-group-members`: Array of strings, optional: Unix groups in this
list will be stored in `/etc/group` instead of `/usr/lib/group`. Use list will be stored in `/etc/group` instead of `/usr/lib/group`. Use

View File

@ -477,6 +477,21 @@ install_packages_in_root (RpmOstreeTreeComposeContext *self,
return FALSE; return FALSE;
} }
/* Before we install packages, drop a file to suppress the kernel.rpm dracut run.
* <https://github.com/systemd/systemd/pull/4174> */
const char *kernel_installd_path = "usr/lib/kernel/install.d";
if (!glnx_shutil_mkdir_p_at (rootfs_dfd, kernel_installd_path, 0755, cancellable, error))
return FALSE;
const char skip_kernel_install_data[] = "#!/usr/bin/sh\nexit 77\n";
const char *kernel_skip_path = glnx_strjoina (kernel_installd_path, "/00-rpmostree-skip.install");
if (!glnx_file_replace_contents_with_perms_at (rootfs_dfd, kernel_skip_path,
(guint8*)skip_kernel_install_data,
strlen (skip_kernel_install_data),
0755, 0, 0,
GLNX_FILE_REPLACE_NODATASYNC,
cancellable, error))
return FALSE;
/* Now actually run through librpm to install the packages. Note this bit /* Now actually run through librpm to install the packages. Note this bit
* will be replaced in the future with a unified core: * will be replaced in the future with a unified core:
* https://github.com/projectatomic/rpm-ostree/issues/729 * https://github.com/projectatomic/rpm-ostree/issues/729

View File

@ -962,7 +962,7 @@ perform_local_assembly (RpmOstreeSysrootUpgrader *self,
return FALSE; return FALSE;
if (!rpmostree_finalize_kernel (self->tmprootfs_dfd, bootdir, kver, kernel_path, if (!rpmostree_finalize_kernel (self->tmprootfs_dfd, bootdir, kver, kernel_path,
&initramfs_tmpf, &initramfs_tmpf, RPMOSTREE_FINALIZE_KERNEL_AUTO,
cancellable, error)) cancellable, error))
return FALSE; return FALSE;

View File

@ -38,6 +38,12 @@
#include "rpmostree-bwrap.h" #include "rpmostree-bwrap.h"
#include "rpmostree-util.h" #include "rpmostree-util.h"
static const char usrlib_ostreeboot[] = "usr/lib/ostree-boot";
/* Keep this in sync with ostree/src/libostree/ostree-sysroot-deploy.c:get_kernel_from_tree().
* Note they are of necessity slightly different since rpm-ostree needs
* to support grabbing wherever the Fedora kernel RPM dropped files as well.
*/
static gboolean static gboolean
find_kernel_and_initramfs_in_bootdir (int rootfs_dfd, find_kernel_and_initramfs_in_bootdir (int rootfs_dfd,
const char *bootdir, const char *bootdir,
@ -57,7 +63,7 @@ find_kernel_and_initramfs_in_bootdir (int rootfs_dfd,
if (dfd < 0) if (dfd < 0)
{ {
if (errno == ENOENT) if (errno == ENOENT)
return TRUE; return TRUE; /* Note early return */
else else
return glnx_throw_errno_prefix (error, "opendir(%s)", bootdir); return glnx_throw_errno_prefix (error, "opendir(%s)", bootdir);
} }
@ -80,25 +86,16 @@ find_kernel_and_initramfs_in_bootdir (int rootfs_dfd,
name = dent->d_name; name = dent->d_name;
/* Current Fedora 23 kernel.spec installs as just vmlinuz */ /* Current Fedora 23 kernel.spec installs as just vmlinuz */
if (strcmp (name, "vmlinuz") == 0 || g_str_has_prefix (name, "vmlinuz-")) if (g_str_equal (name, "vmlinuz") || g_str_has_prefix (name, "vmlinuz-"))
{ {
if (ret_kernel) if (ret_kernel)
{ return glnx_throw (error, "Multiple vmlinuz- in %s", bootdir);
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Multiple vmlinuz- in %s",
bootdir);
return FALSE;
}
ret_kernel = g_strconcat (bootdir, "/", name, NULL); ret_kernel = g_strconcat (bootdir, "/", name, NULL);
} }
else if (g_str_has_prefix (name, "initramfs-")) else if (g_str_equal (name, "initramfs.img") || g_str_has_prefix (name, "initramfs-"))
{ {
if (ret_initramfs) if (ret_initramfs)
{ return glnx_throw (error, "Multiple initramfs- in %s", bootdir);
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Multiple initramfs- in %s", bootdir);
return FALSE;
}
ret_initramfs = g_strconcat (bootdir, "/", name, NULL); ret_initramfs = g_strconcat (bootdir, "/", name, NULL);
} }
} }
@ -153,8 +150,8 @@ find_ensure_one_subdirectory (int rootfs_dfd,
/* Given a root filesystem, return a GVariant of format (sssms): /* Given a root filesystem, return a GVariant of format (sssms):
* - kver: uname -r equivalent * - kver: uname -r equivalent
* - bootdir: Path to the boot directory * - bootdir: Path to the boot directory
* - kernel_path: Relative path to kernel * - kernel_path: Relative (to rootfs) path to kernel
* - initramfs_path: Relative path to initramfs (may be NULL if no initramfs) * - initramfs_path: Relative (to rootfs) path to initramfs (may be NULL if no initramfs)
*/ */
GVariant * GVariant *
rpmostree_find_kernel (int rootfs_dfd, rpmostree_find_kernel (int rootfs_dfd,
@ -175,7 +172,7 @@ rpmostree_find_kernel (int rootfs_dfd,
/* First, look for the kernel in the canonical ostree directory */ /* First, look for the kernel in the canonical ostree directory */
g_autofree char* kernel_path = NULL; g_autofree char* kernel_path = NULL;
g_autofree char* initramfs_path = NULL; g_autofree char* initramfs_path = NULL;
g_autofree char *bootdir = g_strdup ("usr/lib/ostree-boot"); g_autofree char *bootdir = g_strdup (usrlib_ostreeboot);
if (!find_kernel_and_initramfs_in_bootdir (rootfs_dfd, bootdir, if (!find_kernel_and_initramfs_in_bootdir (rootfs_dfd, bootdir,
&kernel_path, &initramfs_path, &kernel_path, &initramfs_path,
cancellable, error)) cancellable, error))
@ -211,8 +208,64 @@ rpmostree_find_kernel (int rootfs_dfd,
return g_variant_ref_sink (g_variant_new ("(sssms)", kver, bootdir, kernel_path, initramfs_path)); return g_variant_ref_sink (g_variant_new ("(sssms)", kver, bootdir, kernel_path, initramfs_path));
} }
/* Given a kernel path and a temporary initramfs, compute their checksum and put /* Given a @rootfs_dfd and path to kernel/initramfs that live in
* them in their final locations. * usr/lib/modules/$kver, possibly update @bootdir to use them. @bootdir should
* be one of either /usr/lib/ostree-boot or /boot. If @only_if_found is set, we
* do the copy only if we find a kernel; this way we avoid e.g. touching /boot
* if it isn't being used.
*/
static gboolean
copy_kernel_into (int rootfs_dfd,
const char *kver,
const char *boot_checksum_str,
const char *kernel_modules_path,
const char *initramfs_modules_path,
gboolean only_if_found,
const char *bootdir,
GCancellable *cancellable,
GError **error)
{
g_autofree char *legacy_kernel_path = NULL;
g_autofree char* legacy_initramfs_path = NULL;
if (!find_kernel_and_initramfs_in_bootdir (rootfs_dfd, bootdir,
&legacy_kernel_path, &legacy_initramfs_path,
cancellable, error))
return FALSE;
/* No kernel found? Skip to the next if we're in "auto"
* mode i.e. only update if found.
*/
if (!legacy_kernel_path && only_if_found)
return TRUE;
/* Update kernel */
if (legacy_kernel_path)
{
if (!glnx_unlinkat (rootfs_dfd, legacy_kernel_path, 0, error))
return FALSE;
g_free (legacy_kernel_path);
}
legacy_kernel_path = g_strconcat (bootdir, "/", "vmlinuz-", kver, "-", boot_checksum_str, NULL);
if (linkat (rootfs_dfd, kernel_modules_path, rootfs_dfd, legacy_kernel_path, 0) < 0)
return glnx_throw_errno_prefix (error, "linkat(%s)", legacy_kernel_path);
/* Update initramfs */
if (legacy_initramfs_path)
{
if (!glnx_unlinkat (rootfs_dfd, legacy_initramfs_path, 0, error))
return FALSE;
g_free (legacy_initramfs_path);
}
legacy_initramfs_path = g_strconcat (bootdir, "/", "initramfs-", kver, ".img-", boot_checksum_str, NULL);
if (linkat (rootfs_dfd, initramfs_modules_path, rootfs_dfd, legacy_initramfs_path, 0) < 0)
return glnx_throw_errno_prefix (error, "linkat(%s)", legacy_initramfs_path);
return TRUE;
}
/* Given a kernel path and a temporary initramfs, place them in their final
* location. We handle /usr/lib/modules as well as the /usr/lib/ostree-boot and
* /boot paths where we need to pre-compute their checksum.
*/ */
gboolean gboolean
rpmostree_finalize_kernel (int rootfs_dfd, rpmostree_finalize_kernel (int rootfs_dfd,
@ -220,43 +273,81 @@ rpmostree_finalize_kernel (int rootfs_dfd,
const char *kver, const char *kver,
const char *kernel_path, const char *kernel_path,
GLnxTmpfile *initramfs_tmpf, GLnxTmpfile *initramfs_tmpf,
RpmOstreeFinalizeKernelDestination dest,
GCancellable *cancellable, GCancellable *cancellable,
GError **error) GError **error)
{ {
g_autoptr(GChecksum) boot_checksum = NULL; const char slash_bootdir[] = "boot";
g_autofree char *kernel_final_path = NULL; g_autofree char *modules_bootdir = g_strconcat ("usr/lib/modules/", kver, NULL);
g_autofree char *initramfs_final_path = NULL;
const char *boot_checksum_str = NULL;
/* Now, calculate the combined sha256sum of the two. We checksum the initramfs /* Calculate the sha256sum of the kernel+initramfs (called the "boot
* from the tmpfile fd (via mmap()) to avoid writing it to disk in another * checksum"). We checksum the initramfs from the tmpfile fd (via mmap()) to
* temporary location. * avoid writing it to disk in another temporary location.
*/ */
boot_checksum = g_checksum_new (G_CHECKSUM_SHA256); g_autoptr(GChecksum) boot_checksum = g_checksum_new (G_CHECKSUM_SHA256);
if (!_rpmostree_util_update_checksum_from_file (boot_checksum, rootfs_dfd, kernel_path, if (!_rpmostree_util_update_checksum_from_file (boot_checksum, rootfs_dfd, kernel_path,
cancellable, error)) cancellable, error))
return FALSE; return FALSE;
{ g_autoptr(GMappedFile) mfile = g_mapped_file_new_from_fd (initramfs_tmpf->fd, FALSE, error); { g_autoptr(GMappedFile) mfile = g_mapped_file_new_from_fd (initramfs_tmpf->fd, FALSE, error);
if (!mfile) if (!mfile)
return FALSE; return FALSE;
g_checksum_update (boot_checksum, (guint8*)g_mapped_file_get_contents (mfile), g_checksum_update (boot_checksum, (guint8*)g_mapped_file_get_contents (mfile),
g_mapped_file_get_length (mfile)); g_mapped_file_get_length (mfile));
} }
boot_checksum_str = g_checksum_get_string (boot_checksum); const char *boot_checksum_str = g_checksum_get_string (boot_checksum);
kernel_final_path = g_strconcat (bootdir, "/", "vmlinuz-", kver, "-", boot_checksum_str, NULL); g_autofree char *kernel_modules_path = g_strconcat (modules_bootdir, "/vmlinuz", NULL);;
initramfs_final_path = g_strconcat (bootdir, "/", "initramfs-", kver, ".img-", boot_checksum_str, NULL); /* It's possible the bootdir is already the modules directory; in that case,
* we don't need to rename.
*/
if (!g_str_equal (kernel_path, kernel_modules_path))
{
g_assert_cmpstr (bootdir, !=, modules_bootdir);
/* Ensure that the /usr/lib/modules kernel is the same as the source.
* Right now we don't support overriding the kernel, but to be
* conservative let's relink (unlink/link). We don't just rename() because
* for _AUTO mode we still want to find the kernel in the old path
* (probably /usr/lib/ostree-boot) and update as appropriate.
*/
if (unlinkat (rootfs_dfd, kernel_modules_path, 0) < 0)
{
if (errno != ENOENT)
return glnx_throw_errno_prefix (error, "unlinkat(%s)", kernel_modules_path);
}
if (linkat (rootfs_dfd, kernel_path, rootfs_dfd, kernel_modules_path, 0) < 0)
return glnx_throw_errno_prefix (error, "linkat(%s)", kernel_modules_path);
}
/* Put the kernel in the final location */ /* Replace the initramfs */
if (!glnx_renameat (rootfs_dfd, kernel_path, rootfs_dfd, kernel_final_path, error)) g_autofree char *initramfs_modules_path = g_strconcat (modules_bootdir, "/initramfs.img", NULL);
return FALSE; if (unlinkat (rootfs_dfd, initramfs_modules_path, 0) < 0)
/* Link the initramfs directly to its final destination */ {
if (errno != ENOENT)
return glnx_throw_errno_prefix (error, "unlinkat(%s)", initramfs_modules_path);
}
if (!glnx_link_tmpfile_at (initramfs_tmpf, GLNX_LINK_TMPFILE_NOREPLACE, if (!glnx_link_tmpfile_at (initramfs_tmpf, GLNX_LINK_TMPFILE_NOREPLACE,
rootfs_dfd, initramfs_final_path, rootfs_dfd, initramfs_modules_path,
error)) error))
return FALSE; return FALSE;
/* Update /usr/lib/ostree-boot and /boot (if desired) */
const gboolean only_if_found = (dest == RPMOSTREE_FINALIZE_KERNEL_AUTO);
if (only_if_found || dest >= RPMOSTREE_FINALIZE_KERNEL_USRLIB_OSTREEBOOT)
{
if (!copy_kernel_into (rootfs_dfd, kver, boot_checksum_str,
kernel_modules_path, initramfs_modules_path,
only_if_found, usrlib_ostreeboot,
cancellable, error))
return FALSE;
}
if (only_if_found || dest >= RPMOSTREE_FINALIZE_KERNEL_SLASH_BOOT)
{
if (!copy_kernel_into (rootfs_dfd, kver, boot_checksum_str,
kernel_modules_path, initramfs_modules_path,
only_if_found, slash_bootdir,
cancellable, error))
return FALSE;
}
return TRUE; return TRUE;
} }
@ -341,12 +432,16 @@ rpmostree_run_dracut (int rootfs_dfd,
&tmpf, error)) &tmpf, error))
goto out; goto out;
/* If we're rebuilding, we use the *current* /etc so we pick up any modified
* config files. Otherwise, we use the usr/etc defaults.
*/
if (rebuild_from_initramfs) if (rebuild_from_initramfs)
bwrap = rpmostree_bwrap_new (rootfs_dfd, RPMOSTREE_BWRAP_IMMUTABLE, error, bwrap = rpmostree_bwrap_new (rootfs_dfd, RPMOSTREE_BWRAP_IMMUTABLE, error,
"--ro-bind", "/etc", "/etc", "--ro-bind", "/etc", "/etc",
NULL); NULL);
else else
bwrap = rpmostree_bwrap_new (rootfs_dfd, RPMOSTREE_BWRAP_IMMUTABLE, error, bwrap = rpmostree_bwrap_new (rootfs_dfd, RPMOSTREE_BWRAP_IMMUTABLE, error,
"--ro-bind", "usr/etc", "/etc",
NULL); NULL);
if (!bwrap) if (!bwrap)
return FALSE; return FALSE;

View File

@ -22,6 +22,13 @@
#include <ostree.h> #include <ostree.h>
typedef enum {
RPMOSTREE_FINALIZE_KERNEL_AUTO,
RPMOSTREE_FINALIZE_KERNEL_USRLIB_MODULES,
RPMOSTREE_FINALIZE_KERNEL_USRLIB_OSTREEBOOT,
RPMOSTREE_FINALIZE_KERNEL_SLASH_BOOT,
} RpmOstreeFinalizeKernelDestination;
GVariant * GVariant *
rpmostree_find_kernel (int rootfs_dfd, rpmostree_find_kernel (int rootfs_dfd,
GCancellable *cancellable, GCancellable *cancellable,
@ -33,6 +40,7 @@ rpmostree_finalize_kernel (int rootfs_dfd,
const char *kver, const char *kver,
const char *kernel_path, const char *kernel_path,
GLnxTmpfile *initramfs_tmpf, GLnxTmpfile *initramfs_tmpf,
RpmOstreeFinalizeKernelDestination dest,
GCancellable *cancellable, GCancellable *cancellable,
GError **error); GError **error);

View File

@ -59,12 +59,24 @@ run_bwrap_mutably (int rootfs_fd,
char **child_argv, char **child_argv,
GError **error) GError **error)
{ {
g_autoptr(RpmOstreeBwrap) bwrap = NULL; struct stat stbuf;
const char *etc_bind;
bwrap = rpmostree_bwrap_new (rootfs_fd, RPMOSTREE_BWRAP_MUTATE_FREELY, error, /* This gets called both by treecompose, where in the non-unified path we just
"--bind", "var", "/var", * have /etc, and in kernel postprocessing where we have usr/etc.
"--bind", "etc", "/etc", */
NULL); if (!glnx_fstatat_allow_noent (rootfs_fd, "etc", &stbuf, 0, error))
return FALSE;
if (errno == ENOENT)
etc_bind = "usr/etc";
else
etc_bind = "etc";
g_autoptr(RpmOstreeBwrap) bwrap =
rpmostree_bwrap_new (rootfs_fd, RPMOSTREE_BWRAP_MUTATE_FREELY, error,
"--bind", "var", "/var",
"--bind", etc_bind, "/etc",
NULL);
if (!bwrap) if (!bwrap)
return FALSE; return FALSE;
@ -142,60 +154,186 @@ init_rootfs (int dfd,
return TRUE; return TRUE;
} }
/* Given a directory referenced by @src_dfd+@src_path,
* Create @dest_dfd+@dest_path as a directory, hardlinking
* all content - recursively.
*/
static gboolean static gboolean
do_kernel_prep (int rootfs_dfd, hardlink_recurse (int src_dfd,
JsonObject *treefile, const char *src_path,
GCancellable *cancellable, int dest_dfd,
GError **error) const char *dest_path,
GCancellable *cancellable,
GError **error)
{ {
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
glnx_fd_close int dest_target_dfd = -1;
if (!glnx_dirfd_iterator_init_at (src_dfd, src_path, TRUE, &dfd_iter, error))
return FALSE;
if (!glnx_opendirat (dest_dfd, dest_path, TRUE, &dest_target_dfd, error))
return FALSE;
while (TRUE)
{
struct dirent *dent = NULL;
struct stat stbuf;
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
return FALSE;
if (!dent)
break;
if (!glnx_fstatat (dfd_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW, error))
return FALSE;
if (dent->d_type == DT_DIR)
{
mode_t perms = stbuf.st_mode & ~S_IFMT;
if (!glnx_ensure_dir (dest_target_dfd, dent->d_name, perms, error))
return FALSE;
if (fchmodat (dest_target_dfd, dent->d_name, perms, 0) < 0)
return glnx_throw_errno_prefix (error, "fchmodat");
if (!hardlink_recurse (dfd_iter.fd, dent->d_name,
dest_target_dfd, dent->d_name,
cancellable, error))
return FALSE;
}
else
{
if (linkat (dfd_iter.fd, dent->d_name,
dest_target_dfd, dent->d_name, 0) < 0)
return glnx_throw_errno_prefix (error, "linkat");
}
}
return TRUE;
}
/* Handle the kernel/initramfs, which can be in at least 2 different places:
* - /boot (CentOS, Fedora treecompose before we suppressed kernel.spec's %posttrans)
* - /usr/lib/modules (Fedora treecompose without kernel.spec's %posttrans)
*
* We then need to handle the boot_location option, which can put that data in
* either both (/boot and /usr/lib/ostree-boot), or just the latter.
*/
static gboolean
process_kernel_and_initramfs (int rootfs_dfd,
JsonObject *treefile,
GCancellable *cancellable,
GError **error)
{
/* The current systemd kernel-install will inject
* /boot/${machine_id}/${uname -r} which we don't use;
* to avoid confusion, we will delete it. This relies
* on systemd itself having set up the machine id from its %post,
* so we need to read it. We'll reset the machine ID after this.
*/
{ glnx_fd_close int fd = openat (rootfs_dfd, "usr/etc/machine-id", O_RDONLY | O_CLOEXEC);
if (fd < 0)
{
if (errno != ENOENT)
return glnx_throw_errno_prefix (error, "openat(usr/etc/machine-id)");
}
else
{
g_autofree char *old_machine_id = glnx_fd_readall_utf8 (fd, NULL, cancellable, error);
if (!old_machine_id)
return FALSE;
if (strlen (old_machine_id) != 33)
return glnx_throw (error, "invalid machine ID '%.33s'", old_machine_id);
/* Trim newline */
old_machine_id[32] = '\0';
const char *boot_machineid_dir = glnx_strjoina ("boot/", old_machine_id);
if (!glnx_shutil_rm_rf_at (rootfs_dfd, boot_machineid_dir, cancellable, error))
return FALSE;
}
}
/* We need to move non-kernel data (bootloader bits usually) into
* /usr/lib/ostree-boot; this will also take care of moving the kernel in legacy
* paths (CentOS, Fedora <= 24), etc.
*/
if (!glnx_renameat (rootfs_dfd, "boot", rootfs_dfd, "usr/lib/ostree-boot", error))
return FALSE;
/* Find the kernel in the source root (at this point one of usr/lib/modules or
* usr/lib/ostree-boot)
*/
g_autoptr(GVariant) kernelstate = rpmostree_find_kernel (rootfs_dfd, cancellable, error); g_autoptr(GVariant) kernelstate = rpmostree_find_kernel (rootfs_dfd, cancellable, error);
if (!kernelstate) if (!kernelstate)
return FALSE; return FALSE;
const char* kernel_path; const char* kernel_path;
const char* initramfs_path; const char* initramfs_path;
const char *kver; const char *kver;
const char *bootdir; const char *bootdir;
/* Used to optionally hardlink result of our dracut run */
g_variant_get (kernelstate, "(&s&s&sm&s)", g_variant_get (kernelstate, "(&s&s&sm&s)",
&kver, &bootdir, &kver, &bootdir,
&kernel_path, &initramfs_path); &kernel_path, &initramfs_path);
/* We generate our own initramfs with custom arguments, so if the RPM install
* generated one (should only happen on CentOS now), delete it.
*/
if (initramfs_path) if (initramfs_path)
{ {
g_assert_cmpstr (bootdir, ==, "usr/lib/ostree-boot");
g_assert_cmpint (*initramfs_path, !=, '/');
g_print ("Removing RPM-generated '%s'\n", initramfs_path); g_print ("Removing RPM-generated '%s'\n", initramfs_path);
if (!glnx_shutil_rm_rf_at (rootfs_dfd, initramfs_path, cancellable, error)) if (!glnx_shutil_rm_rf_at (rootfs_dfd, initramfs_path, cancellable, error))
return FALSE; return FALSE;
initramfs_path = NULL;
} }
/* OSTree needs to own this */ /* Ensure depmod (kernel modules index) is up to date; because on Fedora we
if (!glnx_shutil_rm_rf_at (rootfs_dfd, "boot/loader", cancellable, error)) * suppress the kernel %posttrans we need to take care of this.
return FALSE; */
{ {
char *child_argv[] = { "depmod", (char*)kver, NULL }; char *child_argv[] = { "depmod", (char*)kver, NULL };
if (!run_bwrap_mutably (rootfs_dfd, "depmod", child_argv, error)) if (!run_bwrap_mutably (rootfs_dfd, "depmod", child_argv, error))
return FALSE; return FALSE;
} }
/* Ensure the /etc/machine-id file is present and empty. Apparently systemd RpmOstreePostprocessBootLocation boot_location =
doesn't work when the file is missing (as of systemd-219-9.fc22) but it is RPMOSTREE_POSTPROCESS_BOOT_LOCATION_BOTH;
correctly populated if the file is there. */ const char *boot_location_str = NULL;
if (!_rpmostree_jsonutil_object_get_optional_string_member (treefile,
"boot_location",
&boot_location_str, error))
return FALSE;
if (boot_location_str != NULL)
{
/* Make "legacy" an alias for "both" */
if (strcmp (boot_location_str, "both") == 0 ||
strcmp (boot_location_str, "legacy") == 0)
;
else if (strcmp (boot_location_str, "new") == 0)
boot_location = RPMOSTREE_POSTPROCESS_BOOT_LOCATION_NEW;
else
return glnx_throw (error, "Invalid boot location '%s'", boot_location_str);
}
/* Ensure the /etc/machine-id file is present and empty; it is read by
* dracut. Apparently systemd doesn't work when the file is missing (as of
* systemd-219-9.fc22) but it is correctly populated if the file is there.
*/
g_print ("Creating empty machine-id\n"); g_print ("Creating empty machine-id\n");
if (!glnx_file_replace_contents_at (rootfs_dfd, "etc/machine-id", (guint8*)"", 0, if (!glnx_file_replace_contents_at (rootfs_dfd, "usr/etc/machine-id", (guint8*)"", 0,
GLNX_FILE_REPLACE_NODATASYNC, GLNX_FILE_REPLACE_NODATASYNC,
cancellable, error)) cancellable, error))
return FALSE; return FALSE;
/* Run dracut with our chosen arguments (commonly at least --no-hostonly) */
g_autoptr(GPtrArray) dracut_argv = g_ptr_array_new (); g_autoptr(GPtrArray) dracut_argv = g_ptr_array_new ();
if (json_object_has_member (treefile, "initramfs-args")) if (json_object_has_member (treefile, "initramfs-args"))
{ {
guint i, len; JsonArray *initramfs_args = json_object_get_array_member (treefile, "initramfs-args");
JsonArray *initramfs_args; guint len = json_array_get_length (initramfs_args);
initramfs_args = json_object_get_array_member (treefile, "initramfs-args"); for (guint i = 0; i < len; i++)
len = json_array_get_length (initramfs_args);
for (i = 0; i < len; i++)
{ {
const char *arg = _rpmostree_jsonutil_array_require_string_element (initramfs_args, i, error); const char *arg = _rpmostree_jsonutil_array_require_string_element (initramfs_args, i, error);
if (!arg) if (!arg)
@ -212,12 +350,37 @@ do_kernel_prep (int rootfs_dfd,
cancellable, error)) cancellable, error))
return FALSE; return FALSE;
if (!rpmostree_finalize_kernel (rootfs_dfd, bootdir, kver, /* We always tell rpmostree_finalize_kernel() to skip /boot, since we'll do a
kernel_path, * full hardlink pass if needed after that for the kernel + bootloader data.
*/
if (!rpmostree_finalize_kernel (rootfs_dfd, bootdir, kver, kernel_path,
&initramfs_tmpf, &initramfs_tmpf,
RPMOSTREE_FINALIZE_KERNEL_USRLIB_OSTREEBOOT,
cancellable, error)) cancellable, error))
return FALSE; return FALSE;
/* We always ensure this exists as a mountpoint */
if (!glnx_ensure_dir (rootfs_dfd, "boot", 0755, error))
return FALSE;
/* If the boot location includes /boot, we also need to copy /usr/lib/ostree-boot there */
switch (boot_location)
{
case RPMOSTREE_POSTPROCESS_BOOT_LOCATION_BOTH:
{
g_print ("Using boot location: both\n");
/* Hardlink the existing content, only a little ugly as
* we'll end up sha256'ing it twice, but oh well. */
if (!hardlink_recurse (rootfs_dfd, "usr/lib/ostree-boot",
rootfs_dfd, "boot",
cancellable, error))
return glnx_prefix_error (error, "hardlinking /boot");
}
break;
case RPMOSTREE_POSTPROCESS_BOOT_LOCATION_NEW:
break;
}
return TRUE; return TRUE;
} }
@ -650,60 +813,6 @@ postprocess_selinux_policy_store_location (int rootfs_dfd,
return TRUE; return TRUE;
} }
static gboolean
hardlink_recurse (int src_dfd,
const char *src_path,
int dest_dfd,
const char *dest_path,
GCancellable *cancellable,
GError **error)
{
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
glnx_fd_close int dest_target_dfd = -1;
if (!glnx_dirfd_iterator_init_at (src_dfd, src_path, TRUE, &dfd_iter, error))
return FALSE;
if (!glnx_opendirat (dest_dfd, dest_path, TRUE, &dest_target_dfd, error))
return FALSE;
while (TRUE)
{
struct dirent *dent = NULL;
struct stat stbuf;
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
return FALSE;
if (!dent)
break;
if (!glnx_fstatat (dfd_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW, error))
return FALSE;
if (dent->d_type == DT_DIR)
{
mode_t perms = stbuf.st_mode & ~S_IFMT;
if (!glnx_ensure_dir (dest_target_dfd, dent->d_name, perms, error))
return FALSE;
if (fchmodat (dest_target_dfd, dent->d_name, perms, 0) < 0)
return glnx_throw_errno_prefix (error, "fchmodat");
if (!hardlink_recurse (dfd_iter.fd, dent->d_name,
dest_target_dfd, dent->d_name,
cancellable, error))
return FALSE;
}
else
{
if (linkat (dfd_iter.fd, dent->d_name,
dest_target_dfd, dent->d_name, 0) < 0)
return glnx_throw_errno_prefix (error, "linkat");
}
}
return TRUE;
}
/* Prepare a root filesystem, taking mainly the contents of /usr from pkgroot */ /* Prepare a root filesystem, taking mainly the contents of /usr from pkgroot */
static gboolean static gboolean
create_rootfs_from_pkgroot_content (int target_root_dfd, create_rootfs_from_pkgroot_content (int target_root_dfd,
@ -726,10 +835,7 @@ create_rootfs_from_pkgroot_content (int target_root_dfd,
error)) error))
return FALSE; return FALSE;
g_print ("Preparing kernel\n"); /* Initialize target root */
if (!container && !do_kernel_prep (src_rootfs_fd, treefile, cancellable, error))
return glnx_prefix_error (error, "During kernel processing");
g_print ("Initializing rootfs\n"); g_print ("Initializing rootfs\n");
gboolean tmp_is_dir = FALSE; gboolean tmp_is_dir = FALSE;
if (!_rpmostree_jsonutil_object_get_optional_boolean_member (treefile, if (!_rpmostree_jsonutil_object_get_optional_boolean_member (treefile,
@ -784,65 +890,6 @@ create_rootfs_from_pkgroot_content (int target_root_dfd,
if (!convert_var_to_tmpfiles_d (src_rootfs_fd, target_root_dfd, cancellable, error)) if (!convert_var_to_tmpfiles_d (src_rootfs_fd, target_root_dfd, cancellable, error))
return FALSE; return FALSE;
/* Move boot, but rename the kernel/initramfs to have a checksum */
if (!container)
{
RpmOstreePostprocessBootLocation boot_location =
RPMOSTREE_POSTPROCESS_BOOT_LOCATION_BOTH;
const char *boot_location_str = NULL;
g_print ("Moving /boot\n");
if (!_rpmostree_jsonutil_object_get_optional_string_member (treefile,
"boot_location",
&boot_location_str, error))
return FALSE;
if (boot_location_str != NULL)
{
/* Note that "legacy" is now an alias for "both" */
if (strcmp (boot_location_str, "both") == 0 ||
strcmp (boot_location_str, "legacy") == 0)
boot_location = RPMOSTREE_POSTPROCESS_BOOT_LOCATION_BOTH;
else if (strcmp (boot_location_str, "new") == 0)
boot_location = RPMOSTREE_POSTPROCESS_BOOT_LOCATION_NEW;
else
return glnx_throw (error, "Invalid boot location '%s'", boot_location_str);
}
if (!glnx_shutil_mkdir_p_at (target_root_dfd, "usr/lib", 0755,
cancellable, error))
return FALSE;
switch (boot_location)
{
case RPMOSTREE_POSTPROCESS_BOOT_LOCATION_BOTH:
{
g_print ("Using boot location: both\n");
if (!glnx_renameat (src_rootfs_fd, "boot", target_root_dfd, "boot", error))
return FALSE;
if (!glnx_shutil_mkdir_p_at (target_root_dfd, "usr/lib/ostree-boot", 0755,
cancellable, error))
return FALSE;
/* Hardlink the existing content, only a little ugly as
* we'll end up sha256'ing it twice, but oh well. */
if (!hardlink_recurse (target_root_dfd, "boot",
target_root_dfd, "usr/lib/ostree-boot",
cancellable, error))
return FALSE;
}
break;
case RPMOSTREE_POSTPROCESS_BOOT_LOCATION_NEW:
{
g_print ("Using boot location: new\n");
if (!glnx_renameat (src_rootfs_fd, "boot",
target_root_dfd, "usr/lib/ostree-boot", error))
return FALSE;
}
break;
}
}
/* Also carry along toplevel compat links */ /* Also carry along toplevel compat links */
g_print ("Copying toplevel compat symlinks\n"); g_print ("Copying toplevel compat symlinks\n");
{ {
@ -884,6 +931,27 @@ create_rootfs_from_pkgroot_content (int target_root_dfd,
cancellable, error)) cancellable, error))
return FALSE; return FALSE;
/* Handle kernel/initramfs if we're not doing a container */
if (!container)
{
g_print ("Preparing kernel\n");
/* OSTree needs to own this */
if (!glnx_shutil_rm_rf_at (src_rootfs_fd, "boot/loader", cancellable, error))
return FALSE;
/* The kernel may be in the source rootfs /boot; to handle that, we always
* rename the source /boot to the target, and will handle everything after
* that in the target root.
*/
if (!glnx_renameat (src_rootfs_fd, "boot", target_root_dfd, "boot", error))
return FALSE;
if (!process_kernel_and_initramfs (target_root_dfd, treefile,
cancellable, error))
return glnx_prefix_error (error, "During kernel processing");
}
return TRUE; return TRUE;
} }

View File

@ -30,10 +30,14 @@ ostree --repo=${repobuild} show --print-metadata-key exampleos.tests ${treeref}
assert_file_has_content meta.txt 'smoketested.*e2e' assert_file_has_content meta.txt 'smoketested.*e2e'
echo "ok metadata" echo "ok metadata"
ostree --repo=${repobuild} ls -R ${treeref} /usr/lib/ostree-boot > bootls.txt for path in /boot /usr/lib/ostree-boot; do
assert_file_has_content bootls.txt vmlinuz ostree --repo=${repobuild} ls -R ${treeref} ${path} > bootls.txt
assert_file_has_content bootls.txt initramfs assert_file_has_content bootls.txt vmlinuz-
echo "ok boot files" assert_file_has_content bootls.txt initramfs-
echo "ok boot files"
done
kver=$(grep /vmlinuz bootls.txt | sed -e 's,.*/vmlinuz-\(.*\)-[0-9a-e].*$,\1,')
ostree --repo=${repobuild} ls ${treeref} /usr/lib/modules/${kver}/{vmlinuz,initramfs.img} >/dev/null
ostree --repo=${repobuild} ls -R ${treeref} /usr/share/man > manpages.txt ostree --repo=${repobuild} ls -R ${treeref} /usr/share/man > manpages.txt
assert_file_has_content manpages.txt man5/ostree.repo.5 assert_file_has_content manpages.txt man5/ostree.repo.5

View File

@ -0,0 +1,25 @@
#!/bin/bash
set -xeuo pipefail
dn=$(cd $(dirname $0) && pwd)
. ${dn}/libcomposetest.sh
prepare_compose_test "bootlocation-new"
pysetjsonmember "boot_location" '"new"'
runcompose
echo "ok compose"
# Nothing in /boot (but it should exist)
ostree --repo=${repobuild} ls -R ${treeref} /boot > bootls.txt
cat >bootls-expected.txt <<EOF
d00755 0 0 0 /boot
EOF
diff -u bootls{-expected,}.txt
# Verify /usr/lib/ostree-boot
ostree --repo=${repobuild} ls -R ${treeref} /usr/lib/ostree-boot > bootls.txt
assert_file_has_content bootls.txt vmlinuz-
assert_file_has_content bootls.txt initramfs-
kver=$(grep /vmlinuz bootls.txt | sed -e 's,.*/vmlinuz-\(.*\)-[0-9a-e].*$,\1,')
# And use the kver to find the kernel in /usr/lib/modules
ostree --repo=${repobuild} ls ${treeref} /usr/lib/modules/${kver}/{vmlinuz,initramfs.img} >/dev/null
echo "ok boot location new"