File Coverage

blib/lib/IPC/Session.pm
Criterion Covered Total %
statement 99 111 89.1
branch 29 36 80.5
condition 5 15 33.3
subroutine 14 17 82.3
pod 6 12 50.0
total 153 191 80.1


line stmt bran cond sub pod time code
1             package IPC::Session;
2              
3 3     3   3323 use strict;
  3         6  
  3         105  
4 3     3   2374 use FileHandle;
  3         54875  
  3         21  
5 3     3   4511 use IPC::Open3;
  3         15347  
  3         244  
6              
7 3     3   26 use vars qw($VERSION);
  3         7  
  3         15468  
8              
9             $VERSION = '0.05';
10              
11             =head1 NAME
12              
13             IPC::Session - Drive ssh or other interactive shell, local or remote (like 'expect')
14              
15             =head1 SYNOPSIS
16              
17             use IPC::Session;
18              
19             # open ssh session to fred
20             # -- set timeout of 30 seconds for all send() calls
21             my $session = new IPC::Session("ssh fred",30);
22            
23             $session->send("hostname"); # run `hostname` command on fred
24             print $session->stdout(); # prints "fred"
25             $session->send("date"); # run `date` within same ssh
26             print $session->stdout(); # prints date
27            
28             # use like 'expect':
29             $session->send("uname -s");
30             for ($session->stdout)
31             {
32             /IRIX/ && do { $netstat = "/usr/etc/netstat" };
33             /ConvexOS/ && do { $netstat = "/usr/ucb/netstat" };
34             /Linux/ && do { $netstat = "/bin/netstat" };
35             }
36            
37             # errno returned in scalar context:
38             $errno = $session->send("$netstat -rn");
39             # try this:
40             $session->send("grep '^$user:' /etc/passwd")
41             && warn "$user not there";
42            
43             # hash returned in array context:
44             %netstat = $session->send("$netstat -in");
45             print "$netstat{'stdout'}\n"; # prints interface table
46             print "$netstat{'stderr'}\n"; # prints nothing (hopefully)
47             print "$netstat{'errno'}\n"; # prints 0
48              
49             =head1 DESCRIPTION
50              
51             This module encapsulates the open3() function call (see L)
52             and its associated filehandles. This makes it easy to maintain
53             multiple interactive command sessions, such as multiple persistent
54             'ssh' and/or 'rsh' sessions, within the same perl script.
55              
56             The remote shell session is kept open for the life of the object; this
57             avoids the overhead of repeatedly opening remote shells via multiple
58             ssh or rsh calls. This persistence is particularly useful if you are
59             using ssh for your remote shell invocation; it helps you overcome
60             the high ssh startup time.
61              
62             For applications requiring remote command invocation, this module
63             provides functionality that is similar to 'expect' or Expect.pm,
64             but in a lightweight more Perlish package, with discrete STDOUT,
65             STDERR, and return code processing.
66              
67             By the way, there's nothing inherently ssh-ish about IPC::Session -- it
68             doesn't even know anything about ssh, as a matter of fact. It will
69             work with any interactive shell that supports 'echo'. For instance,
70             'make test' just drives a local /bin/sh session.
71              
72             =head1 METHODS
73              
74             =head2 my $session = new IPC::Session("ssh fred",30);
75              
76             The constructor accepts the command string to be used to open the remote
77             shell session, such as ssh or rsh; it also accepts an optional timeout
78             value, in seconds. It returns a reference to the unique session object.
79              
80             If the timeout is not specified then it defaults to 60 seconds.
81             The timeout value can also be changed later; see L<"timeout()">.
82              
83             =cut
84              
85             sub new
86             {
87 3     3 1 918 my $class=shift;
88 3   33     29 $class = (ref $class || $class);
89 3         10 my $self={};
90 3         10 bless $self, $class;
91              
92 3         10 my ($cmd,$timeout,$handler)=@_;
93 3   50 1   55 $self->{'handler'} = $handler || sub {die @_};
  1         32  
94 3 50       18 $timeout=60 unless defined $timeout;
95 3         11 $self->{'timeout'} = $timeout;
96              
97 3         17 local(*IN,*OUT,*ERR); # so we can use more than one of these objects
98 3 50       23 open3(\*IN,\*OUT,\*ERR,$cmd) || &{$self->{'handler'}}($!);
  0         0  
99              
100 2         14562 ($self->{'stdin'},$self->{'stdout'},$self->{'stderr'}) = (*IN,*OUT,*ERR);
101              
102             # Set to autoflush.
103 2         18 for (*IN,*OUT,*ERR) {
104 6         37 select;
105 6         23 $|++;
106             }
107 2         11 select STDOUT;
108              
109             # determine target shell
110 2         26 $self->{'shell'} = $self->getshell();
111              
112 2         33 return $self;
113             }
114              
115             sub getshell
116             {
117 2     2 0 7 my $self=shift;
118 2         6 my ($tag, $shout);
119              
120 2         27 $tag=$self->tx('stdin', "echo;echo csherrno=\$status\n");
121 2         14 $shout=$self->rx('stdout', $tag);
122 2 50       13 return "csh" if $shout =~ /csherrno=0/;
123              
124 2         11 $tag=$self->tx('stdin', "echo;echo bsherrno=\$?\n");
125 2         13 $shout=$self->rx('stdout', $tag);
126 2 50       29 return "bsh" if $shout =~ /bsherrno=0/;
127              
128 0         0 die "unable to determine remote shell\n";
129             }
130              
131             sub tx
132             {
133 10     10 0 40 my ($self,$handle,$cmd) = @_;
134 10         33 my $fh=$self->{$handle};
135 10   100     82 my $shell = $self->{'shell'} || "";
136              
137 10         271 my $eot="_EoT_" . rand() . "_";
138              
139             # run command
140 10         411 print $fh "$cmd\n";
141              
142 10         81 print $fh "echo $eot";
143 10 100       107 print $fh " errno=\$?" if $shell eq "bsh";
144 10 50       42 print $fh " errno=\$status" if $shell eq "csh";
145 10         53 print $fh "\n";
146              
147             # call /bin/sh to work around csh stupidity -- csh doesn't support
148             # redirection of stderr... BUG this will only work if there is a
149             # /bin/sh on target machine
150 10         20 my $stderrcmd;
151 10 50       35 $stderrcmd="/bin/sh -c 'echo $eot >&2'\n" if $shell eq "csh";
152 10 100       47 $stderrcmd= "echo $eot >&2\n" if $shell eq "bsh";
153 10 100       58 print $fh $stderrcmd if $shell;
154 10         55 return $eot;
155             }
156              
157             sub rx
158             {
159 27     27 0 77 my ($self,$handle, $eot, $timeout) = @_;
160 27 100       81 $timeout = $self->{'timeout'} unless defined($timeout);
161 27         86 my $fh=$self->{$handle};
162              
163 27         71 my $rin = my $win = my $ein = '';
164 27         124 vec($rin,fileno($fh),1) = 1;
165 27         63 $ein = $rin;
166              
167             # Why two nested loops? So we can do eot pattern match (below)
168             # against a full line at a time, while getting one character at a
169             # time. Do we need to get only one character at a time? Probably
170             # not, but it evolved this way. It does let us parse and linebreak
171             # on the \n character, include newlines in the output, but not
172             # include the eot marker.
173              
174             # get full text
175 27         107 my $out="";
176 27         44 my $errno="";
177 27         217 while (!select(undef,undef,my $eout=$ein,0)) # while !eof()
178             {
179             # get one line of text
180 39         63 my $outl = "";
181 39         327 while (!select(undef,undef,my $eout=$ein,0)) # while !eof()
182             {
183             # wait for output on handle
184 514         8020105 my $nready=select(my $rout=$rin, undef, undef, $timeout);
185 514 100       1121 return $nready if $timeout==0;
186              
187             # handle timeout
188 502 100       945 &{$self->{'handler'}}("timeout on $handle") unless $nready;
  1         23  
189              
190             # read one char
191 501         765 my $outc;
192 0         0 sysread($self->{$handle},$outc,1)
193 501 50       3015 || &{$self->{'handler'}}("read error from $handle");
194              
195             # include newlines in output
196 501         792 $outl .= $outc;
197 501 100       3114 last if $outc eq "\n";
198             }
199             # store snarfed return code
200 26 100       725 $outl =~ /$eot errno=(\d+)/ && ($errno = $1);
201              
202             # eot pattern match -- don't include eot tag in output
203 26 100       1334 last if $outl =~ /$eot/;
204 12         103 $out .= $outl;
205             }
206              
207 14 100       120 return $out unless wantarray;
208 5         43 return $out,$errno;
209             }
210              
211             sub rxready
212             {
213 12     12 0 17 my $self=shift;
214 12         17 my $handle = shift;
215 12         31 return $self->rx($handle,"dummy",0);
216             }
217              
218             sub rxflush
219             {
220 12     12 0 18 my $self=shift;
221 12         37 my $handle = shift;
222 12   50     77 my $tag = shift || ".*";
223 12         42 while($self->rxready($handle))
224             {
225 0         0 $self->rx($handle,$tag)
226             }
227             }
228              
229             =head2 $commandhandle = $session->send("hostname");
230              
231             The send() method accepts a command string to be executed on the remote
232             host. The command will be executed in the context of the default shell
233             of the remote user (unless you start a different shell by sending the
234             appropriate command...). All shell escapes, command line terminators, pipes,
235             redirectors, etc. are legal and should work, though you of course will
236             have to escape special characters that have meaning to Perl.
237              
238             In a scalar context, this method returns the return code produced by the
239             command string.
240              
241             In an array context, this method returns a hash containing the return code
242             as well as the full text of the command string's output from the STDOUT
243             and STDERR file handles. The hash keys are 'stdout', 'stderr', and
244             'errno'.
245              
246             =cut
247              
248             sub send
249             {
250 6     6 1 7471 my $self=shift;
251 6         30 my $cmd=join(' ',@_);
252              
253             # send the command
254 6         28 $self->rxflush('stdout');
255 6         19 $self->rxflush('stderr');
256 6         30 my $tag = $self->tx('stdin',$cmd);
257              
258             # snarf the output until we hit eot marker on both streams
259 6         21 my ($stdout,$errno) = $self->rx('stdout', $tag);
260 5         31 my $stderr = $self->rx('stderr', $tag);
261              
262 5         24 $self->{'out'}{'stdout'} = $stdout;
263 5         16 $self->{'out'}{'stderr'} = $stderr;
264 5         12 $self->{'out'}{'errno'} = $errno;
265              
266 5 100       50 return $self->{'out'}{'errno'} unless wantarray;
267             return (
268 1         32 errno => $self->{'out'}{'errno'},
269             stdout => $self->{'out'}{'stdout'},
270             stderr => $self->{'out'}{'stderr'}
271             );
272             }
273              
274             =head2 print $session->stdout();
275              
276             Returns the full STDOUT text generated from the last send() command string.
277              
278             Also available via array context return codes -- see L<"send()">.
279              
280             =cut
281              
282             sub stdout
283             {
284 2     2 1 22 my $self=shift;
285 2         10 return $self->{'out'}{'stdout'};
286             }
287              
288             =head2 print $session->stderr();
289              
290             Returns the full STDERR text generated from the last send() command string.
291              
292             Also available via array context return codes -- see L<"send()">.
293              
294             =cut
295              
296             sub stderr
297             {
298 1     1 1 24 my $self=shift;
299 1         7 return $self->{'out'}{'stderr'};
300             }
301              
302             =head2 print $session->errno();
303              
304             Returns the return code generated from the last send() command string.
305              
306             Also available via array context return codes -- see L<"send()">.
307              
308             =cut
309              
310             sub errno
311             {
312 0     0 1   my $self=shift;
313 0           return $self->{'out'}{'errno'};
314             }
315              
316             =head2 $session->timeout(90);
317              
318             Allows you to change the timeout for subsequent send() calls.
319              
320             The timeout value is in seconds. Fractional seconds are allowed.
321             The timeout applies to all send() calls.
322              
323             Returns the current timeout if called with no args.
324              
325             =cut
326              
327             sub timeout
328             {
329 0     0 1   my $self=shift;
330 0   0       $self->{'timeout'} = ( shift || $self->{'timeout'});
331 0           return $self->{'timeout'};
332             }
333              
334             sub handler
335             {
336 0     0 0   my $self=shift;
337 0   0       $self->{'handler'} = ( shift || $self->{'handler'});
338 0           return $self->{'handler'};
339             }
340              
341             =head1 BUGS/RESTRICTIONS
342              
343             =over 4
344              
345             =item *
346              
347             The remote shell command you specify in new() is assumed to not prompt
348             for any passwords or present any challenge codes; i.e.; you must use
349             .rhosts, authorized_keys, ssh-agent, or the equivalent, and must be
350             prepared to answer any passphrase prompt if using ssh. You can
351             either run ssh-add ahead of time and provide the passphrase, have
352             your script do that itself, or simply set the passphrase to null (if
353             your security model allows it).
354              
355             =item *
356              
357             There must be a working /bin/sh on the target machine.
358              
359             =back
360              
361             =head1 AUTHOR
362              
363             Steve Traugott
364              
365             =head1 SEE ALSO
366              
367             L,
368             L,
369             L,
370             L,
371             L
372              
373             =cut
374              
375             1;
376              
377             __END__