Subject: bug#13070: closed (Re: Use putenv+unsetenv instead of modifying environ directly)

From: [email-masked] (GNU bug Tracking System)
Date: Sat, 08 Dec 2012 17:22:02 +0000

Your bug report

#13070: Use putenv+unsetenv instead of modifying environ directly

which was filed against the emacs package, has been closed.

The explanation is attached below, along with your original report.
If you require more details, please reply to [email-masked].

-- 
13070: http://debbugs.gnu.org/cgi/bugreport.cgi?bug=13070
GNU Bug Tracking System
Contact [email-masked] with problems

Subject: Re: Use putenv+unsetenv instead of modifying environ directly
From: Paul Eggert <[email-masked]>
To: Eli Zaretskii <[email-masked]>
Cc: [email-masked], [email-masked]
Date: Sat, 08 Dec 2012 09:20:57 -0800
List-Id: <debbugs-submit.debbugs.gnu.org>

On 12/08/2012 03:42 AM, Eli Zaretskii wrote:
> Shouldn't we refrain from signaling memory_full when errno is EINVAL?
> I'd suggest an eassert in that case.  memory_full will emit a
> misleading diagnostic.

errno cannot be EINVAL, at least not on a POSIXish host:
all strings are allowed as arguments to putenv.
If Microsoft platforms are different it would
make sense to put in an eassert, in w32.c presumably.

I did see a minor problem in the w32.c implementation of unsetenv:

  /* MS docs says an environment variable cannot be longer than 32K.  */
  if (name_len > 32767)
    {
      errno = ENOMEM;
      return -1;
    }

unsetenv should return 0 in that case, not -1, since
the variable cannot possibly be in the environment.
This bug doesn't affect Emacs so it's not a pressing one.

I committed the patch as trunk bzr 111158 and am marking
this bug as fixed.

Subject: Use putenv+unsetenv instead of modifying environ directly
From: Paul Eggert <[email-masked]>
To: [email-masked]
Cc: Eli Zaretskii <[email-masked]>, Fabrice Popineau <[email-masked]>
Date: Mon, 03 Dec 2012 12:32:50 -0800
List-Id: <debbugs-submit.debbugs.gnu.org>

Tags: patch

