#!/bin/bash # This file is part of curtin. See LICENSE file for copyright and license info. TEMP_D="" CR=" " VERBOSITY=${VERBOSITY:-${CURTIN_VERBOSITY:-0}} error() { echo "$@" 1>&2; } debug() { [ ${VERBOSITY:-0} -ge "$1" ] || return shift error "$@" } partition_main_usage() { cat <&1) || { error "wiping entire '$target' with ${info} failed." error "$out" return 1 } else local fbs=$bs count=$((size / bs)) if [ "$size" -ge "$mb" ]; then count=1 fbs=$mb fi info="size=$size count=$count bs=$fbs" debug 1 "wiping start of '$target' with ${info}." # wipe the first MB (up to 'size') out=$(dd if=/dev/zero conv=notrunc "of=$target" \ "bs=$fbs" "count=$count" 2>&1) || { error "wiping start of '$target' with ${info} failed." error "$out" return 1 } if $wipe_end && [ "$size" -gt "$mb" ]; then # do the last 1MB count=$((mb / bs)) seek=$(((size / bs) - $count)) info="size=$size count=$count bs=$bs seek=$seek" debug 1 "wiping end of '$target' with ${info}." out=$(dd if=/dev/zero conv=notrunc "of=$target" "seek=$seek" \ "bs=$bs" "count=$count" 2>&1) if [ $? -ne 0 ]; then error "wiping end of '$target' with ${info} failed." error "$out"; return 1; fi fi fi if $rereadpt && [ -b "$target" ]; then blockdev --rereadpt "$target" udevadm settle fi } find_partno() { local devname="$1" partno="$2" local devbname cand msg="" slash="/" devbname="${devname#/dev/}" # /dev/cciss/c0d0 -> ccis!c0d0 devbname="${devbname//$slash/!}" if [ -d "/sys/class/block/${devbname}" ]; then local cand candptno name partdev debug 1 "using sys/class/block/$devbname" for cand in /sys/class/block/$devbname/*/partition; do [ -f "$cand" ] || continue read candptno < "$cand" [ "$candptno" = "$partno" ] || continue name=${cand#/sys/class/block/${devbname}/} name=${name%/partition} # ccis!c0d0p1 -> ccis/c0d0p1 name=${name//!/$slash} partdev="/dev/$name" [ -b "$partdev" ] && _RET="$partdev" && return 0 msg="expected $partdev to exist as partition $partno on $devname" error "WARN: $msg. it did not exist." done else for cand in "${devname}$partno" "${devname}p${partno}"; do [ -b "$cand" ] && _RET="$cand" && return 0 done fi return 1 } part2bd() { # part2bd given a partition, return the block device it is on # and the number the partition is. ie, 'sda2' -> '/dev/sda 2' local dev="$1" fp="" sp="" bd="" ptnum="" dev="/dev/${dev#/dev/}" fp=$(readlink -f "$dev") || return 1 sp="/sys/class/block/${fp##*/}" [ -f "$sp/partition" ] || { _RET="$fp 0"; return 0; } read ptnum < "$sp/partition" sp=$(readlink -f "$sp") || return 1 # sp now has some /sys/devices/pci..../0:2:0:0/block/sda/sda1 bd=${sp##*/block/} bd="${bd%/*}" _RET="/dev/$bd $ptnum" return 0 } pt_gpt() { local target="$1" end=${2:-""} boot="$3" size="" s512="" local start="2048" rootsize="" bootsize="1048576" maxend="" local isblk=false getsize "$target" || { error "failed to get size of $target"; return 1; } size="$_RET" if [ -z "$end" ]; then end=$(($size/512)) else end=$(($end/512)) fi if [ "$boot" = true ]; then maxend=$((($size/512)-$start-$bootsize)) if [ $maxend -lt 0 ]; then error "Disk is not big enough for /boot partition on $target"; return 1; fi else maxend=$((($size/512)-$start)) fi [ "$end" -gt "$maxend" ] && end="$maxend" debug 1 "maxend=$maxend end=$end size=$size" [ -b "$target" ] && isblk=true if [ "$boot" = true ]; then # Creating 'efi', '/boot' and '/' partitions sgdisk --new "15:$start:+1M" --typecode=15:ef02 \ --new "1::+512M" --typecode=1:8300 \ --new "2::$end" --typecode=2:8300 "$target" || { error "failed to gpt partition $target"; return 1; } else # Creating 'efi' and '/' partitions sgdisk --new "15:$start:+1M" --typecode=15:ef02 \ --new "1::$end" --typecode=1:8300 "$target" || { error "failed to gpt partition $target"; return 1; } fi if $isblk; then local expected="1 15" [ "$boot" = "true" ] && expected="$expected 2" blockdev --rereadpt "$target" udevadm settle assert_partitions "$target" $expected || { error "$target missing partitions: $_RET"; return 1; } wipe_partitions "$target" $expected || { error "$target: failed to wipe partitions"; return 1; } fi } assert_partitions() { local dev="$1" missing="" part="" shift for part in "$@"; do find_partno "$dev" $part || missing="${missing} ${part}" done _RET="${missing# }" [ -z "$missing" ] } pt_uefi() { local target="$1" end=${2:-""} size="" s512="" local start="2048" rootsize="" maxend="" local isblk=false getsize "$target" || { error "failed to get size of $target"; return 1; } size="$_RET" if [ -z "$end" ]; then end=$(($size/512)) else end=$(($end/512)) fi maxend=$((($size/512)-$start)) [ "$end" -gt "$maxend" ] && end="$maxend" debug 1 "maxend=$maxend end=$end size=$size" [ -b "$target" ] && isblk=true # Creating 'UEFI' and '/' partitions sgdisk --new "15:2048:+512M" --typecode=15:ef00 \ --new "1::$end" --typecode=1:8300 "$target" || { error "failed to sgdisk for uefi to $target"; return 1; } if $isblk; then blockdev --rereadpt "$target" udevadm settle assert_partitions "$target" 1 15 || { error "$target missing partitions: $_RET"; return 1; } wipe_partitions "$target" 1 15 || { error "$target: failed to wipe partitions"; return 1; } fi local pt15 find_partno "$target" 15 && pt15="$_RET" || { error "failed to find partition 15 for $target"; return 1; } mkfs -t vfat -F 32 -n uefi-boot "$pt15" || { error "failed to partition :$pt15' for UEFI vfat"; return 1; } } pt_mbr() { local target="$1" end=${2:-""} boot="$3" size="" s512="" ptype="L" local start="2048" rootsize="" maxsize="4294967296" local maxend="" isblk=false def_bootsize="1048576" bootsize=0 local isblk=false getsize "$target" || { error "failed to get size of $target"; return 1; } size="$_RET" if $boot; then bootsize=$def_bootsize fi s512=$(($size/512)) if [ $s512 -ge $maxsize ]; then debug 1 "disk is larger than max for mbr (2TB)" s512=$maxsize fi # allow 33 sectors for the secondary gpt header in the case that # the user wants to later 'sgdisk --mbrtogpt' local gpt2hsize="33" if [ -n "$end" ]; then rootsize=$(((end/512)-start-bootsize)) else rootsize=$((s512-start-bootsize-$gpt2hsize)) fi [ -b "$target" ] && isblk=true # interact with sfdisk in units of 512 bytes (--unit S) # we start all partitions at 2048 of those (1M) local sfdisk_out="" sfdisk_in="" sfdisk_cmd="" t="" expected="" if "$boot"; then t="$start,$bootsize,$ptype,-${CR}" t="$t$(($start+$bootsize)),$rootsize,$ptype,*" sfdisk_in="$t" expected="1 2" else sfdisk_in="$start,$rootsize,$ptype,*" expected=1 fi sfdisk_cmd=( sfdisk --no-reread --force --Linux --unit S "$target" ) debug 1 "sfdisking with: echo '$sfdisk_in' | ${sfdisk_cmd[*]}" sfdisk_out=$(echo "$sfdisk_in" | "${sfdisk_cmd[@]}" 2>&1) ret=$? [ $ret -eq 0 ] || { error "failed to partition $target [${sfdisk_out}]"; return 1; } if $isblk; then blockdev --rereadpt "$target" udevadm settle assert_partitions "$target" ${expected} || { error "$target missing partitions: $_RET"; return 1; } wipe_partitions "$target" ${expected} || { error "failed to wipe partition 1 on $target"; return 1; } fi } pt_prep() { local target="$1" end=${2:-""} local cmd="" isblk=false [ -b "$target" ] && isblk=true local pprep="1" proot="2" wipedev "$target" || { error "failed to clear $target"; return 1; } cmd=( sgdisk --new "${pprep}::+8M" "--typecode=${pprep}:4100" --new "${proot}::$end" "--typecode=${proot}:8300" "$target" ) debug 1 "partitioning '$target' with ${cmd[*]}" "${cmd[@]}" || fail "Failed to create GPT partitions (${cmd[*]})" udevadm trigger udevadm settle if $isblk; then blockdev --rereadpt "$target" udevadm settle assert_partitions "$target" "${proot}" "${pprep}" || { error "$target missing partitions: $_RET"; return 1; } # wipe the full prep partition wipe_partitions --full "$target" "${pprep}" || { error "$target: failed to wipe full PReP partition"; return 1;} wipe_partitions "$target" "${proot}" || { error "$target: failed to wipe partition ${proot}"; return 1;} fi return 0 } partition_main() { local short_opts="hE:f:bv" local long_opts="help,end:,format:,boot,verbose" local getopt_out=$(getopt --name "${0##*/}" \ --options "${short_opts}" --long "${long_opts}" -- "$@") && eval set -- "${getopt_out}" || { partition_main_usage 1>&2; return 1; } local cur="" next="" local format="mbr" boot=false target="" end="" ret=0 while [ $# -ne 0 ]; do cur="$1"; next="$2"; case "$cur" in -h|--help) partition_main_usage ; exit 0;; -E|--end) end=$next; shift;; -f|--format) format=$next; shift;; -b|--boot) boot=true;; -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));; --) shift; break;; esac shift; done [ $# -gt 1 ] && { partition_main_usage "got $# args, expected 1" 1>&2; return 1; } [ $# -eq 0 ] && { partition_main_usage "must provide target-dev" 1>&2; return 1; } target="$1" if [ -n "$end" ]; then human2bytes "$end" || { error "failed to convert '$end' to bytes"; return 1; } end="$_RET" fi [ "$format" = "gpt" -o "$format" = "mbr" ] || [ "$format" = "uefi" -o "$format" = "prep" ] || { partition_main_usage "invalid format: $format" 1>&2; return 1; } TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") || fail "failed to make tempdir" trap cleanup EXIT [ -e "$target" ] || { error "$target does not exist"; return 1; } [ -f "$target" -o -b "$target" ] || { error "$target not a block device"; return 1; } wipedev "$target" || { error "wiping $target failed"; return 1; } if [ "$format" = "mbr" ]; then pt_mbr "$target" "$end" "$boot" elif [ "$format" = "gpt" ]; then pt_gpt "$target" "$end" "$boot" elif [ "$format" = "uefi" ]; then pt_uefi "$target" "$end" elif [ "$format" = "prep" ]; then pt_prep "$target" "$end" fi ret=$? return $ret } human2bytes() { # converts size suitable for input to resize2fs to bytes # s:512 byte sectors, K:kilobytes, M:megabytes, G:gigabytes # none: block size of the image local input=${1} defunit=${2:-1024} local unit count; case "$input" in *s) count=${input%s}; unit=512;; *K) count=${input%K}; unit=1024;; *M) count=${input%M}; unit=$((1024*1024));; *G) count=${input%G}; unit=$((1024*1024*1024));; *) count=${input} ; unit=${defunit};; esac _RET=$((${count}*${unit})) } getsize() { # return size of target in bytes local target="$1" if [ -b "$target" ]; then _RET=$(blockdev --getsize64 "$target") elif [ -f "$target" ]; then _RET=$(stat "--format=%s" "$target") else return 1; fi } is_md() { case "${1##*/}" in md[0-9]) return 0;; esac return 1 } get_carryover_params() { local cmdline=" $1 " extra="" lead="" carry_extra="" carry_lead="" # return a string to append to installed systems boot parameters # it may include a '--' after a '---' # see LP: 1402042 for some history here. # this is similar to 'user-params' from d-i local preferred_sep="---" # KERNEL_CMDLINE_COPY_TO_INSTALL_SEP local legacy_sep="--" case "$cmdline" in *\ ${preferred_sep}\ *) extra=${cmdline#* ${preferred_sep} } lead=${cmdline%% ${preferred_sep} *} ;; *\ ${legacy_sep}\ *) extra="${cmdline#* ${legacy_sep} }" lead=${cmdline%% ${legacy_sep} *} ;; *) extra="" lead="$cmdline" ;; esac if [ -n "$extra" ]; then carry_extra=$(set -f; c=""; for p in $extra; do case "$p" in (BOOTIF=*|initrd=*|BOOT_IMAGE=*) continue;; esac c="$c $p"; done echo "${c# }" ) fi # these get copied even if they werent after the separator local padded=" $carry_extra " carry_lead=$(set -f; padded=" ${carry_extra} " c="" for p in $lead; do # skip any that are already in carry_extra [ "${padded#* $p }" != "$padded" ] && continue case "$p" in (console=*) c="$c $p";; esac done echo "${c# }" ) [ -n "${carry_lead}" -a -n "${carry_extra}" ] && carry_lead="${carry_lead} " _RET="${carry_lead}${carry_extra}" } shell_config_update() { # shell_config_update(file, name, value) # update variable 'name' setting value to 'val' in shell syntax 'file'. # if 'name' is not present, then append declaration. local file="$1" name="$2" val="$3" if ! [ -f "$file" ] || ! grep -q "^$name=" "$file"; then debug 2 "appending to $file shell $name=\"$val\"" echo "$name=\"$val\"" >> "$file" return fi local cand="" del="" for cand in "|" "," "/"; do [ "${val#*${del}}" = "${val}" ] && del="$cand" && break done [ -n "$del" ] || { error "Couldn't find a sed delimiter for '$val'"; return 1; } sed -i -e "s${del}^$name=.*${del}$name=\"$val\"${del}" "$file" || { error "Failed editing '$file' to set $name=$val"; return 1; } debug 2 "updated $file to set $name=\"$val\"" return 0 } apply_grub_cmdline_linux_default() { local mp="$1" newargs="$2" edg="${3:-etc/default/grub}" local gcld="GRUB_CMDLINE_LINUX_DEFAULT" debug 1 "setting $gcld to '$newargs' in $edg" shell_config_update "$mp/$edg" "$gcld" "$newargs" || { error "Failed to set '$gcld=$newargs' in $edg" return 1 } } install_grub() { local long_opts="uefi,update-nvram,os-family:" local getopt_out="" mp_efi="" getopt_out=$(getopt --name "${0##*/}" \ --options "" --long "${long_opts}" -- "$@") && eval set -- "${getopt_out}" local uefi=0 update_nvram=0 os_family="" while [ $# -ne 0 ]; do cur="$1"; next="$2"; case "$cur" in --os-family) os_family=${next};; --uefi) uefi=$((${uefi}+1));; --update-nvram) update_nvram=$((${update_nvram}+1));; --) shift; break;; esac shift; done [ $# -lt 2 ] && { grub_install_usage "must provide mount-point and target-dev" 1>&2; return 1; } local mp="$1" local cmdline tmp r="" shift local grubdevs grubdevs=( "$@" ) if [ "${#grubdevs[@]}" = "1" -a "${grubdevs[0]}" = "none" ]; then grubdevs=( ) fi # find the mp device local mp_dev="" fstype="" mp_dev=$(awk -v "MP=$mp" '$2 == MP { print $1 }' /proc/mounts) || { error "unable to determine device for mount $mp"; return 1; } fstype=$(awk -v MP=$mp '$2 == MP { print $3 }' /proc/mounts) || { error "unable to fstype for mount $mp"; return 1; } [ -z "$mp_dev" ] && { error "did not find '$mp' in /proc/mounts" cat /proc/mounts 1>&2 return 1 } # check if parsed mount point is a block device # error unless fstype is zfs, where entry will not point to block device. if ! [ -b "$mp_dev" ] && [ "$fstype" != "zfs" ]; then # error unless mp is zfs, entry doesn't point to block devs error "$mp_dev ($fstype) is not a block device!"; return 1; fi local os_variant="" if [ -e "${mp}/etc/os-release" ]; then os_variant=$(chroot "$mp" \ /bin/sh -c 'echo $(. /etc/os-release; echo $ID)') else # Centos6 doesn't have os-release, so check for centos/redhat release # looks like: CentOS release 6.9 (Final) for rel in $(ls ${mp}/etc/*-release); do os_variant=$(awk '{print tolower($1)}' $rel) [ -n "$os_variant" ] && break done fi [ $? != 0 ] && { error "Failed to read ID from $mp/etc/os-release"; return 1; } local rhel_ver="" case $os_variant in debian|ubuntu) os_family="debian";; centos|rhel) os_family="redhat" rhel_ver=$(chroot "$mp" rpm -E '%rhel') ;; esac # ensure we have both settings, family and variant are needed [ -n "${os_variant}" -a -n "${os_family}" ] || { error "Failed to determine os variant and family"; return 1; } # get target arch local target_arch="" r="1" case $os_family in debian) target_arch=$(chroot "$mp" dpkg --print-architecture) r=$? ;; redhat) target_arch=$(chroot "$mp" rpm -E '%_arch') r=$? ;; esac [ $r -eq 0 ] || { error "failed to get target architecture [$r]" return 1; } # grub is not the bootloader you are looking for if [ "${target_arch}" = "s390x" ]; then return 0; fi # set correct grub package local grub_name="" local grub_target="" case "$target_arch" in i386|amd64) # debian grub_name="grub-pc" grub_target="i386-pc" ;; x86_64) case $rhel_ver in 6) grub_name="grub";; 7) grub_name="grub2-pc";; *) error "Unknown rhel_ver [$rhel_ver]"; return 1; ;; esac grub_target="i386-pc" ;; esac if [ "${target_arch#ppc64}" != "${target_arch}" ]; then grub_name="grub-ieee1275" grub_target="powerpc-ieee1275" elif [ "$uefi" -ge 1 ]; then grub_name="grub-efi-$target_arch" case "$target_arch" in x86_64) # centos 7+, no centos6 support grub_name="grub2-efi-x64-modules" grub_target="x86_64-efi" ;; amd64) grub_target="x86_64-efi";; arm64) grub_target="arm64-efi";; esac fi # check that the grub package is installed local r=$? case $os_family in debian) tmp=$(chroot "$mp" dpkg-query --show \ --showformat='${Status}\n' $grub_name) r=$? ;; redhat) tmp=$(chroot "$mp" rpm -q \ --queryformat='install ok installed\n' $grub_name) r=$? ;; esac if [ $r -ne 0 -a $r -ne 1 ]; then error "failed to check if $grub_name installed"; return 1; fi case "$tmp" in install\ ok\ installed) :;; *) debug 1 "$grub_name not installed, not doing anything"; return 1;; esac local grub_d="etc/default/grub.d" # ubuntu writes to /etc/default/grub.d/50-curtin-settings.cfg # to avoid tripping prompts on upgrade LP: #564853 local mygrub_cfg="$grub_d/50-curtin-settings.cfg" case $os_family in redhat) grub_d="etc/default" mygrub_cfg="etc/default/grub";; esac [ -d "$mp/$grub_d" ] || mkdir -p "$mp/$grub_d" || { error "Failed to create $grub_d"; return 1; } # LP: #1179940 . The 50-cloudig-settings.cfg file is written by the cloud # images build and defines/override some settings. Disable it. local cicfg="$grub_d/50-cloudimg-settings.cfg" if [ -f "$mp/$cicfg" ]; then debug 1 "moved $cicfg out of the way" mv "$mp/$cicfg" "$mp/$cicfg.disabled" fi # get the user provided / carry-over kernel arguments local newargs="" read cmdline < /proc/cmdline && get_carryover_params "$cmdline" && newargs="$_RET" || { error "Failed to get carryover parrameters from cmdline"; return 1; } # always append rd.auto=1 for centos case $os_family in redhat) newargs="${newargs:+${newargs} }rd.auto=1";; esac debug 1 "carryover command line params '$newargs'" if [ "${REPLACE_GRUB_LINUX_DEFAULT:-1}" != "0" ]; then apply_grub_cmdline_linux_default "$mp" "$newargs" || { error "Failed to apply grub cmdline." return 1 } fi if [ "${DISABLE_OS_PROBER:-1}" == "1" ]; then { echo "# Curtin disable grub os prober that might find other OS installs." echo "GRUB_DISABLE_OS_PROBER=true" } >> "$mp/$mygrub_cfg" fi if [ -n "${GRUB_TERMINAL}" ]; then { echo "# Curtin configured GRUB_TERMINAL value" echo "GRUB_TERMINAL=${GRUB_TERMINAL}" } >> "$mp/$mygrub_cfg" fi local short="" bd="" grubdev grubdevs_new="" grubdevs_new=() for grubdev in "${grubdevs[@]}"; do if is_md "$grubdev"; then short=${grubdev##*/} for bd in "/sys/block/$short/slaves/"/*; do [ -d "$bd" ] || continue bd=${bd##*/} bd="/dev/${bd%[0-9]}" # FIXME: part2bd grubdevs_new[${#grubdevs_new[@]}]="$bd" done else grubdevs_new[${#grubdevs_new[@]}]="$grubdev" fi done grubdevs=( "${grubdevs_new[@]}" ) if [ "$uefi" -ge 1 ]; then nvram="--no-nvram" if [ "$update_nvram" -ge 1 ]; then nvram="" fi debug 1 "curtin uefi: installing ${grub_name} to: /boot/efi" chroot "$mp" env DEBIAN_FRONTEND=noninteractive sh -exc ' echo "before grub-install efiboot settings" efibootmgr -v || echo "WARN: efibootmgr exited $?" bootid="$4" grubpost="" case $bootid in debian|ubuntu) grubcmd="grub-install" dpkg-reconfigure "$1" update-grub ;; centos|redhat|rhel) grubcmd="grub2-install" grubpost="grub2-mkconfig -o /boot/grub2/grub.cfg" ;; *) echo "Unsupported OS: $bootid" 1>&2 exit 1 ;; esac # grub-install in 12.04 does not contain --no-nvram, --target, # or --efi-directory target="--target=$2" no_nvram="$3" efi_dir="--efi-directory=/boot/efi" gi_out=$($grubcmd --help 2>&1) echo "$gi_out" | grep -q -- "$no_nvram" || no_nvram="" echo "$gi_out" | grep -q -- "--target" || target="" echo "$gi_out" | grep -q -- "--efi-directory" || efi_dir="" $grubcmd $target $efi_dir \ --bootloader-id=$bootid --recheck $no_nvram [ -z "$grubpost" ] || $grubpost;' \ -- "${grub_name}" "${grub_target}" "$nvram" "$os_variant" &2 exit 1 ;; esac for d in "$@"; do echo $grubcmd "$d"; $grubcmd "$d" || exit; done [ -z "$grubpost" ] || $grubpost;' \ -- "${grub_name}" "${os_variant}" "${rhel_ver}" "${grubdevs[@]}"