1 points par GN⁺ 2024-09-30 | 1 commentaires | Partager sur WhatsApp
  • Sur les systèmes glibc 32 bits, la récupération de l’heure courante ou les appels à stat() peuvent échouer après 2038, ce qui impose à Gentoo une voie de migration sûre vers un time_t 64 bits
  • Le support time64 de glibc doit être utilisé avec le Large File Support (LFS) et, en environnement 32 bits, l’ancien ABI, l’ABI LFS et l’ABI LFS+time64 coexistent
  • Dès que time_t apparaît dans une API, une structure ou des arguments de fonction, le changement de largeur du type provoque une rupture d’ABI ; mélanger des binaires time32 et time64 peut alors entraîner des dysfonctionnements à l’exécution et des risques de sécurité
  • En tant que distribution basée sur les sources, Gentoo peut se retrouver avec un système partiellement migré à cause d’échecs ou de dépendances circulaires pendant la reconstruction de @world
  • Depuis la correction du 2024-09-30, il apparaît qu’un simple changement de libdir ne suffit plus, et qu’un marquage time64 englobant aussi le chargeur dynamique et plusieurs toolchains de langage devient la contrainte centrale

Le problème de l’an 2038 et l’ampleur de la transition time64

  • Les applications 32 bits qui utilisent un time_t 32 bits peuvent, en 2038, recevoir l’erreur -1 au lieu de l’heure courante, ou ne plus pouvoir exécuter stat() sur des fichiers
  • La direction générale de la transition consiste à remplacer time_t par un type 64 bits
    • musl a déjà effectué cette transition
    • glibc la prend en charge en option
    • certaines distributions comme Debian l’ont déjà adoptée
  • Les distributions basées sur les sources comme Gentoo obligent l’utilisateur à reconstruire lui-même le système, ce qui impose de réduire au minimum la durée pendant laquelle les paquets restent dans des états ABI différents
  • Le risque principal est que le changement de taille de time_t casse l’ABI
    • si time_t figure dans l’API d’une bibliothèque, tout le code lié à cette bibliothèque doit utiliser la même largeur de type
    • une migration partielle vers time64 n’est pas sûre

LFS et les trois sous-ABI 32 bits

  • Les architectures 32 bits avaient déjà, auparavant, des problèmes de largeur de type liés aux fichiers
    • off_t sert aux offsets de fichier
    • ino_t sert aux numéros d’inode
    • à l’origine, ils étaient codés sur 32 bits, ce qui posait problème pour les fichiers de plus de 2 GiB ou les numéros d’inode dépassant la plage 32 bits
  • Pour y remédier, le Large File Support (LFS) a été introduit
    • off_t et ino_t passent à des variantes 64 bits
    • dans glibc, cela reste encore aujourd’hui optionnel
    • beaucoup de paquets activent LFS en amont et gèrent la rupture d’ABI, mais le problème n’est pas totalement résolu
  • Le support time64 de glibc exige l’usage de LFS, ce qui revient à traiter ensemble les problèmes de taille de fichier et de temps
  • Sur les systèmes 32 bits, il existe trois sous-ABI
    • ABI historique : types 32 bits
    • LFS : off_t 64 bits, ino_t 64 bits, time_t 32 bits
    • time64 : LFS + time_t 64 bits
  • Une même build de glibc reste compatible avec ces trois variantes, mais les bibliothèques qui utilisent ces types dans leur API ne peuvent pas mélanger ces trois variantes entre elles

