File Coverage

blib/lib/Net/Cmd.pm
Criterion Covered Total %
statement 204 379 53.8
branch 64 174 36.7
condition 15 55 27.2
subroutine 28 47 59.5
pod 18 21 85.7
total 329 676 48.6


line stmt bran cond sub pod time code
1             # Net::Cmd.pm
2             #
3             # Copyright (C) 1995-2006 Graham Barr. All rights reserved.
4             # Copyright (C) 2013-2016, 2020 Steve Hay. All rights reserved.
5             # This module is free software; you can redistribute it and/or modify it under
6             # the same terms as Perl itself, i.e. under the terms of either the GNU General
7             # Public License or the Artistic License, as specified in the F file.
8              
9             package Net::Cmd;
10              
11 17     17   89324 use 5.008001;
  17         73  
12              
13 17     17   89 use strict;
  17         37  
  17         488  
14 17     17   107 use warnings;
  17         29  
  17         486  
15              
16 17     17   82 use Carp;
  17         35  
  17         900  
17 17     17   101 use Exporter;
  17         29  
  17         661  
18 17     17   626 use Symbol 'gensym';
  17         845  
  17         1142  
19 17     17   635 use Errno 'EINTR';
  17         1510  
  17         2111  
20              
21             BEGIN {
22 17 50   17   1293 if ($^O eq 'os390') {
23 0         0 require Convert::EBCDIC;
24              
25             # Convert::EBCDIC->import;
26             }
27             }
28              
29             our $VERSION = "3.13";
30             our @ISA = qw(Exporter);
31             our @EXPORT = qw(CMD_INFO CMD_OK CMD_MORE CMD_REJECT CMD_ERROR CMD_PENDING);
32              
33 17     17   118 use constant CMD_INFO => 1;
  17         76  
  17         1433  
34 17     17   109 use constant CMD_OK => 2;
  17         27  
  17         941  
35 17     17   100 use constant CMD_MORE => 3;
  17         28  
  17         864  
36 17     17   129 use constant CMD_REJECT => 4;
  17         31  
  17         911  
37 17     17   125 use constant CMD_ERROR => 5;
  17         39  
  17         1036  
38 17     17   99 use constant CMD_PENDING => 0;
  17         30  
  17         837  
39              
40 17     17   100 use constant DEF_REPLY_CODE => 421;
  17         29  
  17         6266  
