2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
8 As a special exception, if other files instantiate templates or use macros
9 or inline functions from this file, or you compile this file and link it
10 with other works to produce a work based on this file, this file
11 does not by itself cause the resulting work to be covered
12 by the GNU General Public License.
14 However the source code for this file must still be made available
15 in accordance with section (3) of the GNU General Public License.
17 This exception does not invalidate any other reasons why a work based
18 on this file might be covered by the GNU General Public License.
22 * (c) Copyright 2007-2008 by Intra2net AG
27 #include "async_process.hpp"
34 #include <sys/socket.h>
35 #include <sys/types.h>
40 //#include <filefunc.hxx>
46 #define DOUT(msg) std::cout << msg << std::endl
47 #define FODOUT(obj,msg) std::cout << typeid(*obj).name() << "[" << obj << "]:" << msg << std::endl
48 #define ODOUT(msg) std::cout << typeid(*this).name() << "[" << this << "]:" << msg << std::endl
50 #define DOUT(msg) do {} while (0)
51 #define FODOUT(obj,msg) do {} while (0)
52 #define ODOUT(msg) do {} while (0)
59 using namespace AsyncIo;
62 * local configuration values
67 /// the capacity of the child status list (/ vector)
68 const unsigned int pid_pool_capacity= 512;
70 } // eo namespace config
74 /// the previous handler for the child signal (SIGCHLD)
75 void (*oldChildHandler)(int) = NULL;
77 /// method pointer for activating process manager
78 void (ProcessManager::*_activate_manager)();
80 PidStateList pending_pid_states;
84 * signal handler for child signal (SIGCHLD)
85 * @param sig the signal number as provided by the OS
87 void handleSigChild(int sig)
91 while ( (pid = waitpid(-1,&status,WNOHANG)) > 0)
93 pending_pid_states.push_back( PidStatePair(pid,status) );
95 if (_activate_manager)
97 // tricky way to access a protected method without being a (official) friend:
98 ( ProcessManager::getInstance()->*_activate_manager)();
101 signal(sig,handleSigChild);
102 } // eo handleSigChild
108 typedef std::pair<pid_t, ProcessImplementation*> PidProcPair;
109 typedef std::list< PidProcPair > PidProcList;
112 template< typename F, typename S >
116 CmpFirst ( F f ) : _f(f) {}
117 bool operator () ( const std::pair<F,S>& v ) const { return v.first == _f; }
118 }; // eo struct CmpFirst
121 std::list<ProcessImplementation*> g_process_list;
122 PidProcList g_pid_list;
125 void addProcessInstance( ProcessImplementation* obj )
127 g_process_list.push_back(obj);
128 } // eo addProcessInstance(ProcessImplementation*)
131 void removeProcessInstance( ProcessImplementation* obj )
133 // remove obj from list
134 g_process_list.remove(obj);
135 // clear pointers in pid list
136 for(PidProcList::iterator it= g_pid_list.begin();
137 it != g_pid_list.end();
140 if (it->second == obj)
145 } // eo removeProcessInstance(ProcessImplementation*)
148 void addChildProcess( pid_t pid, ProcessImplementation* obj)
150 g_pid_list.push_back ( PidProcPair(pid,obj) );
151 } // eo addChildProcess(pid_t,ProcessImplementation*)
154 void removeChildProcess ( pid_t pid, ProcessImplementation* obj)
156 PidProcList::iterator it= std::find(
157 g_pid_list.begin(), g_pid_list.end(),
158 PidProcPair(pid,obj));
159 if (it != g_pid_list.end())
161 g_pid_list.erase(it);
163 } // eo removeChildProcess(pid_t,ProcessImplementation*)
166 bool findChildProcess ( pid_t pid, ProcessImplementation* & obj )
168 PidProcList::iterator it = std::find_if(
169 g_pid_list.begin(), g_pid_list.end(),
170 CmpFirst<pid_t,ProcessImplementation*>(pid) );
171 if (it == g_pid_list.end())
177 } // eo findChildProcess(pid_t,ProcessImplementation*&)
180 } // eo namespace process
192 * convenience tool for closing file descriptors...
198 FdCloser(int fd=-1) : m_fd(fd) {}
202 if (m_fd >= 0) ::close(m_fd);
205 void release() { m_fd= -1; }
207 }; // eo struct FdCloser
211 } // eo namespace <anonymous>
223 * installs the handler for the child signal (SIGCHLD).
224 * Installing this handler is mandatory for the process subsystem to work correctly.
225 * @return @a true iff the child handler is successfully installed.
227 bool installChildHandler()
234 if (! ProcessManager::getInstance() )
236 // we need an instance of the process manager
239 pending_pid_states.reserve( config::pid_pool_capacity );
240 oldChildHandler = signal( Signal::CHLD, handleSigChild );
241 if (oldChildHandler == SIG_ERR)
243 oldChildHandler= NULL;
247 } // eo installChildHandler
251 * uninstalls the child handler.
252 * @return @a true iff the old child handler is reestablished.
254 bool restoreChildHandler()
256 if (!oldChildHandler)
260 void(*res)(int) = signal( Signal::CHLD, oldChildHandler);
266 oldChildHandler= NULL;
268 } // eo restoreChildHandler
274 * Implementation of ProcessImplementation
277 IOImplementation2* ProcessImplementation::_StderrOnStdout = ((IOImplementation2*) 1);
278 IOImplementation2* ProcessImplementation::_UseParentsStderr = ((IOImplementation2*) 0);
282 * @brief constructor for the process implementation.
284 * the constructor takes the path to the executable and (initial) cli arguments.
286 * @param path path to the executable.
287 * @param args initial command line arguments.
289 ProcessImplementation::ProcessImplementation(
290 const std::string& path,
291 const std::vector<std::string>& args
293 : IOImplementation(-1,-1)
296 , m_create_new_session(false)
298 , m_state(ProcessState::stopped)
301 m_args.push_back(path);
302 std::copy( args.begin(), args.end(), std::back_inserter(m_args) );
303 process::addProcessInstance(this);
304 } // eo ProcessImplementation::ProcessImplementation(const std::string&)
307 ProcessImplementation::~ProcessImplementation()
309 if (m_pid > 0 && m_state!=ProcessState::stopped)
313 process::removeProcessInstance(this);
314 } // eo ProcessImplementation::~ProcessImplementation()
317 void ProcessImplementation::close(Direction direction)
319 inherited::close(direction);
320 if (!inherited::opened() && (m_state != ProcessState::stopped) )
324 } // eo ProcessImplementation::close(Direction)
328 * returns an object for adding new arguments to the argument list.
329 * @return the adder object.
331 PushBackFiller<std::string, std::vector > ProcessImplementation::getArgAdder()
333 return PushBackFiller<std::string, std::vector >(m_args);
334 } // eo ProcessImplementation::getArgAdder()
338 * @brief set if the process should create a new session when started.
339 * @param enable determine if the process should start a new session.
340 * @return @a true iff the value of enable was accepted.
342 * If the process is already running, a new value is not accepted.
344 bool ProcessImplementation::setCreateNewSession( bool enable )
346 if (m_state != ProcessState::stopped and enable != m_create_new_session)
350 m_create_new_session= enable;
352 } // eo ProcessImplementation::setCreateNewSession(bool);
356 * @brief sets a new nice increment.
357 * @param nice the desired nice increment.
358 * @return @a true if the value was accepted and - in case the process was already started -
359 * the nice value was successfully changed.
361 bool ProcessImplementation::setNice(int nice)
364 if (m_state != ProcessState::stopped)
366 int delta= m_nice_inc + nice;
368 int res= ::nice(delta);
369 if (res == -1 and errno !=0 )
379 } // eo ProcessImplementation::setNice(int)
383 * @brief sets the work dir the process should be started with.
384 * @param workdir the workdir
385 * @return @a true if the new workdir was accepted.
387 * The method will return @a false if the process is already started.
388 * The workdir can only be set before the process is started.
390 bool ProcessImplementation::setWorkDir(const std::string& workdir)
392 if ( m_state != ProcessState::stopped and workdir != m_workdir)
396 if (not workdir.empty())
398 Utils::FileStat stat(workdir);
399 if (not stat or not stat.is_directory())
406 } // eo ProcessImplementation::setWorkDir(const std::string&)
410 * @brief sets new arguments for the process (the path to the binary is kept).
412 * @param args the new cli arguments for the subprocess (replacing the old ones).
414 void ProcessImplementation::resetArgs( const std::vector< std::string >& args )
416 if (m_args.size() > 1)
418 m_args.erase( ++m_args.begin(), m_args.end());
420 std::copy( args.begin(), args.end(), std::back_inserter(m_args) );
421 } // eo ProcessImplementation::resetArgs(const std::vectors< std::string >&)
425 * starts the new process.
426 * provides pipes for sending data to/ receiving data from the new process.
427 * Basically forks and execs the new process.
429 * @param stderr if not NULL the given object will be connected to stderr of the new process.
430 * The object can then be used for reading the data from the process' stderr; but cannot be written to.
431 * (The object will be closed if it was open).
432 * If the constant @a ProcessImplementation::StderrOnStdout is passed then stderr of the new process will
433 * be written to the same channel as stdout (i.e. can be read from the process class instance like the
435 * If NULL then the stderr channel from the parent process will also be used by the child.
436 * @return @a true iff the new subprocess started.
438 bool ProcessImplementation::startProcess( IOImplementation2 *stderr )
440 bool stderr2stdout= false;
442 m_input_buffer.clear();
443 if (m_pid > 0 && m_state != ProcessState::stopped)
445 // process still/already running...
450 if (stderr == _StderrOnStdout)
456 int to_process_pipe[2];
457 int from_process_pipe[2];
458 int from_process_stderr_pipe[2]= { -1, -1 };
460 if ( ::pipe(to_process_pipe) )
465 FdCloser closeTo0( to_process_pipe[0] );
466 FdCloser closeTo1( to_process_pipe[1] );
467 if ( ::pipe (from_process_pipe) )
472 FdCloser closeFrom0( from_process_pipe[0] );
473 FdCloser closeFrom1( from_process_pipe[1] );
476 if (stderr->opened()) stderr->close();
477 if ( ::pipe (from_process_stderr_pipe) )
483 FdCloser closeFromErr0( from_process_stderr_pipe[0] );
484 FdCloser closeFromErr1( from_process_stderr_pipe[1] );
488 if ( m_pid == (pid_t)-1 )
492 // error; something went wrong
497 // we are in the parent part
499 // keep the fd's we need and (later) close the other ones:
500 closeTo1.release(); // don't close this fd!
501 setWriteFd(to_process_pipe[1]);
502 closeFrom0.release(); // don't close this fd!
503 setReadFd(from_process_pipe[0]);
507 closeFromErr0.release(); // don't close this fd!
508 stderr->setReadFd(from_process_stderr_pipe[0]);
511 m_state= ProcessState::running;
512 process::addChildProcess(m_pid,this);
513 DOUT(" started child with pid " << m_pid);
518 // we are in the child part
520 // dup the fd's for stdin/-out/-err into place:
521 ::dup2(to_process_pipe[0],0);
522 ::dup2(from_process_pipe[1],1);
525 ::dup2(from_process_stderr_pipe[1],2);
526 ::close(from_process_stderr_pipe[0]); ::close(from_process_stderr_pipe[1]);
528 else if (stderr2stdout)
530 ::dup2(from_process_pipe[1],2);
532 // close what we don't need:
533 ::close(to_process_pipe[0]); ::close(to_process_pipe[1]);
534 ::close(from_process_pipe[0]); ::close(from_process_pipe[1]);
536 // set workdir if requested:
537 if (not m_workdir.empty())
539 int r= ::chdir( m_workdir.c_str() );
549 char **argv= new char*[m_args.size()+1];
551 for(std::vector<std::string>::iterator it= m_args.begin();
555 argv[i]= strdup( it->c_str() );
558 // update nice level:
563 // create a new session id if requested:
564 if (m_create_new_session)
569 execv(m_path.c_str(), argv);
570 // exit if exec failed
572 //cleanup! ... just joking; we exec or we exit, in either case the system cleans
573 // everything which needs to be cleaned up.
575 return false; // keep the compiler happy...
576 } // eo ProcessImplementation::startProcess()
580 * convenience method for starting the child process.
581 * This method uses predefined enum values for the stderr handling mode.
583 * @param stderr_mode the desired stderr mode.
584 * @return @a true iff the child process was created.
586 bool ProcessImplementation::startProcess( ProcessImplementation::StderrMode stderr_mode )
590 case UseParentsStderr:
591 return startProcess( _UseParentsStderr );
594 return startProcess( _StderrOnStdout );
597 }; // eo ProcessImplementation::startProcess(ProcessImplementation::StderrMode)
603 * @todo think about a more intelligent handling...
605 void ProcessImplementation::stopProcess(bool force)
607 // TODO: do it somewhat more intelligent?!
611 //TODO: set running state?
617 } // eo ProcessImplementation::stop(bool)
622 * sends a signal to the child process.
623 * @param signal the Signal which should be send.
624 * @return @a true if the signal was sent; @a false if an error occured.
626 bool ProcessImplementation::kill(Signal signal)
629 if (m_pid == 0 || m_pid == (pid_t)-1)
634 int res = ::kill(m_pid, signal);
640 if (signal == Signal::CONT && m_state == ProcessState::suspended)
642 m_state = ProcessState::running;
645 } // eo ProcessImplementation::kill(Signal)
650 * set a new child state with information gobbled by the child signal handler.
652 * @note This method should only be called by the process manager!
654 * @param pid the pid of the child process.
655 * @param status the new status value (as delivered by waitpid())
657 void ProcessImplementation::setChildState(pid_t pid, int status)
659 DOUT("setChildState("<<pid<<","<<status<<") pid="<<m_pid);
662 // old child... ignore!
665 if (WIFSTOPPED(status))
669 int stopsignal = WSTOPSIG(status);
670 // make stop signal available in exit_code:
671 m_exit_code= (stopsignal << 8);
672 m_state= ProcessState::suspended;
676 if (WIFCONTINUED(status))
679 // continued after a stop:
680 m_state= ProcessState::running;
684 if (WIFEXITED(status))
688 m_exit_code= (0xff & WEXITSTATUS(status));
690 close(Direction::out);
691 m_state= ProcessState::stopped;
692 m_signal_terminated();
695 if (WIFSIGNALED(status))
697 DOUT("signaled stop");
699 int termsignal = WTERMSIG(status);
700 // make term signal available in exit code (normal exit codes are only 8 bit)
701 m_exit_code = (termsignal << 8);
703 close(Direction::out);
704 m_state= ProcessState::stopped;
705 m_signal_terminated();
708 // this point should never be reached...!!
709 } // eo ProcessImplementation::setChildState(pid_t,int)
713 * implementation of ProcessManager
716 /// the instance of the process manager (highlander; there can be only one!)
717 ProcessManager* ProcessManager::the_instance= NULL;
720 ProcessManager::ProcessManager()
723 } // eo ProcessManager::ProcessManager
727 * delivers the process manager instance (generate if it doesn't exist)
728 * @return the process manager instance
730 ProcessManager* ProcessManager::getInstance()
734 the_instance = new ProcessManager();
735 _activate_manager = &ProcessManager::activateMe;
738 } // eo ProcessManager::getInstance
742 * activate the timer so it's handled by the next backend cycle
744 void ProcessManager::activateMe()
748 } // eo ProcessManager::activateMe
752 * real work is done here.
753 * Processes the information collected by the child signal handler.
755 void ProcessManager::execute()
757 PidStateList pid_state_list;
759 // block child signals (within this scope)
760 ScopedSignalBlocker blocker( Signal::CHLD );
761 // and now fetch the list of pending information
762 // (simply swap with our local empty list)
763 std::swap(pid_state_list, pending_pid_states);
764 // reserve the desired (minimum) capacity
765 pending_pid_states.reserve( config::pid_pool_capacity );
767 ODOUT("exec, " << pid_state_list.size() << " entries");
770 for(PidStateList::iterator it = pid_state_list.begin();
771 it != pid_state_list.end();
774 pid_t pid = it->first;
775 int status = it->second;
776 ODOUT(" pid=" << pid << ", status=" << status);
777 ProcessImplementation *process_obj;
778 if (process::findChildProcess(pid,process_obj))
780 ODOUT(" local managed child, process_obj="<< process_obj);
781 // pid found in list:
782 if (!WIFSTOPPED(status)
784 && !WIFCONTINUED(status)
788 // take it from list if the child exited:
789 process::removeChildProcess(pid,process_obj);
793 // give the process object a chance to handle the state change:
794 process_obj->setChildState(pid, status);
799 ODOUT("foreign child");
800 // pid not found in list:
801 /* NOTE: in a non threaded environment this pid must be from a child process which is not
802 managed by this process classes; since this method is called after all setup of a child process
803 is done (; especially entering the new child pid into our internal lists).
805 m_foreign_pid_states.push_back(*it);
809 // handle the foreign childs:
812 * fetch a (pid,status) from the list, erase it (to avoid reentrance problems)
813 * and fire the signal. If someone forks childs outside this module then he can
814 * connect to the signal and receive all necessary status information gobbled by
817 while (! m_foreign_pid_states.empty())
819 PidStateList::iterator it= m_foreign_pid_states.begin();
820 pid_t pid = it->first;
821 int status = it->second;
822 m_foreign_pid_states.erase(it);
823 m_foreign_child_state_changed_signal(pid,status);
827 } // eo ProcessManager::execute
830 } // eo namespace AsyncIo