#!/bin/sh
#
# $Id$
#
# Copyright (c) 2013, 2016, Juniper Networks, Inc.
# All rights reserved.
#

OAM_COMPAT=12016

pkgtools=${PKGTOOLS:-/usr/libexec}
pkgtmp=${TMPDIR:-/tmp}

gblopts=
command=
cmdopts=
cmdargs=
force=
oam_version=
oam_compat=
oamctl_from_pkg=
xml_vars=

Error_more () {
	echo ERROR: $@ >&2
}

Error() {
	echo ERROR: $@ >&2
	exit 1
}

oamctl_usage() {
	echo "usage:" ${0##*/} -v
	echo "      " ${0##*/} '<command>' '[<args>]'
	echo ""
	echo "commands supported:"
	echo "  set-boot-phase <number>"
	echo "  get-boot-phase"
	echo "  set-boot-function <number>"
	echo "  get-boot-function"
	echo "  set-boot-device <name>"
	echo "  get-boot-device"
	echo "  get-boot-device-list"
	echo "  get-boot-cause"
	echo "  get-boot-console"
	echo "  get-console"
	echo "  set-console <selection>"
	echo "  get-last-boot-device"
	echo "  set-boot-volume <volume-name>"
	echo "  get-oam-logs"
	echo "  clear-oam-logs"
	echo "  upload-firmware  <filename>"
	echo "  list-snapshots"
	echo "  delete-snapshot <name>"
	echo "  add-snapshot <filename>"
	echo "  add-file-to-oam <source_file> <target_file>"
	echo "    source_file - Path to source file on the Junos volume"
	echo "    target_file - Path to target file on the OAM volume"
	echo "                  Empty target_file copies to root on OAM volume"
	echo "  list-packages"
	echo "  delete-package <name>"
	echo "  add-package <filename>"
	echo "  update-oam <path>"
	echo "  clear-options <option-list>"
	echo "  get-options"
	echo "  set-options <option-list>"
	echo ""
	echo "flags supported:"
	echo "  [-d, -dd, -ddd ...] display debug information"
	exit 1
}

oamctl_label_source() {
	local s

	for s in mirror gpt ufs; do
		if test -c /dev/$s/oam; then
			echo $s
			return 0
		fi
	done
	echo ""
	return 0
}

run_fsck() {
	fsck_out=`fsck_ufs -C -R -y "$@" 2>&1`
	fsck_status=$?
	case "$fsck_out" in
	*"SKIPPING CHECKS"*) ;;
	*) echo "$fsck_out";;
	esac
	return $fsck_status
}

# only do this once
oamctl_fsck_oam_once=:
oamctl_fsck_oam() {
	$oamctl_fsck_oam_once 0

	if test ! -d /oam/boot; then
		oamctl_fsck_oam_once=return
		run_fsck $1
	fi
}

oamctl_remount_oam() {
	local access=$1
	local source

	source=`oamctl_label_source`
	oamctl_fsck_oam /dev/$source/oam
	mount -u -o $access /dev/$source/oam /oam || return $?
	if test $access = rw; then
		trap "oamctl_remount_oam ro" EXIT
	fi
}

oamctl_mount_oam() {
	local mounted source

	source=`oamctl_label_source`
	mounted=`mount | grep "^/dev/$source/oam on .*/oam "`
	test -n "$mounted" && return 0

	oamctl_fsck_oam /dev/$source/oam
	mount -o ro /dev/$source/oam /oam || return $?
	trap "umount /oam" EXIT
}

