Fragen zu putenv() und setenv()

Lesezeit: 11 Minuten

Benutzeravatar von ValenceElectron
Valenzelektron

Ich habe ein wenig über Umgebungsvariablen nachgedacht und habe ein paar Fragen/Beobachtungen.

  • putenv(char *string);

    Dieser Aufruf scheint fatal fehlerhaft. Da die übergebene Zeichenfolge nicht kopiert wird, können Sie sie nicht lokal aufrufen, und es gibt keine Garantie dafür, dass eine dem Heap zugewiesene Zeichenfolge nicht überschrieben oder versehentlich gelöscht wird. Darüber hinaus (obwohl ich es nicht getestet habe) erscheint dies nutzlos, da eine Verwendung von Umgebungsvariablen darin besteht, Werte an die Umgebung des Kindes zu übergeben, wenn das Kind eine der aufruft exec*() Funktionen. Liege ich da falsch?

  • Die Linux-Manpage gibt an, dass glibc 2.0-2.1.1 das obige Verhalten aufgab und mit dem Kopieren der Zeichenfolge begann, aber dies führte zu einem Speicherleck, das in glibc 2.1.2 behoben wurde. Mir ist nicht klar, was dieses Speicherleck war oder wie es behoben wurde.

  • setenv() kopiert die Zeichenfolge, aber ich weiß nicht genau, wie das funktioniert. Speicherplatz für die Umgebung wird zugewiesen, wenn der Prozess geladen wird, aber er ist behoben. Ist hier eine (willkürliche?) Konvention am Werk? Zum Beispiel mehr Slots im Env-String-Zeiger-Array zuweisen als derzeit verwendet und den Null-Ende-Zeiger nach Bedarf nach unten verschieben? Ist der Speicher für den neuen (kopierten) String im Adressraum der Umgebung selbst allokiert und wenn er zu groß ist, holt man sich einfach ENOMEM?

  • Gibt es in Anbetracht der oben genannten Probleme einen Grund, dies zu bevorzugen? putenv() Über setenv()?

Benutzeravatar von Jonathan Leffler
Jonathan Leffler

  • [The] putenv(char *string); […] Aufruf scheint fatal fehlerhaft.

Ja, es ist fatal fehlerhaft. Es wurde in POSIX (1988) beibehalten, weil dies der Stand der Technik war. Das setenv() Mechanismus kam später. Korrektur: Der POSIX 1990-Standard sagt in §B.4.6.1 „Zusätzliche Funktionen putenv() und clearenv() wurden in Betracht gezogen, aber abgelehnt”. Die Einzelne Unix-Spezifikation (SUS) Version 2 von 1997 listet auf putenv() aber nicht setenv() oder unsetenv(). Die nächste Überarbeitung (2004) definierte beides setenv() und unsetenv() auch.

Da die übergebene Zeichenfolge nicht kopiert wird, können Sie sie nicht lokal aufrufen, und es gibt keine Garantie dafür, dass eine dem Heap zugewiesene Zeichenfolge nicht überschrieben oder versehentlich gelöscht wird.

Sie haben Recht, dass eine lokale Variable fast immer eine schlechte Wahl für die Übergabe ist putenv() — die Ausnahmen sind so dunkel, dass sie fast nicht mehr existieren. Wenn der String auf dem Heap allokiert wird (mit malloc() et al), müssen Sie sicherstellen, dass Ihr Code ihn nicht ändert. Wenn dies der Fall ist, verändert es gleichzeitig die Umgebung.

Darüber hinaus (obwohl ich es nicht getestet habe) erscheint dies nutzlos, da eine Verwendung von Umgebungsvariablen darin besteht, Werte an die Umgebung des Kindes zu übergeben, wenn das Kind eine der aufruft exec*() Funktionen. Liege ich da falsch?

Das exec*() Funktionen erstellen eine Kopie der Umgebung und übergeben diese an den ausgeführten Prozess. Da gibt es kein Problem.

Die Linux-Manpage gibt an, dass glibc 2.0-2.1.1 das obige Verhalten aufgab und mit dem Kopieren der Zeichenfolge begann, aber dies führte zu einem Speicherleck, das in glibc 2.1.2 behoben wurde. Mir ist nicht klar, was dieses Speicherleck war oder wie es behoben wurde.

