File Coverage

blib/lib/SNMP/Extension/PassPersist.pm
Criterion Covered Total %
statement 179 278 64.3
branch 32 68 47.0
condition 14 38 36.8
subroutine 36 47 76.6
pod 5 14 35.7
total 266 445 59.7


line stmt bran cond sub pod time code
1             package SNMP::Extension::PassPersist;
2 6     6   59664 use strict;
  6         14  
  6         233  
3 6     6   34 use warnings;
  6         118  
  6         222  
4              
5 6     6   5181 use parent qw< Class::Accessor >;
  6         1791  
  6         42  
6              
7 6     6   18849 use Carp;
  6         12  
  6         626  
8 6     6   12901 use Getopt::Long;
  6         92872  
  6         43  
9 6     6   1130 use File::Basename;
  6         12  
  6         528  
10 6     6   5455 use IO::Handle;
  6         39927  
  6         326  
11 6     6   5980 use IO::Pipe;
  6         7799  
  6         187  
12 6     6   7014 use IO::Select;
  6         10090  
  6         351  
13 6     6   5309 use List::MoreUtils qw< any >;
  6         7361  
  6         504  
14 6     6   5880 use Storable qw< nfreeze thaw >;
  6         20488  
  6         467  
15 6     6   2219 use Sys::Syslog;
  6         39  
  6         153  
16              
17              
18             {
19 6     6   32 no strict "vars";
  6         10  
  6         316  
20             $VERSION = '0.07';
21             }
22              
23 6 50   6   391 use constant HAVE_SORT_KEY_OID
  6         4797  
  6         55065  
  6         353  
24 6     6   30 => eval "use Sort::Key::OID 0.04 qw; 1" ? 1 : 0;
  6         12  
25              
26              
27             # early initialisations --------------------------------------------------------
28             my @attributes = qw<
29             backend_collect
30             backend_fork
31             backend_init
32             backend_pipe
33             heap
34             idle_count
35             input
36             oid_tree
37             sorted_entries
38             output
39             refresh
40             dispatch
41             >;
42              
43             __PACKAGE__->mk_accessors(@attributes);
44              
45              
46             # constants --------------------------------------------------------------------
47 6     6   53 use constant SNMP_NONE => "NONE";
  6         16  
  6         270  
48 6     6   35 use constant SNMP_PING => "PING";
  6         12  
  6         257  
49 6     6   30 use constant SNMP_PONG => "PONG";
  6         12  
  6         292  
50 6     6   32 use constant SNMP_GET => "get";
  6         12  
  6         240  
51 6     6   105 use constant SNMP_GETNEXT => "getnext";
  6         13  
  6         245  
52 6     6   48 use constant SNMP_SET => "set";
  6         12  
  6         358  
53 6     6   29 use constant SNMP_NOT_WRITABLE => "not-writable";
  6         10  
  6         292  
54 6     6   30 use constant SNMP_WRONG_TYPE => "wrong-type";
  6         9  
  6         249  
55 6     6   56 use constant SNMP_WRONG_LENGTH => "wrong-length";
  6         16  
  6         282  
56 6     6   31 use constant SNMP_WRONG_VALUE => "wrong-value";
  6         12  
  6         279  
57 6     6   32 use constant SNMP_INCONSISTENT_VALUE => "inconsistent-value";
  6         10  
  6         6740  
