#!/bin/sh
#
# Copyright (c) 2000-2026 University of Utah and the Flux Group.
# 
# {{{EMULAB-LICENSE
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
# License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
#

if [ -r /etc/emulab/paths.sh ]; then
	. /etc/emulab/paths.sh
else
	BINDIR=/etc/testbed
	BOOTDIR=/var/emulab/boot
	ETCDIR=/etc/emulab
fi

OS=`uname -s`

if [ $OS = FreeBSD ]; then
    # XXX should fix paths.sh but I am afraid of the consequences
    if [ -d "/etc/emulab" -a "$ETCDIR" = "/etc/testbed" ]; then
	ETCDIR=/etc/emulab
	for f in isvgaonly isflash bossnode emulab.pem client.pem; do
		if [ -e "/etc/testbed/$f" -a ! -e "/etc/emulab/$f" ]; then
			mv /etc/testbed/$f /etc/emulab/$f
		fi
	done
    fi    
fi

freebsd_disk="ada0"

case $# in
1)
	part=$1
	;;
2)
	part=$1
	freebsd_disk=$2
	;;
*)
	echo "Usage: $0 partition [disk]"
	exit 1
esac

#
# See if Linux disk is already known. If not, attempt to figure it out.
#
if [ -n "$SLICEFIX_LDISK" ]; then
    linux_disk=$SLICEFIX_LDISK
    old_linux_disk=$linux_disk
else
    linux_disk=`$BINDIR/freebsd_to_linux_disk $freebsd_disk`
    old_linux_disk=${linux_disk#* }
    linux_disk=${linux_disk%% *}
fi

isgpt=0
freebsd_pchar=s
if [ $OS = Linux ]; then
	disk=$linux_disk
	if fdisk -l /dev/$disk 2>/dev/null | grep -Eqi '(found valid|disklabel type:) gpt'; then
		isgpt=1
		freebsd_pchar=p
	fi
elif [ $OS = FreeBSD ]; then
	disk=$freebsd_disk
	if gpart show $freebsd_disk 2>/dev/null | grep -q GPT; then
		isgpt=1
		freebsd_pchar=p
	fi
fi

#
# Handle pseudo arguments in the environment.
#

#
# Explicitly enable/disable ACPI (BSD only)
#
ACPI=${SLICEFIX_ACPI:-'unknown'}
case $ACPI in
0|no|NO)
    ACPI=no
    ;;
1|yes|YES)
    ACPI=yes
    ;;
*)
    ACPI=unknown
    ;;
esac

#
# Explicitly enable/disable ASF management on Broadcom (BSD only)
#
ASF=${SLICEFIX_ASF:-'unknown'}
case $ASF in
0|no|NO)
    ASF=no
    ;;
1|yes|YES)
    ASF=yes
    ;;
*)
    ASF=unknown
    ;;
esac

#
# Explicitly enable/disable use of clflush instruction (BSD only)
#
NOCLFLUSH=${SLICEFIX_NOCLFLUSH:-'unknown'}
case $NOCLFLUSH in
0|no|NO)
    NOCLFLUSH=0
    ;;
1|yes|YES)
    NOCLFLUSH=1
    ;;
*)
    NOCLFLUSH=unknown
    ;;
esac

#
# Explicitly set the BIOS boot ID for Grub (Linux only)
#
BIOSDISK=$SLICEFIX_BIOSDISK
if [ -n "$BIOSDISK" ]; then
    case $BIOSDISK in
    0x8[0123456789])
	;;
    *)
	echo "WARNING: unknown BIOS disk setting \"$BIOSDISK\" ignored"
	BIOSDISK=
	;;
    esac
fi

#
# See if we need to exclude the management interface.
# If an interface is specified and we can parse, create the "hint"
# line for loader.conf.
#
DISABLEIF=$SLICEFIX_DISABLEIF
DISABLEHINT=
if [ -n "$DISABLEIF" ]; then
    _str=`echo $DISABLEIF | sed -E -e 's/^([a-zA-Z0-9]*[a-zA-Z])([0-9]+)/hint.\1.\2.disabled/'`
    if [ "$_str" = "$DISABLEIF" ]; then
	echo "WARNING: unparsable define name \"$DISABLEIF\" ignored"
    else
	DISABLEHINT=$_str
    fi
fi

#
# Find out what our console should be:
#   if $CONSOLE is set and valid, use that
#   ow if $VGAONLY is set, console is "vga"
#   ow console is "sio1"
# NOTE: we change "sio" to "sio1" and "vga" to "vid" for convenience later
#
CONSOLE=${SLICEFIX_CONSOLE:-'unknown'}

# If CONSOLE has optional ",speed" parse it off and use that, ow 115200.
case $CONSOLE in
*,*)
    SPEED=${CONSOLE##*,}
    CONSOLE=${CONSOLE%%,*}
    ;;
*)
    SPEED=115200
    ;;
