1 points par GN⁺ 3 시간 전 | 1 commentaires | Partager sur WhatsApp
  • Dans Linux 7.2, les usages internes au noyau de l’API strncpy ont disparu, ce qui entraîne la suppression définitive de cette interface de copie de chaînes dont l’abandon était prévu depuis longtemps
  • strncpy() copie un nombre d’octets donné, mais son comportement de terminaison NUL n’est pas intuitif, ce qui en a fait pendant des années une source de bugs dans le noyau
  • Sa tendance à remplir inutilement le tampon de destination avec des zéros provoquait aussi des problèmes de performances, et il a fallu environ 6 ans et 362 commits pour l’éliminer
  • Lors du merge de vendredi, non seulement l’API elle-même a été retirée, mais aussi la dernière implémentation spécifique à l’architecture per-CPU
  • Le code du noyau doit désormais choisir des fonctions de remplacement selon l’usage, comme strscpy(), strscpy_pad(), strtomem_pad(), memcpy_and_pad() ou memcpy()

strncpy disparaît de Linux 7.2

  • Linux 7.2 supprime définitivement du noyau l’API strncpy, dont l’abandon était prévu de longue date
  • Après 6 années de nettoyage, il ne reste plus aucun code interne au noyau utilisant l’interface strncpy
  • Ce changement ne se limite pas à un simple remplacement de fonction : il s’apparente à l’élimination, à l’échelle du noyau, d’anciennes pratiques de copie de chaînes

Ampleur du travail nécessaire à sa suppression

  • La suppression de strncpy a nécessité environ 362 commits
  • Le travail a progressé en éliminant étape par étape les usages de strncpy dans le noyau
  • Avec Linux 7.2, ce chantier de nettoyage arrive à son terme

Pourquoi strncpy posait problème dans le noyau

  • strncpy était considérée depuis des années dans le noyau Linux comme une source récurrente de bugs
  • Deux comportements en particulier étaient problématiques
    • La sémantique et le comportement de terminaison NUL n’étaient pas intuitifs, ce qui favorisait les erreurs d’utilisation
    • Le remplissage redondant du tampon de destination avec des zéros entraînait un coût de performances inutile

Le merge qui a acté sa suppression

  • Le merge effectué vendredi a supprimé l’API strncpy
  • La même opération a aussi retiré la dernière implémentation strncpy spécifique à l’architecture per-CPU

API de remplacement à utiliser dans le code du noyau

  • À la place de strncpy, il faut choisir une fonction adaptée à la cible de copie et aux conditions de terminaison
    • strscpy() : à utiliser pour une destination terminée par NUL
    • strscpy_pad() : à utiliser lorsqu’une destination terminée par NUL nécessite un remplissage par zéros
    • strtomem_pad() : à utiliser pour des champs de largeur fixe non terminés par NUL
    • memcpy_and_pad() : à utiliser pour une copie bornée avec padding explicite
    • memcpy() : à utiliser pour une copie mémoire dont la longueur est connue

