changeset 12300:c8288fd3f281

setenv, unsetenv: work around various bugs POSIX requires setenv(NULL,"",0), setenv("a=b","",0), unsetenv(NULL), and unsetenv("a=b") to fail with EINVAL, but many BSD implementations lack validation. The gnulib replacement for void unsetenv did not do validation, and the replacement for setenv was out of sync with glibc. Also, some BSD implementations of setenv("a","==",1) eat the leading '='. See also some recent Austin Group interpretations on environ: http://austingroupbugs.net/view.php?id=167 http://austingroupbugs.net/view.php?id=185 * lib/setenv.c (setenv) [!HAVE_SETENV]: Resync from glibc. (setenv) [HAVE_SETENV]: Work around bugs. * lib/unsetenv.c (unsetenv) [HAVE_UNSETENV]: Work around bugs. * m4/setenv.m4 (gl_FUNC_SETENV_SEPARATE, gl_FUNC_UNSETENV): Check for bugs. (gl_FUNC_SETENV): Write in terms of gl_FUNC_SETENV_SEPARATE. * m4/environ.m4 (gl_ENVIRON): Avoid expand-before-require. * m4/stdlib_h.m4 (gl_STDLIB_H_DEFAULTS): Update defaults. * modules/stdlib (Makefile.am): Update substitutions. * lib/stdlib.in.h (setenv, unsetenv): Update prototypes. * doc/posix-functions/setenv.texi (setenv): Document the bugs. * doc/posix-functions/unsetenv.texi (unsetenv): Likewise. * modules/setenv-tests: New test. * modules/unsetenv-tests: Likewise. * tests/test-setenv.c: New file. * tests/test-unsetenv.c: Likewise. Signed-off-by: Eric Blake <ebb9@byu.net>
author Eric Blake <ebb9@byu.net>
date Sat, 14 Nov 2009 22:13:10 -0700
parents 7bc45f101cfd
children 8cf4c5e7274b
files ChangeLog doc/posix-functions/setenv.texi doc/posix-functions/unsetenv.texi lib/setenv.c lib/stdlib.in.h lib/unsetenv.c m4/environ.m4 m4/setenv.m4 m4/stdlib_h.m4 modules/setenv-tests modules/stdlib modules/unsetenv-tests tests/test-setenv.c tests/test-unsetenv.c
diffstat 14 files changed, 308 insertions(+), 26 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Mon Nov 16 22:08:52 2009 +0100
+++ b/ChangeLog	Sat Nov 14 22:13:10 2009 -0700
@@ -1,3 +1,23 @@
+2009-11-16  Eric Blake  <ebb9@byu.net>
+
+	setenv, unsetenv: work around various bugs
+	* lib/setenv.c (setenv) [!HAVE_SETENV]: Resync from glibc.
+	(setenv) [HAVE_SETENV]: Work around bugs.
+	* lib/unsetenv.c (unsetenv) [HAVE_UNSETENV]: Work around bugs.
+	* m4/setenv.m4 (gl_FUNC_SETENV_SEPARATE, gl_FUNC_UNSETENV): Check
+	for bugs.
+	(gl_FUNC_SETENV): Write in terms of gl_FUNC_SETENV_SEPARATE.
+	* m4/environ.m4 (gl_ENVIRON): Avoid expand-before-require.
+	* m4/stdlib_h.m4 (gl_STDLIB_H_DEFAULTS): Update defaults.
+	* modules/stdlib (Makefile.am): Update substitutions.
+	* lib/stdlib.in.h (setenv, unsetenv): Update prototypes.
+	* doc/posix-functions/setenv.texi (setenv): Document the bugs.
+	* doc/posix-functions/unsetenv.texi (unsetenv): Likewise.
+	* modules/setenv-tests: New test.
+	* modules/unsetenv-tests: Likewise.
+	* tests/test-setenv.c: New file.
+	* tests/test-unsetenv.c: Likewise.
+
 2009-11-16  Jim Meyering  <meyering@redhat.com>
 
 	version-etc: relax license to LGPLv3+
