#!/bin/bash -efu

. altboot-sh-functions

check_parameter ALTBOOT_DOWNLOAD

srcreg=
dstreg=
devname=
filesize=
free_rd=
entered=

b_a()
{
	get_bootarg DOWNLOAD "$1"
}

b_a to
b_a method
b_a url
b_a server
b_a directory
b_a squash
b_a user
b_a pass
b_a imgsize
b_a timeout
b_a reserve

# BOOTIMG is only used with FTP/HTTP methods in propagator compatibility mode
[ -z "$squash" ] && BOOTIMG="$mntdir/boot-image.iso" || BOOTIMG="$mntdir/rootfs.squash"

# You can change defaults in /etc/sysconfig/bootchain
OEM_URL_NETINST="${OEM_URL_NETINST:-/pub/netinst/current}"


back_to_main_menu()
{
	[ -z "$free_rd" ] ||
		mark_free_ramdisk "$free_rd"
	altboot_restart
}

url_input_form()
{
	enter "url_input_form"

	local title="[ Enter the URL ]"

	local text="Please enter "
	if [ -n "$squash" ]; then
		text="$text the URL containing the squash $squash."
	else
		text="$text full path to the file image."
	fi

	while :; do
		IM_form "$title" "$text" 2		\
			url 200 "URL or filename"	\
			||
			back_to_main_menu
		case "$url" in
		'')
			IM_errmsg "Valid URL/filename required!"
			continue
			;;
		http://*|ftp://*|file:///*|/*)
			[ -n "${url//[^[:space:]]/}" ] ||
				break
			;;
		esac
		IM_errmsg "Invalid URL/filename: '$url'!"
	done

	url="${url//\"/\\\"}"
	server=
	directory=
	user=
	pass=

	leave "url_input_form"
}

http_input_form()
{
	enter "http_input_form"

	local title="[ HTTP-server connection data ]"

	local text="Please enter the name or IP address of the HTTP-server, and"
	if [ -n "$squash" ]; then
		text="$text the directory containing the squash $squash."
	else
		text="$text full path to the file image."
	fi

	while :; do
		[ -n "$server" ] ||
			server="$(get_default_gateway)"
		IM_form "$title" "$text" 2		\
			server     64 "HTTP-server"	\
			directory 128 "Directory"	\
			||
			back_to_main_menu
		if [ -z "$server" ]; then
			IM_errmsg "Valid HTTP-server required!"
		elif [ -z "$directory" ]; then
			IM_errmsg "Valid Directory required!"
		elif [ -n "${server//[^[:space:]]/}" ]; then
			IM_errmsg "Invalid HTTP-server: '$server'!"
		elif [ -n "${directory//[^[:space:]]/}" ] ||
			[ -n "$directory" ] && [ "${directory:0:1}" != "/" ]
		then
			IM_errmsg "Invalid Directory: '$directory'!"
		else
			user=
			pass=
			break
		fi
	done

	server="${server//\"/\\\"}"
	directory="${directory//\"/\\\"}"
	url="http://${server}${directory}"

	leave "http_input_form"
}

ftp_input_form()
{
	enter "ftp_input_form"

	local title="[ FTP-server connection data ]"

	local text="Please enter the name or IP address of the FTP-server, and"
	if [ -n "$squash" ]; then
		text="$text the directory containing the squash $squash"
	else
		text="$text full path to the file image"
	fi
	text="$text, and the Login/Password if necessary"
	text="$text (leave Login blank for anonymous)."

	while :; do
		[ -n "$server" ] ||
			server="$(get_default_gateway)"
		IM_form "$title" "$text" 4		\
			server     64 "FTP-server"	\
			directory 128 "Directory"	\
			user       32 "Login"		\
			pass       32 "Password"	\
			||
			back_to_main_menu
		if [ -z "$server" ]; then
			IM_errmsg "Valid FTP-server required!"
		elif [ -z "$directory" ]; then
			IM_errmsg "Valid Directory required!"
		elif [ -n "${server//[^[:space:]]/}" ]; then
			IM_errmsg "Invalid FTP-server: '$server'!"
		elif [ -n "${directory//[^[:space:]]/}" ] ||
			[ -n "$directory" ] && [ "${directory:0:1}" != "/" ]
		then
			IM_errmsg "Invalid Directory: '$directory'!"
		else
			[ -z "$pass" ] && user="" ||
				pass="${pass//\"/\\\"}"
			[ -z "$user" ] && pass="" ||
				user="${user//\"/\\\"}"
			break
		fi
	done

	server="${server//\"/\\\"}"
	directory="${directory//\"/\\\"}"
	url="ftp://${server}${directory}"

	leave "ftp_input_form"
}

