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 /// indicates if we installed our own signal handler or not
78 bool installedChildHandler = false;
80 /// method pointer for activating process manager
81 void (ProcessManager::*_activate_manager)();
83 PidStateList pending_pid_states;
87 * signal handler for child signal (SIGCHLD)
88 * @param sig the signal number as provided by the OS
90 void handleSigChild(int sig)
94 while ( (pid = waitpid(-1,&status,WNOHANG)) > 0)
96 pending_pid_states.push_back( PidStatePair(pid,status) );
98 if (_activate_manager)
100 // tricky way to access a protected method without being a (official) friend:
101 ( ProcessManager::getInstance()->*_activate_manager)();
104 signal(sig,handleSigChild);
105 } // eo handleSigChild
111 typedef std::pair<pid_t, ProcessImplementation*> PidProcPair;
112 typedef std::list< PidProcPair > PidProcList;
115 template< typename F, typename S >
119 CmpFirst ( F f ) : _f(f) {}
120 bool operator () ( const std::pair<F,S>& v ) const { return v.first == _f; }
121 }; // eo struct CmpFirst
124 std::list<ProcessImplementation*> g_process_list;
125 PidProcList g_pid_list;
128 void addProcessInstance( ProcessImplementation* obj )
130 g_process_list.push_back(obj);
131 } // eo addProcessInstance(ProcessImplementation*)
134 void removeProcessInstance( ProcessImplementation* obj )
136 // remove obj from list
137 g_process_list.remove(obj);
138 // clear pointers in pid list
139 for(PidProcList::iterator it= g_pid_list.begin();
140 it != g_pid_list.end();
143 if (it->second == obj)
148 } // eo removeProcessInstance(ProcessImplementation*)
151 void addChildProcess( pid_t pid, ProcessImplementation* obj)
153 g_pid_list.push_back ( PidProcPair(pid,obj) );
154 } // eo addChildProcess(pid_t,ProcessImplementation*)
157 void removeChildProcess ( pid_t pid, ProcessImplementation* obj)
159 PidProcList::iterator it= std::find(
160 g_pid_list.begin(), g_pid_list.end(),
161 PidProcPair(pid,obj));
162 if (it != g_pid_list.end())
164 g_pid_list.erase(it);
166 } // eo removeChildProcess(pid_t,ProcessImplementation*)
169 bool findChildProcess ( pid_t pid, ProcessImplementation* & obj )
171 PidProcList::iterator it = std::find_if(
172 g_pid_list.begin(), g_pid_list.end(),
173 CmpFirst<pid_t,ProcessImplementation*>(pid) );
174 if (it == g_pid_list.end())
180 } // eo findChildProcess(pid_t,ProcessImplementation*&)
183 } // eo namespace process
195 * convenience tool for closing file descriptors...
201 FdCloser(int fd=-1) : m_fd(fd) {}
205 if (m_fd >= 0) ::close(m_fd);
208 void release() { m_fd= -1; }
210 }; // eo struct FdCloser
214 } // eo namespace <anonymous>
226 * installs the handler for the child signal (SIGCHLD).
227 * Installing this handler is mandatory for the process subsystem to work correctly.
228 * @return @a true iff the child handler is successfully installed.
230 bool installChildHandler()
232 if (installedChildHandler)
237 if (! ProcessManager::getInstance() )
239 // we need an instance of the process manager
242 pending_pid_states.reserve( config::pid_pool_capacity );
243 oldChildHandler = signal( Signal::CHLD, handleSigChild );
244 if (oldChildHandler == SIG_ERR)
246 oldChildHandler = NULL;
249 installedChildHandler = true;
251 } // eo installChildHandler
255 * uninstalls the child handler.
256 * @return @a true iff the old child handler is reestablished.
258 bool restoreChildHandler()
260 if (!installedChildHandler)
263 void(*res)(int) = signal( Signal::CHLD, oldChildHandler );
267 oldChildHandler = NULL;
268 installedChildHandler = false;
271 } // eo restoreChildHandler
277 * Implementation of ProcessImplementation
280 IOImplementation2* ProcessImplementation::_StderrOnStdout = ((IOImplementation2*) 1);
281 IOImplementation2* ProcessImplementation::_UseParentsStderr = ((IOImplementation2*) 0);
285 * @brief constructor for the process implementation.
287 * the constructor takes the path to the executable and (initial) cli arguments.
289 * @param path path to the executable.
290 * @param args initial command line arguments.
292 ProcessImplementation::ProcessImplementation(
293 const std::string& path,
294 const std::vector<std::string>& args
296 : IOImplementation(-1,-1)
299 , m_create_new_session(false)
301 , m_state(ProcessState::stopped)
304 m_args.push_back(path);
305 std::copy( args.begin(), args.end(), std::back_inserter(m_args) );
306 process::addProcessInstance(this);
307 } // eo ProcessImplementation::ProcessImplementation(const std::string&)
310 ProcessImplementation::~ProcessImplementation()
312 if (m_pid > 0 && m_state!=ProcessState::stopped)
316 process::removeProcessInstance(this);
317 } // eo ProcessImplementation::~ProcessImplementation()
320 void ProcessImplementation::close(Direction direction)
322 inherited::close(direction);
323 if (!inherited::opened() && (m_state != ProcessState::stopped) )
327 } // eo ProcessImplementation::close(Direction)
331 * returns an object for adding new arguments to the argument list.
332 * @return the adder object.
334 PushBackFiller<std::string, std::vector > ProcessImplementation::getArgAdder()
336 return PushBackFiller<std::string, std::vector >(m_args);
337 } // eo ProcessImplementation::getArgAdder()
341 * @brief set if the process should create a new session when started.
342 * @param enable determine if the process should start a new session.
343 * @return @a true iff the value of enable was accepted.
345 * If the process is already running, a new value is not accepted.
347 bool ProcessImplementation::setCreateNewSession( bool enable )
349 if (m_state != ProcessState::stopped and enable != m_create_new_session)
353 m_create_new_session= enable;
355 } // eo ProcessImplementation::setCreateNewSession(bool);
359 * @brief sets a new nice increment.
360 * @param nice the desired nice increment.
361 * @return @a true if the value was accepted and - in case the process was already started -
362 * the nice value was successfully changed.
364 bool ProcessImplementation::setNice(int nice)
367 if (m_state != ProcessState::stopped)
369 int delta= m_nice_inc + nice;
371 int res= ::nice(delta);
372 if (res == -1 and errno !=0 )
382 } // eo ProcessImplementation::setNice(int)
386 * @brief sets the work dir the process should be started with.
387 * @param workdir the workdir
388 * @return @a true if the new workdir was accepted.
390 * The method will return @a false if the process is already started.
391 * The workdir can only be set before the process is started.
393 bool ProcessImplementation::setWorkDir(const std::string& workdir)
395 if ( m_state != ProcessState::stopped and workdir != m_workdir)
399 if (not workdir.empty())
401 Utils::FileStat stat(workdir);
402 if (not stat or not stat.is_directory())
409 } // eo ProcessImplementation::setWorkDir(const std::string&)
413 * @brief sets new arguments for the process (the path to the binary is kept).
415 * @param args the new cli arguments for the subprocess (replacing the old ones).
417 void ProcessImplementation::resetArgs( const std::vector< std::string >& args )
419 if (m_args.size() > 1)
421 m_args.erase( ++m_args.begin(), m_args.end());
423 std::copy( args.begin(), args.end(), std::back_inserter(m_args) );
424 } // eo ProcessImplementation::resetArgs(const std::vectors< std::string >&)
428 * starts the new process.
429 * provides pipes for sending data to/ receiving data from the new process.
430 * Basically forks and execs the new process.
432 * @param stderr if not NULL the given object will be connected to stderr of the new process.
433 * The object can then be used for reading the data from the process' stderr; but cannot be written to.
434 * (The object will be closed if it was open).
435 * If the constant @a ProcessImplementation::StderrOnStdout is passed then stderr of the new process will
436 * be written to the same channel as stdout (i.e. can be read from the process class instance like the
438 * If NULL then the stderr channel from the parent process will also be used by the child.
439 * @return @a true iff the new subprocess started.
441 bool ProcessImplementation::startProcess( IOImplementation2 *stderr )
443 bool stderr2stdout= false;
445 m_input_buffer.clear();
446 if (m_pid > 0 && m_state != ProcessState::stopped)
448 // process still/already running...
452 Utils::FileStat stat(m_path);
453 if (! stat.is_exec_file())
455 // command to execute does not exist or is not executable
461 if (stderr == _StderrOnStdout)
467 int to_process_pipe[2];
468 int from_process_pipe[2];
469 int from_process_stderr_pipe[2]= { -1, -1 };
471 if ( ::pipe(to_process_pipe) )
476 FdCloser closeTo0( to_process_pipe[0] );
477 FdCloser closeTo1( to_process_pipe[1] );
478 if ( ::pipe (from_process_pipe) )
483 FdCloser closeFrom0( from_process_pipe[0] );
484 FdCloser closeFrom1( from_process_pipe[1] );
487 if (stderr->opened()) stderr->close();
488 if ( ::pipe (from_process_stderr_pipe) )
494 FdCloser closeFromErr0( from_process_stderr_pipe[0] );
495 FdCloser closeFromErr1( from_process_stderr_pipe[1] );
499 if ( m_pid == (pid_t)-1 )
503 // error; something went wrong
508 // we are in the parent part
510 // keep the fd's we need and (later) close the other ones:
511 closeTo1.release(); // don't close this fd!
512 setWriteFd(to_process_pipe[1]);
513 closeFrom0.release(); // don't close this fd!
514 setReadFd(from_process_pipe[0]);
518 closeFromErr0.release(); // don't close this fd!
519 stderr->setReadFd(from_process_stderr_pipe[0]);
522 m_state= ProcessState::running;
523 process::addChildProcess(m_pid,this);
524 DOUT(" started child with pid " << m_pid);
529 // we are in the child part
531 // dup the fd's for stdin/-out/-err into place:
532 ::dup2(to_process_pipe[0],0);
533 ::dup2(from_process_pipe[1],1);
536 ::dup2(from_process_stderr_pipe[1],2);
537 ::close(from_process_stderr_pipe[0]); ::close(from_process_stderr_pipe[1]);
539 else if (stderr2stdout)
541 ::dup2(from_process_pipe[1],2);
543 // close what we don't need:
544 ::close(to_process_pipe[0]); ::close(to_process_pipe[1]);
545 ::close(from_process_pipe[0]); ::close(from_process_pipe[1]);
547 // set workdir if requested:
548 if (not m_workdir.empty())
550 int r= ::chdir( m_workdir.c_str() );
560 char **argv= new char*[m_args.size()+1];
562 for(std::vector<std::string>::iterator it= m_args.begin();
566 argv[i]= strdup( it->c_str() );
569 // update nice level:
574 // create a new session id if requested:
575 if (m_create_new_session)
580 execv(m_path.c_str(), argv);
581 // exit if exec failed
583 //cleanup! ... just joking; we exec or we exit, in either case the system cleans
584 // everything which needs to be cleaned up.
585 // Using _exit instead of exit, it will not clean up the clone of parent's memory
586 // in case of failure to exec m_path
588 return false; // keep the compiler happy...
589 } // eo ProcessImplementation::startProcess()
593 * convenience method for starting the child process.
594 * This method uses predefined enum values for the stderr handling mode.
596 * @param stderr_mode the desired stderr mode.
597 * @return @a true iff the child process was created.
599 bool ProcessImplementation::startProcess( ProcessImplementation::StderrMode stderr_mode )
603 case UseParentsStderr:
604 return startProcess( _UseParentsStderr );
607 return startProcess( _StderrOnStdout );
610 }; // eo ProcessImplementation::startProcess(ProcessImplementation::StderrMode)
616 * @todo think about a more intelligent handling...
618 void ProcessImplementation::stopProcess(bool force)
620 // TODO: do it somewhat more intelligent?!
624 //TODO: set running state?
630 } // eo ProcessImplementation::stop(bool)
635 * sends a signal to the child process.
636 * @param signal the Signal which should be send.
637 * @return @a true if the signal was sent; @a false if an error occured.
639 bool ProcessImplementation::kill(Signal signal)
642 if (m_pid == 0 || m_pid == (pid_t)-1)
647 int res = ::kill(m_pid, signal);
653 if (signal == Signal::CONT && m_state == ProcessState::suspended)
655 m_state = ProcessState::running;
658 } // eo ProcessImplementation::kill(Signal)
663 * set a new child state with information gobbled by the child signal handler.
665 * @note This method should only be called by the process manager!
667 * @param pid the pid of the child process.
668 * @param status the new status value (as delivered by waitpid())
670 void ProcessImplementation::setChildState(pid_t pid, int status)
672 DOUT("setChildState("<<pid<<","<<status<<") pid="<<m_pid);
675 // old child... ignore!
678 if (WIFSTOPPED(status))
682 int stopsignal = WSTOPSIG(status);
683 // make stop signal available in exit_code:
684 m_exit_code= (stopsignal << 8);
685 m_state= ProcessState::suspended;
689 if (WIFCONTINUED(status))
692 // continued after a stop:
693 m_state= ProcessState::running;
697 if (WIFEXITED(status))
701 m_exit_code= (0xff & WEXITSTATUS(status));
703 close(Direction::out);
704 m_state= ProcessState::stopped;
705 m_signal_terminated();
708 if (WIFSIGNALED(status))
710 DOUT("signaled stop");
712 int termsignal = WTERMSIG(status);
713 // make term signal available in exit code (normal exit codes are only 8 bit)
714 m_exit_code = (termsignal << 8);
716 close(Direction::out);
717 m_state= ProcessState::stopped;
718 m_signal_terminated();
721 // this point should never be reached...!!
722 } // eo ProcessImplementation::setChildState(pid_t,int)
726 * implementation of ProcessManager
729 /// the instance of the process manager (highlander; there can be only one!)
730 ProcessManager* ProcessManager::the_instance= NULL;
733 ProcessManager::ProcessManager()
736 } // eo ProcessManager::ProcessManager
740 * delivers the process manager instance (generate if it doesn't exist)
741 * @return the process manager instance
743 ProcessManager* ProcessManager::getInstance()
747 the_instance = new ProcessManager();
748 _activate_manager = &ProcessManager::activateMe;
751 } // eo ProcessManager::getInstance
755 * activate the timer so it's handled by the next backend cycle
757 void ProcessManager::activateMe()
761 } // eo ProcessManager::activateMe
765 * real work is done here.
766 * Processes the information collected by the child signal handler.
768 void ProcessManager::execute()
770 PidStateList pid_state_list;
772 // block child signals (within this scope)
773 ScopedSignalBlocker blocker( Signal::CHLD );
774 // and now fetch the list of pending information
775 // (simply swap with our local empty list)
776 std::swap(pid_state_list, pending_pid_states);
777 // reserve the desired (minimum) capacity
778 pending_pid_states.reserve( config::pid_pool_capacity );
780 ODOUT("exec, " << pid_state_list.size() << " entries");
783 for(PidStateList::iterator it = pid_state_list.begin();
784 it != pid_state_list.end();
787 pid_t pid = it->first;
788 int status = it->second;
789 ODOUT(" pid=" << pid << ", status=" << status);
790 ProcessImplementation *process_obj;
791 if (process::findChildProcess(pid,process_obj))
793 ODOUT(" local managed child, process_obj="<< process_obj);
794 // pid found in list:
795 if (!WIFSTOPPED(status)
797 && !WIFCONTINUED(status)
801 // take it from list if the child exited:
802 process::removeChildProcess(pid,process_obj);
806 // give the process object a chance to handle the state change:
807 process_obj->setChildState(pid, status);
812 ODOUT("foreign child");
813 // pid not found in list:
814 /* NOTE: in a non threaded environment this pid must be from a child process which is not
815 managed by this process classes; since this method is called after all setup of a child process
816 is done (; especially entering the new child pid into our internal lists).
818 m_foreign_pid_states.push_back(*it);
822 // handle the foreign childs:
825 * fetch a (pid,status) from the list, erase it (to avoid reentrance problems)
826 * and fire the signal. If someone forks childs outside this module then he can
827 * connect to the signal and receive all necessary status information gobbled by
830 while (! m_foreign_pid_states.empty())
832 PidStateList::iterator it= m_foreign_pid_states.begin();
833 pid_t pid = it->first;
834 int status = it->second;
835 m_foreign_pid_states.erase(it);
836 m_foreign_child_state_changed_signal(pid,status);
840 } // eo ProcessManager::execute
843 } // eo namespace AsyncIo