File Coverage

blib/lib/Monitoring/Livestatus.pm
Criterion Covered Total %
statement 133 583 22.8
branch 50 340 14.7
condition 10 105 9.5
subroutine 17 40 42.5
pod 17 17 100.0
total 227 1085 20.9


line stmt bran cond sub pod time code
1             package Monitoring::Livestatus;
2              
3 4     4   919046 use warnings;
  4         13  
  4         382  
4 4     4   31 use strict;
  4         9  
  4         135  
5 4     4   26 use Carp qw/carp confess/;
  4         10  
  4         336  
6 4     4   4904 use Cpanel::JSON::XS ();
  4         27120  
  4         191  
7 4     4   1477 use Data::Dumper qw/Dumper/;
  4         22737  
  4         622  
8 4     4   2335 use IO::Select ();
  4         8543  
  4         149  
9 4     4   1346 use Storable qw/dclone/;
  4         11892  
  4         405  
10              
11 4     4   1823 use Monitoring::Livestatus::INET ();
  4         17  
  4         147  
12 4     4   2729 use Monitoring::Livestatus::UNIX ();
  4         14  
  4         46134  
13              
14             our $VERSION = '0.86';
15              
16              
17             # list of allowed options
18             my $allowed_options = {
19             'addpeer' => 1,
20             'backend' => 1,
21             'columns' => 1,
22             'deepcopy' => 1,
23             'header' => 1,
24             'limit' => 1,
25             'limit_start' => 1,
26             'limit_length' => 1,
27             'rename' => 1,
28             'slice' => 1,
29             'sum' => 1,
30             'callbacks' => 1,
31             'wrapped_json' => 1,
32             'sort' => 1,
33             'offset' => 1,
34             };
35              
36             =head1 NAME
37              
38             Monitoring::Livestatus - Perl API for check_mk livestatus to access runtime
39             data from Nagios and Icinga
40              
41             =head1 SYNOPSIS
42              
43             use Monitoring::Livestatus;
44             my $ml = Monitoring::Livestatus->new(
45             socket => '/var/lib/livestatus/livestatus.sock'
46             );
47             my $hosts = $ml->selectall_arrayref("GET hosts");
48              
49             =head1 DESCRIPTION
50              
51             This module connects via socket/tcp to the livestatus addon for Naemon, Nagios,
52             Icinga and Shinken. You first have to install and activate the livestatus addon
53             in your monitoring installation.
54              
55             =head1 CONSTRUCTOR
56              
57             =head2 new ( [ARGS] )
58              
59             Creates an C object. C takes at least the
60             socketpath. Arguments are in key-value pairs.
61              
62             =over 4
63              
64             =item socket
65              
66             path to the UNIX socket of check_mk livestatus
67              
68             =item server
69              
70             server address when using a TCP connection
71              
72             =item peer
73              
74             alternative way to set socket or server, if value contains ':' server is used,
75             else socket
76              
77             =item name
78              
79             human readable name for this connection, defaults to the the socket/server
80             address
81              
82             =item verbose
83              
84             verbose mode
85              
86             =item line_separator
87              
88             ascii code of the line separator, defaults to 10, (newline)
89              
90             =item column_separator
91              
92             ascii code of the column separator, defaults to 0 (null byte)
93              
94             =item list_separator
95              
96             ascii code of the list separator, defaults to 44 (comma)
97              
98             =item host_service_separator
99              
100             ascii code of the host/service separator, defaults to 124 (pipe)
101              
102             =item keepalive
103              
104             enable keepalive. Default is off
105              
106             =item errors_are_fatal
107              
108             errors will die with an error message. Default: on
109              
110             =item warnings
111              
112             show warnings
113             currently only querys without Columns: Header will result in a warning
114              
115             =item timeout
116              
117             set a general timeout. Used for connect and querys, no default
118              
119             =item query_timeout
120              
121             set a query timeout. Used for retrieving querys, Default 60sec
122              
123             =item connect_timeout
124              
125             set a connect timeout. Used for initial connections, default 5sec
126              
127             =back
128              
129             If the constructor is only passed a single argument, it is assumed to
130             be a the C specification. Use either socker OR server.
131              
132             =cut
133              
134             sub new {
135 24     24 1 11966 my($class,@args) = @_;
136 24 100       84 unshift(@args, 'peer') if scalar @args == 1;
137 24         91 my(%options) = @args;
138              
139 24         349 my $self = {
140             'verbose' => 0, # enable verbose output
141             'socket' => undef, # use unix sockets
142             'server' => undef, # use tcp connections
143             'peer' => undef, # use for socket / server connections
144             'name' => undef, # human readable name
145             'line_separator' => 10, # defaults to newline
146             'column_separator' => 0, # defaults to null byte
147             'list_separator' => 44, # defaults to comma
148             'host_service_separator' => 124, # defaults to pipe
149             'keepalive' => 0, # enable keepalive?
150             'errors_are_fatal' => 1, # die on errors
151             'backend' => undef, # should be keept undef, used internally
152             'timeout' => undef, # timeout for tcp connections
153             'query_timeout' => undef, # query timeout for tcp connections
154             'connect_timeout' => 30, # connect timeout for tcp connections
155             'warnings' => 1, # show warnings, for example on querys without Column: Header
156             'logger' => undef, # logger object used for statistical informations and errors / warnings
157             'deepcopy' => undef, # copy result set to avoid errors with tied structures
158             'retries_on_connection_error' => 3, # retry x times to connect
159             'retry_interval' => 1, # retry after x seconds
160             # tls options
161             'cert' => undef,
162             'key' => undef,
163             'ca_file' => undef,
164             'verify' => undef,
165             'verifycn_name' => undef,
166             };
167              
168 24         134 my %old_key = (
169             line_seperator => 'line_separator',
170             column_seperator => 'column_separator',
171             list_seperator => 'list_separator',
172             host_service_seperator => 'host_service_separator',
173             );
174              
175             # previous versions had spelling errors in the key name
176 24         82 for my $opt_key (keys %old_key) {
177 96 50       225 if(exists $options{$opt_key}) {
178 0         0 my $value = $options{$opt_key};
179 0         0 $options{ $old_key{$opt_key} } = $value;
180 0         0 delete $options{$opt_key};
181             }
182             }
183              
184 24         72 for my $opt_key (keys %options) {
185 93 50       180 if(exists $self->{$opt_key}) {
186 93         175 $self->{$opt_key} = $options{$opt_key};
187             }
188             else {
189 0         0 confess("unknown option: $opt_key");
190             }
191             }
192              
193 24 50 33     86 if($self->{'verbose'} && !defined $self->{'logger'}) {
194 0         0 confess('please specify a logger object when using verbose mode');
195             }
196              
197             # setting a general timeout?
198 24 100       63 if(defined $self->{'timeout'}) {
199 2         6 $self->{'query_timeout'} = $self->{'timeout'};
200 2         5 $self->{'connect_timeout'} = $self->{'timeout'};
201             }
202              
203 24         51 bless $self, $class;
204              
205             # set our peer(s) from the options
206 24         70 my $peer = $self->_get_peer();
207              
208 24 100       92 if(!defined $self->{'backend'}) {
209 10         34 $options{'name'} = $peer->{'name'};
210 10         23 $options{'peer'} = $peer->{'peer'};
211 10 100       31 if($peer->{'type'} eq 'UNIX') {
    50          
212 8         50 $self->{'CONNECTOR'} = Monitoring::Livestatus::UNIX->new(%options);
213             }
214             elsif($peer->{'type'} eq 'INET') {
215 2         21 $self->{'CONNECTOR'} = Monitoring::Livestatus::INET->new(%options);
216             }
217 10         27 $self->{'peer'} = $peer->{'peer'};
218             }
219              
220             # set names and peer for non multi backends
221 24 100 66     120 if(defined $self->{'CONNECTOR'}->{'name'} && !defined $self->{'name'}) {
222 10         22 $self->{'name'} = $self->{'CONNECTOR'}->{'name'};
223             }
224 24 50 66     74 if(defined $self->{'CONNECTOR'}->{'peer'} && !defined $self->{'peer'}) {
225 0         0 $self->{'peer'} = $self->{'CONNECTOR'}->{'peer'};
226             }
227              
228 24         223 return $self;
229             }
230              
231              
232             ########################################
233              
234             =head1 METHODS
235              
236             =head2 do
237              
238             do($statement)
239             do($statement, %opts)
240              
241             Send a single statement without fetching the result.
242             Always returns true.
243              
244             =cut
245              
246             sub do {
247 0     0 1 0 my($self, $statement, $opt) = @_;
248 0         0 $self->_send($statement, $opt);
249 0         0 return(1);
250             }
251              
252              
253             ########################################
254              
255             =head2 selectall_arrayref
256              
257             selectall_arrayref($statement)
258             selectall_arrayref($statement, %opts)
259             selectall_arrayref($statement, %opts, $limit )
260              
261             Sends a query and returns an array reference of arrays
262              
263             my $arr_refs = $ml->selectall_arrayref("GET hosts");
264              
265             to get an array of hash references do something like
266              
267             my $hash_refs = $ml->selectall_arrayref(
268             "GET hosts", { Slice => {} }
269             );
270              
271             to get an array of hash references from the first 2 returned rows only
272              
273             my $hash_refs = $ml->selectall_arrayref(
274             "GET hosts", { Slice => {} }, 2
275             );
276              
277             you may use limit to limit the result to this number of rows
278              
279             column aliases can be defined with a rename hash
280              
281             my $hash_refs = $ml->selectall_arrayref(
282             "GET hosts", {
283             Slice => {},
284             rename => {
285             'name' => 'host_name'
286             }
287             }
288             );
289              
290             =cut
291              
292             sub selectall_arrayref {
293 0     0 1 0 my($self, $statement, $opt, $limit, $result) = @_;
294 0 0       0 $limit = 0 unless defined $limit;
295              
296             # make opt hash keys lowercase
297 0 0       0 $opt = &_lowercase_and_verify_options($self, $opt) unless $result;
298              
299 0 0 0     0 $self->_log_statement($statement, $opt, $limit) if !$result && $self->{'verbose'};
300              
301 0 0       0 if(!defined $result) {
302 0         0 $result = &_send($self, $statement, $opt);
303              
304 0 0       0 if(!defined $result) {
305 0 0       0 return unless $self->{'errors_are_fatal'};
306 0         0 confess("got undef result for: $statement");
307             }
308             }
309              
310             # trim result set down to excepted row count
311 0 0 0     0 if(!$opt->{'offset'} && defined $limit && $limit >= 1) {
      0        
312 0 0       0 if(scalar @{$result->{'result'}} > $limit) {
  0         0  
313 0         0 @{$result->{'result'}} = @{$result->{'result'}}[0..$limit-1];
  0         0  
  0         0  
314             }
315             }
316              
317 0 0       0 if($opt->{'slice'}) {
318 0         0 my $callbacks = $opt->{'callbacks'};
319             # make an array of hashes, inplace to safe memory
320 0         0 my $keys = $result->{'keys'};
321             # renamed columns
322 0 0       0 if($opt->{'rename'}) {
323 0         0 $keys = dclone($result->{'keys'});
324 0         0 my $keysize = scalar @{$keys};
  0         0  
325 0         0 for(my $x=0; $x<$keysize;$x++) {
326 0         0 my $old = $keys->[$x];
327 0 0       0 if($opt->{'rename'}->{$old}) {
328 0         0 $keys->[$x] = $opt->{'rename'}->{$old};
329             }
330             }
331             }
332 0         0 $result = $result->{'result'};
333 0         0 my $rnum = scalar @{$result};
  0         0  
334 0         0 for(my $x=0;$x<$rnum;$x++) {
335             # sort array into hash slices
336 0         0 my %hash;
337 0         0 @hash{@{$keys}} = @{$result->[$x]};
  0         0  
  0         0  
338             # add callbacks
339 0 0       0 if($callbacks) {
340 0         0 for my $key (keys %{$callbacks}) {
  0         0  
341 0         0 $hash{$key} = $callbacks->{$key}->(\%hash);
342             }
343             }
344 0         0 $result->[$x] = \%hash;
345             }
346 0         0 return($result);
347             }
348              
349 0 0       0 if(exists $opt->{'callbacks'}) {
350 0         0 for my $res (@{$result->{'result'}}) {
  0         0  
351             # add callbacks
352 0 0       0 if(exists $opt->{'callbacks'}) {
353 0         0 for my $key (keys %{$opt->{'callbacks'}}) {
  0         0  
354 0         0 push @{$res}, $opt->{'callbacks'}->{$key}->($res);
  0         0  
355             }
356             }
357             }
358              
359 0         0 for my $key (keys %{$opt->{'callbacks'}}) {
  0         0  
360 0         0 push @{$result->{'keys'}}, $key;
  0         0  
361             }
362             }
363 0         0 return($result->{'result'});
364             }
365              
366              
367             ########################################
368              
369             =head2 selectall_hashref
370              
371             selectall_hashref($statement, $key_field)
372             selectall_hashref($statement, $key_field, %opts)
373              
374             Sends a query and returns a hashref with the given key
375              
376             my $hashrefs = $ml->selectall_hashref("GET hosts", "name");
377              
378             =cut
379              
380             sub selectall_hashref {
381 0     0 1 0 my($self, $statement, $key_field, $opt) = @_;
382              
383 0         0 $opt = &_lowercase_and_verify_options($self, $opt);
384              
385 0         0 $opt->{'slice'} = 1;
386              
387 0 0       0 confess('key is required for selectall_hashref') if !defined $key_field;
388              
389 0         0 my $result = $self->selectall_arrayref($statement, $opt);
390              
391 0         0 my %indexed;
392 0         0 for my $row (@{$result}) {
  0         0  
393 0 0       0 if($key_field eq '$peername') {
    0          
394 0         0 $indexed{$self->peer_name} = $row;
395             }
396             elsif(!defined $row->{$key_field}) {
397 0         0 my %possible_keys = keys %{$row};
  0         0  
398 0         0 confess("key $key_field not found in result set, possible keys are: ".join(', ', sort keys %possible_keys));
399             } else {
400 0         0 $indexed{$row->{$key_field}} = $row;
401             }
402             }
403 0         0 return(\%indexed);
404             }
405              
406              
407             ########################################
408              
409             =head2 selectcol_arrayref
410              
411             selectcol_arrayref($statement)
412             selectcol_arrayref($statement, %opt )
413              
414             Sends a query an returns an arrayref for the first columns
415              
416             my $array_ref = $ml->selectcol_arrayref("GET hosts\nColumns: name");
417              
418             $VAR1 = [
419             'localhost',
420             'gateway',
421             ];
422              
423             returns an empty array if nothing was found
424              
425             to get a different column use this
426              
427             my $array_ref = $ml->selectcol_arrayref(
428             "GET hosts\nColumns: name contacts",
429             { Columns => [2] }
430             );
431              
432             you can link 2 columns in a hash result set
433              
434             my %hash = @{
435             $ml->selectcol_arrayref(
436             "GET hosts\nColumns: name contacts",
437             { Columns => [1,2] }
438             )
439             };
440              
441             produces a hash with host the contact assosiation
442              
443             $VAR1 = {
444             'localhost' => 'user1',
445             'gateway' => 'user2'
446             };
447              
448             =cut
449              
450             sub selectcol_arrayref {
451 0     0 1 0 my($self, $statement, $opt) = @_;
452              
453             # make opt hash keys lowercase
454 0         0 $opt = &_lowercase_and_verify_options($self, $opt);
455              
456             # if now colums are set, use just the first one
457 0 0 0     0 if(!defined $opt->{'columns'} || ref $opt->{'columns'} ne 'ARRAY') {
458 0         0 @{$opt->{'columns'}} = qw{1};
  0         0  
459             }
460              
461 0         0 my $result = $self->selectall_arrayref($statement);
462              
463 0         0 my @column;
464 0         0 for my $row (@{$result}) {
  0         0  
465 0         0 for my $nr (@{$opt->{'columns'}}) {
  0         0  
466 0         0 push @column, $row->[$nr-1];
467             }
468             }
469 0         0 return(\@column);
470             }
471              
472              
473             ########################################
474              
475             =head2 selectrow_array
476              
477             selectrow_array($statement)
478             selectrow_array($statement, %opts)
479              
480             Sends a query and returns an array for the first row
481              
482             my @array = $ml->selectrow_array("GET hosts");
483              
484             returns undef if nothing was found
485              
486             =cut
487             sub selectrow_array {
488 0     0 1 0 my($self, $statement, $opt) = @_;
489              
490             # make opt hash keys lowercase
491 0         0 $opt = &_lowercase_and_verify_options($self, $opt);
492              
493 0         0 my @result = @{$self->selectall_arrayref($statement, $opt, 1)};
  0         0  
494 0 0       0 return @{$result[0]} if scalar @result > 0;
  0         0  
495 0         0 return;
496             }
497              
498              
499             ########################################
500              
501             =head2 selectrow_arrayref
502              
503             selectrow_arrayref($statement)
504             selectrow_arrayref($statement, %opts)
505              
506             Sends a query and returns an array reference for the first row
507              
508             my $arrayref = $ml->selectrow_arrayref("GET hosts");
509              
510             returns undef if nothing was found
511              
512             =cut
513             sub selectrow_arrayref {
514 0     0 1 0 my($self, $statement, $opt) = @_;
515              
516             # make opt hash keys lowercase
517 0         0 $opt = &_lowercase_and_verify_options($self, $opt);
518              
519 0         0 my $result = $self->selectall_arrayref($statement, $opt, 1);
520 0 0       0 return if !defined $result;
521 0 0       0 return $result->[0] if scalar @{$result} > 0;
  0         0  
522 0         0 return;
523             }
524              
525              
526             ########################################
527              
528             =head2 selectrow_hashref
529              
530             selectrow_hashref($statement)
531             selectrow_hashref($statement, %opt)
532              
533             Sends a query and returns a hash reference for the first row
534              
535             my $hashref = $ml->selectrow_hashref("GET hosts");
536              
537             returns undef if nothing was found
538              
539             =cut
540             sub selectrow_hashref {
541 0     0 1 0 my($self, $statement, $opt) = @_;
542              
543             # make opt hash keys lowercase
544 0         0 $opt = &_lowercase_and_verify_options($self, $opt);
545 0         0 $opt->{slice} = 1;
546              
547 0         0 my $result = $self->selectall_arrayref($statement, $opt, 1);
548 0 0       0 return if !defined $result;
549 0 0       0 return $result->[0] if scalar @{$result} > 0;
  0         0  
550 0         0 return;
551             }
552              
553              
554             ########################################
555              
556             =head2 selectscalar_value
557              
558             selectscalar_value($statement)
559             selectscalar_value($statement, %opt)
560              
561             Sends a query and returns a single scalar
562              
563             my $count = $ml->selectscalar_value("GET hosts\nStats: state = 0");
564              
565             returns undef if nothing was found
566              
567             =cut
568             sub selectscalar_value {
569 0     0 1 0 my($self, $statement, $opt) = @_;
570              
571             # make opt hash keys lowercase
572 0         0 $opt = &_lowercase_and_verify_options($self, $opt);
573              
574 0         0 my $row = $self->selectrow_arrayref($statement);
575 0 0       0 return if !defined $row;
576 0 0       0 return $row->[0] if scalar @{$row} > 0;
  0         0  
577 0         0 return;
578             }
579              
580             ########################################
581              
582             =head2 errors_are_fatal
583              
584             errors_are_fatal()
585             errors_are_fatal($value)
586              
587             Enable or disable fatal errors. When enabled the module will confess on any error.
588              
589             returns the current setting if called without new value
590              
591             =cut
592             sub errors_are_fatal {
593 0     0 1 0 my($self, $value) = @_;
594 0         0 my $old = $self->{'errors_are_fatal'};
595              
596 0         0 $self->{'errors_are_fatal'} = $value;
597 0 0       0 $self->{'CONNECTOR'}->{'errors_are_fatal'} = $value if defined $self->{'CONNECTOR'};
598              
599 0         0 return $old;
600             }
601              
602             ########################################
603              
604             =head2 warnings
605              
606             warnings()
607             warnings($value)
608              
609             Enable or disable warnings. When enabled the module will carp on warnings.
610              
611             returns the current setting if called without new value
612              
613             =cut
614             sub warnings {
615 0     0 1 0 my($self, $value) = @_;
616 0         0 my $old = $self->{'warnings'};
617              
618 0         0 $self->{'warnings'} = $value;
619 0 0       0 $self->{'CONNECTOR'}->{'warnings'} = $value if defined $self->{'CONNECTOR'};
620              
621 0         0 return $old;
622             }
623              
624              
625              
626             ########################################
627              
628             =head2 verbose
629              
630             verbose()
631             verbose($values)
632              
633             Enable or disable verbose output. When enabled the module will dump out debug output
634              
635             returns the current setting if called without new value
636              
637             =cut
638             sub verbose {
639 0     0 1 0 my($self, $value) = @_;
640 0         0 my $old = $self->{'verbose'};
641              
642 0         0 $self->{'verbose'} = $value;
643 0 0       0 $self->{'CONNECTOR'}->{'verbose'} = $value if defined $self->{'CONNECTOR'};
644              
645 0         0 return $old;
646             }
647              
648              
649             ########################################
650              
651             =head2 peer_addr
652              
653             $ml->peer_addr()
654              
655             returns the current peer address
656              
657             when using multiple backends, a list of all addresses is returned in list context
658              
659             =cut
660             sub peer_addr {
661 9     9 1 29 my($self) = @_;
662 9         52 return ''.$self->{'peer'};
663             }
664              
665              
666             ########################################
667              
668             =head2 peer_name
669              
670             $ml->peer_name()
671             $ml->peer_name($string)
672              
673             if new value is set, name is set to this value
674              
675             always returns the current peer name
676              
677             when using multiple backends, a list of all names is returned in list context
678              
679             =cut
680             sub peer_name {
681 9     9 1 4975 my($self, $value) = @_;
682              
683 9 50 33     49 if(defined $value and $value ne '') {
684 0         0 $self->{'name'} = $value;
685             }
686              
687 9         58 return ''.$self->{'name'};
688             }
689              
690              
691             ########################################
692              
693             =head2 peer_key
694              
695             $ml->peer_key()
696              
697             returns a uniq key for this peer
698              
699             =cut
700             sub peer_key {
701 0     0 1 0 my($self) = @_;
702 0         0 return $self->{'key'};
703             }
704              
705             ########################################
706             # INTERNAL SUBS
707             ########################################
708             sub _send {
709 0     0   0 my($self, $statement, $opt) = @_;
710              
711 0 0       0 confess('duplicate data') if $opt->{'data'};
712              
713 0         0 delete $self->{'meta_data'};
714              
715 0         0 my $header = '';
716 0         0 my $keys;
717              
718 0         0 $Monitoring::Livestatus::ErrorCode = 0;
719 0         0 undef $Monitoring::Livestatus::ErrorMessage;
720              
721 0 0       0 return(490, $self->_get_error(490), undef) if !defined $statement;
722 0         0 chomp($statement);
723              
724 0         0 my($status,$msg,$body);
725 0 0 0     0 if($statement =~ m/^Separators:/mx) {
    0 0        
    0 0        
    0 0        
    0 0        
    0          
    0          
    0          
    0          
726 0         0 $status = 492;
727 0         0 $msg = $self->_get_error($status);
728             }
729              
730             elsif($statement =~ m/^KeepAlive:/mx) {
731 0         0 $status = 496;
732 0         0 $msg = $self->_get_error($status);
733             }
734              
735             elsif($statement =~ m/^ResponseHeader:/mx) {
736 0         0 $status = 495;
737 0         0 $msg = $self->_get_error($status);
738             }
739              
740             elsif($statement =~ m/^ColumnHeaders:/mx) {
741 0         0 $status = 494;
742 0         0 $msg = $self->_get_error($status);
743             }
744              
745             elsif($statement =~ m/^OuputFormat:/mx) {
746 0         0 $status = 493;
747 0         0 $msg = $self->_get_error($status);
748             }
749              
750             # should be cought in mlivestatus directly
751             elsif($statement =~ m/^Limit:\ (.*)$/mx and $1 !~ m/^\d+$/mx) {
752 0         0 $status = 403;
753 0         0 $msg = $self->_get_error($status);
754             }
755             elsif($statement =~ m/^GET\ (.*)$/mx and $1 =~ m/^\s*$/mx) {
756 0         0 $status = 403;
757 0         0 $msg = $self->_get_error($status);
758             }
759              
760             elsif($statement =~ m/^Columns:\ (.*)$/mx and ($1 =~ m/,/mx or $1 =~ /^\s*$/mx)) {
761 0         0 $status = 405;
762 0         0 $msg = $self->_get_error($status);
763             }
764             elsif($statement !~ m/^GET\ /mx and $statement !~ m/^COMMAND\ /mx) {
765 0         0 $status = 401;
766 0         0 $msg = $self->_get_error($status);
767             }
768              
769             else {
770              
771             # Add Limits header
772 0 0       0 if(defined $opt->{'limit_start'}) {
773 0         0 $statement .= "\nLimit: ".($opt->{'limit_start'} + $opt->{'limit_length'});
774             }
775              
776             # for querys with column header, no seperate columns will be returned
777 0 0       0 if($statement =~ m/^Columns:\ (.*)$/mx) {
778 0         0 ($statement,$keys) = $self->_extract_keys_from_columns_header($statement);
779             }
780 0 0 0     0 if($statement =~ m/^Stats:\ (.*)$/mx or $statement =~ m/^StatsGroupBy:\ (.*)$/mx) {
781 0 0       0 my $has_columns = defined $keys ? join(",", @{$keys}) : undef;
  0         0  
782 0         0 ($statement,$keys) = extract_keys_from_stats_statement($statement);
783 0 0       0 unshift @{$keys}, $has_columns if $has_columns;
  0         0  
784             }
785              
786             # Offset header (currently naemon only)
787 0 0       0 if(defined $opt->{'offset'}) {
788 0         0 $statement .= "\nOffset: ".$opt->{'offset'};
789             }
790              
791             # Sort header (currently naemon only)
792 0 0       0 if(defined $opt->{'sort'}) {
793 0         0 for my $sort (@{$opt->{'sort'}}) {
  0         0  
794 0         0 $statement .= "\nSort: ".$sort;
795             }
796             }
797              
798             # Commands need no additional header
799 0 0       0 if($statement !~ m/^COMMAND/mx) {
800 0 0       0 if($opt->{'wrapped_json'}) {
801 0         0 $header .= "OutputFormat: wrapped_json\n";
802             } else {
803 0         0 $header .= "OutputFormat: json\n";
804             }
805 0         0 $header .= "ResponseHeader: fixed16\n";
806 0 0       0 if($self->{'keepalive'}) {
807 0         0 $header .= "KeepAlive: on\n";
808             }
809             # remove empty lines from statement
810 0         0 $statement =~ s/\n+/\n/gmx;
811             }
812              
813             # add additional headers
814 0 0 0     0 if(defined $opt->{'header'} and ref $opt->{'header'} eq 'HASH') {
815 0         0 for my $key ( keys %{$opt->{'header'}}) {
  0         0  
816 0         0 $header .= $key.': '.$opt->{'header'}->{$key}."\n";
817             }
818             }
819              
820 0         0 chomp($statement);
821 0         0 my $send = "$statement\n$header";
822 0 0       0 $self->{'logger'}->debug('> '.Dumper($send)) if $self->{'verbose'};
823 0         0 ($status,$msg,$body) = &_send_socket($self, $send);
824 0 0       0 if($self->{'verbose'}) {
825             #$self->{'logger'}->debug("got:");
826             #$self->{'logger'}->debug(Dumper(\@erg));
827 0         0 $self->{'logger'}->debug('status: '.Dumper($status));
828 0         0 $self->{'logger'}->debug('msg: '.Dumper($msg));
829 0         0 $self->{'logger'}->debug('< '.Dumper($body));
830             }
831             }
832              
833 0 0 0     0 if(!$status || $status >= 300) {
834 0 0       0 $body = '' if !defined $body;
835 0 0       0 $status = 300 if !defined $status;
836 0         0 chomp($body);
837 0         0 $Monitoring::Livestatus::ErrorCode = $status;
838 0 0 0     0 if(defined $body and $body ne '') {
839 0         0 $Monitoring::Livestatus::ErrorMessage = $body;
840             } else {
841 0         0 $Monitoring::Livestatus::ErrorMessage = $msg;
842             }
843 0 0       0 $self->{'logger'}->error($status.' - '.$Monitoring::Livestatus::ErrorMessage." in query:\n".$statement) if $self->{'verbose'};
844 0 0       0 if($self->{'errors_are_fatal'}) {
845 0         0 confess('ERROR '.$status.' - '.$Monitoring::Livestatus::ErrorMessage." in query:\n".$statement."\n");
846             }
847 0         0 return;
848             }
849              
850             # return a empty result set if nothing found
851 0 0       0 return({ keys => [], result => []}) if !defined $body;
852              
853             # body is already parsed
854 0         0 my $result;
855 0 0       0 if($status == 200) {
856 0         0 $result = $body;
857             } else {
858 0         0 my $json_decoder = Cpanel::JSON::XS->new->utf8->relaxed;
859             # fix json output
860 0         0 eval {
861 0         0 $result = $json_decoder->decode($body);
862             };
863             # fix low/high surrogate errors
864             # missing high surrogate character in surrogate pair
865             # surrogate pair expected
866 0 0       0 if($@) {
867             # replace u+D800 to u+DFFF (reserved utf-16 low/high surrogates)
868 0         0 $body =~ s/\\ud[89a-f][0-9a-f]{2}/\\ufffd/gmxio;
869 0         0 eval {
870 0         0 $result = $json_decoder->decode($body);
871             };
872             }
873 0 0       0 if($@) {
874 0         0 my $message = 'ERROR '.$@." in text: '".$body."'\" for statement: '$statement'\n";
875 0 0       0 $self->{'logger'}->error($message) if $self->{'verbose'};
876 0 0       0 if($self->{'errors_are_fatal'}) {
877 0         0 confess($message);
878             }
879 0         0 return({ keys => [], result => []});
880             }
881             }
882 0 0       0 if(!defined $result) {
883 0         0 my $message = "ERROR undef result for text: '".$body."'\" for statement: '$statement'\n";
884 0 0       0 $self->{'logger'}->error($message) if $self->{'verbose'};
885 0 0       0 if($self->{'errors_are_fatal'}) {
886 0         0 confess($message);
887             }
888 0         0 return({ keys => [], result => []});
889             }
890              
891             # for querys with column header, no separate columns will be returned
892 0 0       0 if(!defined $keys) {
893 0 0       0 $self->{'logger'}->warn('got statement without Columns: header!') if $self->{'verbose'};
894 0 0       0 if($self->{'warnings'}) {
895 0         0 carp('got statement without Columns: header! -> '.$statement);
896             }
897 0         0 $keys = shift @{$result};
  0         0  
898             }
899              
900 0         0 return(&post_processing($self, $result, $opt, $keys));
901             }
902              
903             ########################################
904              
905             =head2 post_processing
906              
907             $ml->post_processing($result, $options, $keys)
908              
909             returns postprocessed result.
910              
911             Useful when using select based io.
912              
913             =cut
914             sub post_processing {
915 0     0 1 0 my($self, $result, $opt, $keys) = @_;
916              
917 0         0 my $orig_result;
918 0 0       0 if($opt->{'wrapped_json'}) {
919 0         0 $orig_result = $result;
920 0         0 $result = delete $orig_result->{'data'};
921             }
922              
923             # add peer information?
924 0         0 my $with_peers = 0;
925 0 0 0     0 if(defined $opt->{'addpeer'} and $opt->{'addpeer'}) {
926 0         0 $with_peers = 1;
927             }
928              
929 0 0 0     0 if(defined $with_peers and $with_peers == 1) {
930 0         0 my $peer_name = $self->peer_name;
931 0         0 my $peer_addr = $self->peer_addr;
932 0         0 my $peer_key = $self->peer_key;
933              
934 0         0 unshift @{$keys}, 'peer_name';
  0         0  
935 0         0 unshift @{$keys}, 'peer_addr';
  0         0  
936 0         0 unshift @{$keys}, 'peer_key';
  0         0  
937              
938 0         0 for my $row (@{$result}) {
  0         0  
939 0         0 unshift @{$row}, $peer_name;
  0         0  
940 0         0 unshift @{$row}, $peer_addr;
  0         0  
941 0         0 unshift @{$row}, $peer_key;
  0         0  
942             }
943             }
944              
945             # set some metadata
946             $self->{'meta_data'} = {
947 0         0 'result_count' => scalar @{$result},
  0         0  
948             };
949 0 0       0 if($opt->{'wrapped_json'}) {
950 0         0 $self->{'meta_data'} = $orig_result;
951             }
952              
953 0         0 return({ keys => $keys, result => $result });
954             }
955              
956             ########################################
957             sub _open {
958 0     0   0 my($self) = @_;
959              
960             # return the current socket in keep alive mode
961 0 0 0     0 if($self->{'keepalive'} and defined $self->{'sock'} and $self->{'sock'}->connected) {
      0        
962 0 0       0 $self->{'logger'}->debug('reusing old connection') if $self->{'verbose'};
963 0         0 return($self->{'sock'});
964             }
965              
966 0         0 my $sock = $self->{'CONNECTOR'}->_open();
967              
968             # store socket for later retrieval
969 0 0       0 if($self->{'keepalive'}) {
970 0         0 $self->{'sock'} = $sock;
971             }
972              
973 0 0       0 $self->{'logger'}->debug('using new connection') if $self->{'verbose'};
974 0         0 return($sock);
975             }
976              
977             ########################################
978             sub _close {
979 0     0   0 my($self) = @_;
980 0         0 my $sock = delete $self->{'sock'};
981 0         0 return($self->{'CONNECTOR'}->_close($sock));
982             }
983              
984             ########################################
985              
986             =head1 QUERY OPTIONS
987              
988             In addition to the normal query syntax from the livestatus addon, it is
989             possible to set column aliases in various ways.
990              
991             =head2 AddPeer
992              
993             adds the peers name, addr and key to the result set:
994              
995             my $hosts = $ml->selectall_hashref(
996             "GET hosts\nColumns: name alias state",
997             "name",
998             { AddPeer => 1 }
999             );
1000              
1001             =head2 Backend
1002              
1003             send the query only to some specific backends.
1004             Only useful when using multiple backends.
1005              
1006             my $hosts = $ml->selectall_arrayref(
1007             "GET hosts\nColumns: name alias state",
1008             { Backends => [ 'key1', 'key4' ] }
1009             );
1010              
1011             =head2 Columns
1012              
1013             only return the given column indexes
1014              
1015             my $array_ref = $ml->selectcol_arrayref(
1016             "GET hosts\nColumns: name contacts",
1017             { Columns => [2] }
1018             );
1019              
1020             see L for more examples
1021              
1022             =head2 Deepcopy
1023              
1024             deep copy/clone the result set.
1025              
1026             Only effective when using multiple backends and threads.
1027             This can be safely turned off if you don't change the
1028             result set.
1029             If you get an error like "Invalid value for shared scalar" error" this
1030             should be turned on.
1031              
1032             my $array_ref = $ml->selectcol_arrayref(
1033             "GET hosts\nColumns: name contacts",
1034             { Deepcopy => 1 }
1035             );
1036              
1037             =head2 Limit
1038              
1039             Just like the Limit: option from livestatus itself.
1040             In addition you can add a start,length limit.
1041              
1042             my $array_ref = $ml->selectcol_arrayref(
1043             "GET hosts\nColumns: name contacts",
1044             { Limit => "10,20" }
1045             );
1046              
1047             This example will return 20 rows starting at row 10. You will
1048             get row 10-30.
1049              
1050             Cannot be combined with a Limit inside the query
1051             because a Limit will be added automatically.
1052              
1053             Adding a limit this way will greatly increase performance and
1054             reduce memory usage.
1055              
1056             This option is multibackend safe contrary to the "Limit: " part of a statement.
1057             Sending a statement like "GET...Limit: 10" with 3 backends will result in 30 rows.
1058             Using this options, you will receive only the first 10 rows.
1059              
1060             =head2 Rename
1061              
1062             see L for detailed explainaton
1063              
1064             =head2 Slice
1065              
1066             see L for detailed explainaton
1067              
1068             =head2 Sum
1069              
1070             The Sum option only applies when using multiple backends.
1071             The values from all backends with be summed up to a total.
1072              
1073             my $stats = $ml->selectrow_hashref(
1074             "GET hosts\nStats: state = 0\nStats: state = 1",
1075             { Sum => 1 }
1076             );
1077              
1078             =cut
1079              
1080              
1081             ########################################
1082             # wrapper around _send_socket_do
1083             sub _send_socket {
1084 0     0   0 my($self, $statement) = @_;
1085              
1086 0         0 my $retries = 0;
1087 0         0 my($status, $msg, $recv, $sock);
1088              
1089             # closing a socket sends SIGPIPE to reader
1090             # https://riptutorial.com/posix/example/17424/handle-sigpipe-generated-by-write---in-a-thread-safe-manner
1091 0         0 local $SIG{PIPE} = 'IGNORE';
1092              
1093 0   0     0 my $maxretries = $ENV{'LIVESTATUS_RETRIES'} // $self->{'retries_on_connection_error'};
1094              
1095             # try to avoid connection errors
1096 0         0 eval {
1097 0 0       0 if($maxretries <= 0) {
1098 0         0 ($sock, $msg, $recv) = &_send_socket_do($self, $statement);
1099 0 0       0 return($sock, $msg, $recv) if $msg;
1100 0         0 ($status, $msg, $recv) = &_read_socket_do($self, $sock, $statement);
1101 0         0 return($status, $msg, $recv);
1102             }
1103              
1104 0   0     0 while((!defined $status || ($status == 491 || $status == 497 || $status == 500)) && $retries < $maxretries) {
      0        
1105 0         0 $retries++;
1106 0         0 ($sock, $msg, $recv) = &_send_socket_do($self, $statement);
1107 0 0       0 return($status, $msg, $recv) if $msg;
1108 0         0 ($status, $msg, $recv) = &_read_socket_do($self, $sock, $statement);
1109 0 0       0 $self->{'logger'}->debug('query status '.$status) if $self->{'verbose'};
1110 0 0 0     0 if($status == 491 or $status == 497 or $status == 500) {
      0        
1111 0 0       0 $self->{'logger'}->debug('got status '.$status.' retrying in '.$self->{'retry_interval'}.' seconds') if $self->{'verbose'};
1112 0         0 $self->_close();
1113 0 0       0 sleep($self->{'retry_interval'}) if $retries < $maxretries;
1114             }
1115             }
1116             };
1117 0         0 my $err = $@;
1118 0 0       0 if($err) {
1119 0 0       0 $self->{'logger'}->debug("try 1 failed: $err") if $self->{'verbose'};
1120 0 0       0 if($err =~ /broken\ pipe/mx) {
1121 0         0 ($sock, $msg, $recv) = &_send_socket_do($self, $statement);
1122 0 0       0 return($status, $msg, $recv) if $msg;
1123 0         0 return(&_read_socket_do($self, $sock, $statement));
1124             }
1125 0 0       0 _die_or_confess($err) if $self->{'errors_are_fatal'};
1126             }
1127              
1128 0 0       0 $status = $sock unless $status;
1129 0         0 $msg =~ s/^$status:\s+//gmx;
1130 0 0 0     0 _die_or_confess($status.": ".$msg) if($status >= 400 and $self->{'errors_are_fatal'});
1131              
1132 0         0 return($status, $msg, $recv);
1133             }
1134              
1135             ########################################
1136             sub _send_socket_do {
1137 0     0   0 my($self, $statement) = @_;
1138 0 0 0     0 my $sock = $self->_open() or return(491, $self->_get_error(491, $@ || $!), $@ || $!);
      0        
1139 0         0 utf8::decode($statement); # make sure
1140 0         0 utf8::encode($statement); # query is utf8
1141 0 0       0 $sock->printflush($statement,"\n") || return($self->_socket_error($statement, 'write to socket failed'.($! ? ': '.$! : '')));
    0          
1142 0         0 return $sock;
1143             }
1144              
1145             ########################################
1146             sub _read_socket_do {
1147 0     0   0 my($self, $sock, $statement) = @_;
1148 0         0 my($recv,$header);
1149              
1150 0         0 my $s = IO::Select->new();
1151 0         0 $s->add($sock);
1152              
1153             # COMMAND statements might return a error message
1154 0 0 0     0 if($statement && $statement =~ m/^COMMAND/mx) {
1155 0         0 shutdown($sock, 1);
1156 0 0       0 if($s->can_read(3)) {
1157 0         0 $recv = <$sock>;
1158             }
1159 0 0       0 if($recv) {
1160 0         0 chomp($recv);
1161 0 0       0 if($recv =~ m/^(\d+):\s*(.*)$/mx) {
1162 0         0 return($1, $recv, undef);
1163             }
1164 0         0 return('400', $self->_get_error(400), $recv);
1165             }
1166 0         0 return('200', $self->_get_error(200), undef);
1167             }
1168              
1169 0         0 my $timeout = 180;
1170 0 0       0 if($statement) {
1171             # status requests should not take longer than 20 seconds
1172 0 0       0 $timeout = 20 if($statement =~ m/^GET\s+status/mx);
1173 0 0       0 $timeout = 300 if($statement =~ m/^GET\s+log/mx);
1174             }
1175 0 0       0 $timeout = $self->{'query_timeout'} if $self->{'query_timeout'};
1176              
1177 0         0 local $! = undef;
1178 0         0 my @ready = $s->can_read($timeout);
1179 0 0       0 if(scalar @ready == 0) {
1180 0         0 my $err = $!;
1181 0 0       0 if($err) {
1182 0         0 return($self->_socket_error($statement, 'socket error '.$err));
1183             }
1184 0         0 return($self->_socket_error($statement, 'timeout ('.$timeout.'s) while waiting for socket'));
1185             }
1186              
1187 0 0       0 $sock->read($header, 16) || return($self->_socket_error($statement, 'reading header from socket failed'.($! ? ': '.$! : '')));
    0          
1188 0 0       0 $self->{'logger'}->debug("header: $header") if $self->{'verbose'};
1189 0         0 my($status, $msg, $content_length) = &_parse_header($self, $header, $sock);
1190 0 0       0 return($status, $msg, undef) if !defined $content_length;
1191 0         0 our $json_decoder;
1192 0 0       0 if($json_decoder) {
1193 0         0 $json_decoder->incr_reset;
1194             } else {
1195 0         0 $json_decoder = Cpanel::JSON::XS->new->utf8->relaxed;
1196             }
1197 0 0       0 if($content_length > 0) {
1198 0 0       0 if($status == 200) {
1199 0         0 my $remaining = $content_length;
1200 0         0 my $length = 32768;
1201 0 0       0 if($remaining < $length) { $length = $remaining; }
  0         0  
1202 0   0     0 while($length > 0 && $sock->read(my $buf, $length)) {
1203             # replace u+D800 to u+DFFF (reserved utf-16 low/high surrogates)
1204 0         0 $buf =~ s/\\ud[89a-f][0-9a-f]{2}/\\ufffd/gmxio;
1205 0         0 $json_decoder->incr_parse($buf);
1206 0         0 $remaining = $remaining -$length;
1207 0 0       0 if($remaining < $length) { $length = $remaining; }
  0         0  
1208             }
1209 0 0       0 $recv = $json_decoder->incr_parse or return($self->_socket_error($statement, 'reading remaining '.$length.' bytes from socket failed'.($! ? ': '.$! : '')));
    0          
1210 0         0 $json_decoder->incr_reset;
1211             } else {
1212 0 0       0 $sock->read($recv, $content_length) or return($self->_socket_error($statement, 'reading body from socket failed'.($! ? ': '.$! : '')));
    0          
1213             }
1214             }
1215              
1216 0 0       0 $self->_close() unless $self->{'keepalive'};
1217 0 0 0     0 if($status >= 400 && $recv) {
1218 0         0 $msg .= ' - '.$recv;
1219             }
1220 0         0 return($status, $msg, $recv);
1221             }
1222              
1223             ########################################
1224             sub _socket_error {
1225 0     0   0 my($self, $statement, $err) = @_;
1226              
1227 0         0 my $message = "\n";
1228 0         0 $message .= "peer ".Dumper($self->peer_name);
1229 0         0 $message .= "statement ".Dumper($statement);
1230              
1231 0 0       0 $self->{'logger'}->error($message) if $self->{'verbose'};
1232              
1233 0 0       0 if($self->{'retries_on_connection_error'} <= 0) {
1234 0 0       0 if($self->{'errors_are_fatal'}) {
1235 0         0 _die_or_confess($message);
1236             }
1237             else {
1238 0         0 carp($message);
1239             }
1240             }
1241 0         0 $self->_close();
1242 0 0       0 return(500, $self->_get_error(500).($err ? " - ".$err : ""), $message);
1243             }
1244              
1245             ########################################
1246             sub _parse_header {
1247 1     1   4 my($self, $header, $sock) = @_;
1248              
1249 1 50       5 if(!defined $header) {
1250 0         0 return(497, $self->_get_error(497), undef);
1251             }
1252              
1253 1         2 my $headerlength = length($header);
1254 1 50       4 if($headerlength != 16) {
1255 0         0 return(498, $self->_get_error(498)."\ngot: ".$header.<$sock>, undef);
1256             }
1257 1         4 chomp($header);
1258              
1259 1         15 my $status = substr($header,0,3);
1260 1         3 my $content_length = substr($header,5);
1261 1 50       9 if($content_length !~ m/^\s*(\d+)$/mx) {
1262 0         0 return(499, $self->_get_error(499)."\ngot: ".$header.<$sock>, undef);
1263             } else {
1264 1         3 $content_length = $1;
1265             }
1266              
1267 1         4 return($status, $self->_get_error($status), $content_length);
1268             }
1269              
1270             ########################################
1271              
1272             =head1 COLUMN ALIAS
1273              
1274             In addition to the normal query syntax from the livestatus addon, it is
1275             possible to set column aliases in various ways.
1276              
1277             A valid Columns: Header could look like this:
1278              
1279             my $hosts = $ml->selectall_arrayref(
1280             "GET hosts\nColumns: state as status"
1281             );
1282              
1283             Stats queries could be aliased too:
1284              
1285             my $stats = $ml->selectall_arrayref(
1286             "GET hosts\nStats: state = 0 as up"
1287             );
1288              
1289             This syntax is available for: Stats, StatsAnd, StatsOr and StatsGroupBy
1290              
1291              
1292             An alternative way to set column aliases is to define rename option key/value
1293             pairs:
1294              
1295             my $hosts = $ml->selectall_arrayref(
1296             "GET hosts\nColumns: name", {
1297             rename => { 'name' => 'hostname' }
1298             }
1299             );
1300              
1301             =cut
1302              
1303             ########################################
1304              
1305             =head2 extract_keys_from_stats_statement
1306              
1307             extract_keys_from_stats_statement($statement)
1308              
1309             Extract column keys from statement.
1310              
1311             =cut
1312             sub extract_keys_from_stats_statement {
1313 2     2 1 2399 my($statement) = @_;
1314              
1315 2         4 my(@header, $new_statement);
1316              
1317 2         18 for my $line (split/\n/mx, $statement) {
1318 44 100       108 if(substr($line, 0, 5) ne 'Stats') { # faster shortcut for non-stats lines
1319 2         7 $new_statement .= $line."\n";
1320 2         4 next;
1321             }
1322 42 100       240 if($line =~ m/^Stats:\ (.*)\s+as\s+(.*?)$/mxo) {
    100          
    100          
    100          
    100          
    50          
    0          
    0          
1323 5         18 push @header, $2;
1324 5         15 $line = 'Stats: '.$1;
1325             }
1326             elsif($line =~ m/^Stats:\ (.*)$/mxo) {
1327 27         68 push @header, $1;
1328             }
1329              
1330             elsif($line =~ m/^StatsAnd:\ (\d+)\s+as\s+(.*?)$/mxo) {
1331 4         14 for(my $x = 0; $x < $1; $x++) {
1332 9         26 pop @header;
1333             }
1334 4         10 $line = 'StatsAnd: '.$1;
1335 4         10 push @header, $2;
1336             }
1337             elsif($line =~ m/^StatsAnd:\ (\d+)$/mxo) {
1338 4         8 my @to_join;
1339 4         13 for(my $x = 0; $x < $1; $x++) {
1340 9         30 unshift @to_join, pop @header;
1341             }
1342 4         15 push @header, join(' && ', @to_join);
1343             }
1344              
1345             elsif($line =~ m/^StatsOr:\ (\d+)\s+as\s+(.*?)$/mxo) {
1346 1         5 for(my $x = 0; $x < $1; $x++) {
1347 2         7 pop @header;
1348             }
1349 1         3 $line = 'StatsOr: '.$1;
1350 1         4 push @header, $2;
1351             }
1352             elsif($line =~ m/^StatsOr:\ (\d+)$/mxo) {
1353 1         2 my @to_join;
1354 1         4 for(my $x = 0; $x < $1; $x++) {
1355 2         7 unshift @to_join, pop @header;
1356             }
1357 1         3 push @header, join(' || ', @to_join);
1358             }
1359              
1360             # StatsGroupBy header are always sent first
1361             elsif($line =~ m/^StatsGroupBy:\ (.*)\s+as\s+(.*?)$/mxo) {
1362 0         0 unshift @header, $2;
1363 0         0 $line = 'StatsGroupBy: '.$1;
1364             }
1365             elsif($line =~ m/^StatsGroupBy:\ (.*)$/mxo) {
1366 0         0 unshift @header, $1;
1367             }
1368 42         87 $new_statement .= $line."\n";
1369             }
1370              
1371 2         15 return($new_statement, \@header);
1372             }
1373              
1374             ########################################
1375             sub _extract_keys_from_columns_header {
1376 1     1   920 my($self, $statement) = @_;
1377              
1378 1         3 my(@header, $new_statement);
1379 1         28 for my $line (split/\n/mx, $statement) {
1380 2 100       14 if($line =~ m/^Columns:\s+(.*)$/mx) {
1381 1         6 for my $column (split/\s+/mx, $1) {
1382 8 100       18 if($column eq 'as') {
1383 2         5 pop @header;
1384             } else {
1385 6         14 push @header, $column;
1386             }
1387             }
1388 1         14 $line =~ s/\s+as\s+([^\s]+)/\ /gmx;
1389             }
1390 2         9 $new_statement .= $line."\n";
1391             }
1392              
1393 1         7 return($new_statement, \@header);
1394             }
1395              
1396             ########################################
1397              
1398             =head1 ERROR HANDLING
1399              
1400             Errorhandling can be done like this:
1401              
1402             use Monitoring::Livestatus;
1403             my $ml = Monitoring::Livestatus->new(
1404             socket => '/var/lib/livestatus/livestatus.sock'
1405             );
1406             $ml->errors_are_fatal(0);
1407             my $hosts = $ml->selectall_arrayref("GET hosts");
1408             if($Monitoring::Livestatus::ErrorCode) {
1409             confess($Monitoring::Livestatus::ErrorMessage);
1410             }
1411              
1412             =cut
1413             sub _get_error {
1414 1     1   3 my($self, $code, $append) = @_;
1415              
1416 1         18 my $codes = {
1417             '200' => 'OK. Reponse contains the queried data.',
1418             '201' => 'COMMANDs never return something',
1419             '400' => 'The request contains an invalid header.',
1420             '401' => 'The request contains an invalid header.',
1421             '402' => 'The request is completely invalid.',
1422             '403' => 'The request is incomplete.',
1423             '404' => 'The target of the GET has not been found (e.g. the table).',
1424             '405' => 'A non-existing column was being referred to',
1425             '413' => 'Maximum response size reached',
1426             '452' => 'internal livestatus error',
1427             '490' => 'no query',
1428             '491' => 'failed to connect',
1429             '492' => 'Separators not allowed in statement. Please use the separator options in new()',
1430             '493' => 'OuputFormat not allowed in statement. Header will be set automatically',
1431             '494' => 'ColumnHeaders not allowed in statement. Header will be set automatically',
1432             '495' => 'ResponseHeader not allowed in statement. Header will be set automatically',
1433             '496' => 'Keepalive not allowed in statement. Please use the keepalive option in new()',
1434             '497' => 'got no header',
1435             '498' => 'header is not exactly 16byte long',
1436             '499' => 'not a valid header (no content-length)',
1437             '500' => 'socket error',
1438             '502' => 'backend connection proxy error',
1439             };
1440              
1441 1 50       4 confess('non existant error code: '.$code) if !defined $codes->{$code};
1442 1         3 my $msg = $codes->{$code};
1443 1 50       3 $msg .= ' - '.$append if $append;
1444              
1445 1         9 return($msg);
1446             }
1447              
1448             ########################################
1449             sub _get_peer {
1450 24     24   58 my($self) = @_;
1451              
1452             # check if the supplied peer is a socket or a server address
1453 24 100       98 if(defined $self->{'peer'}) {
1454 21 100       69 if(ref $self->{'peer'} eq '') {
    50          
    0          
1455 17   66     97 my $name = $self->{'name'} || ''.$self->{'peer'};
1456 17 100       57 if(index($self->{'peer'}, ':') > 0) {
1457 5         35 return({ 'peer' => ''.$self->{'peer'}, type => 'INET', name => $name });
1458             } else {
1459 12         87 return({ 'peer' => ''.$self->{'peer'}, type => 'UNIX', name => $name });
1460             }
1461             }
1462             elsif(ref $self->{'peer'} eq 'ARRAY') {
1463 4         8 for my $peer (@{$self->{'peer'}}) {
  4         12  
1464 4 50       11 if(ref $peer eq 'HASH') {
1465 0 0       0 next if !defined $peer->{'peer'};
1466 0 0       0 $peer->{'name'} = ''.$peer->{'peer'} unless defined $peer->{'name'};
1467 0 0       0 if(!defined $peer->{'type'}) {
1468 0         0 $peer->{'type'} = 'UNIX';
1469 0 0       0 if(index($peer->{'peer'}, ':') >= 0) {
1470 0         0 $peer->{'type'} = 'INET';
1471             }
1472             }
1473 0         0 return $peer;
1474             } else {
1475 4         9 my $type = 'UNIX';
1476 4 50       12 if(index($peer, ':') >= 0) {
1477 0         0 $type = 'INET';
1478             }
1479 4         29 return({ 'peer' => ''.$peer, type => $type, name => ''.$peer });
1480             }
1481             }
1482             }
1483             elsif(ref $self->{'peer'} eq 'HASH') {
1484 0         0 for my $peer (keys %{$self->{'peer'}}) {
  0         0  
1485 0         0 my $name = $self->{'peer'}->{$peer};
1486 0         0 my $type = 'UNIX';
1487 0 0       0 if(index($peer, ':') >= 0) {
1488 0         0 $type = 'INET';
1489             }
1490 0         0 return({ 'peer' => ''.$peer, type => $type, name => ''.$name });
1491             }
1492             } else {
1493 0         0 confess('type '.(ref $self->{'peer'}).' is not supported for peer option');
1494             }
1495             }
1496 3 100       12 if(defined $self->{'socket'}) {
1497 2   33     11 my $name = $self->{'name'} || ''.$self->{'socket'};
1498 2         9 return({ 'peer' => ''.$self->{'socket'}, type => 'UNIX', name => $name });
1499             }
1500 1 50       6 if(defined $self->{'server'}) {
1501 1   33     10 my $name = $self->{'name'} || ''.$self->{'server'};
1502 1         30 return({ 'peer' => ''.$self->{'server'}, type => 'INET', name => $name });
1503             }
1504              
1505             # check if we got a peer
1506 0           confess('please specify a peer');
1507             }
1508              
1509              
1510             ########################################
1511             sub _lowercase_and_verify_options {
1512 0     0     my($self, $opts) = @_;
1513 0           my $return = {};
1514              
1515             # make keys lowercase
1516 0           %{$return} = map { lc($_) => $opts->{$_} } keys %{$opts};
  0            
  0            
  0            
1517              
1518 0 0         if($self->{'warnings'}) {
1519 0           for my $key (keys %{$return}) {
  0            
1520 0 0         if(!defined $allowed_options->{$key}) {
1521 0           carp("unknown option used: $key - please use only: ".join(', ', keys %{$allowed_options}));
  0            
1522             }
1523             }
1524             }
1525              
1526             # set limits
1527 0 0         if(defined $return->{'limit'}) {
1528 0 0         if(index($return->{'limit'}, ',') != -1) {
1529 0           my($limit_start,$limit_length) = split /,/mx, $return->{'limit'};
1530 0           $return->{'limit_start'} = $limit_start;
1531 0           $return->{'limit_length'} = $limit_length;
1532             }
1533             else {
1534 0           $return->{'limit_start'} = 0;
1535 0           $return->{'limit_length'} = $return->{'limit'};
1536             }
1537 0           delete $return->{'limit'};
1538             }
1539              
1540 0           return($return);
1541             }
1542              
1543             ########################################
1544             sub _log_statement {
1545 0     0     my($self, $statement, $opt, $limit) = @_;
1546 0           my $d = Data::Dumper->new([$opt]);
1547 0           $d->Indent(0);
1548 0           my $optstring = $d->Dump;
1549 0           $optstring =~ s/^\$VAR1\s+=\s+//mx;
1550 0           $optstring =~ s/;$//mx;
1551              
1552             # remove empty lines from statement
1553 0           $statement =~ s/\n+/\n/gmx;
1554              
1555 0           my $cleanstatement = $statement;
1556 0           $cleanstatement =~ s/\n/\\n/gmx;
1557 0           $self->{'logger'}->debug('selectall_arrayref("'.$cleanstatement.'", '.$optstring.', '.$limit.')');
1558 0           return 1;
1559             }
1560              
1561             ########################################
1562             sub _die_or_confess {
1563 0     0     my($msg) = @_;
1564 0           my @lines = split/\n/mx, $msg;
1565 0 0         if(scalar @lines > 2) {
1566 0           die($msg);
1567             }
1568 0           confess($msg);
1569             }
1570              
1571             ########################################
1572              
1573             1;
1574              
1575             =head1 SEE ALSO
1576              
1577             For more information about the query syntax and the livestatus plugin installation
1578             see the Livestatus page: http://mathias-kettner.de/checkmk_livestatus.html
1579              
1580             =head1 AUTHOR
1581              
1582             Sven Nierlein, 2009-present,
1583              
1584             =head1 COPYRIGHT AND LICENSE
1585              
1586             Copyright (C) by Sven Nierlein
1587              
1588             This library is free software; you can redistribute it and/or modify
1589             it under the same terms as Perl itself.
1590              
1591             =cut
1592              
1593             __END__