Fixes detection of multiple linux installations on different subvolumes of the same partition. This patch is a combination of https://src.fedoraproject.org/rpms/os-prober/blob/a27e5121193e2222ada672db3521a7d9de70991b/f/os-prober-btrfsfix.patch and https://build.opensuse.org/package/view_file/openSUSE:Factory/os-prober/Improve-btrfs-handling-on-os-probing-for-grub2.patch?rev=56 . Bug: https://bugs.gentoo.org/790434 https://bugs.gentoo.org/817905 https://bugs.debian.org/688336 https://bugzilla.redhat.com/888341 --- a/common.sh +++ b/common.sh @@ -155,6 +155,7 @@ parse_proc_mounts () { done } +# add forth parameter to pickup btrfs subvol info parsefstab () { while read -r line; do case "$line" in @@ -165,12 +166,22 @@ parsefstab () { set -f set -- $line set +f - printf '%s %s %s\n' "$1" "$2" "$3" + printf '%s %s %s %s\n' "$1" "$2" "$3" "$4" ;; esac done } +#check_btrfs_mounted $bootsv $bootuuid) +check_btrfs_mounted () { + bootsv="$1" + bootuuid="$2" + bootdev=$(blkid | grep "$bootuuid" | cut -d ':' -f 1) + bindfrom=$(grep " btrfs " /proc/self/mountinfo | + grep " $bootdev " | grep " /$bootsv " | cut -d ' ' -f 5) + printf "%s" "$bindfrom" +} + unescape_mount () { printf %s "$1" | \ sed 's/\\011/ /g; s/\\012/\n/g; s/\\040/ /g; s/\\134/\\/g' --- a/linux-boot-prober +++ b/linux-boot-prober @@ -5,16 +5,143 @@ set -e newns "$@" require_tmpdir +ERR="n" + +tmpmnt=/var/lib/os-prober/mount +if [ ! -d "$tmpmnt" ]; then + mkdir "$tmpmnt" +fi + +mounted= +bootmnt= +bootsv= +bootuuid= grep "^/dev/" /proc/mounts | parse_proc_mounts >"$OS_PROBER_TMP/mounted-map" || true -partition="$1" +if [ -z "$1" ]; then + ERR=y +elif [ "$1" = btrfs -a -z "$2" ]; then + ERR=y +elif [ "$1" = btrfs -a -z "$3" ]; then + ERR=y +elif [ "$1" = btrfs ]; then + type=btrfs + echo "$2" | grep -q "^UUID=" || ERR=y + echo "$3" | grep -q "^subvol=" || ERR=y + export "$2" + export "$3" + partition=$(blkid | grep "$UUID" | cut -d ':' -f 1 | tr '\n' ' ' | cut -d ' ' -f 1) + debug "btrfs: partition=$partition, UUID=$UUID, subvol=$subvol" +else + partition="$1" + type=other +fi -if [ -z "$partition" ]; then +if [ "x$ERR" != xn ]; then echo "usage: linux-boot-prober partition" >&2 + echo " linux-boot-prober btrfs UUID=<> subvol=<>" >&2 exit 1 fi +if [ "$type" = btrfs ]; then + # handle all of the btrfs stuff here + if [ ! -e "/proc/self/mountinfo" ]; then + warn "/proc/self/mountinfo does not exist, exiting" + umount "$tmpmnt" 2>/dev/null + rmdir "$tmpmnt" 2>/dev/null + exit 1 + fi + mpoint=$(grep "btrfs" /proc/self/mountinfo | grep " /$subvol " | grep " $partition " | cut -d ' ' -f 5) + if [ "$mpoint" = "/" ]; then + warn "specifying active root not valid, exiting" + umount "$tmpmnt" 2>/dev/null + rmdir "$tmpmnt" 2>/dev/null + exit 1 + fi + if [ "$mpoint" = "$tmpmnt" ]; then + warn "btrfs subvol=$subvool, UUID=$UUID, already mounted on $tmpmnt **ERROR**" + umount "$tmpmnt" 2>/dev/null + rmdir "$tmpmnt" 2>/dev/null + exit 1 + fi + if [ -z "$mpoint" ]; then + # mount the btrfs root + if ! mount -o subvol=$subvol -t btrfs -U $UUID "$tmpmnt" 2>/dev/null; then + warn "error mounting btrfs subvol=$subvol UUID=$UUID" + umount "$tmpmnt/boot" 2>/dev/null + umount "$tmpmnt" 2>/dev/null + rmdir "$tmpmnt" 2>/dev/null + exit 1 + fi + else + # bind-mount + if ! mount -o bind "$mpoint" "$tmpmnt" 2>/dev/null; then + warn "error mounting btrfs bindfrom=$mpoint subvol=$subvol UUID=$UUID" + umount "$tmpmnt/boot" 2>/dev/null + umount "$tmpmnt" 2>/dev/null + rmdir "$tmpmnt" 2>/dev/null + exit 1 + fi + fi + debug "mounted btrfs $partition, subvol=$subvol on $tmpmnt" + if [ ! -e "$tmpmnt/etc/fstab" ]; then + warn "btrfs subvol=$subvol not root" + umount "$tmpmnt" 2>/dev/null + rmdir "$tmpmnt" 2>/dev/null + exit 1 + fi + bootmnt=$(parsefstab < "$tmpmnt/etc/fstab" | grep " /boot ") || true + if [ -z "$bootmnt" ]; then + # /boot is part of the root + bootpart="$partition" + bootsv="$subvol" + elif echo "$bootmnt" | cut -d ' ' -f 3 | grep -q "btrfs"; then + # separate btrfs /boot subvolume + bootsv=$(echo "$bootmnt" | cut -d ' ' -f 4 | grep "^subvol=" | sed "s/subvol=//" ) + bootuuid=$(echo "$bootmnt" | cut -d ' ' -f 1 | grep "^UUID=" | sed "s/UUID=//" ) + debug "mounting btrfs $tmpmnt/boot UUID=$bootuuid subvol=$bootsv" + bindfrom=$(check_btrfs_mounted $bootsv $bootuuid) + if [ -n "$bindfrom" ]; then + # already mounted some place + if ! mount -o bind $bindfrom "$tmpmnt/boot" 2>/dev/null; then + warn "error bind mounting btrfs boot subvol=$bootsv, from=$bindfrom" + umount "$tmpmnt/boot" 2>/dev/null + umount "$tmpmnt" 2>/dev/null + rmdir "$tmpmnt" 2>/dev/null + exit 1 + fi + elif ! mount -o subvol=$bootsv -t btrfs -U $bootuuid "$tmpmnt/boot" 2>/dev/null; then + warn "error mounting btrfs boot partition subvol=$bootsv, UUID=$bootuuid" + umount "$tmpmnt/boot" 2>/dev/null + umount "$tmpmnt" 2>/dev/null + rmdir "$tmpmnt" 2>/dev/null + exit 1 + fi + bootpart=$(grep " btrfs " /proc/self/mountinfo | grep " /$bootsv " | cut -d ' ' -f 10) + else + # non-btrfs partition or logical volume + linux_mount_boot $partition $tmpmnt + bootpart="${mountboot%% *}" + bootsv= + fi + + test="/usr/lib/linux-boot-probes/mounted/40grub2" + if [ -f $test ] && [ -x $test ]; then + debug "running $test $partition $bootpart $tmpmnt $type $subvol $bootsv" + if $test "$partition" "$bootpart" "$tmpmnt" "$type" "$subvol" "$bootsv"; then + debug "$test succeeded" + fi + fi + umount "$tmpmnt/boot" 2>/dev/null || true + if ! umount "$tmpmnt" 2>/dev/null; then + warn "problem umount $tmpmnt" + fi + rmdir "$tmpmnt" 2>/dev/null || true + + exit 0 +fi + if ! mapped="$(mapdevfs "$partition")"; then log "Device '$partition' does not exist; skipping" continue @@ -22,8 +149,8 @@ fi if ! grep -q "^$mapped " "$OS_PROBER_TMP/mounted-map"; then for test in /usr/lib/linux-boot-probes/*; do - debug "running $test" if [ -x $test ] && [ -f $test ]; then + debug "running $test" if $test "$partition"; then debug "linux detected by $test" break --- a/linux-boot-probes/mounted/common/40grub2 +++ b/linux-boot-probes/mounted/common/40grub2 @@ -2,17 +2,42 @@ . /usr/share/os-prober/common.sh set -e +# add support for btrfs with no separate /boot +# that is, rootsv = bootsv partition="$1" bootpart="$2" mpoint="$3" type="$4" +rootsv="$5" +bootsv="$6" found_item=0 entry_result () { + if [ "x$type" = "xbtrfs" ]; then + bsv=${bootsv:+/}${bootsv} + # if path is not relative to subvolume make it relative + kernel=${kernel#${bsv}} + kernelfile=$kernel + initrd=${initrd#${bsv}} + if [ "x$GRUB_FS" != "xbtrfs" ]; then + # absolute path needed: prepend subvolume if $kernel isn't empty + kernel=${kernel:+${bsv}}${kernel} + # handle multiple initrd paths + local initrd_path= + for path in ${initrd}; do + initrd_path+="${bsv}${path} " + done + initrd="${initrd_path% }" + fi + # assumed: rootsv != bootsv if rootsv isn't ancestor of bootsv + [ "$partition" != "$bootpart" -o "$rootsv" != "$bootsv" ] && kernelfile="/boot${kernelfile}" + else + kernelfile=$kernel + fi if [ "$ignore_item" = 0 ] && \ [ -n "$kernel" ] && \ - [ -e "$mpoint/$kernel" ]; then + [ -e "$mpoint/$kernelfile" ]; then result "$rootpart:$bootpart:$title:$kernel:$initrd:$parameters" found_item=1 fi @@ -64,7 +88,7 @@ parse_grub_menu () { ignore_item=1 fi ;; - linux) + linux|linuxefi|linux16) # Hack alert: sed off any (hdn,n) but # assume the kernel is on the same # partition. @@ -73,18 +98,18 @@ parse_grub_menu () { parameters="$@" # Systems with a separate /boot will not have # the path to the kernel in grub.cfg. - if [ "$partition" != "$bootpart" ]; then + if [ "$partition" != "$bootpart" -a "$type" != "btrfs" ]; then kernel="/boot$kernel" fi ;; - initrd) + initrd|initrdefi|initrd16) shift initrd="" for initrd_path in "$@"; do # sed hack, as above initrd_path="$(echo "$initrd_path" | sed 's/(.*)//')" # Initrd same. - if [ "$partition" != "$bootpart" ]; then + if [ "$partition" != "$bootpart" -a "$type" != "btrfs" ]; then initrd_path="/boot$initrd_path" fi if [ -z "$initrd" ]; then --- a/os-prober +++ b/os-prober @@ -76,9 +76,12 @@ partitions () { # Also detect OSes on LVM volumes (assumes LVM is active) if type lvs >/dev/null 2>&1; then - echo "$(LVM_SUPPRESS_FD_WARNINGS=1 log_output lvs --noheadings --separator : -o vg_name,lv_name | + echo "$(LVM_SUPPRESS_FD_WARNINGS=1 log_output lvs --noheadings --separator : -o vg_name,lv_name 2>/dev/null | sed "s|-|--|g;s|^[[:space:]]*\(.*\):\(.*\)$|/dev/mapper/\1-\2|")" fi + + # now lets make sure we got all of the btrfs partitions and disks + blkid | grep 'TYPE="btrfs"' | cut -d ':' -f 1 } parse_proc_swaps () { @@ -136,6 +139,8 @@ if [ -f /proc/mdstat ] ; then grep "^md" /proc/mdstat | cut -d: -f2- | parse_proc_mdstat >"$OS_PROBER_TMP/raided-map" || true fi +: >"$OS_PROBER_TMP/btrfs-vols" + for partition in $(partitions); do if ! mapped="$(mapdevfs "$partition")"; then log "Device '$partition' does not exist; skipping" @@ -154,7 +159,26 @@ for partition in $(partitions); do continue fi - if ! grep -q "^$mapped " "$OS_PROBER_TMP/mounted-map" ; then + # do btrfs processing here; both mounted and unmounted will + # be handled by 50mounted-tests so we can do a subvol only once. + type=$(blkid -o value -s TYPE $mapped || true) + if [ "$type" = btrfs ]; then + uuid=$(blkid -o value -s UUID $mapped) + if grep -q "^$uuid" "$OS_PROBER_TMP/btrfs-vols" ; then + continue + fi + debug "btrfs volume uuid=$uuid partition=$partition" + echo "$uuid" >>"$OS_PROBER_TMP/btrfs-vols" + test="/usr/lib/os-probes/50mounted-tests" + if [ -f "$test" ] && [ -x "$test" ]; then + debug "running $test on btrfs $partition" + if "$test" btrfs "$uuid" "$partition"; then + debug "os detected by $test" + continue + fi + fi + + elif ! grep -q "^$mapped " "$OS_PROBER_TMP/mounted-map" ; then for test in /usr/lib/os-probes/*; do if [ -f "$test" ] && [ -x "$test" ]; then debug "running $test on $partition" --- a/os-probes/common/50mounted-tests +++ b/os-probes/common/50mounted-tests @@ -14,19 +14,31 @@ do_unmount() { rmdir "$tmpmnt" || true } -types="$(fs_type "$partition")" +if [ "x$1" = xbtrfs ]; then + types=btrfs + if [ -z "$2" -o -z "$3" ]; then + debug "missing btrfs parameters, exiting" + exit 1 + fi + UUID="$2" + BTRFSDEV="$3" +else + partition="$1" + types="$(fs_type "$partition")" || types=NOT-DETECTED +fi + if [ "$types" = NOT-DETECTED ]; then debug "$1 type not recognised; skipping" - exit 0 + exit 1 elif [ "$types" = swap ]; then debug "$1 is a swap partition; skipping" - exit 0 + exit 1 elif [ "$types" = crypto_LUKS ]; then debug "$1 is a LUKS partition; skipping" - exit 0 + exit 1 elif [ "$types" = LVM2_member ]; then debug "$1 is an LVM member; skipping" - exit 0 + exit 1 elif [ "$types" = ntfs ]; then if type ntfs-3g >/dev/null 2>&1; then types='ntfs-3g ntfs' @@ -35,7 +47,7 @@ elif [ -z "$types" ]; then if type cryptsetup >/dev/null 2>&1 && \ cryptsetup luksDump "$partition" >/dev/null 2>&1; then debug "$1 is a LUKS partition; skipping" - exit 0 + exit 1 fi for type in $(grep -v nodev /proc/filesystems); do # hfsplus filesystems are mountable as hfs. Try hfs last so @@ -58,6 +70,108 @@ if [ ! -d "$tmpmnt" ]; then fi mounted= + +# all btrfs processing here. Handle both unmounted and +# mounted subvolumes. +if [ "$types" = btrfs ]; then + partition="$BTRFSDEV" + debug "begin btrfs processing for $UUID" + # note that the btrfs volume must not be mounted ro + if mount -t btrfs -U "$UUID" "$tmpmnt" 2>/dev/null; then + debug "btrfs volume $UUID mounted" + else + warn "cannot mount btrfs volume $UUID, exiting" + rmdir "$tmpmnt" || true + exit 1 + fi + # besides regular subvols, get ro and snapshot so thet can be excluded + subvols=$(btrfs subvolume list "$tmpmnt" | cut -d ' ' -f 9) + rosubvols=$(btrfs subvolume list -r "$tmpmnt" | cut -d ' ' -f 9) + sssubvols=$(btrfs subvolume list -s "$tmpmnt" | cut -d ' ' -f 14) + if ! umount "$tmpmnt"; then + warn "failed to umount btrfs volume on $tmpmnt" + rmdir "$tmpmnt" || true + exit 1 + fi + + found= + mounted= + + mpoint="$(grep btrfs /proc/self/mountinfo | grep "$partition " | cut -d ' ' -f 5)" + if [ -n "$mpoint" -a "x$mpoint" = "x/" ]; then + debug "This is the root for the running system" #running system must be done elsewhere + else + #partition was not root of running system, so lets look for bootable subvols + if [ -n "$mpoint" ] ; then + mounted=1 #partition was already mounted,so lets not unmount it when done + else + # again, do not mount btrfs ro + mount -t btrfs -U "$UUID" "$tmpmnt" + mpoint="$tmpmnt" + fi + + test="/usr/lib/os-probes/mounted/90linux-distro" + if [ -f "$test" ] && [ -x "$test" ]; then + debug "running subtest $test" + if "$test" "$partition" "$mpoint" btrfs "UUID=$UUID"; then + debug "os found by subtest $test on $partition" + found=1 + fi + fi + if [ -z "$mounted" ]; then + if ! umount "$tmpmnt"; then + warn "failed to umount $tmpmnt" + fi + fi + fi + + if [ -z "$subvols" ]; then + debug "no subvols found on btrfs volume $UUID" + else + found= + for subvol in $subvols; do + debug "begin btrfs processing for $UUID subvol=$subvol" + if echo "$rosubvols" | grep -q -x "$subvol"; then + continue + fi + if echo "$sssubvols" | grep -q -x "$subvol"; then + continue + fi + mounted= + mpoint="$(grep btrfs /proc/self/mountinfo | grep "$partition " | grep "/$subvol " | cut -d ' ' -f 5)" + if [ -n "$mpoint" ]; then + if [ "x$mpoint" = "x/" ]; then + continue # this is the root for the running system + fi + mounted=1 + else + # again, do not mount btrfs ro + mount -t btrfs -o subvol="$subvol" -U "$UUID" "$tmpmnt" + mpoint="$tmpmnt" + fi + test="/usr/lib/os-probes/mounted/90linux-distro" + if [ -f "$test" ] && [ -x "$test" ]; then + debug "running subtest $test" + if "$test" "$partition" "$mpoint" btrfs "UUID=$UUID" "subvol=$subvol"; then + debug "os found by subtest $test on subvol $subvol" + found=1 + fi + fi + if [ -z "$mounted" ]; then + if ! umount "$tmpmnt"; then + warn "failed to umount $tmpmnt" + fi + fi + done + fi + rmdir "$tmpmnt" || true + if [ "$found" ]; then + exit 0 + else + exit 1 + fi +fi + if type grub-mount >/dev/null 2>&1 && \ type grub-probe >/dev/null 2>&1 && \ grub-mount "$partition" "$tmpmnt" 2>/dev/null; then --- a/os-probes/mounted/common/90linux-distro +++ b/os-probes/mounted/common/90linux-distro @@ -7,6 +7,8 @@ set -e partition="$1" dir="$2" type="$3" +uuid="$4" +subvol="$5" # This test is inaccurate, but given separate / and /boot partitions and the # fact that only some architectures have ld-linux.so, I can't see anything @@ -143,7 +145,11 @@ if (ls "$dir"/lib*/ld*.so* && [ -d "$dir/boot" ] || ls "$dir"/usr/lib*/ld*.so*) fi label="$(count_next_label "$short")" - result "$partition:$long:$label:linux" + if [ "x$type" = "xbtrfs" -a "x$uuid" != "x" -a "x$subvol" != "x" ]; then + result "$partition:$long:$label:linux:$type:$uuid:$subvol" + else + result "$partition:$long:$label:linux" + fi exit 0 else exit 1