1 commentaires

 
GN⁺ 3 시간 전
Commentaires sur Hacker News
  • Autrefois, on se moquait des développeurs du noyau Linux, pourtant parmi les meilleurs développeurs C du monde, en disant qu’ils n’étaient même pas capables de créer des types stringbuffer ou stringview, mais il faut reconnaître qu’à l’époque il n’existait pas encore de véritable consensus sur le sujet
    La personne qui avait déjà vu la bonne direction, c’était Dennis Ritchie, qui avait proposé en 1990 un type de pointeur gras pour C. Si cela avait été intégré à C99, cela aurait été un ajout parfait ; si le comité l’avait adopté, le monde aurait peut-être été assez différent
    En 2007, il y a eu une deuxième occasion avec l’article de Walter Bright, “C's greatest mistake”, qui expliquait plus clairement l’idée de slice/stringview, essentiellement la même que celle de Ritchie, mais cela n’a pas non plus été intégré à C11. On en est à C23 et ce n’est toujours pas là ; à la place, on a eu _Generic et les VLA, alors autant faire la fête

    • L’article de Walter Bright de 2007 est ici : https://digitalmars.com/articles/C-biggest-mistake.html
      En cherchant, je suis aussi tombé sur un post Reddit sur le même sujet, et la polémique de « l’abri à vélos » m’a fait rire : https://www.reddit.com/r/C_Programming/comments/90uq7c/cs_bi...
      Je me demande pourquoi le comportement de décadence des tableaux C en pointeurs a été conçu ainsi. J’ai vu l’explication selon laquelle le but était de pouvoir compiler du code B en C avec un minimum de changements ; en B, une déclaration de tableau définissait en fait un pointeur et un tableau, puis initialisait ce pointeur pour qu’il pointe vers le premier élément du tableau
    • Les VLA ont été rétrogradés au rang de fonctionnalité optionnelle en C11, et c’est plutôt une bonne chose
      Le plus gros problème aujourd’hui, c’est que la bibliothèque standard du C reste bloquée à l’époque K&R, et que même des fonctionnalités du langage ajoutées en C99, comme les structures en argument ou en valeur de retour, n’ont pas été reflétées dans les API de la bibliothèque standard. Rien qu’avec des structures de plage sous forme de paire pointeur/taille dans la bibliothèque standard, et de nouvelles fonctions de chaîne ou des fonctions de chaîne modernisées qui les utilisent, la situation pourrait déjà nettement s’améliorer
    • Lien vers la proposition de Ritchie : https://web.archive.org/web/20150611114358/https://www.bell-...
    • C’est le schéma qui m’agace le plus dans le travail en équipe. On a les solutions A, B et C, chacune avec ses avantages et ses inconvénients, on en discute pendant deux semaines, et au final on ne choisit rien du tout
    • Cela montre simplement où se situent les priorités du WG14
  • On dit que strncpy dans le noyau Linux a été pendant des années une « source tenace de bugs », à cause de sa sémantique contre-intuitive, de la gestion de la terminaison NUL et du coût en performances dû au remplissage inutile de zéros dans la destination
    Chaque fois qu’on m’a demandé une revue de code C, j’ai cherché les strncpy, et j’y ai toujours trouvé des bugs

  • Certaines choses m’irritent depuis 40 ans. Les chaînes terminées par NUL, et maintenant même les chaînes non UTF-8 en entrée/sortie
    C’est aussi le cas des conventions de fin de ligne en LF, CR ou CRLF, et des champs séparés par des pipes ou des virgules. Si on avait utilisé des caractères ASCII non ambigus comme GS, FS et RS, l’encodage/décodage des fins de ligne serait devenu un simple problème d’E/S, et HT/VT/CR/LF/FF auraient pu rester, au sens propre, dans le code lié à l’affichage

    • J’ai déjà travaillé sur un projet qui transformait des données encadrées par des séparateurs de champ/enregistrement ASCII, et c’était vraiment très simple à traiter
      Toute la saleté liée à la gestion des échappements dans les données séparées par des virgules disparaît, ce qui simplifie énormément les choses
    • Unicode offre désormais encore plus d’options. Il y a NL Next line, qui semble venir d’EBCDIC, ainsi que LS Line separator et PS Paragraph separator, créés par Unicode
      La norme Unicode dit qu’il faut traiter CR, LF, CRLF et ces caractères, mais aussi la tabulation verticale et le saut de page, comme des séparateurs de ligne
    • En entrée/sortie standard, UTF-8 fonctionne parfaitement. Enfin, à condition de ne pas parler de Windows, qui semble toujours coincé au début des années 90 pour l’encodage du texte international
      Les fins de ligne comme LF, CR ou CRLF relèvent aussi des conventions des systèmes d’exploitation, et il vaut mieux qu’un langage de programmation n’essaie pas de « deviner » la bonne fin de ligne. Cela crée plus de problèmes que cela n’en résout, et encore une fois, c’est surtout un problème propre à Windows ; c’est à Microsoft de faire entrer Windows dans le siècle actuel
    • LF est ce qui a le plus de sens, mais pour un fichier texte, peu importe au fond. Le vrai problème, c’est que le CSV n’est pas du texte
      La dernière fois que j’ai dû manipuler un fichier CSV en bash, je l’ai converti en interne en RS et FS pour le traiter
    • À mon avis, il suffit d’utiliser UTF-8 partout
  • Au lieu de strncpy, le code du noyau Linux recommande d’utiliser strscpy() pour les destinations terminées par NUL, strscpy_pad() pour les destinations terminées par NUL qui nécessitent un remplissage en zéros, strtomem_pad() pour les champs de largeur fixe non terminés par NUL, memcpy_and_pad() pour les copies bornées avec padding explicite, et memcpy() pour les copies mémoire dont la longueur est connue
    Ça ressemble à un cauchemar, et je ne vois pas pourquoi cela devrait être aussi complexe

    • La raison, c’est la performance. Une fonction universelle sûre qui gérerait tout cela serait forcément ralentie par des branchements internes, et le choix de la fonction exprime aussi l’intention du développeur
      Je trouve préférable que, lors de la lecture du code, l’intention du développeur soit claire rien qu’à travers le choix de la fonction
    • Bien utiliser strncpy a toujours été compliqué de toute façon
    • On n’aurait pas au moins pu trouver de meilleurs noms ?
  • C’est précisément dans ce genre de travail répétitif et ingrat que se fait le vrai travail de l’ingénierie système
    Ce type de grand projet d’infrastructure, qui rend le noyau Linux plus fiable tout en le maintenant exploitable en production tout au long du processus, ne se joue pas sur quelques mois mais sur des dizaines d’années

    • Je comprends pourquoi cela se compte en dizaines d’années. La longue traîne des utilisateurs et des dépendances est réellement immense
      En revanche, je ne sais pas si l’on peut produire des avancées importantes et durables à ce rythme. Ce n’est pas vraiment une plainte, plutôt une forme de paradoxe de l’infrastructure critique
  • C’est un travail à la fois impressionnant et qui force à l’humilité. C’est étonnant de voir qu’autant de personnes y ont contribué
    Les « nouvelles fonctionnalités géniales » obtiennent facilement de la reconnaissance, mais sur quelque chose d’aussi fondamental que le noyau, retirer de mauvaises fonctionnalités est peut-être encore plus important
    Quand, dans 50 ans, on vivra à une époque où les gens auront oublié comment lire le code source, où les résidus de Claude/Codex s’accumuleront en silence en brûlant l’essentiel de l’énergie de la planète, ce genre de travail restera sans doute comme une légende de « l’âge fondateur »

    • Ça fait penser à A Deepness in the Sky de Vernor Vinge. Dedans, quelqu’un maintient un vaisseau spatial grâce à l’archéologie logicielle
      C’est aussi la seule personne qui sache ce qu’est l’Unix epoch
    • Je ne pense pas que, dans 50 ans, tout le monde aura oublié comment comprendre le code source. Le désir humain de savoir comment les choses fonctionnent existera encore à ce moment-là
    • Le code fourre-tout généré par l’IA deviendra probablement ingérable bien avant ça
  • Je pense que les chaînes terminées par 0 sont la plus grosse erreur de l’histoire de l’informatique. Les chaînes à la Pascal étaient bien plus sûres

    • Il existe aussi des solutions intermédiaires comme BSTR, adopté par Visual Basic puis plus tard par COM
      Cela reste un pointeur vers un tableau de caractères terminé par 0, mais avec un champ de longueur juste avant le premier octet pointé. En supposant l’absence de caractères NUL intégrés, c’est aussi compatible avec les chaînes C, et les fonctions de type BSTR peuvent exploiter la valeur de longueur
    • Je suis plutôt d’accord, mais il y aurait sans doute eu des débats sur le type de données du champ de taille. Si ce n’avait pas été à longueur variable, ça aurait posé encore plus de problèmes, et si ça l’avait été, cela aurait amené d’autres soucis
      Pendant un temps, même 16 bits ont peut-être paru excessifs, et aujourd’hui 32 bits peuvent sembler trop petits. C, qu’on présente comme un langage à « typage fort », est en réalité assez permissif là où c’était important
    • Les chaînes terminées par 0 ont servi de base à une quantité énorme de logiciels utiles. Dire que c’est la plus grande erreur de l’informatique est un peu exagéré
      Je n’ai pas écrit de code lié à Pascal depuis plus de 30 ans, mais je garde le vague souvenir d’avoir déjà trouvé son système de chaînes trop pénible à utiliser à l’époque
    • 255 caractères, ça n’était pas censé suffire à tout le monde ?
    • C’est presque aussi mauvais que les lignes terminées par un saut de ligne
  • Il y a énormément de souffrance et de bricolage juste parce qu’il manque un type chaîne de caractères

    • Plus précisément, la souffrance et le bricolage viennent moins de l’absence d’un type chaîne en général que du fait qu’il faut contourner l’absence de type chaîne en C
    • Quel genre d’approche permettrait d’introduire ici un typage fort ? Il faudrait probablement un gros refactoring pour que le code autour de strncpy utilise aussi ce type et ces fonctions, non ?
  • Je me demande ce qui rendait la réécriture des usages de strncpy si difficile au point que cela ait pris 6 ans
    J’aimerais savoir si son usage était si répandu, si c’était un chantier de long terme mené seulement quand on modifiait déjà le même fichier, ou s’il y avait d’autres difficultés

  • J’ai déjà dû gérer du code dans une appli Win32 qui utilisait des chaînes remplies d’espaces. La chaîne de destination était remplie avec des espaces, mais le dernier octet restait quand même un caractère nul
    Il fallait utiliser des versions dédiées des fonctions de chaîne pour les opérations de longueur, de copie, etc. Je ne sais pas pourquoi c’était fait comme ça, mais vu l’âge du codebase, ça venait peut-être du comportement des structures Pascal

    • Ça venait peut-être de chaînes issues de champs char dans une base SQL. Contrairement à varchar, les champs char sont complétés avec des espaces
    • Je dirais que l’origine de ce comportement est plutôt COBOL que Pascal
    • C’était peut-être pour éviter une réallocation quand la taille de la chaîne change, ou à cause de l’alignement sur les lignes de cache CPU