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