File Coverage

blib/lib/Net/Cmd.pm
Criterion Covered Total %
statement 204 378 53.9
branch 63 172 36.6
condition 15 55 27.2
subroutine 28 47 59.5
pod 18 21 85.7
total 328 673 48.7


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, 2022 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   81759 use 5.008001;
  17         67  
12              
13 17     17   82 use strict;
  17         30  
  17         347  
14 17     17   75 use warnings;
  17         49  
  17         411  
15              
16 17     17   87 use Carp;
  17         33  
  17         1147  
17 17     17   110 use Exporter;
  17         43  
  17         638  
18 17     17   550 use Symbol 'gensym';
  17         848  
  17         879  
19 17     17   592 use Errno 'EINTR';
  17         1425  
  17         2197  
20              
21             BEGIN {
22 17     17   1149 if (ord "A" == 193) {
23             require Convert::EBCDIC;
24              
25             # Convert::EBCDIC->import;
26             }
27             }
28              
29             our $VERSION = "3.15";
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   115 use constant CMD_INFO => 1;
  17         73  
  17         1524  
34 17     17   114 use constant CMD_OK => 2;
  17         28  
  17         912  
35 17     17   93 use constant CMD_MORE => 3;
  17         47  
  17         803  
36 17     17   92 use constant CMD_REJECT => 4;
  17         32  
  17         1043  
37 17     17   111 use constant CMD_ERROR => 5;
  17         26  
  17         835  
38 17     17   118 use constant CMD_PENDING => 0;
  17         65  
  17         904  
39              
40 17     17   97 use constant DEF_REPLY_CODE => 421;
  17         22  
  17         5097  
