Dans la rétrospective LinuxFr.org de la première quinzaine de mars 2019, il était question de la réponse surprenante de diff : « Le fichier /var/lib/lxc/alpha/rootfs/dev/full est un fichier spécial‐caractères alors que le fichier /var/lib/lxc/beta/rootfs/dev/full est un fichier spécial‐caractères. »
Les choses auraient pu en rester là. Mais quand même, ça reste une bonne occasion de se demander si c’est un bogue, une fonctionnalité ou un mauvais usage, non ? Jouons un peu avec ce cas pratique.
Sommaire
- De quels fichiers parlions‐nous ?
- Reproduire le problème
- Un problème de traduction ?
- Débogage
- Les sources
- Le code
- Et avec un autre diff ?
- Ouvrir des bogues ?
De quels fichiers parlions‐nous ?
$ ls -l /var/lib/lxc/alpha/rootfs/dev/full /var/lib/lxc/beta/rootfs/dev/full
crw-rw-rw- 1 root root 1, 7 mars 4 2011 /var/lib/lxc/alpha/rootfs/dev/full
crw-rw-rw- 1 root root 1, 7 oct. 21 2017 /var/lib/lxc/beta/rootfs/dev/full
Il s’agit donc de fichiers spéciaux en mode caractère (le c
au début de la ligne), ayant pour majeur 1 et pour mineur 7. Ce type de fichiers peut être créé avec la commande mknod
(paquet coreutils
chez Debian, logiciel tiré du projet GNU coreutils).
Et le binaire /usr/bin/diff
provenait du paquet Debian Stretch diffutils 1:3.5-3, également tiré du projet GNU diffutils.
$ diff --version
diff (GNU diffutils) 3.5
Reproduire le problème
D’abord créons un petit labo pour reproduire :
$ mkdir dir1 dir2
$ sudo mknod dir1/file c 1 7
$ sudo mknod dir2/file c 1 7
$ ls -l dir*/file
crw-r--r-- 1 root root 1, 7 mars 28 17:43 dir1/file
crw-r--r-- 1 root root 1, 7 mars 28 17:43 dir2/file
$ diff -r dir1 dir2
Le fichier dir1/file est un fichier spécial-caractères alors que le fichier dir2/file est un fichier spécial-caractères
OK, c’est reproduit, ça arrive (au moins) sur la comparaison récursive entre deux répertoires contenant chacun un fichier spécial en mode caractère avec même majeur, même mineur, même nom.
Un problème de traduction ?
Un effet de la langue ? Il s’agit peut‐être juste d’une erreur de traduction en français ?
$ export LC_ALL=C
$ export LANG=C
$ diff -r dir1 dir2
File dir1/file is a character special file while file dir2/file is a character special file
Pas mieux en anglais. Et d’ailleurs ce n’est pas mieux avec diffutils 1:3.7-2 de Debian Sid, en sachant que la 3.7 est la dernière version publiée par le projet GNU.
Avant de continuer à creuser, notons que la comparaison directe des deux fichiers est une mauvaise idée :
$ diff dir1/file dir2/file
(ne rend pas la main)
Débogage
Par curiosité un coup d’œil avec l’outil de débogage (sous GNU/Linux) strace (logiciel venant de strace.io) pour voir les appels système utilisés par un programme et les signaux reçus :
$ strace -f diff dir1/file dir2/file
…
stat("dir1/file", {st_mode=S_IFCHR|0644, st_rdev=makedev(0x1, 0x7), ...}) = 0
stat("dir2/file", {st_mode=S_IFCHR|0644, st_rdev=makedev(0x1, 0x7), ...}) = 0
openat(AT_FDCWD, "dir1/file", O_RDONLY) = 3
openat(AT_FDCWD, "dir2/file", O_RDONLY) = 4
read(3, "(que des NUL)"..., 4096) = 4096
read(4, "(que des NUL)"..., 4096) = 4096
read(3, "(que des NUL)"..., 4096) = 4096
read(4, "(que des NUL)"..., 4096) = 4096
…
Comme on peut lire en boucle sur ces fichiers, ça peut durer longtemps…
En revanche, on peut essayer sur la version récursive sur les deux répertoires :
$ strace -f diff -r dir1 dir2
…
stat("dir1/file", {st_mode=S_IFCHR|0644, st_rdev=makedev(0x1, 0x7), ...}) = 0
stat("dir2/file", {st_mode=S_IFCHR|0644, st_rdev=makedev(0x1, 0x7), ...}) = 0
fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(0x88, 0x3), ...}) = 0
write(1, "File dir1/file is a character sp"..., 92File dir1/file is a character special file while file dir2/file is a character special file
) = 92
…
OK, il n’a pas l’air de pousser la comparaison bien loin… On regarde les attributs des deux fichiers, ce sont des fichiers spéciaux, alors on sort la phrase peu explicative.
Les sources
Et si on regardait les sources du paquet Debian ?
$ apt source diffutils
$ cd diffutils-3.7/
La traduction ?
$ grep -1 "character special file" ./po/fr.po
#: lib/file-type.c:69
msgid "character special file"
msgstr "fichier spécial-caractères"
--
#: lib/file-type.c:84
msgid "multiplexed character special file"
msgstr "fichier spécial avec des caractères multiplexés"
Le premier cas est celui qui nous intéresse. On retrouve bien les lignes attendues côté code en excluant les fichiers de traduction :
$ grep -nr "character special file" .|grep -v "/po/"
./lib/file-type.c:69: return _("character special file");
./lib/file-type.c:84: return _("multiplexed character special file");
Il s’agit en gros d’une fonction file_type
qui écrit « character special file » ou sa traduction lorsqu’elle croise un fichier spécial de type caractère.
Reste à trouver d’où vient le reste de la phrase :
$ grep -2 while po/fr.po
#: src/diff.c:1337 src/diff.c:1387
#, c-format
msgid "File %s is a %s while file %s is a %s\n"
msgstr "Le fichier %s est un %s alors que le fichier %s est un %s\n"
Visiblement une phrase générique prévue pour dire « ah, flûte, dommage, le premier fichier est un machin, tandis que le second est un bidule », sauf qu’ici machin == bidule (ici on sourit légèrement parce qu’on va aller voir la ligne 1 337 de diff.c
, et que c’est l33t).
Le code
La portion de code concernée :
{
/* We have two files that are not to be compared. */
/* See POSIX 1003.1-2001 for this format. */
message5 ("File %s is a %s while file %s is a %s\n",
file_label[0] ? file_label[0] : cmp.file[0].name,
file_type (&cmp.file[0].stat),
file_label[1] ? file_label[1] : cmp.file[1].name,
file_type (&cmp.file[1].stat));
/* This is a difference. */
status = EXIT_FAILURE;
}
En gros, on est dans une zone concernant les fichiers non comparables, alors on affiche le message à base de machin et de bidule sans se poser la question du cas machin == bidule.
Le même code est utilisé ligne 1387, mais il semble plus pertinent, d’après le commentaire qui précise que l’un des fichiers est un lien symbolique et l’autre non, donc ici machin != bidule.
{
/* We have two files that are not to be compared, because
one of them is a symbolic link and the other one is not. */
message5 ("File %s is a %s while file %s is a %s\n",
file_label[0] ? file_label[0] : cmp.file[0].name,
file_type (&cmp.file[0].stat),
file_label[1] ? file_label[1] : cmp.file[1].name,
file_type (&cmp.file[1].stat));
/* This is a difference. */
status = EXIT_FAILURE;
}
Tout laisse à penser que nous sommes en présence d’un bogue, ou en tout cas d’une imprécision dans la réponse fournie à l’utilisateur : la réponse sera toujours la même lorsque l’on compare deux fichiers spéciaux‐caractères, indépendamment de leurs majeur et mineur d’ailleurs, et le fait qu’ils soient différents (le but de diff donc) est seulement sous‐entendu, par le fait que le message apparaît (il n’y aurait pas de tel message sur deux fichiers comparables identiques).
Et avec un autre diff ?
Le commentaire côté GNU « See POSIX 1003.1-2001 for this format » pourrait laisser penser qu’il s’agit d’une contrainte de la norme POSIX. Mais sous FreeBSD diff se comporte différemment :
$ mkdir t1 t2
$ mkfifo t1/toto
$ mkfifo t2/toto
$ diff -r t1 t2
File t1/toto is not a regular file or directory and was skipped
Le BSD diff bloque aussi sur la comparaison entre les deux fichiers. Mais le message est donc plus explicite lorsqu’il s’agit d’une comparaison de deux répertoires.
De la sorte, ce comportement est tel que défini par l’open Group :
« If both file1 and file2 are directories, diff shall not compare block special files, character special files, or FIFO special files to any files and shall not compare regular files to directories. Further details are as specified in Diff Directory Comparison Format. The behavior of diff on other file types is implementation‐defined when found in directories. »
Si file1 et file2 sont des répertoires, diff ne devrait pas entreprendre la comparaison des fichiers spéciaux de type blocs ou caractères ou les tubes nommés (FIFO) à n’importe quel autre fichier ni comparer un fichier à un répertoire.
Ouvrir des bogues ?
Passons à l’étape suivante, récupérer le code source directement du projet en amont et soumettre une proposition de correction ? Cette partie est laissée à l’attention du lecteur.
$ git clone https://git.savannah.gnu.org/git/diffutils.git
ou https://svnweb.freebsd.org/base/release/12.0.0/usr.bin/diff/.
# Super
Posté par barmic . Évalué à 10.
Super dépêche qui remontre à ceux pour qui ça n'est pas encore clair que lorsqu'ils utilisent des logiciels libres, ils peuvent aller voir comment ils fonctionnent et ainsi :
Bon, ça demande un peu de connaissance technique, sinon il faut se faire aider :)
[^] # Re: Super
Posté par Christophe B. (site web personnel) . Évalué à 9.
Je plussoie et dirais même plus :
L'open source est une incroyable matière première pour qui à la volonté de chercher un peu …
Pour le vivre au quotidien rien de plus frustrant que d'avoir un binaire merdique que l'on pourrait améliorer mais dont on ne dispose pas des sources et que l'éditeur ne veut pas transmettre.
[^] # Re: Super
Posté par aiolos . Évalué à 8.
J'ai une variante, tout aussi frustrante : une API qui crashe, qui te transmet sa stacktrace qui te montre le problème, mais que tu ne peux pas toucher puisque c'est pas toi qui a le service…
[^] # Re: Super
Posté par Christophe B. (site web personnel) . Évalué à 2.
Ouais pas mal …
Mais le binaire dont je te parle permet d'exporter des données de base Oracle ou SQL Server de manière neutre ce qui permet de transférer des données indépendemment de la base de données
Mais non seulement il est pas du tout optimisé, pour chaque table il prend un ligne, la transforme en ascii et l'écrit dans un fichier.
Au lieu de lieu n lignes ( n dépendant du nombre de lignes totales ) de les transformer en ascii et de les écrire dans un fichier
Mais en plus il génére beaucoup de données : ainsi un nombre codé sur 4 octets va se retrouver transformer en ascii du style " 123.56"
Ceci dit cela se compresse à quasiment 80 % mais il faut de l'espace disque difficile à recupérer après
Résultat entre un export base de données datapump de chez Oracle et cet utilitaire un rapport de 1 à 10 en terme de temps et de TRES TRES BEAUCOUP en terme de volume
Cet utilitaire n'a quasiment pas évolué depuis des années et est incontournable dans certain cas.
bref BEAUCOUP de temps perdu pour … pas grand chose
# Une petite étape en plus
Posté par Pol' uX (site web personnel) . Évalué à 5.
Pour la didactique et/ou l'exégèse apétiste :
L'utilitaire est diff. Son chemin est
$ which diff
/usr/bin/diff
Le paquet qui l'a installé est
$ dpkg -S /usr/bin/diff
diffutils: /usr/bin/diff
Adhérer à l'April, ça vous tente ?
[^] # Re: Une petite étape en plus
Posté par David Marec . Évalué à 3.
Et sous FreeBSD,
A noter que le traitement sur répertoires et celui sur fichiers sont bien séparés.
Le test
S_ISREG
sur stat(2) n'est effectué que dans le premier cas; aussidiff
bloque comme sous linux dans le deuxième.Ce qui m'a fait supposer que ce comportement est voulu par la norme. Mais je ne l'ai pas trouvé énoncé de manière claire.
[^] # Re: Une petite étape en plus
Posté par Sibian . Évalué à 2. Dernière modification le 02 avril 2019 à 11:59.
Une étape en moins :
dpkg -S $(which diff)
diffutils: /usr/bin/diff
;op
[^] # Re: Une petite étape en plus
Posté par Anonyme . Évalué à 2.
Un fork en moins :
dpkg -S diff
[^] # Re: Une petite étape en plus
Posté par vv222 . Évalué à 4.
Ici (Debian Sid) c'est difficilement exploitable :
Au contraire de la commande donnée plus haut :
Même si j'aurais plutôt tendance à éviter l'appel à
which
en passant par un built-in :[^] # Re: Une petite étape en plus
Posté par barmic . Évalué à 3.
Faut arrêter d'utiliser bash… Zsh c'est très bien :
[^] # Re: Une petite étape en plus
Posté par Pol' uX (site web personnel) . Évalué à 6.
Oui mais va t'en expliquer le fonctionnement des constructeurs jquery dans un shell, maintenant … :)
Adhérer à l'April, ça vous tente ?
# Si personne ne se lance ...
Posté par Pol' uX (site web personnel) . Évalué à 4.
Surtout pas malheureux ! Ça risque de casser des zillions de workflows qui comptent sur cette fonctionnalité.
https://xkcd.com/1172/
Adhérer à l'April, ça vous tente ?
# Fichier spécial caractère ?
Posté par Ytterbium . Évalué à 2.
Merci pour ce très bon article.
Par contre je n'ai pas compris ce que c'est qu'un fichier spécial caractère, je n'ai jamais croisé ce truc avant. De ce que Internet m'apprend rapidement, j'ai compris que les fichiers spéciaux sont utilisés pour la gestion de périphériques, et qu'il est de bloc ou caractère selon le périphérique.
Mais si quelqu'un pouvait m'expliquer à quoi ça sert exactement, pourquoi diff n'arrive pas à les comparer et ce que sont les numéros majeurs et mineurs, je lui serait très reconnaissant.
[^] # Re: Fichier spécial caractère ?
Posté par Benoît Sibaud (site web personnel) . Évalué à 5.
Une doc relevant de l'archéologie informatique (MandrakeLinux 10.0 quoi…) : http://wawadeb.crdp.ac-caen.fr/iso/tmp/ressources/linux/doc.mandrakelinux.com/MandrakeLinux/100/fr/Command-Line.html/ext2fs-special.html
http://manpages.ubuntu.com/manpages/precise/fr/man1/mknod.1.html (le man de mknod donc) fournit aussi quelques infos du même genre.
http://www.linuxfr-france.org.invalid/article/grl/Guide_Rootard-12.html (Version 2.8, 21 Septembre 1998)
[^] # Re: Fichier spécial caractère ?
Posté par SChauveau . Évalué à 5.
Pour faire simple, il s'agit de fichiers virtuels permettant de communiquer avec certains modules du noyau Linux (en général associé à un périphérique). Par exemple, les évènements du clavier sont visible sous la forme d'un 'fichier' tel que /dev/input/event0.
Du point du vue du noyau Linux, le nom du fichier n'a pas vraiment d'importance. La seule chose importante est son type (char ou block) et ses numéros major et minor.
La liste complète se trouve dans
https://www.kernel.org/doc/Documentation/admin-guide/devices.txt
Dans le temps il fallait parfois créer manuellement les entrées dans /dev/ avec mknod quand on ajoutait un périphérique mais sur les systèmes actuels tout se fait automatiquement via udev.
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.