download_input_form()
{
	enter "download_input_form"

	[ "$method" = http ] || [ "$method" = ftp ] ||
		method=url
	${method}_input_form
	entered=1
	imgsize=

	leave "download_input_form"
}

get_file_size()
{
	enter "get_file_size"

	if [ -n "$srcreg" ]; then
		if [ ! -e "$url" ]; then
			message "file not found: '$url'"
			leave "get_file_size"
			return 1
		elif [ ! -r "$url" ]; then
			message "file is not readable: '$url'"
			leave "get_file_size"
			return 1
		fi

		debug "retrieving file size: '$url'"
		filesize="$(run stat -L -c%s -- "$url")"

		if [ -n "$imgsize" ] && [ "$filesize" != "$imgsize" ]; then
			local msg="real and specified image file size mismatch"

			[ -n "$NOASKUSER" ] ||
				IM_errmsg "R${msg:1}!"
			message "$msg"
		fi
	elif [ -n "$imgsize" ]; then
		filesize="$imgsize"
	else
		local opts="-sI ${CURLOPTS-}" i=0 rc tmpf status auth=
		local regex="s/^Content\-Length:[[:space:]]+([0-9]+)/\1/p"

		IM_ponder_start \
			"[ Connecting... ]" \
			"Requesting image from the server..."
		message "retrieving image file size: '$url'"

		opts="$opts --no-buffer --connect-timeout 5 --max-time 7"
		opts="$opts --max-redirs 0 --write-out %{http_code}"
		[ "$method" != ftp ] ||
			opts="$opts --ftp-pasv"
		[ "$method" != ftp ] || [ -z "$user" ] ||
			auth="$user:$pass"
		tmpf="$(run mktemp -qt)"

		while [ "$i" -lt "$timeout" ]; do
			rc=0; run rm -f -- "$tmpf"
			status="$(run curl ${opts}${auth:+ -u "$auth"} \
					--output "$tmpf" --url "$url")" || rc=$?
			[ ! -s "$tmpf" ] ||
				fdump "$tmpf"
			debug "curl status: $rc, protocol status: $status"

			if [ "$rc" = 0 ] && [ "$method" = http ]; then
				case "$status" in
				301|302|401|403|404)
					# This is unrecoverable client/server error,
					# there is no point in waiting for a fix
					message "unrecoverable http error"
					break
					;;
				200)	# OK
					;;
				*)	rc=1
					;;
				esac
			elif [ "$rc" = 0 ] && [ "$method" = ftp ]; then
				case "$status" in
				202|225|231|331|332|404|421|425|426|450|451|5??)
					# This is unrecoverable client/server error,
					# there is no point in waiting for a fix
					message "unrecoverable ftp error"
					break
					;;
				200|226|350)
					# OK
					;;
				*)	rc=1
				esac
			fi

			if [ "$rc" = 0 ] && [ -s "$tmpf" ]; then
				filesize="$(sed -E -n "$regex" "$tmpf" |
						sed -e 's,\r$,,')"
				[ -z "$filesize" ] || break
			fi

			[ -n "$entered" ] ||
				break
			i=$((3 + $i))
			sleep 3
		done

		run rm -f -- "$tmpf"
		IM_ponder_stop
	fi

	if [ -z "$filesize" ]; then
		message "can't retrieve image/file size by specified URL"
		leave "get_file_size"
		return 1
	fi

	leave "get_file_size"
}

