#!/bin/sh /etc/rc.common
# Copyright (C) 2009-2016 OpenWrt.org
# Copyright (C) 2016 Luke McKee <hojuruku@gmail.com>
# Procd init script reference: http://wiki.prplfoundation.org/wiki/Procd_reference

START=98
USE_PROCD=1
PROG=/usr/bin/mini_snmpd
NAME=mini_snmpd

_log() {
        logger -p daemon.info -t mini_snmpd "$@"
}

_err() {
        logger -p daemon.err -t mini_snmpd "$@"
}


# mini_snmpd 1.3+ now starts later in the game. Expects filesystems monitored to be already mounted, or wont pass args to mini_snmpd 
# and at least configuration entry for network physical interface defined in /etc/config/network
# It handles network interfaces not yet present (e.g. ppp) but will statfs() the root/wrong filesystem if device not mounted
# Tip: complex scripts run faster without in openwrt if you stop busybox forking and searching for applets. Faster bootups
#	CONFIG_BUSYBOX_CONFIG_FEATURE_SH_NOFORK
# 	CONFIG_BUSYBOX_CONFIG_FEATURE_PREFER_APPLETS
# 	BUSYBOX_CONFIG_ASH_OPTIMIZE_FOR_SIZE [=n]
#	CONFIG_BUSYBOX_CONFIG_ASH_CMDCMD

mini_snmpd_validation="enabled:bool:0 \
		ipv6:bool:0 \
		debug:bool:0 \
		auth:bool:1 \
		community:rangelength(1,32):public \
		contact:maxlength(255) \
		location:maxlength(255) \
		listen_interface:uciname \
		udp_port:port \
		tcp_port:port \
		vendor_oid:string \
		mib_timeout:and(min(1),uinteger) \
		disks:list(directory) \
		interfaces:list(uciname) \
		respawn_threshold:uinteger respawn_timeout:uinteger respawn_retry:uinteger"
# busybox ash has no array variable support, when put validations in a string be careful to have no spaces in each validate constraint
# this makes it very difficult to use the 'or(uciname, "all")' test, so listen_interface '' or undefined now meands bind to "all".
# this is the sarafice you have to make to avoid typing it all in twice in this script so we can give feedback to user on what's misconfigered
# in syslog

append_disk() {
	local disk="$1" disk_count
	[ -z $disk_count ] && disk_count=0
	if grep -qF "$disk" /proc/mounts ; then
		# check the fileystem is mountpoint, and directory search permissions available for statfs()
		# presence as a directory -d test done is already done by uci_validate_section()
		[ -x "$disk" ] || {
			_err "$cfg: mountpoint $disk for snmp monitoring EACCES error. Check permissions, ignoring"
			return 1
		}
		if [ $disk_count -lt 4 ] ;  then  
			append disks_arg "$disk" ','
			disk_count=$((disk_count++))
		else
			_err "$cfg: more than 4 mountpoints defined in uci. Disc $disk ignored."
		fi
	else
		_err "$cfg: mountpoint $disk for snmp monitoring not mounted, ignoring."
	fi
}

append_interface() {
	local name="$1" netdev netdev_count
	[ -z $netdev_count ] && netdev_count=0
	# for the purposes of snmp monitoring it doesn't need to be up, it just needs to exist in /proc/net/dev
	network_get_device netdev "$name"
	if [ -n "$netdev" ] && grep -qF "$netdev" /proc/net/dev ]; then 
		[ $netdev_count -ge 4 ] && {
			_err "$cfg: too many network interfaces configured, ignoring $name"
			return
		}
		netdev_count=$((netdev_count++))	
		if [ -n "$interfaces_arg" ]; then	
			append interfaces_arg "$netdev" ','
		else	
			append interfaces_arg "$netdev"
		fi
	else
		 _log "$cfg: physical interface for network $name not found in uci or kernel so not monitoring"
	fi
}

append_arg() {
	local var="$2"
	local opt="$1"
	[ -n "$var" ] && procd_append_param command $opt "$var"
}

watch_interfaces() {
	local cfg="$1"
	local enabled listen_interface # listen_interface_up
	config_get_bool enabled "$cfg" "enabled" '1'
	[ "$enabled" -gt 0 ] || return 0
	config_get listen_interface "$cfg" listen_interface
	# If the interface is up & instance is running we'll watch at the instance level and only restart that instance if it's bound interface changes
	# Regardless of ubus knowing about an interface (in the case it's not yet configured)
	[ -n "$listen_interface" ] && trigger_interfaces="${listen_interface} ${trigger_interfaces} "
	# Restart daemon if one of monitored interfaces changes
	config_get reload_interfaces "$cfg" interfaces

}

validate_mini_snmpd_section() {
	# validate a mini_snmpd instance in uci config file mini_snmpd
	# http://luci.subsignal.org/trac/wiki/Documentation/Datatypes ubox/validate/validate.c
	uci_validate_section mini_snmpd mini_snmpd "${1}" $mini_snmpd_validation
}


