Subject: [PATCH v2] Add support for unlocking multi-device root

From: Wolf <[email-masked]>
Date: Sun, 22 Sep 2019 22:08:27 +0200

Add cryptroo[0-9]= family of options to allow unlocking multi-device
root filesystems, which is needed for btrfs raid1.
---
Suppress output of dd.

 initramfs-init.in       | 80 ++++++++++++++++++++++++++++++++++++++---
 mkinitfs-bootparam.7.in | 28 +++++++++++++++
 2 files changed, 103 insertions(+), 5 deletions(-)

diff --git a/initramfs-init.in b/initramfs-init.in
index 4f96b7c..f358b2a 100755
--- a/initramfs-init.in
+++ b/initramfs-init.in
@@ -311,11 +311,15 @@ mount -t tmpfs -o nodev,nosuid,noexec shm /dev/shm
 #  acpi_osi="!Windows 2006" xen-pciback.hide=(01:00.0)
 set -- $(cat /proc/cmdline)
 
-myopts="alpine_dev autodetect autoraid chart cryptroot cryptdm cryptheader cryptoffset
-	cryptdiscards cryptkey debug_init dma init init_args keep_apk_new modules ovl_dev
-	pkgs quiet root_size root usbdelay ip alpine_repo apkovl alpine_start splash
-	blacklist overlaytmpfs rootfstype rootflags nbd resume s390x_net dasd ssh_key
-	BOOTIF"
+myopts="alpine_dev autodetect autoraid chart cryptroot cryptdm cryptheader
+	cryptoffset cryptdiscards cryptkey debug_init dma init init_args
+	keep_apk_new modules ovl_dev pkgs quiet root_size root usbdelay ip
+	alpine_repo apkovl alpine_start splash blacklist overlaytmpfs rootfstype
+	rootflags nbd resume s390x_net dasd ssh_key BOOTIF"
+for i in `seq 0 9`; do
+	myopts="$myopts cryptroot$i cryptdm$i cryptheader$i cryptoffset$i
+		cryptdiscards$i cryptkey$i"
+done
 
 for opt; do
 	case "$opt" in
@@ -442,6 +446,72 @@ if [ -n "$KOPT_cryptroot" ]; then
 	fi
 fi
 
+(
+# Helper functions to avoid obscuring logic with so many evals
+for var in cryptroot cryptdiscards cryptdm cryptoffset cryptheader cryptkey; do
+	eval "$var() { eval 'printf -- %s \"\$KOPT_$var'\$1'\"'; }"
+done
+for i in `seq 0 9`; do
+	[ -n "$(cryptroot $i)" ] || continue
+	any=true
+
+	ebegin "Opening encrypted device $(cryptroot $i)"
+
+	opts="-c $(cryptroot $i)"
+
+	if [ "$(cryptdiscards $i)" = "yes" ]; then
+		opts="$opts -D"
+	fi
+
+	if [ -n "$(cryptdm $i)" ]; then
+		name="$(cryptdm $i)"
+	else
+		name=$(
+			dd if=/dev/urandom bs=4096 count=1 2>/dev/null \
+				| sha1sum | tr -d \ -
+		)
+		cat >&2 <<-EOF
+WARNING: cryptsetup does not work without having a device name set. You should
+         provide one using cryptdm$i=. Using temporary name:
+             $name.
+		EOF
+	fi
+	opts="$opts -m $name"
+
+	if [ -n "$(cryptheader $i)" ]; then
+		opts="$opts -H $(cryptheader $i)"
+	fi
+
+	if [ -n "$(cryptoffset $i)" ]; then
+		opts="$opts -o $(cryptoffset $i)"
+	fi
+
+	if [ "$(cryptkey $i)" = "yes" ]; then
+		opts="$opts -k /crypto_keyfile.bin"
+	elif [ -n "$(cryptkey $i)" ]; then
+		opts="$opts -k $(cryptkey $i)"
+	fi
+
+	nlplug-findfs $opts -p /sbin/mdev ${KOPT_debug_init:+-d} \
+		${KOPT_usbdelay:+-t $(( $KOPT_usbdelay * 1000 ))} \
+		/dev/mapper/"$name"
+	eend $?
+done
+
+# We need to continue only if there is at least one cryptrootX specified
+[ "$any" = "true" ] || exit 0
+
+# Since btrfs needs all devices to be present, scanning cannot be done by the
+# nlplug-findfs above. So lets do both lvm and btrfs manually here.
+if [ -x /sbin/lvm ]; then
+	/sbin/lvm vgchange -aya --noudevsync --sysinit -qq
+fi
+if [ -x /sbin/btrfs ]; then
+	/sbin/btrfs device scan >/dev/null || \
+		echo "Failed to scan devices for btrfs filesystem."
+fi
+)
+
 if [ -n "$KOPT_nbd" ]; then
 	# TODO: Might fail because nlplug-findfs hasn't plugged eth0 yet
 	configure_ip