58              
59              
60             # global variables -------------------------------------------------------------
61             my %snmp_ext_type = (
62             counter => "counter",
63             counter64 => "counter64",
64             gauge => "gauge",
65             integer => "integer",
66             ipaddr => "ipaddress",
67             ipaddress => "ipaddress",
68             netaddr => "ipaddress",
69             objectid => "objectid",
70             octetstr => "string",
71             # opaque => "opaque",
72             string => "string",
73             timeticks => "timeticks",
74             );
75              
76              
77              
78             #
79             # new()
80             # ---
81             sub new {
82 17     17 1 10996 my ($class, @args) = @_;
83 17         27 my %attrs;
84 17         31 my $ref = ref $args[0];
85              
86             # see how arguments were passed
87 17 100 100     87 if ($ref and $ref eq "HASH") {
88 2         5 %attrs = %{$args[0]};
  2         6  
89             }
90             else {
91 15 100       641 croak "error: Don't know how to handle \L$ref reference" if $ref;
92 12 100       336 croak "error: Odd number of arguments" if @args % 2 == 1;
93 10         31 %attrs = @args;
94             }
95              
96             # filter out unknown attributes
97 12         16 my %known_attr;
98 12         117 @known_attr{@attributes} = (1) x @attributes;
99 12   66     68 !$known_attr{$_} && delete $attrs{$_} for keys %attrs;
100              
101             # check that code attributes are coderefs
102 12         28 for my $code_attr (qw) {
103 22 100 100     353 croak "error: Attribute $code_attr must be a code reference"
104             if defined $attrs{$code_attr} and ref $attrs{$code_attr} ne "CODE";
105             }
106              
107             # default values
108             %attrs = (
109 1     1   9 backend_collect => sub {},
110             backend_fork => 0,
111 3     3   30 backend_init => sub {},
112 10         206 heap => {},
113             input => \*STDIN,
114             output => \*STDOUT,
115             oid_tree => {},
116             sorted_entries => [],
117             idle_count => 5,
118             refresh => 10,
119             dispatch => {
120             lc(SNMP_PING) => { nargs => 0, code => \&ping },
121             lc(SNMP_GET) => { nargs => 1, code => \&get_oid },
122             lc(SNMP_GETNEXT) => { nargs => 1, code => \&getnext_oid },
123             lc(SNMP_SET) => { nargs => 2, code => \&set_oid },
124             },
125             %attrs,
126             );
127              
128             # create the object with Class::Accessor
129 10         97 my $self = $class->SUPER::new(\%attrs);
130              
131 10         182 return $self
132             }
133              
134              
135             #
136             # run()
137             # ---
138             sub run {
139 4     4 1 10685 my ($self) = @_;
140              
141             # process command-line arguments
142 4         26 Getopt::Long::Configure(qw);
143 4 50       213 GetOptions(\my %options, qw)
144             or croak "fatal: An error occured while processing runtime arguments";
145              
146 4   33     1453 my $name = $::COMMAND || basename($0);
147 4         24 openlog($name, "ndelay,pid", "local0");
148              
149 4         11 my ($mode_pass, $mode_passpersist);
150 4         22 my $backend_fork = $self->backend_fork;
151              
152             # determine the run mode
153 4 100   10   119 if (any { defined $options{$_} } "get", "getnext", "set") {
  10         27  
154 1         2 $mode_pass = 1;
155 1         2 $mode_passpersist = 0;
156             }
157             else {
158 3         6 $mode_pass = 0;
159 3         5 $mode_passpersist = 1;
160             }
161              
162             # execute the init and collect callback once, except in the case
163             # where the backend run in a separate process
164 4 50 66     33 unless ($mode_passpersist and $backend_fork) {
165             # initialise the backend
166 4 100       7 eval { $self->backend_init->($self); 1 }
  4         17  
  3         29  
167             or croak "fatal: An error occurred while executing the backend "
168             ."initialisation callback: $@";
169              
170             # collect the information
171 3 100       7 eval { $self->backend_collect->($self); 1 }
  3         14  
  2         1738  
172             or croak "fatal: An error occurred while executing the backend "
173             ."collecting callback: $@";
174             }
175              
176             # Net-SNMP "pass" mode
177 2 100       7 if ($mode_pass) {
178 1         2 for my $op (qw) {
179 3 100       32 if ($options{$op}) {
180 1         3 my @args = split /,/, $options{$op};
181 1         3 my $coderef = $self->dispatch->{$op}{code};
182 1         17 my @result = $coderef->($self, @args);
183 1         3 $self->output->print(join "\n", @result, "");
184             }
185             }
186             }
187             # Net-SNMP "pass_persist" mode
188             else {
189 1         2 my $needed = 1;
190 1         5 my $delay = $self->refresh;
191 1         14 my $counter = $self->idle_count;
192 1         10 my ($pipe, $child);
193              
194             # if the backend is to be run in a separate process,
195             # create a pipe and fork
196 1 50       4 if ($backend_fork) {
197 0         0 $pipe = IO::Pipe->new;
198 0         0 $self->backend_pipe($pipe);
199              
200 0         0 $child = fork;
201 0         0 my $msg = "fatal: can't fork: $!";
202 0 0 0     0 syslog err => $msg and die $msg
203             unless defined $child;
204              
205             # child setup is handled in run_backend_loop()
206 0 0       0 goto &run_backend_loop if $child == 0;
207              
208             # parent setup
209 0         0 $pipe->reader; # declare this end of the pipe as the reader
210 0         0 $pipe->autoflush(1);
211             }
212              
213 1         10 my $io = IO::Select->new;
214 1         25 $io->add($self->input);
215 1         57 $self->output->autoflush(1);
216              
217 1 50       71 if ($backend_fork) {
218 0         0 $io->add($pipe);
219 0     0   0 $SIG{CHLD} = sub { $io->remove($pipe); waitpid($child, 0); };
  0         0  
  0         0  
220             }
221              
222              
223             # main loop
224 1   66     9 while ($needed and $counter > 0) {
225 2         9 my $start_time = time;
226              
227             # wait for some input data
228 2         9 my @ready = $io->can_read($delay);
229              
230 2         64 for my $fh (@ready) {
231             # handle input data from netsnmpd
232 2 50       7 if ($fh == $self->input) {
233 2 100       54 if (my $cmd = <$fh>) {
234 1         7 $self->process_cmd(lc($cmd), $fh);
235 1         24 $counter = $self->idle_count;
236             }
237             else {
238 1         3 $needed = 0
239             }
240             }
241              
242             # handle input data from the backend process
243 2 50 33     18 if ($backend_fork and $fh == $pipe) {
244 6     6   2073 use bytes;
  6         27  
  6         44  
245              
246             # read a first chunk from the child
247 0         0 $fh->sysread(my $buffer, 20);
248 0 0       0 last unless length $buffer;
249              
250             # extract the header
251 0         0 my $headline= substr($buffer, 0, index($buffer, "\n")+1, "");
252 0         0 chomp $headline;
253 0         0 my %header = map { split /=/, $_, 2 } split /\|/, $headline;
  0         0  
254              
255             # read the date in Storable format
256 0         0 my $length = $header{length};
257 0         0 $fh->sysread(my $freezed, $length);
258 0         0 $freezed = $buffer.$freezed;
259              
260             # decode the freezed data
261 0         0 my $struct = thaw($freezed);
262 0         0 $self->add_oid_tree($struct);
263             }
264             }
265              
266 2         5 $delay = $delay - (time() - $start_time);
267              
268 2 50       10 if ($delay <= 0) {
269 0 0       0 if (not $backend_fork) {
270             # collect information when the timeout has expired
271 0 0       0 eval { $self->backend_collect->($self); 1 }
  0         0  
  0         0  
272             or croak "fatal: An error occurred while executing "
273             ."the backend collecting callback: $@";
274             }
275              
276             # reset delay
277 0         0 $delay = $self->refresh;
278 0         0 $counter--;
279             }
280             }
281              
282 1 50       15 if ($backend_fork) {
283 0         0 kill TERM => $child;
284 0         0 sleep 1;
285 0         0 kill KILL => $child;
286 0         0 waitpid($child, 0);
287             }
288             }
289             }
290              
291              
292             #
293             # run_backend_loop()
294             # ----------------
295             sub run_backend_loop {
296 0     0 0 0 my ($self) = @_;
297              
298 0         0 my $pipe = $self->backend_pipe;
299 0         0 $pipe->writer; # declare this end of the pipe as the writer
300 0         0 $pipe->autoflush(1);
301              
302             # execute the initialisation callback
303 0 0       0 eval { $self->backend_init->($self); 1 }
  0         0  
  0         0  
304             or croak "fatal: An error occurred while executing the backend "
305             ."initialisation callback: $@";
306              
307 0         0 while (1) {
308 0         0 my $start_time = time;
309              
310             # execute the collect callback
311 0 0       0 eval { $self->backend_collect->($self); 1 }
  0         0  
  0         0  
312             or croak "fatal: An error occurred while executing the backend "
313             ."collecting callback: $@";
314              
315             # freeze the OID tree using Storable
316 6     6   3236 use bytes;
  6         15  
  6         26  
317 0         0 my $freezed = nfreeze($self->oid_tree);
318 0         0 my $length = length $freezed;
319 0         0 my $output = "length=$length\n$freezed";
320              
321             # send it to the parent via the pipe
322 0         0 $pipe->syswrite($output);
323 0         0 select(undef, undef, undef, .000_001);
324              
325             # wait before next execution
326 0         0 my $delay = $self->refresh() - (time() - $start_time);
327 0         0 sleep $delay;
328             }
329             }
330              
331              
332             #
333             # add_oid_entry()
334             # -------------
335             sub add_oid_entry {
336 2     2 1 612 my ($self, $oid, $type, $value) = @_;
337              
338 2 50       9 croak "error: Unknown type '$type'" unless exists $snmp_ext_type{$type};
339 2         11 $self->oid_tree->{$oid} = [$type => $value];
340              
341             # need to resort
342 2         28 @{$self->sorted_entries} = ();
  2         9  
343              
344 2         19 return 1
345             }
346              
347              
348             #
349             # add_oid_tree()
350             # ------------
351             sub add_oid_tree {
352 0     0 1 0 my ($self, $new_tree) = @_;
353              
354             croak "error: Unknown type"
355 0 0   0   0 if any { !$snmp_ext_type{$_->[0]} } values %$new_tree;
  0         0  
356 0         0 my $oid_tree = $self->oid_tree;
357 0         0 @{$oid_tree}{keys %$new_tree} = values %$new_tree;
  0         0  
358              
359             # need to resort
360 0         0 @{$self->sorted_entries} = ();
  0         0  
361              
362 0         0 return 1
363             }
364              
365              
366             #
367             # dump_oid_tree()
368             # -------------
369             sub dump_oid_tree {
370 0     0 1 0 my ($self) = @_;
371              
372 0         0 my $oid_tree = $self->oid_tree;
373 0         0 my $output = $self->output;
374              
375 0         0 for my $oid (sort by_oid keys %$oid_tree) {
376 0         0 my ($type, $value) = @{ $oid_tree->{$oid} };
  0         0  
377 0         0 $output->print("$oid ($type) = $value\n");
378             }
379             }
380              
381              
382             #
383             # ping()
384             # ----
385             sub ping {
386 0     0 0 0 return SNMP_PONG
387             }
388              
389              
390             #
391             # get_oid()
392             # -------
393             sub get_oid {
394 2     2 0 6 my ($self, $req_oid) = @_;
395              
396 2         7 my $oid_tree = $self->oid_tree;
397 2         17 my @result = ();
398              
399 2 50       15 if ($oid_tree->{$req_oid}) {
400 2         3 my ($type, $value) = @{ $oid_tree->{$req_oid} };
  2         6  
401 2         6 @result = ($req_oid, $type, $value);
402             }
403             else {
404 0         0 @result = (SNMP_NONE)
405             }
406              
407             return @result
408 2         8 }
409              
410              
411             #
412             # getnext_oid()
413             # -----------
414             sub getnext_oid {
415 0     0 0 0 my ($self, $req_oid) = @_;
416              
417 0   0     0 my $next_oid = $self->fetch_next_entry($req_oid)
418             || $self->fetch_first_entry();
419              
420 0         0 return $self->get_oid($next_oid)
421             }
422              
423              
424             #
425             # set_oid()
426             # -------
427             sub set_oid {
428 0     0 0 0 my ($self, $req_oid, $value) = @_;
429 0         0 return SNMP_NOT_WRITABLE
430             }
431              
432              
433             #
434             # process_cmd()
435             # -----------
436             # Process and dispatch Net-SNMP commands when in pass_persist mode.
437             #
438             sub process_cmd {
439 1     1 0 3 my ($self, $cmd, $fh) = @_;
440 1         2 my @result = ();
441              
442 1         4 chomp $cmd;
443 1         4 my $dispatch = $self->dispatch;
444              
445 1 50       12 if (exists $dispatch->{$cmd}) {
446              
447             # read the command arguments
448 1         2 my @args = ();
449 1         4 my $n = $dispatch->{$cmd}{nargs};
450              
451 1         14 while ($n-- > 0) {
452 1         4 chomp(my $arg = <$fh>);
453 1         5 push @args, $arg;
454             }
455              
456             # call the command handler
457 1         3 my $coderef = $dispatch->{$cmd}{code};
458 1         8 @result = $coderef->($self, @args);
459             }
460             else {
461 0         0 @result = SNMP_NONE;
462             }
463              
464             # output the result
465 1         4 $self->output->print(join "\n", @result, "");
466             }
467              
468              
469             #
470             # fetch_next_entry()
471             # ----------------
472             sub fetch_next_entry {
473 0     0 0   my ($self, $req_oid) = @_;
474              
475 0           my $entries = $self->sorted_entries;
476              
477 0 0         if (!@$entries) {
478 0           @$entries = HAVE_SORT_KEY_OID
479             ? oidsort(keys %{ $self->oid_tree })
480 0           : sort by_oid keys %{ $self->oid_tree };
481             }
482              
483             # find the index of the current entry
484 0           my $curr_entry_idx = -1;
485              
486 0           for my $i (0..$#$entries) {
487             # exact match of the requested entry
488 0 0 0       $curr_entry_idx = $i and last if $entries->[$i] eq $req_oid;
489              
490             # prefix match of the requested entry
491 0 0 0       $curr_entry_idx = $i - 1 and last
      0        
492             if $curr_entry_idx == -1 and index($entries->[$i], $req_oid) >= 0;
493             }
494              
495             # get the next entry if it exists, otherwise none
496 0   0       my $next_entry_oid = $entries->[$curr_entry_idx + 1] || SNMP_NONE;
497              
498 0           return $next_entry_oid
499             }
500              
501              
502             #
503             # fetch_first_entry()
504             # -----------------
505             sub fetch_first_entry {
506 0     0 0   my ($self) = @_;
507              
508 0           my $entries = $self->sorted_entries;
509              
510 0 0         if (!@$entries) {
511 0           @$entries = HAVE_SORT_KEY_OID
512             ? oidsort(keys %{ $self->oid_tree })
513 0           : sort by_oid keys %{ $self->oid_tree };
514             }
515 0           my $first_entry_oid = $entries->[0];
516              
517 0           return $first_entry_oid
518             }
519              
520              
521             #
522             # by_oid()
523             # ------
524             # sort() sub-function, for sorting by OID
525             #
526             sub by_oid ($$) {
527 0     0 0   my (undef, @a) = split /\./, $_[0];
528 0           my (undef, @b) = split /\./, $_[1];
529 0           my $v = 0;
530 0   0       $v ||= $a[$_] <=> $b[$_] for 0 .. $#a;
531 0           return $v
532             }
533              
534              
535             __PACKAGE__
536              
537             __END__