From 7dff68f6d02afb1174b13c36865d4000cf63aff7 Mon Sep 17 00:00:00 2001
From: David Teigland <teigland@redhat.com>
Date: Thu, 1 Apr 2021 12:08:58 -0500
Subject: [PATCH 08/11] add dracut files
pvscan-udev-initrd.sh is shell wrapper around the
pvscan command for use in the initrd lvm udev rule.
It finds the intersection of complete VG/LVs reported
by pvscan, and the VG/LVs specified on boot cmdline.
The resulting VG or LVs are printed as env-vars that
the udev rule can IMPORT, and pass to vgchange/lvchange.
64-lvm.rules calls pvscan-udev-initrd.sh/pvscan to scan
the PV to check if any VG or LVs are complete given the
new device. The pvscan will only ever read the single
device triggering the uevent. If any VG or LVs are
complete, the udev rule uses systemd-run to run a
vgchange or lvchange command to activate the complete
VG or LVs. (Running vgchange or lvchange directly may
take longer than udev likes, so systemd-run --no-block
is used.)
---
dracut/64-lvm.rules | 44 +++++++++++++++++
dracut/module-setup.sh | 112 +++++++++++++++++++++++++++++++++++++++++++
dracut/parse-lvm.sh | 18 +++++++
dracut/pvscan-udev-initrd.sh | 57 ++++++++++++++++++++++
4 files changed, 231 insertions(+)
create mode 100644 dracut/64-lvm.rules
create mode 100755 dracut/module-setup.sh
create mode 100755 dracut/parse-lvm.sh
create mode 100755 dracut/pvscan-udev-initrd.sh
diff --git a/dracut/64-lvm.rules b/dracut/64-lvm.rules
new file mode 100644
index 0000000..174af1d
--- /dev/null
+++ b/dracut/64-lvm.rules
@@ -0,0 +1,44 @@
+# Copyright 2008,2021 Red Hat, Inc.
+#
+# Jeremy Katz <katzj@redhat.com>
+
+SUBSYSTEM!="block", GOTO="lvm_end"
+ACTION!="add|change", GOTO="lvm_end"
+# Also don't process disks that are slated to be a multipath device
+ENV{DM_MULTIPATH_DEVICE_PATH}=="1", GOTO="lvm_end"
+KERNEL=="dm-[0-9]*", ACTION=="add", GOTO="lvm_end"
+ENV{ID_FS_TYPE}!="LVM2_member", GOTO="lvm_end"
+
+PROGRAM=="/bin/sh -c 'for i in $sys/$devpath/holders/dm-[0-9]*; do [ -e $$i ] && exit 0; done; exit 1;' ", \
+ GOTO="lvm_end"
+
+# pvscan-udev-initrd.sh is a wrapper that calls pvscan and prints
+# LVM_VG_NAME_COMPLETE='...'
+# LVM_LV_NAMES_COMPLETE='...'
+# if the given device completes a VG or LVs listed in
+# rd.lvm.vg or rd.lvm.lv
+#
+# If a VG or LVs are completed by the device, but are not
+# listed in rd.lvm.vg/lv, then they are not printed
+# (we don't want to activate VGs/LVs that are not specified.)
+#
+# If no VG or LVs are completed by the device, then
+# nothing is printed.
+#
+# LVs may be complete and activated before the VG is complete,
+# i.e. the entire VG is not necessary to activate LVs in it.
+#
+# If multiple LV names are completed from one device,
+# e.g. LVM_LV_NAMES_COMPLETE='vg/lv1 vg/lv2 vg/lv3'
+# they will be activated by one lvchange command.
+
+IMPORT{program}="pvscan-udev-initrd.sh $env{DEVNAME}"
+
+# systemd services are used to run vgchange/lvchange
+# because the lvm activation commands can run for longer
+# than udev will tolerate.
+
+ENV{LVM_VG_NAME_COMPLETE}=="?*", RUN+="/usr/bin/systemd-run --no-block --property DefaultDependencies=no /sbin/lvm vgchange -ay --yes --ignoremonitoring --poll n --sysinit $env{LVM_VG_NAME_COMPLETE}"
+ENV{LVM_LV_NAMES_COMPLETE}=="?*", RUN+="/usr/bin/systemd-run --no-block --property DefaultDependencies=no /sbin/lvm lvchange -ay --yes -K --ignoremonitoring --poll n --sysinit $env{LVM_LV_NAMES_COMPLETE}"
+
+LABEL="lvm_end"
diff --git a/dracut/module-setup.sh b/dracut/module-setup.sh
new file mode 100755
index 0000000..51d9cd3
--- /dev/null
+++ b/dracut/module-setup.sh
@@ -0,0 +1,112 @@
+#!/bin/bash
+
+# called by dracut
+check() {
+ # No point trying to support lvm if the binaries are missing
+ require_binaries lvm || return 1
+
+ [[ $hostonly ]] || [[ $mount_needs ]] && {
+ for fs in "${host_fs_types[@]}"; do
+ [[ $fs = LVM*_member ]] && return 0
+ done
+ return 255
+ }
+
+ return 0
+}
+
+# called by dracut
+depends() {
+ # We depend on dm_mod being loaded
+ echo rootfs-block dm
+ return 0
+}
+
+# called by dracut
+cmdline() {
+ local _activated
+ declare -A _activated
+
+ for dev in "${!host_fs_types[@]}"; do
+ [ -e /sys/block/${dev#/dev/}/dm/name ] || continue
+ [ -e /sys/block/${dev#/dev/}/dm/uuid ] || continue
+ uuid=$(</sys/block/${dev#/dev/}/dm/uuid)
+ [[ "${uuid#LVM-}" == "$uuid" ]] && continue
+ dev=$(</sys/block/${dev#/dev/}/dm/name)
+ eval $(dmsetup splitname --nameprefixes --noheadings --rows "$dev" 2>/dev/null)
+ [[ ${DM_VG_NAME} ]] && [[ ${DM_LV_NAME} ]] || return 1
+ if ! [[ ${_activated[${DM_VG_NAME}/${DM_LV_NAME}]} ]]; then
+ printf " rd.lvm.lv=%s " "${DM_VG_NAME}/${DM_LV_NAME} "
+ _activated["${DM_VG_NAME}/${DM_LV_NAME}"]=1
+ fi
+ done
+}
+
+installkernel() {
+ hostonly='' instmods dm-snapshot
+}
+
+# called by dracut
+install() {
+ local _i
+
+ inst lvm
+
+ if [[ $hostonly_cmdline == "yes" ]]; then
+ local _lvmconf=$(cmdline)
+ [[ $_lvmconf ]] && printf "%s\n" "$_lvmconf" >> "${initdir}/etc/cmdline.d/90lvm.conf"
+ fi
+
+ inst_rules "$moddir/64-lvm.rules"
+
+ if [[ $hostonly ]] || [[ $lvmconf = "yes" ]]; then
+ if [ -f $dracutsysrootdir/etc/lvm/lvm.conf ]; then
+ inst_simple -H /etc/lvm/lvm.conf
+ fi
+
+ export LVM_SUPPRESS_FD_WARNINGS=1
+ # Also install any files needed for LVM system id support.
+ if [ -f $dracutsysrootdir/etc/lvm/lvmlocal.conf ]; then
+ inst_simple -H /etc/lvm/lvmlocal.conf
+ fi
+ eval $(lvm dumpconfig global/system_id_source &>/dev/null)
+ if [ "$system_id_source" == "file" ]; then
+ eval $(lvm dumpconfig global/system_id_file)
+ if [ -f "$system_id_file" ]; then
+ inst_simple -H $system_id_file
+ fi
+ fi
+ unset LVM_SUPPRESS_FD_WARNINGS
+ fi
+
+ inst_rules 11-dm-lvm.rules
+
+ inst_script "$moddir/pvscan-udev-initrd.sh" /usr/lib/udev/pvscan-udev-initrd.sh
+ inst_hook cmdline 30 "$moddir/parse-lvm.sh"
+
+ inst_libdir_file "libdevmapper-event-lvm*.so"
+
+ if [[ $hostonly ]] && type -P lvs &>/dev/null; then
+ for dev in "${!host_fs_types[@]}"; do
+ [ -e /sys/block/${dev#/dev/}/dm/name ] || continue
+ dev=$(</sys/block/${dev#/dev/}/dm/name)
+ eval $(dmsetup splitname --nameprefixes --noheadings --rows "$dev" 2>/dev/null)
+ [[ ${DM_VG_NAME} ]] && [[ ${DM_LV_NAME} ]] || continue
+ case "$(lvs --noheadings -o segtype ${DM_VG_NAME} 2>/dev/null)" in
+ *thin*|*cache*|*era*)
+ inst_multiple -o thin_dump thin_restore thin_check thin_repair \
+ cache_dump cache_restore cache_check cache_repair \
+ era_check era_dump era_invalidate era_restore
+ break;;
+ esac
+ done
+ fi
+
+ if ! [[ $hostonly ]]; then
+ inst_multiple -o thin_dump thin_restore thin_check thin_repair \
+ cache_dump cache_restore cache_check cache_repair \
+ era_check era_dump era_invalidate era_restore
+ fi
+
+ dracut_need_initqueue
+}
diff --git a/dracut/parse-lvm.sh b/dracut/parse-lvm.sh
new file mode 100755
index 0000000..8236050
--- /dev/null
+++ b/dracut/parse-lvm.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+if [ -e /etc/lvm/lvm.conf ] && ! getargbool 1 rd.lvm.conf -d -n rd_NO_LVMCONF; then
+ rm -f -- /etc/lvm/lvm.conf
+fi
+
+LV_DEVS="$(getargs rd.lvm.vg -d rd_LVM_VG=) $(getargs rd.lvm.lv -d rd_LVM_LV=)"
+
+if ! getargbool 1 rd.lvm -d -n rd_NO_LVM \
+ || ( [ -z "$LV_DEVS" ] && ! getargbool 0 rd.auto ); then
+ info "rd.lvm=0: removing LVM activation"
+ rm -f -- /etc/udev/rules.d/64-lvm*.rules
+else
+ for dev in $LV_DEVS; do
+ wait_for_dev -n "/dev/$dev"
+ done
+fi
+
diff --git a/dracut/pvscan-udev-initrd.sh b/dracut/pvscan-udev-initrd.sh
new file mode 100755
index 0000000..1743771
--- /dev/null
+++ b/dracut/pvscan-udev-initrd.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+# pvscan wrapper called by initrd lvm udev rule to find the
+# intersection of complete VGs/LVs found by pvscan and the
+# requested VGs/LVs from the cmdline.
+#
+# Used in 64-lvm.rules as:
+# IMPORT{program}="pvscan-udev-initrd.sh $env{DEVNAME}"
+#
+# See /usr/lib/dracut/modules.d/90lvm/64-lvm.rules
+
+dev=$1
+
+type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
+
+
+VGS=$(getargs rd.lvm.vg -d rd_LVM_VG=)
+LVS=$(getargs rd.lvm.lv -d rd_LVM_LV=)
+
+IFS=' '
+
+# pvscan will produce a single VG line, and one or more LV lines.
+# VG <name> complete
+# VG <name> incomplete
+# LV <name> complete
+# LV <name> incomplete
+#
+# LV names are printed as vgname/lvname.
+# We only care about the complete items.
+# Each pvscan will produce a single VG line,
+# and may produce zero, one or more LV lines.
+
+PVSCAN=$(/sbin/lvm pvscan --cache --listlvs --checkcomplete --journal output --config 'global/event_activation=1' $dev)
+
+read -r -a VGSARRAY <<< "$VGS"
+
+for VG in "${VGSARRAY[@]}"
+do
+ if strstr "$PVSCAN" "VG $VG complete" ; then
+ echo LVM_VG_NAME_COMPLETE=\'"$VG"\'
+ fi
+done
+
+# Combine all matching LVs into a single print containing them all,
+# e.g. LVM_LV_NAMES_COMPLETE='vg/lv1 vg/lv2'
+
+read -r -a LVSARRAY <<< "$LVS"
+
+echo -n LVM_LV_NAMES_COMPLETE=\'
+for LV in "${LVSARRAY[@]}"
+do
+ if strstr "$PVSCAN" "LV $LV complete" ; then
+ echo -n "$LV "
+ fi
+done
+echo \'
+
--
1.8.3.1