diff --git a/mkinitfs-bootparam.7.in b/mkinitfs-bootparam.7.in
index d5a3aad..deca9c0 100644
--- a/mkinitfs-bootparam.7.in
+++ b/mkinitfs-bootparam.7.in
@@ -27,20 +27,39 @@ security implications.
 After the block device has been decrypted, make it available as
 /dev/mapper/\fINAME\fR.
 .TP
+\fBcryptdm[0-9]=\fINAME\fR
+After the block device has been decrypted, make it available as
+/dev/mapper/\fINAME\fR. See "CRYPTO NOTES" for details.
+.TP
 \fBcryptheader=\fIDEVICE\fR
 When the LUKS headers and encrypted data are on different devices, this option
 specifies the device with the LUKS headers.
 .TP
+\fBcryptheader[0-9]=\fIDEVICE\fR
+When the LUKS headers and encrypted data are on different devices, this option
+specifies the device with the LUKS headers. See "CRYPTO NOTES" for details.
+.TP
 \fBcryptkey=\fIKEYFILE\fR
 Attempt to decrypt an encypted partition using an keyfile.
 .TP
+\fBcryptkey[0-9]=\fIKEYFILE\fR
+Attempt to decrypt an encypted partition using an keyfile. See "CRYPTO NOTES"
+for details.
+.TP
 \fBcryptoffset=\fISECTORS\fR
 Indicate that the encrypted data begins the given number of sectors after the
 start of the block device.
 .TP
+\fBcryptoffset[0-9]=\fISECTORS\fR
+Indicate that the encrypted data begins the given number of sectors after the
+start of the block device. See "CRYPTO NOTES" for details.
+.TP
 \fBcryptroot=\fIDEVICE\fR
 Attempt to decrypt \fIDEVICE\fR.
 .TP
+\fBcryptroot[0-9]=\fIDEVICE\fR
+Attempt to decrypt \fIDEVICE\fR. See "CRYPTO NOTES" for details.
+.TP
 \fBdasd\fR
 Enable DASD devices on S/390x architectures.
 .TP
@@ -123,6 +142,15 @@ media.
 .TP
 \fI@datadir@/initramfs-init\fR
 Default script that will be run in the initramfs.
+.SH CRYPTO NOTES
+There are two ways to specify what encrypted device should be unlocked.
+.PP
+One is \fBcryptroot=\fR family of options. 99% of the time this is what you want
+to use. It works just fine as long as you need to unlock just one device.
+.PP
+Other is \fBcryptroot[0-9]=\fR family of options. When you have your root on
+for example btrfs raid1, you need to unlock multiple devices. Up to 10 devices
+can be specified.
 .SH AUTHOR
 .PP
 Written by Natanael Copa <[email-masked]>, Timo Teräs <[email-masked]> and others.
-- 
2.23.0