41              
42             my %debug = ();
43              
44             my $tr = ord "A" == 193 ? 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   120 no strict 'refs'; ## no critic (TestingAndDebugging::ProhibitNoStrict)
  17         27  
  17         61048  
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 383 @_ == 1 or @_ == 2 or croak 'usage: $obj->debug([$level])';
104              
105 98         214 my ($cmd, $level) = @_;
106 98   33     237 my $pkg = ref($cmd) || $cmd;
107 98         145 my $oldval = 0;
108              
109 98 50       192 if (ref($cmd)) {
110 98   50     132 $oldval = ${*$cmd}{'net_cmd_debug'} || 0;
111             }
112             else {
113 0   0     0 $oldval = $debug{$pkg} || 0;
114             }
115              
116 98 100       366 return $oldval
117             unless @_ == 2;
118              
119 9 50 0     29 $level = $debug{$pkg} || 0
120             unless defined $level;
121              
122             _print_isa($pkg)
123 9 50 33     33 if ($level && !exists $debug{$pkg});
124              
125 9 50       23 if (ref($cmd)) {
126 9         20 ${*$cmd}{'net_cmd_debug'} = $level;
  9         69  
127             }
128             else {
129 0         0 $debug{$pkg} = $level;
130             }
131              
132 9         40 $oldval;
133             }
134              
135              
136             sub message {
137 16 50   16 1 49 @_ == 1 or croak 'usage: $obj->message()';
138              
139 16         33 my $cmd = shift;
140              
141             wantarray
142 10         16 ? @{${*$cmd}{'net_cmd_resp'}}
  10         47  
143 16 100       64 : join("", @{${*$cmd}{'net_cmd_resp'}});
  6         14  
  6         89  
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 25 @_ == 1 or croak 'usage: $obj->code()';
158              
159 6         13 my $cmd = shift;
160              
161 0         0 ${*$cmd}{'net_cmd_code'} = $cmd->DEF_REPLY_CODE
162 6 50       8 unless exists ${*$cmd}{'net_cmd_code'};
  6         20  
163              
164 6         9 ${*$cmd}{'net_cmd_code'};
  6         30  
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 62 @_ == 3 or croak 'usage: $obj->set_status($code, $resp)';
179              
180 21         50 my $cmd = shift;
181 21         39 my ($code, $resp) = @_;
182              
183 21 50       77 $resp = defined $resp ? [$resp] : []
    50          
184             unless ref($resp);
185              
186 21         38 (${*$cmd}{'net_cmd_code'}, ${*$cmd}{'net_cmd_resp'}) = ($code, $resp);
  21         118  
  21         124  
187              
188 21         51 1;
189             }
190              
191             sub _syswrite_with_timeout {
192 56     56   92 my $cmd = shift;
193 56         82 my $line = shift;
194              
195 56         96 my $len = length($line);
196 56         74 my $offset = 0;
197 56         111 my $win = "";
198 56         225 vec($win, fileno($cmd), 1) = 1;
199 56   100     500 my $timeout = $cmd->timeout || undef;
200 56         461 my $initial = time;
201 56         75 my $pending = $timeout;
202              
203 56 50       1353 local $SIG{PIPE} = 'IGNORE' unless $^O eq 'MacOS';
204              
205 56         209 while ($len) {
206 56         69 my $wout;
207 56         443 my $nfound = select(undef, $wout = $win, undef, $pending);
208 56 50 33     350 if ((defined $nfound and $nfound > 0) or -f $cmd) # -f for testing on win32
    0 33        
209             {
210 56         1269 my $w = syswrite($cmd, $line, $len, $offset);
211 56 50       1897 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         98 $len -= $w;
218 56         150 $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         855 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   102 my $cmd = shift;
263 65 50       326 if (!defined fileno($cmd)) {
264 0         0 $cmd->_set_status_closed($!);
265 0         0 return 1;
266             }
267 65         481 return 0;
268             }
269              
270             sub command {
271 19     19 1 52 my $cmd = shift;
272              
273 19 50       110 return $cmd
274             if $cmd->_is_closed;
275              
276             $cmd->dataend()
277 19 50       56 if (exists ${*$cmd}{'net_cmd_last_ch'});
  19         80  
278              
279 19 50       60 if (scalar(@_)) {
280             my $str = join(
281             " ",
282             map {
283 19         47 /\n/
284 26 50       412 ? do { my $n = $_; $n =~ tr/\n/ /; $n }
  0         0  
  0         0  
  0         0  
285             : $_;
286             } @_
287             );
288 19 50       74 $str = $cmd->toascii($str) if $tr;
289 19         69 $str .= "\015\012";
290              
291 19 50       56 $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       348 $cmd->_syswrite_with_timeout($str)
297             or return $cmd;
298             }
299              
300 19         161 $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 89 my $cmd = shift;
323              
324 10   100     39 ${*$cmd}{'net_cmd_lines'} ||= [];
  10         166  
325              
326 2         3 return shift @{${*$cmd}{'net_cmd_lines'}}
  2         24  
327 10 100       86 if scalar(@{${*$cmd}{'net_cmd_lines'}});
  10         43  
  10         118  
328              
329 8 100       42 my $partial = defined(${*$cmd}{'net_cmd_partial'}) ? ${*$cmd}{'net_cmd_partial'} : "";
  8         61  
  5         13  
330              
331             return
332 8 50       164 if $cmd->_is_closed;
333              
334 8         41 my $fd = fileno($cmd);
335 8         15 my $rin = "";
336 8         96 vec($rin, $fd, 1) = 1;
337              
338 8         57 my $buf;
339              
340 8         37 until (scalar(@{${*$cmd}{'net_cmd_lines'}})) {
  16         32  
  16         87  
341 8   50     161 my $timeout = $cmd->timeout || undef;
342 8         188 my $rout;
343              
344 8         108 my $select_ret = select($rout = $rin, undef, undef, $timeout);
345 8 50       49 if ($select_ret > 0) {
346 8 50       172 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         35 substr($buf, 0, 0) = $partial; ## prepend from last sysread
354              
355 8         178 my @buf = split(/\015?\012/, $buf, -1); ## break into lines
356              
357 8         42 $partial = pop @buf;
358              
359 8         34 push(@{${*$cmd}{'net_cmd_lines'}}, map {"$_\n"} @buf);
  8         21  
  8         58  
  10         55  
360              
361             }
362             else {
363 0         0 $cmd->_set_status_timeout;
364 0         0 return;
365             }
366             }
367              
368 8         48 ${*$cmd}{'net_cmd_partial'} = $partial;
  8         26  
369              
370 8 50       59 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         22 shift @{${*$cmd}{'net_cmd_lines'}};
  8         20  
  8         51  
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 191 unless $_[1] =~ s/^(\d\d\d)(.?)//o;
391 26         205 ($1, $2 eq "-");
392             }
393              
394              
395             sub response {
396 21     21 1 52 my $cmd = shift;
397 21         65 my ($code, $more) = (undef) x 2;
398              
399 21         397 $cmd->set_status($cmd->DEF_REPLY_CODE, undef); # initialize the response
400              
401 21         29 while (1) {
402 26         342 my $str = $cmd->getline();
403              
404 26 50       16469 return CMD_ERROR
405             unless defined($str);
406              
407 26 50       90 $cmd->debug_print(0, $str)
408             if ($cmd->debug);
409              
410 26         152 ($code, $more) = $cmd->parse_response($str);
411 26 50       79 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         37 ${*$cmd}{'net_cmd_code'} = $code;
  26         68  
419              
420 26         49 push(@{${*$cmd}{'net_cmd_resp'}}, $str);
  26         31  
  26         87  
421              
422 26 100       114 last unless ($more);
423             }
424              
425 21 50       44 return unless defined $code;
426 21         129 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 2405 my $cmd = shift;
459 22 50 66     117 my $arr = @_ == 1 && ref($_[0]) ? $_[0] : \@_;
460 22         61 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       46 return 0
475             if $cmd->_is_closed;
476              
477 22         27 my $last_ch = ${*$cmd}{'net_cmd_last_ch'};
  22         69  
478              
479             # We have not send anything yet, so last_ch = "\012" means we are at the start of a line
480 22 100       48 $last_ch = ${*$cmd}{'net_cmd_last_ch'} = "\012" unless defined $last_ch;
  16         47  
481              
482 22 100       53 return 1 unless length $line;
483              
484 21 50       44 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         28 $line =~ tr/\r\n/\015\012/ unless "\r" eq "\015";
491              
492 21         29 my $first_ch = '';
493              
494 21 100       54 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       43 $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       64 $first_ch = "." if $line =~ /^\./;
503             }
504              
505 21         135 $line =~ s/\015?\012(\.?)/\015\012$1$1/sg;
506              
507 21         47 substr($line, 0, 0) = $first_ch;
508              
509 21         41 ${*$cmd}{'net_cmd_last_ch'} = substr($line, -1, 1);
  21         45  
510              
511 21 50       53 $cmd->_syswrite_with_timeout($line)
512             or return;
513              
514 21         100 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 4472 my $cmd = shift;
543              
544 16 50       36 return 0
545             if $cmd->_is_closed;
546              
547 16         21 my $ch = ${*$cmd}{'net_cmd_last_ch'};
  16         43  
548 16         27 my $tosend;
549              
550 16 50       53 if (!defined $ch) {
    100          
551 0         0 return 1;
552             }
553             elsif ($ch ne "\012") {
554 6         9 $tosend = "\015\012";
555             }
556              
557 16         25 $tosend .= ".\015\012";
558              
559 16 50       39 $cmd->debug_print(1, ".\n")
560             if ($cmd->debug);
561              
562 16 50       35 $cmd->_syswrite_with_timeout($tosend)
563             or return 0;
564              
565 16         32 delete ${*$cmd}{'net_cmd_last_ch'};
  16         59  
566              
567 16         50 $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__