#!/bin/bash command -v unpack_img >/dev/null || . /lib/img-lib.sh command -v getarg >/dev/null || . /lib/dracut-lib.sh command -v fetch_url >/dev/null || . /lib/url-lib.sh # show critical error messages more visible to user warn_critical() { local msg="$1" if ! [ -d /run/anaconda ]; then mkdir -p /run/anaconda fi echo "$msg" >> /run/anaconda/initrd_errors.txt warn "$msg" } # config_get SECTION KEY < FILE # read an .ini-style config file, find the KEY in the given SECTION, and return # the value provided for that key. # ex: product=$(config_get Main Product < /.buildstamp) config_get() { local section="$1" key="$2" cursec="" k="" v="" while read -r line; do case "$line" in \#*) continue ;; \[*\]*) cursec="${line#[}"; cursec="${cursec%%]*}" ;; *=*) k="${line%%=*}"; v="${line#*=}" ;; esac if [ "$cursec" = "$section" ] && [ "$k" == "$key" ]; then echo "$v" break fi done } find_iso() { local f="" p="" iso="" isodir="$1" tmpmnt="" tmpmnt=$(mkuniqdir /run/install tmpmnt) for f in "$isodir"/*.iso; do [ -e "$f" ] || continue mount -o loop,ro "$f" "$tmpmnt" || continue # Valid ISOs either have stage2 in one of the supported paths # or have a .treeinfo that might tell use where to find the stage2 image. # If it does not have any of those, it is not valid and will not be used. for p in $tmpmnt/LiveOS/squashfs.img $tmpmnt/images/install.img $tmpmnt/.treeinfo; do if [ -e "$p" ]; then iso=$f; break; fi done umount "$tmpmnt" if [ "$iso" ]; then echo "$iso"; return 0; fi done return 1 } find_runtime() { [ -f "$1" ] && [ "${1%.iso}" == "$1" ] && echo "$1" && return local ti_img="" dir="$1" [ -e "$dir"/.treeinfo ] && \ ti_img=$(config_get stage2 mainimage < "$dir/.treeinfo") for f in $ti_img images/install.img LiveOS/squashfs.img; do [ -e "$dir/$f" ] && echo "$dir/$f" && return done } find_tty() { # find the real tty for /dev/console local tty="console" while [ -f "/sys/class/tty/$tty/active" ]; do tty=$(< "/sys/class/tty/$tty/active") tty=${tty##* } # last item in the list done echo "$tty" } repodir="/run/install/repo" isodir="/run/install/isodir" rulesfile="/etc/udev/rules.d/90-anaconda.rules" # try to find a usable runtime image from the repo mounted at $mnt. # if successful, move the mount(s) to $repodir/$isodir. anaconda_live_root_dir() { local img="" iso="" mnt="$1" path="$2" img=$(find_runtime "$mnt/$path") if [ -n "$img" ]; then info "anaconda: found $img" [ "$mnt" = "$repodir" ] || { mount --make-rprivate /; mount --move "$mnt" $isodir; } anaconda_auto_updates "$repodir/$path/images" else if [ "${path%.iso}" != "$path" ]; then iso=$path path=${path%/*.iso} else iso=$(find_iso "$mnt/$path") fi [ -n "$iso" ] || { warn "no suitable images"; return 1; } info "anaconda: found $iso" mount --make-rprivate / mount --move "$mnt" $isodir iso=${isodir}/${iso#"$mnt"} mount -o loop,ro "$iso" $repodir img=$(find_runtime $repodir) || { warn "$iso has no suitable runtime"; } anaconda_auto_updates $repodir/images fi anaconda_mount_sysroot "$img" } anaconda_net_root() { local repo="$1" info "anaconda: fetching stage2 from $repo" # Try to get the local path to stage2 from treeinfo. treeinfo=$(fetch_url "$repo/.treeinfo" 2> /tmp/treeinfo_err) && \ stage2=$(config_get stage2 mainimage < "$treeinfo") # No treeinfo available. [ -z "$treeinfo" ] && debug_msg "$(cat /tmp/treeinfo_err)" # Use the default local path to stage2. if [ -z "$treeinfo" ] || [ -z "$stage2" ]; then warn "can't find installer main image path in .treeinfo" stage2="images/install.img" fi # Fetch the stage2. if runtime=$(fetch_url "$repo/$stage2") \ || runtime=$(fetch_url "$repo/LiveOS/squashfs.img"); then info "anaconda: successfully fetched stage2 from $repo" # NOTE: Should be the same as anaconda_auto_updates() updates=$(fetch_url "$repo/images/updates.img" 2> /tmp/updates_err) [ -z "$updates" ] && debug_msg "$(cat /tmp/updates_err)" [ -n "$updates" ] && unpack_updates_img "$updates" /updates product=$(fetch_url "$repo/images/product.img" 2> /tmp/product_err) [ -z "$product" ] && debug_msg "$(cat /tmp/product_err)" [ -n "$product" ] && unpack_updates_img "$product" /updates anaconda_mount_sysroot "$runtime" return 0 fi warn_critical "anaconda: failed to fetch stage2 from $repo" return 1 } anaconda_mount_sysroot() { local img="$1" if [ -e "$img" ]; then /sbin/dmsquash-live-root "$img" if [ -d /run/rootfsbase ]; then # /run/rootfsbase has been created # Which means that the Squash filesystem is plain # and does not contain the embedded EXT4 inside. # Also known as flattened SquashFS or directly compressed SquashFS. printf "mount -t overlay LiveOS_rootfs \ -o lowerdir=/run/rootfsbase,upperdir=/run/overlayfs,workdir=/run/ovlwork \ %s" "${NEWROOT}" > "${hookdir}/mount/01-$$-anaconda.sh" else # Otherwise, assumption is that /dev/mapper/live-rw should have been created. # dracut & systemd only mount things with root=live: so we have to do this ourselves # See https://bugzilla.redhat.com/show_bug.cgi?id=1232411 printf 'mount /dev/mapper/live-rw %s\n' "$NEWROOT" > "$hookdir/mount/01-$$-anaconda.sh" fi fi } # find updates.img/product.img/RHUpdates and unpack/copy them so they'll # end up in the location(s) that anaconda expects them anaconda_auto_updates() { local dir="$1" if [ -d "$dir/RHupdates" ]; then copytree "$dir/RHupdates" /updates fi if [ -e "$dir/updates.img" ]; then unpack_updates_img "$dir/updates.img" /updates fi if [ -e "$dir/product.img" ]; then unpack_updates_img "$dir/product.img" /updates fi } # Unpack an image into the given dir. unpack_updates_img() { local img="$1" tmpdir="/tmp/${1##*/}.$$" outdir="${2:-/updates}" # NOTE: unpack_img $img $outdir can clobber existing subdirs in $outdir, # which is why we use a tmpdir and copytree (which doesn't clobber) unpack_img "$img" "$tmpdir" copytree "$tmpdir" "$outdir" rm -rf "$tmpdir" } # These could probably be in dracut-lib or similar copytree() { local src="$1" dest="$2" mkdir -p "$dest"; dest=$(readlink -f -q "$dest") ( cd "$src" || return 1; cp -a . -t "$dest" ) } disk_to_dev_path() { case "$1" in CDLABEL=*|LABEL=*) echo "/dev/disk/by-label/${1#*LABEL=}" ;; UUID=*) echo "/dev/disk/by-uuid/${1#UUID=}" ;; /dev/*) echo "$1" ;; *) echo "/dev/$1" ;; esac } find_mount() { local dev mnt etc wanted_dev wanted_dev="$(readlink -e -q "$1")" # shellcheck disable=SC2034 # etc eats the rest of line while read -r dev mnt etc; do [ "$dev" = "$wanted_dev" ] && echo "$mnt" && return 0 done < /proc/mounts return 1 } when_diskdev_appears() { local dev="${1#/dev/}" cmd=""; shift cmd="/sbin/initqueue --settled --onetime --name $1 $*" { printf 'SUBSYSTEM=="block", KERNEL=="%s", RUN+="%s"\n' "$dev" "$cmd" printf 'SUBSYSTEM=="block", SYMLINK=="%s", RUN+="%s"\n' "$dev" "$cmd" } >> $rulesfile } when_any_cdrom_appears() { local cmd="/sbin/initqueue --settled --onetime --name autocd $*" printf 'SUBSYSTEM=="block", ENV{ID_CDROM_MEDIA}=="1", RUN+="%s"\n' "$cmd" \ >> $rulesfile } when_any_hmcdrv_appears() { local dev="hmcdrv" local cmd="/sbin/initqueue --settled --onetime --name $dev $*" printf 'KERNEL=="%s", RUN+="%s"\n' "$dev" "$cmd" \ >> $rulesfile } plymouth_running() { type plymouth >/dev/null 2>&1 && plymouth --ping 2>/dev/null } # print something to the display (and put it in the log so we know what's up) tell_user() { if plymouth_running; then # NOTE: if we're doing graphical splash but we don't have all the # font-rendering libraries, no message will appear. plymouth display-message --text="$*" echo "$*" # this goes to journal only else echo "$*" >&2 # this goes to journal+console fi } # print something only in if debug/inst.debug/rd.debug debug_msg() { if getargbool 0 rd.debug || getargbool 0 debug || getargbool 0 inst.debug; then echo "$*" >&2 fi } dev_is_cdrom() { udevadm info --query=property --name="$1" | grep -q 'ID_CDROM=1' } dev_is_on_disk_with_iso9660() { # Get the name of the device. local dev_name="${1}" # Get the path of the device. local dev_path dev_path="$(udevadm info -q path --name "${dev_name}")" # Is the device a partition? udevadm info -q property --path "${dev_path}" | grep -q 'DEVTYPE=partition' || return 1 # Get the path of the parent. local disk_path="${dev_path%/*}" # Is the parent a disk? udevadm info -q property --path "${disk_path}" | grep -q 'DEVTYPE=disk' || return 1 # Does the parent has the iso9660 filesystem? udevadm info -q property --path "${disk_path}" | grep -q 'ID_FS_TYPE=iso9660' || return 1 return 0 } # dracut doesn't bring up the network unless: # a) $netroot is set (i.e. you have a network root device), or # b) /tmp/net.ifaces exists. # So for non-root things that need the network (like kickstart) we need to # make sure /tmp/net.ifaces exists. # For details see 40network/net-genrules.sh (and the rest of 40network). set_neednet() { # if there's no netroot, make sure /tmp/net.ifaces exists [ -z "$netroot" ] && true >> /tmp/net.ifaces } parse_kickstart() { PYTHONHASHSEED=42 /sbin/parse-kickstart "$1" > /etc/cmdline.d/80-kickstart.conf unset CMDLINE # re-read the commandline . /tmp/ks.info # save the parsed kickstart [ -e "$parsed_kickstart" ] && cp "$parsed_kickstart" /run/install/ks.cfg } # print a list of net devices that dracut says are set up. online_netdevs() { local netif="" for netif in /tmp/net.*.did-setup; do netif=${netif#*.}; netif=${netif%.*} [ -d "/sys/class/net/$netif" ] && echo "$netif" done } # Filter locations that are http, https or ftp urls. get_urls() { local locations="${1}" local location # Filter locations. for location in $locations; do case "${location%%:*}" in http|https|ftp) echo "$location" ;; *) warn "anaconda: this location will be ignored: $location" ;; esac done } # This is where we actually run the kickstart. Whee! # We can't just add udev rules (we'll miss devices that are already active), # and we can't just run the scripts manually (we'll miss devices that aren't # yet active - think driver disks!). # # So: we have to write out the rules and then retrigger them. # # Really what we want to do here is just start over from the "cmdline" # phase, but since we can't do that, we'll kind of fake it. # # XXX THIS IS KIND OF A GROSS HACK AND WE NEED A BETTER WAY TO DO IT run_kickstart() { local do_disk="" do_net="" # kickstart's done - time to find a real root device [ "$root" = "anaconda-kickstart" ] && root="" # don't look for the kickstart again # shellcheck disable=SC2034 # used by other anaconda-related dracut stuff kickstart="" # re-parse new cmdline stuff from the kickstart . "$hookdir"/cmdline/*parse-anaconda-repo.sh . "$hookdir"/cmdline/*parse-livenet.sh . "$hookdir"/cmdline/*parse-anaconda-dd.sh # Kickstart network configuration (which might even be empty) should be # applied to get installer image or driver disks only if the tasks haven't # already been performed using network configuration by boot options. This # is ensured by the .done files checking. case "$root" in anaconda-net:*) [ ! -f /tmp/anaconda_netroot.done ] && do_net=1 ;; anaconda-hmc) do_disk=1 ;; anaconda-disk:*) do_disk=1 ;; anaconda-auto-cd) do_disk=1 ;; esac [ -f /tmp/dd_net ] && [ ! -f /tmp/dd_net.done ] && do_net=1 [ -f /tmp/dd_disk ] && do_disk=1 # disk: replay udev events to trigger actions if [ "$do_disk" ]; then # set up new rules . "$hookdir"/pre-trigger/*repo-genrules.sh . "$hookdir"/pre-trigger/*driver-updates-genrules.sh udevadm control --reload # trigger the rules for all the block devices we see udevadm trigger --action=change --subsystem-match=block fi # net: re-run online hooks if [ "$do_net" ]; then # If NetworkManager is used in initramfs if ls -U "$hookdir"/cmdline/*-nm-config.sh >/dev/null 2>&1 ; then # First try to re-run online hooks on any online device. # We don't want to reconfigure the network by applying kickstart config # so use existing network connections if there are any. # Based on nm-run.sh for _i in /sys/class/net/*/ do state=/run/NetworkManager/devices/$(cat "$_i/ifindex") grep -q connection-uuid= "$state" 2>/dev/null || continue nm_connected_device_found="yes" ifname=$(basename "$_i") source_hook initqueue/online "$ifname" done if [ "${nm_connected_device_found}" != "yes" ]; then # Configure NM based on the cmdline now updated with kickstart. # The configuration will be applied by the next run of NM # via settled hook in *-nm-run.sh script which also calls the # online hooks. . "$hookdir"/cmdline/*-nm-config.sh if [ -n "$DRACUT_SYSTEMD" ]; then systemctl start nm-initrd fi fi else # make dracut create the net udev rules (based on the new cmdline) . "$hookdir"/pre-udev/*-net-genrules.sh udevadm control --reload udevadm trigger --action=add --subsystem-match=net for netif in $(online_netdevs); do source_hook initqueue/online "$netif" done fi fi # and that's it - we're back to the mainloop. true > /tmp/ks.cfg.done # let wait_for_kickstart know that we're done. } wait_for_kickstart() { echo "[ -e /tmp/ks.cfg.done ]" > "$hookdir/initqueue/finished/kickstart.sh" } wait_for_updates() { echo "[ -e /tmp/liveupdates.done ]" > "$hookdir/initqueue/finished/updates.sh" } wait_for_dd() { echo "[ -e /tmp/dd.done ]" > "$hookdir/initqueue/finished/dd.sh" } wait_for_disks() { # Allow up to 'inst.wait_for_disks' seconds for disks to be enumerated and # related udev rules to execute (defaults to 5 seconds, 0 disables the # feature). This prevents dracut-initqueue from finishing early. # Since a 0.5 second delay is used between two runs of dracut-initqueue, we # force the latter to retry up to twice the value configured, e.g: # 'inst.wait_for_disks=15' --> force looping 30 times at least # 'inst.wait_for_disks=0' --> force looping 0 times (so no wait time) finished_hook="$hookdir/initqueue/finished/wait_for_disks.sh" [ -e "$finished_hook" ] && return DISKS_WAIT_DELAY=$(getargnum 5 0 10000 inst.wait_for_disks) DISKS_WAIT_RETRIES=$((DISKS_WAIT_DELAY * 2)) echo "[ \"\$main_loop\" -ge \"$DISKS_WAIT_RETRIES\" ]" > "$finished_hook" }