aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/channel.c30
-rw-r--r--src/nvim/eval.c77
-rw-r--r--src/nvim/event/libuv_process.c2
-rw-r--r--src/nvim/event/process.h1
-rw-r--r--src/nvim/os/env.c148
5 files changed, 220 insertions, 38 deletions
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index f9102fa0e2..2bb568f025 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -272,11 +272,35 @@ static void close_cb(Stream *stream, void *data)
channel_decref(data);
}
+
+/// Starts a job and returns the associated channel
+///
+/// @param[in] argv Arguments vector specifying the command to run,
+/// NULL-terminated
+/// @param[in] on_stdout Callback to read the job's stdout
+/// @param[in] on_stderr Callback to read the job's stderr
+/// @param[in] on_exit Callback to receive the job's exit status
+/// @param[in] pty True if the job should run attached to a pty
+/// @param[in] rpc True to communicate with the job using msgpack-rpc,
+/// `on_stdout` is ignored
+/// @param[in] detach True if the job should not be killed when nvim exits,
+/// ignored if `pty` is true
+/// @param[in] cwd Initial working directory for the job. Nvim's working
+/// directory if `cwd` is NULL
+/// @param[in] pty_width Width of the pty, ignored if `pty` is false
+/// @param[in] pty_height Height of the pty, ignored if `pty` is false
+/// @param[in] term_name `$TERM` for the pty
+/// @param[in] env Nvim's configured environment is used if this is NULL,
+/// otherwise defines all environment variables
+/// @param[out] status_out 0 for invalid arguments, > 0 for the channel id,
+/// < 0 if the job can't start
+///
+/// @returns [allocated] channel
Channel *channel_job_start(char **argv, CallbackReader on_stdout,
CallbackReader on_stderr, Callback on_exit,
bool pty, bool rpc, bool detach, const char *cwd,
uint16_t pty_width, uint16_t pty_height,
- char *term_name, varnumber_T *status_out)
+ char *term_name, char **env, varnumber_T *status_out)
{
assert(cwd == NULL || os_isdir_executable(cwd));
@@ -314,6 +338,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
proc->events = chan->events;
proc->detach = detach;
proc->cwd = cwd;
+ proc->env = env;
char *cmd = xstrdup(proc->argv[0]);
bool has_out, has_err;
@@ -328,6 +353,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
if (status) {
EMSG3(_(e_jobspawn), os_strerror(status), cmd);
xfree(cmd);
+ os_free_fullenv(proc->env);
if (proc->type == kProcessTypePty) {
xfree(chan->stream.pty.term_name);
}
@@ -336,6 +362,8 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
return NULL;
}
xfree(cmd);
+ os_free_fullenv(proc->env);
+
wstream_init(&proc->in, 0);
if (has_out) {
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index e99c41a915..c14b7f513d 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -8716,18 +8716,25 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
tv_dict_alloc_ret(rettv);
- for (int i = 0; ; i++) {
- // TODO(justinmk): use os_copyfullenv from #7202 ?
- char *envname = os_getenvname_at_index((size_t)i);
- if (envname == NULL) {
- break;
- }
- const char *value = os_getenv(envname);
+ size_t env_size = os_get_fullenv_size();
+ char **env = xmalloc(sizeof(*env) * (env_size + 1));
+ env[env_size] = NULL;
+
+ os_copy_fullenv(env, env_size);
+
+ for (size_t i = 0; i < env_size; i++) {
+ const char * str = env[i];
+ const char * const end = strchr(str + (str[0] == '=' ? 1 : 0),
+ '=');
+ assert(end != NULL);
+ ptrdiff_t len = end - str;
+ assert(len > 0);
+ const char * value = str + len + 1;
tv_dict_add_str(rettv->vval.v_dict,
- (char *)envname, STRLEN((char *)envname),
- value == NULL ? "" : value);
- xfree(envname);
+ str, len,
+ value);
}
+ os_free_fullenv(env);
}
/*
@@ -12577,6 +12584,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
bool executable = true;
char **argv = tv_to_argv(&argvars[0], NULL, &executable);
+ char **env = NULL;
if (!argv) {
rettv->vval.v_number = executable ? 0 : -1;
return; // Did error message in tv_to_argv.
@@ -12594,6 +12602,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
bool detach = false;
bool rpc = false;
bool pty = false;
+ bool clear_env = false;
CallbackReader on_stdout = CALLBACK_READER_INIT,
on_stderr = CALLBACK_READER_INIT;
Callback on_exit = CALLBACK_NONE;
@@ -12604,6 +12613,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
detach = tv_dict_get_number(job_opts, "detach") != 0;
rpc = tv_dict_get_number(job_opts, "rpc") != 0;
pty = tv_dict_get_number(job_opts, "pty") != 0;
+ clear_env = tv_dict_get_number(job_opts, "clear_env") != 0;
if (pty && rpc) {
EMSG2(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set");
shell_free_argv(argv);
@@ -12620,6 +12630,45 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
}
+ dictitem_T *job_env = tv_dict_find(job_opts, S_LEN("env"));
+ if (job_env) {
+ if (job_env->di_tv.v_type != VAR_DICT) {
+ EMSG2(_(e_invarg2), "env");
+ shell_free_argv(argv);
+ return;
+ }
+
+ size_t custom_env_size = (size_t)tv_dict_len(job_env->di_tv.vval.v_dict);
+ size_t i = 0;
+ size_t env_size = 0;
+
+ if (clear_env) {
+ // + 1 for last null entry
+ env = xmalloc((custom_env_size + 1) * sizeof(*env));
+ env_size = 0;
+ } else {
+ env_size = os_get_fullenv_size();
+
+ env = xmalloc((custom_env_size + env_size + 1) * sizeof(*env));
+
+ os_copy_fullenv(env, env_size);
+ i = env_size;
+ }
+ assert(env); // env must be allocated at this point
+
+ TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, {
+ const char *str = tv_get_string(&var->di_tv);
+ assert(str);
+ size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1;
+ env[i] = xmalloc(len);
+ snprintf(env[i], len, "%s=%s", (char *)var->di_key, str);
+ i++;
+ });
+
+ // must be null terminated
+ env[env_size + custom_env_size] = NULL;
+ }
+
if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
shell_free_argv(argv);
@@ -12637,8 +12686,8 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty,
- rpc, detach, cwd, width, height, term_name,
- &rettv->vval.v_number);
+ rpc, detach, cwd, width, height,
+ term_name, env, &rettv->vval.v_number);
if (chan) {
channel_create_event(chan, NULL);
}
@@ -14933,7 +14982,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT,
CALLBACK_READER_INIT, CALLBACK_NONE,
- false, true, false, NULL, 0, 0, NULL,
+ false, true, false, NULL, 0, 0, NULL, NULL,
&rettv->vval.v_number);
if (chan) {
channel_create_event(chan, NULL);
@@ -18320,7 +18369,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit,
true, false, false, cwd,
term_width, curwin->w_height_inner,
- xstrdup("xterm-256color"),
+ xstrdup("xterm-256color"), NULL,
&rettv->vval.v_number);
if (rettv->vval.v_number <= 0) {
return;
diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c
index 63efee59a8..37b9f73ba4 100644
--- a/src/nvim/event/libuv_process.c
+++ b/src/nvim/event/libuv_process.c
@@ -41,7 +41,7 @@ int libuv_process_spawn(LibuvProcess *uvproc)
#endif
uvproc->uvopts.exit_cb = exit_cb;
uvproc->uvopts.cwd = proc->cwd;
- uvproc->uvopts.env = NULL; // Inherits the parent (nvim) env.
+ uvproc->uvopts.env = proc->env;
uvproc->uvopts.stdio = uvproc->uvstdio;
uvproc->uvopts.stdio_count = 3;
uvproc->uvstdio[0].flags = UV_IGNORE;
diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h
index ef9d953ab7..b677b80bfe 100644
--- a/src/nvim/event/process.h
+++ b/src/nvim/event/process.h
@@ -23,6 +23,7 @@ struct process {
uint64_t stopped_time; // process_stop() timestamp
const char *cwd;
char **argv;
+ char **env;
Stream in, out, err;
process_exit_cb cb;
internal_process_cb internal_exit_cb, internal_close_cb;
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index eb86cb8ac7..360609c50d 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -184,39 +184,142 @@ int os_unsetenv(const char *name)
return r == 0 ? 0 : -1;
}
+/// Returns number of variables in the current environment variables block
+size_t os_get_fullenv_size(void)
+{
+ size_t len = 0;
+#ifdef _WIN32
+ wchar_t *envstrings = GetEnvironmentStringsW();
+ wchar_t *p = envstrings;
+ size_t l;
+ if (!envstrings) {
+ return len;
+ }
+ // GetEnvironmentStringsW() result has this format:
+ // var1=value1\0var2=value2\0...varN=valueN\0\0
+ while ((l = wcslen(p)) != 0) {
+ p += l + 1;
+ len++;
+ }
+
+ FreeEnvironmentStringsW(envstrings);
+#else
+# if defined(HAVE__NSGETENVIRON)
+ char **environ = *_NSGetEnviron();
+# else
+ extern char **environ;
+# endif
+
+ while (environ[len] != NULL) {
+ len++;
+ }
+
+#endif
+ return len;
+}
+
+void os_free_fullenv(char **env)
+{
+ if (!env) { return; }
+ for (char **it = env; *it; it++) {
+ XFREE_CLEAR(*it);
+ }
+ xfree(env);
+}
+
+/// Copies the current environment variables into the given array, `env`. Each
+/// array element is of the form "NAME=VALUE".
+/// Result must be freed by the caller.
+///
+/// @param[out] env array to populate with environment variables
+/// @param env_size size of `env`, @see os_fullenv_size
+void os_copy_fullenv(char **env, size_t env_size)
+{
+#ifdef _WIN32
+ wchar_t *envstrings = GetEnvironmentStringsW();
+ if (!envstrings) {
+ return;
+ }
+ wchar_t *p = envstrings;
+ size_t i = 0;
+ size_t l;
+ // GetEnvironmentStringsW() result has this format:
+ // var1=value1\0var2=value2\0...varN=valueN\0\0
+ while ((l = wcslen(p)) != 0 && i < env_size) {
+ char *utf8_str;
+ int conversion_result = utf16_to_utf8(p, -1, &utf8_str);
+ if (conversion_result != 0) {
+ EMSG2("utf16_to_utf8 failed: %d", conversion_result);
+ break;
+ }
+ p += l + 1;
+
+ env[i] = utf8_str;
+ i++;
+ }
+
+ FreeEnvironmentStringsW(envstrings);
+#else
+# if defined(HAVE__NSGETENVIRON)
+ char **environ = *_NSGetEnviron();
+# else
+ extern char **environ;
+# endif
+
+ size_t i = 0;
+ while (environ[i] != NULL && i < env_size) {
+ env[i] = xstrdup(environ[i]);
+ i++;
+ }
+#endif
+}
+
+/// Copy value of the environment variable at `index` in the current
+/// environment variables block.
+/// Result must be freed by the caller.
+///
+/// @param index nth item in environment variables block
+/// @return [allocated] environment variable's value, or NULL
char *os_getenvname_at_index(size_t index)
{
#ifdef _WIN32
- wchar_t *env = GetEnvironmentStringsW();
- if (!env) {
+ wchar_t *envstrings = GetEnvironmentStringsW();
+ if (!envstrings) {
return NULL;
}
+ wchar_t *p = envstrings;
char *name = NULL;
- size_t current_index = 0;
+ size_t i = 0;
+ size_t l;
// GetEnvironmentStringsW() result has this format:
// var1=value1\0var2=value2\0...varN=valueN\0\0
- for (wchar_t *it = env; *it != L'\0' || *(it + 1) != L'\0'; it++) {
- if (index == current_index) {
+ while ((l = wcslen(p)) != 0 && i <= index) {
+ if (i == index) {
char *utf8_str;
- int conversion_result = utf16_to_utf8(it, -1, &utf8_str);
+ int conversion_result = utf16_to_utf8(p, -1, &utf8_str);
if (conversion_result != 0) {
EMSG2("utf16_to_utf8 failed: %d", conversion_result);
break;
}
- size_t namesize = 0;
- while (utf8_str[namesize] != '=' && utf8_str[namesize] != NUL) {
- namesize++;
- }
- name = (char *)vim_strnsave((char_u *)utf8_str, namesize);
+
+ // Some Windows env vars start with =, so skip over that to find the
+ // separator between name/value
+ const char * const end = strchr(utf8_str + (utf8_str[0] == '=' ? 1 : 0),
+ '=');
+ assert(end != NULL);
+ ptrdiff_t len = end - utf8_str;
+ assert(len > 0);
+ name = xstrndup(utf8_str, (size_t)len);
xfree(utf8_str);
break;
}
- if (*it == L'\0') {
- current_index++;
- }
+
+ // Advance past the name and NUL
+ p += l + 1;
+ i++;
}
- FreeEnvironmentStringsW(env);
+ FreeEnvironmentStringsW(envstrings);
return name;
#else
# if defined(HAVE__NSGETENVIRON)
@@ -224,19 +327,20 @@ char *os_getenvname_at_index(size_t index)
# else
extern char **environ;
# endif
- // Check if index is inside the environ array and is not the last element.
+
+ // check if index is inside the environ array
for (size_t i = 0; i <= index; i++) {
if (environ[i] == NULL) {
return NULL;
}
}
char *str = environ[index];
- size_t namesize = 0;
- while (str[namesize] != '=' && str[namesize] != NUL) {
- namesize++;
- }
- char *name = (char *)vim_strnsave((char_u *)str, namesize);
- return name;
+ assert(str != NULL);
+ const char * const end = strchr(str, '=');
+ assert(end != NULL);
+ ptrdiff_t len = end - str;
+ assert(len > 0);
+ return xstrndup(str, (size_t)len);
#endif
}