The attached patch is in response to last week's thread on emacs-devel
<http://lists.gnu.org/archive/html/emacs-devel/2012-11/msg00514.html>.
It's relative to trunk bzr 111078.  Tested on Fedora 17.  I don't see
any issues related to the Microsoft port but I'm CC:ing this to Eli
and Fabrice just in case, as the original bug seems to be Windows-related.
=== modified file 'ChangeLog'
--- ChangeLog	2012-11-30 18:25:59 +0000
+++ ChangeLog	2012-12-03 20:03:22 +0000
@@ -1,3 +1,11 @@
+2012-12-03  Paul Eggert  <[email-masked]>
+
+	Use putenv+unsetenv instead of modifying environ directly.
+	* lib/putenv.c, lib/unsetenv.c, m4/putenv.m4, m4/setenv.m4:
+	New files, copied automatically from gnulib.
+	* lib/gnulib.mk, m4/gnulib-comp.m4: Regenerate.
+	* merge-gnulib (GNULIB_MODULES): Add putenv, unsetenv.
+
 2012-11-30  Paul Eggert  <[email-masked]>
 
 	Merge from gnulib for 'inline' (Bug#13040), incorporating:

=== modified file 'admin/ChangeLog'
--- admin/ChangeLog	2012-11-24 17:20:49 +0000
+++ admin/ChangeLog	2012-12-03 20:03:22 +0000
@@ -1,3 +1,8 @@
+2012-12-03  Paul Eggert  <[email-masked]>
+
+	Use putenv+unsetenv instead of modifying environ directly.
+	* merge-gnulib (GNULIB_MODULES): Add putenv, unsetenv.
+
 2012-11-24  Ken Brown  <[email-masked]>
 
 	* CPP-DEFINES (HAVE_MOUSE): Remove.

=== modified file 'admin/merge-gnulib'
--- admin/merge-gnulib	2012-11-17 22:12:47 +0000
+++ admin/merge-gnulib	2012-12-03 20:03:22 +0000
@@ -31,10 +31,10 @@
   dtoastr dtotimespec dup2 environ execinfo faccessat
   fcntl-h filemode getloadavg getopt-gnu gettime gettimeofday
   ignore-value intprops largefile lstat
-  manywarnings mktime pselect pthread_sigmask readlink
+  manywarnings mktime pselect pthread_sigmask putenv readlink
   socklen stat-time stdalign stdarg stdbool stdio
   strftime strtoimax strtoumax symlink sys_stat
-  sys_time time timer-time timespec-add timespec-sub utimens
+  sys_time time timer-time timespec-add timespec-sub unsetenv utimens
   warnings
 '

=== modified file 'lib/gnulib.mk'
--- lib/gnulib.mk	2012-11-30 18:25:59 +0000
+++ lib/gnulib.mk	2012-12-03 20:03:22 +0000
@@ -21,7 +21,7 @@
 # the same distribution terms as the rest of that program.
 #
 # Generated by gnulib-tool.
-# Reproduce by: gnulib-tool --import --dir=. --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=build-aux --avoid=at-internal --avoid=errno --avoid=fchdir --avoid=fcntl --avoid=fstat --avoid=malloc-posix --avoid=msvc-inval --avoid=msvc-nothrow --avoid=openat-die --avoid=openat-h --avoid=raise --avoid=save-cwd --avoid=select --avoid=sigprocmask --avoid=sys_types --avoid=threadlib --makefile-name=gnulib.mk --conditional-dependencies --no-libtool --macro-prefix=gl --no-vc-files alloca-opt c-ctype c-strcase careadlinkat close-stream crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512 dtoastr dtotimespec dup2 environ execinfo faccessat fcntl-h filemode getloadavg getopt-gnu gettime gettimeofday ignore-value intprops largefile lstat manywarnings mktime pselect pthread_sigmask readlink socklen stat-time stdalign stdarg stdbool stdio strftime strtoimax strtoumax symlink sys_stat sys_time time timer-time timespec-add timespec-sub utimens warnings
+# Reproduce by: gnulib-tool --import --dir=. --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=build-aux --avoid=at-internal --avoid=errno --avoid=fchdir --avoid=fcntl --avoid=fstat --avoid=malloc-posix --avoid=msvc-inval --avoid=msvc-nothrow --avoid=openat-die --avoid=openat-h --avoid=raise --avoid=save-cwd --avoid=select --avoid=sigprocmask --avoid=sys_types --avoid=threadlib --makefile-name=gnulib.mk --conditional-dependencies --no-libtool --macro-prefix=gl --no-vc-files alloca-opt c-ctype c-strcase careadlinkat close-stream crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512 dtoastr dtotimespec dup2 environ execinfo faccessat fcntl-h filemode getloadavg getopt-gnu gettime gettimeofday ignore-value intprops largefile lstat manywarnings mktime pselect pthread_sigmask putenv readlink socklen stat-time stdalign stdarg stdbool stdio strftime strtoimax strtoumax symlink sys_stat sys_time time timer-time timespec-add timespec-sub unsetenv utimens warnings

 MOSTLYCLEANFILES += core *.stackdump
@@ -439,6 +439,15 @@
 
 ## end   gnulib module pthread_sigmask
 
+## begin gnulib module putenv
+
+
+EXTRA_DIST += putenv.c
+
+EXTRA_libgnu_a_SOURCES += putenv.c
+
+## end   gnulib module putenv
+
 ## begin gnulib module readlink

@@ -1400,6 +1409,15 @@
 
 ## end   gnulib module unistd
 
+## begin gnulib module unsetenv
+
+
+EXTRA_DIST += unsetenv.c
+
+EXTRA_libgnu_a_SOURCES += unsetenv.c
+
+## end   gnulib module unsetenv
+
 ## begin gnulib module utimens
 
 libgnu_a_SOURCES += utimens.c

=== added file 'lib/putenv.c'
--- lib/putenv.c	1970-01-01 00:00:00 +0000
+++ lib/putenv.c	2012-12-03 20:03:22 +0000
@@ -0,0 +1,134 @@
+/* Copyright (C) 1991, 1994, 1997-1998, 2000, 2003-2012 Free Software
+   Foundation, Inc.
+
+   NOTE: The canonical source of this file is maintained with the GNU C
+   Library.  Bugs can be reported to [email-masked].
+
+   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>
+
+/* 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 _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);
+
+  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;
+
+  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
+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)
+    {
+      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;
+    }
+  else
+    *ep = string;
+
+  return 0;
+}

=== added file 'lib/unsetenv.c'
--- lib/unsetenv.c	1970-01-01 00:00:00 +0000
+++ lib/unsetenv.c	2012-12-03 20:03:22 +0000
@@ -0,0 +1,127 @@
+/* Copyright (C) 1992, 1995-2002, 2005-2012 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   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
+   (at your option) 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>
+
+/* Don't use __attribute__ __nonnull__ in this compilation unit.  Otherwise gcc
+   optimizes away the name == NULL test below.  */
+#define _GL_ARG_NONNULL(params)
+
+/* Specification.  */
+#include <stdlib.h>
+
+#include <errno.h>
+#if !_LIBC
+# define __set_errno(ev) ((errno) = (ev))
+#endif
+
+#include <string.h>
+#include <unistd.h>
+
+#if !_LIBC
+# define __environ      environ
+#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
+
+/* In the GNU C library we must keep the namespace clean.  */
+#ifdef _LIBC
+# define unsetenv __unsetenv
+#endif
+
+#if _LIBC || !HAVE_UNSETENV
+
+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);
+
+  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;
+
+  return 0;
+}
+
+#ifdef _LIBC
+# undef unsetenv
+weak_alias (__unsetenv, unsetenv)
+#endif
+
+#else /* HAVE_UNSETENV */
+
+# undef unsetenv
+# if !HAVE_DECL_UNSETENV
+#  if VOID_UNSETENV
+extern void unsetenv (const char *);
+#  else
+extern int unsetenv (const char *);
+#  endif
+# endif
+
+/* Call the underlying unsetenv, in case there is hidden bookkeeping
+   that needs updating beyond just modifying environ.  */
+int
+rpl_unsetenv (const char *name)
+{
+  int result = 0;
+  if (!name || !*name || strchr (name, '='))
+    {
+      errno = EINVAL;
+      return -1;
+    }
+  while (getenv (name))
+# if !VOID_UNSETENV
+    result =
+# endif
+      unsetenv (name);
+  return result;
+}
+
+#endif /* HAVE_UNSETENV */

=== modified file 'm4/gnulib-comp.m4'
--- m4/gnulib-comp.m4	2012-11-30 18:25:59 +0000
+++ m4/gnulib-comp.m4	2012-12-03 20:03:22 +0000
@@ -85,6 +85,7 @@
   # Code from module pathmax:
   # Code from module pselect:
   # Code from module pthread_sigmask:
+  # Code from module putenv:
   # Code from module readlink:
   # Code from module root-uid:
   # Code from module signal-h:
@@ -125,6 +126,7 @@
   # Code from module timespec-sub:
   # Code from module u64:
   # Code from module unistd:
+  # Code from module unsetenv:
   # Code from module utimens:
   # Code from module verify:
   # Code from module warnings:
@@ -239,6 +241,11 @@
     gl_PREREQ_PTHREAD_SIGMASK
   fi
   gl_SIGNAL_MODULE_INDICATOR([pthread_sigmask])
+  gl_FUNC_PUTENV
+  if test $REPLACE_PUTENV = 1; then
+    AC_LIBOBJ([putenv])
+  fi
+  gl_STDLIB_MODULE_INDICATOR([putenv])
   gl_FUNC_READLINK
   if test $HAVE_READLINK = 0 || test $REPLACE_READLINK = 1; then
     AC_LIBOBJ([readlink])
@@ -291,6 +298,12 @@
   gl_TIMER_TIME
   gl_TIMESPEC
   gl_UNISTD_H
+  gl_FUNC_UNSETENV
+  if test $HAVE_UNSETENV = 0 || test $REPLACE_UNSETENV = 1; then
+    AC_LIBOBJ([unsetenv])
+    gl_PREREQ_UNSETENV
+  fi
+  gl_STDLIB_MODULE_INDICATOR([unsetenv])
   gl_UTIMENS
   gl_gnulib_enabled_dosname=false
   gl_gnulib_enabled_euidaccess=false
@@ -673,6 +686,7 @@
   lib/pathmax.h
   lib/pselect.c
   lib/pthread_sigmask.c
+  lib/putenv.c
   lib/readlink.c
   lib/root-uid.h
   lib/sha1.c
@@ -715,6 +729,7 @@
   lib/u64.h
   lib/unistd.c
   lib/unistd.in.h
+  lib/unsetenv.c
   lib/utimens.c
   lib/utimens.h
   lib/verify.h
@@ -756,7 +771,9 @@
   m4/pathmax.m4
   m4/pselect.m4
   m4/pthread_sigmask.m4
+  m4/putenv.m4
   m4/readlink.m4
+  m4/setenv.m4
   m4/sha1.m4
   m4/sha256.m4
   m4/sha512.m4

=== added file 'm4/putenv.m4'
--- m4/putenv.m4	1970-01-01 00:00:00 +0000
+++ m4/putenv.m4	2012-12-03 20:03:22 +0000
@@ -0,0 +1,50 @@
+# putenv.m4 serial 19
+dnl Copyright (C) 2002-2012 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Jim Meyering.
+dnl
+dnl Check whether putenv ("FOO") removes FOO from the environment.
+dnl The putenv in libc on at least SunOS 4.1.4 does *not* do that.
+
+AC_DEFUN([gl_FUNC_PUTENV],
+[
+  AC_REQUIRE([gl_STDLIB_H_DEFAULTS])
+  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+  AC_CACHE_CHECK([for putenv compatible with GNU and SVID],
+   [gl_cv_func_svid_putenv],
+   [AC_RUN_IFELSE([AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT],[[
+    /* Put it in env.  */
+    if (putenv ("CONFTEST_putenv=val"))
+      return 1;
+
+    /* Try to remove it.  */
+    if (putenv ("CONFTEST_putenv"))
+      return 2;
+
+    /* Make sure it was deleted.  */
+    if (getenv ("CONFTEST_putenv") != 0)
+      return 3;
+
+    return 0;
+              ]])],
+             gl_cv_func_svid_putenv=yes,
+             gl_cv_func_svid_putenv=no,
+             dnl When crosscompiling, assume putenv is broken.
+             [case "$host_os" in
+                        # Guess yes on glibc systems.
+                *-gnu*) gl_cv_func_svid_putenv="guessing yes" ;;
+                        # If we don't know, assume the worst.
+                *)      gl_cv_func_svid_putenv="guessing no" ;;
+              esac
+             ])
+   ])
+  case "$gl_cv_func_svid_putenv" in
+    *yes) ;;
+    *)
+      REPLACE_PUTENV=1
+      ;;
+  esac
+])

=== added file 'm4/setenv.m4'
--- m4/setenv.m4	1970-01-01 00:00:00 +0000
+++ m4/setenv.m4	2012-12-03 20:03:22 +0000
@@ -0,0 +1,160 @@
+# setenv.m4 serial 26
+dnl Copyright (C) 2001-2004, 2006-2012 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_SETENV],
+[
+  AC_REQUIRE([gl_FUNC_SETENV_SEPARATE])
+  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+  if test $ac_cv_func_setenv = no; then
+    HAVE_SETENV=0
+  else
+    AC_CACHE_CHECK([whether setenv validates arguments],
+      [gl_cv_func_setenv_works],
+      [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+       #include <stdlib.h>
+       #include <errno.h>
+       #include <string.h>
+      ]], [[
+       int result = 0;
+       {
+         if (setenv ("", "", 0) != -1)
+           result |= 1;
+         else if (errno != EINVAL)
+           result |= 2;
+       }
+       {
+         if (setenv ("a", "=", 1) != 0)
+           result |= 4;
+         else if (strcmp (getenv ("a"), "=") != 0)
+           result |= 8;
+       }
+       return result;
+      ]])],
+      [gl_cv_func_setenv_works=yes], [gl_cv_func_setenv_works=no],
+      [case "$host_os" in
+                 # Guess yes on glibc systems.
+         *-gnu*) gl_cv_func_setenv_works="guessing yes" ;;
+                 # If we don't know, assume the worst.
+         *)      gl_cv_func_setenv_works="guessing no" ;;
+       esac
+      ])])
+    case "$gl_cv_func_setenv_works" in
+      *yes) ;;
+      *)
+        REPLACE_SETENV=1
+        ;;
+    esac
+  fi
+])
+
+# Like gl_FUNC_SETENV, except prepare for separate compilation
+# (no REPLACE_SETENV, no AC_LIBOBJ).
+AC_DEFUN([gl_FUNC_SETENV_SEPARATE],
+[
+  AC_REQUIRE([gl_STDLIB_H_DEFAULTS])
+  AC_CHECK_DECLS_ONCE([setenv])
+  if test $ac_cv_have_decl_setenv = no; then
+    HAVE_DECL_SETENV=0
+  fi
+  AC_CHECK_FUNCS_ONCE([setenv])
+  gl_PREREQ_SETENV
+])
+
+AC_DEFUN([gl_FUNC_UNSETENV],
+[
+  AC_REQUIRE([gl_STDLIB_H_DEFAULTS])
+  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+  AC_CHECK_DECLS_ONCE([unsetenv])
+  if test $ac_cv_have_decl_unsetenv = no; then
+    HAVE_DECL_UNSETENV=0
+  fi
+  AC_CHECK_FUNCS([unsetenv])
+  if test $ac_cv_func_unsetenv = no; then
+    HAVE_UNSETENV=0
+  else
+    HAVE_UNSETENV=1
+    dnl Some BSDs return void, failing to do error checking.
+    AC_CACHE_CHECK([for unsetenv() return type], [gt_cv_func_unsetenv_ret],
+      [AC_COMPILE_IFELSE(
+         [AC_LANG_PROGRAM(
+            [[
+#undef _BSD
+#define _BSD 1 /* unhide unsetenv declaration in OSF/1 5.1 <stdlib.h> */
+#include <stdlib.h>
+extern
+#ifdef __cplusplus
+"C"
+#endif
+int unsetenv (const char *name);
+            ]],
+            [[]])],
+         [gt_cv_func_unsetenv_ret='int'],
+         [gt_cv_func_unsetenv_ret='void'])])
+    if test $gt_cv_func_unsetenv_ret = 'void'; then
+      AC_DEFINE([VOID_UNSETENV], [1], [Define to 1 if unsetenv returns void
+       instead of int.])
+      REPLACE_UNSETENV=1
+    fi
+
+    dnl Solaris 10 unsetenv does not remove all copies of a name.
+    dnl Haiku alpha 2 unsetenv gets confused by assignment to environ.
+    dnl OpenBSD 4.7 unsetenv("") does not fail.
+    AC_CACHE_CHECK([whether unsetenv obeys POSIX],
+      [gl_cv_func_unsetenv_works],
+      [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+       #include <stdlib.h>
+       #include <errno.h>
+       extern char **environ;
+      ]], [[
+       char entry1[] = "a=1";
+       char entry2[] = "b=2";
+       char *env[] = { entry1, entry2, NULL };
+       if (putenv ((char *) "a=1")) return 1;
+       if (putenv (entry2)) return 2;
+       entry2[0] = 'a';
+       unsetenv ("a");
+       if (getenv ("a")) return 3;
+       if (!unsetenv ("") || errno != EINVAL) return 4;
+       entry2[0] = 'b';
+       environ = env;
+       if (!getenv ("a")) return 5;
+       entry2[0] = 'a';
+       unsetenv ("a");
+       if (getenv ("a")) return 6;
+      ]])],
+      [gl_cv_func_unsetenv_works=yes], [gl_cv_func_unsetenv_works=no],
+      [case "$host_os" in
+                 # Guess yes on glibc systems.
+         *-gnu*) gl_cv_func_unsetenv_works="guessing yes" ;;
+                 # If we don't know, assume the worst.
+         *)      gl_cv_func_unsetenv_works="guessing no" ;;
+       esac
+      ])])
+    case "$gl_cv_func_unsetenv_works" in
+      *yes) ;;
+      *)
+        REPLACE_UNSETENV=1
+        ;;
+    esac
+  fi
+])
+
+# Prerequisites of lib/setenv.c.
+AC_DEFUN([gl_PREREQ_SETENV],
+[
+  AC_REQUIRE([AC_FUNC_ALLOCA])
+  AC_REQUIRE([gl_ENVIRON])
+  AC_CHECK_HEADERS_ONCE([unistd.h])
+  AC_CHECK_HEADERS([search.h])
+  AC_CHECK_FUNCS([tsearch])
+])
+
+# Prerequisites of lib/unsetenv.c.
+AC_DEFUN([gl_PREREQ_UNSETENV],
+[
+  AC_REQUIRE([gl_ENVIRON])
+  AC_CHECK_HEADERS_ONCE([unistd.h])
+])

=== modified file 'src/ChangeLog'
--- src/ChangeLog	2012-12-03 19:16:17 +0000
+++ src/ChangeLog	2012-12-03 20:07:13 +0000
@@ -1,3 +1,27 @@
+2012-12-03  Paul Eggert  <[email-masked]>
+
+	Use putenv+unsetenv instead of modifying environ directly.
+	* alloc.c (xputenv): New function.
+	* dbusbind.c (Fdbus_init_bus):
+	* emacs.c (main):
+	* xterm.c (x_term_init):
+	Use xputenv instead of setenv or putenv, to detect memory exhaustion.
+	* editfns.c (initial_tz): Move static var decl up.
+	(tzvalbuf_in_environ): New static var.
+	(init_editfns):	Initialize these two static vars.
+	(Fencode_time): Don't assume arbitrary limit on EMACS_INT width.
+	Save old TZ value on stack, if it's small.
+	(Fencode_time, set_time_zone_rule): Don't modify 'environ' directly;
+	instead, use xputenv+unsetenv to set and restore TZ.
+	(environbuf): Remove static var.  All uses removed.
+	(Fset_time_zone_rule): Do not save TZ and environ; no longer needed here.
+	(set_time_zone_rule_tz1, set_time_zone_rule_tz2) [LOCALTIME_CACHE]:
+	Move to inside set_time_zone_rule; they don't need file scope any more.
+	(set_time_zone_rule): Maintain the TZ=value string separately.
+	(syms_of_editfns): Don't initialize initial_tz; init_editfns now does it.
+	* emacs.c (dump_tz) [HAVE_TZSET]: Now const.
+	* lisp.h (xputenv): New decl.
+
 2012-12-03  Jan Djärv  <[email-masked]>
 
 	* gtkutil.c (my_log_handler): New function.

=== modified file 'src/alloc.c'
--- src/alloc.c	2012-12-03 08:06:02 +0000
+++ src/alloc.c	2012-12-03 20:07:13 +0000
@@ -820,6 +820,15 @@
   return p;
 }
 
+/* Like putenv, but (1) use the equivalent of xmalloc and (2) the
+   argument is a const pointer.  */
+
+void
+xputenv (char const *string)
+{
+  if (putenv ((char *) string) != 0)
+    memory_full (0);
+}
 
 /* Unwind for SAFE_ALLOCA */

=== modified file 'src/dbusbind.c'
--- src/dbusbind.c	2012-10-31 11:45:40 +0000
+++ src/dbusbind.c	2012-12-03 20:03:22 +0000
@@ -1203,7 +1203,7 @@
       xd_registered_buses = Fcons (Fcons (bus, val), xd_registered_buses);
 
       /* We do not want to abort.  */
-      putenv ((char *) "DBUS_FATAL_WARNINGS=0");
+      xputenv ("DBUS_FATAL_WARNINGS=0");
 
       /* Cleanup.  */
       dbus_error_free (&derror);

=== modified file 'src/editfns.c'
--- src/editfns.c	2012-12-03 14:13:06 +0000
+++ src/editfns.c	2012-12-03 20:07:13 +0000
@@ -78,6 +78,15 @@
 
 static Lisp_Object Qboundary;
 
+/* The startup value of the TZ environment variable so it can be
+   restored if the user calls set-time-zone-rule with a nil
+   argument.  If null, the TZ environment variable was unset.  */
+static char const *initial_tz;
+
+/* True if the static variable tzvalbuf (defined in
+   set_time_zone_rule) is part of 'environ'.  */
+static bool tzvalbuf_in_environ;
+
 
 void
 init_editfns (void)
@@ -96,6 +105,9 @@
     return;
 #endif /* not CANNOT_DUMP */
 
+  initial_tz = getenv ("TZ");
+  tzvalbuf_in_environ = 0;
+
   pw = getpwuid (getuid ());
 #ifdef MSDOS
   /* We let the real user name default to "root" because that's quite
@@ -1900,9 +1912,11 @@
     }
   else
     {
-      char tzbuf[100];
+      static char const tzbuf_format[] = "XXX%s%"pI"d:%02d:%02d";
+      char tzbuf[sizeof tzbuf_format + INT_STRLEN_BOUND (EMACS_INT)];
+      char *old_tzstring;
       const char *tzstring;
-      char **oldenv = environ, **newenv;
+      USE_SAFE_ALLOCA;
 
       if (EQ (zone, Qt))
 	tzstring = "UTC0";
@@ -1914,13 +1928,20 @@
 	  EMACS_INT zone_hr = abszone / (60*60);
 	  int zone_min = (abszone/60) % 60;
 	  int zone_sec = abszone % 60;
-	  sprintf (tzbuf, "XXX%s%"pI"d:%02d:%02d", "-" + (XINT (zone) < 0),
+	  sprintf (tzbuf, tzbuf_format, "-" + (XINT (zone) < 0),
 		   zone_hr, zone_min, zone_sec);
 	  tzstring = tzbuf;
 	}
       else
 	error ("Invalid time zone specification");
 
+      old_tzstring = getenv ("TZ");
+      if (old_tzstring)
+	{
+	  char *buf = SAFE_ALLOCA (strlen (old_tzstring) + 1);
+	  old_tzstring = strcpy (buf, old_tzstring);
+	}
+
       block_input ();
 
       /* Set TZ before calling mktime; merely adjusting mktime's returned
@@ -1929,15 +1950,12 @@
 
       value = mktime (&tm);
 
-      /* Restore TZ to previous value.  */
-      newenv = environ;
-      environ = oldenv;
+      set_time_zone_rule (old_tzstring);
 #ifdef LOCALTIME_CACHE
       tzset ();
 #endif
       unblock_input ();
-
-      xfree (newenv);
+      SAFE_FREE ();
     }
 
   if (value == (time_t) -1)
@@ -2067,16 +2085,6 @@
   return list2 (zone_offset, zone_name);
 }
 
-/* This holds the value of `environ' produced by the previous
-   call to Fset_time_zone_rule, or 0 if Fset_time_zone_rule
-   has never been called.  */
-static char **environbuf;
-
-/* This holds the startup value of the TZ environment variable so it
-   can be restored if the user calls set-time-zone-rule with a nil
-   argument.  */
-static char *initial_tz;
-
 DEFUN ("set-time-zone-rule", Fset_time_zone_rule, Sset_time_zone_rule, 1, 1, 0,
        doc: /* Set the local time zone using TZ, a string specifying a time zone rule.
 If TZ is nil, use implementation-defined default time zone information.
@@ -2089,18 +2097,10 @@
   (Lisp_Object tz)
 {
   const char *tzstring;
-  char **old_environbuf;
 
   if (! (NILP (tz) || EQ (tz, Qt)))
     CHECK_STRING (tz);
 
-  block_input ();
-
-  /* When called for the first time, save the original TZ.  */
-  old_environbuf = environbuf;
-  if (!old_environbuf)
-    initial_tz = (char *) getenv ("TZ");
-
   if (NILP (tz))
     tzstring = initial_tz;
   else if (EQ (tz, Qt))
@@ -2108,106 +2108,97 @@
   else
     tzstring = SSDATA (tz);
 
+  block_input ();
   set_time_zone_rule (tzstring);
-  environbuf = environ;
-
   unblock_input ();
 
-  xfree (old_environbuf);
   return Qnil;
 }
 
-#ifdef LOCALTIME_CACHE
-
-/* These two values are known to load tz files in buggy implementations,
-   i.e. Solaris 1 executables running under either Solaris 1 or Solaris 2.
-   Their values shouldn't matter in non-buggy implementations.
-   We don't use string literals for these strings,
-   since if a string in the environment is in readonly
-   storage, it runs afoul of bugs in SVR4 and Solaris 2.3.
-   See Sun bugs 1113095 and 1114114, ``Timezone routines
-   improperly modify environment''.  */
-
-static char set_time_zone_rule_tz1[] = "TZ=GMT+0";
-static char set_time_zone_rule_tz2[] = "TZ=GMT+1";
-
-#endif
-
 /* Set the local time zone rule to TZSTRING.
-   This allocates memory into `environ', which it is the caller's
-   responsibility to free.  */
+
+   This function is not thread-safe, partly because putenv, unsetenv
+   and tzset are not, and partly because of the static storage it
+   updates.  Other threads that invoke localtime etc. may be adversely
+   affected while this function is executing.  */
 
 void
 set_time_zone_rule (const char *tzstring)
 {
-  ptrdiff_t envptrs;
-  char **from, **to, **newenv;
-
-  /* Make the ENVIRON vector longer with room for TZSTRING.  */
-  for (from = environ; *from; from++)
-    continue;
-  envptrs = from - environ + 2;
-  newenv = to = xmalloc (envptrs * sizeof *newenv
-			 + (tzstring ? strlen (tzstring) + 4 : 0));
-
-  /* Add TZSTRING to the end of environ, as a value for TZ.  */
+  /* A buffer holding a string of the form "TZ=value", intended
+     to be part of the environment.  */
+  static char *tzvalbuf;
+  static ptrdiff_t tzvalbufsize;
+
+  int tzeqlen = sizeof "TZ=" - 1;
+
+#ifdef LOCALTIME_CACHE
+  /* These two values are known to load tz files in buggy implementations,
+     i.e. Solaris 1 executables running under either Solaris 1 or Solaris 2.
+     Their values shouldn't matter in non-buggy implementations.
+     We don't use string literals for these strings,
+     since if a string in the environment is in readonly
+     storage, it runs afoul of bugs in SVR4 and Solaris 2.3.
+     See Sun bugs 1113095 and 1114114, ``Timezone routines
+     improperly modify environment''.  */
+
+  static char set_time_zone_rule_tz[][sizeof "TZ=GMT+0"]
+    = { "TZ=GMT+0", "TZ=GMT+1" };
+
+  /* In SunOS 4.1.3_U1 and 4.1.4, if TZ has a value like
+     "US/Pacific" that loads a tz file, then changes to a value like
+     "XXX0" that does not load a tz file, and then changes back to
+     its original value, the last change is (incorrectly) ignored.
+     Also, if TZ changes twice in succession to values that do
+     not load a tz file, tzset can dump core (see Sun bug#1225179).
+     The following code works around these bugs.  */
+
   if (tzstring)
     {
-      char *t = (char *) (to + envptrs);
-      strcpy (t, "TZ=");
-      strcat (t, tzstring);
-      *to++ = t;
-    }
-
-  /* Copy the old environ vector elements into NEWENV,
-     but don't copy the TZ variable.
-     So we have only one definition of TZ, which came from TZSTRING.  */
-  for (from = environ; *from; from++)
-    if (strncmp (*from, "TZ=", 3) != 0)
-      *to++ = *from;
-  *to = 0;
-
-  environ = newenv;
-
-  /* If we do have a TZSTRING, NEWENV points to the vector slot where
-     the TZ variable is stored.  If we do not have a TZSTRING,
-     TO points to the vector slot which has the terminating null.  */
+      /* Temporarily set TZ to a value that loads a tz file
+	 and that differs from tzstring.  */
+      bool eq0 = strcmp (tzstring, set_time_zone_rule_tz[0] + tzeqlen) == 0;
+      xputenv (set_time_zone_rule_tz[eq0]);
+    }
+  else
+    {
+      /* The implied tzstring is unknown, so temporarily set TZ to
+	 two different values that each load a tz file.  */
+      xputenv (set_time_zone_rule_tz[0]);
+      tzset ();
+      xputenv (set_time_zone_rule_tz[1]);
+    }
+  tzset ();
+#endif
+
+  if (!tzstring)
+    {
+      unsetenv ("TZ");
+      tzvalbuf_in_environ = 0;
+    }
+  else
+    {
+      ptrdiff_t tzstringlen = strlen (tzstring);
+
+      if (tzvalbufsize <= tzeqlen + tzstringlen)
+	{
+	  unsetenv ("TZ");
+	  tzvalbuf_in_environ = 0;
+	  tzvalbuf = xpalloc (tzvalbuf, &tzvalbufsize,
+			      tzeqlen + tzstringlen - tzvalbufsize + 1, -1, 1);
+	  memcpy (tzvalbuf, "TZ=", tzeqlen);
+	}
+
+      strcpy (tzvalbuf + tzeqlen, tzstring);
+
+      if (!tzvalbuf_in_environ)
+	{
+	  xputenv (tzvalbuf);
+	  tzvalbuf_in_environ = 1;
+	}
+    }
 
 #ifdef LOCALTIME_CACHE
-  {
-    /* In SunOS 4.1.3_U1 and 4.1.4, if TZ has a value like
-       "US/Pacific" that loads a tz file, then changes to a value like
-       "XXX0" that does not load a tz file, and then changes back to
-       its original value, the last change is (incorrectly) ignored.
-       Also, if TZ changes twice in succession to values that do
-       not load a tz file, tzset can dump core (see Sun bug#1225179).
-       The following code works around these bugs.  */
-
-    if (tzstring)
-      {
-	/* Temporarily set TZ to a value that loads a tz file
-	   and that differs from tzstring.  */
-	char *tz = *newenv;
-	*newenv = (strcmp (tzstring, set_time_zone_rule_tz1 + 3) == 0
-		   ? set_time_zone_rule_tz2 : set_time_zone_rule_tz1);
-	tzset ();
-	*newenv = tz;
-      }
-    else
-      {
-	/* The implied tzstring is unknown, so temporarily set TZ to
-	   two different values that each load a tz file.  */
-	*to = set_time_zone_rule_tz1;
-	to[1] = 0;
-	tzset ();
-	*to = set_time_zone_rule_tz2;
-	tzset ();
-	*to = 0;
-      }
-
-    /* Now TZ has the desired value, and tzset can be invoked safely.  */
-  }
-
   tzset ();
 #endif
 }
@@ -4799,9 +4790,6 @@
 void
 syms_of_editfns (void)
 {
-  environbuf = 0;
-  initial_tz = 0;
-
   DEFSYM (Qbuffer_access_fontify_functions, "buffer-access-fontify-functions");
 
   DEFVAR_LISP ("inhibit-field-text-motion", Vinhibit_field_text_motion,

=== modified file 'src/emacs.c'
--- src/emacs.c	2012-11-21 04:47:55 +0000
+++ src/emacs.c	2012-12-03 20:03:22 +0000
@@ -535,7 +535,7 @@
 #ifdef HAVE_TZSET
 /* A valid but unlikely value for the TZ environment value.
    It is OK (though a bit slower) if the user actually chooses this value.  */
-static char dump_tz[] = "UtC0";
+static char const dump_tz[] = "UtC0";
 #endif
 
 #ifndef ORDINARY_LINK
@@ -717,7 +717,7 @@
 
 #ifdef G_SLICE_ALWAYS_MALLOC
   /* This is used by the Cygwin build.  */
-  setenv ("G_SLICE", "always-malloc", 1);
+  xputenv ("G_SLICE=always-malloc");
 #endif
 
 #ifdef GNU_LINUX
@@ -803,9 +803,8 @@
 #ifdef HAVE_PERSONALITY_LINUX32
   if (dumping && ! getenv ("EMACS_HEAP_EXEC"))
     {
-      static char heapexec[] = "EMACS_HEAP_EXEC=true";
       /* Set this so we only do this once.  */
-      putenv (heapexec);
+      xputenv ("EMACS_HEAP_EXEC=true");
 
       /* A flag to turn off address randomization which is introduced
          in linux kernel shipped with fedora core 4 */
@@ -1309,7 +1308,7 @@
      don't pollute Vglobal_environment.  */
   /* Setting LANG here will defeat the startup locale processing...  */
 #ifdef AIX
-  putenv ("LANG=C");
+  xputenv ("LANG=C");
 #endif
 
   init_buffer ();	/* Init default directory of main buffer.  */

=== modified file 'src/lisp.h'
--- src/lisp.h	2012-12-03 14:13:06 +0000
+++ src/lisp.h	2012-12-03 20:07:13 +0000
@@ -3594,6 +3594,7 @@
 extern void *xpalloc (void *, ptrdiff_t *, ptrdiff_t, ptrdiff_t, ptrdiff_t);
 
 extern char *xstrdup (const char *);
+extern void xputenv (const char *);
 
 extern char *egetenv (const char *);

=== modified file 'src/xterm.c'
--- src/xterm.c	2012-12-01 23:06:14 +0000
+++ src/xterm.c	2012-12-03 20:03:22 +0000
@@ -9909,10 +9909,7 @@
 
         /* Emacs can only handle core input events, so make sure
            Gtk doesn't use Xinput or Xinput2 extensions.  */
-        {
-          static char fix_events[] = "GDK_CORE_DEVICE_EVENTS=1";
-          putenv (fix_events);
-        }
+	xputenv ("GDK_CORE_DEVICE_EVENTS=1");
 
         /* Work around GLib bug that outputs a faulty warning. See
            https://bugzilla.gnome.org/show_bug.cgi?id=563627.  */