Configurar scanner de impresora multifunción

2020/09/16

TL;DR:

$ sudo sh -c 'cat > /etc/udev/rules.d/99-libsane-panasonic.rules' <<'END'
ACTION=="add", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="04da", ATTRS{idProduct}=="0f0b", ENV{libsane_matched}="yes", RUN="/usr/local/bin/hack-panasonic $devnode"
END
$ sudo sh -c 'cat > /usr/local/bin/hack-panasonic' <<'END'
#!/bin/bash
[ $# -ne 1 -o ! -c "$1" ] && exit 1
exec /usr/bin/setfacl -m g:users:rw "$1"
END
$ sudo chmod +x /usr/local/bin/hack-panasonic
$ sudo reboot

Se busca escanear desde una impresora multifunción Panasonic KX-MB1520AG bajo Ubuntu 18.04. Para ver si la impresora es reconocida por sane se prueba utilizando el comando scanimage, verificando previamente que esté conectada al sistema (mediante lsusb):

$ lsusb | grep -i panasonic
Bus 001 Device 004: ID 04da:0f0b Panasonic (Matsushita)

Con esto se confirma que la impresora se encuentra conectada al sistema.

$ scanimage -L

el cual no devuelve ninguna impresora. Utilizando por otro lado sudo con el comando anterior se obtiene:

$ sudo scanimage -L
device `panamfs:libusb:001:004' is a Panasonic KX-MB1520AG sheetfed scanner

Lo que indica un problema de permisos que se corrobora comprobando los permisos del archivo /dev/bus/usb/001/004 (obtenidos de la salida de lsusb):

$ ls -lh /dev/bus/usb/001/004
crw-rw-r-- 1 root lp 189, 3 Sep 14 12:38 /dev/bus/usb/001/004
$ getfacl /dev/bus/usb/001/004
getfacl: Removing leading '/' from absolute path names
# file: dev/bus/usb/001/004
# owner: root
# group: lp
user::rw-
group::rw-
other::r--

Para solucionar el problema por lo menos hay dos soluciones:

  1. Agregar el grupo lp al usuario (o los usuarios) del PC:

    $ sudo addgroup jmpc lp
    $ newgrp lp
    

    La desventaja de esta solución es que solo aplica para los usuarios locales. Si la información de los usuarios es obtenida mediante ldap este cambio no es posible.

  2. Utilizar udev y setfacl para dar permisos cuando se conecte el dispositivo.

Obviamente iremos por la opción 2., la cual es más general.

Para dar permisos al archivo de dispositivo (device file), se creará una regla de udev en un archivo de reglas bajo /etc/udev/rules.d que corresponde a las reglas locales. Sobre el orden de procesamiento de la regla, se utilizará el prefijo 99- para que la regla sea procesada al final (esta elección está basada en varios ejemplos vistos en internet). Con esto, el archivo que se creará para incluir la regla de udev será /etc/udev/rules.d/99-libsane-panasonic.rules.

La regla a utilizar, basada en los archivos de /lib/udev/rules.d, será inicialmente de la forma:

ATTRS{idVendor}=="04da", ATTRS{idProduct}=="0f0b", ENV{libsane_matched}="yes", RUN+="/usr/local/bin/hack-panasonic"

la cual al ser detectado el equipo panasonic:

El programa /usr/local/bin/hack-panasonic inicialmente tendrá el siguiente contenido:

#!/bin/bash
set -e
echo "$(date -uIns) -- $@" >> /tmp/hack-panasonic

y su función será indicar mediante el archivo /tmp/hack-panasonic que la regla es ejecutada. Luego se modificará el programa para dar permisos al archivo de dispositivo, pero para ello se requiere conocer la ruta del archivo, la que puede variar con cada conexión. Por ello, se deben utilizar las sustituciones de variables que se indican en la documentación de udev, las cuales son pasadas al script por linea de comandos. El utilizar la variable de shell $@ ayudará a determinar de forma exacta como es realizada la sustitución de variables realizadas por udev en la clave RUN.

Para crear los archivos anteriores se puede utilizar el script setup-hack-panasonic-v1.sh.

Luego de creados los archivos se deben recargar las reglas de udev y verificar su ejecución.

Para recargar las reglas se debe invocar el comando udevadm de la forma udevadm control --reload-rules.

Para verificar la ejecución de la regla hay dos formas:

  1. Desconectando y volviendo a conectar el cable usb de la impresora. Si bien esto es “sencillo”, en la práctica mientras se da con la regla y los parámetros a utilizar hay que realizarlo muchas veces. Otro punto en contra es que no puede realizarse de forma sencilla si se está brindando asistencia de forma remota.

  2. Relanzando los eventos generados al conectar el cable usb utilizando el comando udevadm con la opción trigger.

    Hay que tener cuidado al utilizar la opción trigger, ya que esta por defecto no simula la conexión del usb.

    Para simular la conexión del usb debe utilizarse el comando:

    $ sudo udevadm trigger --action=add --subsystem-match=usb
    

En este punto, ya se está en condiciones de probar si todo anda bien:

$ sudo udevadm control --reload-rules
$ sudo udevadm trigger --action=add --subsystem-match=usb
$ cat /tmp/hack-panasonic
2020-09-16T18:25:14,945230203+00:00 --
2020-09-16T18:25:14,952413377+00:00 --
2020-09-16T18:25:14,952848018+00:00 --

Con lo que se ve que casi está todo funcionando correctamente:

Esto último no es deseado, ya que el evento de conexión debería ser uno solo.

Estudiando el archivo /lib/udev/rules.d/60-libsane1.rules queda de manifiesto que falta agregar a la regla que la acción sea add y que se esté agregando un dispositivo usb, lo cual se escribe como:

ACTION=="add", ENV{DEVTYPE}=="usb_device"

quedando la regla de la forma:

ACTION=="add", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="04da", ATTRS{idProduct}=="0f0b", ENV{libsane_matched}="yes", RUN="/usr/local/bin/hack-panasonic"

Actualizando el archivo /etc/udev/rules.d/99-libsane-panasonic.rules con la regla anterior, recargando las reglas y lanzando los eventos se tiene:

$ sudo rm /tmp/hack-panasonic
$ sudo udevadm control --reload-rules
$ sudo udevadm trigger --action=add --subsystem-match=usb
$ cat /tmp/hack-panasonic
2020-09-16T18:34:42,097239894+00:00 --

Y ahora si estamos seguros de que la regla captura correctamente el evento que estamos buscando.

Ahora hay que determinar los parámetros que se le deben pasar al script hack-panasonic para que este pueda dar permisos al archivo de dispositivo. El archivo que se debe modificar (en este caso) es /dev/bus/usb/001/004, por lo que se debe encontrar que variable de udev tiene este valor o en su defecto permite generarlo.

Revisando la documentación de udev se encuentran como disponibles las sustituciones: $devpath, $number, $id, $name, $devnode, entre otras.

Para no perder tiempo probando cada una se modifica la regla en el archivo /etc/udev/rules.d/99-libsane-panasonic.rules para agregarlas todas a la vez y ver que valores se obtienen:

ACTION=="add", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="04da", ATTRS{idProduct}=="0f0b", ENV{libsane_matched}="yes", RUN="/usr/local/bin/hack-panasonic - $devpath - $number - $id - $name - $devnode -"

Luego de modificada la regla se recarga y se prueba:

$ sudo rm /tmp/hack-panasonic
$ sudo udevadm control --reload-rules
$ sudo udevadm trigger --action=add --subsystem-match=usb
$ cat /tmp/hack-panasonic
2020-09-16T18:45:03,627583256+00:00 -- - /devices/pci0000:00/0000:00:14.0/usb1/1-13 - 13 - 1-13 - bus/usb/001/004 - /dev/bus/usb/001/004 -

Del resultado anterior se encuentra que la sustitución buscada es $devnode.

Con esta información ya es posible escribir la versión final del la regla y el script:

Para crear los archivos anteriores se puede utilizar el script setup-hack-panasonic-v2.sh.

Para probar que todo funciona correctamente luego de los cambios anteriores se recargan las reglas, se generan los eventos y se prueban los archivos bajo /dev y la salida de scanimage:

$ sudo udevadm control --reload-rules
$ sudo udevadm trigger --action=add --subsystem-match=usb
$ ls -lh /dev/bus/usb/001/004
crw-rw-r--+ 1 root lp 189, 3 set 16 15:56 /dev/bus/usb/001/004
$ getfacl /dev/bus/usb/001/004
getfacl: Removing leading '/' from absolute path names
# file: dev/bus/usb/001/004
# owner: root
# group: lp
user::rw-
group::rw-
group:users:rw-
mask::rw-
other::r--
$ scanimage -L
device `panamfs:libusb:001:004' is a Panasonic KX-MB1520AG sheetfed scanner

Sobre el uso de $attr y $env

Para determinar las sustituciones de $attr y $env disponibles se utiliza el comando udevadm info con los parámetros -q y -a respectivamente:

$ lsusb | grep -i panasonic
Bus 001 Device 005: ID 04da:0f0b Panasonic (Matsushita)
$ udevadm info -a /dev/bus/usb/001/005

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.

  looking at device '/devices/pci0000:00/0000:00:14.0/usb1/1-13':
    KERNEL=="1-13"
    SUBSYSTEM=="usb"
    DRIVER=="usb"
    ATTR{authorized}=="1"
    ATTR{avoid_reset_quirk}=="0"
    ATTR{bConfigurationValue}=="1"
...
$ udevadm info -q property /dev/bus/usb/001/005
BUSNUM=001
DEVNAME=/dev/bus/usb/001/005
DEVNUM=005
DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-13
DEVTYPE=usb_device
...

Nota: para el ejemplo anterior cambió el archivo de dispositivo que se venía utilizando como ejemplo previamente.