#!/bin/sh
#
# Copyright (c) 2000-2025 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

# 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    

disk="ad0"

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

#
# See if we are GPT or MBR
#
isgpt=0
pchar=s
if gpart show $disk 2>/dev/null | grep -q GPT; then
	isgpt=1
	pchar=p
fi

#
# Handle pseudo arguments in the environment.
#

OLDBSD=${SLICEFIX_OLDBSD:-''}

#
# 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|null|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

# 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

#
# See if we have a cnet interface MAC address
#
CNETMAC=
if [ -e "$BOOTDIR/controlmac" ]; then
    CNETMAC=`cat $BOOTDIR/controlmac`
fi

#
# 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.
LOCALIZED="/.localized"

islocalized() {
    if [ -e $LOCALIZED ]; 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 /usr/bin/stat ]; then
	    _stat=`/usr/bin/stat -f '%u,%g,%p' /mnt/root/.ssh`
	    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 .preemulab \
	    -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
}

#
# XXX tmp hack for custom images.
# Check that the image has a recent "slothd" binary that supports
# collecting data for GPU usage. A lot of heavy GPU users have custom
# images that predate this addition, but we really want to monitor them.
#
dofixslothd() {
    # Check /etc/os-release to see what OS and version it is
    if [ ! -e /mnt/etc/os-release ]; then
	return
    fi
    _os=`grep ^NAME= /mnt/etc/os-release | sed -E -e 's/NAME="?([^"]+)"?/\1/'`
    _vers=`grep ^VERSION_ID= /mnt/etc/os-release | sed -E -e 's/VERSION_ID="?([^"]+)"?/\1/'`
    case $_os in
    Ubuntu*)
	_os=ubuntu
	_vers=${_vers%%.*}
	case $_vers in
	16|18|20)
	    ;;
	*)	
            return
	    ;;
	esac
	;;
    CentOS*)
	_os=centos
	case $_vers in
	7|8)
	    ;;
	*)	
            return
	    ;;
	esac
	;;
    FreeBSD*)
	_os=freebsd
	_vers=${_vers%%.*}
	case $_vers in
	11|12|13)
	    ;;
	*)	
            return
	    ;;
	esac
	;;
    esac
    # Make sure we have a newer version
    _bin="slothd-$_os-$_vers"
    if [ ! -e /etc/emulab/$_bin ]; then
	return
    fi

    _doit=0
    # If slothd doesn't exist, always install it
    if [ ! -x /mnt/usr/local/etc/emulab/slothd ]; then
	_doit=1
    else
	# find will return /etc/emulab/$_bin if newer than the
	# installed version. There may be an easier way to do this...
	_out=`find /etc/emulab/$_bin -newer /mnt/usr/local/etc/emulab/slothd`
	if [ $? -eq 0 -a -n "$_out" ]; then
	    _doit=1
	fi
    fi

    # Install the new version.
    if [ $_doit -ne 0 ]; then
	echo "  installing updated slothd"
	cp -p /etc/emulab/$_bin /mnt/usr/local/etc/emulab/slothd
    fi
}

