Use dict_T to pass env vars to process spawning code

Co-authored-by: Matthieu Coudron <mattator@gmail.com>
This commit is contained in:
James McCoy 2020-07-31 01:17:24 -04:00
parent 55add1c1c8
commit 7f50c69268
No known key found for this signature in database
GPG Key ID: DFE691AE331BA3DB
7 changed files with 135 additions and 65 deletions

View File

@ -304,7 +304,8 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
bool pty, bool rpc, bool overlapped, bool detach, bool pty, bool rpc, bool overlapped, bool detach,
const char *cwd, const char *cwd,
uint16_t pty_width, uint16_t pty_height, uint16_t pty_width, uint16_t pty_height,
char *term_name, char **env, varnumber_T *status_out) char *term_name, dict_T *env,
varnumber_T *status_out)
{ {
assert(cwd == NULL || os_isdir_executable(cwd)); assert(cwd == NULL || os_isdir_executable(cwd));
@ -358,7 +359,9 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
if (status) { if (status) {
EMSG3(_(e_jobspawn), os_strerror(status), cmd); EMSG3(_(e_jobspawn), os_strerror(status), cmd);
xfree(cmd); xfree(cmd);
os_free_fullenv(proc->env); if (proc->env) {
tv_dict_free(proc->env);
}
if (proc->type == kProcessTypePty) { if (proc->type == kProcessTypePty) {
xfree(chan->stream.pty.term_name); xfree(chan->stream.pty.term_name);
} }
@ -367,8 +370,9 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
return NULL; return NULL;
} }
xfree(cmd); xfree(cmd);
os_free_fullenv(proc->env); if (proc->env) {
tv_dict_free(proc->env);
}
wstream_init(&proc->in, 0); wstream_init(&proc->in, 0);
if (has_out) { if (has_out) {

View File

@ -4887,7 +4887,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
bool executable = true; bool executable = true;
char **argv = tv_to_argv(&argvars[0], NULL, &executable); char **argv = tv_to_argv(&argvars[0], NULL, &executable);
char **env = NULL; dict_T *env = NULL;
if (!argv) { if (!argv) {
rettv->vval.v_number = executable ? 0 : -1; rettv->vval.v_number = executable ? 0 : -1;
return; // Did error message in tv_to_argv. return; // Did error message in tv_to_argv.
@ -4936,7 +4936,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
#endif #endif
char *new_cwd = tv_dict_get_string(job_opts, "cwd", false); char *new_cwd = tv_dict_get_string(job_opts, "cwd", false);
if (new_cwd && strlen(new_cwd) > 0) { if (new_cwd && *new_cwd != NUL) {
cwd = new_cwd; cwd = new_cwd;
// The new cwd must be a directory. // The new cwd must be a directory.
if (!os_isdir_executable((const char *)cwd)) { if (!os_isdir_executable((const char *)cwd)) {
@ -4945,48 +4945,30 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return; return;
} }
} }
dictitem_T *job_env = tv_dict_find(job_opts, S_LEN("env")); dictitem_T *job_env = tv_dict_find(job_opts, S_LEN("env"));
if (job_env) { if (job_env && job_env->di_tv.v_type != VAR_DICT) {
if (job_env->di_tv.v_type != VAR_DICT) { EMSG2(_(e_invarg2), "env");
EMSG2(_(e_invarg2), "env"); shell_free_argv(argv);
shell_free_argv(argv); return;
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;
} }
env = tv_dict_alloc();
if (!clear_env) {
typval_T temp_env = TV_INITIAL_VALUE;
f_environ(NULL, &temp_env, NULL);
tv_dict_extend(env, temp_env.vval.v_dict, "force");
tv_dict_free(temp_env.vval.v_dict);
}
if (job_env) {
tv_dict_extend(env, job_env->di_tv.vval.v_dict, "force");
}
if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
shell_free_argv(argv); shell_free_argv(argv);
tv_dict_free(env);
return; return;
} }
} }

View File

@ -1523,6 +1523,33 @@ varnumber_T tv_dict_get_number(const dict_T *const d, const char *const key)
return tv_get_number(&di->di_tv); return tv_get_number(&di->di_tv);
} }
/// Converts a dict to an environment
///
///
char **tv_dict_to_env(dict_T *denv)
{
size_t env_size = (size_t)tv_dict_len(denv);
size_t i = 0;
char **env = NULL;
// + 1 for NULL
env = xmalloc((env_size + 1) * sizeof(*env));
TV_DICT_ITER(denv, 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] = NULL;
return env;
}
/// Get a string item from a dictionary /// Get a string item from a dictionary
/// ///
/// @param[in] d Dictionary to get item from. /// @param[in] d Dictionary to get item from.

View File

@ -41,7 +41,6 @@ int libuv_process_spawn(LibuvProcess *uvproc)
#endif #endif
uvproc->uvopts.exit_cb = exit_cb; uvproc->uvopts.exit_cb = exit_cb;
uvproc->uvopts.cwd = proc->cwd; uvproc->uvopts.cwd = proc->cwd;
uvproc->uvopts.env = proc->env;
uvproc->uvopts.stdio = uvproc->uvstdio; uvproc->uvopts.stdio = uvproc->uvstdio;
uvproc->uvopts.stdio_count = 3; uvproc->uvopts.stdio_count = 3;
uvproc->uvstdio[0].flags = UV_IGNORE; uvproc->uvstdio[0].flags = UV_IGNORE;
@ -49,6 +48,12 @@ int libuv_process_spawn(LibuvProcess *uvproc)
uvproc->uvstdio[2].flags = UV_IGNORE; uvproc->uvstdio[2].flags = UV_IGNORE;
uvproc->uv.data = proc; uvproc->uv.data = proc;
if (proc->env) {
uvproc->uvopts.env = tv_dict_to_env(proc->env);
} else {
uvproc->uvopts.env = NULL;
}
if (!proc->in.closed) { if (!proc->in.closed) {
uvproc->uvstdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; uvproc->uvstdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
#ifdef WIN32 #ifdef WIN32

View File

@ -4,6 +4,7 @@
#include "nvim/event/loop.h" #include "nvim/event/loop.h"
#include "nvim/event/rstream.h" #include "nvim/event/rstream.h"
#include "nvim/event/wstream.h" #include "nvim/event/wstream.h"
#include "nvim/eval/typval.h"
typedef enum { typedef enum {
kProcessTypeUv, kProcessTypeUv,
@ -23,7 +24,7 @@ struct process {
uint64_t stopped_time; // process_stop() timestamp uint64_t stopped_time; // process_stop() timestamp
const char *cwd; const char *cwd;
char **argv; char **argv;
char **env; dict_T *env;
Stream in, out, err; Stream in, out, err;
process_exit_cb cb; process_exit_cb cb;
internal_process_cb internal_exit_cb, internal_close_cb; internal_process_cb internal_exit_cb, internal_close_cb;

View File

@ -20,6 +20,10 @@
# include <pty.h> # include <pty.h>
#endif #endif
#ifdef __APPLE__
# include <crt_externs.h>
#endif
#include <uv.h> #include <uv.h>
#include "nvim/lib/klist.h" #include "nvim/lib/klist.h"
@ -151,31 +155,24 @@ void pty_process_teardown(Loop *loop)
uv_signal_stop(&loop->children_watcher); uv_signal_stop(&loop->children_watcher);
} }
static const char *ignored_env_vars[] = {
"COLUMNS",
"LINES",
"TERMCAP",
"COLORFGBG"
};
static void init_child(PtyProcess *ptyproc) static void init_child(PtyProcess *ptyproc)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_ALL
{ {
#if defined(HAVE__NSGETENVIRON)
char **environ = *_NSGetEnviron();
#else
extern char **environ;
#endif
// New session/process-group. #6530 // New session/process-group. #6530
setsid(); setsid();
os_unsetenv("COLUMNS");
os_unsetenv("LINES");
os_unsetenv("TERMCAP");
os_unsetenv("COLORFGBG");
// setting COLORTERM to "truecolor" if termguicolors is set and 256
// otherwise, but only if it was set in the parent terminal at all
if (os_env_exists("COLORTERM")) {
const char *colorterm = os_getenv("COLORTERM");
if (colorterm != NULL) {
if (p_tgc) {
os_setenv("COLORTERM", "truecolor", 1);
} else {
os_setenv("COLORTERM", "256", 1);
}
} else {
os_unsetenv("COLORTERM");
}
}
signal(SIGCHLD, SIG_DFL); signal(SIGCHLD, SIG_DFL);
signal(SIGHUP, SIG_DFL); signal(SIGHUP, SIG_DFL);
signal(SIGINT, SIG_DFL); signal(SIGINT, SIG_DFL);
@ -190,9 +187,49 @@ static void init_child(PtyProcess *ptyproc)
} }
char *prog = ptyproc->process.argv[0]; char *prog = ptyproc->process.argv[0];
os_setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1); if (proc->env) {
execvp(prog, ptyproc->process.argv); for (size_t i = 0; i < ARRAY_SIZE(ignored_env_vars); i++) {
dictitem_T *dv = tv_dict_find(proc->env, ignored_env_vars[i], -1);
if (dv) {
tv_dict_item_remove(proc->env, dv);
}
}
tv_dict_add_str(proc->env, S_LEN("TERM"), ptyproc->term_name ? ptyproc->term_name : "ansi");
// setting COLORTERM to "truecolor" if termguicolors is set and 256
// otherwise, but only if it was set in the parent terminal at all
dictitem_T *dv = tv_dict_find(proc->env, S_LEN("COLORTERM"));
if (dv) {
tv_dict_item_remove(proc->env, dv);
tv_dict_add_str(proc->env, S_LEN("COLORTERM"), p_tgc ? "truecolor" : "256");
}
environ = tv_dict_to_env(proc->env);
} else {
for (size_t i = 0; i < ARRAY_SIZE(ignored_env_vars); i++) {
os_unsetenv(ignored_env_vars[i]);
}
// setting COLORTERM to "truecolor" if termguicolors is set and 256
// otherwise, but only if it was set in the parent terminal at all
if (os_env_exists("COLORTERM")) {
const char *colorterm = os_getenv("COLORTERM");
if (colorterm != NULL) {
if (p_tgc) {
os_setenv("COLORTERM", "truecolor", 1);
} else {
os_setenv("COLORTERM", "256", 1);
}
} else {
os_unsetenv("COLORTERM");
}
}
os_setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1);
}
execvp(prog, proc->argv);
ELOG("execvp failed: %s: %s", strerror(errno), prog); ELOG("execvp failed: %s: %s", strerror(errno), prog);
_exit(122); // 122 is EXEC_FAILED in the Vim source. _exit(122); // 122 is EXEC_FAILED in the Vim source.
} }

View File

@ -75,6 +75,20 @@ describe('jobs', function()
}) })
end) end)
it('append environment with pty #env', function()
nvim('command', "let $VAR = 'abc'")
nvim('command', "let g:job_opts.pty = v:true")
nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}")
if iswin() then
nvim('command', [[call jobstart('echo %TOTO% %VAR%', g:job_opts)]])
else
nvim('command', [[call jobstart('echo $TOTO $VAR', g:job_opts)]])
end
expect_msg_seq({
{'notification', 'stdout', {0, {'hello world abc', ''}}},
})
end)
it('replace environment #env', function() it('replace environment #env', function()
nvim('command', "let $VAR = 'abc'") nvim('command', "let $VAR = 'abc'")
nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}") nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}")