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