--- a/doc/posix-functions/setenv.texi	Mon Nov 16 22:08:52 2009 +0100
+++ b/doc/posix-functions/setenv.texi	Sat Nov 14 22:13:10 2009 -0700
@@ -11,11 +11,16 @@
 @item
 This function is missing on some platforms:
 AIX 4.3.2, HP-UX 11, IRIX 6.5, Solaris 9, mingw, BeOS.
+@item
+On some platforms, this function does not fail with @samp{EINVAL} when
+passed a null pointer, an empty string, or a string containing @samp{=}:
+FreeBSD 6.0, NetBSD 1.6, OpenBSD 3.8, Cygwin 1.5.x.
+@item
+On some platforms, this function removes a leading @samp{=} from the
+value argument:
+Cygwin 1.5.x.
 @end itemize
 
 Portability problems not fixed by Gnulib:
 @itemize
-@item
-In some versions of glibc (e.g.@: 2.3.3), @code{setenv} doesn't fail if the
-first argument contains a @samp{=} character.
 @end itemize
--- a/doc/posix-functions/unsetenv.texi	Mon Nov 16 22:08:52 2009 +0100
+++ b/doc/posix-functions/unsetenv.texi	Sat Nov 14 22:13:10 2009 -0700
@@ -15,6 +15,10 @@
 This function has the return type @samp{void} instead of @samp{int} on some
 platforms:
 MacOS X 10.3, FreeBSD 6.0, NetBSD 1.6, OpenBSD 3.8, OSF/1 5.1.
+@item
+On some platforms, this function does not fail with @samp{EINVAL} when
+passed a null pointer, an empty string, or a string containing @samp{=}:
+FreeBSD 6.0, NetBSD 1.6, OpenBSD 3.8.
 @end itemize
 
 Portability problems not fixed by Gnulib:
--- a/lib/setenv.c	Mon Nov 16 22:08:52 2009 +0100
+++ b/lib/setenv.c	Sat Nov 14 22:13:10 2009 -0700
@@ -1,4 +1,4 @@
-/* Copyright (C) 1992,1995-1999,2000-2003,2005-2008 Free Software Foundation, Inc.
+/* Copyright (C) 1992,1995-1999,2000-2003,2005-2009 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
@@ -32,12 +32,12 @@
 # include <unistd.h>
 #endif
 
-#if _LIBC || !HAVE_SETENV
-
 #if !_LIBC
 # include "malloca.h"
 #endif
 
+#if _LIBC || !HAVE_SETENV
+
 #if !_LIBC
 # define __environ	environ
 #endif
@@ -281,6 +281,12 @@
 int
 setenv (const char *name, const char *value, int replace)
 {
+  if (name == NULL || *name == '\0' || strchr (name, '=') != NULL)
+    {
+      __set_errno (EINVAL);
+      return -1;
+    }
+
   return __add_to_environ (name, value, NULL, replace);
 }
 
@@ -328,3 +334,45 @@
 #endif
 
 #endif /* _LIBC || !HAVE_SETENV */
+
+/* The rest of this file is called into use when replacing an existing
+   but buggy setenv.  Known bugs include failure to diagnose invalid
+   name, and consuming a leading '=' from value.  */
+#if HAVE_SETENV
+
+# undef setenv
+# define STREQ(a, b) (strcmp (a, b) == 0)
+
+int
+rpl_setenv (const char *name, const char *value, int replace)
+{
+  int result;
+  if (!name || !*name || strchr (name, '='))
+    {
+      errno = EINVAL;
+      return -1;
+    }
+  /* Call the real setenv even if replace is 0, in case implementation
+     has underlying data to update, such as when environ changes.  */
+  result = setenv (name, value, replace);
+  if (result == 0 && replace && *value == '=')
+    {
+      char *tmp = getenv (name);
+      if (!STREQ (tmp, value))
+        {
+          int saved_errno;
+          size_t len = strlen (value);
+          tmp = malloca (len + 2);
+          /* Since leading '=' is eaten, double it up.  */
+          *tmp = '=';
+          memcpy (tmp + 1, value, len + 1);
+          result = setenv (name, tmp, replace);
+          saved_errno = errno;
+          freea (tmp);
+          errno = saved_errno;
+        }
+    }
+  return result;
+}
+
+#endif /* HAVE_SETENV */
--- a/lib/stdlib.in.h	Mon Nov 16 22:08:52 2009 +0100
+++ b/lib/stdlib.in.h	Sat Nov 14 22:13:10 2009 -0700
@@ -384,11 +384,21 @@
 #endif
 
 #if @GNULIB_SETENV@
