#!/bin/sh
# tazinst - SliTaz GNU/Linux installer.
#
# So this is the SliTaz installer. The script starts with a
# few main variables, then all the functions and then the
# full sequence of functions.
#
# (C) 2007-2016 SliTaz - GNU General Public License v3.
#
# Authors : Christophe Lincoln <pankso@slitaz.org>
#           Dominique Corbex <domcox@slitaz.org>

# Exit codes:
# 1: Parameters error
# 2: Install file error
# 3: Source error
# 4: Target error
# 5: Missing resource
# 6: SliTaz system to upgrade not found
# 7: Another instance is running
# 8: Internal error
# 9: User cancellation


# path
PATH="/usr/sbin:/usr/bin:/sbin:/bin"
umask 0177

# read SliTaz conf
[ -r /etc/slitaz/slitaz.conf ] && . /etc/slitaz/slitaz.conf

# read Tazinst conf
[ -r /etc/slitaz/tazinst.conf ] && . /etc/slitaz/tazinst.conf

# version
readonly VERSION=4.0

# i18n
. /lib/libtaz.sh
export TEXTDOMAIN='tazinst'


# common kernels arguments
readonly KERNEL_ARGS="video=-32 quiet"

# files
readonly DEFAULT_INSTALL_FILE=./tazinst.rc
readonly SOURCE_ROOT=/media/source
readonly TARGET_ROOT=/mnt/target
readonly LOG=/var/log/tazinst.log
readonly LOCK=/run/tazinst.pid

# settings
readonly SETTINGS="mode media source \
root_uuid root_format home_uuid home_format \
hostname root_pwd user_login user_pwd \
bootloader liveboot webboot winboot"

# modes (key:help)
readonly LST_MODE="
install:$(_ 'Fresh install on a HDD')
upgrade:$(_ 'Upgrade an existing system')"

# media (key:help)
readonly LST_MEDIA="
cdrom:$(_ 'LiveCD')
usb:$(_ 'LiveUSB')
iso:$(_ 'ISO image on a local drive')"

# formats (key:help)
readonly LST_FORMAT="
ext4:$(_ 'ext4: journaled, recommended')
ext3:$(_ 'ext3: journaled, older')
ext2:$(_ 'ext2: no journal, for small or old disks')
btrfs:$(_ 'btrfs: snapshots, for advanced users')
xfs:$(_ 'xfs: journaled, for large disks')
jfs:$(_ 'jfs: journaled (IBM)')
reiser4:$(_ 'reiser4: journaled (Namesys)')
minix:$(_ 'minix: MINIX file system, no journal')"

# bootloaders (key:help)
readonly LST_BOOTLOADER="
auto:$(_ 'GRUB2 for BIOS and UEFI, as the target disk allows')"

# GRUB2 modules, one directory per target (i386-pc, x86_64-efi...)
GRUB_LIB="${GRUB_LIB:-/usr/lib/grub}"



#-------
# usage
#-------

# print a short help
usage()
{
	_ 'SliTaz GNU/Linux Installer - Version: %s' "$VERSION"
	newline; boldify "$(_ 'Usage:')"
	echo -n '  '; _ '%s [--machine] [command] <setting> <value> <file>' 'tazinst'
	newline; boldify "$(_ 'Commands:')"
	optlist "\
new		$(_ 'Create a new install file.')
set		$(_ 'Change value of a setting.')
unset	$(_ 'Clear a setting.')
get		$(_ 'Get the value of a setting.')
check	$(_ 'Check settings.')
describe	$(_ 'Describe settings for frontends.')
options	$(_ 'List values a setting can take.')
plan	$(_ 'Show what execute will do.')
help	$(_ 'Print a short help on settings')
list	$(_ 'List system resources.')
execute	$(_ 'Execute a SliTaz installation.')
log		$(_ 'Display log file contents.')
clean	$(_ 'Clean install and log files.')
version	$(_ 'Print version and exit.')
usage	$(_ 'Print this short usage.')
"
	exit 0
}

usage_error()
{
	local cmd="$1" script="$(basename $0)"
	_ 'SliTaz GNU/Linux Installer - Version: %s' "$VERSION"
	newline
	_ "'%s': Unknown command!" "$cmd"

	_ "Run: '%s' to get a list of available commands." "$script help"
	exit 1
}


option_error()
{
	local option="$1" list="$2"
	_ 'SliTaz GNU/Linux Installer - Version: %s' "$VERSION"
	newline
	_ "'%s': Unknown option!" "$option"

	_ 'Please select one of these options:'
	printf "  %s\n\n" "$list"
	exit 1
}

#---------------------
# 1. settings section
#---------------------

#----------
# 1.1 file
#----------

# create a new install file
new_file()
{
	local install_file=$1
	[ -z "$install_file" ] && install_file="$DEFAULT_INSTALL_FILE"
	if [ -e "$install_file" ]; then
		_ 'Warning: file already exists.' 1>&2
		exit 0
	fi
	[ -n "$install_file" ] && touch "$install_file"
	# ext4 unless told otherwise: an unformatted target is emptied anyway
	[ -n "$ROOT_FORMAT" ] || ! type mkfs.ext4 > /dev/null 2>&1 \
		|| ROOT_FORMAT="ext4"
	# and a bootloader: without one, the new system does not start
	[ -n "$BOOTLOADER" ] || ! type grub-install > /dev/null 2>&1 \
		|| BOOTLOADER="auto"
	if [ -w "$install_file" ]; then
		write_file "$install_file"
	else
		_ 'Error: Cannot create file.' 1>&2
		exit 2
	fi
}

# fill up the install file
write_file()
{
	local install_file="$1"
	cat > "$install_file" <<EOT
# SliTaz GNU/Linux Installer - Version: $VERSION
#
# Install file.
#

# Mode of installation:
#  install: Full install of SliTaz on a disk, all previous info will be erased
#  upgrade: Upgrade an existing SliTaz installation to a new version
# run 'tazinst list mode' to have a full list.
MODE="$MODE"

# Media to install from:
# Options are cdrom usb iso.
# run 'tazinst list media' to see available options on your system.
MEDIA="$MEDIA"

# Install source:
# it depends on the media used:
#  usb: partition, run 'tazinst list uuid' to list your partitions
#  iso: file.iso,  ex: SOURCE=~/slitaz.5.0.iso
# run 'tazinst list <MEDIA>' to list source values. ex: tazinst list iso.
SOURCE="$SOURCE"

# root partition that SliTaz will be Installed on:
# Enter the UUID of the partition.
# run 'tazinst list uuid' to list your partitions
ROOT_UUID="$ROOT_UUID"

# Formatting the root partition:
# Let ROOT_FORMAT empty if you do not want to format the root partition.
# SliTaz uses ext2 ext3 ext4 by default but another filesystem can be
# installed if wanted, for this please adjust your /etc/fstab after
# installation.
# run 'tazinst list format' to list installed filesystems on your system.
ROOT_FORMAT="$ROOT_FORMAT"

# Home partition:
# On most GNU/Linux systems users personal files are stored in the directory
# /home. /home can be on another hard disk or on a separate partition.
# Leave HOME_UUID empty if you do not intend to use a specific partition for /home
# or enter the UUID of the partition to be used.
# run 'tazinst list uuid' to list your partitions.
HOME_UUID="$HOME_UUID"

# Formatting the /home partition (if /home is on a separate partition):
# Leave HOME_FORMAT empty if you do not want to format the /home partition.
# For options, see comments on 'Formatting the root partition' above.
HOME_FORMAT="$HOME_FORMAT"

# Hostname of the new system:
HOSTNAME="$HOSTNAME"

# root password:
# The root administrator privilege lets you manage and configure the full
# system. A root user can damage your system so you should always setup a
# strong password with special characters and/or numbers.
ROOT_PWD="$ROOT_PWD"

# Default user:
# The default user for the system will have his personal files stored
# in /home/<USER_LOGIN> (and will be automatically added to the audio group).
USER_LOGIN="$USER_LOGIN"
USER_PWD="$USER_PWD"

# Install bootloader:
# If you do not want to install a bootloader, leave this field empty.
# 'auto' installs GRUB2 for BIOS and/or UEFI, as the target disk allows:
# BIOS needs a msdos table or a BIOS boot partition (gpt), UEFI needs an
# EFI system partition on the same disk.
# Run 'tazinst list bootloader' to list all options.
BOOTLOADER="$BOOTLOADER"

# SliTaz live/rescue boot:
# If you do not want enable live boot, leave LIVEBOOT empty (LIVEBOOT="").
# You may let tazinst automatically find your rootfs file(s) by specifying auto
# (LIVEBOOT="auto").
LIVEBOOT="$LIVEBOOT"

# SliTaz Web boot:
# If you do not want enable Web boot, leave WEBBOOT empty (WEBBOOT="").
# You may let tazinst automatically find your pxe client by specifying auto
# (WEBBOOT="auto").
WEBBOOT="$WEBBOOT"

# Windows dual boot:
# If you do not want enable Dual boot, leave WINBOOT empty (WINBOOT="").
# You may let tazinst automatically find your win partition by specifying auto
# (WINBOOT="auto"), otherwise enter the UUID of the partition to be used.
# Run 'tazinst list winboot' to see the Windows partitions found by tazinst.
WINBOOT="$WINBOOT"

EOT
	return "$?"
}

read_file()
{
	local install_file="$1"
	[ -z "$install_file" ] && install_file="$DEFAULT_INSTALL_FILE"
	if ! [ -r "$install_file" ]; then
		_ 'Error: Unable to read install file.' 1>&2
		exit 2
	fi
	#
	if ! CONTENTS="$(cat "$install_file")"; then
		_ 'Error: Unable to read install file.' 1>&2
		exit 2
	fi
}

# read value of a setting
get_value()
{
	local setting="$1"
	printf "%s" "$CONTENTS" | /bin/busybox awk -v setting="$setting" 'BEGIN{
		setting="^" toupper(setting) "="
		}
		{
		if (match($0,setting)){
			n=index($0,"=")
			value=substr($0,n+1)
			gsub(/[\t\s]*$/,"",value)
			sub(/^"/,"",value)
			sub(/"$/,"",value)
			}
		}
		END{
			print value
		}'
}

# list of settings
get_settings()
{
	local "mode=$(get_value mode)"
	case "$mode" in
		upgrade)
			echo "mode media source root_uuid bootloader liveboot webboot winboot" ;;
		*)
			printf "%s\n" "$SETTINGS" ;;
	esac
}

# get command
get()
{
	local setting="$1"
	[ -z "$setting" ] && setting="all"
	# setting is valid: display value
	if printf "%s" "$setting" | \
		egrep -q "$(regex "$SETTINGS")"; then
			get_value "$setting"
	else
		case "$setting" in
			all)
				for i in mode media source root_uuid root_format home_uuid home_format \
					hostname root_pwd user_login user_pwd bootloader liveboot webboot winboot; do
					if [ -n "$MACHINE" ]; then
						record "$i" "$(get_value $i)"
					else
						printf "%-15s: %s\n" "$i" "$(get_value $i)"
					fi
				done
				;;
			settings)
				get_settings
				;;
			*)
				option_error "$1" "$SETTINGS"
				;;
		esac
	fi
}

