|
160
|
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 }
|