-# if !@HAVE_SETENV@
+# if @REPLACE_SETENV@
+#  undef setenv
+#  define setenv rpl_setenv
+# endif
+# if !@HAVE_SETENV@ || @REPLACE_SETENV@
 /* Set NAME to VALUE in the environment.
    If REPLACE is nonzero, overwrite an existing value.  */
 extern int setenv (const char *name, const char *value, int replace);
 # endif
+#elif defined GNULIB_POSIXCHECK
+# undef setenv
+# define setenv(n,v,o)                                                  \
+    (GL_LINK_WARNING ("setenv is unportable - "                         \
+                      "use gnulib module setenv for portability"),      \
+     setenv (n, v, o))
 #endif
 
 #if @GNULIB_STRTOD@
@@ -448,16 +458,20 @@
 #endif
 
 #if @GNULIB_UNSETENV@
-# if @HAVE_UNSETENV@
-#  if @VOID_UNSETENV@
-/* On some systems, unsetenv() returns void.
-   This is the case for MacOS X 10.3, FreeBSD 4.8, NetBSD 1.6, OpenBSD 3.4.  */
-#   define unsetenv(name) ((unsetenv)(name), 0)
-#  endif
-# else
+# if @REPLACE_UNSETENV@
+#  undef unsetenv
+#  define unsetenv rpl_unsetenv
+# endif
+# if !@HAVE_UNSETENV@ || @REPLACE_UNSETENV@
 /* Remove the variable NAME from the environment.  */
 extern int unsetenv (const char *name);
 # endif
+#elif defined GNULIB_POSIXCHECK
+# undef unsetenv
+# define unsetenv(n)                                                    \
+    (GL_LINK_WARNING ("unsetenv is unportable - "                       \
+                      "use gnulib module unsetenv for portability"),    \
+     unsetenv (n))
 #endif
 
 #ifdef __cplusplus
--- a/lib/unsetenv.c	Mon Nov 16 22:08:52 2009 +0100
+++ b/lib/unsetenv.c	Sat Nov 14 22:13:10 2009 -0700
@@ -1,4 +1,4 @@
-/* Copyright (C) 1992,1995-1999,2000-2002,2005-2008 Free Software Foundation, Inc.
+/* Copyright (C) 1992,1995-1999,2000-2002,2005-2009 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
@@ -47,6 +47,7 @@
 # define unsetenv __unsetenv
 #endif
 
+#if _LIBC || !HAVE_UNSETENV
 
 int
 unsetenv (const char *name)
@@ -88,3 +89,27 @@
 # undef unsetenv
 weak_alias (__unsetenv, unsetenv)
 #endif
+
+#else /* HAVE_UNSETENV */
+
+# undef unsetenv
+
+/* 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;
+    }
+# if !VOID_UNSETENV
+  result =
+# endif
+    unsetenv (name);
+  return result;
+}
+
+#endif /* HAVE_UNSETENV */
--- a/m4/environ.m4	Mon Nov 16 22:08:52 2009 +0100
+++ b/m4/environ.m4	Sat Nov 14 22:13:10 2009 -0700
@@ -1,10 +1,10 @@
-# environ.m4 serial 2
+# environ.m4 serial 3
 dnl Copyright (C) 2001-2004, 2006-2009 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_ENVIRON],
