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   77066 use 5.008001;
  17         106  
12              
13 17     17   74 use strict;
  17         30  
  17         304  
14 17     17   63 use warnings;
  17         32  
  17         419  
15              
16 17     17   72 use Carp;
  17         34  
  17         965  
17 17     17   93 use Exporter;
  17         27  
  17         652  
18 17     17   434 use Symbol 'gensym';
  17         723  
  17         808  
19 17     17   527 use Errno 'EINTR';
  17         1226  
  17         1877  
20              
21             BEGIN {
22 17     17   1011 if (ord "A" == 193) {
23             require Convert::EBCDIC;
24              
25             # Convert::EBCDIC->import;
26             }
27             }
28              
29             our $VERSION = "3.14";
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   108 use constant CMD_INFO => 1;
  17         58  
  17         1352  
34 17     17   98 use constant CMD_OK => 2;
  17         26  
  17         757  
35 17     17   86 use constant CMD_MORE => 3;
  17         21  
  17         673  
36 17     17   80 use constant CMD_REJECT => 4;
  17         37  
  17         796  
37 17     17   89 use constant CMD_ERROR => 5;
  17         31  
  17         740  
38 17     17   111 use constant CMD_PENDING => 0;
  17         29  
  17         751  
39              
40 17     17   84 use constant DEF_REPLY_CODE => 421;
  17         31  
  17         4248  
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   109 no strict 'refs'; ## no critic (TestingAndDebugging::ProhibitNoStrict)
  17         26  
  17         51334  
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 354 @_ == 1 or @_ == 2 or croak 'usage: $obj->debug([$level])';
104              
105 98         179 my ($cmd, $level) = @_;
106 98   33     252 my $pkg = ref($cmd) || $cmd;
107 98         118 my $oldval = 0;
108              
109 98 50       179 if (ref($cmd)) {
110 98   50     112 $oldval = ${*$cmd}{'net_cmd_debug'} || 0;
111             }
112             else {
113 0   0     0 $oldval = $debug{$pkg} || 0;
114             }
115              
116 98 100       338 return $oldval
117             unless @_ == 2;
118              
119 9 50 0     19 $level = $debug{$pkg} || 0
120             unless defined $level;
121              
122             _print_isa($pkg)
123 9 50 33     36 if ($level && !exists $debug{$pkg});
124              
125 9 50       27 if (ref($cmd)) {
126 9         16 ${*$cmd}{'net_cmd_debug'} = $level;
  9         69  
127             }
128             else {
129 0         0 $debug{$pkg} = $level;
130             }
131              
132 9         31 $oldval;
133             }
134              
135              
136             sub message {
137 16 50   16 1 47 @_ == 1 or croak 'usage: $obj->message()';
138              
139 16         24 my $cmd = shift;
140              
141             wantarray
142 10         10 ? @{${*$cmd}{'net_cmd_resp'}}
  10         40  
143 16 100       37 : join("", @{${*$cmd}{'net_cmd_resp'}});
  6         8  
  6         93  
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 17 @_ == 1 or croak 'usage: $obj->code()';
158              
159 6         9 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         77  
163              
164 6         9 ${*$cmd}{'net_cmd_code'};
  6         26  
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 54 @_ == 3 or croak 'usage: $obj->set_status($code, $resp)';
179              
180 21         27 my $cmd = shift;
181 21         39 my ($code, $resp) = @_;
182              
183 21 50       62 $resp = defined $resp ? [$resp] : []
    50          
184             unless ref($resp);
185              
186 21         32 (${*$cmd}{'net_cmd_code'}, ${*$cmd}{'net_cmd_resp'}) = ($code, $resp);
  21         123  
  21         159  
187              
188 21         46 1;
189             }
190              
191             sub _syswrite_with_timeout {
192 56     56   83 my $cmd = shift;
193 56         79 my $line = shift;
194              
195 56         80 my $len = length($line);
196 56         60 my $offset = 0;
197 56         105 my $win = "";
198 56         177 vec($win, fileno($cmd), 1) = 1;
199 56   100     482 my $timeout = $cmd->timeout || undef;
200 56         392 my $initial = time;
201 56         66 my $pending = $timeout;
202              
203 56 50       1167 local $SIG{PIPE} = 'IGNORE' unless $^O eq 'MacOS';
204              
205 56         176 while ($len) {
206 56         64 my $wout;
207 56         374 my $nfound = select(undef, $wout = $win, undef, $pending);
208 56 50 33     282 if ((defined $nfound and $nfound > 0) or -f $cmd) # -f for testing on win32
    0 33        
209             {
210 56         1184 my $w = syswrite($cmd, $line, $len, $offset);
211 56 50       1772 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         85 $len -= $w;
218 56         134 $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         737 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   91 my $cmd = shift;
263 65 50       229 if (!defined fileno($cmd)) {
264 0         0 $cmd->_set_status_closed($!);
265 0         0 return 1;
266             }
267 65         393 return 0;
268             }
269              
270             sub command {
271 19     19 1 39 my $cmd = shift;
272              
273 19 50       109 return $cmd
274             if $cmd->_is_closed;
275              
276             $cmd->dataend()
277 19 50       28 if (exists ${*$cmd}{'net_cmd_last_ch'});
  19         63  
278              
279 19 50       52 if (scalar(@_)) {
280             my $str = join(
281             " ",
282             map {
283 19         55 /\n/
284 26 50       307 ? do { my $n = $_; $n =~ tr/\n/ /; $n }
  0         0  
  0         0  
  0         0  
285             : $_;
286             } @_
287             );
288 19 50       81 $str = $cmd->toascii($str) if $tr;
289 19         51 $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       230 $cmd->_syswrite_with_timeout($str)
297             or return $cmd;
298             }
299              
300 19         85 $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 50 my $cmd = shift;
323              
324 10   100     32 ${*$cmd}{'net_cmd_lines'} ||= [];
  10         103  
325              
326 2         5 return shift @{${*$cmd}{'net_cmd_lines'}}
  2         6  
327 10 100       24 if scalar(@{${*$cmd}{'net_cmd_lines'}});
  10         28  
  10         78  
328              
329 8 100       30 my $partial = defined(${*$cmd}{'net_cmd_partial'}) ? ${*$cmd}{'net_cmd_partial'} : "";
  8         47  
  5         12  
330              
331             return
332 8 50       96 if $cmd->_is_closed;
333              
334 8         21 my $fd = fileno($cmd);
335 8         25 my $rin = "";
336 8         47 vec($rin, $fd, 1) = 1;
337              
338 8         25 my $buf;
339              
340 8         31 until (scalar(@{${*$cmd}{'net_cmd_lines'}})) {
  16         26  
  16         60  
341 8   50     128 my $timeout = $cmd->timeout || undef;
342 8         152 my $rout;
343              
344 8         777 my $select_ret = select($rout = $rin, undef, undef, $timeout);
345 8 50       58 if ($select_ret > 0) {
346 8 50       130 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         32 substr($buf, 0, 0) = $partial; ## prepend from last sysread
354              
355 8         100 my @buf = split(/\015?\012/, $buf, -1); ## break into lines
356              
357 8         28 $partial = pop @buf;
358              
359 8         20 push(@{${*$cmd}{'net_cmd_lines'}}, map {"$_\n"} @buf);
  8         17  
  8         41  
  10         64  
360              
361             }
362             else {
363 0         0 $cmd->_set_status_timeout;
364 0         0 return;
365             }
366             }
367              
368 8         21 ${*$cmd}{'net_cmd_partial'} = $partial;
  8         21  
369              
370 8 50       26 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         16 shift @{${*$cmd}{'net_cmd_lines'}};
  8         17  
  8         30  
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 187 unless $_[1] =~ s/^(\d\d\d)(.?)//o;
391 26         170 ($1, $2 eq "-");
392             }
393              
394              
395             sub response {
396 21     21 1 46 my $cmd = shift;
397 21         62 my ($code, $more) = (undef) x 2;
398              
399 21         291 $cmd->set_status($cmd->DEF_REPLY_CODE, undef); # initialize the response
400              
401 21         26 while (1) {
402 26         286 my $str = $cmd->getline();
403              
404 26 50       15142 return CMD_ERROR
405             unless defined($str);
406              
407 26 50       74 $cmd->debug_print(0, $str)
408             if ($cmd->debug);
409              
410 26         107 ($code, $more) = $cmd->parse_response($str);
411 26 50       64 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         32 ${*$cmd}{'net_cmd_code'} = $code;
  26         55  
419              
420 26         38 push(@{${*$cmd}{'net_cmd_resp'}}, $str);
  26         30  
  26         66  
421              
422 26 100       66 last unless ($more);
423             }
424              
425 21 50       37 return unless defined $code;
426 21         137 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 2139 my $cmd = shift;
459 22 50 66     112 my $arr = @_ == 1 && ref($_[0]) ? $_[0] : \@_;
460 22         64 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     68 utf8::downgrade($line, 1) if $] < 5.010001 && $] != 5.008009;
473              
474 22 50       50 return 0
475             if $cmd->_is_closed;
476              
477 22         26 my $last_ch = ${*$cmd}{'net_cmd_last_ch'};
  22         65  
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       57 return 1 unless length $line;
483              
484 21 50       45 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         23 $line =~ tr/\r\n/\015\012/ unless "\r" eq "\015";
491              
492 21         27 my $first_ch = '';
493              
494 21 100       57 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       40 $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       65 $first_ch = "." if $line =~ /^\./;
503             }
504              
505 21         113 $line =~ s/\015?\012(\.?)/\015\012$1$1/sg;
506              
507 21         46 substr($line, 0, 0) = $first_ch;
508              
509 21         33 ${*$cmd}{'net_cmd_last_ch'} = substr($line, -1, 1);
  21         47  
510              
511 21 50       46 $cmd->_syswrite_with_timeout($line)
512             or return;
513              
514 21         98 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 4147 my $cmd = shift;
543              
544 16 50       38 return 0
545             if $cmd->_is_closed;
546              
547 16         20 my $ch = ${*$cmd}{'net_cmd_last_ch'};
  16         49  
548 16         23 my $tosend;
549              
550 16 50       59 if (!defined $ch) {
    100          
551 0         0 return 1;
552             }
553             elsif ($ch ne "\012") {
554 6         12 $tosend = "\015\012";
555             }
556              
557 16         30 $tosend .= ".\015\012";
558              
559 16 50       37 $cmd->debug_print(1, ".\n")
560             if ($cmd->debug);
561              
562 16 50       39 $cmd->_syswrite_with_timeout($tosend)
563             or return 0;
564              
565 16         30 delete ${*$cmd}{'net_cmd_last_ch'};
  16         54  
566              
567 16         59 $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__