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 }