File Coverage

blib/lib/StorageDisplay/Collect.pm
Criterion Covered Total %
statement 146 1098 13.3
branch 21 386 5.4
condition 1 119 0.8
subroutine 39 124 31.4
pod 3 30 10.0
total 210 1757 11.9


line stmt bran cond sub pod time code
1             #
2             # This file is part of StorageDisplay
3             #
4             # This software is copyright (c) 2014-2023 by Vincent Danjean.
5             #
6             # This is free software; you can redistribute it and/or modify it under
7             # the same terms as the Perl 5 programming language system itself.
8             #
9 1     1   247923 use strict;
  1         10  
  1         31  
10 1     1   6 use warnings;
  1         2  
  1         1176  
11              
12             # Implementation note: this file must contains all required modules
13             # required to collect data, but modules included in perl itself.
14             # This file can be sent to remote machine through SSH, this is why
15             # it must be self-contained.
16              
17             package StorageDisplay::Collect;
18             # ABSTRACT: modules required to collect data.
19             # No dependencies (but perl itself and its basic modules)
20              
21             our $VERSION = '2.04'; # VERSION
22              
23              
24             sub collectors {
25 0     0 0 0 my $self = shift;
26 0         0 return @{$self->{_attr_collectors}};
  0         0  
27             }
28              
29             sub collector {
30 0     0 0 0 my $self = shift;
31 0         0 my $name = shift;
32 0         0 return $self->{_attr_collectors_by_provide}->{$name};
33             }
34              
35             sub registerCollector {
36 0     0 0 0 my $self = shift;
37 0         0 my $collector = shift;
38              
39 0 0       0 die "$collector not a StorageDisplay::Collect::Collector"
40             if not $collector->isa("StorageDisplay::Collect::Collector");
41              
42 0         0 push @{$self->{_attr_collectors}}, $collector;
  0         0  
43 0         0 foreach my $cn ($collector->provides) {
44 0 0       0 if (exists($self->{_attr_collectors_by_provide}->{$cn})) {
45             die "$cn provided by both ".$collector->module." and ".
46 0         0 $self->{_attr_collectors_by_provide}->{$cn}->module;
47             }
48 0         0 $self->{_attr_collectors_by_provide}->{$cn} = $collector;
49             }
50             }
51              
52             sub open_cmd_pipe {
53 0     0 0 0 my $self = shift;
54 0         0 return $self->cmdreader->open_cmd_pipe(@_);
55             }
56              
57             sub open_cmd_pipe_root {
58 0     0 0 0 my $self = shift;
59 0         0 return $self->cmdreader->open_cmd_pipe_root(@_);
60             }
61              
62             sub open_file {
63 0     0 0 0 my $self = shift;
64 0         0 return $self->cmdreader->open_file(@_);
65             }
66              
67             sub has_file {
68 0     0 0 0 my $self = shift;
69 0         0 return $self->cmdreader->has_file(@_);
70             }
71              
72             sub cmdreader {
73 0     0 0 0 my $self = shift;
74 0         0 return $self->{_attr_cmdreader};
75             }
76              
77             my @collectors;
78              
79             sub new {
80 0     0 0 0 my $class = shift;
81 0   0     0 my $reader = shift // 'Local';
82              
83 0 0       0 if (ref($reader) eq '') {
84 0         0 my $fullreadername = 'StorageDisplay::Collect::CMD::'.$reader;
85 0         0 $reader = $fullreadername->new(@_);
86             }
87              
88 0         0 my $self = {
89             _attr_cmdreader => $reader,
90             _attr_collectors => [],
91             _attr_collectors_by_provide => {},
92             };
93              
94 0         0 bless $self, $class;
95              
96 0         0 foreach my $cdata (@collectors) {
97 0         0 my $cn = $cdata->{name};
98 0         0 $cn->new($cdata, $self);
99             }
100 0         0 return $self;
101             }
102              
103             sub registerCollectorModule {
104 13     13 0 25 my $class = shift;
105 13         16 my $collector = shift;
106              
107             #my $collector = caller 0;
108             #print STDERR "Registering $collector from ".(caller 0)."\n";
109 13         57 my $info = { name => $collector, @_ };
110 13         41 foreach my $entry (qw(provides requires)) {
111 26 100       80 next if not exists($info->{$entry});
112 16 100       54 if (ref($info->{$entry}) eq "") {
113 10         24 $info->{$entry} = [ $info->{$entry} ];
114             }
115             }
116 13         7547 push @collectors, $info;
117             }
118              
119             # Main collect function
120             #
121             # It will iterate on the collectors, respecting dependencies.
122             sub collect {
123 0     0 0 0 my $self = shift;
124 0         0 my $req = shift;
125 0         0 my $infos = {};
126              
127 0         0 $infos = $self->cmdreader->data_init($infos);
128              
129             # 0/undef : not computed
130             # 1 : computed
131             # 2 : computing
132             # 3 : N/A
133 0         0 my $collector_state = {};
134              
135 0         0 my $load;
136             $load = sub {
137 0     0   0 my $col = shift;
138 0         0 $collector_state->{$_} = 2 for $col->provides;
139 0         0 foreach my $cname ($col->requires) {
140             #print STDERR " preloading $cname\n";
141 0         0 my $state = $collector_state->{$cname};
142 0 0       0 if (not defined($state)) {
    0          
143 0         0 my $collector = $self->collector($cname);
144 0 0       0 die "E: No $cname collector available for ".$col->module."\n"
145             if not defined($collector);
146 0         0 $load->($collector);
147             } elsif ($collector_state->{$cname} == 1) {
148             next
149 0         0 } else {
150 0         0 die "Loop in collectors requires ($cname required in $col->name)";
151             }
152             }
153             # are files present?
154             my @missing_files =
155             grep {
156 0         0 not $self->has_file($_);
  0         0  
157             } $col->depends('files');
158 0 0       0 if (scalar(@missing_files)) {
159 0         0 print STDERR "I: skipping ", $col->module, " due to missing file(s): '",
160             join("', '", @missing_files), "'\n";
161 0         0 $collector_state->{$_} = 3 for $col->provides;
162 0         0 return;
163             }
164 0 0       0 my $opencmd = $col->depends('root') ?
165             'open_cmd_pipe_root' : 'open_cmd_pipe';
166             # are programs present?
167             my @missing_progs =
168             grep {
169 0         0 my @cmd=('which', $_);
  0         0  
170 0         0 my $dh = $col->$opencmd(@cmd);
171 0         0 my $path = <$dh>;
172 0         0 close($dh);
173 0         0 not defined($path);
174             } $col->depends('progs');
175 0 0       0 if (scalar(@missing_progs)) {
176 0         0 print STDERR "I: skipping ", $col->module, " due to missing program(s): '",
177             join("', '", @missing_progs), "'\n";
178 0         0 $collector_state->{$_} = 3 for $col->provides;
179 0         0 return;
180             }
181             # collecting data while providing required data
182             my $collected_infos = $col->collect(
183             {
184 0         0 map { $_ => $infos->{$_} } $col->requires
  0         0  
185             }, $req);
186             # registering provided data
187 0         0 $infos->{$_} = $collected_infos->{$_} for $col->provides;
188 0         0 $collector_state->{$_} = 1 for $col->provides;
189             #print STDERR "loaded $cn\n";
190 0         0 };
191             # Be sure to collect all collectors
192 0         0 foreach my $col ($self->collectors) {
193 0         0 $load->($col);
194             }
195              
196 0         0 return $self->cmdreader->data_finish($infos);
197             }
198              
199             1;
200              
201             ###########################################################################
202             package StorageDisplay::Collect::JSON;
203              
204             BEGIN {
205             # Mark current package as loaded;
206             # else, we cannot use 'use StorageDisplay::Collect::JSON;' latter
207 1     1   5 my $p = __PACKAGE__;
208 1         5 $p =~ s,::,/,g;
209 1         5393 chomp(my $cwd = `pwd`);
210 1         169 $INC{$p.'.pm'} = $cwd.'/'.__FILE__;#k"current file";
211             }
212              
213             # This package contains
214             # - two public subroutines
215             # - `use_pp_parser` to know if JSON:PP makes all the work alone
216             # - `decode_json` to decode a json text with the $json_parser object
217             # - a public `new` class method that returns
218             # - a plain JSON::PP object (if boolean_values method exists)
219             # - a __PACKAGE__ object (if not) that inherit from JSON::PP
220             # - an overrided `decode` method that
221             # - calls SUPER::decode
222             # - manually transforms JSON:::PP::Boolean into plain scalar
223             # $json_parser is
224             # - either a JSON::PP object (if boolean_values method exists)
225             # - or a StorageDisplay::Collect::JSON that inherit of JSON::PP
226             # but override the decode method
227              
228 1     1   25 use base 'JSON::PP';
  1         2  
  1         1452  
