1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
|
#!/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
|