#!/bin/bash
# SPDX-License-Identifier: GPL-2.0-only
#
# vm-init: actual init script
#
# Copyright (C) 2019-2023 Vitaly Chikunov <vt@altlinux.org>
#

[ $$ != 1 ] && exit 1
export PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/sbin:/usr/local/bin

mount -n -t proc -o nosuid,noexec,nodev proc /proc
read -r cmdline < /proc/cmdline
cmdline=" $cmdline " # Add separators so we can easily match complete words
if [ -z "${cmdline/* VERBOSE *}" ]; then
	V=-v
	V() { echo "- $*"; "$@"; }
else
	V=
	V() { "$@"; }
fi

SCRIPT=${cmdline#* SCRIPT=}
if [ "$SCRIPT" = "$cmdline" ]; then
	echo "vm-init: SCRIPT= is not set."
	SCRIPT=
fi
SCRIPT=${SCRIPT%% *}

V mount -n -t tmpfs -o mode=755,nodev,nosuid,strictatime tmpfs /run

V mount -t sysfs -o nosuid,noexec,nodev sys /sys
if ! grep -sq '^\S\+ / 9p ' /proc/mounts; then
	mount_tag_f=( /sys/bus/virtio/drivers/9pnet_virtio/virtio*/mount_tag )
	if [ -f "$mount_tag_f" ]; then
		read mount_tag < $mount_tag_f
		mkdir -p /mnt/9p
		V mount -t 9p -o "9p2000.L,trans=virtio,access=any,msize=262144" "$mount_tag" /mnt/9p
		unset mount_tag mount_tag_f
		[ -n "$SCRIPT" ] && SCRIPT="/mnt/9p/$SCRIPT"
	fi
fi

eval "$(grep -s -o '  MODPROBE_OPTIONS=.*' "$SCRIPT")"
# Last MODPROBE_OPTIONS will win.
export MODPROBE_OPTIONS

if ! modprobe -q ext4 >/dev/null 2>&1; then
	V /sbin/depmod
fi

# Additional early commands like 'mount --bind'.
eval "$(grep -s -Pxo '#  CMD: \K.*' "$SCRIPT")"

# Save script if it's on /tmp before mounting tmpfs over it.
case "$SCRIPT" in /tmp/*) SCRIPT_TEXT=$(cat $SCRIPT);; esac

V mount -t tmpfs -o size='100%' tmpfs /tmp
if [ -n "${SCRIPT_TEXT-}" ]; then
       echo "$SCRIPT_TEXT" > "$SCRIPT"
       chmod a+x "$SCRIPT"
       unset SCRIPT_TEXT
fi

V mount -t tmpfs tmpfs /var/log
V mount -t devtmpfs -o mode=0755,nosuid,noexec devtmpfs /dev

CONSOLE=${cmdline# console=}
CONSOLE=${CONSOLE%% *}
if [ -c /dev/"$CONSOLE" ]; then
	exec 0<>/dev/"$CONSOLE" 1>&0 2>&0
elif [ -c /dev/console ]; then
	exec 0<>/dev/console 1>&0 2>&0
else
	# Visible if /dev is inaccessible.
	echo "vm-init: console=$CONSOLE is not accessible."
fi

if [ -n "$STDOUT" ]; then
	for vport in /sys/class/virtio-ports/*; do
		read name < "$vport/name"
		if [ "$name" = stdout ]; then
			name=$(basename "$vport")
			exec 1>/dev/"$name"
		fi
	done
	unset STDOUT vport name
fi

install -g utmp -m660 /dev/null /var/log/btmp
install -g utmp -m664 /dev/null /var/log/wtmp

ln -sf /proc/kcore     /dev/core
ln -sf /proc/self/fd   /dev/fd
ln -sf /proc/self/fd/0 /dev/stdin
ln -sf /proc/self/fd/1 /dev/stdout
ln -sf /proc/self/fd/2 /dev/stderr

V mount -t configfs   configfs   /sys/kernel/config
V mount -t debugfs    debugfs    /sys/kernel/debug
V mount -t securityfs securityfs /sys/kernel/security

mkdir -p /dev/pts /dev/shm /dev/disk/by-id /dev/disk/by-path /dev/disk/by-uuid
V mount -t devpts -o gid=tty,mode=620,noexec,nosuid devpts /dev/pts
V mount -t tmpfs -o mode=1777,size=1M,noexec,nosuid,nodev tmpfs /dev/shm

V ip link set dev lo up 2>/dev/null

if grep -q UDEVD=y /proc/cmdline; then
	V udevd --daemon --resolve-names=never
	V udevadm trigger --type=subsystems --action=add
	V udevadm trigger --type=devices --action=add
	V udevadm settle
else
	# try to load some modules
	V modprobe --quiet --all virtio_blk scsi_mod ata_piix pata_acpi \
		ata_generic sd_mod virtio_net
fi
V modprobe virtio-rng

if SWAPDEV=$(blkid -t TYPE=swap -o device); then
	V /sbin/swapon $SWAPDEV
	SWAPSIZE=$(awk '$1 == "SwapTotal:" {print $2}' /proc/meminfo)
	V mount -t tmpfs -o remount,size="$SWAPSIZE"K /tmp
fi
unset SWAPSIZE SWAPDEV

# test writability
if tmp=$(mktemp -p /usr/src 2>/dev/null); then
	rm $tmp
else
	echo >&2 "  Error: Impossible to create files in /usr/src"
	echo >&2 "         Exit code from the command will be lost!"
fi

# Prepare network
network_conf() {
	local iface=$1

	if type ip >/dev/null 2>&1; then
		V ip link set $iface up
		V ip addr add 10.0.2.1/24 dev $iface
		V ip rout add default via 10.0.2.2
	elif type ifconfig >/dev/null 2>&1; then
		V ifconfig lo up
		V ifconfig $iface 10.0.2.1/24 up
		V route add default gw 10.0.2.2
	fi
}
if [ $(wc -l < /proc/net/dev) -gt 3 ]; then
	network_conf $(grep -E -vw 'Inter-|face|lo:' /proc/net/dev | head -1 | cut -d: -f1 | tr -d ' ')
fi

if [ -n "$RESIZE2FS" ]; then
	RESIZE2FS=$(blkid --uuid "$RESIZE2FS")
	if [ -n "$V" ]; then
		V resize2fs -p "$RESIZE2FS"
	else
		resize2fs "$RESIZE2FS" >/dev/null 2>&1
	fi
	unset RESIZE2FS
fi

if [ -n "$RSYNC" ]; then
	V rsync -a $V ${RSYNC//:/ }
fi

# Handle --overlay option
unset UMOUNT ARTIFACTS
ov_count=0
img_count=0
prev_cmdline=$cmdline
while [ -z "${prev_cmdline/* OVERLAY=*}" ]; do
	prev_cmdline=${prev_cmdline#* OVERLAY=}
	OVERLAY=${prev_cmdline%% *}
	IFS=: read ovfs ovpath <<< "$OVERLAY"
	IFS=, read ovfs ovopts <<< "$ovfs"
	mnt=/mnt/$ov_count
	ov_count=$((ov_count+1))
	mkdir -p $mnt
	ARTIFACTS+=("$mnt")
	if [ -b "$ovfs" ]; then
		# full device path
		V mount $V -n "$ovfs" $mnt
	elif [ -b "/dev/$ovfs" ]; then
		# short device name
		V mount $V -n "/dev/$ovfs" $mnt
	elif [ -d "$ovfs" ]; then
		# use existing dir
		mnt=$ovfs
	elif [ "$ovfs" = tmpfs ]; then
		# auto-create tmpfs mount
		V mount $V -t tmpfs ${ovopts:+-o $ovopts} tmpfs $mnt
	elif [ "$ovfs" = ext4 ]; then
		# auto-create ext4 partition
		size=${ovopts#*size=}
		size=${size%%,*}
		img=/usr/src/ext4.$img_count.img
		ARTIFACTS+=("$img")
		# do not delete data if it already exists
		if ! /sbin/tune2fs -l $img >/dev/null 2>&1; then
			> $img
			V truncate -s ${size:-11M} $img
			V /sbin/mkfs.ext4 -q $img
		fi
		V mount $V -t ext4 -o loop $img $mnt
		img_count=$((img_count+1))
	else
		echo >&2 "Unknown how to overlay $OVERLAY"
		unset mnt
	fi
	if [ "$mnt" ]; then
		mkdir -p $mnt/upper $mnt/work
		: ${ovpath:=/usr/src}
		V modprobe overlay
		V mount $V -t overlay -olowerdir=$ovpath,upperdir=$mnt/upper,workdir=$mnt/work overlay $ovpath
		UMOUNT+=("$ovpath" "$mnt")
	fi
	unset mnt ovfs ovopts ovpath
done
unset ov_count img_count prev_cmdline

if [ -n "$FAKESUDO" ]; then
	V install -pm4755 /usr/lib/vm-run/vm-fakesudo /usr/bin/sudo
fi
if [ -n "$RUNUSER" ]; then
	RUNUSER="/usr/lib/vm-run/vm-fakesudo -u $RUNUSER -i exec"
fi

[ -z "$NOTTY" ] && [ -t 1 ] && V vm-resize ${V:--q}

if [ -f "$SCRIPT" ]; then
	set -- $SCRIPT
else
	echo >&2 "vm-init: SCRIPT=$SCRIPT not found, running rescue shell."
	unset RUNUSER
	export PS1='(rescue)# '
	set -- /bin/bash
fi
$RUNUSER setsid $(test -t 1 && echo --ctty) --wait "$@"
RET=$?
# TODO: We can miss some stderr output here, for example script is not
# executable due to incorrect TMPDIR.

[ -v UMOUNT ] && umount $V -n --lazy "${UMOUNT[@]}"
[ -v ARTIFACTS ] && rm -fd -- "${ARTIFACTS[@]}"
case "$SCRIPT" in /tmp/*) umount $V -n --lazy /tmp;; esac
echo $RET 2>/dev/null > "$SCRIPT.ret" ||
	echo >&2 "Exit code $RET is lost"

# Disable `reboot: Power down' message
[ -z "${cmdline/* quiet *}" ] && echo 0 > /proc/sys/kernel/printk

V sync
V exec -a poweroff /usr/lib/vm-run/initrd-init
