Skip to content

Commit af34fa9

Browse files
committed
Support building mkinitcpio images in generate-zbm
1 parent 50a2da4 commit af34fa9

File tree

6 files changed

+293
-49
lines changed

6 files changed

+293
-49
lines changed

bin/generate-zbm

Lines changed: 206 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use constant REFARRAY => ref [];
3535
sub versionedKernel;
3636
sub latestKernel;
3737
sub createInitramfs;
38+
sub createUEFIBundle;
3839
sub execute;
3940
sub safeCopy;
4041
sub nonempty;
@@ -57,7 +58,7 @@ my ( %runConf, %config );
5758
my $default_config = "/etc/zfsbootmenu/config.yaml";
5859

5960
$runConf{bootdir} = "/boot";
60-
$runConf{confd} = "/etc/zfsbootmenu/dracut.conf.d";
61+
$runConf{usecpio} = false;
6162

6263
GetOptions(
6364
"version|v=s" => \$runConf{version},
@@ -71,6 +72,8 @@ GetOptions(
7172
"config|c=s" => \$runConf{config},
7273
"enable" => \$runConf{enable},
7374
"disable" => \$runConf{disable},
75+
"initcpio|i" => \$runConf{usecpio},
76+
"hookd|H=s@" => \$runConf{cpio_hookd},
7477
"debug|d" => \$runConf{debug},
7578
"help|h" => sub {
7679
pod2usage( -verbose => 2 );
@@ -166,9 +169,52 @@ unless ( $config{Global}{ManageImages} ) {
166169
exit;
167170
}
168171

169-
# Override the location of our specific dracut.conf.d directory
170-
if ( nonempty $config{Global}{DracutConfDir} ) {
171-
$runConf{confd} = $config{Global}{DracutConfDir};
172+
unless ( $runConf{usecpio} ) {
173+
174+
# If usecpio wasn't set by cmdline, load from the config
175+
if ( defined $config{Global}{InitCPIO} ) {
176+
$runConf{usecpio} = $config{Global}{InitCPIO};
177+
} else {
178+
$runConf{usecpio} = false;
179+
}
180+
}
181+
182+
unless ( defined $runConf{confd} ) {
183+
184+
# Set initramfs configuration from defaults or config file
185+
186+
# Defaults and config key depend on initramfs generator
187+
my $ckey;
188+
if ( $runConf{usecpio} ) {
189+
$runConf{confd} = "/etc/zfsbootmenu/mkinitcpio.conf";
190+
$ckey = "InitCPIOConfig";
191+
} else {
192+
$runConf{confd} = "/etc/zfsbootmenu/dracut.conf.d";
193+
$ckey = "DracutConfDir";
194+
}
195+
196+
# Replace the default if a configuration option exists
197+
if ( defined $config{Global}{$ckey} ) {
198+
$runConf{confd} = $config{Global}{$ckey};
199+
}
200+
}
201+
202+
if ( $runConf{usecpio} and not defined $runConf{cpio_hookd} ) {
203+
204+
# With initcpio mode, load hookdirs from config when not specified on cmdline
205+
my @hooks;
206+
207+
if ( defined $config{Global}{InitCPIOHookDirs} ) {
208+
if ( ref $config{Global}{InitCPIOHookDirs} eq REFARRAY ) {
209+
foreach my $hookd ( @{ $config{Global}{InitCPIOHookDirs} } ) {
210+
push( @hooks, $hookd );
211+
}
212+
} else {
213+
push( @hooks, $config{Global}{InitCPIOHookDirs} );
214+
}
215+
}
216+
217+
$runConf{cpio_hookd} = \@hooks;
172218
}
173219

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

197243
if ( nonempty $config{Global}{PreHooksDir} and -d $config{Global}{PreHooksDir} ) {
198-
while (my $hook = <$config{Global}{PreHooksDir}/*> ){
244+
while ( my $hook = <$config{Global}{PreHooksDir}/*> ) {
199245
next unless -x $hook;
200246
Log("Processing hook: $hook");
201247
my @output = execute(qq($hook));
@@ -302,7 +348,7 @@ printf "Creating ZFSBootMenu %s from kernel %s\n", $runConf{version}, $runConf{k
302348
my $spl_hostid = "/sys/module/spl/parameters/spl_hostid";
303349
if ( -f $spl_hostid ) {
304350
open PROC, $spl_hostid;
305-
$runConf{hostid}{module} = sprintf("%08x", <PROC>);
351+
$runConf{hostid}{module} = sprintf( "%08x", <PROC> );
306352
close PROC;
307353
} else {
308354
$runConf{hostid}{module} = "00000000";
@@ -315,21 +361,29 @@ if ( $runConf{hostid}{module} ne "00000000" and -f $etc_hostid ) {
315361
close SPL;
316362

317363
if ( unpack( 'c', pack( 's', 1 ) ) eq 1 ) {
364+
318365
# little endian
319-
$runConf{hostid}{etc} = sprintf("%08x", unpack('L<4', $hostid));
366+
$runConf{hostid}{etc} = sprintf( "%08x", unpack( 'L<4', $hostid ) );
320367
} else {
368+
321369
# big endian
322-
$runConf{hostid}{etc} = sprintf("%08x", unpack('L>4', $hostid));
370+
$runConf{hostid}{etc} = sprintf( "%08x", unpack( 'L>4', $hostid ) );
323371
}
324372

325-
if ($runConf{hostid}{module} ne $runConf{hostid}{etc}) {
373+
if ( $runConf{hostid}{module} ne $runConf{hostid}{etc} ) {
326374
print "SPL ($runConf{hostid}{module}) and system ($runConf{hostid}{etc}) hostids do not match!\n";
327375
}
328376
}
329377

378+
# Create the initramfs as long as some output will consume it
379+
my $initramfs;
380+
if ( enabled $config{EFI} or enabled $config{Components} ) {
381+
$initramfs = createInitramfs( $tempdir, $runConf{kernel_version} );
382+
}
383+
330384
# Create a unified kernel/initramfs/command line EFI file
331385
if ( enabled $config{EFI} ) {
332-
my $unified_efi = createInitramfs( $tempdir, $runConf{kernel_version}, $runConf{kernel} );
386+
my $unified_efi = createUEFIBundle( $tempdir, $runConf{kernel}, $initramfs );
333387

334388
my $efi_target;
335389

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

391445
# Create a separate kernel / initramfs. Used by syslinux/extlinux/grub.
392446
if ( enabled $config{Components} ) {
393-
my $initramfs = createInitramfs( $tempdir, $runConf{kernel_version} );
394-
395447
my ( $kernel_target, $initramfs_target );
396448

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

548600
if ( nonempty $config{Global}{PostHooksDir} and -d $config{Global}{PostHooksDir} ) {
549-
while (my $hook = <$config{Global}{PostHooksDir}/*> ){
601+
while ( my $hook = <$config{Global}{PostHooksDir}/*> ) {
550602
next unless -x $hook;
551603
Log("Processing hook: $hook");
552604
my @output = execute(qq($hook));
@@ -674,41 +726,140 @@ EOF
674726
return $namever;
675727
}
676728

677-
# Returns the path to an initramfs, or dies with an error
678-
sub createInitramfs {
679-
my ( $imagedir, $kver, $kernfile ) = @_;
729+
# Creates a UEFI bundle from an initramfs and kernel
730+
# Returns the path to the bundle or dies with an error
731+
sub createUEFIBundle {
732+
my ( $imagedir, $kernel, $initramfs ) = @_;
680733

681-
my $output_file = join( '/', $imagedir, "zfsbootmenu.img" );
734+
my $output_file = join( '/', $imagedir, "zfsbootmenu.EFI" );
682735

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

686-
if ( defined $config{Global}{DracutFlags} ) {
687-
if ( ref $config{Global}{DracutFlags} eq REFARRAY ) {
688-
foreach my $flag ( @{ $config{Global}{DracutFlags} } ) {
689-
push( @cmd, $flag );
741+
my $uefi_stub;
742+
743+
if ( nonempty $config{EFI}{Stub} ) {
744+
$uefi_stub = $config{EFI}{Stub};
745+
} else {
746+
# For now, default stub locations are x86_64 only
747+
my @uefi_stub_defaults = qw(
748+
/usr/lib/gummiboot/linuxx64.efi.stub
749+
/usr/lib/systemd/boot/efi/linuxx64.efi.stub
750+
);
751+
752+
foreach my $stubloc (@uefi_stub_defaults) {
753+
if ( -f $stubloc ) {
754+
$uefi_stub = $stubloc;
755+
last;
690756
}
691-
} else {
692-
push( @cmd, $config{Global}{DracutFlags} );
693757
}
694758
}
695759

696-
# If $kernfile is provided, make a unified EFI image with the named kernel
697-
if ( defined $kernfile ) {
698-
if ( nonempty $config{EFI}{Stub} ) {
699-
push( @cmd, ( qq(--uefi-stub), $config{EFI}{Stub} ) );
760+
unless ( -f $uefi_stub ) {
761+
print "Cannot find kernel or initramfs to create UEFI bundle\n";
762+
exit 1;
763+
}
764+
765+
my @cmd = qw(objcopy);
766+
767+
# Add os-release, if it exists
768+
if ( -f "/etc/os-release" ) {
769+
push( @cmd,
770+
qw(--add-section .osrel=/etc/os-release),
771+
qw(--change-section-vma .osrel=0x20000)
772+
);
773+
}
774+
775+
# Add cmdline, if it exists
776+
if ( nonempty $runConf{cmdline} ) {
777+
my $cmdline = join( '/', $imagedir, "cmdline.txt" );
778+
779+
open( my $fh, '>', $cmdline );
780+
print $fh $runConf{cmdline};
781+
close($fh);
782+
783+
push( @cmd,
784+
( "--add-section", ".cmdline=\"$cmdline\"" ),
785+
qw(--change-section-vma .cmdline=0x30000)
786+
);
787+
}
788+
789+
# Mandatory kernel and initramfs images
790+
push( @cmd, (
791+
( "--add-section", ".linux=\"$kernel\"" ),
792+
qw(--change-section-vma .linux=0x2000000),
793+
( "--add-section", ".initrd=\"$initramfs\"" ),
794+
qw(--change-section-vma .initrd=0x3000000)
795+
));
796+
797+
push( @cmd, ( $uefi_stub, $output_file ) );
798+
799+
my $command = join( ' ', @cmd );
800+
Log("Executing: $command");
801+
802+
my @output = execute(@cmd);
803+
my $status = pop(@output);
804+
if ( $status eq 0 ) {
805+
foreach my $line (@output) {
806+
Log($line);
700807
}
808+
return $output_file;
809+
} else {
810+
foreach my $line (@output) {
811+
print $line;
812+
}
813+
print "Failed to create $output_file\n";
814+
exit $status;
815+
}
816+
}
817+
818+
# Creates an initramfs and returns its path, or dies with an error
819+
sub createInitramfs {
820+
my ( $imagedir, $kver ) = @_;
821+
822+
my $output_file = join( '/', $imagedir, "zfsbootmenu.img" );
823+
824+
my @cmd;
825+
my $flagsKey;
701826

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

704-
if ( nonempty $runConf{cmdline} ) {
705-
push( @cmd, qq(--kernel-cmdline=\"$runConf{cmdline}\") );
831+
# Add hook directories as appropriate
832+
if ( defined $runConf{cpio_hookd} ) {
833+
foreach my $hookd ( @{ $runConf{cpio_hookd} } ) {
834+
push( @cmd, ( "--hookdir", $hookd ) );
835+
}
706836
}
707837

708-
$output_file = join( '/', $imagedir, "zfsbootmenu.efi" );
838+
$flagsKey = "InitCPIOFlags";
839+
} else {
840+
push( @cmd, ( qw(dracut -f --confdir), $runConf{confd} ) );
841+
push( @cmd, qw(-q) ) unless $runConf{debug};
842+
843+
$flagsKey = "DracutFlags";
709844
}
710845

711-
push( @cmd, ( $output_file, $kver ) );
846+
# Load custom flag additions from configuration file
847+
if ( defined $config{Global}{$flagsKey} ) {
848+
if ( ref $config{Global}{$flagsKey} eq REFARRAY ) {
849+
foreach my $flag ( @{ $config{Global}{$flagsKey} } ) {
850+
push( @cmd, $flag );
851+
}
852+
} else {
853+
push( @cmd, $config{Global}{$flagsKey} );
854+
}
855+
}
856+
857+
# Specify kernel version and ouptut location
858+
if ( $runConf{usecpio} ) {
859+
push( @cmd, ( qw(-A zfsbootmenu), "--generate", $output_file, "--kernel", $kver ) );
860+
} else {
861+
push( @cmd, ( $output_file, $kver ) );
862+
}
712863

713864
my $command = join( ' ', @cmd );
714865
Log("Executing: $command");
@@ -947,7 +1098,7 @@ sub Log {
9471098
unless ( ref($entry) ) {
9481099
print STDERR "## $entry\n";
9491100
} elsif ( ref $entry eq REFARRAY ) {
950-
foreach my $line ( @{ $entry } ) {
1101+
foreach my $line ( @{$entry} ) {
9511102
chomp $line;
9521103
print STDERR "## $line\n";
9531104
}
@@ -988,9 +1139,27 @@ Manually specify a specific kernel version; supersedes I<Kernel.Version>
9881139
9891140
Manually specify the output image prefix; supersedes I<Kernel.Prefix>
9901141
991-
=item B<--confd|-C> I<confd-path>
1142+
=item B<--initcpio|-i>
1143+
1144+
Use mkinitcpio instead of dracut.
1145+
1146+
=item B<--confd|-C> I<config-path>
1147+
1148+
Specify initramfs configuration path
1149+
1150+
=over 4
1151+
1152+
=item For dracut: supersedes I<Global.DracutConfDir>
1153+
1154+
=item For mkinitcpio: supersedes I<Global.InitCPIOConfig>
1155+
1156+
=back
1157+
1158+
=item B<--hookd|-H> I<hookd-path>
1159+
1160+
Specify mkinitcpio hook directory; supersedes I<Global.InitCPIOHookDirs>
9921161
993-
Specify the dracut configuration directory; supersedes I<Global.DracutConfDir>
1162+
May be specified more than once. Ignored when using dracut.
9941163
9951164
=item B<--cmdline|-l> I<options>
9961165

man/generate-zbm.5

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,18 @@ The configuration is divided into several logical sections as keys of a \s-1YAML
154154
.IP "\fBManageImages\fR" 4
155155
.IX Item "ManageImages"
156156
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).
157+
.IP "\fBInitCPIO\fR" 4
158+
.IX Item "InitCPIO"
159+
Set to \fItrue\fR to use \fBmkinitcpio\fR instead of \fBdracut\fR to create ZFSBootMenu images.
157160
.IP "\fBDracutConfDir\fR" 4
158161
.IX Item "DracutConfDir"
159-
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.
162+
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.
163+
.IP "\fBInitCPIOConfig\fR" 4
164+
.IX Item "InitCPIOConfig"
165+
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.
166+
.IP "\fBInitCPIOHookDirs\fR" 4
167+
.IX Item "InitCPIOHookDirs"
168+
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.
160169
.IP "\fBBootMountPoint\fR" 4
161170
.IX Item "BootMountPoint"
162171
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.
@@ -165,7 +174,10 @@ In general, this should be the location of your \s-1EFI\s0 System Partition. \fB
165174
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.
166175
.IP "\fBDracutFlags\fR" 4
167176
.IX Item "DracutFlags"
168-
An array of additional arguments that will be passed to \fBdracut\fR when generating an initramfs.
177+
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.
178+
.IP "\fBInitCPIOFlags\fR" 4
179+
.IX Item "InitCPIOFlags"
180+
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.
169181
.IP "\fBPreHooksDir\fR" 4
170182
.IX Item "PreHooksDir"
171183
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.

0 commit comments

Comments
 (0)