#!/usr/bin/bash

CONFIG_DIR="/etc/vstorage"
CLUSTERS_DIR="${CONFIG_DIR}/clusters"
SYSTEMD_DIR="/usr/lib/systemd/system"
SYSTEMD_TOOL="/usr/bin/systemctl"
LOCK_FILE=".lock"

CSD_UNIT_VERSION=2
MDSD_UNIT_VERSION=1

mdsd_target_file() {
cat  <<EOFT
[Unit]
Description=Metadata server(MDS) target for $CLUSTER_NAME cluster
PartOf=vstorage-mdsd.target

[Install]
WantedBy=vstorage-mdsd.target
EOFT
}

csd_target_file() {
cat  <<EOFT
[Unit]
Description=Chunk server(CS) target for $CLUSTER_NAME cluster
PartOf=vstorage-csd.target

[Install]
WantedBy=vstorage-csd.target
EOFT
}

create_cluster_target() {
	local OP="$1"
	local CLUSTER_NAME="$2"
	
	#example: vstorage-csd.my_pcs4.target
	local TARGET_NAME="vstorage-${DAEMON_NAME}.${CLUSTER_NAME}.target"
	[ -f $TARGET_NAME ] && return 0

	$OP > ${SYSTEMD_DIR}/${TARGET_NAME}
	$SYSTEMD_TOOL --quiet enable ${TARGET_NAME}
}

get_current_unit_version()
{
	local unit_file=$1

	sed -n \
		's/^# vstorage-gen-unit-version=\([0-9][0-9]*\)$/\1/p' \
		"$unit_file" |
		head -1
}

service_unit_file() {
	local SPEC_SERVICE_PART=""
	if [ "$DAEMON_NAME" = "csd" ]; then
		SPEC_SERVICE_PART="Environment=\"CS_ID=$SERVICEID\"
ExecCondition=/usr/libexec/vstorage/cs-start-prereq
TimeoutStartSec=10min"
	fi

cat  <<EOFT
# vstorage-gen-unit-version=$expected_version
[Unit]
Description=vstorage-$DAEMON_NAME($DAEMON_DIR)
PartOf=vstorage-$DAEMON_NAME.$CLUSTER_NAME.target
Before=remote-fs-pre.target
After=vstorage-$DAEMON_NAME-pre.target

[Service]
Type=forking
Slice=vstorage-services.slice
Environment="DAEMON=$DAEMON_NAME" "CLUSTER=$CLUSTER_NAME" "DAEMON_DIR=$DAEMON_DIR"
Environment="PIDFILE=$PIDFILE"
$SPEC_SERVICE_PART
ExecStart=/usr/libexec/vstorage/vstorage-service start \$CLUSTER \$DAEMON \$DAEMON_DIR \$PIDFILE
ExecStartPost=/usr/libexec/vstorage/vstorage-service report \$CLUSTER \$DAEMON \$DAEMON_DIR -i "was started"
ExecStopPost=/usr/libexec/vstorage/vstorage-service report \$CLUSTER \$DAEMON \$DAEMON_DIR -i "was stopped"
PIDFile=$PIDFILE
Restart=on-failure
RestartPreventExitStatus=222
RestartSec=1s
RestartForceExitStatus=0
StartLimitInterval=5min
StartLimitBurst=10
TimeoutStopSec=60s
LimitMEMLOCK=infinity
LimitRTPRIO=infinity

[Install]
WantedBy=vstorage-$DAEMON_NAME.$CLUSTER_NAME.target
EOFT
}

is_legacy_generated_unit()
{
	local unit_file=$1

	# Old pre-RHEL 9 generated unit.
	if grep -q '^\.include /etc/vstorage/' "$unit_file"; then
		return 0
	fi

	# Existing standalone vstorage-gen unit.
	grep -qF \
		'ExecStart=/usr/libexec/vstorage/vstorage-service start ' \
		"$unit_file" &&
	grep -qF \
		"PartOf=vstorage-${DAEMON_NAME}.${CLUSTER_NAME}.target" \
		"$unit_file"
}

create_daemon_service() {
	local CLUSTER_NAME="$1"
	local DAEMON_DIR="$2"
	local mapped_id="$3"

	local SERVICEID=""
	local expected_version
	local unit_version

	DAEMON_DIR="${DAEMON_DIR%/}"

	if [ -d "$DAEMON_DIR" ]; then
		[ -f "$DAEMON_DIR/control/id" ] && SERVICEID=$(cat "$DAEMON_DIR/control/id")
		[ -f "$DAEMON_DIR/id" ] && SERVICEID=$(cat "$DAEMON_DIR/id")
	else
		SERVICEID="$mapped_id"
	fi

	[ -z "$SERVICEID" ] && {
		echo "Failed to obtain SERVICEID for DAEMON_DIR=$DAEMON_DIR" >&2
		return 0
	}

	case "$DAEMON_NAME" in
		csd)  expected_version=$CSD_UNIT_VERSION ;;
		mdsd) expected_version=$MDSD_UNIT_VERSION ;;
	esac

	#unit file example vstorage-mdsd.my_pcs4.1.service
	local UNIT_NAME="vstorage-${DAEMON_NAME}.${CLUSTER_NAME}.${SERVICEID}.service"
	local UNIT_FILE="${SYSTEMD_DIR}/${UNIT_NAME}"
	if [ -f "$UNIT_FILE" ] ; then
		unit_version=$(get_current_unit_version "$UNIT_FILE")

		if [ -n "$unit_version" ]; then
			if [ "$unit_version" -eq "$expected_version" ]; then
				return 0
			fi

			if [ "$unit_version" -gt "$expected_version" ]; then
				echo "Unit $UNIT_FILE has newer format version $unit_version" >&2
				return 0
			fi

			# Older recognized version: regenerate below.
		else
			if is_legacy_generated_unit "$UNIT_FILE"; then
				echo "Migrating unversioned unit $UNIT_FILE"
			else
				echo "Not replacing unrecognized unit $UNIT_FILE" >&2
				return 0
			fi
		fi
	fi

	PIDFILE="/run/vstorage/$DAEMON_NAME.$CLUSTER_NAME.$SERVICEID.pid"
	service_unit_file > "${UNIT_FILE}"
	$SYSTEMD_TOOL --quiet enable ${UNIT_NAME}
}