# set command
change()
{
	local setting="$1" value="$2" install_file="$3"
	# validate setting
	if ! printf "%s" "$setting" | \
			egrep -q "$(regex "$SETTINGS")"; then
		_ "Error: '%s' unknown setting." "$setting" 1>&2
		exit 1
	fi
	# and file
	[ -z "$install_file" ] && install_file="$DEFAULT_INSTALL_FILE"
	# write changes to file
	if [ -w "$install_file" ]; then
		printf "%s" "$CONTENTS" | \
			/bin/busybox awk -v setting="$setting" -v value="$value" '
		BEGIN{
			set=0
		}
		{
			if (match($0,"^" toupper(setting) "=")){
				printf toupper(setting) "=\"" value "\"\n"
				set++
			}
			else
				printf $0 "\n"
		}
		END{
			if (! set)
				printf toupper(setting) "=\"" value "\"\n"
		}' > "$install_file"
		# check new value
		read_file "$install_file"
		check "$setting"
	else
		_ 'Error: Unable to write to install file.' 1>&2
		exit 2
	fi
}

#
load_settings()
{
	MODE="$(get mode)"
	local settings="$(get settings)"
	MEDIA="$(get media)"
	echo "source" | egrep -q "$(regex "$settings")" \
		&& SOURCE="$(get source)" \
		|| unset SOURCE
	ROOT_UUID="$(get root_uuid)"
	echo "root_format" | egrep -q "$(regex "$settings")" \
		&& ROOT_FORMAT="$(get root_format)" \
		|| unset ROOT_FORMAT
	echo "home_uuid" | egrep -q "$(regex "$settings")" \
		&& HOME_UUID="$(get home_uuid)" \
		|| unset HOME_UUID
	echo "home_format" | egrep -q "$(regex "$settings")" \
		&& HOME_FORMAT="$(get home_format)" \
		|| unset HOME_FORMAT
	echo "hostname" | egrep -q "$(regex "$settings")" \
		&& HOSTNAME="$(get hostname)" \
		|| unset HOSTNAME
	echo "root_pwd" | egrep -q "$(regex "$settings")" \
		&& ROOT_PWD="$(get root_pwd)" \
		|| unset ROOT_PWD
	echo "user_login" | egrep -q "$(regex "$settings")" \
		&& USER_LOGIN="$(get user_login)" \
		|| unset USER_LOGIN
	echo "user_pwd" | egrep -q "$(regex "$settings")" \
		&& USER_PWD="$(get user_pwd)" \
		|| unset USER_PWD
	echo "bootloader" | egrep -q "$(regex "$settings")" \
		&& BOOTLOADER="$(get bootloader)" \
		|| unset BOOTLOADER
	echo "liveboot" | egrep -q "$(regex "$settings")" \
		&& LIVEBOOT="$(get liveboot)" \
		|| unset LIVEBOOT
	echo "webboot" | egrep -q "$(regex "$settings")" \
		&& WEBBOOT="$(get webboot)" \
		|| unset WEBBOOT
	echo "winboot" | egrep -q "$(regex "$settings")" \
		&& WINBOOT="$(get winboot)" \
		|| unset WINBOOT
}







# clean command
clean()
{
	# rm LOG
	[ -r "$LOG" ] && rm -f "$LOG"
	# rm temp files
	rm -rf /tmp/tazinst
	# rm install file
	local install_file="$1"
	[ -z "$install_file" ] && install_file="$DEFAULT_INSTALL_FILE"
	_ 'Deleting install file: %s' "$install_file"
	if ! [ -w "$install_file" ]; then
		_ 'Error: Unable to delete install file.' 1>&2
		exit 2
	else
		rm -f "$install_file"
	fi
}

#-----------
# 1.2 check
#-----------

# exit if user is not root.
check_root()
{
	if [ $(id -u) -ne 0 ]; then
		_ "You must be the root user (system administrator) \
to install SliTaz, please use 'su' to get a root SHell and restart \
installation." 1>&2
		exit 1
	fi
}

# exit if another instance of tazinst is running
check_instance()
{
	if [ -e "$LOCK" ]; then
		_ 'Another instance of tazinst is running.' 1>&2
		exit 7
	else
		printf "%s" "$$" > $LOCK
	fi
}

# exit if the setting is not in a list of keywords
check_key()
{
	local setting="$1" keylist="$2" keyword="$(get $1)" msg
	if ! printf "%s" "$keyword" | \
			egrep -q "$(regex "$keylist")"; then
		msg="$setting=$keyword
$(_ "Error: '%s' Invalid keyword." "$keyword")
$(_ 'Select one of these options: %s.' "$keylist")
$(_ 'For more information, see tazinst Manual.')"
		printf "%s\n" "$msg" 1>&2
		exit 1
	fi
}

# exit if the partition does not exist
check_uuid()
{
	local setting="$1" value="$(get $1)" found=0 partition msg
	for partition in $(list_uuid); do
		[ "$partition" = "$value" ] && found="$(($found + 1))"
	done
	if [ "$found" != "1" ]; then
		msg="$(gettext "$setting")=$value
$(_ 'Error: Partition not found.')
$(_ "To see available partitions, run '%s'." 'tazinst list uuid')"
		printf "%s\n" "$msg" 1>&2
		exit 1
	fi
}

# exit if the source does not exist
check_source()
{
	local media="$(get media)" source="$(get source)"
	case $media in
		usb)
			check_uuid source ;;
		iso)
			if [ ! -r "$source" ]; then
				_ 'Error: Source file not found.' 1>&2
				exit 1
			fi ;;
	esac
}

# settings holding a partition, but the given one: key device
used_partitions()
{
	local key value
	for key in root_uuid source home_uuid winboot; do
		[ "$key" = "$1" ] && continue
		[ "$key" = "source" ] && [ "$(get media)" != "usb" ] && continue
		value="$(get $key)"
		case "$value" in
			""|auto) continue ;;
		esac
		printf "%s %s\n" "$key" "$(uuid2dev $value)"
	done
}

# exit if the partition of a setting is already used by another one
check_uuid_mix()
{
	local dev="$(uuid2dev $(get $1))" used
	[ -n "$dev" ] || return 0
	used="$(used_partitions $1 | /bin/busybox awk -v dev="$dev" \
		'$2 == dev {print $1; exit}')"
	case "$used" in
		root_uuid) _ 'Error: %s is already the root partition.' "$dev" ;;
		source)    _ 'Error: %s is already the install source.' "$dev" ;;
		home_uuid) _ 'Error: %s is already the /home partition.' "$dev" ;;
		winboot)   _ 'Error: %s is already the Windows partition.' "$dev" ;;
		*) return 0 ;;
	esac 1>&2
	exit 1
}


# exit if a password is invalid
check_password()
{
	local pass="$(get "$1")"
	local invalid="^[A-Za-z0-9!@#$%^&*()_]{0,40}$"
	local errcode=0
	# too long
	if [ "${#pass}" -ge 40 ]; then
		_ 'Error: password too long.' 1>&2
		exit 1
	fi
	# bad chars
	if ! (printf "%s" "$pass" | egrep -q "$invalid"); then
		_ 'Error: Unallowed characters in password.' 1>&2

		exit 1
	fi
	# short pwd
	[ "${#pass}" -le 4 ] && errcode=128
	# empty pwd
	[ -z "$pass" ] && errcode=129
	case "$errcode" in
		128)
			_ 'Warning: short password!' 1>&2 ;;
		129)
			_ 'Warning: no password!' 1>&2 ;;
	esac
	return "$errcode"
}

# exit if a name is invalid
check_name()
{
	local name="$1" value="$(get "$1")" msg
	msg="$name=$value"
	if [ "${#value}" -lt 2 ]; then
		_ '%s Error: Too short.' "$msg" 1>&2
		exit 1
	fi
	if [ "${#value}" -gt 32 ]; then
		_ '%s Error: Too long.' "$msg" 1>&2
		exit 1
	fi
	if printf "%s" "$value" | \
			grep -q "[[:space:]\&\"\'\(\)\|\*\\#\`\+\:/;<>]"; then
		_ '%s Error: Invalid chars.' "$msg" 1>&2
		exit 1
	fi
}

# check bootloader + winboot
check_boot_mix()
{
	local var name
	[ -n "$(get bootloader)" ] && return 0
	while read var name; do
		[ -z "$(get $var)" ] && continue
		_ 'Error: %s set with no bootloader.' "$name" 1>&2
		exit 1
	done <<EOT
liveboot	Rescue/live boot
webboot		Web boot
winboot		Dualboot
EOT
}

# exit if partition table is not in list
check_table()
{
	local pt_list="gpt msdos"
	# get root uuid
	local uuid="$(get root_uuid)"
	if [ "$(blkid | grep -c "$uuid")" = "1" ]; then
		if ! printf "%s" "$(p_table $uuid)" | \
				egrep -q "$(regex "$pt_list")"; then
			_ 'Error: Unsupported Partition Table.' 1>&2
			exit 1
		fi
		if [ -z "$(ROOT_UUID="$uuid" grub_targets)" ]; then
			_ 'Error: No BIOS boot or EFI system partition found on %s' \
				"$(uuid2disk $uuid)" 1>&2
			exit 1
		fi
	else

		_ 'Error: No disk selected, cannot install any bootloader.' 1>&2
		exit 1
	fi
}


# check all settings()
check_all()
{
	# check only settings we need
	for key in $(get settings); do
	case "$key" in
		mode)
			printf "%-15s: " "$key"
			check_key $key "$(key "$LST_MODE")" && echo "ok" ;;
		media)
			printf "%-15s: " "$key"
			check_key $key "$(key "$LST_MEDIA")" && echo "ok" ;;
		source)
			printf "%-15s: " "$key"
			check_source && echo "ok" ;;
		root_uuid)
			printf "%-15s: " "$key"
			check_uuid $key && check_uuid_mix $key && echo "ok" ;;
		root_format|home_format)
			printf "%-15s: " "$key"
			[ -n "$(get $key)" ] && \
			{ check_key $key "$(key "$LST_FORMAT")" && echo "ok"; } \
				|| echo "ok" ;;
		home_uuid)
			printf "%-15s: " "$key"
			[ -n "$(get $key)" ] && \
				{ check_uuid $key && check_uuid_mix $key && echo "ok"; } \
				|| echo "ok" ;;
		hostname|user_login)
			printf "%-15s: " "$key"
			check_name $key && echo "ok" ;;
		root_pwd|user_pwd)
			printf "%-15s: " "${key/pwd/password}"
			check_password $key && echo "ok" ;;
		bootloader)
			printf "%-15s: " "$key"
			[ -n "$(get $key)" ] \
				&& { check_key $key "$(list_bootloader)" \
				&& check_table && echo "ok" ; } \
				|| echo "ok" ;;
		liveboot|webboot)
			printf "%-15s: " "$key"
			[ -n "$(get $key)" ] && [ "$(get $key)" == "auto" ] \
				&& { check_boot_mix && echo "ok"; } \
				|| echo "ok" ;;
		winboot)
			printf "%-15s: " "$key"
			[ -n "$(get $key)" ] && [ "$(get $key)" != "auto" ] \
				&& { check_uuid $key && check_boot_mix && echo "ok"; } \
				|| echo "ok" ;;
	esac
	done
}

