1 /********************************************************************************
2 * *
3 * P r o c e s s S u p p o r t *
4 * *
5 *********************************************************************************
6 * Copyright (C) 1997,2021 by Jeroen van der Zijp. All Rights Reserved. *
7 *********************************************************************************
8 * This library is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU Lesser General Public License as published by *
10 * the Free Software Foundation; either version 3 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This library is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU Lesser General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU Lesser General Public License *
19 * along with this program. If not, see <http://www.gnu.org/licenses/> *
20 ********************************************************************************/
21 #include "xincs.h"
22 #include "fxver.h"
23 #include "fxdefs.h"
24 #include "FXElement.h"
25 #include "FXString.h"
26 #include "FXIO.h"
27 #include "FXIODevice.h"
28 #include "FXProcess.h"
29
30 /*
31 Notes:
32 - On Windows, if custom environment is passed, we must merge some stock environment
33 variables in with the custom ones.
34
35 A tentative list (please let me know if this is a complete list):
36
37 Variable: Typical value:
38 ========= =============================================
39 PATH C:\Windows\System32\;C:\Windows\;C:\Windows\System32
40 PATHEXT .COM; .EXE; .BAT; .CMD; .VBS; .VBE; .JS ; .WSF; .WSH
41 COMSPEC C:\Windows\System32\cmd.exe
42 SYSTEMDRIVE C:
43 SYSTEMROOT C:\Windows
44 OS Windows_NT
45 TMP C:\DOCUME~1\{username}\LOCALS~1\TEMP
46 TEMP C:\DOCUME~1\{username}\LOCALS~1\TEMP
47 HOMEDRIVE C:
48 HOMEPATH \Documents and Settings\{username}
49 PROGRAMFILES C:\Program Files
50 COMPUTERNAME {computername}
51 USERNAME {username}
52 USERDOMAIN {servername}
53 USERPROFILE C:\Documents and Settings\{username}
54 ALLUSERSPROFILE C:\Documents and Settings\All Users
55 COMMONPROGRAMFILES C:\Program Files\Common Files
56 NUMBER_OF_PROCESSORS 1
57 PROCESSOR_ARCHITECTURE x86
58 PROCESSOR_IDENTIFIER {name}
59 PROCESSOR_LEVEL {stepping}
60 PROCESSOR_REVISION {flags}
61 WINDIR C:\Windows
62 LOGONSERVER \\{servername}
63
64 These should be copied from the parent process environment settings.
65
66 - On Windows, no file descriptors are inherited from the parent process. We do pass
67 stdin, stdout, stderr.
68
69 - The following reference was used to achieve proper enquoting of commandline parameters
70 to pass along to CreateProcess():
71
72 http://www.daviddeley.com/autohotkey/parameters/parameters.htm
73
74 - On *NIX, we close all file descriptors except the stdin, stdout, stderr.
75
76 - The Microsoft C/C++ Parameter Parsing Rules Rephrased:
77
78 o Parameters are always separated by a space or tab (multiple spaces/tabs OK).
79
80 o If the parameter does not contain any spaces, tabs, or double quotes, then all the characters
81 in the parameter are accepted as is (there is no need to enclose the parameter in double quotes).
82
83 o Enclose spaces and tabs in a double quoted part.
84
85 o A double quoted part can be anywhere within a parameter.
86
87 o 2n backslashes followed by a " produce n backslashes + start/end double quoted part.
88
89 o 2n+1 backslashes followed by a " produce n backslashes + a literal quotation mark
90 n backslashes not followed by a quotation mark produce n backslashes.
91
92 o undocumented rules regarding double quotes:
93
94 Prior to 2008:
95 A " outside a double quoted block starts a double quoted block.
96 A " inside a double quoted block ends the double quoted block.
97 If a closing " is followed immediately by another ", the 2nd " is accepted literally and
98 added to the parameter.
99
100 Post 2008:
101 Outside a double quoted block a " starts a double quoted block.
102 Inside a double quoted block a " followed by a different character (not another ") ends the
103 double quoted block.
104 Inside a double quoted block a " followed immediately by another " (i.e. "") causes a
105 single " to be added to the output, and the double quoted block continues.
106
107 o Thus:
108
109 Use " to start/end a double quoted part
110 Use \" to insert a literal "
111 Use \\" to insert a \ then start or end a double quoted part
112 Use \\\" to insert a literal \"
113 Use \ to insert a literal \
114
115 - On Linux, consider using posix_spawn() instead of the old fork()/exec() pair.
116 This means setting up some posix_spawn_file_actions and posix_spawnattr to
117 deal with file descriptors, signal masks, process groups, etc.
118 Need POSIX >= 2008 for this.
119 */
120
121 using namespace FX;
122
123 /*******************************************************************************/
124
125 namespace FX {
126
127
128 // Initialize process
FXProcess()129 FXProcess::FXProcess():pid(0),input(NULL),output(NULL),errors(NULL){
130 FXTRACE((100,"FXProcess::FXProcess\n"));
131 }
132
133
134 // Return handle of this process object
id() const135 FXProcessID FXProcess::id() const {
136 return pid;
137 }
138
139 #if defined(WIN32)
140
141 // Extra variables
142 static const FXchar* extravars[]={
143 "PATH",
144 "PATHEXT",
145 "COMSPEC",
146 "SYSTEMDRIVE",
147 "SYSTEMROOT",
148 "OS",
149 "TMP",
150 "TEMP",
151 "HOMEDRIVE",
152 "HOMEPATH",
153 "PROGRAMFILES",
154 "COMPUTERNAME",
155 "USERNAME",
156 "USERDOMAIN",
157 "USERPROFILE",
158 "ALLUSERSPROFILE",
159 "COMMONPROGRAMFILES",
160 "NUMBER_OF_PROCESSORS",
161 "PROCESSOR_ARCHITECTURE",
162 "PROCESSOR_IDENTIFIER",
163 "PROCESSOR_LEVEL",
164 "PROCESSOR_REVISION",
165 "WINDIR",
166 "LOGONSERVER"
167 };
168
169
170 // Sort environment variables
comparison(const void * a1,const void * a2)171 static int CDECL comparison(const void *a1, const void *a2){
172 return compare(*((const FXchar**)a1),*((const FXchar**)a2));
173 }
174
175
176 // See if quotes are needed
needquotes(const FXchar * ptr)177 static FXbool needquotes(const FXchar* ptr){
178 FXchar c;
179 while((c=*ptr++)!='\0'){
180 if(c==' ' || c=='"' || c=='\t' || c=='\v' || c=='\n') return true;
181 }
182 return false;
183 }
184
185 #ifdef UNICODE
186
187
188 // Build command line for windows
commandline(const FXchar * const * args)189 static FXnchar* commandline(const FXchar *const *args){
190 FXnchar *result=NULL;
191 if(args){
192 FXint size,s,n,w;
193 const FXchar *ptr;
194 FXnchar *dst;
195 FXbool q;
196 for(size=s=0; (ptr=args[s])!=NULL; ++s){
197 q=needquotes(ptr);
198 if(q) size+=2;
199 n=0;
200 while(1){
201 w=wc(ptr);
202 if(w=='\0'){
203 if(q){ size+=n; }
204 break;
205 }
206 else if(w=='\\'){
207 size++;
208 n++;
209 }
210 else if(w=='"'){
211 size+=n+2;
212 n=0;
213 }
214 else{
215 size+=wc2nc(w);
216 n=0;
217 }
218 ptr=wcinc(ptr);
219 }
220 size++;
221 }
222 if(allocElms(result,size+1)){
223 for(dst=result,s=0; (ptr=args[s])!=NULL; ++s){
224 q=needquotes(ptr);
225 if(q) *dst++='"';
226 n=0;
227 while(1){
228 w=wc(ptr);
229 if(w=='\0'){
230 if(q){ while(--n>=0){ *dst++='\\'; } }
231 break;
232 }
233 else if(w=='\\'){
234 *dst++='\\';
235 n++;
236 }
237 else if(w=='"'){
238 while(--n>=0){ *dst++='\\'; }
239 *dst++='\\';
240 *dst++='"';
241 n=0;
242 }
243 else{
244 dst+=wc2nc(dst,w);
245 n=0;
246 }
247 ptr=wcinc(ptr);
248 }
249 if(q) *dst++='"';
250 *dst++=' ';
251 }
252 *dst='\0';
253 }
254 }
255 return result;
256 }
257
258
259 // Make windows environment block
enviroblock(const FXchar * const * env)260 static FXnchar* enviroblock(const FXchar *const *env){
261 FXnchar* result=NULL;
262 if(env){
263 const FXchar **tmp;
264 FXint size=0,n=0,s,d;
265 while(env[n]) n++;
266 if(callocElms(tmp,n+1)){
267 for(s=0; s<n; ++s){
268 tmp[s]=env[s];
269 size+=utf2ncs(env[s])+1;
270 }
271 qsort((void*)tmp,(size_t)s,sizeof(const FXchar*),comparison);
272 if(allocElms(result,size+2)){
273 for(s=d=0; s<n; ++s){
274 d+=utf2ncs(&result[d],tmp[s],size-d)+1;
275 }
276 result[d+0]=0;
277 result[d+1]=0;
278 }
279 FXASSERT(d<=size+2);
280 freeElms(tmp);
281 }
282 }
283 return result;
284 }
285
286 #else
287
288 // Build command line for windows
commandline(const FXchar * const * args)289 static FXchar* commandline(const FXchar *const *args){
290 FXchar *result=NULL;
291 if(args){
292 FXint size,s,n,w;
293 const FXchar *ptr;
294 FXchar *dst;
295 FXbool q;
296 for(size=s=0; (ptr=args[s])!=NULL; ++s){
297 q=needquotes(ptr);
298 if(q) size+=2;
299 n=0;
300 while(1){
301 w=*ptr++;
302 if(w=='\0'){
303 if(q){ size+=n; }
304 break;
305 }
306 else if(w=='\\'){
307 size++;
308 n++;
309 }
310 else if(w=='"'){
311 size+=n+2;
312 n=0;
313 }
314 else{
315 size++;
316 n=0;
317 }
318 }
319 size++;
320 }
321 if(allocElms(result,size+1)){
322 for(dst=result,s=0; (ptr=args[s])!=NULL; ++s){
323 q=needquotes(ptr);
324 if(q) *dst++='"';
325 n=0;
326 while(1){
327 w=*ptr++;
328 if(w=='\0'){
329 if(q){ while(--n>=0){ *dst++='\\'; } }
330 break;
331 }
332 else if(w=='\\'){
333 *dst++='\\';
334 n++;
335 }
336 else if(w=='"'){
337 while(--n>=0){ *dst++='\\'; }
338 *dst++='\\';
339 *dst++='"';
340 n=0;
341 }
342 else{
343 *dst++=w;
344 n=0;
345 }
346 }
347 if(q) *dst++='"';
348 *dst++=' ';
349 }
350 *dst='\0';
351 }
352 }
353 return result;
354 }
355
356
357 // Make windows environment block
enviroblock(const FXchar * const * env)358 static FXchar* enviroblock(const FXchar *const *env){
359 FXchar* result=NULL;
360 if(env){
361 const FXchar **tmp;
362 FXint size=0,n=0,s,d;
363 while(env[n]) n++;
364 if(callocElms(tmp,n+1)){
365 for(s=0; s<n; ++s){
366 tmp[s]=env[s];
367 size+=strlen(env[s])+1;
368 }
369 qsort((void*)tmp,(size_t)s,sizeof(const FXchar*),comparison);
370 if(allocElms(result,size+2)){
371 for(s=d=0; s<n; ++s){
372 d+=strlen(strncpy(&result[d],tmp[s],size-d))+1;
373 }
374 result[d+0]=0;
375 result[d+1]=0;
376 }
377 FXASSERT(d<=size+2);
378 freeElms(tmp);
379 }
380 }
381 return result;
382 }
383
384 #endif
385
386 #endif
387
388 // Start subprocess
start(const FXchar * exec,const FXchar * const * args,const FXchar * const * env)389 FXbool FXProcess::start(const FXchar* exec,const FXchar *const *args,const FXchar *const *env){
390 FXbool result=false;
391 if(pid==0 && exec && args){
392 #if defined(WIN32)
393 #if defined(UNICODE)
394 FXnchar uniexec[MAXPATHLEN];
395 utf2ncs(uniexec,exec,MAXPATHLEN);
396 if(::GetFileAttributesW(uniexec)!=INVALID_FILE_ATTRIBUTES){
397 PROCESS_INFORMATION pi;
398 STARTUPINFO si;
399
400 // Zero out process info and startup info
401 memset(&pi,0,sizeof(pi));
402 memset(&si,0,sizeof(si));
403
404 // Init startup info
405 si.cb=sizeof(si);
406 si.dwFlags=STARTF_USESTDHANDLES;
407
408 // Stdin was redirected
409 si.hStdInput=GetStdHandle(STD_INPUT_HANDLE);
410 if(input && input->isOpen()){
411 si.hStdInput=input->handle();
412 }
413
414 // Stdout was redirected
415 si.hStdOutput=GetStdHandle(STD_OUTPUT_HANDLE);
416 if(output && output->isOpen()){
417 si.hStdOutput=output->handle();
418 }
419
420 // Stderr was redirected
421 si.hStdError=GetStdHandle(STD_ERROR_HANDLE);
422 if(errors && errors->isOpen()){
423 si.hStdError=errors->handle();
424 }
425
426 // Build wide-character command line
427 FXnchar *command=commandline(args);
428
429 // Build wide-character environment block
430 FXnchar *envir=enviroblock(env);
431
432 // Create process
433 if(CreateProcessW(uniexec,command,NULL,NULL,true,CREATE_UNICODE_ENVIRONMENT,envir,NULL,&si,&pi)){
434 CloseHandle(pi.hThread);
435 pid=pi.hProcess;
436 result=true;
437 }
438
439 // Free command line and environment block
440 freeElms(envir);
441 freeElms(command);
442 }
443 #else
444 if(::GetFileAttributesA(exec)!=INVALID_FILE_ATTRIBUTES){
445 PROCESS_INFORMATION pi;
446 STARTUPINFO si;
447
448 // Zero out process info and startup info
449 memset(&pi,0,sizeof(pi));
450 memset(&si,0,sizeof(si));
451
452 // Init startup info
453 si.cb=sizeof(si);
454 si.dwFlags=STARTF_USESTDHANDLES;
455
456 // Stdin was redirected
457 si.hStdInput=GetStdHandle(STD_INPUT_HANDLE);
458 if(input && input->isOpen()){
459 si.hStdInput=input->handle();
460 }
461
462 // Stdout was redirected
463 si.hStdOutput=GetStdHandle(STD_OUTPUT_HANDLE);
464 if(output && output->isOpen()){
465 si.hStdOutput=output->handle();
466 }
467
468 // Stderr was redirected
469 si.hStdError=GetStdHandle(STD_ERROR_HANDLE);
470 if(errors && errors->isOpen()){
471 si.hStdError=output->handle();
472 }
473
474 // Build command line
475 FXchar *command=commandline(args);
476
477 // Build environment block
478 FXchar *envir=enviroblock(env);
479
480 // Create process
481 if(CreateProcessA(exec,command,NULL,NULL,true,0,envir,NULL,&si,&pi)){
482 CloseHandle(pi.hThread);
483 pid=pi.hProcess;
484 result=true;
485 }
486
487 // Free command line and environment block
488 freeElms(envir);
489 freeElms(command);
490 }
491 #endif
492 #else
493 struct stat status;
494 if(::stat(exec,&status)==0){
495 FXProcessID hnd=::fork();
496 if(0<=hnd){
497 if(hnd==0){
498
499 // Stdin was redirected
500 if(input && input->isOpen()){
501 dup2(input->handle(),STDIN_FILENO);
502 }
503
504 // Stdout was redirected
505 if(output && output->isOpen()){
506 dup2(output->handle(),STDOUT_FILENO);
507 }
508
509 // Stderr was redirected
510 if(errors && errors->isOpen()){
511 dup2(errors->handle(),STDERR_FILENO);
512 }
513
514 // Close all other handles
515 FXint fd=::sysconf(_SC_OPEN_MAX);
516 while(--fd>STDERR_FILENO){
517 close(fd);
518 }
519
520 //setsid();
521
522 // Kick off with arguments and environment
523 if(env){
524 ::execve(exec,(char* const*)args,(char* const*)env);
525 }
526
527 // Kick off with just arguments
528 else{
529 ::execv(exec,(char* const*)args);
530 }
531
532 // Failed to kick off child
533 fxwarning("failed to exec: %s\n",exec);
534
535 // Child exits
536 FXProcess::exit(-1);
537 }
538 pid=hnd;
539 result=true;
540 }
541 }
542 #endif
543 }
544 return result;
545 }
546
547
548 // Get process id
current()549 FXint FXProcess::current(){
550 #if defined(WIN32)
551 return (FXint)::GetCurrentProcessId();
552 #else
553 return (FXint)::getpid();
554 #endif
555 }
556
557
558 // Exit calling process
exit(FXint code)559 void FXProcess::exit(FXint code){
560 #if defined(WIN32)
561 ::ExitProcess(code);
562 #else
563 ::exit(code);
564 #endif
565 }
566
567
568 // Suspend process
suspend()569 FXbool FXProcess::suspend(){
570 if(pid){
571 #if defined(WIN32)
572 /*
573 HANDLE hThreadSnap=NULL;
574 HANDLE hThreadSnap=CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,0);
575 if(hThreadSnap!=INVALID_HANDLE_VALUE){
576 THREADENTRY32 te32={0};
577 te32.dwSize=sizeof(THREADENTRY32);
578 if(Thread32First(hThreadSnap,&te32)){
579 do{
580 if(te32.th32OwnerProcessID==pid){
581 HANDLE hThread=OpenThread(THREAD_SUSPEND_RESUME,false,te32.th32ThreadID);
582 if(hThread){
583 ResumeThread(hThread);
584 CloseHandle(hThread);
585 }
586 }
587 }
588 while(Thread32Next(hThreadSnap,&te32));
589 }
590 CloseHandle(hThreadSnap);
591 }
592 */
593 #else
594 return ::kill(pid,SIGSTOP)==0;
595 #endif
596 }
597 return false;
598 }
599
600
601 // Resume process
resume()602 FXbool FXProcess::resume(){
603 if(pid){
604 #if defined(WIN32)
605 /*
606 HANDLE hThreadSnap=NULL;
607 HANDLE hThreadSnap=CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,0);
608 if(hThreadSnap!=INVALID_HANDLE_VALUE){
609 THREADENTRY32 te32={0};
610 te32.dwSize=sizeof(THREADENTRY32);
611 if(Thread32First(hThreadSnap,&te32)){
612 do{
613 if(te32.th32OwnerProcessID==pid){
614 HANDLE hThread=OpenThread(THREAD_SUSPEND_RESUME,false,te32.th32ThreadID);
615 if(hThread){
616 SuspendThread(hThread);
617 CloseHandle(hThread);
618 }
619 }
620 }
621 while(Thread32Next(hThreadSnap,&te32));
622 }
623 CloseHandle(hThreadSnap);
624 }
625 */
626 #else
627 return ::kill(pid,SIGCONT)==0;
628 #endif
629 }
630 return false;
631 }
632
633
634 // Kill process
kill()635 FXbool FXProcess::kill(){
636 if(pid){
637 #if defined(WIN32)
638 return ::TerminateProcess((HANDLE)pid,-1)!=0;
639 #else
640 return ::kill(pid,SIGKILL)==0;
641 #endif
642 }
643 return false;
644 }
645
646
647 // Wait for child process
wait()648 FXbool FXProcess::wait(){
649 FXbool result=false;
650 if(pid){
651 #if defined(WIN32)
652 if(::WaitForSingleObject((HANDLE)pid,INFINITE)==WAIT_OBJECT_0){
653 ::CloseHandle((HANDLE)pid);
654 result=true;
655 pid=0;
656 }
657 #else
658 FXint code;
659 if(0<waitpid(pid,&code,0)){
660 result=true;
661 pid=0;
662 }
663 #endif
664 }
665 return result;
666 }
667
668
669 // Wait for child process, returning exit code
wait(FXint & code)670 FXbool FXProcess::wait(FXint& code){
671 FXbool result=false;
672 if(pid){
673 #if defined(WIN32)
674 if(::WaitForSingleObject((HANDLE)pid,INFINITE)==WAIT_OBJECT_0){
675 ::GetExitCodeProcess((HANDLE)pid,(ULONG*)&code);
676 ::CloseHandle((HANDLE)pid);
677 result=true;
678 pid=0;
679 }
680 #else
681 if(0<waitpid(pid,&code,0)){
682 result=true;
683 pid=0;
684 }
685 #endif
686 }
687 return result;
688 }
689
690
691
692 // Delete
~FXProcess()693 FXProcess::~FXProcess(){
694 FXTRACE((100,"FXProcess::~FXProcess\n"));
695 if(pid){
696 #if defined(WIN32)
697 ::CloseHandle((HANDLE)pid);
698 #else
699 //// Zombie ////
700 #endif
701 }
702 }
703
704
705 }
706