+AC_DEFUN_ONCE([gl_ENVIRON],
 [
   AC_REQUIRE([gl_UNISTD_H_DEFAULTS])
   dnl Persuade glibc <unistd.h> to declare environ.
--- a/m4/setenv.m4	Mon Nov 16 22:08:52 2009 +0100
+++ b/m4/setenv.m4	Sat Nov 14 22:13:10 2009 -0700
@@ -1,4 +1,4 @@
-# setenv.m4 serial 11
+# setenv.m4 serial 12
 dnl Copyright (C) 2001-2004, 2006-2009 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -6,12 +6,9 @@
 
 AC_DEFUN([gl_FUNC_SETENV],
 [
-  AC_REQUIRE([gl_STDLIB_H_DEFAULTS])
-  AC_CHECK_FUNCS_ONCE([setenv])
-  if test $ac_cv_func_setenv = no; then
-    HAVE_SETENV=0
+  AC_REQUIRE([gl_FUNC_SETENV_SEPARATE])
+  if test $HAVE_SETENV$REPLACE_SETENV != 10; then
     AC_LIBOBJ([setenv])
-    gl_PREREQ_SETENV
   fi
 ])
 
@@ -22,6 +19,24 @@
   AC_CHECK_FUNCS_ONCE([setenv])
   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>
+      ]], [[
+       if (setenv (NULL, "", 0) != -1) return 1;
+       if (errno != EINVAL) return 2;
+       if (setenv ("a", "=", 1) != 0) return 3;
+       if (strcmp (getenv ("a"), "=") != 0) return 4;
+      ]])],
+      [gl_cv_func_setenv_works=yes], [gl_cv_func_setenv_works=no],
+      [gl_cv_func_setenv_works="guessing no"])])
+    if test "$gl_cv_func_setenv_works" != yes; then
+      REPLACE_SETENV=1
+      AC_LIBOBJ([setenv])
+    fi
   fi
   gl_PREREQ_SETENV
 ])
@@ -48,7 +63,10 @@
 #endif
 ], , gt_cv_func_unsetenv_ret='int', gt_cv_func_unsetenv_ret='void')])
     if test $gt_cv_func_unsetenv_ret = 'void'; then
-      VOID_UNSETENV=1
+      AC_DEFINE([VOID_UNSETENV], [1], [Define to 1 if unsetenv returns void
+       instead of int.])
+      REPLACE_UNSETENV=1
+      AC_LIBOBJ([unsetenv])
     fi
   fi
 ])
--- a/m4/stdlib_h.m4	Mon Nov 16 22:08:52 2009 +0100
+++ b/m4/stdlib_h.m4	Sat Nov 14 22:13:10 2009 -0700
@@ -80,6 +80,7 @@
   REPLACE_MKSTEMP=0;         AC_SUBST([REPLACE_MKSTEMP])
   REPLACE_PUTENV=0;          AC_SUBST([REPLACE_PUTENV])
   REPLACE_REALPATH=0;        AC_SUBST([REPLACE_REALPATH])
+  REPLACE_SETENV=0;          AC_SUBST([REPLACE_SETENV])
   REPLACE_STRTOD=0;          AC_SUBST([REPLACE_STRTOD])
-  VOID_UNSETENV=0;           AC_SUBST([VOID_UNSETENV])
+  REPLACE_UNSETENV=0;        AC_SUBST([REPLACE_UNSETENV])
 ])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/setenv-tests	Sat Nov 14 22:13:10 2009 -0700
@@ -0,0 +1,10 @@
+Files:
+tests/test-setenv.c
+
+Depends-on:
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-setenv
+check_PROGRAMS += test-setenv
--- a/modules/stdlib	Mon Nov 16 22:08:52 2009 +0100
+++ b/modules/stdlib	Sat Nov 14 22:13:10 2009 -0700
@@ -73,8 +73,9 @@
 	      -e 's|@''REPLACE_MKSTEMP''@|$(REPLACE_MKSTEMP)|g' \
 	      -e 's|@''REPLACE_PUTENV''@|$(REPLACE_PUTENV)|g' \
 	      -e 's|@''REPLACE_REALPATH''@|$(REPLACE_REALPATH)|g' \
