samedi 13 juin 2015

redirections revisitées

Pourquoi revisitées?
Parce que nous avons déjà traité du sujet dans ce billet et dans celui-ci.
Et aussi parce que l'approche sera cette fois quelque peu différente.
Dans un répertoire test, nous lançons les commandes suivantes touch et ls:

toto@aldebaran:~/test$ touch titi
toto@aldebaran:~/test$ ls titi tata
ls: impossible d'accéder à tata: Aucun fichier ou dossier de ce type
titi

La commande ls nous fournit 2 lignes en sortie:

  • un message qui nous informe que le fichier tata n'existe pas
  • le nom du fichier titi qui vient d'être créé par touch

Le message est venu via le canal de sortie des erreurs (stderr), titi (nom du fichier) via la sortie standard (stdout).
Stdout est géré par le système via le descripteur de fichier #1, tandis que stderr est lié au descripteur de fichier #2
Le descripteur de fichier #0 correspond quant à lui à l'entrée standard (stdin).
Ces fichiers se trouvent dans le répertoire /proc/pid/fd où pid est l'identifiant du processus concerné:


(Ici l'identifiant du processus ls est 2609).

Ce répertoire n'existe que pendant la durée de vie du processus.
Les fichiers /proc/self/fd sont hérités de ceux existant dans le shell d'où est lancé le processus

Examinons le résultat suivant:

toto@aldebaran:~/test$ ls titi tata 2>&1 1>/dev/null
ls: impossible d'accéder à tata: Aucun fichier ou dossier de ce type
toto@aldebaran:~/test$ 

2>&1 : le descripteur 2 prend toutes les caractéristiques du descripteur 1. Les messages d'erreur sont dirigés suivant le canal correspondant au descripteur 1 (canal 1).
1>/dev/null : le descripteur 1 pointe vers /dev/null. La sortie standard est envoyée vers le néant. Cela ne modifie en rien les nouvelles caractéristiques acquises juste avant par le descripteur 2.

Remarquons que la première redirection ne change rien: les messages d'erreur seraient de toute façon arrivés à l'écran.
Toutes ces redirections sont éphémères: elles s'effectuent au niveau des descripteurs qui se trouvent dans /proc/self/fd.
Il est possible de définir des redirections au niveau du shell comme ici:

toto@aldebaran:~/test$ echo $$
2959
toto@aldebaran:~/test$ exec 3>&1 1>sortie
toto@aldebaran:~/test$ ls titi
toto@aldebaran:~/test$ echo
toto@aldebaran:~/test$ ls -l /proc/2959/fd/[0-3]

Avec la commande echo $$ nous cherchons à connaître l'identifiant du processus (pid) correspondant au shell dans lequel nous sommes.
La commande suivante crée un descripteur 3 qui reprend toutes les caractéristiques du descripteur 1 (ce descripteur 3 constitue en quelques sorte une sauvegarde du descripteur 1), puis nous faisons pointer ce descripteur 1 vers le fichier sortie.
Dorénavant, seuls les messages d'erreur arriveront à l'écran: les deux commandes ls suivantes n'affichent aucun résultat puisque les descripteurs au niveau des processus fils (processus des commandes) sont hérités de ceux qui existent au niveau du shell. Dans le cas de deux commandes reliées par un tube (pipe), cette dernière affirmation n'est pas tout a fait exacte.
Vérifions:

toto@aldebaran:~/test$ echo
toto@aldebaran:~/test$ ls -l /proc/self/fd/[0-3] | cat
toto@aldebaran:~/test$ echo
toto@aldebaran:~/test$ ls titi | ls -l /proc/self/fd/[0-3]

La première commande ls fournira les descripteurs du processus en amont du tube.
La commande en aval, cat, recopie simplement l'entée standard vers la sortie standard.
La troisième commande ls nous informera sur les descripteurs au niveau du processus en aval (la commande ls titi n'enverra rien dans le fichier sortie, ni à l'écran, mais il faut une commande en amont).
Les commandes echo ont simplement pour but de rendre le fichier sortie plus lisible.
Examinons maintenant le contenu du fichier sortie à l'aide de la commande cat, en n'oubliant pas que le descripteur 3 est une sauvegarde du descripteur 1:

toto@aldebaran:~/test$ cat sortie >&3
titi

lrwx------ 1 toto toto 64 jun 12 15:19 /proc/2959/fd/0 -> /dev/pts/1

l-wx------ 1 toto toto 64 jun 12 15:19 /proc/2959/fd/1 -> /home/toto/test/sortie
lrwx------ 1 toto toto 64 jun 12 15:19 /proc/2959/fd/2 -> /dev/pts/1
lrwx------ 1 toto toto 64 jun 12 15:19 /proc/2959/fd/3 -> /dev/pts/1

lrwx------ 1 toto toto 64 jun 12 15:20 /proc/self/fd/0 -> /dev/pts/1

l-wx------ 1 toto toto 64 jun 12 15:20 /proc/self/fd/1 -> pipe:[36369]
lrwx------ 1 toto toto 64 jun 12 15:20 /proc/self/fd/2 -> /dev/pts/1
lrwx------ 1 toto toto 64 jun 12 15:20 /proc/self/fd/3 -> /dev/pts/1

lr-x------ 1 toto toto 64 jun 12 15:21 /proc/self/fd/0 -> pipe:[36372]

l-wx------ 1 toto toto 64 jun 12 15:21 /proc/self/fd/1 -> /home/toto/test/sortie
lrwx------ 1 toto toto 64 jun 12 15:21 /proc/self/fd/2 -> /dev/pts/1
lrwx------ 1 toto toto 64 jun 12 15:21 /proc/self/fd/3 -> /dev/pts/1
toto@aldebaran:~/test$ 

Dans le processus en amont, fd/1 pointe vers le tube et n'a donc aucun rapport avec fd/1 au niveau du shell (qui pointe vers sortie).
Dans le processus en aval, l'entrée standard est alimentée par la sortie du tube: donc la sortie standard du processus en amont est connectée via le tube (pipe) à l'entrée standard du processus en aval.
Les deux tubes (pipe) portent des numéros différents car ils concernent deux lignes de commandes différentes.
Essayons d'écrire une ligne de commande qui affichent les descripteurs en amont et en aval d'un tube.
Tout d'abord nous réinitialisons le descripteur 1 à partir de sa sauvegarde puis nous clôturons le descripteur 3, tout cela avant de procéder:


Ça ne fonctionne pas: la commande en amont ne fournit aucun résultat car celui-ci est réceptionné à droite du tube via l'entrée standard qui n'est pas utilisée par la commande ls en aval.
Nous allons rediriger le résultat de la commande en amont vers stderr de cette commande pour éviter le tube. Mais par là même du fait de vouloir visualiser un résultat, nous allons le modifier. Cela rappelle un peu la théorie de la mécanique quantique qui dit que l'observation d'un phénomène modifie ce phénomène. Aussi nous allons avant tout sauvegarder l'état initial du descripteur 1 amont dans un descripteur 3. Cette fois tout est OK:


Revenons sur la commande ls titi tata 2>&1 1>/dev/null que nous avons considérée au début. Si cette commande se trouve en amont d'un tube, la redirection 2>&1 (qui ne servait à rien) trouve cette fois toute son utilité: les messages d'erreur sont envoyés à l'écran via le tube ce qui n'est pas le cas autrement:


toto@aldebaran:~/test$ ls titi tata 2>&1 1>/dev/null | tr t b
ls: impossible d'accéder à baba: Aucun fichier ou dossier de ce bype
toto@aldebaran:~/test$ ls titi tata 1>/dev/null | tr t b
ls: impossible d'accéder à tata: Aucun fichier ou dossier de ce type
toto@aldebaran:~/test$ 

On voudrait maintenant que la sortie standard arrive également sur l'écran mais sans transiter par le tube (pipe).
Ceci réalise notre souhait:

toto@aldebaran:~/test$ ls titi tata 3>&1 1>&2 2>&3 | tr t b
titi
ls: impossible d'accéder à baba: Aucun fichier ou dossier de ce bype
toto@aldebaran:~/test$
(c'est bien titi qui est affiché et non bibi)

Nous créons tout d'abord le descripteur 3 en tant que copie du descripteur 1. Ensuite le descripteur 1 acquiert toutes les caractéristiques du descripteur 2, la sortie standard est donc envoyée sur le canal 2. Le canal 1 est perdu, heureusement nous en avons une copie  (le canal 3 qui conduit au tube) vers lequel nous envoyons la sortie des erreurs.

Si par contre le descripteur 3 est créé au niveau du shell, il lui correspond un canal qui arrive sur l'écran (puisque fd/3 est alors une copie de fd/1 au niveau du shell).
Au niveau de la commande, nous pouvons utiliser les mêmes redirections qu'avant, mais il est plus simple de procéder comme ceci:

toto@aldebaran:~/test$ ls titi tata 2>&1 1>&3 | tr t b
ls: impossible d'accéder à baba: Aucun fichier ou dossier de ce bype
titi
toto@aldebaran:~/test$ 

D'autres détails concernant notamment stdin, peuvent être trouvés dans ce billet.

mardi 2 juin 2015

souris qui s'endort

Quoi de plus énervant que d'avoir une souris qui s'endort tout le temps: après quelques secondes d'inactivité, il faut chaque fois la réveiller avec un clic.
Essayons de trouver la source du problème.
Utilisons tout d'abord lsusb:

[toto@rigel ~]$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 002: ID 0461:4d64 Primax Electronics, Ltd 
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
[toto@rigel ~]$ 

La deuxième ligne correspond à notre souris.

Cherchons des infos sur le descripteur de périphérique /dev/bus/usb/002/002

[toto@rigel ~]$ udevadm info /dev/bus/usb/002/002 
P: /devices/pci0000:00/0000:00:02.0/usb2/2-3
N: bus/usb/002/002
E: BUSNUM=002
E: DEVNAME=/dev/bus/usb/002/002
E: DEVNUM=002
E: DEVPATH=/devices/pci0000:00/0000:00:02.0/usb2/2-3
E: DEVTYPE=usb_device
E: DRIVER=usb
E: ID_BUS=usb
E: ID_MODEL=USB_Optical_Mouse
E: ID_MODEL_ENC=USB\x20Optical\x20Mouse
E: ID_MODEL_ID=4d64
E: ID_REVISION=0200
E: ID_SERIAL=0461_USB_Optical_Mouse
E: ID_USB_INTERFACES=:030102:
E: ID_VENDOR=0461
E: ID_VENDOR_ENC=0461
E: ID_VENDOR_FROM_DATABASE=Primax Electronics, Ltd
E: ID_VENDOR_ID=0461
E: MAJOR=189
E: MINOR=129
E: PRODUCT=461/4d64/200
E: SUBSYSTEM=usb
E: TYPE=0/0/0
E: USEC_INITIALIZED=201152
[toto@rigel ~]$ 

(En gras ce que nous allons utiliser pour la suite)

Vérifions le contenu du fichier power/control adéquat:

[toto@rigel ~]$ cat /sys/bus/usb/devices/2-3/power/control
auto
[toto@rigel ~]$ 

Sur cette machine, plusieurs distributions sont installées. Cette fois nous sommes dans Manjaro. Pour d'autres distributions où la souris fonctionne correctement, le même fichier contient on.
De ce fait nous essayons

[root@rigel ~]# echo "on" > /sys/bus/usb/devices/2-3/power/control
[root@rigel ~]# 

qui résout instantanément le problème, mais jusqu'au prochain boot seulement

Pour une solution définitive ajoutons la règle

ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="0461", ATTR{idProduct}=="4d64", TEST=="power/control", ATTR{power/control}="on"

nommée usb-power.rules dans /etc/udev/rules.d, puis nous redémarrons le système.

Caramba, ça ne fonctionne pas!

Vérifions ce qu'il en est en lançant la commande:

[root@rigel ~]# udevadm test /bus/usb/devices/2-3  2>&1 | grep writing

Nous redirigeons le canal 2 vers le canal 1 afin que tout soit filtré par grep (plus d'infos sur les redirections dans ce billet), car la plus grande partie du flux de sortie de la commande udevadm transite par le canal 2 (qui n'est pas filtré par grep).

Le résultat en image:


Donc power/control est effectivement mis sur on et la souris fonctionne aussitôt la commande exécutée (bien qu'il s'agisse d'un test).
La règle ajoutée joue bien son rôle. Le mystère reste entier.

Cependant:

[root@rigel ~]# udevadm test /bus/usb/devices/2-3  2>/dev/null | tail -1
run: 'lmt-udev force modules=runtime-pm devices=2-3'
[root@rigel ~]# 

Le responsable ne serait-il pas lmt-udev qui s'exécute en dernier, après application de toutes les règles?
Si lmt-udev est le responsable, pourquoi cette fois la souris ne s'endort elle plus?
Mais sans doute parce-qu'il s'agit d'un test et que lmt-udev n'est pas exécuté.
D'ailleurs nous sommes prévenu:

[root@rigel ~]# udevadm test /sys/bus/usb/devices/2-3 2>/dev/null | head -3
This program is for debugging only, it does not run any program
specified by a RUN key. It may show incorrect results, because
some values may be different, or not available at a simulation run.

D'autre part:

[toto@rigel ~]$ udevadm test /sys/bus/usb/devices/2-3  2>&1 1>/dev/null | grep 'RUN'
RUN 'lmt-udev force modules=runtime-pm devices=%k' /usr/lib/udev/rules.d/99-laptop-mode.rules:3
toto@rigel ~]$ awk 'NR==3' /usr/lib/udev/rules.d/99-laptop-mode.rules
ACTION=="add|remove", SUBSYSTEM=="usb", RUN+="lmt-udev force modules=runtime-pm devices=%k"

/usr/lib/udev/lmt-udev est bien un programme spécifié par une clef RUN (règle 3 de 99-laptop-mode.rules). C'est un script qui appelle /usr/bin/laptop_mode.
Après quelques recherches nous trouvons le fichier /etc/laptop-mode/conf.d/runtime-pm.conf dans lequel nous pouvons mettre notre souris sur une blacklist:

# The list of Device IDs that should not use autosuspend. Use system commands or
# look into sysfs to find out the IDs of your devices.
# Example: AUTOSUSPEND_DEVID_BLACKLIST="046d:c025 0123:abcd"
AUTOSUSPEND_RUNTIME_DEVID_BLACKLIST="0461:4d64"

Dans le même fichier on pourrait à la place blacklister le pilote usb:

# The list of device driver types that should not use autosuspend.  The driver
# type is given by "DRIVER=..." in a device's uevent file.
# Example: AUTOSUSPEND_DEVID_BLACKLIST="usbhid usb-storage"
AUTOSUSPEND_RUNTIME_DEVTYPE_BLACKLIST="usb"


Le problème est résolu!