1 =head1 smart-status-lib.pl
3 Functions for getting SMART status
7 BEGIN { push(@INC, ".."); };
10 =head2 get_smart_version()
12 Returns the version number of the SMART tools on this system
17 if (!defined($smartctl_version_cache)) {
18 local $out = &backquote_command(
19 "$config{'smartctl'} --version 2>&1 </dev/null");
20 if ($out =~ /smartmontools release\s+(\S+)/i) {
21 $smartctl_version_cache = $1;
24 return $smartctl_version_cache;
27 =head2 list_smart_disks_partitions
29 Returns a sorted list of disks that can support SMART.
32 sub list_smart_disks_partitions
34 if (&foreign_check("fdisk")) {
35 return &list_smart_disks_partitions_fdisk();
37 elsif (&foreign_check("mount")) {
38 return &list_smart_disks_partitions_fstab();
43 =head2 list_smart_disks_partitions_fdisk
45 Returns a sorted list of disks that can support SMART, using the Linux fdisk
46 module. May include faked-up 3ware devices.
49 sub list_smart_disks_partitions_fdisk
51 &foreign_require("fdisk");
54 foreach my $d (sort { $a->{'device'} cmp $b->{'device'} }
55 &fdisk::list_disks_partitions()) {
56 if (($d->{'type'} eq 'scsi' || $d->{'type'} eq 'raid') &&
57 $d->{'model'} =~ /3ware|amcc/i) {
58 # A 3ware hardware RAID device.
60 # First find the controllers.
61 local @ctrls = &list_3ware_controllers();
63 # For each controller, find all the units (u0, u1, etc..)
65 foreach my $c (@ctrls) {
66 push(@units, &list_3ware_subdisks($c));
69 # Assume that /dev/sdX maps to units in order
71 foreach my $sd (@{$units[$twcount]->[2]}) {
72 my $c = $units[$twcount]->[1];
73 my $cidx = &indexof($c, @ctrls);
74 my $dev = "/dev/twa".$cidx;
76 $dev = "/dev/twe".$cidx;
78 push(@rv, { 'device' => $dev,
80 'desc' => '3ware physical disk unit '.
81 $units[$twcount]->[0].' number '.$sd,
84 'subdisk' => substr($sd, 1),
91 elsif ($d->{'device'} =~ /^\/dev\/cciss\/(.*)$/) {
92 # HP Smart Array .. add underlying disks
93 my $count = &count_subdisks($d, "cciss");
94 for(my $i=0; $i<$count; $i++) {
95 push(@rv, { 'device' => $d->{'device'},
96 'prefix' => $d->{'device'},
97 'desc' => 'HP Smart Array physical disk '.$i,
105 elsif ($d->{'type'} eq 'scsi' || $d->{'type'} eq 'ide') {
110 return sort { $a->{'device'} cmp $b->{'device'} ||
111 $a->{'subdisk'} <=> $b->{'subdisk'} } @rv;
114 =head2 list_3ware_subdisks(controller)
116 Returns a list, each element of which is a unit, controller and list of subdisks
119 sub list_3ware_subdisks
122 local $out = &backquote_command("tw_cli info $ctrl");
125 foreach my $l (split(/\r?\n/, $out)) {
126 if ($l =~ /^(u\d+)\s/) {
127 push(@rv, [ $1, $ctrl, [ ] ]);
129 elsif ($l =~ /^(p\d+)\s+(\S+)\s+(\S+)/ &&
130 $2 ne 'NOT-PRESENT') {
131 my ($u) = grep { $_->[0] eq $3 } @rv;
133 push(@{$u->[2]}, $1);
140 =head2 list_3ware_controllers()
142 Returns a list of 3ware controllers, each of which is just a string like c0
145 sub list_3ware_controllers
147 local $out = &backquote_command("tw_cli show");
150 foreach my $l (split(/\r?\n/, $out)) {
151 if ($l =~ /^(c\d+)\s/) {
158 =head2 count_subdisks(&drive, type, [device])
160 Returns the number of sub-disks for a hardware RAID device, by calling
161 smartctl on them until failure.
166 local ($d, $type, $device) = @_;
167 $device ||= $d->{'device'};
170 local $cmd = "$config{'smartctl'} -d $type,$count ".quotemeta($device);
171 &execute_command($cmd);
178 =head2 list_smart_disks_partitions_fstab
180 Returns a list of disks on which we can use SMART, taken from /etc/fstab.
183 sub list_smart_disks_partitions_fstab
185 &foreign_require("mount");
187 foreach my $m (&mount::list_mounted(1)) {
188 if ($m->[1] =~ /^(\/dev\/(da|ad)([0-9]+))/ &&
189 $m->[2] ne 'cd9660') {
190 # FreeBSD-style disk name
191 push(@rv, { 'device' => $1,
192 'desc' => ($2 eq 'ad' ? 'IDE' : 'SCSI').
195 elsif ($m->[1] =~ /^(\/dev\/disk\d+)/ &&
196 ($m->[2] eq 'ufs' || $m->[2] eq 'hfs')) {
198 push(@rv, { 'device' => $1,
201 elsif ($m->[1] =~ /^(\/dev\/([hs])d([a-z]))/ &&
202 $m->[2] ne 'iso9660') {
204 push(@rv, { 'device' => $1,
205 'desc' => ($2 eq 'h' ? 'IDE' : 'SCSI').
210 @rv = grep { !$done{$_->{'device'}}++ } @rv;
214 =head2 get_drive_status(device-name, [&drive])
216 Returns a hash reference containing the status of some drive
221 local ($device, $drive) = @_;
223 local $qd = quotemeta($device);
224 local $extra_args = &get_extra_args($device, $drive);
225 if (&get_smart_version() > 5.0) {
226 # Use new command format
229 local $out = &backquote_command(
230 "$config{'smartctl'} $extra_args -i $qd 2>&1");
231 if ($out =~ /SMART\s+support\s+is:\s+Available/i) {
234 elsif ($out =~ /Device\s+supports\s+SMART/i) {
240 if ($out =~ /SMART\s+support\s+is:\s+Enabled/i) {
243 elsif ($out =~ /Device.*is\+Enabled/i) {
246 elsif ($out =~ /Device\s+supports\s+SMART\s+and\s+is\s+Enabled/i) {
247 # Added to match output from RHEL5
254 if (!$rv{'support'} || !$rv{'enabled'}) {
255 # No point checking further!
260 $out = &backquote_command(
261 "$config{'smartctl'} $extra_args -H $qd 2>&1");
262 if ($out =~ /test result: FAILED/i) {
270 # Use old command format
273 local $out = &backquote_command(
274 "$config{'smartctl'} $extra_args -c $qd 2>&1");
275 if ($out =~ /supports S.M.A.R.T./i) {
281 if ($out =~ /is enabled/i) {
288 if (!$rv{'support'} || !$rv{'enabled'}) {
289 # No point checking further!
292 if ($out =~ /Check S.M.A.R.T. Passed/i) {
300 if ($config{'attribs'}) {
301 # Fetch other attributes
302 local ($lastline, @attribs);
303 local $doneknown = 0;
305 open(OUT, "$config{'smartctl'} $extra_args -a $qd |");
308 if (/^\((\s*\d+)\)(.*)\s(0x\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/) {
309 # An old-style vendor attribute
311 push(@attribs, [ $2, $7 ]);
313 elsif (/^\s*(\d+)\s+(\S+)\s+(0x\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/) {
314 # A new-style vendor attribute
316 push(@attribs, [ $2, $10 ]);
317 $attribs[$#attribs]->[0] =~ s/_/ /g;
319 elsif (/^(\S.*\S):\s+\(\s*(\S+)\)\s*(.*)/ && !$doneknown) {
321 local $attrib = [ $1, $2, $3 ];
322 if ($lastline =~ /^\S/ && $lastline !~ /:/) {
323 $attrib->[0] = $lastline." ".$attrib->[0];
325 push(@attribs, $attrib);
327 elsif (/^\s+(\S.*)/ && @attribs && !$doneknown) {
328 # Continuation of a known attribute description
330 local $ls = $attribs[$#attribs];
331 if ($ls->[2] =~ /\.\s*$/) {
332 $ls->[2] .= "<br>".$cont;
335 $ls->[2] .= " ".$cont;
338 elsif (/ATA\s+Error\s+Count:\s+(\d+)/i) {
343 $rv{'raw'} .= $_."\n";
346 $rv{'attribs'} = \@attribs;
351 # short_test(device, [&drive])
352 # Starts a short drive test, and returns 1 for success or 0 for failure, plus
356 local ($device, $drive) = @_;
357 local $qm = quotemeta($device);
358 local $extra_args = &get_extra_args($device, $drive);
359 if (&get_smart_version() > 5.0) {
360 local $out = &backquote_logged("$config{'smartctl'} $extra_args -t short $qm 2>&1");
361 if ($? || $out !~ /testing has begun/i) {
369 local $out = &backquote_logged("$config{'smartctl'} $extra_args -S $qm 2>&1");
370 if ($? || $out !~ /test has begun/i) {
379 # ext_test(device, [&drive])
380 # Starts an extended drive test, and returns 1 for success or 0 for failure,
384 local ($device, $drive) = @_;
385 local $qm = quotemeta($device);
386 local $extra_args = &get_extra_args($device, $drive);
387 if (&get_smart_version() > 5.0) {
388 local $out = &backquote_logged("$config{'smartctl'} $extra_args -t long $qm 2>&1");
389 if ($? || $out !~ /testing has begun/i) {
397 local $out = &backquote_logged("$config{'smartctl'} $extra_args -X $qm 2>&1");
398 if ($? || $out !~ /test has begun/i) {
407 # data_test(device, [&drive])
408 # Starts offline data collection, and returns 1 for success or 0 for failure,
412 local ($device, $drive) = @_;
413 local $qm = quotemeta($device);
414 local $extra_args = &get_extra_args($device, $drive);
415 if (&get_smart_version() > 5.0) {
416 local $out = &backquote_logged("$config{'smartctl'} $extra_args -t offline $qm 2>&1");
417 if ($? || $out !~ /testing has begun/i) {
425 local $out = &backquote_logged("$config{'smartctl'} $extra_args -O $qm 2>&1");
426 if ($? || $out !~ /test has begun/i) {
435 =head2 get_extra_args(device, [&drive])
437 Returns extra command-line args to smartctl, needed for some drive type.
442 local ($device, $drive) = @_;
444 ($drive) = grep { $_->{'device'} eq $device }
445 &list_smart_disks_partitions();
447 local $extra_args = $config{'extra'};
448 if ($drive && defined($drive->{'subdisk'})) {
449 $extra_args .= " -d $drive->{'subtype'},$drive->{'subdisk'}";
451 elsif ($config{'ata'}) {
452 $extra_args .= " -d ata";