esac

case $CONSOLE in
sio)
    CONSOLE=sio1
    ;;
vga)
    CONSOLE=vid
    ;;
sio1|sio2|sio3|sio4|vid|null|hvc0|unknown)
    ;;
*)
    echo "WARNING: unknown console type \"$CONSOLE\", assuming \"sio1\""
    CONSOLE=sio1
    ;;
esac
if [ $CONSOLE = "unknown" ]; then
    VGAONLY=${SLICEFIX_VGAONLY:-'unknown'}
    case $VGAONLY in
    0|no|NO)
	;;
    1|yes|YES)
	CONSOLE=vid
	;;
    *)
	if [ -r $ETCDIR/isvgaonly ]; then
	    CONSOLE=vid
	fi
	;;
    esac
fi
if [ $CONSOLE = "unknown" ]; then
    echo "WARNING: console type not set, assuming \"sio1\""
    CONSOLE=sio1
fi

#
# XXX if we picked a console here, reset SLICEFIX_CONSOLE for the benefit
# of linux_slicefix. Otherwise, we don't want to mess with any speed setting
# in the original SLICEFIX_CONSOLE string.
#
if [ $OS = Linux -a "$SLICEFIX_CONSOLE" = "unknown" ]; then
    SLICEFIX_CONSOLE=$CONSOLE
fi

# Console ISA port
PORT=
case $CONSOLE in
sio1)
    PORT="0x3F8"
    ;;
sio2)
    PORT="0x2F8"
    ;;
sio3)
    PORT="0x3E8"
    ;;
sio4)
    PORT="0x2E8"
    ;;
esac

# DOM0MEM is optional
DOM0MEM=$SLICEFIX_DOM0MEM

#
# Handle pseudo-arguments in the filesystem
#
if [ -r $ETCDIR/isflash ]; then
    FLASHBOOT=1
else
    FLASHBOOT=0
fi

if [ -r $ETCDIR/bossnode ]; then
    bossnode=`cat $ETCDIR/bossnode`;
    setboss=1
else
    setboss=0
fi

# Indicates the MFS has been properly localized and we can copy stuff
# into the image.
LOCALIZED1="/.localized"
LOCALIZED2="/root/.localized"

islocalized() {
    if [ -e $LOCALIZED1 -o -e $LOCALIZED2 ]; then
	return 0;
    fi
    return 1;
}

#
# Get the last setting of a loader variable from the list of standard files
#
getloadervar() {
    _var=$1
    _mnt=$2
    _flist="$_mnt/boot/defaults/loader.conf $_mnt/boot/device.hints $_mnt/boot/loader.conf $_mnt/boot/loader.conf.local"

    _last=`grep "^${_var}=" $_flist 2>/dev/null | tail -1`
    _val=`echo ${_last#*=} | sed -e 's/^"//' -e 's/"$//'`
    echo $_val
}

#
# Make sure /root/.ssh contains only an authorized_keys file with the boot
# root pubkey.
#
# Called with arg=1 if you just want to see if anything is wrong (returns
# non-zero if so), 0 to fix.
#
dofixauthkeys() {
    _test=$1

    if [ $_test -ne 0 ]; then
	if [ ! -d /mnt/root/.ssh ]; then
	    return 1
	fi
	if [ -x /bin/stat -o -x /usr/bin/stat ]; then
	    if [ $OS = Linux ]; then
		_stat=`/bin/stat -c '%u,%g,40%a' /mnt/root/.ssh`
	    else
		_stat=`/usr/bin/stat -f '%u,%g,%p' /mnt/root/.ssh`
	    fi
	    if [ "$_stat" != "0,0,40700" ]; then
		return 1
	    fi
	fi
	if [ ! -e /mnt/root/.ssh/authorized_keys ]; then
	    return 1
	fi
	if [ -e /mnt/root/.ssh/authorized_keys2 ]; then
	    return 1
	fi
    fi

    #
    # If we are a localized MFS, we just need to use the authorized_keys2
    # file from the MFS. Otherwise we get the key(s) from tmcd and put
    # them into the MFS authorized_keys2 file.
    #
    if ! islocalized; then
	rm -f /root/.ssh/authorized_keys2
	_key=`$BINDIR/tmcc localization | grep 'ROOTPUBKEY=' | head -1 | \
	      sed -e "s/^ROOTPUBKEY='//" | sed -e "s/'$//"`
	if [ $? -ne 0 -o -z "$_key" ]; then
	    echo "WARNING: no boss pubkey returned!"
	else
	    echo "$_key" > /root/.ssh/authorized_keys2
	fi
    fi

    if [ $_test -ne 0 ]; then
	cmp -s /root/.ssh/authorized_keys2 /mnt/root/.ssh/authorized_keys
	if [ $? -ne 0 ]; then
	    return 1
	fi
    else
	echo "  updating /root/.ssh"

	# make sure /root/.ssh exists and has proper permissions
	mkdir -p /mnt/root/.ssh
	chown root:0 /mnt/root/.ssh
	chmod 700 /mnt/root/.ssh
	rm -f /mnt/root/.ssh/authorized_keys2

	#
	# XXX no proper pubkey, just leave the current file intact.
	# XXX maybe we should just nuke it instead?
	#
	if [ ! -r /root/.ssh/authorized_keys2 ]; then
	    return 0
	fi

	# create authkeys file with just root key(s)
	rm -f /mnt/root/.ssh/authorized_keys 
	cp /root/.ssh/authorized_keys2 /mnt/root/.ssh/authorized_keys
	chmod 644 /mnt/root/.ssh/authorized_keys
    fi

    return 0
}