Comment le changement d’ABI casse concrètement les choses

  • Quand time_t passe de 32 à 64 bits, la disposition des structures change
    • dans une structure d’exemple avec int a, time_t b, int c dans cet ordre, l’offset de c diffère selon que time_t est sur 32 ou 64 bits
    • si l’on mélange des binaires time32 et time64, on peut lire ou écrire les mauvais champs, voire accéder hors limites
  • La taille de struct stat change elle aussi selon l’ABI
    • valeur par défaut de glibc sur x86 32 bits : 88 octets
    • LFS : 96 octets
    • LFS + time64 : 108 octets
  • Même sans structure, des problèmes apparaissent dans les arguments de fonction
    • sur x86, les arguments de fonction sont passés via la pile
    • si l’un des arguments est un time_t, la position sur la pile des arguments suivants change
  • Dans l’expérience d’exemple, un programme time32 voit ses valeurs corrompues lorsqu’il est lié à une bibliothèque reconstruite en time64
    • la sortie d’origine est a = 1, une valeur de temps correcte, puis c = 3
    • si seule la bibliothèque est reconstruite avec -D_FILE_OFFSET_BITS=64 -D_TIME_BITS=64, b et c sont interprétés de travers
  • À l’heure actuelle, il n’existe pas de mécanisme de protection réellement efficace contre ce mélange d’ABI, ce qui peut provoquer des ruptures à l’exécution et des problèmes de sécurité

Pourquoi la transition est plus difficile pour Gentoo

  • Les distributions binaires reconstruisent tous les paquets avant que l’utilisateur effectue la mise à niveau, selon une étape relativement atomique
    • des problèmes peuvent subsister avec des dépôts tiers ou des programmes compilés localement, mais l’ensemble du processus reste relativement sûr
  • Gentoo doit modifier l’ABI sur place pendant la reconstruction de @world
    • entre la reconstruction de deux paquets, des ABI incompatibles peuvent se retrouver mélangés
    • si certaines reconstructions échouent, le système peut rester dans un état partiellement migré
    • des dépendances circulaires peuvent casser les outils de build lorsqu’on reconstruit les paquets requis, rendant la suite de la migration impossible

Les pistes de mitigation à l’étude

  • Trois directions sont discutées
    • changer le tuple de plateforme CHOST pour distinguer le nouvel ABI de l’ABI 32 bits existant
    • changer le libdir du nouvel ABI afin d’installer les bibliothèques reconstruites séparément des anciennes
    • introduire une distinction d’ABI au niveau binaire pour empêcher l’édition de liens entre binaires de sous-ABI différents
  • Les trois approches peuvent être implémentées de manière assez indépendante, même si certaines peuvent dépendre des autres
  • Les chaînes d’exemple dans l’article ne correspondent pas nécessairement aux chaînes finales de la solution retenue

Distinguer l’ABI via CHOST

  • Le tuple de plateforme identifie la plateforme cible de la toolchain et, dans Gentoo, sert aussi à distinguer proprement les ABI pour le support multilib
  • Le tuple se compose de quatre parties : architecture, vendor, système d’exploitation et libc
    • exemple : i386-pc-linux-gnu
    • exemple : i686-pc-linux-gnu
    • exemple : i686-unknown-linux-gnu
  • Lors de l’introduction d’un nouvel ABI, on a déjà utilisé soit une modification du champ vendor, soit un suffixe ajouté au champ libc
    • pour l’ABI ARM hardfloat, on a vu par le passé des formes comme armv7a-hardfloat-linux-gnueabi et armv7a-unknown-linux-gnueabihf
  • Des options comparables existent pour l’ABI time64
    • i686-gentoo_t64-linux-gnu
    • i686-pc-linux-gnut64
    • armv7a-gentoo_t64-linux-gnueabihf
    • armv7a-unknown-linux-gnueabihft64
  • Le changement de tuple ne semble pas nécessiter un grand nombre de patchs
    • la GNU toolchain et le système de build GNU ignorent ce qui suit gnu dans le champ libc
    • Clang aurait besoin d’un patch pour sélectionner automatiquement le bon ABI selon le tuple

