#!/bin/bash
# virtme-init: virtme's basic init (PID 1) process
# Copyright © 2014 Andy Lutomirski
# Licensed under the GPLv2, which is available in the virtme distribution
# as a file called LICENSE with SHA-256 hash:
# 8177f97513213526df2cf6184d8ff986c675afb514d4e68a404010521b880643

export PATH=/bin:/sbin:/usr/bin:/usr/sbin

log() {
    if [[ -e /dev/kmsg ]]; then
	echo "<6>virtme-init: $*" >/dev/kmsg
    else
	echo "virtme-init: $*"
    fi
}

mount -t sysfs -o nosuid,noexec,nodev sys /sys/

declare -A mount_tags
for i in /sys/bus/virtio/drivers/9pnet_virtio/virtio*/mount_tag; do
    mount_tags["`cat "$i"`"]=1
done

if [[ -n "${mount_tags[virtme.moddir]}" ]]; then
    mount -t tmpfs none /lib/modules
    kver="`uname -r`"
    mkdir /lib/modules/"$kver"
    mount -n -t 9p -o ro,version=9p2000.L,trans=virtio,access=any virtme.moddir /lib/modules/"$kver"
fi

mount -t tmpfs tmpfs /tmp/
[[ -w /var/log ]] || mount -t tmpfs tmpfs /var/log/
[[ -n "${virtme_run_mounted}" ]] || mount -t tmpfs run /run

# Fix up /etc a little bit
touch /tmp/fstab
mount --bind /tmp/fstab /etc/fstab
rm /tmp/fstab

# Find udevd
if [[ -x /usr/lib/systemd/systemd-udevd ]]; then
    udevd=/usr/lib/systemd/systemd-udevd
else
    udevd=`which udevd`
fi

# Mount proc (needed for stat, sadly)
mount -t proc -o nosuid,noexec,nodev proc /proc/

# devtmpfs might be automounted; if not, mount it.
if [[ "`stat --format=%m /dev`" != "/dev" ]]; then
    # Ideally we'll use devtmpfs (but don't rely on /dev/null existing).
    if [[ -c /dev/null ]]; then
	mount -n -t devtmpfs -o mode=0755,nosuid,noexec devtmpfs /dev \
	    &>/dev/null
    else
	mount -n -t devtmpfs -o mode=0755,nosuid,noexec devtmpfs /dev
    fi

    if (( $? != 0 )); then
	# The running kernel doesn't have devtmpfs.  Use regular tmpfs.
	mount -t tmpfs -o mode=0755,nosuid,noexec none /dev

	# Make some basic devices first, and let udev handle the rest
	mknod -m 0666 /dev/null c 1 3
	mknod -m 0660 /dev/kmsg c 1 11
	mknod -m 0600 /dev/console c 5 1
    fi
fi

if [[ -n "virtme_chdir" ]]; then
    cd -- "${virtme_chdir}"
fi

log "basic initialization done"

######## The remainder of this script is a very simple init (PID 1) ########

# Try to get udevd to coldplug everything.
# We could use virtme-loadmods as a lightweight alternative.
"$udevd" --daemon --resolve-names=never
udevadm trigger --action=add  >/dev/null 2>&1
udevadm settle

# Set up filesystems that live in /dev
mkdir -p -m 0755 /dev/shm /dev/pts
mount -t devpts -o gid=tty,mode=620,noexec,nosuid devpts /dev/pts
mount -t tmpfs -o mode=1777,nosuid,nodev tmpfs /dev/shm

# Bring up networking
ip link set dev lo up

if cat /proc/cmdline |grep -q -E '(^| )virtme.dhcp($| )'; then
    if [[ -f /etc/resolv.conf ]]; then
	tmpfile="`mktemp --tmpdir=/tmp`"
	mount --bind "$tmpfile" /etc/resolv.conf
	rm "$tmpfile"
    fi
    busybox udhcpc -i eth0 -t 1 -n -q -f -s "$(dirname $0)/virtme-udhcpc-script"
fi

if [[ -x /run/virtme/data/script ]]; then
    if [[ ! -e "/dev/virtio-ports/virtme.scriptio" ]]; then
	echo "virtme-init: cannot find script I/O port; make sure virtio-serial is available"
	poweroff -f
	exit 1
    fi

    log 'starting script'
    setsid /run/virtme/data/script <>/dev/virtio-ports/virtme.scriptio 2>&1
    log "script returned $?"

    # Hmm.  We should expose the return value somehow.
    sync
    poweroff -f
    exit 1
fi

# Create some VTs
deallocvt
openvt -c 2 -- /bin/bash
openvt -c 3 -- /bin/bash
openvt -c 4 -- /bin/bash

# Figure out what the main console is
consdev="`grep ' ... (.C' /proc/consoles  |cut -d' ' -f1`"
if [[ -z "$consdev" ]]; then
    log "can't deduce console device"
    exec bash --login  # At least try to be helpful
fi
if [[ "$consdev" == "tty0" ]]; then
    consdev=tty1  # sigh
fi
if [[ ! -e "/dev/$consdev" ]]; then
    log "/dev/$consdev doesn't exist."
    exec bash --login
fi

# Parameters that start with virtme_ shouldn't pollute the environment
for p in "${!virtme_@}"; do export -n "$p"; done

echo "virtme-init: console is $consdev"

# Bring up a functioning shell on the console.  This is a bit magical:
# We have no controlling terminal because we're attached to a fake
# console device (probably something like /dev/console), which can't
# be a controlling terminal.  We are also not a member of a session.
# Init apparently can't setsid (whether that's a limitation of the
# setsid binary or the system call, I don't know).
while true; do
    # Program the console sensibly
    if [[ -n "${virtme_stty_con}" ]]; then
	stty ${virtme_stty_con} <"/dev/$consdev"
    fi

    setsid bash 0<>"/dev/$consdev" 1>&0 2>&0
    echo "Shell died.  Will respawn."
    sleep 0.5
done
