GNU/Linux >> Tutoriels Linux >  >> Linux

Questions sur putenv() et setenv()

  • [Le] putenv(char *string); [...] l'appel semble fatalement défectueux.

Oui, c'est fatalement défectueux. Il a été conservé dans POSIX (1988) car c'était l'art antérieur. Le setenv() le mécanisme est arrivé plus tard. Correction : La norme POSIX 1990 dit au §B.4.6.1 "Fonctions supplémentaires putenv() et clearenv() ont été considérés mais rejetés". La version 2 de la spécification Unix unique (SUS) de 1997 répertorie putenv() mais pas setenv() ou unsetenv() . La révision suivante (2004) a défini à la fois setenv() et unsetenv() aussi.

Parce qu'il ne copie pas la chaîne passée, vous ne pouvez pas l'appeler avec un local et il n'y a aucune garantie qu'une chaîne allouée au tas ne sera pas écrasée ou supprimée accidentellement.

Vous avez raison de dire qu'une variable locale est presque toujours un mauvais choix à passer à putenv() — les exceptions sont obscures au point de presque ne pas exister. Si la chaîne est allouée sur le tas (avec malloc() et al), vous devez vous assurer que votre code ne le modifie pas. Si c'est le cas, il modifie l'environnement en même temps.

De plus (bien que je ne l'aie pas testé), puisque l'une des utilisations des variables d'environnement consiste à transmettre des valeurs à l'environnement de l'enfant, cela semble inutile si l'enfant appelle l'un des exec*() les fonctions. Ai-je tort ?

Le exec*() les fonctions font une copie de l'environnement et la transmettent au processus exécuté. Il n'y a aucun problème.

La page de manuel Linux indique que la glibc 2.0-2.1.1 a abandonné le comportement ci-dessus et a commencé à copier la chaîne, mais cela a conduit à une fuite de mémoire qui a été corrigée dans la glibc 2.1.2. Je ne sais pas quelle était cette fuite de mémoire ni comment elle a été corrigée.

La fuite de mémoire survient car une fois que vous avez appelé putenv() avec une chaîne, vous ne pouvez plus utiliser cette chaîne à quelque fin que ce soit car vous ne pouvez pas dire si elle est toujours utilisée, bien que vous puissiez modifier la valeur en l'écrasant (avec des résultats indéterminés si vous changez le nom en celui d'une variable d'environnement trouvé à une autre position dans l'environnement). Donc, si vous avez alloué de l'espace, le classique putenv() le fuit si vous modifiez à nouveau la variable. Quand putenv() a commencé à copier des données, les variables allouées sont devenues non référencées car putenv() ne conservait plus de référence à l'argument, mais l'utilisateur s'attendait à ce que l'environnement y fasse référence, de sorte que la mémoire a été divulguée. Je ne sais pas quel était le correctif - je m'attendrais aux 3/4 à ce qu'il revienne à l'ancien comportement.

setenv() copie la chaîne mais je ne sais pas exactement comment cela fonctionne. L'espace pour l'environnement est alloué lors du chargement du processus, mais il est fixe.

L'espace de l'environnement d'origine est fixe ; lorsque vous commencez à le modifier, les règles changent. Même avec putenv() , l'environnement d'origine est modifié et peut s'agrandir suite à l'ajout de nouvelles variables ou suite à la modification de variables existantes pour avoir des valeurs plus longues.

Y a-t-il une convention (arbitraire ?) à l'œuvre ici ? Par exemple, allouer plus d'emplacements dans le tableau de pointeurs de chaîne env qu'actuellement utilisé et déplacer le pointeur de terminaison nul vers le bas si nécessaire ?

C'est ce que le setenv() mécanisme est susceptible de faire. La variable (globale) environ pointe vers le début du tableau de pointeurs vers les variables d'environnement. S'il pointe vers un bloc de mémoire à la fois et vers un bloc différent à un autre moment, alors l'environnement est changé, juste comme ça.

La mémoire pour la nouvelle chaîne (copiée) est-elle allouée dans l'espace d'adressage de l'environnement lui-même et si elle est trop grande pour tenir, vous obtenez juste ENOMEM ?

Eh bien, oui, vous pourriez obtenir ENOMEM, mais vous devriez essayer très fort. Et si vous agrandissez trop l'environnement, vous ne pourrez peut-être pas exécuter correctement d'autres programmes - soit l'environnement sera tronqué, soit l'opération d'exécution échouera.

Compte tenu des problèmes ci-dessus, y a-t-il une raison de préférer putenv() à setenv() ?

  • Utilisez setenv() dans le nouveau code.
  • Mettre à jour l'ancien code pour utiliser setenv() , mais n'en faites pas une priorité absolue.
  • Ne pas utiliser putenv() dans le nouveau code.

Il n'y a pas d'espace spécial "l'environnement" - setenv alloue dynamiquement de l'espace pour les chaînes (avec malloc par exemple) comme vous le feriez normalement. Étant donné que l'environnement ne contient aucune indication sur l'origine de chaque chaîne, il est impossible pour setenv ou unsetenv pour libérer tout espace qui peut avoir été alloué dynamiquement par des appels précédents à setenv.

"Parce qu'il ne copie pas la chaîne passée, vous ne pouvez pas l'appeler avec un local et il n'y a aucune garantie qu'une chaîne allouée au tas ne sera pas écrasée ou supprimée accidentellement." Le but de putenv est de s'assurer que si vous avez une chaîne allouée par tas, il est possible de la supprimer exprès . C'est ce que le texte de justification signifie par "la seule fonction disponible à ajouter à l'environnement sans permettre les fuites de mémoire". Et oui, vous pouvez l'appeler avec un local, supprimez simplement la chaîne de l'environnement (putenv("FOO=") ou unsetenv) avant de revenir de la fonction.

Le fait est que l'utilisation de putenv rend le processus de suppression d'une chaîne de l'environnement entièrement déterministe. Alors que setenv modifiera sur certaines implémentations existantes une chaîne existante dans l'environnement si la nouvelle valeur est plus courte (pour éviter toujours fuite de mémoire), et comme il a fait une copie lorsque vous avez appelé setenv, vous ne contrôlez pas la chaîne allouée dynamiquement à l'origine, vous ne pouvez donc pas la libérer lorsqu'elle est supprimée.

Pendant ce temps, setenv lui-même (ou unsetenv) ne peut pas libérer la chaîne précédente, car - même en ignorant putenv - la chaîne peut provenir de l'environnement d'origine au lieu d'être allouée par une invocation précédente de setenv.

(Cette réponse entière suppose un putenv correctement implémenté, c'est-à-dire pas celui de la glibc 2.0-2.1.1 que vous avez mentionné.)


Linux
  1. Coloriser votre environnement terminal et shell ?

  2. À propos de Mem et Vmem ?

  3. Qui définit les variables d'environnement $user et $username ?

  4. Variables d'environnement utilisateur avec "su" et "sudo" sous Linux

  5. Des questions sur IPTables et DHCP ?

20 questions et réponses d'entretien sur le serveur Red Hat Satellite

Comment définir et répertorier les variables d'environnement sous Linux

10 faits amusants sur Linus Torvalds et Linux

Comment définir et supprimer des variables d'environnement sous Linux

Meilleur environnement de bureau Linux :15 examinés et comparés

Meilleures combinaisons de distribution Linux et d'environnement de bureau