Changement de libdir et preserved-libs

  • Le libdir est le nom par défaut du répertoire d’installation des bibliothèques
    • la valeur la plus courante est lib
    • sur les architectures compatibles 64 bits, on utilise souvent lib64 par convention
    • l’ABI x32 sur x86 utilise libx32, et l’ABI n32 sur MIPS utilise lib32
  • Pour l’ABI 32 bits en time64, l’idée étudiée consiste à remplacer lib par quelque chose comme libt64
  • Un libdir séparé sert de mécanisme pour limiter le mélange d’ABI pendant la transition
    • il réduit le risque qu’un exécutable time64 se lie par erreur à une bibliothèque time32
    • il permet de conserver les bibliothèques time32 grâce à la fonctionnalité preserved-libs de Portage
    • il devient aussi possible de proposer, en option, un profil multilib time32 + time64 afin de garder la compatibilité avec les applications précompilées existantes en time32
  • Avec preserved-libs, les anciens exécutables continuent d’utiliser les bibliothèques time32 jusqu’à leur reconstruction, tandis que les bibliothèques reconstruites en time64 sont installées dans le nouveau libdir
  • Le changement de libdir nécessite des patchs dans la toolchain
    • glibc peut recevoir un traitement particulier, car le même ensemble de bibliothèques reste valable pour plusieurs sous-ABI
    • un ld.so distinct pourrait être nécessaire pour que le champ .interp des exécutables time64 pointe vers le ld.so prévu pour time64
  • Un support multilib correct exigerait aussi un tuple de plateforme propre à cet ABI

Marquer l’incompatibilité au niveau binaire

  • Quand on mélange des binaires de différents ABI, l’éditeur de liens ou le chargeur dynamique devraient en principe l’empêcher
    • si l’on tente de lier un programme 64 bits avec une bibliothèque 32 bits, l’éditeur de liens refuse avec file in wrong format
    • le chargeur dynamique refuse lui aussi avec des erreurs comme wrong ELF class: ELFCLASS32
  • Plusieurs mécanismes sont déjà utilisés pour distinguer les ABI existants
    • ELFCLASS32 et ELFCLASS64
    • des identifiants de machine comme EM_386 et EM_X86_64
    • le champ flags sur ARM et MIPS
    • des sections d’attributs propres à certaines architectures
  • time32 et time64 auraient besoin d’un mécanisme comparable, mais la solution n’est pas simple
    • il ne semble pas exister de mécanisme générique réutilisable
    • il faut une solution adaptée à plusieurs architectures
    • l’ajout d’une nouvelle section de note ELF, avec le support correspondant dans la toolchain, apparaît comme un candidat réaliste
  • Il faut aussi tenir compte du fait que les utilisateurs pourraient désactiver cette protection
    • si un logiciel précompilé sans code source n’appelle pas d’API utilisant time_t, il peut continuer à fonctionner avec les bibliothèques système
    • l’empêcher dans tous les cas pourrait être pire que le problème lui-même
  • Avec un libdir distinct, il devient relativement simple de mettre en place des contrôles QA non bloquants
    • on distingue les exécutables time64 via .interp
    • on vérifie qu’un programme time32 ne charge pas de bibliothèque depuis libt64
    • on vérifie qu’un programme time64 ne charge pas directement de bibliothèque depuis lib

Les limites des applications 32 bits précompilées

  • Indépendamment des paquets compilés depuis les sources, il existe encore sur x86 et PowerPC des applications fournies uniquement sous forme de binaires précompilés anciens
    • c’est notamment le cas de logiciels propriétaires et de vieux jeux
  • Elles rencontrent à la fois des problèmes de compatibilité avec les bibliothèques système et le problème de l’an 2038 lui-même
  • Pour les problèmes de compatibilité, l’infrastructure multilib existante apporte déjà en partie une solution
    • sur amd64, il existe déjà un agencement multilib et des mécanismes pour compiler plusieurs versions de bibliothèques afin de prendre en charge les logiciels 32 bits
    • cela pourrait être étendu en distinguant abi_x86_32 et abi_x86_t64
    • un nouveau profil x86 multilib pourrait prendre en charge les deux ABI
  • En revanche, l’échec intrinsèque des programmes 32 bits après 2038 reste plus difficile à résoudre
    • il est possible de contrôler l’heure système avec faketime
    • il est aussi possible d’exécuter une VM dont l’horloge est réglée dans le passé

