#!/bin/bash # # netboot - PXE-style boot for KVM on s390 # # Sample script to build an initramfs image suitable for performing a # PXELINUX-style boot for KVM guests from a DHCP/BOOTP Server. # Has to be executed on the KVM host where it will be deployed and needs # to be re-run after kernel updates on the host, unless the virtio # drivers are statically built into the host kernel. # # The script requires a busybox install tree, e.g. resulting from a build # from source, after make defconfig && make install # # To keep things simple, we don't include udev but use devtmpfs # which means the host kernel must have been built with CONFIG_DEVTMPFS=y # # Sample invocation: # # ./mk-pxelinux-ramfs -b /downloads/busyboxdir pxelinux.initramfs # # Copyright IBM Corp. 2017 # # s390-tools is free software; you can redistribute it and/or modify # it under the terms of the MIT license. See LICENSE for details. # Variables cmd=$(basename $0) busyboxdir= builddir= initramfs= success=no # Cleanup on exit cleanup() { if [ -n $builddir ] then rm -rf $builddir fi } trap cleanup EXIT # Usage usage() { cat <<-EOF Usage: $cmd -b BUSYBOX_DIR [-k KERNEL_VERSION] INITRAMFS_FILE Build a PXELINUX style boot initramfs INITRAMFS_FILE using a busybox installed in BUSYBOX_DIR and kernel modules from the currently running kernel or from the kernel version specified with the '-k KERNEL_VERSION' option. OPTIONS -b Search installed busybox in directory BUSYBOX_DIR -k Use KERNEL_VERSION instead of currently running kernel -h Print this help, then exit -v Print version information, then exit EOF } printversion() { cat <<-EOD $cmd: version %S390_TOOLS_VERSION% Copyright IBM Corp. 2017 EOD } # Get shared objects for binary sharedobjs() { ldd $1 | sed -e 's?[^/]*??' -e 's/(.*)//' } # Check args args=$(getopt b:k:hv $*) if [ $? = 0 ] then set -- $args while [ -n $1 ] do case $1 in -b) busyboxdir=$2; shift 2;; -k) kernelversion=$2; shift 2;; -h) usage; exit 0;; -v) printversion; exit 0;; --) shift; break;; *) echo "$cmd: Unexpected argument $1, exiting..." >&2; exit 1;; esac done fi if [ $# != 1 -o "$busyboxdir" = "" ] then usage >&2 exit 1 fi # Full output file path initramfs=$(readlink -m $(dirname $1))/$(basename $1) # Exit on error set -e # Module locations if [ -n $kernelversion ]; then moddir=/lib/modules/$kernelversion else moddir=/lib/modules/$(uname -r) fi netdir=$moddir/kernel/drivers/net blkdir=$moddir/kernel/drivers/block # Setup build directory builddir=$(mktemp -d) echo "$cmd: Building in $builddir" ramfsdirs="/bin /dev /etc /lib64 /lib /mnt /proc /run /sbin /sys /tmp /usr /var" for d in $ramfsdirs do mkdir -p $builddir/$d done # Kexec echo "$cmd: Copying kexec" # Install both binary and required shared libraries OLDPATH=$PATH PATH=$OLDPATH:/sbin:/usr/sbin kexec_bin=$(command -v kexec) kexec_sos=$(sharedobjs $kexec_bin) PATH=$OLDPATH cp $kexec_bin $builddir/sbin for so in $kexec_sos do mkdir -p $builddir/$(dirname $so) cp $so $builddir/$(dirname $so) done # virtio module(s), if present echo "$cmd: Copying virtio modules" mkdir -p $builddir/$netdir mkdir -p $builddir/$blkdir set +e cp $netdir/virtio_net.ko* $builddir/$netdir 2> /dev/null cp $blkdir/virtio_blk.ko* $builddir/$blkdir 2> /dev/null set -e # Busybox (+ dependencies) echo "$cmd: Copying busybox files" cp -a $busyboxdir/* $builddir busybox_sos=$(sharedobjs $busyboxdir/bin/busybox) for so in $busybox_sos do mkdir -p $builddir/$(dirname $so) cp $so $builddir/$(dirname $so) done # ad_packet module(s), if present echo "$cmd: Copying af_packet modules" packetdir=$moddir/kernel/net/packet mkdir -p $builddir/$packetdir set +e cp $packetdir/* $builddir/$packetdir 2> /dev/null set -e # Init script echo "$cmd: Making init script" # --- begin init script cat <<'EOF' > $builddir/init #!/bin/sh /bin/mount -t devtmpfs none /dev /bin/mount -t proc none /proc /bin/mount -t sysfs none /sys /bin/mount -t tmpfs none /run /sbin/modprobe virtio_net /sbin/udhcpc -O pxeconffile -O pxepathprefix -x 93:001F & /sbin/pxeboot.script EOF # --- end init script chmod +x $builddir/init # udhcpc script echo "$cmd: Making DHCP script" mkdir -p $builddir/usr/share/udhcpc # -- begin dhcp script cat <<'EOF' > $builddir/usr/share/udhcpc/default.script #!/bin/sh # Setup name resolution and PXE boot configuration # called by udhcpc RESOLVCONF="/etc/resolv.conf" PXECONF="/etc/pxe.conf" ccidr() { # clumsy netmask to cidr transformation # with minimal sanity checking OLDIFS=$IFS IFS=. c=0 n=4 for i in $1 do n=$(/usr/bin/expr $n - 1) case $i in 255) c=$(/usr/bin/expr $c + 8);; 254) c=$(/usr/bin/expr $c + 7); break;; 252) c=$(/usr/bin/expr $c + 6); break;; 248) c=$(/usr/bin/expr $c + 5); break;; 240) c=$(/usr/bin/expr $c + 4); break;; 224) c=$(/usr/bin/expr $c + 3); break;; 192) c=$(/usr/bin/expr $c + 2); break;; 128) c=$(/usr/bin/expr $c + 1); break;; 0) break;; *) c=0; break;; esac if [ $n = 0 ] then break fi done IFS=$OLDIFS echo $c } echo "DHCP response $1: " case "$1" in deconfig) echo " interface: $interface" /sbin/ip route flush table all /sbin/ip addr flush $interface /sbin/ip link set $interface up /bin/rm -f $PXECONF $RESOLVCONF ;; renew|bound) echo " interface: $interface $ip $subnet" echo " router: $router" echo " domain: $domain $dns" echo " tftp: $siaddr" echo " pxepathprefix: $pxepathprefix" # flush routes /sbin/ip route flush table all # setup if link /sbin/ip addr flush $interface /sbin/ip link set $interface up # setup if addr if [ -n "$subnet" ] then maskedip="$ip"/$(ccidr $subnet) else maskedip="ip" fi /sbin/ip addr add $maskedip broadcast $broadcast dev $interface # setup default routes if [ -n "$router" ] then /sbin/ip route add default via $router fi # setup resolv.conf if [ -n "$domain" ] then echo "search $domain" > $RESOLVCONF for i in $dns do echo " nameserver $i" >> $RESOLVCONF done fi # pxe control if [ -n "$siaddr" ] then echo "siaddr=$siaddr" > $PXECONF echo "interface=$interface" >> $PXECONF echo "ip=$ip" >> $PXECONF echo "pxepathprefix=$pxepathprefix" >> $PXECONF fi ;; nak) ;; *) exit 1 ;; esac exit 0 EOF # -- end dhcp script chmod +x $builddir/usr/share/udhcpc/default.script # pxeboot script echo "$cmd: Making PXE boot script" # -- begin pxeboot script cat <<'EOF' > $builddir/sbin/pxeboot.script #!/bin/sh # Perform a PXE style boot using kexec # Supports only super-simple config files set -e # Check if a valid pxe conf is available # so far a non configurable 600 sec timeout PXE_CONF=/etc/pxe.conf TIMEOUT=600 WAITED=0 echo "waiting for pxe config from DHCP (max $TIMEOUT sec)" while [ ! -f $PXE_CONF ] do sleep 1 WAITED=$(($WAITED + 1)) if [ $WAITED -gt $TIMEOUT ] then echo Error waiting for PXE configuration from DHCP exit fi done # Source the DHCP-generated TFTP info # Currently we are just looking for siaddr . $PXE_CONF # Retrieve the config (default only for now) CONFIGS="" if [ -n "$siaddr" ]; then # Enable UUID based config on s390 if [ $(/bin/uname -m) = "s390x" ] then set +e uuid=$(/bin/grep UUID /proc/sysinfo | tail -1 | tr -d ' ' | cut -d ':' -f 2) 2> /dev/null set -e else # not caring for other arches right now uuid="" fi CONFIGS="$CONFIGS $uuid" # Enable MAC based config ifaddr=$(/bin/cat /sys/class/net/$interface/address | tr ':' '-') CONFIGS="$CONFIGS 01-$ifaddr" # Enable IP based config iphex=$(printf %02X $(echo $ip | tr '.' ' ')) for i in 8 7 6 5 4 3 2 1 do CONFIGS="$CONFIGS $(echo $iphex | cut -c 1-$i)" done # Finally enable default config CONFIGS="$CONFIGS default" set +e for c in $CONFIGS do echo "fetching config pxelinux.cfg/$c from $siaddr" if /usr/bin/tftp -g -l /tmp/config -r ${pxepathprefix}pxelinux.cfg/$c $siaddr then break fi done fi if [ ! -f /tmp/config ] then echo no config found exit fi # Simple config file parsing, only one entry allowed kernel=$(/bin/grep -i "^[[:space:]]*kernel" /tmp/config | sed "s/^[[:space:]]*kernel[[:space:]]*//I") initrd=$(/bin/grep -i "^[[:space:]]*initrd" /tmp/config | sed "s/^[[:space:]]*initrd[[:space:]]*//I") append=$(/bin/grep -i "^[[:space:]]*append" /tmp/config | sed "s/^[[:space:]]*append[[:space:]]*//I") ipappend=$(/bin/grep -i "^[[:space:]]*ipappend" /tmp/config | sed "s/^[[:space:]]*ipappend[[:space:]]*//I") if [ -z "$kernel" ] then echo no kernel statement found in config exit else echo fetch kernel $kernel from $siaddr /usr/bin/tftp -g -l /tmp/kernel -r $pxepathprefix$kernel $siaddr fi if [ -n "$initrd" ] then echo fetch initrd $initrd from $siaddr /usr/bin/tftp -g -l /tmp/initrd -r $pxepathprefix$initrd $siaddr INITRD="--initrd=/tmp/initrd" else INITRD="" fi if [ -z "$append" ]; then echo "Kexec load: kexec -l /tmp/kernel $INITRD" kexec -l /tmp/kernel $INITRD else if [ "$ipappend" = "2" ]; then $append="$append BOOTIF=01-$ifaddr" fi echo "Kexec load: kexec -l /tmp/kernel $INITRD --append=\"$append\"" kexec -l /tmp/kernel $INITRD --append="$append" fi kexec -e EOF # -- end pxeboot script chmod +x $builddir/sbin/pxeboot.script # The final initramfs echo Building initramfs cd $builddir find . | cpio -o -Hnewc | gzip - > $initramfs cd $OLDPWD