Das Speicherleck entsteht, weil man einmal angerufen hat putenv() mit einer Zeichenfolge können Sie diese Zeichenfolge für keinen Zweck erneut verwenden, da Sie nicht feststellen können, ob sie noch verwendet wird, obwohl Sie den Wert ändern könnten, indem Sie ihn überschreiben (mit unbestimmten Ergebnissen, wenn Sie den Namen in den einer Umgebungsvariablen ändern an anderer Stelle in der Umgebung gefunden). Also, wenn Sie Platz zugeteilt haben, der Klassiker putenv() leckt es, wenn Sie die Variable erneut ändern. Wann putenv() begann, Daten zu kopieren, zugewiesene Variablen wurden unreferenziert, weil putenv() Es wurde kein Verweis mehr auf das Argument beibehalten, aber der Benutzer erwartete, dass die Umgebung darauf verweisen würde, sodass der Speicher verloren ging. Ich bin mir nicht sicher, was der Fix war – ich würde zu 3/4 erwarten, dass es zum alten Verhalten zurückkehrt.

setenv() kopiert die Zeichenfolge, aber ich weiß nicht genau, wie das funktioniert. Speicherplatz für die Umgebung wird zugewiesen, wenn der Prozess geladen wird, aber er ist behoben.

Der ursprüngliche Umgebungsraum ist festgelegt; Wenn Sie anfangen, es zu ändern, ändern sich die Regeln. Sogar mit putenv()wird die ursprüngliche Umgebung geändert und könnte durch das Hinzufügen neuer Variablen oder durch das Ändern vorhandener Variablen mit längeren Werten wachsen.

Ist hier eine (willkürliche?) Konvention am Werk? Zum Beispiel mehr Slots im Env-String-Zeiger-Array zuweisen als derzeit verwendet und den Null-Ende-Zeiger nach Bedarf nach unten verschieben?

Das ist, was die setenv() Mechanismus ist wahrscheinlich zu tun. Die (globale) Variable environ zeigt auf den Anfang des Arrays von Zeigern auf Umgebungsvariablen. Wenn es zu einem Zeitpunkt auf einen Speicherblock und zu einem anderen Zeitpunkt auf einen anderen Block zeigt, wird die Umgebung einfach so umgeschaltet.

Ist der Speicher für den neuen (kopierten) String im Adressraum der Umgebung selbst allokiert und wenn er zu groß ist, holt man sich einfach ENOMEM?

Nun, ja, Sie könnten ENOMEM bekommen, aber Sie müssten sich ziemlich anstrengen. Und wenn Sie die Umgebung zu groß machen, können Sie andere Programme möglicherweise nicht richtig ausführen – entweder wird die Umgebung abgeschnitten oder die Ausführungsoperation schlägt fehl.

Gibt es angesichts der oben genannten Probleme einen Grund, putenv() gegenüber setenv() zu bevorzugen?

  • Verwenden setenv() im neuen Code.
  • Aktualisieren Sie den alten Code, um ihn zu verwenden setenv()aber machen Sie es nicht zur obersten Priorität.
  • Verwende nicht putenv() im neuen Code.

  • Nur für den Fall: Wenn Sie anrufen putenv() mit einer lokalen Variablen, dann sollte es höchste Priorität haben, sie zu ersetzen putenv() mit setenv().

    – Yeow_Meng

    21. Oktober 2015 um 14:56 Uhr

  • @Yeow_Meng: Nun, irgendwie … der Code wurde vorher gebrochen, also ist es unwahrscheinlich, dass es viele Leute gibt, die das tun, einfach weil es gebrochen wäre.

    – Jonathan Leffler

    21. Oktober 2015 um 18:56 Uhr

  • @Yeow_Meng: Es sei denn, es gibt eine exec() (oder, unwahrscheinlich, ein exit()) im gleichen Umfang erreicht. Z.B github.com/apk/c-utils/blob/… ist ok und putenv ist hier die beste Wahl, denn die hast du ja schon NAME=value Schnur.

    – Andreas Krey

    17. Juni 2017 um 11:45 Uhr


  • @JonathanLeffler Nur ist es schlimmer als das… setenv() gefolgt von unsetenv() ist ein Speicherleck. setenv() gefolgt von setenv() mit dem gleichen Schlüssel kann auch lecken, plus/minus Qualität der Implementierung. putenv() mit Handbuch free() vor dem Überschreiben und zum Zeitpunkt von unsetenv() könnte auf leckagefreie Weise verwendet werden, aber dann müssten Sie verfolgen, welche Schlüssel-Wert-Paare tatsächlich Heap-zugewiesen sind, indem Sie eine externe Datenstruktur verwenden, oder undefiniertes Verhalten riskieren usw. – es ist ein Chaos. 🙂

    – Arne Vogel

    29. Mai 2018 um 16:16 Uhr


  • @ArneVogel — der setenv() und unsetenv() Funktionen sollten keine Speicherlecks verursachen. Intern für die *env() Funktionspaket gibt es viele unangenehme Details zu verwalten, wie Sie andeuten, und ihre Verwaltung erfordert mehr Informationen als nur das Array von Zeigern, die über verfügbar gemacht werden environ. Diese exponierte Variable öffnet eine Hintertür (obwohl eine Änderung undefiniertes Verhalten hervorruft). Es wird zu einem QoI-Problem (Qualität der Implementierung). Schlechte (aber einfache) Implementierungen verlieren leicht Speicher. Implementierungen mit höherer Qualität vermeiden die meisten Leaks, können aber durch schlecht benommene Programme zu Leaks gezwungen werden.

    – Jonathan Leffler

    23. August 2019 um 21:02 Uhr