#
# Make sshd more secure by default: no password based login.
# We will fix if there are multiple settings of the same variable,
# if it is set incorrectly, or it is not set at all.
#
# Called with arg=1 if you just want to see if anything is wrong (returns
# non-zero if so), 0 to fix.
#
dofixsshd() {
    _test=$1

    if [ $_test -ne 0 ]; then
	# sshd_config doesn't exist, call it okay
	if [ ! -f /mnt/etc/ssh/sshd_config ]; then
	    echo "WARNING: no sshd_config found!"
	    return 0
	fi

	# find all uncommented instances of variables we care about
	OIFS="$IFS"
	IFS='
'
	_fix=0
	_valP=
	_valPA=
	_valCRA=
	_valPRL=
	for _opt in `grep -E '^(Protocol|PasswordAuthentication|ChallengeResponseAuthentication|PermitRootLogin) ' /mnt/etc/ssh/sshd_config`; do
	    _k=${_opt%% *}
	    _v=${_opt#* }
	    case $_k in
	    Protocol)
		if [ -n "$_valP" -o "$_v" != "2" ]; then
		    _fix=1
		fi
		_valP=$_v
		;;
	    PasswordAuthentication)
		if [ -n "$_valPA" -o "$_v" != "no" ]; then
		    _fix=1
		fi
		_valPA=$_v
		;;
	    ChallengeResponseAuthentication)
		if [ -n "$_valCRA" -o "$_v" != "no" ]; then
		    _fix=1
		fi
		_valCRA=$_v
		;;
	    PermitRootLogin)
		if [ -n "$_valPRL" -o "$_v" != "without-password" ]; then
		    _fix=1
		fi
		_valPRL=$_v
		;;
	    esac
	done
	IFS=$OIFS
	# a var had wrong value or more than one setting, fix
	if [ $_fix -ne 0 ]; then
	    return 1
	fi
	# a var was not explicitly set, fix
	if [ -z "$_valP" -o -z "$_valPA" -o -z "$_valCRA" -o -z "$_valPRL" ]; then
	    return 1
	fi
    else
	echo "  updating /etc/ssh/sshd_config"

	sed -i \
	    -e '/^Protocol /d' \
	    -e '/^PasswordAuthentication /d' \
	    -e '/^ChallengeResponseAuthentication /d' \
	    -e '/^PermitRootLogin /d' \
	    -e '/^# Emulab/d' /mnt/etc/ssh/sshd_config
	cat <<EOF8 >>/mnt/etc/ssh/sshd_config
# Emulab config
Protocol 2
PasswordAuthentication no
ChallengeResponseAuthentication no
PermitRootLogin without-password
EOF8
    fi

    return 0
}

