Skip to content

Commit

Permalink
Support building mkinitcpio images in generate-zbm
Browse files Browse the repository at this point in the history
  • Loading branch information
ahesford committed Jan 27, 2022
1 parent 50a2da4 commit af34fa9
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 49 deletions.
243 changes: 206 additions & 37 deletions bin/generate-zbm
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use constant REFARRAY => ref [];
sub versionedKernel;
sub latestKernel;
sub createInitramfs;
sub createUEFIBundle;
sub execute;
sub safeCopy;
sub nonempty;
Expand All @@ -57,7 +58,7 @@ my ( %runConf, %config );
my $default_config = "/etc/zfsbootmenu/config.yaml";

$runConf{bootdir} = "/boot";
$runConf{confd} = "/etc/zfsbootmenu/dracut.conf.d";
$runConf{usecpio} = false;

GetOptions(
"version|v=s" => \$runConf{version},
Expand All @@ -71,6 +72,8 @@ GetOptions(
"config|c=s" => \$runConf{config},
"enable" => \$runConf{enable},
"disable" => \$runConf{disable},
"initcpio|i" => \$runConf{usecpio},
"hookd|H=s@" => \$runConf{cpio_hookd},
"debug|d" => \$runConf{debug},
"help|h" => sub {
pod2usage( -verbose => 2 );
Expand Down Expand Up @@ -166,9 +169,52 @@ unless ( $config{Global}{ManageImages} ) {
exit;
}

# Override the location of our specific dracut.conf.d directory
if ( nonempty $config{Global}{DracutConfDir} ) {
$runConf{confd} = $config{Global}{DracutConfDir};
unless ( $runConf{usecpio} ) {

# If usecpio wasn't set by cmdline, load from the config
if ( defined $config{Global}{InitCPIO} ) {
$runConf{usecpio} = $config{Global}{InitCPIO};
} else {
$runConf{usecpio} = false;
}
}

unless ( defined $runConf{confd} ) {

# Set initramfs configuration from defaults or config file

# Defaults and config key depend on initramfs generator
my $ckey;
if ( $runConf{usecpio} ) {
$runConf{confd} = "/etc/zfsbootmenu/mkinitcpio.conf";
$ckey = "InitCPIOConfig";
} else {
$runConf{confd} = "/etc/zfsbootmenu/dracut.conf.d";
$ckey = "DracutConfDir";
}

# Replace the default if a configuration option exists
if ( defined $config{Global}{$ckey} ) {
$runConf{confd} = $config{Global}{$ckey};
}
}

if ( $runConf{usecpio} and not defined $runConf{cpio_hookd} ) {

# With initcpio mode, load hookdirs from config when not specified on cmdline
my @hooks;

if ( defined $config{Global}{InitCPIOHookDirs} ) {
if ( ref $config{Global}{InitCPIOHookDirs} eq REFARRAY ) {
foreach my $hookd ( @{ $config{Global}{InitCPIOHookDirs} } ) {
push( @hooks, $hookd );
}
} else {
push( @hooks, $config{Global}{InitCPIOHookDirs} );
}
}

$runConf{cpio_hookd} = \@hooks;
}

# Ensure our bootloader partition is mounted
Expand All @@ -195,7 +241,7 @@ if ( nonempty $config{Global}{BootMountPoint} ) {
}

if ( nonempty $config{Global}{PreHooksDir} and -d $config{Global}{PreHooksDir} ) {
while (my $hook = <$config{Global}{PreHooksDir}/*> ){
while ( my $hook = <$config{Global}{PreHooksDir}/*> ) {
next unless -x $hook;
Log("Processing hook: $hook");
my @output = execute(qq($hook));
Expand Down Expand Up @@ -302,7 +348,7 @@ printf "Creating ZFSBootMenu %s from kernel %s\n", $runConf{version}, $runConf{k
my $spl_hostid = "/sys/module/spl/parameters/spl_hostid";
if ( -f $spl_hostid ) {
open PROC, $spl_hostid;
$runConf{hostid}{module} = sprintf("%08x", <PROC>);
$runConf{hostid}{module} = sprintf( "%08x", <PROC> );
close PROC;
} else {
$runConf{hostid}{module} = "00000000";
Expand All @@ -315,21 +361,29 @@ if ( $runConf{hostid}{module} ne "00000000" and -f $etc_hostid ) {
close SPL;

if ( unpack( 'c', pack( 's', 1 ) ) eq 1 ) {

# little endian
$runConf{hostid}{etc} = sprintf("%08x", unpack('L<4', $hostid));
$runConf{hostid}{etc} = sprintf( "%08x", unpack( 'L<4', $hostid ) );
} else {

# big endian
$runConf{hostid}{etc} = sprintf("%08x", unpack('L>4', $hostid));
$runConf{hostid}{etc} = sprintf( "%08x", unpack( 'L>4', $hostid ) );
}

if ($runConf{hostid}{module} ne $runConf{hostid}{etc}) {
if ( $runConf{hostid}{module} ne $runConf{hostid}{etc} ) {
print "SPL ($runConf{hostid}{module}) and system ($runConf{hostid}{etc}) hostids do not match!\n";
}
}

# Create the initramfs as long as some output will consume it
my $initramfs;
if ( enabled $config{EFI} or enabled $config{Components} ) {
$initramfs = createInitramfs( $tempdir, $runConf{kernel_version} );
}

# Create a unified kernel/initramfs/command line EFI file
if ( enabled $config{EFI} ) {
my $unified_efi = createInitramfs( $tempdir, $runConf{kernel_version}, $runConf{kernel} );
my $unified_efi = createUEFIBundle( $tempdir, $runConf{kernel}, $initramfs );

my $efi_target;

Expand Down Expand Up @@ -390,8 +444,6 @@ if ( enabled $config{EFI} ) {

# Create a separate kernel / initramfs. Used by syslinux/extlinux/grub.
if ( enabled $config{Components} ) {
my $initramfs = createInitramfs( $tempdir, $runConf{kernel_version} );

my ( $kernel_target, $initramfs_target );

my $component_prefix = sprintf( "%s/%s", $config{Components}{ImageDir}, $runConf{kernel_prefix} );
Expand Down Expand Up @@ -546,7 +598,7 @@ EOF
}

if ( nonempty $config{Global}{PostHooksDir} and -d $config{Global}{PostHooksDir} ) {
while (my $hook = <$config{Global}{PostHooksDir}/*> ){
while ( my $hook = <$config{Global}{PostHooksDir}/*> ) {
next unless -x $hook;
Log("Processing hook: $hook");
my @output = execute(qq($hook));
Expand Down Expand Up @@ -674,41 +726,140 @@ EOF
return $namever;
}

# Returns the path to an initramfs, or dies with an error
sub createInitramfs {
my ( $imagedir, $kver, $kernfile ) = @_;
# Creates a UEFI bundle from an initramfs and kernel
# Returns the path to the bundle or dies with an error
sub createUEFIBundle {
my ( $imagedir, $kernel, $initramfs ) = @_;

my $output_file = join( '/', $imagedir, "zfsbootmenu.img" );
my $output_file = join( '/', $imagedir, "zfsbootmenu.EFI" );

my @cmd = ( qw(dracut -f --confdir), $runConf{confd} );
push(@cmd, qw(-q)) unless $runConf{debug};
unless ( -f $kernel and -f $initramfs ) {
print "Cannot find kernel or initramfs to create UEFI bundle\n";
exit 1;
}

if ( defined $config{Global}{DracutFlags} ) {
if ( ref $config{Global}{DracutFlags} eq REFARRAY ) {
foreach my $flag ( @{ $config{Global}{DracutFlags} } ) {
push( @cmd, $flag );
my $uefi_stub;

if ( nonempty $config{EFI}{Stub} ) {
$uefi_stub = $config{EFI}{Stub};
} else {
# For now, default stub locations are x86_64 only
my @uefi_stub_defaults = qw(
/usr/lib/gummiboot/linuxx64.efi.stub
/usr/lib/systemd/boot/efi/linuxx64.efi.stub
);

foreach my $stubloc (@uefi_stub_defaults) {
if ( -f $stubloc ) {
$uefi_stub = $stubloc;
last;
}
} else {
push( @cmd, $config{Global}{DracutFlags} );
}
}

# If $kernfile is provided, make a unified EFI image with the named kernel
if ( defined $kernfile ) {
if ( nonempty $config{EFI}{Stub} ) {
push( @cmd, ( qq(--uefi-stub), $config{EFI}{Stub} ) );
unless ( -f $uefi_stub ) {
print "Cannot find kernel or initramfs to create UEFI bundle\n";
exit 1;
}

my @cmd = qw(objcopy);

# Add os-release, if it exists
if ( -f "/etc/os-release" ) {
push( @cmd,
qw(--add-section .osrel=/etc/os-release),
qw(--change-section-vma .osrel=0x20000)
);
}

# Add cmdline, if it exists
if ( nonempty $runConf{cmdline} ) {
my $cmdline = join( '/', $imagedir, "cmdline.txt" );

open( my $fh, '>', $cmdline );
print $fh $runConf{cmdline};
close($fh);

push( @cmd,
( "--add-section", ".cmdline=\"$cmdline\"" ),
qw(--change-section-vma .cmdline=0x30000)
);
}

# Mandatory kernel and initramfs images
push( @cmd, (
( "--add-section", ".linux=\"$kernel\"" ),
qw(--change-section-vma .linux=0x2000000),
( "--add-section", ".initrd=\"$initramfs\"" ),
qw(--change-section-vma .initrd=0x3000000)
));

push( @cmd, ( $uefi_stub, $output_file ) );

my $command = join( ' ', @cmd );
Log("Executing: $command");

my @output = execute(@cmd);
my $status = pop(@output);
if ( $status eq 0 ) {
foreach my $line (@output) {
Log($line);
}
return $output_file;
} else {
foreach my $line (@output) {
print $line;
}
print "Failed to create $output_file\n";
exit $status;
}
}

# Creates an initramfs and returns its path, or dies with an error
sub createInitramfs {
my ( $imagedir, $kver ) = @_;

my $output_file = join( '/', $imagedir, "zfsbootmenu.img" );

my @cmd;
my $flagsKey;

push( @cmd, ( qw(--uefi --kernel-image), $kernfile ) );
if ( $runConf{usecpio} ) {
push( @cmd, ( qw(mkinitcpio --config), $runConf{confd} ) );
push( @cmd, qw(-v) ) if $runConf{debug};

if ( nonempty $runConf{cmdline} ) {
push( @cmd, qq(--kernel-cmdline=\"$runConf{cmdline}\") );
# Add hook directories as appropriate
if ( defined $runConf{cpio_hookd} ) {
foreach my $hookd ( @{ $runConf{cpio_hookd} } ) {
push( @cmd, ( "--hookdir", $hookd ) );
}
}

$output_file = join( '/', $imagedir, "zfsbootmenu.efi" );
$flagsKey = "InitCPIOFlags";
} else {
push( @cmd, ( qw(dracut -f --confdir), $runConf{confd} ) );
push( @cmd, qw(-q) ) unless $runConf{debug};

$flagsKey = "DracutFlags";
}

push( @cmd, ( $output_file, $kver ) );
# Load custom flag additions from configuration file
if ( defined $config{Global}{$flagsKey} ) {
if ( ref $config{Global}{$flagsKey} eq REFARRAY ) {
foreach my $flag ( @{ $config{Global}{$flagsKey} } ) {
push( @cmd, $flag );
}
} else {
push( @cmd, $config{Global}{$flagsKey} );
}
}

# Specify kernel version and ouptut location
if ( $runConf{usecpio} ) {
push( @cmd, ( qw(-A zfsbootmenu), "--generate", $output_file, "--kernel", $kver ) );
} else {
push( @cmd, ( $output_file, $kver ) );
}

my $command = join( ' ', @cmd );
Log("Executing: $command");
Expand Down Expand Up @@ -947,7 +1098,7 @@ sub Log {
unless ( ref($entry) ) {
print STDERR "## $entry\n";
} elsif ( ref $entry eq REFARRAY ) {
foreach my $line ( @{ $entry } ) {
foreach my $line ( @{$entry} ) {
chomp $line;
print STDERR "## $line\n";
}
Expand Down Expand Up @@ -988,9 +1139,27 @@ Manually specify a specific kernel version; supersedes I<Kernel.Version>
Manually specify the output image prefix; supersedes I<Kernel.Prefix>
=item B<--confd|-C> I<confd-path>
=item B<--initcpio|-i>
Use mkinitcpio instead of dracut.
=item B<--confd|-C> I<config-path>
Specify initramfs configuration path
=over 4
=item For dracut: supersedes I<Global.DracutConfDir>
=item For mkinitcpio: supersedes I<Global.InitCPIOConfig>
=back
=item B<--hookd|-H> I<hookd-path>
Specify mkinitcpio hook directory; supersedes I<Global.InitCPIOHookDirs>
Specify the dracut configuration directory; supersedes I<Global.DracutConfDir>
May be specified more than once. Ignored when using dracut.
=item B<--cmdline|-l> I<options>
Expand Down
16 changes: 14 additions & 2 deletions man/generate-zbm.5
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,18 @@ The configuration is divided into several logical sections as keys of a \s-1YAML
.IP "\fBManageImages\fR" 4
.IX Item "ManageImages"
This must be set to \fItrue\fR before \fBgenerate-zbm\fR will attempt to perform any action (e.g., image creation or pruning old files).
.IP "\fBInitCPIO\fR" 4
.IX Item "InitCPIO"
Set to \fItrue\fR to use \fBmkinitcpio\fR instead of \fBdracut\fR to create ZFSBootMenu images.
.IP "\fBDracutConfDir\fR" 4
.IX Item "DracutConfDir"
The path of the dracut configuration directory for ZFSBootMenu. This \fB\s-1MUST NOT\s0\fR be the same location as the system \fIdracut.conf.d\fR, as the configuration files there interfere with the creation of the ZFSBootMenu initramfs. If unspecified, a default value of \fI/etc/zfsbootmenu/dracut.conf.d\fR is assumed.
The path of the dracut configuration directory for ZFSBootMenu. This \fB\s-1MUST NOT\s0\fR be the same location as the system \fIdracut.conf.d\fR, as the configuration files there interfere with the creation of the ZFSBootMenu initramfs. If unspecified, a default value of \fI/etc/zfsbootmenu/dracut.conf.d\fR is assumed. This value is ignored when \fIGlobal.InitCPIO\fR is \fItrue\fR.
.IP "\fBInitCPIOConfig\fR" 4
.IX Item "InitCPIOConfig"
The path to a mkinitcpio configuration file for ZFSBootMenu. The \fIzfsbootmenu\fR hook will be forcefully added when \fBgenerate-zbm\fR invokes \fBmkinitcpio\fR using a command-line argument and does not need to be specified in the \fI\s-1HOOKS\s0\fR array in the configuration file. This value is ignored when \fIGlobal.InitCPIO\fR is not \fItrue\fR.
.IP "\fBInitCPIOHookDirs\fR" 4
.IX Item "InitCPIOHookDirs"
A single path or an array of paths to \fBmkinitcpio\fR hook directories. When specifying a custom directory for the \fIzfsbootmenu\fR hook, it is generally required to also specify the default location as well. This option is ignored when \fIGlobal.InitCPIO\fR is not \fItrue\fR.
.IP "\fBBootMountPoint\fR" 4
.IX Item "BootMountPoint"
In general, this should be the location of your \s-1EFI\s0 System Partition. \fBgenerate-zbm\fR will ensure that this is mounted when images are created and, if \fBgenerate-zbm\fR does the mounting, will unmount this filesystem on exit. When this parameter is not specified, \fBgenerate-zbm\fR will not verify or attempt to mount any filesystems.
Expand All @@ -165,7 +174,10 @@ In general, this should be the location of your \s-1EFI\s0 System Partition. \fB
A specific ZFSBootMenu version string to use in producing images. In the string, the value \fI%{current}\fR will be replaced with the release version of ZFSBootMenu. The default value is simply \fI%{current}\fR.
.IP "\fBDracutFlags\fR" 4
.IX Item "DracutFlags"
An array of additional arguments that will be passed to \fBdracut\fR when generating an initramfs.
An array of additional arguments that will be passed to \fBdracut\fR when generating an initramfs. This option is ignored when \fIGlobal.InitCPIO\fR is \fItrue\fR.
.IP "\fBInitCPIOFlags\fR" 4
.IX Item "InitCPIOFlags"
An array of additional arguments that will be passed to \fBmkinitcpio\fR when generating an initramfs. This option is ignored when \fIGlobal.InitCPIO\fR is not \fItrue\fR.
.IP "\fBPreHooksDir\fR" 4
.IX Item "PreHooksDir"
The path of the directory containing executables that should be executed after \fIBootMountPoint\fR has been mounted. Files in this directory should be \fB+x\fR, and are executed in the order returned by a shell glob. The exit code of each hook is ignored.
Expand Down

0 comments on commit af34fa9

Please sign in to comment.