Current File : //usr/local/jetapps/usr/share/rear/output/RAWDISK/Linux-i386/280_create_bootable_disk_image.sh
# Create the raw disk image
#
# The disk image will be created as a bootable disk image supporting booting via EFI and/or Legacy BIOS if the
# respective bootloaders are available. This script expects an EFI bootloader to be prepared in advance in a
# staging directory.
#
# If an EFI bootloader has been prepared, $RAWDISK_BOOT_EFI_STAGING_ROOT must point to the EFI staging directory.
#


### Check prerequisites

local use_syslinux_legacy=no
if has_binary syslinux && [[ -z "$SECURE_BOOT_BOOTLOADER" ]]; then
    if is_true "${RAWDISK_BOOT_EXCLUDE_SYSLINUX_LEGACY:-no}"; then
        LogPrint "DISABLED: Using syslinux to create a BIOS Legacy bootloader"
    else
        use_syslinux_legacy=yes
    fi
fi

if [[ -z "$RAWDISK_BOOT_EFI_STAGING_ROOT" ]] && ! is_true $use_syslinux_legacy; then
    Error "Creating a raw disk image requires an EFI bootloader or syslinux"
fi


### Create an initial disk image

# Wait for file systems to settle before trying to determine partition content size
sync
sleep 3  # should not be strictly necessary as Linux sync(2) waits until data is written

# Determine the appropriate size (adding 1 MiB for plus 7% for file system overhead/reserve)
local staged_boot_partition_contents=( "$KERNEL_FILE" "$TMP_DIR/$REAR_INITRD_FILENAME" )
[[ -n "$RAWDISK_BOOT_EFI_STAGING_ROOT" ]] && staged_boot_partition_contents+=( "$RAWDISK_BOOT_EFI_STAGING_ROOT" )
local disk_image_size_MiB="$(du -m --summarize --total "${staged_boot_partition_contents[@]}" | awk '$2 == "total" { printf("%.0f\n", 1 + ($1 * 1.07) + .5); }')"

local full_disk_name="${RAWDISK_IMAGE_NAME:-rear-$HOSTNAME}.raw"
local disk_image="$TMP_DIR/$full_disk_name"

LogPrint "Creating $disk_image_size_MiB MiB raw disk image \"$full_disk_name\""

dd if=/dev/zero of="$disk_image" bs=1M count="$disk_image_size_MiB"
[ -f "$disk_image" ] || Error "Could not create initial disk image file $disk_image"


### Create a GPT partition table

# Determine the configuration for the boot partition
local typecode="8300"  # Linux partition for non-EFI booting
[[ -n "$RAWDISK_BOOT_EFI_STAGING_ROOT" ]] && typecode="ef00"  # EFI System partition if an EFI bootloader has been prepared

local legacy_boot_option=""
is_true $use_syslinux_legacy && legacy_boot_option="--attributes=1:set:2"  # mark partition as Legacy BIOS-bootable

local guid_option=""
[[ -n "$RAWDISK_PTUUID" ]] && guid_option="--disk-guid=$RAWDISK_PTUUID"  # Use a pre-determined partition UUID

sgdisk $guid_option --new 1::0 --typecode=1:"$typecode" --change-name=1:"${RAWDISK_GPT_PARTITION_NAME:-Rescue System}" $legacy_boot_option "$disk_image"
StopIfError "Could not create GPT partition table on $disk_image"

Log "Raw disk image partition table:"
gdisk -l "$disk_image" >>"$RUNTIME_LOGFILE"


### Create block devices representing the raw disk image

local disk_device  # separate 'local' statement to avoid losing $(...) exit status - cf. https://stackoverflow.com/a/10397996
# Set up the loop device, trying the '--partscan' option first (introduced in util-linux v2.21)
# Trying '--partscan' here first avoids a situation where all of the methods below fail to make the kernel recognize
# partitions on the loop device (cf. https://github.com/rear/rear/pull/2071).
disk_device="$(losetup --show --find "$disk_image" --partscan || losetup --show --find "$disk_image")"
StopIfError "Could not create loop device on $disk_image"
AddExitTask "losetup -d $disk_device >&2"

local boot_partition="${disk_device}p1"

# Try several methods to make the kernel recognize partitions on the loop device, if not already done automatically:

# 1. partprobe
if [[ ! -b "$boot_partition" ]] && has_binary partprobe; then
    partprobe "$disk_device" || LogPrintError "partprobe failed to make the kernel recognize loop device partitions"