dofreebsd() {
	#
	# ARGH!  FreeBSD 5, which primarily uses UFS2, internally converts
	# UFS1 filesystems to an updated, UFS2 compatible form.  However,
	# some of the converted fields leak back to disk when the filesystem
	# is unmounted, creating the potential for corruption in the following
	# scenerio:
	#
	# 1. mount, update, and unmount a UFS1 FS under FBSD5
	# 2. mount, update, and unmount the FS under FBSD4
	# 3. go back and access the FS under FBSD5
	#
	# #2 will update the old version of the csum structure.  At step
	# #3, the mount will see some of the leaked info from the unmount
	# in #1 and determine that it has already "upgraded" this FS and
	# thus doesn't need to copy the old csum into the new one.  The
	# result is that it will use stale csum info.
	#
	# In theory we could encounter this problem when we slicefix
	# a newly loaded FBSD image (#1), boot up and update stuff on
	# the image (#2), and then create a new image with imagezip
	# (#3, as imagezip uses the FBSD5 filesystem logic).
	#
	# How do we deal with this?  What we will have to do is either
	# fix the FBSD5 filesystem code or we will have to run a little
	# hack after the slicefix unmount to undo the damage that FBSD5
	# does.  Currently I have not done either.  Instead we avoid the
	# problem as follows.  Before making any change to the FS with
	# slicefix, we first RO-mount the FS to see if anything needs to
	# be done.  If not, we just leave it alone.  Since I currently
	# create all new images on machines with "ad" disks and that is
	# what the default fstab contains for its root device, we avoid
	# making any mods with slicefix and thus avoid #1 above.
	#
	# This is clearly not a solution.
	#
	vers=`uname -v | sed -e 's/FreeBSD \([0-9][0-9]*\).*/\1/'`

	if [ $isgpt -eq 0 ]; then
	    # 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
	    }

	    rootdev=/dev/${disk}s${part}a
	    echo "fixing FreeBSD root partition ${disk}s${part}a"
	    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
		}
	    }
	else
	    rootdev=/dev/${disk}p${part}
	fi

	mount -o ro $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/${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
	umount $rootdev
	mount $rootdev /mnt || {
		echo "Mount of $rootdev failed"
		return 1
	}

	# 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
	    echo "  updating /etc/fstab"
	    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 .preemulab -E -e "s;^/dev/[a-zA-Z]+[0-9]+[sp][0-9]+[a-z]*;/dev/${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 .preemulab -E -e "s;^/dev/[a-zA-Z]+[0-9]+[sp][0-9]+([a-z]*);/dev/${disk}${pchar}${part}\1;" /mnt/etc/fstab || {
		    echo "Failed to update /etc/fstab"
		    umount $rootdev
		    return 1
		}
	    fi
	    if [ -f /mnt/etc/dumpdates ]; then
		echo "  updating /etc/dumpdates"
		sed -i .preemulab -E -e "s;^/dev/[a-zA-Z]+[0-9]+[sp][0-9]+;/dev/${disk}${pchar}${part};" /mnt/etc/dumpdates || {
		    echo "WARNING: Failed to update /etc/dumpdates"
		}
	    fi
	    echo "  updating /etc/ttys"
	    sed -i .preemulab -e "/^console/s;std.115200;std.$SPEED;" /mnt/etc/ttys || {
		echo "WARNING: Failed to update /etc/ttys"
	    }
	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 || {
	    echo "WARNING: unmount of FS failed, beware corruption"
	    sync
	    sleep 2
	    umount $rootdev
	}

	# XXX need to fixup FS, see big comment above
	if [ "x$vers" != "x4" -a -n "$OLDBSD" ]; then
		echo "WARNING: FBSD${vers} may have modified the UFS1 root FS"
	fi

	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() {
	# figure out the linux device name
	dunit=`echo $disk | sed -e 's/..\([0-7]\)/\1/'`
	case $disk in
	# IDE
	ad[0-3])
		dtype="hd"
		d=`echo $dunit | sed -e 'y/0123/048c/'`
		lrootdev="3${d}${part}"
		;;
	# SATA
	ad[4-7])
		dtype="sd"
		dunit=`expr $dunit - 4`
		lrootdev="8${dunit}${part}"
		;;
	ad2[4-7])
		dtype="sd"
		dunit=`expr $dunit - 24`
		lrootdev="8${dunit}${part}"
		;;
	# SATA, new names
	ada[0-9])
		dtype="sd"
		dunit=`echo $disk | sed -e 's/ada\([0-9][0-9]*\)$/\1/'`
		lrootdev="8${dunit}${part}"
		;;
	# NVM disks
	nvd[0-9])
		dtype="nvme"
		dunit=`echo $disk | sed -e 's/nvd\([0-9][0-9]*\)$/\1/'`
		lrootdev="8${dunit}${part}"

		# XXX assume "n1"
		ldisk="${dtype}${dunit}n1p"
		rootdev="/dev/${disk}${pchar}${part}"
		;;
	# SCSI
	da[0-7])
		dtype="sd"
		lrootdev="8${dunit}${part}"
		;;
	# PERC
	mfid[0-9]|mfisyspd[0-9])
		dtype="sd"
		dunit=`echo $disk | sed -e 's/.*\([0-9]\)$/\1/'`
		lrootdev="8${dunit}${part}"
		;;
	# Adaptec
	aacd[0-7])
		dunit=`echo $disk | sed -e 's/aacd\([0-7]\)/\1/'`
		dtype="sd"
		lrootdev="8${dunit}${part}"
		;;
	# Megaraid
	amrd[0-7])
		dunit=`echo $disk | sed -e 's/amrd\([0-7]\)/\1/'`
		dtype="sd"
		lrootdev="8${dunit}${part}"
		;;
	*)
		echo "slicefix: linux: unknown disk $disk"
		return 1
		;;
	esac

	dunit=`echo $dunit | sed -e 'y/0123456789/abcdefghij/'`;
	if [ "$dtype" != "nvme" ]; then
	    ldisk=$dtype$dunit
	    rootdev=/dev/${disk}${pchar}${part}
	fi

	#
	# XXX Ugh, we seem to be facing any number of FS issues. Now we
	# are seeing cases where the image is not captured cleanly.
	# In an attempt to compensate for this, we do a precautionary
	# fsck here out of an abundance of caution.
	#
	echo "  running precautionary fsck on $rootdev"
	e2fsck -p $rootdev >/dev/null 2>&1
	if [ $? -ne 0 ]; then
	    echo "WARNING: loaded image had filesystem problems (exit $?)."
	fi
	
	needfsck=0

	#
	# XXX FreeBSD 9.x had a buggy implementation of support for the
	# Linux dir_index feature that could cause filesystem corruption.
	# If we are a FreeBSD 9.x kernel and that feature is enabled,
	# we turn off the feature, make our changes, reenable the feature
	# and then run e2fsck to re-index the directories.
	#
	vers=`uname -v | sed -e 's/FreeBSD \([0-9][0-9]*\).*/\1/'`
	GOTDI=""
	if [ -x /usr/local/sbin/dumpe2fs -a "x$vers" = "x9" ]; then
	    GOTDI=`dumpe2fs -h $rootdev 2>/dev/null | grep '^Filesystem features:.*dir_index'`
	    if [ -n "$GOTDI" ]; then
		echo "  temporarily disabling EXT dir_index"
		tune2fs -O '^dir_index' $rootdev
		needfsck=1
	    fi
	fi

	#
	# See if there is a valid filesystem UUID to use in fstab and grub.
	#
	RUUID=""
	if [ -x /usr/local/sbin/dumpe2fs ]; then
	    RUUID=`dumpe2fs $rootdev 2>/dev/null | grep UUID: | sed -e 's/.*UUID:  *\([^ ]*\).*/\1/'`
	    #
	    # To be pedantic, we should do the following (generate a new
	    # UUID, but this seems to be causing strange problems with the
	    # UUID not always being updated. It will also cause the root FS
	    # to be fsck'ed everytime (see needfsck below).
	    #
	    if false && [ -n "$RUUID" -a -x /usr/local/sbin/tune2fs ]; then
		# generate/install a new random UUID with tune2fs
		echo "  setting new root FS UUID"
		tune2fs -U random $rootdev 2>/dev/null
		RUUID=`dumpe2fs $rootdev 2>/dev/null | grep UUID: | sed -e 's/.*UUID:  *\([^ ]*\).*/\1/'`
	    fi
	fi

	#
	# Turn off time-dependent fsck.
	#
	if  [ -x /usr/local/sbin/tune2fs ]; then
	    echo "  disabling time-dependent fsck"
	    tune2fs -i 0 $rootdev >/dev/null 2>&1
	    if [ $? -ne 0 ]; then
	        echo "  WARNING: could not disable time-dependent fsck"
	    fi
	fi

	#
	# Attempt to mount filesystem   
	#
	echo "  fixing Linux root partition ${disk}${pchar}${part}"
	mount -t ext2fs $rootdev /mnt || {
	    # might need a journal recovery, try fsck
	    echo "EXT2FS mount failed, trying recovery..."
	    e2fsck -yf $rootdev
	    mount -t ext2fs $rootdev /mnt || {
		echo "EXT2FS mount failed"
		return 1
	    }
	}

	# 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

	# change the devices in fstab
	echo "  updating /etc/fstab"
	sed -i .preemulab -e "s;^/dev/[hs]d.\([0-9]\);/dev/${ldisk}\1;" /mnt/etc/fstab || {
	    echo "Failed to update /etc/fstab"
	    umount $rootdev
	    return 1
	}
	# XXX handle NVMe too...
	sed -i '' -e "s;^/dev/nvme.n1p\([0-9]\);/dev/${ldisk}\1;" /mnt/etc/fstab || {
	    echo "Failed to update /etc/fstab"
	    umount $rootdev
	    return 1
	}

	#
	# If filesystem has a UUID, make sure that is used in fstab.
	#
	# Note that we will only use the UUID if it is already being used
	# in the image we just downloaded. This is just because I don't want
	# to chance breaking some really old image.
	#
	if [ -n "$RUUID" ]; then
	    sed -E -i '' -e "s;^UUID=[^ ]+ +/ ;UUID=$RUUID / ;" /mnt/etc/fstab || {
		echo "Failed to update /etc/fstab"
		umount $rootdev
		return 1
	    }
	fi
	
	#
	# If there's a GRUB config file, update all root=X kernel params.
	# Also fix up console parameters.
	#
	fgconf=""
	for d in grub2 grub; do
	    for f in grub.conf grub.cfg menu.lst; do
		if [ -z "$fgconf" -a -r /mnt/boot/$d/$f ]; then
		    fgconf=/boot/$d/$f
		    gconf=/mnt$fgconf
		fi
	    done
	done
	if [ -n "$fgconf" -a -r "$gconf" ]; then
	    echo "  updating $fgconf"

	    #
	    # We go out of our way to avoid changing grub.conf if we don't
	    # have to, since it might require us to fsck the filesystem
	    # afterward (see "XXX woeful hack" below).
	    #
	    tgconf="$gconf.tmp"
	    rm -f $tgconf
	    cp $gconf $tgconf || {
		echo "Cannot copy $fgconf, will change in place"
		cp -p $gconf $gconf.preemulab
		needfsck=1
		tgconf=gconf
	    }

	    #
	    # Remove any emulabcnet=nn:nn:nn:nn:nn:nn clauses.
	    # We will put them back with the correct value as needed.
	    #
	    if grep -q 'emulabcnet=' $tgconf 2>/dev/null; then
		echo "    removing old emulabcnet lines"
		sed -E -i '' -e 's; ?emulabcnet=[0-9a-fA-F:]+;;g' $tgconf
	    fi

	    # see if we need to add emulabcnet= Linux command line option
	    cnetstr=""
	    if [ -n "$CNETMAC" ]; then
		cnetstr=" emulabcnet=$CNETMAC"
		echo "    setting emulabcnet"
	    fi

	    # fix any (non-commented) boot=/root= lines
	    if grep -q '^[^#].*[br]oot=/dev' $tgconf 2>/dev/null; then
		echo "    fixing root (dev)"
		sed -i '' -e "s;\([br]oot=/dev\)/[hs]d.[0-7];\1/${ldisk}${part}${cnetstr};" $tgconf
	    fi

	    # Ditto for UUID if we have one and grub conf uses it
	    if [ -n "$RUUID" ] && \
		grep -q -E '[br]oot=UUID=[^ ]+ ' $tgconf 2>/dev/null; then
		OUUID=`grep -E -m 1 '[br]oot=UUID=[^ ]+ ' $tgconf | \
			sed -e 's;.*UUID=\([^ ]*\) .*;\1;'`
		if [ -n "$OUUID" -a $RUUID != $OUUID ]; then
		    echo "    fixing root (UUID)"
		    sed -i '' -e "s;$OUUID;$RUUID;g" $tgconf
		fi
		# XXX need to make a second pass to put emulabcnet string on
		# just the root=UUID= lines, not everywhere the UUID appears
		if [ -n "$OUUID" -a -n "$cnetstr" ]; then
		    sed -i '' -e "s;\(root=UUID=$RUUID\);\1$cnetstr;" $tgconf
		fi
	    fi

	    #
	    # Tweak grub's notion of root.
	    #   If BIOSDISK is set, we prefer that.
	    #   Otherwise (for compat), we check FLASHBOOT.
	    #   Otherwise, map from DISK.
	    #
	    if [ -n "$BIOSDISK" ]; then
		gdisk=`echo $BIOSDISK | sed -e 's/0x8\([0-9]\)/\1/'`
	    else
		gdisk=`echo $dunit | sed -e 'y/abcdefghij/0123456789/'`
		#
		# If we booted from a flash device we also have to further
		# tweak grub's notion of root disk since the flash device
		# will be "hd0". It appears that we do NOT have to update
		# Linux's drive mapping.
		#
		if [ $FLASHBOOT -eq 1 ]; then
	            gdisk=`expr $gdisk + 1`
		fi
	    fi

	    # For XEN, dom0 mem is hardwired on kernel command line.
	    if [ -n "$DOM0MEM" ]; then
		if grep -q 'dom0_mem=' $tgconf 2>/dev/null; then
		    echo "    setting grub2 dom0_mem to $DOM0MEM"
		    tstr="dom0_mem=$DOM0MEM"
		    sed -i '' -e "s;dom0_mem=[A-Za-z0-9]*;$tstr;" $tgconf
		fi
	    fi
	    
	    # grub's partition numbering is zero-based.
	    gpart=`expr $part - 1`

	    # grub1
	    if grep -q '^[^#].*root (hd[0-9],[0-9])' $tgconf 2>/dev/null; then
		echo "    setting grub1 root to (hd${gdisk},${gpart})"
		sed -i '' -e "s;root (hd[0-9],[0-9]);root (hd${gdisk},${gpart});" $tgconf
	    fi

	    # grub2 (note 'part' and not 'gpart'; no -1 in grub2)
	    if grep -q "^[^#].*set root='(hd[0-9],[^)]*)'" $tgconf 2>/dev/null; then
		echo "    setting grub2 root to (hd${gdisk},${part})"
		sed -i '' -e "s;set root='(hd[0-9],[^)]*)';set root='(hd${gdisk},${part})';" $tgconf
	    fi
	    # maybe no parens around the expression
	    if grep -q "^[^#].*set root='hd[0-9],[^']*'" $tgconf 2>/dev/null; then
		echo "    setting grub2 root to hd${gdisk},${part}"
		sed -i '' -e "s;set root='hd[0-9],[^']*';set root='hd${gdisk},${part}';" $tgconf
	    fi

	    #
	    # Console.
	    #
	    # For vid or null, comment out terminal/serial lines and
	    # fix up any terminal_{input,output} lines (for grub2).
	    #
	    # For sio[1-3], add:
	    #   serial --unit=N --port=P --speed=S
	    #   terminal --dumb --timeout=0 serial console # grub1
	    #   terminal_input serial			   # grub2
	    #   terminal_output serial			   # grub2
	    #
	    # XXX we have a situation where SOL redirection puts the console
	    # at --unit=1, but grub does not recognize that as a valid unit
	    # and the result is that grub does not accept console input.
	    # But if we specify --port=0x2F8 it works fine. So we put both
	    # (this is documented, --port takes precedence).
	    #
	    echo "    setting console to $CONSOLE"
	    pstr=""
	    case $CONSOLE in
	    null|vid)
		# comment out any "serial" line
		if grep -q '^serial' $tgconf 2>/dev/null; then
		    sed -i '' -e 's;^serial;#serial;' $tgconf
		fi
		# make sure the "terminal" line is correct
		if grep -q '^terminal ' $tgconf 2>/dev/null; then
		    tstr="terminal --timeout=5 console"
		    sed -i '' -e "s;^terminal.*;$tstr;" $tgconf
		fi
		# or terminal_{input,output} for grub2
		if grep -q '^terminal_input' $tgconf 2>/dev/null; then
		    tstr="terminal_input console"
		    sed -i '' -e "s;^terminal_input.*;$tstr;" $tgconf
		fi
		if grep -q '^terminal_output' $tgconf 2>/dev/null; then
		    tstr="terminal_output console"
		    sed -i '' -e "s;^terminal_output.*;$tstr;" $tgconf
		fi
		# and fixup kernel/initrd command lines
		if grep -q 'console=tty0 ' $tgconf 2>/dev/null; then
		    # already have console=tty0, get rid of any console=ttyS? clauses
		    sed -E -i '' -e 's;console=ttyS[^ ]+;;' $tgconf
		else
		    # otherwise, replace any console=ttyS? clauses
		    sed -E -i '' -e 's;console=ttyS[^ ]+;console=tty0;' $tgconf
		fi
		# Xen command line uses vga
		if grep -q 'console=vga ' $tgconf 2>/dev/null; then
		    # already have console=vga, get rid of any console=com? clauses
		    sed -E -i '' -e 's;console=com[^ ]+;;' $tgconf
		    sed -E -i '' -e 's;com[^=]+=[^ ]+;;' $tgconf
		else
		    # otherwise, replace any console=com? clauses
		    sed -E -i '' -e 's;console=com[^ ]+;console=vga;' $tgconf
		    sed -E -i '' -e 's;com[^=]+=[^ ]+;;' $tgconf
		fi

		# take care of upstart getty process
		# XXX cannot use .override as old version don't support it
		getty="/mnt/etc/init/ttyS0"
		if [ -e "$getty.conf" ]; then
		    mv $getty.conf $getty.conf.preemulab
		fi

		# or inittab
		getty="/mnt/etc/inittab"
		if [ -e "$getty" ]; then
		    cp -p $getty $getty.preemulab
		    sed -i '' -e 's/^S0/#S0/' $getty
		fi
		;;
	    sio*)
		u=`expr ${CONSOLE#sio} - 1`
		s=$SPEED
		pstr="--port=$PORT"

		# put back the "serial" line
		sstr="serial --unit=$u $pstr --speed=$s"
		if ! grep -q -- "^$sstr" $tgconf 2>/dev/null; then
		    sed -E -i '' -e "s;^#?serial.*;$sstr;" $tgconf
		fi
		# make sure the "terminal" line is correct
		if grep -q '^terminal ' $tgconf 2>/dev/null; then
		    tstr="terminal --dumb --timeout=0 serial console"
		    sed -i '' -e "s;^terminal.*;$tstr;" $tgconf
		fi
		# or terminal_{input,output} for grub2
		if grep -q '^terminal_input' $tgconf 2>/dev/null; then
		    tstr="terminal_input serial"
		    sed -i '' -e "s;^terminal_input.*;$tstr;" $tgconf
		fi
		if grep -q '^terminal_output' $tgconf 2>/dev/null; then
		    tstr="terminal_output serial"
		    sed -i '' -e "s;^terminal_output.*;$tstr;" $tgconf
		fi
		# and fixup kernel/initrd command lines
		if grep -q "console=ttyS[^ ]" $tgconf 2>/dev/null; then
		    # already have console=ttyS?, make sure it is correct
		    sed -E -i '' -e "s;console=ttyS[^ ]+;console=ttyS$u,$s;" $tgconf
		    # and remove console=tty0
		    sed -i '' -e 's;console=tty0;;' $tgconf
		else
		    # otherwise, replace console=tty0
		    sed -i '' -e "s;console=tty0;console=ttyS$u,$s;" $tgconf
		fi
		# and fixup Xen command lines
		xu=`expr $u + 1`
		if grep -q "console=com[^ ]" $tgconf 2>/dev/null; then
		    # already have console=com?, make sure it is correct
		    sed -E -i '' -e "s;console=com[^ ]+;console=com$xu;" $tgconf
		    sed -E -i '' -e "s;com[^=]+=[^ ]+;com$xu=$s;" $tgconf
		    # and remove console=vga
		    sed -i '' -e 's;console=vga;;' $tgconf
		else
		    # otherwise, replace console=vga
		    sed -i '' -e "s;console=vga;com$xu=$s console=com$xu;" $tgconf
		fi

		# take care of upstart getty process
		getty="/mnt/etc/init/ttyS0"
		if [ -e "$getty.conf" ]; then
		    cp -p $getty.conf $getty.conf.preemulab
		    # XXX wtf, apparently port/baud can be in either order
		    #     helluva way to run a command line...
		    sed -E -i '' -e "s/ttyS. [0-9]+ /ttyS$u $s /" -e "s/[0-9]+ ttyS. /$s ttyS$u /" $getty.conf
		elif [ -e "$getty.conf.preemulab" ]; then
		    # we previously moved it out of the way
		    cp -p $getty.conf.preemulab $getty.conf
		    # XXX see previous wtf
		    sed -E -i '' -e "s/ttyS. [0-9]+ /ttyS$u $s /" -e "s/[0-9]+ ttyS. /$s ttyS$u /" $getty.conf
		fi

		# or inittab
		getty="/mnt/etc/inittab"
		if [ -e "$getty" ]; then
		    cp -p $getty $getty.preemulab
		    sed -E -i '' -e 's/^#S0/S0/' -e "s/ttyS. [0-9]+/ttyS$u $s/" $getty
		fi
		;;
	    esac

	    #
	    # If we had to change something, then move the new version into
	    # place and mark that we did so.
	    #
	    if [ "$tgconf" != "$gconf" ]; then
		cmp -s $tgconf $gconf 2>/dev/null
		if [ $? -ne 0 ]; then
		    cp -p $gconf $gconf.preemulab
		    mv $tgconf $gconf || {
			echo "Failed to update $fgconf"
			mv $gconf.preemulab $gconf
		    }
		    needfsck=1
		fi
		rm -f $tgconf
	    fi

	    #
	    # Handle default settings file for grub since package installation
	    # might cause the grub.cfg file to get recreated.
	    #
	    # We just append variable definitions to the end of the file and
	    # override any existing settings. This would only adversely affect
	    # the GRUB_CMDLINE_LINUX param if it was being used for some other
	    # options.
	    #
	    # For vid or null we add:
	    #
	    # GRUB_CMDLINE_LINUX="console=tty0 emulabcnet=CNETMAC"
	    # GRUB_TERMINAL=console
	    # GRUB_SERIAL_COMMAND=""
	    #
	    # and for sio[1-3] we add:
	    #
	    # GRUB_CMDLINE_LINUX="console=ttySN,S emulabcnet=CNETMAC"
	    # GRUB_TERMINAL=serial
	    # GRUB_SERIAL_COMMAND="serial --speed=S --unit=N"
	    #
	    # XXX Need to put in Xen lines:
	    #
	    # GRUB_CMDLINE_XEN="gnttab_max_nr_frames=64 loglvl=all guest_loglvl=all com2=115200,8n1 console=com2 dom0_mem=4096M"
	    # GRUB_CMDLINE_LINUX_XEN_REPLACE="console=hvc0 earlyprintk=xen net.ifnames=0"
	    #
	    gdef="/mnt/etc/default/grub"
	    if [ -e "$gdef" ]; then
		echo "  updating /etc/default/grub"
		echo "    setting console to $CONSOLE"
		esig="# The remaining lines were added by Emulab slicefix"
		if ! grep -q "^$esig" $gdef; then
		    cp -p $gdef $gdef.preemulab
		else
		    sed -i '' -e "/^$esig/,$$d" $gdef
		fi
		cat <<EOF5 >>$gdef
