1 //------------------------------------------------------------------------------
2 // emProcess.cpp
3 //
4 // Copyright (C) 2006-2009,2012,2014,2017-2018 Oliver Hamann.
5 //
6 // Homepage: http://eaglemode.sourceforge.net/
7 //
8 // This program is free software: you can redistribute it and/or modify it under
9 // the terms of the GNU General Public License version 3 as published by the
10 // Free Software Foundation.
11 //
12 // This program is distributed in the hope that it will be useful, but WITHOUT
13 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 // FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for
15 // more details.
16 //
17 // You should have received a copy of the GNU General Public License version 3
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
19 //------------------------------------------------------------------------------
20
21 #include <emCore/emProcess.h>
22
23
24 #if defined(_WIN32)
25 //==============================================================================
26 //==================== Windows implementation of emProcess =====================
27 //==============================================================================
28
29 #include <windows.h>
30 #ifndef FILE_FLAG_FIRST_PIPE_INSTANCE
31 # define FILE_FLAG_FIRST_PIPE_INSTANCE 0x00080000
32 #endif
33
34
35 struct emProcessPrivate {
36
37 emString Arg0;
38 HANDLE HProcess;
39 HANDLE HThread;
40 DWORD ProcessId;
41 DWORD ThreadId;
42 DWORD ExitStatus;
43
44 struct PipeStruct {
45 HANDLE Handle;
46 HANDLE EventHandle;
47 OVERLAPPED Overlapped;
48 DWORD Status;
49 char Buf[16384];
50 DWORD Len;
51 DWORD Pos;
52 };
53 PipeStruct Pipe[3]; // stdin, stdout and stderr in that order.
54
55 HANDLE PreparePipe(int index);
56 void DoPipeIO(int index);
57 void ClosePipe(int index);
58 };
59
60
emProcess()61 emProcess::emProcess()
62 {
63 int i;
64
65 P=new emProcessPrivate;
66 P->HProcess=INVALID_HANDLE_VALUE;
67 P->HThread=INVALID_HANDLE_VALUE;
68 P->ProcessId=(DWORD)(-1);
69 P->ThreadId=(DWORD)(-1);
70 P->ExitStatus=0;
71 for (i=0; i<3; i++) {
72 P->Pipe[i].Handle=INVALID_HANDLE_VALUE;
73 P->Pipe[i].EventHandle=INVALID_HANDLE_VALUE;
74 }
75 }
76
77
~emProcess()78 emProcess::~emProcess()
79 {
80 Terminate();
81 delete P;
82 }
83
84
TryStart(const emArray<emString> & args,const emArray<emString> & extraEnv,const char * dirPath,int flags)85 void emProcess::TryStart(
86 const emArray<emString> & args, const emArray<emString> & extraEnv,
87 const char * dirPath, int flags
88 )
89 {
90 PROCESS_INFORMATION pi;
91 STARTUPINFO si;
92 emString str,msg,useDir;
93 emArray<emString> env;
94 emArray<char> envBlock;
95 emArray<char> cmd;
96 const char * p;
97 char * * pp;
98 HANDLE tmpHandle[10];
99 HANDLE h;
100 DWORD d;
101 int i,j,k,tmpHandleCount;
102 char c;
103
104 if (args.IsEmpty()) {
105 emFatalError("emProcess: No arguments.");
106 }
107
108 if (P->HProcess!=INVALID_HANDLE_VALUE) {
109 emFatalError(
110 "emProcess: TryStart called while still managing another process."
111 );
112 }
113
114 if ((flags&SF_PIPE_STDIN )!=0) flags&=~SF_SHARE_STDIN ;
115 if ((flags&SF_PIPE_STDOUT)!=0) flags&=~SF_SHARE_STDOUT;
116 if ((flags&SF_PIPE_STDERR)!=0) flags&=~SF_SHARE_STDERR;
117
118 for (i=0; i<args.GetCount(); i++) {
119 if (i) {
120 cmd+=' ';
121 for (j=args[i].GetCount()-1; j>=0; j--) {
122 c=args[i][j];
123 if ((c<'0' || c>'9') && (c<'A' || c>'Z') && (c<'a' || c>'z') &&
124 c!='\\' && c!='/' && c!='.' && c!=':' && c!='-') break;
125 }
126 if (j<0) {
127 cmd.Add(args[i].Get(),args[i].GetLen());
128 continue;
129 }
130 }
131 cmd+='"';
132 for (j=0, k=0; j<args[i].GetCount(); j++) {
133 c=args[i][j];
134 if (c=='"') {
135 while (k>0) { cmd+='\\'; k--; }
136 cmd+='\\';
137 }
138 else if (c=='\\') k++;
139 else k=0;
140 cmd+=c;
141 }
142 while (k>0) { cmd+='\\'; k--; }
143 cmd+='"';
144 }
145 cmd+='\0';
146 cmd+='\0';
147
148 pp=environ; if (!pp) { getenv("X"); pp=environ; }
149 while (*pp) { env.Add(*pp); pp++; }
150 for (i=0; i<extraEnv.GetCount(); i++) {
151 str=extraEnv[i];
152 p=strchr(str.Get(),'=');
153 if (p) k=p-str.Get();
154 else k=str.GetLen();
155 for (j=0; j<env.GetCount(); j++) {
156 if (
157 env[j].GetLen()>=k &&
158 memcmp(env[j].Get(),str.Get(),k)==0 &&
159 (env[j][k]==0 || env[j][k]=='=')
160 ) {
161 env.Remove(j);
162 break;
163 }
164 }
165 if (str[k]=='=') env.Add(str);
166 }
167 env.Sort(emStdComparer<emString>::Compare);
168 for (i=0; i<env.GetCount(); i++) envBlock.Add(env[i].Get(),env[i].GetLen()+1);
169 envBlock.Add('\0');
170
171 if (dirPath) useDir=emGetAbsolutePath(dirPath);
172 else useDir=emGetCurrentDirectory();
173
174 memset(&si,0,sizeof(si));
175 si.cb=sizeof(si);
176 si.dwFlags=STARTF_USESTDHANDLES;
177 si.hStdInput=INVALID_HANDLE_VALUE;
178 si.hStdOutput=INVALID_HANDLE_VALUE;
179 si.hStdError=INVALID_HANDLE_VALUE;
180
181 tmpHandleCount=0;
182
183 if ((flags&SF_SHARE_STDIN)!=0) {
184 si.hStdInput=GetStdHandle(STD_INPUT_HANDLE);
185 }
186 if ((flags&SF_PIPE_STDIN)!=0) {
187 h=P->PreparePipe(0);
188 si.hStdInput=h;
189 tmpHandle[tmpHandleCount++]=h;
190 }
191
192 if ((flags&SF_SHARE_STDOUT)!=0) {
193 si.hStdOutput=GetStdHandle(STD_OUTPUT_HANDLE);
194 }
195 if ((flags&SF_PIPE_STDOUT)!=0) {
196 h=P->PreparePipe(1);
197 si.hStdOutput=h;
198 tmpHandle[tmpHandleCount++]=h;
199 }
200
201 if ((flags&SF_SHARE_STDERR)!=0) {
202 si.hStdError=GetStdHandle(STD_ERROR_HANDLE);
203 }
204 if ((flags&SF_PIPE_STDERR)!=0) {
205 h=P->PreparePipe(2);
206 si.hStdError=h;
207 tmpHandle[tmpHandleCount++]=h;
208 }
209
210 if (
211 !CreateProcess(
212 NULL,
213 cmd.GetWritable(),
214 NULL,
215 NULL,
216 TRUE,
217 (flags&SF_NO_WINDOW) ? CREATE_NO_WINDOW : 0,
218 (LPVOID)envBlock.Get(),
219 useDir.Get(),
220 &si,
221 &pi
222 )
223 ) {
224 d=GetLastError();
225 while (tmpHandleCount>0) CloseHandle(tmpHandle[--tmpHandleCount]);
226 CloseWriting();
227 CloseReading();
228 CloseReadingErr();
229 throw emException(
230 "Failed to start process \"%s\": %s",
231 args[0].Get(),
232 emGetErrorText(d).Get()
233 );
234 }
235
236 while (tmpHandleCount>0) CloseHandle(tmpHandle[--tmpHandleCount]);
237 P->Arg0=args[0];
238 P->HProcess=pi.hProcess;
239 P->HThread=pi.hThread;
240 P->ProcessId=pi.dwProcessId;
241 P->ThreadId=pi.dwThreadId;
242 P->ExitStatus=0;
243 }
244
245
TryStartUnmanaged(const emArray<emString> & args,const emArray<emString> & extraEnv,const char * dirPath,int flags)246 void emProcess::TryStartUnmanaged(
247 const emArray<emString> & args, const emArray<emString> & extraEnv,
248 const char * dirPath, int flags
249 )
250 {
251 emProcess p;
252
253 flags&=~(SF_PIPE_STDIN|SF_PIPE_STDOUT|SF_PIPE_STDERR);
254 p.TryStart(args,extraEnv,dirPath,flags);
255 CloseHandle(p.P->HThread);
256 CloseHandle(p.P->HProcess);
257 p.P->HProcess=INVALID_HANDLE_VALUE;
258 p.P->HThread=INVALID_HANDLE_VALUE;
259 p.P->ProcessId=(DWORD)(-1);
260 p.P->ThreadId=(DWORD)(-1);
261 p.P->ClosePipe(0);
262 p.P->ClosePipe(1);
263 p.P->ClosePipe(2);
264 }
265
266
TryWrite(const char * buf,int len)267 int emProcess::TryWrite(const char * buf, int len)
268 {
269 emProcessPrivate::PipeStruct * pipe;
270 int done,l;
271 DWORD s;
272
273 pipe=&P->Pipe[0];
274 if (pipe->Handle==INVALID_HANDLE_VALUE) return -1;
275 done=0;
276 for (;;) {
277 P->DoPipeIO(0);
278 if (pipe->Status!=0) break;
279 l=sizeof(pipe->Buf)-pipe->Len;
280 if (l>len-done) l=len-done;
281 if (l<=0) break;
282 memcpy(pipe->Buf+pipe->Len,buf+done,l);
283 pipe->Len+=l;
284 done+=l;
285 }
286 if (done>0) return done;
287 s=pipe->Status;
288 if (s==0 || s==ERROR_IO_PENDING || s==ERROR_IO_INCOMPLETE) return 0;
289 CloseWriting();
290 if (s==ERROR_HANDLE_EOF || s==ERROR_BROKEN_PIPE) return -1;
291 throw emException(
292 "Failed to write to stdin pipe of child process \"%s\": %s",
293 P->Arg0.Get(),
294 emGetErrorText(s).Get()
295 );
296 }
297
298
TryRead(char * buf,int maxLen)299 int emProcess::TryRead(char * buf, int maxLen)
300 {
301 emProcessPrivate::PipeStruct * pipe;
302 int done,l;
303 DWORD s;
304
305 pipe=&P->Pipe[1];
306 if (pipe->Handle==INVALID_HANDLE_VALUE) return -1;
307 done=0;
308 for (;;) {
309 P->DoPipeIO(1);
310 if (pipe->Status!=0) break;
311 l=pipe->Len-pipe->Pos;
312 if (l>maxLen-done) l=maxLen-done;
313 if (l<=0) break;
314 memcpy(buf+done,pipe->Buf+pipe->Pos,l);
315 pipe->Pos+=l;
316 done+=l;
317 }
318 if (done>0) return done;
319 s=pipe->Status;
320 if (s==0 || s==ERROR_IO_PENDING || s==ERROR_IO_INCOMPLETE) return 0;
321 CloseReading();
322 if (s==ERROR_HANDLE_EOF || s==ERROR_BROKEN_PIPE) return -1;
323 throw emException(
324 "Failed to read stdout pipe of child process \"%s\": %s",
325 P->Arg0.Get(),
326 emGetErrorText(s).Get()
327 );
328 }
329
330
TryReadErr(char * buf,int maxLen)331 int emProcess::TryReadErr(char * buf, int maxLen)
332 {
333 emProcessPrivate::PipeStruct * pipe;
334 int done,l;
335 DWORD s;
336
337 pipe=&P->Pipe[2];
338 if (pipe->Handle==INVALID_HANDLE_VALUE) return -1;
339 done=0;
340 for (;;) {
341 P->DoPipeIO(2);
342 if (pipe->Status!=0) break;
343 l=pipe->Len-pipe->Pos;
344 if (l>maxLen-done) l=maxLen-done;
345 if (l<=0) break;
346 memcpy(buf+done,pipe->Buf+pipe->Pos,l);
347 pipe->Pos+=l;
348 done+=l;
349 }
350 if (done>0) return done;
351 s=pipe->Status;
352 if (s==0 || s==ERROR_IO_PENDING || s==ERROR_IO_INCOMPLETE) return 0;
353 CloseReadingErr();
354 if (s==ERROR_HANDLE_EOF || s==ERROR_BROKEN_PIPE) return -1;
355 throw emException(
356 "Failed to read stderr pipe of child process \"%s\": %s",
357 P->Arg0.Get(),
358 emGetErrorText(s).Get()
359 );
360 }
361
362
WaitPipes(int waitFlags,unsigned timeoutMS)363 void emProcess::WaitPipes(int waitFlags, unsigned timeoutMS)
364 {
365 static const int flag[3] = { WF_WAIT_STDIN, WF_WAIT_STDOUT, WF_WAIT_STDERR };
366 HANDLE handles[3];
367 DWORD i,n;
368
369 if (timeoutMS==0) return;
370 n=0;
371 for (i=0; i<3; i++) {
372 if ((waitFlags&flag[i])==0) continue;
373 if (P->Pipe[i].Handle==INVALID_HANDLE_VALUE) continue;
374 P->DoPipeIO(i);
375 if (
376 P->Pipe[i].Status!=ERROR_IO_PENDING &&
377 P->Pipe[i].Status!=ERROR_IO_INCOMPLETE
378 ) return;
379 handles[n++]=P->Pipe[i].EventHandle;
380 }
381 if (n<=0) return;
382 if (WaitForMultipleObjects(
383 n,
384 handles,
385 FALSE,
386 timeoutMS==UINT_MAX ? INFINITE : (DWORD)timeoutMS
387 )==WAIT_FAILED) {
388 emFatalError(
389 "emProcess: WaitForMultipleObjects failed: %s",
390 emGetErrorText(GetLastError()).Get()
391 );
392 }
393 }
394
395
CloseWriting()396 void emProcess::CloseWriting()
397 {
398 P->ClosePipe(0);
399 }
400
401
CloseReading()402 void emProcess::CloseReading()
403 {
404 P->ClosePipe(1);
405 }
406
407
CloseReadingErr()408 void emProcess::CloseReadingErr()
409 {
410 P->ClosePipe(2);
411 }
412
413
SendTerminationSignal()414 void emProcess::SendTerminationSignal()
415 {
416 if (IsRunning()) PostThreadMessage(P->ThreadId,WM_QUIT,0,0);
417 }
418
419
SendKillSignal()420 void emProcess::SendKillSignal()
421 {
422 if (IsRunning()) TerminateProcess(P->HProcess,128+9);
423 }
424
425
WaitForTermination(unsigned timeoutMS)426 bool emProcess::WaitForTermination(unsigned timeoutMS)
427 {
428 DWORD res;
429
430 if (P->HProcess!=INVALID_HANDLE_VALUE) {
431 res=WaitForSingleObject(
432 P->HProcess,
433 timeoutMS==UINT_MAX ? INFINITE : (DWORD)timeoutMS
434 );
435 if (res!=WAIT_OBJECT_0) {
436 if (res!=WAIT_TIMEOUT) {
437 emFatalError(
438 "emProcess: WaitForSingleObject failed: %s",
439 emGetErrorText(GetLastError()).Get()
440 );
441 }
442 return false;
443 }
444 GetExitCodeProcess(P->HProcess,&P->ExitStatus);
445 CloseHandle(P->HThread);
446 CloseHandle(P->HProcess);
447 P->HProcess=INVALID_HANDLE_VALUE;
448 P->HThread=INVALID_HANDLE_VALUE;
449 P->ProcessId=(DWORD)(-1);
450 P->ThreadId=(DWORD)(-1);
451 P->ClosePipe(0);
452 P->ClosePipe(1);
453 P->ClosePipe(2);
454 }
455 return true;
456 }
457
458
IsRunning()459 bool emProcess::IsRunning()
460 {
461 return !WaitForTermination(0);
462 }
463
464
Terminate(unsigned fatalTimeoutMS)465 void emProcess::Terminate(unsigned fatalTimeoutMS)
466 {
467 if (IsRunning()) {
468 SendTerminationSignal();
469 if (!WaitForTermination(fatalTimeoutMS)) {
470 emFatalError(
471 "Child process \"%s\" not willing to terminate.",
472 P->Arg0.Get()
473 );
474 }
475 }
476 }
477
478
GetExitStatus() const479 int emProcess::GetExitStatus() const
480 {
481 return P->ExitStatus;
482 }
483
484
PreparePipe(int index)485 HANDLE emProcessPrivate::PreparePipe(int index)
486 {
487 static int counter=0;
488 SECURITY_ATTRIBUTES sec;
489 HANDLE h1,h2,he;
490 emString name;
491 DWORD i,openMode;
492
493 for (i=0; ; i++) {
494 name=emString::Format(
495 "%d:%p:%d:%d:%d",
496 (int)GetCurrentProcessId(),
497 (char*)this,
498 index,
499 (int)emGetClockMS(),
500 counter++
501 );
502 name=
503 emString("\\\\.\\pipe\\emProcess-") +
504 emCalcHashName(name.Get(),name.GetCount(),40)
505 ;
506 openMode=FILE_FLAG_OVERLAPPED|FILE_FLAG_FIRST_PIPE_INSTANCE;
507 if (index==0) openMode|=PIPE_ACCESS_OUTBOUND;
508 else openMode|=PIPE_ACCESS_INBOUND;
509 h1=CreateNamedPipe(
510 name.Get(),
511 openMode,
512 0,
513 1,
514 16384,
515 16384,
516 1000,
517 NULL
518 );
519 if (h1!=INVALID_HANDLE_VALUE) break;
520 if (i>1000) {
521 emFatalError(
522 "emProcess: CreateNamedPipe failed: %s",
523 emGetErrorText(GetLastError()).Get()
524 );
525 }
526 }
527
528 if (index==0) openMode=GENERIC_READ;
529 else openMode=GENERIC_WRITE;
530 memset(&sec,0,sizeof(sec));
531 sec.nLength=sizeof(sec);
532 sec.bInheritHandle=TRUE;
533 h2=CreateFile(
534 name.Get(),
535 openMode,
536 0,
537 &sec,
538 OPEN_EXISTING,
539 0,
540 NULL
541 );
542 if (h2==INVALID_HANDLE_VALUE) {
543 emFatalError(
544 "emProcess: CreateFile on named pipe failed: %s",
545 emGetErrorText(GetLastError()).Get()
546 );
547 }
548
549 he=CreateEvent(NULL,TRUE,FALSE,NULL);
550 if (he==NULL) {
551 emFatalError(
552 "emProcess: CreateEvent failed: %s",
553 emGetErrorText(GetLastError()).Get()
554 );
555 }
556
557 Pipe[index].Handle=h1;
558 Pipe[index].EventHandle=he;
559 Pipe[index].Status=0;
560 Pipe[index].Len=0;
561 Pipe[index].Pos=0;
562 return h2;
563 }
564
565
DoPipeIO(int index)566 void emProcessPrivate::DoPipeIO(int index)
567 {
568 PipeStruct * pipe;
569 BOOL res;
570
571 pipe=&Pipe[index];
572 if (pipe->Handle==INVALID_HANDLE_VALUE) return;
573 for (;;) {
574 if (pipe->Status==0) {
575 if (index==0) {
576 if (pipe->Len==0) break;
577 }
578 else {
579 if (pipe->Pos<pipe->Len) break;
580 }
581 ResetEvent(pipe->EventHandle);
582 memset(&pipe->Overlapped,0,sizeof(pipe->Overlapped));
583 pipe->Overlapped.hEvent=pipe->EventHandle;
584 pipe->Pos=0;
585 if (index==0) {
586 res=WriteFile(
587 pipe->Handle,
588 pipe->Buf,
589 pipe->Len,
590 &pipe->Pos,
591 &pipe->Overlapped
592 );
593 }
594 else {
595 res=ReadFile(
596 pipe->Handle,
597 pipe->Buf,
598 sizeof(pipe->Buf),
599 &pipe->Pos,
600 &pipe->Overlapped
601 );
602 }
603 }
604 else if (
605 pipe->Status==ERROR_IO_PENDING ||
606 pipe->Status==ERROR_IO_INCOMPLETE
607 ) {
608 res=GetOverlappedResult(
609 pipe->Handle,
610 &pipe->Overlapped,
611 &pipe->Pos,
612 FALSE
613 );
614 }
615 else {
616 break;
617 }
618 if (!res) {
619 pipe->Status=GetLastError();
620 break;
621 }
622 if (index==0 && pipe->Pos==0) {
623 pipe->Status=ERROR_BROKEN_PIPE;
624 break;
625 }
626 pipe->Status=0;
627 if (index==0) {
628 if (pipe->Pos<pipe->Len) {
629 memmove(pipe->Buf,pipe->Buf+pipe->Pos,pipe->Len-pipe->Pos);
630 pipe->Len-=pipe->Pos;
631 }
632 else {
633 pipe->Len=0;
634 }
635 }
636 else {
637 pipe->Len=pipe->Pos;
638 }
639 pipe->Pos=0;
640 }
641 }
642
643
ClosePipe(int index)644 void emProcessPrivate::ClosePipe(int index)
645 {
646 PipeStruct * pipe;
647
648 pipe=&Pipe[index];
649 if (pipe->Handle!=INVALID_HANDLE_VALUE) {
650 CancelIo(pipe->Handle);
651 CloseHandle(pipe->Handle);
652 pipe->Handle=INVALID_HANDLE_VALUE;
653 if (pipe->EventHandle!=INVALID_HANDLE_VALUE) {
654 CloseHandle(pipe->EventHandle);
655 pipe->EventHandle=INVALID_HANDLE_VALUE;
656 }
657 }
658 }
659
660
661 #else
662 //==============================================================================
663 //====================== UNIX implementation of emProcess ======================
664 //==============================================================================
665
666 #include <signal.h>
667 #include <sys/wait.h>
668 #include <sys/time.h>
669 #include <fcntl.h>
670 #include <unistd.h>
671
672
673 struct emProcessPrivate {
674
675 static void EmptySigHandler(int signum);
676
677 static void TryStart(
678 const emArray<emString> & args,
679 const emArray<emString> & extraEnv,
680 const char * dirPath,
681 int flags,
682 emProcessPrivate * managed
683 );
684
685 emString Arg0;
686 pid_t Pid;
687 int FdIn;
688 int FdOut;
689 int FdErr;
690 int ExitStatus;
691 };
692
693
emProcess()694 emProcess::emProcess()
695 {
696 P=new emProcessPrivate;
697 P->Pid=-1;
698 P->FdIn=-1;
699 P->FdOut=-1;
700 P->FdErr=-1;
701 P->ExitStatus=0;
702 }
703
704
~emProcess()705 emProcess::~emProcess()
706 {
707 Terminate();
708 delete P;
709 }
710
711
TryStart(const emArray<emString> & args,const emArray<emString> & extraEnv,const char * dirPath,int flags)712 void emProcess::TryStart(
713 const emArray<emString> & args, const emArray<emString> & extraEnv,
714 const char * dirPath, int flags
715 )
716 {
717 emProcessPrivate::TryStart(args,extraEnv,dirPath,flags,P);
718 }
719
720
TryStartUnmanaged(const emArray<emString> & args,const emArray<emString> & extraEnv,const char * dirPath,int flags)721 void emProcess::TryStartUnmanaged(
722 const emArray<emString> & args, const emArray<emString> & extraEnv,
723 const char * dirPath, int flags
724 )
725 {
726 flags&=~(SF_PIPE_STDIN|SF_PIPE_STDOUT|SF_PIPE_STDERR);
727 emProcessPrivate::TryStart(args,extraEnv,dirPath,flags,NULL);
728 }
729
730
TryWrite(const char * buf,int len)731 int emProcess::TryWrite(const char * buf, int len)
732 {
733 ssize_t r;
734 int e;
735
736 if (P->FdIn==-1) return -1;
737 if (len<=0) return 0;
738 r=write(P->FdIn,buf,len);
739 if (r>=0) return (int)r;
740 if (errno==EAGAIN) return 0;
741 if (errno==EPIPE) {
742 CloseWriting();
743 return -1;
744 }
745 e=errno;
746 CloseWriting();
747 throw emException(
748 "Failed to write to stdin pipe of child process \"%s\" (pid %d): %s",
749 P->Arg0.Get(),
750 (int)P->Pid,
751 emGetErrorText(e).Get()
752 );
753 }
754
755
TryRead(char * buf,int maxLen)756 int emProcess::TryRead(char * buf, int maxLen)
757 {
758 ssize_t r;
759 int e;
760
761 if (P->FdOut==-1) return -1;
762 if (maxLen<=0) return 0;
763 r=read(P->FdOut,buf,maxLen);
764 if (r>0) return (int)r;
765 if (r==0) {
766 CloseReading();
767 return -1;
768 }
769 if (errno==EAGAIN) return 0;
770 e=errno;
771 CloseReading();
772 throw emException(
773 "Failed to read stdout pipe of child process \"%s\" (pid %d): %s",
774 P->Arg0.Get(),
775 (int)P->Pid,
776 emGetErrorText(e).Get()
777 );
778 }
779
780
TryReadErr(char * buf,int maxLen)781 int emProcess::TryReadErr(char * buf, int maxLen)
782 {
783 ssize_t r;
784 int e;
785
786 if (P->FdErr==-1) return -1;
787 if (maxLen<=0) return 0;
788 r=read(P->FdErr,buf,maxLen);
789 if (r>0) return (int)r;
790 if (r==0) {
791 CloseReadingErr();
792 return -1;
793 }
794 if (errno==EAGAIN) return 0;
795 e=errno;
796 CloseReadingErr();
797 throw emException(
798 "Failed to read stderr pipe of child process \"%s\" (pid %d): %s",
799 P->Arg0.Get(),
800 (int)P->Pid,
801 emGetErrorText(e).Get()
802 );
803 }
804
805
WaitPipes(int waitFlags,unsigned timeoutMS)806 void emProcess::WaitPipes(int waitFlags, unsigned timeoutMS)
807 {
808 timeval tv;
809 timeval * ptv;
810 fd_set rset;
811 fd_set wset;
812 int fdMax;
813
814 if (timeoutMS==0) return;
815 FD_ZERO(&rset);
816 FD_ZERO(&wset);
817 fdMax=-1;
818 if ((waitFlags&WF_WAIT_STDIN)!=0 && P->FdIn!=-1) {
819 FD_SET(P->FdIn,&wset);
820 if (fdMax<P->FdIn) fdMax=P->FdIn;
821 }
822 if ((waitFlags&WF_WAIT_STDOUT)!=0 && P->FdOut!=-1) {
823 FD_SET(P->FdOut,&rset);
824 if (fdMax<P->FdOut) fdMax=P->FdOut;
825 }
826 if ((waitFlags&WF_WAIT_STDERR)!=0 && P->FdErr!=-1) {
827 FD_SET(P->FdErr,&rset);
828 if (fdMax<P->FdErr) fdMax=P->FdErr;
829 }
830 if (fdMax==-1) return;
831 if (timeoutMS==UINT_MAX) {
832 ptv=NULL;
833 }
834 else {
835 tv.tv_sec=(time_t)(timeoutMS/1000);
836 tv.tv_usec=(time_t)((timeoutMS%1000)*1000);
837 ptv=&tv;
838 }
839 if (select(fdMax+1,&rset,&wset,NULL,ptv)<0) {
840 if (errno!=EINTR) {
841 emFatalError(
842 "emProcess: select failed: %s",
843 emGetErrorText(errno).Get()
844 );
845 }
846 }
847 }
848
849
CloseWriting()850 void emProcess::CloseWriting()
851 {
852 if (P->FdIn!=-1) {
853 close(P->FdIn);
854 P->FdIn=-1;
855 }
856 }
857
858
CloseReading()859 void emProcess::CloseReading()
860 {
861 if (P->FdOut!=-1) {
862 close(P->FdOut);
863 P->FdOut=-1;
864 }
865 }
866
867
CloseReadingErr()868 void emProcess::CloseReadingErr()
869 {
870 if (P->FdErr!=-1) {
871 close(P->FdErr);
872 P->FdErr=-1;
873 }
874 }
875
876
SendTerminationSignal()877 void emProcess::SendTerminationSignal()
878 {
879 if (IsRunning()) kill(P->Pid,SIGTERM);
880 // If the process would be a group leader, kill(-P->Pid,SIGTERM) could
881 // be used to send the signal to all processes in the group.
882 }
883
884
SendKillSignal()885 void emProcess::SendKillSignal()
886 {
887 if (IsRunning()) kill(P->Pid,SIGKILL);
888 }
889
890
WaitForTermination(unsigned timeoutMS)891 bool emProcess::WaitForTermination(unsigned timeoutMS)
892 {
893 pid_t pr;
894 unsigned t, u;
895
896 if (P->Pid==-1) return true;
897 for (t=0;;) {
898 pr=waitpid(P->Pid,&P->ExitStatus,WNOHANG);
899 if (pr) break;
900 if (timeoutMS==0) return false;
901 u=t;
902 if (u>timeoutMS) u=timeoutMS;
903 emSleepMS(u);
904 if (timeoutMS!=UINT_MAX) timeoutMS-=u;
905 if (t<10) t++;
906 }
907 if (pr!=P->Pid) {
908 if (pr<0) {
909 emFatalError("emProcess: waitpid failed: %s",emGetErrorText(errno).Get());
910 }
911 else {
912 emFatalError("emProcess: unexpected return value from waitpid.");
913 }
914 }
915 P->Pid=-1;
916 if (WIFEXITED(P->ExitStatus)) P->ExitStatus=WEXITSTATUS(P->ExitStatus);
917 else P->ExitStatus=128+WTERMSIG(P->ExitStatus);
918 CloseWriting();
919 CloseReading();
920 CloseReadingErr();
921 return true;
922 }
923
924
IsRunning()925 bool emProcess::IsRunning()
926 {
927 return !WaitForTermination(0);
928 }
929
930
Terminate(unsigned fatalTimeoutMS)931 void emProcess::Terminate(unsigned fatalTimeoutMS)
932 {
933 if (IsRunning()) {
934 SendTerminationSignal();
935 if (!WaitForTermination(fatalTimeoutMS)) {
936 emFatalError(
937 "Child process \"%s\" (pid %d) not willing to terminate.",
938 P->Arg0.Get(),
939 (int)P->Pid
940 );
941 }
942 }
943 }
944
945
GetExitStatus() const946 int emProcess::GetExitStatus() const
947 {
948 return P->ExitStatus;
949 }
950
951
EmptySigHandler(int signum)952 void emProcessPrivate::EmptySigHandler(int signum)
953 {
954 }
955
956
TryStart(const emArray<emString> & args,const emArray<emString> & extraEnv,const char * dirPath,int flags,emProcessPrivate * managed)957 void emProcessPrivate::TryStart(
958 const emArray<emString> & args, const emArray<emString> & extraEnv,
959 const char * dirPath, int flags, emProcessPrivate * managed
960 )
961 {
962 char buf[1024];
963 emString msg;
964 int pipeIn[2],pipeOut[2],pipeErr[2],pipeTmp[2];
965 char * * cargs;
966 int i,n,f,len,r;
967 pid_t pr,pid;
968
969 if (args.IsEmpty()) {
970 emFatalError("emProcess: No arguments.");
971 }
972
973 if (managed && managed->Pid!=-1) {
974 emFatalError(
975 "emProcess: TryStart called while still managing another process."
976 );
977 }
978
979 if (!managed) {
980 flags&=~emProcess::SF_PIPE_STDIN;
981 flags&=~emProcess::SF_PIPE_STDOUT;
982 flags&=~emProcess::SF_PIPE_STDERR;
983 }
984 if ((flags&emProcess::SF_PIPE_STDIN)!=0) {
985 flags&=~emProcess::SF_SHARE_STDIN;
986 }
987 if ((flags&emProcess::SF_PIPE_STDOUT)!=0) {
988 flags&=~emProcess::SF_SHARE_STDOUT;
989 }
990 if ((flags&emProcess::SF_PIPE_STDERR)!=0) {
991 flags&=~emProcess::SF_SHARE_STDERR;
992 }
993
994 static const struct SigHandlersInstaller {
995 SigHandlersInstaller() {
996 struct sigaction sa;
997 memset(&sa,0,sizeof(sa));
998 sa.sa_handler=EmptySigHandler;
999 sa.sa_flags=SA_RESTART;
1000 if (sigaction(SIGCHLD,&sa,NULL)) {
1001 emFatalError(
1002 "emProcess: Failed to install handler for SIGCHLD: %s",
1003 emGetErrorText(errno).Get()
1004 );
1005 }
1006 memset(&sa,0,sizeof(sa));
1007 sa.sa_handler=EmptySigHandler;
1008 sa.sa_flags=SA_RESTART;
1009 if (sigaction(SIGPIPE,&sa,NULL)) {
1010 emFatalError(
1011 "emProcess: Failed to install handler for SIGPIPE: %s",
1012 emGetErrorText(errno).Get()
1013 );
1014 }
1015 }
1016 } sigHandlersInstaller;
1017
1018 pid=-1;
1019 pipeIn[0]=-1;
1020 pipeIn[1]=-1;
1021 pipeOut[0]=-1;
1022 pipeOut[1]=-1;
1023 pipeErr[0]=-1;
1024 pipeErr[1]=-1;
1025 pipeTmp[0]=-1;
1026 pipeTmp[1]=-1;
1027
1028 if ((flags&emProcess::SF_PIPE_STDIN)!=0) {
1029 if (pipe(pipeIn)) goto L_PipeErr;
1030 if ((f=fcntl(pipeIn[1],F_GETFL))<0) goto L_GetFlErr;
1031 if (fcntl(pipeIn[1],F_SETFL,f|O_NONBLOCK)<0) goto L_SetFlErr;
1032 }
1033 if ((flags&emProcess::SF_PIPE_STDOUT)!=0) {
1034 if (pipe(pipeOut)) goto L_PipeErr;
1035 if ((f=fcntl(pipeOut[0],F_GETFL))<0) goto L_GetFlErr;
1036 if (fcntl(pipeOut[0],F_SETFL,f|O_NONBLOCK)<0) goto L_SetFlErr;
1037 }
1038 if ((flags&emProcess::SF_PIPE_STDERR)!=0) {
1039 if (pipe(pipeErr)) goto L_PipeErr;
1040 if ((f=fcntl(pipeErr[0],F_GETFL))<0) goto L_GetFlErr;
1041 if (fcntl(pipeErr[0],F_SETFL,f|O_NONBLOCK)<0) goto L_SetFlErr;
1042 }
1043 if (pipe(pipeTmp)) goto L_PipeErr;
1044
1045 pid=fork();
1046 if (pid<0) goto L_ForkErr;
1047 // If we ever want to support an option for making the managed child
1048 // process a group leader, insert code like this here:
1049 // if (managed && wantToMakeItAGroupLeader) setpgid(pid,pid);
1050 // Yes, insert it exactly at this point for both, child and parent!
1051 // For the unmanaged case, look around next fork more below.
1052 if (pid==0) {
1053
1054 //--------- Child process ---------
1055
1056 for (i=0; i<extraEnv.GetCount(); i++) {
1057 if (putenv((char*)extraEnv[i].Get())<0) {
1058 msg=emString::Format(
1059 "Failed to set environment variable: %s",
1060 emGetErrorText(errno).Get()
1061 );
1062 goto L_ChildErr;
1063 }
1064 }
1065
1066 if (dirPath) {
1067 if (chdir(dirPath)<0) {
1068 msg=emString::Format(
1069 "Failed to set working directory to \"%s\": %s",
1070 dirPath,
1071 emGetErrorText(errno).Get()
1072 );
1073 goto L_ChildErr;
1074 }
1075 }
1076
1077 if (pipeIn[0]!=-1) {
1078 if (dup2(pipeIn[0],STDIN_FILENO)!=STDIN_FILENO) {
1079 msg=emString::Format("dup2 failed: %s",emGetErrorText(errno).Get());
1080 goto L_ChildErr;
1081 }
1082 }
1083 if (pipeOut[1]!=-1) {
1084 if (dup2(pipeOut[1],STDOUT_FILENO)!=STDOUT_FILENO) {
1085 msg=emString::Format("dup2 failed: %s",emGetErrorText(errno).Get());
1086 goto L_ChildErr;
1087 }
1088 }
1089 if (pipeErr[1]!=-1) {
1090 if (dup2(pipeErr[1],STDERR_FILENO)!=STDERR_FILENO) {
1091 msg=emString::Format("dup2 failed: %s",emGetErrorText(errno).Get());
1092 goto L_ChildErr;
1093 }
1094 }
1095
1096 n=sysconf(_SC_OPEN_MAX);
1097 if (n<=0) {
1098 msg=emString::Format(
1099 "sysconf(_SC_OPEN_MAX) failed: %s",
1100 emGetErrorText(errno).Get()
1101 );
1102 goto L_ChildErr;
1103 }
1104 for (i=0; i<n; i++) {
1105 if (
1106 i==STDIN_FILENO &&
1107 (flags&(emProcess::SF_SHARE_STDIN|emProcess::SF_PIPE_STDIN))!=0
1108 ) continue;
1109 if (
1110 i==STDOUT_FILENO &&
1111 (flags&(emProcess::SF_SHARE_STDOUT|emProcess::SF_PIPE_STDOUT))!=0
1112 ) continue;
1113 if (
1114 i==STDERR_FILENO &&
1115 (flags&(emProcess::SF_SHARE_STDERR|emProcess::SF_PIPE_STDERR))!=0
1116 ) continue;
1117 if (i==pipeTmp[1]) continue;
1118 close(i);
1119 }
1120
1121 if (fcntl(pipeTmp[1],F_SETFD,FD_CLOEXEC)<0) {
1122 msg=emString::Format(
1123 "fcntl(pipeTmp[1],F_SETFD,FD_CLOEXEC) failed: %s",
1124 emGetErrorText(errno).Get()
1125 );
1126 goto L_ChildErr;
1127 }
1128
1129 cargs=new char*[args.GetCount()+1];
1130 for (i=0; i<args.GetCount(); i++) {
1131 cargs[i]=(char*)args[i].Get();
1132 }
1133 cargs[i]=NULL;
1134
1135 if (!managed) {
1136 pid=fork();
1137 if (pid<0) {
1138 msg=emString::Format(
1139 "fork failed: %s",
1140 emGetErrorText(errno).Get()
1141 );
1142 goto L_ChildErr;
1143 }
1144 if (pid!=0) _exit(0);
1145 setsid();
1146 // setsid() makes this unmanaged process a group
1147 // and session leader. Should we make it just a
1148 // group leader with setpgid(0,0) instead?
1149 }
1150
1151 execvp(cargs[0],cargs);
1152 msg=emGetErrorText(errno);
1153 L_ChildErr:
1154 i=0;
1155 len=msg.GetLen();
1156 while (i<len) {
1157 r=(int)write(pipeTmp[1],msg.Get()+i,len-i);
1158 if (r<=0) break;
1159 i+=r;
1160 }
1161 _exit(-1);
1162
1163 //--------- End of child process ---------
1164 }
1165
1166 close(pipeTmp[1]);
1167 pipeTmp[1]=-1;
1168 len=0;
1169 do {
1170 r=(int)read(pipeTmp[0],buf+len,sizeof(buf)-1-len);
1171 if (r<=0) break;
1172 len+=r;
1173 } while(len<(int)sizeof(buf)-1);
1174 if (len>0) {
1175 msg=emString(buf,len);
1176 goto L_Err;
1177 }
1178 close(pipeTmp[0]);
1179 pipeTmp[0]=-1;
1180
1181 if (managed) {
1182 if (pipeIn[0]!=-1) close(pipeIn[0]);
1183 if (pipeOut[1]!=-1) close(pipeOut[1]);
1184 if (pipeErr[1]!=-1) close(pipeErr[1]);
1185 managed->Pid=pid;
1186 managed->Arg0=args[0];
1187 managed->FdIn=pipeIn[1];
1188 managed->FdOut=pipeOut[0];
1189 managed->FdErr=pipeErr[0];
1190 managed->ExitStatus=0;
1191 }
1192 else {
1193 for (;;) {
1194 pr=waitpid(pid,NULL,0);
1195 if (pr==pid) break;
1196 if (pr<0) {
1197 if (errno==EINTR) continue;
1198 emFatalError("emProcess: waitpid failed: %s",emGetErrorText(errno).Get());
1199 }
1200 else {
1201 emFatalError("emProcess: unexpected return value from waitpid.");
1202 }
1203 }
1204 }
1205 return;
1206
1207 L_PipeErr:
1208 msg=emString::Format(
1209 "Pipe creation failed: %s",
1210 emGetErrorText(errno).Get()
1211 );
1212 goto L_Err;
1213 L_GetFlErr:
1214 msg=emString::Format(
1215 "fcntl(...,F_GETFL) failed: %s",
1216 emGetErrorText(errno).Get()
1217 );
1218 goto L_Err;
1219 L_SetFlErr:
1220 msg=emString::Format(
1221 "fcntl(...,F_SETFL) failed: %s",
1222 emGetErrorText(errno).Get()
1223 );
1224 goto L_Err;
1225 L_ForkErr:
1226 msg=emString::Format(
1227 "fork() failed: %s",
1228 emGetErrorText(errno).Get()
1229 );
1230 goto L_Err;
1231 L_Err:
1232 if (pid!=-1) {
1233 for (;;) {
1234 pr=waitpid(pid,NULL,0);
1235 if (pr==pid) break;
1236 if (pr<0) {
1237 if (errno==EINTR) continue;
1238 emFatalError("emProcess: waitpid failed: %s",emGetErrorText(errno).Get());
1239 }
1240 else {
1241 emFatalError("emProcess: unexpected return value from waitpid.");
1242 }
1243 }
1244 }
1245 if (pipeIn[0]!=-1) close(pipeIn[0]);
1246 if (pipeIn[1]!=-1) close(pipeIn[1]);
1247 if (pipeOut[0]!=-1) close(pipeOut[0]);
1248 if (pipeOut[1]!=-1) close(pipeOut[1]);
1249 if (pipeErr[0]!=-1) close(pipeErr[0]);
1250 if (pipeErr[1]!=-1) close(pipeErr[1]);
1251 if (pipeTmp[0]!=-1) close(pipeTmp[0]);
1252 if (pipeTmp[1]!=-1) close(pipeTmp[1]);
1253 throw emException(
1254 "Failed to start process \"%s\": %s",
1255 args[0].Get(),
1256 msg.Get()
1257 );
1258 }
1259
1260
1261 #endif
1262