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