$esig
# DO NOT ADD ANYTHING AFTER THIS POINT AS IT WILL GET REMOVED.
EOF5
		case $CONSOLE in
		null|vid)
		    cat <<EOF6 >>$gdef
GRUB_CMDLINE_LINUX="console=tty0$cnetstr"
GRUB_TERMINAL=console
GRUB_SERIAL_COMMAND=""
EOF6
		    ;;
		sio*)
		    u=`expr ${CONSOLE#sio} - 1`
		    s=$SPEED
		    cat <<EOF7 >>$gdef
GRUB_CMDLINE_LINUX="console=ttyS$u,$s$cnetstr"
GRUB_TERMINAL=serial
GRUB_SERIAL_COMMAND="serial --unit=$u $pstr --speed=$s"
EOF7
		    ;;
		esac
		needfsck=1
	    fi
	fi

	#
	# If using lilo, update lilo.conf and set indicator to tell Emulab
	# to rerun lilo.
	#
	linux=
	lconf=/mnt/etc/lilo.conf
	if [ -r $lconf ]; then
	    echo "  updating /etc/lilo.conf"
	    sed -i .preemulab -e "s;\([br]oot=/dev\)/[hs]d.[0-7];\1/${ldisk}${part};" $lconf || {
		    echo "Failed to update /etc/lilo.conf"
		    umount $rootdev
		    return 1
	    }

	    # XXX note the hardwired path, cannot use BOOTDIR as it is
	    # different in frisbee MFS.  This also assumes Linux partition
	    # is one big filesystem.
	    cp /dev/null /mnt/var/emulab/boot/runlilo

	    # grab the default linux kernel to boot
	    linux=`lilogetkernel $lconf`
	fi

	#
	# Blacklist the hp ilo stuff.
	#
	if [ -d "/mnt/etc/modprobe.d" ]; then
	    modules="hpilo hpwdt"
	    for mod in ${modules}; do
		grep $mod /mnt/etc/modprobe.d/blacklist.conf >/dev/null 2>&1
		case $? in
		0)
		    ;;
		*)
		    echo "  blacklisting $mod"
                    echo "blacklist $mod" >> /mnt/etc/modprobe.d/blacklist.conf
	            ;;
		esac
            done
	fi

	if [ -f /mnt/var/lib/random-seed -a -c /dev/urandom ]; then
	    echo "  updating /var/lib/random-seed"
	    dd if=/dev/urandom of=/mnt/var/lib/random-seed bs=512 count=1 >/dev/null 2>&1
	fi

	#
	# Fixup sshd config
	#
	if ! dofixsshd 1; then
	    dofixsshd 0
	fi

	#
	# Fixup root authorized keys
	#
	if ! dofixauthkeys 1; then
	    dofixauthkeys 0
	fi

	#
	# Handle one-off expired root certificate
	#
	if [ -x $BINDIR/removecert ]; then
	    echo "  checking for expired CA root certificate"
	    $BINDIR/removecert -M
	fi

	#
	# XXX update slothd binary
	#
	dofixslothd

	#
	# Localize the image. We only do this if the MFS we are running in
	# has the necessary files.
	#
	if islocalized; then
	    echo "  localizing ..."
	    $BINDIR/localize_image /mnt || {
		umount $rootdev
		return 1
	    }
	fi

	# run any postconfig scripts:
	if [ -x $BINDIR/osconfig ]; then
	    echo "  checking for dynamic client-side updates to slice";
	    $BINDIR/osconfig -m /mnt -M '-t ext2fs' -f ext2fs \
		-D $rootdev -s Linux postload
	fi

	umount $rootdev || {
	    echo "WARNING: unmount of FS failed, beware corruption"
	    sync
	    sleep 2
	    umount $rootdev
	}

	#
	# There are a couple of conditions under which we might need to fsck:
	#
	# XXX woeful hack I: on an SELinux-enabled filesystem, grub.conf
	# might have an SELinux context and the BSD extfs implmentation
	# doesn't know how to deal with extended attributes and leaves
	# the FS in an inconsistent state. So if we changed grub.conf,
	# we must fsck the filesystem to clean up.
	#
	# XXX woeful hack II: on a FreeBSD 9.x MFS and an EXT filesystem
	# with the dir_index feature set ($GOTDI != ""), we had to disable
	# that feature above to avoid filesystem corruption. We need to
	# re-enable that feature and clean up the filesystem.
	#
	if [ $needfsck -ne 0 ]; then
	    if [ -n "$GOTDI" ]; then
		echo "  reeabling EXT dir_index"
		tune2fs -O dir_index $rootdev || {
		    echo "WARNING: could not reenable EXT dir_index"
		    needfsck=0
		}
	    elif [ ! -e /mnt/etc/selinux/config ]; then
		needfsck=0
	    fi
	fi

	if [ $needfsck -ne 0 ]; then
	    echo -n "  ensuring FS is consistent..."
	    e2fsck -yf $rootdev >/dev/null 2>&1
	    if [ $? -ne 0 ]; then
		echo "FIXED"
	    else
		echo "OK"
	    fi
	fi

	if [ "x${linux}" != x ]; then
	    echo -n "  "
	    # setup one time lilo command line, must be done after unmount
	    $BINDIR/groklilo -c "$linux root=$lrootdev" $part /dev/$disk || {
		echo "Failed to set LILO command line"
		return 1
	    }
	fi

	return 0
}

