Greboca  

DLFP - Dépêches  -  Bogue, fonctionnalité, mauvais usage ? Un cas pratique

 -  31 mars - 

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 ?

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

Commentaires : voir le flux atom ouvrir dans le navigateur

par Benoît Sibaud, Davy Defaud, David Marec, ZeroHeure, tankey

DLFP - Dépêches

LinuxFr.org

Sortie de GNU Compiler Collection 9.1

 -  8 mai - 

La nouvelle version de la collection de compilateurs GNU est sortie le 3 mai 2019. Plus qu’à son habitude, elle apporte de très nombreuses (...)


Pijul, contrôle de version et théorie des patchs, version 0.12

 -  29 avril - 

Pijul est un système de contrôle de version distribué (DVCS) développé en Rust et publié sous licence GPL v2. Il est basé sur une théorie des patches. (...)


De Sozi 12 à Sozi 19

 -  29 avril - 

Sozi est une alternative à Prezi, un logiciel de création de présentations animées. En lisant un journal récent à propos d’un éditeur de SVG (...)


Jeu de stratégie temps réel et de construction/gestion Unknown Horizons 2019.1

 -  28 avril - 

Le jeu libre Unknown Horizons mélange la stratégie en temps réel et la construction/gestion de villes (solo ou multi‐joueur, Windows et GNU/Linux). (...)


Publication du SILL 2019

 -  26 avril - 

Le SILL, socle interministériel de logiciels libres 2019 vient de sortir. C’est le référentiel des logiciels libres recommandés par l’État français. (...)