#!/bin/sh
set -eu

ARG0="$0"
BASE="/run/rootless-cni-infra"

wait_unshare_net() {
	pid="$1"
	# NOTE: busybox shell doesn't support the `for ((i=0; i < $MAX; i++)); do foo; done` statement
	i=0
	while :; do
		if [ "$(readlink /proc/self/ns/net)" != "$(readlink /proc/${pid}/ns/net)" ]; then
			break
		fi
		sleep 0.1
		if [ $i -ge 10 ]; then
			echo >&2 "/proc/${pid}/ns/net cannot be unshared"
			exit 1
		fi
		i=$((i + 1))
	done
}

# CLI subcommand: "alloc $CONTAINER_ID $NETWORK_NAME $POD_NAME"
cmd_entrypoint_alloc() {
	if [ "$#" -ne 3 ]; then
		echo >&2 "Usage: $ARG0 alloc CONTAINER_ID NETWORK_NAME POD_NAME"
		exit 1
	fi

	ID="$1"
	NET="$2"
	K8S_POD_NAME="$3"

	dir="${BASE}/${ID}"
	mkdir -p "${dir}/attached"

	pid=""
	if [ -f "${dir}/pid" ]; then
		pid=$(cat "${dir}/pid")
	else
		unshare -n sleep infinity &
		pid="$!"
		wait_unshare_net "${pid}"
		echo "${pid}" >"${dir}/pid"
		nsenter -t "${pid}" -n ip link set lo up
	fi
	CNI_ARGS="IgnoreUnknown=1;K8S_POD_NAME=${K8S_POD_NAME}"
	nwcount=$(find "${dir}/attached" -type f | wc -l)
	CNI_IFNAME="eth${nwcount}"
	export CNI_ARGS CNI_IFNAME
	cnitool add "${NET}" "/proc/${pid}/ns/net" >"${dir}/attached/${NET}"

	# return the result
	ns="/proc/${pid}/ns/net"
	echo "{\"ns\":\"${ns}\"}"
}

# CLI subcommand: "dealloc $CONTAINER_ID $NETWORK_NAME"
cmd_entrypoint_dealloc() {
	if [ "$#" -ne 2 ]; then
		echo >&2 "Usage: $ARG0 dealloc CONTAINER_ID NETWORK_NAME"
		exit 1
	fi

	ID=$1
	NET=$2

	dir="${BASE}/${ID}"
	if [ ! -f "${dir}/pid" ]; then
		exit 0
	fi
	pid=$(cat "${dir}/pid")
	cnitool del "${NET}" "/proc/${pid}/ns/net"
	rm -f "${dir}/attached/${NET}"

	nwcount=$(find "${dir}/attached" -type f | wc -l)
	if [ "${nwcount}" = 0 ]; then
		kill -9 "${pid}"
		rm -rf "${dir}"
	fi

	# return empty json
	echo "{}"
}

# CLI subcommand: "is-idle"
cmd_entrypoint_is_idle() {
	if [ ! -d ${BASE} ]; then
		echo '{"idle": true}'
	elif [ -z "$(ls -1 ${BASE})" ]; then
		echo '{"idle": true}'
	else
		echo '{"idle": false}'
	fi
}

# CLI subcommand: "print-cni-result $CONTAINER_ID $NETWORK_NAME"
cmd_entrypoint_print_cni_result() {
	if [ "$#" -ne 2 ]; then
		echo >&2 "Usage: $ARG0 print-cni-result CONTAINER_ID NETWORK_NAME"
		exit 1
	fi

	ID=$1
	NET=$2

	# the result shall be CNI JSON
	cat "${BASE}/${ID}/attached/${NET}"
}

# CLI subcommand: "print-netns-path $CONTAINER_ID"
cmd_entrypoint_print_netns_path() {
	if [ "$#" -ne 1 ]; then
		echo >&2 "Usage: $ARG0 print-netns-path CONTAINER_ID"
		exit 1
	fi

	ID=$1

	pid=$(cat "${BASE}/${ID}/pid")
	path="/proc/${pid}/ns/net"

	# return the result
	echo "{\"path\":\"${path}\"}"
}

# CLI subcommand: "help"
cmd_entrypoint_help() {
	echo "Usage: ${ARG0} COMMAND"
	echo
	echo "Rootless CNI Infra container"
	echo
	echo "Commands:"
	echo "  alloc             Allocate a netns"
	echo "  dealloc           Deallocate a netns"
	echo "  is-idle           Print whether the infra container is idle"
	echo "  print-cni-result  Print CNI result"
	echo "  print-netns-path  Print netns path"
	echo "  help              Print help"
	echo "  version           Print version"
}

# CLI subcommand: "version"
cmd_entrypoint_version() {
	echo "{\"version\": \"${ROOTLESS_CNI_INFRA_VERSION}\"}"
}

# parse args
command="${1:-}"
if [ -z "$command" ]; then
	echo >&2 "No command was specified. Run \`${ARG0} help\` to see the usage."
	exit 1
fi

command_func=$(echo "cmd_entrypoint_${command}" | sed -e "s/-/_/g")
if ! command -v "${command_func}" >/dev/null 2>&1; then
	echo >&2 "Unknown command: ${command}. Run \`${ARG0} help\` to see the usage."
	exit 1
fi

# start the command func
shift
"${command_func}" "$@"
