57149d9eb1
When building an image, say RHEL9, on a host installed with that same image, you will be blocked from mounting the filesystems to extract contents, as the host OS kernel will identify the duplicate UUIDs and error accordingly. This was previously fixed for the root filesystem, but not the boot filesystem. Change-Id: I63a34fba033ed1c459aeb9c201c8821fa38a36e9
211 lines
8.2 KiB
Bash
Executable file
211 lines
8.2 KiB
Bash
Executable file
#!/bin/bash
|
|
|
|
# Intended to be called from the root.d cloud-image script as follows:
|
|
# $TMP_HOOKS_PATH/bin/extract-image $BASE_IMAGE_FILE $BASE_IMAGE_TAR $IMAGE_LOCATION $CACHED_IMAGE
|
|
|
|
if [ ${DIB_DEBUG_TRACE:-0} -gt 0 ]; then
|
|
set -x
|
|
fi
|
|
set -eu
|
|
set -o pipefail
|
|
|
|
BASE_IMAGE_FILE=$1
|
|
BASE_IMAGE_TAR=$2
|
|
IMAGE_LOCATION=$3
|
|
CACHED_IMAGE=$4
|
|
|
|
CACHED_TAR=$DIB_IMAGE_CACHE/$BASE_IMAGE_TAR
|
|
DIB_LOCAL_IMAGE=${DIB_LOCAL_IMAGE:-""}
|
|
TAR_LOCK=$CACHED_TAR.lock
|
|
|
|
# GPT GUIDs of interest.
|
|
# See https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs
|
|
# also https://systemd.io/BOOT_LOADER_SPECIFICATION/
|
|
GUID_EFI="c12a7328-f81f-11d2-ba4b-00a0c93ec93b"
|
|
GUID_LINUX_BOOT="bc13c2ff-59e6-4262-a352-b275fd6f7172"
|
|
|
|
function extract_image() {
|
|
if [ -n "$DIB_OFFLINE" -a -f "$CACHED_TAR" ] ; then
|
|
echo "Not checking freshness of cached $CACHED_TAR."
|
|
else
|
|
if [ -z "$DIB_LOCAL_IMAGE" ]; then
|
|
echo "Fetching Base Image"
|
|
|
|
# There seems to be some bad Fedora mirrors returning http 404's for the cloud image.
|
|
# If the image fails to download due to a 404 we retry once.
|
|
set +e
|
|
$TMP_HOOKS_PATH/bin/cache-url $IMAGE_LOCATION $CACHED_IMAGE
|
|
RV=$?
|
|
set -e
|
|
|
|
if [ "$RV" == "44" ] ; then
|
|
$TMP_HOOKS_PATH/bin/cache-url $IMAGE_LOCATION $CACHED_IMAGE
|
|
elif [ "$RV" != "0" ] ; then
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [ ! -f $CACHED_TAR -o \
|
|
$CACHED_IMAGE -nt $CACHED_TAR ] ; then
|
|
echo "Repacking base image as tarball."
|
|
|
|
WORKING=$(mktemp --tmpdir=${TMP_DIR:-/tmp} -d)
|
|
EACTION="rm -r $WORKING"
|
|
trap "$EACTION" EXIT
|
|
echo "Working in $WORKING"
|
|
|
|
RAW_FILE=$(mktemp --tmpdir=$WORKING XXXXXX.raw)
|
|
|
|
if [ "${CACHED_IMAGE: -3}" == ".xz" ] ; then
|
|
QCOW2_FILE=$(mktemp --tmpdir=$WORKING XXXXXX.qcow2)
|
|
# This leaves the old image in place so cache-url wont get it again
|
|
unxz --stdout $CACHED_IMAGE > $QCOW2_FILE
|
|
CACHED_IMAGE=$QCOW2_FILE
|
|
fi
|
|
|
|
qemu-img convert -f qcow2 -O raw $CACHED_IMAGE $RAW_FILE
|
|
|
|
# kpartx fails if no /dev/loop* exists, "losetup -f" prints first unused
|
|
# loop device and creates it if it doesn't exist
|
|
LOOPDEV_BASE=$(basename $(sudo losetup -f))
|
|
|
|
# add partition mappings
|
|
sudo kpartx -av $RAW_FILE
|
|
|
|
# If running inside Docker, make our nodes manually, because udev will not be working.
|
|
if [ -f /.dockerenv ]; then
|
|
sudo dmsetup --noudevsync mknodes
|
|
fi
|
|
if ! timeout 5 sh -c "while ! ls /dev/mapper/${LOOPDEV_BASE}p* ; do sleep 1; done"; then
|
|
echo "Error: Could not find any ${LOOPDEV_BASE} devices"
|
|
exit 1
|
|
fi
|
|
|
|
EACTION="sudo kpartx -d $RAW_FILE ; $EACTION"
|
|
trap "$EACTION" EXIT
|
|
|
|
ROOT_LOOPDEV=""
|
|
BOOT_LOOPDEV=""
|
|
EFI_LOOPDEV=""
|
|
|
|
LOOPDEVS=$(ls /dev/mapper/${LOOPDEV_BASE}p* | sort -r)
|
|
LOOPDEV_COUNT=$(echo $LOOPDEVS | wc -w)
|
|
if [ $LOOPDEV_COUNT == "1" ]; then
|
|
# if there is one partition device, assume it is the root device
|
|
ROOT_LOOPDEV=${LOOPDEVS}
|
|
LOOPDEVS=""
|
|
fi
|
|
|
|
for LOOPDEV in ${LOOPDEVS}; do
|
|
fstype=$(lsblk --all --nodeps --noheadings --output FSTYPE $LOOPDEV)
|
|
label=$(lsblk --all --nodeps --noheadings --output LABEL $LOOPDEV)
|
|
part_type=$(lsblk --all --nodeps --noheadings --output PARTTYPE $LOOPDEV)
|
|
|
|
if [ -z "${fstype}" ]; then
|
|
# Ignore block device with no filesystem type
|
|
continue
|
|
fi
|
|
|
|
# look for EFI partition to mount at /boot/efi either by GUID or
|
|
# label convention
|
|
if [ -z "$EFI_LOOPDEV" ]; then
|
|
if [[ ${part_type} == ${GUID_EFI} ]]; then
|
|
EFI_LOOPDEV=$LOOPDEV
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
# look for EFI partition to mount at /boot/efi either by GUID or
|
|
# label convention.
|
|
if [ -z "$BOOT_LOOPDEV" ]; then
|
|
if [[ ${part_type} == ${GUID_LINUX_BOOT} || ${label} == "boot" ]]; then
|
|
BOOT_LOOPDEV=$LOOPDEV
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$ROOT_LOOPDEV" ]; then
|
|
ROOT_LOOPDEV=$LOOPDEV
|
|
continue
|
|
fi
|
|
done
|
|
|
|
mkdir $WORKING/mnt
|
|
ROOT_FSTYPE=$(sudo blkid -o value -s TYPE $ROOT_LOOPDEV)
|
|
if [ "xfs" = "$ROOT_FSTYPE" ]; then
|
|
# mount xfs with nouuid, just in case that uuid is already mounted
|
|
# use ro to avoid/workaround xfs uuid issues on older
|
|
# kernels with newer rhel images which seem to set
|
|
# flags to generate unique uuid's:
|
|
# xfs superblock has incompatible features (0x4)
|
|
# we don't need to worry about this, we just want the data
|
|
MOUNTOPTS="-o nouuid,ro"
|
|
elif [ "btrfs" = "$ROOT_FSTYPE" ]; then
|
|
# Fedora has a btrfs filesystem with a subvolume called root.
|
|
# For now assume there will be a 'root' subvolume, but in the
|
|
# future the subvolume layout may need to be discovered for different
|
|
# images
|
|
MOUNTOPTS="-o subvol=root"
|
|
else
|
|
MOUNTOPTS=""
|
|
fi
|
|
|
|
sudo mount $MOUNTOPTS $ROOT_LOOPDEV $WORKING/mnt
|
|
EACTION="sudo umount -f $WORKING/mnt ; $EACTION"
|
|
trap "$EACTION" EXIT
|
|
|
|
if [ ! -z "$BOOT_LOOPDEV" ]; then
|
|
# mount to /boot
|
|
BOOT_FSTYPE=$(sudo blkid -o value -s TYPE $ROOT_LOOPDEV)
|
|
if [ "xfs" = "$BOOT_FSTYPE" ]; then
|
|
BOOT_MOUNTOPTS="-o nouuid,ro"
|
|
# Similar to root filesystem, if the boot filesystem
|
|
# is XFS and the base OS is the same as the image being
|
|
# rebuilt, we need to pass "nouuid" to bypass UUID safety
|
|
# checks and successfully mounts so we can extract the
|
|
# contents.
|
|
else
|
|
BOOT_MOUNTOPTS=""
|
|
fi
|
|
sudo mount $BOOT_MOUNTOPTS $BOOT_LOOPDEV $WORKING/mnt/boot
|
|
EACTION="sudo umount -f $BOOT_LOOPDEV ; $EACTION"
|
|
trap "$EACTION" EXIT
|
|
fi
|
|
if [ ! -z "$EFI_LOOPDEV" ]; then
|
|
# mount to /boot/efi
|
|
sudo mount $EFI_LOOPDEV $WORKING/mnt/boot/efi
|
|
EACTION="sudo umount -f $EFI_LOOPDEV ; $EACTION"
|
|
trap "$EACTION" EXIT
|
|
fi
|
|
|
|
# find out if chroot tar has full xattr support
|
|
if [ 0 == `sudo chroot $WORKING/mnt bin/tar --help | grep -c xattrs-exclude` ]; then
|
|
TAROPTS="--no-xattrs"
|
|
else
|
|
TAROPTS="--xattrs --xattrs-include=* --xattrs-exclude=security.selinux"
|
|
fi
|
|
# Chroot in so that we get the correct uid/gid
|
|
sudo chroot $WORKING/mnt bin/tar $TAROPTS -cz . > $WORKING/tmp.tar
|
|
mv $WORKING/tmp.tar $CACHED_TAR
|
|
else
|
|
echo "Using cached tar from $CACHED_TAR"
|
|
fi
|
|
fi
|
|
|
|
# Extract the base image (use --numeric-owner to avoid UID/GID mismatch between
|
|
# image tarball and host OS e.g. when building Fedora image on an openSUSE host)
|
|
# Include all xattrs except selinux because the selinux ones cause issues in our
|
|
# chroot environment, and we restore all of those at the end of the build anyway.
|
|
echo "Extracting base root image from $CACHED_TAR"
|
|
sudo tar -C $TARGET_ROOT --numeric-owner --xattrs --xattrs-include='*' --xattrs-exclude='security.selinux' -xzf $CACHED_TAR
|
|
}
|
|
|
|
(
|
|
echo "Getting $TAR_LOCK: $(date)"
|
|
# Wait up to 20 minutes for another process to download
|
|
if ! flock -w 1200 9 ; then
|
|
echo "Did not get $TAR_LOCK: $(date)"
|
|
exit 1
|
|
fi
|
|
extract_image
|
|
) 9> $TAR_LOCK
|