dofreebsd() {
	pchar=$freebsd_pchar

	if [ -n "$SLICEFIX_CLOUDINIT" -a "$SLICEFIX_CLOUDINIT" != "0" ]; then
	    echo "WARNING: cloud-init not supported for FreeBSD images yet!"
	fi

	if [ $OS = FreeBSD ]; then
		vers=`uname -v | sed -e 's/FreeBSD \([0-9]\).*/\1/'`

		# see if there is a root ('a') partition on this BSD slice
		`disklabel ${disk}s${part} 2>&1 | grep -s -E '^[ ]+a:' >/dev/null` || {
			return 1
		}

		if [ $isgpt -eq 0 ]; then
			rootdev=/dev/${disk}s${part}a
		else
			rootdev=/dev/${disk}p${part}
		fi
		echo "fixing FreeBSD root partition $rootdev"
		fsck -p $rootdev || {
			# 8.x fsck fails to determine the FS type, try again with ufs
			echo "retrying fsck with '-t ufs'"
			fsck -t ufs -p $rootdev || {
				echo "Fsck of $rootdev failed"
				return 1
			}
		}

		fstype=ufs
		fsopts=ro
	elif [ $OS = Linux ]; then
		echo "Reloading partition table for $disk..."
		if [ -x /usr/sbin/partprobe ]; then
			/usr/sbin/partprobe /dev/$disk
			sleep 1
		else
			echo w | /sbin/fdisk /dev/$disk > /dev/null 2>&1
		fi

		rootdev=/dev/
		if [ $isgpt -eq 0 ]; then
			rootdev=/dev/`$BINDIR/check_disklabel $disk $part`
		fi
		#
		# XXX Linux seems to no longer detect the BSD disk label
		# when there is a single BSD partition. For all our recent
		# BSD images, the single BSD partition starts at offset 0
		# of the DOS partition, so we can just use the DOS device.
		# Dodgey, but since only mike cares about FreeBSD, have him
		# complain to himself if this breaks.
		#
		if [ "$rootdev" = /dev/ ]; then
			case $disk in
			nvme*)
			    rootdev="/dev/${disk}p${part}"
			    ;;
			*)
			    rootdev="/dev/${disk}${part}"
			    ;;
			esac
		fi
		fstype=ufs
		fsopts="ro,ufstype=44bsd"
	fi

	# Try mounting the filesystem as UFSv1 first,
	# then try UFSv2 if that doesn't work.

	mount -t $fstype -o $fsopts $rootdev /mnt 2> /dev/null || {
		fsopts=ro,ufstype=ufs2
		mount -t $fstype -o $fsopts $rootdev /mnt || {
			echo "RO-mount of $rootdev failed"
			return 1
		}
	}

	#
	# See if we need to modify anything in the image
	#
	changedisk=0
	changeentropy=0
	changeacpi=0
	changeasf=0
	changeclflush=0
	changecons=0
	changecerts=0
	changehostkeys=0
	changerootkeys=0
	changezone=0
	changentp=0
	changechrony=0
	changesshd=0
	changeauth=0
	changercert=0
	changelconf=0

	fixit=0

	# Hardwire the boss?
	if [ $setboss -eq 1 ]; then
	    fixit=1
        fi

	# Disable mgmt interface?
	if [ -n "$DISABLEHINT" ]; then
	    fixit=1
	fi

	# If our disk/part does not appear anywhere in fstab, we need to fix it
	grep "^/dev/${freebsd_disk}${pchar}${part}" /mnt/etc/fstab >/dev/null 2>&1
	case $? in
	0)
	    ;;
	*)
	    changedisk=1
	    fixit=1
	    ;;
	esac

	# If there is a /entropy file, we need to update it
	if [ -f /mnt/entropy -a -c /dev/random ]; then
		changeentropy=1
		fixit=1
	fi

	#
	# See if we need to change the ACPI setting.
	#
	# If ACPI was not specified via TMCD, leave it along.
	#
	# If "acpi_load" does not appear at all in the loader.conf file,
	# assume the OS cannot handle it (or that we do not know how to
	# change it) and leave it alone.
	#
	if [ $ACPI != "unknown" ]; then
	    grep "acpi_load=" /mnt/boot/loader.conf >/dev/null 2>&1
	    case $? in
	    0)
		grep "^acpi_load=\"YES\"" /mnt/boot/loader.conf >/dev/null 2>&1
		case $? in
		0)
		    if [ $ACPI = "no" ]; then
			changeacpi=1
			fixit=1
		    fi
		    ;;
		*)
		    if [ $ACPI = "yes" ]; then
			changeacpi=1
			changelconf=1
			fixit=1
		    fi
		    ;;
		esac
		;;
	    *)
		;;
	    esac
	fi

	#
	# See if we need to change the ASF setting.
	#
	# Note that, unlike ACPI, we change it even if there was no
	# previous setting in /boot/loader.conf.
	#
	if [ $ASF != "unknown" ]; then
	    grep "hw.bge.allow_asf=" /mnt/boot/loader.conf >/dev/null 2>&1
	    case $? in
	    0)
		grep "^hw.bge.allow_asf=1" /mnt/boot/loader.conf >/dev/null 2>&1
		case $? in
		0)
		    if [ $ASF = "no" ]; then
			changeasf=1
			changelconf=1
			fixit=1
		    fi
		    ;;
		*)
		    if [ $ASF = "yes" ]; then
			changeasf=1
			changelconf=1
			fixit=1
		    fi
		    ;;
		esac
		;;
	    *)
	        changeasf=1
		fixit=1
		;;
	    esac
	fi

	if [ $NOCLFLUSH != "unknown" ]; then
	    changelconf=1
	    fixit=1
	fi

	#
	# See if we need to fix console related settings.
	#
	curconsole=`getloadervar console /mnt`
	case $CONSOLE in
	null|vid)
	    if [ "$curconsole" != "${CONSOLE}console" ]; then
		changecons=1
		changelconf=1
		fixit=1
	    fi
	    ;;
	sio*)
	    if [ "$curconsole" != "comconsole" ]; then
		# not comconsole, fix it
		changecons=1
		changelconf=1
		fixit=1
	    else
		# see if the com port has changed
		unit=`expr ${CONSOLE#sio} - 1`
		flags=`getloadervar hint.uart.$unit.flags /mnt`
		if [ -z "$flags" ]; then
		    flags=`getloadervar hint.sio.$unit.flags /mnt`
		fi
		if [ "$flags" != "0x10" ]; then
		    changecons=1
		    changelconf=1
		    fixit=1
		fi
	    fi
	    if [ "$SPEED" != "115200" ]; then
		# non-standard speed requires change to /etc/ttys
		changedisk=1
		fixit=1
	    fi
	    ;;
	esac

        # check to see if we need to download any postconfig scripts:
	doosconfig=0
	if [ -x $BINDIR/osconfig ]; then
	    $BINDIR/osconfig -c -m /mnt -D $rootdev -s FreeBSD postload
	    if [ $? -eq 0 ]; then
		doosconfig=1
		fixit=1
	    fi
	fi

	#
	# Localize the image. We only do this if the MFS we are running in
	# has the necessary files.
	#
	if islocalized; then
	    # Check the certs.
	    if [ -e $ETCDIR/emulab.pem ]; then
		cmp -s $ETCDIR/emulab.pem /mnt/etc/emulab/emulab.pem
		if [ $? -ne 0 ]; then
		    changecerts=1
		    fixit=1
		fi
	    fi
	    if [ -e $ETCDIR/client.pem ]; then
		cmp -s $ETCDIR/client.pem /mnt/etc/emulab/client.pem
		if [ $? -ne 0 ]; then
		    changecerts=1
		    fixit=1
		fi
	    fi

	    # Root keys are handled by dofixauthkeys() below
	
	    # Check the host keys.
	    for k in "" dsa_ ecdsa_ ed25519_ rsa_; do
		if [ -e /etc/ssh/ssh_host_${k}key ]; then
		    cmp -s /etc/ssh/ssh_host_${k}key /mnt/etc/ssh/ssh_host_${k}key
		    if [ $? -ne 0 ]; then
			changehostkeys=1
			fixit=1
		    fi
		fi
	    done

	    # Check the time zone.
	    if [ -e /etc/localtime ]; then
		cmp -s /etc/localtime /mnt/etc/localtime
		if [ $? -ne 0 ]; then
		    changezone=1
		    fixit=1
		fi
	    fi

	    # Check the NTP config.
	    if [ -e /etc/ntp.conf ]; then
		cmp -s /etc/ntp.conf /mnt/etc/ntp.conf
		if [ $? -ne 0 ]; then
		    changentp=1
		    fixit=1
		fi
	    fi

	    # Check the chrony config.
	    if [ -e /etc/chrony.conf ]; then
		cmp -s /etc/chrony.conf /mnt/etc/chrony.conf
		if [ $? -ne 0 ]; then
		    changechrony=1
		    fixit=1
		fi
	    fi

	fi

	# See if we need to update sshd_config
	if ! dofixsshd 1; then
	    changesshd=1
	    fixit=1
	fi

	# See if we need to update root's .ssh directory
	if ! dofixauthkeys 1; then
	    changeauth=1
	    fixit=1
	fi

	# XXX See if we need to get rid of expired root certificate
	if [ -x $BINDIR/removecert ]; then
	    if $BINDIR/removecert -n -M >/dev/null 2>&1; then
		changercert=1
		fixit=1
	    fi
	fi
	
	if [ $fixit -eq 0 ]; then
		echo "  no changes necessary"
		umount $rootdev
		return 0
	fi

	if [ 0 -eq 1 ]; then
		echo changedisk=$changedisk
		echo changeentropy=$changeentropy
		echo changeacpi=$changeacpi
		echo changeasf=$changeasf
		echo changeclflush=$changeclflush
		echo changecons=$changecons
		echo changecerts=$changecerts
		echo changehostkeys=$changehostkeys
		echo changerootkeys=$changerootkeys
		echo changezone=$changezone
		echo changentp=$changentp
		echo changechrony=$changechrony
		echo changesshd=$changesshd
		echo changeauth=$changeauth
		echo changercert=$changercert
		echo changelconf=$changelconf
		umount $rootdev
		return 0
	fi

	# We need to change something, remount RW and change it
	if [ $OS = FreeBSD ]; then
		umount $rootdev
		mount $rootdev /mnt || {
			echo "Mount of $rootdev failed"
			return 1
		}
	elif [ $OS = Linux ]; then
		mount -o remount,rw $rootdev || {
			echo "Remount of $rootdev failed"
			return 1
		}
	fi

	# hardwire the boss node
	if [ $setboss -eq 1 -a -d /mnt/etc/emulab ]; then
	    echo "  hardwiring boss to $bossnode"
	    cp $ETCDIR/bossnode /mnt/etc/emulab/bossnode || {
	        echo "Failed to create /etc/emulab/bossnode"
		umount $rootdev
		return 1
	    }
	fi

	if [ $changedisk -eq 1 ]; then
	    if [ -f /mnt/etc/fstab ]; then
	    	    echo "  updating /etc/fstab"
		    cp -fp /mnt/etc/fstab /mnt/etc/fstab.preemulab
		    if [ $isgpt -ne 0 ]; then
			# XXX for GPT we need to remove any BSD partition specifier
			# There should only be a root partition specified.
			sed -i -E -e "s;^/dev/[a-zA-Z]+[0-9]+[sp][0-9]+[a-z]*;/dev/${freebsd_disk}${pchar}${part};" /mnt/etc/fstab || {
			    echo "Failed to update /etc/fstab"
			    umount $rootdev
			    return 1
			}
		    else
			# XXX otherwise we need to leave any BSD partition specifier
			sed -i -E -e "s;^/dev/[a-zA-Z]+[0-9]+[sp][0-9]+([a-z]*);/dev/${freebsd_disk}${pchar}${part}\1;" /mnt/etc/fstab || {
			    echo "Failed to update /etc/fstab"
			    umount $rootdev
			    return 1
			}
		    fi
    	    fi
	    if [ -f /mnt/etc/dumpdates ]; then
		    echo "  updating /etc/dumpdates"
		    cp -fp /mnt/etc/dumpdates /mnt/etc/dumpdates.preemulab
		    sed -i -E -e "s;^/dev/[a-zA-Z]+[0-9]+[sp][0-9]+;/dev/${freebsd_disk}${pchar}${part};" /mnt/etc/dumpdates || {
			echo "WARNING: Failed to update /etc/dumpdates"
		    }
	    fi
	    if [ -f /mnt/etc/ttys ]; then
		    echo "  updating /etc/ttys"
		    cp -fp /mnt/etc/ttys /mnt/etc/ttys.preemulab
		    sed -i -e "/^console/s;std.115200;std.$SPEED;" /mnt/etc/ttys || {
			echo "WARNING: Failed to update /etc/ttys"
		    }
	    fi
	fi

	if [ $changeentropy -eq 1 ]; then
	    echo "  updating /entropy"
	    dd if=/dev/random of=/mnt/entropy bs=4096 count=1 >/dev/null 2>&1
	fi

	#
	# Modify /boot/loader.conf.
	#
	# We append all our settings at the end, prefixed by a header line.
	# We also create a copy of the file before we make any modifications.
	# This way the "prepare" script run when creating a new image can
	# remove all these settings easily.
	#
	cp -p /mnt/boot/loader.conf /mnt/boot/loader.conf.preemulab || {
	    echo "WARNING: could not create a copy of loader.conf"
	}
	if [ $changelconf -eq 1 ]; then
	    cat <<EOF4 >>/mnt/boot/loader.conf