# check command
check()
{
	local setting="$1"
	case "$setting" in
		mode)
			check_key $setting "$(key "$LST_MODE")" ;;
		media)
			check_key $setting "$(key "$LST_MEDIA")" ;;
		source)
			check_source ;;
		root_uuid)
			check_uuid $setting
			check_uuid_mix $setting ;;
		home_uuid)
			[ -z "$(get $setting)" ] || check_uuid $setting
			check_uuid_mix $setting ;;
		root_format)
			[ -z "$(get $setting)" ] \
				|| check_key $setting "$(key "$LST_FORMAT")" ;;
		home_format)
			[ -z "$(get $setting)" ] \
				|| check_key $setting "$(key "$LST_FORMAT")" ;;
		hostname|user_login)
			check_name $setting ;;
		root_pwd|user_pwd)
			check_password $setting ;;
		bootloader)
			[ -z "$(get $setting)" ] \
				|| (check_key $setting "$(list_bootloader)" \
				&& check_table ; ) ;;
		liveboot|webboot)
			{ [ -z "$(get $setting)" ] || [ "$(get $setting)" = "auto" ]; } \
				&& check_boot_mix ;;
		winboot)
			{ [ -z "$(get $setting)" ] || [ "$(get $setting)" = "auto" ]; } \
				|| check_uuid $setting && check_boot_mix ;;
		""|all)
			check_all ;;
		*)
			option_error "$setting" "$SETTINGS" ;;
	esac
}

#----------
# 1.3 help
#----------

help_source()
{

	_ 'The Source setting depends on the type of media:'
	printf "%-12s%s\n" "cdrom" "$(help cdrom)"
	printf "%-12s%s\n" "usb" "$(help usb)"
	printf "%-12s%s\n" "iso" "$(help iso)"
}

help_all()
{
	_ 'List of settings:'
	optlist "\
mode		$(_ 'Mode of install')
media		$(_ 'Media containing the SliTaz source files')
source		$(_ 'Source file containing SliTaz')
root_uuid	$(_ 'The name of the target partition')
root_format	$(_ 'Format of the target partition')
home_uuid	$(_ 'Separate home partition')
home_format	$(_ 'Format of the home partition')
hostname	$(_ 'Name of the system')
root_pwd	$(_ 'Superuser password')
user_login	$(_ 'First user name')
user_pwd	$(_ 'First user password')
bootloader	$(_ 'Install a bootloader')
liveboot	$(_ 'Install a live/rescue boot (with root password)')
webboot		$(_ 'Install a web boot (with root password)')
winboot		$(_ 'Partition to dualboot Windows from')
"
}

# help command
help()
{
	local setting="$1"
	case "$setting" in
	mode)
		printf "%s" "$LST_MODE" | \
			/bin/busybox awk -F: '/..*/{printf "%-12s%s\n", $1, $2}' ;;
	media)
		printf "%s" "$LST_MEDIA" | \
			/bin/busybox awk -F: '/..*/{printf "%-12s%s\n", $1, $2}'	;;
	source)
		help_source ;;
	cdrom)
		_ 'CD. Automatically set' ;;
	usb)

		_ 'USB partition. For a list, type: %s' 'tazinst list usb' ;;
	iso)

		_ 'ISO file name. For a list, type: %s' 'tazinst list iso' ;;
	root_format)
	 	printf "%s" "$LST_FORMAT" | \
			/bin/busybox awk -F: '/..*/{printf "%-12s%s\n", $1, $2}' ;;
	home_uuid|root_uuid)
		/sbin/blkid -s TYPE -s LABEL | sort ;;
	home_format)
		printf "%s" "$LST_FORMAT" | \
				/bin/busybox awk -F: '/..*/{printf "%-12s%s\n", $1, $2}' ;;
	hostname)
		_ 'Name of the system' ;;
	root_pwd)
		_ 'Superuser password' ;;
	user_login)
		_ 'First user name' ;;
	user_pwd)
		_ 'First user password' ;;
	bootloader)
		printf "%s" "$LST_BOOTLOADER" | \
			/bin/busybox awk -F: '/..*/{printf "%-12s%s\n",$1,$2}' ;;
	liveboot|webboot)
		_ "Empty string, or 'auto'" ;;
	winboot)
		_ "Partition containing Windows, or 'auto'" ;;
	""|all)
		help_all ;;
	*)
		option_error "$setting" "$SETTINGS" ;;
	esac
}

#---------
# 2. list
#---------

list_media()
{
	local key media
	for key in $(key "$LST_MEDIA") ; do
		case "$key" in
			cdrom)
				[ -e "/dev/cdrom" ] && media="cdrom" ;;
			usb)
				[ -e "/sys/bus/usb" ] && media="$media usb" ;;
			*)
				media="$media $key"
		esac
	done
	printf "%s\n" "$media" | sed 's/^\s//'
}

list_usb()
{
	# List plugged USB disks
	if [ -d /proc/scsi/usb-storage ]; then
		for DEV in /sys/block/sd* ; do
			if readlink $DEV | grep -q usb; then
				DEV=$(basename $DEV)
				if [ -d /sys/block/${DEV}/${DEV}1 ]; then
					blkid /dev/$DEV* | tr ' ' '\n' | /bin/busybox awk '
						/^\/dev\// {
							DEV=$1
							gsub(/:/,"",DEV)
						}
						/^UUID/ {
							UUID=$1
							gsub(/"/,"",UUID)
							printf "%s %s\n",UUID,DEV}'
				fi
			fi
		done
	fi
}

# ISO images in home dirs, and on USB keys or disks mounted on /media, /mnt
list_iso()
{
	find /root /home /media /mnt -maxdepth 4 -type f -name '*.iso' \
		2> /dev/null | sort
}

list_format()
{
	local fs
	# the recommended one first
	type mkfs.ext4    > /dev/null && fs="ext4"
	type mkfs.ext3    > /dev/null && fs="$fs ext3"
	type mkfs.ext2    > /dev/null && fs="$fs ext2"
	type mkfs.btrfs   > /dev/null && fs="$fs btrfs"
	type mkfs.xfs     > /dev/null && fs="$fs xfs"
	type mkfs.jfs     > /dev/null && fs="$fs jfs"
	type mkfs.reiser4 > /dev/null && fs="$fs reiser4"
	type mkfs.minix   > /dev/null && fs="$fs minix"
	printf "%s" "$fs" | sed 's/^\s//'
}

# list partitions
list_uuid()
{
	# list all drives but cdroms
	/sbin/blkid -o export| /bin/busybox awk '
		BEGIN{FS="="}
		/DEVNAME/{dev=$2, type="unknown", label=""}
		/LABEL/{label=$2" "}
		/TYPE/{type=$2}
		/PARTUUID/{if (type != "iso9660"){
			printf "%s %s(%s)\n", dev, label, type}}' | sort -k 2
}

list_partition_table()
{
	/usr/sbin/parted -lms 2>&1 | \
		/bin/busybox awk -F: '/^\/dev\//{printf "%s: %s\n", $1,$6}'
}

list_bootloader()
{
	type grub-install > /dev/null 2>&1 && printf "%s\n" "auto"
}

# list Windows partitions
list_winboot()
{
	/usr/sbin/parted -lms 2>&1 | sed '/zram/,$d' | /bin/busybox awk '
	BEGIN{
		FS=":"
		disknum=-1
		found=0
		winboot=""
		printf "auto"
	}
	{
		# Count disks
		if (match($1,"^/dev")){
			disknum++
			part=0
			disk=$1
			dev=substr($1,6)
			# get removable status
			file="/sys/block/"dev"/removable"
			"cat " file | getline removable
			close("cat ")
		}
		# Count partitions
		if (match($1,"[0-9][0-9]?")){
			# List fixed drives only
			if (removable==0){
				part++
				# Read partition Id
		 		if (match($7,"boot")){
					fs=$5
					# Detect Windows Partition Type: ntfs vfat
					WPT="ntfs|vfat"
					if (fs ~ WPT){
						found++
						# record 1st Windows partition found
						if (found==1){
							printf(" %s%d",disk,part)
						}
					}
				}
			}
		}
	}
	END{printf "\n"}'
}

# list command
list()
{
	local ressource="$1"
	case "$ressource" in
		mode)
			printf "%s\n" "$(key "$LST_MODE")" ;;
		media)
			list_media ;;
		iso)
			list_iso ;;
		usb)
			list_usb ;;
		uuid)
			list_uuid ;;
		format)
			list_format ;;
		bootloader)
			list_bootloader ;;
		winboot)
			list_winboot ;;
		partition_table)
			list_partition_table ;;
		partitions)
			list_partitions ;;
		""|all)
			printf "* mode:\n%s\n\n" "$(key "$LST_MODE")"
			printf "* media:\n%s\n\n" "$(list_media)"
			printf "* usb:\n%s\n\n" "$(list usb)"
			printf "* iso:\n%s\n\n" "$(list_iso)"
			printf "* uuid:\n%s\n\n" "$(list_uuid)"
			printf "* format:\n%s\n\n" "$(list_format)"
			printf "* bootloader:\n%s\n\n" "$(list_bootloader)"
			printf "* partition_table:\n%s\n\n" "$(list_partition_table)"
			printf "* winboot:\n%s\n" "$(list_winboot)"
			;;
		*)
			local options="mode media usb iso uuid format bootloader \
partition_table partitions winboot"
			option_error "$1" "$options" ;;
	esac
}

#-----------------------
# 2.1 frontend protocol
#-----------------------

# Machine readable output for frontends (TUI, CGI, GUI): one record per
# line, fields separated by a TAB. The format is described in
# doc/protocol.txt and is a stable contract: add fields at the end,
# never reorder them.

TAB="$(printf '\t')"

# print a record, fields joined by a TAB
record()
{
	local IFS="$TAB" field fields
	for field in "$@"; do
		# a field never holds a TAB or a newline
		fields="$fields$TAB$(printf "%s" "$field" | tr '\t\n' '  ')"
	done
	printf "%s\n" "${fields#$TAB}"
}

# print the help text of a key in a key:help list
key_help()
{
	printf "%s" "$1" | /bin/busybox awk -v key="$2" \
		'{i=index($0,":"); if (substr($0,1,i-1)==key) print substr($0,i+1)}'
}

