#!/usr/bin/env bash
#-
# Copyright © 2010
#	Waldemar Brodkorb <wbx@openadk.org>
#	Thorsten Glaser <tg@mirbsd.org>
#
# Provided that these terms and disclaimer and all copyright notices
# are retained or reproduced in an accompanying document, permission
# is granted to deal in this work without restriction, including un‐
# limited rights to use, publicly perform, distribute, sell, modify,
# merge, give away, or sublicence.
#
# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
# the utmost extent permitted by applicable law, neither express nor
# implied; without malicious intent or gross negligence. In no event
# may a licensor, author or contributor be held liable for indirect,
# direct, other damage, loss, or other issues arising in any way out
# of dealing in the work, even if advised of the possibility of such
# damage or existence of a defect, except proven that it results out
# of said person’s immediate fault when using the work as intended.
#
# Alternatively, this work may be distributed under the terms of the
# General Public License, any version, as published by the Free Soft-
# ware Foundation.
#-
# Prepare a USB stick or CF/SD/MMC card or hard disc for installation
# of OpenADK:
# • install a Master Boot Record containing a MirBSD PBR loading GRUB
# • write GRUB2 core.img just past the MBR
# • create a root partition with ext2fs and extract the OpenADK image
#   just built there
# • create a cfgfs partition

TOPDIR=$(pwd)
me=$0

case :$PATH: in
(*:$TOPDIR/bin/tools:*) ;;
(*) export PATH=$PATH:$TOPDIR/bin/tools ;;
esac

test -n "$KSH_VERSION" || if ! which mksh >/dev/null 2>&1; then
	make package=mksh fetch || exit 1
	df=$(cd package/mksh; TOPDIR="$TOPDIR" gmake show=DISTFILES)
	mkdir -p build_mksh
	gzip -dc dl/"$df" | (cd build_mksh; cpio -mid)
	cd build_mksh/mksh
	bash Build.sh -r || exit 1
	cp mksh "$TOPDIR"/bin/tools/
	cd "$TOPDIR"
	rm -rf build_mksh
fi

test -n "$KSH_VERSION" || exec mksh "$me" "$@"
if test -z "$KSH_VERSION"; then
	echo >&2 Fatal error: could not run myself with mksh!
	exit 255
fi

### run with mksh from here onwards ###

