// Environment inspection
#include <assert.h>
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <uv.h>
#include "auto/config.h"
#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/cmdexpand_defs.h"
#include "nvim/eval.h"
#include "nvim/globals.h"
#include "nvim/log.h"
#include "nvim/macros_defs.h"
#include "nvim/map_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option_vars.h"
#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
#include "nvim/path.h"
#include "nvim/strings.h"
#include "nvim/types_defs.h"
#include "nvim/version.h"
#include "nvim/vim_defs.h"
#ifdef MSWIN
# include "nvim/mbyte.h"
#endif
#ifdef BACKSLASH_IN_FILENAME
# include "nvim/fileio.h"
#endif
#ifdef HAVE__NSGETENVIRON
# include <crt_externs.h>
#endif
#ifdef HAVE_SYS_UTSNAME_H
# include <sys/utsname.h>
#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "auto/pathdef.h"
# include "os/env.c.generated.h"
#endif
// Because `uv_os_getenv` requires allocating, we must manage a map to maintain
// the behavior of `os_getenv`.
static PMap(cstr_t) envmap = MAP_INIT;
/// Like getenv(), but returns NULL if the variable is empty.
/// @see os_env_exists
const char *os_getenv(const char *name)
FUNC_ATTR_NONNULL_ALL
{
char *e = NULL;
if (name[0] == NUL) {
return NULL;
}
int r = 0;
if (map_has(cstr_t, &envmap, name)
&& !!(e = (char *)pmap_get(cstr_t)(&envmap, name))) {
if (e[0] != NUL) {
// Found non-empty cached env var.
// NOTE: This risks incoherence if an in-process library changes the
// environment without going through our os_setenv() wrapper. If
// that turns out to be a problem, we can just remove this codepath.
goto end;
}
pmap_del2(&envmap, name);
}
#define INIT_SIZE 64
size_t size = INIT_SIZE;
char buf[INIT_SIZE];
r = uv_os_getenv(name, buf, &size);
if (r == UV_ENOBUFS) {
e = xmalloc(size);
r = uv_os_getenv(name, e, &size);
if (r != 0 || size == 0 || e[0] == NUL) {
XFREE_CLEAR(e);
goto end;
}
} else if (r != 0 || size == 0 || buf[0] == NUL) {
e = NULL;
goto end;
} else {
// NB: `size` param of uv_os_getenv() includes the NUL-terminator,
// except when it does not include the NUL-terminator.
e = xmemdupz(buf, size);
}
pmap_put(cstr_t)(&envmap, xstrdup(name), e);
end:
if (r != 0 && r != UV_ENOENT && r != UV_UNKNOWN) {
ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r));
}
return e;
}
/// Returns true if environment variable `name` is defined (even if empty).
/// Returns false if not found (UV_ENOENT) or other failure.
bool os_env_exists(const char *name)
FUNC_ATTR_NONNULL_ALL
{
if (name[0] == NUL) {
return false;
}
// Use a tiny buffer because we don't care about the value: if uv_os_getenv()
// returns UV_ENOBUFS, the env var was found.
char buf[1];
size_t size = sizeof(buf);
int r = uv_os_getenv(name, buf, &size);
assert(r != UV_EINVAL);
if (r != 0 && r != UV_ENOENT && r != UV_ENOBUFS) {
ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r));
}
return (r == 0 || r == UV_ENOBUFS);
}
/// Sets an environment variable.
///
/// Windows (Vim-compat): Empty string (:let $FOO="") undefines the env var.
///
/// @warning Existing pointers to the result of os_getenv("foo") are
/// INVALID after os_setenv("foo", …).
int os_setenv(const char *name, const char *value, int overwrite)
FUNC_ATTR_NONNULL_ALL
{
if (name[0] == NUL) {
return -1;
}
#ifdef MSWIN
if (!overwrite && os_getenv(name) != NULL) {
return 0;
}
if (value[0] == NUL) {
// Windows (Vim-compat): Empty string undefines the env var.
return os_unsetenv(name);
}
#else
if (!overwrite && os_env_exists(name)) {
return 0;
}
#endif
int r;
#ifdef MSWIN
// libintl uses getenv() for LC_ALL/LANG/etc., so we must use _putenv_s().
if (striequal(name, "LC_ALL") || striequal(name, "LANGUAGE")
|| striequal(name, "LANG") || striequal(name, "LC_MESSAGES")) {
r = _putenv_s(name, value); // NOLINT
assert(r == 0);
}
#endif
r = uv_os_setenv(name, value);
assert(r != UV_EINVAL);
// Destroy the old map item. Do this AFTER uv_os_setenv(), because `value`
// could be a previous os_getenv() result.
pmap_del2(&envmap, name);
if (r != 0) {
ELOG("uv_os_setenv(%s) failed: %d %s
|