# print human readable size from bytes
human_size()
{
	/bin/busybox awk -v b="$1" 'BEGIN{
		split("B K M G T", u); i=1
		while (b >= 1024 && i < 5) {b/=1024; i++}
		printf (i == 1 ? "%d%s\n" : "%.1f%s\n"), b, u[i]}'
}

setting_label()
{
	case "$1" in
		mode)		_ 'Install mode' ;;
		media)		_ 'Source media' ;;
		source)		_ 'SliTaz source' ;;
		root_uuid)	_ 'Root partition' ;;
		root_format)	_ 'Root partition format' ;;
		home_uuid)	_ 'Home partition' ;;
		home_format)	_ 'Home partition format' ;;
		hostname)	_ 'Hostname' ;;
		root_pwd)	_ 'Root password' ;;
		user_login)	_ 'User name' ;;
		user_pwd)	_ 'User password' ;;
		bootloader)	_ 'Bootloader' ;;
		liveboot)	_ 'Rescue boot entry' ;;
		webboot)	_ 'Web boot entry' ;;
		winboot)	_ 'Windows boot entry' ;;
	esac
}

# what a setting is for, in a few sentences
setting_help()
{
	case "$1" in
		mode)
			_ "A fresh install puts SliTaz on a partition. An upgrade \
keeps the settings, users and packages of an installed SliTaz." ;;
		media)
			_ "SliTaz installs from the running live system (CD or USB \
key) or from an ISO image on a local disk." ;;
		source)
			case "$(get media)" in
				usb) _ 'Select the partition of the SliTaz live USB key.' ;;
				iso) _ "Select the SliTaz ISO image to install. If it is \
not listed, choose Other file." ;;
			esac ;;
		root_uuid)
			if [ "$(get mode)" = "upgrade" ]; then
				_ "Select the partition of the SliTaz to upgrade. Its \
settings, /home and list of packages are saved, the new version is \
installed, then they are restored."
			else
				_ 'Select the partition where SliTaz will be installed.'
			fi ;;
		root_format)
			_ 'If you are not sure, choose ext4.' ;;
		home_uuid)
			_ "Personal files are stored in /home. On a separate \
partition, they are kept across reinstalls and upgrades." ;;
		home_format)
			_ 'Keep the partition as it is to keep the files on it.' ;;
		hostname)
			_ "The name of the machine on the network. It can be \
changed after the install." ;;
		root_pwd)
			_ "The root administrator manages the whole system: choose \
a strong password, with numbers and special characters." ;;
		user_login)
			_ "The first user keeps personal files in /home/<user> \
and belongs to the audio group." ;;
		user_pwd)
			_ "The password of the first user. It has to be strong if \
the machine accepts SSH connections." ;;
		bootloader)
			_ "GRUB2 starts SliTaz at power on, on BIOS and UEFI \
machines. Skip it only if another bootloader manages this disk: you will \
have to set it up to start SliTaz." ;;
		liveboot)
			_ "Adds a boot menu entry to start the live system in RAM, \
for rescue." ;;
		webboot)
			_ 'Adds a boot menu entry to start SliTaz from the Internet.' ;;
		winboot)
			_ 'Adds a boot menu entry to start Windows.' ;;
	esac
}

# what a setting can destroy or needs, if anything
setting_warning()
{
	case "$1" in
		root_uuid)
			if [ "$(get mode)" = "upgrade" ]; then
				_ 'The upgrade needs an Internet connection.'
			fi ;;
		root_format)
			_ "Formatting erases everything on the partition. Without \
formatting, it is emptied too, except /home." ;;
		home_format)
			_ 'Formatting erases everything on the partition.' ;;
		liveboot|webboot)
			_ 'The entry asks for the root password.' ;;
	esac
}

# type of the value a frontend has to ask for
setting_type()
{
	case "$1" in
		mode|media|root_format|home_format|bootloader|winboot)
			echo "choice" ;;
		source)
			case "$(get media)" in
				usb) echo "partition" ;;
				iso) echo "file" ;;
				*) echo "none" ;;
			esac ;;
		root_uuid|home_uuid)
			echo "partition" ;;
		hostname|user_login)
			echo "text" ;;
		root_pwd|user_pwd)
			echo "password" ;;
		liveboot|webboot)
			echo "bool" ;;
	esac
}

# describe command: the settings a frontend has to ask for, in order
# key type required active value label help warning
describe()
{
	local key required active
	for key in $(get settings); do
		required=0; active=1
		case "$key" in
			mode|media|root_uuid|hostname|user_login)
				required=1 ;;
			source)
				[ "$(get media)" = "cdrom" ] && active=0 || required=1 ;;
			home_format)
				[ -n "$(get home_uuid)" ] || active=0 ;;
			liveboot|webboot|winboot)
				[ -n "$(get bootloader)" ] || active=0 ;;
		esac
		record "$key" "$(setting_type $key)" "$required" "$active" \
			"$(get $key)" "$(setting_label $key)" \
			"$(setting_help $key)" "$(setting_warning $key)"
	done
}

# name of the system a partition holds, read only
part_system()
{
	local dev="$1" fstype="$2" root="$3" opts="ro" name
	if [ -z "$root" ]; then
		case "$fstype" in
			ext3|ext4) opts="ro,noload" ;;
			ext2|btrfs|xfs|jfs|reiser4|f2fs) ;;
			*) return 0 ;;
		esac
		[ "$(id -u)" = 0 ] || return 0
		root="$(mktemp -d)"
		if ! mount -t "$fstype" -o "$opts" "$dev" "$root" 2> /dev/null; then
			rmdir "$root"
			return 0
		fi
	fi
	# read, never sourced: the file comes from another system
	if [ -r "$root/etc/os-release" ]; then
		name="$(sed -n 's/^PRETTY_NAME=//p' "$root/etc/os-release" \
			| sed q | tr -d '"'"'"'\\`$')"
	elif [ -r "$root/etc/slitaz-release" ]; then
		name="SliTaz $(cat "$root/etc/slitaz-release")"
	fi
	if [ "$root" != "$3" ]; then
		umount "$root"
		rmdir "$root"
	fi
	printf "%s" "$name"
}

# list partitions: device bytes size fstype label disk removable role
# mountpoint model transport system
list_partitions()
{
	local part name disk dev fstype label role removable model mnt transport
	for part in /sys/class/block/*; do
		[ -e "$part/partition" ] || continue
		name="${part##*/}"
		disk="$(basename "$(dirname "$(readlink -f "$part")")")"
		dev="/dev/$name"
		fstype="$(part_tag $dev TYPE)"
		label="$(part_tag $dev LABEL)"
		case "$(part_tag $dev PART_ENTRY_TYPE)" in
			c12a7328-f81f-11d2-ba4b-00a0c93ec93b|0xef)
				role="esp" ;;
			21686148-6449-6e6f-744e-656564454649)
				role="bios_boot" ;;
			0x5|0xf|0x85)
				role="extended" ;;
			*)
				case "$fstype" in
					swap) role="swap" ;;
					iso9660) role="media" ;;
					*) role="data" ;;
				esac ;;
		esac
		removable="$(cat /sys/block/$disk/removable 2>/dev/null)"
		model="$(cat /sys/block/$disk/device/model 2>/dev/null | sed 's/ *$//')"
		case "$(readlink -f /sys/block/$disk)" in
			*/usb[0-9]*) transport="usb"; removable=1 ;;
			*/mmc_host/*) transport="mmc"; removable=1 ;;
			*) transport="" ;;
		esac
		# by device number: the running root shows as /dev/root
		mnt="$(/bin/busybox awk -v dev="$(cat $part/dev)" \
			'$3==dev{print $5; exit}' /proc/self/mountinfo)"
		record "$dev" "$(($(cat $part/size) * 512))" \
			"$(human_size $(($(cat $part/size) * 512)))" "$fstype" "$label" \
			"/dev/$disk" "${removable:-0}" "$role" "$mnt" "$model" \
			"$transport" "$([ "$role" = "data" ] && part_system $dev "$fstype" "$mnt")"
	done
}

# print partitions a setting can use: value label
# (awk: a TAB is IFS white space, 'read' would merge empty fields)
partition_options()
{
	list_partitions | /bin/busybox awk -F"$TAB" -v OFS="$TAB" \
		-v used=" $(used_partitions $1 | cut -d' ' -f2 | tr '\n' ' ') " \
		-v t_holds="$(gettext 'holds %s')" '
		# data partitions only, never one the running system is using,
		# nor one another setting already took
		$8 == "data" && ($9 == "" || $9 ~ /^\/(media|mnt)\//) \
			&& index(used, " " $1 " ") == 0 {
			label = $1 " " $3 " " $4
			if ($5 != "") label = label " \"" $5 "\""
			if ($12 != "") label = label ", " sprintf(t_holds, $12)
			disk = $10
			if ($11 == "usb") disk = "USB" (disk != "" ? " " disk : "")
			else if ($11 == "mmc") disk = "SD" (disk != "" ? " " disk : "")
			if (disk != "") label = label " [" disk "]"
			print $1, label
		}'
}

# options command: values a frontend can offer for a setting
# value label
options()
{
	local key="$1" value
	case "$key" in
		mode)
			for value in $(key "$LST_MODE"); do
				record "$value" "$(key_help "$LST_MODE" $value)"
			done ;;
		media)
			for value in $(list_media); do
				record "$value" "$(key_help "$LST_MEDIA" $value)"
			done ;;
		source)
			case "$(get media)" in
				usb) partition_options source ;;
				iso) list_iso | while IFS= read -r value; do
					record "$value" "$value"
				done ;;
			esac ;;
		root_uuid)
			partition_options root_uuid ;;
		home_uuid)
			record "" "$(_ 'No separate partition')"
			partition_options home_uuid ;;
		root_format|home_format)
			if [ "$key" = "home_format" ]; then
				record "" "$(_ 'Do not format: keep the files')"
			fi
			for value in $(list_format); do
				record "$value" "$(key_help "$LST_FORMAT" $value)"
			done
			if [ "$key" = "root_format" ]; then
				record "" "$(_ 'Do not format: empty it but keep /home')"
			fi ;;
		bootloader)
			for value in $(list_bootloader); do
				record "$value" "$(key_help "$LST_BOOTLOADER" $value)"
			done
			record "" "$(_ 'No bootloader')" ;;
		liveboot|webboot)
			record "auto" "$(_ 'Yes')"
			record "" "$(_ 'No')" ;;
		winboot)
			record "" "$(_ 'No')"
			record "auto" "$(_ 'Automatic selection')"
			for value in $(list_winboot | sed 's/^auto *//'); do
				record "$value" "$value"
			done ;;
		hostname|root_pwd|user_login|user_pwd)
			;;
		*)
			option_error "$key" "$SETTINGS" ;;
	esac
}

