#!/bin/sh
#$Id$
#This file is part of EdgeBSD etch
#Copyright (c) 2014-2022 Pierre Pronchery <khorben@edgebsd.org>
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
#AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
#DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
#FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
#DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
#SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
#CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
#OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
#OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.



#variables
DESTDIR="$PWD/destdir"
DEVNULL="/dev/null"
DEVZERO="/dev/zero"
ETCH_PREFIX="@PREFIX@"
ETCH_DATADIR="$ETCH_PREFIX/share/etch"
ETCH_SYSCONFDIR="$ETCH_PREFIX/etc/etch"
HOSTNAME="localhost.localdomain"
IMAGE_BS="1024"
IMAGE_SIZE=
PASSWORD=
PROGNAME="etch"
SHELL="/bin/sh"
TARGET=
TIMEZONE="Europe/Berlin"
USERNAME="user"
VERBOSE=1
#executables
CAT="cat"
DD="dd"
DEBUG=
DIRNAME="dirname"
MKDIR="mkdir -p"
MKSPARSE="mksparse"
MV="mv -f"
PROGRESS="_progress"
QEMU_IMG="qemu-img"
RM="rm -f"
SH="/bin/sh"
SUDO=
WC="wc"
WGET="wget"
#platform-specific
PLATFORM="EdgeBSD"


#functions
#debug
_debug()
{
	echo "$@" 1>&2
	"$@"
}


#download
_download()
{
	if [ $# -ne 1 ]; then
		_usage "_download url..."
		return $?
	fi
	url="$1"

	#FIXME no longer use the standard error
	case "$url" in
		"ftp://"*)
			_download_ftp "$url" 1>&2		|| return 2
			echo "$PWD/$url"
			;;
		"http://"*|"https://"*)
			_download_http "$url" 1>&2		|| return 2
			echo "$PWD/$url"
			;;
		"/"*)
			_download_file "$url" 1>&2		|| return 2
			echo "$url"
			;;
		*)
			_error "$url: Unknown protocol"
			;;
	esac
}

_download_file()
{
	url="$1"

	#XXX untested
	if [ ! -f "$url" ]; then
		_error "$url: File not found"
		return 2
	fi
}

_download_ftp()
{
	url="$1"

	#re-use the HTTP code
	_download_http "$url"
}

_download_http()
{
	url="$1"

	[ -f "$url" ] && return 0

	dirname=$($DIRNAME "$url")
	$DEBUG $MKDIR -- "$dirname"				|| return 2
	_info "Downloading ${url}..."
	$DEBUG $WGET -O "${url}.part" -- "$url"
	if [ $? -ne 0 ]; then
		$DEBUG $RM -- "${url}.part"
		return 2
	fi
	$DEBUG $MV -- "${url}.part" "$url"			|| return 2
}


#error
_error()
{
	echo "$PROGNAME: Error: $@" 1>&2
	return 2
}


