1
2 #include <R.h>
3 #include <R_ext/Rdynload.h>
4
5 #include "../processx.h"
6
7 #include <wchar.h>
8
9 static HANDLE processx__global_job_handle = NULL;
10
processx__init_global_job_handle(void)11 static void processx__init_global_job_handle(void) {
12 /* Create a job object and set it up to kill all contained processes when
13 * it's closed. Since this handle is made non-inheritable and we're not
14 * giving it to anyone, we're the only process holding a reference to it.
15 * That means that if this process exits it is closed and all the
16 * processes it contains are killed. All processes created with processx
17 * that are spawned without the cleanup flag are assigned to this job.
18 *
19 * We're setting the JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK flag so only
20 * the processes that we explicitly add are affected, and *their*
21 * subprocesses are not. This ensures that our child processes are not
22 * limited in their ability to use job control on Windows versions that
23 * don't deal with nested jobs (prior to Windows 8 / Server 2012). It
24 * also lets our child processes create detached processes without
25 * explicitly breaking away from job control (which processx_exec
26 * doesn't do, either). */
27
28 SECURITY_ATTRIBUTES attr;
29 JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
30
31 memset(&attr, 0, sizeof attr);
32 attr.bInheritHandle = FALSE;
33
34 memset(&info, 0, sizeof info);
35 info.BasicLimitInformation.LimitFlags =
36 JOB_OBJECT_LIMIT_BREAKAWAY_OK |
37 JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK |
38 JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION |
39 JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
40
41 processx__global_job_handle = CreateJobObjectW(&attr, NULL);
42 if (processx__global_job_handle == NULL) {
43 R_THROW_SYSTEM_ERROR("Creating global job object");
44 }
45
46 if (!SetInformationJobObject(processx__global_job_handle,
47 JobObjectExtendedLimitInformation,
48 &info,
49 sizeof info)) {
50 R_THROW_SYSTEM_ERROR("Setting up global job object");
51 }
52 }
53
R_init_processx_win()54 void R_init_processx_win() {
55 /* Nothing to do currently */
56 }
57
processx__unload_cleanup()58 SEXP processx__unload_cleanup() {
59
60 if (processx__connection_iocp) CloseHandle(processx__connection_iocp);
61 if (processx__iocp_thread) TerminateThread(processx__iocp_thread, 0);
62 if (processx__thread_start) CloseHandle(processx__thread_start);
63 if (processx__thread_done) CloseHandle(processx__thread_done);
64
65 processx__connection_iocp = processx__iocp_thread =
66 processx__thread_start = processx__thread_done = NULL;
67
68 if (processx__global_job_handle) {
69 TerminateJobObject(processx__global_job_handle, 1);
70 CloseHandle(processx__global_job_handle);
71 processx__global_job_handle = NULL;
72 }
73 return R_NilValue;
74 }
75
processx__utf8_to_utf16_alloc(const char * s,WCHAR ** ws_ptr)76 int processx__utf8_to_utf16_alloc(const char* s, WCHAR** ws_ptr) {
77 int ws_len, r;
78 WCHAR* ws;
79
80 ws_len = MultiByteToWideChar(
81 /* CodePage = */ CP_UTF8,
82 /* dwFlags = */ 0,
83 /* lpMultiByteStr = */ s,
84 /* cbMultiByte = */ -1,
85 /* lpWideCharStr = */ NULL,
86 /* cchWideChar = */ 0);
87
88 if (ws_len <= 0) { return GetLastError(); }
89
90 ws = (WCHAR*) R_alloc(ws_len, sizeof(WCHAR));
91 if (ws == NULL) { return ERROR_OUTOFMEMORY; }
92
93 r = MultiByteToWideChar(
94 /* CodePage = */ CP_UTF8,
95 /* dwFlags = */ 0,
96 /* lpMultiByteStr = */ s,
97 /* cbMultiBytes = */ -1,
98 /* lpWideCharStr = */ ws,
99 /* cchWideChar = */ ws_len);
100
101 if (r != ws_len) {
102 R_THROW_ERROR("processx error interpreting UTF8 command or arguments: '%s'", s);
103 }
104
105 *ws_ptr = ws;
106 return 0;
107 }
108
processx__quote_cmd_arg(const WCHAR * source,WCHAR * target)109 WCHAR* processx__quote_cmd_arg(const WCHAR *source, WCHAR *target) {
110 size_t len = wcslen(source);
111 size_t i;
112 int quote_hit;
113 WCHAR* start;
114
115 if (len == 0) {
116 /* Need double quotation for empty argument */
117 *(target++) = L'"';
118 *(target++) = L'"';
119 return target;
120 }
121
122 if (NULL == wcspbrk(source, L" \t\"")) {
123 /* No quotation needed */
124 wcsncpy(target, source, len);
125 target += len;
126 return target;
127 }
128
129 if (NULL == wcspbrk(source, L"\"\\")) {
130 /*
131 * No embedded double quotes or backlashes, so I can just wrap
132 * quote marks around the whole thing.
133 */
134 *(target++) = L'"';
135 wcsncpy(target, source, len);
136 target += len;
137 *(target++) = L'"';
138 return target;
139 }
140
141 /*
142 * Expected input/output:
143 * input : hello"world
144 * output: "hello\"world"
145 * input : hello""world
146 * output: "hello\"\"world"
147 * input : hello\world
148 * output: hello\world
149 * input : hello\\world
150 * output: hello\\world
151 * input : hello\"world
152 * output: "hello\\\"world"
153 * input : hello\\"world
154 * output: "hello\\\\\"world"
155 * input : hello world\
156 * output: "hello world\\"
157 */
158
159 *(target++) = L'"';
160 start = target;
161 quote_hit = 1;
162
163 for (i = len; i > 0; --i) {
164 *(target++) = source[i - 1];
165
166 if (quote_hit && source[i - 1] == L'\\') {
167 *(target++) = L'\\';
168 } else if(source[i - 1] == L'"') {
169 quote_hit = 1;
170 *(target++) = L'\\';
171 } else {
172 quote_hit = 0;
173 }
174 }
175 target[0] = L'\0';
176 wcsrev(start);
177 *(target++) = L'"';
178 return target;
179 }
180
processx__make_program_args(SEXP args,int verbatim_arguments,WCHAR ** dst_ptr)181 static int processx__make_program_args(SEXP args, int verbatim_arguments,
182 WCHAR **dst_ptr) {
183 const char* arg;
184 WCHAR* dst = NULL;
185 WCHAR* temp_buffer = NULL;
186 size_t dst_len = 0;
187 size_t temp_buffer_len = 0;
188 WCHAR* pos;
189 int arg_count = LENGTH(args);
190 int err = 0;
191 int i;
192
193 /* Count the required size. */
194 for (i = 0; i < arg_count; i++) {
195 DWORD arg_len;
196 arg = CHAR(STRING_ELT(args, i));
197
198 arg_len = MultiByteToWideChar(
199 /* CodePage = */ CP_UTF8,
200 /* dwFlags = */ 0,
201 /* lpMultiByteStr = */ arg,
202 /* cbMultiBytes = */ -1,
203 /* lpWideCharStr = */ NULL,
204 /* cchWideChar = */ 0);
205
206 if (arg_len == 0) { return GetLastError(); }
207
208 dst_len += arg_len;
209
210 if (arg_len > temp_buffer_len) { temp_buffer_len = arg_len; }
211 }
212
213 /* Adjust for potential quotes. Also assume the worst-case scenario */
214 /* that every character needs escaping, so we need twice as much space. */
215 dst_len = dst_len * 2 + arg_count * 2;
216
217 /* Allocate buffer for the final command line. */
218 dst = (WCHAR*) R_alloc(dst_len, sizeof(WCHAR));
219
220 /* Allocate temporary working buffer. */
221 temp_buffer = (WCHAR*) R_alloc(temp_buffer_len, sizeof(WCHAR));
222
223 pos = dst;
224 for (i = 0; i < arg_count; i++) {
225 DWORD arg_len;
226 arg = CHAR(STRING_ELT(args, i));
227
228 /* Convert argument to wide char. */
229 arg_len = MultiByteToWideChar(
230 /* CodePage = */ CP_UTF8,
231 /* dwFlags = */ 0,
232 /* lpMultiByteStr = */ arg,
233 /* cbMultiBytes = */ -1,
234 /* lpWideCharStr = */ temp_buffer,
235 /* cchWideChar = */ (int) (dst + dst_len - pos));
236
237 if (arg_len == 0) {
238 err = GetLastError();
239 goto error;
240 }
241
242 if (verbatim_arguments) {
243 /* Copy verbatim. */
244 wcscpy(pos, temp_buffer);
245 pos += arg_len - 1;
246 } else {
247 /* Quote/escape, if needed. */
248 pos = processx__quote_cmd_arg(temp_buffer, pos);
249 }
250
251 *pos++ = i < arg_count - 1 ? L' ' : L'\0';
252 }
253
254 *dst_ptr = dst;
255 return 0;
256
257 error:
258 return err;
259 }
260
261 /*
262 * The way windows takes environment variables is different than what C does;
263 * Windows wants a contiguous block of null-terminated strings, terminated
264 * with an additional null.
265 *
266 * Windows has a few "essential" environment variables. winsock will fail
267 * to initialize if SYSTEMROOT is not defined; some APIs make reference to
268 * TEMP. SYSTEMDRIVE is probably also important. We therefore ensure that
269 * these get defined if the input environment block does not contain any
270 * values for them.
271 *
272 * Also add variables known to Cygwin to be required for correct
273 * subprocess operation in many cases:
274 * https://github.com/Alexpux/Cygwin/blob/b266b04fbbd3a595f02ea149e4306d3ab9b1fe3d/winsup/cygwin/environ.cc#L955
275 *
276 */
277
278 typedef struct env_var {
279 const WCHAR* const wide;
280 const WCHAR* const wide_eq;
281 const size_t len; /* including null or '=' */
282 } env_var_t;
283
284 #define E_V(str) { L##str, L##str L"=", sizeof(str) }
285
286 static const env_var_t required_vars[] = { /* keep me sorted */
287 E_V("HOMEDRIVE"),
288 E_V("HOMEPATH"),
289 E_V("LOGONSERVER"),
290 E_V("PATH"),
291 E_V("SYSTEMDRIVE"),
292 E_V("SYSTEMROOT"),
293 E_V("TEMP"),
294 E_V("USERDOMAIN"),
295 E_V("USERNAME"),
296 E_V("USERPROFILE"),
297 E_V("WINDIR"),
298 };
299 static size_t n_required_vars = ARRAY_SIZE(required_vars);
300
env_strncmp(const wchar_t * a,int na,const wchar_t * b)301 int env_strncmp(const wchar_t* a, int na, const wchar_t* b) {
302 wchar_t* a_eq;
303 wchar_t* b_eq;
304 wchar_t* A;
305 wchar_t* B;
306 int nb;
307 int r;
308
309 if (na < 0) {
310 a_eq = wcschr(a, L'=');
311 na = (int)(long)(a_eq - a);
312 } else {
313 na--;
314 }
315 b_eq = wcschr(b, L'=');
316 nb = b_eq - b;
317
318 A = alloca((na+1) * sizeof(wchar_t));
319 B = alloca((nb+1) * sizeof(wchar_t));
320
321 r = LCMapStringW(LOCALE_INVARIANT, LCMAP_UPPERCASE, a, na, A, na);
322 if (!r) R_THROW_SYSTEM_ERROR("make environment for process");
323 A[na] = L'\0';
324 r = LCMapStringW(LOCALE_INVARIANT, LCMAP_UPPERCASE, b, nb, B, nb);
325 if (!r) R_THROW_SYSTEM_ERROR("make environment for process");
326 B[nb] = L'\0';
327
328 while (1) {
329 wchar_t AA = *A++;
330 wchar_t BB = *B++;
331 if (AA < BB) {
332 return -1;
333 } else if (AA > BB) {
334 return 1;
335 } else if (!AA && !BB) {
336 return 0;
337 }
338 }
339 }
340
qsort_wcscmp(const void * a,const void * b)341 static int qsort_wcscmp(const void *a, const void *b) {
342 wchar_t* astr = *(wchar_t* const*)a;
343 wchar_t* bstr = *(wchar_t* const*)b;
344 return env_strncmp(astr, -1, bstr);
345 }
346
processx__add_tree_id_env(const char * ctree_id,WCHAR ** dst_ptr)347 static int processx__add_tree_id_env(const char *ctree_id, WCHAR **dst_ptr) {
348 WCHAR *env = GetEnvironmentStringsW();
349 int len = 0, len2 = 0;
350 WCHAR *ptr = env;
351 WCHAR *id = 0;
352 int err;
353 int idlen;
354 WCHAR *dst_copy;
355
356 if (!env) return GetLastError();
357
358 err = processx__utf8_to_utf16_alloc(ctree_id, &id);
359 if (err) {
360 FreeEnvironmentStringsW(env);
361 return(err);
362 }
363
364 while (1) {
365 WCHAR *prev = ptr;
366 if (!*ptr) break;
367 while (*ptr) ptr++;
368 ptr++;
369 len += (ptr - prev);
370 }
371
372 /* Plus the id */
373 idlen = wcslen(id) + 1;
374 len2 = len + idlen;
375
376 /* Allocate, copy */
377 dst_copy = (WCHAR*) R_alloc(len2 + 1, sizeof(WCHAR)); /* +1 for final zero */
378 memcpy(dst_copy, env, len * sizeof(WCHAR));
379 memcpy(dst_copy + len, id, idlen * sizeof(WCHAR));
380
381 /* Final \0 */
382 *(dst_copy + len2) = L'\0';
383 *dst_ptr = dst_copy;
384
385 FreeEnvironmentStringsW(env);
386 return 0;
387 }
388
processx__make_program_env(SEXP env_block,const char * tree_id,WCHAR ** dst_ptr,const char * cname)389 static int processx__make_program_env(SEXP env_block, const char *tree_id,
390 WCHAR** dst_ptr, const char *cname) {
391 WCHAR* dst;
392 WCHAR* ptr;
393 size_t env_len = 0;
394 int len;
395 size_t i;
396 DWORD var_size;
397 size_t env_block_count = 1; /* 1 for null-terminator */
398 WCHAR* dst_copy;
399 WCHAR** ptr_copy;
400 WCHAR** env_copy;
401 DWORD* required_vars_value_len = alloca(n_required_vars * sizeof(DWORD*));
402 int j, num = LENGTH(env_block);
403
404 /* first pass: determine size in UTF-16 */
405 for (j = 0; j < num; j++) {
406 const char *env = CHAR(STRING_ELT(env_block, j));
407 if (strchr(env, '=')) {
408 len = MultiByteToWideChar(CP_UTF8,
409 0,
410 env,
411 -1,
412 NULL,
413 0);
414 if (len <= 0) {
415 return GetLastError();
416 }
417 env_len += len;
418 env_block_count++;
419 }
420 }
421
422 /* Plus the tree id */
423 len = MultiByteToWideChar(CP_UTF8, 0, tree_id, -1, NULL, 0);
424 if (len <= 0) return GetLastError();
425 env_len += len;
426 env_block_count++;
427
428 /* second pass: copy to UTF-16 environment block */
429 dst_copy = (WCHAR*) R_alloc(env_len, sizeof(WCHAR));
430 env_copy = alloca(env_block_count * sizeof(WCHAR*));
431
432 ptr = dst_copy;
433 ptr_copy = env_copy;
434 for (j = 0; j < num; j++) {
435 const char *env = CHAR(STRING_ELT(env_block, j));
436 if (strchr(env, '=')) {
437 len = MultiByteToWideChar(CP_UTF8,
438 0,
439 env,
440 -1,
441 ptr,
442 (int) (env_len - (ptr - dst_copy)));
443 if (len <= 0) {
444 DWORD err = GetLastError();
445 return err;
446 }
447 *ptr_copy++ = ptr;
448 ptr += len;
449 }
450 }
451 /* Plus the tree id */
452 len = MultiByteToWideChar(CP_UTF8, 0, tree_id, -1, ptr,
453 (int) (env_len - (ptr - dst_copy)));
454 if (len <= 0) return GetLastError();
455 *ptr_copy++ = ptr;
456 ptr += len;
457
458 *ptr_copy = NULL;
459
460 /* sort our (UTF-16) copy */
461 qsort(env_copy, env_block_count-1, sizeof(wchar_t*), qsort_wcscmp);
462
463 /* third pass: check for required variables */
464 for (ptr_copy = env_copy, i = 0; i < n_required_vars; ) {
465 int cmp;
466 if (!*ptr_copy) {
467 cmp = -1;
468 } else {
469 cmp = env_strncmp(required_vars[i].wide_eq,
470 required_vars[i].len,
471 *ptr_copy);
472 }
473 if (cmp < 0) {
474 /* missing required var */
475 var_size = GetEnvironmentVariableW(required_vars[i].wide, NULL, 0);
476 required_vars_value_len[i] = var_size;
477 if (var_size != 0) {
478 env_len += required_vars[i].len;
479 env_len += var_size;
480 }
481 i++;
482 } else {
483 ptr_copy++;
484 if (cmp == 0)
485 i++;
486 }
487 }
488
489 /* final pass: copy, in sort order, and inserting required variables */
490 dst = (WCHAR*) R_alloc(1 + env_len, sizeof(WCHAR));
491
492 for (ptr = dst, ptr_copy = env_copy, i = 0;
493 *ptr_copy || i < n_required_vars;
494 ptr += len) {
495 int cmp;
496 if (i >= n_required_vars) {
497 cmp = 1;
498 } else if (!*ptr_copy) {
499 cmp = -1;
500 } else {
501 cmp = env_strncmp(required_vars[i].wide_eq,
502 required_vars[i].len,
503 *ptr_copy);
504 }
505 if (cmp < 0) {
506 /* missing required var */
507 len = required_vars_value_len[i];
508 if (len) {
509 wcscpy(ptr, required_vars[i].wide_eq);
510 ptr += required_vars[i].len;
511 var_size = GetEnvironmentVariableW(required_vars[i].wide,
512 ptr,
513 (int) (env_len - (ptr - dst)));
514 if (var_size != len-1) { /* race condition? */
515 R_THROW_SYSTEM_ERROR("GetEnvironmentVariableW for process '%s'",
516 cname);
517 }
518 }
519 i++;
520 } else {
521 /* copy var from env_block */
522 len = wcslen(*ptr_copy) + 1;
523 wmemcpy(ptr, *ptr_copy, len);
524 ptr_copy++;
525 if (cmp == 0)
526 i++;
527 }
528 }
529
530 /* Terminate with an extra NULL. */
531 *ptr = L'\0';
532
533 *dst_ptr = dst;
534 return 0;
535 }
536
processx__search_path_join_test(const WCHAR * dir,size_t dir_len,const WCHAR * name,size_t name_len,const WCHAR * ext,size_t ext_len,const WCHAR * cwd,size_t cwd_len)537 static WCHAR* processx__search_path_join_test(const WCHAR* dir,
538 size_t dir_len,
539 const WCHAR* name,
540 size_t name_len,
541 const WCHAR* ext,
542 size_t ext_len,
543 const WCHAR* cwd,
544 size_t cwd_len) {
545 WCHAR *result, *result_pos;
546 DWORD attrs;
547 if (dir_len > 2 && dir[0] == L'\\' && dir[1] == L'\\') {
548 /* It's a UNC path so ignore cwd */
549 cwd_len = 0;
550 } else if (dir_len >= 1 && (dir[0] == L'/' || dir[0] == L'\\')) {
551 /* It's a full path without drive letter, use cwd's drive letter only */
552 cwd_len = 2;
553 } else if (dir_len >= 2 && dir[1] == L':' &&
554 (dir_len < 3 || (dir[2] != L'/' && dir[2] != L'\\'))) {
555 /* It's a relative path with drive letter (ext.g. D:../some/file)
556 * Replace drive letter in dir by full cwd if it points to the same drive,
557 * otherwise use the dir only.
558 */
559 if (cwd_len < 2 || _wcsnicmp(cwd, dir, 2) != 0) {
560 cwd_len = 0;
561 } else {
562 dir += 2;
563 dir_len -= 2;
564 }
565 } else if (dir_len > 2 && dir[1] == L':') {
566 /* It's an absolute path with drive letter
567 * Don't use the cwd at all
568 */
569 cwd_len = 0;
570 }
571
572 /* Allocate buffer for output */
573 result = result_pos = (WCHAR*) R_alloc(
574 (cwd_len + 1 + dir_len + 1 + name_len + 1 + ext_len + 1),
575 sizeof(WCHAR));
576
577 /* Copy cwd */
578 wcsncpy(result_pos, cwd, cwd_len);
579 result_pos += cwd_len;
580
581 /* Add a path separator if cwd didn't end with one */
582 if (cwd_len && wcsrchr(L"\\/:", result_pos[-1]) == NULL) {
583 result_pos[0] = L'\\';
584 result_pos++;
585 }
586
587 /* Copy dir */
588 wcsncpy(result_pos, dir, dir_len);
589 result_pos += dir_len;
590
591 /* Add a separator if the dir didn't end with one */
592 if (dir_len && wcsrchr(L"\\/:", result_pos[-1]) == NULL) {
593 result_pos[0] = L'\\';
594 result_pos++;
595 }
596
597 /* Copy filename */
598 wcsncpy(result_pos, name, name_len);
599 result_pos += name_len;
600
601 if (ext_len) {
602 /* Add a dot if the filename didn't end with one */
603 if (name_len && result_pos[-1] != '.') {
604 result_pos[0] = L'.';
605 result_pos++;
606 }
607
608 /* Copy extension */
609 wcsncpy(result_pos, ext, ext_len);
610 result_pos += ext_len;
611 }
612
613 /* Null terminator */
614 result_pos[0] = L'\0';
615
616 attrs = GetFileAttributesW(result);
617
618 if (attrs != INVALID_FILE_ATTRIBUTES &&
619 !(attrs & FILE_ATTRIBUTE_DIRECTORY)) {
620 return result;
621 }
622
623 return NULL;
624 }
625
626
627 /*
628 * Helper function for search_path
629 */
processx__path_search_walk_ext(const WCHAR * dir,size_t dir_len,const WCHAR * name,size_t name_len,WCHAR * cwd,size_t cwd_len,int name_has_ext)630 static WCHAR* processx__path_search_walk_ext(const WCHAR *dir,
631 size_t dir_len,
632 const WCHAR *name,
633 size_t name_len,
634 WCHAR *cwd,
635 size_t cwd_len,
636 int name_has_ext) {
637 WCHAR* result;
638
639 /* If the name itself has a nonempty extension, try this extension first */
640 if (name_has_ext) {
641 result = processx__search_path_join_test(dir, dir_len,
642 name, name_len,
643 L"", 0,
644 cwd, cwd_len);
645 if (result != NULL) {
646 return result;
647 }
648 }
649
650 /* Try .com extension */
651 result = processx__search_path_join_test(dir, dir_len,
652 name, name_len,
653 L"com", 3,
654 cwd, cwd_len);
655 if (result != NULL) {
656 return result;
657 }
658
659 /* Try .exe extension */
660 result = processx__search_path_join_test(dir, dir_len,
661 name, name_len,
662 L"exe", 3,
663 cwd, cwd_len);
664 if (result != NULL) {
665 return result;
666 }
667
668 return NULL;
669 }
670
671
672 /*
673 * search_path searches the system path for an executable filename -
674 * the windows API doesn't provide this as a standalone function nor as an
675 * option to CreateProcess.
676 *
677 * It tries to return an absolute filename.
678 *
679 * Furthermore, it tries to follow the semantics that cmd.exe, with this
680 * exception that PATHEXT environment variable isn't used. Since CreateProcess
681 * can start only .com and .exe files, only those extensions are tried. This
682 * behavior equals that of msvcrt's spawn functions.
683 *
684 * - Do not search the path if the filename already contains a path (either
685 * relative or absolute).
686 *
687 * - If there's really only a filename, check the current directory for file,
688 * then search all path directories.
689 *
690 * - If filename specified has *any* extension, search for the file with the
691 * specified extension first.
692 *
693 * - If the literal filename is not found in a directory, try *appending*
694 * (not replacing) .com first and then .exe.
695 *
696 * - The path variable may contain relative paths; relative paths are relative
697 * to the cwd.
698 *
699 * - Directories in path may or may not end with a trailing backslash.
700 *
701 * - CMD does not trim leading/trailing whitespace from path/pathex entries
702 * nor from the environment variables as a whole.
703 *
704 * - When cmd.exe cannot read a directory, it will just skip it and go on
705 * searching. However, unlike posix-y systems, it will happily try to run a
706 * file that is not readable/executable; if the spawn fails it will not
707 * continue searching.
708 *
709 * UNC path support: we are dealing with UNC paths in both the path and the
710 * filename. This is a deviation from what cmd.exe does (it does not let you
711 * start a program by specifying an UNC path on the command line) but this is
712 * really a pointless restriction.
713 *
714 */
processx__search_path(const WCHAR * file,WCHAR * cwd,const WCHAR * path)715 static WCHAR* processx__search_path(const WCHAR *file,
716 WCHAR *cwd,
717 const WCHAR *path) {
718 int file_has_dir;
719 WCHAR* result = NULL;
720 WCHAR *file_name_start;
721 WCHAR *dot;
722 const WCHAR *dir_start, *dir_end, *dir_path;
723 size_t dir_len;
724 int name_has_ext;
725
726 size_t file_len = wcslen(file);
727 size_t cwd_len = wcslen(cwd);
728
729 /* If the caller supplies an empty filename,
730 * we're not gonna return c:\windows\.exe -- GFY!
731 */
732 if (file_len == 0
733 || (file_len == 1 && file[0] == L'.')) {
734 return NULL;
735 }
736
737 /* Find the start of the filename so we can split the directory from the */
738 /* name. */
739 for (file_name_start = (WCHAR*)file + file_len;
740 file_name_start > file
741 && file_name_start[-1] != L'\\'
742 && file_name_start[-1] != L'/'
743 && file_name_start[-1] != L':';
744 file_name_start--);
745
746 file_has_dir = file_name_start != file;
747
748 /* Check if the filename includes an extension */
749 dot = wcschr(file_name_start, L'.');
750 name_has_ext = (dot != NULL && dot[1] != L'\0');
751
752 if (file_has_dir) {
753 /* The file has a path inside, don't use path */
754 result = processx__path_search_walk_ext(
755 file, file_name_start - file,
756 file_name_start, file_len - (file_name_start - file),
757 cwd, cwd_len,
758 name_has_ext);
759
760 } else {
761 dir_end = path;
762
763 /* The file is really only a name; look in cwd first, then scan path */
764 result = processx__path_search_walk_ext(L"", 0,
765 file, file_len,
766 cwd, cwd_len,
767 name_has_ext);
768
769 while (result == NULL) {
770 if (*dir_end == L'\0') {
771 break;
772 }
773
774 /* Skip the separator that dir_end now points to */
775 if (dir_end != path || *path == L';') {
776 dir_end++;
777 }
778
779 /* Next slice starts just after where the previous one ended */
780 dir_start = dir_end;
781
782 /* Slice until the next ; or \0 is found */
783 dir_end = wcschr(dir_start, L';');
784 if (dir_end == NULL) {
785 dir_end = wcschr(dir_start, L'\0');
786 }
787
788 /* If the slice is zero-length, don't bother */
789 if (dir_end - dir_start == 0) {
790 continue;
791 }
792
793 dir_path = dir_start;
794 dir_len = dir_end - dir_start;
795
796 /* Adjust if the path is quoted. */
797 if (dir_path[0] == '"' || dir_path[0] == '\'') {
798 ++dir_path;
799 --dir_len;
800 }
801
802 if (dir_path[dir_len - 1] == '"' || dir_path[dir_len - 1] == '\'') {
803 --dir_len;
804 }
805
806 result = processx__path_search_walk_ext(dir_path, dir_len,
807 file, file_len,
808 cwd, cwd_len,
809 name_has_ext);
810 }
811 }
812
813 return result;
814 }
815
816 void processx__collect_exit_status(SEXP status, DWORD exitcode);
817
processx__terminate(processx_handle_t * handle,SEXP status)818 DWORD processx__terminate(processx_handle_t *handle, SEXP status) {
819 DWORD err;
820
821 err = TerminateProcess(handle->hProcess, 2);
822 if (err) processx__collect_exit_status(status, 2);
823
824 WaitForSingleObject(handle->hProcess, INFINITE);
825 CloseHandle(handle->hProcess);
826 handle->hProcess = 0;
827 return err;
828 }
829
processx__finalizer(SEXP status)830 void processx__finalizer(SEXP status) {
831 processx_handle_t *handle = (processx_handle_t*) R_ExternalPtrAddr(status);
832
833 if (!handle) return;
834
835 if (handle->cleanup && !handle->collected) {
836 /* Just in case it is running */
837 processx__terminate(handle, status);
838 }
839
840 if (handle->hProcess) CloseHandle(handle->hProcess);
841 handle->hProcess = NULL;
842 R_ClearExternalPtr(status);
843 processx__handle_destroy(handle);
844 }
845
processx__make_handle(SEXP private,int cleanup)846 SEXP processx__make_handle(SEXP private, int cleanup) {
847 processx_handle_t * handle;
848 SEXP result;
849
850 handle = (processx_handle_t*) malloc(sizeof(processx_handle_t));
851 if (!handle) { R_THROW_ERROR("Out of memory when creating subprocess"); }
852 memset(handle, 0, sizeof(processx_handle_t));
853
854 result = PROTECT(R_MakeExternalPtr(handle, private, R_NilValue));
855 R_RegisterCFinalizerEx(result, processx__finalizer, 1);
856 handle->cleanup = cleanup;
857
858 UNPROTECT(1);
859 return result;
860 }
861
processx__handle_destroy(processx_handle_t * handle)862 void processx__handle_destroy(processx_handle_t *handle) {
863 if (!handle) return;
864 if (handle->child_stdio_buffer) free(handle->child_stdio_buffer);
865 free(handle);
866 }
867
processx_exec(SEXP command,SEXP args,SEXP pty,SEXP pty_options,SEXP connections,SEXP env,SEXP windows_verbatim_args,SEXP windows_hide,SEXP windows_detached_process,SEXP private,SEXP cleanup,SEXP wd,SEXP encoding,SEXP tree_id)868 SEXP processx_exec(SEXP command, SEXP args, SEXP pty, SEXP pty_options,
869 SEXP connections, SEXP env, SEXP windows_verbatim_args,
870 SEXP windows_hide, SEXP windows_detached_process,
871 SEXP private, SEXP cleanup, SEXP wd, SEXP encoding,
872 SEXP tree_id) {
873
874 const char *ccommand = CHAR(STRING_ELT(command, 0));
875 const char *cencoding = CHAR(STRING_ELT(encoding, 0));
876 const char *ccwd = isNull(wd) ? 0 : CHAR(STRING_ELT(wd, 0));
877 const char *ctree_id = CHAR(STRING_ELT(tree_id, 0));
878
879 int err = 0;
880 WCHAR *path;
881 WCHAR *application_path = NULL, *application = NULL, *arguments = NULL,
882 *cenv = NULL, *cwd = NULL;
883 processx_options_t options;
884 STARTUPINFOW startup = { 0 };
885 PROCESS_INFORMATION info = { 0 };
886 DWORD process_flags;
887
888 processx_handle_t *handle;
889 int ccleanup = INTEGER(cleanup)[0];
890 SEXP result;
891 DWORD dwerr;
892
893 options.windows_verbatim_args = LOGICAL(windows_verbatim_args)[0];
894 options.windows_hide = LOGICAL(windows_hide)[0];
895
896 err = processx__utf8_to_utf16_alloc(CHAR(STRING_ELT(command, 0)),
897 &application);
898 if (err) {
899 R_THROW_SYSTEM_ERROR_CODE(err, "utf8 -> utf16 conversion '%s'",
900 ccommand);
901 }
902
903 err = processx__make_program_args(
904 args,
905 options.windows_verbatim_args,
906 &arguments);
907 if (err) { R_THROW_SYSTEM_ERROR_CODE(err, "making program args '%s'", ccommand); }
908
909 if (isNull(env)) {
910 err = processx__add_tree_id_env(ctree_id, &cenv);
911 } else {
912 err = processx__make_program_env(env, ctree_id, &cenv, ccommand);
913 }
914 if (err) R_THROW_SYSTEM_ERROR_CODE(err, "making environment '%s'", ccommand);
915
916 if (ccwd) {
917 /* Explicit cwd */
918 err = processx__utf8_to_utf16_alloc(ccwd, &cwd);
919 if (err) {
920 R_THROW_SYSTEM_ERROR("convert current directory encoding '%s'", ccommand);
921 }
922
923 } else {
924 /* Inherit cwd */
925 DWORD cwd_len, r;
926
927 cwd_len = GetCurrentDirectoryW(0, NULL);
928 if (!cwd_len) {
929 R_THROW_SYSTEM_ERROR("get current directory length '%s'", ccommand);
930 }
931
932 cwd = (WCHAR*) R_alloc(cwd_len, sizeof(WCHAR));
933
934 r = GetCurrentDirectoryW(cwd_len, cwd);
935 if (r == 0 || r >= cwd_len) {
936 R_THROW_SYSTEM_ERROR("get current directory '%s'", ccommand);
937 }
938 }
939
940 /* Get PATH environment variable */
941 {
942 DWORD path_len, r;
943
944 path_len = GetEnvironmentVariableW(L"PATH", NULL, 0);
945 if (!path_len) {
946 R_THROW_SYSTEM_ERROR("get env var length '%s'", ccommand);
947 }
948
949 path = (WCHAR*) R_alloc(path_len, sizeof(WCHAR));
950
951 r = GetEnvironmentVariableW(L"PATH", path, path_len);
952 if (r == 0 || r >= path_len) {
953 R_THROW_SYSTEM_ERROR("get PATH env var for '%s'", ccommand);
954 }
955 }
956
957 result = PROTECT(processx__make_handle(private, ccleanup));
958 handle = R_ExternalPtrAddr(result);
959
960 int inherit_std = 0;
961 err = processx__stdio_create(handle, connections,
962 &handle->child_stdio_buffer, private,
963 cencoding, ccommand, &inherit_std);
964 if (err) { R_THROW_SYSTEM_ERROR_CODE(err, "setup stdio for '%s'", ccommand); }
965
966 application_path = processx__search_path(application, cwd, path);
967
968 /* If a UNC Path, then we try to flip the forward slashes, if any.
969 * It is apparently enough to flip the first two slashes, the rest
970 * are not important. */
971 if (! application_path && wcslen(path) >= 2 &&
972 application[0] == L'/' && application[1] == L'/') {
973 application[0] = L'\\';
974 application[1] = L'\\';
975 application_path = processx__search_path(application, cwd, path);
976 }
977
978 if (!application_path) {
979 R_ClearExternalPtr(result);
980 processx__stdio_destroy(handle->child_stdio_buffer);
981 free(handle);
982 R_THROW_ERROR("Command '%s' not found", ccommand);
983 }
984
985 startup.cb = sizeof(startup);
986 startup.lpReserved = NULL;
987 startup.lpDesktop = NULL;
988 startup.lpTitle = NULL;
989 startup.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
990
991 startup.cbReserved2 = processx__stdio_size(handle->child_stdio_buffer);
992 startup.lpReserved2 = (BYTE*) handle->child_stdio_buffer;
993
994 startup.hStdInput = processx__stdio_handle(handle->child_stdio_buffer, 0);
995 startup.hStdOutput = processx__stdio_handle(handle->child_stdio_buffer, 1);
996 startup.hStdError = processx__stdio_handle(handle->child_stdio_buffer, 2);
997 startup.wShowWindow = options.windows_hide ? SW_HIDE : SW_SHOWDEFAULT;
998
999 process_flags = CREATE_UNICODE_ENVIRONMENT | CREATE_SUSPENDED;
1000
1001 /* We only use CREATE_NO_WINDOW if none of stdin, stdout and stderr
1002 * are inherited, because if there is no window, then inherited
1003 * handles do not work. Other inherited handles should be fine,
1004 * I think. See https://github.com/gaborcsardi/win32-console-docs
1005 * for more about CREATE_NO_WINDOW. */
1006
1007 if (! inherit_std) process_flags |= CREATE_NO_WINDOW;
1008
1009 if (!ccleanup) {
1010 /* Note that we're not setting the CREATE_BREAKAWAY_FROM_JOB flag. That
1011 * means that processx might not let you create a fully deamonized
1012 * process when run under job control. However the type of job control
1013 * that processx itself creates doesn't trickle down to subprocesses
1014 * so they can still daemonize.
1015 *
1016 * A reason to not do this is that CREATE_BREAKAWAY_FROM_JOB makes the
1017 * CreateProcess call fail if we're under job control that doesn't
1018 * allow breakaway.
1019 */
1020
1021 process_flags |= CREATE_NEW_PROCESS_GROUP;
1022 }
1023
1024 if (LOGICAL(windows_detached_process)[0]) {
1025 process_flags |= DETACHED_PROCESS;
1026 }
1027
1028 err = CreateProcessW(
1029 /* lpApplicationName = */ application_path,
1030 /* lpCommandLine = */ arguments,
1031 /* lpProcessAttributes = */ NULL,
1032 /* lpThreadAttributes = */ NULL,
1033 /* bInheritHandles = */ 1,
1034 /* dwCreationFlags = */ process_flags,
1035 /* lpEnvironment = */ cenv,
1036 /* lpCurrentDirectory = */ cwd,
1037 /* lpStartupInfo = */ &startup,
1038 /* lpProcessInformation = */ &info);
1039
1040 if (!err) { R_THROW_SYSTEM_ERROR("create process '%s'", ccommand); }
1041
1042 handle->hProcess = info.hProcess;
1043 handle->dwProcessId = info.dwProcessId;
1044
1045 /* Query official creation time. On Windows this is not used as
1046 an id, since the pid itself is valid until the process handle
1047 is released. */
1048 handle->create_time = processx__create_time(handle->hProcess);
1049
1050 /* If the process isn't spawned as detached, assign to the global job */
1051 /* object so windows will kill it when the parent process dies. */
1052 if (ccleanup) {
1053 if (! processx__global_job_handle) processx__init_global_job_handle();
1054
1055 if (!AssignProcessToJobObject(processx__global_job_handle, info.hProcess)) {
1056 /* AssignProcessToJobObject might fail if this process is under job
1057 * control and the job doesn't have the
1058 * JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK flag set, on a Windows
1059 * version that doesn't support nested jobs.
1060 *
1061 * When that happens we just swallow the error and continue without
1062 * establishing a kill-child-on-parent-exit relationship, otherwise
1063 * there would be no way for R/processx applications run under job
1064 * control to spawn processes at all.
1065 */
1066 DWORD err = GetLastError();
1067 if (err != ERROR_ACCESS_DENIED) {
1068 R_THROW_SYSTEM_ERROR_CODE(err, "Assign to job object '%s'",
1069 ccommand);
1070 }
1071 }
1072 }
1073
1074 dwerr = ResumeThread(info.hThread);
1075 if (dwerr == (DWORD) -1) {
1076 R_THROW_SYSTEM_ERROR("resume thread for '%s'", ccommand);
1077 }
1078 CloseHandle(info.hThread);
1079
1080 processx__stdio_destroy(handle->child_stdio_buffer);
1081 handle->child_stdio_buffer = NULL;
1082
1083 UNPROTECT(1);
1084 return result;
1085 }
1086
processx__collect_exit_status(SEXP status,DWORD exitcode)1087 void processx__collect_exit_status(SEXP status, DWORD exitcode) {
1088 processx_handle_t *handle = R_ExternalPtrAddr(status);
1089 handle->exitcode = exitcode;
1090 handle->collected = 1;
1091 }
1092
processx_wait(SEXP status,SEXP timeout,SEXP name)1093 SEXP processx_wait(SEXP status, SEXP timeout, SEXP name) {
1094 int ctimeout = INTEGER(timeout)[0], timeleft = ctimeout;
1095 const char *cname = isNull(name) ? "???" : CHAR(STRING_ELT(name, 0));
1096 processx_handle_t *handle = R_ExternalPtrAddr(status);
1097 DWORD err, err2, exitcode;
1098
1099 if (!handle) return ScalarLogical(1);
1100 if (handle->collected) return ScalarLogical(1);
1101
1102 err2 = WAIT_TIMEOUT;
1103 while (ctimeout < 0 || timeleft > PROCESSX_INTERRUPT_INTERVAL) {
1104 err2 = WaitForSingleObject(handle->hProcess, PROCESSX_INTERRUPT_INTERVAL);
1105 if (err2 != WAIT_TIMEOUT) break;
1106 R_CheckUserInterrupt();
1107 timeleft -= PROCESSX_INTERRUPT_INTERVAL;
1108 }
1109
1110 /* Maybe there is some time left from the timeout */
1111 if (err2 == WAIT_TIMEOUT && timeleft >= 0) {
1112 err2 = WaitForSingleObject(handle->hProcess, timeleft);
1113 }
1114
1115 if (err2 == WAIT_FAILED) {
1116 R_THROW_SYSTEM_ERROR("failed to wait on process '%s'", cname);
1117 } else if (err2 == WAIT_TIMEOUT) {
1118 return ScalarLogical(FALSE);
1119 }
1120
1121 /* Collect */
1122 err = GetExitCodeProcess(handle->hProcess, &exitcode);
1123 if (!err) {
1124 R_THROW_SYSTEM_ERROR("cannot get exit code after wait for '%s'", cname);
1125 }
1126
1127 processx__collect_exit_status(status, exitcode);
1128
1129 return ScalarLogical(TRUE);
1130 }
1131
processx_is_alive(SEXP status,SEXP name)1132 SEXP processx_is_alive(SEXP status, SEXP name) {
1133 processx_handle_t *handle = R_ExternalPtrAddr(status);
1134 const char *cname = isNull(name) ? "???" : CHAR(STRING_ELT(name, 0));
1135 DWORD err, exitcode;
1136
1137 /* This might happen if it was finalized at the end of the session,
1138 even though there are some references to the R object. */
1139 if (!handle) return ScalarLogical(0);
1140
1141 if (handle->collected) return ScalarLogical(0);
1142
1143 /* Otherwise try to get exit code */
1144 err = GetExitCodeProcess(handle->hProcess, &exitcode);
1145 if (!err) {
1146 R_THROW_SYSTEM_ERROR("failed to get exit code to check if ",
1147 "'%s' is alive", cname);
1148 }
1149
1150 if (exitcode == STILL_ACTIVE) {
1151 return ScalarLogical(1);
1152 } else {
1153 processx__collect_exit_status(status, exitcode);
1154 return ScalarLogical(0);
1155 }
1156 }
1157
processx_get_exit_status(SEXP status,SEXP name)1158 SEXP processx_get_exit_status(SEXP status, SEXP name) {
1159 processx_handle_t *handle = R_ExternalPtrAddr(status);
1160 const char *cname = isNull(name) ? "???" : CHAR(STRING_ELT(name, 0));
1161 DWORD err, exitcode;
1162
1163 /* This might happen if it was finalized at the end of the session,
1164 even though there are some references to the R object. */
1165 if (!handle) return R_NilValue;
1166
1167 if (handle->collected) return ScalarInteger(handle->exitcode);
1168
1169 /* Otherwise try to get exit code */
1170 err = GetExitCodeProcess(handle->hProcess, &exitcode);
1171 if (!err) {R_THROW_SYSTEM_ERROR("get exit status failed for '%s'", cname); }
1172
1173 if (exitcode == STILL_ACTIVE) {
1174 return R_NilValue;
1175 } else {
1176 processx__collect_exit_status(status, exitcode);
1177 return ScalarInteger(handle->exitcode);
1178 }
1179 }
1180
processx_signal(SEXP status,SEXP signal,SEXP name)1181 SEXP processx_signal(SEXP status, SEXP signal, SEXP name) {
1182 processx_handle_t *handle = R_ExternalPtrAddr(status);
1183 const char *cname = isNull(name) ? "???" : CHAR(STRING_ELT(name, 0));
1184 DWORD err, exitcode = STILL_ACTIVE;
1185
1186 if (!handle) return ScalarLogical(0);
1187 if (handle->collected) return ScalarLogical(0);
1188
1189 switch (INTEGER(signal)[0]) {
1190
1191 case 15: /* SIGTERM */
1192 case 9: /* SIGKILL */
1193 case 2: { /* SIGINT */
1194 /* Call GetExitCodeProcess to see if it is done */
1195 /* TODO: there is a race condition here, might finish right before
1196 we are terminating it... */
1197 err = GetExitCodeProcess(handle->hProcess, &exitcode);
1198 if (!err) {
1199 R_THROW_SYSTEM_ERROR("get exit code after signal for '%s'", cname);
1200 }
1201
1202 if (exitcode == STILL_ACTIVE) {
1203 err = processx__terminate(handle, status);
1204 return ScalarLogical(err != 0);
1205
1206 } else {
1207 processx__collect_exit_status(status, exitcode);
1208 return ScalarLogical(0);
1209 }
1210 }
1211
1212 case 0: {
1213 /* Health check: is the process still alive? */
1214 err = GetExitCodeProcess(handle->hProcess, &exitcode);
1215 if (!err) {
1216 R_THROW_SYSTEM_ERROR("get exit code for signal 0 for '%s'", cname);
1217 }
1218
1219 if (exitcode == STILL_ACTIVE) {
1220 return ScalarLogical(1);
1221 } else {
1222 return ScalarLogical(0);
1223 }
1224 }
1225
1226 default:
1227 R_THROW_ERROR("Unsupported signal on this platform for '%s'", cname);
1228 return R_NilValue;
1229 }
1230 }
1231
processx_interrupt(SEXP status,SEXP name)1232 SEXP processx_interrupt(SEXP status, SEXP name) {
1233 R_THROW_ERROR("Internal processx error, `processx_interrupt()` should not be called");
1234 return R_NilValue;
1235 }
1236
processx_kill(SEXP status,SEXP grace,SEXP name)1237 SEXP processx_kill(SEXP status, SEXP grace, SEXP name) {
1238 return processx_signal(status, ScalarInteger(9), name);
1239 }
1240
processx_get_pid(SEXP status)1241 SEXP processx_get_pid(SEXP status) {
1242 processx_handle_t *handle = R_ExternalPtrAddr(status);
1243
1244 /* This might happen if it was finalized at the end of the session,
1245 even though there are some references to the R object. */
1246 if (!handle) return ScalarInteger(NA_INTEGER);
1247
1248 return ScalarInteger(handle->dwProcessId);
1249 }
1250
processx__process_exists(SEXP pid)1251 SEXP processx__process_exists(SEXP pid) {
1252 DWORD cpid = INTEGER(pid)[0];
1253 HANDLE proc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, cpid);
1254 if (proc == NULL) {
1255 DWORD err = GetLastError();
1256 if (err == ERROR_INVALID_PARAMETER) return ScalarLogical(0);
1257 R_THROW_SYSTEM_ERROR_CODE(err, "open process '%d' to check if it exists", cpid);
1258 return R_NilValue;
1259 } else {
1260 /* Maybe just finished, and in that case we still have a valid handle.
1261 Let's see if this is the case. */
1262 DWORD exitcode;
1263 DWORD err = GetExitCodeProcess(proc, &exitcode);
1264 CloseHandle(proc);
1265 if (!err) {
1266 R_THROW_SYSTEM_ERROR("get exit code to check if process '%d' exists", cpid);
1267 }
1268 return ScalarLogical(exitcode == STILL_ACTIVE);
1269 }
1270 }
1271