File Coverage

perllib/Arch/Run.pm
Criterion Covered Total %
statement 28 111 25.2
branch 0 40 0.0
condition 0 10 0.0
subroutine 10 22 45.4
pod 9 10 90.0
total 47 193 24.3


line stmt bran cond sub pod time code
1             # Arch Perl library, Copyright (C) 2004-2005 Mikhael Goikhman, Enno Cramer
2             #
3             # This program is free software; you can redistribute it and/or modify
4             # it under the terms of the GNU General Public License as published by
5             # the Free Software Foundation; either version 2 of the License, or
6             # (at your option) any later version.
7             #
8             # This program is distributed in the hope that it will be useful,
9             # but WITHOUT ANY WARRANTY; without even the implied warranty of
10             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11             # GNU General Public License for more details.
12             #
13             # You should have received a copy of the GNU General Public License
14             # along with this program; if not, write to the Free Software
15             # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16              
17 1     1   21 use 5.006;
  1         4  
  1         40  
18 1     1   5 use strict;
  1         2  
  1         46  
19              
20             package Arch::Run;
21              
22 1     1   838 use IO::Poll qw(POLLIN POLLOUT POLLERR);
  1         11653  
  1         119  
23 1     1   1178 use POSIX qw(waitpid WNOHANG setsid);
  1         7707  
  1         9  
24              
25 1     1   1389 use constant RAW => 0;
  1         5  
  1         72  
26 1     1   6 use constant LINES => 1;
  1         2  
  1         43  
27 1     1   6 use constant ALL => 2;
  1         3  
  1         50  
28              
29 1     1   7 use vars qw(@ISA @EXPORT_OK @OBSERVERS %SUBS $DETACH_CONSOLE);
  1         3  
  1         93  
30              
31 1     1   7 use Exporter;
  1         2  
  1         71  