# The remaining lines were added by Emulab slicefix.
# DO NOT ADD ANYTHING AFTER THIS POINT AS IT WILL GET REMOVED.
EOF4
	fi

	if [ $changeacpi -eq 1 ]; then
	    if [ $ACPI = "no" ]; then
		on="dis"
	    else
		on="en"
	    fi
	    echo "  ${on}abling ACPI"
	    if [ $ACPI = "no" ]; then
		cat <<EOF1 >>/mnt/boot/loader.conf
# disable ACPI
acpi_load="NO"
hint.acpi.0.disabled=1
loader.acpi_disabled_by_user=1
EOF1
	    else
		cat <<EOF2 >>/mnt/boot/loader.conf
# enable ACPI
acpi_load="YES"
hint.acpi.0.disabled=0
EOF2
	    fi
	fi

	if [ $changeasf -eq 1 ]; then
	    if [ $ASF = "no" ]; then
		on="dis"
	    else
		on="en"
	    fi
	    echo "  ${on}abling ASF"
	    if [ $ASF = "no" ]; then
		cat <<EOF1 >>/mnt/boot/loader.conf
# disable ASF
hw.bge.allow_asf=0
EOF1
	    else
		cat <<EOF2 >>/mnt/boot/loader.conf
# enable ASF
hw.bge.allow_asf=1
EOF2
	    fi
	fi

	#
	# Disable the management interface in the OS.
	#
	if [ -n "$DISABLEHINT" ]; then
	    echo "  disabling interface $DISABLEIF"
	    cat <<EOF0 >>/mnt/boot/loader.conf