oamctl_on_oam() {
	case `realpath $0` in
	/oam/*) return 0;;
	*) return 1;;
	esac
}

oamctl_load_options() {
	local f cookie options

	for f in `'ls' /oam/boot/loader.*.cookie 2>/dev/null`
	do
		cookie=${f#/oam/boot/loader.}
		cookie=${cookie%.cookie}
		options="${options}${options:+ }${cookie}"
	done
	oam_options=${options}
}

oamctl_get_options() {
	oamctl_load_options
	echo ${oam_options}
}

oamctl_load_version() {
	if test -s /oam/boot/OAM-VERSION; then
		while read line
		do
			case "$line" in
			"Version: "*[0-9]*)
				oam_version=${line#Version: }
				;;
			"Compatible: "*[0-9]*)
				oam_compat=${line#Compatible: }
				;;
			"Loader_utc: "*[0-9]*)
				oam_loader_utc=${line#Loader_utc: }
				;;
			esac
		done < /oam/boot/OAM-VERSION
	fi

	oam_version=${oam_version:-0}
	oam_compat=${oam_compat:-$oam_version}
	oam_loader_utc=${oam_loader_utc:-0}
}

oamctl_version_ok() {
	local pkg_version=$1
	local pkg_compat=$2
	local pkg_loader_utc=${3:-0}

	if test -z $oam_version; then
		oamctl_load_version
	fi

	# If the OAM volume version is at least the package version,
	# and/or OAM loader_utc is at least the package version,
	# then everything is okay
	test $oam_version -ge $pkg_version -a $oam_loader_utc -ge $pkg_loader_utc
}

oamctl_compat_ok() {
	if test -z $oam_version; then
		oamctl_load_version
	fi

	test $oam_version -ge $OAM_COMPAT
}

oamctl_compat_check() {
	oamctl_compat_ok && return
	$oamctl_from_pkg Error "'oam'" package needs to be updated in order to use OAM functionality
	return 1
}

oamctl_set() {
	local name=$1
	local value="$2"

	if test -c /dev/oamctl; then
		echo "${name}=$value" > /dev/oamctl
	else
		echo "oamctl: cannot set $name: no /dev/oamctl" >&2
	fi
}

oamctl_get() {
	local name=$1

	if test -c /dev/oamctl; then
		awk -F= "/^${name}=/ { print \$2 }" /dev/oamctl
	else
		echo "oamctl: cannot get $name: no /dev/oamctl" >&2
	fi
}

oamctl_get_boot_phase() {
	oamctl_get "phase"
}

oamctl_set_boot_phase() {
	oamctl_set "phase" $@
}

oamctl_get_boot_function() {
	oamctl_get "function"
}

oamctl_set_boot_function() {
	oamctl_set "function" $@
}

oamctl_get_boot_device() {
	oamctl_get "device"
}

oamctl_get_boot_device_list() {
	oamctl_get "bootlist"
}

oamctl_set_boot_device() {
	oamctl_set "device" "$@"
}

oamctl_get_boot_cause() {
	oamctl_get "bootcause"
}

oamctl_get_last_boot_device() {
	oamctl_get "bootdevice"
}

oamctl_set_boot_volume() {
	Error unknown boot volume "'$1'"
}

oamctl_get_loader_console() {
	kenv -q loader.console
}

# return the first of $* that exists
# we generally use this to deal with expanding wildcards.
Exists() {
	local _t=-s

	case "$1" in
	-?) _t=$1; shift;;
	esac

	for f in $*
	do
		test $_t $f || continue
		echo $f
		return 0
	done
	return 1
}

if test -s /usr/libexec/debug.sh; then
    . /usr/libexec/debug.sh
    DebugOn oamctl
else
DebugOn() { : ; }
DebugOff() { : ; }
DEBUG_DO=:
DEBUG_SKIP=
    case ",$DEBUG_SH," in
    *,oamctl,*) DEBUG_DO= DEBUG_SKIP=: ; set -x;;
    esac
fi

# called from update_oam
cleanup_oam() {
    return 0
}

:
#
# $Id$
#
# Copyright (c) 2016, 2019, 2020 Juniper Networks, Inc.
# All rights reserved.
#

oamctl_set_boot_volume() {
	local dev

	case `kenv -q hw.board.name` in
	ex2300-24mp|ex2300-48mp|ericsson-6675|acx-710) dev=mmc;;
	ex4100*) dev=da0;;
        *) dev=nand;;
	esac

	case "$1" in
	oam|junos) oamctl_set_boot_device $dev;;
	usb) oamctl_set_boot_device usb;;
	*) Error unknown boot volume "'$1'" ;;
	esac
}
:
# $Id$
#
# Copyright (c) 2013, 2016, 2020, Juniper Networks, Inc.
# All rights reserved.
#

update_oam() {
	local pkgdir=$1
	local loader_tar=`Exists -s $pkgdir/loader-[bv]*oot.tar`
	local old_4th=
	local new_4th=:
	
	MACHINE=${MACHINE:-`uname -m`}
	efi_version=${EFI_VERSION:-`kenv -q efi-version`}
	board_name=${BOARD_NAME:=`kenv -q hw.board.name`}

	case "$efi_version,$MACHINE,$board_name" in
	*,mips*|*,oceteon*) old_4th=: new_4th=;; # new 4th
	,*|*,acx-710) ;;	# old 4th
	*) old_4th=: new_4th=;;	# new 4th
	esac

	if test -z "$EXTRACT_SKIP"; then
		DESTDIR=/oam
		if test -d $pkgdir/contents; then
			if test -z "$xml_package_1_volume_1_version" -o \
			    -z "$xml_package_1_volume_1_compat"; then
				echo "${0##*/}: update-oam should only be run by pkg"
				oamctl_usage
				return 1
			fi
		elif test -z "$loader_tar$efi_version"; then
			return 1
		fi

		# we want oam_version etc
		oamctl_load_version
	fi

	local pkg_version=${xml_package_1_volume_1_version:-$oam_version}
	local pkg_compat=${xml_package_1_volume_1_compat:-$oam_compat}
	local pkg_loader_utc=${xml_package_1_volume_1_loader_utc:-$oam_loader_utc}
	local skip want have b sub C t thash msig

	if test -z "$EXTRACT_SKIP$force"; then
		oamctl_version_ok $pkg_version $pkg_compat $pkg_loader_utc &&
		return 0
	fi
	$EXTRACT_SKIP oamctl_remount_oam rw

	if test -d $pkgdir/contents; then
		echo "Installing OAM volume contents ..."
		tar -C $pkgdir/contents --one-file-system -cf - . | \
		    tar -C $DESTDIR/boot -xpf -
		cat > $DESTDIR/boot/OAM-VERSION << _EOR_