Benutzeravatar von Random832
Zufällig832

Es gibt keinen speziellen “Umgebungs”-Speicherplatz – setenv weist einfach dynamisch Speicherplatz für die Zeichenfolgen zu (mit malloc zum Beispiel) wie Sie es normalerweise tun würden. Da die Umgebung keinen Hinweis darauf enthält, woher die einzelnen Zeichenfolgen stammen, ist dies unmöglich setenv oder unsetenv um Speicherplatz freizugeben, der möglicherweise durch vorherige Aufrufe von setenv dynamisch zugewiesen wurde.

“Da die übergebene Zeichenfolge nicht kopiert wird, können Sie sie nicht lokal aufrufen, und es gibt keine Garantie, dass eine dem Heap zugewiesene Zeichenfolge nicht überschrieben oder versehentlich gelöscht wird.” Der Zweck von putenv besteht darin, sicherzustellen, dass eine dem Heap zugewiesene Zeichenfolge gelöscht werden kann absichtlich. Das meint der Begründungstext mit „der einzigen verfügbaren Funktion, die der Umgebung hinzugefügt werden kann, ohne Speicherlecks zuzulassen“. Und ja, Sie können es mit einem lokalen aufrufen, entfernen Sie einfach die Zeichenfolge aus der Umgebung (putenv("FOO=") oder unsetenv), bevor Sie von der Funktion zurückkehren.

Der Punkt ist, dass die Verwendung von putenv den Prozess des Entfernens einer Zeichenfolge aus der Umgebung vollständig deterministisch macht. Während setenv bei einigen vorhandenen Implementierungen eine vorhandene Zeichenfolge in der Umgebung ändert, wenn der neue Wert kürzer ist (um zu vermeiden stets Speicherverlust), und da beim Aufrufen von setenv eine Kopie erstellt wurde, haben Sie keine Kontrolle über die ursprünglich dynamisch zugewiesene Zeichenfolge, sodass Sie sie nicht freigeben können, wenn sie entfernt wird.

Inzwischen setenv selbst (oder unsetenv) kann die vorherige Zeichenfolge nicht freigeben, da die Zeichenfolge – selbst wenn Sie putenv ignorieren – möglicherweise aus der ursprünglichen Umgebung stammt, anstatt durch einen vorherigen Aufruf von setenv zugewiesen zu werden.

(Diese ganze Antwort setzt ein korrekt implementiertes Putenv voraus, dh nicht die in glibc 2.0-2.1.1, die Sie erwähnt haben.)

Benutzeravatar von jim mcnamara
Jim Mcnamara

Lies das BEGRÜNDUNG Abschnitt der setenv man-Seite von The Open Group Base Specifications Ausgabe 6.

putenv und setenv sollen beide POSIX-kompatibel sein. Wenn Sie Code mit haben putenv darin, und der Code funktioniert gut, lassen Sie es in Ruhe. Wenn Sie neuen Code entwickeln, sollten Sie dies berücksichtigen setenv.

Schaue auf die glibc-Quellcode wenn Sie ein Beispiel für eine Implementierung von sehen möchten setenv (stdlib/setenv.c) oder putenv (stdlib/putenv.c).

Darüber hinaus (obwohl ich es nicht getestet habe) erscheint dies nutzlos, da eine Verwendung von Umgebungsvariablen darin besteht, Werte an die Umgebung des Kindes zu übergeben, wenn das Kind eine der exec() -Funktionen aufruft. Liege ich da falsch?