Correction du 2024-09-30 : le libdir seul ne suffit pas

  • La conception initiale était trop optimiste, et un simple changement de libdir ne permet pas une séparation fiable
  • Tous les libdir étant listés dans ld.so.conf, on ne peut pas s’appuyer sur un chemin de libdir codé en dur dans ld.so
    • les préfixes LLVM personnalisés ajustent déjà les chemins, et ce cas nécessite lui aussi un traitement particulier
  • En conséquence, il devient plus probable que le changement de libdir doive dépendre d’une distinction d’incompatibilité binaire
  • Trois objectifs de base doivent être remplis
    • le chargeur dynamique doit distinguer les binaires time32 et time64
    • tous les binaires sans marquage time64 explicite doivent être considérés comme time32 pour préserver la rétrocompatibilité
    • tous les nouveaux binaires doivent porter un marquage time64 explicite, y compris ceux construits hors de l’environnement C, par exemple en Rust
  • Cela représente un travail qui implique de patcher plusieurs toolchains dans plusieurs langages
    • Gentoo aurait du mal à maintenir cela uniquement en local, et la coopération de plusieurs parties serait nécessaire
    • les architectures visées sont souvent considérées comme legacy ou ne bénéficient plus d’un support suffisant
  • Une autre question est de savoir si les autres toolchains produiront bien des exécutables time64 corrects
    • si elles ne sont pas adaptées pour suivre _TIME_BITS comme les programmes C, elles peuvent coder en dur une certaine largeur de time_t et se casser
  • Comme tous les binaires sans marquage time64 explicite utiliseraient les bibliothèques time32, Gentoo ne pourrait plus exécuter des exécutables tiers sans patch qui ne portent pas le marquage correct
  • Des objectifs plus modestes sont aussi envisagés
    • injecter un RPATH dans tous les exécutables time64 pour forcer directement l’usage du libdir time64
    • cette approche n’empêche pas totalement le chargeur dynamique d’utiliser des bibliothèques time32, mais elle peut faciliter la transition sans gros problèmes de compatibilité
  • À l’inverse, une autre option consiste à ne pas changer durablement le libdir time64, mais à renommer temporairement le libdir time32
    • on injecte un RPATH dans les programmes existants et on change le nom du libdir
    • les nouvelles bibliothèques time64 sont installées dans le libdir existant
    • les nouveaux programmes time64 n’ont pas de RPATH imposant les bibliothèques time32
    • cela présente l’avantage de préserver la compatibilité avec d’autres distributions qui ont déjà effectué la transition

Les défis qui restent

  • Si les trois solutions sont toutes implémentées, elles pourraient offrir aux systèmes Gentoo 32 bits utilisant glibc une voie de migration plus propre et relativement sûre
  • Cela dit, ces solutions s’appliquent surtout aux paquets compilés depuis les sources
  • Pour les applications 32 bits précompilées, le problème de l’an 2038 demeure même si la compatibilité ABI est préservée
  • L’ensemble de la conception reste encore à l’état d’ébauche et peut continuer à évoluer en fonction des expérimentations, des discussions et des propositions de patchs