32              
33             @ISA = qw(Exporter);
34             @EXPORT_OK = qw(
35             run_with_pipe run_async poll wait unobserve observe
36             RAW LINES ALL
37             );
38              
39             BEGIN {
40 1     1   2922 $DETACH_CONSOLE = 0;
41             }
42              
43             sub set_detach_console ($) {
44 0     0 0   $DETACH_CONSOLE = shift;
45             }
46              
47             sub run_with_pipe (@) {
48 0   0 0 1   my $arg0 = shift || die "Missing command to run_with_pipe\n";
49 0           my @args = (split(/\s+/, $arg0), @_);
50              
51 0           pipe TO_PARENT_RDR, TO_PARENT_WRT;
52 0           pipe TO_CHILD_RDR, TO_CHILD_WRT;
53              
54 0           my $pid = fork;
55 0 0         die "Can't fork: $!\n" unless defined $pid;
56              
57 0 0         if ($pid) {
58 0           close TO_PARENT_WRT;
59 0           close TO_CHILD_RDR;
60              
61             return wantarray
62 0 0         ? (\*TO_PARENT_RDR, \*TO_CHILD_WRT, $pid)
63             : \*TO_PARENT_RDR;
64              
65             } else {
66 0           close TO_PARENT_RDR;
67 0           close TO_CHILD_WRT;
68              
69 0           close STDIN;
70             # my perl won't compile this if i use
71             # open STDIN, "<&", TO_CHILD_RDR
72             # the same thing for STDOUT is accepted though,
73             # the "<&" vs ">&" makes the difference
74 0           open STDIN, "<&TO_CHILD_RDR";
75 0           close TO_CHILD_RDR;
76              
77 0           close STDOUT;
78 0           open STDOUT, ">&TO_PARENT_WRT";
79 0           close TO_PARENT_WRT;
80              
81 0 0         setsid
82             if $DETACH_CONSOLE;
83              
84 0           exec(@args);
85             }
86             }
87              
88             sub run_async (%) {
89 0     0 1   my %args = @_;
90              
91 0 0         die "Missing command to run_async\n"
92             unless exists $args{command};
93              
94 0 0         my @args = ref $args{command} ? @{$args{command}} : $args{command};
  0            
95 0           my ($out, $in, $pid) = run_with_pipe(@args);
96              
97 0           _notify('cmd_start', $pid, @args);
98              
99 0           $SUBS{$pid} = {
100             # in => $in, # not for now
101             out => $out,
102             mode => $args{mode},
103             data => $args{datacb},
104             exit => $args{exitcb},
105              
106             accum => '',
107             };
108              
109 0           close($in); # no input for now
110              
111 0           return $pid;
112             }
113              
114             sub get_output_handle ($) {
115 0     0 1   my $key = shift;
116              
117 0           return $SUBS{$key}->{out};
118             }
119              
120             sub handle_output ($) {
121 0     0 1   my $key = shift;
122 0           my $rec = $SUBS{$key};
123              
124 0           my $buffer;
125 0           my $result = sysread $rec->{out}, $buffer, 4096;
126              
127 0 0         _notify('cmd_output_raw', $key, $buffer)
128             if $result > 0;
129              
130             # handle output
131 0 0         if ($result) {
132             # raw mode
133 0 0         if ($rec->{mode} eq RAW) {
    0          
134 0           $rec->{data}->($buffer);
135              
136             # line mode
137             } elsif ($rec->{mode} eq LINES) {
138 0           $rec->{accum} .= $buffer;
139              
140 0           while ($rec->{accum} =~ s/^.*?(\015\012|\012|\015)//) {
141 0           $rec->{data}->($&);
142             }
143              
144             # bloody big block mode
145             } else {
146 0           $rec->{accum} .= $buffer;
147 0 0         $rec->{data}->($rec->{accum})
148             if $result == 0;
149             }
150              
151             # error and eof
152             } else {
153 0 0         $rec->{data}->($rec->{accum})
154             if length $rec->{accum};
155              
156 0           my $pid = waitpid $key, 0;
157 0 0         my $exitcode = $pid == $key ? $? : undef;
158              
159 0           _notify('cmd_exit', $exitcode);
160              
161 0 0         $rec->{exit}->($exitcode)
162             if defined $rec->{exit};
163              
164 0           delete $SUBS{$key};
165             }
166             }
167              
168             sub poll (;$) {
169 0     0 1   my $count = 0;
170              
171             # check for output
172 0           my $poll = IO::Poll->new;
173 0           foreach my $key (keys %SUBS) {
174 0 0         $poll->mask($SUBS{$key}->{out}, POLLIN | POLLERR)
175             unless $SUBS{$key}->{done};
176             }
177              
178 0           my $result = $poll->poll($_[0]);
179 0           foreach my $key (keys %SUBS) {
180 0 0         if ($poll->events($SUBS{$key}->{out})) {
181 0           handle_output($key);
182 0           ++$count;
183             }
184             }
185              
186 0           return $count;
187             }
188              
189             sub wait ($) {
190 0     0 1   my $pid = shift;
191              
192 0           my $ret;
193              
194             # overwrite callback to capture exit code
195 0 0         if (exists $SUBS{$pid}) {
196 0           my $old_cb = $SUBS{$pid}->{exit};
197             $SUBS{$pid}->{exit} = sub {
198 0     0     $ret = shift;
199 0 0         $old_cb->($ret)
200             if defined $old_cb;
201 0           };
202              
203             # Poll until a) our target has exited or b) there are no more
204             # file handles to poll for.
205 0   0       while (exists $SUBS{$pid} && poll(undef)) {}
206             }
207              
208             # returns undef if childs exit has already been handled
209 0           return $ret;
210             }
211              
212             sub killall (;$) {
213 0   0 0 1   my $signal = shift || 'INT';
214              
215 0           kill $signal, keys %SUBS;
216 0   0       while (%SUBS && poll(undef)) {}
217             }
218              
219             sub _notify (@) {
220 0 0   0     die "no touching\n"
221             if caller ne __PACKAGE__;
222              
223 0           my $method = shift;
224 0           foreach my $observer (@OBSERVERS) {
225 0 0         $observer->$method(@_) if $observer->can($method);
226             }
227             }
228              
229             sub unobserve ($) {
230 0     0 1   my $observer = shift;
231 0           @OBSERVERS = grep { $_ ne $observer } @OBSERVERS;
  0            
232             }
233              
234             sub observe ($) {
235 0     0 1   my $observer = shift;
236 0           unobserve($observer);
237 0           push @OBSERVERS, $observer;
238             }
239              
240             1;
241              
242             __END__