* Lazy-initialize the environment variables.
This is the first in a series of PRs to make it easier to use WASI libc
in Wasm modules that don't have a `main` function. By initializing the
environment on demand, we avoid depending on having `__wasm_call_ctors`
run.
This uses weak symbols strategically to ensure that if `environ` is
used, it is initialized eagerly, but if only `getenv` and friends
are used, the environment is initialized lazily.
Eventually, I expect we'll have a convention for wasm modules without
main functions which will allow the `__wasm_call_ctors` function to be
called automatically, but this helps in simple cases for now.
Fixes #180.
* Add comments explaining the libc-environ-compat.h header usage.
__env_rm_add
__env_rm_add
__env_rm_add
-__environ
__exp2f_data
__exp_data
__expo2
__unlist_locked_file
__uselocale
__utc
+__wasilibc_ensure_environ
+__wasilibc_environ
+__wasilibc_environ
__wasilibc_fd_renumber
__wasilibc_find_relpath
+__wasilibc_initialize_environ
__wasilibc_open_nomode
__wasilibc_openat_nomode
__wasilibc_register_preopened_fd
#include <unistd.h>
#include <values.h>
#include <wasi/api.h>
+#include <wasi/libc-environ.h>
#include <wasi/libc-find-relpath.h>
#include <wasi/libc.h>
#include <wchar.h>
#define __va_copy(d,s) __builtin_va_copy(d,s)
#define __wasi__ 1
#define __wasi_api_h
+#define __wasi_libc_environ_h
#define __wasi_libc_find_relpath_h
#define __wasi_libc_h
#define __wasilibc___errno_values_h
--- /dev/null
+#ifndef __wasi_libc_environ_h
+#define __wasi_libc_environ_h
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// Initialize the global environment variable state. Only needs to be
+/// called once; most users should call `__wasilibc_ensure_environ` instead.
+void __wasilibc_initialize_environ(void);
+
+/// If `__wasilibc_initialize_environ` has not yet been called, call it.
+void __wasilibc_ensure_environ(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
/// This is referenced by weak reference from crt1.c and lives in the same source
/// file as `__wasilibc_find_relpath` so that it's linked in when it's needed.
// Concerning the 51 -- see the comment by the constructor priority in
-// libc-bottom-half/sources/__environ.c.
+// libc-bottom-half/sources/__wasilibc_environ.c.
__attribute__((constructor(51)))
static void
__wasilibc_populate_libpreopen(void)
+++ /dev/null
-#include <unistd.h>
-#include <stdlib.h>
-#include <sysexits.h>
-#include <wasi/api.h>
-#include <wasi/libc.h>
-
-static char *empty_environ[1] = { NULL };
-char **__environ = empty_environ;
-extern __typeof(__environ) _environ __attribute__((weak, alias("__environ")));
-extern __typeof(__environ) environ __attribute__((weak, alias("__environ")));
-
-// We define this function here in the same source file as __environ, so that
-// this function is called in iff environment variable support is used.
-// Concerning the 50 -- levels up to 100 are reserved for the implementation,
-// so we an arbitrary number in the middle of the range to allow other
-// reserved things to go before or after.
-__attribute__((constructor(50)))
-static void __wasilibc_populate_environ(void) {
- __wasi_errno_t err;
-
- // Get the sizes of the arrays we'll have to create to copy in the environment.
- size_t environ_count;
- size_t environ_buf_size;
- err = __wasi_environ_sizes_get(&environ_count, &environ_buf_size);
- if (err != __WASI_ERRNO_SUCCESS) {
- goto oserr;
- }
- if (environ_count == 0) {
- return;
- }
-
- // Add 1 for the NULL pointer to mark the end, and check for overflow.
- size_t num_ptrs = environ_count + 1;
- if (num_ptrs == 0) {
- goto software;
- }
-
- // Allocate memory for storing the environment chars.
- char *environ_buf = malloc(environ_buf_size);
- if (environ_buf == NULL) {
- goto software;
- }
-
- // Allocate memory for the array of pointers. This uses `calloc` both to
- // handle overflow and to initialize the NULL pointer at the end.
- char **environ_ptrs = calloc(num_ptrs, sizeof(char *));
- if (environ_ptrs == NULL) {
- free(environ_buf);
- goto software;
- }
-
- // Fill the environment chars, and the __environ array with pointers into those chars.
- // TODO: Remove the casts on `environ_ptrs` and `environ_buf` once the witx is updated with char8 support.
- err = __wasi_environ_get((uint8_t **)environ_ptrs, (uint8_t *)environ_buf);
- if (err != __WASI_ERRNO_SUCCESS) {
- free(environ_buf);
- free(environ_ptrs);
- goto oserr;
- }
-
- __environ = environ_ptrs;
- return;
-oserr:
- _Exit(EX_OSERR);
-software:
- _Exit(EX_SOFTWARE);
-}
--- /dev/null
+#include <unistd.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <wasi/api.h>
+#include <wasi/libc.h>
+#include <wasi/libc-environ.h>
+
+/// If the program doesn't use `environ`, it'll get this version of
+/// `__wasilibc_environ`, which isn't initialized with a constructor function.
+/// `getenv` etc. call `__wasilibc_ensure_environ()` before accessing it.
+/// Statically-initialize it to an invalid pointer value so that we can
+/// detect if it's been explicitly initialized (we can't use `NULL` because
+/// `clearenv` sets it to NULL.
+char **__wasilibc_environ __attribute__((weak)) = (char **)-1;
+
+// See the comments in libc-environ.h.
+void __wasilibc_ensure_environ(void) {
+ if (__wasilibc_environ == (char **)-1) {
+ __wasilibc_initialize_environ();
+ }
+}
+
+/// Avoid dynamic allocation for the case where there are no environment
+/// variables, but we still need a non-NULL pointer to an (empty) array.
+static char *empty_environ[1] = { NULL };
+
+// See the comments in libc-environ.h.
+void __wasilibc_initialize_environ(void) {
+ // Get the sizes of the arrays we'll have to create to copy in the environment.
+ size_t environ_count;
+ size_t environ_buf_size;
+ __wasi_errno_t err = __wasi_environ_sizes_get(&environ_count, &environ_buf_size);
+ if (err != __WASI_ERRNO_SUCCESS) {
+ goto oserr;
+ }
+ if (environ_count == 0) {
+ __wasilibc_environ = empty_environ;
+ return;
+ }
+
+ // Add 1 for the NULL pointer to mark the end, and check for overflow.
+ size_t num_ptrs = environ_count + 1;
+ if (num_ptrs == 0) {
+ goto software;
+ }
+
+ // Allocate memory for storing the environment chars.
+ char *environ_buf = malloc(environ_buf_size);
+ if (environ_buf == NULL) {
+ goto software;
+ }
+
+ // Allocate memory for the array of pointers. This uses `calloc` both to
+ // handle overflow and to initialize the NULL pointer at the end.
+ char **environ_ptrs = calloc(num_ptrs, sizeof(char *));
+ if (environ_ptrs == NULL) {
+ free(environ_buf);
+ goto software;
+ }
+
+ // Fill the environment chars, and the `__wasilibc_environ` array with
+ // pointers into those chars.
+ // TODO: Remove the casts on `environ_ptrs` and `environ_buf` once the witx is updated with char8 support.
+ err = __wasi_environ_get((uint8_t **)environ_ptrs, (uint8_t *)environ_buf);
+ if (err != __WASI_ERRNO_SUCCESS) {
+ free(environ_buf);
+ free(environ_ptrs);
+ goto oserr;
+ }
+
+ __wasilibc_environ = environ_ptrs;
+ return;
+oserr:
+ _Exit(EX_OSERR);
+software:
+ _Exit(EX_SOFTWARE);
+}
--- /dev/null
+#include <unistd.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <wasi/api.h>
+#include <wasi/libc.h>
+#include <wasi/libc-environ.h>
+
+// If the program does use `environ`, it'll get this version of
+// `__wasilibc_environ`, which is initialized with a constructor function, so
+// that it's initialized whenever user code might want to access it.
+char **__wasilibc_environ;
+extern __typeof(__wasilibc_environ) _environ
+ __attribute__((weak, alias("__wasilibc_environ")));
+extern __typeof(__wasilibc_environ) environ
+ __attribute__((weak, alias("__wasilibc_environ")));
+
+// We define this function here in the same source file as
+// `__wasilibc_environ`, so that this function is called in iff environment
+// variable support is used.
+// Concerning the 50 -- levels up to 100 are reserved for the implementation,
+// so we an arbitrary number in the middle of the range to allow other
+// reserved things to go before or after.
+__attribute__((constructor(50)))
+static void __wasilibc_initialize_environ_eagerly(void) {
+ __wasilibc_initialize_environ();
+}
--- /dev/null
+// This header file is meant to be included withinin the body of a function
+// which uses `__environ`. Code using `__environ` expects it will be initialized
+// eagerly. `__wasilibc_environ` is initialized lazily. Provide `__environ` as
+// an alias and arrange for the lazy initialization to be performed.
+
+extern char **__wasilibc_environ;
+
+__wasilibc_ensure_environ();
+
+#ifndef __wasilibc_environ
+#define __environ __wasilibc_environ
+#endif
int clearenv()
{
+#ifdef __wasilibc_unmodified_upstream // Lazy environment variable init.
+#else
+// This specialized header is included within the function body to arranges for
+// the environment variables to be lazily initialized. It redefined `__environ`,
+// so don't remove or reorder it with respect to other code.
+#include "wasi/libc-environ-compat.h"
+#endif
char **e = __environ;
__environ = 0;
if (e) while (*e) __env_rm_add(*e++, 0);
char *getenv(const char *name)
{
+#ifdef __wasilibc_unmodified_upstream // Lazy environment variable init.
+#else
+// This specialized header is included within the function body to arranges for
+// the environment variables to be lazily initialized. It redefined `__environ`,
+// so don't remove or reorder it with respect to other code.
+#include "wasi/libc-environ-compat.h"
+#endif
size_t l = __strchrnul(name, '=') - name;
if (l && !name[l] && __environ)
for (char **e = __environ; *e; e++)
int __putenv(char *s, size_t l, char *r)
{
+#ifdef __wasilibc_unmodified_upstream // Lazy environment variable init.
+#else
+// This specialized header is included within the function body to arranges for
+// the environment variables to be lazily initialized. It redefined `__environ`,
+// so don't remove or reorder it with respect to other code.
+#include "wasi/libc-environ-compat.h"
+#endif
size_t i=0;
if (__environ) {
for (char **e = __environ; *e; e++, i++)
errno = EINVAL;
return -1;
}
+#ifdef __wasilibc_unmodified_upstream // Lazy environment variable init.
+#else
+// This specialized header is included within the function body to arranges for
+// the environment variables to be lazily initialized. It redefined `__environ`,
+// so don't remove or reorder it with respect to other code.
+#include "wasi/libc-environ-compat.h"
+#endif
if (__environ) {
char **e = __environ, **eo = e;
for (; *e; e++)
#include "../../include/unistd.h"
+#ifdef __wasilibc_unmodified_upstream // Lazy environment variable init.
extern char **__environ;
+#else
+// To support lazy initialization of environment variables, `__environ` is
+// omitted, and a lazy `__wasilibc_environ` is used instead. Use
+// "wasi/libc-environ-compat.h" in functions that use `__environ`.
+#include "wasi/libc-environ.h"
+#endif
hidden int __dup3(int, int, int);
hidden int __mkostemps(char *, int, int);