# disable dedicated management interface
$DISABLEHINT="1"
EOF0
	fi

	#
	# Fix up console.
	# null and vga are easy, just set console={null,vid}console
	# Serial port requires tweaking/adding hints.
	#
	if [ $changecons -eq 1 ]; then
	    echo "  setting console to $CONSOLE"

	    case $CONSOLE in
	    null|vid)
		cname="${CONSOLE}console"
		;;
	    sio1)
		cname="comconsole"
		unit=0
		irq=4
		;;
	    sio2)
		cname="comconsole"
		unit=1
		irq=3
		;;
	    sio3)
		cname="comconsole"
		unit=2
		irq=5
		;;
	    sio4)
		cname="comconsole"
		unit=3
		irq=9
		;;
	    esac

	    # spit out the name
	    cat <<EOF1 >>/mnt/boot/loader.conf
# setting console to $CONSOLE
console="$cname"
EOF1

	    # now deal with comconsole settings
	    if [ "$cname" = "comconsole" ]; then
		#
		# Determine driver name:
		# if /boot/device.hints does not exist, assume "sio" (pre 5?)
		# ow, if hint.uart.0.at exists, it is "uart" (8+)
		# ow, if hint.sio.0.at exists, it is "sio" (5-7)
		#
		drv=none
		at=`getloadervar hint.uart.0.at /mnt`
		if [ -n "$at" ]; then
		    drv=uart
		else
		    at=`getloadervar hint.sio.0.at /mnt`
		    if [ -n "$at" ]; then
			drv=sio
		    fi
		fi

		# only change things if there were hints found
		if [ $drv != "none" ]; then
		    # put in the new info
		    cat <<EOF2 >>/mnt/boot/loader.conf