So wird die Umwelt nicht an das Kind weitergegeben. All die verschiedenen Geschmacksrichtungen von exec() (die Sie in Abschnitt 3 des Handbuchs finden, da es sich um Bibliotheksfunktionen handelt) rufen letztendlich den Systemaufruf auf execve() (die Sie in Abschnitt 2 des Handbuchs finden). Die Argumente sind:

   int execve(const char *filename, char *const argv[], char *const envp[]);

Der Vektor der Umgebungsvariablen wird explizit übergeben (und kann teilweise aus den Ergebnissen Ihrer putenv() und setenv() Anrufe). Der Kernel kopiert diese in den Adressraum des neuen Prozesses. In der Vergangenheit gab es eine Begrenzung der Größe Ihrer Umgebung, die sich aus dem für diese Kopie verfügbaren Speicherplatz ergab (ähnlich der Argumentbegrenzung), aber ich bin mit den Einschränkungen eines modernen Linux-Kernels nicht vertraut.

Ich würde dringend davon abraten, eine dieser Funktionen zu verwenden. Entweder kann sicher und ohne Lecks verwendet werden, solange Sie vorsichtig sind und nur ein Teil Ihres Codes für die Änderung der Umgebung verantwortlich ist, aber es ist schwer richtig zu machen und gefährlich, wenn irgendein Code Threads verwendet und die Umgebung lesen könnte (zB für Zeitzone, Gebietsschema, DNS-Konfiguration usw.).

Die einzigen zwei Zwecke, die mir zum Ändern der Umgebung einfallen, sind das Ändern der Zeitzone zur Laufzeit oder das Übergeben einer geänderten Umgebung an untergeordnete Prozesse. Für Ersteres müssen Sie wahrscheinlich eine dieser Funktionen verwenden (setenv/putenv), oder Sie könnten zu Fuß gehen environ manuell zu ändern (this könnte sicherer sein, wenn Sie befürchten, andere Threads könnten gleichzeitig versuchen, die Umgebung zu lesen). Verwenden Sie für die letztere Verwendung (untergeordnete Prozesse) eine der exec-Familienfunktionen, mit denen Sie Ihr eigenes Umgebungsarray angeben können, oder einfach clobber environ (das globale) oder verwenden setenv/putenv im untergeordneten Prozess nach fork Aber vorher execin diesem Fall müssen Sie sich nicht um Speicherlecks oder Thread-Sicherheit kümmern, da es keine anderen Threads gibt und Sie im Begriff sind, Ihren Adressraum zu zerstören und durch ein neues Prozessabbild zu ersetzen.

  • das ist vielleicht ein bisschen off-topic, aber wenn du es verwendest vfork() ein Kind zu forken, dann die Umgebung zu ändern exec()würde die übergeordnete Umgebung geändert werden?

    Benutzer339222

    6. November 2012 um 9:02 Uhr

  • Es ist undefiniert. Im schlimmsten Fall ändern Sie es nicht nur, sondern korrumpieren den Zustand des übergeordneten Elements auf schreckliche Weise. Dies könnte beispielsweise passieren, wenn Sie verwenden setenv in dem vfork Kind und setenv Anrufe malloc. Denken Sie einfach nicht einmal daran, so etwas zu tun. Beachten Sie, dass es keinen Grund gibt, die Umgebung vor dem Aufruf zu ändern exec; Verwenden Sie einfach eine der Formen von exec damit können Sie einen neuen Umgebungszeiger übergeben.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    6. November 2012 um 12:55 Uhr

  • das ist vielleicht ein bisschen off-topic, aber wenn du es verwendest vfork() ein Kind zu forken, dann die Umgebung zu ändern exec()würde die übergeordnete Umgebung geändert werden?

    Benutzer339222

    6. November 2012 um 9:02 Uhr

  • Es ist undefiniert. Im schlimmsten Fall ändern Sie es nicht nur, sondern korrumpieren den Zustand des übergeordneten Elements auf schreckliche Weise. Dies könnte beispielsweise passieren, wenn Sie verwenden setenv in dem vfork Kind und setenv Anrufe malloc. Denken Sie einfach nicht einmal daran, so etwas zu tun. Beachten Sie, dass es keinen Grund gibt, die Umgebung vor dem Aufruf zu ändern exec; Verwenden Sie einfach eine der Formen von exec damit können Sie einen neuen Umgebungszeiger übergeben.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    6. November 2012 um 12:55 Uhr

1396990cookie-checkFragen zu putenv() und setenv()

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy