// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
// Environment inspection
#include <assert.h>
#include <uv.h>
#include "nvim/ascii.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/macros.h"
#include "nvim/map.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/strings.h"
#include "nvim/version.h"
#include "nvim/vim.h"
#ifdef WIN32
# include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8
#endif
#ifdef HAVE__NSGETENVIRON
# include <crt_externs.h>
#endif
#ifdef HAVE_SYS_UTSNAME_H
# include <sys/utsname.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;
static uv_mutex_t mutex;
void env_init(void)
{
uv_mutex_init(&mutex);
}
void os_env_var_lock(void)
{
uv_mutex_lock(&mutex);
}
void os_env_var_unlock(void)
{
uv_mutex_unlock(&mutex);
}
/// 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;
size_t size = 64;
if (name[0] == '\0') {
return NULL;
}
uv_mutex_lock(&mutex);
int r = 0;
if (pmap_has(cstr_t)(&envmap, name)
&& !!(e = (char *)pmap_get(cstr_t)(&envmap, name))) {
if (e[0] != '\0') {
// 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);
}
e = xmalloc(size);
r = uv_os_getenv(name, e, &size);
if (r == UV_ENOBUFS) {
e = xrealloc(e, size);
r = uv_os_getenv(name, e, &size);
}
if (r != 0 || size == 0 || e[0] == '\0') {
xfree(e);
e = NULL;
goto end;
}
pmap_put(cstr_t)(&envmap, xstrdup(name), e);
end:
// Must do this before ELOG, log.c may call os_setenv.
uv_mutex_unlock(&mutex);
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 == NULL || size == 0 || e[0] == '\0') ? NULL : 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] == '\0') {
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] == '\0') {
return -1;
}
#ifdef WIN32
if (!overwrite && os_getenv(name) != NULL) {
return 0;
}
if (value[0] == '\0') {
// Windows (Vim-compat): Empty string undefines the env var.
return os_unsetenv(name);
}
#else
if (!overwrite && os_env_exists(name)) {
return 0;
}
#endif
uv_mutex_lock(&mutex);
int r;
#ifdef WIN32
// 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);
// Must do this before ELOG, log.c may call os_setenv.
uv_mutex_unlock(&mutex);
if (r != 0) {
ELOG("uv_os_setenv(%s) failed: %d %s", name, r, uv_err_name(r));
}
return r == 0 ? 0 : -1;
}
/// Unset environment variable
int os_unsetenv(const char *name)
FUNC_ATTR_NONNULL_ALL
{
if (name[0
|