+	      -e 's|@''REPLACE_SETENV''@|$(REPLACE_SETENV)|g' \
 	      -e 's|@''REPLACE_STRTOD''@|$(REPLACE_STRTOD)|g' \
-	      -e 's|@''VOID_UNSETENV''@|$(VOID_UNSETENV)|g' \
+	      -e 's|@''REPLACE_UNSETENV''@|$(REPLACE_UNSETENV)|g' \
 	      -e '/definition of GL_LINK_WARNING/r $(LINK_WARNING_H)' \
 	      < $(srcdir)/stdlib.in.h; \
 	} > $@-t && \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/unsetenv-tests	Sat Nov 14 22:13:10 2009 -0700
@@ -0,0 +1,11 @@
+Files:
+tests/test-unsetenv.c
+
+Depends-on:
+putenv
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-unsetenv
+check_PROGRAMS += test-unsetenv
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-setenv.c	Sat Nov 14 22:13:10 2009 -0700
@@ -0,0 +1,60 @@
+/* Tests of setenv.
+   Copyright (C) 2009 Free Software Foundation, Inc.
+
+   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/>.  */
+
+/* Written by Eric Blake <ebb9@byu.net>, 2009.  */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define ASSERT(expr) \
+  do                                                                         \
+    {                                                                        \
+      if (!(expr))                                                           \
+        {                                                                    \
+          fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__);  \
+          fflush (stderr);                                                   \
+          abort ();                                                          \
+        }                                                                    \
+    }                                                                        \
+  while (0)
+
+int
+main (void)
+{
+  /* Test overwriting.  */
+  ASSERT (setenv ("a", "==", -1) == 0);
+  ASSERT (setenv ("a", "2", 0) == 0);
+  ASSERT (strcmp (getenv ("a"), "==") == 0);
+
+  /* Required to fail with EINVAL.  */
+  errno = 0;
+  ASSERT (setenv ("", "", 1) == -1);
+  ASSERT (errno == EINVAL);
+  errno = 0;
+  ASSERT (setenv ("a=b", "", 0) == -1);
+  ASSERT (errno == EINVAL);
+  errno = 0;
+  ASSERT (setenv (NULL, "", 0) == -1);
+  ASSERT (errno == EINVAL);
+
+  return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-unsetenv.c	Sat Nov 14 22:13:10 2009 -0700
@@ -0,0 +1,65 @@
+/* Tests of unsetenv.
+   Copyright (C) 2009 Free Software Foundation, Inc.
+
+   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/>.  */
+
+/* Written by Eric Blake <ebb9@byu.net>, 2009.  */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define ASSERT(expr) \
+  do                                                                         \
+    {                                                                        \
+      if (!(expr))                                                           \
+        {                                                                    \
+          fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__);  \
+          fflush (stderr);                                                   \
+          abort ();                                                          \
+        }                                                                    \
+    }                                                                        \
+  while (0)
+
+int
+main (void)
+{
+  char entry[] = "b=2";
+
+  /* Test removal when multiple entries present.  */
+  ASSERT (putenv ("a=1") == 0);
+  ASSERT (putenv (entry) == 0);
+  entry[0] = 'a'; /* Unspecified what getenv("a") would be at this point.  */
+  ASSERT (unsetenv ("a") == 0); /* Both entries will be removed.  */
+  ASSERT (getenv ("a") == NULL);
+  ASSERT (unsetenv ("a") == 0);
+
+  /* Required to fail with EINVAL.  */
+  errno = 0;
+  ASSERT (unsetenv ("") == -1);
+  ASSERT (errno == EINVAL);
+  errno = 0;
+  ASSERT (unsetenv ("a=b") == -1);
+  ASSERT (errno == EINVAL);
+  errno = 0;
+  ASSERT (unsetenv (NULL) == -1);
+  ASSERT (errno == EINVAL);
+
+  return 0;
+}