check_avail_space()
{
	enter "check_avail_space"

	local text avail number sysfs mem=
	local szkb="$(( $filesize / 1024 + 1 ))"
	local sizemin="$(( ${reserve:-512} * 1024 + $szkb ))"
	local regex="s/^MemAvailable:[[:space:]]+([0-9]+)[^0-9]*/\1/p"

	avail="$(sed -n -E "$regex" /proc/meminfo ||:)"
	avail="${avail:-0}"

	if [ -z "$dstreg" ]; then
		number="$(mountpoint -x -- "$to")"
		sysfs="/sys/dev/block/$number/size"
		[ -r "$sysfs" ] && read -r mem <"$sysfs" ||:
		mem="$(( ${mem:-0} / 2 ))"

		if [ "${to:0:8}" = /dev/ram ]; then
			mem="$(( ${reserve:-512} * 1024 + $mem ))"
			[ "$avail" -le "$mem" ] || avail="$mem"
		else
			avail="$mem"
			sizemin="$szkb"
		fi
	fi

	message "available: $avail KiB, required: $sizemin KiB"

	if [ "$avail" -lt "$sizemin" ]; then
		text="not enough memory for download specified image"
		[ -z "$NOASKUSER" ] ||
			fatal "$text, dialogs are disabled"
		text="N${text:1}. Available space: $avail KiB."
		[ -n "$dstreg" ] || [ "${to:0:8}" != /dev/ram ] ||
			text="$text Retry with ramdisk_size=$szkb after reboot."
		IM_errmsg "$text $IM_RBMSG"
		bc_reboot
	fi

	leave "check_avail_space"
}

read_image()
{
	if [ -n "$srcreg" ]; then
		pv -n -i 1 -- "$url" ||
			printf '%s, read\n' "$?" >"$datadir/ERROR"
	else
		local auth="" opts="${CURLOPTS-}"
		opts="$opts --silent --no-buffer --connect-timeout 5"
		opts="$opts --max-redirs 0 --max-filesize $filesize"

		[ "$method" != ftp ] ||
			opts="$opts --ftp-pasv"
		[ "$method" != ftp ] || [ -z "$user" ] ||
			auth="$user:$pass"
		( curl ${opts}${auth:+ -u "$auth"} -- "$url" ||
			printf '%s, curl\n' "$?" >"$datadir/ERROR"
		) |pv -n -i 1 -s "$filesize"
	fi
}

save_image()
{
	if [ -z "$dstreg" ]; then
		dd "of=$to" bs=32k 2>/dev/null ||
			printf '%s, dd\n' "$?" >"$datadir/ERROR"
	else
		cat >"$to" ||
			printf '%s, write\n' "$?" >"$datadir/ERROR"
	fi

	[ -s "$datadir/ERROR" ] || sync
}

download_image()
{
	enter "download_image"

	local text="Downloading ${url##*/} into $target..."
	message "downloading image: '$url'"
	(read_image |save_image) 2>&1 |
		IM_gauge "[ Downloading image... ]" "$text"
	[ -z "$CONSOLE" ] || reset

	leave "download_image"
}


# Entry point
debug "$PROG started ($(get_parameter ALTBOOT_DOWNLOAD))"

IM_start_output form errmsg gauge ponder
[ -n "$to" ] || [ -z "$prevdir" ] || [ ! -s "$prevdir/DEVNAME" ] ||
	read -r to <"$prevdir/DEVNAME" ||:

case "$to" in
step[0-9]*|pipe[0-9]*)
	target="$(resolve_target "$to")/DEVNAME"
	[ -s "$target" ] && read -r to <"$target" && [ -n "$to" ] ||
		fatal "invalid step# specified, can't resolve the device name"
	;;
RD)
	if ! get_free_ramdisk to; then
		[ ! -b /dev/ram0 ] || [ ! -r /sys/block/ram0/size ] ||
			IM_fatal "no free RAM-disk found"
		to="$datadir/image"
		dstreg=1
	fi
	;;
/dev/?*)
	;;
*)
	to="$datadir/image"
	dstreg=1
	;;
esac

[ -z "$dstreg" ] || [ -z "$ALTBOOT_OLDROOT" ] ||
[ "$method" = url ] || [ -f "$mntdir"/DIRTY ] ||
	to="$BOOTIMG"
target="the RAM"
timeout="${timeout:-$BC_DEVICE_TIMEOUT}"

if [ -z "$dstreg" ]; then
	[ -b "$to" ] ||
		IM_fatal "specified target block device not found: $to"
	case "$to" in
	/dev/ram[0-9]*)
		mark_used_ramdisk "$to"
		target="the RAM-disk"
		free_rd="$to"
		;;
	*)
		target="device $to"
		;;
	esac
