| 152 | rodolico | 1 | #!/usr/bin/env perl
 | 
        
           |  |  | 2 | use warnings;
 | 
        
           | 165 | rodolico | 3 | use strict;
 | 
        
           |  |  | 4 | use Data::Dumper;
 | 
        
           | 152 | rodolico | 5 |   | 
        
           | 169 | rodolico | 6 | # Description: Use smartctl, lsblk and geom to get disk information
 | 
        
           |  |  | 7 | # will decode lsblk first. Then use geom to get information on BSD systems
 | 
        
           |  |  | 8 | # finally, smartctl will be used to gather additional information.
 | 
        
           | 152 | rodolico | 9 |   | 
        
           | 169 | rodolico | 10 | # 20200224 RWR v2.1
 | 
        
           |  |  | 11 | # large re-organization of code and inclusion to get basic information from lsblk if it is on the system
 | 
        
           | 152 | rodolico | 12 |   | 
        
           | 169 | rodolico | 13 | our $VERSION = '2.1';
 | 
        
           |  |  | 14 |   | 
        
           | 152 | rodolico | 15 | # R. W. Rodolico
 | 
        
           |  |  | 16 | # grabs smart readings from all drives in system
 | 
        
           | 157 | rodolico | 17 | # note that in the case of some hardware RAID controller, will list the components of a "drive" and the drive itself,
 | 
        
           |  |  | 18 | # so you may have up to twice the number of entries than you have physical drives, for example, if you have a megaraid
 | 
        
           |  |  | 19 | # controller where each physical drive is also a single logical drive.
 | 
        
           | 152 | rodolico | 20 |   | 
        
           | 165 | rodolico | 21 | # find our location and use it for searching for libraries
 | 
        
           | 152 | rodolico | 22 | BEGIN {
 | 
        
           | 165 | rodolico | 23 |    use FindBin;
 | 
        
           |  |  | 24 |    use File::Spec;
 | 
        
           |  |  | 25 |    use lib File::Spec->catdir($FindBin::Bin);
 | 
        
           |  |  | 26 |    use library;
 | 
        
           | 152 | rodolico | 27 | }
 | 
        
           |  |  | 28 |   | 
        
           | 165 | rodolico | 29 | exit 0 unless checkDate( 'm' ); # run this only on first of month
 | 
        
           | 152 | rodolico | 30 |   | 
        
           | 169 | rodolico | 31 | my %driveDefinitions; # this will be a global that everything will put info into
 | 
        
           |  |  | 32 |   | 
        
           | 165 | rodolico | 33 | my %ignoreDriveTypes = ( # a list of drive "types" used by virtuals that we just ignore.
 | 
        
           |  |  | 34 |          'VBOX HARDDISK' => 1,
 | 
        
           |  |  | 35 |          );
 | 
        
           | 152 | rodolico | 36 |   | 
        
           |  |  | 37 |   | 
        
           | 170 | rodolico | 38 | # routine used by geom to process one block at a time.
 | 
        
           |  |  | 39 | # first parameter is a pointer to a hash to populate, the rest are
 | 
        
           |  |  | 40 | # considered to be a splice of an array that contains only one
 | 
        
           |  |  | 41 | # block. Returns the name of the device
 | 
        
           |  |  | 42 | sub doGeomBlock {
 | 
        
           |  |  | 43 |    my $hashPointer = shift;
 | 
        
           |  |  | 44 | #   die join( "\n", @_ ) . "\n";
 | 
        
           |  |  | 45 |    while ( my $line = shift ) {
 | 
        
           |  |  | 46 |       if ( $line ) {
 | 
        
           |  |  | 47 |          my ($key, $value) = split( ':', $line );
 | 
        
           |  |  | 48 |          if ( $key =~ m/^\d+\.\s+(.*)$/ ) {
 | 
        
           |  |  | 49 |             $key = $1;
 | 
        
           |  |  | 50 |          }
 | 
        
           |  |  | 51 |          $key = &trim( $key );
 | 
        
           |  |  | 52 |          $value = &trim( $value );
 | 
        
           |  |  | 53 |          $hashPointer->{$key} = $value;
 | 
        
           |  |  | 54 |       }
 | 
        
           |  |  | 55 |    }
 | 
        
           |  |  | 56 | #   die Dumper( $hashPointer );
 | 
        
           |  |  | 57 |    return $hashPointer->{'Name'} ? $hashPointer->{'Name'} : 'Unknown';
 | 
        
           |  |  | 58 | }
 | 
        
           |  |  | 59 |   | 
        
           |  |  | 60 | # grab data from geom command (BSD) and import parts of it into driveDefinitions
 | 
        
           |  |  | 61 | sub geom {
 | 
        
           |  |  | 62 |    my $line = 0;
 | 
        
           |  |  | 63 |    my $startBlock = 0;
 | 
        
           |  |  | 64 |    my $endBlock = 0;
 | 
        
           |  |  | 65 |    my @report = `geom disk list`;
 | 
        
           |  |  | 66 |    chomp @report;
 | 
        
           |  |  | 67 |   | 
        
           |  |  | 68 |    while ( $line < scalar( @report ) ) {
 | 
        
           |  |  | 69 |       #print "Working on $line\n";
 | 
        
           |  |  | 70 |       while ( $line < scalar( @report ) && $report[$line] !~ m/^Geom name:\s+(.*)$/ ) {
 | 
        
           |  |  | 71 |          $line++;
 | 
        
           |  |  | 72 |       }
 | 
        
           |  |  | 73 |       $endBlock = $line - 1;
 | 
        
           |  |  | 74 |       if ( $endBlock > $startBlock ) {
 | 
        
           |  |  | 75 |          my $thisDrive = {};
 | 
        
           |  |  | 76 |          my $key = &doGeomBlock( $thisDrive, @report[$startBlock..$endBlock] );
 | 
        
           |  |  | 77 |          # die "$key\n" . Dumper( $thisDrive );
 | 
        
           |  |  | 78 |          $key = '/dev/' . $key;
 | 
        
           |  |  | 79 |          $driveDefinitions{$key}{'Capacity'} = $thisDrive->{'Mediasize'} if defined $thisDrive->{'Mediasize'};
 | 
        
           |  |  | 80 |          if ( defined( $driveDefinitions{$key}{'Capacity'} ) && $driveDefinitions{$key}{'Capacity'} =~ m/^(\d+)/ ) {
 | 
        
           |  |  | 81 |             $driveDefinitions{$key}{'Capacity'} = $1;
 | 
        
           |  |  | 82 |          }
 | 
        
           |  |  | 83 |          $driveDefinitions{$key}{'Model'} = $thisDrive->{'descr'} if defined $thisDrive->{'descr'};
 | 
        
           |  |  | 84 |          $driveDefinitions{$key}{'Serial'} = $thisDrive->{'ident'} if defined $thisDrive->{'ident'};
 | 
        
           |  |  | 85 |          $driveDefinitions{$key}{'Sector Size'} = $thisDrive->{'Sectorsize'} if defined $thisDrive->{'Sectorsize'};
 | 
        
           |  |  | 86 |          $driveDefinitions{$key}{'Rotation'} = $thisDrive->{'rotationrate'} if defined $thisDrive->{'rotationrate'};
 | 
        
           |  |  | 87 |   | 
        
           |  |  | 88 |          $startBlock = $line;
 | 
        
           |  |  | 89 | #         die Dumper( $thisDrive );
 | 
        
           |  |  | 90 |       }
 | 
        
           |  |  | 91 |       $line++;
 | 
        
           |  |  | 92 |    }
 | 
        
           |  |  | 93 |   | 
        
           |  |  | 94 | }
 | 
        
           |  |  | 95 |   | 
        
           |  |  | 96 |   | 
        
           |  |  | 97 |   | 
        
           | 169 | rodolico | 98 | # acquires information using lsblk, if it is available
 | 
        
           |  |  | 99 | # uses global %driveDefinitions to store the results
 | 
        
           |  |  | 100 |   | 
        
           |  |  | 101 | sub lsblk {
 | 
        
           |  |  | 102 |    eval ( 'use JSON qw( decode_json );' );
 | 
        
           |  |  | 103 |    if ( $@ ) {
 | 
        
           |  |  | 104 |       warn "Could not load JSON library\n";    
 | 
        
           |  |  | 105 |       return;
 | 
        
           |  |  | 106 |    }
 | 
        
           |  |  | 107 |   | 
        
           |  |  | 108 |    my $output = qx'lsblk -bdJO 2>/dev/null';
 | 
        
           |  |  | 109 |    # older versions do not have the O option, so we'll run it without
 | 
        
           |  |  | 110 |    $output = qx'lsblk -bdJ 2>/dev/null' if $?;
 | 
        
           |  |  | 111 |    my $drives = decode_json( join( '', $output ) );
 | 
        
           |  |  | 112 |    $drives = $drives->{'blockdevices'};
 | 
        
           |  |  | 113 |    while ( my $thisDrive = shift @{$drives} ) {
 | 
        
           |  |  | 114 |       if ( $thisDrive->{'type'} eq 'disk' ) {
 | 
        
           |  |  | 115 |          my $key = '/dev/' . $thisDrive->{'name'};
 | 
        
           |  |  | 116 |          $driveDefinitions{$key}{'Capacity'} = $thisDrive->{'size'};
 | 
        
           |  |  | 117 |          $driveDefinitions{$key}{'Model'} = $thisDrive->{'model'} if defined $thisDrive->{'model'};
 | 
        
           |  |  | 118 |          $driveDefinitions{$key}{'Serial'} = $thisDrive->{'serial'} if defined $thisDrive->{'serial'};
 | 
        
           |  |  | 119 |       }
 | 
        
           |  |  | 120 |    }
 | 
        
           |  |  | 121 | }
 | 
        
           |  |  | 122 |   | 
        
           |  |  | 123 |   | 
        
           | 165 | rodolico | 124 | sub getSmartInformationReport {
 | 
        
           | 157 | rodolico | 125 |    my ($drive, $type) = @_;
 | 
        
           | 165 | rodolico | 126 |    $type = '' if ( $type =~ m/scsi/ );
 | 
        
           |  |  | 127 |    my @report = `smartctl -i $drive $type`;
 | 
        
           | 157 | rodolico | 128 |    chomp @report;
 | 
        
           | 165 | rodolico | 129 |    my %reportHash;
 | 
        
           |  |  | 130 |    for ( my $i = 0; $i < @report; $i++ ) {
 | 
        
           |  |  | 131 |       if ( $report[$i] =~ m/^(.*):(.*)$/ ) {
 | 
        
           |  |  | 132 |          $reportHash{$1} = trim($2);
 | 
        
           |  |  | 133 |       }
 | 
        
           |  |  | 134 |    }
 | 
        
           |  |  | 135 |    return \%reportHash;
 | 
        
           |  |  | 136 | } # getSmartInformationReport
 | 
        
           |  |  | 137 |   | 
        
           |  |  | 138 | sub getSmartAttributeReport {
 | 
        
           |  |  | 139 |    my ($drive, $type) = @_;
 | 
        
           | 203 | rodolico | 140 |    $type = '' if ( ! defined( $type ) || $type =~ m/scsi/ );
 | 
        
           | 165 | rodolico | 141 |    my @report = `smartctl -A $drive $type`;
 | 
        
           |  |  | 142 |    chomp @report;
 | 
        
           |  |  | 143 |    my %reportHash;
 | 
        
           |  |  | 144 |    my %headers;
 | 
        
           |  |  | 145 |    # bypass all the header information
 | 
        
           |  |  | 146 |    my $i;
 | 
        
           |  |  | 147 |    for ( $i = 0; $i < @report && $report[$i] !~ m/^ID#/; $i++ ) {}
 | 
        
           |  |  | 148 |    if ( $i < @report ) { # did we get an actual report? some drives will not give us one
 | 
        
           |  |  | 149 |       my $char = 0;
 | 
        
           |  |  | 150 |       while ( $char < length($report[$i]) ) {
 | 
        
           |  |  | 151 |          substr( $report[$i],$char ) =~ m/^([^ ]+\s*)/;
 | 
        
           |  |  | 152 |          my $header = $1;
 | 
        
           |  |  | 153 |          my $start = $char;
 | 
        
           |  |  | 154 |          my $length = length($header);
 | 
        
           |  |  | 155 |          if ( $header = &trim( $header ) ) {
 | 
        
           |  |  | 156 |             $headers{$header}{'start'} = $start;
 | 
        
           |  |  | 157 |             $headers{$header}{'length'} = $length-1;
 | 
        
           |  |  | 158 |          }
 | 
        
           |  |  | 159 |          $char += $length;
 | 
        
           |  |  | 160 |       }
 | 
        
           |  |  | 161 |       while ( ++$i < @report ) {
 | 
        
           |  |  | 162 |          last unless $report[$i];
 | 
        
           |  |  | 163 |          my $id = &trim(substr( $report[$i], $headers{'ID#'}{'start'}, $headers{'ID#'}{'length'} ));
 | 
        
           |  |  | 164 |          my $name = &trim(substr( $report[$i], $headers{'ATTRIBUTE_NAME'}{'start'}, $headers{'ATTRIBUTE_NAME'}{'length'} ));
 | 
        
           |  |  | 165 |          my $value = &trim(substr( $report[$i], $headers{'RAW_VALUE'}{'start'} ));
 | 
        
           |  |  | 166 |          $reportHash{$id}{'value'} = $value;
 | 
        
           |  |  | 167 |          $reportHash{$id}{'name'} = $name;
 | 
        
           |  |  | 168 |       }
 | 
        
           |  |  | 169 |    }
 | 
        
           |  |  | 170 |    #print Dumper( \%reportHash ); die;
 | 
        
           |  |  | 171 |    return \%reportHash;
 | 
        
           | 152 | rodolico | 172 | }
 | 
        
           |  |  | 173 |   | 
        
           |  |  | 174 |   | 
        
           | 165 | rodolico | 175 | sub getAttributes {
 | 
        
           | 169 | rodolico | 176 |    my ($drive,$type,$sectorSize) = @_;
 | 
        
           |  |  | 177 |   | 
        
           |  |  | 178 |    my $report = &getSmartAttributeReport( $drive, $type );
 | 
        
           |  |  | 179 |   | 
        
           | 165 | rodolico | 180 |    # first let's get total disk writes
 | 
        
           | 203 | rodolico | 181 |    if ( defined( $report->{'241'} ) && defined( $sectorSize ) ) {
 | 
        
           | 165 | rodolico | 182 |       $sectorSize =~ m/^(\d+)/;
 | 
        
           | 169 | rodolico | 183 |       $driveDefinitions{$drive}{'writes'} = $report->{'241'} * $sectorSize;
 | 
        
           | 165 | rodolico | 184 |    }
 | 
        
           |  |  | 185 |    # find total uptime
 | 
        
           |  |  | 186 |    if ( defined( $report->{'9'} ) ) {
 | 
        
           |  |  | 187 |       $report->{'9'}->{'value'} =~ m/^(\d+)/;
 | 
        
           | 169 | rodolico | 188 |       $driveDefinitions{$drive}{'uptime'} = $1;
 | 
        
           | 165 | rodolico | 189 |    }
 | 
        
           | 152 | rodolico | 190 | }
 | 
        
           |  |  | 191 |   | 
        
           |  |  | 192 | sub getInformation {
 | 
        
           | 169 | rodolico | 193 |    my ($drive, $type) = @_;
 | 
        
           |  |  | 194 |   | 
        
           |  |  | 195 |    my $report = &getSmartInformationReport( $drive, $type );
 | 
        
           |  |  | 196 |   | 
        
           | 152 | rodolico | 197 |    my %info;
 | 
        
           |  |  | 198 |    my %keys = ( 
 | 
        
           | 165 | rodolico | 199 |                   'Model Family'  => { 
 | 
        
           | 152 | rodolico | 200 |                                           'tag' => 'Make',
 | 
        
           |  |  | 201 |                                           'regex' => '(.*)'
 | 
        
           |  |  | 202 |                                       },
 | 
        
           | 165 | rodolico | 203 |                   'Device Model'  => { 
 | 
        
           | 152 | rodolico | 204 |                                           'tag' => 'Model',
 | 
        
           |  |  | 205 |                                           'regex' => '(.*)'
 | 
        
           |  |  | 206 |                                       },
 | 
        
           | 165 | rodolico | 207 |                   'Serial number' => { 
 | 
        
           | 157 | rodolico | 208 |                                           'tag' => 'Serial',
 | 
        
           |  |  | 209 |                                           'regex' => '(.*)'
 | 
        
           |  |  | 210 |                                       },
 | 
        
           | 165 | rodolico | 211 |                   'Serial Number' => { 
 | 
        
           | 152 | rodolico | 212 |                                           'tag' => 'Serial',
 | 
        
           |  |  | 213 |                                           'regex' => '(.*)'
 | 
        
           |  |  | 214 |                                       },
 | 
        
           | 165 | rodolico | 215 |                   'User Capacity' => { 
 | 
        
           | 152 | rodolico | 216 |                                           'tag' => 'Capacity',
 | 
        
           |  |  | 217 |                                           'regex' => '([0-9,]+)'
 | 
        
           |  |  | 218 |                                       },
 | 
        
           | 165 | rodolico | 219 |                   'Logical block size' =>{ 
 | 
        
           | 157 | rodolico | 220 |                                           'tag' => 'Sector Size',
 | 
        
           |  |  | 221 |                                           'regex' => '(\d+)'
 | 
        
           |  |  | 222 |                                       },
 | 
        
           | 165 | rodolico | 223 |                   'Sector Size'   => { 
 | 
        
           | 152 | rodolico | 224 |                                           'tag' => 'Sector Size',
 | 
        
           |  |  | 225 |                                           'regex' => '(\d+)'
 | 
        
           |  |  | 226 |                                       },
 | 
        
           | 165 | rodolico | 227 |                   'Rotation Rate' => { 
 | 
        
           | 152 | rodolico | 228 |                                           'tag' => 'Rotation',
 | 
        
           |  |  | 229 |                                           'regex' => '(.*)'
 | 
        
           | 165 | rodolico | 230 |                                       },
 | 
        
           | 152 | rodolico | 231 |                );
 | 
        
           |  |  | 232 |    foreach my $key ( keys %keys ) {
 | 
        
           | 165 | rodolico | 233 |       if ( defined( $report->{$key} ) && $report->{$key} =~ m/$keys{$key}->{'regex'}/ ) {
 | 
        
           | 169 | rodolico | 234 |          $driveDefinitions{$drive}{$keys{$key}->{'tag'}} = $1;
 | 
        
           | 152 | rodolico | 235 |       }
 | 
        
           |  |  | 236 |    }
 | 
        
           |  |  | 237 | }
 | 
        
           |  |  | 238 |   | 
        
           | 169 | rodolico | 239 | sub smartctl {
 | 
        
           |  |  | 240 |    # Get all the drives on the system
 | 
        
           | 203 | rodolico | 241 |    my %allDrives;
 | 
        
           |  |  | 242 |    eval{ 
 | 
        
           |  |  | 243 |       %allDrives = map { $_ =~ '(^[a-z0-9/]+)\s+(.*)\#'; ($1,$2) } `smartctl --scan`; 
 | 
        
           |  |  | 244 |    };
 | 
        
           |  |  | 245 |    return if ( $@ ); # we failed to get anything back
 | 
        
           |  |  | 246 |   | 
        
           | 152 | rodolico | 247 |   | 
        
           | 169 | rodolico | 248 |    # output the drives and information as tab delimited,
 | 
        
           |  |  | 249 |    foreach my $thisDrive ( sort keys %allDrives ) {
 | 
        
           |  |  | 250 |       $driveDefinitions{$thisDrive}{'type'} = $allDrives{$thisDrive};
 | 
        
           |  |  | 251 |       &getInformation( $thisDrive, $allDrives{$thisDrive} );
 | 
        
           |  |  | 252 |       #print Dumper( $info ); die;
 | 
        
           |  |  | 253 |       my $attributes = &getAttributes( $thisDrive, $allDrives{$thisDrive}, $driveDefinitions{$thisDrive}{'Sector Size'} );
 | 
        
           |  |  | 254 |       #print Dumper( $attributes ); die;
 | 
        
           |  |  | 255 |       #print Dumper( $info ); die;
 | 
        
           |  |  | 256 |    }
 | 
        
           |  |  | 257 |   | 
        
           |  |  | 258 | }
 | 
        
           | 152 | rodolico | 259 |   | 
        
           | 169 | rodolico | 260 |   | 
        
           |  |  | 261 |   | 
        
           | 152 | rodolico | 262 | # category we will use for all values found
 | 
        
           |  |  | 263 | # see sysinfo for a list of valid categories
 | 
        
           | 157 | rodolico | 264 | my $CATEGORY = 'attributes';
 | 
        
           | 152 | rodolico | 265 |   | 
        
           |  |  | 266 | # run the commands necessary to do whatever you want to do
 | 
        
           |  |  | 267 | # The library entered above has some helper routines
 | 
        
           |  |  | 268 | # validCommandOnSystem -- passed a name, returns the fully qualified path or '' if it does not exist
 | 
        
           |  |  | 269 | # cleanUp - passed a delimiter and a string, does the following (delimiter can be '')
 | 
        
           |  |  | 270 | #           chomps the string (removes trailing newlines)
 | 
        
           |  |  | 271 | #           removes all text BEFORE the delimiter, the delimiter, and any whitespace
 | 
        
           |  |  | 272 | #           thus, the string 'xxI Am x  a weird string' with a newline will become
 | 
        
           |  |  | 273 | #           'a weird string' with no newline
 | 
        
           |  |  | 274 |   | 
        
           |  |  | 275 | # now, return the tab delimited output (to STDOUT). $CATEGORY is the first
 | 
        
           |  |  | 276 | # item, name as recognized by sysinfo is the second and the value is
 | 
        
           |  |  | 277 | # the last one. For multiple entries, place on separate lines (ie, newline separated)
 | 
        
           |  |  | 278 |   | 
        
           |  |  | 279 | # check for commands we want to run
 | 
        
           |  |  | 280 | my %commands = ( 
 | 
        
           |  |  | 281 |                   'smartctl' => '',
 | 
        
           | 169 | rodolico | 282 |                   'lsblk' => '',
 | 
        
           |  |  | 283 |                   'geom' => '',
 | 
        
           | 152 | rodolico | 284 |                );
 | 
        
           |  |  | 285 |   | 
        
           |  |  | 286 | # check the commands for validity
 | 
        
           |  |  | 287 | foreach my $command ( keys %commands ) {
 | 
        
           |  |  | 288 |    $commands{$command} = &validCommandOnSystem( $command );
 | 
        
           |  |  | 289 | }
 | 
        
           |  |  | 290 |   | 
        
           |  |  | 291 | # bail if we don't have the commands we need
 | 
        
           | 169 | rodolico | 292 | exit 1 unless $commands{'smartctl'} || $commands{'lsblk'} || $commands{'geom'};
 | 
        
           | 152 | rodolico | 293 |   | 
        
           | 169 | rodolico | 294 | # first, get basic information using lsblk for linux systems
 | 
        
           |  |  | 295 | &lsblk() if $commands{'lsblk'};
 | 
        
           |  |  | 296 | # now, try geom for bsd systems
 | 
        
           | 170 | rodolico | 297 | &geom() if $commands{'geom'};
 | 
        
           |  |  | 298 |   | 
        
           |  |  | 299 | #die Dumper( \%driveDefinitions );
 | 
        
           | 169 | rodolico | 300 | # finally, populate whatever using smartctl if it is on system.
 | 
        
           |  |  | 301 | &smartctl() if $commands{'smartctl'};
 | 
        
           | 152 | rodolico | 302 |   | 
        
           | 169 | rodolico | 303 | for my $drive ( sort keys %driveDefinitions ) {
 | 
        
           |  |  | 304 |    # don't print iSCSI definitions
 | 
        
           |  |  | 305 |    next if defined( $driveDefinitions{$drive}{'Transport protocol'} ) && $driveDefinitions{$drive}{'Transport protocol'} eq 'ISCSI';
 | 
        
           |  |  | 306 |    #also, blow off our ignored types
 | 
        
           |  |  | 307 |    next if ( defined( $driveDefinitions{$drive}{'Model'} ) && defined( $ignoreDriveTypes{ $driveDefinitions{$drive}{'Model'} }  ) );
 | 
        
           |  |  | 308 |   | 
        
           |  |  | 309 |    # remove comma's from capacity
 | 
        
           |  |  | 310 |    $driveDefinitions{$drive}{'Capacity'} =~ s/,//g if $driveDefinitions{$drive}{'Capacity'};
 | 
        
           |  |  | 311 |    foreach my $key ( sort keys %{$driveDefinitions{$drive}} ) {
 | 
        
           | 203 | rodolico | 312 |       print "$CATEGORY\t$drive $key\t$driveDefinitions{$drive}{$key}\n" if defined $driveDefinitions{$drive}{$key};
 | 
        
           | 169 | rodolico | 313 |    }
 | 
        
           | 152 | rodolico | 314 | }
 | 
        
           |  |  | 315 |   | 
        
           | 169 | rodolico | 316 |   | 
        
           | 152 | rodolico | 317 | exit 0;
 |