On 02/15/2013 03:16 PM, Michael Goffioul wrote:
On Fri, Feb 15, 2013 at 2:10 PM, Paul Eggert <egg...@cs.ucla.edu
<mailto:egg...@cs.ucla.edu>> wrote:

    On 02/15/2013 07:10 AM, Michael Goffioul wrote:
     > I guess the only solution to this problem is to always _putenv
    and not manipulate "environ" directly.

    Thanks, is that something you can write and contribute
    to the GNU project?  This sort of thing really requires
    Microsoft expertise, which I don't have.


I can give it a try, but not before a couple of days. Also to avoid
breaking things, what is the putenv module supposed to solve? From what
I understand, it is:
- make putenv("VARNAME") remove VARNAME from the environment
- make putenv("VARNAME=") create and empty environment variable
Is there anything else?

Reading the specs here [1], I don't think the bit: "In either case, the
string pointed to by /string/ shall become part of the environment, so
altering the string shall change the environment." is actually possible.
I believe _putenv ultimately calls SetEnvironmentVariable [2] and
nothing there says that altering the given string will effectively alter
the environment.

Michael.

[1] http://pubs.opengroup.org/onlinepubs/9699919799/functions/putenv.html
[2]
http://msdn.microsoft.com/en-us/library/windows/desktop/ms686206(v=vs.85).aspx

How about something like the attached patch? The program I used for testing (modified from Michael's, and with the modified putenv function included) is also attached.

I'm not sure I like the way I've nested the conditionals, or if it is even the right thing to do. Maybe it would be better to leave the original code as it was and just have a separate version for Windows systems that uses _putenv and SetEnvironmentVariable. I'm willing to rework this as necessary.

In any case, I think it is important to fix putenv so that we not have to do some tricks at the time of subprocess creation in order for the environment to be passed to subprocesses. Relying on that is likely to lead to unexpected results and problems that are hard to debug. I think it is unreasonable to expect that we can control the way all subprocesses are created.

jwe

diff --git a/lib/putenv.c b/lib/putenv.c
--- a/lib/putenv.c
+++ b/lib/putenv.c
@@ -34,6 +34,10 @@
 #include <string.h>
 #include <unistd.h>
 
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+# include <windows.h>
+#endif
+
 #if _LIBC
 # if HAVE_GNU_LD
 #  define environ __environ
@@ -67,6 +71,15 @@
 
   len = strlen (name);
 
+#if HAVE__PUTENV
+  {
+    char *name_x = malloc (len+2);
+    strcpy (name_x, name);
+    strcat (name_x, "=");
+    _putenv (name_x);
+    free (name_x);
+  }
+#else
   LOCK;
 
   ep = environ;
@@ -85,6 +98,7 @@
       ++ep;
 
   UNLOCK;
+#endif
 
   return 0;
 }
@@ -93,7 +107,7 @@
 /* Put STRING, which is of the form "NAME=VALUE", in the environment.
    If STRING contains no '=', then remove STRING from the environment.  */
 int
-putenv (char *string)
+my_putenv (char *string)
 {
   const char *const name_end = strchr (string, '=');
   register size_t size;
@@ -123,6 +137,18 @@
         return _putenv (string);
       else
         {
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+          int putenv_result;
+          char *val;
+          size_t len = strlen (string);
+          char *name_x = malloc (len + 1);
+          strcpy (name_x, string);
+          name_x[name_end - string] = '\0';
+          val = &name_x[name_end - string + 1];
+          putenv_result = !SetEnvironmentVariable (name_x, val);
+          free (name_x);
+          return putenv_result;
+#else
           /* _putenv ("NAME=") unsets NAME, so invoke _putenv ("NAME=x")
              to allocate the environ vector and then replace the new
              entry with "NAME=".  */
@@ -144,6 +170,7 @@
           free (name_x);
           __set_errno (putenv_errno);
           return putenv_result;
+#endif
         }
 #else
       static char **last_environ = NULL;
@@ -160,7 +187,13 @@
 #endif
     }
   else
-    *ep = string;
+    {
+#if HAVE__PUTENV
+      _putenv (string);
+#else
+      *ep = string;
+#endif
+    }
 
   return 0;
 }
#include <stdio.h>

/* Copyright (C) 1991, 1994, 1997-1998, 2000, 2003-2013 Free Software
   Foundation, Inc.

   NOTE: The canonical source of this file is maintained with the GNU C
   Library.  Bugs can be reported to bug-gl...@prep.ai.mit.edu.

   This program is free software: you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 3 of the License, or any
   later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

/* #include <config.h> */
#define HAVE__PUTENV 1

/* Specification.  */
#include <stdlib.h>

#include <stddef.h>

/* Include errno.h *after* sys/types.h to work around header problems
   on AIX 3.2.5.  */
#include <errno.h>
#ifndef __set_errno
# define __set_errno(ev) ((errno) = (ev))
#endif

#include <string.h>
#include <unistd.h>

#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
# include <windows.h>
#endif

#if _LIBC
# if HAVE_GNU_LD
#  define environ __environ
# else
extern char **environ;
# endif
#endif

