#!/bin/sh
# Copyright (C) 2025 iopsys Software Solutions AB
#
# Mutual exclusion for the transfer diagnostics. Sourced by the download and
# upload backends, both the generic and the Airoha variants:
#
#     . "${ROOT}"/bbf_api
#     . "${ROOT}"/tr143_lock
#     ...
#     transfer_lock_try "download" "bbf_download" "${proto}" || return
#
# Source it after bbf_api and after jshn.sh: it uses the UCI_* helpers from the
# former and json_* from the latter.
#
# Why one lock across all four: the Airoha variants drive the same fastpath.
# They insmod and rmmod the same modules and configure them through the single
# /proc/tc3162/speed_test control file, which holds one test configuration, so a
# second test overwrites the first's while it runs and whichever finishes first
# pulls the modules out from under the other. Two transfers also cannot measure
# a shared link at the same time whatever the backend, so the generic variants
# take the same lock.

TRANSFER_LOCK="/var/lock/tr143_transfer.lock"

# Reports that another transfer diagnostic holds the lock.
#
# $1 op (download|upload, also the dmmap section), $2 logger tag, $3 proto.
#
# Deliberately not routed through the callers' own error functions: the one in
# scripts/download emits nothing at all once dmmap Status is "complete", which is
# the normal state after any previous run, and the caller would be left with no
# result rather than a status.
transfer_lock_busy() {
	logger -p err -t "${2}" "${1} not started: another transfer diagnostic is running"

	json_init
	json_add_string "Status" "Error_Internal"
	json_dump

	# Store data in dmmap_diagnostics for both protocols (cwmp/usp)
	[ "${3}" = "both_proto" ] && {
		$UCI_SET_BBF_DMMAP "dmmap_diagnostics.${1}.DiagnosticState=Error_Internal"
		$UCI_SET_BBF_DMMAP "dmmap_diagnostics.${1}.Status=complete"
		$UCI_COMMIT_BBF_DMMAP
	}
}

# Takes the lock, or reports the run as busy and fails.
#
# $1 op (download|upload), $2 logger tag, $3 proto. Returns non-zero when the
# caller should give up, so the call site reads:
#
#     transfer_lock_try "download" "bbf_download" "${proto}" || return
#
# The lock is held for as long as the process, and any child that inherited the
# descriptor, stays alive; the kernel drops it when the last of them exits. That
# is what makes it safe against a killed run, which the dmmap Status option it
# replaces was not: a run killed midway left Status="running" behind and every
# later run was refused for good.
transfer_lock_try() {
	# Descriptor 209 keeps clear of 211 and 212, which the download and upload
	# error paths use for their own locks. It lives in this file alone.
	exec 209>"${TRANSFER_LOCK}" || {
		transfer_lock_busy "${1}" "${2}" "${3}"
		return 1
	}

	flock -n 209 || {
		transfer_lock_busy "${1}" "${2}" "${3}"
		return 1
	}

	return 0
}
