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