From 2c44d9257287c3e804dcc5a2bfe073c20554c0b9 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Tue, 7 Jun 2016 03:48:03 +1000 Subject: [PATCH 1/4] eval: allow setting cwd in {jobstart,termopen}() Processes in vim are always started in the current directory, which causes issues when the process is a daemon and the current directory is a mountpoint. Fix this by adding an option to set the cwd of the new process with jobstart(). In addition, fix termopen() so that it actually uses the cwd option from the dict (it couldn't previously set the cwd value due to dead code). Signed-off-by: Aleksa Sarai --- src/nvim/eval.c | 39 +++++++++++++++++++++++++++------- src/nvim/event/libuv_process.c | 2 +- src/nvim/event/process.h | 2 ++ src/nvim/os/pty_process_unix.c | 7 ++++++ 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 3cd53b841d..56440d6ad6 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -11731,8 +11731,21 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv) dict_T *job_opts = NULL; ufunc_T *on_stdout = NULL, *on_stderr = NULL, *on_exit = NULL; + char *cwd = NULL; if (argvars[1].v_type == VAR_DICT) { job_opts = argvars[1].vval.v_dict; + + char *new_cwd = (char *)get_dict_string(job_opts, (char_u *)"cwd", false); + if (new_cwd && strlen(new_cwd) > 0) { + cwd = new_cwd; + // The new cwd must be a directory. + if (!os_isdir((char_u *)cwd)) { + EMSG2(_(e_invarg2), "expected valid directory"); + shell_free_argv(argv); + return; + } + } + if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { shell_free_argv(argv); return; @@ -11742,7 +11755,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv) bool pty = job_opts && get_dict_number(job_opts, (uint8_t *)"pty") != 0; bool detach = job_opts && get_dict_number(job_opts, (uint8_t *)"detach") != 0; TerminalJobData *data = common_job_init(argv, on_stdout, on_stderr, on_exit, - job_opts, pty, detach); + job_opts, pty, detach, cwd); Process *proc = (Process *)&data->proc; if (pty) { @@ -16424,8 +16437,21 @@ static void f_termopen(typval_T *argvars, typval_T *rettv) ufunc_T *on_stdout = NULL, *on_stderr = NULL, *on_exit = NULL; dict_T *job_opts = NULL; + char *cwd = "."; if (argvars[1].v_type == VAR_DICT) { job_opts = argvars[1].vval.v_dict; + + char *new_cwd = (char *)get_dict_string(job_opts, (char_u *)"cwd", false); + if (new_cwd && strlen(new_cwd) > 0) { + cwd = new_cwd; + // The new cwd must be a directory. + if (!os_isdir((char_u *)cwd)) { + EMSG2(_(e_invarg2), "expected valid directory"); + shell_free_argv(argv); + return; + } + } + if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { shell_free_argv(argv); return; @@ -16433,7 +16459,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv) } TerminalJobData *data = common_job_init(argv, on_stdout, on_stderr, on_exit, - job_opts, true, false); + job_opts, true, false, cwd); data->proc.pty.width = curwin->w_width; data->proc.pty.height = curwin->w_height; data->proc.pty.term_name = xstrdup("xterm-256color"); @@ -16448,11 +16474,6 @@ static void f_termopen(typval_T *argvars, typval_T *rettv) topts.resize_cb = term_resize; topts.close_cb = term_close; - char *cwd = "."; - if (argvars[1].v_type == VAR_STRING - && os_isdir(argvars[1].vval.v_string)) { - cwd = (char *)argvars[1].vval.v_string; - } int pid = data->proc.pty.process.pid; char buf[1024]; @@ -21717,7 +21738,8 @@ static inline TerminalJobData *common_job_init(char **argv, ufunc_T *on_exit, dict_T *self, bool pty, - bool detach) + bool detach, + char *cwd) { TerminalJobData *data = xcalloc(1, sizeof(TerminalJobData)); data->stopped = false; @@ -21741,6 +21763,7 @@ static inline TerminalJobData *common_job_init(char **argv, proc->cb = on_process_exit; proc->events = data->events; proc->detach = detach; + proc->cwd = cwd; return data; } diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index 9ef3468284..a68badcc8f 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -25,7 +25,7 @@ bool libuv_process_spawn(LibuvProcess *uvproc) uvproc->uvopts.flags |= UV_PROCESS_DETACHED; } uvproc->uvopts.exit_cb = exit_cb; - uvproc->uvopts.cwd = NULL; + uvproc->uvopts.cwd = proc->cwd; uvproc->uvopts.env = NULL; uvproc->uvopts.stdio = uvproc->uvstdio; uvproc->uvopts.stdio_count = 3; diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h index e23c8ea60f..a4c6e7eeb2 100644 --- a/src/nvim/event/process.h +++ b/src/nvim/event/process.h @@ -21,6 +21,7 @@ struct process { int pid, status, refcount; // set to the hrtime of when process_stop was called for the process. uint64_t stopped_time; + char *cwd; char **argv; Stream *in, *out, *err; process_exit_cb cb; @@ -40,6 +41,7 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data) .status = 0, .refcount = 0, .stopped_time = 0, + .cwd = NULL, .argv = NULL, .in = NULL, .out = NULL, diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index d0a38e663b..436de030ba 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -28,6 +28,7 @@ #include "nvim/event/process.h" #include "nvim/os/pty_process_unix.h" #include "nvim/log.h" +#include "nvim/os/os.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/pty_process_unix.c.generated.h" @@ -131,6 +132,12 @@ static void init_child(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL signal(SIGTERM, SIG_DFL); signal(SIGALRM, SIG_DFL); + Process *proc = (Process *)ptyproc; + if (proc->cwd && os_chdir(proc->cwd) != 0) { + fprintf(stderr, "chdir failed: %s\n", strerror(errno)); + return; + } + setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1); execvp(ptyproc->process.argv[0], ptyproc->process.argv); fprintf(stderr, "execvp failed: %s\n", strerror(errno)); From 1bb8930c92ae603f7dcef935fc1b7d760ed9e372 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Wed, 25 May 2016 18:09:33 +1000 Subject: [PATCH 2/4] test: add tests for cwd handling Add both a test for cwd=/ and cwd=/tmp/nvim.XXXXX, to make sure that we don't have regressions in cwd handling. Signed-off-by: Aleksa Sarai --- test/functional/job/job_spec.lua | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/test/functional/job/job_spec.lua b/test/functional/job/job_spec.lua index d21b9051e2..ec0bc84ea8 100644 --- a/test/functional/job/job_spec.lua +++ b/test/functional/job/job_spec.lua @@ -1,11 +1,11 @@ local helpers = require('test.functional.helpers') local clear, eq, eval, execute, feed, insert, neq, next_msg, nvim, - nvim_dir, ok, source, write_file = helpers.clear, + nvim_dir, ok, source, write_file, mkdir, rmdir = helpers.clear, helpers.eq, helpers.eval, helpers.execute, helpers.feed, helpers.insert, helpers.neq, helpers.next_message, helpers.nvim, helpers.nvim_dir, helpers.ok, helpers.source, - helpers.write_file + helpers.write_file, helpers.mkdir, helpers.rmdir local Screen = require('test.functional.ui.screen') @@ -37,6 +37,31 @@ describe('jobs', function() eq({'notification', 'exit', {0, 0}}, next_msg()) end) + it('changes to / when cwd provided', function() + nvim('command', "let g:job_opts.cwd = '/'") + nvim('command', "let j = jobstart('pwd', g:job_opts)") + eq({'notification', 'stdout', {0, {'/', ''}}}, next_msg()) + eq({'notification', 'exit', {0, 0}}, next_msg()) + end) + + it('changes to random directory when cwd provided', function() + local dir = eval('resolve(tempname())') + mkdir(dir) + nvim('command', "let g:job_opts.cwd = '" .. dir .. "'") + nvim('command', "let j = jobstart('pwd', g:job_opts)") + eq({'notification', 'stdout', {0, {dir, ''}}}, next_msg()) + eq({'notification', 'exit', {0, 0}}, next_msg()) + rmdir(dir) + end) + + it('fails to change to non-existent directory when provided', function() + local _, err = pcall(function() + nvim('command', "let g:job_opts.cwd = '/NONEXISTENT'") + nvim('command', "let j = jobstart('pwd', g:job_opts)") + end) + ok(string.find(err, "E475: Invalid argument: expected valid directory$") ~= nil) + end) + it('returns 0 when it fails to start', function() local status, rv = pcall(eval, "jobstart([])") eq(false, status) From bc99b4c483198287dfbe606575612571dc1e6db0 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Tue, 7 Jun 2016 00:02:33 +1000 Subject: [PATCH 3/4] doc: update docs to reflect new cwd option The termopen() docs don't need to be updated because they link to jobstart(). Signed-off-by: Aleksa Sarai --- runtime/doc/eval.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index a920d65992..314af7d311 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -4305,6 +4305,9 @@ jobstart({cmd}[, {opts}]) {Nvim} *jobstart()* - on_stdout: stdout event handler - on_stderr: stderr event handler - on_exit: exit event handler + - cwd: Working directory the job will be started from + (defaults to the current working directory of nvim, as given + by |getcwd()|). - pty: If set, the job will be connected to a new pseudo terminal, and the job streams are connected to the master file descriptor. From 96546fb69636bd53d8f72d193af709f13af75b68 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Wed, 25 May 2016 15:20:32 +1000 Subject: [PATCH 4/4] runtime: clipboard: start daemons in / This avoids the issue of nvim started daemons causing mountpoints to be unmountable. This is currently the only place in runtime/ where this calling convention occurred. Signed-off-by: Aleksa Sarai --- runtime/autoload/provider/clipboard.vim | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index c7cb14ded7..2272519dfd 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -94,6 +94,7 @@ function! s:clipboard.set(lines, regtype, reg) let selection.data = [a:lines, a:regtype] let argv = split(s:copy[a:reg], " ") let selection.detach = s:cache_enabled + let selection.cwd = "/" let jobid = jobstart(argv, selection) if jobid <= 0 echohl WarningMsg