service_triggers() {
        config_load 'mini_snmpd'
        procd_open_trigger
        procd_add_config_trigger "config.change" "mini_snmpd" /etc/init.d/mini_snmpd reload
        config_foreach watch_interfaces 'mini_snmpd' 
	# this only watches interfaces for which there is no running instance due to interface down / not in ubus
	# hence start not reload, this trigger will not affect running instances as another start will not change their procd command arguments
	# or stop the already running process
        [ -n "$trigger_interfaces" ] & {
                for n in $trigger_interfaces ; do
			procd_add_interface_trigger "interface.*" $n /etc/init.d/mini_snmpd start
                done
        }
	[ -n "$reload_interfaces" ] && {
		for n in $reload_interfaces; do
			procd_add_reload_interface_trigger $n
		done
	}
        procd_close_trigger
	procd_add_validation validate_mini_snmpd_section
}


start_instance() {
	local cfg validation_failed validation_err disks_arg interfaces_arg
	cfg="$1"	
	#uci_validate_section should unset undefined variables from other instances
	#however defining uci variables as local will scope them to this instance
	#"local variables are also visible to functions called by the parent function" so it's good practice
	local enabled ipv6 debug auth community contact location listen_interface \
		udp_port tcp_port vendor_oid mib_timeout
	local disks="" interfaces=""
	validate_mini_snmpd_section "$cfg" 2>/dev/null || validation_failed=1
	[ "$enabled" == 1 ] || { 
		_log "instance:$cfg disabled not starting" 
		 return 1
	}
	
	local listen_interface_json listen_interface_ip listen_interface_device listen_interface_up
	[ -n "$listen_interface" ] && {
		if [ "$ipv6" = 1 ]; then
			network_get_ipaddrs6 listen_interface_ip "$listen_interface"
		else
			network_get_ipaddrs listen_interface_ip "$listen_interface"
		fi
		network_is_up "$listen_interface" && [ -n "$listen_interface_ip" ] || {
			_log "$cfg:listen interface $listen_interface not up yet / not configured properly"
			_log "$cfg:procd will try again when interface state changes"
			return 1
		}
		network_get_physdev listen_interface_device "$listen_interface"
	}

	[ $validation_failed ] && {  
		_err "validation of $NAME configuration for $cfg instance failed, all tests should be within constraints"
		_err "please edit the configuration values below using [l]uci "
		validation_err=`/sbin/validate_data mini_snmpd mini_snmpd "$cfg" $mini_snmpd_validation 2>&1 | sed '/with\ false$/!d;s/validates\ as\ /needs\ to\ be\ /;s/with\ false//' `
		_err "${validation_err}"
		return 1
	}
	config_list_foreach "$cfg" 'disks' append_disk
	config_list_foreach "$cfg" 'interfaces' append_interface
	# test if variables are unset or zero length
	[ -z "${disks_arg:+1}" -a -z "${interfaces_arg:+1}" ] && {
		_err "$cfg: you haven't sucessfully configured any mountpoints or disks for this instance, not starting"
		return 1
	}
	
	procd_open_instance

	procd_set_param command "$PROG" -n
 	procd_set_param stdout "1"
	procd_set_param stderr "1"
	# don't the like default respawn values? you can override through uci.
	# vars left as global so you only need to do it in the first mini_snmpd instance
	procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-10} ${respawn_retry:-1}
	# this monitors ubus changes
	[ -n "$listen_interface" ] && {
		 #procd_open_trigger
		 #procd_add_interface_trigger "interface.*" $listen_interface /etc/init.d/mini_snmpd reload 
		 #procd_close_trigger
		 procd_add_reload_interface_trigger $listen_interface #or use shorthand of above
	}
	# this re-starts the daemon if a properly configured network interface is changed whilst it is already running
	# igmpproxy has this as well as "procd_set_param netdev" 

	append_arg "-c" "$community" 
	append_arg "-L" "${location}"
	append_arg "-C" "${contact}" 
	append_arg "-p" $udp_port  
	append_arg "-P" $tcp_port  
	append_arg "-V" "${vendor_oid}"  
	append_arg "-t" $mib_timeout 
	
	[ "$ipv6" = 1  ] && procd_append_param command "-6"
	[ "$debug" = 1 ] && procd_append_param command "-v"
	# uci_validate_section() aka /sbin/validate_data can only cast default values not defined in /etc/config/* to string 
	# e.g. ="1" however it sets bools defined in /etc/config/* to =1 / =0
	[ "$auth" = 1 -o "$auth" = "1" ] && procd_append_param command "-a"
	[ -n "$disks_arg" ] && procd_append_param command "-d" "$disks_arg"
	[ -n "$interfaces_arg" ] && {
		procd_append_param netdev ${interfaces_arg//,/ }
		procd_append_param command "-i" "$interfaces_arg"
	}
	[ -n "$listen_interface_device" ] && {
		 procd_append_param command "-I" "$listen_interface_device"
		 # and this monitors the hardware device for changes outside of ubus - just a guess
		 procd_append_param netdev $listen_interface_device
	}
	procd_close_instance
}

start_service() {
	. /lib/functions.sh
	. /lib/functions/network.sh

	config_load 'mini_snmpd'
	config_foreach start_instance 'mini_snmpd'
}