=head1 smart-status-lib.pl Functions for getting SMART status =cut BEGIN { push(@INC, ".."); }; use WebminCore; &init_config(); =head2 get_smart_version() Returns the version number of the SMART tools on this system =cut sub get_smart_version { if (!defined($smartctl_version_cache)) { local $out = &backquote_command( "$config{'smartctl'} --version 2>&1 </dev/null"); if ($out =~ /smartmontools release\s+(\S+)/i) { $smartctl_version_cache = $1; } } return $smartctl_version_cache; } =head2 list_smart_disks_partitions Returns a sorted list of disks that can support SMART. =cut sub list_smart_disks_partitions { if (&foreign_check("fdisk")) { return &list_smart_disks_partitions_fdisk(); } elsif (&foreign_check("bsdfdisk")) { return &list_smart_disks_partitions_bsdfdisk(); } elsif (&foreign_check("mount")) { return &list_smart_disks_partitions_fstab(); } return ( ); } =head2 list_smart_disks_partitions_fdisk Returns a sorted list of disks that can support SMART, using the Linux fdisk module. May include faked-up 3ware devices. =cut sub list_smart_disks_partitions_fdisk { &foreign_require("fdisk"); local @rv; my $twcount = 0; foreach my $d (sort { $a->{'device'} cmp $b->{'device'} } &fdisk::list_disks_partitions()) { if (($d->{'type'} eq 'scsi' || $d->{'type'} eq 'raid') && $d->{'model'} =~ /3ware|amcc|9750/i) { # A 3ware hardware RAID device. # First find the controllers. local @ctrls = &list_3ware_controllers(); # For each controller, find all the units (u0, u1, etc..) local @units; foreach my $c (@ctrls) { push(@units, &list_3ware_subdisks($c)); } # Assume that /dev/sdX maps to units in order my $i = 0; foreach my $sd (@{$units[$twcount]->[2]}) { my $c = $units[$twcount]->[1]; my $cidx = &indexof($c, @ctrls); my $dev = "/dev/twa".$cidx; if (!-r $dev) { $dev = "/dev/twe".$cidx; } if (!-r $dev) { $dev = "/dev/twl".$cidx; } push(@rv, { 'device' => $dev, 'prefix' => $dev, 'desc' => '3ware physical disk unit '. $units[$twcount]->[0].' number '.$sd, 'type' => 'scsi', 'subtype' => '3ware', 'subdisk' => substr($sd, 1), 'id' => $d->{'id'}, 'ids' => $d->{'ids'}, }); $i++; } $twcount++; } elsif (($d->{'type'} eq 'scsi' || $d->{'type'} eq 'raid') && $d->{'model'} =~ /LSI/i && $d->{'model'} !~ /9750/) { # A LSI megaraid device. local @units = &list_megaraid_subdisks(0); foreach my $i (@units) { push(@rv, { 'device' => $d->{'device'}, 'prefix' => $d->{'device'}, 'desc' => 'LSI Array '.$i->[1].' physical disk ID '.$i->[0], 'type' => 'scsi', 'subtype' => 'sat+megaraid', 'subdisk' => $i->[0], 'id' => $d->{'id'}, 'ids' => $d->{'ids'}, }); } } elsif ($d->{'device'} =~ /^\/dev\/cciss\/(.*)$/) { # HP Smart Array .. add underlying disks my $count = &count_subdisks($d, "cciss"); for(my $i=0; $i<$count; $i++) { push(@rv, { 'device' => $d->{'device'}, 'prefix' => $d->{'device'}, 'desc' => 'HP Smart Array physical disk '.$i, 'type' => 'scsi', 'subtype' => 'cciss', 'subdisk' => $i, 'id' => $d->{'id'}, 'ids' => $d->{'ids'}, }); } } elsif ($d->{'type'} eq 'scsi' || $d->{'type'} eq 'ide') { # Some other disk push(@rv, $d); } } return sort { $a->{'device'} cmp $b->{'device'} || $a->{'subdisk'} <=> $b->{'subdisk'} } @rv; } =head2 list_megaraid_subdisks(adapter) Returns a list, each element of which is a unit, controller and list of subdisks =cut sub list_megaraid_subdisks { local ($adap) = @_; return () if (!&has_command("megacli")); local $out = &backquote_command("megacli -pdlist -a$adap 2>/dev/null"); return () if ($?); my @rv; foreach my $l (split(/\r?\n/, $out)) { if ($l =~ /^Device\sId:\s(\d+)$/) { push(@rv, [ $1, $adap, [ ] ]); } } return @rv; } =head2 list_3ware_subdisks(controller) Returns a list, each element of which is a unit, controller and list of subdisks =cut sub list_3ware_subdisks { local ($ctrl) = @_; local $out = &backquote_command("tw_cli info $ctrl 2>/dev/null"); return () if ($?); my @rv; foreach my $l (split(/\r?\n/, $out)) { if ($l =~ /^(u\d+)\s/) { push(@rv, [ $1, $ctrl, [ ] ]); } elsif ($l =~ /^(p\d+)\s+(\S+)\s+(\S+)/ && $2 ne 'NOT-PRESENT') { my ($u) = grep { $_->[0] eq $3 } @rv; if ($u) { push(@{$u->[2]}, $1); } } } return @rv; } =head2 list_3ware_controllers() Returns a list of 3ware controllers, each of which is just a string like c0 =cut sub list_3ware_controllers { local $out = &backquote_command("tw_cli show 2>/dev/null"); return () if ($?); my @rv; foreach my $l (split(/\r?\n/, $out)) { if ($l =~ /^(c\d+)\s/) { push(@rv, $1); } } return @rv; } =head2 count_subdisks(&drive, type, [device]) Returns the number of sub-disks for a hardware RAID device, by calling smartctl on them until failure. =cut sub count_subdisks { local ($d, $type, $device) = @_; $device ||= $d->{'device'}; local $count = 0; while(1) { local $cmd = "$config{'smartctl'} -d $type,$count ".quotemeta($device); &execute_command($cmd); last if ($?); $count++; } return $count; } =head2 list_smart_disks_partitions_fstab Returns a list of disks on which we can use SMART, taken from /etc/fstab. =cut sub list_smart_disks_partitions_fstab { &foreign_require("mount"); my @rv; foreach my $m (&mount::list_mounted(1)) { if ($m->[1] =~ /^(\/dev\/(da|ad|ada)([0-9]+))/ && $m->[2] ne 'cd9660') { # FreeBSD-style disk name push(@rv, { 'device' => $1, 'desc' => ($2 eq 'ad' ? 'IDE' : 'SCSI'). ' disk '.$3 }); } elsif ($m->[1] =~ /^(\/dev\/disk\d+)/ && ($m->[2] eq 'ufs' || $m->[2] eq 'hfs')) { # MacOS disk name push(@rv, { 'device' => $1, 'desc' => $1 }); } elsif ($m->[1] =~ /^(\/dev\/([hs])d([a-z]))/ && $m->[2] ne 'iso9660') { # Linux disk name push(@rv, { 'device' => $1, 'desc' => ($2 eq 'h' ? 'IDE' : 'SCSI'). ' disk '.uc($3) }); } } my %done; @rv = grep { !$done{$_->{'device'}}++ } @rv; return @rv; } =head2 list_smart_disks_partitions_bsdfdisk Returns a sorted list of disks that can support SMART, using the FreeBSD fdisk module =cut sub list_smart_disks_partitions_bsdfdisk { &foreign_require("bsdfdisk"); local @rv; foreach my $d (sort { $a->{'device'} cmp $b->{'device'} } &bsdfdisk::list_disks_partitions()) { if ($d->{'type'} eq 'scsi' || $d->{'type'} eq 'ide') { push(@rv, $d); } } return sort { $a->{'device'} cmp $b->{'device'} } @rv; } =head2 get_drive_status(device-name, [&drive]) Returns a hash reference containing the status of some drive =cut sub get_drive_status { local ($device, $drive) = @_; if ($device =~ /^(\/dev\/nvme\d+)n\d+$/) { # For NVME drives, try the underlying device first local $nd = $1; local $st = &get_drive_status($nd, $drive); return $st if ($st->{'support'} && $st->{'enabled'}); } local %rv; local $qd = quotemeta($device); local $extra_args = &get_extra_args($device, $drive); if (&get_smart_version() > 5.0) { # Use new command format # Check support local $out = &backquote_command( "$config{'smartctl'} $extra_args -i $qd 2>&1"); if ($out =~ /SMART\s+support\s+is:\s+Available/i) { $rv{'support'} = 1; } elsif ($out =~ /Device\s+supports\s+SMART/i) { $rv{'support'} = 1; } elsif ($out =~ /NVMe\s+Version:/i) { $rv{'support'} = 1; } else { $rv{'support'} = 0; } if ($out =~ /SMART\s+support\s+is:\s+Enabled/i) { $rv{'enabled'} = 1; } elsif ($out =~ /Device.*is\+Enabled/i) { $rv{'enabled'} = 1; } elsif ($out =~ /Device\s+supports\s+SMART\s+and\s+is\s+Enabled/i) { # Added to match output from RHEL5 $rv{'enabled'} = 1; } elsif ($out =~ /NVMe\s+Version:/i) { $rv{'enabled'} = 1; } else { # Not enabled! $rv{'enabled'} = 0; } if ($device =~ /^\/dev\/nvme/ && $out =~ /(Model\s+Number|Device\s+Model):/i) { # For NVME devices, surprisingly smart support/enabled info is # not shown. So assume they work $rv{'support'} = 1; $rv{'enabled'} = 1; } if (!$rv{'support'} || !$rv{'enabled'}) { # No point checking further! return \%rv; } # Check status $out = &backquote_command( "$config{'smartctl'} $extra_args -H $qd 2>&1"); if ($out =~ /test result: FAILED/i) { $rv{'check'} = 0; } else { $rv{'check'} = 1; } } else { # Use old command format # Check status local $out = &backquote_command( "$config{'smartctl'} $extra_args -c $qd 2>&1"); if ($out =~ /supports S.M.A.R.T./i) { $rv{'support'} = 1; } else { $rv{'support'} = 0; } if ($out =~ /is enabled/i) { $rv{'enabled'} = 1; } else { # Not enabled! $rv{'enabled'} = 0; } if (!$rv{'support'} || !$rv{'enabled'}) { # No point checking further! return \%rv; } if ($out =~ /Check S.M.A.R.T. Passed/i) { $rv{'check'} = 1; } else { $rv{'check'} = 0; } } if ($config{'attribs'}) { # Fetch other attributes local ($lastline, @attribs); local $doneknown = 0; $rv{'raw'} = ""; open(OUT, "$config{'smartctl'} $extra_args -a $qd |"); while(<OUT>) { s/\r|\n//g; if (/Model\s+Family:\s+(.*)/i) { $rv{'family'} = $1; } elsif (/Device\s+Model:\s+(.*)/i) { $rv{'model'} = $1; } elsif (/Serial\s+Number:\s+(.*)/i) { $rv{'serial'} = $1; } elsif (/User\s+Capacity:\s+(.*)/i) { $rv{'capacity'} = $1; } if (/^\((\s*\d+)\)(.*)\s(0x\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/) { # An old-style vendor attribute $doneknown = 1; push(@attribs, [ $2, $7 ]); } elsif (/^\s*(\d+)\s+(\S+)\s+(0x\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/) { # A new-style vendor attribute $doneknown = 1; push(@attribs, [ $2, $10, undef, $4 ]); $attribs[$#attribs]->[0] =~ s/_/ /g; } elsif (/^((\w+.*):\s+([0-9]+(,[0-9]+)+)|(\w+.*):\s+(\d+x\d+)|(\w+.*):\s+(\d+%)|^(\w+.*):\s+(\d+))/) { # NVME style $doneknown = 1; push(@attribs, [ $5 || $7 || $9 || $2, $6 || $8 || $10 || $3, undef, undef ]); } elsif (/^(\S.*\S):\s+\(\s*(\S+)\)\s*(.*)/ && !$doneknown) { # A known attribute local $attrib = [ $1, $2, $3 ]; if ($lastline =~ /^\S/ && $lastline !~ /:/) { $attrib->[0] = $lastline." ".$attrib->[0]; } push(@attribs, $attrib); } elsif (/^\s+(\S.*)/ && @attribs && !$doneknown) { # Continuation of a known attribute description local $cont = $1; local $ls = $attribs[$#attribs]; if ($ls->[2] =~ /\.\s*$/) { $ls->[2] .= "<br>".$cont; } else { $ls->[2] .= " ".$cont; } } elsif (/ATA\s+Error\s+Count:\s+(\d+)/i) { # An error line! $rv{'errors'} = $1; } $lastline = $_; $rv{'raw'} .= $_."\n"; } close(OUT); $rv{'attribs'} = \@attribs; } return \%rv; } # short_test(device, [&drive]) # Starts a short drive test, and returns 1 for success or 0 for failure, plus # any output. sub short_test { local ($device, $drive) = @_; local $qm = quotemeta($device); local $extra_args = &get_extra_args($device, $drive); if (&get_smart_version() > 5.0) { local $out = &backquote_logged("$config{'smartctl'} $extra_args -t short $qm 2>&1"); if ($? || $out !~ /testing has begun/i) { return (0, $out); } else { return (1, $out); } } else { local $out = &backquote_logged("$config{'smartctl'} $extra_args -S $qm 2>&1"); if ($? || $out !~ /test has begun/i) { return (0, $out); } else { return (1, $out); } } } # ext_test(device, [&drive]) # Starts an extended drive test, and returns 1 for success or 0 for failure, # plus any output. sub ext_test { local ($device, $drive) = @_; local $qm = quotemeta($device); local $extra_args = &get_extra_args($device, $drive); if (&get_smart_version() > 5.0) { local $out = &backquote_logged("$config{'smartctl'} $extra_args -t long $qm 2>&1"); if ($? || $out !~ /testing has begun/i) { return (0, $out); } else { return (1, $out); } } else { local $out = &backquote_logged("$config{'smartctl'} $extra_args -X $qm 2>&1"); if ($? || $out !~ /test has begun/i) { return (0, $out); } else { return (1, $out); } } } # data_test(device, [&drive]) # Starts offline data collection, and returns 1 for success or 0 for failure, # plus any output. sub data_test { local ($device, $drive) = @_; local $qm = quotemeta($device); local $extra_args = &get_extra_args($device, $drive); if (&get_smart_version() > 5.0) { local $out = &backquote_logged("$config{'smartctl'} $extra_args -t offline $qm 2>&1"); if ($? || $out !~ /testing has begun/i) { return (0, $out); } else { return (1, $out); } } else { local $out = &backquote_logged("$config{'smartctl'} $extra_args -O $qm 2>&1"); if ($? || $out !~ /test has begun/i) { return (0, $out); } else { return (1, $out); } } } =head2 get_extra_args(device, [&drive]) Returns extra command-line args to smartctl, needed for some drive type. =cut sub get_extra_args { local ($device, $drive) = @_; if (!$drive) { ($drive) = grep { $_->{'device'} eq $device } &list_smart_disks_partitions(); } local $extra_args = $config{'extra'}; if ($drive && defined($drive->{'subdisk'})) { $extra_args .= " -d $drive->{'subtype'},$drive->{'subdisk'}"; } elsif ($config{'ata'}) { $extra_args .= " -d ata"; } return $extra_args; } 1;
Name | Type | Size | Permission | Actions |
---|---|---|---|---|
images | Folder | 0755 |
|
|
lang | Folder | 0755 |
|
|
CHANGELOG | File | 1.34 KB | 0644 |
|
action.cgi | File | 810 B | 0755 |
|
config | File | 41 B | 0644 |
|
config-CentOS-Linux-5.0-6.9 | File | 41 B | 0644 |
|
config-Redhat-Enterprise-Linux-5.0-6.9 | File | 41 B | 0644 |
|
config.info | File | 221 B | 0644 |
|
config.info.ca | File | 264 B | 0644 |
|
config.info.de | File | 261 B | 0644 |
|
config.info.es | File | 166 B | 0644 |
|
config.info.fr | File | 0 B | 0644 |
|
config.info.ms | File | 245 B | 0644 |
|
config.info.nl | File | 251 B | 0644 |
|
config.info.no | File | 230 B | 0644 |
|
config.info.ru | File | 360 B | 0644 |
|
index.cgi | File | 4.37 KB | 0755 |
|
install_check.pl | File | 343 B | 0755 |
|
module.info | File | 195 B | 0644 |
|
module.info.af | File | 0 B | 0644 |
|
module.info.af.auto | File | 127 B | 0644 |
|
module.info.ar | File | 0 B | 0644 |
|
module.info.ar.auto | File | 152 B | 0644 |
|
module.info.be | File | 0 B | 0644 |
|
module.info.be.auto | File | 169 B | 0644 |
|
module.info.bg | File | 0 B | 0644 |
|
module.info.bg.auto | File | 230 B | 0644 |
|
module.info.ca | File | 136 B | 0644 |
|
module.info.cs | File | 26 B | 0644 |
|
module.info.cs.auto | File | 91 B | 0644 |
|
module.info.da | File | 0 B | 0644 |
|
module.info.da.auto | File | 126 B | 0644 |
|
module.info.de | File | 144 B | 0644 |
|
module.info.el | File | 0 B | 0644 |
|
module.info.el.auto | File | 222 B | 0644 |
|
module.info.es | File | 37 B | 0644 |
|
module.info.es.auto | File | 102 B | 0644 |
|
module.info.eu | File | 0 B | 0644 |
|
module.info.eu.auto | File | 127 B | 0644 |
|
module.info.fa | File | 0 B | 0644 |
|
module.info.fa.auto | File | 190 B | 0644 |
|
module.info.fi | File | 0 B | 0644 |
|
module.info.fi.auto | File | 116 B | 0644 |
|
module.info.fr | File | 0 B | 0644 |
|
module.info.fr.auto | File | 143 B | 0644 |
|
module.info.he | File | 0 B | 0644 |
|
module.info.he.auto | File | 158 B | 0644 |
|
module.info.hr | File | 0 B | 0644 |
|
module.info.hr.auto | File | 123 B | 0644 |
|
module.info.hu | File | 0 B | 0644 |
|
module.info.hu.auto | File | 163 B | 0644 |
|
module.info.it | File | 0 B | 0644 |
|
module.info.it.auto | File | 132 B | 0644 |
|
module.info.ja | File | 0 B | 0644 |
|
module.info.ja.auto | File | 166 B | 0644 |
|
module.info.ko | File | 0 B | 0644 |
|
module.info.ko.auto | File | 156 B | 0644 |
|
module.info.lt | File | 0 B | 0644 |
|
module.info.lt.auto | File | 135 B | 0644 |
|
module.info.lv | File | 0 B | 0644 |
|
module.info.lv.auto | File | 129 B | 0644 |
|
module.info.ms | File | 117 B | 0644 |
|
module.info.mt | File | 0 B | 0644 |
|
module.info.mt.auto | File | 125 B | 0644 |
|
module.info.nl | File | 27 B | 0644 |
|
module.info.nl.auto | File | 110 B | 0644 |
|
module.info.no | File | 25 B | 0644 |
|
module.info.no.auto | File | 100 B | 0644 |
|
module.info.pl | File | 0 B | 0644 |
|
module.info.pl.auto | File | 120 B | 0644 |
|
module.info.pt | File | 0 B | 0644 |
|
module.info.pt.auto | File | 130 B | 0644 |
|
module.info.pt_BR | File | 0 B | 0644 |
|
module.info.pt_BR.auto | File | 136 B | 0644 |
|
module.info.ro | File | 0 B | 0644 |
|
module.info.ro.auto | File | 136 B | 0644 |
|
module.info.ru | File | 46 B | 0644 |
|
module.info.ru.auto | File | 157 B | 0644 |
|
module.info.sk | File | 26 B | 0644 |
|
module.info.sk.auto | File | 95 B | 0644 |
|
module.info.sl | File | 0 B | 0644 |
|
module.info.sl.auto | File | 120 B | 0644 |
|
module.info.sv | File | 0 B | 0644 |
|
module.info.sv.auto | File | 134 B | 0644 |
|
module.info.th | File | 0 B | 0644 |
|
module.info.th.auto | File | 273 B | 0644 |
|
module.info.tr | File | 0 B | 0644 |
|
module.info.tr.auto | File | 145 B | 0644 |
|
module.info.uk | File | 0 B | 0644 |
|
module.info.uk.auto | File | 190 B | 0644 |
|
module.info.ur | File | 0 B | 0644 |
|
module.info.ur.auto | File | 213 B | 0644 |
|
module.info.vi | File | 0 B | 0644 |
|
module.info.vi.auto | File | 172 B | 0644 |
|
module.info.zh | File | 0 B | 0644 |
|
module.info.zh.auto | File | 109 B | 0644 |
|
module.info.zh_TW | File | 0 B | 0644 |
|
module.info.zh_TW.auto | File | 115 B | 0644 |
|
prefs.info | File | 20 B | 0644 |
|
smart-status-lib.pl | File | 12.98 KB | 0755 |
|
status_monitor.pl | File | 4.17 KB | 0755 |
|