me=${me##*/}

if (( USER_ID )); then
	print -u2 Installation is only possible as root!
	exit 1
fi

TOPDIR=$(realpath .)
ostype=$(uname -s)

cfgfs=1
quiet=0
serial=0
speed=115200
panicreboot=10

function usage {
cat >&2 <<EOF
Syntax: $me [-c cfgfssize] [-p panictime] [±q] [-s serialspeed]
    [±t] /dev/sdb image
Defaults: -c 1 -p 10 -s 115200; -t = enable serial console
EOF
	exit $1
}

while getopts "c:hp:qs:t" ch; do
	case $ch {
	(c)	if (( (cfgfs = OPTARG) < 0 || cfgfs > 5 )); then
			print -u2 "$me: -c $OPTARG out of bounds"
			exit 1
		fi ;;
	(h)	usage 0 ;;
	(p)	if (( (panicreboot = OPTARG) < 0 || panicreboot > 300 )); then
			print -u2 "$me: -p $OPTARG out of bounds"
			exit 1
		fi ;;
	(q)	quiet=1 ;;
	(+q)	quiet=0 ;;
	(s)	if [[ $OPTARG != @(96|192|384|576|1152)00 ]]; then
			print -u2 "$me: serial speed $OPTARG invalid"
			exit 1
		fi
		speed=$OPTARG ;;
	(t)	serial=1 ;;
	(+t)	serial=0 ;;
	(*)	usage 1 ;;
	}
done
shift $((OPTIND - 1))

(( $# == 2 )) || usage 1

f=0
tools='mke2fs tune2fs'
case $ostype {
(DragonFly|*BSD*)
	;;
(Darwin)
	tools="$tools fuse-ext2"
	;;
(Linux)
	;;
(*)
	print -u2 Sorry, not ported to the OS "'$ostype'" yet.
	exit 1
	;;
}
for tool in $tools; do
	print -n Checking if $tool is installed...
	if whence -p $tool >/dev/null; then
		print " okay"
	else
		print " failed"
		f=1
	fi
done
(( f )) && exit 1

tgt=$1
src=$2

if [[ ! -b $tgt ]]; then
	print -u2 "'$tgt' is not a block device, exiting"
	exit 1
fi
if [[ ! -f $src ]]; then
	print -u2 "'$src' is not a file, exiting"
	exit 1
fi
(( quiet )) || print "Installing $src on $tgt."

case $ostype {
(DragonFly|*BSD*)
	basedev=${tgt%c}
	tgt=${basedev}c
	part=${basedev}i
	match=\'${basedev}\''[a-p]'
	function mount_ext2fs {
		mount -t ext2fs "$1" "$2"
	}
	;;
(Darwin)
	basedev=$tgt
	part=${basedev}s1
	match=\'${basedev}\''?(s+([0-9]))'
	function mount_ext2fs {
		fuse-ext2 "$1" "$2" -o rw+
		sleep 3
	}
	;;
(Linux)
	basedev=$tgt
	part=${basedev}1
	match=\'${basedev}\''+([0-9])'
	function mount_ext2fs {
		mount -t ext2 "$1" "$2"
	}
	;;
}

mount |&
while read -p dev rest; do
	eval [[ \$dev = $match ]] || continue
	print -u2 "Block device $tgt is in use, please umount first."
	exit 1
done

if (( !quiet )); then
	print "WARNING: This will overwrite $basedev - type Yes to continue!"
	read x
	[[ $x = Yes ]] || exit 0
fi

dksz=$(dkgetsz "$tgt")
heads=64
secs=32
(( cyls = dksz / heads / secs ))
if (( cyls < (cfgfs + 2) )); then
	print -u2 "Size of $tgt is $dksz, this looks fishy?"
	exit 1
fi

if stat -qs .>/dev/null 2>&1; then
	statcmd='stat -f %z'	# BSD stat (or so we assume)
else
	statcmd='stat -c %s'	# GNU stat
fi

if ! T=$(mktemp -d /tmp/openadk.XXXXXXXXXX); then
	print -u2 Error creating temporary directory.
	exit 1
fi
tar -xOzf "$src" usr/share/grub-bin/core.img >"$T/core.img"
integer coreimgsz=$($statcmd "$T/core.img")
if (( coreimgsz < 1024 )); then
	print -u2 core.img is probably too small: $coreimgsz
	rm -rf "$T"
	exit 1
fi
if (( coreimgsz > 65024 )); then
	print -u2 core.img is larger than 64K-512: $coreimgsz
	rm -rf "$T"
	exit 1
fi
(( coreendsec = (coreimgsz + 511) / 512 ))
if [[ $basedev = /dev/svnd+([0-9]) ]]; then
	# BSD svnd0 mode: protect sector #1
	corestartsec=2
	(( ++coreendsec ))
	corepatchofs=$((0x614))
else
	corestartsec=1
	corepatchofs=$((0x414))
fi
# partition offset: at least coreendsec+1 but aligned on a multiple of secs
(( partofs = ((coreendsec / secs) + 1) * secs ))

(( quiet )) || print Preparing MBR and GRUB2...
dd if=/dev/zero of="$T/firsttrack" count=$partofs 2>/dev/null
echo $corestartsec $coreendsec | mksh "$TOPDIR/scripts/bootgrub.mksh" \
    -A -g $((cyls-cfgfs)):$heads:$secs -M 1:0x83 -O $partofs | \
    dd of="$T/firsttrack" conv=notrunc 2>/dev/null
dd if="$T/core.img" of="$T/firsttrack" conv=notrunc seek=$corestartsec \
    2>/dev/null
# set partition where it can find /boot/grub
print -n '\0\0\0\0' | \
    dd of="$T/firsttrack" conv=notrunc bs=1 seek=$corepatchofs 2>/dev/null

# create cfgfs partition (mostly taken from bootgrub.mksh)
set -A thecode
typeset -Uui8 thecode
mbrpno=0
set -A g_code $cyls $heads $secs
(( psz = g_code[0] * g_code[1] * g_code[2] ))
(( pofs = (cyls - cfgfs) * g_code[1] * g_code[2] ))
set -A o_code	# g_code equivalent for partition offset
(( o_code[2] = pofs % g_code[2] + 1 ))
(( o_code[1] = pofs / g_code[2] ))
(( o_code[0] = o_code[1] / g_code[1] + 1 ))
(( o_code[1] = o_code[1] % g_code[1] + 1 ))
# boot flag; C/H/S offset
thecode[mbrpno++]=0x00
(( thecode[mbrpno++] = o_code[1] - 1 ))
(( cylno = o_code[0] > 1024 ? 1023 : o_code[0] - 1 ))
(( thecode[mbrpno++] = o_code[2] | ((cylno & 0x0300) >> 2) ))
(( thecode[mbrpno++] = cylno & 0x00FF ))
# partition type; C/H/S end
(( thecode[mbrpno++] = 0x88 ))
(( thecode[mbrpno++] = g_code[1] - 1 ))
(( cylno = g_code[0] > 1024 ? 1023 : g_code[0] - 1 ))
(( thecode[mbrpno++] = g_code[2] | ((cylno & 0x0300) >> 2) ))
(( thecode[mbrpno++] = cylno & 0x00FF ))
# partition offset, size (LBA)
(( thecode[mbrpno++] = pofs & 0xFF ))
(( thecode[mbrpno++] = (pofs >> 8) & 0xFF ))
(( thecode[mbrpno++] = (pofs >> 16) & 0xFF ))
(( thecode[mbrpno++] = (pofs >> 24) & 0xFF ))
(( pssz = psz - pofs ))
(( thecode[mbrpno++] = pssz & 0xFF ))
(( thecode[mbrpno++] = (pssz >> 8) & 0xFF ))
(( thecode[mbrpno++] = (pssz >> 16) & 0xFF ))
(( thecode[mbrpno++] = (pssz >> 24) & 0xFF ))
# write partition table entry
ostr=
curptr=0
while (( curptr < 16 )); do
	ostr=$ostr\\0${thecode[curptr++]#8#}
done
print -n "$ostr" | \
    dd of="$T/firsttrack" conv=notrunc bs=1 seek=$((0x1CE)) 2>/dev/null

(( quiet )) || print Writing MBR and GRUB2 to target device...
dd if="$T/firsttrack" of="$tgt"

if [[ $basedev = /dev/svnd+([0-9]) ]]; then
	(( quiet )) || print "Creating BSD disklabel on target device..."
	# c: whole device (must be so)
	# i: ext2fs (matching first partition)
	# j: cfgfs (matching second partition)
	# p: MBR and GRUB2 area (by tradition)
	cat >"$T/bsdlabel" <<-EOF
		type: vnd
		disk: vnd device
		label: OpenADK
		flags:
		bytes/sector: 512
		sectors/track: $secs
		tracks/cylinder: $heads
		sectors/cylinder: $((heads * secs))
		cylinders: $cyls
		total sectors: $((cyls * heads * secs))
		rpm: 3600
		interleave: 1
		trackskew: 0
		cylinderskew: 0
		headswitch: 0
		track-to-track seek: 0
		drivedata: 0

		16 partitions:
		c: $((cyls * heads * secs)) 0 unused
		i: $(((cyls - cfgfs) * heads * secs - partofs)) $partofs ext2fs
		j: $((cfgfs * heads * secs)) $(((cyls - cfgfs) * heads * secs)) unknown
		p: $partofs 0 unknown
EOF
	disklabel -R ${basedev#/dev/} "$T/bsdlabel"
fi

(( quiet )) || print "Creating ext2fs on ${part}..."
q=
(( quiet )) && q=-q
mke2fs $q "$part"
partuuid=$(tune2fs -l "$part" | sed -n '/^Filesystem UUID:[	 ]*/s///p')
tune2fs -c 0 -i 0 "$part"

(( quiet )) || print Extracting installation archive...
mount_ext2fs "$part" "$T"
gzip -dc "$src" | (cd "$T"; tar -xvpf -)
cd "$T"
rnddev=/dev/urandom
[[ -c /dev/arandom ]] && rnddev=/dev/arandom
dd if=$rnddev bs=16 count=1 >>etc/.rnd 2>/dev/null
(( quiet )) || print Fixing up permissions...
chown 0:0 tmp
chmod 1777 tmp
chmod 4755 bin/busybox
[[ -f usr/bin/Xorg ]] && chmod 4755 usr/bin/Xorg
[[ -f usr/bin/sudo ]] && chmod 4755 usr/bin/sudo
(( quiet )) || print Configuring GRUB2 bootloader...
mkdir -p boot/grub
(
	print set default=0
	print set timeout=1
	if (( serial )); then
		print serial --unit=0 --speed=$speed
		print terminal_output serial
		print terminal_input serial
		consargs="console=ttyS0,$speed console=tty0"
	else
		print terminal_output console
		print terminal_input console
		consargs="console=tty0"
	fi
	print
	print 'menuentry "GNU/Linux (OpenADK)" {'
	linuxargs="root=UUID=$partuuid $consargs"
	(( panicreboot )) && linuxargs="$linuxargs panic=$panicreboot"
	print "\tlinux /boot/kernel $linuxargs"
	print '}'
) >boot/grub/grub.cfg
set -A grubfiles
ngrubfiles=0
for a in usr/lib/grub/*-pc/{*.mod,efiemu??.o,command.lst,moddep.lst,fs.lst,handler.lst,parttool.lst}; do
	[[ -e $a ]] && grubfiles[ngrubfiles++]=$a
done
cp "${grubfiles[@]}" boot/grub/
(( quiet )) || print Finishing up...
cd "$TOPDIR"
umount "$T"

(( quiet )) || print "\nNote: the rootfs UUID is: $partuuid"

rm -rf "$T"
exit 0
