p910nd: hotplug: small refactor
[feed/packages.git] / net / p910nd / files / p910nd.hotplug
1 #!/bin/sh
2 # c 2024 systemcrash (GitHub)
3
4 #hotplug.d triggers this script on the plug {in|out} of USB printers
5
6 # Define the uci config section
7 DAEMON=p910nd
8 DAEMON_HOTPLUG="$DAEMON hotplug"
9 DAEMON_ERR="daemon.err"
10 DAEMON_INFO="daemon.info"
11 DRIVER_HOME_DEFAULT="/opt/"$DAEMON"_drivers"
12 SYSUPGRADE_CONF="/etc/sysupgrade.conf"
13
14 # Assumptions:
15 # * There is no guarantee that multiple devices are re-assigned the same
16 # character device upon plug/unplug unless connection hierarchy/tree is
17 # unchanged i.e. reboot gives the same order if connection topology is identical.
18 # * Depends on udev. char dev number assignment order not guaranteed.
19 # * most users likely only have a single printer connected (mDNS announces one)
20
21 # Step 1. Get /dev/usb/lpX and build THIS_USB_VIDPID from hotplug passed device.
22
23 # Step 2a. Absent p910nd settings, auto configure settings with provided info.
24 # A usbvidpid is an anchor: to ensure the printer receives the right blob.
25 # Add other ieee1284_id derived info.
26
27 # Step 2b. For a matching character device, augment its existing config with any
28 # missing usbvidpid and ieee1284_id derived info.
29
30 # Step 3. For matching character device and usbvidpid: send_driver
31
32 # Caveat: hotplug always maps the first plugged device as /dev/usb/lp0. The 1st
33 # /dev/usb/lp0 match in config gets augmented with THIS_USB_VIDPID, whether
34 # it is the same "device" or not.
35 # The process below runs send_driver, but the driver is not yet on disk.
36 # This hotplug script aims for convenience, not technical perfection.
37 # Note that this does not matter if the configured lp0 does not match the
38 # current lp0. Why? We chose a specific filename for the blob, to which the user
39 # provides the file post-factum. So worst-case: soft-bricking if the user puts
40 # the wrong blob at the specified file path. This is an acceptable compromise to
41 # perfection until we find better ways of shooting ourselves in the foot. :)
42
43 # It is a configuration complexity that a p910nd end-user should anyhow be aware
44 # of: that multiple devices cannot simultaneously use the same /dev/usb/lpX.
45
46
47 # If run as hotplug usb module (as opposed to usbmisc module):
48 # DEV_TYPE_FILTER="usb_device"
49
50 # Test the script by running $0 -d.
51 if [ -n "$1" ] && [ "$1" = "-d" ]; then
52 # Set the variable DEBUG to true (or anything) for extra debug output
53 DEBUG=true
54
55 # Normal hotplug invocation provides these parameters:
56 DEVPATH="/devices/platform/ahb/1b400000.usb/usb2/2-1/2-1:1.0/usbmisc/lp0"
57 DEVNAME="usb/lp0"
58 ACTION="add"
59 fi
60
61 # For usbmisc, hotplug passes the following usable variables:
62 # $0: /sbin/hotplug-call
63 # $1: usbmisc
64 # $ACTION: add|remove
65 # $DEVNAME: usb/lp0
66 # $DEVPATH: /devices/platform/ahb/1b400000.usb/usb2/2-1/2-1:1.0/usbmisc/lp0
67 # $DEVICENAME: lp0
68 # $SEQNUM: 1555
69 # $MAJOR: 180
70 # $MINOR: 0
71
72 # For usb, hotplug passes the following usable variables:
73 # outputs:
74 # $0: /sbin/hotplug-call
75 # $1: usb
76 # $ACTION: add|remove|bind|unbind
77 # $DEVNAME: bus/usb/002/009
78 # $DEVNUM: 009
79 # $DEVPATH: /devices/platform/ahb/1b400000.usb/usb2/2-1
80 # $DEVICENAME: 2-1
81 # $DEVTYPE: usb_device
82 # $DRIVER: usb
83 # $TYPE: 0/0/0
84 # $PRODUCT: 3f0/4117/100
85 # $SEQNUM: 1534
86 # $BUSNUM: 002
87 # $MAJOR: 180
88 # $MINOR: 0
89
90
91 # usbmisc scripts have access to fewer parameters than usb hotplug scripts, so
92 # we must be able to assemble THIS_USB_VIDPID ourselves.
93
94 # use % for shortest match, and trim away "/usbmisc/lp*"
95 ACTUAL_DEVPATH=${DEVPATH%/usbmisc/lp*}
96 # Prepend /sys/ to get actual device path,
97 ACTUAL_DEVPATH="/sys$ACTUAL_DEVPATH"
98 [ $DEBUG ] && echo ACTUAL_DEVPATH is $ACTUAL_DEVPATH
99 PARENT_DEVPATH=$( dirname "${ACTUAL_DEVPATH}" )
100 # We might need to do this if symlinks are problematic. Might not:
101 # devpath="$(readlink -f $ACTUAL_DEVPATH)"
102
103
104 # https://www.usb.org/sites/default/files/usbprint11a021811.pdf
105 # Check whether connected device is a "Printer"
106 [ "$(cat "$ACTUAL_DEVPATH/bInterfaceClass")" == "07" ] && [ "$(cat "$ACTUAL_DEVPATH/bInterfaceSubClass")" == "01" ] && iAmAPrinter=true
107 # Not a printer? Bail.
108 [ ! $iAmAPrinter ] && exit 0
109
110
111 # Port directionality
112 BIP=$( cat "$ACTUAL_DEVPATH/bInterfaceProtocol" )
113 [ $DEBUG ] && echo BIP: $BIP
114 case $BIP in
115 01 )
116 BIDIR=0
117 ;;
118 02 | 03 )
119 BIDIR=1
120 ;;
121 esac
122
123
124 # Next, we need THIS_USB_VIDPID. This is to ensure that we send the right blob
125 # to the right USB printer. THIS_USB_VIDPID is an anchor, or filter, if you will.
126
127 # THIS_USB_VIDPID is formed by: idVendor/idProduct
128 # Found under: /sys/bus/usb/devices/*/idVendor
129 # Avoid anchoring also to bcdDevice which is like a hw version
130 # printer driver blobs account for different hw versions anyway, so ignore it.
131 # THIS_USB_VIDPID="3f0/4117"
132
133 idVendor=$( cat ""$PARENT_DEVPATH"/idVendor" )
134 idProduct=$( cat ""$PARENT_DEVPATH"/idProduct" )
135 [ $DEBUG ] && echo idVendor+idProduct: $idVendor + $idProduct
136 THIS_USB_VIDPID="$idVendor/$idProduct"
137 # Driver blob e.g.: Hewlett-Packard_HP_LaserJet_1018_03f0_4117.bin
138
139
140 # Not always available:
141 iSerialNumber=$( cat "iSerialNumber" 2>/dev/null ) || iSerialNumber=$( cat "serial" 2>/dev/null )
142 [ $DEBUG ] && echo iSerialNumber is $iSerialNumber
143
144
145 # Get the special IEEE1284 Device ID string (apparently limited to 127 chars)
146 ieee1284info=$(cat ""$ACTUAL_DEVPATH"/ieee1284_id" )
147 [ $DEBUG ] && echo ieee1284info is $ieee1284info
148
149
150 # Absent the uci daemon hotplug config group, set it to a default
151 [ -z $(uci -q get $DAEMON.@hotplug[0]) ] && uci -q add $DAEMON hotplug
152
153 # # Absent the driver_home path config, set it to a default
154 [ -z $(uci -q get $DAEMON.@hotplug[0].driver_home) ] && uci -q set $DAEMON.@hotplug[-1].driver_home=$DRIVER_HOME_DEFAULT
155
156 # Make the driver folder hierarchy
157 if ! $(mkdir -p $DRIVER_HOME_DEFAULT); then
158 logger -t "$DAEMON_HOTPLUG" -p $DAEMON_ERR "Error running 'mkdir -p "$DRIVER_HOME_DEFAULT"'."
159 fi
160
161 # Help the folder survive a sysupgrade:
162 if ! $( grep -q "^$DRIVER_HOME_DEFAULT$" "$SYSUPGRADE_CONF" ); then
163 # TODO: remove old non-existent p910nd paths from $SYSUPGRADE_CONF?
164 # Absent the path, try to add it to $SYSUPGRADE_CONF
165 if ! echo $DRIVER_HOME_DEFAULT >> $SYSUPGRADE_CONF; then
166 logger -t "$DAEMON_HOTPLUG" -p $DAEMON_ERR "Problem adding "$DRIVER_HOME_DEFAULT" path to $SYSUPGRADE_CONF."
167 else
168 logger -t "$DAEMON_HOTPLUG" -p $DAEMON_INFO "Added "$DRIVER_HOME_DEFAULT" path to $SYSUPGRADE_CONF."
169 fi
170 fi
171
172 DRIVER_HOME=$(uci -q get $DAEMON.@hotplug[-1].driver_home)
173 [ $DEBUG ] && echo DRIVER_HOME is $DRIVER_HOME
174 # Trim trailing forward slash if it crept in somehow.
175 DRIVER_HOME=${DRIVER_HOME%/}
176 DRIVER_BLOBNAME_TAIL=""$idVendor"_"$idProduct".bin"
177 [ $DEBUG ] && echo DRIVER_BLOBNAME_TAIL is $DRIVER_BLOBNAME_TAIL
178
179
180 # Global device config number variable
181 UCI_DEV_CFG_NUMBER=-1
182
183
184 # find which daemon configs have the matching lpX interface
185 match_current_device() {
186 # Build array of /dev/usb/lpX character devices already configured
187 set -- $(IFS="\n" && uci -q batch <<- EOI
188 get $DAEMON.@$DAEMON[0].device
189 get $DAEMON.@$DAEMON[1].device
190 get $DAEMON.@$DAEMON[2].device
191 get $DAEMON.@$DAEMON[3].device
192 get $DAEMON.@$DAEMON[4].device
193 get $DAEMON.@$DAEMON[5].device
194 get $DAEMON.@$DAEMON[6].device
195 get $DAEMON.@$DAEMON[7].device
196 get $DAEMON.@$DAEMON[8].device
197 get $DAEMON.@$DAEMON[9].device
198 EOI
199 )
200 # $1-$10 are now set
201
202 x=0
203 for i in $@; do
204 # $DEVNAME is passed by hotplug
205 [ $DEBUG ] && echo UCI_DEV_CFG_NUMBER is $UCI_DEV_CFG_NUMBER and CHAR_DEV is $CHAR_DEV
206 [ $DEVNAME == ${i#/dev/} ] && UCI_DEV_CFG_NUMBER=$x && CHAR_DEV=$i
207 # TODO: multiple configured devices could have same CHAR_DEV if not connected concurrently
208 x=$(( $x+1 ))
209 done
210 }
211
212
213 get_and_store_printer_info() {
214 # gets /sys/bus/usb/devices/2-1:1.0/ieee1284_id:
215 # MFG:Hewlett-Packard
216 # MDL:HP LaserJet 1018
217 # CMD:ACL
218 # CLS:PRINTER
219 # DES:HP LaserJet 1018
220 local MFG
221 local MDL
222 local CMD
223 local CLS
224 local DES
225 local CID
226 local CMT
227 local SN
228
229
230 # Build array of /dev/usb/lpX character devices already configured
231 match_current_device
232
233 uqgddu_cmd="uci -q get $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER]"
234 uqsddu_cmd="uci -q set $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER]"
235
236 # set Internal Field Separator to semicolon found in ieee1284_id files
237 IFS=";"
238 # Got 1284 Device ID string
239 set -- $ieee1284info
240 [ $DEBUG ] && echo ieee1284info: $ieee1284info
241
242 for i in "$@"; do
243 [ $DEBUG ] && echo i:"$i"
244
245 case $i in
246 MFG:* | MANUFACTURER:* )
247 MFG=${i##*:};;
248 MDL:* | MODEL:* )
249 MDL=${i##*:};;
250 CMD:* | "COMMAND SET:*" )
251 CMD=${i##*:};;
252 CLS:* )
253 CLS=${i##*:};;
254 DES:* )
255 DES=${i##*:};;
256 CID:* | COMPATIBLEID:* )
257 CID=${i##*:};;
258 COMMENT:* )
259 CMT=${i##*:};;
260 SN:* )
261 SN=${i##*:};;
262 esac
263
264 [ -n "$SN" ] || SN=$iSerialNumber
265 [ $DEBUG ] && echo ${MFG:+MFG=$MFG} ${MDL:+MDL=$MDL} ${CMD:+CMD=$CMD} ${CLS:+CLS=$CLS} ${DES:+DES=$DES} ${SN:+SN=$SN}
266
267 [ $DEBUG ] && echo 'uci set' for UCI_DEV_CFG_NUMBER: $UCI_DEV_CFG_NUMBER
268 # Take the USB info as fact: set bidir regardless. It seems to be a source of confusion.
269 eval "$uqsddu_cmd.bidirectional='$BIDIR'"
270 [ -z "$(eval "$uqgddu_cmd".port)" ] && eval "$uqsddu_cmd.port='0'"
271 [ -z "$(eval "$uqgddu_cmd".enabled)" ] && eval "$uqsddu_cmd.enabled='1'"
272 [ -z "$(eval "$uqgddu_cmd".usbvidpid)" ] && [ -n "$THIS_USB_VIDPID" ] && eval "$uqsddu_cmd.usbvidpid='$THIS_USB_VIDPID'"
273 # Safe to default to on for mDNS if we found one of the mandatory properties (e.g. MDL)
274 [ -z "$(eval "$uqgddu_cmd".mdns)" ] && [ -n "$MDL" ] && eval "$uqsddu_cmd.mdns='1'"
275 [ -z "$(eval "$uqgddu_cmd".mdns_ty)" ] && [ -n "$DES" ] && eval "$uqsddu_cmd.mdns_ty='$DES'"
276 [ -z "$(eval "$uqgddu_cmd".mdns_product)" ] && [ -n "$DES" ] && eval "$uqsddu_cmd.mdns_product='($DES)'"
277 [ -z "$(eval "$uqgddu_cmd".mdns_mfg)" ] && [ -n "$MFG" ] && eval "$uqsddu_cmd.mdns_mfg='$MFG'"
278 [ -z "$(eval "$uqgddu_cmd".mdns_mdl)" ] && [ -n "$MDL" ] && eval "$uqsddu_cmd.mdns_mdl='$MDL'"
279 [ -z "$(eval "$uqgddu_cmd".mdns_cmd)" ] && [ -n "$CMD" ] && eval "$uqsddu_cmd.mdns_cmd='$CMD'"
280 [ -z "$(eval "$uqgddu_cmd".mdns_note)" ] && eval "$uqsddu_cmd.mdns_note='Located near router'"
281 # Optional ieee1284_id parameters
282 [ -z "$(eval "$uqgddu_cmd".mdns_cid)" ] && [ -n "$CID" ] && eval "$uqsddu_cmd.mdns_cid='$CID'"
283 [ -z "$(eval "$uqgddu_cmd".mdns_cls)" ] && [ -n "$CLS" ] && eval "$uqsddu_cmd.mdns_cls='$CLS'"
284 [ -z "$(eval "$uqgddu_cmd".mdns_cmt)" ] && [ -n "$CMT" ] && eval "$uqsddu_cmd.mdns_cmt='$CMT'"
285 [ -z "$(eval "$uqgddu_cmd".mdns_sn)" ] && [ -n "$SN" ] && eval "$uqsddu_cmd.mdns_sn='$SN'"
286
287 if [ -n "$MFG" ] && [ -n "$MDL" ] && [ -n "$DRIVER_BLOBNAME_TAIL" ] && [ -z "$($uqgddu_cmd.driver_file)" ]; then
288 DRIVER_FILE="$MFG"_"$MDL"_"$DRIVER_BLOBNAME_TAIL"
289 # Make blob filename more friendly: change space to underscore
290 DRIVER_FILE="$DRIVER_HOME"/"${DRIVER_FILE// /_}"
291 [ $DEBUG ] && echo DRIVER_FILE: $DRIVER_FILE
292 uci set $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].driver_file="$DRIVER_FILE"
293 fi
294
295 done
296
297 }
298
299 daemon_restart() {
300 logger -t "$DAEMON_HOTPLUG" -p $DAEMON_INFO "(Re)starting $DAEMON"
301 /etc/init.d/$DAEMON restart
302 }
303 daemon_stop() {
304 logger -t "$DAEMON_HOTPLUG" -p $DAEMON_INFO "Stopping $DAEMON"
305 /etc/init.d/$DAEMON stop
306 }
307 send_driver() {
308 DRIVER_FILE=$( uci -q get $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].driver_file )
309
310 if [ -e $DRIVER_FILE ]; then
311 logger -t "$DAEMON_HOTPLUG" -p $DAEMON_INFO "Sending driver to $DAEMON printer $THIS_USB_VIDPID"
312
313 if ! cat $DRIVER_FILE > $CHAR_DEV; then
314 logger -t "$DAEMON_HOTPLUG" -p $DAEMON_ERR "Sending driver to $CHAR_DEV [ $THIS_USB_VIDPID ] failed for some reason."
315 else
316 logger -t "$DAEMON_HOTPLUG" -p $DAEMON_INFO "Sent $DRIVER_FILE to $CHAR_DEV [ $THIS_USB_VIDPID ]."
317 daemon_restart
318 fi
319 else
320 logger -t "$DAEMON_HOTPLUG" -p $DAEMON_ERR "Missing driver file: $DRIVER_FILE for $CHAR_DEV [ $THIS_USB_VIDPID ] (please upload it)."
321 fi
322 }
323
324 case "$ACTION" in
325 add)
326 # Set permissions on the /dev/usb/lpX char dev
327 [ -n "${DEVNAME}" ] && [ "${DEVNAME##usb/lp*}" = "" ] && {
328 chmod 660 /dev/"$DEVNAME"
329 chgrp lp /dev/"$DEVNAME"
330 }
331
332 get_and_store_printer_info
333
334 [ $DEBUG ] && echo THIS_USB_VIDPID: $THIS_USB_VIDPID
335 [ $DEBUG ] && echo CHAR_DEV: $CHAR_DEV
336 # usb subsys only:
337 # [ $DEBUG ] && echo DEVTYPE: $DEVTYPE
338 # [ $DEBUG ] && echo DEV_TYPE_FILTER: $DEV_TYPE_FILTER
339 # [ $DEBUG ] && echo PRODUCT: $PRODUCT
340
341 # Extra checks available when run as hotplug usb script:
342 # [ "$DEVTYPE" == "${DEV_TYPE_FILTER}" ]
343 # [ -z "${PRODUCT##*$THIS_USB_VIDPID*}" ]
344
345 # Ensure dev is character device
346 if [ -n "$THIS_USB_VIDPID" ] && [ -c $CHAR_DEV ]; then
347 # if zero string, i.e. usb_ID is a match for $PRODUCT supplied by hotplug
348 if [ $(uci -q get $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].usbvidpid) == "$THIS_USB_VIDPID" ]; then
349 [ $DEBUG ] && echo "THIS_USB_VIDPID match for $DAEMON device $THIS_USB_VIDPID."
350
351 send_driver
352
353 else
354 [ $DEBUG ] && echo "No THIS_USB_VIDPID match."
355 fi
356 fi
357
358 ;;
359 remove)
360 # device is gone
361 ;;
362 # Special actions available to "usb" subsystem
363 # bind)
364 # # special action
365 # ;;
366 # unbind)
367 # # special action
368 # ;;
369 esac
370
371 # Commit any changes
372 [ -n $( uci -q changes $DAEMON ) ] && uci commit $DAEMON