#if _LIBC
/* This lock protects against simultaneous modifications of 'environ'.  */
# include <bits/libc-lock.h>
__libc_lock_define_initialized (static, envlock)
# define LOCK   __libc_lock_lock (envlock)
# define UNLOCK __libc_lock_unlock (envlock)
#else
# define LOCK
# define UNLOCK
#endif

static int
_unsetenv (const char *name)
{
  size_t len;
  char **ep;

  if (name == NULL || *name == '\0' || strchr (name, '=') != NULL)
    {
      __set_errno (EINVAL);
      return -1;
    }

  len = strlen (name);

#if HAVE__PUTENV
  {
    char *name_x = malloc (len+2);
    strcpy (name_x, name);
    strcat (name_x, "=");
    _putenv (name_x);
    free (name_x);
  }
#else
  LOCK;

  ep = environ;
  while (*ep != NULL)
    if (!strncmp (*ep, name, len) && (*ep)[len] == '=')
      {
        /* Found it.  Remove this pointer by moving later ones back.  */
        char **dp = ep;

        do
          dp[0] = dp[1];
        while (*dp++);
        /* Continue the loop in case NAME appears again.  */
      }
    else
      ++ep;

  UNLOCK;
#endif

  return 0;
}


/* Put STRING, which is of the form "NAME=VALUE", in the environment.
   If STRING contains no '=', then remove STRING from the environment.  */
int
my_putenv (char *string)
{
  const char *const name_end = strchr (string, '=');
  register size_t size;
  register char **ep;

  if (name_end == NULL)
    {
      /* Remove the variable from the environment.  */
      return _unsetenv (string);
    }

  size = 0;
  for (ep = environ; *ep != NULL; ++ep)
    if (!strncmp (*ep, string, name_end - string) &&
        (*ep)[name_end - string] == '=')
      break;
    else
      ++size;

  if (*ep == NULL)
    {
#if HAVE__PUTENV
      /* Rely on _putenv to allocate the new environment.  If other
         parts of the application use _putenv, the !HAVE__PUTENV code
         would fight over who owns the environ vector, causing a crash.  */
      if (name_end[1])
        return _putenv (string);
      else
        {
#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
          int putenv_result;
          char *val;
          size_t len = strlen (string);
          char *name_x = malloc (len + 1);
          strcpy (name_x, string);
          name_x[name_end - string] = '\0';
          val = &name_x[name_end - string + 1];
          putenv_result = !SetEnvironmentVariable (name_x, val);
          free (name_x);
          return putenv_result;
#else
          /* _putenv ("NAME=") unsets NAME, so invoke _putenv ("NAME=x")
             to allocate the environ vector and then replace the new
             entry with "NAME=".  */
          int putenv_result, putenv_errno;
          char *name_x = malloc (name_end - string + sizeof "=x");
          if (!name_x)
            return -1;
          memcpy (name_x, string, name_end - string + 1);
          name_x[name_end - string + 1] = 'x';
          name_x[name_end - string + 2] = 0;
          putenv_result = _putenv (name_x);
          putenv_errno = errno;
          for (ep = environ; *ep; ep++)
            if (*ep == name_x)
              {
                *ep = string;
                break;
              }
          free (name_x);
          __set_errno (putenv_errno);
          return putenv_result;
#endif
        }
#else
      static char **last_environ = NULL;
      char **new_environ = (char **) malloc ((size + 2) * sizeof (char *));
      if (new_environ == NULL)
        return -1;
      (void) memcpy ((void *) new_environ, (void *) environ,
                     size * sizeof (char *));
      new_environ[size] = (char *) string;
      new_environ[size + 1] = NULL;
      free (last_environ);
      last_environ = new_environ;
      environ = new_environ;
#endif
    }
  else
    {
#if HAVE__PUTENV
      _putenv (string);
#else
      *ep = string;
#endif
    }

  return 0;
}


int main (int argc, char **argv)
{
  if (my_putenv ("CONFTEST_putenv=val"))
    return 1;

  printf ("variable creation\n");
  printf ("internal: %s\n", getenv ("CONFTEST_putenv"));
  system ("echo external: %CONFTEST_putenv%");

  if (my_putenv ("CONFTEST_putenv=newval"))
    return 4;

  printf ("variable change with gnulib::putenv\n");
  printf ("internal: %s\n", getenv ("CONFTEST_putenv"));
  system ("echo external: %CONFTEST_putenv%");

  if (_putenv ("CONFTEST_putenv=newval"))
    return 5;

  printf ("variable change with _putenv\n");
  printf ("internal: %s\n", getenv ("CONFTEST_putenv"));
  system ("echo external: %CONFTEST_putenv%");

  /* Try to remove it.  */
  if (my_putenv ("CONFTEST_putenv"))
    return 2;

  /* Make sure it was deleted.  */
  if (getenv ("CONFTEST_putenv") != 0)
    return 3;

  /* Create an variable with an empty definition.  */
  if (my_putenv ("CONFTEST_putenv="))
    return 6;

  /* Make sure it was create.  */
  if (getenv ("CONFTEST_getenv") != 0)
    return 7;

  return 0;
}

Reply via email to