File Coverage

blib/lib/StorageDisplay/Collect.pm
Criterion Covered Total %
statement 890 1113 79.9
branch 241 390 61.7
condition 54 119 45.3
subroutine 101 124 81.4
pod 3 30 10.0
total 1289 1776 72.5


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