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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
|
#!/bin/sh
# Shebang line included so editors and shellcheck/shfmt know this contains
# shell code
# Helper functions for NUT server service handling
# In recent (relevant) versions of shellcheck busybox is a valid shell type
# shellcheck shell=busybox
# config_load is done by the sourcing script, before executing any functions in
# this file
# Pre-requisite sourcing for functions this script uses
# * /lib/functions.sh has been sourced
# * /lib/functions/nut/nut-common.sh has been sourced
# find_runas to find the RUNAS user should already have been
# run by nut-server initscript prior to calling
# ensure_usb_ups_access.
# Ensure the RUNAS user has access to the USB UPS device
ensure_usb_ups_access() {
local ups="$1"
local known_devname="$2"
local cfg_vendorid
local cfg_productid
local device_owner
local device_group
local usb_bus
local usb_dev
local device_path
local cur_devname
local device_serial
local sysfs_vendorid
local sysfs_productid
local serial
# When no USB bus or USB device is found, the value returned in each case,
# after printf, will be '000', which is not a valid USB bus or USB device
# number (i.e. it is a missing data indicator).
local missing_usb_num="000"
device_owner="$RUNAS"
[ -n "$RUNAS" ] || {
log_error "No RUNAS user specified. Skipping ups '$ups'" nut-server-service.sh nut-server-service
return 1
}
device_group="$(id -gn "$RUNAS")" || {
log_error "Unable to find group for RUNAS='$RUNAS'. Skipping ups '$ups'" nut-server-service.sh nut-server-service
return 1
}
config_get cfg_vendorid "$ups" vendorid
# replacement in variable parameter expansion is supported by ash in OpenWrt
# since at least OpenWrt-24.10 (the previous stable release, which two
# versions older than the targetted next release of OpenWrt (after
# OpenWrt-25.12). This has been verified by the author of this script).
if [ -n "$cfg_vendorid" ] && ! check_valid_hex_number "${cfg_vendorid/0x/}"; then
log_error "Configured vendorid for '$ups' is not valid" nut-server-service.sh nut-server-service
return 1
fi
# Configuration might not have a vendorid. This is allowed, but we have
# nothing to do here in that case.
[ -n "$cfg_vendorid" ] || return 0
case "$cfg_vendorid" in
0x*)
cfg_vendorid="$(printf "%04x" "$cfg_vendorid")"
;;
*)
cfg_vendorid="$(printf "%04x" "0x$cfg_vendorid")"
;;
esac
config_get cfg_productid "$ups" productid
if [ -n "$cfg_productid" ] && ! check_valid_hex_number "${cfg_productid/0x/}"; then
log_error "Configured productid for '$ups' is not valid" nut-server-service.sh nut-server-service
return 1
fi
# Configuration might not have a productid. This is allowed, but we have
# nothing to do here in that case.
[ -n "$cfg_productid" ] || return 0
case "$cfg_productid" in
0x*)
cfg_productid="$(printf "%04x" "$cfg_productid")"
;;
*)
cfg_productid="$(printf "%04x" "0x$cfg_productid")"
;;
esac
config_get serial "$ups" serial
# $'\x' syntax is supported by ash in OpenWrt since at least OpenWrt-24.10
# (the previous stable release, which two versions older than the targetted
# next release of OpenWrt (after OpenWrt-25.12). This has been verified by
# the author of this script).
# shellcheck disable=SC3003
local NL=$'\n'
# Loop on the idVendor of all USB devices attached to the system
# We do not need any return values from this loop, which exists solely for the side-effects of
# setting the appropriate device permissions and group, so subshell is okay.
# IFS is set to newline *only*. Default IFS also includes tab and space
# Disable globbing. `find` will still use patterns.
set -f
find /sys/devices -name idVendor -a -path '*/usb[0-9]*/*' | while IFS="$NL" read -r vendor_path; do
set +f
usb_bus=""
usb_dev=""
device_path=""
cur_devname=""
device_serial=""
# Filter by vendor ID first
sysfs_vendorid="$(cat "$vendor_path")"
if [ -z "$sysfs_vendorid" ]; then
log_error "Could not read vendor ID from $vendor_path" nut-server-service.sh nut-server-service
continue
fi
if [ "$sysfs_vendorid" != "$cfg_vendorid" ]; then
continue
fi
device_path="$(dirname "$vendor_path")"
# Then filter by product ID
sysfs_productid="$(cat "$device_path/idProduct")"
if [ -z "$sysfs_productid" ]; then
log_error "Could not read product ID from $device_path" nut-server-service.sh nut-server-service
continue
fi
if [ "$sysfs_productid" != "$cfg_productid" ]; then
continue
fi
# Next filter by serial (number), if provided
# guard against disappearing device_path (e.g. due to 'bouncing' device connection)
if [ ! -e "$device_path" ]; then
continue
fi
# Only check against device serial number if a serial number is
# in the configuration for this UPS
if [ -n "$serial" ]; then
device_serial="$(cat "$device_path/serial" 2>/dev/null)"
if [ "$serial" != "$device_serial" ]; then
continue
fi
fi
usb_bus="$(printf "%03d" "$(cat "$device_path/busnum")")"
usb_dev="$(printf "%03d" "$(cat "$device_path/devnum")")"
# usb_bus and usb_dev must each be at least 001
# a missing value will be present as 000 due to 'printf "%03d"'
if [ "$usb_bus" = "$missing_usb_num" ] || [ "$usb_dev" = "$missing_usb_num" ]; then
# If this sysfs device is not valid, keep looking (and ignore this
# sysfs device).
continue
else
# If we are called from hotplug, we know the device name, so skip any other devices
cur_devname="/dev/bus/usb/$usb_bus/$usb_dev"
if [ -n "$known_devname" ] && [ "$known_devname" != "$cur_devname" ]; then
continue
fi
# guard against disappearing cur_devname (e.g. due to 'bouncing' device connection)
if [ -e "$cur_devname" ]; then
chmod 0660 "$cur_devname" || {
log_error "'chmod' failed for $cur_devname (ups '$ups')" nut-server-service.sh nut-server-service
continue
}
# Re-check in case device disappeared after chmod
if [ -e "$cur_devname" ]; then
chown "${device_owner}:${device_group}" "$cur_devname" || {
log_error "'chown' failed for $cur_devname (ups '$ups')" nut-server-service.sh nut-server-service
continue
}
fi
else
continue
fi
# Serial numbers are defined as unique, so do not loop further (on /sys/devices - while),
# if serial was present and matched
if [ -n "$serial" ]; then
# Break out of while loop (so we do not process more 'find' results)
break
fi
# If a serial number is not provided we need all vendor:product matches
# to have permissions for NUT as we do not know the method used
# to match UPS device to a configuration, in this script
# (but if we have a known_devname, limit to that device)
fi
set -f
done
set +f
}
|