#!/bin/sh # In recent versions of shellcheck, busybox is a valid shell type # shellcheck shell=busybox # OpenWrt hotplug script to set permissions on USB serial # ports to allow NUT to access UPSes attached via # USB-to-serial cables # OpenWrt hotplug calls this script with DEVNAME and # ACTION set in the environment # IPKG_INSTROOT is intentionally only set when building an image and # is intentionally empty on a live OpenWrt device # shellcheck source=net/nut/files/functions.sh.functions . "${IPKG_INSTROOT}"/lib/functions.sh || { # Before our sourcing error logging definitions are sourced logger -s -t nut-serial-hotplug "Unable to source 'functions.sh' in 'nut-serial' hotplug. Bailing" exit 1 } # 'shellcheck' complains about nut-common.sh due to a case pattern it does not understand, even # though it is correctly POSIX compliant # shellcheck disable=SC1094 # shellcheck source=net/nut/files/nut-common.sh.functions . "${IPKG_INSTROOT}"/lib/functions/nut/nut-common.sh || { # Before our sourcing error logging definitions are sourced logger -s -t nut-serial-hotplug "Unable to source 'nut-common.sh' in 'nut-serial' hotplug. Bailing" exit 1 } # shellcheck source=net/nut/files/nut-service.sh.functions . "${IPKG_INSTROOT}"/lib/functions/nut/nut-service.sh || { log_source_error "nut-service.sh" "nut-serial" "nut-serial-hotplug" exit 1 } # 'shellcheck' does not understand config_foreach and therefore treats # functions and variables referenced only from config_foreach as not reachable, # or never invoked. # shellcheck disable=SC2317,SC2329 nut_set_serial_port_permissions() { local devname="$1" # Must be full path (e.g. /dev/...) local ups="$2" local device_group # Get group of RUNAS user, to set the group of the hotplugged device node # If RUNAS user does not exist, exit with an error. device_group="$(id -gn "${RUNAS:-nut}")" || { log_error "Unable to find group for '$RUNAS'. Skipping ups '$ups'" nut-serial nut-serial-hotplug return 1 } # Guard against disappearing device (e.g. due to 'bouncing' device) if [ -e "$devname" ]; then if ! chgrp "$device_group" "$devname"; then log_error "Unable to set group on '$devname'" nut-serial nut-serial-hotplug return 1 fi else log_error "'$devname' disappeared before setting group" nut-serial nut-serial-hotplug return 1 fi # Device could disappear between chgrp and chmod if [ -e "$devname" ]; then if ! chmod g+rw "$devname"; then log_error "Unable to set permissions on '$devname'" nut-serial nut-serial-hotplug return 1 fi else log_error "'$devname' disappeared before setting permissions" nut-serial nut-serial-hotplug return 1 fi return 0 } # If enable_usb_serial is set, set the permissions on applicable USB # serial devices to allow NUT access # We do not exit with error as that would stop processing of other UPS config # sections. # We use 'did_set_perms' as a 'pseudo-global' from nut_on_hotplug_add in order # to return a value to config_foreach # 'shellcheck' does not understand config_foreach and therefore treats # functions and variables referenced only from config_foreach as not reachable, # or never invoked. # shellcheck disable=SC2317,SC2329 nut_serial() { local ups="$1" # UCI section name is also the UPS configuration name local enable_usb_serial port normal_devname config_get_bool enable_usb_serial "$ups" enable_usb_serial 0 config_get port "$ups" port [ "$enable_usb_serial" = "1" ] && { # If port is specified only change tty's matching port if [ -n "$port" ] && [ "${port#/dev/}" = "${port}" ]; then port="/dev/$port" fi # Normalize DEVNAME normal_devname="$DEVNAME" if [ -n "$DEVNAME" ] && [ "${DEVNAME#/dev/}" = "${DEVNAME}" ]; then normal_devname="/dev/$DEVNAME" fi # Empty port from configuration means match any device. if [ "$port" = "$normal_devname" ] || [ "$port" = "" ]; then if nut_set_serial_port_permissions "$normal_devname" "$ups"; then did_set_perms="true" fi fi } } nut_on_hotplug_add() { # We use did_set_perms as a 'pseudo-global'. It is visible and can be # modified by called functions, but the changes do not propagate up from # this function. local did_set_perms="false" config_load nut_server || { log_config_load_error "nut_server" "nut-serial" "nut-serial-hotplug" exit 1 } # Only find statepath and runas once per event # sets RUNAS find_runas "upsd" "nut_server" || { log_error_exit "Failed to set RUNAS" nut-serial nut-serial-hotplug } # Check if serial USB UPS for each NUT driver instance (UPS), and if so # configure permissions to allow NUT to access the port config_foreach nut_serial driver # If we set permissions on a device that may have a UPS attached, run # the UPS (NUT) server reload to check for UPS configuration and # attempt to reload the driver and/or server if [ "$did_set_perms" = "true" ]; then # Informational and not an error logger -t nut-serial-hotplug "Successfully set permissions for serial device(s)" # We shouldn't get here if hotplug is disabled (race condition if we do), so log it as an error if ! allow_hotplug_restart; then log_error "Did not restart NUT after setting permissions as hotplug restart is disabled" nut-serial nut-serial-hotplug # Disabled hotplug is an allowed state return 0 fi if ! /etc/init.d/nut-server enabled; then # shellcheck disable=SC2153 log_error "NUT server disabled when processing hotplug event 'add' for '$DEVNAME'." nut-serial nut-serial-hotplug # Disabled nut-server is an allowed state return 0 else # Initscript should have its own logging /etc/init.d/nut-server reload || return 1 fi fi return 0 } # Do not do any hotplug actions if hotplug is disabled. Not an error but log # for information if ! allow_hotplug_restart; then # Informational and not an error logger -t nut-serial-hotplug "Did not set permissions on serial port hotplug as NUT hotplug is disabled" exit 0 fi if ! [ "$(id -u)" = "0" ]; then log_error_exit "Cannot set update device node on serial port hotplug. Hotplug not running as root." nut-serial nut-serial-hotplug fi # Only act on hotplug events with ACTION 'add' and where # the hotplugged device is a USB serial port # shellcheck disable=SC2153 if [ "$ACTION" = "add" ] && [ -n "$DEVNAME" ]; then # On add of a USB serial port with name # ttyUSB* or ttyAMA* or ttyACM* or ttyGS* case "$DEVNAME" in ttyUSB* | ttyAMA* | ttyACM* | ttyGS*) nut_on_hotplug_add || exit 1 ;; esac fi exit 0