41              
42             my %debug = ();
43              
44             my $tr = $^O eq 'os390' ? Convert::EBCDIC->new() : undef;
45              
46             sub toebcdic {
47 0     0 0 0 my $cmd = shift;
48              
49 0 0       0 unless (exists ${*$cmd}{'net_cmd_asciipeer'}) {
  0         0  
50 0         0 my $string = $_[0];
51 0         0 my $ebcdicstr = $tr->toebcdic($string);
52 0   0     0 ${*$cmd}{'net_cmd_asciipeer'} = $string !~ /^\d+/ && $ebcdicstr =~ /^\d+/;
  0         0  
53             }
54              
55 0 0       0 ${*$cmd}{'net_cmd_asciipeer'}
  0         0  
56             ? $tr->toebcdic($_[0])
57             : $_[0];
58             }
59              
60              
61             sub toascii {
62 0     0 0 0 my $cmd = shift;
63 0 0       0 ${*$cmd}{'net_cmd_asciipeer'}
  0         0  
64             ? $tr->toascii($_[0])
65             : $_[0];
66             }
67              
68              
69             sub _print_isa {
70 17     17   124 no strict 'refs'; ## no critic (TestingAndDebugging::ProhibitNoStrict)
  17         29  
  17         61953  
71              
72 0     0   0 my $pkg = shift;
73 0         0 my $cmd = $pkg;
74              
75 0   0     0 $debug{$pkg} ||= 0;
76              
77 0         0 my %done = ();
78 0         0 my @do = ($pkg);
79 0         0 my %spc = ($pkg, "");
80              
81 0         0 while ($pkg = shift @do) {
82 0 0       0 next if defined $done{$pkg};
83              
84 0         0 $done{$pkg} = 1;
85              
86             my $v =
87 0         0 defined ${"${pkg}::VERSION"}
88 0 0       0 ? "(" . ${"${pkg}::VERSION"} . ")"
  0         0  
89             : "";
90              
91 0         0 my $spc = $spc{$pkg};
92 0         0 $cmd->debug_print(1, "${spc}${pkg}${v}\n");
93              
94 0 0       0 if (@{"${pkg}::ISA"}) {
  0         0  
95 0         0 @spc{@{"${pkg}::ISA"}} = (" " . $spc{$pkg}) x @{"${pkg}::ISA"};
  0         0  
  0         0  
96 0         0 unshift(@do, @{"${pkg}::ISA"});
  0         0  
97             }
98             }
99             }
100              
101              
102             sub debug {
103 98 50 66 98 1 500 @_ == 1 or @_ == 2 or croak 'usage: $obj->debug([$level])';
104              
105 98         253 my ($cmd, $level) = @_;
106 98   33     290 my $pkg = ref($cmd) || $cmd;
107 98         193 my $oldval = 0;
108              
109 98 50       234 if (ref($cmd)) {
110 98   50     154 $oldval = ${*$cmd}{'net_cmd_debug'} || 0;
111             }
112             else {
113 0   0     0 $oldval = $debug{$pkg} || 0;
114             }
115              
116 98 100       426 return $oldval
117             unless @_ == 2;
118              
119 9 50 0     67 $level = $debug{$pkg} || 0
120             unless defined $level;
121              
122             _print_isa($pkg)
123 9 50 33     48 if ($level && !exists $debug{$pkg});
124              
125 9 50       38 if (ref($cmd)) {
126 9         19 ${*$cmd}{'net_cmd_debug'} = $level;
  9         81  
127             }
128             else {
129 0         0 $debug{$pkg} = $level;
130             }
131              
132 9         89 $oldval;
133             }
134              
135              
136             sub message {
137 16 50   16 1 98 @_ == 1 or croak 'usage: $obj->message()';
138              
139 16         43 my $cmd = shift;
140              
141             wantarray
142 10         31 ? @{${*$cmd}{'net_cmd_resp'}}
  10         77  
143 16 100       128 : join("", @{${*$cmd}{'net_cmd_resp'}});
  6         14  
  6         123  
144             }
145              
146              
147 0     0 1 0 sub debug_text { $_[2] }
148              
149              
150             sub debug_print {
151 0     0 1 0 my ($cmd, $out, $text) = @_;
152 0 0       0 print STDERR $cmd, ($out ? '>>> ' : '<<< '), $cmd->debug_text($out, $text);
153             }
154              
155              
156             sub code {
157 6 50   6 1 24 @_ == 1 or croak 'usage: $obj->code()';
158              
159 6         10 my $cmd = shift;
160              
161 0         0 ${*$cmd}{'net_cmd_code'} = $cmd->DEF_REPLY_CODE
162 6 50       15 unless exists ${*$cmd}{'net_cmd_code'};
  6         22  
163              
164 6         11 ${*$cmd}{'net_cmd_code'};
  6         21  
165             }
166              
167              
168             sub status {
169 0 0   0 1 0 @_ == 1 or croak 'usage: $obj->status()';
170              
171 0         0 my $cmd = shift;
172              
173 0         0 substr(${*$cmd}{'net_cmd_code'}, 0, 1);
  0         0  
174             }
175              
176              
177             sub set_status {
178 21 50   21 0 74 @_ == 3 or croak 'usage: $obj->set_status($code, $resp)';
179              
180 21         43 my $cmd = shift;
181 21         49 my ($code, $resp) = @_;
182              
183 21 50       142 $resp = defined $resp ? [$resp] : []
    50          
184             unless ref($resp);
185              
186 21         58 (${*$cmd}{'net_cmd_code'}, ${*$cmd}{'net_cmd_resp'}) = ($code, $resp);
  21         145  
  21         143  
187              
188 21         64 1;
189             }
190              
191             sub _syswrite_with_timeout {
192 56     56   186 my $cmd = shift;
193 56         133 my $line = shift;
194              
195 56         98 my $len = length($line);
196 56         81 my $offset = 0;
197 56         136 my $win = "";
198 56         234 vec($win, fileno($cmd), 1) = 1;
199 56   100     584 my $timeout = $cmd->timeout || undef;
200 56         490 my $initial = time;
201 56         89 my $pending = $timeout;
202              
203 56 50       1611 local $SIG{PIPE} = 'IGNORE' unless $^O eq 'MacOS';
204              
205 56         227 while ($len) {
206 56         78 my $wout;
207 56         531 my $nfound = select(undef, $wout = $win, undef, $pending);
208 56 50 33     389 if ((defined $nfound and $nfound > 0) or -f $cmd) # -f for testing on win32
    0 33        
209             {
210 56         1396 my $w = syswrite($cmd, $line, $len, $offset);
211 56 50       2502 if (! defined($w) ) {
212 0         0 my $err = $!;
213 0         0 $cmd->close;
214 0         0 $cmd->_set_status_closed($err);
215 0         0 return;
216             }
217 56         116 $len -= $w;
218 56         175 $offset += $w;
219             }
220             elsif ($nfound == -1) {
221 0 0       0 if ( $! == EINTR ) {
222 0 0       0 if ( defined($timeout) ) {
223 0 0       0 redo if ($pending = $timeout - ( time - $initial ) ) > 0;
224 0         0 $cmd->_set_status_timeout;
225 0         0 return;
226             }
227 0         0 redo;
228             }
229 0         0 my $err = $!;
230 0         0 $cmd->close;
231 0         0 $cmd->_set_status_closed($err);
232 0         0 return;
233             }
234             else {
235 0         0 $cmd->_set_status_timeout;
236 0         0 return;
237             }
238             }
239              
240 56         1034 return 1;
241             }
242              
243             sub _set_status_timeout {
244 0     0   0 my $cmd = shift;
245 0   0     0 my $pkg = ref($cmd) || $cmd;
246              
247 0         0 $cmd->set_status($cmd->DEF_REPLY_CODE, "[$pkg] Timeout");
248 0 0       0 carp(ref($cmd) . ": " . (caller(1))[3] . "(): timeout") if $cmd->debug;
249             }
250              
251             sub _set_status_closed {
252 0     0   0 my $cmd = shift;
253 0         0 my $err = shift;
254 0   0     0 my $pkg = ref($cmd) || $cmd;
255              
256 0         0 $cmd->set_status($cmd->DEF_REPLY_CODE, "[$pkg] Connection closed");
257 0 0       0 carp(ref($cmd) . ": " . (caller(1))[3]
258             . "(): unexpected EOF on command channel: $err") if $cmd->debug;
259             }
260              
261             sub _is_closed {
262 65     65   122 my $cmd = shift;
263 65 50       345 if (!defined fileno($cmd)) {
264 0         0 $cmd->_set_status_closed($!);
265 0         0 return 1;
266             }
267 65         545 return 0;
268             }
269              
270             sub command {
271 19     19 1 59 my $cmd = shift;
272              
273 19 50       191 return $cmd
274             if $cmd->_is_closed;
275              
276             $cmd->dataend()
277 19 50       52 if (exists ${*$cmd}{'net_cmd_last_ch'});
  19         87  
278              
279 19 50       89 if (scalar(@_)) {
280             my $str = join(
281             " ",
282             map {
283 19         70 /\n/
284 26 50       464 ? do { my $n = $_; $n =~ tr/\n/ /; $n }
  0         0  
  0         0  
  0         0  
285             : $_;
286             } @_
287             );
288 19 50       98 $str = $cmd->toascii($str) if $tr;
289 19         53 $str .= "\015\012";
290              
291 19 50       77 $cmd->debug_print(1, $str)
292             if ($cmd->debug);
293              
294             # though documented to return undef on failure, the legacy behavior
295             # was to return $cmd even on failure, so this odd construct does that
296 19 50       349 $cmd->_syswrite_with_timeout($str)
297             or return $cmd;
298             }
299              
300 19         110 $cmd;
301             }
302              
303              
304             sub ok {
305 0 0   0 1 0 @_ == 1 or croak 'usage: $obj->ok()';
306              
307 0         0 my $code = $_[0]->code;
308 0 0       0 0 < $code && $code < 400;
309             }
310              
311              
312             sub unsupported {
313 0     0 1 0 my $cmd = shift;
314              
315 0         0 $cmd->set_status(580, 'Unsupported command');
316              
317 0         0 0;
318             }
319              
320              
321             sub getline {
322 10     10 1 96 my $cmd = shift;
323              
324 10   100     31 ${*$cmd}{'net_cmd_lines'} ||= [];
  10         224  
325              
326 2         15 return shift @{${*$cmd}{'net_cmd_lines'}}
  2         9  
327 10 100       24 if scalar(@{${*$cmd}{'net_cmd_lines'}});
  10         44  
  10         83  
328              
329 8 100       50 my $partial = defined(${*$cmd}{'net_cmd_partial'}) ? ${*$cmd}{'net_cmd_partial'} : "";
  8         98  
  5         17  
330              
331             return
332 8 50       162 if $cmd->_is_closed;
333              
334 8         68 my $fd = fileno($cmd);
335 8         46 my $rin = "";
336 8         39 vec($rin, $fd, 1) = 1;
337              
338 8         39 my $buf;
339              
340 8         31 until (scalar(@{${*$cmd}{'net_cmd_lines'}})) {
  16         56  
  16         109  
341 8   50     208 my $timeout = $cmd->timeout || undef;
342 8         140 my $rout;
343              
344 8         502 my $select_ret = select($rout = $rin, undef, undef, $timeout);
345 8 50       86 if ($select_ret > 0) {
346 8 50       154 unless (sysread($cmd, $buf = "", 1024)) {
347 0         0 my $err = $!;
348 0         0 $cmd->close;
349 0         0 $cmd->_set_status_closed($err);
350 0         0 return;
351             }
352              
353 8         45 substr($buf, 0, 0) = $partial; ## prepend from last sysread
354              
355 8         145 my @buf = split(/\015?\012/, $buf, -1); ## break into lines
356              
357 8         41 $partial = pop @buf;
358              
359 8         19 push(@{${*$cmd}{'net_cmd_lines'}}, map {"$_\n"} @buf);
  8         12  
  8         66  
  10         58  
360              
361             }
362             else {
363 0         0 $cmd->_set_status_timeout;
364 0         0 return;
365             }
366             }
367              
368 8         26 ${*$cmd}{'net_cmd_partial'} = $partial;
  8         105  
369              
370 8 50       50 if ($tr) {
371 0         0 foreach my $ln (@{${*$cmd}{'net_cmd_lines'}}) {
  0         0  
  0         0  
372 0         0 $ln = $cmd->toebcdic($ln);
373             }
374             }
375              
376 8         39 shift @{${*$cmd}{'net_cmd_lines'}};
  8         15  
  8         57  
377             }
378              
379              
380             sub ungetline {
381 0     0 1 0 my ($cmd, $str) = @_;
382              
383 0   0     0 ${*$cmd}{'net_cmd_lines'} ||= [];
  0         0  
384 0         0 unshift(@{${*$cmd}{'net_cmd_lines'}}, $str);
  0         0  
  0         0  
385             }
386              
387              
388             sub parse_response {
389             return ()
390 26 50   26 1 273 unless $_[1] =~ s/^(\d\d\d)(.?)//o;
391 26         201 ($1, $2 eq "-");
392             }
393              
394              
395             sub response {
396 21     21 1 56 my $cmd = shift;
397 21         92 my ($code, $more) = (undef) x 2;
398              
399 21         426 $cmd->set_status($cmd->DEF_REPLY_CODE, undef); # initialize the response
400              
401 21         34 while (1) {
402 26         427 my $str = $cmd->getline();
403              
404 26 50       158297 return CMD_ERROR
405             unless defined($str);
406              
407 26 50       99 $cmd->debug_print(0, $str)
408             if ($cmd->debug);
409              
410 26         236 ($code, $more) = $cmd->parse_response($str);
411 26 50       109 unless (defined $code) {
412 0 0       0 carp("$cmd: response(): parse error in '$str'") if ($cmd->debug);
413 0         0 $cmd->ungetline($str);
414 0         0 $@ = $str; # $@ used as tunneling hack
415 0         0 return CMD_ERROR;
416             }
417              
418 26         49 ${*$cmd}{'net_cmd_code'} = $code;
  26         93  
419              
420 26         48 push(@{${*$cmd}{'net_cmd_resp'}}, $str);
  26         41  
  26         107  
421              
422 26 100       77 last unless ($more);
423             }
424              
425 21 50       76 return unless defined $code;
426 21         155 substr($code, 0, 1);
427             }
428              
429              
430             sub read_until_dot {
431 0     0 1 0 my $cmd = shift;
432 0         0 my $fh = shift;
433 0         0 my $arr = [];
434              
435 0         0 while (1) {
436 0 0       0 my $str = $cmd->getline() or return;
437              
438 0 0       0 $cmd->debug_print(0, $str)
439             if ($cmd->debug & 4);
440              
441 0 0       0 last if ($str =~ /^\.\r?\n/o);
442              
443 0         0 $str =~ s/^\.\././o;
444              
445 0 0       0 if (defined $fh) {
446 0         0 print $fh $str;
447             }
448             else {
449 0         0 push(@$arr, $str);
450             }
451             }
452              
453 0         0 $arr;
454             }
455              
456              
457             sub datasend {
458 22     22 1 7817 my $cmd = shift;
459 22 50 66     137 my $arr = @_ == 1 && ref($_[0]) ? $_[0] : \@_;
460 22         70 my $line = join("", @$arr);
461              
462             # Perls < 5.10.1 (with the exception of 5.8.9) have a performance problem with
463             # the substitutions below when dealing with strings stored internally in
464             # UTF-8, so downgrade them (if possible).
465             # Data passed to datasend() should be encoded to octets upstream already so
466             # shouldn't even have the UTF-8 flag on to start with, but if it so happens
467             # that the octets are stored in an upgraded string (as can sometimes occur)
468             # then they would still downgrade without fail anyway.
469             # Only Unicode codepoints > 0xFF stored in an upgraded string will fail to
470             # downgrade. We fail silently in that case, and a "Wide character in print"
471             # warning will be emitted later by syswrite().
472 22 50 33     62 utf8::downgrade($line, 1) if $] < 5.010001 && $] != 5.008009;
473              
474 22 50       55 return 0
475             if $cmd->_is_closed;
476              
477 22         39 my $last_ch = ${*$cmd}{'net_cmd_last_ch'};
  22         75  