# machine check: one record per active setting
# key status message   (status: ok, warning or error)
check_machine()
{
	local keys="$1" key msg status
	[ -n "$keys" ] || keys="$(describe | /bin/busybox awk -F"$TAB" '$4 == 1 {print $1}')"
	for key in $keys; do
		msg="$( (check "$key") 2>&1 >/dev/null)"
		case "$?" in
			0) status="ok" ;;
			128|129) status="warning" ;;
			*) status="error" ;;
		esac
		# drop the 'key=value' reminder of human messages
		msg="$(printf "%s\n" "$msg" | /bin/busybox awk -v p="$key=$(get $key)" '{
			if (index($0, p) == 1) {
				$0 = substr($0, length(p) + 1); sub(/^ +/, "")
			}
			if ($0 != "") print}')"
		record "$key" "$status" "$msg"
	done
}

# plan command: what execute will do, without doing it
# action target detail
plan()
{
	load_settings
	record "mode" "$MODE"
	record "source" "$MEDIA" "$SOURCE"
	case "$MODE" in
		install)
			if [ -n "$ROOT_FORMAT" ]; then
				record "format" "$ROOT_UUID" "$ROOT_FORMAT"
			else
				record "clean" "$ROOT_UUID" "$(_ 'keeping /home')"
			fi
			if [ -n "$HOME_UUID" ]; then
				[ -n "$HOME_FORMAT" ] \
					&& record "format" "$HOME_UUID" "$HOME_FORMAT"
				record "home" "$HOME_UUID"
			fi
			record "hostname" "$HOSTNAME"
			record "user" "$USER_LOGIN" ;;
		upgrade)
			record "upgrade" "$ROOT_UUID" "/etc /home /var/www" ;;
	esac
	if [ -n "$BOOTLOADER" ]; then
		record "bootloader" "$(uuid2disk $ROOT_UUID)" "$(grub_targets)"
		[ -n "$LIVEBOOT" ] && record "liveboot" "$ROOT_UUID"
		[ -n "$WEBBOOT" ] && record "webboot" "$ROOT_UUID"
		[ -n "$(winboot_uuid)" ] && record "winboot" "$(winboot_uuid)"
	fi
}

#----------
# 3. tools
#----------

# list indexes from a list
key()
{
	printf "%s" "$1" | /bin/busybox awk -F: 'BEGIN{
		other=-1
		}
		!/^#|^$/{
			if(other){
				printf "%s", $1
				other++}
			else
				printf " %s", $1
		}
		END{printf "\n"}'
}

# convert a list of words to a regex
regex()
{
	printf "%s" "^$1$" | sed s'/ /$|^/g'
}

# print dev from uuid
uuid2dev()
{
	local uuid="$1"
	if [ "${uuid%=*}" = "UUID" ]; then
		uuid="$(blkid | sed '/ UUID="'${uuid#*=}'"/!d;s|:.*||')"
	fi
	printf "%s" "$uuid"
}

# print disk from uuid
uuid2disk()
{
	printf "%s" "$(uuid2dev $1 | /bin/busybox sed 's|p*[0-9]*$||')"
}

dev2uuid()
{
	local uuid="$1"
	if printf "%s" "$uuid" | grep -q dev; then
		printf "UUID=%s" "$(/sbin/blkid -p -i -o udev "$uuid" \
			| grep ID_FS_UUID= | cut -d '=' -f2)"
	else
		printf "%s" "$uuid"
	fi
}

# print partition scheme from uuid
p_table()
{
	local uuid="$1" table
	table="$(/sbin/blkid -p -s PTTYPE -o value "$(uuid2disk $uuid)" 2>/dev/null)"
	# same names as parted
	[ "$table" = "dos" ] && table="msdos"
	printf "%s" "$table"
}

# print filesystem from uuid
filesys()
{
	local uuid="$1"
	local fs="$(/sbin/blkid -s UUID -s TYPE | \
		grep "$(uuid2dev $uuid)" | cut -d' ' -f3)"
	fs="${fs#TYPE=\"}"
	fs="${fs%\"}"
	printf "%s" "$fs"
}

# return removable status from uuid
is_removable()
{
	local removable=1
	local disk="$(uuid2disk $1 | sed 's|/dev/||')"
	if [ "$disk" ]; then
		[ "$(cat /sys/block/"$disk"/removable 2> /dev/null)" -gt "0" ] \
			&& removable=0
	fi
	return "$removable"
}

# install pkg in current system
need_package()
{
	local pkg="$1"
	if [ ! -d /var/lib/tazpkg/installed/$pkg ]; then
		log "$(_ 'Installing package %s to the current system.' "$pkg")"
		[ -z "$RECHARGE" ] && RECHARGE=true && /usr/bin/tazpkg recharge
		/usr/bin/tazpkg get-install "$pkg" || error 5 "$(_ 'Cannot install %s.' "$pkg")"
	fi
}

# install pkg in target system
add_pkg()
{
	local pkg="$1"
	log "$(_ 'Adding package %s to the target system...' "$pkg")"
	[ -z "$RECHARGE" ] && RECHARGE=true && /usr/bin/tazpkg recharge
	/usr/bin/tazpkg get "$pkg" >> "$LOG"  2>> "$LOG"
	yes "" | /usr/bin/tazpkg install $pkg.tazpkg \
		--root=$TARGET_ROOT >> "$LOG"  2>> "$LOG"
	rm -f $pkg.tazpkg
}

#---------
# 3.1 log
#---------

# start log
log_open()
{
	_ '=== Tazinst: started on %s ===' "$(date "+%x %X")" > "$LOG"
	LOGGING="true"
}

# print and log a comment
log(){
	# a message starting with a number is a progress step (1-100)
	local num="$(printf "%s" "$1" | /bin/busybox awk '{
		num=$1+0
		if (num>0 && num<=100 && $1 ~ /^[0-9]+$/)
			print num
		}')"
	if [ -n "$MACHINE" ]; then
		if [ -n "$num" ]; then
			record "progress" "$num" "${1#* }" >&3
		else
			record "info" "$1" >&3
		fi
	else
		# let dialog frontends display the step
		[ -n "$num" ] && sleep 1
		printf "%s\n" "$1"
	fi
	[ -n "$LOGGING" ] && printf "%s\n" "$1" >> $LOG
}

#--------------------
# 3.2 error handling
#--------------------

# print an error msg & exit
error()
{
	local error="$1" msg="$2" cancel="$(_ 'Process not completed')"
	if [ -n "$MACHINE" ]; then
		record "error" "$error" "$msg" >&3
	else
		_ 'Error: %s' "$msg" >&2; echo "$cancel" >&2
	fi
	if [ -n "$LOGGING" ]; then
		# 1st pattern
		echo "-x-x- " >> "$LOG"
		_ 'Error: %s' "$msg" >> "$LOG"; echo "$cancel" >> "$LOG"
		# 2nd pattern
		echo "x-x-x " >> "$LOG"
		_ '=== Tazinst error on %s ===' "$(date "+%x %X")" >> "$LOG"
	fi
	unset LOGGING
	umount_devices
	exit "$error"
}

error8()
{
	error 8 "$(_ 'Internal error')"
}

error9()
{
	error 9 "$(_ 'Cancelled by user')"
}

#------------------
# 4. disks section
#------------------

#------------
# 4.1 source
#------------

# liveCD
mount_cdrom()
{
	# set device name
	local drive="$(grep "drive name" < \
		/proc/sys/dev/cdrom/info | cut -f 3)"
	[ -n "$drive" ] || drive=cdrom
	local cdrom=/dev/$drive
	# mount cdrom
	if mount -t iso9660 "$cdrom" "$SOURCE_ROOT" 2>>"$LOG"; then
		log "$(_ 'Using files from %s.' "$cdrom")"
	else
		error 3 "$(_ '%s: Mount failed.' "$cdrom")"
	fi
}

# liveUSB
mount_usb()
{
	# /home is on LiveUSB
	if [ -d /home/boot ]; then
		log "$(_ 'Using files from USB device...')"
		ln -s /home/boot $SOURCE_ROOT/boot
	else
		# mount LiveUSB
		if mount "$SOURCE" "$SOURCE_ROOT" 2>>"$LOG"; then
			log "$(_ 'Using files from USB device %s.' "$SOURCE")"
		else
			error 3 "$(_ '%s: Failed to mount USB device.' "$SOURCE")"
		fi
	fi
}

# ISO file on HDD
mount_iso()
{
	# check integrity
	local md5file=$(printf "%s" $SOURCE | sed 's/.iso$/.md5/')
	if [ -r "$md5file" ]; then
		local md5ref="$(cut -d' ' -f1 < "$md5file")"
		local md5calc="$(md5sum $SOURCE | cut -d' ' -f1)"
		if [ ! "$md5calc" = "$md5ref" ]; then
			log "md5sum $md5file=$md5ref md5sum $SOURCE=$md5calc"
			error 3 "$(_ '%s does not match %s: the ISO is damaged or the md5 file is outdated.' \
				"$SOURCE" "$md5file")"
		fi
	else
		log "$(_ '%s: md5 file not found, cannot check integrity.' "$SOURCE")"
	fi
	# mount ISO
	if mount -o loop -t iso9660 "$SOURCE" \
			"$SOURCE_ROOT" 2>>"$LOG"; then
		log "$(_ 'Using files from ISO %s.' "$SOURCE")"
	else
		error 3 "$(_ '%s: Failed to mount ISO.' "$SOURCE")"
	fi
}

# set up source and check SliTaz' content
mount_source()
{
	log "$(_ 'Creating mount point: %s...' "$SOURCE_ROOT")"
	mkdir -p "$SOURCE_ROOT"
	case "$MEDIA" in
	cdrom)
		mount_cdrom ;;
	usb)
		mount_usb ;;
	iso)
		mount_iso ;;
	*)
		error8 ;;
	esac

	#  exit if no rootfs.gz found.
	log "$(_ 'Checking installation media...')"
	if [ ! -f $SOURCE_ROOT/boot/rootfs.gz ] &&
	   [ ! -f $SOURCE_ROOT/boot/rootfs1.gz ]; then
		error 3 "$(_ 'Invalid source')"
	else
		log "$(_ 'Installation media checked ok')"
	fi
}

#------------
# 4.2 target
#------------

# format a partition
format()
{
	local uuid="$1" fmt="$2" dest="$3" dev format
	log "$(_ 'Format %s (%s)' "$uuid" "$fmt")"
	format="mkfs.$fmt"
	if (printf "%s" "$uuid" | grep -q "UUID="); then
		# case UUID=
		dev="$(uuid2dev $uuid)"
		"$format" "$dev" >>"$LOG" 2>>"$LOG" || error 4 "$(_ 'Formatting has failed')"
		# uuid has changed
		case "$dest" in
		root_uuid)
			ROOT_UUID="$(/sbin/blkid -p "$dev" -o export | \
				grep ^UUID=)" ;;
		home_uuid)
			HOME_UUID="$(/sbin/blkid -p "$dev" -o export | \
				grep ^UUID=)" ;;
		esac
	else
		# case /dev/xxxx
		"$format" $uuid >>"$LOG" 2>>"$LOG" || error 4 "$(_ 'Formatting has failed')"
	fi
}

