4 * (c) Copyright 2007-2008 by Intra2net AG
11 #include "async_process.hpp"
18 #include <sys/socket.h>
19 #include <sys/types.h>
24 #include <filefunc.hxx>
30 #define DOUT(msg) std::cout << msg << std::endl
31 #define FODOUT(obj,msg) std::cout << typeid(*obj).name() << "[" << obj << "]:" << msg << std::endl
32 #define ODOUT(msg) std::cout << typeid(*this).name() << "[" << this << "]:" << msg << std::endl
34 #define DOUT(msg) do {} while (0)
35 #define FODOUT(obj,msg) do {} while (0)
36 #define ODOUT(msg) do {} while (0)
43 using namespace AsyncIo;
46 * local configuration values
51 /// the capacity of the child status list (/ vector)
52 const unsigned int pid_pool_capacity= 512;
54 } // eo namespace config
58 /// the previous handler for the child signal (SIGCHLD)
59 void (*oldChildHandler)(int) = NULL;
61 /// method pointer for activating process manager
62 void (ProcessManager::*_activate_manager)();
64 PidStateList pending_pid_states;
68 * signal handler for child signal (SIGCHLD)
69 * @param sig the signal number as provided by the OS
71 void handleSigChild(int sig)
75 while ( (pid = waitpid(-1,&status,WNOHANG)) > 0)
77 pending_pid_states.push_back( PidStatePair(pid,status) );
79 if (_activate_manager)
81 // tricky way to access a protected method without being a (official) friend:
82 ( ProcessManager::getInstance()->*_activate_manager)();
85 signal(sig,handleSigChild);
86 } // eo handleSigChild
92 typedef std::pair<pid_t, ProcessImplementation*> PidProcPair;
93 typedef std::list< PidProcPair > PidProcList;
96 template< typename F, typename S >
100 CmpFirst ( F f ) : _f(f) {}
101 bool operator () ( const std::pair<F,S>& v ) const { return v.first == _f; }
102 }; // eo struct CmpFirst
105 std::list<ProcessImplementation*> g_process_list;
106 PidProcList g_pid_list;
109 void addProcessInstance( ProcessImplementation* obj )
111 g_process_list.push_back(obj);
112 } // eo addProcessInstance(ProcessImplementation*)
115 void removeProcessInstance( ProcessImplementation* obj )
117 // remove obj from list
118 g_process_list.remove(obj);
119 // clear pointers in pid list
120 for(PidProcList::iterator it= g_pid_list.begin();
121 it != g_pid_list.end();
124 if (it->second == obj)
129 } // eo removeProcessInstance(ProcessImplementation*)
132 void addChildProcess( pid_t pid, ProcessImplementation* obj)
134 g_pid_list.push_back ( PidProcPair(pid,obj) );
135 } // eo addChildProcess(pid_t,ProcessImplementation*)
138 void removeChildProcess ( pid_t pid, ProcessImplementation* obj)
140 PidProcList::iterator it= std::find(
141 g_pid_list.begin(), g_pid_list.end(),
142 PidProcPair(pid,obj));
143 if (it != g_pid_list.end())
145 g_pid_list.erase(it);
147 } // eo removeChildProcess(pid_t,ProcessImplementation*)
150 bool findChildProcess ( pid_t pid, ProcessImplementation* & obj )
152 PidProcList::iterator it = std::find_if(
153 g_pid_list.begin(), g_pid_list.end(),
154 CmpFirst<pid_t,ProcessImplementation*>(pid) );
155 if (it == g_pid_list.end())
161 } // eo findChildProcess(pid_t,ProcessImplementation*&)
164 } // eo namespace process
176 * convenience tool for closing file descriptors...
182 FdCloser(int fd=-1) : m_fd(fd) {}
186 if (m_fd >= 0) ::close(m_fd);
189 void release() { m_fd= -1; }
191 }; // eo struct FdCloser
195 } // eo namespace <anonymous>
207 * installs the handler for the child signal (SIGCHLD).
208 * Installing this handler is mandatory for the process subsystem to work correctly.
209 * @return @a true iff the child handler is successfully installed.
211 bool installChildHandler()
218 if (! ProcessManager::getInstance() )
220 // we need an instance of the process manager
223 pending_pid_states.reserve( config::pid_pool_capacity );
224 oldChildHandler = signal( Signal::CHLD, handleSigChild );
225 if (oldChildHandler == SIG_ERR)
227 oldChildHandler= NULL;
231 } // eo installChildHandler
235 * uninstalls the child handler.
236 * @return @a true iff the old child handler is reestablished.
238 bool restoreChildHandler()
240 if (!oldChildHandler)
244 void(*res)(int) = signal( Signal::CHLD, oldChildHandler);
250 oldChildHandler= NULL;
252 } // eo restoreChildHandler
258 * Implementation of ProcessImplementation
261 IOImplementation2* ProcessImplementation::_StderrOnStdout = ((IOImplementation2*) 1);
262 IOImplementation2* ProcessImplementation::_UseParentsStderr = ((IOImplementation2*) 0);
266 * @brief constructor for the process implementation.
268 * the constructor takes the path to the executable and (initial) cli arguments.
270 * @param path path to the executable.
271 * @param args initial command line arguments.
273 ProcessImplementation::ProcessImplementation(
274 const std::string& path,
275 const std::vector<std::string>& args
277 : IOImplementation(-1,-1)
280 , m_create_new_session(false)
282 , m_state(ProcessState::stopped)
285 m_args.push_back(path);
286 std::copy( args.begin(), args.end(), std::back_inserter(m_args) );
287 process::addProcessInstance(this);
288 } // eo ProcessImplementation::ProcessImplementation(const std::string&)
291 ProcessImplementation::~ProcessImplementation()
293 if (m_pid > 0 && m_state!=ProcessState::stopped)
297 process::removeProcessInstance(this);
298 } // eo ProcessImplementation::~ProcessImplementation()
301 void ProcessImplementation::close(Direction direction)
303 inherited::close(direction);
304 if (!inherited::opened() && (m_state != ProcessState::stopped) )
308 } // eo ProcessImplementation::close(Direction)
312 * returns an object for adding new arguments to the argument list.
313 * @return the adder object.
315 PushBackFiller<std::string, std::vector > ProcessImplementation::getArgAdder()
317 return PushBackFiller<std::string, std::vector >(m_args);
318 } // eo ProcessImplementation::getArgAdder()
322 * @brief set if the process should create a new session when started.
323 * @param enable determine if the process should start a new session.
324 * @return @a true iff the value of enable was accepted.
326 * If the process is already running, a new value is not accepted.
328 bool ProcessImplementation::setCreateNewSession( bool enable )
330 if (m_state != ProcessState::stopped and enable != m_create_new_session)
334 m_create_new_session= enable;
336 } // eo ProcessImplementation::setCreateNewSession(bool);
340 * @brief sets a new nice increment.
341 * @param nice the desired nice increment.
342 * @return @a true if the value was accepted and - in case the process was already started -
343 * the nice value was successfully changed.
345 bool ProcessImplementation::setNice(int nice)
348 if (m_state != ProcessState::stopped)
350 int delta= m_nice_inc + nice;
352 int res= ::nice(delta);
353 if (res == -1 and errno !=0 )
363 } // eo ProcessImplementation::setNice(int)
367 * @brief sets the work dir the process should be started with.
368 * @param workdir the workdir
369 * @return @a true if the new workdir was accepted.
371 * The method will return @a false if the process is already started.
372 * The workdir can only be set before the process is started.
374 bool ProcessImplementation::setWorkDir(const std::string& workdir)
376 if ( m_state != ProcessState::stopped and workdir != m_workdir)
380 if (not workdir.empty())
382 I2n::Stat stat(workdir);
383 if (not stat or not stat.is_directory())
390 } // eo ProcessImplementation::setWorkDir(const std::string&)
394 * @brief sets new arguments for the process (the path to the binary is kept).
396 * @param args the new cli arguments for the subprocess (replacing the old ones).
398 void ProcessImplementation::resetArgs( const std::vector< std::string >& args )
400 if (m_args.size() > 1)
402 m_args.erase( ++m_args.begin(), m_args.end());
404 std::copy( args.begin(), args.end(), std::back_inserter(m_args) );
405 } // eo ProcessImplementation::resetArgs(const std::vectors< std::string >&)
409 * starts the new process.
410 * provides pipes for sending data to/ receiving data from the new process.
411 * Basically forks and execs the new process.
413 * @param stderr if not NULL the given object will be connected to stderr of the new process.
414 * The object can then be used for reading the data from the process' stderr; but cannot be written to.
415 * (The object will be closed if it was open).
416 * If the constant @a ProcessImplementation::StderrOnStdout is passed then stderr of the new process will
417 * be written to the same channel as stdout (i.e. can be read from the process class instance like the
419 * If NULL then the stderr channel from the parent process will also be used by the child.
420 * @return @a true iff the new subprocess started.
422 bool ProcessImplementation::startProcess( IOImplementation2 *stderr )
424 bool stderr2stdout= false;
426 m_input_buffer.clear();
427 if (m_pid > 0 && m_state != ProcessState::stopped)
429 // process still/already running...
434 if (stderr == _StderrOnStdout)
440 int to_process_pipe[2];
441 int from_process_pipe[2];
442 int from_process_stderr_pipe[2]= { -1, -1 };
444 if ( ::pipe(to_process_pipe) )
449 FdCloser closeTo0( to_process_pipe[0] );
450 FdCloser closeTo1( to_process_pipe[1] );
451 if ( ::pipe (from_process_pipe) )
456 FdCloser closeFrom0( from_process_pipe[0] );
457 FdCloser closeFrom1( from_process_pipe[1] );
460 if (stderr->opened()) stderr->close();
461 if ( ::pipe (from_process_stderr_pipe) )
467 FdCloser closeFromErr0( from_process_stderr_pipe[0] );
468 FdCloser closeFromErr1( from_process_stderr_pipe[1] );
472 if ( m_pid == (pid_t)-1 )
476 // error; something went wrong
481 // we are in the parent part
483 // keep the fd's we need and (later) close the other ones:
484 closeTo1.release(); // don't close this fd!
485 setWriteFd(to_process_pipe[1]);
486 closeFrom0.release(); // don't close this fd!
487 setReadFd(from_process_pipe[0]);
491 closeFromErr0.release(); // don't close this fd!
492 stderr->setReadFd(from_process_stderr_pipe[0]);
495 m_state= ProcessState::running;
496 process::addChildProcess(m_pid,this);
497 DOUT(" started child with pid " << m_pid);
502 // we are in the child part
504 // dup the fd's for stdin/-out/-err into place:
505 ::dup2(to_process_pipe[0],0);
506 ::dup2(from_process_pipe[1],1);
509 ::dup2(from_process_stderr_pipe[1],2);
510 ::close(from_process_stderr_pipe[0]); ::close(from_process_stderr_pipe[1]);
512 else if (stderr2stdout)
514 ::dup2(from_process_pipe[1],2);
516 // close what we don't need:
517 ::close(to_process_pipe[0]); ::close(to_process_pipe[1]);
518 ::close(from_process_pipe[0]); ::close(from_process_pipe[1]);
520 // set workdir if requested:
521 if (not m_workdir.empty())
523 int r= ::chdir( m_workdir.c_str() );
533 char **argv= new char*[m_args.size()+1];
535 for(std::vector<std::string>::iterator it= m_args.begin();
539 argv[i]= strdup( it->c_str() );
542 // update nice level:
547 // create a new session id if requested:
548 if (m_create_new_session)
553 execv(m_path.c_str(), argv);
554 // exit if exec failed
556 //cleanup! ... just joking; we exec or we exit, in either case the system cleans
557 // everything which needs to be cleaned up.
559 return false; // keep the compiler happy...
560 } // eo ProcessImplementation::startProcess()
564 * convenience method for starting the child process.
565 * This method uses predefined enum values for the stderr handling mode.
567 * @param stderr_mode the desired stderr mode.
568 * @return @a true iff the child process was created.
570 bool ProcessImplementation::startProcess( ProcessImplementation::StderrMode stderr_mode )
574 case UseParentsStderr:
575 return startProcess( _UseParentsStderr );
578 return startProcess( _StderrOnStdout );
581 }; // eo ProcessImplementation::startProcess(ProcessImplementation::StderrMode)
587 * @todo think about a more intelligent handling...
589 void ProcessImplementation::stopProcess(bool force)
591 // TODO: do it somewhat more intelligent?!
595 //TODO: set running state?
601 } // eo ProcessImplementation::stop(bool)
606 * sends a signal to the child process.
607 * @param signal the Signal which should be send.
608 * @return @a true if the signal was sent; @a false if an error occured.
610 bool ProcessImplementation::kill(Signal signal)
613 if (m_pid == 0 || m_pid == (pid_t)-1)
618 int res = ::kill(m_pid, signal);
624 if (signal == Signal::CONT && m_state == ProcessState::suspended)
626 m_state = ProcessState::running;
629 } // eo ProcessImplementation::kill(Signal)
634 * set a new child state with information gobbled by the child signal handler.
636 * @note This method should only be called by the process manager!
638 * @param pid the pid of the child process.
639 * @param status the new status value (as delivered by waitpid())
641 void ProcessImplementation::setChildState(pid_t pid, int status)
643 DOUT("setChildState("<<pid<<","<<status<<") pid="<<m_pid);
646 // old child... ignore!
649 if (WIFSTOPPED(status))
653 int stopsignal = WSTOPSIG(status);
654 // make stop signal available in exit_code:
655 m_exit_code= (stopsignal << 8);
656 m_state= ProcessState::suspended;
660 if (WIFCONTINUED(status))
663 // continued after a stop:
664 m_state= ProcessState::running;
668 if (WIFEXITED(status))
672 m_exit_code= (0xff & WEXITSTATUS(status));
674 close(Direction::out);
675 m_state= ProcessState::stopped;
676 m_signal_terminated();
679 if (WIFSIGNALED(status))
681 DOUT("signaled stop");
683 int termsignal = WTERMSIG(status);
684 // make term signal available in exit code (normal exit codes are only 8 bit)
685 m_exit_code = (termsignal << 8);
687 close(Direction::out);
688 m_state= ProcessState::stopped;
689 m_signal_terminated();
692 // this point should never be reached...!!
693 } // eo ProcessImplementation::setChildState(pid_t,int)
697 * implementation of ProcessManager
700 /// the instance of the process manager (highlander; there can be only one!)
701 ProcessManager* ProcessManager::the_instance= NULL;
704 ProcessManager::ProcessManager()
707 } // eo ProcessManager::ProcessManager
711 * delivers the process manager instance (generate if it doesn't exist)
712 * @return the process manager instance
714 ProcessManager* ProcessManager::getInstance()
718 the_instance = new ProcessManager();
719 _activate_manager = &ProcessManager::activateMe;
722 } // eo ProcessManager::getInstance
726 * activate the timer so it's handled by the next backend cycle
728 void ProcessManager::activateMe()
732 } // eo ProcessManager::activateMe
736 * real work is done here.
737 * Processes the information collected by the child signal handler.
739 void ProcessManager::execute()
741 PidStateList pid_state_list;
743 // block child signals (within this scope)
744 ScopedSignalBlocker blocker( Signal::CHLD );
745 // and now fetch the list of pending information
746 // (simply swap with our local empty list)
747 std::swap(pid_state_list, pending_pid_states);
748 // reserve the desired (minimum) capacity
749 pending_pid_states.reserve( config::pid_pool_capacity );
751 ODOUT("exec, " << pid_state_list.size() << " entries");
754 for(PidStateList::iterator it = pid_state_list.begin();
755 it != pid_state_list.end();
758 pid_t pid = it->first;
759 int status = it->second;
760 ODOUT(" pid=" << pid << ", status=" << status);
761 ProcessImplementation *process_obj;
762 if (process::findChildProcess(pid,process_obj))
764 ODOUT(" local managed child, process_obj="<< process_obj);
765 // pid found in list:
766 if (!WIFSTOPPED(status)
768 && !WIFCONTINUED(status)
772 // take it from list if the child exited:
773 process::removeChildProcess(pid,process_obj);
777 // give the process object a chance to handle the state change:
778 process_obj->setChildState(pid, status);
783 ODOUT("foreign child");
784 // pid not found in list:
785 /* NOTE: in a non threaded environment this pid must be from a child process which is not
786 managed by this process classes; since this method is called after all setup of a child process
787 is done (; especially entering the new child pid into our internal lists).
789 m_foreign_pid_states.push_back(*it);
793 // handle the foreign childs:
796 * fetch a (pid,status) from the list, erase it (to avoid reentrance problems)
797 * and fire the signal. If someone forks childs outside this module then he can
798 * connect to the signal and receive all necessary status information gobbled by
801 while (! m_foreign_pid_states.empty())
803 PidStateList::iterator it= m_foreign_pid_states.begin();
804 pid_t pid = it->first;
805 int status = it->second;
806 m_foreign_pid_states.erase(it);
807 m_foreign_child_state_changed_signal(pid,status);
811 } // eo ProcessManager::execute
814 } // eo namespace AsyncIo