#!/bin/sh

prog=${0##*/}
version=3.1.0-r5

cleanup_mounts() {
	local i=
	cd /
	sync
	sleep 1
	for i in $read_only_mounts; do
		mount -o remount,ro "$i" || echo "Warning: Failed to remount as read-only. Is modloop mounted?"
	done
	if [ -n "$umounts" ]; then
		umount $umounts
	fi
}

cleanup_installs() {
	if [ -n "$uninstalls" ]; then
		apk del --quiet syslinux
	fi
}

die() {
	echo "$@" >&2
	cleanup_mounts
	cleanup_installs
	exit 1
}

# find device for mountpoint
find_dev() {
	local mnt="${1%/}" # strip trailing /
	awk "\$2 == \"$mnt\" {print \$1}" /proc/mounts
}

# check if given device is on usb bus
on_usb_bus() {
	local dev="$1"
	[ -e /sys/block/$dev ] || return 1
	local sysdev=$(readlink -f /sys/block/$dev/device)
	test "${sysdev##*/usb[0-9]}" != "$sysdev"
}

# mount source as loopback and set srcdir
mount_srcdir() {
	local srcmnt=${MNT:-/mnt}
	mount -o loop -t iso9660 "$src" $srcmnt \
		|| die "Failed to mount loopback $src"
	umounts="$srcmnt"
	srcdir="$srcmnt"
	if ! [ -f "$srcdir"/.alpine-release ]; then
		die "No .alpine-release found on image $src"
	fi
}

vecho() {
	[ -z "$verbose" ] && return 0
	echo "$@"
}

# check if given dir is read-only
is_read_only() {
	local tmpfile=$(mktemp -p "$1" 2>/dev/null)
	[ -z "$tmpfile" ] && return 0
	rm -f "$tmpfile"
	return 1
}

# find what disk this partition belongs to
find_disk_dev() {
	local i= sysfsname=${1#/dev/}
	sysfsname=${sysfsname//\/!}	# cciss/c0d0 -> cciss!c0d0
	if [ -e /sys/block/$sysfsname ]; then
		echo "/dev/${sysfsname//!/'/'}"
		return 0
	fi
	for i in /sys/block/*/$sysfsname; do
		[ -e "$i" ] || continue
		echo "$i" | cut -d/ -f4 | sed -e 's:!:/:g' -e 's:^:/dev/:'
		return 0
	done
	return 1
}

usage() {
	cat <<__EOF__
$prog $version
usage: $prog [-fhUusv] SOURCE [DEST]

Copy the contents of SOURCE to DEST and make DEST bootable.

SOURCE can be a directory or a ISO image. DEST can be a mounted directory
or a device. If DEST is ommitted /media/usb will be used.

Options:
 -f  Force overwrite existing files. Will overwrite syslinux.cfg if upgrade.
 -h  Show this help.
 -U  Replace current alpine_dev in syslinux.cfg with UUID if UUID found.
 -u  Upgrade mode. Keep existing syslinux.cfg and don't run syslinux.
 -s  Force run syslinux, even if upgrade mode.
 -v  Verbose mode. Display whats going on.

__EOF__
	exit 1
}

while getopts "fhUusv" opt; do
	case "$opt" in
	f) force=1;;
	h) usage;;
	U) replace_alpine_dev=1;;
	u) upgrade=1;;
	s) syslinux=1;;
	v) verbose=1;;
	esac
done

shift $(($OPTIND - 1))

src=${1}
dest=${2:-/media/usb}


[ -z "$src" ] && usage

srcdir=
# Find the srcdir or srcurl. mount loopback if needed
if [ -f "$src"/.alpine-release ]; then
	srcdir="`echo $src | sed -r 's,/$,,'`"
else
	case "$src" in
	http://*|ftp://*) srcurl="$src";;
	*) mount_srcdir;;
	esac
fi

if [ -n "$srcdir" ]; then
	to_version=$(cat "$srcdir"/.alpine-release)
fi

# find target device
if [ -d "$dest" ]; then
	dest=${dest%/} # strip trailing /
	if ! awk '{print $2}' /proc/mounts | grep -q "^$dest\$"; then
		mount "$dest" || die "Failed to mount $dest"
		umounts="$umounts $dest"
	elif [ -n "$syslinux" ]; then
		die "Cannot run syslinux on mounted device"
	else
		nosyslinux=1
	fi
	destdir="$dest"
	dest=$(find_dev "$destdir")
elif [ -b "$dest" ]; then
	destdir="/media/${dest##*/}"
	mkdir -p "$destdir"
	mount "$dest" "$destdir" || die "Failed to mount $dest on $destdir"
	umounts="$umounts $destdir"
fi

# remount as rw if needed
if is_read_only "$destdir"; then
	vecho "Remounting $destdir as read/write"
	mount -o remount,rw "$dest" || die "Failed to remount $destdir as rw"
	read_only_mounts="$read_only_mounts $destdir"
fi

# fish out label, uuid and type
eval $(blkid $dest | cut -d: -f2-)

vecho "Using $dest as target (mounted on $destdir)"

# find parent device (i.e sda)
dev="$dest"
while [ -L "$dev" ]; do
	dev=$(readlink -f $dev)
done
parent_dev=$(find_disk_dev $dev)

# check if this files exist and not in upgrade mode
if [ -z "$upgrade" ] && [ -z "$force" ]; then
	for i in boot apks syslinux.cfg .alpine-release; do
		[ -e "$destdir"/$i ] && die "$destdir/$i already exists. Use -u to upgrade."
	done
fi

# check if its same version
if [ -n "$upgrade" ] && [ -e "$destdir"/.alpine-release ]; then
	from_version=$(cat "$destdir"/.alpine-release)
	if [ -z "$force" ] && [ -n "$to_version" ] && [ "$from_version" = "$to_version" ]; then
		die "Source and target seems to have same version ($from_version). Aborting."
	fi
fi

# Display what versions we are installing/upgrading
if [ -n "$from_version" ]; then
	echo "Upgrading $dest from $from_version to $to_version"
else
	echo "Copying $to_version to $dest (mounted on $destdir)"
fi

# remove partial upgrades if any.
rm -rf "$destdir"/.new "$destdir"/.old
mkdir -p "$destdir"/.new || die "Failed to create $destdir/.new"

# check that we have the space we need
# we calculate on MB since shell arthimetic gets problems with big disks
# and bytes.
free_blocks=$(stat -f -c "%f" "$destdir")
block_size=$(stat -f -c "%s" "$destdir")
blocks_per_mb=$(( 1024 * 1024 / $block_size))
available_space=$(( $free_blocks / $blocks_per_mb ))
vecho "Available space: $available_space MiB"

if [ -n "$srcdir" ]; then
	needed_space=$(cd "$srcdir" && du -m -s -c boot apks .alpine-release | awk '$2 == "total" {print $1}')
	vecho "Needed space:    $needed_space MiB"
	[ $available_space -lt $needed_space ] \
		&& die "Not enough space on $destdir. Aborting."

	# copy the files to .new
	for i in boot apks syslinux.cfg .alpine-release; do
		if [ -e "$srcdir"/$i ]; then
			vecho "Copying $srcdir/$i to $destdir/.new/"
			cp -a "$srcdir"/$i "$destdir"/.new/
		fi
	done
elif [ -n "$srcurl" ]; then
	cd "$destdir"/.new
	${WGET:-wget} -O - "$srcurl" | uniso \
		|| die "Failed to download or extract $srcurl"
	echo ""
fi

# find where new syslinux.cfg is
for i in boot/syslinux/syslinux.cfg syslinux.cfg; do
	if [ -e "$destdir"/.new/$i ]; then
		syslinux_cfg=$i
		vecho "Found $syslinux_cfg"
		break
	fi
done
# abort early in case unexpected trouble
if [ -z "$syslinux_cfg" ]; then
	die "Could not find any syslinux.cfg on new iso?"
fi

# make sure files are really there before we replace existing
vecho "Flushing cache..."
sync

vecho "Replacing existing files..."
mkdir -p "$destdir"/.old || die "Failed to create $destdir/.old"

tomove="boot apks syslinux.cfg .alpine-release"

# move current files to .old
for i in $tomove; do
	if [ -e "$destdir"/$i ]; then
		mv "$destdir"/$i "$destdir"/.old/ || die "Failed to move $destdir/$i to $destdir/.old/"
	fi
done

# keep any existing syslinux.cfg
if [ -e "$destdir"/.old/$syslinux_cfg ]; then
	mv "$destdir"/.old/$syslinux_cfg "$destdir"/.new/$syslinux_cfg
elif [ -e "$destdir"/.old/syslinux.cfg ] \
		&& [ -e "$destdir"/.new/boot/syslinux/syslinux.cfg ]; then
	echo "Warning: moving syslinux.cfg to boot/syslinux/syslinux.cfg" >&2
	mv "$destdir"/.old/syslinux.cfg "$destdir"/.new/boot/syslinux
	if [ -z "$syslinux" ]; then
		echo "         You might need run: syslinux $dest" >&2
	fi
fi

# move .new to current
for i in $tomove; do
	if [ -e "$destdir"/.new/$i ]; then
		mv "$destdir"/.new/$i "$destdir"/ \
			|| die "Failed to move $destdir/.new/ to $destdir"
	fi
done

if [ -n "$replace_alpine_dev" -o -z "$upgrade" ] && [ -n "$UUID" ]; then
	sed -E -i -e "s/alpine_dev=[^ \t:]+/alpine_dev=UUID=$UUID/" \
		"$destdir"/$syslinux_cfg
fi

# cleanup
[ -z "$keep_old" ] && rm -rf "$destdir"/.old "$destdir"/.new

# If we only copy then we are done.
if [ -n "$upgrade" ] && [ -z "$syslinux" ]; then
	cleanup_installs
	cleanup_mounts
	exit 0
fi

# prevent running syslinux on mounted device
if [ -n "$nosyslinux" ]; then
	echo "Warning: Can not run syslinux on a mounted device"
	echo "         You might need run syslinux manually and install MBR manually"
	exit 0
fi

echo "Making $dest bootable..."

if ! [ -x "$(which syslinux)" ]; then
	apk add --quiet syslinux || die "Failed to install syslinux"
	uninstalls="syslinux"
fi

# we need to unmount the device before we can run syslinux
cleanup_mounts
fsync $dest
syslinux $dest

if [ -b $parent_dev ]; then
	dd if=/usr/share/syslinux/mbr.bin of=$parent_dev
else
	echo "Warning: Could not find the parent device for $dest"
fi
cleanup_installs