fi

# 2. Heuristic: wait
[[ ! -b "$boot_partition" ]] && sleep 5

# 3. kpartx
local use_kpartx=no
if [[ ! -b "$boot_partition" ]]; then
    if has_binary kpartx; then
        kpartx -as "$disk_device"  # detect partitions, create device nodes - synchronously (wait until done)
        StopIfError "kpartx could not create loop partition device nodes from loop device $disk_device"
        AddExitTask "kpartx -d $disk_device >&2"
        if [[ ! -b "$boot_partition" ]]; then
            # Some versions of kpartx (as on CentOS 6) do not create partition device nodes under /dev, but leave
            # them under /dev/mapper instead. This kludge will pick those up.
            local alternate_boot_partition="/dev/mapper/"$(basename "$boot_partition")
            [[ -b "$alternate_boot_partition" ]] && boot_partition="$alternate_boot_partition"
        fi
        use_kpartx=yes
    else
        LogPrintError "It seems that the current linux kernel does not support loop device partitions."
        LogPrintError "TIP: You might achieve loop device partition support by installing the 'kpartx' tool, if available."
    fi
fi

# If unsuccessful, say so.
[[ -b "$boot_partition" ]] || Error "Cannot prepare boot file system for the RAWDISK image: Missing OS support for loop device partitions."


### Create and populate the boot file system

# Note: Having a small EFI System Partition (ESP) might introduce problems:
# - The UEFI spec seems to require a FAT32 EFI System Partition (ESP).
# - syslinux/Legacy BIOS fails to install on small FAT32 partitions with "syslinux: zero FAT sectors (FAT12/16)".
# - Some firmwares fail to boot from small FAT32 partitions.
# - Some firmwares fail to boot from FAT16 partitions.
# See:
# - http://www.rodsbooks.com/efi-bootloaders/principles.html
# - http://lists.openembedded.org/pipermail/openembedded-core/2012-January/055999.html
# As there seems to be no silver bullet, let mkfs.vfat choose the 'right' FAT partition type based on partition size
# (i.e. do not use the '-F 32' option) and hope for the best...
mkfs.vfat $v "$boot_partition" -n "${RAWDISK_FAT_VOLUME_LABEL:-RESCUE SYS}" 2>>"$RUNTIME_LOGFILE" || Error "Could not create boot file system"

local boot_partition_root="$TMP_DIR/boot"
mkdir -p "$boot_partition_root" 2>>"$RUNTIME_LOGFILE" || Error "Could not create boot file system mount point"
mount "$boot_partition" "$boot_partition_root" 2>>"$RUNTIME_LOGFILE" || Error "Could not mount boot file system"
AddExitTask "umount $boot_partition_root >&2"

# Populate the boot file system with kernel, initrd and possibly EFI bootloader
cp -rL $v "${staged_boot_partition_contents[@]}" "$boot_partition_root" >&2 || Error "Could not populate boot partition"


### Install syslinux stuff as required

# Note: This may add files to the boot file system *and* modify the boot sector directly within the raw disk image.
# After installing a Legacy BIOS bootloader, files on the boot partition should not change: The bootloader file is
# patched during installation with a list of its exact on-disk block locations.

if is_true "$RAWDISK_BOOT_USING_SYSLINUX" || is_true $use_syslinux_legacy; then
    # Install syslinux configuration, which may be shared between syslinux/EFI and syslinux/Legacy bootloaders.
    local syslinux_installation_dir="$boot_partition_root/syslinux"
    mkdir -p "$syslinux_installation_dir" 2>>"$RUNTIME_LOGFILE" || Error "Could not create syslinux bootloader directory"
    cat > "$syslinux_installation_dir/syslinux.cfg" << EOF
DEFAULT rescue
LABEL rescue
 SAY
 SAY ${RAWDISK_BOOT_SYSLINUX_START_INFORMATION:-Starting the rescue system...}
 KERNEL ../$(basename "$KERNEL_FILE")
 APPEND $KERNEL_CMDLINE
 INITRD ../$REAR_INITRD_FILENAME
EOF
    StopIfError "Could not write syslinux bootloader configuration"

    if is_true $use_syslinux_legacy; then
        LogPrint "Using syslinux to install a Legacy BIOS bootloader"

        # Install syslinux as Legacy BIOS bootloader
        syslinux --directory "/syslinux" --install "$boot_partition"
        StopIfError "Could not install Legacy BIOS bootloader (syslinux)"

        # Install the BIOS boot sector
        dd if="$(find_syslinux_file gptmbr.bin)" of="$disk_device" bs=440 count=1 oflag=sync
        StopIfError "Could not install Legacy BIOS boot sector (syslinux)"
    fi
