Commit | Line | Data |
---|---|---|
8c15b8c7 TJ |
1 | /* |
2 | The software in this package is distributed under the GNU General | |
3 | Public License version 2 (with a special exception described below). | |
4 | ||
5 | A copy of GNU General Public License (GPL) is included in this distribution, | |
6 | in the file COPYING.GPL. | |
7 | ||
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. | |
13 | ||
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. | |
16 | ||
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. | |
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 | ||
56 | namespace | |
57 | { | |
58 | ||
42b7c46d | 59 | using namespace AsyncIo; |
5c8a3d40 RP |
60 | |
61 | /** | |
62 | * local configuration values | |
63 | */ | |
64 | namespace 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) | |
75 | void (*oldChildHandler)(int) = NULL; | |
76 | ||
a3c5225a TJ |
77 | /// indicates if we installed our own signal handler or not |
78 | bool installedChildHandler = false; | |
79 | ||
5c8a3d40 RP |
80 | /// method pointer for activating process manager |
81 | void (ProcessManager::*_activate_manager)(); | |
82 | ||
83 | PidStateList 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 | */ | |
90 | void 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 | ||
108 | namespace process | |
109 | { | |
110 | ||
111 | typedef std::pair<pid_t, ProcessImplementation*> PidProcPair; | |
112 | typedef std::list< PidProcPair > PidProcList; | |
113 | ||
114 | ||
115 | template< typename F, typename S > | |
116 | struct 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 | ||
124 | std::list<ProcessImplementation*> g_process_list; | |
125 | PidProcList g_pid_list; | |
126 | ||
127 | ||
128 | void addProcessInstance( ProcessImplementation* obj ) | |
129 | { | |
130 | g_process_list.push_back(obj); | |
131 | } // eo addProcessInstance(ProcessImplementation*) | |
132 | ||
133 | ||
134 | void 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 | ||
151 | void addChildProcess( pid_t pid, ProcessImplementation* obj) | |
152 | { | |
153 | g_pid_list.push_back ( PidProcPair(pid,obj) ); | |
154 | } // eo addChildProcess(pid_t,ProcessImplementation*) | |
155 | ||
156 | ||
157 | void 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 | ||
169 | bool 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 | */ | |
197 | struct 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 | 217 | namespace 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 | */ | |
230 | bool 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 | */ | |
258 | bool 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 | ||
280 | IOImplementation2* ProcessImplementation::_StderrOnStdout = ((IOImplementation2*) 1); | |
281 | IOImplementation2* 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 | */ | |
292 | ProcessImplementation::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 | ||
310 | ProcessImplementation::~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 | ||
320 | void 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 | */ | |
334 | PushBackFiller<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 | */ | |
347 | bool 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 | */ | |
364 | bool 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 | */ | |
393 | bool 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 | */ | |
417 | void 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 | */ | |
441 | bool 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 | */ | |
599 | bool 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 | */ | |
618 | void 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 | */ |
639 | bool 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 | */ | |
670 | void 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!) | |
730 | ProcessManager* ProcessManager::the_instance= NULL; | |
731 | ||
732 | ||
733 | ProcessManager::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 | */ | |
743 | ProcessManager* 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 | */ | |
757 | void 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 | */ | |
768 | void 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 |