478              
479             # We have not send anything yet, so last_ch = "\012" means we are at the start of a line
480 22 100       64 $last_ch = ${*$cmd}{'net_cmd_last_ch'} = "\012" unless defined $last_ch;
  16         49  
481              
482 22 100       56 return 1 unless length $line;
483              
484 21 50       52 if ($cmd->debug) {
485 0         0 foreach my $b (split(/\n/, $line)) {
486 0         0 $cmd->debug_print(1, "$b\n");
487             }
488             }
489              
490 21         27 $line =~ tr/\r\n/\015\012/ unless "\r" eq "\015";
491              
492 21         33 my $first_ch = '';
493              
494 21 100       62 if ($last_ch eq "\015") {
    100          
495             # Remove \012 so it does not get prefixed with another \015 below
496             # and escape the . if there is one following it because the fixup
497             # below will not find it
498 4 50       55 $first_ch = "\012" if $line =~ s/^\012(\.?)/$1$1/;
499             }
500             elsif ($last_ch eq "\012") {
501             # Fixup below will not find the . as the first character of the buffer
502 16 100       59 $first_ch = "." if $line =~ /^\./;
503             }
504              
505 21         134 $line =~ s/\015?\012(\.?)/\015\012$1$1/sg;
506              
507 21         52 substr($line, 0, 0) = $first_ch;
508              
509 21         39 ${*$cmd}{'net_cmd_last_ch'} = substr($line, -1, 1);
  21         50  
