Sommaire
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/.