Version: $pkg_version
Compatible: $pkg_compat
_EOR_
		# Should clean up any old files here
		if test -z "$EXTRACT_SKIP" -a \
			-s $pkgdir/contents/kernel/boot/contents.iso; then
			# be sure we remove the right one
			rm -f $DESTDIR/boot/contents.iso
			# take care of arch specifics
			cleanup_oam
		fi
		echo "The OAM volume is now installed"
	fi
	for t in $pkgdir/*-boot.tar
	do
		test -s $t || continue
		C= skip= sub=
		b=`basename $t -boot.tar`
		case "$b" in
		oam*) $new_4th continue;;
		volume-oam) $old_4th continue;;
		loader|loader-ve) continue;; # done below
		*) $old_4th continue; C=/boot sub=/$b;;
		esac
		if test -z "$EXTRACT_SKIP"; then
			thash=${t%.tar}.hash
			msig=$DESTDIR/boot$sub/manifest.esig
			if test -s $thash -a -s $msig; then
				want=`cat $thash`
				have=`sha1 < $msig`
				test x$have = x$want && skip=:
			fi
		fi
		$skip echo "Updating OAM boot$sub"
		$skip tar -C $DESTDIR$C -xpf $t
	done
	if test -n "$loader_tar"; then
		# we want to avoid accidental rollback
		# so we compare loader_utc values from oam
		# and the package and proceed only if package is newer.
		if test $oam_loader_utc -lt $pkg_loader_utc; then
			echo "Updating OAM loader"
			tar -C $DESTDIR -xpf $loader_tar
			oam_loader_utc=$pkg_loader_utc
		fi
		echo "Loader_utc: $oam_loader_utc" >> $DESTDIR/boot/OAM-VERSION
	fi
}

add_snapshot() {
	local snapshot=$1
	local bootbits=$2

	oamctl_remount_oam rw

	rm -rf /oam/boot/junos /oam/snapshot

	mkdir /oam/snapshot
	cp $snapshot /oam/snapshot

	if test ! -z $bootbits; then
		mkdir /oam/boot/junos
		tar -C /oam/boot/junos -xf $bootbits
	fi
}

add_file_to_oam() {
	local srcfile=$1
	local target=$2
	local destdir="/oam/${target#/}"

	oamctl_remount_oam rw

	test -f "$srcfile" || Error Not a valid source_file

	if test -d "$destdir"; then
		destfile=${srcfile##*/}
	else
		destfile=${destdir##*/}
		destdir=${destdir%/*}
	fi

	case $(realpath -q "$destdir") in
	/oam|/oam/*) ;;
	*) Error: "'$target'" is not a valid destination ;;
	esac
    
	cp -f $srcfile $destdir/$destfile
}

delete_snapshot() {
	local snapshot=$1
	local nsnaps=0

	local snap
	for snap in /oam/snapshot/${1}.*
	do
		test -s "$snap" || continue

		local name=$(basename $snap)
		test "$snapshot" = "${name%.*}" || continue
		oamctl_remount_oam rw
		rm -f "$snap"
		nsnaps=$((nsnaps + 1))
	done
	if test $nsnaps -gt 0; then
		echo $snapshot snapshot deleted.
	else
		echo No snapshots named $snapshot were found
	fi
}

list_snapshots() {
	local nsnaps=0

	local snap
	for snap in /oam/snapshot/recovery*
	do
		local name=$(basename $snap)
		test -s "$snap" || continue
		test $nsnaps -eq 0 && \
			echo Snapshots available on the OAM volume:
		echo ${name%.*}
		echo "Date created: `date -r $snap`"
		echo "Junos version: `cat /oam/snapshot/VERSION`"
		nsnaps=$((nsnaps + 1))
	done

	if test $nsnaps -gt 0; then
		echo
		echo Total recovery snapshots: $nsnaps
	else
		echo No recovery snapshots available on the OAM volume
	fi
}

get_oam_logs() {
	if test ! -s /oam/var/log/oam; then
		echo No OAM logs available
	else
		cat /oam/var/log/oam
	fi
}

clear_oam_logs() {

	if test -s /oam/var/log/oam; then
		oamctl_remount_oam rw
		rm -f /oam/var/log/oam
	fi
	echo OAM logs cleared
}

upload_firmware() {
	echo The upload-firmware command is not supported in this release
}

list_packages() {
	echo The list-packages command is not supported in this release
}

add_package() {
	echo The add-package command is not supported in this release
}

delete_package() {
	echo The delete-package command is not supported in this release
}

rm_cookie() {
	oamctl_remount_oam rw
	rm -f /oam/boot/loader.$1.cookie
}

touch_cookie() {
	oamctl_remount_oam rw
	touch /oam/boot/loader.$1.cookie
}

while test $# -ne 0; do
	if test -z $command; then
		case $1 in
		-v|--version)      echo 0.1.0; exit 0 ;;
		-d*)    debuglength=${1#-}
			debuglevel=${#debuglength} ;;
		-f)	force=: gblopts="$gblopts${gblopts:+ }$1" ;;
		-x)     set -x; gblopts="$gblopts${gblopts:+ }$1" ;;
		-*)     gblopts="$gblopts${gblopts:+ }$1" ;;
		*=*)    eval "$1";;
		*)      command=$1 ;;
		esac
	else
		case $1 in
		-*)     cmdopts="$cmdopts${cmdopts:+ }$1" ;;
		*)      cmdargs="$cmdargs${cmdargs:+ }$1" ;;
		esac
	fi
	shift
done

if test -z $command; then
	oamctl_usage
fi

EXTRACT_DO=:
EXTRACT_SKIP=

case $command in
set-boot-phase)		# set-boot-phase <number>
	oamctl_set_boot_phase "$cmdargs"
	;;
get-boot-phase) 	# get-boot-phase
	oamctl_get_boot_phase
	;;
set-boot-function)	# set-boot-function <number>
	oamctl_set_boot_function "$cmdargs"
	;;
get-boot-function)	# get-boot-function
	oamctl_get_boot_function
	;;
set-boot-device)	# set-boot-device <name>
	oamctl_set_boot_device "$cmdargs"
	;;
get-boot-device)	# get-boot-device
	oamctl_get_boot_device
	;;
get-boot-cause)		# get-boot-cause
	oamctl_get_boot_cause
	;;
get-last-boot-device)	# get-last-boot-device
	oamctl_get_last_boot_device
	;;
set-boot-volume)	# set-boot-volume <volume-name>
	oamctl_set_boot_volume "$cmdargs"
	;;
get-oam-logs)		# get-oam-logs
	get_oam_logs
	;;
clear-oam-logs)		# clear-oam-logs
	clear_oam_logs
	;;
upload-firmware)	# upload-firmware  <filename>
	upload_firmware $cmdargs
	;;
list-snapshots)		# list-snapshots
	list_snapshots
	;;
delete-snapshot)	# delete-snapshot <name>
	delete_snapshot $cmdargs
	;;
add-snapshot)		# add-snapshot <filename>
	add_snapshot $cmdargs
	;;
add-file-to-oam)	# add-file-to-oam <file> <destdir>
	add_file_to_oam $cmdargs
	;;
list-packages)		# list-packages
	list_packages
	;;
delete-package)		# delete-package <name>
	delete_package $cmdargs
	;;
add-package)		# add-package <filename>
	add_package $cmdargs
	;;
update-oam*)		# update-oam <path>
	set -- $cmdargs
	pkgdir=$1

	if oamctl_on_oam; then
		# If running from oam volume, exec the oamctl in the package
		# dir instead
		oamctl=`Exists $pkgdir/contents/oamctl $pkgdir/scripts/oamctl $pkgdir/oamctl`
		if test -n "$oamctl"; then
			exec $oamctl $gblopts $command $cmdopts $cmdargs
		fi
	else
		update_oam $pkgdir
	fi
	;;
update-veloader)
        set -- $cmdargs
        pkgdir=$1

        update_oam $pkgdir
        ;;
extract-oam)
	EXTRACT_DO=
	EXTRACT_SKIP=:
	set -- $cmdargs
	pkgdir=$1

	update_oam $pkgdir
	;;
set-console)
	cons=`oamctl_get_loader_console`
	test -n "$cons" || Error set-console is not supported on this platform

	case "$cmdargs" in
	[pP][rR][iI][mM][aA][rR][yY]) cons="PRIMARY" ;;
	[aA][uU][xX]) cons="AUX" ;;
	*) Error invalid argument "'$cmdargs'" ;;
	esac

	oamctl_remount_oam rw
	echo "loader.console=\"$cons\"" > /oam/boot/console.conf
	;;
get-boot-console)
	cons=`oamctl_get_loader_console`
	echo ${cons:-PRIMARY}
	;;
get-console)
	cons=`oamctl_get_loader_console`
	test -n "$cons" || Error get-console is not supported on this platform

	cons=`sed -n '/^loader\.console="[^"]*"$/s/^.*"\([^"]*\)"$/\1/p' \
	    /oam/boot/console.conf 2>/dev/null`
	echo ${cons:-PRIMARY}
	;;
get-options)
	oamctl_get_options
	;;
clear-options)
	for arg in $cmdargs
	do
		case "$arg" in
		[nN][oO][mM][eE][nN][uU]) rm_cookie nomenu ;;
		*) Error invalid argument "'$cmdargs'" ;;
		esac
	done
	;;
set-options)
	for arg in $cmdargs
	do
		case "$arg" in
		[nN][oO][mM][eE][nN][uU]) touch_cookie nomenu ;;
		*) Error invalid argument "'$cmdargs'" ;;
		esac
	done
	;;
*)
	oamctl_usage
	;;
esac