fi

if [ -f "$altboot_auto" ]; then
	if [ "$method" != url ]; then
		imgsize=
		url=
	fi
	directory=
	server=
	user=
	pass=
fi

if [ "$method" = http ] || [ "$method" = ftp ]; then
	. altboot-net-functions

	if [ -z "$directory" ] && [ -n "$OEM_URL_NETINST" ]; then
		directory="$OEM_URL_NETINST"
		imgsize=
	fi

	if [ -z "$server" ] && [ -n "$OEM_SRV_NETINST" ]; then
		server="$OEM_SRV_NETINST"
		imgsize=
	fi

	url=
	[ -z "$server" ] || [ -z "$directory" ] ||
		url="$method://${server}${directory}"
fi

if [ "$method" != ftp ]; then
	user=""; pass=""
elif [ -z "$user" ]; then
	pass=
fi

if [ -z "$url" ]; then
	[ -z "$NOASKUSER" ] ||
		fatal "URL for boot from not specified, dialogs are disabled"
	download_input_form
fi

while :; do
	saved_url="$url"

	if [ "${url:0:8}" = "file:///" ]; then
		url="${url:7}"
		srcreg=1
	elif [ "${url:0:1}" = "/" ]; then
		srcreg=1
	else
		[ -z "$squash" ] ||
			url="$url/$squash"
		srcreg=
	fi

	if [ -n "$srcreg" ]; then
		[ -n "$prevdir" ] && mountpoint -q -- "$prevdir" ||
			fatal "no previous step results to download image"
		url="${prevdir}${url}"
	fi

	:> /.initrd/rootdelay/addtime
	rootdelay_pause

	get_file_size ||
		printf '%s, determinate file size\n' "$?" >"$datadir/ERROR"
	[ -z "$filesize" ] || check_avail_space ||
		printf '%s, check available space\n' "$?" >"$datadir/ERROR"
	[ -z "$filesize" ] ||
		download_image
	if [ "$method" = ftp ] && [ -s "$datadir/ERROR" ] &&
	   [ "$(head -n1 -- "$datadir/ERROR" ||:)" = "7, curl" ]
	then
		[ -z "$dstreg" ] ||
			run rm -f -- "$to"
		rm -f -- "$datadir/ERROR"
		sleep 2
		download_image
	fi
	url="$saved_url"
	unset saved_url

	[ -s "$datadir/ERROR" ] ||
		break
	read -r rc <"$datadir/ERROR" ||
		rc=1
	[ -z "$dstreg" ] ||
		run rm -f -- "$to"
	rm -f -- "$datadir/ERROR"
	debug "iteration failed (rc=$rc)"
	unset rc

	text="connection error or bad URL/filename: '$url'"
	[ -z "$NOASKUSER" ] ||
		fatal "$text, dialogs are disabled"
	[ -z "$entered" ] ||
		IM_errmsg "Connection error or specified URL unavailable now!"
	message "$text, go to the next ring"
	download_input_form
done

[ -z "$dstreg" ] ||
	run chmod -- 0600 "$to"
lomount devname "$to"
[ -z "$dstreg" ] || [ "$to" = "$BOOTIMG" ] ||
	:>"$mntdir"/DIRTY
debug "DEVNAME: $devname"
debug "FILESIZE: $filesize"
printf '%s\n' "$filesize" >"$destdir/FILESIZE"
printf '%s\n' "$devname" >"$destdir/DEVNAME"
run cp -a -- "$devname" "$destdir/dev"

if [ -z "$ALTBOOT_OLDROOT" ]; then
	IM_update_bootsplash "found_media"
elif [ -z "$(stage2_getenv METHOD)" ]; then
	stage2_setenv METHOD "$method"
	if [ "$method" = url ] || [ -z "$squash" ]; then
		stage2_setenv URL "$url"
	else
		stage2_setenv HOST "$server"
		stage2_setenv PREFIX "$directory"
		if [ "$method" = ftp ] && [ -n "$user" ]; then
			stage2_setenv LOGIN "$user"
			stage2_setenv PASSWORD "$pass"
		fi
	fi
	IM_update_bootsplash "found_media"
fi

debug "$PROG finished"
