#!/bin/bash
#
# This file was automatically created by shellScriptFlatten
#
# DO NOT EDIT!
#

# Installs or upgrades software on Axia Engine.
#
# Copyright (C) 2008, Axia Audio
#
# This program 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.

# ==============================================================================

# Preconditions: script executed by root

# ==============================================================================

version=0.3.4
revision='$Revision: 1154 $'

targetErasureSectorCount=1
fullInstallMode=
bootloaderPartitionSizeMin=$(( 2 * 1024 * 1024 ))
sysCount=2
dataPartitionSize=$(( 20 * 1024 * 1024 ))
sys_mount_dir=/tmp/target-sys
bootloaderDirectory=/boot/grub

# the lowest allowed percentage of disk usable capacity remaining after
# leaving free space at the end of disk to ensure safe image copying across
# different disk geometries for "same" size Compact Flash cards.
diskUsableCapacityPercentageMin=96

targetSize=
targetGeometry=
targetTemplateDevice=
# number of different ways how target geometry is specified
targetGeometrySpecifierCount=0

revision=${revision//$/}
revision=${revision% }

this=`basename $0`
 
# ==============================================================================

# Prints usage information
#
function usagePrint() {
	cat << EOF
Usage: $this [OPTIONS] PACKAGE TARGET

Install or upgrade of software on Axia Engine.

  -h, --help               prints this message and exit.
  -e, --erase              erases TARGET completely before installation.
  -f, --full-install       performs a full install on TARGET.
  -p=SIZE, --persistent-rw-fs-size=SIZE
                           specifies size of persistent read/write partition.
  -n=NAME, --target-final-name=NAME
                           specifies name of the TARGET on the system
                           it will be used at the end instead of filesystem labels.
  -s=SIZE, --target-size=SIZE
                           specifies size of TARGET when creating image.
  -g=C:H:S, --target-geometry=C:H:S
                           specifies geometry of TARGET when creating image.
  -t=TEMPLATE_DEVICE, --target-template=TEMPLATE_DEVICE
                           specifies device whose geometry to
                           use as template of TARGET when creating image.
  -b=BOOTLOADER_DIRECTORY, --bootloader-directory=BOOTLOADER_DIRECTORY
                           specifies directory where bootloader partition is
                           mounted; this is needed for version compatibility checks.

SIZE parameters may be followed by the following multiplicative suffixes:
kB = 1000, K = 1024, MB = 1000 * 1000, M = 1024 * 1024,
GB = 1000 * 1000 * 1000, G = 1024 * 1024 * 1024

PACKAGE is an Engine software release package.

TARGET is a targed image file, partition or disk device;
- if TARGET appears to be a partition no options are expected;
- if TARGET appears to be the entire disk it must be full install and
  appropriate option (-f or --full-install) is expected,
  in the same time NONE of TARGET size specifying options
  (-s, --target-size, -g, --target-geometry, -t, --target-template)
  is expected;
- if TARGET appears to be loop device or regular file it is treated as disk image,
  full install is assumed and -f or --full-install option expected;
  in the same time ONE of TARGET size specifying options
  (-s, --target-size, -g, --target-geometry, -t, --target-template)
  is expected;
  name option of the TARGET in the final system (-n or --target-final-name)
  can be provided;

if name of the TARGET in the final system is not provided
(via options -n or --target-final-name ), filesystem labels are used
by bootloader to specify root devices (root=LABEL=ROOT_<index>)

if BOOTLOADER_DIRECTORY is not specified '/boot/grub' is assumed;

WARNING: all existing content of TARGET will be lost!
NO confirmation will be asked!


Exit codes:
0 - Success
1 - Unspecified failure
16 - Usage or syntax error
96 - Version check failed: bootloader from the package is too old
97 - Version check failed: invalid bootloader version numbers encountered
98 - Version check failed: no usr/etc/bootloader-version.txt in package
100 - Version check failed: application from the package is too old
101 - Version check failed: invalid application version numbers encountered
102 - Version check failed: no etc/Axia/sysVersion.txt in package
103 - Version check failed: wrong package product type (e.g. PowerStation, iPort etc)
104 - Version check failed: wrong package product target hardware type (e.g. SOM-5786, SOM-5790 etc)
105 - Version check failed: wrong package product target cpu class (e.g. P4, Core i5 etc)

Report bugs to <armands@temporarius.com>
EOF
}

# Suggests to look into documentation
#
function usageSuggest() {
	cat << EOF

Usage: $this [OPTIONS] PACKAGE TARGET

Try '$this --help' for more information.
EOF
}

# ------------------------------------------------------------------------------
# . trace
# BEGINNING: trace

#
# Some debugging and testing support
#

# ==============================================================================

function try() {
	local r=0
	local caller=$(caller)
	local script=${caller#[0-9]* }
	local lineNr=${caller%% *}
	eval $@ || { 
		r=$?
		echo "$script: FAILURE @ $lineNr: $@" >&2
	}
	return $r
}

function tryVerbosely() {
	local r=0
	local caller=$(caller)
	local script=${caller#[0-9]* }
	local lineNr=${caller%% *}
	echo "   $@"
	eval $@ || { 
		r=$?
		echo "$script: FAILURE @ $lineNr: $@" >&2
	}
	return $r
}

function assert() {
	local r=0
	local caller=$(caller)
	local script=${caller#[0-9]* }
	local lineNr=${caller%% *}
	eval $@ || { 
		r=$?
		echo "$script: ASSERTION @ $lineNr FAILED: $@"
		failureCount=$(( failureCount + 1 ))
	}
	return $r
}

# EOF
# ENDING: trace
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# . ../bootloader/util/diskTools
# BEGINNING: ../bootloader/util/diskTools

# Miscelaneous disk utilities
#
# Copyright (C) 2010, Axia Audio
#
# This program 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.

# converts to number of bytes size specified with multiplicative suffix
# $1 - size specifier
#
function sizeNormalize() {
	local size=`echo "$1" | sed "s/[[:space:]]//g"`
	if echo "$size" | grep -q "^[[:digit:]]\+GB$"; then
		echo $(( `echo $size | sed "s/[[:alpha:]]\+//"` * 1000 * 1000 * 1000 ))
		return
	fi
	if echo "$size" | grep -q "^[[:digit:]]\+G$"; then
		echo $(( `echo $size | sed "s/[[:alpha:]]\+//"` * 1024 * 1024 * 1024 ))
		return
	fi
	if echo "$size" | grep -q "^[[:digit:]]\+MB$"; then
		echo $(( `echo $size | sed "s/[[:alpha:]]\+//"` * 1000 * 1000 ))
		return
	fi
	if echo "$size" | grep -q "^[[:digit:]]\+M$"; then
		echo $(( `echo $size | sed "s/[[:alpha:]]\+//"` * 1024 * 1024 ))
		return
	fi
	if echo "$size" | grep -q "^[[:digit:]]\+kB$"; then
		echo $(( `echo $size | sed "s/[[:alpha:]]\+//"` * 1000 ))
		return
	fi
	if echo "$size" | grep -q "^[[:digit:]]\+K$"; then
		echo $(( `echo $size | sed "s/[[:alpha:]]\+//"` * 1024 ))
		return
	fi
	if echo "$size" | grep -q "^[[:digit:]]\+$"; then
		echo $size
		return
	fi
	echo 0
	return 1
}

# clones block or character device node
# $1 - source device node
# $2 - destination device node name
#
function nodeClone() {
	declare -a entry
	entry=( `ls -gG $1 | grep '^[bc]' | sed 's/,//g'` ) || return $?
	if [ "${entry[0]}" -a "${entry[2]}" -a "${entry[3]}" ]; then
		try mknod \"$2\" ${entry[0]:0:1} ${entry[2]} ${entry[3]} || return $?
		try chmod --reference=\"$1\" \"$2\" || return $?
		try chown --reference=\"$1\" \"$2\"
		return $?
	fi
	echo "$this: ERR: Failed to query $1!" >&2
	return 1
}

# proposes disk geometry according to the required sector count
# $1 - maximal sector count
#
function diskGeometryPropose() {
	local sectorCount=$1
	local cylinderCount=0
	local trackCount=0
	local headCount=0
	local trackSectorCount=0
	# calculating possible geometry (CHS)
	# taken from Microsoft Virtual Hard Disk Image Format Specification Version 1.0
	if [ $sectorCount -ge $(( 65535 * 16 * 63 )) ]; then
		trackSectorCount=255
		headCount=16
		trackCount=$(( $sectorCount / $trackSectorCount ))
	else
		trackSectorCount=17
		trackCount=$(( $sectorCount / $trackSectorCount ))
		headCount=$(( ($trackCount - 1) / 1024 + 1 ))
		if [ $headCount -lt 4 ]; then
			headCount=4
		fi
		if [ $trackCount -ge $(( $headCount * 1024 )) -o $headCount -gt 16 ]; then
			trackSectorCount=31
			headCount=16
			trackCount=$(( $sectorCount / $trackSectorCount ))
		fi
		if [ $trackCount -ge $(( $headCount * 1024 )) ]; then
			trackSectorCount=63
			headCount=16
			trackCount=$(( $sectorCount / $trackSectorCount ))
		fi
	fi
	cylinderCount=$(( $trackCount / $headCount ))
	if [ $cylinderCount -gt 65535 ]; then
		echo "$this: ERR: the resulting disk image is too large!" >&2
		exit 1 
	fi
	echo $cylinderCount $headCount $trackSectorCount
}

# checks if file is IDE device
# $1 - file to query
#
function fileIsIdeDevice() {
	declare deviceMajor
	deviceMajor=$(( 0x`stat -c %t $1 2> /dev/null || echo 0` ))
	declare -a majorList
	majorList=( 3 22 33 34 56 57 88 89 90 91 )
	for major in ${majorList[*]}; do
		[ $deviceMajor -eq $major ] && return 0
	done
	return 1
}

# checks if file is SCSI device
# $1 - file to query
#
function fileIsScsiDevice() {
	declare deviceMajor
	deviceMajor=$(( 0x`stat -c %t $1 2> /dev/null || echo 0` ))
	declare -a majorList
	majorList=( 8 65 66 67 68 69 70 71 128 129 130 131 132 133 134 135 )
	for major in ${majorList[*]}; do
		[ $deviceMajor -eq $major ] && return 0
	done
	return 1
}

# gets disk minor for storage device file
# $1 - file to query
#
function diskMinorGet() {
	declare deviceMinor
	deviceMinor=`stat -c %T $1 2> /dev/null` || return $?
	if fileIsIdeDevice $1; then
		echo $(( 0x${deviceMinor} & 0xC0 ))
		return $?
	fi
	if fileIsScsiDevice $1; then
		echo $(( 0x${deviceMinor} & 0xF0 ))
		return $?
	fi
	return 1
}

# gets partition index for storage device file
# $1 - file to query
#
function partitionIndexGet() {
	declare deviceMinor
	deviceMinor=`stat -c %T $1 2> /dev/null` || return $?
	if fileIsIdeDevice $1; then
		echo $(( 0x${deviceMinor} & 0x3F ))
		return $?
	fi
	if fileIsScsiDevice $1; then
		echo $(( 0x${deviceMinor} & 0x0F ))
		return $?
	fi
	return 1
}

# checks if file is disk device
# $1 - file to query
#
function fileIsDisk() {
	declare partitionIndex
	partitionIndex=`partitionIndexGet $1` || return $?
	[ $partitionIndex -eq 0 ] && return 0
	return 1
}

# checks if file is partition device
# $1 - file to query
#
function fileIsPartition() {
	declare partitionIndex
	partitionIndex=`partitionIndexGet $1` || return $?
	[ $partitionIndex -gt 0 ] && return 0
	return 1
}

# queries disk geometry
# $1 - device to query
#
function diskGeometryQuery() {
	declare -a geometry
	geometry=( `try sfdisk -q -g \"$1\" 2> /dev/null` ) || return $?
	local sectorCount=$(( ${geometry[1]} * ${geometry[3]} * ${geometry[5]} ))
	if [ $sectorCount -gt 0 ]; then
		echo ${geometry[1]} ${geometry[3]} ${geometry[5]}
		return 0
	fi
	echo "$this: ERR: Failed to get disk $1 geometry!" >&2
	return 1
}

# gets location of partition from disk image file 
# $1 - disk image file
# $2 - index of partition to query
#
function diskImagePartitionLocationGet() {
	declare -a entry
	entry=( `try sfdisk -q -l -uS -d \"$1\" 2> /dev/null | grep 'start=' \
			| sed -n "$2{s/.*://;s/,* *[A-Za-z]*=/ /g;p;q}"` ) \
		|| return $?
	if [ "${entry[0]}" -gt 0 -a "${entry[1]}" -gt 0 ]; then
		# partition looks plausible
		echo ${entry[0]} ${entry[1]}
		return 0
	fi
	echo "$this: ERR: Partition $2 of $1 is inconsistent!" >&2
	return 1
}

# maps file part via loop device
# $1 - file to map
# $2 - index of first 512 bytes block to map
# $3 - number of 512 bytes block to map
#
function fileLoopMap() {
	local offset=0
	local sizeSpecifier=
	declare device
	[ $2 ] && offset=$(( $2 * 512 ))
	[ $3 ] && sizeSpecifier="-s $(( $3 * 512 ))"
	for device in /dev/loop*; do
		if losetup -o $offset $sizeSpecifier $device $1 2> /dev/null; then
			echo $device
			return 0
		fi
	done
	echo "$this: ERR: Failed to allocate loop device!" >&2
	return 1	
}                     

# maps partition of disk image file via loop device
# $1 - disk image
# $2 - index of partition to map
#
function diskImagePartitionLoopMap() {
	declare -a location
	location=( `try diskImagePartitionLocationGet \"$1\" $2` ) || return $?
	try fileLoopMap \"$1\" ${location[0]} ${location[1]}
}

# invokes GRUB shell with parameters supplied and checks for errors
function grubShellInvoke() {
	# WARNING! GRUB uses to terminate with 0 even on failure
	( grub $@ || ( echo "Error 0: " && false ) ) \
		| if grep "Error [0-9]*: "; then false; fi
	return $?
}

# EOF
# ENDING: ../bootloader/util/diskTools
# ------------------------------------------------------------------------------

# executes commands with the specified partition of disk device
# usage: withPartitionTry <VARIABLE_NAME> <DEVICE> <PARTITION_INDEX> <COMMAND> ...
#
# $1 - name of variable that will contain name of partition device
# $2 - disk device where partition belongs to
# $3 - index of partition on disk device
# $4 ... - command to execute with partition
#
function withPartitionTry() {
	local r=0
	local $1
	local withPartitionTry_image=$2
	local withPartitionTry_partition=${2}${3}

	if fileIsDisk $2; then
		# the real disk device is used
		withPartitionTry_image=
	else
		# disk image is used
		withPartitionTry_partition=`try diskImagePartitionLoopMap \"$2\" $3` || r=$?
	fi
	if (( r == 0 )); then
		eval $1=\"$withPartitionTry_partition\"
		shift 3
		try $@ || r=$?
		if [[ $withPartitionTry_image ]]; then
			# partition image was mapped via loop device
			try losetup -d \"$withPartitionTry_partition\"
		fi
	fi
	return $r
}

# truncates cylinder count of disk geometry in a sane manner
# $1 - disk geometry cylinder count
# $2 - disk geometry head count
# $3 - disk geometry track sector count
# $4 - the lowest allowed percentage of disk capacity to retain
#
function diskCylinderCountTruncate() {
	local cylinderCount=$1
	local headCount=$2
	local trackSectorCount=$3
	local diskCapacityPercentageMin=$4
	local result=$cylinderCount
	# the minimal number of cylinders for proposal to qualify
	local cylinderCountMin=$(( $cylinderCount \
		- ($cylinderCount * $diskCapacityPercentageMin / 100) ))
	declare proposal
	declare sectorCount
	declare -a list
	declare -a geometry
	declare i

	for (( i=0; i < 2; i++ )); do
		proposal=$(( $cylinderCount - $i ))
		list[${#list[*]}]=$proposal
		sectorCount=$(( $proposal * $headCount * $trackSectorCount ))
		# proposal to truncate to fit optimal geometry
		geometry=( `try diskGeometryPropose $sectorCount` )
		proposal=$(( ${geometry[0]} * ${geometry[1]} * ${geometry[2]} \
			/ $headCount / $trackSectorCount ))
		list[${#list[*]}]=$proposal
		# proposal to truncate to fit geometry with largest cylinder size
		proposal=$(( $sectorCount / 255 / 63 * 255 * 63 \
			/ $headCount / $trackSectorCount ))
		list[${#list[*]}]=$proposal
	done
	for proposal in ${list[*]}; do
		if [ $proposal -ge $cylinderCountMin \
				-a $proposal -lt $result ]; then
			# proposal qualifies and is the lowest so far
			result=$proposal
		fi
	done
	echo $result
}

# repartitions disk
# $target - disk device to repartition
# $dataPartitionSize - size of persistent read/write partition
#
function disk_repartition() {
	local target=$1
	local geometry=( $2 $3 $4 )
	local cylinderCount=${geometry[0]}
	local headCount=${geometry[1]}
	local trackSectorCount=${geometry[2]}
	local cylinderSize=$(( $headCount * $trackSectorCount * 512 ))
	local size=$(( $cylinderCount * $cylinderSize ))
	local sfdiskOptions=

	# reserving free space at the end of disk
	cylinderCount=`try diskCylinderCountTruncate ${geometry[*]} \
		$diskUsableCapacityPercentageMin`

	echo "$this: Using first $cylinderCount cylinders of $target disk"

	# configuration partition cylinder count
	extendedPartitionSize=$(( dataPartitionSize * 2 ))
	extendedPartitionCylinderCount=$(( ($extendedPartitionSize - 1) / $cylinderSize + 1 ))
	if [ $extendedPartitionCylinderCount -lt 2 ]; then
		# adjust for minimum
		$extendedPartitionCylinderCount=2
	fi

	# bootloader partition cylinder count
	bootloaderPartitionCylinderCount=$(( ($bootloaderPartitionSizeMin - 1) / $cylinderSize + 1 ))

	# number of cylinders for each system partition
	remainingCylinderCount=$(( $cylinderCount \
		- $bootloaderPartitionCylinderCount - $extendedPartitionCylinderCount ))

	sysPartitionCylinderCount=$(( $remainingCylinderCount / $sysCount ))
	if [ $sysPartitionCylinderCount -gt 0 ]; then
		remainder=$(( $remainingCylinderCount % $sysCount ))
		extendedPartitionCylinderCount=$(( $extendedPartitionCylinderCount + $remainder ))
	else
		echo "$this: ERR: insufficient space on '$target'!" >&2
		return 1
	fi

	# persistent read/write partition cylinder count
	dataPartitionCylinderCount=$(( $extendedPartitionCylinderCount / 2 ))

	# unconditionally erase existing MBR and entire disk (optionally)
	if [ $targetErasureSectorCount -eq 0 -o ! -e $target -o -f $target ]; then
		targetErasureSectorCount=$(( ${geometry[0]} * ${geometry[1]} * ${geometry[2]} ))
	fi
	tryVerbosely dd if=/dev/zero of=\"$target\" status=noxfer bs=512 \
		count=$targetErasureSectorCount > /dev/null

	fileIsDisk $target || sfdiskOptions=--no-reread

	# create partition table
	{
		tryVerbosely sfdisk -q $sfdiskOptions \
			-C ${geometry[0]} -H ${geometry[1]} -S ${geometry[2]} \
			\"$target\" > /dev/null << EOF
,$bootloaderPartitionCylinderCount,L,*
,$sysPartitionCylinderCount,L
,$sysPartitionCylinderCount,L
,$extendedPartitionCylinderCount,E
,$dataPartitionCylinderCount,L
,,L
EOF
	} || return 1

	echo "$this: Disk $target repartitioning complete."
}

# creates file system
# $1 - target
# [
# $2 - filesystem label
# ]
#
function e2fsCreate() {
	mke2fs -q -j $1 || return $?
	tune2fs -c 0 $1 || return $?
	if [ $2 ]; then
		e2label $1 $2 || return $?
	fi
}

# check if an upgrade package to be applied is compatible with current system
# see doc/version-compatibility.txt
# $1 - source package
# $2 - directory with the initial version files from the factory
#
function versionCompatibilityCheck() {
	local r=0
	local productId
	local initialVersion
	local expectedProductType
	local version
	local productType
	if [[ -r $2/bootloader-version.txt ]]; then
		# there is bootloader version number as it comes from factory available
		if tar -C /tmp --strip-components=3 -xf $1 ./usr/etc/bootloader-version.txt; then
			# there is bootloader version number available from package
			initialVersion=$(( 10#`sed -n '2,1{s/^.*: //;s/ .*//;p;q}' $2/bootloader-version.txt` ))
			version=$(( 10#`sed -n '2,1{s/^.*: //;s/ .*//;p;q}' /tmp/bootloader-version.txt` ))
			if (( initialVersion > 0 && version > 0 )); then
				if (( initialVersion > version )); then 
					cat >&2 << EOF
$this: ERR: Version check: bootloader from the package is too old ($version < $initialVersion) !
EOF
					r=96
				fi
			else
				cat >&2 << EOF
$this: ERR: Version check: invalid bootloader version numbers encountered !
EOF
				r=97
			fi
		else
			cat >&2 << EOF
$this: ERR: Version check: no usr/etc/bootloader-version.txt in package !
EOF
			r=98
		fi
		rm -f /tmp/bootloader-version.txt
	fi
	if [[ -r $2/sysVersion.txt ]]; then
		# there is application version number as it comes from factory available
		if tar -C /tmp --strip-components=3 -xf $1 ./etc/Axia/sysVersion.txt; then
			# there is application version number available from package
			initialVersion=$(( 10#`sed -n '4,1{p;q}' $2/sysVersion.txt` ))
			version=$(( 10#`sed -n '4,1{p;q}' /tmp/sysVersion.txt` ))
			if (( initialVersion > 0 && version > 0 )); then
				# product type must match (see RM2241)
				productId=`sed -n '1,1{p;q}' $2/sysVersion.txt`
				expectedProductType=${productId:0:3}
				productId=`sed -n '1,1{p;q}' /tmp/sysVersion.txt`
				productType=${productId:0:3}
				if [[ "$expectedProductType" == "$productType" ]]; then
					# product type does match, ok to check target hardware cpu class
					expectedCpuClass=$(sed -n '8,1{p;q}' $2/sysVersion.txt)
					if [[ ! "$expectedCpuClass" ]] && which sysQuery 2> /dev/null; then
						# no note on target cpu class made during production, detecting now
						targetType=$(sysQuery -t)
						case $targetType in
						1)
							# ADVANTECH_SOM_5786 -> Core
							expectedCpuClass=1
							;;
						2)
							# ADVANTECH_SOM_5790 -> i5
							expectedCpuClass=5
							;;
						3)
							# INTEL_D865GLC -> Pentium4
							expectedCpuClass=4
							;;
						4)
							# ASUS_N4L_VM -> Core
							expectedCpuClass=1
							;;
						5)
							# BCM_MX965GME -> Core
							expectedCpuClass=1
							;;
						10)
							# CCT_AM31X -> i7
							expectedCpuClass=7
							;;
						*)
							# assuming Core
							expectedCpuClass=1
							;;
						esac
					fi
					[[ "$expectedCpuClass" ]] || expectedCpuClass=1
					cpuClass=$(sed -n '8,1{p;q}' /tmp/sysVersion.txt)
					if [[ ! "$cpuClass" ]] || [[ "$cpuClass" == 0 ]]; then
						# specific cpu class not enforced, any will fit
						cpuClass=$expectedCpuClass
					fi
					if [[ "$expectedCpuClass" == "$cpuClass" ]]; then
						# target cpu class does match, ok to check version numbers
						if (( initialVersion > version )); then 
							cat >&2 << EOF
$this: ERR: Version check: application from the package is too old ($version < $initialVersion) !
EOF
							r=100
						fi
					else
						cat >&2 << EOF
$this: ERR: Version check: wrong package product target cpu class ("$cpuClass" != "$expectedCpuClass") !
EOF
						r=105
					fi
				else
					cat >&2 << EOF
$this: ERR: Version check: wrong package product type ($productType != $expectedProductType) !
EOF
					r=103
				fi
			else
				cat >&2 << EOF
$this: ERR: Version check: invalid application version numbers encountered !
EOF
				r=101
			fi
		else
			cat >&2 << EOF
$this: ERR: Version check: no etc/Axia/sysVersion.txt in package !
EOF
			r=102
		fi
		rm -f /tmp/sysVersion.txt
	fi
	return $r
}

# create a system on partition and optionally installs bootloader from it
# $1 - source package
# $2 - destination partition device
# $3 - index of system being installed
# [
# $4 - target device to install bootloader on
# $5 - name of target disk device
# ]
#
function systemCreate() {
	local r=0
	local nestedPackageName=
	local bootloaderWorkDirectory=/tmp/bootloader-install.d
	if [[ ! $fullInstallMode ]]; then
		if [[ -d $bootloaderDirectory/factory || -f $bootloaderDirectory/stage2 ]]; then
			versionCompatibilityCheck $1 $bootloaderDirectory/factory
			r=$?
			if (( r != 0 )); then
				cat >&2 << EOF
$this: ERR: Version check: package may not be applied to this system!
EOF
				return $r
			fi
		else
			cat >&2 << EOF
$this: ERR: Version check: bootloader partition is not mounted!
EOF
			return 1
		fi
	fi
	echo "$this: Creating system on $2."
	tryVerbosely mke2fs -q \"$2\" || return $?
	try tune2fs -c 0 \"$2\" -L ROOT_$3 || return $?
	try mkdir -p --mode=755 $sys_mount_dir || return $?
	try mount \"$2\" $sys_mount_dir || return $?
	nestedPackageName=`tar -tf $1 | grep ^.*\.tbz2$`
	if [ $? -eq 0 ]; then
		# nested package encountered, format version 2 at least
		tryVerbosely "tar -xf \"$1\" -O \"$nestedPackageName\" \
			| tar -C $sys_mount_dir -xjp --atime-preserve \
				--exclude ./etc/Axia/sysVersion.txt" || r=$?
	else
		# no nested package, format version 1
		tryVerbosely tar -C $sys_mount_dir -xjp --atime-preserve -f \"$1\" \
			--exclude ./etc/Axia/sysVersion.txt || r=$?
	fi
	if [ $r -eq 0 ]; then
		# majority of files were extracted
		if [ $4 ]; then
			# bootloader shall be installed from this system
			rm -Rf $bootloaderWorkDirectory || true
			try mkdir -p \"$bootloaderWorkDirectory/output/factory\" || r=$?
			if (( r == 0 )); then
				try tar -C \"$bootloaderWorkDirectory/output/factory\" \
					--strip-components=3 -xp --atime-preserve -f \"$1\" \
					./etc/Axia/sysVersion.txt || r=$?
			fi
			tryVerbosely $sys_mount_dir/usr/sbin/bootloader-install \
				--full-install --target-final-name=\"$5\" \
				--work-directory=\"$bootloaderWorkDirectory\" \
				\"$4\" $sys_mount_dir || r=$?
		fi
		try sync
	fi
	if [ $r -eq 0 ]; then
		# completing system preparation
		try tar -C $sys_mount_dir -xp --atime-preserve -f \"$1\" \
			./etc/Axia/sysVersion.txt || r=$?
		try sync
	fi
	try umount $sys_mount_dir
	if [ $r -eq 0 ]; then
		tryVerbosely fsck -T -p \"$2\" > /dev/null
		r=$?
		if [ $(( $r & 0xFE )) -eq 0 ]; then
			# system partition now is ready
			r=0
		else
			# error other than correctable file system error was encountered
			echo "$this: ERR: fsck $2 -> $r!" >&2
		fi
	fi
	return $r
}

# cleans system start failure counter
# $1 - destination partition device
# $2 - index of system to clean
#
function systemBootClean() {
	local r=0
	local prefix=`readlink -f $sys_mount_dir`
	echo "$this: Cleaning boot for system $2."
	try mkdir -p --mode=755 $sys_mount_dir || return $?
	try mount -r \"$1\" $sys_mount_dir || return $?
	PATH=${prefix}${PATH//:/:$prefix}:$PATH try bootClean \"$2\" || r=$?
	try umount $sys_mount_dir
	return $r
}

# ensures that source package is available, fetches from remote server if required
# $1 - source package path or URL
#
function packageEnsure() {
	local r=0
	local package=$1
	if echo $package | grep -q "^http://\|^https://\|^ftp://"; then
		if try pushd /tmp; then
			if try wget \"$package\"; then
				package=`basename $package`
			else
				r=$?
			fi
  			popd
		fi
	fi
	echo `readlink -f $package`
	return $r
}

# ==============================================================================

# stopping on the first error
set -e

echo "$this (version $version, $revision)"

# retrieve parameters
for token in $@; do
	case $token in
	-h | --help)
		usagePrint
		exit 0
		;;
	-e | --erase)
		targetErasureSectorCount=0
		;;
	-f | --full-install)
		fullInstallMode=.
		;;
	-p=* | --persistent-rw-fs-size=*)
		v=`echo $token | sed 's/.*=//'`
		dataPartitionSize=`sizeNormalize $v`
		;;
	-n=* | --target-final-name=*)
		targetFinalName=`echo $token | sed 's/.*=//'`
		;;
	-s=* | --target-size=*)
		(( targetGeometrySpecifierCount++ ))
		v=`echo $token | sed 's/.*=//'`
		targetSize=`sizeNormalize $v`
		if [ ! "$targetSize" -gt 0 ]; then
			echo "$this: ERR: Bad target size specified." >&2
			usageSuggest
			exit 16
		fi
		;;
	-g=* | --target-geometry=*)
		(( targetGeometrySpecifierCount++ ))
		IFS=":"
		targetGeometry=( `echo $token | sed 's/.*=//'` )
		IFS=
		if [ ${#targetGeometry[*]} -ne 3 \
				-o ! "${targetGeometry[0]}" -gt 0 -o ${targetGeometry[0]} -gt 65535 \
				-o ! "${targetGeometry[1]}" -gt 0 -o ${targetGeometry[1]} -gt 16 \
				-o ! "${targetGeometry[2]}" -gt 0 -o ${targetGeometry[2]} -gt 255 ]; then
			echo "$this: ERR: Bad geometry specified." >&2
			usageSuggest
			exit 16
		fi
		;;
	-t=* | --target-template=*)
		(( targetGeometrySpecifierCount++ ))
		targetTemplateDevice=`echo $token | sed 's/.*=//'`
		;;
	-b=* | --bootloader-directory=*)
		bootloaderDirectory=`readlink -f "${token#*=}"`
		;;
	-*)
		echo "$this: ERR: Unrecognized option." >&2
		usageSuggest
		exit 16
		;;
	*)
		if [ -z $package ]; then
			package=$token
		else
			if [ -z $target ]; then
				target=$token
			else
				echo "$this: ERR: Unexpected argument." >&2
				usageSuggest
				exit 16
			fi
		fi
		;;
	esac
done

# checking parameters
if [ -z $package ]; then
	echo "$this: ERR: Source package is not specified!" >&2
	usageSuggest
	exit 16
fi

if [ -z $target ]; then
	echo "$this: ERR: Target device is not specified!" >&2
	usageSuggest
	exit 16
fi

if [ $targetGeometrySpecifierCount -gt 1 ]; then
	# multiple geometry specifying options (-s, -g, -t) provided
	cat >&2 << EOF
$this: ERR: Target device size, geometry and template options
   are mutualy exclusive. More than one of them provided!
EOF
	usageSuggest
	exit 16
fi

if [ $targetGeometrySpecifierCount -gt 0 ]; then
	# at least one geometry specifying option (-s, -g, -t) provided
	if fileIsDisk $target || fileIsPartition $target; then
		# the specified target is either disk or partition
		cat >&2 << EOF
$this: ERR: Target device size, geometry and template options
   shall be provided only when target image file is to be composed!
EOF
		usageSuggest
		exit 16
	fi
fi

partitionIndex=`partitionIndexGet $target` || partitionIndex=-1
if [[ $fullInstallMode ]]; then
	if (( partitionIndex < 0 )); then
		# not a disk partition; apparently an image file
		if [[ $targetSize ]] && (( targetSize > 0 )); then
			targetGeometry=( `try diskGeometryPropose $(( targetSize / 512 ))` )
		fi
		if [[ $targetTemplateDevice ]]; then
			targetGeometry=( `try diskGeometryQuery \"$targetTemplateDevice\"` )
		fi
		if (( ${#targetGeometry[*]} < 3 )); then
			cat >&2 << EOF
$this: ERR: Target is disk image but its size or geometry is not specified!
EOF
			usageSuggest
			exit 16
		fi
	elif (( partitionIndex == 0 )); then
		# the specified target is disk device
		targetGeometry=( `try diskGeometryQuery \"$target\"` )
	else
		# the specified target is a partition
		cat >&2 << EOF
$this: ERR: Target device is partition but full install mode is requested!
EOF
		usageSuggest
		exit 16
	fi		
	package=`try packageEnsure \"$package\"`
	# repartitioning target
	try disk_repartition \"$target\" ${targetGeometry[*]}
	# extract system on both partitions and install bootloader
	withPartitionTry partition $target 3 systemCreate \"$package\" '$partition' 1
	withPartitionTry partition $target 2 systemCreate \"$package\" '$partition' 0 \"$target\" \"$targetFinalName\"
	withPartitionTry partition $target 5 e2fsCreate '$partition' CONFIG
	withPartitionTry partition $target 6 e2fsCreate '$partition' BACKUP
else
	if (( partitionIndex < 0 )); then
		# not a disk partition; apparently an image file
		partitionIndex=2
	elif (( partitionIndex == 0 )); then
		# the specified target is disk device
		cat >&2 << EOF
$this: ERR: Target device is a disk but full install mode is no requested!
EOF
		usageSuggest
		exit 16
	elif (( partitionIndex < 2 )); then
		# the specified target is a partition inappropriate for system
		cat >&2 << EOF
$this: ERR: Inappropriate partition specified as target!
EOF
		usageSuggest
		exit 16
	else
		# the specified target is a partition
		# trying to reset failure counter for target system
		systemBootClean $target $(( partitionIndex - 2 )) \
			|| cat >&2 << EOF
$this: ERR: systemBootClean failed! Continuing...
EOF
	fi
	package=`try packageEnsure \"$package\"`
	# extracting system on requested partition
	try systemCreate \"$package\" \"$target\" $(( partitionIndex - 2 ))
fi

# EOF
