Il faut parfois partir du fait bien connu que Linux sait tout faire pour ne pas désespérer. J'espère que le titre est assez explicite pour google, car il n'y pour l'instant aucune bonne réponse sur google. Et à vrai dire, il n'y a pas d'aide du tout, même sur ce qui sert justement à résoudre le problème. Quel est-il, d'ailleurs ? Eh bien lors de la remigration de p'tite soeur vers les forces du bien, et sauvant in extremis les données (avant application de la fameuse méthode windaubienne par défaut : réinstallation de XP -- et Toshiba, ils ne mettent qu'une seule partition, donc le neuneu perd ses données sans même savoir ce qui lui arrive), avec une Mandriva One qui traînait dans mon sac (déjà une chance que je passais par là, une autre chance que j'avais piqué un live CD au dernier picnic libriste), en copiant directement le dossier des utilisateurs de la partoche NTFS (reconnue et montée automatiquement pas la Mandriva, notera-t-on) sur un disque dur externe en FAT32 (une autre mauvaise idée : ce format obsolète est le plus simple moyen au windaubien moyen de se faire couilloner dans les largeurs).

Et là, ce qui se passe, c'est que les accents sont paumés : le NTFS est en UTF-8, et le FAT32 en autre chose ; enfin, je crois, toujours est-il qu'à un moment où à un autre, quand vous regardez le dossier que vous avez copié, tous les accents sont morts. Et le problème est assez courant. Nous allons donc mener l'enquête et régler le problème (spoiler : en fait je suis parti dans un grand délire faute de doc correcte, donc si vous voulez vraiment avoir la solution, et que gogole vous a amené ici, allez directement à la fin du billet, c'est en gras).

Déjà, il faut chercher un peu sur le net d'où vient le problème : c'est une question de codage différent, comme je disais précédemment, et on apprend que sur FAT32 on parle le windows-1258. D'ailleurs, sous DOS, on parle autre chose : eh oui, encore une fois, m$ n'est pas foutu de rester compatible avec lui-même. Mais niveau solutions proposées ici et là, c'est mortel : on a droit à du renommage à la main (super !) ou à de l'astucieux iconv : un ls pipé dans "iconv -f windows-1258 -t
utf-8", et le tour est joué, y'a plus qu'à faire un script et rendre le truc récursif : au secours !! (essentiellement parce que la solution va entraîner une surcharge pas possible, outre le script chiant comme la mort à écrire).

On se dit donc qu'un montage intelligent du volume pourrait régler la situation : man mount, et voilà qu'on apprend que

iocharset=valeur
   Jeu de caractère pour les conversion entre les caractères 8 bits et  les  caractères  16  bitS  Unicode.  Par  défaut  c'est iso8859-1.  Les noms de fichiers longs sont stockés sur le disque en format Unicode.

Et ça tombe bien, parce qu'on est dans les options pour le montage de volumes FAT. Et on se dit que iso8859-1, c'est pas pareil que windows-1258, ah ça non. Du coup, quand on fait un ls sur le répertoire, on tombe par exemple sur ce genre de choses pas belles :

test�

Bouh pas beau. Alors, on va regarder de quoi il s'agit exactement, ce truc-là :

# ls test/test� | hexdump -C
00000000  74 65 73 74 2f 74 65 73  74 e9 0a                 |test/test..|
0000000b

Plusieurs remarques : la complétion marche, et si c'est mal affiché, c'est que l'encoding du terminal n'est pas bon ; ceci dit, j'ai essayé de le changer, ça ne m'a pas affiché mes accents pour autant. Deuxième remarque : on n'est pas ici sur le disque dur de ma p'tite soeur mais sur une maquette de test que j'ai faite à la main, déjà parce que c'est mieux pour bidouiller sans altérer des données, ensuite parce que j'étais déjà retourner à Paris lors de mon enquête, mais j'ai noté l'essentiel, à savoir qu'un accent "é" se note 0xE9. Et ça tombe bien, c'est justement ce que nous dit windows1258 (en cherchant ce lien, j'apprends que c'est le codage préféré pour le vietnamien, gné ?). En tout cas, c'est fortement incompatible avec l'ASCII, mais on s'en doutait, m$ adoooore faire des trucs incompatible avec tout ce qui existe déjà.

