blocxx
Process.cpp
Go to the documentation of this file.
1/*******************************************************************************
2* Copyright (C) 2005, Quest Software, Inc. All rights reserved.
3* Copyright (C) 2006, Novell, Inc. All rights reserved.
4*
5* Redistribution and use in source and binary forms, with or without
6* modification, are permitted provided that the following conditions are met:
7*
8* * Redistributions of source code must retain the above copyright notice,
9* this list of conditions and the following disclaimer.
10* * Redistributions in binary form must reproduce the above copyright
11* notice, this list of conditions and the following disclaimer in the
12* documentation and/or other materials provided with the distribution.
13* * Neither the name of
14* Quest Software, Inc.,
15* nor Novell, Inc.,
16* nor Network Associates,
17* nor the names of its contributors or employees may be used to
18* endorse or promote products derived from this software without
19* specific prior written permission.
20*
21* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31* POSSIBILITY OF SUCH DAMAGE.
32*******************************************************************************/
33
34
39
40#include "blocxx/BLOCXX_config.h"
41
42#include "blocxx/DateTime.hpp"
43#include "blocxx/Exec.hpp"
44 // To get ExecErrorException declaration
45#include "blocxx/Format.hpp"
46#include "blocxx/Process.hpp"
48#include "blocxx/String.hpp"
49#include "blocxx/Thread.hpp"
51#include "blocxx/Paths.hpp"
54#include "blocxx/ThreadPool.hpp"
55#include "blocxx/Runnable.hpp"
56#include "blocxx/LazyGlobal.hpp"
57#include "blocxx/Logger.hpp"
60#include "blocxx/System.hpp"
61
62#ifdef BLOCXX_WIN32
63#include "blocxx/WinProcessUtils.hpp"
64#else
65#include <sys/wait.h>
66#endif
67
68#include <fcntl.h>
69#include <signal.h>
70#include <cerrno>
71#include <cmath>
72#include <algorithm>
73#include <limits>
74#ifdef BLOCXX_HAVE_UNISTD_H
75#include <unistd.h>
76#endif
77
78#if defined(sigemptyset)
79// We want to use the function instead of the macro (for scoping reasons).
80#undef sigemptyset
81#endif // sigemptyset
82
83namespace BLOCXX_NAMESPACE
84{
85
86static const char* TERM_MESSAGE = "Terminate Process";
87
88namespace
89{
90 GlobalString COMPONENT_NAME = BLOCXX_GLOBAL_STRING_INIT("blocxx.Process");
91}
92
93// This function is called by both
94// ProcessChildImpl::pollStatus and WaitpidThreadFix::waitPid
95Process::Status pollStatusImpl(ProcId pid);
96
98
99// -------------------- Process::Status ---------------------------
100//-----------------------------------------------------------------
101
104 m_status(status)
105{
106}
107
108Process::Status::Status(int rep1, int rep2, Repr)
109: m_status_available(static_cast<bool>(rep1)),
110 m_status(rep2)
111{
112}
113
114#ifdef BLOCXX_WIN32
115
116Process::Status::Status() : m_status_available(false), m_status(STILL_ACTIVE)
117{
118}
119
120bool Process::Status::running() const
121{
122 return m_status == STILL_ACTIVE;
123}
124
126{
127 return m_status != STILL_ACTIVE;
128}
129
131{
132 return m_status != STILL_ACTIVE;
133}
134
136{
137 return m_status;
138}
139
141{
142 return m_status;
143}
144
146{
147 return false;
148}
149
151{
152 return -1;
153}
154
155bool Process::Status::stopped() const
156{
157 return false;
158}
159
161{
162 return -1;
163}
164
165#else
166
170
172{
173 return !m_status_available;
174}
175
177{
178 return m_status_available && (WIFEXITED(m_status) || WIFSIGNALED(m_status));
179}
180
182{
183 return m_status_available && WIFEXITED(m_status);
184}
185
187{
188 return WEXITSTATUS(m_status);
189}
190
192{
193 return m_status;
194}
195
197{
198 return m_status_available && WIFSIGNALED(m_status);
199}
200
202{
203 return WTERMSIG(m_status);
204}
205
207{
208 return m_status_available && WIFSTOPPED(m_status);
209}
210
212{
213 return WSTOPSIG(m_status);
214}
215
216#endif
217
218void Process::Status::repr(int & rep1, int & rep2) const
219{
220 rep1 = static_cast<int>(m_status_available);
221 rep2 = m_status;
222}
223
225{
226 return exitTerminated() && exitStatus() == 0;
227}
228
230{
231 if (running())
232 {
233 return "running";
234 }
235 else if (stopped())
236 {
237 return Format("stopped by %1", SignalUtils::signalName(stopSignal()));
238 }
239 else if (terminated())
240 {
241 if (exitTerminated())
242 {
243 return Format("exited with status %1", String(exitStatus()));
244 }
245 else if (signalTerminated())
246 {
247 return Format("terminated by signal %1", SignalUtils::signalName(termSignal()));
248 }
249 }
250 return "Unknown";
251}
252
253//-----------------------------------------------------------------------------
254
258
259namespace
260{
261
262class ChildProcessImpl : public ProcessImpl
263{
264public:
265 virtual int kill(ProcId pid, int sig)
266 {
267#ifdef BLOCXX_WIN32
268 return -1;
269#else
270 return ::kill(pid, sig) == 0 ? 0 : errno;
271#endif
272 }
273
274 virtual Process::Status pollStatus(ProcId pid)
275 {
276 if (WaitpidThreadFix::shouldUseWaitpidThreadFix())
277 {
278 return WaitpidThreadFix::waitPid(pid);
279 }
280 return pollStatusImpl(pid);
281 }
282};
283
284
285struct ZombieReaperPoolCreator
286{
287 static ThreadPool* create(int dummy)
288 {
289 return new ThreadPool(ThreadPool::DYNAMIC_SIZE, (std::numeric_limits<UInt32>::max)(), 0);
290 }
291};
292LazyGlobal<ThreadPool, int, ZombieReaperPoolCreator> g_zombieReaperPool = BLOCXX_LAZY_GLOBAL_INIT(0);
293
294class ZombieReaper : public Runnable
295{
296public:
297 ZombieReaper(ProcId pid, const ProcessImplRef& impl)
298 : m_pid(pid)
299 , m_impl(impl)
300 {
301 }
302 virtual void run()
303 {
304 Logger lgr(COMPONENT_NAME);
305 BLOCXX_LOG_DEBUG(lgr, Format("ZombieReaper getting status for %1.", m_pid));
306 Process::Status status = m_impl->pollStatus(m_pid);
307 while (!status.terminated())
308 {
309 Thread::sleep(Timeout::relative(10));
310 BLOCXX_LOG_DEBUG(lgr, Format("ZombieReaper getting status for %1.", m_pid));
311 status = m_impl->pollStatus(m_pid);
312 }
313 BLOCXX_LOG_DEBUG(lgr, Format("ZombieReaper got status for %1: %2.", m_pid, status.toString()));
314 }
315private:
316 ProcId m_pid;
317 ProcessImplRef m_impl;
318};
319
320} // end unnamed namespace
321
322
323// --- Process ---
324
326 UnnamedPipeRef const & in, UnnamedPipeRef const & out,
328)
329: m_impl(new ChildProcessImpl())
330, m_in(in)
331, m_out(out)
332, m_err(err)
333, m_pid(pid)
334, m_status()
335{
336}
337
339 const ProcessImplRef& impl, UnnamedPipeRef const & in, UnnamedPipeRef const & out,
341)
342: m_impl(impl)
343, m_in(in)
344, m_out(out)
345, m_err(err)
346, m_pid(pid)
347, m_status()
348{
349}
350
351
353: m_impl(new ChildProcessImpl())
354, m_in()
355, m_out()
356, m_err()
357, m_pid(pid)
358, m_status()
359{
360}
361
363{
364 if (m_pid < 0)
365 {
366 return;
367 }
368 try
369 {
371 }
372 catch (Exception& e)
373 {
374 Logger lgr(COMPONENT_NAME);
375 BLOCXX_LOG_DEBUG(lgr, Format("Process::~Process caught %1 from waitCloseTerm()", e));
376 // Make a last ditch attempt to prevent zombies.
377 if (!m_status.terminated())
378 {
379 BLOCXX_LOG_DEBUG(lgr, Format("Process %1 didn't exit cleanly. Creating a ZombieReaper for it.", m_pid));
380 static_cast<ThreadPool>(g_zombieReaperPool).addWork(new ZombieReaper(m_pid, m_impl));
381 }
382 }
383 catch (...)
384 {
385 // Make a last ditch attempt to prevent zombies.
386 if (!m_status.terminated())
387 {
388 Logger lgr(COMPONENT_NAME);
389 BLOCXX_LOG_DEBUG(lgr, Format("Process %1 didn't exit cleanly. Creating a ZombieReaper for it.", m_pid));
390 static_cast<ThreadPool>(g_zombieReaperPool).addWork(new ZombieReaper(m_pid, m_impl));
391 }
392 }
393}
394
396{
397 m_in = 0;
398 m_out = 0;
399 m_err = 0;
401}
402
404{
405 return m_in;
406}
407
409{
410 return m_out;
411}
412
414{
415 return m_err;
416}
417
419{
420 return m_pid;
421}
422
424{
425 // m_pid tested in case this method is called inappropriately
426 if (m_pid >= 0 && !m_status.terminated())
427 {
428 m_status = m_impl->pollStatus(m_pid);
429 }
430 return m_status;
431}
432
433namespace
434{
435 inline void upr_close(UnnamedPipeRef & x)
436 {
437 if (x)
438 {
439 x->close();
440 }
441 }
442}
443
444void Process::waitCloseTerm(float wait_initial, float wait_close, float wait_term)
445{
446 waitCloseTerm(Timeout::relative(wait_initial), Timeout::relative(wait_close), Timeout::relative(wait_term));
447}
448
449void Process::waitCloseTerm(const Timeout& wait_initial, const Timeout& wait_close, const Timeout& wait_term,
450 ETerminationSelectionFlag terminationSelectionFlag)
451{
452 if (m_pid < 0) // safety check in case called inappropriately
453 {
454 return;
455 }
456
457 processStatus(); // update m_status
458
459 if (m_status.terminated())
460 {
461 return;
462 }
463
464 if (m_pid == getCurProcessId())
465 {
466 BLOCXX_THROW(ProcessErrorException, "Process::m_pid == the current process id");
467 }
468
469 TimeoutTimer initialTimer(wait_initial);
470 TimeoutTimer closeTimer(wait_close);
471 TimeoutTimer termTimer(wait_term);
472
473 if (wait_initial.getType() == Timeout::E_RELATIVE && wait_initial.getRelative() > 0 && this->terminatesWithin(initialTimer.asAbsoluteTimeout()))
474 {
475 return;
476 }
477
478 if (wait_close.getType() == Timeout::E_RELATIVE && wait_close.getRelative() > 0)
479 {
480 // Close the streams. If the child process is blocked waiting to output,
481 // then this will cause it to get a SIGPIPE (or ERROR_BROKEN_PIPE on Windows),
482 // and it may be able to clean up after itself. Likewise, if the child process
483 // is blocked waiting for input, it will now detect EOF.
484 upr_close(m_in);
485 upr_close(m_out);
486 upr_close(m_err);
487
488 if (this->terminatesWithin(closeTimer.asAbsoluteTimeout()))
489 {
490 return;
491 }
492 }
493
494#ifdef BLOCXX_WIN32
495
496 if (wait_term.getType() == Timeout::E_RELATIVE && wait_term.getRelative() > 0 && this->terminateByMessage(termTimer.asAbsoluteTimeout()))
497 {
498 return;
499 }
500
501 // Give it a full minute to make sure we don't leave zombies hanging around
502 // if the system is heavily loaded
503 Timeout const killTimeout = Timeout::relative(60.0);
504 if (!killProcess(killTimeout, terminationSelectionFlag))
505 {
506 BLOCXX_THROW(ProcessErrorException, "Child process has not terminated after killProcess().");
507 }
508
509#else
510
511 if (wait_term.getType() == Timeout::E_RELATIVE && wait_term.getRelative() > 0 && this->killWait(termTimer.asAbsoluteTimeout(), SIGTERM, "SIGTERM", terminationSelectionFlag))
512 {
513 return;
514 }
515 // Give it a full minute to make sure we don't leave zombies hanging around
516 // if the system is heavily loaded
517 Timeout const sigkillTimeout = Timeout::relative(60.0);
518 if (!killWait(sigkillTimeout, SIGKILL, "SIGKILL", terminationSelectionFlag))
519 {
520 BLOCXX_THROW(ProcessErrorException, "Child process has not terminated after sending it a SIGKILL.");
521 }
522
523#endif
524}
525
526// Waits wait_time at most wait_time seconds for process to terminate, setting
527// m_status.
528// RETURNS: whether or not process terminated.
529//
530bool Process::terminatesWithin(const Timeout& wait_time)
531{
532 float const mult = 1.20;
533 float const max_period = 5000.0; // milliseconds
534 float period = 100.0; // milliseconds
535 TimeoutTimer timer(wait_time);
536 while (!timer.expired() && !m_status.terminated())
537 {
538 Thread::sleep(static_cast<UInt32>(period));
539 period = (std::min)(max_period, period * mult);
540 m_status = m_impl->pollStatus(m_pid);
541 timer.loop();
542 }
543 return m_status.terminated();
544}
545
546//------------------ Platform-dependent methods --------------------------
547//------------------------------------------------------------------------
548#ifdef BLOCXX_WIN32
549
551{
552 DWORD exitCode;
553
554 DWORD rc1 = WaitForSingleObject(pid, 0);
555 if(rc1 == WAIT_FAILED)
556 {
557 String msg;
558 System::lastErrorMsg("pollStatusImpl() 1: ", msg);
560 }
561
562 BOOL rc = GetExitCodeProcess(pid, &exitCode);
563
564 if (!rc)
565 {
566 String msg;
567 System::lastErrorMsg("pollStatusImpl() 2: ", msg);
568 BLOCXX_THROW_ERRNO_MSG(ProcessErrorException, msg);
569 }
570
571 return Process::Status(pid, exitCode);
572}
573
574// Sends a defined message to a process hoping that the process knows it and
575// will be able to terminate itself
576bool Process::terminateByMessage(const Timeout& waitTime)
577{
578 DWORD bsmApp = BSM_APPLICATIONS;
579 UINT termMsg = RegisterWindowMessage(TERM_MESSAGE);
580 BOOL bSucceed = BroadcastSystemMessage(BSF_IGNORECURRENTTASK, &bsmApp, termMsg, NULL, NULL);
581
582 if (bSucceed == -1)
583 {
584 if (this->processStatus().terminated())
585 {
586 return true;
587 }
588 else
589 {
590 String msg;
591 System::lastErrorMsg("Process::terminateByMessage()", msg);
592 BLOCXX_THROW_ERRNO_MSG(ProcessErrorException, msg);
593 }
594 }
595
596 return this->terminatesWithin(waitTime);
597}
598
599bool Process::killProcess(const Timeout& waitTime, ETerminationSelectionFlag terminationSelectionFlag)
600{
601 DWORD result = ERROR_SUCCESS;
602
603 DWORD pId = WinUtils::getProcessIdNT(m_pid);
604 if (terminationSelectionFlag == E_TERMINATE_PROCESS_GROUP)
605 {
606 result = WinUtils::killProcessGroup(pId);
607 }
608 else
609 {
610 result = WinUtils::killProcess(pId);
611 }
612
613 if (result != ERROR_SUCCESS)
614 {
615 if (this->processStatus().terminated())
616 {
617 return true;
618 }
619 else
620 {
621 String msg;
622 System::lastErrorMsg("Process::killProcess()", msg);
623 BLOCXX_THROW_ERRNO_MSG(ProcessErrorException, msg);
624 }
625 }
626
627 return this->terminatesWithin(waitTime);
628}
629
631{
632 return GetCurrentProcess();
633}
634
635#else
636
638{
639 ProcId wpid;
640 int status;
641
642 do
643 {
644 // Use WUNTRACED so that we can detect if process stopped
645 wpid = ::waitpid(pid, &status, WNOHANG | WUNTRACED);
646
647 } while (wpid < 0 && errno == EINTR);
648
649 if (wpid < 0)
650 {
652 }
653 return Process::Status(wpid, status);
654}
655
656// Sends signal sig to child process and waits wait_time seconds for it
657// to terminate. If an error occurs, signame is used in constructing the
658// error message.
659//
660bool Process::killWait(const Timeout& wait_time, int sig, char const * signame, ETerminationSelectionFlag terminationSelectionFlag)
661{
662 ProcId killArg = terminationSelectionFlag == E_TERMINATE_PROCESS_GROUP ? -m_pid : m_pid;
663 int errnum = m_impl->kill(killArg, sig);
664 if (errnum != 0)
665 {
666 // maybe kill() failed because child terminated first
667 if (this->processStatus().terminated())
668 {
669 return true;
670 }
671 else
672 {
673 Format fmt("Failed sending %1 to process %2.", signame, m_pid);
674 char const * msg = fmt.c_str();
675 errno = errnum;
677 }
678 }
679 return this->terminatesWithin(wait_time);
680}
681
683{
684 return ::getpid();
685}
686
687#endif
688
689} // namespace BLOCXX_NAMESPACE
#define BLOCXX_DEFINE_EXCEPTION(NAME)
Define a new exception class named <NAME>Exception that derives from Exception.
#define BLOCXX_THROW(exType, msg)
Throw an exception using FILE and LINE.
#define BLOCXX_THROW_ERRNO_MSG(exType, msg)
Throw an exception using FILE, LINE, errno and strerror(errno)
#define BLOCXX_GLOBAL_STRING_INIT(str)
#define BLOCXX_LAZY_GLOBAL_INIT(...)
Statically initialize a LazyGlobal instance.
#define BLOCXX_LOG_DEBUG(logger, message)
Log message to logger with the Debug level.
Definition Logger.hpp:381
#define BLOCXX_INVALID_HANDLE
Definition Types.hpp:136
This class is the base of all exceptions thrown by BloCxx code.
Definition Exception.hpp:66
const char * c_str() const
Definition Format.cpp:55
Logging interface.
Definition Logger.hpp:87
Portable process status.
Definition Process.hpp:123
String toString() const
Get a string representation of the status suitable for debugging or logging.
Definition Process.cpp:229
int getPOSIXwaitpidStatus() const
Get the result from waitpid()
Definition Process.cpp:191
void repr(int &rep1, int &rep2) const
Definition Process.cpp:218
@ E_TERMINATE_PROCESS_GROUP
The process and any descendent processes which are in the process group will be terminated.
Definition Process.hpp:212
Process(UnnamedPipeRef const &in, UnnamedPipeRef const &out, UnnamedPipeRef const &err, ProcId pid)
Definition Process.cpp:325
void waitCloseTerm(const Timeout &wait_initial=Timeout::relative(5.0), const Timeout &wait_close=Timeout::relative(10.0), const Timeout &wait_term=Timeout::relative(15.0), ETerminationSelectionFlag terminationSelectionFlag=E_TERMINATE_PROCESS_GROUP)
Waits for the child process to terminate, taking increasingly severe measures to ensure that this hap...
Definition Process.cpp:449
UnnamedPipeRef out() const
Stdout for the child process.
Definition Process.cpp:408
ProcessImplRef m_impl
Definition Process.hpp:285
ProcId pid() const
Process ID for the child process.
Definition Process.cpp:418
bool terminatesWithin(const Timeout &wait_time)
Definition Process.cpp:530
void release()
Releases ownership of the ProcId and UnnamedPipes held by this object.
Definition Process.cpp:395
virtual ~Process()
If release has been called on this object, does nothing.
Definition Process.cpp:362
UnnamedPipeRef in() const
Stdin for the child process.
Definition Process.cpp:403
UnnamedPipeRef err() const
Stderr for the child process.
Definition Process.cpp:413
bool killWait(const Timeout &wait_time, int sig, char const *signame, ETerminationSelectionFlag terminationSelectionFlag)
Definition Process.cpp:660
Abstract interface for abstracting details of dealing with a process.
Definition Process.hpp:297
This String class is an abstract data type that represents as NULL terminated string of characters.
Definition String.hpp:67
static void sleep(UInt32 milliSeconds)
Suspend execution of the current thread until the given number of milliSeconds have elapsed.
Definition Thread.hpp:317
The ThreadPool class is used to coordinate a group of threads.
A timeout can be absolute, which means that it will happen at the specified DateTime.
Definition Timeout.hpp:56
float getRelative() const
Definition Timeout.cpp:88
ETimeoutType getType() const
Definition Timeout.cpp:76
static Timeout relative(float seconds)
Definition Timeout.cpp:58
A TimeoutTimer is used by an algorithm to determine when a timeout has expired.
bool expired() const
Indicates whether the last loop time has exceeded the timeout.
void loop()
Meant to be called by timeout functions which loop, but don't want to reset the interval.
Timeout asAbsoluteTimeout() const
Converts the timer to an absolute timeout.
virtual int close()=0
Close the pipe.
const char * signalName(int sig)
String lastErrorMsg(bool socketError)
Definition System.cpp:96
Taken from RFC 1321.
LazyGlobal< String, char const *const > GlobalString
IntrusiveReference< UnnamedPipe > UnnamedPipeRef
Process::Status pollStatusImpl(ProcId pid)
Definition Process.cpp:637
IntrusiveReference< ProcessImpl > ProcessImplRef
Definition Process.hpp:57
class BLOCXX_COMMON_API Logger
Definition CommonFwd.hpp:63
static const char * TERM_MESSAGE
Definition Process.cpp:86