fix comment
[libasyncio] / asyncio / async_process.cpp
CommitLineData
8c15b8c7
TJ
1/*
2The software in this package is distributed under the GNU General
3Public License version 2 (with a special exception described below).
4
5A copy of GNU General Public License (GPL) is included in this distribution,
6in the file COPYING.GPL.
7
8As a special exception, if other files instantiate templates or use macros
9or inline functions from this file, or you compile this file and link it
10with other works to produce a work based on this file, this file
11does not by itself cause the resulting work to be covered
12by the GNU General Public License.
13
14However the source code for this file must still be made available
15in accordance with section (3) of the GNU General Public License.
16
17This exception does not invalidate any other reasons why a work based
18on this file might be covered by the GNU General Public License.
19*/
5c8a3d40
RP
20/** @file
21 *
5c8a3d40 22 * (c) Copyright 2007-2008 by Intra2net AG
5c8a3d40
RP
23 */
24
25//#define NOISEDEBUG
26
42b7c46d 27#include "async_process.hpp"
5c8a3d40
RP
28
29#include <iterator>
30#include <algorithm>
31
32#include <unistd.h>
33#include <fcntl.h>
34#include <sys/socket.h>
35#include <sys/types.h>
36#include <errno.h>
37#include <signal.h>
38#include <sys/wait.h>
39
ce4ab835 40//#include <filefunc.hxx>
5c8a3d40
RP
41
42
43#ifdef NOISEDEBUG
44#include <iostream>
45#include <iomanip>
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
49#else
50#define DOUT(msg) do {} while (0)
51#define FODOUT(obj,msg) do {} while (0)
52#define ODOUT(msg) do {} while (0)
53#endif
54
55
56namespace
57{
58
42b7c46d 59using namespace AsyncIo;
5c8a3d40
RP
60
61/**
62 * local configuration values
63 */
64namespace config
65{
66
67 /// the capacity of the child status list (/ vector)
68 const unsigned int pid_pool_capacity= 512;
69
70} // eo namespace config
71
72
73
74/// the previous handler for the child signal (SIGCHLD)
75void (*oldChildHandler)(int) = NULL;
76
a3c5225a
TJ
77/// indicates if we installed our own signal handler or not
78bool installedChildHandler = false;
79
5c8a3d40
RP
80/// method pointer for activating process manager
81void (ProcessManager::*_activate_manager)();
82
83PidStateList pending_pid_states;
84
85
86/**
87 * signal handler for child signal (SIGCHLD)
88 * @param sig the signal number as provided by the OS
89 */
90void handleSigChild(int sig)
91{
92 int status;
93 pid_t pid;
94 while ( (pid = waitpid(-1,&status,WNOHANG)) > 0)
95 {
96 pending_pid_states.push_back( PidStatePair(pid,status) );
97 }
98 if (_activate_manager)
99 {
100 // tricky way to access a protected method without being a (official) friend:
101 ( ProcessManager::getInstance()->*_activate_manager)();
102 }
103 //TODO: ?
104 signal(sig,handleSigChild);
105} // eo handleSigChild
106
107
108namespace process
109{
110
111typedef std::pair<pid_t, ProcessImplementation*> PidProcPair;
112typedef std::list< PidProcPair > PidProcList;
113
114
115template< typename F, typename S >
116struct CmpFirst
117{
118 F _f;
119 CmpFirst ( F f ) : _f(f) {}
120 bool operator () ( const std::pair<F,S>& v ) const { return v.first == _f; }
121}; // eo struct CmpFirst
122
123
124std::list<ProcessImplementation*> g_process_list;
125PidProcList g_pid_list;
126
127
128void addProcessInstance( ProcessImplementation* obj )
129{
130 g_process_list.push_back(obj);
131} // eo addProcessInstance(ProcessImplementation*)
132
133
134void removeProcessInstance( ProcessImplementation* obj )
135{
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();
141 ++it)
142 {
143 if (it->second == obj)
144 {
145 it->second= NULL;
146 }
147 }
148} // eo removeProcessInstance(ProcessImplementation*)
149
150
151void addChildProcess( pid_t pid, ProcessImplementation* obj)
152{
153 g_pid_list.push_back ( PidProcPair(pid,obj) );
154} // eo addChildProcess(pid_t,ProcessImplementation*)
155
156
157void removeChildProcess ( pid_t pid, ProcessImplementation* obj)
158{
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())
163 {
164 g_pid_list.erase(it);
165 }
166} // eo removeChildProcess(pid_t,ProcessImplementation*)
167
168
169bool findChildProcess ( pid_t pid, ProcessImplementation* & obj )
170{
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())
175 {
176 return false;
177 }
178 obj = it->second;
179 return true;
180} // eo findChildProcess(pid_t,ProcessImplementation*&)
181
182
183} // eo namespace process
184
185
186
187
188
189/*
190** misc tools
191*/
192
193
194/**
195 * convenience tool for closing file descriptors...
196 */
197struct FdCloser
198{
199 int m_fd;
200
201 FdCloser(int fd=-1) : m_fd(fd) {}
202
203 ~FdCloser()
204 {
205 if (m_fd >= 0) ::close(m_fd);
206 }
207
208 void release() { m_fd= -1; }
209
210}; // eo struct FdCloser
211
212
213
214} // eo namespace <anonymous>
215
216
42b7c46d 217namespace AsyncIo
5c8a3d40
RP
218{
219
220
221/*
222 * global functions
223 */
224
225/**
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.
229 */
230bool installChildHandler()
231{
a3c5225a 232 if (installedChildHandler)
5c8a3d40
RP
233 {
234 // already installed
235 return true;
236 }
237 if (! ProcessManager::getInstance() )
238 {
239 // we need an instance of the process manager
240 return false;
241 }
242 pending_pid_states.reserve( config::pid_pool_capacity );
243 oldChildHandler = signal( Signal::CHLD, handleSigChild );
244 if (oldChildHandler == SIG_ERR)
245 {
a3c5225a 246 oldChildHandler = NULL;
5c8a3d40
RP
247 return false;
248 }
a3c5225a 249 installedChildHandler = true;
5c8a3d40
RP
250 return true;
251} // eo installChildHandler
252
253
254/**
255 * uninstalls the child handler.
256 * @return @a true iff the old child handler is reestablished.
257 */
258bool restoreChildHandler()
259{
a3c5225a 260 if (!installedChildHandler)
5c8a3d40 261 return false;
5c8a3d40 262
a3c5225a 263 void(*res)(int) = signal( Signal::CHLD, oldChildHandler );
5c8a3d40 264 if (res == SIG_ERR)
5c8a3d40 265 return false;
a3c5225a
TJ
266
267 oldChildHandler = NULL;
268 installedChildHandler = false;
269
5c8a3d40
RP
270 return true;
271} // eo restoreChildHandler
272
273
274
275
276/*
277 * Implementation of ProcessImplementation
278 */
279
280IOImplementation2* ProcessImplementation::_StderrOnStdout = ((IOImplementation2*) 1);
281IOImplementation2* ProcessImplementation::_UseParentsStderr = ((IOImplementation2*) 0);
282
283
284/**
285 * @brief constructor for the process implementation.
286 *
287 * the constructor takes the path to the executable and (initial) cli arguments.
288 *
289 * @param path path to the executable.
290 * @param args initial command line arguments.
291 */
292ProcessImplementation::ProcessImplementation(
293 const std::string& path,
294 const std::vector<std::string>& args
295 )
296: IOImplementation(-1,-1)
297, m_path(path)
298, m_nice_inc(0)
299, m_create_new_session(false)
300, m_pid(0)
301, m_state(ProcessState::stopped)
302, m_exit_code(0)
303{
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&)
308
309
310ProcessImplementation::~ProcessImplementation()
311{
312 if (m_pid > 0 && m_state!=ProcessState::stopped)
313 {
314 stopProcess(true);
315 }
316 process::removeProcessInstance(this);
317} // eo ProcessImplementation::~ProcessImplementation()
318
319
320void ProcessImplementation::close(Direction direction)
321{
322 inherited::close(direction);
323 if (!inherited::opened() && (m_state != ProcessState::stopped) )
324 {
325 stopProcess(false);
326 }
327} // eo ProcessImplementation::close(Direction)
328
329
330/**
331 * returns an object for adding new arguments to the argument list.
332 * @return the adder object.
333 */
334PushBackFiller<std::string, std::vector > ProcessImplementation::getArgAdder()
335{
336 return PushBackFiller<std::string, std::vector >(m_args);
337} // eo ProcessImplementation::getArgAdder()
338
339
340/**
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.
344 *
345 * If the process is already running, a new value is not accepted.
346 */
347bool ProcessImplementation::setCreateNewSession( bool enable )
348{
349 if (m_state != ProcessState::stopped and enable != m_create_new_session)
350 {
351 return false;
352 }
353 m_create_new_session= enable;
354 return true;
355} // eo ProcessImplementation::setCreateNewSession(bool);
356
357
358/**
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.
363 */
364bool ProcessImplementation::setNice(int nice)
365{
366 errno= 0;
367 if (m_state != ProcessState::stopped)
368 {
369 int delta= m_nice_inc + nice;
370 m_nice_inc= nice;
371 int res= ::nice(delta);
372 if (res == -1 and errno !=0 )
373 {
374 return false;
375 }
376 }
377 else
378 {
379 m_nice_inc = nice;
380 }
381 return true;
382} // eo ProcessImplementation::setNice(int)
383
384
385/**
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.
389 *
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.
392 */
393bool ProcessImplementation::setWorkDir(const std::string& workdir)
394{
395 if ( m_state != ProcessState::stopped and workdir != m_workdir)
396 {
397 return false;
398 }
399 if (not workdir.empty())
400 {
ce4ab835 401 Utils::FileStat stat(workdir);
5c8a3d40
RP
402 if (not stat or not stat.is_directory())
403 {
404 return false;
405 }
406 }
407 m_workdir= workdir;
408 return true;
409} // eo ProcessImplementation::setWorkDir(const std::string&)
410
411
412/**
413 * @brief sets new arguments for the process (the path to the binary is kept).
414 *
415 * @param args the new cli arguments for the subprocess (replacing the old ones).
416 */
417void ProcessImplementation::resetArgs( const std::vector< std::string >& args )
418{
419 if (m_args.size() > 1)
420 {
421 m_args.erase( ++m_args.begin(), m_args.end());
422 }
423 std::copy( args.begin(), args.end(), std::back_inserter(m_args) );
424} // eo ProcessImplementation::resetArgs(const std::vectors< std::string >&)
425
426
427/**
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.
431 *
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
437 * normal output).
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.
440 */
441bool ProcessImplementation::startProcess( IOImplementation2 *stderr )
442{
443 bool stderr2stdout= false;
444 m_errno = 0;
445 m_input_buffer.clear();
446 if (m_pid > 0 && m_state != ProcessState::stopped)
447 {
448 // process still/already running...
449 return false;
450 }
c3b830a2
CH
451
452 Utils::FileStat stat(m_path);
453 if (! stat.is_exec_file())
454 {
455 // command to execute does not exist or is not executable
456 return false;
457 }
458
5c8a3d40
RP
459 m_exit_code= 0;
460
461 if (stderr == _StderrOnStdout)
462 {
463 stderr2stdout= true;
464 stderr= NULL;
465 }
466
467 int to_process_pipe[2];
468 int from_process_pipe[2];
469 int from_process_stderr_pipe[2]= { -1, -1 };
470
471 if ( ::pipe(to_process_pipe) )
472 {
473 m_errno= errno;
474 return false;
475 }
476 FdCloser closeTo0( to_process_pipe[0] );
477 FdCloser closeTo1( to_process_pipe[1] );
478 if ( ::pipe (from_process_pipe) )
479 {
480 m_errno= errno;
481 return false;
482 }
483 FdCloser closeFrom0( from_process_pipe[0] );
484 FdCloser closeFrom1( from_process_pipe[1] );
485 if (stderr)
486 {
487 if (stderr->opened()) stderr->close();
488 if ( ::pipe (from_process_stderr_pipe) )
489 {
490 m_errno= errno;
491 return false;
492 }
493 }
494 FdCloser closeFromErr0( from_process_stderr_pipe[0] );
495 FdCloser closeFromErr1( from_process_stderr_pipe[1] );
496
497 m_pid = ::fork();
498
499 if ( m_pid == (pid_t)-1 )
500 {
501 m_errno= errno;
502 m_pid= 0;
503 // error; something went wrong
504 return false;
505 }
506 else if (m_pid > 0)
507 {
508 // we are in the parent part
509
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]);
515
516 if (stderr)
517 {
518 closeFromErr0.release(); // don't close this fd!
519 stderr->setReadFd(from_process_stderr_pipe[0]);
520 }
521
522 m_state= ProcessState::running;
523 process::addChildProcess(m_pid,this);
524 DOUT(" started child with pid " << m_pid);
525 return true;
526 }
62289850 527 else // pid == 0
5c8a3d40
RP
528 {
529 // we are in the child part
530
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);
534 if (stderr)
535 {
536 ::dup2(from_process_stderr_pipe[1],2);
537 ::close(from_process_stderr_pipe[0]); ::close(from_process_stderr_pipe[1]);
538 }
539 else if (stderr2stdout)
540 {
541 ::dup2(from_process_pipe[1],2);
542 }
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]);
546
547 // set workdir if requested:
548 if (not m_workdir.empty())
549 {
550 int r= ::chdir( m_workdir.c_str() );
551 if (r !=0 )
552 {
553 //TODO?
c3b830a2 554 _exit(255);
5c8a3d40
RP
555 }
556 }
557
558 //
559 // collect args:
560 char **argv= new char*[m_args.size()+1];
561 int i=0;
562 for(std::vector<std::string>::iterator it= m_args.begin();
563 it != m_args.end();
564 ++it,++i)
565 {
566 argv[i]= strdup( it->c_str() );
567 }
568 argv[i]= NULL;
569 // update nice level:
570 if (m_nice_inc)
571 {
572 nice(m_nice_inc);
573 }
574 // create a new session id if requested:
575 if (m_create_new_session)
576 {
577 setsid();
578 }
579 // execute:
580 execv(m_path.c_str(), argv);
581 // exit if exec failed
c3b830a2 582 _exit(255);
5c8a3d40
RP
583 //cleanup! ... just joking; we exec or we exit, in either case the system cleans
584 // everything which needs to be cleaned up.
c3b830a2
CH
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
5c8a3d40
RP
587 }
588 return false; // keep the compiler happy...
589} // eo ProcessImplementation::startProcess()
590
591
592/**
593 * convenience method for starting the child process.
594 * This method uses predefined enum values for the stderr handling mode.
595 *
596 * @param stderr_mode the desired stderr mode.
597 * @return @a true iff the child process was created.
598 */
599bool ProcessImplementation::startProcess( ProcessImplementation::StderrMode stderr_mode )
600{
601 switch (stderr_mode)
602 {
603 case UseParentsStderr:
604 return startProcess( _UseParentsStderr );
605
606 case StderrOnStdout:
607 return startProcess( _StderrOnStdout );
608 }
609 return false;
610}; // eo ProcessImplementation::startProcess(ProcessImplementation::StderrMode)
611
612
613/**
614 * stops the process.
615 *
616 * @todo think about a more intelligent handling...
617 */
618void ProcessImplementation::stopProcess(bool force)
619{
620 // TODO: do it somewhat more intelligent?!
621 if (force)
622 {
623 kill(Signal::KILL);
624 //TODO: set running state?
625 }
626 else
627 {
628 kill(Signal::TERM);
629 }
630} // eo ProcessImplementation::stop(bool)
631
632
633
634/**
635 * sends a signal to the child process.
636 * @param signal the Signal which should be send.
9b1d9a8b 637 * @return @a true if the signal was sent; @a false if an error occurred.
5c8a3d40
RP
638 */
639bool ProcessImplementation::kill(Signal signal)
640{
641 m_errno = 0;
642 if (m_pid == 0 || m_pid == (pid_t)-1)
643 {
644 m_errno= ESRCH;
645 return false;
646 }
647 int res = ::kill(m_pid, signal);
648 if (res < 0)
649 {
650 m_errno= errno;
651 return false;
652 }
653 if (signal == Signal::CONT && m_state == ProcessState::suspended)
654 {
655 m_state = ProcessState::running;
656 }
657 return true;
658} // eo ProcessImplementation::kill(Signal)
659
660
661
662/**
663 * set a new child state with information gobbled by the child signal handler.
664 *
665 * @note This method should only be called by the process manager!
666 *
667 * @param pid the pid of the child process.
668 * @param status the new status value (as delivered by waitpid())
669 */
670void ProcessImplementation::setChildState(pid_t pid, int status)
671{
672 DOUT("setChildState("<<pid<<","<<status<<") pid="<<m_pid);
673 if (pid != m_pid)
674 {
675 // old child... ignore!
676 return;
677 }
678 if (WIFSTOPPED(status))
679 {
680 DOUT("stopped");
681 // stopped:
682 int stopsignal = WSTOPSIG(status);
683 // make stop signal available in exit_code:
684 m_exit_code= (stopsignal << 8);
685 m_state= ProcessState::suspended;
686 return;
687 }
688#ifdef WIFCONTINUED
689 if (WIFCONTINUED(status))
690 {
691 DOUT("continued");
692 // continued after a stop:
693 m_state= ProcessState::running;
694 return;
695 }
696#endif
697 if (WIFEXITED(status))
698 {
699 DOUT("normal exit");
700 //normal exit:
701 m_exit_code= (0xff & WEXITSTATUS(status));
702 m_pid= 0;
703 close(Direction::out);
704 m_state= ProcessState::stopped;
705 m_signal_terminated();
706 return;
707 }
708 if (WIFSIGNALED(status))
709 {
710 DOUT("signaled stop");
711 // exit by signal:
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);
715 m_pid= 0;
716 close(Direction::out);
717 m_state= ProcessState::stopped;
718 m_signal_terminated();
719 return;
720 }
721 // this point should never be reached...!!
722} // eo ProcessImplementation::setChildState(pid_t,int)
723
724
725/*
726 * implementation of ProcessManager
727 */
728
729/// the instance of the process manager (highlander; there can be only one!)
730ProcessManager* ProcessManager::the_instance= NULL;
731
732
733ProcessManager::ProcessManager()
734{
735 setWhenTime(0);
736} // eo ProcessManager::ProcessManager
737
738
739/**
740 * delivers the process manager instance (generate if it doesn't exist)
741 * @return the process manager instance
742 */
743ProcessManager* ProcessManager::getInstance()
744{
745 if (! the_instance)
746 {
747 the_instance = new ProcessManager();
748 _activate_manager = &ProcessManager::activateMe;
749 }
750 return the_instance;
751} // eo ProcessManager::getInstance
752
753
754/**
755 * activate the timer so it's handled by the next backend cycle
756 */
757void ProcessManager::activateMe()
758{
759 setWhenTime(0);
760 activate();
761} // eo ProcessManager::activateMe
762
763
764/**
765 * real work is done here.
766 * Processes the information collected by the child signal handler.
767 */
768void ProcessManager::execute()
769{
770 PidStateList pid_state_list;
771 {
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 );
779 }
780 ODOUT("exec, " << pid_state_list.size() << " entries");
781
782 // interpret states:
783 for(PidStateList::iterator it = pid_state_list.begin();
784 it != pid_state_list.end();
785 ++it)
786 {
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))
792 {
793 ODOUT(" local managed child, process_obj="<< process_obj);
794 // pid found in list:
795 if (!WIFSTOPPED(status)
796#ifdef WIFCONTINUED
797 && !WIFCONTINUED(status)
798#endif
799 )
800 {
801 // take it from list if the child exited:
802 process::removeChildProcess(pid,process_obj);
803 }
804 if (process_obj)
805 {
806 // give the process object a chance to handle the state change:
807 process_obj->setChildState(pid, status);
808 }
809 }
810 else
811 {
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).
817 */
818 m_foreign_pid_states.push_back(*it);
819 }
820 }
821
822 // handle the foreign childs:
823 {
824 /* idea:
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
828 * our child handler.
829 */
830 while (! m_foreign_pid_states.empty())
831 {
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);
837 }
838 }
839
840} // eo ProcessManager::execute
841
842
42b7c46d 843} // eo namespace AsyncIo