229              
230             my $has_boolean_values;
231              
232             sub new {
233 2     2 1 1728 my $class = shift;
234 2         11 my $json_pp_parser;
235 2 100       8 if (!defined($has_boolean_values)) {
236 1         7 $json_pp_parser = JSON::PP->new;
237 1         13 $has_boolean_values = 0;
238 1         3 eval {
239             # workaround if not supported
240 1         14 $json_pp_parser->boolean_values(0, 1);
241 0         0 $has_boolean_values = 1;
242             };
243             }
244 2         4 my $parser;
245 2 50       8 if ($has_boolean_values) {
246 0         0 $parser = JSON::PP->new(@_);
247 0         0 $parser->boolean_values(0, 1);
248             } else {
249 2         7 $parser = JSON::PP::new(__PACKAGE__, @_);
250             }
251 2         26 eval {
252             # ignore if not supported
253 2         55 $parser->allow_bignum;
254             };
255 2         43 return $parser;
256             }
257              
258             sub decode {
259 1     1 1 2 my $self = shift;
260              
261 1         7 my $data = $self->SUPER::decode(@_);
262              
263 1         100342 my %unrecognized;
264              
265             local *_convert_bools = sub {
266 8     8   131 my $ref_type = ref($_[0]);
267 8 100       32 if (!$ref_type) {
    100          
    100          
    100          
    50          
    50          
268             # Nothing.
269             }
270             elsif ($ref_type eq 'HASH') {
271 1         25 _convert_bools($_) for values(%{ $_[0] });
  1         11  
272             }
273             elsif ($ref_type eq 'ARRAY') {
274 2         3 _convert_bools($_) for @{ $_[0] };
  2         9  
275             }
276             elsif ($ref_type eq 'JSON::PP::Boolean') {
277 2 100       37 $_[0] = $_[0] ? 1 : 0;
278             }
279             elsif ($ref_type eq 'Math::BigInt') {
280 0 0       0 if ($_[0]->beq($_[0]->numify())) {
281             # old versions of JSON::PP always use Math::Big*
282             # even if this is not required
283 0         0 $_[0] = $_[0]->numify();
284             }
285             }
286             elsif ($ref_type eq 'Math::BigFloat') {
287 1 50 33     10 if ($_[0]->is_int()
288             && $_[0]->beq($_[0]->numify())) {
289 1         631 $_[0] = $_[0]->numify();
290             }
291             }
292             else {
293 0         0 ++$unrecognized{$ref_type};
294             }
295 1         10 };
296              
297 1         5 &_convert_bools($data);
298              
299             warn("Encountered an object of unrecognized type $_")
300 1         12 for sort values(%unrecognized);
301              
302 1         19 return $data;
303             }
304              
305             my $json_parser;
306              
307             sub decode_json {
308 1 50   1 1 665 if (not defined($json_parser)) {
309 1         10 $json_parser = __PACKAGE__->new();
310             }
311              
312 1         5 $json_parser->decode(@_);
313             }
314              
315             sub pp_parser_has_boolean_values {
316 2     2 0 7074 return $has_boolean_values;
317             }
318              
319             sub jsonarray2perlhash {
320 0     0 0 0 my $json = shift;
321 0         0 my $root = shift;
322 0         0 my $key = shift;
323             my $info = {
324 0         0 map { $_->{$key} => $_ }
325 0         0 (@{decode_json($json)->{$root}})
  0         0  
326             };
327 0         0 return $info;
328             }
329              
330             1;
331              
332             ###########################################################################
333             package StorageDisplay::Collect::CMD;
334              
335             sub new {
336 0     0   0 my $class = shift;
337 0         0 my $self = {};
338 0         0 bless $self, $class;
339 0         0 return $self;
340             }
341              
342             sub cmd2str {
343 0     0   0 my $self = shift;
344 0         0 my @cmd = @_;
345             my $str = join(' ', map {
346 0         0 my $s = $_;
  0         0  
347 0         0 $s =~ s/(['\\])/\\$1/g;
348 0 0       0 if ($s !~ /^[0-9a-zA-Z_@,:\/=-]+$/) {
349 0         0 $s="'".$s."'";
350             }
351 0         0 $s;
352             } @cmd);
353 0         0 return $str;
354             }
355              
356             sub data_init {
357 0     0   0 my $self = shift;
358 0         0 my $data = shift;
359              
360 0         0 return $data;
361             }
362              
363             sub data_finish {
364 0     0   0 my $self = shift;
365 0         0 my $data = shift;
366              
367 0         0 return $data;
368             }
369              
370             sub open_file {
371 0     0   0 my $self = shift;
372 0         0 my $filename = shift;
373              
374 0         0 return $self->open_cmd_pipe('cat', $filename);
375              
376 0         0 my $dh;
377 0 0       0 open($dh, '<', $filename) or die "Cannot open $filename: $!";
378 0         0 return $dh;
379             }
380              
381             sub has_file {
382 0     0   0 my $self = shift;
383 0         0 my $filename = shift;
384              
385 0         0 return -e $filename;
386             }
387              
388             1;
389              
390             ###########################################################################
391             package StorageDisplay::Collect::CMD::Local;
392              
393 1     1   737 use parent -norequire => "StorageDisplay::Collect::CMD";
  1         393  
  1         16  
394              
395             sub open_cmd_pipe {
396 0     0   0 my $self = shift;
397 0         0 my @cmd = @_;
398 0         0 print STDERR "Running: ", $self->cmd2str(@cmd)."\n";
399 0 0       0 open(my $dh, '-|', @cmd) or
400             die "Cannot run ".$self->cmd2str(@cmd).": $!\n";
401 0         0 return $dh;
402             }
403              
404             sub open_cmd_pipe_root {
405 0     0   0 my $self = shift;
406 0 0       0 if ($> != 0) {
407 0         0 return $self->open_cmd_pipe('sudo', @_);
408             } else {
409 0         0 return $self->open_cmd_pipe(@_);
410             }
411             }
412              
413             1;
414              
415             ###########################################################################
416             package StorageDisplay::Collect::CMD::LocalBySSH;
417              
418 1     1   227 use parent -norequire => "StorageDisplay::Collect::CMD";
  1         2  
  1         9  
419              
420             sub open_cmd_pipe {
421 0     0   0 my $self = shift;
422 0         0 my @cmd = @_;
423 0         0 my $cmd = $self->cmd2str(@cmd);
424 0         0 $cmd =~ s/sudo password:\n/sudo password:/;
425 0         0 print STDERR "Running: $cmd\n";
426 0 0       0 open(my $dh, '-|', @cmd) or
427             die "Cannot run $cmd: $!\n";
428 0         0 return $dh;
429             }
430              
431             sub open_cmd_pipe_root {
432 0     0   0 my $self = shift;
433 0 0       0 if ($> != 0) {
434 0         0 return $self->open_cmd_pipe(qw(sudo -S -p), 'sudo password:'."\n", '--', @_);
435             } else {
436 0         0 return $self->open_cmd_pipe(@_);
437             }
438             }
439              
440             1;
441              
442             ###########################################################################
443             package StorageDisplay::Collect::CMD::Proxy::Recorder;
444              
445 1     1   275 use parent -norequire => "StorageDisplay::Collect::CMD";
  1         42  
  1         9  
446 1     1   63 use Scalar::Util 'blessed';
  1         2  
  1         783  
447              
448             sub new {
449 0     0   0 my $class = shift;
450 0         0 my %args = ( @_ );
451 0 0       0 if (not exists($args{'recorder-reader'})) {
452 0         0 die 'recorder-reader argument required';
453             }
454 0         0 my $reader = $args{'recorder-reader'};
455 0 0       0 if (ref($reader) eq '') {
456 0         0 my $fullreadername = 'StorageDisplay::Collect::CMD::'.$reader;
457 0   0     0 $reader = $fullreadername->new(@_, %{$args{'recorder-args-pass'} // {}});
  0         0  
458             }
459 0 0 0     0 die "Invalid reader" if not blessed($reader) or not $reader->isa("StorageDisplay::Collect::CMD");
460 0         0 my $self = $class->SUPER::new(@_);
461 0         0 $self->{'_attr_reader'} = $reader;
462 0         0 return $self;
463             }
464              
465             sub reader {
466 0     0   0 my $self = shift;
467 0         0 return $self->{_attr_reader};
468             }
469              
470             sub data_finish {
471 0     0   0 my $self = shift;
472 0         0 my $infos = shift;
473 0         0 $infos = $self->SUPER::data_finish($infos);
474 0         0 $infos->{'recorder'} = $self->{_attr_records};
475 0         0 return $infos;
476             }
477              
478             sub _record {
479 0     0   0 my $self = shift;
480 0         0 my $args = { @_ };
481 0         0 my $dh = $args->{'dh'};
482 0         0 delete($args->{'dh'});
483 0         0 my @infos = <$dh>;
484 0         0 @infos = map { chomp; $_ } @infos;
  0         0  
  0         0  
485 0         0 close($dh);
486 0         0 $args->{'stdout'} = \@infos;
487 0         0 push @{$self->{'_attr_records'}}, $args;
  0         0  
488 0         0 my $infos = join("\n", @infos);
489 0 0       0 if (scalar(@infos)) {
490 0         0 $infos .= "\n";
491             }
492 0         0 open(my $fh, "<", \$infos);
493 0         0 return $fh;
494             }
495              
496             sub open_cmd_pipe {
497 0     0   0 my $self = shift;
498 0         0 return $self->_record(
499             'root' => 0,
500             'cmd' => [ @_ ],
501             'dh' => $self->reader->open_cmd_pipe(@_),
502             );
503             }
504              
505             sub open_cmd_pipe_root {
506 0     0   0 my $self = shift;
507 0         0 return $self->_record(
508             'root' => 1,
509             'cmd' => [ @_ ],
510             'dh' => $self->reader->open_cmd_pipe_root(@_),
511             );
512             }
513              
514             sub has_file {
515 0     0   0 my $self = shift;
516 0         0 my $filename = shift;
517 0         0 my $ret = $self->reader->has_file($filename);
518 0         0 push @{$self->{'_attr_records'}}, {
  0         0  
519             'filename' => $filename,
520             'value' => $ret,
521             };
522 0         0 return $ret;
523             }
524              
525             1;
526              
527             ###########################################################################
528             package is_collector;
529              
530             our $CALLER;
531              
532             sub import {
533 13     13   29 my $class = shift;
534              
535 13         25 my $inheritor = caller(0);
536              
537             {
538 1     1   9 no strict 'refs'; ## no critic
  1         2  
  1         167  
  13         25  
539 13         21 push @{"$inheritor\::ISA"}, 'StorageDisplay::Collect::Collector'; # dies if a loop is detected
  13         178  
540 13         30 $CALLER = $inheritor;
541 13         54 StorageDisplay::Collect->registerCollectorModule($inheritor, @_);
542             };
543             };
544              
545             BEGIN {
546             # Mark current package as loaded;
547 1     1   4 my $p = __PACKAGE__;
548 1         3 $p =~ s,::,/,g;
549 1         3912 chomp(my $cwd = `pwd`);
550 1         207 $INC{$p.'.pm'} = $cwd.'/'.__FILE__;#k"current file";
551             }
552              
553             1;
554              
555             ###########################################################################
556             package StorageDisplay::Collect::Collector;
557              
558 1     1   1134 use Storable;
  1         3902  
  1         709  
559              
560             sub open_cmd_pipe {
561 0     0 0   my $self = shift;
562 0           return $self->proxy->open_cmd_pipe(@_);
563             }
564              
565             sub open_cmd_pipe_root {
566 0     0 0   my $self = shift;
567 0           return $self->proxy->open_cmd_pipe_root(@_);
568             }
569              
570             sub open_file {
571 0     0 0   my $self = shift;
572 0           return $self->proxy->open_file(@_);
573             }
574              
575             sub has_file {
576 0     0 0   my $self = shift;
577 0           return $self->proxy->has_file(@_);
578             }
579              
580             sub collect {
581 0     0 0   my $self = shift;
582 0           print STDERR "collect must be implemented in $self\n";
583             }
584              
585             sub names_avail {
586 0     0 0   my $self = shift;
587 0           print STDERR "names_avail must be implemented in $self\n";
588             }
589              
590             sub import {
591 0     0     print STDERR __PACKAGE__." imported from ".(caller 0)."\n";
592             }
593              
594             BEGIN {
595             # Mark current package as loaded;
596 1     1   5 my $p = __PACKAGE__;
597 1         11 $p =~ s,::,/,g;
598 1         419 $INC{$p.'.pm'} = "current file";
599             }
600              
601             sub module {
602 0     0 0   my $self = shift;
603 0           return $self->{_attr_module};
604             }
605              
606             sub requires {
607 0     0 0   my $self = shift;
608 0           return @{$self->{_attr_requires}};
  0            
609             }
610              
611             sub depends {
612 0     0 0   my $self = shift;
613 0           my $kind = shift;
614             return wantarray
615 0   0       ? @{$self->{_attr_depends}->{$kind} // []}
616 0 0         : $self->{_attr_depends}->{$kind};
617             }
618              
619             sub provides {
620 0     0 0   my $self = shift;
621 0           return @{$self->{_attr_provides}};
  0            
622             }
623              
624             sub proxy {
625 0     0 0   my $self = shift;
626 0           return $self->{_attr_collect};
627             }
628              
629             sub select {
630 0     0 0   my $self = shift;
631 0           my $infos = shift;
632 0   0       my $request = shift // {};
633 0           return $self->names_avail;
634             }
635              
636             sub new {
637 0     0 0   my $class = shift;
638 0           my $infos = shift;
639 0           my $collect = shift;
640              
641 0           my $self = {};
642 0           bless $self, $class;
643              
644 0           $self->{_attr_module} = $infos->{name};
645 0           $self->{_attr_collect} = $collect;
646 0   0       $self->{_attr_requires} = Storable::dclone($infos->{requires}//[]);
647 0   0       $self->{_attr_provides} = Storable::dclone($infos->{provides}//[]);
648 0   0       $self->{_attr_depends} = Storable::dclone($infos->{depends}//{});
649 0           $collect->registerCollector($self);
650              
651 0           return $self;
652             }
653              
654             1;
655              
656             ###########################################################################
657             ###########################################################################
658             ###########################################################################
659             ###########################################################################
660             package StorageDisplay::Collect::Host;
661              
662             use is_collector
663 1         8 provides => [ qw(hostname) ],
664             no_names => 1,
665             depends => {
666             progs => [ 'hostname', 'date' ],
667 1     1   8 };
  1         2  
668              
669             sub collect {
670 0     0     my $self = shift;
671 0           my $infos = {};
672 0           my $dh;
673              
674 0           $dh=$self->open_cmd_pipe(qw(hostname));
675 0           my $hostname = <$dh>;
676 0           close $dh;
677 0           chomp($hostname);
678 0           $dh=$self->open_cmd_pipe(qw(hostname --fqdn));
679 0           my $fqdn_hostname = <$dh>;
680 0           close $dh;
681 0           chomp($fqdn_hostname);
682 0           $dh=$self->open_cmd_pipe(qw(date --rfc-3339=s));
683 0           my $date = <$dh>;
684 0           close $dh;
685 0           chomp($date);
686 0           $dh=$self->open_cmd_pipe(qw(uname -a));
687 0           my $uname = <$dh>;
688 0           close $dh;
689 0           chomp($uname);
690              
691             return {
692 0           hostname => $hostname,
693             fqdn_hostname => $fqdn_hostname,
694             date => $date,
695             uname => $uname,
696             };
697             }
698              
699             1;
700              
701             ###########################################################################
702             package StorageDisplay::Collect::SystemBlocks;
703              
704             use is_collector
705 1         5 provides => [ qw(lsblk lsblk-hierarchy udev) ],
706             no_names => 1,
707             depends => {
708             progs => [ 'lsblk', 'udevadm' ],
709 1     1   7 };
  1         3  
710              
711 1     1   10 use StorageDisplay::Collect::JSON;
  1         2  
  1         803  
712              
713             sub lsblkjson2perl {
714 0     0     my $self = shift;
715 0           my $json = shift;
716 0           return StorageDisplay::Collect::JSON::jsonarray2perlhash($json, 'blockdevices', 'kname');
717             }
718              
719             sub collect {
720 0     0     my $self = shift;
721 0           my $infos = {};
722 0           my $dh;
723             my $json;
724              
725             # Get all infos on system blocks
726             # 'lsblk-json-hierarchy' -> Str(json)
727             #my $dh=open_cmd_pipe(qw(lsblk --json --bytes --output-all));
728 0           $dh=$self->open_cmd_pipe(qw(lsblk --all --json --output), 'name,kname');
729 0           $json=join("\n", <$dh>);
730 0           close $dh;
731 0           $infos->{'lsblk-hierarchy'}=$self->lsblkjson2perl($json);
732              
733             # And keep json infos
734             # 'lsblk-json' -> kn -> Str(json)
735 0           $dh=$self->open_cmd_pipe(qw(lsblk --all --json --bytes --output-all --list));
736 0           $infos->{'lsblk'}=$self->lsblkjson2perl(join("\n", <$dh>));
737 0           close $dh;
738              
739             # Get all infos with udev
740             # 'udev' -> kn ->
741             # - 'path' -> Str(P:)
742             # - 'name' -> Str(N:)
743             # - 'names' -> [ N:, S:... ]
744             # - '_udev_infos' -> id -> Str(val)
745 0           $dh=$self->open_cmd_pipe(qw(udevadm info --query all --export-db));
746 0           my $data={'_udev_infos' => {}};
747 0           my $dname;
748             my $lastline;
749 0           while (defined(my $line=<$dh>)) {
750 0           chomp($line);
751 0           $lastline=$line;
752 0 0         if ($line eq '') {
753 0 0         if (defined($dname)) {
754 0 0         if (exists($data->{'names'})) {
755 0           my @sorted_names=sort @{$data->{'names'}};
  0            
756 0           $data->{'names'}=\@sorted_names;
757             }
758 0           $infos->{'udev'}->{$dname}=$data;
759             } else {
760             #print STDERR "No 'N:' tag in udev entry ".($data->[0]//"")."\n";
761             }
762 0           $data={'_udev_infos' => {}};
763 0           $dname=undef;
764             } else {
765 0 0         if ($line =~ /^P: (.*)$/) {
    0          
    0          
    0          
    0          
    0          
766 0           $data->{'path'}=$1;
767             } elsif ($line =~ /^N: (.*)$/) {
768 0           $dname=$1;
769 0           $data->{'name'}=$1;
770 0           push @{$data->{'names'}}, $1;
  0            
771             } elsif ($line =~ /^S: (.*)$/) {
772 0           push @{$data->{'names'}}, $1;
  0            
773             } elsif ($line =~ /^E: (DEVLINKS)=(.*)$/) {
774 0           $data->{'_udev_infos'}->{$1}=join(' ', sort(split(' ',$2)));
775             } elsif ($line =~ /^E: ([^=]*)=(.*)$/) {
776 0           $data->{'_udev_infos'}->{$1}=$2;
777             } elsif ($line =~ /^[MRUTDILQV]: .*$/) {
778             # Unused info. See udevadm(8) / Table 1 for more info
779             } else {
780 0 0         print STDERR "Ignoring '$line'".(defined($dname)?(' for '.$dname):'')."\n";
781             }
782             }
783             }
784 0           close $dh;
785 0 0         if(defined($dname)) {
786 0           die "E: pb avec $dname ($lastline)", "\n";
787             }
788 0           return $infos;
789             }
790              
791             1;
792              
793             ###########################################################################
794             package StorageDisplay::Collect::DeviceMapper;
795              
796             use is_collector
797 1         14 provides => qw(dm),
798             depends => {
799             progs => [ 'dmsetup' ],
800             root => 1,
801 1     1   8 };
  1         7  
802              
803             sub collect {
804 0     0     my $self = shift;
805 0           my $dm={};
806 0           my $dh;
807              
808             # Get all infos with dmsetup
809             # 'dm' -> kn ->
810             # DM_NAME
811             # DM_BLKDEVNAME
812             # DM_BLKDEVS_USED
813             # DM_SUBSYSTEM
814             # DM_DEVS_USED
815 0           $dh=$self->open_cmd_pipe_root(qw(dmsetup info -c --nameprefix --noheadings -o),
816             'name,blkdevname,blkdevs_used,subsystem,devs_used',
817             '--separator', "\n ");
818 0           my $data={};
819 0           my $dname;
820 0           while (defined(my $line=<$dh>)) {
821 0           chomp($line);
822 0 0         next if $line eq 'No devices found';
823 0 0         if ($line =~ /^DM_/) {
824 0 0         if (defined($dname)) {
825 0           $dm->{$dname}=$data;
826             } else {
827             #print STDERR "No 'N:' tag in udev entry ".($data->[0]//"")."\n";
828             }
829 0           $data={};
830 0           $dname=undef;
831             }
832 0 0         if ($line =~ /^ ?(DM_[^=]*)='(.*)'$/) {
833 0 0         if ($2 ne '') {
834 0           $data->{$1} = $2;
835             }
836 0 0         if ($1 eq 'DM_BLKDEVNAME') {
837 0           $dname = $2;
838             }
839             } else {
840 0 0         print STDERR "Ignoring '$line'".(defined($dname)?(' for '.$dname):'')."\n";
841             }
842             }
843 0 0         if (defined($dname)) {
844 0           $dm->{$dname}=$data;
845             }
846 0           close $dh;
847 0           return { 'dm' => $dm };
848             }
849              
850             1;
851              
852             ###########################################################################
853             package StorageDisplay::Collect::Partitions;
854              
855             use is_collector
856 1         18 provides => [ qw(partitions disks-no-part)],
857             requires => [ qw(lsblk udev) ],
858             depends => {
859             progs => [ 'parted' ],
860             root => 1,
861 1     1   21 };
  1         4  
862              
863             sub select {
864 0     0     my $self = shift;
865 0           my $infos = shift;
866 0   0       my $request = shift // {};
867 0           my @devs=();
868              
869 0           foreach my $kn (sort keys %{$infos->{'lsblk'}}) {
  0            
870 0           my $udev_info = $infos->{'udev'}->{$kn};
871 0           my $lsblk_info = $infos->{'lsblk'}->{$kn};
872 0 0         next if not defined($udev_info);
873 0 0 0       if (($udev_info->{'_udev_infos'}->{DEVTYPE} // '') ne 'disk') {
874 0           next;
875             }
876 0 0 0       if (($udev_info->{'_udev_infos'}->{ID_PART_TABLE_TYPE} // '') eq '') {
877 0 0 0       if (($lsblk_info->{'rm'} // 0) == 1) {
878             # removed disk (cd, ...), skipping
879 0           next;
880             }
881 0 0 0       if (($lsblk_info->{'type'} // '') eq 'loop'
      0        
      0        
882             && ($lsblk_info->{'size'} // 0) == 0) {
883             # loop device not attached
884 0           next;
885             }
886 0 0 0       if (($lsblk_info->{'type'} // '') eq 'lvm') {
887             # handled by lvm subsystem
888 0           next;
889             }
890             # disk with no partition, just get it
891 0           push @devs, $kn;
892 0           next;
893             }
894 0 0 0       if (
      0        
      0        
      0        
      0        
895             ($udev_info->{'_udev_infos'}->{ID_PART_TABLE_TYPE} // '') eq 'dos'
896             && ($udev_info->{'_udev_infos'}->{ID_PART_ENTRY_NUMBER} // '') ne ''
897             && ($udev_info->{'_udev_infos'}->{DM_TYPE} // '') eq 'raid'
898             ) {
899 0           print STDERR "I: $kn seems to be a dm-mapped extended dos partition. Skipping it as disk\n";
900             #$partitions->{$kn}->{"dos-extended"}=1;
901 0           next;
902             }
903 0           push @devs, $kn;
904             }
905 0           return @devs;
906             }
907              
908             sub collect {
909 0     0     my $self = shift;
910 0           my $infos = shift;
911 0           my $partitions;
912             my $noparts;
913 0           my $dh;
914              
915 0           my @devs=$self->select($infos);
916              
917 0           foreach my $kn (@devs) {
918 0           my $udev_info = $infos->{'udev'}->{$kn};
919 0 0 0       if (($udev_info->{'_udev_infos'}->{ID_PART_TABLE_TYPE} // '') eq '') {
920 0           $noparts->{$kn}={'no partitions' => 1};
921 0           next;
922             }
923 0           $dh=$self->open_cmd_pipe_root(qw(parted -m -s), "/dev/".$kn, qw(unit B print free));
924 0           my $state=0;
925 0           my $parted={ 'parts' => [] };
926 0           my $startline = '';
927 0           while(defined(my $line=<$dh>)) {
928 0           chomp($line);
929 0           my $multiline = 0;
930 0 0         if ($startline ne '') {
931 0           $line = $startline . $line;
932 0           $multiline = 1;
933             }
934 0 0         if ($line !~ /;$/) {
935 0           $startline = $line;
936 0           next;
937             }
938 0           $startline = '';
939 0 0         if ($state == 0) {
    0          
    0          
940 0 0         if ($line eq "BYT;") {
941 0           $state = 1;
942 0           next;
943             }
944             } elsif ($state == 1) {
945 0 0         if ($line =~ /.*:([0-9]+)B:[^:]*:[0-9]+:[0-9]+:([^:]*):(.*):;/) {
946 0           $parted->{size} = $1;
947 0           $parted->{type} = $2;
948 0           $parted->{label} = $3;
949 0           $state = 2;
950 0           next;
951             }
952             } elsif ($state == 2) {
953 0 0         if ($line =~ m/^1:([0-9]+)B:[0-9]+B:([0-9]+)B:free;$/) {
    0          
954 0           push @{$parted->{parts}}, {
  0            
955             'kind' => 'free',
956             'start' => $1,
957             'size' => $2,
958             };
959 0           next;
960             } elsif ($line =~ m/^([0-9]+):([0-9]+)B:[0-9]+B:([0-9]+)B:[^:]*:(.*):([^:]*);$/) {
961 0           push @{$parted->{parts}}, {
  0            
962             'kind' => 'part',
963             'id' => $1,
964             'start' => $2,
965             'size' => $3,
966             'label' => $4,
967             'flags' => $5,
968             };
969 0 0         if ($multiline) {
970 0           my $label = $4;
971 0 0         if ($label =~ /^Project-Id.*Content-Transfer-Encoding: 8bit$/) {
972             # workaround a parted bug with xfs partitions (at least)
973 0           $parted->{parts}->[-1]->{'label'}='';
974             }
975             }
976 0           next;
977             }
978             }
979 0           print STDERR "W: parted on $kn: Unknown line '$line'\n";
980             }
981 0           close($dh);
982 0 0         if ($state != 2) {
983 0           print STDERR "W: parted on $kn: invalid data (skipping device)\n";
984 0           next;
985             }
986 0 0         if ($udev_info->{'_udev_infos'}->{ID_PART_TABLE_TYPE} eq 'dos') {
987 0           $dh=$self->open_cmd_pipe_root(qw(parted -s), "/dev/".$kn, qw(print));
988 0           $state=0;
989 0           while(defined(my $line=<$dh>)) {
990 0           chomp($line);
991 0 0         if ($line =~ /^\s([1234]) .* extended( .*)?$/) {
992 0           $parted->{extended} = $1;
993 0           last;
994             }
995             }
996             }
997 0           $partitions->{$kn}=$parted;
998             }
999             return {
1000 0           'partitions' => $partitions,
1001             'disks-no-part' => $noparts,
1002             };
1003             }
1004              
1005             1;
1006              
1007             ###########################################################################
1008             package StorageDisplay::Collect::LVM;
1009              
1010             use is_collector
1011 1         5 provides => 'lvm',
1012             depends => {
1013             progs => [ 'lvm' ],
1014             root => 1,
1015 1     1   16 };
  1         4  
1016              
1017 1     1   6 use StorageDisplay::Collect::JSON;
  1         2  
  1         618  
1018              
1019             sub lvmjson2perl {
1020 0     0     my $self = shift;
1021 0           my $kind = shift;
1022 0           my $kstore = shift;
1023 0           my $uniq = shift;
1024 0           my $keys = shift;
1025 0   0       my $info = shift // {};
1026 0           my $json = shift;
1027             my $alldata = StorageDisplay::Collect::JSON::decode_json($json)
1028 0           ->{'report'}->[0]->{$kind};
1029 0           foreach my $data (@$alldata) {
1030 0   0       my $vg=$data->{vg_name} // die "no vg_name in data!";
1031 0           my $base = $info->{$vg}->{$kstore};
1032 0           my $hashs = [ [$info->{$vg}, $kstore] ];
1033 0 0         if (scalar(@$keys) == 1) {
    0          
1034             # force creation of $info->{$vg}->{$kstore} hash if needed
1035 0           my $dummy=$info->{$vg}->{$kstore}->{$data->{$keys->[0]}};
1036             $hashs = [ [ $info->{$vg}->{$kstore},
1037 0           $data->{$keys->[0]} ] ];
1038             } elsif (scalar(@$keys) > 1) {
1039             $hashs = [
1040             map {
1041             # force creation of $info->{$vg}->{$kstore}->{$_} hash if needed
1042 0           my $dummy=$info->{$vg}->{$kstore}->{$_}->{$data->{$_}};
  0            
1043             [ $info->{$vg}->{$kstore}->{$_},
1044 0           $data->{$_} ]
1045             } @$keys
1046             ];
1047             }
1048 0           foreach my $i (@$hashs) {
1049 0           my ($h, $k) = @$i;
1050 0 0         if ($uniq) {
1051 0 0         die "duplicate info" if defined($h->{$k});
1052 0           $h->{$k} = $data;
1053             } else {
1054 0           push @{$h->{$k}}, $data;
  0            
1055             }
1056             }
1057             }
1058 0           return $info;
1059             }
1060              
1061             sub collect {
1062 0     0     my $self = shift;
1063 0           my $dh;
1064 0           my $lvm = {};
1065              
1066             # Get all infos on LVM
1067             # 'lvm' -> 'pv'| -> Str(json)
1068 0           $dh=$self->open_cmd_pipe_root(
1069             qw(lvm pvs --units B --reportformat json --all -o),
1070             'pv_name,pv_size,pv_free,pv_used,seg_size,seg_start,segtype,pvseg_start,pvseg_size,lv_name,lv_role,vg_name',
1071             '--select', 'pv_size > 0 || vg_name != ""');
1072 0           $self->lvmjson2perl('pv', 'pvs', 0, [], $lvm, join("\n", <$dh>));
1073 0           close $dh;
1074              
1075 0           $dh=$self->open_cmd_pipe_root(
1076             qw(lvm lvs --units B --reportformat json --all -o),
1077             'lv_name,seg_size,segtype,seg_start,seg_pe_ranges,seg_le_ranges,vgname,devices,pool_lv,lv_parent');
1078 0           $self->lvmjson2perl('lv', 'lvs', 0, [], $lvm, join("\n", <$dh>));
1079 0           close $dh;
1080              
1081 0           $dh=$self->open_cmd_pipe_root(
1082             qw(lvm vgs --units B --reportformat json --all -o),
1083             'vg_name,vg_size,vg_free');
1084 0           $self->lvmjson2perl('vg', 'vgs-vg', 1, [], $lvm, join("\n", <$dh>));
1085 0           close $dh;
1086              
1087 0           $dh=$self->open_cmd_pipe_root(
1088             qw(lvm vgs --units B --reportformat json --all -o),
1089             'vg_name,pv_name,pv_size,pv_free,pv_used');
1090 0           $self->lvmjson2perl('vg', 'vgs-pv', 1, ['pv_name'], $lvm, join("\n", <$dh>));
1091 0           close $dh;
1092              
1093 0           $dh=$self->open_cmd_pipe_root(
1094             qw(lvm vgs --units B --reportformat json --all -o),
1095             'vg_name,lv_name,lv_size,data_percent,origin,pool_lv,lv_role');
1096 0           $self->lvmjson2perl('vg', 'vgs-lv', 1, ['lv_name'], $lvm, join("\n", <$dh>));
1097 0           close $dh;
1098              
1099 0           return {'lvm' => $lvm };
1100             }
1101              
1102             1;
1103              
1104             ###########################################################################
1105             package StorageDisplay::Collect::FS;
1106              
1107             use is_collector
1108 1         5 provides => 'fs',
1109             no_names => 1,
1110             depends => {
1111             progs => [ '/sbin/swapon', 'findmnt', 'stat' ],
1112             root => 1,
1113 1     1   8 };
  1         3  
1114              
1115 1     1   7 use StorageDisplay::Collect::JSON;
  1         2  
  1         694  
1116              
1117             sub collect {
1118 0     0     my $self = shift;
1119 0           my $dh;
1120              
1121             # Swap and mounted FS
1122 0           $dh=$self->open_cmd_pipe(qw(/sbin/swapon --noheadings --raw --bytes),
1123             '--show=NAME,TYPE,SIZE,USED');
1124 0           my $fs={};
1125 0           while(defined(my $line=<$dh>)) {
1126 0           chomp($line);
1127 0 0         if ($line =~ m,([^ ]+) (partition|file) ([0-9]+) ([0-9]+)$,) {
    0          
1128 0           my $info={
1129             filesystem => $1,
1130             size => $3,
1131             used => $4,
1132             free => ''.($3-$4),
1133             fstype => $2,
1134             mountpoint => 'SWAP',
1135             };
1136 0           my $dev = $1;
1137 0 0         if ($2 eq 'file') {
1138 0           my $dh2=$self->open_cmd_pipe_root(qw(findmnt -n -o TARGET --target), $1);
1139 0           my $mountpoint = <$dh2>;
1140 0 0         chomp($mountpoint) if defined($mountpoint);
1141 0           close $dh2;
1142 0           $info->{'file-mountpoint'}=$mountpoint;
1143 0           $dh2=$self->open_cmd_pipe_root(qw(stat -c %s), $1);
1144 0           my $size = <$dh2>;
1145 0           chomp($size);
1146 0           close $dh2;
1147 0           $info->{'file-size'}=$size;
1148             }
1149 0           $fs->{swap}->{$dev} = $info;
1150             } elsif ($line =~ m,([^ ]+) ([^ ]+) ([0-9]+) ([0-9]+)$,) {
1151             # skipping other kind of swap
1152             } else {
1153 0           print STDERR "W: swapon: Unknown line '$line'\n";
1154             }
1155             }
1156 0           close $dh;
1157              
1158             #$dh=$self->open_cmd_pipe_root(qw(findmnt --all --output-all --json --bytes --list));
1159             #my @json=<$dh>;
1160             #close $dh;
1161             #$fs->{flatfull} = StorageDisplay::Collect::JSON::jsonarray2perlhash(join("",@json), 'filesystems', 'id');
1162              
1163 0           $dh=$self->open_cmd_pipe_root(qw(findmnt --all --output-all --json --bytes));
1164 0           my @json=<$dh>;
1165 0           close $dh;
1166 0           my $data = StorageDisplay::Collect::JSON::decode_json(join("",@json))->{"filesystems"}->[0];
1167 0           my $rec;
1168             $rec = sub {
1169 0     0     my $node = shift;
1170             my $res = {
1171             id => $node->{id},
1172             target => $node->{target},
1173 0           };
1174 0 0         if (exists($node->{children})) {
1175 0           $res->{children} = [ map { $rec->($_) } @{$node->{children}} ];
  0            
  0            
1176             }
1177 0           return $res;
1178 0           };
1179 0           $fs->{hierarchy} = $rec->($data);
1180              
1181             $rec = sub {
1182 0     0     my $node = shift;
1183 0           my $id = $node->{id};
1184 0           $fs->{flatfull}->{$id} = $node;
1185 0 0         if (exists($node->{children})) {
1186 0           for my $child (@{$node->{children}}) {
  0            
1187 0           $rec->($child);
1188             }
1189 0           delete $node->{children};
1190             }
1191 0           };
1192 0           $rec->($data);
1193              
1194 0           return { 'fs' => $fs };
1195             }
1196              
1197             1;
1198              
1199             ###########################################################################
1200             package StorageDisplay::Collect::LUKS;
1201              
1202             use is_collector
1203 1         8 provides => 'luks',
1204             requires => [ qw(dm lsblk udev) ],
1205             depends => {
1206             progs => [ 'cryptsetup' ],
1207             root => 1,
1208 1     1   8 };
  1         2  
1209              
1210             sub select {
1211 0     0     my $self = shift;
1212 0           my $infos = shift;
1213 0   0       my $request = shift // {};
1214 0           my @devs=();
1215              
1216 0           my $dh;
1217 0           foreach my $kn (sort keys %{$infos->{'lsblk'}}) {
  0            
1218 0           my $udev_info = $infos->{'udev'}->{$kn};
1219 0 0         next if not defined($udev_info);
1220 0 0 0       if (($udev_info->{'_udev_infos'}->{ID_FS_TYPE} // '') ne 'crypto_LUKS') {
1221 0           next;
1222             }
1223 0           push @devs, $kn;
1224             }
1225 0           return @devs;
1226             }
1227              
1228             sub collect {
1229 0     0     my $self = shift;
1230 0           my $infos = shift;
1231 0           my $dh;
1232 0           my $luks={};
1233              
1234 0           my @devs=$self->select($infos);
1235              
1236             my $decrypted={
1237             map {
1238             $_->{DM_BLKDEVS_USED} => $_->{DM_BLKDEVNAME}
1239 0           } grep {
1240 0   0       ($_->{DM_SUBSYSTEM} // '') eq 'CRYPT'
1241 0           } values(%{$infos->{dm}})
  0            
1242             };
1243              
1244 0           foreach my $dev (@devs) {
1245 0           $dh=$self->open_cmd_pipe_root(
1246             qw(cryptsetup luksDump), '/dev/'.$dev);
1247 0           my $l={};
1248 0           my $luks_header=0;
1249 0           while(defined(my $line=<$dh>)) {
1250 0           chomp($line);
1251 0 0         if ($line =~ /^LUKS header information/) {
    0          
1252 0           $luks_header=1;
1253             } elsif ($line =~ /^Version:\s*([^\s]*)$/) {
1254 0           $l->{VERSION} = $1;
1255             }
1256             }
1257 0           close $dh;
1258 0 0         if ($luks_header) {
1259 0           $l->{decrypted} = $decrypted->{$dev};
1260 0           $luks->{$dev} = $l;
1261             }
1262             }
1263              
1264 0           return { 'luks' => $luks };
1265             }
1266              
1267             1;
1268              
1269             ###########################################################################
1270             package StorageDisplay::Collect::MD;
1271              
1272             use is_collector
1273 1         11 provides => 'md',
1274             requires => [ qw(dm lsblk udev) ],
1275             depends => {
1276             files => [ '/proc/mdstat' ],
1277             progs => [ 'mdadm' ],
1278             root => 1,
1279 1     1   9 };
  1         2  
1280              
1281             sub names_avail {
1282 0     0     my $self = shift;
1283 0           my $infos = shift;
1284 0           my @devs=();
1285              
1286 0           my $dh=$self->open_file('/proc/mdstat');
1287 0           while (defined(my $line=<$dh>)) {
1288 0           chomp($line);
1289 0 0         next if ($line =~ /^Personalities/);
1290 0 0         next if ($line =~ /^unused devices/);
1291 0 0         next if ($line =~ /^\s/);
1292 0           push @devs, ((split(/\s/, $line))[0]);
1293             }
1294 0           close $dh;
1295 0           return @devs;
1296             }
1297              
1298             sub collect {
1299 0     0     my $self = shift;
1300 0           my $infos = shift;
1301 0   0       my @devs = @{ shift // [ $self->select($infos) ] };
  0            
1302 0           my $dh;
1303 0           my $md={};
1304              
1305 0           foreach my $dev (@devs) {
1306 0           $dh=$self->open_cmd_pipe_root(
1307             qw(mdadm --misc --detail), '/dev/'.$dev);
1308 0           my $l={};
1309 0           my $container=0;
1310 0           while(defined(my $line=<$dh>)) {
1311 0           chomp($line);
1312 0 0         if ($line =~ /^\s*Array Size :\s*([0-9]+)\s*\(/) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
1313 0           $l->{'array-size'} = $1*1024;
1314             } elsif ($line =~ /^\s*Used Dev Size :\s*([0-9]+)\s*\(/) {
1315 0           $l->{'used-dev-size'} = $1*1024;
1316             } elsif ($line =~ /^\s*Raid Level :\s*([^\s].*)/) {
1317 0           $l->{'raid-level'} = $1;
1318 0 0         if ($1 eq 'container') {
1319 0           $l->{'raid-container'} = 1;
1320 0           $container = 1;
1321             }
1322             } elsif ($line =~ /^\s*State : \s*([^\s].*)/) {
1323 0           $l->{'raid-state'} = $1;
1324             } elsif ($line =~ /^\s*Version : \s*([^\s].*)/) {
1325 0           $l->{'raid-version'} = $1;
1326             } elsif ($line =~ /^\s*Name : \s*([^\s]+)\s*/) {
1327 0           $l->{'raid-name'} = $1;
1328             } elsif ($line =~ /^\s*Member Arrays : \s*([^\s]+.*[^\s])\s*/) {
1329 0           $l->{'raid-member-arrays'} = [ split(/ +/, $1) ];
1330             } elsif ($line =~ /^\s*Container : \s*([^\s]+), member ([0-9]+)\s*/) {
1331 0           $l->{'raid-container-device'} = $1;
1332 0           $l->{'raid-container-member'} = $2;
1333             } elsif ($line =~ /^\s*Number\s*Major\s*Minor\s*RaidDevice(\s*State)?/) {
1334 0           last;
1335             }
1336             }
1337              
1338 0           my $raid_id = 0;
1339 0           while(defined(my $line=<$dh>)) {
1340 0           chomp($line);
1341 0 0 0       if ((! $container)
    0 0        
    0          
1342             && $line =~ /^\s*([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9-]+)\s+([^\s].*[^\s])\s+([^\s]+)$/) {
1343 0           $l->{'devices'}->{$6} = {
1344             state => $5,
1345             raiddevice => $4,
1346             };
1347             } elsif ($container
1348             && $line =~ /^\s*(-)\s+([0-9]+)\s+([0-9]+)\s+(-)\s+([^\s]+)$/) {
1349 0           $l->{'devices'}->{$5} = {
1350             raiddevice => $raid_id++,
1351             };
1352             } elsif ($line =~ /^\s*$/) {
1353             } else {
1354 0           print STDERR "W: mdadm on $dev: Unknown line '$line'\n";
1355             }
1356             }
1357 0           close $dh;
1358 0           $md->{$dev} = $l;
1359             }
1360              
1361 0           return { 'md' => $md };
1362             }
1363              
1364             1;
1365              
1366             ###########################################################################
1367             package StorageDisplay::Collect::LSI::Sas2ircu;
1368              
1369             use is_collector
1370 1         6 provides => 'lsi-sas-ircu',
1371             depends => {
1372             progs => [ 'sas2ircu' ],
1373             root => 1,
1374 1     1   9 };
  1         2  
1375              
1376             sub select {
1377 0     0     my $self = shift;
1378 0           my $infos = shift;
1379 0   0       my $request = shift // {};
1380 0           my @devs=();
1381              
1382 0           my $dh;
1383 0           $dh=$self->open_cmd_pipe_root(qw(sas2ircu LIST));
1384 0           my $state=0;
1385 0           my $nodata=0;
1386 0           while (defined(my $line=<$dh>)) {
1387 0           chomp($line);
1388 0 0         if ($state == 0) {
    0          
    0          
1389 0 0         $nodata=1 if $line eq 'SAS2IRCU: MPTLib2 Error 1';
1390 0 0         next if $line !~ /^[\s-]*-[\s-]*$/;
1391 0           $state = 1;
1392             } elsif ($state == 1) {
1393 0 0         if ($line =~ /^SAS2IRCU:/) {
    0          
1394 0 0         if ($line ne 'SAS2IRCU: Utility Completed Successfully.') {
1395 0           print STDERR "W: sas2ircu: $line\n";
1396             }
1397 0           $state = 2;
1398             } elsif ($line =~ /^\s*([0-9]+)\s+/) {
1399 0           push @devs, $1;
1400             } else {
1401 0           print STDERR "E: sas2ircu: unknown line: $line\n";
1402             }
1403             } elsif ($state == 2) {
1404 0           print STDERR "W: sas2ircu: $line\n";
1405             }
1406             }
1407 0 0         if ($state != 2) {
1408 0 0 0       if ($state != 0 || $nodata != 1) {
1409 0           print STDERR "E: sas2ircu: Cannot parse data\n";
1410             }
1411             }
1412 0           close $dh;
1413 0           return @devs;
1414             }
1415              
1416             sub parse {
1417 0     0     my $parser = shift;
1418 0           my $closure = shift;
1419 0   0       my $res = shift // {};
1420              
1421             }
1422              
1423             my %name = (
1424             'Size (in MB)' => 'size-mb',
1425             'Volume ID' => 'id',
1426             'Volume wwid' => 'wwid',
1427             'Status of volume' => 'status',
1428             );
1429              
1430             sub collect {
1431 0     0     my $self = shift;
1432 0           my $infos = shift;
1433 0           my $dh;
1434 0           my $lsi={};
1435              
1436 0           my @devs=$self->select($infos);
1437              
1438              
1439             my $parse_section = sub {
1440 0     0     my $self = shift;
1441 0           my $line = shift;
1442 0 0         if ($line eq 'Controller information') {
    0          
    0          
    0          
    0          
1443             #$self->push_new_section->($parse_controller, $closure_controller);
1444             } elsif ($line eq 'IR Volume information') {
1445             #return (1, $parse_volumes);
1446             } elsif ($line eq 'Physical device information') {
1447             #return (1, $parse_phydev);
1448             } elsif ($line eq 'Enclosure information') {
1449             #return (1, $parse_phydev);
1450             } elsif ($line =~ /SAS2IRCU:/) {
1451 0 0 0       if ($line eq 'SAS2IRCU: Command DISPLAY Completed Successfully.'
1452             or $line eq 'SAS2IRCU: Utility Completed Successfully.') {
1453             } else {
1454 0           print STDERR "W: sas2ircu: $line\n";
1455             }
1456             } else {
1457             #if (scalar(keys %$l) != 0) {
1458             # print STDERR "W: sas2ircu: unknown line: $line\n";
1459             #}
1460             }
1461 0           return 1;
1462 0           };
1463              
1464              
1465 0           foreach my $dev (@devs) {
1466 0           $dh=$self->open_cmd_pipe_root('sas2ircu', $dev, 'DISPLAY');
1467 0           my $l={};
1468 0           my $state = 0;
1469 0           my $wwid = {};
1470 0           my $guid = {};
1471              
1472 0           my $data = undef;
1473 0           my $secdata = undef;
1474              
1475 0     0     my $closure=sub {} ;
1476 0     0     my $subclosure=sub {} ;
1477 0           while(defined(my $line=<$dh>)) {
1478 0           chomp($line);
1479 0 0         next if $line =~ /^[\s-]*$/;
1480 0 0 0       if ($line =~ /^(Controller) information$/
    0 0        
    0          
    0          
    0          
    0          
    0          
    0          
1481             || $line =~ /^(Enclosure) information$/) {
1482 0           my $section = lc($1);
1483 0           $subclosure->($data);
1484 0           $closure->($data);
1485 0           $data = {};
1486 0     0     $subclosure = sub {};
1487             $closure = sub {
1488 0     0     my $curdata = shift;
1489 0 0         if (exists($l->{$section})) {
1490 0           print STDERR "E: sas2ircu: duplicate section: $line\n";
1491             }
1492 0           $l->{$section}=$curdata;
1493 0           return {};
1494 0           };
1495 0           $state=10;
1496             } elsif ($line =~ /^IR (Volume) information$/
1497             || $line =~ /^Physical (device) information$/) {
1498 0           my $section = lc($1).'s';
1499 0           $subclosure->($data);
1500 0           $closure->($data);
1501 0           $secdata=[];
1502 0     0     $subclosure = sub { };
1503             $closure=sub {
1504 0     0     my $data = shift;
1505 0 0         if (exists($l->{$section})) {
1506 0           print STDERR "E: sas2ircu: duplicate section: $line\n";
1507             }
1508 0           $l->{$section}=$secdata;
1509             return
1510 0           };
  0            
1511             } elsif ($line =~ /^IR volume ([^\s])+$/) {
1512 0           my $name = $1;
1513 0           $subclosure->($data);
1514 0           $data = {
1515             name => $name,
1516             };
1517             $subclosure = sub {
1518 0     0     my $data = shift;
1519 0           push @$secdata, $data;
1520 0           };
1521             } elsif ($line =~ /^Device is a Hard disk$/) {
1522 0           $subclosure->($data);
1523 0           $data = {};
1524             $subclosure = sub {
1525 0     0     my $data = shift;
1526 0           push @$secdata, $data;
1527 0           };
1528             } elsif ($line =~ /^Initiator at ID .*$/) {
1529             } elsif ($line =~ /^SAS2IRCU: .* Completed Successfully.$/) {
1530             } elsif ($line =~ /^[^\s]/) {
1531 0 0         if ($state != 0) {
1532 0           print STDERR "W: sas2ircu: unknown line: $line\n";
1533             }
1534             } elsif ($line =~ /^\s+([^\s][^:]*[^\s])\s*:\s+([^\s].*)$/) {
1535 0           my $k = $1;
1536 0           my $v = $2;
1537 0 0         if ($k =~ m,^PHY\[([^\]]+)\] Enclosure#/Slot#,) {
    0          
1538 0           my $phy=$1;
1539 0           my ($e, $s) = split(':', $v);
1540 0           $data->{PHY}->{$phy} = { enclosure => $e, slot => $s };
1541 0           next;
1542             } elsif ($k eq 'Size (in MB)/(in sectors)') {
1543 0           my ($s1, $s2) = split('/', $v);
1544 0           $data->{'size-mb'}=$s1;
1545 0           $data->{'size-s'}=$s2;
1546 0           $data->{'size'}=$s2 * 512;
1547 0           next;
1548             }
1549 0   0       $k = $name{$k} // $k;
1550 0           $k =~ s/\s*[#]//;
1551 0           $k = lc($k);
1552 0           $k =~ s/\s+/-/g;
1553 0 0         if ($k eq 'guid') {
    0          
1554 0           $guid->{$v}=1;
1555             } elsif ($k eq 'wwid') {
1556 0           $wwid->{$v}=1;
1557             }
1558 0           $data->{$k}=$v;
1559             }
1560             }
1561 0           $subclosure->($data);
1562 0           $closure->($data);
1563 0           close $dh;
1564 0           $dh=$self->open_cmd_pipe(qw(find /sys/devices -name sas_address));
1565 0           my @lines=<$dh>;
1566 0           for my $line (sort @lines) {
1567 0           chomp($line);
1568 0 0         my $dh2 = $self->open_file($line)
1569             or die "Cannot open $line: $!\n";
1570 0           my $addr=<$dh2>;
1571 0           close $dh2;
1572 0           chomp($addr);
1573 0           $addr =~ s/^0x//;
1574 0 0         if (defined($wwid->{$addr})) {
1575 0           my $dir = $line;
1576 0           $dir =~ s/sas_address/block/;
1577 0           my $dh3 = $self->open_cmd_pipe('ls', '-1', $dir);
1578 0           my @dirs=<$dh3>;
1579 0           close($dh3);
1580 0 0         if (scalar(@dirs) != 1) {
1581 0           print STDERR "E: sas2ircu: bad number of block devices for $addr\n";
1582             } else {
1583 0           chomp($l->{wwid}->{$addr} = $dirs[0]);
1584             }
1585             }
1586             }
1587 0           $lsi->{$dev} = $l;
1588             }
1589              
1590 0           return { 'lsi-sas-ircu' => $lsi };
1591             }
1592              
1593             1;
1594              
1595             ###########################################################################
1596             package StorageDisplay::Collect::LSI::Megacli;
1597              
1598             use is_collector
1599 1         5 provides => 'lsi-megacli',
1600             depends => {
1601             progs => [ 'megaclisas-status', 'megacli' ],
1602             root => 1,
1603 1     1   8 };
  1         4  
1604              
1605             sub select {
1606 0     0     my $self = shift;
1607 0           my $infos = shift;
1608 0   0       my $request = shift // {};
1609 0           my @devs=();
1610              
1611 0           my $dh;
1612 0           $dh=$self->open_cmd_pipe_root(qw(megacli -adpCount -NoLog));
1613 0           while (defined(my $line=<$dh>)) {
1614 0           chomp($line);
1615 0 0         next if $line !~ /^Controller Count:\s*([0-9]+)\.?\s*$/;
1616 0           my $nb_controllers = $1;
1617 0           for (my $i=0; $i<$nb_controllers; $i++) {
1618 0           push @devs, $i;
1619             }
1620 0           close $dh;
1621 0           return @devs;
1622             }
1623 0           print STDERR "E: megacli: cannot find the number of controllers, assuming 0\n";
1624 0           close $dh;
1625 0           return @devs;
1626             }
1627              
1628             sub parse {
1629 0     0     my $parser = shift;
1630 0           my $closure = shift;
1631 0   0       my $res = shift // {};
1632              
1633             }
1634              
1635             sub interleave {
1636 0     0     my @lists = map [@$_], @_;
1637 0           my @res;
1638 0           while (my $list = shift @lists) {
1639 0 0         if (@$list) {
1640 0           push @res, shift @$list;
1641 0           push @lists, $list;
1642             }
1643             }
1644 0 0         wantarray ? @res : \@res;
1645             }
1646              
1647             sub collect {
1648 0     0     my $self = shift;
1649 0           my $infos = shift;
1650 0           my $dh;
1651              
1652 0           my @devs=$self->select($infos);
1653              
1654 0           my $megacli={ map { $_ => {} } @devs };
  0            
1655              
1656 0           $dh=$self->open_cmd_pipe_root('megaclisas-status');
1657              
1658 0           my $section_name;
1659             my @headers;
1660 0           while(defined(my $line=<$dh>)) {
1661 0           chomp($line);
1662 0 0         next if $line =~ /^\s*$/;
1663 0 0         if ($line =~ /^-- (.*) [Ii]nformation(s)?(\s*--)?\s*$/) {
    0          
    0          
    0          
    0          
    0          
1664 0           $section_name=$1;
1665 0 0         if ($section_name =~ /Disk/) {
1666 0           $section_name = 'Disk';
1667             }
1668             } elsif ($line =~ /^--\s*(ID\s*|.*[^\s])\s*$/) {
1669 0           @headers = split(/\s*[|]\s*/, $1);
1670             } elsif ($line =~ /^(c([0-9]+)(\s|u).*[^\s])\s*$/) {
1671 0           my $idc = $2;
1672 0 0         next if not exists($megacli->{$idc});
1673 0           my @infos = split(/\s*[|]\s*/, $1);
1674 0 0         if (scalar(@infos) != scalar(@headers)) {
1675 0           print STDERR "E: megaclisas-status: invalid number of information: $line\n";
1676 0           next;
1677             }
1678 0           my $infos = { interleave(\@headers, \@infos) };
1679 0           my $id = $infos->{ID};
1680 0 0         if ($section_name eq 'Disk') {
1681 0           $id = $infos->{'Slot ID'};
1682             }
1683 0 0         if (exists($megacli->{$idc}->{$section_name}->{$id})) {
1684 0           print STDERR "E: megaclisas-status: duplicate info for $id: $line\n";
1685             }
1686 0           $megacli->{$idc}->{$section_name}->{$id}=$infos;
1687             } elsif ($line =~ /^There is at least one disk\/array in a NOT OPTIMAL state.$/) {
1688             # skip
1689             } elsif ($line =~ /^RAID ERROR - Arrays: OK:[0-9]+ Bad:[0-9]+ - Disks: OK:[0-9]+ Bad:[0-9]+$/) {
1690             # skip
1691             } elsif ($line =~ /^No MegaRAID or PERC adapter detected on your system!$/) {
1692             # skip
1693             } else {
1694 0           print STDERR "E: megaclisas-status: invalid line: $line\n";
1695             }
1696             }
1697 0           close($dh);
1698              
1699 0           for my $dev (@devs) {
1700 0           $dh=$self->open_cmd_pipe_root(qw(megacli -PDList), "-a$dev");
1701 0           my $cur_enc;
1702             my $cur_slot;
1703 0           my $cur_size;
1704             my $get_cur_disk=sub {
1705 0     0     my $slot_id = "[$cur_enc:$cur_slot]";
1706 0 0         if (not exists($megacli->{$dev}->{'Disk'}->{$slot_id})) {
1707 0           print STDERR "E: missing disk with slot $slot_id\n";
1708 0           return;
1709             }
1710 0           return $megacli->{$dev}->{'Disk'}->{$slot_id};
1711 0           };
1712 0           while(defined(my $line=<$dh>)) {
1713 0           chomp($line);
1714 0 0         next if $line =~ /^\s*$/;
1715 0 0         next if $line eq "Adapter #$dev";
1716 0 0         if ($line eq "^Adapter") {
1717 0           print STDERR "W: megacli: strange adapter for #$dev: $line\n";
1718 0           next;
1719             }
1720 0 0         if ($line =~ /^Enclosure Device ID: *([0-9]+|N\/A) *$/) {
1721 0           $cur_enc=$1;
1722 0 0         $cur_enc='' if $cur_enc eq 'N/A';
1723 0           $cur_slot=undef;
1724 0           next;
1725             }
1726 0 0         if ($line =~ /^Enclosure Device ID: *(.*) *$/) {
1727 0           print STDERR "W: megacli: strange enclosure device ID '$1'\n";
1728             }
1729 0 0         if ($line =~ /^Slot Number: *([0-9]+) *$/) {
1730 0 0 0       if (defined($cur_slot) || not defined($cur_enc)) {
1731 0           print STDERR "W: megacli: strange state when finding slot number $1\n";
1732             }
1733 0           $cur_slot=$1;
1734 0           next;
1735             }
1736 0 0         if ($line =~ /^Array *#: *([0-9]+) *$/) {
1737 0   0       my $d=$get_cur_disk->() // next;
1738 0 0         if ($d->{'ID'} !~ /^c[0-9]+uXpY$/) {
1739 0           my $slot_id = $d->{'Slot ID'};
1740 0           print STDERR "E: slot $slot_id has a strange ID\n";
1741 0           next;
1742             }
1743 0           $d->{'ID'} =~ s/X/$dev/;
1744             }
1745 0 0         if ($line =~ /^Coerced Size:.*\[(0x[0-9a-f]+) *Sectors\]/i) {
1746 0   0       my $d=$get_cur_disk->() // next;
1747 0           $d->{'# sectors'} = $1;
1748             }
1749 0 0         if ($line =~ /^Sector Size: *([0-9]+)$/i) {
1750 0   0       my $d=$get_cur_disk->() // next;
1751 0 0         $d->{'sector size'} = ($1==0)?512:$1;
1752             }
1753             }
1754 0           close($dh);
1755             }
1756              
1757 0           return { 'lsi-megacli' => $megacli };
1758             }
1759              
1760             1;
1761              
1762             ###########################################################################
1763             package StorageDisplay::Collect::Libvirt;
1764              
1765             use is_collector
1766 1         6 provides => 'libvirt',
1767             depends => {
1768             progs => [ 'virsh' ],
1769             root => 1,
1770 1     1   9 };
  1         2  
1771              
1772             sub select {
1773 0     0     my $self = shift;
1774 0           my $infos = shift;
1775 0   0       my $request = shift // {};
1776 0           my @vms=();
1777              
1778 0           my $dh=$self->open_cmd_pipe_root(qw(virsh list --all --name));
1779 0           while(defined(my $line=<$dh>)) {
1780 0           chomp($line);
1781 0 0         next if $line =~ /^\s*$/;
1782 0           push @vms, $line;
1783             }
1784 0           close $dh;
1785 0           @vms = sort @vms;
1786 0           return @vms;
1787             }
1788              
1789             sub collect {
1790 0     0     my $self = shift;
1791 0           my $infos = shift;
1792 0           my $dh;
1793 0           my $libvirt={};
1794              
1795 0           my @vms=$self->select($infos);
1796              
1797 0           foreach my $vm (@vms) {
1798 0           $dh=$self->open_cmd_pipe_root(qw(virsh domstate), $vm);
1799 0           my $v={ name => $vm };
1800 0           while(defined(my $line=<$dh>)) {
1801 0           chomp($line);
1802 0 0         if ($line =~ /running/) {
1803 0           $v->{state} = 'running';
1804 0           last;
1805             }
1806             }
1807 0           close $dh;
1808 0           $dh=$self->open_cmd_pipe_root(qw(virsh domblklist --details), $vm);
1809 0           while(defined(my $line=<$dh>)) {
1810 0           chomp($line);
1811 0 0         next if $line =~ /^[\s-]*$/;
1812 0           my @info=split(' ', $line);
1813 0 0 0       next if ($info[0]//'') eq 'Type';
1814             #next if ($info[0]//'') ne 'block';
1815 0 0         next if $info[3] eq '-';
1816 0 0         if (scalar(@info) != 4) {
1817 0           print STDERR "W: libvirt on $vm: Unknown line '$line'\n";
1818 0           next;
1819             }
1820 0           $v->{'blocks'}->{$info[3]} = {
1821             type => $info[0],
1822             device => $info[1],
1823             target => $info[2],
1824             source => $info[3],
1825             };
1826 0 0         if ($info[0] eq 'file') {
    0          
1827 0           StorageDisplay::Collect::File::select_file($info[3]);
1828             } elsif ($info[0] eq 'block') {
1829             } else {
1830 0           print STDERR "W: unknown VM device type: $info[0]\n";
1831             }
1832             }
1833 0           close $dh;
1834 0 0 0       if ($v->{state}//'' eq 'running') {
1835             # trying to get infos from QEMU guest agent
1836 0           $dh=$self->open_cmd_pipe_root(qw(virsh guestinfo --hostname --disk), $vm);
1837 0           my $curdisk='';
1838 0           my $curdiskinfo={};
1839 0           while(defined(my $line=<$dh>)) {
1840 0           chomp($line);
1841 0 0 0       if ($curdisk ne '' && $line !~ /^disk\.$curdisk\./) {
1842 0 0 0       if (exists($curdiskinfo->{name}) && exists($curdiskinfo->{alias})) {
1843             #print STDERR "W: libvirt guestagent on $vm: adding ".$curdiskinfo->{alias}."\n";
1844 0           $v->{ga}->{disks}->{$curdiskinfo->{alias}}=$curdiskinfo;
1845             }
1846 0           $curdiskinfo={};
1847 0           $curdisk = '';
1848             }
1849 0 0         next if $line =~ /^[\s-]*$/;
1850 0 0         if ($line !~ m/^([^:\s]+)\s*: (.*)$/) {
1851 0           print STDERR "W: libvirt guestagent on $vm: Unknown line '$line'\n";
1852             }
1853 0           my $key=$1;
1854 0           my $value=$2;
1855 0 0         if ($key eq 'hostname') {
1856 0           $v->{ga}->{hostname} = $value;
1857 0           next;
1858             }
1859 0 0         if ($key =~ /^disk\.([0-9]+)\./) {
1860 0           $curdisk = $1;
1861 0 0         if ($key =~ /\.(name|alias)$/) {
1862 0           $curdiskinfo->{$1} = $value;
1863             }
1864             }
1865             }
1866 0           close $dh;
1867 0 0         if ($curdisk ne '') {
1868             # the last empty line should have set $curdisk to ''
1869 0           print STDERR "W: libvirt guestagent on $vm: end-before-end '$curdisk'\n";
1870             }
1871             }
1872 0           $libvirt->{$vm} = $v;
1873             }
1874              
1875 0           return { 'libvirt' => $libvirt };
1876             }
1877              
1878             1;
1879              
1880             ###########################################################################
1881             package StorageDisplay::Collect::Loops;
1882              
1883             use is_collector
1884 1         5 provides => 'loops',
1885             depends => {
1886             progs => [ 'losetup' ],
1887             root => 1,
1888 1     1   8 };
  1         3  
1889              
1890 1     1   7 use StorageDisplay::Collect::JSON;
  1         2  
  1         421  
1891             sub losetupjson2perl {
1892 0     0     my $self = shift;
1893 0           my $json = shift;
1894 0           return StorageDisplay::Collect::JSON::jsonarray2perlhash($json, 'loopdevices', 'name');
1895             }
1896              
1897             sub select {
1898 0     0     my $self = shift;
1899 0           my $infos = shift;
1900 0   0       my $request = shift // {};
1901 0           my @loops;
1902              
1903 0           my $dh=$self->open_cmd_pipe_root(qw(losetup --output NAME));
1904 0           while(defined(my $line=<$dh>)) {
1905 0           chomp($line);
1906 0 0         next if $line =~ /^NAME$/;
1907 0           push @loops, $line;
1908             }
1909 0           close $dh;
1910 0           @loops = sort @loops;
1911 0           return @loops;
1912             }
1913              
1914             sub collect {
1915 0     0     my $self = shift;
1916 0           my $infos = shift;
1917 0           my $files={};
1918              
1919 0           my @loops=$self->select($infos);
1920              
1921 0           my $dh=$self->open_cmd_pipe_root(qw(losetup --output-all --list --json));
1922 0           my $all_loops = $self->losetupjson2perl(join(' ', <$dh>));
1923 0           close $dh;
1924              
1925 0           my $loops={};
1926 0           foreach my $loop (@loops) {
1927 0           $loops->{$loop} = $all_loops->{$loop};
1928 0           my $filename = $loops->{$loop}->{'back-file'};
1929 0           StorageDisplay::Collect::File::select_file($filename);
1930 0 0         if ($filename =~ /(.*) \(deleted\)/) {
1931 0           StorageDisplay::Collect::File::select_file($1);
1932             }
1933             }
1934 0           return { 'loops' => $loops };
1935             }
1936              
1937             1;
1938              
1939             ###########################################################################
1940             package StorageDisplay::Collect::File;
1941              
1942             # This collector must be the last one as it collects info on requested
1943             # files by other collectors.
1944              
1945             use is_collector
1946 1         7 provides => 'files',
1947             depends => {
1948             progs => [ 'findmnt' ],
1949             root => 1,
1950 1     1   10 };
  1         9  
1951              
1952 1     1   8 use StorageDisplay::Collect::JSON;
  1         3  
  1         51  
1953 1     1   682 use Data::Dumper;
  1         6559  
  1         516  
1954             sub statjson2perl {
1955 0     0     my $self = shift;
1956 0           my $json = shift;
1957 0           return StorageDisplay::Collect::JSON::jsonarray2perlhash($json, 'stats', 'name');
1958             }
1959              
1960             my $files = {};
1961              
1962             sub select_file {
1963 0     0     my $filename = shift;
1964 0           $files->{$filename} = 1;
1965             }
1966              
1967             sub select {
1968 0     0     my $self = shift;
1969 0           my $infos = shift;
1970 0   0       my $request = shift // {};
1971 0           my @files = sort keys %{$files};
  0            
1972 0           return @files;
1973             }
1974              
1975             sub collect {
1976 0     0     my $self = shift;
1977 0           my $infos = shift;
1978 0           my $files={};
1979              
1980 0           my @files=$self->select($infos);
1981 0           my @json;
1982             my @present;
1983              
1984 0 0         if (scalar(@files) == 0) {
1985 0           return { 'files' => [] };
1986             }
1987 0           my $dh0=$self->open_cmd_pipe_root(
1988             qw(perl -e),
1989             'my '.Dumper(\@files).
1990             'grep { if (-e $_) { print "OK\n" } else { print "NACK\n" }; 1 } @{$VAR1};');
1991 0           my @res = <$dh0>;
1992 0           close $dh0;
1993 0 0         if (scalar(@files) != scalar(@res)) {
1994 0           print STDERR "WARNING: Something goes wrong with stat files\n";
1995             } else {
1996 0           for(my $i=0; $i<scalar(@res); $i++) {
1997 0 0         if ($res[$i] =~ /^OK/) {
1998 0           push @present, $files[$i];
1999             } else {
2000 0           push @json, '{ "name":"'.$files[$i].'", "deleted":true },'."\n";
2001             }
2002             }
2003             }
2004 0 0         if (scalar(@present) > 0) {
2005 0           my $dh=$self->open_cmd_pipe_root(
2006             # '%[LH][rd]' do not exist on old stat tool
2007             qw(stat -c),
2008             '{ "name":"%n", "deleted":false, "size":%s, "inode":%i,'.
2009             ' "permission":"0%a", "mode":"0x%f", "blocks":%b,'.
2010             ' "blocksize":%B, "st_dev":%d, "hardlinks":%h,'.
2011             ' "mountpoint":"%m", "st_rdev":"%r", "st_special":"%t:%T" },',
2012             @present);
2013 0           push @json, <$dh>;
2014             }
2015 0           my $json=join("", @json);
2016 0           chomp($json);
2017 0           $json =~ s/,$//;
2018             #print STDERR "json=$json\n";
2019 0           return { 'files' => $self->statjson2perl('{ "stats": [ '.$json."\n] }") };
2020             }
2021              
2022             # This collector must be the last one as it collects info on requested
2023             # files by other collectors.
2024              
2025             1;
2026              
2027             ###########################################################################
2028             ###########################################################################
2029             ###########################################################################
2030             ###########################################################################
2031             package StorageDisplay::Collect;
2032              
2033             sub dump_collect {
2034 0   0 0 0   my $reader = shift // 'Local';
2035 0           my $collector = __PACKAGE__->new($reader, @_);
2036              
2037 0           my $info = $collector->collect();
2038              
2039 1     1   8 use Data::Dumper;
  1         3  
  1         135  
2040             # sort keys
2041 0           $Data::Dumper::Sortkeys = 1;
2042 0           $Data::Dumper::Purity = 1;
2043              
2044 0           print Dumper($info);
2045             #print Dumper(\%INC);
2046             }
2047              
2048             1;
2049              
2050             __END__
2051              
2052             =pod
2053              
2054             =encoding UTF-8
2055              
2056             =head1 NAME
2057              
2058             StorageDisplay::Collect - modules required to collect data.
2059              
2060             =head1 VERSION
2061              
2062             version 2.04
2063              
2064             Main class, allows one to register collectors and run them
2065             (through the collect method)
2066              
2067             Collectors will be registered when their class is loaded
2068              
2069             Wrapper around JSON:PP as old versions do not support the
2070             boolean_value method.
2071              
2072             Base (abstract) class to run command to collect infos
2073              
2074             Only one instance should be created
2075              
2076             # sub classes must implement open_cmd_pipe and open_cmd_pipe_root
2077              
2078             Run commands locally
2079              
2080             Run commands through SSH
2081              
2082             Record commands
2083              
2084             Used to declare a class to be a collector.
2085              
2086             The collector will be registered
2087              
2088             Base class for collectors
2089              
2090             =head1 AUTHOR
2091              
2092             Vincent Danjean <Vincent.Danjean@ens-lyon.org>
2093              
2094             =head1 COPYRIGHT AND LICENSE
2095              
2096             This software is copyright (c) 2014-2023 by Vincent Danjean.
2097              
2098             This is free software; you can redistribute it and/or modify it under
2099             the same terms as the Perl 5 programming language system itself.
2100              
2101             =cut