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"
47 #include "blocxx/SafeCString.hpp"
48 #include "blocxx/String.hpp"
49 #include "blocxx/Thread.hpp"
50 #include "blocxx/UnnamedPipe.hpp"
51 #include "blocxx/Paths.hpp"
52 #include "blocxx/TimeoutTimer.hpp"
53 #include "blocxx/SignalUtils.hpp"
54 #include "blocxx/ThreadPool.hpp"
55 #include "blocxx/Runnable.hpp"
56 #include "blocxx/LazyGlobal.hpp"
57 #include "blocxx/Logger.hpp"
58 #include "blocxx/GlobalString.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 
83 namespace BLOCXX_NAMESPACE
84 {
85 
86 static const char* TERM_MESSAGE = "Terminate Process";
87 
88 namespace
89 {
91 }
92 
93 // This function is called by both
94 // ProcessChildImpl::pollStatus and WaitpidThreadFix::waitPid
95 Process::Status pollStatusImpl(ProcId pid);
96 
98 
99 // -------------------- Process::Status ---------------------------
100 //-----------------------------------------------------------------
101 
103 : m_status_available(pid > 0),
104  m_status(status)
105 {
106 }
107 
108 Process::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 
116 Process::Status::Status() : m_status_available(false), m_status(STILL_ACTIVE)
117 {
118 }
119 
120 bool Process::Status::running() const
121 {
122  return m_status == STILL_ACTIVE;
123 }
124 
125 bool Process::Status::terminated() const
126 {
127  return m_status != STILL_ACTIVE;
128 }
129 
131 {
132  return m_status != STILL_ACTIVE;
133 }
134 
135 int Process::Status::exitStatus() const
136 {
137  return m_status;
138 }
139 
141 {
142  return m_status;
143 }
144 
146 {
147  return false;
148 }
149 
150 int Process::Status::termSignal() const
151 {
152  return -1;
153 }
154 
155 bool Process::Status::stopped() const
156 {
157  return false;
158 }
159 
160 int Process::Status::stopSignal() const
161 {
162  return -1;
163 }
164 
165 #else
166 
167 Process::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 
218 void 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 
256 {
257 }
258 
259 namespace
260 {
261 
262 class ChildProcessImpl : public ProcessImpl
263 {
264 public:
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  {
277  {
278  return WaitpidThreadFix::waitPid(pid);
279  }
280  return pollStatusImpl(pid);
281  }
282 };
283 
284 
285 struct ZombieReaperPoolCreator
286 {
287  static ThreadPool* create(int dummy)
288  {
289  return new ThreadPool(ThreadPool::DYNAMIC_SIZE, (std::numeric_limits<UInt32>::max)(), 0);
290  }
291 };
292 LazyGlobal<ThreadPool, int, ZombieReaperPoolCreator> g_zombieReaperPool = BLOCXX_LAZY_GLOBAL_INIT(0);
293 
294 class ZombieReaper : public Runnable
295 {
296 public:
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  {
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  }
315 private:
318 };
319 
320 } // end unnamed namespace
321 
322 
323 // --- Process ---
324 
326  UnnamedPipeRef const & in, UnnamedPipeRef const & out,
327  UnnamedPipeRef const & err, ProcId pid
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 
433 namespace
434 {
435  inline void upr_close(UnnamedPipeRef & x)
436  {
437  if (x)
438  {
439  x->close();
440  }
441  }
442 }
443 
444 void 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 
449 void 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 //
530 bool 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);
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
576 bool 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 
599 bool 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  {
651  BLOCXX_THROW_ERRNO_MSG(ProcessErrorException, "waitpid() failed");
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 //
660 bool 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