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
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
103: m_status_available(pid > 0),
104 m_status(status)
105{
106}
107
109: m_status_available(static_cast<bool>(rep1)),
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
167Process::Status::Status() : m_status_available(false), m_status(0)
168{
169}
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};
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,
340 UnnamedPipeRef const & err, ProcId pid
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 {
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
448
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
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.
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
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
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//
531{
532 float const mult = 1.20;
533 float const max_period = 5000.0; // milliseconds
534 float period = 100.0; // milliseconds
536 while (!timer.expired() && !m_status.terminated())
537 {
538 Thread::sleep(static_cast<UInt32>(period));
539 period = (std::min)(max_period, period * mult);
541 timer.loop();
542 }
543 return m_status.terminated();
544}
545
546//------------------ Platform-dependent methods --------------------------
547//------------------------------------------------------------------------
548#ifdef BLOCXX_WIN32
549
551{
553
554 DWORD rc1 = WaitForSingleObject(pid, 0);
555 if(rc1 == WAIT_FAILED)
556 {
557 String msg;
558 System::lastErrorMsg("pollStatusImpl() 1: ", msg);
560 }
561
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{
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{
602
603 DWORD pId = WinUtils::getProcessIdNT(m_pid);
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
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//
661{
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
ProcessImplRef m_impl
Definition Process.cpp:317
ProcId m_pid
Definition Process.cpp:316
int sig
#define BLOCXX_INVALID_HANDLE
Definition Types.hpp:136
ExceptionPtr m_err
This class is the base of all exceptions thrown by BloCxx code.
Definition Exception.hpp:66
This class can be used to store a global variable that is lazily initialized in a thread safe manner.
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
virtual Process::Status pollStatus(ProcId pid)=0
virtual int kill(ProcId pid, int sig)=0
Sends signal sig to process pid.
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
static Timeout relative(float seconds)
Definition Timeout.cpp:58
A TimeoutTimer is used by an algorithm to determine when a timeout has expired.
Timeout asAbsoluteTimeout() const
Converts the timer to an absolute timeout.
const char * signalName(int sig)
String lastErrorMsg(bool socketError)
Definition System.cpp:96
Taken from RFC 1321.
Process::Status pollStatusImpl(ProcId pid)
Definition Process.cpp:637
bool operator==(const Array< T > &x, const Array< T > &y)
class BLOCXX_COMMON_API Logger
Definition CommonFwd.hpp:63
static const char * TERM_MESSAGE
Definition Process.cpp:86