Merge branch 'master' of steve@erxz.com:/home/uri/git_repo/stem
[urisagit/Stem.git] / lib / Stem / Proc.pm
CommitLineData
4536f655 1# File: Stem/Proc.pm
2
3# This file is part of Stem.
4# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
5
6# Stem is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10
11# Stem is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15
16# You should have received a copy of the GNU General Public License
17# along with Stem; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
20# For a license to use the Stem under conditions other than those
21# described here, to purchase support for this software, or to purchase a
22# commercial warranty contract, please contact Stem Systems at:
23
24# Stem Systems, Inc. 781-643-7504
25# 79 Everett St. info@stemsystems.com
26# Arlington, MA 02474
27# USA
28
29package Stem::Proc ;
30
31use Carp qw( cluck ) ;
32
33use strict ;
34
35use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ;
36use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ;
37
38use IO::Socket ;
39use Symbol ;
40use Carp ;
41use POSIX qw( :sys_wait_h ) ;
42use constant EXEC_ERROR => 199 ;
43
44use Stem::Route qw( :cell ) ;
45
46use base 'Stem::Cell' ;
47
48my %pid_to_obj ;
49
50my $child_event = Stem::Event::Signal->new(
51 'object' => bless({}),
52 'signal' => 'CHLD'
53) ;
54
55ref $child_event or return
56 "Stem::Proc can't create SIG_CHLD handler: $child_event\n" ;
57
58my $attr_spec = [
59
60
61###############
62# if you pass in an optional object, then that will be the base for
63# all the callback methods. the message and log options will not be
64# done as they work only using the callbacks internal to Stem::Proc.
65###############
66
67 {
68 'name' => 'reg_name',
69 'help' => <<HELP,
70This is the name under which this Cell was registered.
71HELP
72 },
73 {
74 'name' => 'object',
75 'type' => 'object',
76 'help' => <<HELP,
77This is the owner object for this Cell and it will get the callbacks.
78HELP
79 },
80 {
81 'name' => 'path',
82 'required' => 1,
83 'help' => <<HELP,
84This is the path to the program to run.
85HELP
86 },
87 {
88 'name' => 'proc_args',
89 'default' => [],
90 'type' => 'list',
91 'help' => <<HELP,
92This is a list of the arguments to the program to be run.
93HELP
94 },
95 {
96 'name' => 'spawn_now',
97 'type' => 'boolean',
98 'help' => <<HELP,
99This flag means to spawn the process at constructor time. Default is to
100spawn it when triggered via a message.
101HELP
102 },
103 {
104 'name' => 'no_io',
105 'type' => 'boolean',
106 'help' => <<HELP,
107This flag means the process will do no standard I/O and those pipes will
108not be created.
109HELP
110 },
111 {
112 'name' => 'no_read',
113 'type' => 'boolean',
114 'help' => <<HELP,
115This flag means the Cell will not read from the process and that pipe
116will not be created. (unsupported)
117HELP
118 },
119 {
120 'name' => 'no_write',
121 'type' => 'boolean',
122 'help' => <<HELP,
123This flag means the Cell will not write to the process and that pipe
124will not be created. (unsupported)
125HELP
126 },
127 {
128 'name' => 'use_stderr',
129 'type' => 'boolean',
130 'help' => <<HELP,
131This flag means the Cell will read from the stderr handle of the process.
132By default the stderr pipe is not created and its output comes in on stdout.
133HELP
134 },
135
136 {
137 'name' => 'use_pty',
138 'type' => 'boolean',
139 'help' => <<HELP,
140This flag will cause the process to be run behind a pseudo-tty device.
141HELP
142 },
143 {
144 'name' => 'exited_method',
145 'default' => 'proc_ended',
146 'help' => <<HELP,
147This method is called on the owner object when the process exits.
148HELP
149 },
150 {
151 'name' => 'cell_attr',
152 'class' => 'Stem::Cell',
153 'help' => <<HELP,
154This value is the attributes for the included Stem::Cell which handles
155cloning, async I/O and pipes.
156HELP
157 },
158] ;
159
160
161sub new {
162
163 my( $class ) = shift ;
164
165 my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
166 return $self unless ref $self ;
167
168
169 my $err = $self->find_exec_path() ;
170 return $err if $err ;
171
172 $self->{ 'use_stderr' } = 0 if $self->{ 'use_pty' } ;
173
174 $err = $self->cell_set_args(
175 'path' => $self->{'path'},
176 'proc_args' => $self->{'proc_args'},
177 ) ;
178
179 return $err if $err ;
180
181 $self->cell_set_args( 'no_async' => 1 ) if $self->{ 'no_io' } ;
182
183###########
184# cloneable and spawn_now should be mutually exclusive
185##########
186
187 if ( $self->{'spawn_now'} ) {
188
189TraceStatus "New Spawn" ;
190
191 my $err = $self->cell_trigger();
192 return $err unless ref $err ;
193
194 $err = $self->spawn() ;
195 return $err if $err ;
196 }
197
198 return $self ;
199}
200
201
202sub find_exec_path {
203
204 my( $self ) = shift ;
205
206 my $proc_path = $self->{'path'} ;
207
208 return if -x $proc_path ;
209
210 foreach my $path ( File::Spec->path() ) {
211
212 my $exec_path = File::Spec->catfile( $path, $proc_path ) ;
213
214 next unless -f $exec_path ;
215
216 if ( -x $exec_path ) {
217
218 $self->{'path'} = $exec_path ;
219 return ;
220 }
221 }
222
223 return "$self->{'path'} is not found in $ENV{PATH}" ;
224}
225
226sub triggered_cell {
227
228 my( $self ) = @_ ;
229
230 my $err = $self->spawn() ;
231 return $err if $err ;
232
233#use Data::Dumper ;
234#print Dumper \%INC ;
235
236#print $self->status_cmd() ;
237
238 return ;
239}
240
241
242sub spawn {
243
244 my( $self ) = @_ ;
245
246 unless( $self->{'no_io'} ) {
247
248 $self->_parent_io() ;
249 }
250
251 $self->{'ppid'} = $$ ;
252
253 my @exec_args = @{$self->{'proc_args'}} ;
254
255 if ( my $pipe_args_ref = $self->cell_get_args( 'args' ) ) {
256
257 push( @exec_args, (ref $pipe_args_ref) ?
258 @{$pipe_args_ref} : $pipe_args_ref ) ;
259 }
260
261 my $pid = fork() ;
262 defined $pid or die "Stem::Proc can't fork $!" ;
263
264 if ( $pid ) {
265
266# in parent
267
268# must close the child fh in the parent so we will see a closed socket
269# when the child exits
270
271 unless( $self->{'no_io'} ) {
272
273 close $self->{'child_fh'} ;
274 close $self->{'child_err_fh'} if $self->{'use_stderr'} ;
275
276 delete( $self->{'child_fh'} ) ;
277 delete( $self->{'child_err_fh'} ) ;
278 }
279
280 TraceStatus "forked $pid" ;
281
282 $self->{'pid'} = $pid ;
283 $pid_to_obj{ $pid } = $self ;
284
285 $self->cell_set_args( 'info' => <<INFO ) ;
286
287Path: $self->{'path'}
288Args: @exec_args
289Pid: $pid
290
291INFO
292
293
294 }
295 else {
296
297# in child
298 unless( $self->{'no_io'} ) {
299
300 $self->_child_io() ;
301 }
302
303###############
304###############
305## add support for setting local(%ENV)
306###############
307###############
308
309#TraceStatus "Exec'ing $self->{'path'}, @exec_args" ;
310
311 exec $self->{'path'}, @exec_args ;
312
313 exit EXEC_ERROR ;
314 }
315
316# back in parent (unless no exec -- FIX THAT!! unless path is
317# required) we could do a forked stem hub by execing stem with a new
318# config which has a portal with STDIN/STDOUT as fh's
319
320 my $err = $self->cell_set_args( 'aio_args' => [
321 'read_fh' => $self->{'parent_fh'},
322 'write_fh' => $self->{'parent_fh'},
323 'stderr_fh' => $self->{'parent_err_fh'},
324 'closed_method' => $self->{'exited_method'},
325 ]
326 ) ;
327
328 return $err if $err ;
329
330 $self->cell_worker_ready() ;
331
332 return ;
333}
334
335
336sub _parent_io {
337
338 my( $self ) = @_ ;
339
340 my( $parent_fh, $child_fh ) ;
341
342
343 if ( $self->{'use_pty'} ) {
344
345 require IO::Pty ;
346 $parent_fh = IO::Pty->new() ;
347 $child_fh = $parent_fh->slave() ;
348 }
349 else {
350
351 $parent_fh = gensym ;
352 $child_fh = gensym ;
353
354 socketpair( $parent_fh, $child_fh,
355 AF_UNIX, SOCK_STREAM, PF_UNSPEC ) ||
356 die "can't make socket pair $!" ;
357 }
358
359 bless $parent_fh, 'IO::Socket' ;
360
361 $self->{'parent_fh'} = $parent_fh ;
362
363 $parent_fh->blocking( 0 ) ;
364
365 $self->{'child_fh'} = $child_fh ;
366
367#############
368# add pty support here
369#############
370
371 if ( $self->{'use_stderr'} ) {
372
373 my $parent_err_fh = gensym ;
374 my $child_err_fh = gensym ;
375
376 socketpair( $parent_err_fh, $child_err_fh,
377 AF_UNIX, SOCK_STREAM, PF_UNSPEC ) ||
378 die "can't make socket pair $!" ;
379
380 $self->{'parent_err_fh'} = $parent_err_fh ;
381 $self->{'child_err_fh'} = $child_err_fh ;
382 }
383}
384
385sub _child_io {
386
387 my( $self ) = @_ ;
388
389 close $self->{'parent_fh'} ;
390 close $self->{'parent_err_fh'} if $self->{'use_stderr'} ;
391
392 my $child_fd = fileno( $self->{'child_fh'} ) ;
393
394 open( \*STDIN, "<&$child_fd" ) ||
395 croak "dup open of STDIN failed $!" ;
396
397 open( \*STDOUT, ">&$child_fd" ) ||
398 croak "dup open of STDOUT failed $!" ;
399
400 if ( $self->{'use_stderr'} ) {
401
402 my $child_err_fd = fileno( $self->{'child_err_fh'} ) ;
403
404 open( \*STDERR, ">&$child_err_fd" ) ||
405 croak "dup open of STDERR failed $!" ;
406
407 }
408 else {
409 open( \*STDERR, ">&$child_fd" ) ||
410 croak "dup open of STDERR failed $!" ;
411 }
412}
413
414sub write {
415
416 my( $self, $data ) = @_ ;
417
418 $self->cell_write( $data ) ;
419}
420
421
422sub read_fh {
423
424 $_[0]->{'parent_fh'} ;
425}
426
427sub write_fh {
428
429 $_[0]->{'parent_fh'} ;
430}
431
432sub stderr_fh {
433
434 $_[0]->{'parent_err_fh'} ;
435}
436
437sub proc_ended {
438
439 my( $self ) = @_ ;
440
441#print "PROC ended, shutting down\n" ;
442
443 $self->shut_down() ;
444}
445
446sub signal_cmd {
447
448 my( $self, $msg ) = @_ ;
449
450 my $data = $msg->data() ;
451
452 return unless ref $data eq 'SCALAR' ;
453
454 my $signal = ${$data} ;
455
456 $self->signal( $signal ) ;
457
458 return ;
459}
460
461sub signal {
462
463 my( $self, $signal ) = @_ ;
464
465 $signal ||= 'SIGTERM' ;
466
467 TraceStatus "$self->{'pid'} received SIGTERM" ;
468
469 kill $signal, $self->{'pid'} ;
470}
471
472sub sig_chld_handler {
473
474 while ( 1 ) {
475
476 my $child_pid = waitpid( -1, WNOHANG ) ;
477
478 return if $child_pid == 0 || $child_pid == -1 ;
479
480 my $proc_status = $? ;
481
482 my ( $exit_code, $exit_signal ) ;
483
484 if ( WIFEXITED( $proc_status ) ) {
485
486 $exit_code = WEXITSTATUS( $proc_status ) ;
487
488 TraceStatus "EXIT: $exit_code" ;
489
490 }
491 else {
492 $exit_signal = WTERMSIG( $proc_status ) ;
493
494 TraceStatus "EXIT signal: $exit_signal" ;
495
496 }
497
498#print "EXIT CODE [$exit_code]\n" ;
499
500 if ( my $self = $pid_to_obj{ $child_pid } ) {
501
502 $self->{'exit_code'} = $exit_code ;
503 $self->{'exit_signal'} = $exit_signal ;
504
505 if ( defined( $exit_code ) &&
506 $exit_code == EXEC_ERROR ) {
507
508 print <<ERR ;
509Stem::Proc exec failed on path '$self->{'path'}'
510ERR
511
512 }
513
514 $self->exited() ;
515 }
516 else {
517#### ERROR
518print "reaped unknown process pid $child_pid\n"
519 }
520
521 }
522}
523
524sub exited {
525
526 my( $self ) = @_ ;
527
528######################
529# handle watchdog here
530######################
531
532 $self->{'exited'} = 1 ;
533
534#print "EXITED\n" ;
535
536 $self->shut_down() if $self->{'no_io'} ;
537
538 TraceStatus "Proc $self->{'pid'} exited" ;
539}
540
541
542sub shut_down {
543
544 my( $self ) = @_ ;
545
546#print "PROC SHUT\n" ;
547
548 unless( $self->{'exited'} ) {
549
550 kill 'SIGTERM', $self->{'pid'} ;
551
552 TraceStatus "kill of proc $self->{'pid'}" ;
553 }
554
555 return if $self->{'no_io'} ;
556
557 if ( my $pid = $self->{'pid'} ) {
558
559 delete( $pid_to_obj{ $pid } ) ;
560 }
561
562 $self->cell_shut_down() ;
563
564 close $self->{'parent_fh'} ;
565 close $self->{'parent_err_fh'} if $self->{'use_stderr'} ;
566}
567
5681 ;