neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

pty_proc_win.c (11682B)


      1 #include <assert.h>
      2 #include <stdbool.h>
      3 #include <stdlib.h>
      4 
      5 #include "nvim/ascii_defs.h"
      6 #include "nvim/eval/typval.h"
      7 #include "nvim/event/loop.h"
      8 #include "nvim/log.h"
      9 #include "nvim/mbyte.h"
     10 #include "nvim/memory.h"
     11 #include "nvim/os/os.h"
     12 #include "nvim/os/pty_conpty_win.h"
     13 #include "nvim/os/pty_proc_win.h"
     14 
     15 #include "os/pty_proc_win.c.generated.h"
     16 
     17 static void CALLBACK pty_proc_terminate_cb(void *context, BOOLEAN unused)
     18  FUNC_ATTR_NONNULL_ALL
     19 {
     20  PtyProc *ptyproc = (PtyProc *)context;
     21  Proc *proc = (Proc *)ptyproc;
     22 
     23  os_conpty_free(ptyproc->conpty);
     24  // NB: pty_proc_terminate_cb() is called on a separate thread,
     25  // but finishing up the process needs to be done on the main thread.
     26  loop_schedule_fast(proc->loop, event_create(pty_proc_finish_when_eof, ptyproc));
     27 }
     28 
     29 static void pty_proc_finish_when_eof(void **argv)
     30  FUNC_ATTR_NONNULL_ALL
     31 {
     32  PtyProc *ptyproc = (PtyProc *)argv[0];
     33 
     34  if (ptyproc->finish_wait != NULL) {
     35    if (pty_proc_can_finish(ptyproc)) {
     36      pty_proc_finish(ptyproc);
     37    } else {
     38      uv_timer_start(&ptyproc->wait_eof_timer, wait_eof_timer_cb, 200, 200);
     39    }
     40  }
     41 }
     42 
     43 static bool pty_proc_can_finish(PtyProc *ptyproc)
     44 {
     45  Proc *proc = (Proc *)ptyproc;
     46 
     47  assert(ptyproc->finish_wait != NULL);
     48  return proc->out.s.closed || proc->out.did_eof || !uv_is_readable(proc->out.s.uvstream);
     49 }
     50 
     51 /// @returns zero on success, or negative error code.
     52 int pty_proc_spawn(PtyProc *ptyproc)
     53  FUNC_ATTR_NONNULL_ALL
     54 {
     55  Proc *proc = (Proc *)ptyproc;
     56  int status = 0;
     57  conpty_t *conpty_object = NULL;
     58  char *in_name = NULL;
     59  char *out_name = NULL;
     60  HANDLE proc_handle = NULL;
     61  uv_connect_t *in_req = NULL;
     62  uv_connect_t *out_req = NULL;
     63  wchar_t *cmd_line = NULL;
     64  wchar_t *cwd = NULL;
     65  wchar_t *env = NULL;
     66  const char *emsg = NULL;
     67 
     68  assert(proc->err.s.closed);
     69 
     70  if (!os_has_conpty_working() || (conpty_object = os_conpty_init(&in_name,
     71                                                                  &out_name, ptyproc->width,
     72                                                                  ptyproc->height)) == NULL) {
     73    status = UV_ENOSYS;
     74    goto cleanup;
     75  }
     76 
     77  if (!proc->in.closed) {
     78    in_req = xmalloc(sizeof(uv_connect_t));
     79    uv_pipe_connect(in_req,
     80                    &proc->in.uv.pipe,
     81                    in_name,
     82                    pty_proc_connect_cb);
     83  }
     84 
     85  if (!proc->out.s.closed) {
     86    out_req = xmalloc(sizeof(uv_connect_t));
     87    uv_pipe_connect(out_req,
     88                    &proc->out.s.uv.pipe,
     89                    out_name,
     90                    pty_proc_connect_cb);
     91  }
     92 
     93  if (proc->cwd != NULL) {
     94    status = utf8_to_utf16(proc->cwd, -1, &cwd);
     95    if (status != 0) {
     96      emsg = "utf8_to_utf16(proc->cwd) failed";
     97      goto cleanup;
     98    }
     99  }
    100 
    101  status = build_cmd_line(proc->argv, &cmd_line,
    102                          os_shell_is_cmdexe(proc->argv[0]));
    103  if (status != 0) {
    104    emsg = "build_cmd_line failed";
    105    goto cleanup;
    106  }
    107 
    108  if (proc->env != NULL) {
    109    status = build_env_block(proc->env, &env);
    110  }
    111 
    112  if (status != 0) {
    113    emsg = "build_env_block failed";
    114    goto cleanup;
    115  }
    116 
    117  if (!os_conpty_spawn(conpty_object,
    118                       &proc_handle,
    119                       NULL,
    120                       cmd_line,
    121                       cwd,
    122                       env)) {
    123    emsg = "os_conpty_spawn failed";
    124    status = (int)GetLastError();
    125    goto cleanup;
    126  }
    127  proc->pid = (int)GetProcessId(proc_handle);
    128 
    129  uv_timer_init(&proc->loop->uv, &ptyproc->wait_eof_timer);
    130  ptyproc->wait_eof_timer.data = (void *)ptyproc;
    131  if (!RegisterWaitForSingleObject(&ptyproc->finish_wait,
    132                                   proc_handle,
    133                                   pty_proc_terminate_cb,
    134                                   ptyproc,
    135                                   INFINITE,
    136                                   WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE)) {
    137    abort();
    138  }
    139 
    140  // Wait until pty_proc_connect_cb is called.
    141  while ((in_req != NULL && in_req->handle != NULL)
    142         || (out_req != NULL && out_req->handle != NULL)) {
    143    uv_run(&proc->loop->uv, UV_RUN_ONCE);
    144  }
    145 
    146  ptyproc->conpty = conpty_object;
    147  ptyproc->proc_handle = proc_handle;
    148  conpty_object = NULL;
    149  proc_handle = NULL;
    150 
    151 cleanup:
    152  if (status) {
    153    // In the case of an error of MultiByteToWideChar or CreateProcessW.
    154    ELOG("pty_proc_spawn(%s): %s: error code: %d",
    155         proc->argv[0], emsg, status);
    156    status = os_translate_sys_error(status);
    157  }
    158  os_conpty_free(conpty_object);
    159  xfree(in_name);
    160  xfree(out_name);
    161  if (proc_handle != NULL) {
    162    CloseHandle(proc_handle);
    163  }
    164  xfree(in_req);
    165  xfree(out_req);
    166  xfree(cmd_line);
    167  xfree(env);
    168  xfree(cwd);
    169  return status;
    170 }
    171 
    172 const char *pty_proc_tty_name(PtyProc *ptyproc)
    173 {
    174  return "?";
    175 }
    176 
    177 void pty_proc_resize(PtyProc *ptyproc, uint16_t width, uint16_t height)
    178  FUNC_ATTR_NONNULL_ALL
    179 {
    180  os_conpty_set_size(ptyproc->conpty, width, height);
    181 }
    182 
    183 void pty_proc_resume(PtyProc *ptyproc)
    184  FUNC_ATTR_NONNULL_ALL
    185 {
    186 }
    187 
    188 void pty_proc_flush_master(PtyProc *ptyproc)
    189  FUNC_ATTR_NONNULL_ALL
    190 {
    191 }
    192 
    193 void pty_proc_close(PtyProc *ptyproc)
    194  FUNC_ATTR_NONNULL_ALL
    195 {
    196  Proc *proc = (Proc *)ptyproc;
    197 
    198  pty_proc_close_master(ptyproc);
    199 
    200  if (ptyproc->finish_wait != NULL) {
    201    UnregisterWaitEx(ptyproc->finish_wait, NULL);
    202    ptyproc->finish_wait = NULL;
    203    uv_close((uv_handle_t *)&ptyproc->wait_eof_timer, NULL);
    204  }
    205  if (ptyproc->proc_handle != NULL) {
    206    CloseHandle(ptyproc->proc_handle);
    207    ptyproc->proc_handle = NULL;
    208  }
    209 
    210  if (proc->internal_close_cb) {
    211    proc->internal_close_cb(proc);
    212  }
    213 }
    214 
    215 void pty_proc_close_master(PtyProc *ptyproc)
    216  FUNC_ATTR_NONNULL_ALL
    217 {
    218 }
    219 
    220 void pty_proc_teardown(Loop *loop)
    221  FUNC_ATTR_NONNULL_ALL
    222 {
    223 }
    224 
    225 static void pty_proc_connect_cb(uv_connect_t *req, int status)
    226  FUNC_ATTR_NONNULL_ALL
    227 {
    228  assert(status == 0);
    229  req->handle = NULL;
    230 }
    231 
    232 static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer)
    233  FUNC_ATTR_NONNULL_ALL
    234 {
    235  PtyProc *ptyproc = wait_eof_timer->data;
    236  if (pty_proc_can_finish(ptyproc)) {
    237    uv_timer_stop(&ptyproc->wait_eof_timer);
    238    pty_proc_finish(ptyproc);
    239  }
    240 }
    241 
    242 static void pty_proc_finish(PtyProc *ptyproc)
    243  FUNC_ATTR_NONNULL_ALL
    244 {
    245  Proc *proc = (Proc *)ptyproc;
    246 
    247  DWORD exit_code = 0;
    248  GetExitCodeProcess(ptyproc->proc_handle, &exit_code);
    249  proc->status = proc->exit_signal ? 128 + proc->exit_signal : (int)exit_code;
    250 
    251  proc->internal_exit_cb(proc);
    252 }
    253 
    254 /// Build the command line to pass to CreateProcessW.
    255 ///
    256 /// @param[in]  argv  Array with string arguments.
    257 /// @param[out]  cmd_line  Location where saved built cmd line.
    258 ///
    259 /// @returns zero on success, or error code of MultiByteToWideChar function.
    260 ///
    261 static int build_cmd_line(char **argv, wchar_t **cmd_line, bool is_cmdexe)
    262  FUNC_ATTR_NONNULL_ALL
    263 {
    264  size_t utf8_cmd_line_len = 0;
    265  size_t argc = 0;
    266  QUEUE args_q;
    267 
    268  QUEUE_INIT(&args_q);
    269  while (*argv) {
    270    size_t buf_len = is_cmdexe ? (strlen(*argv) + 1) : (strlen(*argv) * 2 + 3);
    271    ArgNode *arg_node = xmalloc(sizeof(*arg_node));
    272    arg_node->arg = xmalloc(buf_len);
    273    if (is_cmdexe) {
    274      xstrlcpy(arg_node->arg, *argv, buf_len);
    275    } else {
    276      quote_cmd_arg(arg_node->arg, buf_len, *argv);
    277    }
    278    utf8_cmd_line_len += strlen(arg_node->arg);
    279    QUEUE_INIT(&arg_node->node);
    280    QUEUE_INSERT_TAIL(&args_q, &arg_node->node);
    281    argc++;
    282    argv++;
    283  }
    284 
    285  utf8_cmd_line_len += argc;
    286  char *utf8_cmd_line = xmalloc(utf8_cmd_line_len);
    287  *utf8_cmd_line = NUL;
    288  QUEUE *q;
    289  QUEUE_FOREACH(q, &args_q, {
    290    ArgNode *arg_node = QUEUE_DATA(q, ArgNode, node);
    291    xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len);
    292    QUEUE_REMOVE(q);
    293    xfree(arg_node->arg);
    294    xfree(arg_node);
    295    if (!QUEUE_EMPTY(&args_q)) {
    296      xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len);
    297    }
    298  })
    299 
    300  int result = utf8_to_utf16(utf8_cmd_line, -1, cmd_line);
    301  xfree(utf8_cmd_line);
    302  return result;
    303 }
    304 
    305 /// Emulate quote_cmd_arg of libuv and quotes command line argument.
    306 /// Most of the code came from libuv.
    307 ///
    308 /// @param[out]  dest  Location where saved quotes argument.
    309 /// @param  dest_remaining  Destination buffer size.
    310 /// @param[in]  src Pointer to argument.
    311 ///
    312 static void quote_cmd_arg(char *dest, size_t dest_remaining, const char *src)
    313  FUNC_ATTR_NONNULL_ALL
    314 {
    315  size_t src_len = strlen(src);
    316  bool quote_hit = true;
    317  char *start = dest;
    318 
    319  if (src_len == 0) {
    320    // Need double quotation for empty argument.
    321    snprintf(dest, dest_remaining, "\"\"");
    322    return;
    323  }
    324 
    325  if (NULL == strpbrk(src, " \t\"")) {
    326    // No quotation needed.
    327    xstrlcpy(dest, src, dest_remaining);
    328    return;
    329  }
    330 
    331  if (NULL == strpbrk(src, "\"\\")) {
    332    // No embedded double quotes or backlashes, so I can just wrap quote marks.
    333    // around the whole thing.
    334    snprintf(dest, dest_remaining, "\"%s\"", src);
    335    return;
    336  }
    337 
    338  // Expected input/output:
    339  //   input : 'hello"world'
    340  //   output: '"hello\"world"'
    341  //   input : 'hello""world'
    342  //   output: '"hello\"\"world"'
    343  //   input : 'hello\world'
    344  //   output: 'hello\world'
    345  //   input : 'hello\\world'
    346  //   output: 'hello\\world'
    347  //   input : 'hello\"world'
    348  //   output: '"hello\\\"world"'
    349  //   input : 'hello\\"world'
    350  //   output: '"hello\\\\\"world"'
    351  //   input : 'hello world\'
    352  //   output: '"hello world\\"'
    353 
    354  assert(dest_remaining--);
    355  *(dest++) = NUL;
    356  assert(dest_remaining--);
    357  *(dest++) = '"';
    358  for (size_t i = src_len; i > 0; i--) {
    359    assert(dest_remaining--);
    360    *(dest++) = src[i - 1];
    361    if (quote_hit && src[i - 1] == '\\') {
    362      assert(dest_remaining--);
    363      *(dest++) = '\\';
    364    } else if (src[i - 1] == '"') {
    365      quote_hit = true;
    366      assert(dest_remaining--);
    367      *(dest++) = '\\';
    368    } else {
    369      quote_hit = false;
    370    }
    371  }
    372  assert(dest_remaining);
    373  *dest = '"';
    374 
    375  while (start < dest) {
    376    char tmp = *start;
    377    *start = *dest;
    378    *dest = tmp;
    379    start++;
    380    dest--;
    381  }
    382 }
    383 
    384 typedef struct EnvNode {
    385  wchar_t *str;
    386  size_t len;
    387  QUEUE node;
    388 } EnvNode;
    389 
    390 /// Build the environment block to pass to CreateProcessW.
    391 ///
    392 /// @param[in]  denv  Dict of environment name/value pairs
    393 /// @param[out]  env  Allocated environment block
    394 ///
    395 /// @returns zero on success or error code of MultiByteToWideChar function.
    396 static int build_env_block(dict_T *denv, wchar_t **env_block)
    397 {
    398  const size_t denv_size = (size_t)tv_dict_len(denv);
    399  size_t env_block_len = 0;
    400  int rc = 0;
    401  char **env = tv_dict_to_env(denv);
    402 
    403  QUEUE *q;
    404  QUEUE env_q;
    405  QUEUE_INIT(&env_q);
    406  // Convert env vars to wchar_t and calculate how big the final env block
    407  // needs to be
    408  for (size_t i = 0; i < denv_size; i++) {
    409    EnvNode *env_node = xmalloc(sizeof(*env_node));
    410    rc = utf8_to_utf16(env[i], -1, &env_node->str);
    411    if (rc != 0) {
    412      goto cleanup;
    413    }
    414    env_node->len = wcslen(env_node->str) + 1;
    415    env_block_len += env_node->len;
    416    QUEUE_INSERT_TAIL(&env_q, &env_node->node);
    417  }
    418 
    419  // Additional NUL after the final entry
    420  env_block_len++;
    421 
    422  *env_block = xmalloc(sizeof(**env_block) * env_block_len);
    423  wchar_t *pos = *env_block;
    424 
    425  QUEUE_FOREACH(q, &env_q, {
    426    EnvNode *env_node = QUEUE_DATA(q, EnvNode, node);
    427    memcpy(pos, env_node->str, env_node->len * sizeof(*pos));
    428    pos += env_node->len;
    429  })
    430 
    431  *pos = L'\0';
    432 
    433 cleanup:
    434  q = QUEUE_HEAD(&env_q);
    435  while (q != &env_q) {
    436    QUEUE *next = q->next;
    437    EnvNode *env_node = QUEUE_DATA(q, EnvNode, node);
    438    XFREE_CLEAR(env_node->str);
    439    QUEUE_REMOVE(q);
    440    xfree(env_node);
    441    q = next;
    442  }
    443 
    444  return rc;
    445 }
    446 
    447 PtyProc pty_proc_init(Loop *loop, void *data)
    448 {
    449  PtyProc rv;
    450  rv.proc = proc_init(loop, kProcTypePty, data);
    451  rv.width = 80;
    452  rv.height = 24;
    453  rv.conpty = NULL;
    454  rv.finish_wait = NULL;
    455  rv.proc_handle = NULL;
    456  return rv;
    457 }