commit 057d27a9d6ef0bb2ee5130704c45b9e9197e7c36
parent 5792546777332361a9ac49107e46149c703de90e
Author: Justin M. Keyes <justinkz@gmail.com>
Date: Sun, 15 Sep 2024 12:20:58 -0700
refactor: rename "process" => "proc" #30387
Problem:
- "process" is often used as a verb (`multiqueue_process_events`), which
is ambiguous for cases where it's used as a topic.
- The documented naming convention for processes is "proc".
- `:help dev-name-common`
- Shorter is better, when it doesn't harm readability or
discoverability.
Solution:
Rename "process" => "proc" in all C symbols and module names.
Diffstat:
39 files changed, 1950 insertions(+), 1949 deletions(-)
diff --git a/src/.valgrind.supp b/src/.valgrind.supp
@@ -10,7 +10,7 @@
Memcheck:Leak
fun:malloc
fun:uv_spawn
- fun:libuv_process_spawn
- fun:process_spawn
+ fun:libuv_proc_spawn
+ fun:proc_spawn
fun:job_start
}
diff --git a/src/clint.py b/src/clint.py
@@ -848,7 +848,7 @@ def CheckIncludes(filename, lines, error):
or filename.endswith('.in.h')
or FileInfo(filename).RelativePath() in {
'func_attr.h',
- 'os/pty_process.h',
+ 'os/pty_proc.h',
}):
return
@@ -869,7 +869,7 @@ def CheckIncludes(filename, lines, error):
"src/nvim/msgpack_rpc/unpacker.h",
"src/nvim/option.h",
"src/nvim/os/pty_conpty_win.h",
- "src/nvim/os/pty_process_win.h",
+ "src/nvim/os/pty_proc_win.h",
]
skip_headers = [
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
@@ -417,10 +417,10 @@ list(SORT NVIM_HEADERS)
foreach(sfile ${NVIM_SOURCES})
get_filename_component(f ${sfile} NAME)
- if(WIN32 AND ${f} MATCHES "^(pty_process_unix.c)$")
+ if(WIN32 AND ${f} MATCHES "^(pty_proc_unix.c)$")
list(REMOVE_ITEM NVIM_SOURCES ${sfile})
endif()
- if(NOT WIN32 AND ${f} MATCHES "^(pty_process_win.c)$")
+ if(NOT WIN32 AND ${f} MATCHES "^(pty_proc_win.c)$")
list(REMOVE_ITEM NVIM_SOURCES ${sfile})
endif()
if(NOT WIN32 AND ${f} MATCHES "^(pty_conpty_win.c)$")
@@ -436,7 +436,7 @@ foreach(hfile ${NVIM_HEADERS})
if(WIN32 AND ${f} MATCHES "^(unix_defs.h)$")
list(REMOVE_ITEM NVIM_HEADERS ${hfile})
endif()
- if(WIN32 AND ${f} MATCHES "^(pty_process_unix.h)$")
+ if(WIN32 AND ${f} MATCHES "^(pty_proc_unix.h)$")
list(REMOVE_ITEM NVIM_HEADERS ${hfile})
endif()
if(NOT WIN32 AND ${f} MATCHES "^(win_defs.h)$")
@@ -832,12 +832,12 @@ find_program(CLANG_TIDY_PRG clang-tidy)
set(EXCLUDE_CLANG_TIDY typval_encode.c.h ui_events.in.h)
if(WIN32)
list(APPEND EXCLUDE_CLANG_TIDY
- os/pty_process_unix.h
+ os/pty_proc_unix.h
os/unix_defs.h)
else()
list(APPEND EXCLUDE_CLANG_TIDY
os/win_defs.h
- os/pty_process_win.h
+ os/pty_proc_win.h
os/pty_conpty_win.h
os/os_win_console.h)
endif()
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
@@ -70,7 +70,7 @@
#include "nvim/optionstr.h"
#include "nvim/os/input.h"
#include "nvim/os/os_defs.h"
-#include "nvim/os/process.h"
+#include "nvim/os/proc.h"
#include "nvim/popupmenu.h"
#include "nvim/pos_defs.h"
#include "nvim/runtime.h"
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
@@ -19,7 +19,7 @@
#include "nvim/eval/typval.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
-#include "nvim/event/process.h"
+#include "nvim/event/proc.h"
#include "nvim/event/rstream.h"
#include "nvim/event/socket.h"
#include "nvim/event/stream.h"
@@ -88,7 +88,7 @@ void channel_free_all_mem(void)
bool channel_close(uint64_t id, ChannelPart part, const char **error)
{
Channel *chan;
- Process *proc;
+ Proc *proc;
const char *dummy;
if (!error) {
@@ -139,8 +139,8 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error)
if (part == kChannelPartStderr || part == kChannelPartAll) {
rstream_may_close(&proc->err);
}
- if (proc->type == kProcessTypePty && part == kChannelPartAll) {
- pty_process_close_master(&chan->stream.pty);
+ if (proc->type == kProcTypePty && part == kChannelPartAll) {
+ pty_proc_close_master(&chan->stream.pty);
}
break;
@@ -289,7 +289,7 @@ static void channel_destroy(Channel *chan)
}
if (chan->streamtype == kChannelStreamProc) {
- process_free(&chan->stream.proc);
+ proc_free(&chan->stream.proc);
}
callback_reader_free(&chan->on_data);
@@ -376,7 +376,7 @@ Channel *channel_job_start(char **argv, const char *exepath, CallbackReader on_s
*status_out = 0;
return NULL;
}
- chan->stream.pty = pty_process_init(&main_loop, chan);
+ chan->stream.pty = pty_proc_init(&main_loop, chan);
if (pty_width > 0) {
chan->stream.pty.width = pty_width;
}
@@ -384,22 +384,22 @@ Channel *channel_job_start(char **argv, const char *exepath, CallbackReader on_s
chan->stream.pty.height = pty_height;
}
} else {
- chan->stream.uv = libuv_process_init(&main_loop, chan);
+ chan->stream.uv = libuv_proc_init(&main_loop, chan);
}
- Process *proc = &chan->stream.proc;
+ Proc *proc = &chan->stream.proc;
proc->argv = argv;
proc->exepath = exepath;
- proc->cb = channel_process_exit_cb;
+ proc->cb = channel_proc_exit_cb;
proc->events = chan->events;
proc->detach = detach;
proc->cwd = cwd;
proc->env = env;
proc->overlapped = overlapped;
- char *cmd = xstrdup(process_get_exepath(proc));
+ char *cmd = xstrdup(proc_get_exepath(proc));
bool has_out, has_err;
- if (proc->type == kProcessTypePty) {
+ if (proc->type == kProcTypePty) {
has_out = true;
has_err = false;
} else {
@@ -410,7 +410,7 @@ Channel *channel_job_start(char **argv, const char *exepath, CallbackReader on_s
bool has_in = stdin_mode == kChannelStdinPipe;
- int status = process_spawn(proc, has_in, has_out, has_err);
+ int status = proc_spawn(proc, has_in, has_out, has_err);
if (status) {
semsg(_(e_jobspawn), os_strerror(status), cmd);
xfree(cmd);
@@ -760,7 +760,7 @@ void channel_reader_callbacks(Channel *chan, CallbackReader *reader)
}
}
-static void channel_process_exit_cb(Process *proc, int status, void *data)
+static void channel_proc_exit_cb(Proc *proc, int status, void *data)
{
Channel *chan = data;
if (chan->term) {
@@ -847,7 +847,7 @@ static void term_write(const char *buf, size_t size, void *data)
static void term_resize(uint16_t width, uint16_t height, void *data)
{
Channel *chan = data;
- pty_process_resize(&chan->stream.pty, width, height);
+ pty_proc_resize(&chan->stream.pty, width, height);
}
static inline void term_delayed_free(void **argv)
@@ -867,7 +867,7 @@ static inline void term_delayed_free(void **argv)
static void term_close(void *data)
{
Channel *chan = data;
- process_stop(&chan->stream.proc);
+ proc_stop(&chan->stream.proc);
multiqueue_put(chan->events, term_delayed_free, data);
}
@@ -907,7 +907,7 @@ bool channel_job_running(uint64_t id)
Channel *chan = find_channel(id);
return (chan
&& chan->streamtype == kChannelStreamProc
- && !process_is_stopped(&chan->stream.proc));
+ && !proc_is_stopped(&chan->stream.proc));
}
Dictionary channel_info(uint64_t id, Arena *arena)
@@ -924,8 +924,8 @@ Dictionary channel_info(uint64_t id, Arena *arena)
switch (chan->streamtype) {
case kChannelStreamProc: {
stream_desc = "job";
- if (chan->stream.proc.type == kProcessTypePty) {
- const char *name = pty_process_tty_name(&chan->stream.pty);
+ if (chan->stream.proc.type == kProcTypePty) {
+ const char *name = pty_proc_tty_name(&chan->stream.pty);
PUT_C(info, "pty", CSTR_TO_ARENA_OBJ(arena, name));
}
diff --git a/src/nvim/channel.h b/src/nvim/channel.h
@@ -7,11 +7,11 @@
#include "nvim/channel_defs.h" // IWYU pragma: keep
#include "nvim/eval/typval_defs.h"
#include "nvim/event/defs.h"
-#include "nvim/event/libuv_process.h"
+#include "nvim/event/libuv_proc.h"
#include "nvim/macros_defs.h"
#include "nvim/map_defs.h"
#include "nvim/msgpack_rpc/channel_defs.h"
-#include "nvim/os/pty_process.h"
+#include "nvim/os/pty_proc.h"
#include "nvim/types_defs.h"
struct Channel {
@@ -21,9 +21,9 @@ struct Channel {
ChannelStreamType streamtype;
union {
- Process proc;
- LibuvProcess uv;
- PtyProcess pty;
+ Proc proc;
+ LibuvProc uv;
+ PtyProc pty;
RStream socket;
StdioPair stdio;
StderrState err;
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
@@ -32,7 +32,7 @@
#include "nvim/eval/vars.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
-#include "nvim/event/process.h"
+#include "nvim/event/proc.h"
#include "nvim/event/time.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
@@ -8506,7 +8506,7 @@ Channel *find_job(uint64_t id, bool show_error)
{
Channel *data = find_channel(id);
if (!data || data->streamtype != kChannelStreamProc
- || process_is_stopped(&data->stream.proc)) {
+ || proc_is_stopped(&data->stream.proc)) {
if (show_error) {
if (data && data->streamtype != kChannelStreamProc) {
emsg(_(e_invchanjob));
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
@@ -49,7 +49,7 @@
#include "nvim/event/defs.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
-#include "nvim/event/process.h"
+#include "nvim/event/proc.h"
#include "nvim/event/time.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
@@ -101,7 +101,7 @@
#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
-#include "nvim/os/pty_process.h"
+#include "nvim/os/pty_proc.h"
#include "nvim/os/shell.h"
#include "nvim/os/stdpaths_defs.h"
#include "nvim/os/time.h"
@@ -3770,7 +3770,7 @@ static void f_jobpid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- Process *proc = &data->stream.proc;
+ Proc *proc = &data->stream.proc;
rettv->vval.v_number = proc->pid;
}
@@ -3796,13 +3796,13 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- if (data->stream.proc.type != kProcessTypePty) {
+ if (data->stream.proc.type != kProcTypePty) {
emsg(_(e_channotpty));
return;
}
- pty_process_resize(&data->stream.pty, (uint16_t)argvars[1].vval.v_number,
- (uint16_t)argvars[2].vval.v_number);
+ pty_proc_resize(&data->stream.pty, (uint16_t)argvars[1].vval.v_number,
+ (uint16_t)argvars[2].vval.v_number);
rettv->vval.v_number = 1;
}
@@ -4077,7 +4077,7 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// Ignore return code, but show error later.
channel_close(data->id, kChannelPartRpc, &error);
}
- process_stop(&data->stream.proc);
+ proc_stop(&data->stream.proc);
rettv->vval.v_number = 1;
if (error) {
emsg(error);
@@ -4113,10 +4113,10 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|| !(chan = find_channel((uint64_t)TV_LIST_ITEM_TV(arg)->vval.v_number))
|| chan->streamtype != kChannelStreamProc) {
jobs[i] = NULL; // Invalid job.
- } else if (process_is_stopped(&chan->stream.proc)) {
+ } else if (proc_is_stopped(&chan->stream.proc)) {
// Job is stopped but not fully destroyed.
// Ensure all callbacks on its event queue are executed. #15402
- process_wait(&chan->stream.proc, -1, NULL);
+ proc_wait(&chan->stream.proc, -1, NULL);
jobs[i] = NULL; // Invalid job.
} else {
jobs[i] = chan;
@@ -4144,8 +4144,8 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (jobs[i] == NULL) {
continue; // Invalid job, will assign status=-3 below.
}
- int status = process_wait(&jobs[i]->stream.proc, remaining,
- waiting_jobs);
+ int status = proc_wait(&jobs[i]->stream.proc, remaining,
+ waiting_jobs);
if (status < 0) {
break; // Interrupted (CTRL-C) or timeout, skip remaining jobs.
}
@@ -8207,7 +8207,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- int pid = chan->stream.pty.process.pid;
+ int pid = chan->stream.pty.proc.pid;
// "./…" => "/home/foo/…"
vim_FullName(cwd, NameBuff, sizeof(NameBuff), false);
diff --git a/src/nvim/event/defs.h b/src/nvim/event/defs.h
@@ -142,30 +142,31 @@ struct socket_watcher {
};
typedef enum {
- kProcessTypeUv,
- kProcessTypePty,
-} ProcessType;
+ kProcTypeUv,
+ kProcTypePty,
+} ProcType;
-typedef struct process Process;
-typedef void (*process_exit_cb)(Process *proc, int status, void *data);
-typedef void (*internal_process_cb)(Process *proc);
+/// OS process
+typedef struct proc Proc;
+typedef void (*proc_exit_cb)(Proc *proc, int status, void *data);
+typedef void (*internal_proc_cb)(Proc *proc);
-struct process {
- ProcessType type;
+struct proc {
+ ProcType type;
Loop *loop;
void *data;
int pid, status, refcount;
uint8_t exit_signal; // Signal used when killing (on Windows).
- uint64_t stopped_time; // process_stop() timestamp
+ uint64_t stopped_time; // proc_stop() timestamp
const char *cwd;
char **argv;
const char *exepath;
dict_T *env;
Stream in;
RStream out, err;
- /// Exit handler. If set, user must call process_free().
- process_exit_cb cb;
- internal_process_cb internal_exit_cb, internal_close_cb;
+ /// Exit handler. If set, user must call proc_free().
+ proc_exit_cb cb;
+ internal_proc_cb internal_exit_cb, internal_close_cb;
bool closed, detach, overlapped, fwd_err;
MultiQueue *events;
};
diff --git a/src/nvim/event/libuv_proc.c b/src/nvim/event/libuv_proc.c
@@ -0,0 +1,139 @@
+#include <assert.h>
+#include <locale.h>
+#include <stdint.h>
+#include <uv.h>
+
+#include "nvim/eval/typval.h"
+#include "nvim/event/defs.h"
+#include "nvim/event/libuv_proc.h"
+#include "nvim/event/loop.h"
+#include "nvim/event/proc.h"
+#include "nvim/log.h"
+#include "nvim/os/os.h"
+#include "nvim/os/os_defs.h"
+#include "nvim/types_defs.h"
+#include "nvim/ui_client.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "event/libuv_proc.c.generated.h"
+#endif
+
+/// @returns zero on success, or negative error code
+int libuv_proc_spawn(LibuvProc *uvproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+ Proc *proc = (Proc *)uvproc;
+ uvproc->uvopts.file = proc_get_exepath(proc);
+ uvproc->uvopts.args = proc->argv;
+ uvproc->uvopts.flags = UV_PROCESS_WINDOWS_HIDE;
+#ifdef MSWIN
+ // libuv collapses the argv to a CommandLineToArgvW()-style string. cmd.exe
+ // expects a different syntax (must be prepared by the caller before now).
+ if (os_shell_is_cmdexe(proc->argv[0])) {
+ uvproc->uvopts.flags |= UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS;
+ }
+ if (proc->detach) {
+ uvproc->uvopts.flags |= UV_PROCESS_DETACHED;
+ }
+#else
+ // Always setsid() on unix-likes. #8107
+ uvproc->uvopts.flags |= UV_PROCESS_DETACHED;
+#endif
+ uvproc->uvopts.exit_cb = exit_cb;
+ uvproc->uvopts.cwd = proc->cwd;
+
+ uvproc->uvopts.stdio = uvproc->uvstdio;
+ uvproc->uvopts.stdio_count = 3;
+ uvproc->uvstdio[0].flags = UV_IGNORE;
+ uvproc->uvstdio[1].flags = UV_IGNORE;
+ uvproc->uvstdio[2].flags = UV_IGNORE;
+
+ if (ui_client_forward_stdin) {
+ assert(UI_CLIENT_STDIN_FD == 3);
+ uvproc->uvopts.stdio_count = 4;
+ uvproc->uvstdio[3].data.fd = 0;
+ uvproc->uvstdio[3].flags = UV_INHERIT_FD;
+ }
+ 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) {
+ uvproc->uvstdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
+#ifdef MSWIN
+ uvproc->uvstdio[0].flags |= proc->overlapped ? UV_OVERLAPPED_PIPE : 0;
+#endif
+ uvproc->uvstdio[0].data.stream = (uv_stream_t *)(&proc->in.uv.pipe);
+ }
+
+ if (!proc->out.s.closed) {
+ uvproc->uvstdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
+#ifdef MSWIN
+ // pipe must be readable for IOCP to work on Windows.
+ uvproc->uvstdio[1].flags |= proc->overlapped
+ ? (UV_READABLE_PIPE | UV_OVERLAPPED_PIPE) : 0;
+#endif
+ uvproc->uvstdio[1].data.stream = (uv_stream_t *)(&proc->out.s.uv.pipe);
+ }
+
+ if (!proc->err.s.closed) {
+ uvproc->uvstdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
+ uvproc->uvstdio[2].data.stream = (uv_stream_t *)(&proc->err.s.uv.pipe);
+ } else if (proc->fwd_err) {
+ uvproc->uvstdio[2].flags = UV_INHERIT_FD;
+ uvproc->uvstdio[2].data.fd = STDERR_FILENO;
+ }
+
+ int status;
+ if ((status = uv_spawn(&proc->loop->uv, &uvproc->uv, &uvproc->uvopts))) {
+ ILOG("uv_spawn(%s) failed: %s", uvproc->uvopts.file, uv_strerror(status));
+ if (uvproc->uvopts.env) {
+ os_free_fullenv(uvproc->uvopts.env);
+ }
+ return status;
+ }
+
+ proc->pid = uvproc->uv.pid;
+ return status;
+}
+
+void libuv_proc_close(LibuvProc *uvproc)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ uv_close((uv_handle_t *)&uvproc->uv, close_cb);
+}
+
+static void close_cb(uv_handle_t *handle)
+{
+ Proc *proc = handle->data;
+ if (proc->internal_close_cb) {
+ proc->internal_close_cb(proc);
+ }
+ LibuvProc *uvproc = (LibuvProc *)proc;
+ if (uvproc->uvopts.env) {
+ os_free_fullenv(uvproc->uvopts.env);
+ }
+}
+
+static void exit_cb(uv_process_t *handle, int64_t status, int term_signal)
+{
+ Proc *proc = handle->data;
+#if defined(MSWIN)
+ // Use stored/expected signal.
+ term_signal = proc->exit_signal;
+#endif
+ proc->status = term_signal ? 128 + term_signal : (int)status;
+ proc->internal_exit_cb(proc);
+}
+
+LibuvProc libuv_proc_init(Loop *loop, void *data)
+{
+ LibuvProc rv = {
+ .proc = proc_init(loop, kProcTypeUv, data)
+ };
+ return rv;
+}
diff --git a/src/nvim/event/libuv_proc.h b/src/nvim/event/libuv_proc.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <uv.h>
+
+#include "nvim/event/defs.h"
+
+typedef struct {
+ Proc proc;
+ uv_process_t uv;
+ uv_process_options_t uvopts;
+ uv_stdio_container_t uvstdio[4];
+} LibuvProc;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "event/libuv_proc.h.generated.h"
+#endif
diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c
@@ -1,139 +0,0 @@
-#include <assert.h>
-#include <locale.h>
-#include <stdint.h>
-#include <uv.h>
-
-#include "nvim/eval/typval.h"
-#include "nvim/event/defs.h"
-#include "nvim/event/libuv_process.h"
-#include "nvim/event/loop.h"
-#include "nvim/event/process.h"
-#include "nvim/log.h"
-#include "nvim/os/os.h"
-#include "nvim/os/os_defs.h"
-#include "nvim/types_defs.h"
-#include "nvim/ui_client.h"
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "event/libuv_process.c.generated.h"
-#endif
-
-/// @returns zero on success, or negative error code
-int libuv_process_spawn(LibuvProcess *uvproc)
- FUNC_ATTR_NONNULL_ALL
-{
- Process *proc = (Process *)uvproc;
- uvproc->uvopts.file = process_get_exepath(proc);
- uvproc->uvopts.args = proc->argv;
- uvproc->uvopts.flags = UV_PROCESS_WINDOWS_HIDE;
-#ifdef MSWIN
- // libuv collapses the argv to a CommandLineToArgvW()-style string. cmd.exe
- // expects a different syntax (must be prepared by the caller before now).
- if (os_shell_is_cmdexe(proc->argv[0])) {
- uvproc->uvopts.flags |= UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS;
- }
- if (proc->detach) {
- uvproc->uvopts.flags |= UV_PROCESS_DETACHED;
- }
-#else
- // Always setsid() on unix-likes. #8107
- uvproc->uvopts.flags |= UV_PROCESS_DETACHED;
-#endif
- uvproc->uvopts.exit_cb = exit_cb;
- uvproc->uvopts.cwd = proc->cwd;
-
- uvproc->uvopts.stdio = uvproc->uvstdio;
- uvproc->uvopts.stdio_count = 3;
- uvproc->uvstdio[0].flags = UV_IGNORE;
- uvproc->uvstdio[1].flags = UV_IGNORE;
- uvproc->uvstdio[2].flags = UV_IGNORE;
-
- if (ui_client_forward_stdin) {
- assert(UI_CLIENT_STDIN_FD == 3);
- uvproc->uvopts.stdio_count = 4;
- uvproc->uvstdio[3].data.fd = 0;
- uvproc->uvstdio[3].flags = UV_INHERIT_FD;
- }
- 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) {
- uvproc->uvstdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
-#ifdef MSWIN
- uvproc->uvstdio[0].flags |= proc->overlapped ? UV_OVERLAPPED_PIPE : 0;
-#endif
- uvproc->uvstdio[0].data.stream = (uv_stream_t *)(&proc->in.uv.pipe);
- }
-
- if (!proc->out.s.closed) {
- uvproc->uvstdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
-#ifdef MSWIN
- // pipe must be readable for IOCP to work on Windows.
- uvproc->uvstdio[1].flags |= proc->overlapped
- ? (UV_READABLE_PIPE | UV_OVERLAPPED_PIPE) : 0;
-#endif
- uvproc->uvstdio[1].data.stream = (uv_stream_t *)(&proc->out.s.uv.pipe);
- }
-
- if (!proc->err.s.closed) {
- uvproc->uvstdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
- uvproc->uvstdio[2].data.stream = (uv_stream_t *)(&proc->err.s.uv.pipe);
- } else if (proc->fwd_err) {
- uvproc->uvstdio[2].flags = UV_INHERIT_FD;
- uvproc->uvstdio[2].data.fd = STDERR_FILENO;
- }
-
- int status;
- if ((status = uv_spawn(&proc->loop->uv, &uvproc->uv, &uvproc->uvopts))) {
- ILOG("uv_spawn(%s) failed: %s", uvproc->uvopts.file, uv_strerror(status));
- if (uvproc->uvopts.env) {
- os_free_fullenv(uvproc->uvopts.env);
- }
- return status;
- }
-
- proc->pid = uvproc->uv.pid;
- return status;
-}
-
-void libuv_process_close(LibuvProcess *uvproc)
- FUNC_ATTR_NONNULL_ARG(1)
-{
- uv_close((uv_handle_t *)&uvproc->uv, close_cb);
-}
-
-static void close_cb(uv_handle_t *handle)
-{
- Process *proc = handle->data;
- if (proc->internal_close_cb) {
- proc->internal_close_cb(proc);
- }
- LibuvProcess *uvproc = (LibuvProcess *)proc;
- if (uvproc->uvopts.env) {
- os_free_fullenv(uvproc->uvopts.env);
- }
-}
-
-static void exit_cb(uv_process_t *handle, int64_t status, int term_signal)
-{
- Process *proc = handle->data;
-#if defined(MSWIN)
- // Use stored/expected signal.
- term_signal = proc->exit_signal;
-#endif
- proc->status = term_signal ? 128 + term_signal : (int)status;
- proc->internal_exit_cb(proc);
-}
-
-LibuvProcess libuv_process_init(Loop *loop, void *data)
-{
- LibuvProcess rv = {
- .process = process_init(loop, kProcessTypeUv, data)
- };
- return rv;
-}
diff --git a/src/nvim/event/libuv_process.h b/src/nvim/event/libuv_process.h
@@ -1,16 +0,0 @@
-#pragma once
-
-#include <uv.h>
-
-#include "nvim/event/defs.h"
-
-typedef struct {
- Process process;
- uv_process_t uv;
- uv_process_options_t uvopts;
- uv_stdio_container_t uvstdio[4];
-} LibuvProcess;
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "event/libuv_process.h.generated.h"
-#endif
diff --git a/src/nvim/event/proc.c b/src/nvim/event/proc.c
@@ -0,0 +1,451 @@
+#include <assert.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <uv.h>
+
+#include "klib/klist.h"
+#include "nvim/event/libuv_proc.h"
+#include "nvim/event/loop.h"
+#include "nvim/event/multiqueue.h"
+#include "nvim/event/proc.h"
+#include "nvim/event/rstream.h"
+#include "nvim/event/stream.h"
+#include "nvim/event/wstream.h"
+#include "nvim/globals.h"
+#include "nvim/log.h"
+#include "nvim/main.h"
+#include "nvim/os/proc.h"
+#include "nvim/os/pty_proc.h"
+#include "nvim/os/shell.h"
+#include "nvim/os/time.h"
+#include "nvim/ui_client.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "event/proc.c.generated.h"
+#endif
+
+// Time for a process to exit cleanly before we send KILL.
+// For PTY processes SIGTERM is sent first (in case SIGHUP was not enough).
+#define KILL_TIMEOUT_MS 2000
+
+/// Externally defined with gcov.
+#ifdef USE_GCOV
+void __gcov_flush(void);
+#endif
+
+static bool proc_is_tearing_down = false;
+
+// Delay exit until handles are closed, to avoid deadlocks
+static int exit_need_delay = 0;
+
+/// @returns zero on success, or negative error code
+int proc_spawn(Proc *proc, bool in, bool out, bool err)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // forwarding stderr contradicts with processing it internally
+ assert(!(err && proc->fwd_err));
+
+ if (in) {
+ uv_pipe_init(&proc->loop->uv, &proc->in.uv.pipe, 0);
+ } else {
+ proc->in.closed = true;
+ }
+
+ if (out) {
+ uv_pipe_init(&proc->loop->uv, &proc->out.s.uv.pipe, 0);
+ } else {
+ proc->out.s.closed = true;
+ }
+
+ if (err) {
+ uv_pipe_init(&proc->loop->uv, &proc->err.s.uv.pipe, 0);
+ } else {
+ proc->err.s.closed = true;
+ }
+
+#ifdef USE_GCOV
+ // Flush coverage data before forking, to avoid "Merge mismatch" errors.
+ __gcov_flush();
+#endif
+
+ int status;
+ switch (proc->type) {
+ case kProcTypeUv:
+ status = libuv_proc_spawn((LibuvProc *)proc);
+ break;
+ case kProcTypePty:
+ status = pty_proc_spawn((PtyProc *)proc);
+ break;
+ }
+
+ if (status) {
+ if (in) {
+ uv_close((uv_handle_t *)&proc->in.uv.pipe, NULL);
+ }
+ if (out) {
+ uv_close((uv_handle_t *)&proc->out.s.uv.pipe, NULL);
+ }
+ if (err) {
+ uv_close((uv_handle_t *)&proc->err.s.uv.pipe, NULL);
+ }
+
+ if (proc->type == kProcTypeUv) {
+ uv_close((uv_handle_t *)&(((LibuvProc *)proc)->uv), NULL);
+ } else {
+ proc_close(proc);
+ }
+ proc_free(proc);
+ proc->status = -1;
+ return status;
+ }
+
+ if (in) {
+ stream_init(NULL, &proc->in, -1, (uv_stream_t *)&proc->in.uv.pipe);
+ proc->in.internal_data = proc;
+ proc->in.internal_close_cb = on_proc_stream_close;
+ proc->refcount++;
+ }
+
+ if (out) {
+ stream_init(NULL, &proc->out.s, -1, (uv_stream_t *)&proc->out.s.uv.pipe);
+ proc->out.s.internal_data = proc;
+ proc->out.s.internal_close_cb = on_proc_stream_close;
+ proc->refcount++;
+ }
+
+ if (err) {
+ stream_init(NULL, &proc->err.s, -1, (uv_stream_t *)&proc->err.s.uv.pipe);
+ proc->err.s.internal_data = proc;
+ proc->err.s.internal_close_cb = on_proc_stream_close;
+ proc->refcount++;
+ }
+
+ proc->internal_exit_cb = on_proc_exit;
+ proc->internal_close_cb = decref;
+ proc->refcount++;
+ kl_push(WatcherPtr, proc->loop->children, proc);
+ DLOG("new: pid=%d exepath=[%s]", proc->pid, proc_get_exepath(proc));
+ return 0;
+}
+
+void proc_teardown(Loop *loop) FUNC_ATTR_NONNULL_ALL
+{
+ proc_is_tearing_down = true;
+ kl_iter(WatcherPtr, loop->children, current) {
+ Proc *proc = (*current)->data;
+ if (proc->detach || proc->type == kProcTypePty) {
+ // Close handles to process without killing it.
+ CREATE_EVENT(loop->events, proc_close_handles, proc);
+ } else {
+ proc_stop(proc);
+ }
+ }
+
+ // Wait until all children exit and all close events are processed.
+ LOOP_PROCESS_EVENTS_UNTIL(loop, loop->events, -1,
+ kl_empty(loop->children) && multiqueue_empty(loop->events));
+ pty_proc_teardown(loop);
+}
+
+void proc_close_streams(Proc *proc) FUNC_ATTR_NONNULL_ALL
+{
+ wstream_may_close(&proc->in);
+ rstream_may_close(&proc->out);
+ rstream_may_close(&proc->err);
+}
+
+/// Synchronously wait for a process to finish
+///
+/// @param process Process instance
+/// @param ms Time in milliseconds to wait for the process.
+/// 0 for no wait. -1 to wait until the process quits.
+/// @return Exit code of the process. proc->status will have the same value.
+/// -1 if the timeout expired while the process is still running.
+/// -2 if the user interrupted the wait.
+int proc_wait(Proc *proc, int ms, MultiQueue *events)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ if (!proc->refcount) {
+ int status = proc->status;
+ LOOP_PROCESS_EVENTS(proc->loop, proc->events, 0);
+ return status;
+ }
+
+ if (!events) {
+ events = proc->events;
+ }
+
+ // Increase refcount to stop the exit callback from being called (and possibly
+ // freed) before we have a chance to get the status.
+ proc->refcount++;
+ LOOP_PROCESS_EVENTS_UNTIL(proc->loop, events, ms,
+ // Until...
+ got_int // interrupted by the user
+ || proc->refcount == 1); // job exited
+
+ // Assume that a user hitting CTRL-C does not like the current job. Kill it.
+ if (got_int) {
+ got_int = false;
+ proc_stop(proc);
+ if (ms == -1) {
+ // We can only return if all streams/handles are closed and the job
+ // exited.
+ LOOP_PROCESS_EVENTS_UNTIL(proc->loop, events, -1,
+ proc->refcount == 1);
+ } else {
+ LOOP_PROCESS_EVENTS(proc->loop, events, 0);
+ }
+
+ proc->status = -2;
+ }
+
+ if (proc->refcount == 1) {
+ // Job exited, free its resources.
+ decref(proc);
+ if (proc->events) {
+ // decref() created an exit event, process it now.
+ multiqueue_process_events(proc->events);
+ }
+ } else {
+ proc->refcount--;
+ }
+
+ return proc->status;
+}
+
+/// Ask a process to terminate and eventually kill if it doesn't respond
+void proc_stop(Proc *proc) FUNC_ATTR_NONNULL_ALL
+{
+ bool exited = (proc->status >= 0);
+ if (exited || proc->stopped_time) {
+ return;
+ }
+ proc->stopped_time = os_hrtime();
+ proc->exit_signal = SIGTERM;
+
+ switch (proc->type) {
+ case kProcTypeUv:
+ os_proc_tree_kill(proc->pid, SIGTERM);
+ break;
+ case kProcTypePty:
+ // close all streams for pty processes to send SIGHUP to the process
+ proc_close_streams(proc);
+ pty_proc_close_master((PtyProc *)proc);
+ break;
+ }
+
+ // (Re)start timer to verify that stopped process(es) died.
+ uv_timer_start(&proc->loop->children_kill_timer, children_kill_cb,
+ KILL_TIMEOUT_MS, 0);
+}
+
+/// Frees process-owned resources.
+void proc_free(Proc *proc) FUNC_ATTR_NONNULL_ALL
+{
+ if (proc->argv != NULL) {
+ shell_free_argv(proc->argv);
+ proc->argv = NULL;
+ }
+}
+
+/// Sends SIGKILL (or SIGTERM..SIGKILL for PTY jobs) to processes that did
+/// not terminate after proc_stop().
+static void children_kill_cb(uv_timer_t *handle)
+{
+ Loop *loop = handle->loop->data;
+
+ kl_iter(WatcherPtr, loop->children, current) {
+ Proc *proc = (*current)->data;
+ bool exited = (proc->status >= 0);
+ if (exited || !proc->stopped_time) {
+ continue;
+ }
+ uint64_t term_sent = UINT64_MAX == proc->stopped_time;
+ if (kProcTypePty != proc->type || term_sent) {
+ proc->exit_signal = SIGKILL;
+ os_proc_tree_kill(proc->pid, SIGKILL);
+ } else {
+ proc->exit_signal = SIGTERM;
+ os_proc_tree_kill(proc->pid, SIGTERM);
+ proc->stopped_time = UINT64_MAX; // Flag: SIGTERM was sent.
+ // Restart timer.
+ uv_timer_start(&proc->loop->children_kill_timer, children_kill_cb,
+ KILL_TIMEOUT_MS, 0);
+ }
+ }
+}
+
+static void proc_close_event(void **argv)
+{
+ Proc *proc = argv[0];
+ if (proc->cb) {
+ // User (hint: channel_job_start) is responsible for calling
+ // proc_free().
+ proc->cb(proc, proc->status, proc->data);
+ } else {
+ proc_free(proc);
+ }
+}
+
+static void decref(Proc *proc)
+{
+ if (--proc->refcount != 0) {
+ return;
+ }
+
+ Loop *loop = proc->loop;
+ kliter_t(WatcherPtr) **node = NULL;
+ kl_iter(WatcherPtr, loop->children, current) {
+ if ((*current)->data == proc) {
+ node = current;
+ break;
+ }
+ }
+ assert(node);
+ kl_shift_at(WatcherPtr, loop->children, node);
+ CREATE_EVENT(proc->events, proc_close_event, proc);
+}
+
+static void proc_close(Proc *proc)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ if (proc_is_tearing_down && (proc->detach || proc->type == kProcTypePty)
+ && proc->closed) {
+ // If a detached/pty process dies while tearing down it might get closed
+ // twice.
+ return;
+ }
+ assert(!proc->closed);
+ proc->closed = true;
+
+ if (proc->detach) {
+ if (proc->type == kProcTypeUv) {
+ uv_unref((uv_handle_t *)&(((LibuvProc *)proc)->uv));
+ }
+ }
+
+ switch (proc->type) {
+ case kProcTypeUv:
+ libuv_proc_close((LibuvProc *)proc);
+ break;
+ case kProcTypePty:
+ pty_proc_close((PtyProc *)proc);
+ break;
+ }
+}
+
+/// Flush output stream.
+///
+/// @param proc Process, for which an output stream should be flushed.
+/// @param stream Stream to flush.
+static void flush_stream(Proc *proc, RStream *stream)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ if (!stream || stream->s.closed) {
+ return;
+ }
+
+ // Maximal remaining data size of terminated process is system
+ // buffer size.
+ // Also helps with a child process that keeps the output streams open. If it
+ // keeps sending data, we only accept as much data as the system buffer size.
+ // Otherwise this would block cleanup/teardown.
+ int system_buffer_size = 0;
+ int err = uv_recv_buffer_size((uv_handle_t *)&stream->s.uv.pipe,
+ &system_buffer_size);
+ if (err) {
+ system_buffer_size = ARENA_BLOCK_SIZE;
+ }
+
+ size_t max_bytes = stream->num_bytes + (size_t)system_buffer_size;
+
+ // Read remaining data.
+ while (!stream->s.closed && stream->num_bytes < max_bytes) {
+ // Remember number of bytes before polling
+ size_t num_bytes = stream->num_bytes;
+
+ // Poll for data and process the generated events.
+ loop_poll_events(proc->loop, 0);
+ if (stream->s.events) {
+ multiqueue_process_events(stream->s.events);
+ }
+
+ // Stream can be closed if it is empty.
+ if (num_bytes == stream->num_bytes) {
+ if (stream->read_cb && !stream->did_eof) {
+ // Stream callback could miss EOF handling if a child keeps the stream
+ // open. But only send EOF if we haven't already.
+ stream->read_cb(stream, stream->buffer, 0, stream->s.cb_data, true);
+ }
+ break;
+ }
+ }
+}
+
+static void proc_close_handles(void **argv)
+{
+ Proc *proc = argv[0];
+
+ exit_need_delay++;
+ flush_stream(proc, &proc->out);
+ flush_stream(proc, &proc->err);
+
+ proc_close_streams(proc);
+ proc_close(proc);
+ exit_need_delay--;
+}
+
+static void exit_delay_cb(uv_timer_t *handle)
+{
+ uv_timer_stop(&main_loop.exit_delay_timer);
+ multiqueue_put(main_loop.fast_events, exit_event, main_loop.exit_delay_timer.data);
+}
+
+static void exit_event(void **argv)
+{
+ int status = (int)(intptr_t)argv[0];
+ if (exit_need_delay) {
+ main_loop.exit_delay_timer.data = argv[0];
+ uv_timer_start(&main_loop.exit_delay_timer, exit_delay_cb, 0, 0);
+ return;
+ }
+
+ if (!exiting) {
+ if (ui_client_channel_id) {
+ ui_client_exit_status = status;
+ os_exit(status);
+ } else {
+ assert(status == 0); // Called from rpc_close(), which passes 0 as status.
+ preserve_exit(NULL);
+ }
+ }
+}
+
+void exit_from_channel(int status)
+{
+ multiqueue_put(main_loop.fast_events, exit_event, (void *)(intptr_t)status);
+}
+
+static void on_proc_exit(Proc *proc)
+{
+ Loop *loop = proc->loop;
+ ILOG("exited: pid=%d status=%d stoptime=%" PRIu64, proc->pid, proc->status,
+ proc->stopped_time);
+
+ if (ui_client_channel_id) {
+ exit_from_channel(proc->status);
+ }
+
+ // Process has terminated, but there could still be data to be read from the
+ // OS. We are still in the libuv loop, so we cannot call code that polls for
+ // more data directly. Instead delay the reading after the libuv loop by
+ // queueing proc_close_handles() as an event.
+ MultiQueue *queue = proc->events ? proc->events : loop->events;
+ CREATE_EVENT(queue, proc_close_handles, proc);
+}
+
+static void on_proc_stream_close(Stream *stream, void *data)
+{
+ Proc *proc = data;
+ decref(proc);
+}
diff --git a/src/nvim/event/proc.h b/src/nvim/event/proc.h
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "nvim/event/defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h"
+
+static inline Proc proc_init(Loop *loop, ProcType type, void *data)
+{
+ return (Proc) {
+ .type = type,
+ .data = data,
+ .loop = loop,
+ .events = NULL,
+ .pid = 0,
+ .status = -1,
+ .refcount = 0,
+ .stopped_time = 0,
+ .cwd = NULL,
+ .argv = NULL,
+ .exepath = NULL,
+ .in = { .closed = false },
+ .out = { .s.closed = false },
+ .err = { .s.closed = false },
+ .cb = NULL,
+ .closed = false,
+ .internal_close_cb = NULL,
+ .internal_exit_cb = NULL,
+ .detach = false,
+ .fwd_err = false,
+ };
+}
+
+/// Get the path to the executable of the process.
+static inline const char *proc_get_exepath(Proc *proc)
+{
+ return proc->exepath != NULL ? proc->exepath : proc->argv[0];
+}
+
+static inline bool proc_is_stopped(Proc *proc)
+{
+ bool exited = (proc->status >= 0);
+ return exited || (proc->stopped_time != 0);
+}
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "event/proc.h.generated.h"
+#endif
diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c
@@ -1,451 +0,0 @@
-#include <assert.h>
-#include <inttypes.h>
-#include <signal.h>
-#include <uv.h>
-
-#include "klib/klist.h"
-#include "nvim/event/libuv_process.h"
-#include "nvim/event/loop.h"
-#include "nvim/event/multiqueue.h"
-#include "nvim/event/process.h"
-#include "nvim/event/rstream.h"
-#include "nvim/event/stream.h"
-#include "nvim/event/wstream.h"
-#include "nvim/globals.h"
-#include "nvim/log.h"
-#include "nvim/main.h"
-#include "nvim/os/process.h"
-#include "nvim/os/pty_process.h"
-#include "nvim/os/shell.h"
-#include "nvim/os/time.h"
-#include "nvim/ui_client.h"
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "event/process.c.generated.h"
-#endif
-
-// Time for a process to exit cleanly before we send KILL.
-// For PTY processes SIGTERM is sent first (in case SIGHUP was not enough).
-#define KILL_TIMEOUT_MS 2000
-
-/// Externally defined with gcov.
-#ifdef USE_GCOV
-void __gcov_flush(void);
-#endif
-
-static bool process_is_tearing_down = false;
-
-// Delay exit until handles are closed, to avoid deadlocks
-static int exit_need_delay = 0;
-
-/// @returns zero on success, or negative error code
-int process_spawn(Process *proc, bool in, bool out, bool err)
- FUNC_ATTR_NONNULL_ALL
-{
- // forwarding stderr contradicts with processing it internally
- assert(!(err && proc->fwd_err));
-
- if (in) {
- uv_pipe_init(&proc->loop->uv, &proc->in.uv.pipe, 0);
- } else {
- proc->in.closed = true;
- }
-
- if (out) {
- uv_pipe_init(&proc->loop->uv, &proc->out.s.uv.pipe, 0);
- } else {
- proc->out.s.closed = true;
- }
-
- if (err) {
- uv_pipe_init(&proc->loop->uv, &proc->err.s.uv.pipe, 0);
- } else {
- proc->err.s.closed = true;
- }
-
-#ifdef USE_GCOV
- // Flush coverage data before forking, to avoid "Merge mismatch" errors.
- __gcov_flush();
-#endif
-
- int status;
- switch (proc->type) {
- case kProcessTypeUv:
- status = libuv_process_spawn((LibuvProcess *)proc);
- break;
- case kProcessTypePty:
- status = pty_process_spawn((PtyProcess *)proc);
- break;
- }
-
- if (status) {
- if (in) {
- uv_close((uv_handle_t *)&proc->in.uv.pipe, NULL);
- }
- if (out) {
- uv_close((uv_handle_t *)&proc->out.s.uv.pipe, NULL);
- }
- if (err) {
- uv_close((uv_handle_t *)&proc->err.s.uv.pipe, NULL);
- }
-
- if (proc->type == kProcessTypeUv) {
- uv_close((uv_handle_t *)&(((LibuvProcess *)proc)->uv), NULL);
- } else {
- process_close(proc);
- }
- process_free(proc);
- proc->status = -1;
- return status;
- }
-
- if (in) {
- stream_init(NULL, &proc->in, -1, (uv_stream_t *)&proc->in.uv.pipe);
- proc->in.internal_data = proc;
- proc->in.internal_close_cb = on_process_stream_close;
- proc->refcount++;
- }
-
- if (out) {
- stream_init(NULL, &proc->out.s, -1, (uv_stream_t *)&proc->out.s.uv.pipe);
- proc->out.s.internal_data = proc;
- proc->out.s.internal_close_cb = on_process_stream_close;
- proc->refcount++;
- }
-
- if (err) {
- stream_init(NULL, &proc->err.s, -1, (uv_stream_t *)&proc->err.s.uv.pipe);
- proc->err.s.internal_data = proc;
- proc->err.s.internal_close_cb = on_process_stream_close;
- proc->refcount++;
- }
-
- proc->internal_exit_cb = on_process_exit;
- proc->internal_close_cb = decref;
- proc->refcount++;
- kl_push(WatcherPtr, proc->loop->children, proc);
- DLOG("new: pid=%d exepath=[%s]", proc->pid, process_get_exepath(proc));
- return 0;
-}
-
-void process_teardown(Loop *loop) FUNC_ATTR_NONNULL_ALL
-{
- process_is_tearing_down = true;
- kl_iter(WatcherPtr, loop->children, current) {
- Process *proc = (*current)->data;
- if (proc->detach || proc->type == kProcessTypePty) {
- // Close handles to process without killing it.
- CREATE_EVENT(loop->events, process_close_handles, proc);
- } else {
- process_stop(proc);
- }
- }
-
- // Wait until all children exit and all close events are processed.
- LOOP_PROCESS_EVENTS_UNTIL(loop, loop->events, -1,
- kl_empty(loop->children) && multiqueue_empty(loop->events));
- pty_process_teardown(loop);
-}
-
-void process_close_streams(Process *proc) FUNC_ATTR_NONNULL_ALL
-{
- wstream_may_close(&proc->in);
- rstream_may_close(&proc->out);
- rstream_may_close(&proc->err);
-}
-
-/// Synchronously wait for a process to finish
-///
-/// @param process Process instance
-/// @param ms Time in milliseconds to wait for the process.
-/// 0 for no wait. -1 to wait until the process quits.
-/// @return Exit code of the process. proc->status will have the same value.
-/// -1 if the timeout expired while the process is still running.
-/// -2 if the user interrupted the wait.
-int process_wait(Process *proc, int ms, MultiQueue *events)
- FUNC_ATTR_NONNULL_ARG(1)
-{
- if (!proc->refcount) {
- int status = proc->status;
- LOOP_PROCESS_EVENTS(proc->loop, proc->events, 0);
- return status;
- }
-
- if (!events) {
- events = proc->events;
- }
-
- // Increase refcount to stop the exit callback from being called (and possibly
- // freed) before we have a chance to get the status.
- proc->refcount++;
- LOOP_PROCESS_EVENTS_UNTIL(proc->loop, events, ms,
- // Until...
- got_int // interrupted by the user
- || proc->refcount == 1); // job exited
-
- // Assume that a user hitting CTRL-C does not like the current job. Kill it.
- if (got_int) {
- got_int = false;
- process_stop(proc);
- if (ms == -1) {
- // We can only return if all streams/handles are closed and the job
- // exited.
- LOOP_PROCESS_EVENTS_UNTIL(proc->loop, events, -1,
- proc->refcount == 1);
- } else {
- LOOP_PROCESS_EVENTS(proc->loop, events, 0);
- }
-
- proc->status = -2;
- }
-
- if (proc->refcount == 1) {
- // Job exited, free its resources.
- decref(proc);
- if (proc->events) {
- // decref() created an exit event, process it now.
- multiqueue_process_events(proc->events);
- }
- } else {
- proc->refcount--;
- }
-
- return proc->status;
-}
-
-/// Ask a process to terminate and eventually kill if it doesn't respond
-void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL
-{
- bool exited = (proc->status >= 0);
- if (exited || proc->stopped_time) {
- return;
- }
- proc->stopped_time = os_hrtime();
- proc->exit_signal = SIGTERM;
-
- switch (proc->type) {
- case kProcessTypeUv:
- os_proc_tree_kill(proc->pid, SIGTERM);
- break;
- case kProcessTypePty:
- // close all streams for pty processes to send SIGHUP to the process
- process_close_streams(proc);
- pty_process_close_master((PtyProcess *)proc);
- break;
- }
-
- // (Re)start timer to verify that stopped process(es) died.
- uv_timer_start(&proc->loop->children_kill_timer, children_kill_cb,
- KILL_TIMEOUT_MS, 0);
-}
-
-/// Frees process-owned resources.
-void process_free(Process *proc) FUNC_ATTR_NONNULL_ALL
-{
- if (proc->argv != NULL) {
- shell_free_argv(proc->argv);
- proc->argv = NULL;
- }
-}
-
-/// Sends SIGKILL (or SIGTERM..SIGKILL for PTY jobs) to processes that did
-/// not terminate after process_stop().
-static void children_kill_cb(uv_timer_t *handle)
-{
- Loop *loop = handle->loop->data;
-
- kl_iter(WatcherPtr, loop->children, current) {
- Process *proc = (*current)->data;
- bool exited = (proc->status >= 0);
- if (exited || !proc->stopped_time) {
- continue;
- }
- uint64_t term_sent = UINT64_MAX == proc->stopped_time;
- if (kProcessTypePty != proc->type || term_sent) {
- proc->exit_signal = SIGKILL;
- os_proc_tree_kill(proc->pid, SIGKILL);
- } else {
- proc->exit_signal = SIGTERM;
- os_proc_tree_kill(proc->pid, SIGTERM);
- proc->stopped_time = UINT64_MAX; // Flag: SIGTERM was sent.
- // Restart timer.
- uv_timer_start(&proc->loop->children_kill_timer, children_kill_cb,
- KILL_TIMEOUT_MS, 0);
- }
- }
-}
-
-static void process_close_event(void **argv)
-{
- Process *proc = argv[0];
- if (proc->cb) {
- // User (hint: channel_job_start) is responsible for calling
- // process_free().
- proc->cb(proc, proc->status, proc->data);
- } else {
- process_free(proc);
- }
-}
-
-static void decref(Process *proc)
-{
- if (--proc->refcount != 0) {
- return;
- }
-
- Loop *loop = proc->loop;
- kliter_t(WatcherPtr) **node = NULL;
- kl_iter(WatcherPtr, loop->children, current) {
- if ((*current)->data == proc) {
- node = current;
- break;
- }
- }
- assert(node);
- kl_shift_at(WatcherPtr, loop->children, node);
- CREATE_EVENT(proc->events, process_close_event, proc);
-}
-
-static void process_close(Process *proc)
- FUNC_ATTR_NONNULL_ARG(1)
-{
- if (process_is_tearing_down && (proc->detach || proc->type == kProcessTypePty)
- && proc->closed) {
- // If a detached/pty process dies while tearing down it might get closed
- // twice.
- return;
- }
- assert(!proc->closed);
- proc->closed = true;
-
- if (proc->detach) {
- if (proc->type == kProcessTypeUv) {
- uv_unref((uv_handle_t *)&(((LibuvProcess *)proc)->uv));
- }
- }
-
- switch (proc->type) {
- case kProcessTypeUv:
- libuv_process_close((LibuvProcess *)proc);
- break;
- case kProcessTypePty:
- pty_process_close((PtyProcess *)proc);
- break;
- }
-}
-
-/// Flush output stream.
-///
-/// @param proc Process, for which an output stream should be flushed.
-/// @param stream Stream to flush.
-static void flush_stream(Process *proc, RStream *stream)
- FUNC_ATTR_NONNULL_ARG(1)
-{
- if (!stream || stream->s.closed) {
- return;
- }
-
- // Maximal remaining data size of terminated process is system
- // buffer size.
- // Also helps with a child process that keeps the output streams open. If it
- // keeps sending data, we only accept as much data as the system buffer size.
- // Otherwise this would block cleanup/teardown.
- int system_buffer_size = 0;
- int err = uv_recv_buffer_size((uv_handle_t *)&stream->s.uv.pipe,
- &system_buffer_size);
- if (err) {
- system_buffer_size = ARENA_BLOCK_SIZE;
- }
-
- size_t max_bytes = stream->num_bytes + (size_t)system_buffer_size;
-
- // Read remaining data.
- while (!stream->s.closed && stream->num_bytes < max_bytes) {
- // Remember number of bytes before polling
- size_t num_bytes = stream->num_bytes;
-
- // Poll for data and process the generated events.
- loop_poll_events(proc->loop, 0);
- if (stream->s.events) {
- multiqueue_process_events(stream->s.events);
- }
-
- // Stream can be closed if it is empty.
- if (num_bytes == stream->num_bytes) {
- if (stream->read_cb && !stream->did_eof) {
- // Stream callback could miss EOF handling if a child keeps the stream
- // open. But only send EOF if we haven't already.
- stream->read_cb(stream, stream->buffer, 0, stream->s.cb_data, true);
- }
- break;
- }
- }
-}
-
-static void process_close_handles(void **argv)
-{
- Process *proc = argv[0];
-
- exit_need_delay++;
- flush_stream(proc, &proc->out);
- flush_stream(proc, &proc->err);
-
- process_close_streams(proc);
- process_close(proc);
- exit_need_delay--;
-}
-
-static void exit_delay_cb(uv_timer_t *handle)
-{
- uv_timer_stop(&main_loop.exit_delay_timer);
- multiqueue_put(main_loop.fast_events, exit_event, main_loop.exit_delay_timer.data);
-}
-
-static void exit_event(void **argv)
-{
- int status = (int)(intptr_t)argv[0];
- if (exit_need_delay) {
- main_loop.exit_delay_timer.data = argv[0];
- uv_timer_start(&main_loop.exit_delay_timer, exit_delay_cb, 0, 0);
- return;
- }
-
- if (!exiting) {
- if (ui_client_channel_id) {
- ui_client_exit_status = status;
- os_exit(status);
- } else {
- assert(status == 0); // Called from rpc_close(), which passes 0 as status.
- preserve_exit(NULL);
- }
- }
-}
-
-void exit_from_channel(int status)
-{
- multiqueue_put(main_loop.fast_events, exit_event, (void *)(intptr_t)status);
-}
-
-static void on_process_exit(Process *proc)
-{
- Loop *loop = proc->loop;
- ILOG("exited: pid=%d status=%d stoptime=%" PRIu64, proc->pid, proc->status,
- proc->stopped_time);
-
- if (ui_client_channel_id) {
- exit_from_channel(proc->status);
- }
-
- // Process has terminated, but there could still be data to be read from the
- // OS. We are still in the libuv loop, so we cannot call code that polls for
- // more data directly. Instead delay the reading after the libuv loop by
- // queueing process_close_handles() as an event.
- MultiQueue *queue = proc->events ? proc->events : loop->events;
- CREATE_EVENT(queue, process_close_handles, proc);
-}
-
-static void on_process_stream_close(Stream *stream, void *data)
-{
- Process *proc = data;
- decref(proc);
-}
diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h
@@ -1,49 +0,0 @@
-#pragma once
-
-#include <stdbool.h>
-#include <stddef.h>
-
-#include "nvim/event/defs.h" // IWYU pragma: keep
-#include "nvim/types_defs.h"
-
-static inline Process process_init(Loop *loop, ProcessType type, void *data)
-{
- return (Process) {
- .type = type,
- .data = data,
- .loop = loop,
- .events = NULL,
- .pid = 0,
- .status = -1,
- .refcount = 0,
- .stopped_time = 0,
- .cwd = NULL,
- .argv = NULL,
- .exepath = NULL,
- .in = { .closed = false },
- .out = { .s.closed = false },
- .err = { .s.closed = false },
- .cb = NULL,
- .closed = false,
- .internal_close_cb = NULL,
- .internal_exit_cb = NULL,
- .detach = false,
- .fwd_err = false,
- };
-}
-
-/// Get the path to the executable of the process.
-static inline const char *process_get_exepath(Process *proc)
-{
- return proc->exepath != NULL ? proc->exepath : proc->argv[0];
-}
-
-static inline bool process_is_stopped(Process *proc)
-{
- bool exited = (proc->status >= 0);
- return exited || (proc->stopped_time != 0);
-}
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "event/process.h.generated.h"
-#endif
diff --git a/src/nvim/main.c b/src/nvim/main.c
@@ -43,7 +43,7 @@
#include "nvim/eval/userfunc.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
-#include "nvim/event/process.h"
+#include "nvim/event/proc.h"
#include "nvim/event/stream.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
@@ -174,7 +174,7 @@ bool event_teardown(void)
loop_poll_events(&main_loop, 0); // Drain thread_events, fast_events.
input_stop();
channel_teardown();
- process_teardown(&main_loop);
+ proc_teardown(&main_loop);
timer_teardown();
server_teardown();
signal_teardown();
@@ -2207,7 +2207,7 @@ static void usage(void)
printf(_(" --headless Don't start a user interface\n"));
printf(_(" --listen <address> Serve RPC API from this address\n"));
printf(_(" --remote[-subcommand] Execute commands remotely on a server\n"));
- printf(_(" --server <address> Specify RPC server to send commands to\n"));
+ printf(_(" --server <address> Connect to this Nvim server\n"));
printf(_(" --startuptime <file> Write startup timing messages to <file>\n"));
printf(_("\nSee \":help startup-options\" for all options.\n"));
}
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
@@ -84,7 +84,7 @@
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
-#include "nvim/os/process.h"
+#include "nvim/os/proc.h"
#include "nvim/os/time.h"
#include "nvim/os/time_defs.h"
#include "nvim/path.h"
@@ -743,7 +743,7 @@ static void add_b0_fenc(ZeroBlock *b0p, buf_T *buf)
/// @param swap_fname Name of the swapfile. If it's from before a reboot, the result is 0.
///
/// @return PID, or 0 if process is not running or the swapfile is from before a reboot.
-static int swapfile_process_running(const ZeroBlock *b0p, const char *swap_fname)
+static int swapfile_proc_running(const ZeroBlock *b0p, const char *swap_fname)
{
FileInfo st;
double uptime;
@@ -1214,7 +1214,7 @@ void ml_recover(bool checkext)
msg(_("Recovery completed. Buffer contents equals file contents."), 0);
}
msg_puts(_("\nYou may want to delete the .swp file now."));
- if (swapfile_process_running(b0p, fname_used)) {
+ if (swapfile_proc_running(b0p, fname_used)) {
// Warn there could be an active Vim on the same file, the user may
// want to kill it.
msg_puts(_("\nNote: process STILL RUNNING: "));
@@ -1462,7 +1462,7 @@ char *make_percent_swname(char *dir, char *dir_end, const char *name)
}
// PID of swapfile owner, or zero if not running.
-static int process_running;
+static int proc_running;
/// For Vimscript "swapinfo()".
///
@@ -1488,7 +1488,7 @@ void swapfile_dict(const char *fname, dict_T *d)
tv_dict_add_str_len(d, S_LEN("fname"), b0.b0_fname,
B0_FNAME_SIZE_ORG);
- tv_dict_add_nr(d, S_LEN("pid"), swapfile_process_running(&b0, fname));
+ tv_dict_add_nr(d, S_LEN("pid"), swapfile_proc_running(&b0, fname));
tv_dict_add_nr(d, S_LEN("mtime"), char_to_long(b0.b0_mtime));
tv_dict_add_nr(d, S_LEN("dirty"), b0.b0_dirty ? 1 : 0);
tv_dict_add_nr(d, S_LEN("inode"), char_to_long(b0.b0_ino));
@@ -1572,7 +1572,7 @@ static time_t swapfile_info(char *fname)
if (char_to_long(b0.b0_pid) != 0) {
msg_puts(_("\n process ID: "));
msg_outnum((int)char_to_long(b0.b0_pid));
- if ((process_running = swapfile_process_running(&b0, fname))) {
+ if ((proc_running = swapfile_proc_running(&b0, fname))) {
msg_puts(_(" (STILL RUNNING)"));
}
}
@@ -1640,7 +1640,7 @@ static bool swapfile_unchanged(char *fname)
}
// process must be known and not running.
- if (char_to_long(b0.b0_pid) == 0 || swapfile_process_running(&b0, fname)) {
+ if (char_to_long(b0.b0_pid) == 0 || swapfile_proc_running(&b0, fname)) {
ret = false;
}
@@ -3399,7 +3399,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
fd = os_open(fname, O_RDONLY, 0);
if (fd >= 0) {
if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) {
- process_running = swapfile_process_running(&b0, fname);
+ proc_running = swapfile_proc_running(&b0, fname);
// If the swapfile has the same directory as the
// buffer don't compare the directory names, they can
@@ -3459,7 +3459,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
choice = SEA_CHOICE_READONLY;
}
- process_running = 0; // Set by attention_message..swapfile_info.
+ proc_running = 0; // Set by attention_message..swapfile_info.
if (choice == SEA_CHOICE_NONE) {
// Show info about the existing swapfile.
attention_message(buf, fname);
@@ -3491,12 +3491,12 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
= do_dialog(VIM_WARNING,
_("VIM - ATTENTION"),
name,
- process_running
+ proc_running
? _("&Open Read-Only\n&Edit anyway\n&Recover\n&Quit\n&Abort")
: _("&Open Read-Only\n&Edit anyway\n&Recover\n&Delete it\n&Quit\n&Abort"),
1, NULL, false);
- if (process_running && dialog_result >= 4) {
+ if (proc_running && dialog_result >= 4) {
// compensate for missing "Delete it" button
dialog_result++;
}
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
@@ -14,7 +14,7 @@
#include "nvim/event/defs.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
-#include "nvim/event/process.h"
+#include "nvim/event/proc.h"
#include "nvim/event/rstream.h"
#include "nvim/event/wstream.h"
#include "nvim/globals.h"
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
@@ -344,7 +344,7 @@ char *os_getenvname_at_index(size_t index)
#endif
}
-/// Get the process ID of the Neovim process.
+/// Get the process ID of the Nvim process.
///
/// @return the process ID.
int64_t os_get_pid(void)
diff --git a/src/nvim/os/proc.c b/src/nvim/os/proc.c
@@ -0,0 +1,286 @@
+/// OS process functions
+///
+/// psutil is a good reference for cross-platform syscall voodoo:
+/// https://github.com/giampaolo/psutil/tree/master/psutil/arch
+
+// IWYU pragma: no_include <sys/param.h>
+
+#include <assert.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <uv.h>
+
+#ifdef MSWIN
+# include <tlhelp32.h>
+#endif
+
+#if defined(__FreeBSD__)
+# include <string.h>
+# include <sys/types.h>
+# include <sys/user.h>
+#endif
+
+#if defined(__NetBSD__) || defined(__OpenBSD__)
+# include <sys/param.h>
+#endif
+
+#if defined(__APPLE__) || defined(BSD)
+# include <sys/sysctl.h>
+
+# include "nvim/macros_defs.h"
+#endif
+
+#if defined(__linux__)
+# include <stdio.h>
+#endif
+
+#include "nvim/log.h"
+#include "nvim/memory.h"
+#include "nvim/os/proc.h"
+
+#ifdef MSWIN
+# include "nvim/api/private/helpers.h"
+#endif
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/proc.c.generated.h"
+#endif
+
+#ifdef MSWIN
+static bool os_proc_tree_kill_rec(HANDLE proc, int sig)
+{
+ if (proc == NULL) {
+ return false;
+ }
+ PROCESSENTRY32 pe;
+ DWORD pid = GetProcessId(proc);
+
+ if (pid != 0) {
+ HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (h != INVALID_HANDLE_VALUE) {
+ pe.dwSize = sizeof(PROCESSENTRY32);
+ if (!Process32First(h, &pe)) {
+ goto theend;
+ }
+ do {
+ if (pe.th32ParentProcessID == pid) {
+ HANDLE ph = OpenProcess(PROCESS_ALL_ACCESS, false, pe.th32ProcessID);
+ if (ph != NULL) {
+ os_proc_tree_kill_rec(ph, sig);
+ CloseHandle(ph);
+ }
+ }
+ } while (Process32Next(h, &pe));
+ CloseHandle(h);
+ }
+ }
+
+theend:
+ return (bool)TerminateProcess(proc, (unsigned)sig);
+}
+/// Kills process `pid` and its descendants recursively.
+bool os_proc_tree_kill(int pid, int sig)
+{
+ assert(sig >= 0);
+ assert(sig == SIGTERM || sig == SIGKILL);
+ if (pid > 0) {
+ ILOG("terminating process tree: %d", pid);
+ HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, false, (DWORD)pid);
+ return os_proc_tree_kill_rec(h, sig);
+ } else {
+ ELOG("invalid pid: %d", pid);
+ }
+ return false;
+}
+#else
+/// Kills process group where `pid` is the process group leader.
+bool os_proc_tree_kill(int pid, int sig)
+{
+ assert(sig == SIGTERM || sig == SIGKILL);
+ if (pid == 0) {
+ // Never kill self (pid=0).
+ return false;
+ }
+ ILOG("sending %s to PID %d", sig == SIGTERM ? "SIGTERM" : "SIGKILL", -pid);
+ return uv_kill(-pid, sig) == 0;
+}
+#endif
+
+/// Gets the process ids of the immediate children of process `ppid`.
+///
+/// @param ppid Process to inspect.
+/// @param[out,allocated] proc_list Child process ids.
+/// @param[out] proc_count Number of child processes.
+/// @return 0 on success, 1 if process not found, 2 on other error.
+int os_proc_children(int ppid, int **proc_list, size_t *proc_count)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (ppid < 0) {
+ return 2;
+ }
+
+ int *temp = NULL;
+ *proc_list = NULL;
+ *proc_count = 0;
+
+#ifdef MSWIN
+ PROCESSENTRY32 pe;
+
+ // Snapshot of all processes.
+ HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (h == INVALID_HANDLE_VALUE) {
+ return 2;
+ }
+
+ pe.dwSize = sizeof(PROCESSENTRY32);
+ // Get root process.
+ if (!Process32First(h, &pe)) {
+ CloseHandle(h);
+ return 2;
+ }
+ // Collect processes whose parent matches `ppid`.
+ do {
+ if (pe.th32ParentProcessID == (DWORD)ppid) {
+ temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp));
+ temp[*proc_count] = (int)pe.th32ProcessID;
+ (*proc_count)++;
+ }
+ } while (Process32Next(h, &pe));
+ CloseHandle(h);
+
+#elif defined(__APPLE__) || defined(BSD)
+# if defined(__APPLE__)
+# define KP_PID(o) o.kp_proc.p_pid
+# define KP_PPID(o) o.kp_eproc.e_ppid
+# elif defined(__FreeBSD__)
+# define KP_PID(o) o.ki_pid
+# define KP_PPID(o) o.ki_ppid
+# else
+# define KP_PID(o) o.p_pid
+# define KP_PPID(o) o.p_ppid
+# endif
+# ifdef __NetBSD__
+ static int name[] = {
+ CTL_KERN, KERN_PROC2, KERN_PROC_ALL, 0, (int)(sizeof(struct kinfo_proc2)), 0
+ };
+# else
+ static int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
+# endif
+
+ // Get total process count.
+ size_t len = 0;
+ int rv = sysctl(name, ARRAY_SIZE(name) - 1, NULL, &len, NULL, 0);
+ if (rv) {
+ return 2;
+ }
+
+ // Get ALL processes.
+# ifdef __NetBSD__
+ struct kinfo_proc2 *p_list = xmalloc(len);
+# else
+ struct kinfo_proc *p_list = xmalloc(len);
+# endif
+ rv = sysctl(name, ARRAY_SIZE(name) - 1, p_list, &len, NULL, 0);
+ if (rv) {
+ xfree(p_list);
+ return 2;
+ }
+
+ // Collect processes whose parent matches `ppid`.
+ bool exists = false;
+ size_t p_count = len / sizeof(*p_list);
+ for (size_t i = 0; i < p_count; i++) {
+ exists = exists || KP_PID(p_list[i]) == ppid;
+ if (KP_PPID(p_list[i]) == ppid) {
+ temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp));
+ temp[*proc_count] = KP_PID(p_list[i]);
+ (*proc_count)++;
+ }
+ }
+ xfree(p_list);
+ if (!exists) {
+ return 1; // Process not found.
+ }
+
+#elif defined(__linux__)
+ char proc_p[256] = { 0 };
+ // Collect processes whose parent matches `ppid`.
+ // Rationale: children are defined in thread with same ID of process.
+ snprintf(proc_p, sizeof(proc_p), "/proc/%d/task/%d/children", ppid, ppid);
+ FILE *fp = fopen(proc_p, "r");
+ if (fp == NULL) {
+ return 2; // Process not found, or /proc/…/children not supported.
+ }
+ int match_pid;
+ while (fscanf(fp, "%d", &match_pid) > 0) {
+ temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp));
+ temp[*proc_count] = match_pid;
+ (*proc_count)++;
+ }
+ fclose(fp);
+#endif
+
+ *proc_list = temp;
+ return 0;
+}
+
+#ifdef MSWIN
+/// Gets various properties of the process identified by `pid`.
+///
+/// @param pid Process to inspect.
+/// @return Map of process properties, empty on error.
+Dictionary os_proc_info(int pid, Arena *arena)
+{
+ Dictionary pinfo = ARRAY_DICT_INIT;
+ PROCESSENTRY32 pe;
+
+ // Snapshot of all processes. This is used instead of:
+ // OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, …)
+ // to avoid ERROR_PARTIAL_COPY. https://stackoverflow.com/a/29942376
+ HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (h == INVALID_HANDLE_VALUE) {
+ return pinfo; // Return empty.
+ }
+
+ pe.dwSize = sizeof(PROCESSENTRY32);
+ // Get root process.
+ if (!Process32First(h, &pe)) {
+ CloseHandle(h);
+ return pinfo; // Return empty.
+ }
+ // Find the process.
+ do {
+ if (pe.th32ProcessID == (DWORD)pid) {
+ break;
+ }
+ } while (Process32Next(h, &pe));
+ CloseHandle(h);
+
+ if (pe.th32ProcessID == (DWORD)pid) {
+ pinfo = arena_dict(arena, 3);
+ PUT_C(pinfo, "pid", INTEGER_OBJ(pid));
+ PUT_C(pinfo, "ppid", INTEGER_OBJ((int)pe.th32ParentProcessID));
+ PUT_C(pinfo, "name", CSTR_TO_ARENA_OBJ(arena, pe.szExeFile));
+ }
+
+ return pinfo;
+}
+#endif
+
+/// Return true if process `pid` is running.
+bool os_proc_running(int pid)
+{
+ int err = uv_kill(pid, 0);
+ // If there is no error the process must be running.
+ if (err == 0) {
+ return true;
+ }
+ // If the error is ESRCH then the process is not running.
+ if (err == UV_ESRCH) {
+ return false;
+ }
+ // If the process is running and owned by another user we get EPERM. With
+ // other errors the process might be running, assuming it is then.
+ return true;
+}
diff --git a/src/nvim/os/proc.h b/src/nvim/os/proc.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <stddef.h> // IWYU pragma: keep
+
+#ifdef MSWIN
+# include "nvim/api/private/defs.h" // IWYU pragma: keep
+#endif
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/proc.h.generated.h"
+#endif
diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c
@@ -1,286 +0,0 @@
-/// OS process functions
-///
-/// psutil is a good reference for cross-platform syscall voodoo:
-/// https://github.com/giampaolo/psutil/tree/master/psutil/arch
-
-// IWYU pragma: no_include <sys/param.h>
-
-#include <assert.h>
-#include <signal.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <uv.h>
-
-#ifdef MSWIN
-# include <tlhelp32.h>
-#endif
-
-#if defined(__FreeBSD__)
-# include <string.h>
-# include <sys/types.h>
-# include <sys/user.h>
-#endif
-
-#if defined(__NetBSD__) || defined(__OpenBSD__)
-# include <sys/param.h>
-#endif
-
-#if defined(__APPLE__) || defined(BSD)
-# include <sys/sysctl.h>
-
-# include "nvim/macros_defs.h"
-#endif
-
-#if defined(__linux__)
-# include <stdio.h>
-#endif
-
-#include "nvim/log.h"
-#include "nvim/memory.h"
-#include "nvim/os/process.h"
-
-#ifdef MSWIN
-# include "nvim/api/private/helpers.h"
-#endif
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "os/process.c.generated.h"
-#endif
-
-#ifdef MSWIN
-static bool os_proc_tree_kill_rec(HANDLE process, int sig)
-{
- if (process == NULL) {
- return false;
- }
- PROCESSENTRY32 pe;
- DWORD pid = GetProcessId(process);
-
- if (pid != 0) {
- HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
- if (h != INVALID_HANDLE_VALUE) {
- pe.dwSize = sizeof(PROCESSENTRY32);
- if (!Process32First(h, &pe)) {
- goto theend;
- }
- do {
- if (pe.th32ParentProcessID == pid) {
- HANDLE ph = OpenProcess(PROCESS_ALL_ACCESS, false, pe.th32ProcessID);
- if (ph != NULL) {
- os_proc_tree_kill_rec(ph, sig);
- CloseHandle(ph);
- }
- }
- } while (Process32Next(h, &pe));
- CloseHandle(h);
- }
- }
-
-theend:
- return (bool)TerminateProcess(process, (unsigned)sig);
-}
-/// Kills process `pid` and its descendants recursively.
-bool os_proc_tree_kill(int pid, int sig)
-{
- assert(sig >= 0);
- assert(sig == SIGTERM || sig == SIGKILL);
- if (pid > 0) {
- ILOG("terminating process tree: %d", pid);
- HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, false, (DWORD)pid);
- return os_proc_tree_kill_rec(h, sig);
- } else {
- ELOG("invalid pid: %d", pid);
- }
- return false;
-}
-#else
-/// Kills process group where `pid` is the process group leader.
-bool os_proc_tree_kill(int pid, int sig)
-{
- assert(sig == SIGTERM || sig == SIGKILL);
- if (pid == 0) {
- // Never kill self (pid=0).
- return false;
- }
- ILOG("sending %s to PID %d", sig == SIGTERM ? "SIGTERM" : "SIGKILL", -pid);
- return uv_kill(-pid, sig) == 0;
-}
-#endif
-
-/// Gets the process ids of the immediate children of process `ppid`.
-///
-/// @param ppid Process to inspect.
-/// @param[out,allocated] proc_list Child process ids.
-/// @param[out] proc_count Number of child processes.
-/// @return 0 on success, 1 if process not found, 2 on other error.
-int os_proc_children(int ppid, int **proc_list, size_t *proc_count)
- FUNC_ATTR_NONNULL_ALL
-{
- if (ppid < 0) {
- return 2;
- }
-
- int *temp = NULL;
- *proc_list = NULL;
- *proc_count = 0;
-
-#ifdef MSWIN
- PROCESSENTRY32 pe;
-
- // Snapshot of all processes.
- HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
- if (h == INVALID_HANDLE_VALUE) {
- return 2;
- }
-
- pe.dwSize = sizeof(PROCESSENTRY32);
- // Get root process.
- if (!Process32First(h, &pe)) {
- CloseHandle(h);
- return 2;
- }
- // Collect processes whose parent matches `ppid`.
- do {
- if (pe.th32ParentProcessID == (DWORD)ppid) {
- temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp));
- temp[*proc_count] = (int)pe.th32ProcessID;
- (*proc_count)++;
- }
- } while (Process32Next(h, &pe));
- CloseHandle(h);
-
-#elif defined(__APPLE__) || defined(BSD)
-# if defined(__APPLE__)
-# define KP_PID(o) o.kp_proc.p_pid
-# define KP_PPID(o) o.kp_eproc.e_ppid
-# elif defined(__FreeBSD__)
-# define KP_PID(o) o.ki_pid
-# define KP_PPID(o) o.ki_ppid
-# else
-# define KP_PID(o) o.p_pid
-# define KP_PPID(o) o.p_ppid
-# endif
-# ifdef __NetBSD__
- static int name[] = {
- CTL_KERN, KERN_PROC2, KERN_PROC_ALL, 0, (int)(sizeof(struct kinfo_proc2)), 0
- };
-# else
- static int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
-# endif
-
- // Get total process count.
- size_t len = 0;
- int rv = sysctl(name, ARRAY_SIZE(name) - 1, NULL, &len, NULL, 0);
- if (rv) {
- return 2;
- }
-
- // Get ALL processes.
-# ifdef __NetBSD__
- struct kinfo_proc2 *p_list = xmalloc(len);
-# else
- struct kinfo_proc *p_list = xmalloc(len);
-# endif
- rv = sysctl(name, ARRAY_SIZE(name) - 1, p_list, &len, NULL, 0);
- if (rv) {
- xfree(p_list);
- return 2;
- }
-
- // Collect processes whose parent matches `ppid`.
- bool exists = false;
- size_t p_count = len / sizeof(*p_list);
- for (size_t i = 0; i < p_count; i++) {
- exists = exists || KP_PID(p_list[i]) == ppid;
- if (KP_PPID(p_list[i]) == ppid) {
- temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp));
- temp[*proc_count] = KP_PID(p_list[i]);
- (*proc_count)++;
- }
- }
- xfree(p_list);
- if (!exists) {
- return 1; // Process not found.
- }
-
-#elif defined(__linux__)
- char proc_p[256] = { 0 };
- // Collect processes whose parent matches `ppid`.
- // Rationale: children are defined in thread with same ID of process.
- snprintf(proc_p, sizeof(proc_p), "/proc/%d/task/%d/children", ppid, ppid);
- FILE *fp = fopen(proc_p, "r");
- if (fp == NULL) {
- return 2; // Process not found, or /proc/…/children not supported.
- }
- int match_pid;
- while (fscanf(fp, "%d", &match_pid) > 0) {
- temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp));
- temp[*proc_count] = match_pid;
- (*proc_count)++;
- }
- fclose(fp);
-#endif
-
- *proc_list = temp;
- return 0;
-}
-
-#ifdef MSWIN
-/// Gets various properties of the process identified by `pid`.
-///
-/// @param pid Process to inspect.
-/// @return Map of process properties, empty on error.
-Dictionary os_proc_info(int pid, Arena *arena)
-{
- Dictionary pinfo = ARRAY_DICT_INIT;
- PROCESSENTRY32 pe;
-
- // Snapshot of all processes. This is used instead of:
- // OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, …)
- // to avoid ERROR_PARTIAL_COPY. https://stackoverflow.com/a/29942376
- HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
- if (h == INVALID_HANDLE_VALUE) {
- return pinfo; // Return empty.
- }
-
- pe.dwSize = sizeof(PROCESSENTRY32);
- // Get root process.
- if (!Process32First(h, &pe)) {
- CloseHandle(h);
- return pinfo; // Return empty.
- }
- // Find the process.
- do {
- if (pe.th32ProcessID == (DWORD)pid) {
- break;
- }
- } while (Process32Next(h, &pe));
- CloseHandle(h);
-
- if (pe.th32ProcessID == (DWORD)pid) {
- pinfo = arena_dict(arena, 3);
- PUT_C(pinfo, "pid", INTEGER_OBJ(pid));
- PUT_C(pinfo, "ppid", INTEGER_OBJ((int)pe.th32ParentProcessID));
- PUT_C(pinfo, "name", CSTR_TO_ARENA_OBJ(arena, pe.szExeFile));
- }
-
- return pinfo;
-}
-#endif
-
-/// Return true if process `pid` is running.
-bool os_proc_running(int pid)
-{
- int err = uv_kill(pid, 0);
- // If there is no error the process must be running.
- if (err == 0) {
- return true;
- }
- // If the error is ESRCH then the process is not running.
- if (err == UV_ESRCH) {
- return false;
- }
- // If the process is running and owned by another user we get EPERM. With
- // other errors the process might be running, assuming it is then.
- return true;
-}
diff --git a/src/nvim/os/process.h b/src/nvim/os/process.h
@@ -1,11 +0,0 @@
-#pragma once
-
-#include <stddef.h> // IWYU pragma: keep
-
-#ifdef MSWIN
-# include "nvim/api/private/defs.h" // IWYU pragma: keep
-#endif
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "os/process.h.generated.h"
-#endif
diff --git a/src/nvim/os/pty_conpty_win.c b/src/nvim/os/pty_conpty_win.c
@@ -143,7 +143,7 @@ finished:
return conpty_object;
}
-bool os_conpty_spawn(conpty_t *conpty_object, HANDLE *process_handle, wchar_t *name,
+bool os_conpty_spawn(conpty_t *conpty_object, HANDLE *proc_handle, wchar_t *name,
wchar_t *cmd_line, wchar_t *cwd, wchar_t *env)
{
PROCESS_INFORMATION pi = { 0 };
@@ -159,7 +159,7 @@ bool os_conpty_spawn(conpty_t *conpty_object, HANDLE *process_handle, wchar_t *n
&pi)) {
return false;
}
- *process_handle = pi.hProcess;
+ *proc_handle = pi.hProcess;
return true;
}
diff --git a/src/nvim/os/pty_proc.h b/src/nvim/os/pty_proc.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#ifdef MSWIN
+# include "nvim/os/pty_proc_win.h"
+#else
+# include "nvim/os/pty_proc_unix.h"
+#endif
diff --git a/src/nvim/os/pty_proc_unix.c b/src/nvim/os/pty_proc_unix.c
@@ -0,0 +1,417 @@
+// Some of the code came from pangoterm and libuv
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <uv.h>
+
+// forkpty is not in POSIX, so headers are platform-specific
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+# include <libutil.h>
+#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
+# include <util.h>
+#elif defined(__sun)
+# include <fcntl.h>
+# include <signal.h>
+# include <sys/stream.h>
+# include <sys/syscall.h>
+# include <unistd.h>
+#else
+# include <pty.h>
+#endif
+
+#ifdef __APPLE__
+# include <crt_externs.h>
+#endif
+
+#include "auto/config.h"
+#include "klib/klist.h"
+#include "nvim/eval/typval.h"
+#include "nvim/event/defs.h"
+#include "nvim/event/loop.h"
+#include "nvim/event/proc.h"
+#include "nvim/log.h"
+#include "nvim/os/fs.h"
+#include "nvim/os/os_defs.h"
+#include "nvim/os/pty_proc.h"
+#include "nvim/os/pty_proc_unix.h"
+#include "nvim/types_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/pty_proc_unix.c.generated.h"
+#endif
+
+#if defined(__sun) && !defined(HAVE_FORKPTY)
+
+// this header defines STR, just as nvim.h, but it is defined as ('S'<<8),
+// to avoid #undef STR, #undef STR, #define STR ('S'<<8) just delay the
+// inclusion of the header even though it gets include out of order.
+# include <sys/stropts.h>
+
+static int openpty(int *amaster, int *aslave, char *name, struct termios *termp,
+ struct winsize *winp)
+{
+ int slave = -1;
+ int master = open("/dev/ptmx", O_RDWR);
+ if (master == -1) {
+ goto error;
+ }
+
+ // grantpt will invoke a setuid program to change permissions
+ // and might fail if SIGCHLD handler is set, temporarily reset
+ // while running
+ void (*sig_saved)(int) = signal(SIGCHLD, SIG_DFL);
+ int res = grantpt(master);
+ signal(SIGCHLD, sig_saved);
+
+ if (res == -1 || unlockpt(master) == -1) {
+ goto error;
+ }
+
+ char *slave_name = ptsname(master);
+ if (slave_name == NULL) {
+ goto error;
+ }
+
+ slave = open(slave_name, O_RDWR|O_NOCTTY);
+ if (slave == -1) {
+ goto error;
+ }
+
+ // ptem emulates a terminal when used on a pseudo terminal driver,
+ // must be pushed before ldterm
+ ioctl(slave, I_PUSH, "ptem");
+ // ldterm provides most of the termio terminal interface
+ ioctl(slave, I_PUSH, "ldterm");
+ // ttcompat compatibility with older terminal ioctls
+ ioctl(slave, I_PUSH, "ttcompat");
+
+ if (termp) {
+ tcsetattr(slave, TCSAFLUSH, termp);
+ }
+ if (winp) {
+ ioctl(slave, TIOCSWINSZ, winp);
+ }
+
+ *amaster = master;
+ *aslave = slave;
+ // ignoring name, not passed and size is unknown in the API
+
+ return 0;
+
+error:
+ if (slave != -1) {
+ close(slave);
+ }
+ if (master != -1) {
+ close(master);
+ }
+ return -1;
+}
+
+static int login_tty(int fd)
+{
+ setsid();
+ if (ioctl(fd, TIOCSCTTY, NULL) == -1) {
+ return -1;
+ }
+
+ dup2(fd, STDIN_FILENO);
+ dup2(fd, STDOUT_FILENO);
+ dup2(fd, STDERR_FILENO);
+ if (fd > STDERR_FILENO) {
+ close(fd);
+ }
+
+ return 0;
+}
+
+static pid_t forkpty(int *amaster, char *name, struct termios *termp, struct winsize *winp)
+{
+ int master, slave;
+ if (openpty(&master, &slave, name, termp, winp) == -1) {
+ return -1;
+ }
+
+ pid_t pid = fork();
+ switch (pid) {
+ case -1:
+ close(master);
+ close(slave);
+ return -1;
+ case 0:
+ close(master);
+ login_tty(slave);
+ return 0;
+ default:
+ close(slave);
+ *amaster = master;
+ return pid;
+ }
+}
+
+#endif
+
+/// @returns zero on success, or negative error code
+int pty_proc_spawn(PtyProc *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // termios initialized at first use
+ static struct termios termios_default;
+ if (!termios_default.c_cflag) {
+ init_termios(&termios_default);
+ }
+
+ int status = 0; // zero or negative error code (libuv convention)
+ Proc *proc = (Proc *)ptyproc;
+ assert(proc->err.s.closed);
+ uv_signal_start(&proc->loop->children_watcher, chld_handler, SIGCHLD);
+ ptyproc->winsize = (struct winsize){ ptyproc->height, ptyproc->width, 0, 0 };
+ uv_disable_stdio_inheritance();
+ int master;
+ int pid = forkpty(&master, NULL, &termios_default, &ptyproc->winsize);
+
+ if (pid < 0) {
+ status = -errno;
+ ELOG("forkpty failed: %s", strerror(errno));
+ return status;
+ } else if (pid == 0) {
+ init_child(ptyproc); // never returns
+ }
+
+ // make sure the master file descriptor is non blocking
+ int master_status_flags = fcntl(master, F_GETFL);
+ if (master_status_flags == -1) {
+ status = -errno;
+ ELOG("Failed to get master descriptor status flags: %s", strerror(errno));
+ goto error;
+ }
+ if (fcntl(master, F_SETFL, master_status_flags | O_NONBLOCK) == -1) {
+ status = -errno;
+ ELOG("Failed to make master descriptor non-blocking: %s", strerror(errno));
+ goto error;
+ }
+
+ // Other jobs and providers should not get a copy of this file descriptor.
+ if (os_set_cloexec(master) == -1) {
+ status = -errno;
+ ELOG("Failed to set CLOEXEC on ptmx file descriptor");
+ goto error;
+ }
+
+ if (!proc->in.closed
+ && (status = set_duplicating_descriptor(master, &proc->in.uv.pipe))) {
+ goto error;
+ }
+ if (!proc->out.s.closed
+ && (status = set_duplicating_descriptor(master, &proc->out.s.uv.pipe))) {
+ goto error;
+ }
+
+ ptyproc->tty_fd = master;
+ proc->pid = pid;
+ return 0;
+
+error:
+ close(master);
+ kill(pid, SIGKILL);
+ waitpid(pid, NULL, 0);
+ return status;
+}
+
+const char *pty_proc_tty_name(PtyProc *ptyproc)
+{
+ return ptsname(ptyproc->tty_fd);
+}
+
+void pty_proc_resize(PtyProc *ptyproc, uint16_t width, uint16_t height)
+ FUNC_ATTR_NONNULL_ALL
+{
+ ptyproc->winsize = (struct winsize){ height, width, 0, 0 };
+ ioctl(ptyproc->tty_fd, TIOCSWINSZ, &ptyproc->winsize);
+}
+
+void pty_proc_close(PtyProc *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+ pty_proc_close_master(ptyproc);
+ Proc *proc = (Proc *)ptyproc;
+ if (proc->internal_close_cb) {
+ proc->internal_close_cb(proc);
+ }
+}
+
+void pty_proc_close_master(PtyProc *ptyproc) FUNC_ATTR_NONNULL_ALL
+{
+ if (ptyproc->tty_fd >= 0) {
+ close(ptyproc->tty_fd);
+ ptyproc->tty_fd = -1;
+ }
+}
+
+void pty_proc_teardown(Loop *loop)
+{
+ uv_signal_stop(&loop->children_watcher);
+}
+
+static void init_child(PtyProc *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+#if defined(HAVE__NSGETENVIRON)
+# define environ (*_NSGetEnviron())
+#else
+ extern char **environ;
+#endif
+ // New session/process-group. #6530
+ setsid();
+
+ signal(SIGCHLD, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGALRM, SIG_DFL);
+
+ Proc *proc = (Proc *)ptyproc;
+ if (proc->cwd && os_chdir(proc->cwd) != 0) {
+ ELOG("chdir(%s) failed: %s", proc->cwd, strerror(errno));
+ return;
+ }
+
+ const char *prog = proc_get_exepath(proc);
+
+ assert(proc->env);
+ environ = tv_dict_to_env(proc->env);
+ execvp(prog, proc->argv);
+ ELOG("execvp(%s) failed: %s", prog, strerror(errno));
+
+ _exit(122); // 122 is EXEC_FAILED in the Vim source.
+}
+
+static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL
+{
+ // Taken from pangoterm
+ termios->c_iflag = ICRNL|IXON;
+ termios->c_oflag = OPOST|ONLCR;
+#ifdef TAB0
+ termios->c_oflag |= TAB0;
+#endif
+ termios->c_cflag = CS8|CREAD;
+ termios->c_lflag = ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK;
+
+ // not using cfsetspeed, not available on all platforms
+ cfsetispeed(termios, 38400);
+ cfsetospeed(termios, 38400);
+
+#ifdef IUTF8
+ termios->c_iflag |= IUTF8;
+#endif
+#ifdef NL0
+ termios->c_oflag |= NL0;
+#endif
+#ifdef CR0
+ termios->c_oflag |= CR0;
+#endif
+#ifdef BS0
+ termios->c_oflag |= BS0;
+#endif
+#ifdef VT0
+ termios->c_oflag |= VT0;
+#endif
+#ifdef FF0
+ termios->c_oflag |= FF0;
+#endif
+#ifdef ECHOCTL
+ termios->c_lflag |= ECHOCTL;
+#endif
+#ifdef ECHOKE
+ termios->c_lflag |= ECHOKE;
+#endif
+
+ termios->c_cc[VINTR] = 0x1f & 'C';
+ termios->c_cc[VQUIT] = 0x1f & '\\';
+ termios->c_cc[VERASE] = 0x7f;
+ termios->c_cc[VKILL] = 0x1f & 'U';
+ termios->c_cc[VEOF] = 0x1f & 'D';
+ termios->c_cc[VEOL] = _POSIX_VDISABLE;
+ termios->c_cc[VEOL2] = _POSIX_VDISABLE;
+ termios->c_cc[VSTART] = 0x1f & 'Q';
+ termios->c_cc[VSTOP] = 0x1f & 'S';
+ termios->c_cc[VSUSP] = 0x1f & 'Z';
+ termios->c_cc[VREPRINT] = 0x1f & 'R';
+ termios->c_cc[VWERASE] = 0x1f & 'W';
+ termios->c_cc[VLNEXT] = 0x1f & 'V';
+ termios->c_cc[VMIN] = 1;
+ termios->c_cc[VTIME] = 0;
+}
+
+static int set_duplicating_descriptor(int fd, uv_pipe_t *pipe)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int status = 0; // zero or negative error code (libuv convention)
+ int fd_dup = dup(fd);
+ if (fd_dup < 0) {
+ status = -errno;
+ ELOG("Failed to dup descriptor %d: %s", fd, strerror(errno));
+ return status;
+ }
+
+ if (os_set_cloexec(fd_dup) == -1) {
+ status = -errno;
+ ELOG("Failed to set CLOEXEC on duplicate fd");
+ goto error;
+ }
+
+ status = uv_pipe_open(pipe, fd_dup);
+ if (status) {
+ ELOG("Failed to set pipe to descriptor %d: %s",
+ fd_dup, uv_strerror(status));
+ goto error;
+ }
+ return status;
+
+error:
+ close(fd_dup);
+ return status;
+}
+
+static void chld_handler(uv_signal_t *handle, int signum)
+{
+ int stat = 0;
+ int pid;
+
+ Loop *loop = handle->loop->data;
+
+ kl_iter(WatcherPtr, loop->children, current) {
+ Proc *proc = (*current)->data;
+ do {
+ pid = waitpid(proc->pid, &stat, WNOHANG);
+ } while (pid < 0 && errno == EINTR);
+
+ if (pid <= 0) {
+ continue;
+ }
+
+ if (WIFEXITED(stat)) {
+ proc->status = WEXITSTATUS(stat);
+ } else if (WIFSIGNALED(stat)) {
+ proc->status = 128 + WTERMSIG(stat);
+ }
+ proc->internal_exit_cb(proc);
+ }
+}
+
+PtyProc pty_proc_init(Loop *loop, void *data)
+{
+ PtyProc rv;
+ rv.proc = proc_init(loop, kProcTypePty, data);
+ rv.width = 80;
+ rv.height = 24;
+ rv.tty_fd = -1;
+ return rv;
+}
diff --git a/src/nvim/os/pty_proc_unix.h b/src/nvim/os/pty_proc_unix.h
@@ -0,0 +1,18 @@
+#pragma once
+// IWYU pragma: private, include "nvim/os/pty_proc.h"
+
+#include <stdint.h>
+#include <sys/ioctl.h>
+
+#include "nvim/event/defs.h"
+
+typedef struct {
+ Proc proc;
+ uint16_t width, height;
+ struct winsize winsize;
+ int tty_fd;
+} PtyProc;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/pty_proc_unix.h.generated.h"
+#endif
diff --git a/src/nvim/os/pty_proc_win.c b/src/nvim/os/pty_proc_win.c
@@ -0,0 +1,440 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include "nvim/ascii_defs.h"
+#include "nvim/eval/typval.h"
+#include "nvim/event/loop.h"
+#include "nvim/log.h"
+#include "nvim/mbyte.h"
+#include "nvim/memory.h"
+#include "nvim/os/os.h"
+#include "nvim/os/pty_conpty_win.h"
+#include "nvim/os/pty_proc_win.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/pty_proc_win.c.generated.h"
+#endif
+
+static void CALLBACK pty_proc_finish1(void *context, BOOLEAN unused)
+ FUNC_ATTR_NONNULL_ALL
+{
+ PtyProc *ptyproc = (PtyProc *)context;
+ Proc *proc = (Proc *)ptyproc;
+
+ os_conpty_free(ptyproc->conpty);
+ // NB: pty_proc_finish1() is called on a separate thread,
+ // but the timer only works properly if it's started by the main thread.
+ loop_schedule_fast(proc->loop, event_create(start_wait_eof_timer, ptyproc));
+}
+
+static void start_wait_eof_timer(void **argv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ PtyProc *ptyproc = (PtyProc *)argv[0];
+
+ if (ptyproc->finish_wait != NULL) {
+ uv_timer_start(&ptyproc->wait_eof_timer, wait_eof_timer_cb, 200, 200);
+ }
+}
+
+/// @returns zero on success, or negative error code.
+int pty_proc_spawn(PtyProc *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+ Proc *proc = (Proc *)ptyproc;
+ int status = 0;
+ conpty_t *conpty_object = NULL;
+ char *in_name = NULL;
+ char *out_name = NULL;
+ HANDLE proc_handle = NULL;
+ uv_connect_t *in_req = NULL;
+ uv_connect_t *out_req = NULL;
+ wchar_t *cmd_line = NULL;
+ wchar_t *cwd = NULL;
+ wchar_t *env = NULL;
+ const char *emsg = NULL;
+
+ assert(proc->err.s.closed);
+
+ if (!os_has_conpty_working() || (conpty_object = os_conpty_init(&in_name,
+ &out_name, ptyproc->width,
+ ptyproc->height)) == NULL) {
+ status = UV_ENOSYS;
+ goto cleanup;
+ }
+
+ if (!proc->in.closed) {
+ in_req = xmalloc(sizeof(uv_connect_t));
+ uv_pipe_connect(in_req,
+ &proc->in.uv.pipe,
+ in_name,
+ pty_proc_connect_cb);
+ }
+
+ if (!proc->out.s.closed) {
+ out_req = xmalloc(sizeof(uv_connect_t));
+ uv_pipe_connect(out_req,
+ &proc->out.s.uv.pipe,
+ out_name,
+ pty_proc_connect_cb);
+ }
+
+ if (proc->cwd != NULL) {
+ status = utf8_to_utf16(proc->cwd, -1, &cwd);
+ if (status != 0) {
+ emsg = "utf8_to_utf16(proc->cwd) failed";
+ goto cleanup;
+ }
+ }
+
+ status = build_cmd_line(proc->argv, &cmd_line,
+ os_shell_is_cmdexe(proc->argv[0]));
+ if (status != 0) {
+ emsg = "build_cmd_line failed";
+ goto cleanup;
+ }
+
+ if (proc->env != NULL) {
+ status = build_env_block(proc->env, &env);
+ }
+
+ if (status != 0) {
+ emsg = "build_env_block failed";
+ goto cleanup;
+ }
+
+ if (!os_conpty_spawn(conpty_object,
+ &proc_handle,
+ NULL,
+ cmd_line,
+ cwd,
+ env)) {
+ emsg = "os_conpty_spawn failed";
+ status = (int)GetLastError();
+ goto cleanup;
+ }
+ proc->pid = (int)GetProcessId(proc_handle);
+
+ uv_timer_init(&proc->loop->uv, &ptyproc->wait_eof_timer);
+ ptyproc->wait_eof_timer.data = (void *)ptyproc;
+ if (!RegisterWaitForSingleObject(&ptyproc->finish_wait,
+ proc_handle,
+ pty_proc_finish1,
+ ptyproc,
+ INFINITE,
+ WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE)) {
+ abort();
+ }
+
+ // Wait until pty_proc_connect_cb is called.
+ while ((in_req != NULL && in_req->handle != NULL)
+ || (out_req != NULL && out_req->handle != NULL)) {
+ uv_run(&proc->loop->uv, UV_RUN_ONCE);
+ }
+
+ ptyproc->conpty = conpty_object;
+ ptyproc->proc_handle = proc_handle;
+ conpty_object = NULL;
+ proc_handle = NULL;
+
+cleanup:
+ if (status) {
+ // In the case of an error of MultiByteToWideChar or CreateProcessW.
+ ELOG("pty_proc_spawn(%s): %s: error code: %d",
+ proc->argv[0], emsg, status);
+ status = os_translate_sys_error(status);
+ }
+ os_conpty_free(conpty_object);
+ xfree(in_name);
+ xfree(out_name);
+ if (proc_handle != NULL) {
+ CloseHandle(proc_handle);
+ }
+ xfree(in_req);
+ xfree(out_req);
+ xfree(cmd_line);
+ xfree(env);
+ xfree(cwd);
+ return status;
+}
+
+const char *pty_proc_tty_name(PtyProc *ptyproc)
+{
+ return "?";
+}
+
+void pty_proc_resize(PtyProc *ptyproc, uint16_t width, uint16_t height)
+ FUNC_ATTR_NONNULL_ALL
+{
+ os_conpty_set_size(ptyproc->conpty, width, height);
+}
+
+void pty_proc_close(PtyProc *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+ Proc *proc = (Proc *)ptyproc;
+
+ pty_proc_close_master(ptyproc);
+
+ if (ptyproc->finish_wait != NULL) {
+ UnregisterWaitEx(ptyproc->finish_wait, NULL);
+ ptyproc->finish_wait = NULL;
+ uv_close((uv_handle_t *)&ptyproc->wait_eof_timer, NULL);
+ }
+ if (ptyproc->proc_handle != NULL) {
+ CloseHandle(ptyproc->proc_handle);
+ ptyproc->proc_handle = NULL;
+ }
+
+ if (proc->internal_close_cb) {
+ proc->internal_close_cb(proc);
+ }
+}
+
+void pty_proc_close_master(PtyProc *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+}
+
+void pty_proc_teardown(Loop *loop)
+ FUNC_ATTR_NONNULL_ALL
+{
+}
+
+static void pty_proc_connect_cb(uv_connect_t *req, int status)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(status == 0);
+ req->handle = NULL;
+}
+
+static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer)
+ FUNC_ATTR_NONNULL_ALL
+{
+ PtyProc *ptyproc = wait_eof_timer->data;
+ Proc *proc = (Proc *)ptyproc;
+
+ assert(ptyproc->finish_wait != NULL);
+ if (proc->out.s.closed || proc->out.did_eof || !uv_is_readable(proc->out.s.uvstream)) {
+ uv_timer_stop(&ptyproc->wait_eof_timer);
+ pty_proc_finish2(ptyproc);
+ }
+}
+
+static void pty_proc_finish2(PtyProc *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+ Proc *proc = (Proc *)ptyproc;
+
+ DWORD exit_code = 0;
+ GetExitCodeProcess(ptyproc->proc_handle, &exit_code);
+ proc->status = proc->exit_signal ? 128 + proc->exit_signal : (int)exit_code;
+
+ proc->internal_exit_cb(proc);
+}
+
+/// Build the command line to pass to CreateProcessW.
+///
+/// @param[in] argv Array with string arguments.
+/// @param[out] cmd_line Location where saved built cmd line.
+///
+/// @returns zero on success, or error code of MultiByteToWideChar function.
+///
+static int build_cmd_line(char **argv, wchar_t **cmd_line, bool is_cmdexe)
+ FUNC_ATTR_NONNULL_ALL
+{
+ size_t utf8_cmd_line_len = 0;
+ size_t argc = 0;
+ QUEUE args_q;
+
+ QUEUE_INIT(&args_q);
+ while (*argv) {
+ size_t buf_len = is_cmdexe ? (strlen(*argv) + 1) : (strlen(*argv) * 2 + 3);
+ ArgNode *arg_node = xmalloc(sizeof(*arg_node));
+ arg_node->arg = xmalloc(buf_len);
+ if (is_cmdexe) {
+ xstrlcpy(arg_node->arg, *argv, buf_len);
+ } else {
+ quote_cmd_arg(arg_node->arg, buf_len, *argv);
+ }
+ utf8_cmd_line_len += strlen(arg_node->arg);
+ QUEUE_INIT(&arg_node->node);
+ QUEUE_INSERT_TAIL(&args_q, &arg_node->node);
+ argc++;
+ argv++;
+ }
+
+ utf8_cmd_line_len += argc;
+ char *utf8_cmd_line = xmalloc(utf8_cmd_line_len);
+ *utf8_cmd_line = NUL;
+ QUEUE *q;
+ QUEUE_FOREACH(q, &args_q, {
+ ArgNode *arg_node = QUEUE_DATA(q, ArgNode, node);
+ xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len);
+ QUEUE_REMOVE(q);
+ xfree(arg_node->arg);
+ xfree(arg_node);
+ if (!QUEUE_EMPTY(&args_q)) {
+ xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len);
+ }
+ })
+
+ int result = utf8_to_utf16(utf8_cmd_line, -1, cmd_line);
+ xfree(utf8_cmd_line);
+ return result;
+}
+
+/// Emulate quote_cmd_arg of libuv and quotes command line argument.
+/// Most of the code came from libuv.
+///
+/// @param[out] dest Location where saved quotes argument.
+/// @param dest_remaining Destination buffer size.
+/// @param[in] src Pointer to argument.
+///
+static void quote_cmd_arg(char *dest, size_t dest_remaining, const char *src)
+ FUNC_ATTR_NONNULL_ALL
+{
+ size_t src_len = strlen(src);
+ bool quote_hit = true;
+ char *start = dest;
+
+ if (src_len == 0) {
+ // Need double quotation for empty argument.
+ snprintf(dest, dest_remaining, "\"\"");
+ return;
+ }
+
+ if (NULL == strpbrk(src, " \t\"")) {
+ // No quotation needed.
+ xstrlcpy(dest, src, dest_remaining);
+ return;
+ }
+
+ if (NULL == strpbrk(src, "\"\\")) {
+ // No embedded double quotes or backlashes, so I can just wrap quote marks.
+ // around the whole thing.
+ snprintf(dest, dest_remaining, "\"%s\"", src);
+ return;
+ }
+
+ // Expected input/output:
+ // input : 'hello"world'
+ // output: '"hello\"world"'
+ // input : 'hello""world'
+ // output: '"hello\"\"world"'
+ // input : 'hello\world'
+ // output: 'hello\world'
+ // input : 'hello\\world'
+ // output: 'hello\\world'
+ // input : 'hello\"world'
+ // output: '"hello\\\"world"'
+ // input : 'hello\\"world'
+ // output: '"hello\\\\\"world"'
+ // input : 'hello world\'
+ // output: '"hello world\\"'
+
+ assert(dest_remaining--);
+ *(dest++) = NUL;
+ assert(dest_remaining--);
+ *(dest++) = '"';
+ for (size_t i = src_len; i > 0; i--) {
+ assert(dest_remaining--);
+ *(dest++) = src[i - 1];
+ if (quote_hit && src[i - 1] == '\\') {
+ assert(dest_remaining--);
+ *(dest++) = '\\';
+ } else if (src[i - 1] == '"') {
+ quote_hit = true;
+ assert(dest_remaining--);
+ *(dest++) = '\\';
+ } else {
+ quote_hit = false;
+ }
+ }
+ assert(dest_remaining);
+ *dest = '"';
+
+ while (start < dest) {
+ char tmp = *start;
+ *start = *dest;
+ *dest = tmp;
+ start++;
+ dest--;
+ }
+}
+
+typedef struct EnvNode {
+ wchar_t *str;
+ size_t len;
+ QUEUE node;
+} EnvNode;
+
+/// Build the environment block to pass to CreateProcessW.
+///
+/// @param[in] denv Dict of environment name/value pairs
+/// @param[out] env Allocated environment block
+///
+/// @returns zero on success or error code of MultiByteToWideChar function.
+static int build_env_block(dict_T *denv, wchar_t **env_block)
+{
+ const size_t denv_size = (size_t)tv_dict_len(denv);
+ size_t env_block_len = 0;
+ int rc = 0;
+ char **env = tv_dict_to_env(denv);
+
+ QUEUE *q;
+ QUEUE env_q;
+ QUEUE_INIT(&env_q);
+ // Convert env vars to wchar_t and calculate how big the final env block
+ // needs to be
+ for (size_t i = 0; i < denv_size; i++) {
+ EnvNode *env_node = xmalloc(sizeof(*env_node));
+ rc = utf8_to_utf16(env[i], -1, &env_node->str);
+ if (rc != 0) {
+ goto cleanup;
+ }
+ env_node->len = wcslen(env_node->str) + 1;
+ env_block_len += env_node->len;
+ QUEUE_INSERT_TAIL(&env_q, &env_node->node);
+ }
+
+ // Additional NUL after the final entry
+ env_block_len++;
+
+ *env_block = xmalloc(sizeof(**env_block) * env_block_len);
+ wchar_t *pos = *env_block;
+
+ QUEUE_FOREACH(q, &env_q, {
+ EnvNode *env_node = QUEUE_DATA(q, EnvNode, node);
+ memcpy(pos, env_node->str, env_node->len * sizeof(*pos));
+ pos += env_node->len;
+ })
+
+ *pos = L'\0';
+
+cleanup:
+ q = QUEUE_HEAD(&env_q);
+ while (q != &env_q) {
+ QUEUE *next = q->next;
+ EnvNode *env_node = QUEUE_DATA(q, EnvNode, node);
+ XFREE_CLEAR(env_node->str);
+ QUEUE_REMOVE(q);
+ xfree(env_node);
+ q = next;
+ }
+
+ return rc;
+}
+
+PtyProc pty_proc_init(Loop *loop, void *data)
+{
+ PtyProc rv;
+ rv.proc = proc_init(loop, kProcTypePty, data);
+ rv.width = 80;
+ rv.height = 24;
+ rv.conpty = NULL;
+ rv.finish_wait = NULL;
+ rv.proc_handle = NULL;
+ return rv;
+}
diff --git a/src/nvim/os/pty_proc_win.h b/src/nvim/os/pty_proc_win.h
@@ -0,0 +1,27 @@
+#pragma once
+// IWYU pragma: private, include "nvim/os/pty_proc.h"
+
+#include <uv.h>
+
+#include "nvim/event/proc.h"
+#include "nvim/lib/queue_defs.h"
+#include "nvim/os/pty_conpty_win.h"
+
+typedef struct pty_process {
+ Proc proc;
+ uint16_t width, height;
+ conpty_t *conpty;
+ HANDLE finish_wait;
+ HANDLE proc_handle;
+ uv_timer_t wait_eof_timer;
+} PtyProc;
+
+// Structure used by build_cmd_line()
+typedef struct arg_node {
+ char *arg; // pointer to argument.
+ QUEUE node; // QUEUE structure.
+} ArgNode;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/pty_proc_win.h.generated.h"
+#endif
diff --git a/src/nvim/os/pty_process.h b/src/nvim/os/pty_process.h
@@ -1,7 +0,0 @@
-#pragma once
-
-#ifdef MSWIN
-# include "nvim/os/pty_process_win.h"
-#else
-# include "nvim/os/pty_process_unix.h"
-#endif
diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c
@@ -1,417 +0,0 @@
-// Some of the code came from pangoterm and libuv
-
-#include <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/wait.h>
-#include <uv.h>
-
-// forkpty is not in POSIX, so headers are platform-specific
-#if defined(__FreeBSD__) || defined(__DragonFly__)
-# include <libutil.h>
-#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
-# include <util.h>
-#elif defined(__sun)
-# include <fcntl.h>
-# include <signal.h>
-# include <sys/stream.h>
-# include <sys/syscall.h>
-# include <unistd.h>
-#else
-# include <pty.h>
-#endif
-
-#ifdef __APPLE__
-# include <crt_externs.h>
-#endif
-
-#include "auto/config.h"
-#include "klib/klist.h"
-#include "nvim/eval/typval.h"
-#include "nvim/event/defs.h"
-#include "nvim/event/loop.h"
-#include "nvim/event/process.h"
-#include "nvim/log.h"
-#include "nvim/os/fs.h"
-#include "nvim/os/os_defs.h"
-#include "nvim/os/pty_process.h"
-#include "nvim/os/pty_process_unix.h"
-#include "nvim/types_defs.h"
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "os/pty_process_unix.c.generated.h"
-#endif
-
-#if defined(__sun) && !defined(HAVE_FORKPTY)
-
-// this header defines STR, just as nvim.h, but it is defined as ('S'<<8),
-// to avoid #undef STR, #undef STR, #define STR ('S'<<8) just delay the
-// inclusion of the header even though it gets include out of order.
-# include <sys/stropts.h>
-
-static int openpty(int *amaster, int *aslave, char *name, struct termios *termp,
- struct winsize *winp)
-{
- int slave = -1;
- int master = open("/dev/ptmx", O_RDWR);
- if (master == -1) {
- goto error;
- }
-
- // grantpt will invoke a setuid program to change permissions
- // and might fail if SIGCHLD handler is set, temporarily reset
- // while running
- void (*sig_saved)(int) = signal(SIGCHLD, SIG_DFL);
- int res = grantpt(master);
- signal(SIGCHLD, sig_saved);
-
- if (res == -1 || unlockpt(master) == -1) {
- goto error;
- }
-
- char *slave_name = ptsname(master);
- if (slave_name == NULL) {
- goto error;
- }
-
- slave = open(slave_name, O_RDWR|O_NOCTTY);
- if (slave == -1) {
- goto error;
- }
-
- // ptem emulates a terminal when used on a pseudo terminal driver,
- // must be pushed before ldterm
- ioctl(slave, I_PUSH, "ptem");
- // ldterm provides most of the termio terminal interface
- ioctl(slave, I_PUSH, "ldterm");
- // ttcompat compatibility with older terminal ioctls
- ioctl(slave, I_PUSH, "ttcompat");
-
- if (termp) {
- tcsetattr(slave, TCSAFLUSH, termp);
- }
- if (winp) {
- ioctl(slave, TIOCSWINSZ, winp);
- }
-
- *amaster = master;
- *aslave = slave;
- // ignoring name, not passed and size is unknown in the API
-
- return 0;
-
-error:
- if (slave != -1) {
- close(slave);
- }
- if (master != -1) {
- close(master);
- }
- return -1;
-}
-
-static int login_tty(int fd)
-{
- setsid();
- if (ioctl(fd, TIOCSCTTY, NULL) == -1) {
- return -1;
- }
-
- dup2(fd, STDIN_FILENO);
- dup2(fd, STDOUT_FILENO);
- dup2(fd, STDERR_FILENO);
- if (fd > STDERR_FILENO) {
- close(fd);
- }
-
- return 0;
-}
-
-static pid_t forkpty(int *amaster, char *name, struct termios *termp, struct winsize *winp)
-{
- int master, slave;
- if (openpty(&master, &slave, name, termp, winp) == -1) {
- return -1;
- }
-
- pid_t pid = fork();
- switch (pid) {
- case -1:
- close(master);
- close(slave);
- return -1;
- case 0:
- close(master);
- login_tty(slave);
- return 0;
- default:
- close(slave);
- *amaster = master;
- return pid;
- }
-}
-
-#endif
-
-/// @returns zero on success, or negative error code
-int pty_process_spawn(PtyProcess *ptyproc)
- FUNC_ATTR_NONNULL_ALL
-{
- // termios initialized at first use
- static struct termios termios_default;
- if (!termios_default.c_cflag) {
- init_termios(&termios_default);
- }
-
- int status = 0; // zero or negative error code (libuv convention)
- Process *proc = (Process *)ptyproc;
- assert(proc->err.s.closed);
- uv_signal_start(&proc->loop->children_watcher, chld_handler, SIGCHLD);
- ptyproc->winsize = (struct winsize){ ptyproc->height, ptyproc->width, 0, 0 };
- uv_disable_stdio_inheritance();
- int master;
- int pid = forkpty(&master, NULL, &termios_default, &ptyproc->winsize);
-
- if (pid < 0) {
- status = -errno;
- ELOG("forkpty failed: %s", strerror(errno));
- return status;
- } else if (pid == 0) {
- init_child(ptyproc); // never returns
- }
-
- // make sure the master file descriptor is non blocking
- int master_status_flags = fcntl(master, F_GETFL);
- if (master_status_flags == -1) {
- status = -errno;
- ELOG("Failed to get master descriptor status flags: %s", strerror(errno));
- goto error;
- }
- if (fcntl(master, F_SETFL, master_status_flags | O_NONBLOCK) == -1) {
- status = -errno;
- ELOG("Failed to make master descriptor non-blocking: %s", strerror(errno));
- goto error;
- }
-
- // Other jobs and providers should not get a copy of this file descriptor.
- if (os_set_cloexec(master) == -1) {
- status = -errno;
- ELOG("Failed to set CLOEXEC on ptmx file descriptor");
- goto error;
- }
-
- if (!proc->in.closed
- && (status = set_duplicating_descriptor(master, &proc->in.uv.pipe))) {
- goto error;
- }
- if (!proc->out.s.closed
- && (status = set_duplicating_descriptor(master, &proc->out.s.uv.pipe))) {
- goto error;
- }
-
- ptyproc->tty_fd = master;
- proc->pid = pid;
- return 0;
-
-error:
- close(master);
- kill(pid, SIGKILL);
- waitpid(pid, NULL, 0);
- return status;
-}
-
-const char *pty_process_tty_name(PtyProcess *ptyproc)
-{
- return ptsname(ptyproc->tty_fd);
-}
-
-void pty_process_resize(PtyProcess *ptyproc, uint16_t width, uint16_t height)
- FUNC_ATTR_NONNULL_ALL
-{
- ptyproc->winsize = (struct winsize){ height, width, 0, 0 };
- ioctl(ptyproc->tty_fd, TIOCSWINSZ, &ptyproc->winsize);
-}
-
-void pty_process_close(PtyProcess *ptyproc)
- FUNC_ATTR_NONNULL_ALL
-{
- pty_process_close_master(ptyproc);
- Process *proc = (Process *)ptyproc;
- if (proc->internal_close_cb) {
- proc->internal_close_cb(proc);
- }
-}
-
-void pty_process_close_master(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL
-{
- if (ptyproc->tty_fd >= 0) {
- close(ptyproc->tty_fd);
- ptyproc->tty_fd = -1;
- }
-}
-
-void pty_process_teardown(Loop *loop)
-{
- uv_signal_stop(&loop->children_watcher);
-}
-
-static void init_child(PtyProcess *ptyproc)
- FUNC_ATTR_NONNULL_ALL
-{
-#if defined(HAVE__NSGETENVIRON)
-# define environ (*_NSGetEnviron())
-#else
- extern char **environ;
-#endif
- // New session/process-group. #6530
- setsid();
-
- signal(SIGCHLD, SIG_DFL);
- signal(SIGHUP, SIG_DFL);
- signal(SIGINT, SIG_DFL);
- signal(SIGQUIT, SIG_DFL);
- signal(SIGTERM, SIG_DFL);
- signal(SIGALRM, SIG_DFL);
-
- Process *proc = (Process *)ptyproc;
- if (proc->cwd && os_chdir(proc->cwd) != 0) {
- ELOG("chdir(%s) failed: %s", proc->cwd, strerror(errno));
- return;
- }
-
- const char *prog = process_get_exepath(proc);
-
- assert(proc->env);
- environ = tv_dict_to_env(proc->env);
- execvp(prog, proc->argv);
- ELOG("execvp(%s) failed: %s", prog, strerror(errno));
-
- _exit(122); // 122 is EXEC_FAILED in the Vim source.
-}
-
-static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL
-{
- // Taken from pangoterm
- termios->c_iflag = ICRNL|IXON;
- termios->c_oflag = OPOST|ONLCR;
-#ifdef TAB0
- termios->c_oflag |= TAB0;
-#endif
- termios->c_cflag = CS8|CREAD;
- termios->c_lflag = ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK;
-
- // not using cfsetspeed, not available on all platforms
- cfsetispeed(termios, 38400);
- cfsetospeed(termios, 38400);
-
-#ifdef IUTF8
- termios->c_iflag |= IUTF8;
-#endif
-#ifdef NL0
- termios->c_oflag |= NL0;
-#endif
-#ifdef CR0
- termios->c_oflag |= CR0;
-#endif
-#ifdef BS0
- termios->c_oflag |= BS0;
-#endif
-#ifdef VT0
- termios->c_oflag |= VT0;
-#endif
-#ifdef FF0
- termios->c_oflag |= FF0;
-#endif
-#ifdef ECHOCTL
- termios->c_lflag |= ECHOCTL;
-#endif
-#ifdef ECHOKE
- termios->c_lflag |= ECHOKE;
-#endif
-
- termios->c_cc[VINTR] = 0x1f & 'C';
- termios->c_cc[VQUIT] = 0x1f & '\\';
- termios->c_cc[VERASE] = 0x7f;
- termios->c_cc[VKILL] = 0x1f & 'U';
- termios->c_cc[VEOF] = 0x1f & 'D';
- termios->c_cc[VEOL] = _POSIX_VDISABLE;
- termios->c_cc[VEOL2] = _POSIX_VDISABLE;
- termios->c_cc[VSTART] = 0x1f & 'Q';
- termios->c_cc[VSTOP] = 0x1f & 'S';
- termios->c_cc[VSUSP] = 0x1f & 'Z';
- termios->c_cc[VREPRINT] = 0x1f & 'R';
- termios->c_cc[VWERASE] = 0x1f & 'W';
- termios->c_cc[VLNEXT] = 0x1f & 'V';
- termios->c_cc[VMIN] = 1;
- termios->c_cc[VTIME] = 0;
-}
-
-static int set_duplicating_descriptor(int fd, uv_pipe_t *pipe)
- FUNC_ATTR_NONNULL_ALL
-{
- int status = 0; // zero or negative error code (libuv convention)
- int fd_dup = dup(fd);
- if (fd_dup < 0) {
- status = -errno;
- ELOG("Failed to dup descriptor %d: %s", fd, strerror(errno));
- return status;
- }
-
- if (os_set_cloexec(fd_dup) == -1) {
- status = -errno;
- ELOG("Failed to set CLOEXEC on duplicate fd");
- goto error;
- }
-
- status = uv_pipe_open(pipe, fd_dup);
- if (status) {
- ELOG("Failed to set pipe to descriptor %d: %s",
- fd_dup, uv_strerror(status));
- goto error;
- }
- return status;
-
-error:
- close(fd_dup);
- return status;
-}
-
-static void chld_handler(uv_signal_t *handle, int signum)
-{
- int stat = 0;
- int pid;
-
- Loop *loop = handle->loop->data;
-
- kl_iter(WatcherPtr, loop->children, current) {
- Process *proc = (*current)->data;
- do {
- pid = waitpid(proc->pid, &stat, WNOHANG);
- } while (pid < 0 && errno == EINTR);
-
- if (pid <= 0) {
- continue;
- }
-
- if (WIFEXITED(stat)) {
- proc->status = WEXITSTATUS(stat);
- } else if (WIFSIGNALED(stat)) {
- proc->status = 128 + WTERMSIG(stat);
- }
- proc->internal_exit_cb(proc);
- }
-}
-
-PtyProcess pty_process_init(Loop *loop, void *data)
-{
- PtyProcess rv;
- rv.process = process_init(loop, kProcessTypePty, data);
- rv.width = 80;
- rv.height = 24;
- rv.tty_fd = -1;
- return rv;
-}
diff --git a/src/nvim/os/pty_process_unix.h b/src/nvim/os/pty_process_unix.h
@@ -1,18 +0,0 @@
-#pragma once
-// IWYU pragma: private, include "nvim/os/pty_process.h"
-
-#include <stdint.h>
-#include <sys/ioctl.h>
-
-#include "nvim/event/defs.h"
-
-typedef struct {
- Process process;
- uint16_t width, height;
- struct winsize winsize;
- int tty_fd;
-} PtyProcess;
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "os/pty_process_unix.h.generated.h"
-#endif
diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c
@@ -1,440 +0,0 @@
-#include <assert.h>
-#include <stdbool.h>
-#include <stdlib.h>
-
-#include "nvim/ascii_defs.h"
-#include "nvim/eval/typval.h"
-#include "nvim/event/loop.h"
-#include "nvim/log.h"
-#include "nvim/mbyte.h"
-#include "nvim/memory.h"
-#include "nvim/os/os.h"
-#include "nvim/os/pty_conpty_win.h"
-#include "nvim/os/pty_process_win.h"
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "os/pty_process_win.c.generated.h"
-#endif
-
-static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused)
- FUNC_ATTR_NONNULL_ALL
-{
- PtyProcess *ptyproc = (PtyProcess *)context;
- Process *proc = (Process *)ptyproc;
-
- os_conpty_free(ptyproc->conpty);
- // NB: pty_process_finish1() is called on a separate thread,
- // but the timer only works properly if it's started by the main thread.
- loop_schedule_fast(proc->loop, event_create(start_wait_eof_timer, ptyproc));
-}
-
-static void start_wait_eof_timer(void **argv)
- FUNC_ATTR_NONNULL_ALL
-{
- PtyProcess *ptyproc = (PtyProcess *)argv[0];
-
- if (ptyproc->finish_wait != NULL) {
- uv_timer_start(&ptyproc->wait_eof_timer, wait_eof_timer_cb, 200, 200);
- }
-}
-
-/// @returns zero on success, or negative error code.
-int pty_process_spawn(PtyProcess *ptyproc)
- FUNC_ATTR_NONNULL_ALL
-{
- Process *proc = (Process *)ptyproc;
- int status = 0;
- conpty_t *conpty_object = NULL;
- char *in_name = NULL;
- char *out_name = NULL;
- HANDLE process_handle = NULL;
- uv_connect_t *in_req = NULL;
- uv_connect_t *out_req = NULL;
- wchar_t *cmd_line = NULL;
- wchar_t *cwd = NULL;
- wchar_t *env = NULL;
- const char *emsg = NULL;
-
- assert(proc->err.s.closed);
-
- if (!os_has_conpty_working() || (conpty_object = os_conpty_init(&in_name,
- &out_name, ptyproc->width,
- ptyproc->height)) == NULL) {
- status = UV_ENOSYS;
- goto cleanup;
- }
-
- if (!proc->in.closed) {
- in_req = xmalloc(sizeof(uv_connect_t));
- uv_pipe_connect(in_req,
- &proc->in.uv.pipe,
- in_name,
- pty_process_connect_cb);
- }
-
- if (!proc->out.s.closed) {
- out_req = xmalloc(sizeof(uv_connect_t));
- uv_pipe_connect(out_req,
- &proc->out.s.uv.pipe,
- out_name,
- pty_process_connect_cb);
- }
-
- if (proc->cwd != NULL) {
- status = utf8_to_utf16(proc->cwd, -1, &cwd);
- if (status != 0) {
- emsg = "utf8_to_utf16(proc->cwd) failed";
- goto cleanup;
- }
- }
-
- status = build_cmd_line(proc->argv, &cmd_line,
- os_shell_is_cmdexe(proc->argv[0]));
- if (status != 0) {
- emsg = "build_cmd_line failed";
- goto cleanup;
- }
-
- if (proc->env != NULL) {
- status = build_env_block(proc->env, &env);
- }
-
- if (status != 0) {
- emsg = "build_env_block failed";
- goto cleanup;
- }
-
- if (!os_conpty_spawn(conpty_object,
- &process_handle,
- NULL,
- cmd_line,
- cwd,
- env)) {
- emsg = "os_conpty_spawn failed";
- status = (int)GetLastError();
- goto cleanup;
- }
- proc->pid = (int)GetProcessId(process_handle);
-
- uv_timer_init(&proc->loop->uv, &ptyproc->wait_eof_timer);
- ptyproc->wait_eof_timer.data = (void *)ptyproc;
- if (!RegisterWaitForSingleObject(&ptyproc->finish_wait,
- process_handle,
- pty_process_finish1,
- ptyproc,
- INFINITE,
- WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE)) {
- abort();
- }
-
- // Wait until pty_process_connect_cb is called.
- while ((in_req != NULL && in_req->handle != NULL)
- || (out_req != NULL && out_req->handle != NULL)) {
- uv_run(&proc->loop->uv, UV_RUN_ONCE);
- }
-
- ptyproc->conpty = conpty_object;
- ptyproc->process_handle = process_handle;
- conpty_object = NULL;
- process_handle = NULL;
-
-cleanup:
- if (status) {
- // In the case of an error of MultiByteToWideChar or CreateProcessW.
- ELOG("pty_process_spawn(%s): %s: error code: %d",
- proc->argv[0], emsg, status);
- status = os_translate_sys_error(status);
- }
- os_conpty_free(conpty_object);
- xfree(in_name);
- xfree(out_name);
- if (process_handle != NULL) {
- CloseHandle(process_handle);
- }
- xfree(in_req);
- xfree(out_req);
- xfree(cmd_line);
- xfree(env);
- xfree(cwd);
- return status;
-}
-
-const char *pty_process_tty_name(PtyProcess *ptyproc)
-{
- return "?";
-}
-
-void pty_process_resize(PtyProcess *ptyproc, uint16_t width, uint16_t height)
- FUNC_ATTR_NONNULL_ALL
-{
- os_conpty_set_size(ptyproc->conpty, width, height);
-}
-
-void pty_process_close(PtyProcess *ptyproc)
- FUNC_ATTR_NONNULL_ALL
-{
- Process *proc = (Process *)ptyproc;
-
- pty_process_close_master(ptyproc);
-
- if (ptyproc->finish_wait != NULL) {
- UnregisterWaitEx(ptyproc->finish_wait, NULL);
- ptyproc->finish_wait = NULL;
- uv_close((uv_handle_t *)&ptyproc->wait_eof_timer, NULL);
- }
- if (ptyproc->process_handle != NULL) {
- CloseHandle(ptyproc->process_handle);
- ptyproc->process_handle = NULL;
- }
-
- if (proc->internal_close_cb) {
- proc->internal_close_cb(proc);
- }
-}
-
-void pty_process_close_master(PtyProcess *ptyproc)
- FUNC_ATTR_NONNULL_ALL
-{
-}
-
-void pty_process_teardown(Loop *loop)
- FUNC_ATTR_NONNULL_ALL
-{
-}
-
-static void pty_process_connect_cb(uv_connect_t *req, int status)
- FUNC_ATTR_NONNULL_ALL
-{
- assert(status == 0);
- req->handle = NULL;
-}
-
-static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer)
- FUNC_ATTR_NONNULL_ALL
-{
- PtyProcess *ptyproc = wait_eof_timer->data;
- Process *proc = (Process *)ptyproc;
-
- assert(ptyproc->finish_wait != NULL);
- if (proc->out.s.closed || proc->out.did_eof || !uv_is_readable(proc->out.s.uvstream)) {
- uv_timer_stop(&ptyproc->wait_eof_timer);
- pty_process_finish2(ptyproc);
- }
-}
-
-static void pty_process_finish2(PtyProcess *ptyproc)
- FUNC_ATTR_NONNULL_ALL
-{
- Process *proc = (Process *)ptyproc;
-
- DWORD exit_code = 0;
- GetExitCodeProcess(ptyproc->process_handle, &exit_code);
- proc->status = proc->exit_signal ? 128 + proc->exit_signal : (int)exit_code;
-
- proc->internal_exit_cb(proc);
-}
-
-/// Build the command line to pass to CreateProcessW.
-///
-/// @param[in] argv Array with string arguments.
-/// @param[out] cmd_line Location where saved built cmd line.
-///
-/// @returns zero on success, or error code of MultiByteToWideChar function.
-///
-static int build_cmd_line(char **argv, wchar_t **cmd_line, bool is_cmdexe)
- FUNC_ATTR_NONNULL_ALL
-{
- size_t utf8_cmd_line_len = 0;
- size_t argc = 0;
- QUEUE args_q;
-
- QUEUE_INIT(&args_q);
- while (*argv) {
- size_t buf_len = is_cmdexe ? (strlen(*argv) + 1) : (strlen(*argv) * 2 + 3);
- ArgNode *arg_node = xmalloc(sizeof(*arg_node));
- arg_node->arg = xmalloc(buf_len);
- if (is_cmdexe) {
- xstrlcpy(arg_node->arg, *argv, buf_len);
- } else {
- quote_cmd_arg(arg_node->arg, buf_len, *argv);
- }
- utf8_cmd_line_len += strlen(arg_node->arg);
- QUEUE_INIT(&arg_node->node);
- QUEUE_INSERT_TAIL(&args_q, &arg_node->node);
- argc++;
- argv++;
- }
-
- utf8_cmd_line_len += argc;
- char *utf8_cmd_line = xmalloc(utf8_cmd_line_len);
- *utf8_cmd_line = NUL;
- QUEUE *q;
- QUEUE_FOREACH(q, &args_q, {
- ArgNode *arg_node = QUEUE_DATA(q, ArgNode, node);
- xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len);
- QUEUE_REMOVE(q);
- xfree(arg_node->arg);
- xfree(arg_node);
- if (!QUEUE_EMPTY(&args_q)) {
- xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len);
- }
- })
-
- int result = utf8_to_utf16(utf8_cmd_line, -1, cmd_line);
- xfree(utf8_cmd_line);
- return result;
-}
-
-/// Emulate quote_cmd_arg of libuv and quotes command line argument.
-/// Most of the code came from libuv.
-///
-/// @param[out] dest Location where saved quotes argument.
-/// @param dest_remaining Destination buffer size.
-/// @param[in] src Pointer to argument.
-///
-static void quote_cmd_arg(char *dest, size_t dest_remaining, const char *src)
- FUNC_ATTR_NONNULL_ALL
-{
- size_t src_len = strlen(src);
- bool quote_hit = true;
- char *start = dest;
-
- if (src_len == 0) {
- // Need double quotation for empty argument.
- snprintf(dest, dest_remaining, "\"\"");
- return;
- }
-
- if (NULL == strpbrk(src, " \t\"")) {
- // No quotation needed.
- xstrlcpy(dest, src, dest_remaining);
- return;
- }
-
- if (NULL == strpbrk(src, "\"\\")) {
- // No embedded double quotes or backlashes, so I can just wrap quote marks.
- // around the whole thing.
- snprintf(dest, dest_remaining, "\"%s\"", src);
- return;
- }
-
- // Expected input/output:
- // input : 'hello"world'
- // output: '"hello\"world"'
- // input : 'hello""world'
- // output: '"hello\"\"world"'
- // input : 'hello\world'
- // output: 'hello\world'
- // input : 'hello\\world'
- // output: 'hello\\world'
- // input : 'hello\"world'
- // output: '"hello\\\"world"'
- // input : 'hello\\"world'
- // output: '"hello\\\\\"world"'
- // input : 'hello world\'
- // output: '"hello world\\"'
-
- assert(dest_remaining--);
- *(dest++) = NUL;
- assert(dest_remaining--);
- *(dest++) = '"';
- for (size_t i = src_len; i > 0; i--) {
- assert(dest_remaining--);
- *(dest++) = src[i - 1];
- if (quote_hit && src[i - 1] == '\\') {
- assert(dest_remaining--);
- *(dest++) = '\\';
- } else if (src[i - 1] == '"') {
- quote_hit = true;
- assert(dest_remaining--);
- *(dest++) = '\\';
- } else {
- quote_hit = false;
- }
- }
- assert(dest_remaining);
- *dest = '"';
-
- while (start < dest) {
- char tmp = *start;
- *start = *dest;
- *dest = tmp;
- start++;
- dest--;
- }
-}
-
-typedef struct EnvNode {
- wchar_t *str;
- size_t len;
- QUEUE node;
-} EnvNode;
-
-/// Build the environment block to pass to CreateProcessW.
-///
-/// @param[in] denv Dict of environment name/value pairs
-/// @param[out] env Allocated environment block
-///
-/// @returns zero on success or error code of MultiByteToWideChar function.
-static int build_env_block(dict_T *denv, wchar_t **env_block)
-{
- const size_t denv_size = (size_t)tv_dict_len(denv);
- size_t env_block_len = 0;
- int rc = 0;
- char **env = tv_dict_to_env(denv);
-
- QUEUE *q;
- QUEUE env_q;
- QUEUE_INIT(&env_q);
- // Convert env vars to wchar_t and calculate how big the final env block
- // needs to be
- for (size_t i = 0; i < denv_size; i++) {
- EnvNode *env_node = xmalloc(sizeof(*env_node));
- rc = utf8_to_utf16(env[i], -1, &env_node->str);
- if (rc != 0) {
- goto cleanup;
- }
- env_node->len = wcslen(env_node->str) + 1;
- env_block_len += env_node->len;
- QUEUE_INSERT_TAIL(&env_q, &env_node->node);
- }
-
- // Additional NUL after the final entry
- env_block_len++;
-
- *env_block = xmalloc(sizeof(**env_block) * env_block_len);
- wchar_t *pos = *env_block;
-
- QUEUE_FOREACH(q, &env_q, {
- EnvNode *env_node = QUEUE_DATA(q, EnvNode, node);
- memcpy(pos, env_node->str, env_node->len * sizeof(*pos));
- pos += env_node->len;
- })
-
- *pos = L'\0';
-
-cleanup:
- q = QUEUE_HEAD(&env_q);
- while (q != &env_q) {
- QUEUE *next = q->next;
- EnvNode *env_node = QUEUE_DATA(q, EnvNode, node);
- XFREE_CLEAR(env_node->str);
- QUEUE_REMOVE(q);
- xfree(env_node);
- q = next;
- }
-
- return rc;
-}
-
-PtyProcess pty_process_init(Loop *loop, void *data)
-{
- PtyProcess rv;
- rv.process = process_init(loop, kProcessTypePty, data);
- rv.width = 80;
- rv.height = 24;
- rv.conpty = NULL;
- rv.finish_wait = NULL;
- rv.process_handle = NULL;
- return rv;
-}
diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h
@@ -1,27 +0,0 @@
-#pragma once
-// IWYU pragma: private, include "nvim/os/pty_process.h"
-
-#include <uv.h>
-
-#include "nvim/event/process.h"
-#include "nvim/lib/queue_defs.h"
-#include "nvim/os/pty_conpty_win.h"
-
-typedef struct pty_process {
- Process process;
- uint16_t width, height;
- conpty_t *conpty;
- HANDLE finish_wait;
- HANDLE process_handle;
- uv_timer_t wait_eof_timer;
-} PtyProcess;
-
-// Structure used by build_cmd_line()
-typedef struct arg_node {
- char *arg; // pointer to argument.
- QUEUE node; // QUEUE structure.
-} ArgNode;
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "os/pty_process_win.h.generated.h"
-#endif
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
@@ -14,10 +14,10 @@
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/event/defs.h"
-#include "nvim/event/libuv_process.h"
+#include "nvim/event/libuv_proc.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
-#include "nvim/event/process.h"
+#include "nvim/event/proc.h"
#include "nvim/event/rstream.h"
#include "nvim/event/stream.h"
#include "nvim/event/wstream.h"
@@ -872,12 +872,12 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
char prog[MAXPATHL];
xstrlcpy(prog, argv[0], MAXPATHL);
- LibuvProcess uvproc = libuv_process_init(&main_loop, &buf);
- Process *proc = &uvproc.process;
+ LibuvProc uvproc = libuv_proc_init(&main_loop, &buf);
+ Proc *proc = &uvproc.proc;
MultiQueue *events = multiqueue_new_child(main_loop.events);
proc->events = events;
proc->argv = argv;
- int status = process_spawn(proc, has_input, true, true);
+ int status = proc_spawn(proc, has_input, true, true);
if (status) {
loop_poll_events(&main_loop, 0);
// Failed, probably 'shell' is not executable.
@@ -910,7 +910,7 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
if (!wstream_write(&proc->in, input_buffer)) {
// couldn't write, stop the process and tell the user about it
- process_stop(proc);
+ proc_stop(proc);
return -1;
}
// close the input stream after everything is written
@@ -927,7 +927,7 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
msg_no_more = true;
lines_left = -1;
}
- int exitcode = process_wait(proc, -1, NULL);
+ int exitcode = proc_wait(proc, -1, NULL);
if (!got_int && out_data_decide_throttle(0)) {
// Last chunk of output was skipped; display it now.
out_data_ring(NULL, SIZE_MAX);
diff --git a/src/nvim/profile.c b/src/nvim/profile.c
@@ -950,8 +950,8 @@ void time_msg(const char *mesg, const proftime_T *start)
/// Initializes the `time_fd` stream for the --startuptime report.
///
/// @param fname startuptime report file path
-/// @param process_name name of the current Nvim process to write in the report.
-void time_init(const char *fname, const char *process_name)
+/// @param proc_name name of the current Nvim process to write in the report.
+void time_init(const char *fname, const char *proc_name)
{
const size_t bufsize = 8192; // Big enough for the entire --startuptime report.
time_fd = fopen(fname, "a");
@@ -972,7 +972,7 @@ void time_init(const char *fname, const char *process_name)
semsg("time_init: setvbuf failed: %d %s", r, uv_err_name(r));
return;
}
- fprintf(time_fd, "--- Startup times for process: %s ---\n", process_name);
+ fprintf(time_fd, "--- Startup times for process: %s ---\n", proc_name);
}
/// Flushes the startuptimes to disk for the current process
diff --git a/test/README.md b/test/README.md
@@ -103,7 +103,7 @@ Debugging tests
DBG 2022-06-15T18:37:45.227 T57.58016.0/c UI: stop
INF 2022-06-15T18:37:45.227 T57.58016.0/c os_exit:595: Nvim exit: 0
DBG 2022-06-15T18:37:45.229 T57.58016.0 read_cb:118: closing Stream (0x7fd5d700ea18): EOF (end of file)
- INF 2022-06-15T18:37:45.229 T57.58016.0 on_process_exit:400: exited: pid=58017 status=0 stoptime=0
+ INF 2022-06-15T18:37:45.229 T57.58016.0 on_proc_exit:400: exited: pid=58017 status=0 stoptime=0
```
- You can set `$GDB` to [run functional tests under gdbserver](https://github.com/neovim/neovim/pull/1527):