On a GitHub CI machine, I see the nstrftime tests - succeed on mingw, - but fail on MSVC.
The failure output is: PST8PDT: expected "1969-12-31 16:00:00 -0800 (PST)", got "1970-01-01 00:00:00 +0000 ()" MST7: expected "1969-12-31 17:00:00 -0700 (MST)", got "1970-01-01 00:00:00 +0000 ()" CET-1CEST: expected "1970-01-01 01:00:00 +0100 (CET)", got "1970-01-01 00:00:00 +0000 ()" JST-9: expected "1970-01-01 09:00:00 +0900 (JST)", got "1970-01-01 00:00:00 +0000 ()" NST-13NDT: expected "1970-01-01 13:00:00 +1300 (NZDT)", got "1970-01-01 00:00:00 +0000 ()" PST8PDT: expected "1985-11-04 16:53:21 -0800 (PST)", got "1985-11-05 00:53:21 +0000 ()" MST7: expected "1985-11-04 17:53:21 -0700 (MST)", got "1985-11-05 00:53:21 +0000 ()" CET-1CEST: expected "1985-11-05 01:53:21 +0100 (CET)", got "1985-11-05 00:53:21 +0000 ()" JST-9: expected "1985-11-05 09:53:21 +0900 (JST)", got "1985-11-05 00:53:21 +0000 ()" NST-13NDT: expected "1985-11-05 13:53:21 +1300 (NZDT)", got "1985-11-05 00:53:21 +0000 ()" MST7: expected "2001-09-08 18:46:42 -0700 (MST)", got "2001-09-09 01:46:42 +0000 ()" JST-9: expected "2001-09-09 10:46:42 +0900 (JST)", got "2001-09-09 01:46:42 +0000 ()" which indicates that in all cases, UTC has been used instead of the timezone from the test. The cause is that * the test uses the setenv(), unsetenv() replacements by Gnulib (since MSVC has only _putenv, no setenv, no unsetenv), * these replacement manipulate the contents of _environ, but have no effect on _wenviron (unlike _putenv, which modifies both), * and apparently on this machine, MSVC uses a Microsoft CRT that looks at _wgetenv (L"TZ"), not getenv ("TZ"). The fix is to change setenv and unsetenv to use _putenv(). The patches below do this, and also improve comments, fix a possible out-of-memory crash, and reduce code duplication. 2024-06-05 Bruno Haible <br...@clisp.org> setenv: On native Windows, don't modify _environ directly. * m4/setenv.m4 (gl_PREREQ_SETENV): Check for _putenv. * lib/setenv.c: On native Windows, include <windows.h> and define SetEnvironmentVariable. (setenv) [HAVE_DECL__PUTENV]: New implementation for platforms with _putenv. * modules/setenv (Depends-on): Add malloc-posix. 2024-06-05 Bruno Haible <br...@clisp.org> unsetenv: On native Windows, don't modify _environ directly. * m4/setenv.m4 (gl_PREREQ_UNSETENV): Check for _putenv. * lib/unsetenv.c (unsetenv): Add native Windows handling, from lib/putenv.c. * modules/unsetenv (Depends-on): Add free-posix, malloc-posix. * m4/putenv.m4 (gl_FUNC_PUTENV): Use AC_CHECK_DECLS_ONCE. * lib/putenv.c (_unsetenv): Moved to lib/unsetenv.c. (putenv): Invoke unsetenv instead of _unsetenv. * modules/putenv-gnu (Depends-on): Add unsetenv. 2024-06-05 Bruno Haible <br...@clisp.org> putenv: Don't crash upon out-of-memory. * lib/putenv.c (_unsetenv): Handle malloc failure. 2024-06-05 Bruno Haible <br...@clisp.org> putenv: Improve comments. * lib/putenv.c (_unsetenv, putenv): Improve comments regarding native Windows.
>From 1b91f9fba3efd1e2e0c608e287e71e79236aed4b Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Thu, 6 Jun 2024 01:08:45 +0200 Subject: [PATCH 1/4] putenv: Improve comments. * lib/putenv.c (_unsetenv, putenv): Improve comments regarding native Windows. --- ChangeLog | 6 ++++++ lib/putenv.c | 36 +++++++++++++++++++++++++++--------- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/ChangeLog b/ChangeLog index 2d681333de..8c487d98de 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2024-06-05 Bruno Haible <br...@clisp.org> + + putenv: Improve comments. + * lib/putenv.c (_unsetenv, putenv): Improve comments regarding native + Windows. + 2024-06-05 Bruno Haible <br...@clisp.org> setenv: Modernize. diff --git a/lib/putenv.c b/lib/putenv.c index 2ab6f248db..525d12aed9 100644 --- a/lib/putenv.c +++ b/lib/putenv.c @@ -68,9 +68,6 @@ static int _unsetenv (const char *name) { size_t len; -#if !HAVE_DECL__PUTENV - char **ep; -#endif if (name == NULL || *name == '\0' || strchr (name, '=') != NULL) { @@ -80,7 +77,18 @@ _unsetenv (const char *name) len = strlen (name); -#if HAVE_DECL__PUTENV +#if HAVE_DECL__PUTENV /* native Windows */ + /* The Microsoft documentation + <https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-wputenv> + says: + "Don't change an environment entry directly: instead, + use _putenv or _wputenv to change it." + Note: Microsoft's _putenv updates not only the contents of _environ but + also the contents of _wenviron, so that both are in kept in sync. + + The way to remove an environment variable is to pass to _putenv a string + of the form "NAME=". (NB: This is a different convention than with glibc + putenv, which expects a string of the form "NAME"!) */ { int putenv_result; char *name_ = malloc (len + 2); @@ -88,6 +96,8 @@ _unsetenv (const char *name) name_[len] = '='; name_[len + 1] = 0; putenv_result = _putenv (name_); + /* In this particular case it is OK to free() the argument passed to + _putenv. */ free (name_); return putenv_result; } @@ -95,7 +105,7 @@ _unsetenv (const char *name) LOCK; - ep = environ; + char **ep = environ; while (*ep != NULL) if (!strncmp (*ep, name, len) && (*ep)[len] == '=') { @@ -131,10 +141,18 @@ putenv (char *string) return _unsetenv (string); } -#if HAVE_DECL__PUTENV - /* Rely on _putenv to allocate the new environment. If other - parts of the application use _putenv, the !HAVE_DECL__PUTENV code - would fight over who owns the environ vector, causing a crash. */ +#if HAVE_DECL__PUTENV /* native Windows */ + /* The Microsoft documentation + <https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-wputenv> + says: + "Don't change an environment entry directly: instead, + use _putenv or _wputenv to change it." + Note: Microsoft's _putenv updates not only the contents of _environ but + also the contents of _wenviron, so that both are in kept in sync. + + If we didn't follow this advice, our code and other parts of the + application (that use _putenv) would fight over who owns the environ vector + and thus cause a crash. */ if (name_end[1]) return _putenv (string); else -- 2.34.1
>From adb76c754290c328a88438af89e491ece7e6a9c5 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Thu, 6 Jun 2024 02:24:44 +0200 Subject: [PATCH 2/4] putenv: Don't crash upon out-of-memory. * lib/putenv.c (_unsetenv): Handle malloc failure. --- ChangeLog | 5 +++++ lib/putenv.c | 2 ++ 2 files changed, 7 insertions(+) diff --git a/ChangeLog b/ChangeLog index 8c487d98de..212bc3bb6f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2024-06-05 Bruno Haible <br...@clisp.org> + + putenv: Don't crash upon out-of-memory. + * lib/putenv.c (_unsetenv): Handle malloc failure. + 2024-06-05 Bruno Haible <br...@clisp.org> putenv: Improve comments. diff --git a/lib/putenv.c b/lib/putenv.c index 525d12aed9..1d70717e0f 100644 --- a/lib/putenv.c +++ b/lib/putenv.c @@ -92,6 +92,8 @@ _unsetenv (const char *name) { int putenv_result; char *name_ = malloc (len + 2); + if (name_ == NULL) + return -1; memcpy (name_, name, len); name_[len] = '='; name_[len + 1] = 0; -- 2.34.1
>From 43433e89de71ad9f7979605355ebee315ee78ddc Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Thu, 6 Jun 2024 01:21:28 +0200 Subject: [PATCH 3/4] unsetenv: On native Windows, don't modify _environ directly. * m4/setenv.m4 (gl_PREREQ_UNSETENV): Check for _putenv. * lib/unsetenv.c (unsetenv): Add native Windows handling, from lib/putenv.c. * modules/unsetenv (Depends-on): Add free-posix, malloc-posix. * m4/putenv.m4 (gl_FUNC_PUTENV): Use AC_CHECK_DECLS_ONCE. * lib/putenv.c (_unsetenv): Moved to lib/unsetenv.c. (putenv): Invoke unsetenv instead of _unsetenv. * modules/putenv-gnu (Depends-on): Add unsetenv. --- ChangeLog | 12 +++++++++ lib/putenv.c | 67 +--------------------------------------------- lib/unsetenv.c | 32 ++++++++++++++++++++-- m4/putenv.m4 | 4 +-- m4/setenv.m4 | 3 ++- modules/putenv-gnu | 1 + modules/unsetenv | 2 ++ 7 files changed, 50 insertions(+), 71 deletions(-) diff --git a/ChangeLog b/ChangeLog index 212bc3bb6f..4cd92956da 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2024-06-05 Bruno Haible <br...@clisp.org> + + unsetenv: On native Windows, don't modify _environ directly. + * m4/setenv.m4 (gl_PREREQ_UNSETENV): Check for _putenv. + * lib/unsetenv.c (unsetenv): Add native Windows handling, from + lib/putenv.c. + * modules/unsetenv (Depends-on): Add free-posix, malloc-posix. + * m4/putenv.m4 (gl_FUNC_PUTENV): Use AC_CHECK_DECLS_ONCE. + * lib/putenv.c (_unsetenv): Moved to lib/unsetenv.c. + (putenv): Invoke unsetenv instead of _unsetenv. + * modules/putenv-gnu (Depends-on): Add unsetenv. + 2024-06-05 Bruno Haible <br...@clisp.org> putenv: Don't crash upon out-of-memory. diff --git a/lib/putenv.c b/lib/putenv.c index 1d70717e0f..d3084c9e60 100644 --- a/lib/putenv.c +++ b/lib/putenv.c @@ -64,71 +64,6 @@ __libc_lock_define_initialized (static, envlock) # define SetEnvironmentVariable SetEnvironmentVariableA #endif -static int -_unsetenv (const char *name) -{ - size_t len; - - if (name == NULL || *name == '\0' || strchr (name, '=') != NULL) - { - __set_errno (EINVAL); - return -1; - } - - len = strlen (name); - -#if HAVE_DECL__PUTENV /* native Windows */ - /* The Microsoft documentation - <https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-wputenv> - says: - "Don't change an environment entry directly: instead, - use _putenv or _wputenv to change it." - Note: Microsoft's _putenv updates not only the contents of _environ but - also the contents of _wenviron, so that both are in kept in sync. - - The way to remove an environment variable is to pass to _putenv a string - of the form "NAME=". (NB: This is a different convention than with glibc - putenv, which expects a string of the form "NAME"!) */ - { - int putenv_result; - char *name_ = malloc (len + 2); - if (name_ == NULL) - return -1; - memcpy (name_, name, len); - name_[len] = '='; - name_[len + 1] = 0; - putenv_result = _putenv (name_); - /* In this particular case it is OK to free() the argument passed to - _putenv. */ - free (name_); - return putenv_result; - } -#else - - LOCK; - - char **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; -#endif -} - - /* Put STRING, which is of the form "NAME=VALUE", in the environment. If STRING contains no '=', then remove STRING from the environment. */ int @@ -140,7 +75,7 @@ putenv (char *string) if (name_end == NULL) { /* Remove the variable from the environment. */ - return _unsetenv (string); + return unsetenv (string); } #if HAVE_DECL__PUTENV /* native Windows */ diff --git a/lib/unsetenv.c b/lib/unsetenv.c index d8ada2aaed..5e989968c1 100644 --- a/lib/unsetenv.c +++ b/lib/unsetenv.c @@ -57,7 +57,6 @@ int unsetenv (const char *name) { size_t len; - char **ep; if (name == NULL || *name == '\0' || strchr (name, '=') != NULL) { @@ -67,9 +66,37 @@ unsetenv (const char *name) len = strlen (name); +#if HAVE_DECL__PUTENV /* native Windows */ + /* The Microsoft documentation + <https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-wputenv> + says: + "Don't change an environment entry directly: instead, + use _putenv or _wputenv to change it." + Note: Microsoft's _putenv updates not only the contents of _environ but + also the contents of _wenviron, so that both are in kept in sync. + + The way to remove an environment variable is to pass to _putenv a string + of the form "NAME=". (NB: This is a different convention than with glibc + putenv, which expects a string of the form "NAME"!) */ + { + int putenv_result; + char *name_ = malloc (len + 2); + if (name_ == NULL) + return -1; + memcpy (name_, name, len); + name_[len] = '='; + name_[len + 1] = 0; + putenv_result = _putenv (name_); + /* In this particular case it is OK to free() the argument passed to + _putenv. */ + free (name_); + return putenv_result; + } +#else + LOCK; - ep = __environ; + char **ep = __environ; while (*ep != NULL) if (!strncmp (*ep, name, len) && (*ep)[len] == '=') { @@ -87,6 +114,7 @@ unsetenv (const char *name) UNLOCK; return 0; +#endif } #ifdef _LIBC diff --git a/m4/putenv.m4 b/m4/putenv.m4 index 5179105dc7..5616fdf558 100644 --- a/m4/putenv.m4 +++ b/m4/putenv.m4 @@ -1,5 +1,5 @@ # putenv.m4 -# serial 27 +# serial 28 dnl Copyright (C) 2002-2024 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -61,5 +61,5 @@ AC_DEFUN([gl_FUNC_PUTENV] # Prerequisites of lib/putenv.c. AC_DEFUN([gl_PREREQ_PUTENV], [ - AC_CHECK_DECLS([_putenv]) + AC_CHECK_DECLS_ONCE([_putenv]) ]) diff --git a/m4/setenv.m4 b/m4/setenv.m4 index e7f00f398e..2ff7868461 100644 --- a/m4/setenv.m4 +++ b/m4/setenv.m4 @@ -1,5 +1,5 @@ # setenv.m4 -# serial 33 +# serial 34 dnl Copyright (C) 2001-2004, 2006-2024 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -163,4 +163,5 @@ AC_DEFUN([gl_PREREQ_UNSETENV] [ AC_REQUIRE([gl_ENVIRON]) AC_CHECK_HEADERS_ONCE([unistd.h]) + AC_CHECK_DECLS_ONCE([_putenv]) ]) diff --git a/modules/putenv-gnu b/modules/putenv-gnu index be929f60be..356a74bdc7 100644 --- a/modules/putenv-gnu +++ b/modules/putenv-gnu @@ -10,6 +10,7 @@ stdlib environ [test $REPLACE_PUTENV = 1] free-posix [test $REPLACE_PUTENV = 1] malloc-posix [test $REPLACE_PUTENV = 1] +unsetenv [test $REPLACE_PUTENV = 1] configure.ac: gl_FUNC_PUTENV diff --git a/modules/unsetenv b/modules/unsetenv index 0e603a5b76..302d970814 100644 --- a/modules/unsetenv +++ b/modules/unsetenv @@ -9,6 +9,8 @@ Depends-on: stdlib unistd [test $HAVE_UNSETENV = 0 || test $REPLACE_UNSETENV = 1] environ [test $HAVE_UNSETENV = 0 || test $REPLACE_UNSETENV = 1] +free-posix [test $HAVE_UNSETENV = 0 || test $REPLACE_UNSETENV = 1] +malloc-posix [test $HAVE_UNSETENV = 0 || test $REPLACE_UNSETENV = 1] configure.ac: gl_FUNC_UNSETENV -- 2.34.1
>From cd90c9c82055056e9986244528df19228363951a Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Thu, 6 Jun 2024 02:33:41 +0200 Subject: [PATCH 4/4] setenv: On native Windows, don't modify _environ directly. * m4/setenv.m4 (gl_PREREQ_SETENV): Check for _putenv. * lib/setenv.c: On native Windows, include <windows.h> and define SetEnvironmentVariable. (setenv) [HAVE_DECL__PUTENV]: New implementation for platforms with _putenv. * modules/setenv (Depends-on): Add malloc-posix. --- ChangeLog | 10 ++++++ lib/setenv.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++++- m4/setenv.m4 | 3 +- modules/setenv | 1 + 4 files changed, 104 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4cd92956da..eec970671b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2024-06-05 Bruno Haible <br...@clisp.org> + + setenv: On native Windows, don't modify _environ directly. + * m4/setenv.m4 (gl_PREREQ_SETENV): Check for _putenv. + * lib/setenv.c: On native Windows, include <windows.h> and define + SetEnvironmentVariable. + (setenv) [HAVE_DECL__PUTENV]: New implementation for platforms with + _putenv. + * modules/setenv (Depends-on): Add malloc-posix. + 2024-06-05 Bruno Haible <br...@clisp.org> unsetenv: On native Windows, don't modify _environ directly. diff --git a/lib/setenv.c b/lib/setenv.c index 2c26734ea1..7505716ef6 100644 --- a/lib/setenv.c +++ b/lib/setenv.c @@ -38,11 +38,23 @@ # include <unistd.h> #endif +#if defined _WIN32 && ! defined __CYGWIN__ +# define WIN32_LEAN_AND_MEAN +# include <windows.h> +#endif + #if !_LIBC # include "malloca.h" #endif +#if defined _WIN32 && ! defined __CYGWIN__ +/* Don't assume that UNICODE is not defined. */ +# undef SetEnvironmentVariable +# define SetEnvironmentVariable SetEnvironmentVariableA +#endif + #if _LIBC || !HAVE_SETENV +#if !HAVE_DECL__PUTENV #if !_LIBC # define __environ environ @@ -342,6 +354,84 @@ weak_alias (__setenv, setenv) weak_alias (__clearenv, clearenv) #endif +#else /* HAVE_DECL__PUTENV */ +/* Native Windows */ + +int +setenv (const char *name, const char *value, int replace) +{ + if (name == NULL || *name == '\0' || strchr (name, '=') != NULL) + { + errno = EINVAL; + return -1; + } + + /* The Microsoft documentation + <https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-wputenv> + says: + "Don't change an environment entry directly: instead, + use _putenv or _wputenv to change it." + Note: Microsoft's _putenv updates not only the contents of _environ but + also the contents of _wenviron, so that both are in kept in sync. */ + const char *existing_value = getenv (name); + if (existing_value != NULL) + { + if (replace) + { + if (strcmp (existing_value, value) == 0) + /* No need to allocate memory. */ + return 0; + } + else + /* Keep the existing value. */ + return 0; + } + /* Allocate a new environment entry in the heap. */ + /* _putenv ("NAME=") unsets NAME, so if VALUE is the empty string, invoke + _putenv ("NAME= ") and fix up the result afterwards. */ + const char *value_ = (value[0] == '\0' ? " " : value); + size_t name_len = strlen (name); + size_t value_len = strlen (value_); + char *string = (char *) malloc (name_len + 1 + value_len + 1); + if (string == NULL) + return -1; + memcpy (string, name, name_len); + string[name_len] = '='; + memcpy (&string[name_len + 1], value_, value_len + 1); + /* Use _putenv. */ + if (_putenv (string) < 0) + return -1; + if (value[0] == '\0') + { + /* Fix up the result. */ + char *new_value = getenv (name); + if (new_value != NULL && new_value[0] == ' ' && new_value[1] == '\0') + new_value[0] = '\0'; +# if defined _WIN32 && ! defined __CYGWIN__ + /* _putenv propagated "NAME= " into the subprocess environment; + fix that by calling SetEnvironmentVariable directly. */ + /* Documentation: + <https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setenvironmentvariable> */ + if (!SetEnvironmentVariable (name, "")) + { + switch (GetLastError ()) + { + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_OUTOFMEMORY: + errno = ENOMEM; + break; + default: + errno = EINVAL; + break; + } + return -1; + } +# endif + } + return 0; +} + +#endif /* HAVE_DECL__PUTENV */ #endif /* _LIBC || !HAVE_SETENV */ /* The rest of this file is called into use when replacing an existing @@ -359,7 +449,7 @@ int rpl_setenv (const char *name, const char *value, int replace) { int result; - if (!name || !*name || strchr (name, '=')) + if (name == NULL || *name == '\0' || strchr (name, '=') != NULL) { errno = EINVAL; return -1; diff --git a/m4/setenv.m4 b/m4/setenv.m4 index 2ff7868461..ae7fcec690 100644 --- a/m4/setenv.m4 +++ b/m4/setenv.m4 @@ -1,5 +1,5 @@ # setenv.m4 -# serial 34 +# serial 35 dnl Copyright (C) 2001-2004, 2006-2024 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -155,6 +155,7 @@ AC_DEFUN([gl_PREREQ_SETENV] AC_REQUIRE([gl_ENVIRON]) AC_CHECK_HEADERS_ONCE([unistd.h]) AC_CHECK_HEADERS([search.h]) + AC_CHECK_DECLS_ONCE([_putenv]) gl_CHECK_FUNCS_ANDROID([tsearch], [[#include <search.h>]]) ]) diff --git a/modules/setenv b/modules/setenv index a907d1e5d0..f7d7f6ea91 100644 --- a/modules/setenv +++ b/modules/setenv @@ -9,6 +9,7 @@ Depends-on: stdlib malloca [test $HAVE_SETENV = 0 || test $REPLACE_SETENV = 1] alloca-opt [test $HAVE_SETENV = 0 || test $REPLACE_SETENV = 1] +malloc-posix [test $HAVE_SETENV = 0 || test $REPLACE_SETENV = 1] unistd [test $HAVE_SETENV = 0 || test $REPLACE_SETENV = 1] environ [test $HAVE_SETENV = 0 || test $REPLACE_SETENV = 1] -- 2.34.1