Parlons rapidement de cette maquette de test :

# cd /tmp
# dd if=/dev/zero of=testfat bs=1M count=1
# mkfs.vfat testfat
# mkdir testdir
# mount -o loop testfat testdir
# cd testdir; touch teste; umount testdir

Nous voilà donc avec un fichier "testfat" contenant une partition FAT32, où se trouve un fichier unique "teste" : on ouvre avec un éditeur hexadécimal (et faute de trouver que khexedit avait changé son nom en okteta, j'ai utilisé ht en curses), on se place à hauteur du second "e" de "teste", et on replace par un 0xE9. On sauve, et c'est prêt : si l'on remonte le volume FAT, on voit un "?" à la place de l'accent, chouette non ? Notons que nous devons monter en loop device, puisqu'il s'agit d'un fichier (pour ceux qui viendrait de BSD : c'est pareil que le vnconfig, mais là ça marche, et c'est pas limité à quatre...), mais qui faut tout de même être root pour faire ça (ce que je trouve être un peu trop, des fois les restrictions peuvent être trop de choses -- pour ceux qui voudraient bidouiller du ext sans avoir à monter un fichier de la sorte, j'indique l'utilitaire debugfs).

Revenons-en à notre iocharset. On essaie de monter avec windows1258 :

# mount -o loop,iocharset=windows1258 testfat test
mount: wrong fs type, bad option, bad superblock on /dev/loop1,
       missing codepage or helper program, or other error
       Dans quelques cas certaines informations sont utiles dans syslog - essayez
       dmesg | tail  ou quelque chose du genre

"C'est le drame" (avec ou sans tiret). Manifestement iocharset admet des trucs en iso*, mais pas en windows*. La question est donc de savoir : que peut-il accepter, et y aurait-il un codage compatible avec notre cochonerie made in m$ ? Je vous le donne dans le mille : y'a pas de doc, pas de liste, que dalle. C'est là où il n'y a plus qu'une solution : explorer le code du noyau.

Car on s'en doute bien, c'est au niveau du noyau que ça se passe. Je vous fait grâce du cours sur les systèmes de fichier : faut bien que je puisse me faire de l'argent de poche dans les écoles d'ingé de la capitale :D  (mais c'est rudement bien foutu -- assez classique en fait comme montage pour les UNIX, mais là, comme je disais, ça marche). On va directement dans les dossier de gestion du FAT. En fait, il y a : vfat, msdos, et une partie commune fat (tout court). C'est là qu'on fait un pari : celui que l'appel ioctl va filer l'option avec le même nom de "iocharset" au kernel, parce que le pifomètre nous dit, après apprentissage, que le monde Linux est souvent très cohérent avec lui-même. Au pire il faudra mater le code de mount, mais je vous laissez deviner : bingo !  :)

À partir de là, il faut remonter où mène cette option iocharset ; en fait, pas bien loin. Et là ça devient très intéressant. On remarque qu'un message d'erreur kernel doit être émis lorsque le mount chie dans la colle pour cause de charset invalide : vérifions.

# dmesg | tail -2
Unable to load NLS charset windows1258
FAT: IO charset windows1258 not found

Oh oh ! On est bien au bon endroit. On voit des trucs qui s'appelle "NLS" partout : dans le message d'erreur, mais aussi dans les noms des fonctions, comme "load_nls", qui prend d'ailleurs comme argument... "options.iocharset". Ça tombe bien, tiens. Je vous fait grâce de la recherche sur internet : y'a pas de doc. Y'a pas de manuel non plus (c'est la peine d'essayer info ?). Et manifestement, rien dans /usr/src/linux/Documentation/filesystems/, bref le truc est codé, mais on ne sait rien dessus...

Filons donc dans le code...

#ifdef CONFIG_KMOD
        ret = request_module("nls_%s", charset);
        if (ret != 0) {
                printk("Unable to load NLS charset %s\n", charset);
                return NULL;
        }
        nls = find_nls(charset);
#endif

Voilà donc notre erreur ! Et d'où ça vient ? Du "request_module" : chargement de module. Qui doit avoir un nom en nls_* : regardons donc dans le dossier NLS ! Y'a du cp*, du iso*, du koi8*, du eucjp, et notre ami l'utf8. Bonne nouvelle : les cp*, après recherche, sont les équivalents en norme unicode des codage windows*. Mauvaise nouvelle : y'a un tas de numéro, mais pas de cp1258 à l'horizon, et les numéros en dessous ne sont pas compatibles. Cherchons tout de même la table des caractères. On y trouve :

0xE9    0x00E9    #LATIN SMALL LETTER E WITH ACUTE

On googlise le truc, et on trouve l'ISO8859-14. Miracle : les accents et autre cédille correspondent au niveau de leurs codages en hexadécimal ! Et surtout, re-miracle : l'iso8859-14 est supporté niveau iocharset par NLS ! NLS, on l'a bien compris, c'est un module avec un ensemble de modules auxiliaires, qui permet la traduction à la volée des noms de fichiers d'un codage à un autre. On essaie donc le montage :

# mount -o loop,iocharset=iso8859-14 testfat test

Et là... ça marche pas  >_<". Va falloir continuer l'enquête... Regardons la fonction __fat_readdir, ie la fonction auxiliaire où est codé ce qui est appelé par "ls" via l'appel système stat/readdir : plus exactement au moment de l'affichage. On y remarque que les circuits passent soit par du iocharset, soit par de la traduction utf8, et selon que l'option "utf8" est présente ou non. Moralité :

# mount -o loop,utf8 testfat test

Et ça marche. Parfois faut pas se faire chier  :D. Il semblerait qu'en fait, iocharset soit destiné à... la traduction inverse, d'unicode (sur le disque) vers charset (à l'affichage). Mais franchement, l'aide la plus complète dans les sources du noyau Linux, linux/Documentation/filesystems/vfat.txt :

iocharset=name -- Character set to use for converting between the
                 encoding is used for user visible filename and 16 bit
                 Unicode characters. Long filenames are stored on disk
                 in Unicode format, but Unix for the most part doesn't
                 know how to deal with Unicode.
                 By default, FAT_DEFAULT_IOCHARSET setting is used.

                 There is also an option of doing UTF-8 translations
                 with the utf8 option.

                 NOTE: "iocharset=utf8" is not recommended. If unsure,
                 you should consider the following option instead.

utf8=<bool>   -- UTF-8 is the filesystem safe version of Unicode that
                 is used by the console.  It can be enabled for the
                 filesystem with this option. If 'uni_xlate' gets set,
                 UTF-8 gets disabled.

uni_xlate=<bool> -- Translate unhandled Unicode characters to special
                 escaped sequences.  This would let you backup and
                 restore filenames that are created with any Unicode
                 characters.  Until Linux supports Unicode for real,
                 this gives you an alternative.  Without this option,
                 a '?' is used when no translation is possible.  The
                 escape character is ':' because it is otherwise
                 illegal on the vfat filesystem.  The escape sequence
                 that gets used is ':' and the four digits of hexadecimal
                 unicode.

Assez sibyllin... Bref, on aura appris des trucs au moins  :). Notamment que Linux sait tout faire, même des trucs qu'on sait pas trop ce que c'est, et qui sont documentés bizarrement... En tout cas on aura trouvé notre solution au problème de codage des caractères accentués sur volume FAT32 : il faut monter avec l'option utf8. C'est tout  :D.

Ce moment d'intense geekitude vous était offert par...