# prepare partitions
prepare_uuid()
{
	log "$(_ 'Preparing target partition...')"
	local uuid
	# target may be in use
	if mount | grep -q "$ROOT_UUID" ;then
		log "$(_ 'Partition is already mounted, unmounting.')"
		umount "$ROOT_UUID"
	fi
	mount | grep -q "$ROOT_UUID" && \
		error 4 "$ROOT_UUID: $(_ 'Partition is already in use.')"
	# Mount point can be already used.
	mount | grep -q "$TARGET_ROOT" && \
		umount "$TARGET_ROOT" 2>>"$LOG"

	# Formatting root partition
	case "$ROOT_FORMAT" in
		"")
			log "$(_ '%s: The partition will be cleaned...' "$ROOT_UUID")" ;;
		*)
			format "$ROOT_UUID" "$ROOT_FORMAT" root_uuid
	esac

	# Formatting /home
	if [ -n "$HOME_UUID" ]; then
		case "$HOME_FORMAT" in
			"")
				log "$(_ '%s: The partition will be kept...' "$HOME_UUID")" ;;
			*)
				format "$HOME_UUID" "$HOME_FORMAT" home_uuid
		esac
	fi

	log "$(_ 'Creating mount point: %s...' "$TARGET_ROOT")"
	mkdir -p "$TARGET_ROOT" >> "$LOG" || error8
	# Mount target.
	local mount_fs="$ROOT_FORMAT"
	[ -z "$mount_fs" ] && mount_fs="$(filesys $ROOT_UUID)"
	mount -t "$mount_fs" "$ROOT_UUID" \
			"$TARGET_ROOT" >>"$LOG" 2>>"$LOG"
	if [ $(mount | \
			grep -c "mnt/target") -eq 0 ]; then
		error 4 "$(_ '%s: Unable to mount partition' "$ROOT_UUID")"
	fi
}


#------------
# 4.3 umount
#------------

# copy log file, umount target and eject cdrom.
umount_devices()
{
	# umount target
	if mount | grep -q "$TARGET_ROOT"; then
		log "$(_ 'Unmounting target partition: %s' "$ROOT_UUID")"
		umount -l "$TARGET_ROOT" 2>>$LOG
	fi

	# umount source
	if mount | grep -q "$SOURCE_ROOT"; then
		log "$(_ 'Unmounting: %s' "$SOURCE_ROOT")"
		umount -l "$SOURCE_ROOT"
	fi
	if [ -h $SOURCE_ROOT/boot ]; then
		log "$(_ 'Unlinking: %s' "$SOURCE_ROOT")"
		rm -f $SOURCE_ROOT/boot
	fi

	# eject cd
	if [ "$SOURCE" = "cdrom" ]; then
		_ 'Ejecting CD-ROM...'
		eject
	fi
	# remove lock file
	rm -f "$LOCK"
}

end_of_install()
{
	log "$(_ 'Process completed. You can now restart (reboot) from your SliTaz GNU/Linux system.' |\
		fold -s)"
	_ '=== Tazinst ended on %s ===' "$(date "+%x %X")" >> "$LOG"
	unset LOGGING
	# saving log
	log "$(_ 'Copying log to %s' '/var/log/tazinst.log')"
	cp -a "$LOG" $TARGET_ROOT/var/log
	umount_devices
}


#---------------
# 5. bootloader
#---------------

# GRUB2 is the only bootloader, on i486 and x86_64 alike. Every target
# the disk layout allows is installed, so the same disk boots on BIOS
# and on UEFI firmware:
#  - BIOS (i386-pc): msdos table, or gpt with a BIOS boot partition
#  - UEFI (x86_64-efi, i386-efi): an EFI system partition on the disk
# A target is skipped when its modules are missing in $GRUB_LIB.

readonly ESP_TYPES="c12a7328-f81f-11d2-ba4b-00a0c93ec93b|0xef"
readonly BIOS_BOOT_TYPE="21686148-6449-6e6f-744e-656564454649"

# print the partitions of the disk holding a partition
disk_partitions()
{
	local disk="$(uuid2disk $1)" part
	disk="${disk#/dev/}"
	for part in /sys/block/$disk/$disk*; do
		[ -e "$part/partition" ] && printf "/dev/%s\n" "${part##*/}"
	done
}

# print the first partition of the root disk with a type in $1 (regex)
find_partition()
{
	local part type
	for part in $(disk_partitions $ROOT_UUID); do
		type="$(/sbin/blkid -p -s PART_ENTRY_TYPE -o value $part 2>/dev/null)"
		printf "%s" "$type" | egrep -qi "^($1)$" && printf "%s" "$part" && return
	done
}

# print the boot targets available for the root disk
grub_targets()
{
	local targets
	if [ -d "$GRUB_LIB/i386-pc" ]; then
		case "$(p_table $ROOT_UUID)" in
			msdos)
				targets="i386-pc" ;;
			gpt)
				[ -n "$(find_partition "$BIOS_BOOT_TYPE")" ] && targets="i386-pc" ;;
		esac
	fi
	if [ -n "$(find_partition "$ESP_TYPES")" ]; then
		[ -d "$GRUB_LIB/x86_64-efi" ] && targets="$targets x86_64-efi"
		[ -d "$GRUB_LIB/i386-efi" ] && targets="$targets i386-efi"
	fi
	printf "%s" "${targets# }"
}

# print value of a blkid tag for a partition
part_tag()
{
	/sbin/blkid -p -s "$2" -o value "$(uuid2dev $1)" 2>/dev/null
}

# print winboot partition
winboot_uuid()
{
	if [ "$WINBOOT" = "auto" ]; then
		# get the first uuid if any
		[ $(list_winboot | wc -w) -gt 1 ] \
			&& printf "%s" "$(list_winboot | cut -d' ' -f2)"
	else
		printf "%s" "$WINBOOT"
	fi
}

# print kernel arguments of the live/rescue boot
live_args()
{
	local lang kmap
	lang="$(sed '/^LANG=/!d;s|LANG=||' $TARGET_ROOT/etc/locale.conf 2>/dev/null)"
	kmap="$(cat $TARGET_ROOT/etc/keymap.conf 2>/dev/null)"
	printf "%s" "rw root=/dev/null autologin"
	[ -n "$lang" ] && printf " lang=%s" "$lang"
	[ -n "$kmap" ] && printf " kmap=%s" "$kmap"
}

# create /boot/grub/grub.cfg
grub_config()
{
	local cfg="$TARGET_ROOT/boot/grub/grub.cfg"
	local release="$(cat $TARGET_ROOT/etc/slitaz-release)"
	local fs_uuid="$(part_tag $ROOT_UUID UUID)"
	local part_uuid="$(part_tag $ROOT_UUID PART_ENTRY_UUID)"
	local restricted hash winpart
	[ -n "$fs_uuid" ] && [ -n "$part_uuid" ] || error8
	# backup an existing conf
	mkdir -p $TARGET_ROOT/boot/grub || error8
	[ -e "$cfg" ] && cp "$cfg" "${cfg%.cfg}.bak"

	# root password is required to edit entries and for rescue boots
	[ -n "$ROOT_PWD" ] && hash="$(printf "%s\n%s\n" "$ROOT_PWD" "$ROOT_PWD" | \
		grub-mkpasswd-pbkdf2 2>/dev/null | sed '/grub\.pbkdf2/!d;s|.* ||')"
	[ -n "$hash" ] && restricted="--unrestricted"

	cat > "$cfg" <<EOF
# /boot/grub/grub.cfg: GRUB2 configuration, written by tazinst.

set default=0
set timeout=5

terminal_output console
set menu_color_normal=brown/black
set menu_color_highlight=black/brown

# A framebuffer for the kernel: UEFI provides one, a BIOS needs a VESA
# mode, else X has no /dev/fb0 when no KMS driver claims the card.
insmod all_video
if [ "\$grub_platform" = pc ]; then
	set gfxpayload=1024x768x32,800x600x32,640x480x32
fi

search --no-floppy --fs-uuid --set=root $fs_uuid
EOF
	[ -n "$hash" ] && cat >> "$cfg" <<EOF

set superusers="root"
password_pbkdf2 root $hash
EOF
	cat >> "$cfg" <<EOF

menuentry "SliTaz GNU/Linux $release (Kernel $KERNEL)" $restricted {
	linux /boot/$KERNEL root=PARTUUID=$part_uuid rootwait $KERNEL_ARGS
}
EOF

	if [ -n "$LIVEBOOT" ] && [ -n "$ROOTFS" ]; then
		local rootfs_files="$(cd $TARGET_ROOT/boot/tazinst.$$ ; ls rootfs*)"
		log "$(_ 'Enabling live/rescue boot with %s' "$rootfs_files")"
		for i in $rootfs_files; do
			[ -s $TARGET_ROOT/boot/$i ] &&
				mv $TARGET_ROOT/boot/$i $TARGET_ROOT/boot/$i.bak
			mv $TARGET_ROOT/boot/tazinst.$$/$i $TARGET_ROOT/boot/
		done
		cat >> "$cfg" <<EOF

menuentry "SliTaz GNU/Linux live/rescue $release" {
	linux /boot/$KERNEL $(live_args)
	initrd /boot/$ROOTFS
}
EOF
	fi

	if [ -n "$WEBBOOT" ] && [ -n "$IPXE" ]; then
		log "$(_ 'Enabling Webboot using %s' $IPXE)"
		mv -f $TARGET_ROOT/boot/tazinst.$$/$IPXE $TARGET_ROOT/boot/
		cat >> "$cfg" <<EOF

# iPXE is a BIOS kernel image.
if [ "\$grub_platform" = pc ]; then
	menuentry "SliTaz GNU/Linux web boot" {
		linux16 /boot/$IPXE
	}
fi
EOF
	fi

	winpart="$(winboot_uuid)"
	if [ -n "$winpart" ]; then
		log "$(_ 'Enabling Windows dual-boot')"
		cat >> "$cfg" <<EOF

menuentry "Microsoft Windows" $restricted {
	if [ "\$grub_platform" = efi ]; then
		search --no-floppy --file --set=root /EFI/Microsoft/Boot/bootmgfw.efi
		chainloader /EFI/Microsoft/Boot/bootmgfw.efi
	else
		search --no-floppy --fs-uuid --set=root $(part_tag $winpart UUID)
		chainloader +1
	fi
}
EOF
	fi

	# the password hash is for root eyes only
	[ -n "$hash" ] && chmod 600 "$cfg"

	# log without the password hash
	printf "%s\n" "--- grub.cfg -------------" >> "$LOG"
	sed 's|^password_pbkdf2 .*|password_pbkdf2 root ...|' "$cfg" >> "$LOG"
	printf "%s\n\n" "--- grub.cfg -------------" >> "$LOG"
}

