mini_snmpd: 1.4-rc1 add git,autoconf,procd init script,uci validation - new upstream...
[feed/packages.git] / net / mini_snmpd / files / mini_snmpd.init
index 20e2c62237c182a6d5c7048f4cd35f7ddf533412..442e350af3abb2e1c61c86103f97484114a6d35a 100644 (file)
 #!/bin/sh /etc/rc.common
-# Copyright (C) 2009-2012 OpenWrt.org
+# 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=50
+START=98
+USE_PROCD=1
+PROG=/usr/bin/mini_snmpd
+NAME=mini_snmpd
 
-SERVICE_DAEMONIZE=1
-SERVICE_WRITE_PID=1
+_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"
-       append disks "$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"
-       local device
-       network_get_device device "$name"
-       append interfaces "${device:-$name}" ','
+       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
+       netdev=$(ubus -S call network.interface dump|jsonfilter -e "@.interface[@.interface=\"$name\"].device")  
+       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
+                _err "$cfg: physical interface for network $name not found in uci or kernel so not monitoring"
+       fi
 }
 
-append_string() {
-       local section="$1"
-       local option="$2"
-       local value="$3"
-       local _val
-       config_get _val "$section" "$option"
-       [ -n "$_val" ] && append args "$3 $_val"
+append_arg() {
+       local var="$2"
+       local opt="$1"
+       [ -n "$var" ] && procd_append_param command $opt "$var"
 }
 
-start_instance() {
+watch_interfaces() {
        local cfg="$1"
-       local args=""
-       local disks=""
-       local interfaces=""
-       local ipv6
+       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
+       # listen_interface_up=$(ubus -S call network.interface dump | jsonfilter -e "@.interface[@.interface=\"$listen_interface\"].up")
+       # 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} "
+}
 
-       append_string "$cfg" community "-c"
-       append_string "$cfg" location "-L"
-       append_string "$cfg" contact "-C"
+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
+}
 
-       config_get_bool ipv6 "$cfg" "ipv6" '0'
-       [ "$ipv6" -gt 0 ] && append args "-6"
 
-       config_get_bool enabled "$cfg" "enabled" '1'
-       [ "$enabled" -gt 0 ] || return 1
+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
+        }
+        procd_close_trigger
+       procd_add_validation validate_mini_snmpd_section
+}
+
 
-       config_list_foreach "$section" 'disks' append_disk
-       args="${args}${disks:+ -d $disks}"
+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 ubus_exit ubus_err
+       [ -n "$listen_interface" ] && {
+               listen_interface_json=$(ubus -S call network.interface.$listen_interface status)
+               ubus_exit=$?
+               [ $ubus_exit = 4 ] && {
+                       _err "$cfg: listen_interface $listen_interface not properly configured in ubus network.interface.* not starting this instance "
+                       return 1
+               }
+               [ $ubus_exit = 255 -a -z "$listen_interface_json" ] && { 
+                       _log "$cfg: ubusd not yet up, will try to start mini_snmpd shorlty when procd detects $listen_interface comes up"
+                       return 1
+               }
+               [ -z "$listen_interface_json" ] && { 
+                       ubus_err=`ubus call network.interface.$listen_interface status 2>&1 >/dev/null`
+                       _err "$cfg: unknown ubus error. exit: $ubus_exit errormsg: $ubus_err "
+                       return 1
+               }
+               listen_interface_up=$(jsonfilter -s "$listen_interface_json" -e '@.up')
+               if [ "$ipv6" = 1 ]; then
+                       listen_interface_ip=$(jsonfilter -s "$listen_interface_json" -e "@['ipv6-address'][0].address")
+               else
+                       listen_interface_ip=$(jsonfilter -s "$listen_interface_json" -e "@['ipv4-address'][0].address")
+               fi
+               [ -n "$listen_interface_ip" -a "$listen_interface_up" = 'true' ] || {
+                       _log "$cfg:listen interface $listen_interface not up yet / not configured properly"
+                       _log "$cfg:procd will try again when interface state changes"
+                       return 1
+               }
+               listen_interface_device=$(jsonfilter -s "$listen_interface_json" -e '@.l3_device')
+       }
 
-       config_list_foreach "$section" 'interfaces' append_interface
-       args="${args}${interfaces:+ -i $interfaces}"
+       [ $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
 
-       service_start /usr/bin/mini_snmpd $args
-}
+       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" 
 
-start() {
-       . /lib/functions/network.sh
+       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 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_set_param netdev $listen_interface_device
+       }
+       procd_close_instance
+}
 
+start_service() {
        config_load 'mini_snmpd'
        config_foreach start_instance 'mini_snmpd'
 }
 
-stop() {
-       service_stop /usr/bin/mini_snmpd
-}