fi

Log "Raw disk boot partition capacity after copying:"
df -h "$boot_partition_root" >>"$RUNTIME_LOGFILE"


### Allow examining the boot file system and loop device for debugging

if is_true ${RAWDISK_DEBUG:-no}; then
    LogUserOutput "Entering shell to examine the raw disk's boot file system, exit to continue:"
    (cd "$boot_partition_root"; bash) <&6 >&7 2>&8
fi


### Unmount the boot partition

# Note: Unmounting before possibly copying the boot partition to local disk partitions ensures that
#       the file system contents are completely flushed from its caches and the boot partition is synced.

umount "$boot_partition_root" 2>>"$RUNTIME_LOGFILE" || Error "Could not unmount boot file system"
RemoveExitTask "umount $boot_partition_root >&2"


### Copy the EFI boot partition to local disk partitions named "$RAWDISK_INSTALL_GPT_PARTITION_NAME" if configured

if [[ -n "$RAWDISK_BOOT_EFI_STAGING_ROOT" && -n "$RAWDISK_INSTALL_GPT_PARTITION_NAME" ]]; then
    local detected_fs_type detected_mount_point install_partition install_partition_count=0

    LogPrint "Installing the EFI rescue system to partitions labeled '$RAWDISK_INSTALL_GPT_PARTITION_NAME'"

    for install_partition in $(lsblk --paths --list --noheadings --output=NAME,PARTLABEL | awk "/ $RAWDISK_INSTALL_GPT_PARTITION_NAME"'$/ { print $1; }'); do
        local cannot_install_partition="Cannot install the EFI rescue system to partition '$install_partition'"

        # The loop device partition we have just created will most probably have the required partition name: Ignore it.
        [[ "$install_partition" == "$boot_partition" ]] && continue

        detected_fs_type="$(lsblk --output FSTYPE --noheadings "$install_partition")" 2>>"$RUNTIME_LOGFILE" || Error "Could not analyze the file system type on '$install_partition'"
        if [[ "$detected_fs_type" != "" && "$detected_fs_type" != "vfat" ]]; then
            LogPrintError "$cannot_install_partition with type '$detected_fs_type' (must be 'vfat' or empty)"
            continue
        fi

        detected_mount_point="$(lsblk --output MOUNTPOINT --noheadings "$install_partition")" 2>>"$RUNTIME_LOGFILE" || Error "Could not detect whether '$install_partition' is mounted"
        if [[ -n "$detected_mount_point" ]]; then
            LogPrintError "$cannot_install_partition as it is currently mounted at '$detected_mount_point'"
            continue
        fi

        LogPrint "Installing the EFI rescue system to partition '$install_partition'"
        dd if="$boot_partition" bs=1024 of="$install_partition" 2>>"$RUNTIME_LOGFILE" || Error "Could not copy the EFI rescue system to partition '$install_partition'"
        install_partition_count=$((install_partition_count + 1))
    done

    ((install_partition_count == 0)) && LogPrint "Could not detect suitable partitions labeled '$RAWDISK_INSTALL_GPT_PARTITION_NAME'"
fi


### Release the loop device

if is_true $use_kpartx; then
    kpartx -d "$disk_device" 2>>"$RUNTIME_LOGFILE" || Error "Could not delete  partition device nodes from loop device $disk_device"
    RemoveExitTask "kpartx -d $disk_device >&2"
fi
losetup -d "$disk_device" 2>>"$RUNTIME_LOGFILE" || Error "Could not delete loop device"
RemoveExitTask "losetup -d $disk_device >&2"


### Compress the disk image on request
if [[ -n "$RAWDISK_IMAGE_COMPRESSION_COMMAND" ]]; then
    $RAWDISK_IMAGE_COMPRESSION_COMMAND "$disk_image"
    StopIfError "Could not compress disk image using <<$RAWDISK_IMAGE_COMPRESSION_COMMAND \"$disk_image\">>"
    disk_image="$(echo "$disk_image"*)"
    [[ -f "$disk_image" ]] || Error "Could not find compressed disk image ${disk_image}*"
fi


### Add disk the image to the result files

RESULT_FILES+=( "$disk_image" )