putenv: port better to native Windows
[gnulib.git] / lib / putenv.c
1 /* Copyright (C) 1991, 1994, 1997-1998, 2000, 2003-2013 Free Software
2    Foundation, Inc.
3
4    NOTE: The canonical source of this file is maintained with the GNU C
5    Library.  Bugs can be reported to bug-glibc@prep.ai.mit.edu.
6
7    This program is free software: you can redistribute it and/or modify it
8    under the terms of the GNU General Public License as published by the
9    Free Software Foundation; either version 3 of the License, or any
10    later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
19
20 #include <config.h>
21
22 /* Specification.  */
23 #include <stdlib.h>
24
25 #include <stddef.h>
26
27 /* Include errno.h *after* sys/types.h to work around header problems
28    on AIX 3.2.5.  */
29 #include <errno.h>
30 #ifndef __set_errno
31 # define __set_errno(ev) ((errno) = (ev))
32 #endif
33
34 #include <string.h>
35 #include <unistd.h>
36
37 #if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
38 # define WIN32_LEAN_AND_MEAN
39 # include <windows.h>
40 #endif
41
42 #if _LIBC
43 # if HAVE_GNU_LD
44 #  define environ __environ
45 # else
46 extern char **environ;
47 # endif
48 #endif
49
50 #if _LIBC
51 /* This lock protects against simultaneous modifications of 'environ'.  */
52 # include <bits/libc-lock.h>
53 __libc_lock_define_initialized (static, envlock)
54 # define LOCK   __libc_lock_lock (envlock)
55 # define UNLOCK __libc_lock_unlock (envlock)
56 #else
57 # define LOCK
58 # define UNLOCK
59 #endif
60
61 static int
62 _unsetenv (const char *name)
63 {
64   size_t len;
65   char **ep;
66
67   if (name == NULL || *name == '\0' || strchr (name, '=') != NULL)
68     {
69       __set_errno (EINVAL);
70       return -1;
71     }
72
73   len = strlen (name);
74
75 #if HAVE__PUTENV
76   {
77     int putenv_result, putenv_errno;
78     char *name_ = malloc (len + 2);
79     memcpy (name_, name, len);
80     name_[len] = '=';
81     name_[len + 1] = 0;
82     putenv_result = _putenv (name_);
83     putenv_errno = errno;
84     free (name_);
85     __set_errno (putenv_errno);
86     return putenv_result;
87   }
88 #else
89
90   LOCK;
91
92   ep = environ;
93   while (*ep != NULL)
94     if (!strncmp (*ep, name, len) && (*ep)[len] == '=')
95       {
96         /* Found it.  Remove this pointer by moving later ones back.  */
97         char **dp = ep;
98
99         do
100           dp[0] = dp[1];
101         while (*dp++);
102         /* Continue the loop in case NAME appears again.  */
103       }
104     else
105       ++ep;
106
107   UNLOCK;
108
109   return 0;
110 #endif
111 }
112
113
114 /* Put STRING, which is of the form "NAME=VALUE", in the environment.
115    If STRING contains no '=', then remove STRING from the environment.  */
116 int
117 putenv (char *string)
118 {
119   const char *name_end = strchr (string, '=');
120   char **ep;
121
122   if (name_end == NULL)
123     {
124       /* Remove the variable from the environment.  */
125       return _unsetenv (string);
126     }
127
128 #if HAVE__PUTENV
129       /* Rely on _putenv to allocate the new environment.  If other
130          parts of the application use _putenv, the !HAVE__PUTENV code
131          would fight over who owns the environ vector, causing a crash.  */
132       if (name_end[1])
133         return _putenv (string);
134       else
135         {
136           /* _putenv ("NAME=") unsets NAME, so invoke _putenv ("NAME= ")
137              to allocate the environ vector and then replace the new
138              entry with "NAME=".  */
139           int putenv_result, putenv_errno;
140           char *name_x = malloc (name_end - string + sizeof "= ");
141           if (!name_x)
142             return -1;
143           memcpy (name_x, string, name_end - string + 1);
144           name_x[name_end - string + 1] = ' ';
145           name_x[name_end - string + 2] = 0;
146           putenv_result = _putenv (name_x);
147           putenv_errno = errno;
148           for (ep = environ; *ep; ep++)
149             if (strcmp (*ep, name_x) == 0)
150               {
151                 *ep = string;
152                 break;
153               }
154 # if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
155           if (putenv_result == 0)
156             {
157               /* _putenv propagated "NAME= " into the subprocess environment;
158                  fix that by calling SetEnvironmentVariable directly.  */
159               name_x[name_end - string] = 0;
160               putenv_result = SetEnvironmentVariable (name_x, "") ? 0 : -1;
161               putenv_errno = ENOMEM; /* ENOMEM is the only way to fail.  */
162             }
163 # endif
164           free (name_x);
165           __set_errno (putenv_errno);
166           return putenv_result;
167         }
168 #else
169   for (ep = environ; *ep; ep++)
170     if (strncmp (*ep, string, name_end - string) == 0
171         && (*ep)[name_end - string] == '=')
172       break;
173
174   if (*ep)
175     *ep = string;
176   else
177     {
178       static char **last_environ = NULL;
179       size_t size = ep - environ;
180       char **new_environ = malloc ((size + 2) * sizeof *new_environ);
181       if (! new_environ)
182         return -1;
183       new_environ[0] = string;
184       memcpy (new_environ + 1, environ, (size + 1) * sizeof *new_environ);
185       free (last_environ);
186       last_environ = new_environ;
187       environ = new_environ;
188     }
189 #endif
190
191   return 0;
192 }