aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/os/pty_proc_win.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/os/pty_proc_win.c')
-rw-r--r--src/nvim/os/pty_proc_win.c440
1 files changed, 440 insertions, 0 deletions
diff --git a/src/nvim/os/pty_proc_win.c b/src/nvim/os/pty_proc_win.c
new file mode 100644
index 0000000000..5bd6eead51
--- /dev/null
+++ 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;
+}