1 commentaires

 
GN⁺ 2024-09-30
Avis de Hacker News
  • Gentoo dispose de quelques options non abordées dans l’article, qui semblent avoir été omises parce que la conception du système Gentoo rendrait le travail conséquent

    1. Permettre de compiler un paquet en le ciblant sans l’installer. Le point clé est que, dans Gentoo, la compilation et l’installation d’un paquet se font en une seule étape, si bien qu’il n’est pas possible de compiler d’abord plusieurs éléments interdépendants puis d’installer atomiquement les résultats. Lors d’une mise à jour impliquant un changement d’ABI, le système risque facilement de se retrouver partiellement cassé
    2. Étendre la gestion habituelle des versions de .so afin de refléter aussi les changements d’ABI des paquets dépendants. En général, les bibliothèques partagées incluent un numéro de version dans le nom de fichier et dans la version interne, comme libfoo.so.1.0.0, et le paquet suit ses propres ruptures d’ABI. Pour prendre en charge time_t en 64 bits, il faudrait ajouter, pour chaque .so, un élément de version contrôlé par l’ABI de ses dépendances. Le résultat serait similaire au « recours à un autre libdir » décrit dans l’article, mais cela pourrait constituer une base réutilisable pour de futurs changements d’ABI, au prix d’une approche probablement bien plus intrusive
    • Pour « compiler sans installer », les mises à jour partielles par étapes semblent être ce qui convient le mieux
      On pourrait planifier la compilation de plusieurs nouveaux paquets, les construire dans un bac à sable, puis faire en sorte que les nouvelles compilations consultent d’abord le bac à sable via une union, avant de se rabattre sur le système. Une fois tout compilé, il suffirait de empaqueter les résultats et de les déplacer du bac à sable vers le système réel. Ainsi, toute la mise à jour Gentoo pourrait devenir transactionnelle, ce qui apporterait de gros avantages à d’autres égards également
    • Gentoo prend déjà en charge le point 1 en permettant de désigner le répertoire où installer l’image finale produite. Il suffit de modifier ROOT, qui vaut généralement /
      On peut reconstruire tout @system et @world, les installer dans le sous-répertoire indiqué, puis synchroniser le tout en une seule fois. Si possible, il vaut mieux le faire depuis une session live ; en théorie, on peut aussi monter / en bind dans un sous-répertoire du nouvel emplacement installé, entrer dedans avec chroot, puis synchroniser vers le vrai / parent
      https://devmanual.gentoo.org/ebuild-writing/variables/#root
    • Gentoo conserve déjà les anciennes bibliothèques jusqu’à ce que toutes les dépendances aient été mises à jour ; encoder le changement d’ABI dans l’architecture et le SONAME, qui sont les endroits où les changements d’ABI devraient normalement être enregistrés, permettrait donc de résoudre ce problème
  • La manière dont Mac OS X a traité off_t et ino_t pourrait donner une piste. Les appels et structures existants ont conservé leur comportement, de nouveaux appels et types avec le suffixe 64 ont été ajoutés, et des macros de préprocesseur permettaient de choisir la cible réellement référencée, même si on les utilisait rarement directement
    À la place, l’OS et le SDK sont versionnés, et l’on peut indiquer au moment de la compilation la plus ancienne version de l’OS sur laquelle le binaire doit pouvoir s’exécuter. Les en-têtes sélectionnaient automatiquement les macros appropriées sur cette base, et les annotations d’API nouvelles ou obsolètes utilisaient le même mécanisme pour produire des liens faibles ou des avertissements. Au début, c’était géré par le préprocesseur, mais aujourd’hui le compilateur comprend plus finement ce qu’Apple appelle la disponibilité des API, si bien qu’une approche similaire semble possible sur d’autres plateformes

    • Cela ne résout pas le problème central expliqué par l’article. Les applications utilisant des « versions d’OS cibles de compilation » différentes ne peuvent alors plus être liées entre elles
      Même lorsqu’elles s’exécutent sur OS v.B, une application X déclarée comme ciblant OS v.B peut ne pas pouvoir être liée avec une application Y déclarée comme ciblant OS v.A. En réalité, cette méthode ressemble à ce que presque toutes les plateformes font déjà, et procéder autrement casserait immédiatement la compatibilité avec les binaires existants
    • Les bibliothèques auxiliaires autres que glibc ne définiront pas plusieurs variantes de fonctions selon la taille de off_t, et il n’y aura pas non plus dans les en-têtes de mécanisme choisissant de façon transparente le bon ensemble de fonctions selon la taille de type souhaitée par le programme client
      Pourtant, l’article souligne que time_t pose un problème plus important que off_t. La raison plausible est que time_t est beaucoup plus répandu. off_t est un type POSIX impliqué dans un nombre relativement limité d’interfaces, tandis que time_t appartient à l’ISO C et est utilisé un peu partout. De plus, beaucoup de code C suppose que time_t est un type entier de même largeur que int, alors que ce genre d’hypothèse est moins courant pour off_t
    • Cela ressemble à une solution élégante, mais en pratique ça se lit comme un hack épouvantable. Les macros sans type sont un cauchemar avec lequel je ne veux plus jamais avoir affaire
    • Cela ne fonctionne que si l’on peut forcer toute la plateforme à suivre. C’est une bonne solution, mais il faut contrôler la bibliothèque C. Gentoo ne contrôle pas ce que fait la libc, et les utilisateurs peuvent employer GNU libc, musl ou autre chose
  • Debian aussi a beaucoup souffert. Certains ont peut-être fait un burn-out, et beaucoup de gens pointaient les distributions basées sur les sources en disant : « là-bas, ce sera très facile »

    • Je serais curieux de trouver des ressources détaillant à quel point la transition time64 de Debian a été douloureuse. Vue de l’extérieur, elle a semblé relativement fluide et peu controversée, bien plus que la fusion de /usr, par exemple
    • J’ai effectué les transitions m68k, powerpc et sh4, et j’ai aussi aidé en partie pour hppa ; avec l’aide d’autres développeurs Debian, je suis encore en vie
    • Si « facile » signifie « il suffit de demander aux utilisateurs de tout reconstruire d’un coup », alors oui, probablement
  • Chaque fois que je vois des gens souffrir de ce genre de problème, je me dis que c’était vraiment une bonne chose d’avoir imposé ça lors du portage amd64 initial de FreeBSD. On pouvait définir les types de base de l’ABI, et on a décidé de regarder vers l’avenir plutôt que vers le passé.
    amd64 avait une caractéristique intéressante qui a facilité ce travail. Pendant les appels de fonction, les arguments 32 bits étaient automatiquement convertis en 64 bits ; du coup, même si l’on passait un entier temporel 32 bits à une fonction qui attendait un time_t 64 bits, cela fonctionnait généralement pendant les premiers travaux sur la plateforme. On pouvait donc repousser les petites finitions à plus tard.
    Il existait d’autres plateformes 64 bits à l’époque, mais elles n’avaient pas de time_t 64 bits, et FreeBSD/amd64 a été, vers 2003-2005, la première de cette lignée. De mémoire, sparc64 est aussi passé à un time_t 64 bits.
    Le plus gros problème était qu’à l’époque, tzcode n’était pas sûr en 64 bits. L’algorithme de normalisation de struct tm tombait dans un cas dégénéré où il essayait de calculer en boucle le jour, le mois et l’année de time_t(2^62). Plutôt que de modifier lourdement tzcode, je crois qu’on avait traité comme des échecs les dates antérieures à environ 1900 ou postérieures à l’an 10000. Il est très probable que cela ait été corrigé upstream depuis longtemps.
    Pendant quelques années, on a joué à la taupe avec le code tiers qui mélangeait les temps 32/64 bits en traitant négligemment int/long/time_t dans les structures de données de fichiers ou de réseau, mais dans l’ensemble ce n’était pas un gros problème. Avoir utilisé un time_t 64 bits dès le premier jour a permis d’éviter la plupart des problèmes, et le faire dès le départ était facile. Linux a raté une grande occasion de faire la même chose au lancement de son port amd64/x86_64.
    À côté de ça, le ino_t 64 bits n’a pas été terminé à l’époque. Les numéros d’inode 32 bits étaient exposés à énormément d’endroits : structures on-disk des systèmes de fichiers, structures de répertoires UFS, etc. Quand FreeBSD/amd64 était encore une plateforme de rang inférieur, il n’y avait pas de façon réaliste de régler ça dès le départ sans bousculer fortement d’autres architectures tier-1. Le travail a été fait deux fois, mais quelqu’un d’autre a fini par le terminer, en corrigeant au passage des constantes beaucoup trop courtes, comme la longueur des chemins de mountpoints.

    • Si je comprends bien, tous les ports Linux 64 bits ont utilisé dès le départ des time_t, off_t et ino_t 64 bits. Le problème actuel est de faire passer Linux 32 bits à un time_t 64 bits.
    • FreeBSD a aussi été plus audacieux avec off_t, qui est en 64 bits depuis la 2.0. Les versions 32 bits de Linux conservent encore des traces de l’ancienne taille.
      Pour la partie indiquant que les arguments de fonction 32 bits sont automatiquement convertis en 64 bits lors de l’appel, je comprends que cela ne fonctionne que pour les arguments non signés. C’est parce qu’un chargement dans %edi efface la partie haute de %rdi. La spécification de l’ABI SysV pour x86-64 ne dit pas que toutes les valeurs dans les registres ou sur la pile sont étendues en valeurs complètes de 64 bits, et la note sur les booléens indique aussi que seul l’octet de poids faible est significatif, ce qui suggère que c’est la règle générale.
    • Si cela signifie que FreeBSD a aussi porté le time_t d’i386 en 64 bits quand amd64 est apparu, c’est assez surprenant. Je me demande si d’autres architectures 32 bits comme Motorola 68000 ou sparc32 sont aussi passées à un time_t 64 bits.
  • Sur un vieux gros système Unix 32 bits, pour gérer des dates futures, j’ai déjà remplacé les fonctions libc utilisant un time_t 32 bits signé par des équivalents en time_t 32 bits non signé. Cela a permis de gagner 68 ans après 2038, et d’ici là je ne serai déjà plus là.
    L’inconvénient, c’est qu’on ne peut plus représenter les dates antérieures à 1970, l’epoch Unix, mais comme c’était un système de gestion d’agenda, ce n’était pas un problème. Si les dates passées sont importantes, on peut aussi déplacer l’epoch de quelques décennies ou réduire la résolution temporelle de 1 seconde à 2 secondes. Chacune de ces solutions a ses subtilités, donc cela dépend du cas d’usage.

    • Si l’on peut faire passer tout le système de signé à non signé, je me demande pourquoi ne pas être passé au 64 bits.
  • Dans la page de manuel BSD originale de tunefs, la section « Bugs » contenait la célèbre blague : « You can tune a file system, but you can't tune a fish. » D’après « Expert C Programming », le code source de cette page de manuel contenait, à côté de cette blague, le commentaire suivant :
    « Si vous retirez ceci, le démon UNIX vous poursuivra à quatre pattes à partir de maintenant et jusqu’au wrap around de time_t. »
    Quand cette phrase a été écrite dans les années 70, 2038 devait certainement sembler un avenir inimaginablement lointain.
    https://progforperf.github.io/Expert_C_Programming.pdf

  • Ce qui me frappe surtout, c’est que, même si je respecte l’effort, du point de vue utilisateur j’aurais envie de passer à une distribution non basée sur les sources comme Debian pour en finir avec ce problème.

    • Les difficultés des distributions basées sur les sources semblent venir du fait qu’elles tentent une mise à niveau sur place tout en introduisant un changement incompatible d’ABI. Donc passer à une distribution complètement différente peut être tout aussi disruptif qu’installer proprement un Gentoo utilisant la nouvelle ABI, peut-être moins long, mais au moins du même ordre.
    • Il existe aussi une façon simple de le faire avec Gentoo. On démarre sur une clé USB ou équivalent, on exécute mkfs.ext4 — ou le système de fichiers utilisé — sur les partitions / et /usr, on les monte, on extrait stage3, on entre dans le chroot et on lance emerge $all-my-packages-that-where-installed-before-mkfs.
      Au lieu de mettre à niveau progressivement, on peut installer une nouvelle copie de Gentoo.
    • La distinction consistant à dire qu’il suffit de passer à une distribution non basée sur les sources est plus subtile. Une distribution basée sur les sources comme NixOS n’a pas le même problème. Le point clé n’est pas tant de savoir si l’on compile depuis les sources, mais la manière dont Gentoo construit et installe les paquets.
      S’il y a des logiciels tiers propriétaires, on peut encore rencontrer des problèmes même avec un système binaire. Des paquets first-party installés séparément comme étape indépendante peuvent aussi poser problème.
  • Je ne suis pas spécialiste du C, mais je pensais que les alias de types comme off_t avaient été introduits justement pour pouvoir les changer plus tard. Or cela ne semble manifestement pas fonctionner ainsi ; je me demande si je me trompe

    • C’est la différence entre compatibilité source et compatibilité binaire. Utiliser un typedef comme off_t évite généralement de devoir réécrire le code, mais il faut recompiler tout ce qui utilise ce type
    • Ça fonctionne dans une certaine mesure, mais c’est mal adapté aux distributions basées sur les sources. Si, après avoir changé la définition de off_t, on pouvait reconstruire @world de façon atomique, il n’y aurait pas de problème ; mais une distribution basée sur les sources ne reconstruit pas @world atomiquement, elle reconstruit les paquets un par un
      On peut alors se retrouver avec un libc.so utilisant un off_t 64 bits, tandis que gcc a été construit en supposant un off_t 32 bits, ce qui peut faire planter gcc. Des paquets nécessaires à la reconstruction de @world, comme bash, coreutils, make ou binutils, peuvent aussi se casser, et à ce stade on est bloqué. C’est pourquoi ce genre de mise à niveau demande de la prudence
    • Ce n’est que la première pièce du puzzle, voire une demi-pièce. Comme le dit l’article, dès que off_t se retrouve dans une structure, utilisé dans un appel de fonction ou intégré à un protocole, l’abstraction disparaît et sa taille réelle devient importante
      Si l’on mélange ancien et nouveau code en chargeant des bibliothèques ou en communiquant via un protocole, les décalages deviennent incorrects et les plantages commencent. Au final, la transition impose à tout le monde de séparer les programmes entre « legacy » et « portés, ou au moins vérifiés », ce qui est très pénible
    • Les alias de types ne facilitent les choses qu’au niveau du code source. Ce n’est pas une vraie abstraction, surtout pas complète. Par exemple, si l’on remplace un type interne par un type à virgule flottante, la sémantique change fortement et cela devient totalement visible pour le code utilisateur
      Même remplacer par un type plus grand à la sémantique proche peut casser des choses. Exemple simple : le padding des structures ; et il existe aussi beaucoup de cas où l’on convertit des pointeurs en entiers puis inversement, donc si la représentation interne change, cela ne peut que casser. Que ce soit une bonne pratique ou non est une autre question, mais ce n’est pas rare. Le point clé, c’est la compatibilité ABI
    • Ça fonctionne, mais le problème d’un changement d’ABI est qu’il faut le faire partout en même temps. En gros, rien n’empêche de lier une bibliothèque construite avec un off_t 32 bits à une autre construite avec un off_t 64 bits, et le comportement obtenu peut être très imprévisible
  • Dans la structure donnée en exemple, il est dit qu’avec un time_t 32 bits, l’offset de c est 8, et qu’avec un type 64 bits il est 12 ; mais en réalité, ne devrait-il pas être 16 ? Comme b doit être aligné sur 64 bits, il devrait y avoir du padding entre a et b. Cela renforce même l’argument de l’auteur

    • La plupart des ABI x86 n’imposent pas de padding pour les types 64 bits, car à l’époque il n’y avait pas de chargements 64 bits
  • Quand on voit tout cela, l’étrange représentation du temps de Windows a quand même un petit avantage : un entier 64 bits comptant des unités de 100 ns depuis le 1er janvier 1601 à 00:00 GMT, dans le calendrier grégorien. La résolution est excellente, et cela fonctionnera encore jusqu’à ce que toute la galaxie soit conquise