File Coverage

blib/lib/Zoidberg/Contractor.pm
Criterion Covered Total %
statement 297 522 56.9
branch 96 276 34.7
condition 25 98 25.5
subroutine 31 53 58.4
pod 9 10 90.0
total 458 959 47.7


line stmt bran cond sub pod time code
1             package Zoidberg::Contractor;
2              
3             our $VERSION = '0.981';
4              
5 19     19   98 use strict;
  19         38  
  19         648  
6 19     19   23827 use POSIX ();
  19         138019  
  19         569  
7 19     19   159 use Config;
  19         39  
  19         786  
8 19     19   114 use Zoidberg::Utils;
  19         54  
  19         175  
9 19     19   4147 no warnings; # yes, undefined == '' == 0
  19         22  
  19         36434  
10              
11             =head1 NAME
12              
13             Zoidberg::Contractor - Module to manage jobs
14              
15             =head1 SYNOPSIS
16              
17             use Zoidberg::Contractor;
18             my $c = Zoidberg::Contractor->new();
19            
20             $c->shell_list( [qw(cat ./log)], '|', [qw(grep -i error)] );
21              
22             =head1 DESCRIPTION
23              
24             Zoidberg inherits from this module, it manages jobs.
25              
26             It uses Zoidberg::StringParser.
27              
28             Also it defines Zoidberg::Job and subclasses.
29              
30             FIXME lots of documentation
31              
32             =head1 METHODS
33              
34             =over 4
35              
36             =item new()
37              
38             Simple constructor, calls C.
39              
40             =cut
41              
42             sub new { # stub, to be overloaded
43 0     0 1 0 my $class = shift;
44 0         0 shell_init(bless {@_}, $class);
45             }
46              
47             =item shell_init()
48              
49             Initialises things like hashes with signal names and sets terminal control.
50             Should be called before usage when the constructor is overloaded.
51              
52             =cut
53              
54             # Job control code adapted from example code
55             # in the glibc manual
56             # also some snippets from this manual include as comment blocks
57              
58             # A subshell that runs non-interactively cannot and should not support job control.
59              
60             sub shell_init {
61 16     16 1 48 my $self = shift;
62 16 50       82 bug 'Contractor can\'t live without a shell' unless $$self{shell};
63              
64             ## jobs stuff
65 16         63 $self->{jobs} = [];
66 16         49 $self->{_sighash} = {};
67 16         95 $self->{terminal} = fileno(STDIN);
68              
69 16         14484 my @sno = split /[, ]/, $Config{sig_num};
70 16         47892 my @sna = split /[, ]/, $Config{sig_name};
71 16         3487 $self->{_sighash}{$sno[$_]} = $sna[$_] for (0..$#sno);
72              
73 16 50       144 if ($self->{shell}{settings}{interactive}) {
74             # Loop check until we are in the foreground.
75 0         0 while (POSIX::tcgetpgrp($self->{terminal}) != ($self->{pgid} = getpgrp)) {
76             # FIXME is this logic allright !??
77 0         0 CORE::kill($$self{_sighash}{TTIN}, -$self->{pgid}); # stop ourselfs
78             }
79             # ignore interactive and job control signals
80 0         0 $SIG{$_} = 'IGNORE' for qw/INT QUIT TSTP TTIN TTOU/;
81              
82             # And get terminal control
83 0         0 POSIX::tcsetpgrp($self->{terminal}, $self->{pgid});
84 0         0 $self->{tmodes} = POSIX::Termios->new;
85 0         0 $self->{tmodes}->getattr;
86             }
87 16         275 else { $self->{pgid} = getpgrp }
88              
89 16         209 return $self;
90             }
91              
92             =item round_up()
93              
94             Recursively calls the C function of all current jobs.
95              
96             =cut
97              
98 2     2 1 10 sub round_up { $_->round_up() for @{$_[0]->{jobs}} }
  2         13  
99              
100             =item shell_list(@blocks)
101              
102             Executes a list of jobs and logic operators.
103              
104             =cut
105              
106             sub shell_list {
107 200     200 1 833 my ($self, @list) = grep {defined $_} @_;
  645         2452  
108              
109 200         708 my $save_fg_job = $$self{shell}{fg_job}; # could be undef
110              
111 200 100       1019 my $meta = (ref($list[0]) eq 'HASH') ? shift(@list) : {} ;
112 200 50       685 return unless @list;
113              
114 200         477 my @re;
115 200 100       698 for (@list) { $_ = $$self{shell}->prepare_block($_) if ref $_ }
  250         2825  
116             PARSE_LIST:
117 205 50       855 return unless @list;
118 205 100       639 if (ref $list[0]) {
  5 50       95  
119 200         398 eval {
120 200         2704 my $j = Zoidberg::Job->new(%$meta, boss => $self, tree => \@list);
121 200 100 50     12474 @list = @{$$j{tree}} and goto PARSE_LIST if $$j{empty};
  5         360  
122 195 50       572 unless ( $$meta{prepare} ) { @re = $j->exec() }
  195         1629  
123             else {
124 0         0 $j->bg(); # put it in @jobs
125 0         0 $$j{bg} = 0;
126             }
127             };
128 179 50       1234 complain $@ if $@; # FIXME FIXME check eval {} blocks for redundancy
129             }
130             elsif (@{$$self{jobs}}) {
131 0         0 debug 'enqueuing '.scalar(@list).' blocks';
132 0         0 push @{$$self{jobs}[-1]{tree}}, @list;
  0         0  
133             }
134             else {
135 5         100 debug 'no job to enqueu in, trying logic';
136 5         120 @list = $self->_logic($$self{shell}{error}, @list);
137 5         35 @re = $self->shell_list(@list);
138             }
139              
140 183         1518 $$self{shell}{fg_job} = $save_fg_job;
141              
142 183         48289 return @re;
143             }
144              
145             =item shell_job($block)
146              
147             Executes a single job.
148              
149             =cut
150              
151             sub shell_job {
152 0     0 1 0 my ($self, $meta, $block) = @_;
153 0 0       0 $block = $meta unless ref($meta) eq 'HASH';
154 0         0 my $save_fg_job = $$self{shell}{fg_job}; # could be undef
155 0         0 my @re;
156 0         0 eval {
157 0         0 my $j = Zoidberg::Job->new(%$meta, boss => $self, procs => [$block]);
158 0         0 @re = $j->exec()
159             };
160 0 0       0 complain $@ if $@;
161 0         0 $$self{shell}{fg_job} = $save_fg_job;
162 0         0 return @re;
163             }
164              
165             =item reap_jobs()
166              
167             Checks for jobs that are finished and removes them from the job list.
168              
169             =cut
170              
171             sub reap_jobs {
172 0     0 1 0 my $self = shift;
173 0 0       0 return unless @{$self->{jobs}};
  0         0  
174 0         0 my (@completed, @running);
175             #debug 'reaping jobs';
176 0         0 for ( @{$self->{jobs}} ) {
  0         0  
177 0 0       0 next unless ref($_) =~ /Job/; # prohibit autogenerated faults
178 0         0 $_->update_status;
179 0 0       0 if ($_->completed) {
180 0 0       0 if (@{$$_{tree}}) { $self->reinc_job($_) } # reincarnate it
  0         0  
  0         0  
181 0         0 else { push @completed, $_ }
182             }
183 0         0 else { push @running, $_ }
184             }
185 0         0 $self->{jobs} = \@running;
186             #debug 'body count: '.scalar(@completed);
187 0 0       0 if ($$self{shell}{settings}{interactive}) {
188 0         0 ++$$_{completed} and message $_->status_string
189 0   0     0 for sort {$$a{id} <=> $$b{id}} grep {! $$_{no_notify}} @completed;
  0         0  
190             }
191             }
192              
193             sub reinc_job { # reincarnate
194 13     13 0 222 my ($self, $job) = @_;
195 13         514 debug "job \%$$job{id} reincarnates (error: $$job{error})";
196 13         107 my @b = $self->_logic($$job{error}, @{$$job{tree}});
  13         373  
197 13         92 $$job{tree} = [];
198 13 50       94 return unless @b;
199 13         134 debug @b. ' blocks left';
200 13         107 $self->shell_list({ map {($_ => $$job{$_})} qw#bg id capture wantarray# }, @b);
  52         739  
201             }
202              
203             sub _logic {
204 18     18   214 my ($self, $error, @list) = @_;
205 18 50       141 my $op = ref( $list[0] ) ? 'EOS' : shift @list ;
206             # mind that logic grouping for AND and OR isn't the same, OR is stronger
207 18 100       201 while ( $error ? ( $op eq 'AND' ) : ( $op eq 'OR' ) ) { # skip
208 6         96 my $i = 0;
209 6   66     198 while ( ref $list[0] or $list[0] eq 'AND' ) {
210 6         36 shift @list;
211 6         102 $i++;
212             }
213 6 50       72 debug( ($error ? 'error' : 'no error') . " => $i blocks skipped" );
214 6         24 $op = shift @list;
215             }
216 18         72 return @list;
217             }
218              
219             # ############# #
220             # info routines #
221             # ############# #
222              
223             =item job_by_id($id)
224              
225             Returns a job object based on the (numeric) id.
226              
227             (Note that the job list is un-ordered,
228             so the id and the index are not usually identical.)
229            
230             =item job_by_spec($string)
231              
232             Returns a job object based on a string.
233             The following formats are supported:
234              
235             =over 4
236              
237             =item %I
238              
239             Job with id I
240              
241             =item %+
242              
243             Current job
244              
245             =item %-
246              
247             Previous job
248              
249             =item %?I
250              
251             Last job matching I
252              
253             =item %I
254              
255             Last job starting with I
256              
257             =back
258              
259             =item sig_by_spec($string)
260              
261             Returns the signal number for a named signal
262             or undef if no such signal exists.
263              
264             =cut
265              
266             sub job_by_id {
267 0     0 1 0 my ($self, $id) = @_;
268 0 0       0 for (@{$$self{jobs}}) { return $_ if $$_{id} eq $id }
  0         0  
  0         0  
269 0         0 return undef;
270             }
271              
272             sub job_by_spec {
273 0     0 1 0 my ($self, $spec) = @_;
274 0 0       0 return @{$$self{jobs}} ? $$self{jobs}[-1] : undef unless $spec;
  0 0       0  
275             # see posix 1003.2 speculation for arbitrary cruft
276 0 0 0     0 $spec = '%+' if $spec eq '%%' or $spec eq '%';
277 0         0 $spec =~ /^ \%? (?: (\d+) | ([\+\-\?]?) (.*) ) $/x;
278 0         0 my ($id, $q, $string) = ($1, $2, $3);
279 0 0       0 if ($id) {
    0          
    0          
    0          
280 0 0       0 for (@{$$self{jobs}}) { return $_ if $$_{id} == $id }
  0         0  
  0         0  
281             }
282 0 0       0 elsif ($q eq '+') { return $$self{jobs}[-1] if @{$$self{jobs}} }
  0         0  
283 0 0       0 elsif ($q eq '-') { return $$self{jobs}[-2] if @{$$self{jobs}} > 1 }
  0         0  
284             elsif ($q eq '?') {
285 0 0       0 for (reverse @{$$self{jobs}}) { return $_ if $$_{zoidcmd} =~ /$string/ }
  0         0  
  0         0  
286             }
287             else {
288 0 0       0 for (reverse @{$$self{jobs}}) { return $_ if $$_{zoidcmd} =~ /^\W*$string/ }
  0         0  
  0         0  
289             }
290 0         0 return undef;
291             }
292              
293             sub sig_by_spec {
294 0     0 1 0 my ($self, $z) = @_;
295 0 0       0 return $z if exists $$self{_sighash}{$z};
296 0         0 $z =~ s{^(sig)?(.*)$}{uc($2)}ei;
  0         0  
297 0         0 while (my ($k, $v) = each %{$$self{_sighash}}) {
  0         0  
298 0 0       0 return $k if $v eq $z
299             }
300 0         0 return undef;
301             }
302              
303              
304             # ########### #
305             # Job objects #
306             # ########### #
307              
308             package Zoidberg::Job;
309              
310             our $VERSION = '0.981';
311              
312 19     19   157 use strict;
  19         37  
  19         752  
313 19     19   131 use vars '$AUTOLOAD';
  19         40  
  19         874  
314 19     19   101 use POSIX qw/:sys_wait_h :signal_h/;
  19         54  
  19         137  
315 19     19   32199 use Zoidberg::Utils;
  19         40  
  19         115  
316              
317             use overload
318 0     0   0 '@{}' => sub { $_[0]->{tree} },
319 19     19   4692 fallback => 'TRUE';
  19         67  
  19         343  
320              
321             our @ISA = qw/Zoidberg::Contractor/;
322              
323             =back
324              
325             =head1 JOBS
326              
327             Jobs are objects of the class C or a subclass of this class.
328              
329             This object AUTOLOADS methods to process signals. For example:
330              
331             $job->TERM(); # is identical to
332             $job->kill('TERM');
333              
334             =head2 Methods
335              
336             The job obbjects have the following methods:
337              
338             =over 4
339              
340             =item new()
341              
342             Simple constructor.
343              
344             =item exec()
345              
346             Execute the job.
347              
348             =item round_up()
349              
350             Recursively kill the job, ends all child processes forcefully.
351              
352             =cut
353              
354             sub new { # @_ should at least contain 'boss' and either 'proc' or 'tree'
355 200     200   336 shift; # class
356 200         6984 my $self = { new => 1, id => 0, procs => [], @_ };
357 200   33     2864 $$self{shell} ||= $$self{boss}{shell};
358 200   100     3168 $$self{$_} ||= [] for qw/jobs tree/;
359 200         2415 $$self{$_} = $$self{boss}{$_} for qw/_sighash terminal/; # FIXME check this
360              
361 200 50       1070 if ($$self{tree}) {
362 200         1036 while ( ref $$self{tree}[0] ) {
363 200         1151 my @b = grep {defined $_} $$self{shell}->parse_block(shift @{$$self{tree}}); # FIXME breaks interface, should be a hook
  200         1389  
  200         2276  
364 200 50       1077 if (@b > 1) { unshift @{$$self{tree}}, @b } # probably macro expansion
  0         0  
  0         0  
365 200         339 else { push @{$$self{procs}}, @b }
  200         1422  
366             }
367 200 50       952 $$self{bg}++ if $$self{tree}[0] eq 'EOS_BG';
368             }
369              
370 200 100       359 return bless {%$self, empty => 1}, 'Zoidberg::Job' unless @{$$self{procs}};
  200         1499  
371 195         1102 debug 'blocks in job ', $$self{procs};
372 195         491 my $pipe = @{$$self{procs}} > 1;
  195         792  
373 195 50 66     3133 $$self{string} ||= ($pipe ? '|' : '') . $$self{procs}[-1][0]{string};
374 195   33     1331 $$self{zoidcmd} ||= $$self{procs}[-1][0]{zoidcmd};
375 195   33     1678 $$self{pwd} ||= $$self{procs}[0][0]{env}{pwd} || $ENV{PWD};
      33        
376            
377 195         697 my $meta = $$self{procs}[0][0];
378 195 100 66     1876 unless ($pipe || ( defined($$meta{fork_job}) ? $$meta{fork_job} : 0 ) || $$self{bg}) {
379 76         595 bless $self, 'Zoidberg::Job::builtin'
380             }
381 119         603 else { bless $self, 'Zoidberg::Job' }
382              
383 195         641 return $self;
384             }
385              
386             sub exec {
387 195 50   195   2238 die unless ref($_[0]); # check against deprecated api
388 195         318 my $self = shift;
389 195 50       713 if (ref $_[0]) { %$self = (%$self, %{$_[0]}) }
  0         0  
  0         0  
390              
391 195 50       715 message $self->status_string('Running') if $$self{prepare};
392 195         513 $$self{new} = 0;
393              
394 195 50       221 return unless @{$$self{procs}};
  195         1243  
395 195         3323 local $ENV{ZOIDREF} = "$$self{shell}";
396              
397 195         566 my @re = eval { $self->_run };
  195         2821  
398 180 50       2014 if ($$self{error}) { $$self{shell}{error} = $$self{error} }
  0 100       0  
399             elsif ($@) {
400 10         84 complain;
401 10 50 0     40 my $error = ref($@) ? $@ : bless { string => ($@ || 'Error') }, 'Zoidberg::Utils::Error';
402 10         48 $error->PROPAGATE(); # just for the record
403 10         46 $$self{error} = $$self{shell}{error} = $error;
404             }
405 170         2366 else { delete $$self{shell}{error} }
406              
407 180 50       1900 if ($self->completed()) {
408 180         1454 $$self{shell}->broadcast('envupdate'); # FIXME breaks interface
409 180 100       1439 $$self{boss}->reinc_job($self) if @{ $$self{tree} };
  180         13351  
410             }
411              
412 179 50       2155 if ( $$self{tree}[0] eq 'EOS_BG' ) { # step over it - FIXME conflicts with fg_job
413 0         0 shift @{$$self{tree}};
  0         0  
414 0         0 my $ref = $$self{tree};
415 0         0 $$self{tree} = [];
416 0         0 $$self{boss}->shell_list(@$ref);
417             }
418              
419 179         8449 return @re;
420             }
421              
422             sub round_up {
423 0     0   0 $_[0]->kill('HUP', 'WIPE');
424 0         0 $_->round_up() for @{$_[0]->{jobs}};
  0         0  
425             }
426              
427             # ######## #
428             # Run code #
429             # ######## #
430              
431             # As each process is forked, it should put itself in the new process group by calling setpgid
432             # The shell should also call setpgid to put each of its child processes into the new process
433             # group. This is because there is a potential timing problem: each child process must be put
434             # in the process group before it begins executing a new program, and the shell depends on
435             # having all the child processes in the group before it continues executing. If both the child
436             # processes and the shell call setpgid, this ensures that the right things happen no matter
437             # which process gets to it first.
438              
439             # If the job is being launched as a foreground job, the new process group also needs to be
440             # put into the foreground on the controlling terminal using tcsetpgrp. Again, this should be
441             # done by the shell as well as by each of its child processes, to avoid race conditions.
442              
443             sub _run {
444 119     119   272 my $self = shift;
445 119         496 $$self{shell}{fg_job} = $self;
446              
447 119         2603 $self->{tmodes} = POSIX::Termios->new;
448              
449 119 100       816 $self->{procs}[-1][0]{last}++ unless $$self{capture}; # don't close the pipeline if capturing
450              
451 119         187 my ($pid, @pipe, $stdin, $stdout);
452 119         1385 my $zoidpid = $$;
453 119         461 $stdin = fileno STDIN;
454              
455             # use pgid of boss when boss is part of a pipeline
456 119 50       2063 $$self{pgid} = $$self{boss}{pgid} unless $$self{shell}{settings}{interactive};
457              
458 119         342 my $i = 0;
459 119         221 for my $proc (@{$self->{procs}}) {
  119         532  
460 119         226 $i++;
461 119 100       423 if ($$proc[0]{last}) { $stdout = fileno STDOUT }
  110         267  
462             else { # open pipe to next process
463 9         195 @pipe = POSIX::pipe;
464 9         41 $stdout = $pipe[1];
465             }
466              
467 119         496147 $pid = fork; # fork process
468 119 100       7770 if ($pid) { # parent process
469             # set pid and pgid
470 105         17184 $$proc[0]{pid} = $pid;
471 105   33     2995 $self->{pgid} ||= $pid ;
472 105         4153 POSIX::setpgid($pid, $self->{pgid});
473 105         24213 debug "job \%$$self{id} part $i has pid $pid and pgid $$self{pgid}";
474             # set terminal control
475 105 50 33     966 POSIX::tcsetpgrp($self->{shell}{terminal}, $self->{pgid})
476             if $$self{shell}{settings}{interactive} && ! $$self{bg};
477             }
478             else { # child process
479             # set pgid
480 14   33     2938 $self->{pgid} ||= $$; # after first pgid is set allready
481 14         2248 POSIX::setpgid($$, $self->{pgid});
482             # set terminal control
483 14 50 33     2336 POSIX::tcsetpgrp($self->{shell}{terminal}, $self->{pgid})
484             if $$self{shell}{settings}{interactive} && ! $$self{bg};
485             # and run child
486 14         1460 $ENV{ZOIDPID} = $zoidpid;
487 14         263 eval { $self->_run_child($proc, $stdin, $stdout) };
  14         1179  
488 0   0     0 my $error = $@ || 0;
489 0 0       0 if ($error) {
490 0         0 complain;
491 0 0 0     0 $error = ref($error) ? ($$error{exit_status} || 1) : 1 if $error;
    0          
492             }
493 0         0 exit $error; # exit child process
494             }
495              
496 105 50       1420 POSIX::close($stdin) unless $stdin == fileno STDIN ;
497 105 100       580 POSIX::close($stdout) unless $stdout == fileno STDOUT;
498 105 100       2743 $stdin = $pipe[0] unless $$proc[0]{last} ;
499             }
500              
501 105 100       4816 my @re = $$self{bg} ? $self->bg
    50          
502             : $$self{capture} ? ($self->_capture($stdin)) : $self->fg ;
503            
504             # postrun
505 104         2255 POSIX::tcsetpgrp($$self{shell}{terminal}, $$self{shell}{pgid});
506              
507 104         1366 return @re;
508             }
509              
510             sub _capture { # called in parent when capturing
511 6     6   113 my ($self, $stdin) = @_;
512 6 50 33     494 local $/ = (exists $ENV{RS} and defined $ENV{RS})
513             ? $ENV{RS} : "\n" ; # Record Separator
514 6         267 debug "capturing output from fd $stdin, \$/ = '$/'";
515 6         320 open IN, "<&=$stdin"; # open file descriptor
516 6         123463902 my @re = ();
517 6         268 close IN;
518 6 50       184 POSIX::close($stdin) unless $stdin == fileno STDIN ;
519 6         140 $self->wait_job; # job should be dead by now
520 6 50       108 return $$self{wantarray} ? (map {chomp $_; $_} @re) : (join '', @re);
  0         0  
  0         0  
521             }
522              
523             sub _run_child { # called in child process
524 14     14   223 my $self = shift;
525 14         354 my ($block, $stdin, $stdout) = @_;
526              
527 14         162 $self->{shell}{round_up} = 0;
528 14         1604 $self->{shell}{settings}{interactive} = 0;
529 14         293 map { $SIG{$_} = 'DEFAULT' } qw{INT QUIT TSTP TTIN TTOU};
  70         1608  
530              
531             # make sure stdin and stdout are right, else dup them
532 14         491 for ([$stdin, fileno STDIN], [$stdout, fileno STDOUT]) {
533 28 100       481 next if $_->[0] == $_->[1];
534 3         92 POSIX::dup2(@$_);
535 3         55 POSIX::close($_->[0]);
536             }
537              
538 14         978 $self->_set_env($block);
539              
540             # here we go ... finally
541 14         535 $$self{shell}->eval_block($block); # FIXME should be hook
542             }
543              
544             # ##################### #
545             # Execution environment #
546             # ##################### #
547              
548             sub _set_env {
549 90     90   216 my ($self, $block) = @_;
550              
551             # check the pwd we want
552 90         524 my $pwd = $$block[0]{env}{pwd};
553 90 50 33     1376 if ($pwd and $pwd ne $ENV{PWD}) {
554 0         0 debug "Changing pwd to: $pwd";
555 0 0       0 chdir $pwd or error "Could not change dir to: $pwd";
556 0         0 $$self{pwd} = $pwd;
557             }
558              
559             # variables
560 90         236 my @save_env;
561 90         171 while (my ($env, $val) = each %{$$block[0]{env}}) {
  276         2438  
562 186         1339 debug "env $env, val $val";
563 186         923 push @save_env, [$env, $ENV{$env}];
564 186         2207 $ENV{$env} = $val;
565             }
566 90 100       1226 return [\@save_env, []] unless $$block[0]{fd};
567              
568             # redirection
569 1         8 my @save_fd;
570 1         3 for my $fd (@{$$block[0]{fd}}) { # FIXME allow for IO objects
  1         16  
571 2         29 my $newfd;
572 2 50       139 $fd =~ m#^(\w*)(\W+)(.*)# or error "wrongly formatted redirection: $fd";
573 2         79 my ($n, $op, $f) = ($1, $2, $3);
574 2 0 33     18 $n ||= ($op =~ />/) ? 1 : 0;
575 2 100       33 if ($op =~ /&=?$/) { # our dupping logic differs from open()
576 1 50       31 if (! $f) { $newfd = 1 }
  0 50       0  
577 1         13 elsif ($f =~ /^\d+$/) { $newfd = $f }
578             else {
579 19     19   39035 no strict 'refs';
  19         40  
  19         45423  
580 0   0     0 my $class = $$self{shell}{settings}{perl}{namespace}
581             || 'Zoidberg::Eval';
582 0         0 $newfd = fileno *{$class.'::'.$f};
  0         0  
583 0 0       0 error $f.': no such filehandle' unless $newfd;
584             }
585             }
586             else {
587 1 50       29 error 'redirection needs argument' unless $f;
588 1 0 33     19 error $f.': cannot overwrite existing file'
      33        
589             if $op eq '>'
590             and $$self{shell}{settings}{noclobber}
591             and -e $f;
592 1 50       7 $op = '>' if $op eq '>!';
593 1         26 debug "redirecting fd $n to $op$f";
594 1         4 my $fh; # undefined scalar => new anonymous filehandle on open()
595 1 50       462 open($fh, $op.$f) || error "Failed to open $op$f";
596 1         10 ($f, $newfd) = ($fh, fileno $fh); # re-using $f to have object in outer scope
597             }
598 2         47 debug "dupping fd $newfd to $n";
599 2         37 push @save_fd, [POSIX::dup($n), $n];
600 2 50       73 POSIX::dup2($newfd, $n) || error "Failed to dup $newfd to $n";
601             }
602              
603 1         12 return [\@save_env, \@save_fd];
604             }
605              
606             sub _restore_env {
607 76     76   157 my ($save_env, $save_fd) = @{ pop @_ };
  76         1027  
608              
609 76         286 for (@$save_fd) {
610 0         0 POSIX::dup2(@$_);
611 0         0 POSIX::close($_->[0]);
612             }
613              
614 76 50       281 if (my ($PWD) = grep {$$_[0] eq 'PWD'} @$save_env) {
  158         758  
615 0         0 debug "Changing pwd back to: $$PWD[1]";
616 0 0       0 chdir $$PWD[1] or error "Could not change dir to: $$PWD[1]";
617             }
618 76         818 $ENV{$$_[0]} = $$_[1] for @$save_env;
619             }
620              
621             # ########### #
622             # Signal code #
623             # ########### #
624              
625             =item fg()
626              
627             Take terminal control and run this job in the foreground.
628              
629             =item bg()
630              
631             Run this job in the background.
632              
633             =cut
634              
635             sub fg {
636 99     99   255 my $self = shift;
637              
638 99 50       784 if ($$self{new}) {
639 0         0 unshift @_, $self;
640 0         0 goto &exec;
641             }
642              
643 99 50       296 message $self->status_string('Running') if $$self{bg};
644 99         1026 $$self{bg} = 0;
645              
646 99         170 @{$$self{boss}{jobs}} = grep {$_ ne $self} @{$$self{boss}{jobs}};
  99         437  
  0         0  
  99         428  
647 99         8635 $$self{shell}{fg_job} = $self;
648              
649 99 50       715 POSIX::tcsetpgrp($self->{shell}{terminal}, $self->{pgid})
650             if $self->{shell}{settings}{interactive};
651              
652 99 50       634 if ($self->{stopped}) {
653 0         0 CORE::kill(SIGCONT, -$self->{pgid});
654 0         0 $self->{stopped} = 0;
655             }
656 99         1778 $self->wait_job;
657              
658 99 50       852 POSIX::tcsetpgrp($self->{shell}{terminal}, $self->{shell}{pgid})
659             if $self->{shell}{settings}{interactive};
660            
661 99 50 33     1577 if ($$self{stopped} or $$self{terminated}) {
662 0 0 0     0 if ($$self{stopped} and $$self{shell}{settings}{notify_verbose}) {
663 0         0 $$self{shell}->jobs();
664             }
665             else {
666 0         0 message $self->status_string;
667             }
668             }
669              
670 99 50       773 if ($self->completed()) {
671 99         504 $$self{shell}->broadcast('envupdate'); # FIXME breaks interface
672 99 100       577 $$self{boss}->reinc_job($self) if @{ $$self{tree} };
  99         47111  
673             }
674             }
675              
676             sub bg {
677 0     0   0 my $self = shift;
678 0         0 $self->_register_bg;
679              
680 0 0       0 if ($self->{stopped}) {
681 0         0 CORE::kill(SIGCONT => -$self->{pgid});
682 0         0 $self->{stopped} = 0;
683             }
684              
685 0         0 message $self->status_string;
686             }
687              
688             sub _register_bg { # register oneself as a background job
689 0     0   0 my $self = shift;
690              
691 0 0       0 unless ($$self{id}) {
692 0   0     0 $$_{id} > $$self{id} and $$self{id} = $$_{id} for @{$$self{boss}{jobs}};
  0         0  
693 0         0 $$self{id}++;
694             }
695              
696 0         0 @{$$self{boss}{jobs}} = grep {$_ ne $self} @{$$self{boss}{jobs}};
  0         0  
  0         0  
  0         0  
697 0         0 push @{$$self{boss}{jobs}}, $self;
  0         0  
698              
699 0         0 $self->{bg} = 1;
700             }
701              
702             # FIXME wait code when not interactive
703              
704             sub wait_job {
705 105     105   391 my $self = shift;
706 105   66     2503 while ( ! $self->{stopped} && ! $self->completed ) {
707 105         167 my $pid;
708 105         1366 until ($pid = waitpid(-$self->{pgid}, WUNTRACED|WNOHANG)) {
709 1374935         14840178 $self->{shell}->broadcast('ipc_poll');
710 1374935         1778922521 select(undef, undef, undef, 0.001);
711             }
712 105         2669 $self->_update_child($pid, $?);
713             }
714             }
715              
716             sub update_status {
717 0     0   0 my $self = shift;
718 0 0       0 return if $$self{new};
719 0         0 while (my $pid = waitpid(-$self->{pgid}, WUNTRACED|WNOHANG)) {
720 0         0 $self->_update_child($pid, $?);
721 0 0       0 last unless $pid > 0;
722             }
723             }
724              
725 518     518   3611 sub completed { ! grep { ! $$_[0]{completed} } @{$_[0]{procs}} }
  518         6681  
  518         6026  
726              
727             sub _update_child {
728 105     105   985 my ($self, $pid, $status) = @_;
729 105 50       1230 return unless $pid; # 0 == all is well
730 105         3706 debug "pid: $pid returned: $status";
731              
732 105 50       781 if ($pid == -1) { # -1 == all processes in group ended
733 0         0 CORE::kill(SIGTERM => -$self->{pgid} ); # just to be sure
734 0         0 debug "group $$self{pgid} has disappeared:($!)";
735 0         0 $$_[0]{completed}++ for @{$self->{procs}};
  0         0  
736             }
737             else {
738 105         233 my ($child) = grep {$$_[0]{pid} == $pid} @{$$self{procs}};
  105         1322  
  105         771  
739 105 50       434 bug "Don't know this pid: $pid" unless $child;
740 105         1438 $$child[0]{exit_status} = $status;
741 105 50       851 if (WIFSTOPPED($status)) { # STOP TSTP TTIN TTOUT
742 0         0 $$self{stopped} = 1;
743 0 0 0     0 if ( ! $$self{bg} and (
      0        
744             WSTOPSIG($status) == SIGTTIN or
745             WSTOPSIG($status) == SIGTTOU
746 0         0 ) ) { $self->fg } # FIXME not sure why but this proves nescessary
747 0         0 else { $self->_register_bg }
748             }
749             else {
750 105         961 $$child[0]{completed} = 1;
751 105 50       818 if ($pid == $$self{procs}[-1][0]{pid}) { # the end of the line ..
752 105         630 $$self{exit_status} = $status;
753 105 50       350 if ($status) { # parse error codes
754             # bitmasks for return status of system commands
755             # exit_value = $? >> 8;
756             # signal_num = $? & 127;
757             # dumped_core = $? & 128;
758 0         0 my $signal = $status & 127;
759 0 0       0 $$self{terminated}++ if $signal; # was terminated by a signal
760 0 0       0 $$self{core_dump}++ if $status & 128;
761 0 0       0 $$self{error} = bless {
762             silent => 1,
763             string => $status >> 8,
764             exit_value => $status > 8,
765             core_dump => $$self{core_dump},
766             ( $signal ? (signal => $$self{_sighash}{$signal}) : () ),
767             }, 'Zoidberg::Utils::Error';
768 0 0       0 $$self{shell}{error} = $$self{error} unless $$self{bg};
769             }
770 105 50       722 unless ($self->completed) { # kill the pipeline
771 0         0 local $SIG{PIPE} = 'IGNORE'; # just to be sure
772 0         0 $self->kill(SIGPIPE);
773             }
774             }
775             }
776             }
777             }
778              
779             # TODO
780             # don't set shell exitstatus etc. if bg !
781             # run condition between the clean up and the kill for non interactive mode ?
782             # job seems not to get reaped whille stopped - should be continued at kill
783              
784             # ###### #
785             # OO api #
786             # ###### #
787              
788             =item kill($signal, $wipe_list)
789              
790             Sends $signal (numeric or named) to all child processes belonging to this job;
791             $signal defaults to SIGTERM.
792              
793             If the boolean $wipe_list is set all jobs pending in the same logic list are
794             removed.
795              
796             =cut
797              
798             sub kill {
799 0     0   0 my ($self, $sig_s, $kill_tree) = @_;
800 0 0       0 my $sig = defined($sig_s) ? $$self{shell}->sig_by_spec($sig_s) : SIGTERM;
801 0 0       0 error "$sig_s: no such signal" unless $sig;
802 0 0       0 @{$$self{tree}} = () if $kill_tree;
  0         0  
803 0 0       0 if ($self->{shell}{settings}{interactive}) {
804 0         0 CORE::kill( $sig => -$$self{pgid} );
805             }
806             else {
807 0         0 CORE::kill( $sig => $_ )
808 0         0 for map { $$_[0]{pid} } @{$$self{procs}};
  0         0  
809             }
810 0         0 $self->update_status();
811             }
812              
813             =item env(\%env)
814              
815             Set local environment for the current job.
816             Can't be set after the job has started.
817              
818             =item fd(\@redir)
819              
820             Set redirections for the current job.
821             Can't be set after the job has started.
822              
823             =cut
824              
825             sub env {
826 0     0   0 my $self = shift;
827 0 0       0 my $env = ref($_[0]) ? shift : { @_ };
828 0 0       0 error "to late to set env, job is already running" unless $$self{new};
829 0         0 for (@{$$self{procs}}) {
  0         0  
830 0 0       0 $$_[0]{env} = $$_[0]{env} ? { %{$$_[0]{env}}, %$env } : $env;
  0         0  
831             }
832             }
833              
834             sub fd {
835 0     0   0 my $self = shift;
836 0 0       0 my $fd = ref($_[0]) ? shift : [ @_ ];
837 0 0       0 error "to late to set fd, job is already running" unless $$self{new};
838 0         0 for (@$fd) {
839 0 0       0 my $block = /^[0<]/ ? $$self{procs}[0] : $$self{procs}[-1]; # in- or output
840 0   0     0 $$block[0]{fd} ||= [];
841 0         0 push @{$$block[0]{fd}}, $_;
  0         0  
842             }
843             }
844              
845             sub AUTOLOAD { # autoload signals - bo args
846 0     0   0 my $self = shift;
847 0         0 $AUTOLOAD =~ s/.*:://;
848 0         0 $self->kill($AUTOLOAD);
849             }
850              
851             # ############ #
852             # Notification #
853             # ############ #
854              
855             sub status_string {
856             # POSIX: "[%d]%c %s %s\n", , , ,
857 0     0   0 my ($self, $status, $list) = @_;
858              
859 0         0 my $pref = '';
860 0 0       0 if ($$self{id}) {
861 0 0       0 $pref = "[$$self{id}]" . (
    0          
862             ($self eq $$self{boss}{jobs}[-1]) ? '+ ' :
863             ($self eq $$self{boss}{jobs}[-2]) ? '- ' : ' ' );
864             }
865              
866             $status ||=
867 0 0 0     0 $$self{new} ? 'New' :
    0          
    0          
    0          
    0          
868             $$self{stopped} ? 'Stopped' :
869             $$self{core_dump} ? 'Core dumped' :
870             $$self{terminated} ? 'Terminated' :
871             $$self{completed} ? 'Done' : 'Running' ;
872              
873 0         0 my $string = $$self{string};
874 0         0 chomp $string;
875 0 0 0     0 $string .= " \t(pwd: $$self{pwd})"
876             if $$self{pwd} and $$self{pwd} ne $ENV{PWD};
877              
878 0 0       0 if ($list) { # more verbose output for `jobs --list`
879             # FIXME this can no doubt be handled more gracefully
880 0         0 my ($t, @stack) = ($$self{tree});
881 0         0 for (my $i = 0; $i < @$t; $i++) {
882 0         0 my $string;
883 0   0     0 until (ref $$t[$i] or $i >= @$t) {
884 0         0 $string .= $$t[$i] . ' ';
885 0         0 $i++
886             }
887 0 0       0 if (ref $$t[$i]) {
888 0   0     0 $string .= $$t[$i][0]{string} || $$t[$i][-1];
889 0         0 chomp $string;
890 0         0 $string =~ s/\n/\n\t/g;
891             }
892 0         0 else { $string .= $$t[$i] }
893 0         0 push @stack, $string;
894             }
895            
896 0         0 $string = join "\n\t", $string, grep /\S/, @stack;
897 0         0 return $pref . $$self{pgid} . " $status\t$string";
898             }
899 0         0 else { return $pref . $status . "\t$string" }
900             }
901              
902             package Zoidberg::Job::builtin;
903              
904             our $VERSION = '0.981';
905              
906 19     19   126 use strict;
  19         55  
  19         596  
907 19     19   115 use Zoidberg::Utils;
  19         40  
  19         145  
908              
909             our @ISA = qw/Zoidberg::Job/;
910              
911 0     0   0 sub round_up { $_->round_up() for @{$_[0]->{jobs}} }
  0         0  
912              
913             sub _run { # TODO something about capturing :(
914 76     76   176 my $self = shift;
915 76         198 my $block = $self->{procs}[0];
916 76         231 $$self{shell}{fg_job} = $self;
917              
918 76         479 my $saveint = $SIG{INT};
919 76 50       381 if ($self->{settings}{interactive}) {
920 0         0 my $ii = 0;
921             $SIG{INT} = sub {
922 0 0   0   0 if (++$ii < 3) { message "[$$self{id}] instruction terminated by SIGINT" }
  0         0  
923 0         0 else { die "Got SIGINT 3 times, killing native scuddle\n" }
924 0         0 };
925             }
926 76     0   1837 else { $SIG{INT} = sub { die "[SIGINT]\n" } }
  0         0  
927              
928 76         130 my $save_capt;
929 76 50       521 if ($$self{capture}) {
930 0         0 debug "trying to capture a builtin";
931 0         0 $save_capt = $$self{shell}{_builtin_output};
932 0         0 $$self{shell}{_builtin_output} = [];
933             }
934 76         1097 my $save_env = $self->_set_env($block);
935              
936             # here we go !
937 76         146 eval { $$self{shell}->eval_block($block) };
  76         741  
938             # FIXME should be hook
939             # VUNZig om hier een eval te moeten gebruiken
940              
941 76         1007 $self->_restore_env($save_env);
942 76         160 my @re;
943 76 50       318 if ($$self{capture}) {
944 0         0 @re = @{ $$self{shell}{_builtin_output} };
  0         0  
945 0         0 $$self{shell}{_builtin_output} = $save_capt;
946             }
947              
948             # restore other stuff
949 76         932 $SIG{INT} = $saveint;
950 76         752 $self->{completed}++;
951            
952 76 100       328 die if $@;
953              
954 66         395 return @re;
955             }
956              
957 0     0   0 sub kill { error q#Can't kill a builtin# }
958 0     0   0 sub bg { error q#Can't put builtin in the background# }
959 0     0   0 sub fg { error q#Can't put builtin in the foreground# }
960              
961 76     76   368 sub completed { $_[0]->{completed} }
962              
963             1;
964              
965             __END__