#file_append
_file_append()
{
	if [ $# -ne 1 ]; then
		_usage "_file_append filename"
		return $?
	fi

	filename="$1"
	$SUDO $SH -c "$CAT >> '$DESTDIR/$filename'"		|| return 2
	return 0
}


#file_create
_file_create()
{
	if [ $# -ne 1 ]; then
		_usage "_file_create filename"
		return $?
	fi

	filename="$1"
	$SUDO $SH -c "$CAT > '$DESTDIR/$filename'"		|| return 2
	return 0
}


#group_get
_group_get()
{
	if [ $# -ne 1 ]; then
		_usage "_group_get gid"
		return $?
	fi

	while read line; do
		groupname="${line%%:*}"
		password="${line#*:}"
		gid="${password#*:}"
		password="${password%%:*}"
		gid="${gid%%:*}"
		if [ "$gid" = "$1" ]; then
			echo "$groupname"
			return 0
		fi
	done < "$DESTDIR/etc/group"
	return 2
}


#group_get_id
_group_get_id()
{
	if [ $# -ne 1 ]; then
		_usage "_group_get_id group"
		return $?
	fi

	while read line; do
		groupname="${line%%:*}"
		[ "$groupname" = "$1" ] || continue
		password="${line#*:}"
		gid="${password#*:}"
		password="${password%%:*}"
		gid="${gid%%:*}"
		echo "$gid"
		return 0
	done < "$DESTDIR/etc/group"
	return 2
}


#info
_info()
{
	[ $VERBOSE -ge 1 ] && echo "$@"
}


#etch
_etch()
{
	ret=2

	while true; do
		if [ -n "$TARGET" ]; then
			_volume_create				|| break
			_volume_partition			|| break
			_volume_format				|| break
			_volume_mount				|| break
		fi
		_install
		ret=$?
		if [ -n "$TARGET" ]; then
			_volume_umount				|| break
			if [ $ret -eq 0 ]; then
				_volume_convert
				ret=$?
			fi
		fi
		break
	done
	[ $ret -eq 0 ]						|| return $ret
	_info "Installation completed."
}


#install
_install()
{
	_install_kernel						|| return 2
	_install_base						|| return 2
	_install_packages					|| return 2
	_install_bootloader					|| return 2
	_install_configure					|| return 2
}


#install_base
_install_base()
{
	:
}


#install_bootloader
_install_bootloader()
{
	:
}


#install_configure
_install_configure()
{
	:
}


#install_kernel
_install_kernel()
{
	:
}


#install_packages
_install_packages()
{
	:
}


#progress
_progress()
{
	if [ $# -lt 2 ]; then
		_usage "_progress filename command [arguments...]"
		return $?
	fi
	filename="$1"
	shift

	$DEBUG $CAT -- "$filename" | "$@"
}


#trap
_trap()
{
	_error "Interrupted by a signal"
	ret=$?
	_trap_platform
	exit $ret
}


#trap_platform
_trap_platform()
{
	:
}


#usage
_usage()
{
	usage="Usage: $PROGNAME [-cqv][-O name=value][-h hostname][-o filename]
  -c	Remove the target file and content of staging directory
  -h	Hostname for etching
  -o	Target filename
  -q	Quiet mode
  -v	Verbose mode"

	if [ $# -gt 0 ]; then
		usage="Usage: $PROGNAME $@"
	fi
	echo "$usage" 1>&2
	return 1
}


#user_get
_user_get()
{
	if [ $# -ne 1 ]; then
		_usage "_user_get uid"
		return $?
	fi

	while read line; do
		username="${line%%:*}"
		password="${line#*:}"
		uid="${password#*:}"
		password="${password%%:*}"
		uid="${uid%%:*}"
		if [ "$uid" = "$1" ]; then
			echo "$username"
			return 0
		fi
	done < "$DESTDIR/etc/passwd"
	return 2
}


#user_get_id
_user_get_id()
{
	if [ $# -ne 1 ]; then
		_usage "_user_get_id username"
		return $?
	fi

	while read line; do
		username="${line%%:*}"
		[ "$username" = "$1" ] || continue
		password="${line#*:}"
		uid="${password#*:}"
		password="${password%%:*}"
		uid="${uid%%:*}"
		echo "$uid"
		return 0
	done < "$DESTDIR/etc/passwd"
	return 2
}


#volume_convert
_volume_convert()
{
	convert="$QEMU_IMG convert"
	format="${TARGET##*.}"

	[ -n "$PS1" -a $VERBOSE -ge 2 ] && convert="$convert -p"
	case "$format" in
		vdi|vmdk)
			_info "Converting image to the $format format..."
			#XXX may overwrite an existing file
			$DEBUG $convert -O "$format" \
				"$TARGET" "$TARGET.$format"	|| break
			$DEBUG $MV -- "$TARGET.$format" "$TARGET"
			$DEBUG $RM -- "$TARGET.$format" > "$DEVNULL" 2>&1
			return 0
			;;
		*)
			return 0
	esac
	return 2
}


#volume_create
_volume_create()
{
	[ -n "$TARGET" ] || return 0
	if [ -f "$TARGET" ]; then
		IMAGE_SIZE=$($WC -c < "$TARGET")
		IMAGE_SIZE=$((IMAGE_SIZE / IMAGE_BS))
		return 0
	fi
	if [ -z "$IMAGE_SIZE" ]; then
		_error "IMAGE_SIZE must be specified"
		return $?
	fi
	[ -f "$TARGET" ] && return 0
	_info "Creating image ${TARGET}..."
	#XXX verify that the final size is as expected
	#FIXME doesn't always work with $PROGRESS
	#$DEBUG $PROGRESS "$DEVZERO" \
	#	$DD of="$TARGET" bs="$IMAGE_BS" count="$IMAGE_SIZE"
	#FIXME doesn't always work with $DD either
	#$DEBUG $DD if="$DEVZERO" of="$TARGET" bs="$IMAGE_BS" \
	#	count="$IMAGE_SIZE" conv="sparse"
	$DEBUG $MKSPARSE -s "$((IMAGE_SIZE * IMAGE_BS))" "$TARGET"
	if [ $? -ne 0 ]; then
		$RM -- "$TARGET"
		return 2
	fi
}


#volume_format
_volume_format()
{
	:
}


#volume_mount
_volume_mount()
{
	$DEBUG $MKDIR -- "$DESTDIR"				|| return 2
}


#volume_partition
_volume_partition()
{
	:
}


#volume_umount
_volume_umount()
{
	:
}


#warning
_warning()
{
	echo "$PROGNAME: Warning: $@" 1>&2
	return 2
}


#main
clean=0
while getopts "ch:O:o:qv" name; do
	case "$name" in
		c)
			clean=1
			;;
		h)
			HOSTNAME="$OPTARG"
			;;
		O)
			export "${OPTARG%%=*}"="${OPTARG#*=}"
			;;
		o)
			TARGET="$OPTARG"
			;;
		q)
			VERBOSE=0
			;;
		v)
			VERBOSE=$((VERBOSE + 1))
			;;
		?)
			_usage
			exit $?
			;;
	esac
done
shift $(($OPTIND - 1))
if [ $# -ne 0 ]; then
	_usage
	exit $?
fi

if [ $clean -ne 0 ]; then
	[ -n "$TARGET" ] && $DEBUG $RM -- "$TARGET"		|| exit 2
	$DEBUG $RM -r -- "$DESTDIR"				|| exit 2
fi
[ $VERBOSE -ge 4 ] && DEBUG="_debug"
if [ -f "$ETCH_DATADIR/platforms/$PLATFORM" ]; then
	. "$ETCH_DATADIR/platforms/$PLATFORM"
else
	_error "$PLATFORM: Unknown platform"
	exit $?
fi
if [ -f "$ETCH_SYSCONFDIR/hosts/$HOSTNAME" ]; then
	. "$ETCH_SYSCONFDIR/hosts/$HOSTNAME"
else
	_warning "$HOSTNAME: Unknown host (no definition found)"
fi

#trap signals for recovery
trap "_trap" INT QUIT

#etch
umask 022
_etch