510              
511 21 50       56 $cmd->_syswrite_with_timeout($line)
512             or return;
513              
514 21         110 1;
515             }
516              
517              
518             sub rawdatasend {
519 0     0 1 0 my $cmd = shift;
520 0 0 0     0 my $arr = @_ == 1 && ref($_[0]) ? $_[0] : \@_;
521 0         0 my $line = join("", @$arr);
522              
523 0 0       0 return 0
524             if $cmd->_is_closed;
525              
526 0 0       0 return 1
527             unless length($line);
528              
529 0 0       0 if ($cmd->debug) {
530 0         0 my $b = "$cmd>>> ";
531 0         0 print STDERR $b, join("\n$b", split(/\n/, $line)), "\n";
532             }
533              
534 0 0       0 $cmd->_syswrite_with_timeout($line)
535             or return;
536              
537 0         0 1;
538             }
539              
540              
541             sub dataend {
542 16     16 1 11295 my $cmd = shift;
543              
544 16 50       39 return 0
545             if $cmd->_is_closed;
546              
547 16         25 my $ch = ${*$cmd}{'net_cmd_last_ch'};
  16         53  
548 16         26 my $tosend;
549              
550 16 50       66 if (!defined $ch) {
    100          
551 0         0 return 1;
552             }
553             elsif ($ch ne "\012") {
554 6         10 $tosend = "\015\012";
555             }
556              
557 16         31 $tosend .= ".\015\012";
558              
559 16 50       44 $cmd->debug_print(1, ".\n")
560             if ($cmd->debug);
561              
562 16 50       41 $cmd->_syswrite_with_timeout($tosend)
563             or return 0;
564              
565 16         39 delete ${*$cmd}{'net_cmd_last_ch'};
  16         67  
566              
567 16         60 $cmd->response() == CMD_OK;
568             }
569              
570             # read and write to tied filehandle
571             sub tied_fh {
572 0     0 1   my $cmd = shift;
573 0           ${*$cmd}{'net_cmd_readbuf'} = '';
  0            
574 0           my $fh = gensym();
575 0           tie *$fh, ref($cmd), $cmd;
576 0           return $fh;
577             }
578              
579             # tie to myself
580             sub TIEHANDLE {
581 0     0     my $class = shift;
582 0           my $cmd = shift;
583 0           return $cmd;
584             }
585              
586             # Tied filehandle read. Reads requested data length, returning
587             # end-of-file when the dot is encountered.
588             sub READ {
589 0     0     my $cmd = shift;
590 0           my ($len, $offset) = @_[1, 2];
591 0 0         return unless exists ${*$cmd}{'net_cmd_readbuf'};
  0            
592 0           my $done = 0;
593 0   0       while (!$done and length(${*$cmd}{'net_cmd_readbuf'}) < $len) {
  0            
594 0 0         ${*$cmd}{'net_cmd_readbuf'} .= $cmd->getline() or return;
  0            
595 0 0         $done++ if ${*$cmd}{'net_cmd_readbuf'} =~ s/^\.\r?\n\Z//m;
  0            
596             }
597              
598 0           $_[0] = '';
599 0           substr($_[0], $offset + 0) = substr(${*$cmd}{'net_cmd_readbuf'}, 0, $len);
  0            
600 0           substr(${*$cmd}{'net_cmd_readbuf'}, 0, $len) = '';
  0            
601 0 0         delete ${*$cmd}{'net_cmd_readbuf'} if $done;
  0            
602              
603 0           return length $_[0];
604             }
605              
606              
607             sub READLINE {
608 0     0     my $cmd = shift;
609              
610             # in this context, we use the presence of readbuf to
611             # indicate that we have not yet reached the eof
612 0 0         return unless exists ${*$cmd}{'net_cmd_readbuf'};
  0            
613 0           my $line = $cmd->getline;
614 0 0         return if $line =~ /^\.\r?\n/;
615 0           $line;
616             }
617              
618              
619             sub PRINT {
620 0     0     my $cmd = shift;
621 0           my ($buf, $len, $offset) = @_;
622 0   0       $len ||= length($buf);
623 0           $offset += 0;
624 0 0         return unless $cmd->datasend(substr($buf, $offset, $len));
625 0           ${*$cmd}{'net_cmd_sending'}++; # flag that we should call dataend()
  0            
626 0           return $len;
627             }
628              
629              
630             sub CLOSE {
631 0     0     my $cmd = shift;
632 0 0         my $r = exists(${*$cmd}{'net_cmd_sending'}) ? $cmd->dataend : 1;
  0            
633 0           delete ${*$cmd}{'net_cmd_readbuf'};
  0            
634 0           delete ${*$cmd}{'net_cmd_sending'};
  0            
635 0           $r;
636             }
637              
638             1;
639              
640             __END__