Mercurial
comparison third_party/libuv/src/win/process.c @ 160:948de3f54cea
[ThirdParty] Added libuv
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Wed, 14 Jan 2026 19:39:52 -0800 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 159:05cf9467a1c3 | 160:948de3f54cea |
|---|---|
| 1 /* Copyright Joyent, Inc. and other Node contributors. All rights reserved. | |
| 2 * | |
| 3 * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 4 * of this software and associated documentation files (the "Software"), to | |
| 5 * deal in the Software without restriction, including without limitation the | |
| 6 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | |
| 7 * sell copies of the Software, and to permit persons to whom the Software is | |
| 8 * furnished to do so, subject to the following conditions: | |
| 9 * | |
| 10 * The above copyright notice and this permission notice shall be included in | |
| 11 * all copies or substantial portions of the Software. | |
| 12 * | |
| 13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| 18 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
| 19 * IN THE SOFTWARE. | |
| 20 */ | |
| 21 | |
| 22 #include <assert.h> | |
| 23 #include <io.h> | |
| 24 #include <stdio.h> | |
| 25 #include <stdlib.h> | |
| 26 #include <signal.h> | |
| 27 #include <limits.h> | |
| 28 #include <wchar.h> | |
| 29 | |
| 30 #include "uv.h" | |
| 31 #include "internal.h" | |
| 32 #include "handle-inl.h" | |
| 33 #include "req-inl.h" | |
| 34 #include <dbghelp.h> | |
| 35 #include <shlobj.h> | |
| 36 #include <psapi.h> /* GetModuleBaseNameW */ | |
| 37 | |
| 38 | |
| 39 #define SIGKILL 9 | |
| 40 | |
| 41 | |
| 42 typedef struct env_var { | |
| 43 const WCHAR* const wide; | |
| 44 const WCHAR* const wide_eq; | |
| 45 const size_t len; /* including null or '=' */ | |
| 46 } env_var_t; | |
| 47 | |
| 48 #define E_V(str) { L##str, L##str L"=", sizeof(str) } | |
| 49 | |
| 50 static const env_var_t required_vars[] = { /* keep me sorted */ | |
| 51 E_V("HOMEDRIVE"), | |
| 52 E_V("HOMEPATH"), | |
| 53 E_V("LOGONSERVER"), | |
| 54 E_V("PATH"), | |
| 55 E_V("SYSTEMDRIVE"), | |
| 56 E_V("SYSTEMROOT"), | |
| 57 E_V("TEMP"), | |
| 58 E_V("USERDOMAIN"), | |
| 59 E_V("USERNAME"), | |
| 60 E_V("USERPROFILE"), | |
| 61 E_V("WINDIR"), | |
| 62 }; | |
| 63 | |
| 64 | |
| 65 static HANDLE uv_global_job_handle_; | |
| 66 static uv_once_t uv_global_job_handle_init_guard_ = UV_ONCE_INIT; | |
| 67 | |
| 68 | |
| 69 static void uv__init_global_job_handle(void) { | |
| 70 /* Create a job object and set it up to kill all contained processes when | |
| 71 * it's closed. Since this handle is made non-inheritable and we're not | |
| 72 * giving it to anyone, we're the only process holding a reference to it. | |
| 73 * That means that if this process exits it is closed and all the processes | |
| 74 * it contains are killed. All processes created with uv_spawn that are not | |
| 75 * spawned with the UV_PROCESS_DETACHED flag are assigned to this job. | |
| 76 * | |
| 77 * We're setting the JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK flag so only the | |
| 78 * processes that we explicitly add are affected, and *their* subprocesses | |
| 79 * are not. This ensures that our child processes are not limited in their | |
| 80 * ability to use job control on Windows versions that don't deal with | |
| 81 * nested jobs (prior to Windows 8 / Server 2012). It also lets our child | |
| 82 * processes created detached processes without explicitly breaking away | |
| 83 * from job control (which uv_spawn doesn't, either). | |
| 84 */ | |
| 85 SECURITY_ATTRIBUTES attr; | |
| 86 JOBOBJECT_EXTENDED_LIMIT_INFORMATION info; | |
| 87 | |
| 88 memset(&attr, 0, sizeof attr); | |
| 89 attr.bInheritHandle = FALSE; | |
| 90 | |
| 91 memset(&info, 0, sizeof info); | |
| 92 info.BasicLimitInformation.LimitFlags = | |
| 93 JOB_OBJECT_LIMIT_BREAKAWAY_OK | | |
| 94 JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK | | |
| 95 JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION | | |
| 96 JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; | |
| 97 | |
| 98 uv_global_job_handle_ = CreateJobObjectW(&attr, NULL); | |
| 99 if (uv_global_job_handle_ == NULL) | |
| 100 uv_fatal_error(GetLastError(), "CreateJobObjectW"); | |
| 101 | |
| 102 if (!SetInformationJobObject(uv_global_job_handle_, | |
| 103 JobObjectExtendedLimitInformation, | |
| 104 &info, | |
| 105 sizeof info)) | |
| 106 uv_fatal_error(GetLastError(), "SetInformationJobObject"); | |
| 107 | |
| 108 | |
| 109 if (!AssignProcessToJobObject(uv_global_job_handle_, GetCurrentProcess())) { | |
| 110 /* Make sure this handle is functional. The Windows kernel has a bug that | |
| 111 * if the first use of AssignProcessToJobObject is for a Windows Store | |
| 112 * program, subsequent attempts to use the handle with fail with | |
| 113 * INVALID_PARAMETER (87). This is possibly because all uses of the handle | |
| 114 * must be for the same Terminal Services session. We can ensure it is tied | |
| 115 * to our current session now by adding ourself to it. We could remove | |
| 116 * ourself afterwards, but there doesn't seem to be a reason to. | |
| 117 */ | |
| 118 DWORD err = GetLastError(); | |
| 119 if (err != ERROR_ACCESS_DENIED) | |
| 120 uv_fatal_error(err, "AssignProcessToJobObject"); | |
| 121 } | |
| 122 } | |
| 123 | |
| 124 | |
| 125 static int uv__utf8_to_utf16_alloc(const char* s, WCHAR** ws_ptr) { | |
| 126 return uv__convert_utf8_to_utf16(s, ws_ptr); | |
| 127 } | |
| 128 | |
| 129 | |
| 130 static void uv__process_init(uv_loop_t* loop, uv_process_t* handle) { | |
| 131 uv__handle_init(loop, (uv_handle_t*) handle, UV_PROCESS); | |
| 132 handle->exit_cb = NULL; | |
| 133 handle->pid = 0; | |
| 134 handle->exit_signal = 0; | |
| 135 handle->wait_handle = INVALID_HANDLE_VALUE; | |
| 136 handle->process_handle = INVALID_HANDLE_VALUE; | |
| 137 handle->exit_cb_pending = 0; | |
| 138 | |
| 139 UV_REQ_INIT(&handle->exit_req, UV_PROCESS_EXIT); | |
| 140 handle->exit_req.data = handle; | |
| 141 } | |
| 142 | |
| 143 | |
| 144 /* | |
| 145 * Path search functions | |
| 146 */ | |
| 147 | |
| 148 /* | |
| 149 * Helper function for search_path | |
| 150 */ | |
| 151 static WCHAR* search_path_join_test(const WCHAR* dir, | |
| 152 size_t dir_len, | |
| 153 const WCHAR* name, | |
| 154 size_t name_len, | |
| 155 const WCHAR* ext, | |
| 156 size_t ext_len, | |
| 157 const WCHAR* cwd, | |
| 158 size_t cwd_len) { | |
| 159 WCHAR *result, *result_pos; | |
| 160 DWORD attrs; | |
| 161 if (dir_len > 2 && | |
| 162 ((dir[0] == L'\\' || dir[0] == L'/') && | |
| 163 (dir[1] == L'\\' || dir[1] == L'/'))) { | |
| 164 /* It's a UNC path so ignore cwd */ | |
| 165 cwd_len = 0; | |
| 166 } else if (dir_len >= 1 && (dir[0] == L'/' || dir[0] == L'\\')) { | |
| 167 /* It's a full path without drive letter, use cwd's drive letter only */ | |
| 168 cwd_len = 2; | |
| 169 } else if (dir_len >= 2 && dir[1] == L':' && | |
| 170 (dir_len < 3 || (dir[2] != L'/' && dir[2] != L'\\'))) { | |
| 171 /* It's a relative path with drive letter (ext.g. D:../some/file) | |
| 172 * Replace drive letter in dir by full cwd if it points to the same drive, | |
| 173 * otherwise use the dir only. | |
| 174 */ | |
| 175 if (cwd_len < 2 || _wcsnicmp(cwd, dir, 2) != 0) { | |
| 176 cwd_len = 0; | |
| 177 } else { | |
| 178 dir += 2; | |
| 179 dir_len -= 2; | |
| 180 } | |
| 181 } else if (dir_len > 2 && dir[1] == L':') { | |
| 182 /* It's an absolute path with drive letter | |
| 183 * Don't use the cwd at all | |
| 184 */ | |
| 185 cwd_len = 0; | |
| 186 } | |
| 187 | |
| 188 /* Allocate buffer for output */ | |
| 189 result = result_pos = (WCHAR*)uv__malloc(sizeof(WCHAR) * | |
| 190 (cwd_len + 1 + dir_len + 1 + name_len + 1 + ext_len + 1)); | |
| 191 | |
| 192 /* Copy cwd */ | |
| 193 wcsncpy(result_pos, cwd, cwd_len); | |
| 194 result_pos += cwd_len; | |
| 195 | |
| 196 /* Add a path separator if cwd didn't end with one */ | |
| 197 if (cwd_len && wcsrchr(L"\\/:", result_pos[-1]) == NULL) { | |
| 198 result_pos[0] = L'\\'; | |
| 199 result_pos++; | |
| 200 } | |
| 201 | |
| 202 /* Copy dir */ | |
| 203 wcsncpy(result_pos, dir, dir_len); | |
| 204 result_pos += dir_len; | |
| 205 | |
| 206 /* Add a separator if the dir didn't end with one */ | |
| 207 if (dir_len && wcsrchr(L"\\/:", result_pos[-1]) == NULL) { | |
| 208 result_pos[0] = L'\\'; | |
| 209 result_pos++; | |
| 210 } | |
| 211 | |
| 212 /* Copy filename */ | |
| 213 wcsncpy(result_pos, name, name_len); | |
| 214 result_pos += name_len; | |
| 215 | |
| 216 if (ext_len) { | |
| 217 /* Add a dot if the filename didn't end with one */ | |
| 218 if (name_len && result_pos[-1] != '.') { | |
| 219 result_pos[0] = L'.'; | |
| 220 result_pos++; | |
| 221 } | |
| 222 | |
| 223 /* Copy extension */ | |
| 224 wcsncpy(result_pos, ext, ext_len); | |
| 225 result_pos += ext_len; | |
| 226 } | |
| 227 | |
| 228 /* Null terminator */ | |
| 229 result_pos[0] = L'\0'; | |
| 230 | |
| 231 attrs = GetFileAttributesW(result); | |
| 232 | |
| 233 if (attrs != INVALID_FILE_ATTRIBUTES && | |
| 234 !(attrs & FILE_ATTRIBUTE_DIRECTORY)) { | |
| 235 return result; | |
| 236 } | |
| 237 | |
| 238 uv__free(result); | |
| 239 return NULL; | |
| 240 } | |
| 241 | |
| 242 | |
| 243 /* | |
| 244 * Helper function for search_path | |
| 245 */ | |
| 246 static WCHAR* path_search_walk_ext(const WCHAR *dir, | |
| 247 size_t dir_len, | |
| 248 const WCHAR *name, | |
| 249 size_t name_len, | |
| 250 WCHAR *cwd, | |
| 251 size_t cwd_len, | |
| 252 int name_has_ext) { | |
| 253 WCHAR* result; | |
| 254 | |
| 255 /* If the name itself has a nonempty extension, try this extension first */ | |
| 256 if (name_has_ext) { | |
| 257 result = search_path_join_test(dir, dir_len, | |
| 258 name, name_len, | |
| 259 L"", 0, | |
| 260 cwd, cwd_len); | |
| 261 if (result != NULL) { | |
| 262 return result; | |
| 263 } | |
| 264 } | |
| 265 | |
| 266 /* Try .com extension */ | |
| 267 result = search_path_join_test(dir, dir_len, | |
| 268 name, name_len, | |
| 269 L"com", 3, | |
| 270 cwd, cwd_len); | |
| 271 if (result != NULL) { | |
| 272 return result; | |
| 273 } | |
| 274 | |
| 275 /* Try .exe extension */ | |
| 276 result = search_path_join_test(dir, dir_len, | |
| 277 name, name_len, | |
| 278 L"exe", 3, | |
| 279 cwd, cwd_len); | |
| 280 if (result != NULL) { | |
| 281 return result; | |
| 282 } | |
| 283 | |
| 284 return NULL; | |
| 285 } | |
| 286 | |
| 287 | |
| 288 /* | |
| 289 * search_path searches the system path for an executable filename - | |
| 290 * the windows API doesn't provide this as a standalone function nor as an | |
| 291 * option to CreateProcess. | |
| 292 * | |
| 293 * It tries to return an absolute filename. | |
| 294 * | |
| 295 * Furthermore, it tries to follow the semantics that cmd.exe, with this | |
| 296 * exception that PATHEXT environment variable isn't used. Since CreateProcess | |
| 297 * can start only .com and .exe files, only those extensions are tried. This | |
| 298 * behavior equals that of msvcrt's spawn functions. | |
| 299 * | |
| 300 * - Do not search the path if the filename already contains a path (either | |
| 301 * relative or absolute). | |
| 302 * | |
| 303 * - If there's really only a filename, check the current directory for file, | |
| 304 * then search all path directories. | |
| 305 * | |
| 306 * - If filename specified has *any* extension, or already contains a path | |
| 307 * and the UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME flag is specified, | |
| 308 * search for the file with the exact specified filename first. | |
| 309 * | |
| 310 * - If the literal filename is not found in a directory, try *appending* | |
| 311 * (not replacing) .com first and then .exe. | |
| 312 * | |
| 313 * - The path variable may contain relative paths; relative paths are relative | |
| 314 * to the cwd. | |
| 315 * | |
| 316 * - Directories in path may or may not end with a trailing backslash. | |
| 317 * | |
| 318 * - CMD does not trim leading/trailing whitespace from path/pathex entries | |
| 319 * nor from the environment variables as a whole. | |
| 320 * | |
| 321 * - When cmd.exe cannot read a directory, it will just skip it and go on | |
| 322 * searching. However, unlike posix-y systems, it will happily try to run a | |
| 323 * file that is not readable/executable; if the spawn fails it will not | |
| 324 * continue searching. | |
| 325 * | |
| 326 * UNC path support: we are dealing with UNC paths in both the path and the | |
| 327 * filename. This is a deviation from what cmd.exe does (it does not let you | |
| 328 * start a program by specifying an UNC path on the command line) but this is | |
| 329 * really a pointless restriction. | |
| 330 * | |
| 331 */ | |
| 332 static WCHAR* search_path(const WCHAR *file, | |
| 333 WCHAR *cwd, | |
| 334 const WCHAR *path, | |
| 335 unsigned int flags) { | |
| 336 int file_has_dir; | |
| 337 WCHAR* result = NULL; | |
| 338 WCHAR *file_name_start; | |
| 339 WCHAR *dot; | |
| 340 const WCHAR *dir_start, *dir_end, *dir_path; | |
| 341 size_t dir_len; | |
| 342 int name_has_ext; | |
| 343 | |
| 344 size_t file_len = wcslen(file); | |
| 345 size_t cwd_len = wcslen(cwd); | |
| 346 | |
| 347 /* If the caller supplies an empty filename, | |
| 348 * we're not gonna return c:\windows\.exe -- GFY! | |
| 349 */ | |
| 350 if (file_len == 0 | |
| 351 || (file_len == 1 && file[0] == L'.')) { | |
| 352 return NULL; | |
| 353 } | |
| 354 | |
| 355 /* Find the start of the filename so we can split the directory from the | |
| 356 * name. */ | |
| 357 for (file_name_start = (WCHAR*)file + file_len; | |
| 358 file_name_start > file | |
| 359 && file_name_start[-1] != L'\\' | |
| 360 && file_name_start[-1] != L'/' | |
| 361 && file_name_start[-1] != L':'; | |
| 362 file_name_start--); | |
| 363 | |
| 364 file_has_dir = file_name_start != file; | |
| 365 | |
| 366 /* Check if the filename includes an extension */ | |
| 367 dot = wcschr(file_name_start, L'.'); | |
| 368 name_has_ext = (dot != NULL && dot[1] != L'\0'); | |
| 369 | |
| 370 if (file_has_dir) { | |
| 371 /* The file has a path inside, don't use path */ | |
| 372 result = path_search_walk_ext( | |
| 373 file, file_name_start - file, | |
| 374 file_name_start, file_len - (file_name_start - file), | |
| 375 cwd, cwd_len, | |
| 376 name_has_ext || (flags & UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME)); | |
| 377 | |
| 378 } else { | |
| 379 dir_end = path; | |
| 380 | |
| 381 if (NeedCurrentDirectoryForExePathW(L"")) { | |
| 382 /* The file is really only a name; look in cwd first, then scan path */ | |
| 383 result = path_search_walk_ext(L"", 0, | |
| 384 file, file_len, | |
| 385 cwd, cwd_len, | |
| 386 name_has_ext); | |
| 387 } | |
| 388 | |
| 389 while (result == NULL) { | |
| 390 if (dir_end == NULL || *dir_end == L'\0') { | |
| 391 break; | |
| 392 } | |
| 393 | |
| 394 /* Skip the separator that dir_end now points to */ | |
| 395 if (dir_end != path || *path == L';') { | |
| 396 dir_end++; | |
| 397 } | |
| 398 | |
| 399 /* Next slice starts just after where the previous one ended */ | |
| 400 dir_start = dir_end; | |
| 401 | |
| 402 /* If path is quoted, find quote end */ | |
| 403 if (*dir_start == L'"' || *dir_start == L'\'') { | |
| 404 dir_end = wcschr(dir_start + 1, *dir_start); | |
| 405 if (dir_end == NULL) { | |
| 406 dir_end = wcschr(dir_start, L'\0'); | |
| 407 } | |
| 408 } | |
| 409 /* Slice until the next ; or \0 is found */ | |
| 410 dir_end = wcschr(dir_end, L';'); | |
| 411 if (dir_end == NULL) { | |
| 412 dir_end = wcschr(dir_start, L'\0'); | |
| 413 } | |
| 414 | |
| 415 /* If the slice is zero-length, don't bother */ | |
| 416 if (dir_end - dir_start == 0) { | |
| 417 continue; | |
| 418 } | |
| 419 | |
| 420 dir_path = dir_start; | |
| 421 dir_len = dir_end - dir_start; | |
| 422 | |
| 423 /* Adjust if the path is quoted. */ | |
| 424 if (dir_path[0] == '"' || dir_path[0] == '\'') { | |
| 425 ++dir_path; | |
| 426 --dir_len; | |
| 427 } | |
| 428 | |
| 429 if (dir_path[dir_len - 1] == '"' || dir_path[dir_len - 1] == '\'') { | |
| 430 --dir_len; | |
| 431 } | |
| 432 | |
| 433 result = path_search_walk_ext(dir_path, dir_len, | |
| 434 file, file_len, | |
| 435 cwd, cwd_len, | |
| 436 name_has_ext); | |
| 437 } | |
| 438 } | |
| 439 | |
| 440 return result; | |
| 441 } | |
| 442 | |
| 443 | |
| 444 /* | |
| 445 * Quotes command line arguments | |
| 446 * Returns a pointer to the end (next char to be written) of the buffer | |
| 447 */ | |
| 448 WCHAR* quote_cmd_arg(const WCHAR *source, WCHAR *target) { | |
| 449 size_t len = wcslen(source); | |
| 450 size_t i; | |
| 451 int quote_hit; | |
| 452 WCHAR* start; | |
| 453 | |
| 454 if (len == 0) { | |
| 455 /* Need double quotation for empty argument */ | |
| 456 *(target++) = L'"'; | |
| 457 *(target++) = L'"'; | |
| 458 return target; | |
| 459 } | |
| 460 | |
| 461 if (NULL == wcspbrk(source, L" \t\"")) { | |
| 462 /* No quotation needed */ | |
| 463 wcsncpy(target, source, len); | |
| 464 target += len; | |
| 465 return target; | |
| 466 } | |
| 467 | |
| 468 if (NULL == wcspbrk(source, L"\"\\")) { | |
| 469 /* | |
| 470 * No embedded double quotes or backlashes, so I can just wrap | |
| 471 * quote marks around the whole thing. | |
| 472 */ | |
| 473 *(target++) = L'"'; | |
| 474 wcsncpy(target, source, len); | |
| 475 target += len; | |
| 476 *(target++) = L'"'; | |
| 477 return target; | |
| 478 } | |
| 479 | |
| 480 /* | |
| 481 * Expected input/output: | |
| 482 * input : hello"world | |
| 483 * output: "hello\"world" | |
| 484 * input : hello""world | |
| 485 * output: "hello\"\"world" | |
| 486 * input : hello\world | |
| 487 * output: hello\world | |
| 488 * input : hello\\world | |
| 489 * output: hello\\world | |
| 490 * input : hello\"world | |
| 491 * output: "hello\\\"world" | |
| 492 * input : hello\\"world | |
| 493 * output: "hello\\\\\"world" | |
| 494 * input : hello world\ | |
| 495 * output: "hello world\\" | |
| 496 */ | |
| 497 | |
| 498 *(target++) = L'"'; | |
| 499 start = target; | |
| 500 quote_hit = 1; | |
| 501 | |
| 502 for (i = len; i > 0; --i) { | |
| 503 *(target++) = source[i - 1]; | |
| 504 | |
| 505 if (quote_hit && source[i - 1] == L'\\') { | |
| 506 *(target++) = L'\\'; | |
| 507 } else if(source[i - 1] == L'"') { | |
| 508 quote_hit = 1; | |
| 509 *(target++) = L'\\'; | |
| 510 } else { | |
| 511 quote_hit = 0; | |
| 512 } | |
| 513 } | |
| 514 target[0] = L'\0'; | |
| 515 _wcsrev(start); | |
| 516 *(target++) = L'"'; | |
| 517 return target; | |
| 518 } | |
| 519 | |
| 520 | |
| 521 int make_program_args(char** args, int verbatim_arguments, WCHAR** dst_ptr) { | |
| 522 char** arg; | |
| 523 WCHAR* dst = NULL; | |
| 524 WCHAR* temp_buffer = NULL; | |
| 525 size_t dst_len = 0; | |
| 526 size_t temp_buffer_len = 0; | |
| 527 WCHAR* pos; | |
| 528 int arg_count = 0; | |
| 529 int err = 0; | |
| 530 | |
| 531 /* Count the required size. */ | |
| 532 for (arg = args; *arg; arg++) { | |
| 533 ssize_t arg_len; | |
| 534 | |
| 535 arg_len = uv_wtf8_length_as_utf16(*arg); | |
| 536 if (arg_len < 0) | |
| 537 return arg_len; | |
| 538 | |
| 539 dst_len += arg_len; | |
| 540 | |
| 541 if ((size_t) arg_len > temp_buffer_len) | |
| 542 temp_buffer_len = arg_len; | |
| 543 | |
| 544 arg_count++; | |
| 545 } | |
| 546 | |
| 547 /* Adjust for potential quotes. Also assume the worst-case scenario that | |
| 548 * every character needs escaping, so we need twice as much space. */ | |
| 549 dst_len = dst_len * 2 + arg_count * 2; | |
| 550 | |
| 551 /* Allocate buffer for the final command line. */ | |
| 552 dst = uv__malloc(dst_len * sizeof(WCHAR)); | |
| 553 if (dst == NULL) { | |
| 554 err = UV_ENOMEM; | |
| 555 goto error; | |
| 556 } | |
| 557 | |
| 558 /* Allocate temporary working buffer. */ | |
| 559 temp_buffer = uv__malloc(temp_buffer_len * sizeof(WCHAR)); | |
| 560 if (temp_buffer == NULL) { | |
| 561 err = UV_ENOMEM; | |
| 562 goto error; | |
| 563 } | |
| 564 | |
| 565 pos = dst; | |
| 566 for (arg = args; *arg; arg++) { | |
| 567 ssize_t arg_len; | |
| 568 | |
| 569 /* Convert argument to wide char. */ | |
| 570 arg_len = uv_wtf8_length_as_utf16(*arg); | |
| 571 assert(arg_len > 0); | |
| 572 assert(temp_buffer_len >= (size_t) arg_len); | |
| 573 uv_wtf8_to_utf16(*arg, temp_buffer, arg_len); | |
| 574 | |
| 575 if (verbatim_arguments) { | |
| 576 /* Copy verbatim. */ | |
| 577 wcscpy(pos, temp_buffer); | |
| 578 pos += arg_len - 1; | |
| 579 } else { | |
| 580 /* Quote/escape, if needed. */ | |
| 581 pos = quote_cmd_arg(temp_buffer, pos); | |
| 582 } | |
| 583 | |
| 584 *pos++ = *(arg + 1) ? L' ' : L'\0'; | |
| 585 assert(pos <= dst + dst_len); | |
| 586 } | |
| 587 | |
| 588 uv__free(temp_buffer); | |
| 589 | |
| 590 *dst_ptr = dst; | |
| 591 return 0; | |
| 592 | |
| 593 error: | |
| 594 uv__free(dst); | |
| 595 uv__free(temp_buffer); | |
| 596 return err; | |
| 597 } | |
| 598 | |
| 599 | |
| 600 static int env_strncmp(const wchar_t* a, int na, const wchar_t* b) { | |
| 601 wchar_t* a_eq; | |
| 602 wchar_t* b_eq; | |
| 603 int nb; | |
| 604 int r; | |
| 605 | |
| 606 if (na < 0) { | |
| 607 a_eq = wcschr(a, L'='); | |
| 608 assert(a_eq); | |
| 609 na = (int)(long)(a_eq - a); | |
| 610 } else { | |
| 611 na--; | |
| 612 } | |
| 613 b_eq = wcschr(b, L'='); | |
| 614 assert(b_eq); | |
| 615 nb = b_eq - b; | |
| 616 | |
| 617 r = CompareStringOrdinal(a, na, b, nb, /*case insensitive*/TRUE); | |
| 618 return r - CSTR_EQUAL; | |
| 619 } | |
| 620 | |
| 621 | |
| 622 static int qsort_wcscmp(const void *a, const void *b) { | |
| 623 wchar_t* astr = *(wchar_t* const*)a; | |
| 624 wchar_t* bstr = *(wchar_t* const*)b; | |
| 625 return env_strncmp(astr, -1, bstr); | |
| 626 } | |
| 627 | |
| 628 | |
| 629 /* | |
| 630 * The way windows takes environment variables is different than what C does; | |
| 631 * Windows wants a contiguous block of null-terminated strings, terminated | |
| 632 * with an additional null. | |
| 633 * | |
| 634 * Windows has a few "essential" environment variables. winsock will fail | |
| 635 * to initialize if SYSTEMROOT is not defined; some APIs make reference to | |
| 636 * TEMP. SYSTEMDRIVE is probably also important. We therefore ensure that | |
| 637 * these get defined if the input environment block does not contain any | |
| 638 * values for them. | |
| 639 * | |
| 640 * Also add variables known to Cygwin to be required for correct | |
| 641 * subprocess operation in many cases: | |
| 642 * https://github.com/Alexpux/Cygwin/blob/b266b04fbbd3a595f02ea149e4306d3ab9b1fe3d/winsup/cygwin/environ.cc#L955 | |
| 643 * | |
| 644 */ | |
| 645 int make_program_env(char* env_block[], WCHAR** dst_ptr) { | |
| 646 WCHAR* dst; | |
| 647 WCHAR* ptr; | |
| 648 char** env; | |
| 649 size_t env_len = 0; | |
| 650 size_t len; | |
| 651 size_t i; | |
| 652 size_t var_size; | |
| 653 size_t env_block_count = 1; /* 1 for null-terminator */ | |
| 654 WCHAR* dst_copy; | |
| 655 WCHAR** ptr_copy; | |
| 656 WCHAR** env_copy; | |
| 657 char* p; | |
| 658 size_t required_vars_value_len[ARRAY_SIZE(required_vars)]; | |
| 659 | |
| 660 /* first pass: determine size in UTF-16 */ | |
| 661 for (env = env_block; *env; env++) { | |
| 662 ssize_t len; | |
| 663 if (strchr(*env, '=')) { | |
| 664 len = uv_wtf8_length_as_utf16(*env); | |
| 665 if (len < 0) | |
| 666 return len; | |
| 667 env_len += len; | |
| 668 env_block_count++; | |
| 669 } | |
| 670 } | |
| 671 | |
| 672 /* second pass: copy to UTF-16 environment block */ | |
| 673 len = env_block_count * sizeof(WCHAR*); | |
| 674 p = uv__malloc(len + env_len * sizeof(WCHAR)); | |
| 675 if (p == NULL) { | |
| 676 return UV_ENOMEM; | |
| 677 } | |
| 678 env_copy = (void*) &p[0]; | |
| 679 dst_copy = (void*) &p[len]; | |
| 680 | |
| 681 ptr = dst_copy; | |
| 682 ptr_copy = env_copy; | |
| 683 for (env = env_block; *env; env++) { | |
| 684 ssize_t len; | |
| 685 if (strchr(*env, '=')) { | |
| 686 len = uv_wtf8_length_as_utf16(*env); | |
| 687 assert(len > 0); | |
| 688 assert((size_t) len <= env_len - (ptr - dst_copy)); | |
| 689 uv_wtf8_to_utf16(*env, ptr, len); | |
| 690 *ptr_copy++ = ptr; | |
| 691 ptr += len; | |
| 692 } | |
| 693 } | |
| 694 *ptr_copy = NULL; | |
| 695 assert(env_len == 0 || env_len == (size_t) (ptr - dst_copy)); | |
| 696 | |
| 697 /* sort our (UTF-16) copy */ | |
| 698 qsort(env_copy, env_block_count-1, sizeof(wchar_t*), qsort_wcscmp); | |
| 699 | |
| 700 /* third pass: check for required variables */ | |
| 701 for (ptr_copy = env_copy, i = 0; i < ARRAY_SIZE(required_vars); ) { | |
| 702 int cmp; | |
| 703 if (!*ptr_copy) { | |
| 704 cmp = -1; | |
| 705 } else { | |
| 706 cmp = env_strncmp(required_vars[i].wide_eq, | |
| 707 required_vars[i].len, | |
| 708 *ptr_copy); | |
| 709 } | |
| 710 if (cmp < 0) { | |
| 711 /* missing required var */ | |
| 712 var_size = GetEnvironmentVariableW(required_vars[i].wide, NULL, 0); | |
| 713 required_vars_value_len[i] = var_size; | |
| 714 if (var_size != 0) { | |
| 715 env_len += required_vars[i].len; | |
| 716 env_len += var_size; | |
| 717 } | |
| 718 i++; | |
| 719 } else { | |
| 720 ptr_copy++; | |
| 721 if (cmp == 0) | |
| 722 i++; | |
| 723 } | |
| 724 } | |
| 725 | |
| 726 /* final pass: copy, in sort order, and inserting required variables */ | |
| 727 dst = uv__malloc((1+env_len) * sizeof(WCHAR)); | |
| 728 if (!dst) { | |
| 729 uv__free(p); | |
| 730 return UV_ENOMEM; | |
| 731 } | |
| 732 | |
| 733 for (ptr = dst, ptr_copy = env_copy, i = 0; | |
| 734 *ptr_copy || i < ARRAY_SIZE(required_vars); | |
| 735 ptr += len) { | |
| 736 int cmp; | |
| 737 if (i >= ARRAY_SIZE(required_vars)) { | |
| 738 cmp = 1; | |
| 739 } else if (!*ptr_copy) { | |
| 740 cmp = -1; | |
| 741 } else { | |
| 742 cmp = env_strncmp(required_vars[i].wide_eq, | |
| 743 required_vars[i].len, | |
| 744 *ptr_copy); | |
| 745 } | |
| 746 if (cmp < 0) { | |
| 747 /* missing required var */ | |
| 748 len = required_vars_value_len[i]; | |
| 749 if (len) { | |
| 750 wcscpy(ptr, required_vars[i].wide_eq); | |
| 751 ptr += required_vars[i].len; | |
| 752 var_size = GetEnvironmentVariableW(required_vars[i].wide, | |
| 753 ptr, | |
| 754 (int) (env_len - (ptr - dst))); | |
| 755 if (var_size != (DWORD) (len - 1)) { /* TODO: handle race condition? */ | |
| 756 uv_fatal_error(GetLastError(), "GetEnvironmentVariableW"); | |
| 757 } | |
| 758 } | |
| 759 i++; | |
| 760 } else { | |
| 761 /* copy var from env_block */ | |
| 762 len = wcslen(*ptr_copy) + 1; | |
| 763 wmemcpy(ptr, *ptr_copy, len); | |
| 764 ptr_copy++; | |
| 765 if (cmp == 0) | |
| 766 i++; | |
| 767 } | |
| 768 } | |
| 769 | |
| 770 /* Terminate with an extra NULL. */ | |
| 771 assert(env_len == (size_t) (ptr - dst)); | |
| 772 *ptr = L'\0'; | |
| 773 | |
| 774 uv__free(p); | |
| 775 *dst_ptr = dst; | |
| 776 return 0; | |
| 777 } | |
| 778 | |
| 779 /* | |
| 780 * Attempt to find the value of the PATH environment variable in the child's | |
| 781 * preprocessed environment. | |
| 782 * | |
| 783 * If found, a pointer into `env` is returned. If not found, NULL is returned. | |
| 784 */ | |
| 785 static WCHAR* find_path(WCHAR *env) { | |
| 786 for (; env != NULL && *env != 0; env += wcslen(env) + 1) { | |
| 787 if ((env[0] == L'P' || env[0] == L'p') && | |
| 788 (env[1] == L'A' || env[1] == L'a') && | |
| 789 (env[2] == L'T' || env[2] == L't') && | |
| 790 (env[3] == L'H' || env[3] == L'h') && | |
| 791 (env[4] == L'=')) { | |
| 792 return &env[5]; | |
| 793 } | |
| 794 } | |
| 795 | |
| 796 return NULL; | |
| 797 } | |
| 798 | |
| 799 /* | |
| 800 * Called on Windows thread-pool thread to indicate that | |
| 801 * a child process has exited. | |
| 802 */ | |
| 803 static void CALLBACK exit_wait_callback(void* data, BOOLEAN didTimeout) { | |
| 804 uv_process_t* process = (uv_process_t*) data; | |
| 805 uv_loop_t* loop = process->loop; | |
| 806 | |
| 807 assert(didTimeout == FALSE); | |
| 808 assert(process); | |
| 809 assert(!process->exit_cb_pending); | |
| 810 | |
| 811 process->exit_cb_pending = 1; | |
| 812 | |
| 813 /* Post completed */ | |
| 814 POST_COMPLETION_FOR_REQ(loop, &process->exit_req); | |
| 815 } | |
| 816 | |
| 817 | |
| 818 /* Called on main thread after a child process has exited. */ | |
| 819 void uv__process_proc_exit(uv_loop_t* loop, uv_process_t* handle) { | |
| 820 int64_t exit_code; | |
| 821 DWORD status; | |
| 822 | |
| 823 assert(handle->exit_cb_pending); | |
| 824 handle->exit_cb_pending = 0; | |
| 825 | |
| 826 /* If we're closing, don't call the exit callback. Just schedule a close | |
| 827 * callback now. */ | |
| 828 if (handle->flags & UV_HANDLE_CLOSING) { | |
| 829 uv__want_endgame(loop, (uv_handle_t*) handle); | |
| 830 return; | |
| 831 } | |
| 832 | |
| 833 /* Unregister from process notification. */ | |
| 834 if (handle->wait_handle != INVALID_HANDLE_VALUE) { | |
| 835 UnregisterWait(handle->wait_handle); | |
| 836 handle->wait_handle = INVALID_HANDLE_VALUE; | |
| 837 } | |
| 838 | |
| 839 /* Set the handle to inactive: no callbacks will be made after the exit | |
| 840 * callback. */ | |
| 841 uv__handle_stop(handle); | |
| 842 | |
| 843 if (GetExitCodeProcess(handle->process_handle, &status)) { | |
| 844 exit_code = status; | |
| 845 } else { | |
| 846 /* Unable to obtain the exit code. This should never happen. */ | |
| 847 exit_code = uv_translate_sys_error(GetLastError()); | |
| 848 } | |
| 849 | |
| 850 /* Fire the exit callback. */ | |
| 851 if (handle->exit_cb) { | |
| 852 handle->exit_cb(handle, exit_code, handle->exit_signal); | |
| 853 } | |
| 854 } | |
| 855 | |
| 856 | |
| 857 void uv__process_close(uv_loop_t* loop, uv_process_t* handle) { | |
| 858 uv__handle_closing(handle); | |
| 859 | |
| 860 if (handle->wait_handle != INVALID_HANDLE_VALUE) { | |
| 861 /* This blocks until either the wait was cancelled, or the callback has | |
| 862 * completed. */ | |
| 863 BOOL r = UnregisterWaitEx(handle->wait_handle, INVALID_HANDLE_VALUE); | |
| 864 if (!r) { | |
| 865 /* This should never happen, and if it happens, we can't recover... */ | |
| 866 uv_fatal_error(GetLastError(), "UnregisterWaitEx"); | |
| 867 } | |
| 868 | |
| 869 handle->wait_handle = INVALID_HANDLE_VALUE; | |
| 870 } | |
| 871 | |
| 872 if (!handle->exit_cb_pending) { | |
| 873 uv__want_endgame(loop, (uv_handle_t*)handle); | |
| 874 } | |
| 875 } | |
| 876 | |
| 877 | |
| 878 void uv__process_endgame(uv_loop_t* loop, uv_process_t* handle) { | |
| 879 assert(!handle->exit_cb_pending); | |
| 880 assert(handle->flags & UV_HANDLE_CLOSING); | |
| 881 assert(!(handle->flags & UV_HANDLE_CLOSED)); | |
| 882 | |
| 883 /* Clean-up the process handle. */ | |
| 884 CloseHandle(handle->process_handle); | |
| 885 | |
| 886 uv__handle_close(handle); | |
| 887 } | |
| 888 | |
| 889 | |
| 890 int uv_spawn(uv_loop_t* loop, | |
| 891 uv_process_t* process, | |
| 892 const uv_process_options_t* options) { | |
| 893 int i; | |
| 894 int err = 0; | |
| 895 WCHAR* path = NULL, *alloc_path = NULL; | |
| 896 BOOL result; | |
| 897 WCHAR* application_path = NULL, *application = NULL, *arguments = NULL, | |
| 898 *env = NULL, *cwd = NULL; | |
| 899 STARTUPINFOW startup; | |
| 900 PROCESS_INFORMATION info; | |
| 901 DWORD process_flags, cwd_len; | |
| 902 BYTE* child_stdio_buffer; | |
| 903 | |
| 904 uv__process_init(loop, process); | |
| 905 process->exit_cb = options->exit_cb; | |
| 906 child_stdio_buffer = NULL; | |
| 907 | |
| 908 if (options->flags & (UV_PROCESS_SETGID | UV_PROCESS_SETUID)) { | |
| 909 return UV_ENOTSUP; | |
| 910 } | |
| 911 | |
| 912 if (options->file == NULL || | |
| 913 options->args == NULL) { | |
| 914 return UV_EINVAL; | |
| 915 } | |
| 916 | |
| 917 assert(options->file != NULL); | |
| 918 assert(!(options->flags & ~(UV_PROCESS_DETACHED | | |
| 919 UV_PROCESS_SETGID | | |
| 920 UV_PROCESS_SETUID | | |
| 921 UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME | | |
| 922 UV_PROCESS_WINDOWS_HIDE | | |
| 923 UV_PROCESS_WINDOWS_HIDE_CONSOLE | | |
| 924 UV_PROCESS_WINDOWS_HIDE_GUI | | |
| 925 UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS))); | |
| 926 | |
| 927 err = uv__utf8_to_utf16_alloc(options->file, &application); | |
| 928 if (err) | |
| 929 goto done_uv; | |
| 930 | |
| 931 err = make_program_args( | |
| 932 options->args, | |
| 933 options->flags & UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS, | |
| 934 &arguments); | |
| 935 if (err) | |
| 936 goto done_uv; | |
| 937 | |
| 938 if (options->env) { | |
| 939 err = make_program_env(options->env, &env); | |
| 940 if (err) | |
| 941 goto done_uv; | |
| 942 } | |
| 943 | |
| 944 if (options->cwd) { | |
| 945 /* Explicit cwd */ | |
| 946 err = uv__utf8_to_utf16_alloc(options->cwd, &cwd); | |
| 947 if (err) | |
| 948 goto done_uv; | |
| 949 | |
| 950 cwd_len = wcslen(cwd); | |
| 951 } else { | |
| 952 /* Inherit cwd */ | |
| 953 DWORD r; | |
| 954 | |
| 955 cwd_len = GetCurrentDirectoryW(0, NULL); | |
| 956 if (!cwd_len) { | |
| 957 err = GetLastError(); | |
| 958 goto done; | |
| 959 } | |
| 960 | |
| 961 cwd = (WCHAR*) uv__malloc(cwd_len * sizeof(WCHAR)); | |
| 962 if (cwd == NULL) { | |
| 963 err = ERROR_OUTOFMEMORY; | |
| 964 goto done; | |
| 965 } | |
| 966 | |
| 967 r = GetCurrentDirectoryW(cwd_len, cwd); | |
| 968 if (r == 0 || r >= cwd_len) { | |
| 969 err = GetLastError(); | |
| 970 goto done; | |
| 971 } | |
| 972 } | |
| 973 | |
| 974 /* If cwd is too long, shorten it */ | |
| 975 if (cwd_len >= MAX_PATH) { | |
| 976 cwd_len = GetShortPathNameW(cwd, cwd, cwd_len); | |
| 977 if (cwd_len == 0) { | |
| 978 err = GetLastError(); | |
| 979 goto done; | |
| 980 } | |
| 981 } | |
| 982 | |
| 983 /* Get PATH environment variable. */ | |
| 984 path = find_path(env); | |
| 985 if (path == NULL) { | |
| 986 DWORD path_len, r; | |
| 987 | |
| 988 path_len = GetEnvironmentVariableW(L"PATH", NULL, 0); | |
| 989 if (path_len != 0) { | |
| 990 alloc_path = (WCHAR*) uv__malloc(path_len * sizeof(WCHAR)); | |
| 991 if (alloc_path == NULL) { | |
| 992 err = ERROR_OUTOFMEMORY; | |
| 993 goto done; | |
| 994 } | |
| 995 path = alloc_path; | |
| 996 | |
| 997 r = GetEnvironmentVariableW(L"PATH", path, path_len); | |
| 998 if (r == 0 || r >= path_len) { | |
| 999 err = GetLastError(); | |
| 1000 goto done; | |
| 1001 } | |
| 1002 } | |
| 1003 } | |
| 1004 | |
| 1005 err = uv__stdio_create(loop, options, &child_stdio_buffer); | |
| 1006 if (err) | |
| 1007 goto done; | |
| 1008 | |
| 1009 application_path = search_path(application, | |
| 1010 cwd, | |
| 1011 path, | |
| 1012 options->flags); | |
| 1013 if (application_path == NULL) { | |
| 1014 /* Not found. */ | |
| 1015 err = ERROR_FILE_NOT_FOUND; | |
| 1016 goto done; | |
| 1017 } | |
| 1018 | |
| 1019 startup.cb = sizeof(startup); | |
| 1020 startup.lpReserved = NULL; | |
| 1021 startup.lpDesktop = NULL; | |
| 1022 startup.lpTitle = NULL; | |
| 1023 startup.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; | |
| 1024 | |
| 1025 startup.cbReserved2 = uv__stdio_size(child_stdio_buffer); | |
| 1026 startup.lpReserved2 = (BYTE*) child_stdio_buffer; | |
| 1027 | |
| 1028 startup.hStdInput = uv__stdio_handle(child_stdio_buffer, 0); | |
| 1029 startup.hStdOutput = uv__stdio_handle(child_stdio_buffer, 1); | |
| 1030 startup.hStdError = uv__stdio_handle(child_stdio_buffer, 2); | |
| 1031 | |
| 1032 process_flags = CREATE_UNICODE_ENVIRONMENT; | |
| 1033 | |
| 1034 if ((options->flags & UV_PROCESS_WINDOWS_HIDE_CONSOLE) || | |
| 1035 (options->flags & UV_PROCESS_WINDOWS_HIDE)) { | |
| 1036 /* Avoid creating console window if stdio is not inherited. */ | |
| 1037 for (i = 0; i < options->stdio_count; i++) { | |
| 1038 if (options->stdio[i].flags & UV_INHERIT_FD) | |
| 1039 break; | |
| 1040 if (i == options->stdio_count - 1) | |
| 1041 process_flags |= CREATE_NO_WINDOW; | |
| 1042 } | |
| 1043 } | |
| 1044 if ((options->flags & UV_PROCESS_WINDOWS_HIDE_GUI) || | |
| 1045 (options->flags & UV_PROCESS_WINDOWS_HIDE)) { | |
| 1046 /* Use SW_HIDE to avoid any potential process window. */ | |
| 1047 startup.wShowWindow = SW_HIDE; | |
| 1048 } else { | |
| 1049 startup.wShowWindow = SW_SHOWDEFAULT; | |
| 1050 } | |
| 1051 | |
| 1052 if (options->flags & UV_PROCESS_DETACHED) { | |
| 1053 /* Note that we're not setting the CREATE_BREAKAWAY_FROM_JOB flag. That | |
| 1054 * means that libuv might not let you create a fully daemonized process | |
| 1055 * when run under job control. However the type of job control that libuv | |
| 1056 * itself creates doesn't trickle down to subprocesses so they can still | |
| 1057 * daemonize. | |
| 1058 * | |
| 1059 * A reason to not do this is that CREATE_BREAKAWAY_FROM_JOB makes the | |
| 1060 * CreateProcess call fail if we're under job control that doesn't allow | |
| 1061 * breakaway. | |
| 1062 */ | |
| 1063 process_flags |= DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP; | |
| 1064 process_flags |= CREATE_SUSPENDED; | |
| 1065 } | |
| 1066 | |
| 1067 if (!CreateProcessW(application_path, | |
| 1068 arguments, | |
| 1069 NULL, | |
| 1070 NULL, | |
| 1071 1, | |
| 1072 process_flags, | |
| 1073 env, | |
| 1074 cwd, | |
| 1075 &startup, | |
| 1076 &info)) { | |
| 1077 /* CreateProcessW failed. */ | |
| 1078 err = GetLastError(); | |
| 1079 goto done; | |
| 1080 } | |
| 1081 | |
| 1082 /* If the process isn't spawned as detached, assign to the global job object | |
| 1083 * so windows will kill it when the parent process dies. */ | |
| 1084 if (!(options->flags & UV_PROCESS_DETACHED)) { | |
| 1085 uv_once(&uv_global_job_handle_init_guard_, uv__init_global_job_handle); | |
| 1086 | |
| 1087 if (!AssignProcessToJobObject(uv_global_job_handle_, info.hProcess)) { | |
| 1088 /* AssignProcessToJobObject might fail if this process is under job | |
| 1089 * control and the job doesn't have the | |
| 1090 * JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK flag set, on a Windows version | |
| 1091 * that doesn't support nested jobs. | |
| 1092 * | |
| 1093 * When that happens we just swallow the error and continue without | |
| 1094 * establishing a kill-child-on-parent-exit relationship, otherwise | |
| 1095 * there would be no way for libuv applications run under job control | |
| 1096 * to spawn processes at all. | |
| 1097 */ | |
| 1098 DWORD err = GetLastError(); | |
| 1099 if (err != ERROR_ACCESS_DENIED) | |
| 1100 uv_fatal_error(err, "AssignProcessToJobObject"); | |
| 1101 } | |
| 1102 } | |
| 1103 | |
| 1104 if (process_flags & CREATE_SUSPENDED) { | |
| 1105 if (ResumeThread(info.hThread) == ((DWORD)-1)) { | |
| 1106 err = GetLastError(); | |
| 1107 TerminateProcess(info.hProcess, 1); | |
| 1108 goto done; | |
| 1109 } | |
| 1110 } | |
| 1111 | |
| 1112 /* Spawn succeeded. Beyond this point, failure is reported asynchronously. */ | |
| 1113 | |
| 1114 process->process_handle = info.hProcess; | |
| 1115 process->pid = info.dwProcessId; | |
| 1116 | |
| 1117 /* Set IPC pid to all IPC pipes. */ | |
| 1118 for (i = 0; i < options->stdio_count; i++) { | |
| 1119 const uv_stdio_container_t* fdopt = &options->stdio[i]; | |
| 1120 if (fdopt->flags & UV_CREATE_PIPE && | |
| 1121 fdopt->data.stream->type == UV_NAMED_PIPE && | |
| 1122 ((uv_pipe_t*) fdopt->data.stream)->ipc) { | |
| 1123 ((uv_pipe_t*) fdopt->data.stream)->pipe.conn.ipc_remote_pid = | |
| 1124 info.dwProcessId; | |
| 1125 } | |
| 1126 } | |
| 1127 | |
| 1128 /* Setup notifications for when the child process exits. */ | |
| 1129 result = RegisterWaitForSingleObject(&process->wait_handle, | |
| 1130 process->process_handle, exit_wait_callback, (void*)process, INFINITE, | |
| 1131 WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE); | |
| 1132 if (!result) { | |
| 1133 uv_fatal_error(GetLastError(), "RegisterWaitForSingleObject"); | |
| 1134 } | |
| 1135 | |
| 1136 CloseHandle(info.hThread); | |
| 1137 | |
| 1138 assert(!err); | |
| 1139 | |
| 1140 /* Make the handle active. It will remain active until the exit callback is | |
| 1141 * made or the handle is closed, whichever happens first. */ | |
| 1142 uv__handle_start(process); | |
| 1143 | |
| 1144 goto done_uv; | |
| 1145 | |
| 1146 /* Cleanup, whether we succeeded or failed. */ | |
| 1147 done: | |
| 1148 err = uv_translate_sys_error(err); | |
| 1149 | |
| 1150 done_uv: | |
| 1151 uv__free(application); | |
| 1152 uv__free(application_path); | |
| 1153 uv__free(arguments); | |
| 1154 uv__free(cwd); | |
| 1155 uv__free(env); | |
| 1156 uv__free(alloc_path); | |
| 1157 | |
| 1158 if (child_stdio_buffer != NULL) { | |
| 1159 /* Clean up child stdio handles. */ | |
| 1160 uv__stdio_destroy(child_stdio_buffer); | |
| 1161 child_stdio_buffer = NULL; | |
| 1162 } | |
| 1163 | |
| 1164 return err; | |
| 1165 } | |
| 1166 | |
| 1167 | |
| 1168 static int uv__kill(HANDLE process_handle, int signum) { | |
| 1169 if (signum < 0 || signum >= NSIG) { | |
| 1170 return UV_EINVAL; | |
| 1171 } | |
| 1172 | |
| 1173 /* Create a dump file for the targeted process, if the registry key | |
| 1174 * `HKLM:Software\Microsoft\Windows\Windows Error Reporting\LocalDumps` | |
| 1175 * exists. The location of the dumps can be influenced by the `DumpFolder` | |
| 1176 * sub-key, which has a default value of `%LOCALAPPDATA%\CrashDumps`, see [0] | |
| 1177 * for more detail. Note that if the dump folder does not exist, we attempt | |
| 1178 * to create it, to match behavior with WER itself. | |
| 1179 * [0]: https://learn.microsoft.com/en-us/windows/win32/wer/collecting-user-mode-dumps */ | |
| 1180 if (signum == SIGQUIT) { | |
| 1181 HKEY registry_key; | |
| 1182 DWORD pid, ret; | |
| 1183 WCHAR basename[MAX_PATH]; | |
| 1184 | |
| 1185 /* Get target process name. */ | |
| 1186 GetModuleBaseNameW(process_handle, NULL, &basename[0], sizeof(basename)); | |
| 1187 | |
| 1188 /* Get PID of target process. */ | |
| 1189 pid = GetProcessId(process_handle); | |
| 1190 | |
| 1191 /* Get LocalDumps directory path. */ | |
| 1192 ret = RegOpenKeyExW( | |
| 1193 HKEY_LOCAL_MACHINE, | |
| 1194 L"SOFTWARE\\Microsoft\\Windows\\Windows Error Reporting\\LocalDumps", | |
| 1195 0, | |
| 1196 KEY_QUERY_VALUE, | |
| 1197 ®istry_key); | |
| 1198 if (ret == ERROR_SUCCESS) { | |
| 1199 HANDLE hDumpFile = NULL; | |
| 1200 WCHAR dump_folder[MAX_PATH], dump_name[MAX_PATH]; | |
| 1201 DWORD dump_folder_len = sizeof(dump_folder), key_type = 0; | |
| 1202 ret = RegGetValueW(registry_key, | |
| 1203 NULL, | |
| 1204 L"DumpFolder", | |
| 1205 RRF_RT_ANY, | |
| 1206 &key_type, | |
| 1207 (PVOID) dump_folder, | |
| 1208 &dump_folder_len); | |
| 1209 if (ret != ERROR_SUCCESS) { | |
| 1210 /* Workaround for missing uuid.dll on MinGW. */ | |
| 1211 static const GUID FOLDERID_LocalAppData_libuv = { | |
| 1212 0xf1b32785, 0x6fba, 0x4fcf, | |
| 1213 {0x9d, 0x55, 0x7b, 0x8e, 0x7f, 0x15, 0x70, 0x91} | |
| 1214 }; | |
| 1215 | |
| 1216 /* Default value for `dump_folder` is `%LOCALAPPDATA%\CrashDumps`. */ | |
| 1217 WCHAR* localappdata; | |
| 1218 SHGetKnownFolderPath(&FOLDERID_LocalAppData_libuv, | |
| 1219 0, | |
| 1220 NULL, | |
| 1221 &localappdata); | |
| 1222 _snwprintf_s(dump_folder, | |
| 1223 sizeof(dump_folder), | |
| 1224 _TRUNCATE, | |
| 1225 L"%ls\\CrashDumps", | |
| 1226 localappdata); | |
| 1227 CoTaskMemFree(localappdata); | |
| 1228 } | |
| 1229 RegCloseKey(registry_key); | |
| 1230 | |
| 1231 /* Create dump folder if it doesn't already exist. */ | |
| 1232 CreateDirectoryW(dump_folder, NULL); | |
| 1233 | |
| 1234 /* Construct dump filename from process name and PID. */ | |
| 1235 _snwprintf_s(dump_name, | |
| 1236 sizeof(dump_name), | |
| 1237 _TRUNCATE, | |
| 1238 L"%ls\\%ls.%d.dmp", | |
| 1239 dump_folder, | |
| 1240 basename, | |
| 1241 pid); | |
| 1242 | |
| 1243 hDumpFile = CreateFileW(dump_name, | |
| 1244 GENERIC_WRITE, | |
| 1245 0, | |
| 1246 NULL, | |
| 1247 CREATE_NEW, | |
| 1248 FILE_ATTRIBUTE_NORMAL, | |
| 1249 NULL); | |
| 1250 if (hDumpFile != INVALID_HANDLE_VALUE) { | |
| 1251 DWORD dump_options, sym_options; | |
| 1252 FILE_DISPOSITION_INFO DeleteOnClose = { TRUE }; | |
| 1253 | |
| 1254 /* If something goes wrong while writing it out, delete the file. */ | |
| 1255 SetFileInformationByHandle(hDumpFile, | |
| 1256 FileDispositionInfo, | |
| 1257 &DeleteOnClose, | |
| 1258 sizeof(DeleteOnClose)); | |
| 1259 | |
| 1260 /* Tell wine to dump ELF modules as well. */ | |
| 1261 sym_options = SymGetOptions(); | |
| 1262 SymSetOptions(sym_options | 0x40000000); | |
| 1263 | |
| 1264 /* MiniDumpWithAvxXStateContext might be undef in server2012r2 or mingw < 12 */ | |
| 1265 #ifndef MiniDumpWithAvxXStateContext | |
| 1266 #define MiniDumpWithAvxXStateContext 0x00200000 | |
| 1267 #endif | |
| 1268 /* We default to a fairly complete dump. In the future, we may want to | |
| 1269 * allow clients to customize what kind of dump to create. */ | |
| 1270 dump_options = MiniDumpWithFullMemory | | |
| 1271 MiniDumpIgnoreInaccessibleMemory | | |
| 1272 MiniDumpWithAvxXStateContext; | |
| 1273 | |
| 1274 if (MiniDumpWriteDump(process_handle, | |
| 1275 pid, | |
| 1276 hDumpFile, | |
| 1277 dump_options, | |
| 1278 NULL, | |
| 1279 NULL, | |
| 1280 NULL)) { | |
| 1281 /* Don't delete the file on close if we successfully wrote it out. */ | |
| 1282 FILE_DISPOSITION_INFO DontDeleteOnClose = { FALSE }; | |
| 1283 SetFileInformationByHandle(hDumpFile, | |
| 1284 FileDispositionInfo, | |
| 1285 &DontDeleteOnClose, | |
| 1286 sizeof(DontDeleteOnClose)); | |
| 1287 } | |
| 1288 SymSetOptions(sym_options); | |
| 1289 CloseHandle(hDumpFile); | |
| 1290 } | |
| 1291 } | |
| 1292 } | |
| 1293 | |
| 1294 switch (signum) { | |
| 1295 case SIGQUIT: | |
| 1296 case SIGTERM: | |
| 1297 case SIGKILL: | |
| 1298 case SIGINT: { | |
| 1299 /* Unconditionally terminate the process. On Windows, killed processes | |
| 1300 * normally return 1. */ | |
| 1301 int err; | |
| 1302 DWORD status; | |
| 1303 | |
| 1304 if (TerminateProcess(process_handle, 1)) | |
| 1305 return 0; | |
| 1306 | |
| 1307 /* If the process already exited before TerminateProcess was called, | |
| 1308 * TerminateProcess will fail with ERROR_ACCESS_DENIED. */ | |
| 1309 err = GetLastError(); | |
| 1310 if (err == ERROR_ACCESS_DENIED) { | |
| 1311 /* First check using GetExitCodeProcess() with status different from | |
| 1312 * STILL_ACTIVE (259). This check can be set incorrectly by the process, | |
| 1313 * though that is uncommon. */ | |
| 1314 if (GetExitCodeProcess(process_handle, &status) && | |
| 1315 status != STILL_ACTIVE) { | |
| 1316 return UV_ESRCH; | |
| 1317 } | |
| 1318 | |
| 1319 /* But the process could have exited with code == STILL_ACTIVE, use then | |
| 1320 * WaitForSingleObject with timeout zero. This is prone to a race | |
| 1321 * condition as it could return WAIT_TIMEOUT because the handle might | |
| 1322 * not have been signaled yet.That would result in returning the wrong | |
| 1323 * error code here (UV_EACCES instead of UV_ESRCH), but we cannot fix | |
| 1324 * the kernel synchronization issue that TerminateProcess is | |
| 1325 * inconsistent with WaitForSingleObject with just the APIs available to | |
| 1326 * us in user space. */ | |
| 1327 if (WaitForSingleObject(process_handle, 0) == WAIT_OBJECT_0) { | |
| 1328 return UV_ESRCH; | |
| 1329 } | |
| 1330 } | |
| 1331 | |
| 1332 return uv_translate_sys_error(err); | |
| 1333 } | |
| 1334 | |
| 1335 case 0: { | |
| 1336 /* Health check: is the process still alive? */ | |
| 1337 DWORD status; | |
| 1338 | |
| 1339 if (!GetExitCodeProcess(process_handle, &status)) | |
| 1340 return uv_translate_sys_error(GetLastError()); | |
| 1341 | |
| 1342 if (status != STILL_ACTIVE) | |
| 1343 return UV_ESRCH; | |
| 1344 | |
| 1345 switch (WaitForSingleObject(process_handle, 0)) { | |
| 1346 case WAIT_OBJECT_0: | |
| 1347 return UV_ESRCH; | |
| 1348 case WAIT_FAILED: | |
| 1349 return uv_translate_sys_error(GetLastError()); | |
| 1350 case WAIT_TIMEOUT: | |
| 1351 return 0; | |
| 1352 default: | |
| 1353 return UV_UNKNOWN; | |
| 1354 } | |
| 1355 } | |
| 1356 | |
| 1357 default: | |
| 1358 /* Unsupported signal. */ | |
| 1359 return UV_ENOSYS; | |
| 1360 } | |
| 1361 } | |
| 1362 | |
| 1363 | |
| 1364 int uv_process_kill(uv_process_t* process, int signum) { | |
| 1365 int err; | |
| 1366 | |
| 1367 if (process->process_handle == INVALID_HANDLE_VALUE) { | |
| 1368 return UV_EINVAL; | |
| 1369 } | |
| 1370 | |
| 1371 err = uv__kill(process->process_handle, signum); | |
| 1372 if (err) { | |
| 1373 return err; /* err is already translated. */ | |
| 1374 } | |
| 1375 | |
| 1376 process->exit_signal = signum; | |
| 1377 | |
| 1378 return 0; | |
| 1379 } | |
| 1380 | |
| 1381 | |
| 1382 int uv_kill(int pid, int signum) { | |
| 1383 int err; | |
| 1384 HANDLE process_handle; | |
| 1385 | |
| 1386 if (pid == 0) { | |
| 1387 process_handle = GetCurrentProcess(); | |
| 1388 } else { | |
| 1389 process_handle = OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | SYNCHRONIZE, | |
| 1390 FALSE, | |
| 1391 pid); | |
| 1392 } | |
| 1393 | |
| 1394 if (process_handle == NULL) { | |
| 1395 err = GetLastError(); | |
| 1396 if (err == ERROR_INVALID_PARAMETER) { | |
| 1397 return UV_ESRCH; | |
| 1398 } else { | |
| 1399 return uv_translate_sys_error(err); | |
| 1400 } | |
| 1401 } | |
| 1402 | |
| 1403 err = uv__kill(process_handle, signum); | |
| 1404 CloseHandle(process_handle); | |
| 1405 | |
| 1406 return err; /* err is already translated. */ | |
| 1407 } |