# install GRUB2 for every target the root disk allows
grub_install()
{
	local disk="$(uuid2disk $ROOT_UUID)" targets="$(grub_targets)"
	local target esp mnt="/media/esp$$" loader status
	[ -n "$targets" ] || error 5 "$(_ 'No BIOS boot or EFI system partition found on %s' "$disk")"
	grub-install --version >> "$LOG" || error 5 "$(_ 'GRUB not found')"
	for target in $targets; do
		log "$(_ 'Installing GRUB (%s) on: %s' "$target" "$disk")"
		case "$target" in
		i386-pc)
			grub-install --target=i386-pc --directory="$GRUB_LIB/i386-pc" \
				--boot-directory=$TARGET_ROOT/boot "$disk" \
				>> "$LOG" 2>> "$LOG" || error 5 "$(_ 'GRUB installation failed')"
			# some BIOS only boot a disk with an active partition
			if [ "$(p_table $ROOT_UUID)" = "msdos" ]; then
				log "$(_ 'Setting the boot flag')"
				/usr/sbin/parted "$disk" set \
					"$(uuid2dev $ROOT_UUID | sed 's|.*[^0-9]||')" \
					boot on >> "$LOG" 2>> "$LOG"
			fi ;;
		*-efi)
			esp="$(find_partition "$ESP_TYPES")"
			mkdir -p $mnt && mount "$esp" $mnt 2>> "$LOG" \
				|| error 4 "$(_ '%s: Unable to mount partition' "$esp")"
			if is_removable "$ROOT_UUID"; then
				# the firmware looks for /EFI/BOOT/BOOT*.EFI
				grub-install --target=$target --directory="$GRUB_LIB/$target" \
					--efi-directory=$mnt --boot-directory=$TARGET_ROOT/boot \
					--removable --no-nvram >> "$LOG" 2>> "$LOG"
				status=$?
			else
				# a boot entry for /EFI/slitaz, plus the fallback
				# loader when no other system owns it
				local nvram="--no-nvram"
				[ -d /sys/firmware/efi/efivars ] && \
					type efibootmgr > /dev/null 2>&1 && nvram=""
				grub-install --target=$target --directory="$GRUB_LIB/$target" \
					--efi-directory=$mnt --boot-directory=$TARGET_ROOT/boot \
					--bootloader-id=slitaz $nvram >> "$LOG" 2>> "$LOG"
				status=$?
				case "$target" in
					x86_64-efi) loader="X64" ;;
					i386-efi) loader="IA32" ;;
				esac
				mkdir -p $mnt/EFI/BOOT
				[ -e $mnt/EFI/BOOT/BOOT$loader.EFI ] || \
					cp $mnt/EFI/slitaz/grub$(echo $loader | tr A-Z a-z).efi \
						$mnt/EFI/BOOT/BOOT$loader.EFI 2>> "$LOG"
			fi
			umount $mnt && rmdir $mnt
			[ "$status" = 0 ] || error 5 "$(_ 'GRUB installation failed')" ;;
		esac
	done
}

# selection
bootloader()
{
	# /boot/grub is world readable, like any installed system dir
	umask 0022
	if [ -n "$BOOTLOADER" ]; then
		grub_config
		grub_install
	else
		log "$(_ 'No bootloader to install.')"
	fi
	rm -rf $TARGET_ROOT/boot/tazinst.$$ 2> /dev/null
	umask 0177
}

#--------------------
# 6. execute section
#--------------------

execute()
{
	local mode="$(get mode)" sighup=1 sigint=2 sigquit=3 sigterm=15
	trap "error9" "$sighup" "$sigint" "$sigquit" "$sigterm"
	case "$mode" in
	install)
		install ;;
	upgrade)
		upgrade ;;
	esac
}

#-------------
# 6.1 install
#-------------

# get a clean target device
clean_target()
{
	if [ -z "$ROOT_FORMAT" ]; then
		# partition was not formatted
		log "$(_ 'Cleaning the root partition (%s)...' "$ROOT_UUID")"
		# keep /home in case of reinstall.
		local path="$(pwd)"
		cd "$TARGET_ROOT" || error8
		local dir
		for dir in *
		do
			case "$dir" in
				home)
					log "$(_ 'keeping /home found on: %s' "$ROOT_UUID")"
					mv home home.bak ;;
				lost+found)
					continue ;;
				*)
					log "$(_ 'removing target: %s' "$dir")"
					rm -rf "$dir" 2>>"$LOG" ;;
			esac
		done
		if [ ! -d lost+found ]; then
			mklost+found 2>>"$LOG"
		fi
		cd "$path" || error8
	fi
}

