From 825b22a4db952c891b07341e0176bc6d64f2d72a Mon Sep 17 00:00:00 2001 From: Paul Donald Date: Tue, 16 Jan 2024 02:47:32 +0100 Subject: [PATCH] p910nd: hotplug script Signed-off-by: Paul Donald --- net/p910nd/Makefile | 2 +- net/p910nd/files/p910nd.hotplug | 361 +++++++++++++++++++++++++++++++- 2 files changed, 357 insertions(+), 6 deletions(-) diff --git a/net/p910nd/Makefile b/net/p910nd/Makefile index cb5b7faecf..14af837fdb 100644 --- a/net/p910nd/Makefile +++ b/net/p910nd/Makefile @@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=p910nd PKG_VERSION:=0.97 -PKG_RELEASE:=9 +PKG_RELEASE:=10 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.bz2 PKG_SOURCE_URL:=@SF/p910nd diff --git a/net/p910nd/files/p910nd.hotplug b/net/p910nd/files/p910nd.hotplug index 0c2291efaf..15107e377a 100644 --- a/net/p910nd/files/p910nd.hotplug +++ b/net/p910nd/files/p910nd.hotplug @@ -1,13 +1,364 @@ #!/bin/sh +# c 2024 systemcrash (GitHub) + +#hotplug.d triggers this script on the plug {in|out} of USB printers + +# Define the uci config section +DAEMON=p910nd +DAEMON_HOTPLUG="$DAEMON hotplug" +DAEMON_ERR="daemon.err" +DAEMON_INFO="daemon.info" +DRIVER_HOME_DEFAULT="/opt/"$DAEMON"_drivers" +SYSUPGRADE_CONF="/etc/sysupgrade.conf" + +# Assumptions: +# * There is no guarantee that multiple devices are re-assigned the same +# character device upon plug/unplug unless connection hierarchy/tree is +# unchanged i.e. reboot gives the same order if connection topology is identical. +# * Depends on udev. char dev number assignment order not guaranteed. +# * most users likely only have a single printer connected (mDNS announces one) + +# Step 1. Get /dev/usb/lpX and build THIS_USB_VIDPID from hotplug passed device. + +# Step 2a. Absent p910nd settings, auto configure settings with provided info. +# A usbvidpid is an anchor: to ensure the printer receives the right blob. +# Add other ieee1284_id derived info. + +# Step 2b. For a matching character device, augment its existing config with any +# missing usbvidpid and ieee1284_id derived info. + +# Step 3. For matching character device and usbvidpid: send_driver + +# Caveat: hotplug always maps the first plugged device as /dev/usb/lp0. The 1st +# /dev/usb/lp0 match in config gets augmented with THIS_USB_VIDPID, whether +# it is the same "device" or not. +# The process below runs send_driver, but the driver is not yet on disk. +# This hotplug script aims for convenience, not technical perfection. +# Note that this does not matter if the configured lp0 does not match the +# current lp0. Why? We chose a specific filename for the blob, to which the user +# provides the file post-factum. So worst-case: soft-bricking if the user puts +# the wrong blob at the specified file path. This is an acceptable compromise to +# perfection until we find better ways of shooting ourselves in the foot. :) + +# It is a configuration complexity that a p910nd end-user should anyhow be aware +# of: that multiple devices cannot simultaneously use the same /dev/usb/lpX. + + +# If run as hotplug usb module (as opposed to usbmisc module): +# DEV_TYPE_FILTER="usb_device" + +# Test the script by running $0 -d. +if [ -n "$1" -a "$1" == "-d" ]; then + # Set the variable DEBUG to true (or anything) for extra debug output + DEBUG=true + + # Normal hotplug invocation provides these parameters: + DEVPATH="/devices/platform/ahb/1b400000.usb/usb2/2-1/2-1:1.0/usbmisc/lp0" + DEVNAME="usb/lp0" + ACTION="add" +fi + +# For usbmisc, hotplug passes the following usable variables: +# $0: /sbin/hotplug-call +# $1: usbmisc +# $ACTION: add|remove +# $DEVNAME: usb/lp0 +# $DEVPATH: /devices/platform/ahb/1b400000.usb/usb2/2-1/2-1:1.0/usbmisc/lp0 +# $DEVICENAME: lp0 +# $SEQNUM: 1555 +# $MAJOR: 180 +# $MINOR: 0 + +# For usb, hotplug passes the following usable variables: +# outputs: +# $0: /sbin/hotplug-call +# $1: usb +# $ACTION: add|remove|bind|unbind +# $DEVNAME: bus/usb/002/009 +# $DEVNUM: 009 +# $DEVPATH: /devices/platform/ahb/1b400000.usb/usb2/2-1 +# $DEVICENAME: 2-1 +# $DEVTYPE: usb_device +# $DRIVER: usb +# $TYPE: 0/0/0 +# $PRODUCT: 3f0/4117/100 +# $SEQNUM: 1534 +# $BUSNUM: 002 +# $MAJOR: 180 +# $MINOR: 0 + + +# usbmisc scripts have access to fewer parameters than usb hotplug scripts, so +# we must be able to assemble THIS_USB_VIDPID ourselves. + +# use % for shortest match, and trim away "/usbmisc/lp*" +ACTUAL_DEVPATH=${DEVPATH%/usbmisc/lp*} +# Prepend /sys/ to get actual device path, +ACTUAL_DEVPATH="/sys$ACTUAL_DEVPATH" +[ $DEBUG ] && echo ACTUAL_DEVPATH is $ACTUAL_DEVPATH +PARENT_DEVPATH=$( dirname "${ACTUAL_DEVPATH}" ) +# We might need to do this if symlinks are problematic. Might not: +# devpath="$(readlink -f $ACTUAL_DEVPATH)" + + +# https://www.usb.org/sites/default/files/usbprint11a021811.pdf +# Check whether connected device is a "Printer" +[ "$(cat "$ACTUAL_DEVPATH/bInterfaceClass")" == "07" ] && [ "$(cat "$ACTUAL_DEVPATH/bInterfaceSubClass")" == "01" ] && iAmAPrinter=true +# Not a printer? Bail. +[ ! $iAmAPrinter ] && exit 0 + + +# Port directionality +BIP=$( cat "$ACTUAL_DEVPATH/bInterfaceProtocol" ) +[ $DEBUG ] && echo BIP: $BIP +case $BIP in + 01 ) + BIDIR=0 + ;; + 02 | 03 ) + BIDIR=1 + ;; +esac + + +# Next, we need THIS_USB_VIDPID. This is to ensure that we send the right blob +# to the right USB printer. THIS_USB_VIDPID is an anchor, or filter, if you will. + +# THIS_USB_VIDPID is formed by: idVendor/idProduct +# Found under: /sys/bus/usb/devices/*/idVendor +# Avoid anchoring also to bcdDevice which is like a hw version +# printer driver blobs account for different hw versions anyway, so ignore it. +# THIS_USB_VIDPID="3f0/4117" + +idVendor=$( cat ""$PARENT_DEVPATH"/idVendor" ) +idProduct=$( cat ""$PARENT_DEVPATH"/idProduct" ) +[ $DEBUG ] && echo idVendor+idProduct: $idVendor + $idProduct +THIS_USB_VIDPID="$idVendor/$idProduct" +# Driver blob e.g.: Hewlett-Packard_HP_LaserJet_1018_03f0_4117.bin + + +# Not always available: +iSerialNumber=$( cat "iSerialNumber" 2>/dev/null ) || iSerialNumber=$( cat "serial" 2>/dev/null ) +[ $DEBUG ] && echo iSerialNumber is $iSerialNumber + + +# Get the special IEEE1284 Device ID string (apparently limited to 127 chars) +ieee1284info=$(cat ""$ACTUAL_DEVPATH"/ieee1284_id" ) +[ $DEBUG ] && echo ieee1284info is $ieee1284info + + +# Absent the uci daemon hotplug config group, set it to a default +[ -z $(uci -q get $DAEMON.@hotplug[0]) ] && uci -q add $DAEMON hotplug + +# # Absent the driver_home path config, set it to a default +[ -z $(uci -q get $DAEMON.@hotplug[0].driver_home) ] && uci -q set $DAEMON.@hotplug[-1].driver_home=$DRIVER_HOME_DEFAULT + +# Make the driver folder hierarchy +if ! $(mkdir -p $DRIVER_HOME_DEFAULT); then + logger -t "$DAEMON_HOTPLUG" -p $DAEMON_ERR "Error running 'mkdir -p "$DRIVER_HOME_DEFAULT"'." +fi + +# Help the folder survive a sysupgrade: +if ! $( grep -q "^$DRIVER_HOME_DEFAULT$" "$SYSUPGRADE_CONF" ); then + # TODO: remove old non-existent p910nd paths from $SYSUPGRADE_CONF? + # Absent the path, try to add it to $SYSUPGRADE_CONF + if ! echo $DRIVER_HOME_DEFAULT >> $SYSUPGRADE_CONF; then + logger -t "$DAEMON_HOTPLUG" -p $DAEMON_ERR "Problem adding "$DRIVER_HOME_DEFAULT" path to $SYSUPGRADE_CONF." + else + logger -t "$DAEMON_HOTPLUG" -p $DAEMON_INFO "Added "$DRIVER_HOME_DEFAULT" path to $SYSUPGRADE_CONF." + fi +fi + +DRIVER_HOME=$(uci -q get $DAEMON.@hotplug[-1].driver_home) +[ $DEBUG ] && echo DRIVER_HOME is $DRIVER_HOME +# Trim trailing forward slash if it crept in somehow. +DRIVER_HOME=${DRIVER_HOME%/} +DRIVER_BLOBNAME_TAIL=""$idVendor"_"$idProduct".bin" +[ $DEBUG ] && echo DRIVER_BLOBNAME_TAIL is $DRIVER_BLOBNAME_TAIL + + +# Global device config number variable +UCI_DEV_CFG_NUMBER=-1 + + +# find which daemon configs have the matching lpX interface +match_current_device() { + # Build array of /dev/usb/lpX character devices already configured + set -- $(IFS="\n" && uci -q batch <<- EOI + get $DAEMON.@$DAEMON[0].device + get $DAEMON.@$DAEMON[1].device + get $DAEMON.@$DAEMON[2].device + get $DAEMON.@$DAEMON[3].device + get $DAEMON.@$DAEMON[4].device + get $DAEMON.@$DAEMON[5].device + get $DAEMON.@$DAEMON[6].device + get $DAEMON.@$DAEMON[7].device + get $DAEMON.@$DAEMON[8].device + get $DAEMON.@$DAEMON[9].device + EOI + ) + # $1-$10 are now set + + x=0 + for i in $@; do + # $DEVNAME is passed by hotplug + [ $DEBUG ] && echo UCI_DEV_CFG_NUMBER is $UCI_DEV_CFG_NUMBER and CHAR_DEV is $CHAR_DEV + [ $DEVNAME == ${i#/dev/} ] && UCI_DEV_CFG_NUMBER=$x && CHAR_DEV=$i + # TODO: multiple configured devices could have same CHAR_DEV if not connected concurrently + x=$(( $x+1 )) + done +} + + +get_and_store_printer_info() { + # gets /sys/bus/usb/devices/2-1:1.0/ieee1284_id: + # MFG:Hewlett-Packard + # MDL:HP LaserJet 1018 + # CMD:ACL + # CLS:PRINTER + # DES:HP LaserJet 1018 + local MFG + local MDL + local CMD + local CLS + local DES + local CID + local CMT + local SN + + + # Build array of /dev/usb/lpX character devices already configured + match_current_device + + # set Internal Field Separator to semicolon found in ieee1284_id files + IFS=";" + # Got 1284 Device ID string + set -- $ieee1284info + [ $DEBUG ] && echo ieee1284info: $ieee1284info + + for i in "$@"; do + [ $DEBUG ] && echo "$i" + [ $DEBUG ] && echo + + case $i in + MFG:* | MANUFACTURER:* ) + MFG=${i##*:};; + MDL:* | MODEL:* ) + MDL=${i##*:};; + CMD:* | "COMMAND SET:*" ) + CMD=${i##*:};; + CLS:* ) + CLS=${i##*:};; + DES:* ) + DES=${i##*:};; + CID:* | COMPATIBLEID:* ) + CID=${i##*:};; + COMMENT:* ) + CMT=${i##*:};; + SN:* ) + SN=${i##*:};; + esac + + [ -n "$SN" ] || SN=$iSerialNumber + [ $DEBUG ] && echo ${MFG:+MFG=$MFG} ${MDL:+MDL=$MDL} ${CMD:+CMD=$CMD} ${CLS:+CLS=$CLS} ${DES:+DES=$DES} ${SN:+SN=$SN} + + [ $DEBUG ] && echo 'uci set' for UCI_DEV_CFG_NUMBER: $UCI_DEV_CFG_NUMBER + # Take the USB info as fact: set bidir regardless. It seems to be a source of confusion. + uci -q set $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].bidirectional="$BIDIR" + [ -z "$(uci -q get $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].port)" ] && uci set $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].port="0" + [ -z "$(uci -q get $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].enabled)" ] && uci set $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].enabled="1" + [ -z "$(uci -q get $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].usbvidpid)" -a -n "$THIS_USB_VIDPID" ] && uci set $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].usbvidpid="$THIS_USB_VIDPID" + # [ -z "$(uci -q get $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].mdns)" ] && uci set $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].mdns="1" + [ -z "$(uci -q get $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].mdns_ty)" -a -n "$DES" ] && uci set $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].mdns_ty="$DES" + [ -z "$(uci -q get $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].mdns_product)" -a -n "$DES" ] && uci set $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].mdns_product="($DES)" + [ -z "$(uci -q get $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].mdns_mfg)" -a -n "$MFG" ] && uci set $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].mdns_mfg="$MFG" + [ -z "$(uci -q get $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].mdns_mdl)" -a -n "$MDL" ] && uci set $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].mdns_mdl="$MDL" + [ -z "$(uci -q get $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].mdns_cmd)" -a -n "$CMD" ] && uci set $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].mdns_cmd="$CMD" + [ -z "$(uci -q get $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].mdns_note)" -a -n "$SN" ] && uci set $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].mdns_note="SN:"$SN" Auto-configured by $DAEMON_HOTPLUG" + + if [ -n "$MFG" -a -n "$MDL" -a -n "$DRIVER_BLOBNAME_TAIL" ] && [ -z "$(uci -q get $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].driver_file)" ]; then + DRIVER_FILE="$MFG"_"$MDL"_"$DRIVER_BLOBNAME_TAIL" + # Make blob filename more friendly: change space to underscore + DRIVER_FILE="$DRIVER_HOME"/"${DRIVER_FILE// /_}" + [ $DEBUG ] && echo DRIVER_FILE: $DRIVER_FILE + uci set $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].driver_file="$DRIVER_FILE" + fi + + done + +} + +daemon_restart() { + logger -t "$DAEMON_HOTPLUG" -p $DAEMON_INFO "(Re)starting $DAEMON" + /etc/init.d/$DAEMON restart +} +daemon_stop() { + logger -t "$DAEMON_HOTPLUG" -p $DAEMON_INFO "Stopping $DAEMON" + /etc/init.d/$DAEMON stop +} +send_driver() { + DRIVER_FILE=$( uci -q get $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].driver_file ) + + if [ -e $DRIVER_FILE ]; then + logger -t "$DAEMON_HOTPLUG" -p $DAEMON_INFO "Sending driver to $DAEMON printer $THIS_USB_VIDPID" + + if ! cat $DRIVER_FILE > $CHAR_DEV; then + logger -t "$DAEMON_HOTPLUG" -p $DAEMON_ERR "Sending driver to $CHAR_DEV [ $THIS_USB_VIDPID ] failed for some reason." + else + logger -t "$DAEMON_HOTPLUG" -p $DAEMON_INFO "Sent $DRIVER_FILE to $CHAR_DEV [ $THIS_USB_VIDPID ]." + daemon_restart + fi + else + logger -t "$DAEMON_HOTPLUG" -p $DAEMON_ERR "Missing driver file: $DRIVER_FILE for $CHAR_DEV [ $THIS_USB_VIDPID ] (please upload it)." + fi +} case "$ACTION" in - add) + add) + # Set permissions on the /dev/usb/lpX char dev [ -n "${DEVNAME}" ] && [ "${DEVNAME##usb/lp*}" = "" ] && { chmod 660 /dev/"$DEVNAME" chgrp lp /dev/"$DEVNAME" } - ;; - remove) - # device is gone - ;; + + get_and_store_printer_info + + [ $DEBUG ] && echo THIS_USB_VIDPID: $THIS_USB_VIDPID + [ $DEBUG ] && echo CHAR_DEV: $CHAR_DEV + # usb subsys only: + # [ $DEBUG ] && echo DEVTYPE: $DEVTYPE + # [ $DEBUG ] && echo DEV_TYPE_FILTER: $DEV_TYPE_FILTER + # [ $DEBUG ] && echo PRODUCT: $PRODUCT + + # Extra checks available when run as hotplug usb script: + # [ "$DEVTYPE" == "${DEV_TYPE_FILTER}" ] + # [ -z "${PRODUCT##*$THIS_USB_VIDPID*}" ] + + # Ensure dev is character device + if [ -n "$THIS_USB_VIDPID" -a -c $CHAR_DEV ]; then + # if zero string, i.e. usb_ID is a match for $PRODUCT supplied by hotplug + if [ $(uci -q get $DAEMON.@$DAEMON[$UCI_DEV_CFG_NUMBER].usbvidpid) == "$THIS_USB_VIDPID" ]; then + [ $DEBUG ] && echo "THIS_USB_VIDPID match for $DAEMON device $THIS_USB_VIDPID." + + send_driver + + else + [ $DEBUG ] && echo "No THIS_USB_VIDPID match." + fi + fi + + ;; + remove) + # device is gone + ;; + # Special actions available to "usb" subsystem + # bind) + # # special action + # ;; + # unbind) + # # special action + # ;; esac + +# Commit any changes +[ -n $( uci -q changes $DAEMON ) ] && uci commit $DAEMON -- 2.30.2