for_cluster() {
	local OP="$1"
	local CLUSTER_NAME="$2"
	local CLUSTER_DIR="${CLUSTERS_DIR}/$CLUSTER_NAME"
	declare -A SERVICE_DIRS=()

	if [ "$DAEMON_NAME" = "csd" ]; then
		for unit_file in "${SYSTEMD_DIR}"/vstorage-"${DAEMON_NAME}.${CLUSTER_NAME}".*.service; do
			[ -f "$unit_file" ] || continue

			dir=$(sed -n 's/.*DAEMON_DIR=\([^ "]*\).*/\1/p' "$unit_file" | head -1)
			dir=${dir%/}

			base=${unit_file##*/}
			base=${base%.service}
			service_id=${base##*.}

			[ -n "$dir" ] && [ -n "$service_id" ] || continue

			SERVICE_DIRS["$dir"]="$service_id"
		done
	fi

	# lock per-cluster lock before accessing lists
	( flock -x -w 10 100 || exit 1
		cat "${CLUSTER_DIR}/$LIST_FILE" 2>/dev/null | grep -v '^#' | grep -v '^\s*$' | while read dir; do
			list_dir="${dir%/}"
			# '100<&-' explicitely closes fd 100 for childs, especially monitor
			("$OP" "${CLUSTER_NAME}" "${list_dir}" "${SERVICE_DIRS[$list_dir]}") 100<&-
		done
	) 100>"${CLUSTER_DIR}/$LOCK_FILE" || echo "Failed to obtain lock: ${CLUSTER_DIR}/$LOCK_FILE" 1>&2
}

for_all() {
	local OP1="$1"
	local OP2="$2"
	ls -d "${CLUSTERS_DIR}"/* 2>/dev/null | while read cluster; do
		local CLUSTER_NAME=$(basename "${cluster}")
		create_cluster_target "${OP2}" "${CLUSTER_NAME}"
		for_cluster "${OP1}" "${CLUSTER_NAME}"
	done
}

new_target() {
	local cluster_name="$1"
	if [ -z "$cluster_name" ]; then
		echo "new-target: Cluster name not specified" 1>&2
		exit 1
	fi

	if [ ! -d "${CLUSTERS_DIR}/$cluster_name" ]; then
		echo "new-service: this host is not authenticated in cluster '$cluster_name'" 1>&2
		exit 2
	fi

	case "$SERVICE" in
		mds)
			create_cluster_target mdsd_target_file "$cluster_name"
			;;
		cs)
			create_cluster_target csd_target_file "$cluster_name"
			;;
	esac
}

new_service() {
	local cluster_name="$1"
	local dir="$2"
	
	if [ -z "$cluster_name" ] ; then
		echo "new-service: Cluster name not specified" 1>&2
		print_usage
	fi
	
	if [ -z "$dir" ]; then
		echo "new-service: Path to service repo not specified" 1>&2
		print_usage
	fi

	if [ ! -d "${CLUSTERS_DIR}/$cluster_name" ]; then
		echo "new-service: this host is not authenticated in cluster '$cluster_name'" 1>&2
		exit 2
	fi

	create_daemon_service "$cluster_name" "$dir"
}

do_restore() {
	case "$SERVICE" in
		mds)
			for_all create_daemon_service mdsd_target_file
			;;
		cs)
			for_all create_daemon_service csd_target_file
			;;
	esac
}

print_usage() {
cat 1>&2 <<EOFT 

vstorage-gen CMD [OPTS]
Commands:
  new-target   - create new target unit
     vstorage-gen new-target cs|mds \$cluster-name
  new-service   - create new service unit for csd or mdsd
     vstorage-gen new-service cs|mds \$cluster-name \$service-dir
  restore-units - restore unit files for all mdsd and csd serices on this host
     vstorage-gen restore-units cs|mds"

EOFT
	exit 1
}

handle_gen()
{
	local cmd="$1"
	shift

	SERVICE="$1"
	if [ -z "$SERVICE" ] ; then
		echo "Service type (cs|mds) must be specified" 1>&2
		print_usage
	fi

	DAEMON_NAME="${SERVICE}d"
	SUBSYS="vstorage-${DAEMON_NAME}"

	if [ "x$SERVICE" != "xcs" -a "x$SERVICE" != "xmds" ] ;  then
		echo "Unknown service type, expected 'mds' or 'cs'" 1>&2
		print_usage
	fi

	shift

	case "$cmd" in
		new-target)
			new_target "$1"
			;;
		new-service)
			new_service "$1" "$2"
			;;
		restore-units)
			LIST_FILE="${SERVICE}.list"
			do_restore
			;;
		*)
			echo "Unknown command '$cmd'" 1>&2
			print_usage
	esac
}

handle_gen "$@"
