Skip to content

Instantly share code, notes, and snippets.

@arrjay
Created August 22, 2024 05:16
Show Gist options
  • Select an option

  • Save arrjay/f957ab0a50939a88d9c435cbedae1e26 to your computer and use it in GitHub Desktop.

Select an option

Save arrjay/f957ab0a50939a88d9c435cbedae1e26 to your computer and use it in GitHub Desktop.
dracut module to rewrite a partition table pre-mount
#!/usr/bin/bash
# called by dracut
check() {
require_binaries awk gdisk lsblk mknod parted partx sgdisk sfdisk || return 1
return 0
}
# called by dracut
depends() {
return 0
}
# called by dracut
installkernel() {
return 0
}
# lsblk -A -e 1,2,11 -d -P -o name,pttype
# called by dracut
install() {
inst_multiple awk gdisk lsblk mknod parted partx sgdisk sfdisk
inst_hook cmdline za "$moddir/parse-cmdline-resizer.sh"
inst_hook initqueue/finished za "$moddir/resizer.sh"
dracut_need_initqueue
}
#!/usr/bin/sh
type getargbool > /dev/null 2>&1 || . /lib/dracut-lib.sh
if getargbool 0 rd.growpart ; then
[ -z "$root" ] && root=$(getarg root=)
targetpart=$(getargs rd.growpart=)
case "${targetpart}" in
# we should not be here, but good to write out.
0|no|off) targetpart="" ;;
# determine partition to resize from root partition spec if possible.
1|yes|on) targetpart="AUTO:${root}" ;;
# we were handed a specific partition.
*) : ;;
esac
# write that information for a later handoff.
[ -n "${targetpart}" ] && {
printf 'RESIZE_TARGET="%s"\n' "${targetpart}" > /etc/cmdline.d/12-resize-partition.conf
}
fi
# we never claim any root devices.
#!/usr/bin/sh
# if we have no config immediately get outta here.
[ -e /etc/cmdline.d/12-resize-partition.conf ] || return 0
. /etc/cmdline.d/12-resize-partition.conf
info "check disk/partition target ${RESIZE_TARGET} for resize"
# pull AUTO: off the resize target it was just informational today
_resize_target="${RESIZE_TARGET#AUTO:}"
case "${_resize_target}" in
# if we're handed a device..., we're done finding it
/*) : ;;
*=*)
# handle KEY=VALUE pairing, kinda
_key="$(printf '%s' "${_resize_target%%=*}"|awk '{ print tolower($0) }')"
_value="${_resize_target#*=}"
[ -e "/dev/disk/by-${_key}/${_value}" ] && _resize_target="/dev/disk/by-${_key}/${_value}"
;;
esac
# find the disk(s) that target sits on at this point
disks="$(lsblk -Ps -o NAME,TYPE "${_resize_target}" | awk '$0 ~ "TYPE=\"disk\"" { sub(/\" TYPE=.*/,"") ; sub(/NAME=\"/,"") ; print }')"
# check all disks wired to the partition for GPT header needing rework first.
mod=0
for disk in ${disks} ; do
info "checking disk ${disk} GPT table"
majhx="$(stat -c "%t" "/dev/${disk}")" ; minhx="$(stat -c "%T" "/dev/${disk}")"
mknod -m 0400 "/tmp/${disk}" b "0x${majhx}" "0x${minhx}"
# sfdisk tells us the disk is wrong, but sgdisk is better at the header wrangle.
sfdisk -l "/tmp/${disk}" 2>&1 1>/dev/null | grep -qF 'The backup GPT table is not on the end of the device.' && {
info "updating disk ${disk} GPT table"
sgdisk -e "/dev/${disk}"
mod=1
}
rm "/tmp/${disk}"
done
# wait on udev if we touched anything here, first.
[ "${mod}" -gt 0 ] && udevadm settle
# now do partitions
parts="$(lsblk -Ps -o NAME,TYPE "${_resize_target}" | awk '$0 ~ "TYPE=\"part\"" { sub(/\" TYPE=.*/,"") ; sub(/NAME=\"/,"") ; print }')"
mod=0
for part in ${parts} ; do
# this is painful. none of the tools give a good working model of how much bigger the partition can get and I hate it.
# we want to make sure that the rootfs can be extended at least 20M, to an aligned last _sector_.
# otherwise we will _not_ bother extending the partition.
# we're gonna pull the decision-making out of parted *output*
# look to makediskimage.sh, ~L300 ("for image in...")
disk="$(lsblk -Ps -o NAME,TYPE "/dev/${part}" | awk '$0 ~ "TYPE=\"disk\"" { sub(/\" TYPE=.*/,"") ; sub(/NAME=\"/,"") ; print }')"
# _additionally(!)_ parted is triggering udev on reads. so make a kernel device it stops writing on...
majhx="$(stat -c "%t" "/dev/${disk}")" ; minhx="$(stat -c "%T" "/dev/${disk}")"
mknod -m 0400 "/tmp/${disk}" b "0x${majhx}" "0x${minhx}"
majhx="$(stat -c "%t" "/dev/${part}")" ; minhx="$(stat -c "%T" "/dev/${part}")"
mknod -m 0400 "/tmp/${part}" b "0x${majhx}" "0x${minhx}"
sectorsz="$(parted "/tmp/${disk}" -s print | awk '$0 ~ "Sector size" { print $NF }')"
sectorsz="${sectorsz%B/*}"
sectorchunk=$(( 2097152 / sectorsz ))
minimum_sect=$(( 20971520 / sectorsz ))
partno="$(partx -s -g -o NR "/tmp/${part}")"
info "checking part ${part} (${disk}:${partno}) for resizing"
freestat="$(parted -s "/tmp/${disk}" unit s print free | awk '$1 == '"${partno}"' { pn=1 ; next } ; $1 ~ /^[0-9]+$/ { pn=0 } ; pn == 1 { $1=$1 ; print ; pn=0 }')"
rm "/tmp/${part}"
# now that we know what free space we have (if any), determine if we even want to resize the disk.
case "${freestat}" in
*"Free Space")
# break the line down
freestat="${freestat%s*Free Space}"
start="${freestat%% *}"
end="${freestat#"${start} "}" ; end="${end%% *}"
size="${freestat#"${start} ${end} "}" ; size="${size%% *}"
# file the units off
start="${start%s}"
end="${end%s}"
size="${size%s}"
# now we can check to see if the partition _should_ be resized
[ "${size}" -gt "${minimum_sect}" ] && {
# check for a hybrid mbr _here_ - parted resizing will stomp on it.
parts="$(sfdisk -l -Y dos "/tmp/${disk}" | awk 'BEGIN { c=0 } ; $1 == "Device" { p=1;next };p == 1 { if ($0 == "") { p=0;next } ; c++;print $2,$3,$6 } END { print c }')"
partct="$(echo "${parts}"|awk 'END { print }')"
# more than one partition means someone tinkered with the mbr header
[ "${partct}" -gt 1 ] && {
hybridparts=""
hybridcodes=""
hmbr="$(mktemp)"
gptparts="$(mktemp)"
echo "${parts}" > "${hmbr}"
# grab the gpt partition layout here
parted -s "/tmp/${disk}" 'unit s print' | awk '$1 ~ /[0-9]+/ {sub(/s/,"",$2);sub(/s/,"",$3);print $1,$2,$3}' > "${gptparts}"
# we're going to assemble arguments for gdisk as a big string, by going through the hybrid partition table, and finding matching sector alignments in the gpt table.
while read -r mbrline ; do
gptpart=0
mbrcode="${mbrline##* }"
[ "${mbrcode}" = "${mbrline}" ] && continue # skip over the count at the end
[ "${mbrcode}" = "ee" ] && continue # skip over protective partitions
mbrline="${mbrline% "${mbrcode}"}"
while read -r gptline ; do
[ "${gptpart}" -ne 0 ] && continue
case "${gptline}" in
*" ${mbrline}") gptpart="${gptline%% *}" ;;
esac
done < "${gptparts}"
[ "${gptpart}" -ne 0 ] && {
hybridparts="${hybridparts} ${gptpart}"
hybridcodes="${hybridcodes} ${mbrcode}"
}
done < "${hmbr}"
rm "${hmbr}"
rm "${gptparts}"
}
# align the partition end on a chunk boundary
align=$((end % sectorchunk))
[ "${align}" -ne 0 ] && end=$((end - align))
info "resizing partition ${part} (${disk}:${partno}) to maximum available space (end ${end}s)"
parted "/dev/${disk}" -s resizepart "${partno}" "${end}s"
[ "${hybridparts}" ] && {
# we need to reapply the hybrid mbr now.
hybridparts="${hybridparts# }"
# script to gdisk
{
# preamble...
# invoke recovery menu, create hybrid mbr using partition numbers we have, do not put guard partitions first.
printf '%s\n' \
'r' \
'h' \
"${hybridparts}" \
'n'
# we need to answer for each partition what code we want, and disable the bootable flag.
for code in ${hybridcodes} ; do
printf '%s\n' \
"${code}" \
'n'
done
# the below won't work if we had 4 partitions hybridized, but I don't...
# create guard partitions in unused slots, type 'ee' - write partition table and confirm.
printf '%s\n' \
'y' \
'ee' \
'w' \
'y'
} | gdisk "/dev/${disk}"
}
mod=1
}
;;
esac
# don't forget the disk device we were reading from (for hybrid check)
rm "/tmp/${disk}"
done
# wait on udev if we touched something
[ "${mod}" -gt 0 ] && udevadm settle
return 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment