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

From: Paul Eggert <[email-masked]>
Date: Mon, 03 Dec 2012 12:32:50 -0800

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.  */