fixone() {
    if [ -n "$SLICEFIX_CLOUDINIT" -a "$SLICEFIX_CLOUDINIT" != "0" ]; then
	echo "WARNING: cloud-init not supported from FreeBSD MFS yet!"
    fi

    echo "*** ${disk}${pchar}${part}:"
    if [ $isgpt -eq 0 ]; then
	ptype=`fdisk -${part} ${disk} | grep sysid | \
	    sed 's/^sysid \([0-9][0-9]*\).*/\1/'`
    else
	# Human readable gpart output...Bah! Who knew this could be such fun?
	ptype=`gpart show $disk | \
	    grep -E " +[0-9]+ +[0-9]+ +$part +[^ ]+" | \
	    sed -E "s/ +[0-9]+ +[0-9]+ +$part +([^ ]+).*/\1/"`
    fi
    ptype=${ptype:-0}

    case $ptype in
    165|freebsd-ufs)
	echo "fixing FreeBSD OS partition"
	dofreebsd
	return $?
	;;
    131|linux-data)
	echo "fixing Linux OS partition"
	dolinux
	return $?
	;;
    130|linux-swap)
	echo "ignoring Linux swap partition"
	;;
    7|ms-basic-data)
	echo "ignoring NTFS partition"
	;;
    0)
	echo "ignoring unused/non-existent partition $part"
	;;
    *)
	echo "ignoring partition $part 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 $?