comconsole_port="$PORT"
comconsole_speed="$SPEED"
boot_multicons="YES"
boot_serial="YES"
hint.$drv.0.flags="0x0"
hint.$drv.1.flags="0x0"
hint.$drv.2.flags="0x0"
hint.$drv.3.flags="0x0"
hint.$drv.$unit.at="isa"
hint.$drv.$unit.port="$PORT"
hint.$drv.$unit.irq="$irq"
hint.$drv.$unit.flags="0x10"
hint.$drv.$unit.disabled="0"
EOF2
		fi
	    fi
	fi

	#
	# See if we need to change the use of NOCLFLUSH.
	# If explicitly specified, we override any previous setting.
	#
	if [ $NOCLFLUSH != "unknown" ]; then
	    if [ $NOCLFLUSH = "1" ]; then
		on="dis"
	    else
		on="en"
	    fi
	    echo "  ${on}abling use of CLFLUSH"
	    cat <<EOF1 >>/mnt/boot/loader.conf
# ${on}able CLFLUSH (hw.clflush_disable)
hw.clflush_disable=$NOCLFLUSH
EOF1
	fi

	# Copy in new certs
	if [ $changecerts -eq 1 ]; then
	    echo "  updating /etc/emulab/{emulab.pem,client.pem}"

	    if [ ! -d /mnt/etc/emulab ]; then
		mkdir -m 755 /mnt/etc/emulab || {
	            echo "Failed to mkdir /mnt/etc/emulab"
		    umount $rootdev
		    return 1
		}
	    fi
	    cp -p $ETCDIR/emulab.pem $ETCDIR/client.pem /mnt/etc/emulab/ || {
	        echo "Failed to create $ETCDIR/emulab.pem or $ETCDIR/client.pem"
		umount $rootdev
		return 1
	    }
	fi

	# Copy in new root keys handled by dofixauthkeys() below

	# Copy in new host keys
	if [ $changehostkeys -eq 1 ]; then
	    echo "  updating /etc/ssh/hostkeys"

	    if [ ! -d /mnt/etc/ssh ]; then
		mkdir -m 755 /mnt/etc/ssh || {
	            echo "Failed to mkdir /mnt/etc/ssh"
		    umount $rootdev
		    return 1
		}
	    fi
	    cp -p /etc/ssh/ssh_host_* /mnt/etc/ssh/ || {
	        echo "Failed to create /etc/ssh/hostkeys"
		umount $rootdev
		return 1
	    }
	fi

	# Copy in new localtime
	if [ $changezone -eq 1 ]; then
	    echo "  updating /etc/localtime"

	    cp -p /etc/localtime /mnt/etc/localtime || {
	        echo "Failed to create /etc/localtime"
		umount $rootdev
		return 1
	    }
	fi

	# Copy in new NTP config
	if [ $changentp -eq 1 ]; then
	    echo "  updating /etc/ntp.conf"

	    cp -p /etc/ntp.conf /mnt/etc/ntp.conf || {
	        echo "Failed to create /etc/ntp.conf"
		umount $rootdev
		return 1
	    }
	fi

	# Copy in new chrony config
	if [ $changechrony -eq 1 ]; then
	    echo "  updating /etc/chrony.conf"

	    cp -p /etc/chrony.conf /mnt/etc/chrony.conf || {
	        echo "Failed to create /etc/chrony.conf"
		umount $rootdev
		return 1
	    }
	fi

	if [ $changesshd -eq 1 ]; then
	    dofixsshd 0
	fi

	if [ $changeauth -eq 1 ]; then
	    dofixauthkeys 0
	fi

	if [ $changercert -eq 1 ]; then
	    $BINDIR/removecert -M
	fi

	# actually run any postconfig scripts if we're supposed to:
	if [ $doosconfig -eq 1 -a -x $BINDIR/osconfig ]; then
	    $BINDIR/osconfig -m /mnt -D $rootdev -s FreeBSD postload
	fi

	umount $rootdev
	return 0
}