# kernel is renamed to standard vmlinuz-$VERSION.
install_kernel()
{
	# name the kernel after the installed modules, not the running system
	local version="$(ls $TARGET_ROOT/lib/modules 2> /dev/null | sed q)"
	if [ -z "$version" ]; then
		version="$(uname -r)"
		log "$(_ 'Kernel name not found, falling back to: %s' "$version")"
	fi
	KERNEL="vmlinuz-$version"
	mkdir -p -m 755 $TARGET_ROOT/boot || error8
	for i in $SOURCE_ROOT/boot/bzImage* ; do
		cp $i $TARGET_ROOT/boot/${KERNEL%slitaz*}slitaz${i#*bzImage}
	done
	mkdir $TARGET_ROOT/boot/tazinst.$$
	for i in $(ls -r $SOURCE_ROOT/boot/rootfs?*.gz 2> /dev/null | sed q) \
		 $SOURCE_ROOT/boot/rootfs.gz $SOURCE_ROOT/boot/*pxe* ; do
		[ -e "$i" ] && cp $i $TARGET_ROOT/boot/tazinst.$$/
	done
	IPXE="$(cd $TARGET_ROOT/boot/tazinst.$$ ; ls *pxe* 2> /dev/null)"
	ROOTFS="$(cd $TARGET_ROOT/boot/tazinst.$$ ; ls rootfs.* 2> /dev/null)"
	[ -n "$ROOTFS" ] ||
		ROOTFS="$(cd $TARGET_ROOT/boot/tazinst.$$ ; ls -r rootfs?*.* | sed q)"
	log "$(_ 'install_kernel: %s' "$KERNEL")"
}

# extract packed rootfs: squashfs or cromfs
extract_loramfs()
{
	local i
	for i in $(/bin/busybox cpio -idvum 2> /dev/null); do
		case "$i" in
		rootfs*)
			need_package squashfs
			if ! unsquashfs $i ; then
				need_package cromfs
				unmkcromfs $i squashfs-root
			fi
			mv -f squashfs-root/* .
			rmdir squashfs-root
			rm -f $i
		esac
	done
}

# this is a loram rootfs.gz, skip loram bootstrap and extract
extract_first_loramfs()
{
	local path="$(pwd)"
	(zcat $1 || /usr/bin/unlzma < $1) | \
		/bin/busybox cpio -i extractfs.cpio 2> /dev/null &&
		( cd / ; /bin/busybox cpio -id ) < extractfs.cpio && \
		rm -f extractfs.cpio
	ofs=$(/bin/busybox awk '/07070100/ { o+=index($0,"07070100"); printf "%d\n",o/4 ; exit } { o+=1+length() }' < $1)
	dd if=$1 skip=$(($ofs / 1024)) bs=4k count=1 2> /dev/null | \
	( dd skip=$(($ofs % 1024)) bs=4 2> /dev/null ; \
	  dd if=$1 skip=$((1 + ($ofs / 1024) )) bs=4k ) | \
		extract_loramfs
	cd "$path" || error8
}

# extract lzma'ed or gziped rootfs.
extract_rootfs()
{
	local path="$(pwd)"
	local isloramfs
	cd "$TARGET_ROOT" || error8
	if [ -d "$1"/../fs/etc ]; then
		# this is a tazlitobox loram (cdrom)
		cp -a "$1"/../fs/. .
	else
	for i in $(ls "$1"/rootfs* | sort -r); do
		if [ ! -d etc ]; then
			if [ $( (zcat "$i" 2>/dev/null \
					|| /usr/bin/lzma d "$i" -so) | \
				wc -c) -lt $(stat -c %s "$i") ]; then
				# this is a tazlitobox loram (ram)
				isloramfs="$i"
				extract_first_loramfs "$i"
				continue
			fi
		fi
		if [ -n "$isloramfs" ]; then
			extract_loramfs < "$i"
			continue
		fi
		( zcat "$i" 2>/dev/null || /usr/bin/lzma d "$i" -so || \
		  cat "$i" ) 2>>"$LOG" | /bin/busybox cpio -idu
	done 2>>"$LOG" > /dev/null
	fi
	$(which install) -m 644 /etc/keymap.conf /etc/locale.conf /etc/TZ /etc/network.conf etc
	cp -a /usr/lib/locale usr/lib/
	# unpack /usr (double check...)
	if ls etc/tazlito | grep -q ".extract"; then
		for i in etc/tazlito/*.extract; do
			[ -f "$i" ] && . "$i" /media/cdrom
		done
	fi
	cd "$pwd" || error8
}


# pre configure freshly installed system (60 - 80%).
pre_config_system()
{
	local path="$(pwd)"
	cd "$TARGET_ROOT" || error8
	# restore backup of existing /home if exists.
	# (created by prepare_target_dev)
	if [ -d home.bak ]; then
		log "$(_ 'Restoring directory: /home...')"
		rm -rf home
		mv home.bak home
	fi
	# add root device to CHECK_FS in rcS.conf to check filesystem
	# on each boot.
	log "$(_ 'Adding / partition and CHECK_FS to file %s...' '/etc/rcS.conf')"
	sed	-i s#'CHECK_FS=\"\"'#"CHECK_FS=\"$(dev2uuid $ROOT_UUID)\""# etc/rcS.conf
	# set hostname.
	log "$(_ 'Configuring host name: %s' "$HOSTNAME")"
	printf "%s\n" "$HOSTNAME" > etc/hostname
	printf "%s\n" "127.0.0.1	localhost $HOSTNAME tazpanel" > etc/hosts
	cd "$path" || error8
}

# set root passwd and create user after rootfs extraction.
users_settings()
{
	# create file
	cat > "$TARGET_ROOT/users.sh" << _EOF_
#!/bin/sh
umask 0022
printf "root:%s" "$ROOT_PWD" | chpasswd -m
adduser -D -H $USER_LOGIN

for grp in audio cdrom floppy dialout disk kmem tape tty video; do
 if ! grep \$grp /etc/group | grep -q $USER_LOGIN ; then
	grep -q \$grp /etc/group && addgroup $USER_LOGIN \$grp
 fi
done

printf "%s:%s" "$USER_LOGIN" "$USER_PWD" | chpasswd -m
if [ ! -d /home/$USER_LOGIN ]; then
	cp -a /etc/skel /home/$USER_LOGIN
	[ -e /root/.xinitrc ] && cp /root/.xinitrc /home/$USER_LOGIN
	mkdir -p /home/$USER_LOGIN/.config/slitaz
	cp -a /etc/slitaz/applications.conf /home/$USER_LOGIN/.config/slitaz
	# Set ownership
	if grep -q ^users: /etc/group; then
		chown -R $USER_LOGIN:users /home/$USER_LOGIN
	else
		chown -R $USER_LOGIN:$USER_LOGIN /home/$USER_LOGIN
	fi
	# Path for user desktop files.
	for i in /home/$USER_LOGIN/.local/share/applications/*.desktop
	do
		[ -e "$i" ] && sed -i s/"user_name"/"$USER_LOGIN"/g \$i
	done
fi
# Slim default user.
if [ -f /etc/slim.conf ]; then
	sed -i s/"default_user .*"/"default_user        $USER_LOGIN"/ \
		/etc/slim.conf
fi
_EOF_
	chmod o+x "$TARGET_ROOT/users.sh"
	chroot "$TARGET_ROOT" ./users.sh 2>>"$LOG" >> "$LOG"
	rm "$TARGET_ROOT/users.sh"
	umask 0177
}

# /home can be on a separate partition. If default user exists in /home
# we remove default file created by users_settings().
home_config()
{
	if [ -n "$HOME_UUID" ]; then
		local path="$(pwd)" uuid
		cd "$TARGET_ROOT" || error8
		# detect fs
		local home_fs="$HOME_FORMAT"
		[ -z "$home_fs" ] && home_fs="$(filesys $HOME_UUID)"
		# manage /home
		log "$(_ 'Configuring partition to be used as /home: %s' "$HOME_UUID")"
		mv home/$USER_LOGIN tmp
		mount -t "$home_fs" "$HOME_UUID" home >> "$LOG" 2>> "$LOG"
		if [ -d $TARGET_ROOT/home/$USER_LOGIN ]; then
			rm -rf tmp/home/$USER_LOGIN
		else
			mv tmp/$USER_LOGIN home
		fi
		# write entry in fstab, force uuid
		uuid="$(dev2uuid "$HOME_UUID")"
		printf "%b\n" "$uuid /home $home_fs defaults \t0 \t2"	>> etc/fstab
		umount home >> "$LOG" 2>> "$LOG"
		cd "$path" || error8
	fi
}

install()
{
	log_open
	log "1 $(_ 'Installing SliTaz on: %s' "$(get root_uuid)")"
	log "5 $(_ 'Checking settings...')"
	check all >> "$LOG" 2>> "$LOG"
	load_settings

	log "10 $(_ 'Preparing source media...')"
	mount_source

	log "20 $(_ 'Preparing target disk...')"
	prepare_uuid

	log "30 $(_ 'Cleaning the root partition if necessary...')"
	clean_target

	log "40 $(_ 'Extracting the root system...')"
	extract_rootfs $SOURCE_ROOT/boot

	log "50 $(_ 'Installing the Kernel...')"
	install_kernel

	log "60 $(_ 'Preconfiguring the system...')"
	pre_config_system

	log "70 $(_ 'Configuring root and default user account...')"
	users_settings

	log "80 $(_ 'Configuring /home...')"
	home_config

	log "90 $(_ 'Checking bootloader installation...')"
	bootloader

	log "100 $(_ 'Files installation completed')"
	end_of_install
}


#-------------
# 6.2 upgrade
#-------------

# search for SliTaz
check_release()
{
	if [ -f $TARGET_ROOT/etc/slitaz-release ]; then
		local release="$(cat $TARGET_ROOT/etc/slitaz-release)"
		log "$(_ 'Preparing upgrade of SliTaz release: %s' "$release")"
	else
		error 6 "$(_ "%s: This partition doesn't appear to contain a valid \
SliTaz system, the file: %s doesn't exist." "$ROOT_UUID" '/etc/slitaz-release')"
	fi
}

# backup packages list.
backup_files()
{
	local path="$(pwd)"
	cd "$TARGET_ROOT" || error8
	ls -1 var/lib/tazpkg/installed > home/packages-selection.list
	local dir
	for dir in *
	do
		case "$dir" in
			boot)
				rm -rf boot/vmlinuz-* ;;
			home)
				mv home home.bak
				log "$(_ 'keeping /home found on: %s' "$ROOT_UUID")" ;;
			etc)
				/bin/busybox tar czf etc.tar.gz etc
				mv etc etc.bak
				log "$(_ 'keeping /etc found on: %s' "$ROOT_UUID")" ;;
			var)
				if [ -d var/www ]; then
					mv var/www www.bak
					log "$(_ 'keeping /var/www found on: %s' "$ROOT_UUID")"
				fi
				rm -rf var 2>>"$LOG" ;;
			lost+found)
				continue ;;
			*)
				log "$(_ 'removing target: %s' "$dir")"
				rm -rf "$dir" 2>>"$LOG" ;;
		esac
	done
	if [ -d mklost+found ]; then
		mklost+found 2>>"$LOG"
	fi
	cd "$path" || error8
}

# restore backups.
restore_files()
{
	rm -rf $TARGET_ROOT/home
	mv $TARGET_ROOT/home.bak $TARGET_ROOT/home
	rm -rf $TARGET_ROOT/etc
	mv $TARGET_ROOT/etc.bak $TARGET_ROOT/etc
	if [ -d $TARGET_ROOT/www.bak ]; then
		rm -rf $TARGET_ROOT/var/www
		mv $TARGET_ROOT/www.bak $TARGET_ROOT/var/www
	fi
	log "$(_ 'backups restored: %s' "$(date)")"

	# /var/lib/slitaz-installer
	mkdir -p $TARGET_ROOT/var/lib/tazinst && \
	mv $TARGET_ROOT/etc.tar.gz $TARGET_ROOT/var/lib/tazinst && \
	mv $TARGET_ROOT/home/packages-selection.list \
		$TARGET_ROOT/var/lib/tazinst \
		&& log "$(_ 'backups saved in %s' '/var/lib/tazinst')"
}

# upgrade added pkgs
install_pkgs()
{
	# check if the pkg is on the mirror.
	log "$(_ 'Checking the availability of packages...')"
	touch packages-to-install.list
	packages=0
	diff="$(sort < packages-selection.diff)"
	for pkg in $diff
	do
		if grep -q "^$pkg-[0-9]" /var/lib/tazpkg/packages.list; then
			packages="$(($packages+1))"
			printf "%s\n" "$pkg" >> packages-to-install.list
		fi
	done

	# install packages.
	log "$(_ 'Installing packages...')"
	if [ "$packages" = "0" ]; then
		log "$(_ 'packages to install: 0')"
	else
		# get-install all missing pkgs.
		while read pkg
		do
			log "$(_ 'Installing: %s...' "$pkg")"
			# get install package and answer yes in case of dependencies.
			pkgname="$(grep ^$pkg /var/lib/tazpkg/packages.list)"
			/usr/bin/tazpkg get "$pkg" >/dev/null 2>/dev/null
			yes "" | /usr/bin/tazpkg install $pkgname.tazpkg \
--root=$TARGET_ROOT >/dev/null 2>/dev/null
			rm -f $pkgname.tazpkg
		done < packages-to-install.list
	fi
	log "$(_ 'Installation of packages complete...')"
}

# search for added pkgs
update_pkgs()
{
	local path="$(pwd)"
	cd $TARGET_ROOT/var/lib/tazinst || error8
	# LiveCD packages list.
	log "$(_ 'Creating package lists...')"
	ls -1 $TARGET_ROOT/var/lib/tazpkg/installed > packages-source.list || error8
	log "$(_ '%s: done' 'packages-source.list')"
	# diff
	/bin/busybox diff packages-source.list packages-selection.list | \
		grep '^+[a-z]' | sed 's/^+//' > packages-selection.diff
	log "$(_ '%s: done' 'packages-selection.diff')"
	# get mirror list.
	/usr/bin/tazpkg recharge >>$LOG 2>>$LOG
	if [ -f /var/lib/tazpkg/packages.list ]; then
		install_pkgs
	else
		touch packages-to-install.list
		longline "$(_ "The list of available packages on the mirror could not \
be downloaded. No missing packages will be reinstalled now, but you can do so \
later by looking at the following list:")"
		echo '/var/lib/tazinst/packages-selection.diff'
	fi
	cd "$path" || error8
}

# upgrade command
upgrade()
{
	log_open
	log "1 $(_ 'Upgrading SliTaz on: %s' "$(get root_uuid)")"
	log "5 $(_ 'Checking settings...')"
	check all >> "$LOG"
	load_settings

	log "10 $(_ 'Preparing source media...')"
	mount_source

	log "20 $(_ 'Preparing target disk...')"
	prepare_uuid

	log "30 $(_ 'Searching for %s...' '/etc/slitaz-release')"
	check_release

	log "40 $(_ 'Backup /etc, /home and the packages list...')"
	backup_files

	log "50 $(_ 'Extracting the root system...')"
	extract_rootfs $SOURCE_ROOT/boot

	log "60 $(_ 'Restoring configuration files...')"
	restore_files

	log "70 $(_ 'Installing the Kernel...')"
	install_kernel

	log "80 $(_ 'Upgrading added packages...')"
	update_pkgs

	log "90 $(_ 'Bootloader...')"
	bootloader

	log "100 $(_ 'Files installation completed')"
	end_of_install
}


#--------------
# tazinst main
#--------------

# frontends ask for machine readable output. Records of execute go to
# fd 3, a copy of stdout: a trap can fire while stdout is redirected.
if [ "$1" = "--machine" ]; then
	MACHINE="true"
	exec 3>&1
	shift
fi

case $1 in
	new)
		new_file "$2" ;;
	set)
		[ -s "${4:-$DEFAULT_INSTALL_FILE}" ] || new_file "$4"
		read_file "$4"
		change "$2" "$3" "$4" ;;
	unset)
		read_file "$3"
		change "$2" "" "$3" "$4" ;;
	get)
		read_file "$3"
		get "$2" ;;
	check)
		read_file "$3"
		if [ -n "$MACHINE" ]; then
			check_machine "$2"
		else
			check "$2"
		fi ;;
	describe)
		read_file "$2"
		describe ;;
	options)
		read_file "$3"
		options "$2" ;;
	plan)
		read_file "$2"
		plan ;;
	list)
		list "$2" "$3" ;;
	execute)
		check_root
		read_file "$2"
		execute "$2" ;;
	log)
		[ -r "$LOG" ] && cat "$LOG" ;;
	clean)
		clean "$2" ;;
	version)
		printf "%s\n" "$VERSION" ;;
	""|usage)
		usage ;;
	help)
		help "$2" ;;
	*)
		usage_error "$1" ;;
esac