#
# echo the name of the kernel that lilo will boot by default
#
lilogetkernel() {
    _lconf=$1
    _re='[a-zA-Z0-9][-_\.a-zA-Z0-9]*'

    # see if there is a default= line and prefer that
    linux=`grep -E "default=$_re" $_lconf | sed -e "s/default=\($_re\).*/\1/"`

    # no explicit default, use first kernel listed
    if [ "x${linux}" = x ]; then
	linux=`grep -E "label=$_re" $_lconf | head -1 | \
		sed -e "s/label=\($_re\).*/\1/"`
    fi

    # still no hit, just use 'linux'
    linux=${linux:-'linux'}

    echo $linux
}

dolinux() {
	devpath=/dev/$linux_disk$part
	olddevpath=/dev/$old_linux_disk$part
	(echo $linux_disk | grep -q ^nvme) && devpath=/dev/${linux_disk}p$part
	(echo $old_linux_disk | grep -q ^nvme) && olddevpath=/dev/${old_linux_disk}p$part

	$BINDIR/linux_slicefix.pl $devpath $olddevpath

	return $?
}

fixone() {
    if [ $OS = FreeBSD ]; then
    	echo "*** ${disk}${freebsd_pchar}${part}:"
    	ptype=`fdisk -${part} ${disk} | grep sysid | sed 's/^sysid \([0-9][0-9]*\).*/\1/'`
    elif [ $OS = Linux ]; then
	fullpath=${disk}${part}
	(echo ${disk} | grep -q ^nvme) && fullpath=${disk}p${part}
    	echo "*** ${fullpath}:"
	if [ $isgpt -ne 0 ]; then
	    # gdisk output converts the gpt partition ID to, e.g., 8300.
	    ptype=`gdisk -l /dev/$disk | awk "/^ *${part} / {print \\$6}"`
	else
	    # busybox fdisk and "real" fdisk have different output styles
	    isrealfdisk=1
	    fdiskpath=`which fdisk`
	    if [ -L $fdiskpath ]; then
		readlink $fdiskpath | grep -q busybox
		isrealfdisk=$?
	    fi
	    if [ $isrealfdisk -eq 0 ]; then
    		ptype=`fdisk -l /dev/$disk | awk /${fullpath}/'{sub(/\*/,""); print "0x"$8}'`
	    else
    		ptype=`fdisk -l /dev/$disk | awk /${fullpath}/'{sub(/\*/,""); print "0x"$6}'`
	    fi
	fi
    fi
    ptype=${ptype:-0}

    case $ptype in
    165|0xa5|A503)
	dofreebsd
	return $?
	;;
    131|0x83|8300|8304)
	dolinux
	return $?
	;;
    130|0x82|8200)
	echo "ignoring Linux swap partition"
	;;
    7|0x7)
	echo "ignoring NTFS partition"
	;;
    0|0x0)
	echo "ignoring unused partition"
	;;
    EF00)
	echo "ignoring EFI partition"
	;;
    A501)
	echo "ignoring FreeBSD boot partition"
	;;
    *)
	echo "ignoring partition type $ptype"
	;;
    esac
    return 0
}

case $part in
0)
    part=1; fixone
    part=2; fixone
    part=3; fixone
    part=4; fixone

    # when fixing all partitions, do not fail on an error
    if [ $? -ne 0 ]; then
    	echo "WARNING: failed to fix one or more partitions, BEWARE!"
    fi

    # for the CD boot, we need to intialize the magic boot block
    if [ -e /cdrom.inf -a -x $BINDIR/tbbootconfig ]; then
	$BINDIR/tbbootconfig -f -c 1 -k 0 -m 1 /dev/${disk}
    fi
    ;;
1|2|3|4)
    fixone
    ;;
*)
    echo "bad partition: $part"
    false
    ;;
esac

exit $?
