270 lines
10 KiB
Perl
Executable File
270 lines
10 KiB
Perl
Executable File
#!/usr/bin/perl -w
|
|
use strict;
|
|
use warnings;
|
|
use Digest::MD5 "md5_base64";
|
|
use POSIX;
|
|
use v5.10;
|
|
|
|
$| = 1; #turn off IO buffering for STDOUT
|
|
|
|
# set CONFIRMATION_PHRASE to anything falsy (like zero) if you are a fast and
|
|
# loose badass and you don't need no stinkin' safety net.
|
|
use constant CONFIRMATION_PHRASE => "YES";
|
|
|
|
my $termios = POSIX::Termios->new;
|
|
$termios->getattr(fileno(STDIN));
|
|
# fetch this value once and cache it
|
|
my $startup_lflag = $termios->getlflag();
|
|
|
|
# set termios lflag to not echo keystrokes, and take us out of line mode
|
|
$termios->setlflag($startup_lflag & ~(ECHO | ECHOK | ICANON));
|
|
$termios->setattr(fileno(STDIN), TCSANOW);
|
|
|
|
END { # reset termios lflag on exit
|
|
$termios->setlflag($startup_lflag);
|
|
$termios->setattr(fileno(STDIN), TCSANOW);
|
|
}
|
|
|
|
if ($>) {
|
|
say "This program requires root, sorry.";
|
|
exit;
|
|
}
|
|
|
|
# define some colors for convenience
|
|
my %color = (
|
|
# use tput to support anything with a terminfo entry
|
|
"brightred" => `tput setaf 9`,
|
|
"red" => `tput setaf 1`,
|
|
"green" => `tput setaf 2`,
|
|
"reset" => `tput sgr0`
|
|
);
|
|
|
|
my $pretend = grep /--dryrun/, @ARGV;
|
|
my $longrun = grep /--long/, @ARGV;
|
|
|
|
if (!$pretend && CONFIRMATION_PHRASE) {
|
|
say "$color{brightred}WARNING$color{reset}: This program will $color{brightred}COMPLETELY DESTROY ALL DATA ON ALL SYSTEM DRIVES$color{reset}";
|
|
print 'Please type "'.CONFIRMATION_PHRASE.qq(" if you are $color{brightred}CERTAIN$color{reset} you want to continue: );
|
|
chomp(my $confirm = <STDIN>);
|
|
do { say; exit } unless CONFIRMATION_PHRASE eq $confirm;
|
|
}
|
|
|
|
# create our random data and store it in RAM so we can keep reusing it
|
|
open(my $drand, "<", "/dev/urandom");
|
|
my $clobber;
|
|
read($drand, $clobber, 10240000);
|
|
close($drand);
|
|
my $clobsum = md5_base64($clobber);
|
|
|
|
my @failed = ();
|
|
my @good = ();
|
|
|
|
# for providing a waiting prompt
|
|
sub anykey {
|
|
$termios->setattr(fileno(STDIN), TCSAFLUSH);
|
|
print shift;
|
|
return getc(STDIN);
|
|
}
|
|
|
|
# to handle errors without using die
|
|
sub warning {
|
|
print shift;
|
|
return 0;
|
|
}
|
|
|
|
# home made uniq function
|
|
sub uniq {
|
|
my %seen;
|
|
return grep { !$seen{$_}++ } @_;
|
|
}
|
|
|
|
# To get the list of devices we will be cleaning
|
|
sub getdisks {
|
|
my $devices = `find /dev -name "sd*[a-z]"`;
|
|
my %listhash = ();
|
|
foreach(split("\n",$devices)) {
|
|
my $devicepath = $_;
|
|
my $serial = `udevadm info --query=all --name=$_ | grep SERIAL_SHORT | sed -e 's/^.*=//'`;
|
|
my $model = `udevadm info --query=all --name=$_ | grep "ID_MODEL=" | sed -e 's/^.*=//'`;
|
|
chomp( $serial );
|
|
chomp( $model );
|
|
if ( $serial && $model ) {
|
|
say "$devicepath\t$serial";
|
|
$listhash{$_}{path} = $devicepath;
|
|
$listhash{$_}{serial} = $serial;
|
|
$listhash{$_}{model} = $model;
|
|
} else { next; }
|
|
}
|
|
return %listhash;
|
|
}
|
|
|
|
# To whipe a drive
|
|
sub wipethemdrives {
|
|
my $diskid = shift;
|
|
my $disks = shift;
|
|
say "$disks->{$diskid}{path} - Serial:$disks->{$diskid}{serial} is being wiped...";
|
|
my $disksum;
|
|
my $disksum2;
|
|
my $spacer = 29;
|
|
# Write
|
|
if ($pretend) {
|
|
$spacer = 18;
|
|
print "Pretend wipe complete, pretending to check...";
|
|
$disksum = $clobsum;
|
|
$disksum2 = $clobsum;
|
|
} else {
|
|
system("hdparm -W 0 ".$disks->{$diskid}{path}." 1>/dev/null 2>/dev/null") and do {
|
|
print "Cannot turn off drive write caching: $!\n";
|
|
print "Disk considered $color{red}FAILED$color{reset}\n";
|
|
$disks->{$diskid}{failure} = "Cannot turn off write caching";
|
|
return 0;
|
|
};
|
|
open(my $diskw, ">", $disks->{$diskid}{path}) or return warning("could not open $disks->{$diskid}{path}");
|
|
print $diskw $clobber; #Start of Disk
|
|
seek($diskw,-10240000,2);
|
|
print $diskw $clobber; #End of Disk
|
|
close($diskw);
|
|
print "Wipe attempt complete, checking...";
|
|
# Read
|
|
system("hdparm -F ".$disks->{$diskid}{path}." 1>/dev/null 2>/dev/null") and do {
|
|
print "Cannot flush drive buffer: $!\n";
|
|
print "Disk considered $color{red}FAILED$color{reset}\n";
|
|
$disks->{$diskid}{failure} = "Cannot flush drive buffer";
|
|
return 0;
|
|
};
|
|
open(my $diskr, "<", $disks->{$diskid}{path}) or return warning("could not open $disks->{$diskid}{path}");
|
|
my $diskdata;
|
|
read($diskr, $diskdata, 10240000);
|
|
$disksum = md5_base64($diskdata);
|
|
seek($diskr,-10240000,2);
|
|
read($diskr, $diskdata, 10240000);
|
|
$disksum2 = md5_base64($diskdata);
|
|
close($diskr);
|
|
}
|
|
if ($disksum eq $clobsum) {
|
|
say +("."x$spacer)."Checksum $color{green}PASSED$color{reset}";
|
|
print " Checking raid wipe...";
|
|
if ($disksum2 eq $clobsum) {
|
|
say +("."x$spacer)."Checksum $color{green}PASSED$color{reset}";
|
|
} else {
|
|
say +("."x$spacer)."Checksum $color{red}FAILED$color{reset}";
|
|
$disks->{$diskid}{failure} = "Raid wipe failed";
|
|
return 0;
|
|
}
|
|
} else {
|
|
say +("."x$spacer)."Checksum $color{red}FAILED$color{reset}";
|
|
$disks->{$diskid}{failure} = "Main Wipe failed";
|
|
return 0;
|
|
}
|
|
# If long flag than zero whole disk and give a progress bar
|
|
if ($longrun && !$pretend) {
|
|
open(my $longdiskw, ">:raw", $disks->{$diskid}{path}) or return warning("could not open $disks->{$diskid}{path}");
|
|
binmode($longdiskw);
|
|
my $old_file_handle = select($longdiskw); #Disable I/O buffering on $longdiskw
|
|
$| = 1;
|
|
select($old_file_handle);
|
|
seek($longdiskw,0,2);
|
|
my $totalBytes = tell($longdiskw);
|
|
seek($longdiskw,0,0);
|
|
for (0..($totalBytes/4096 - 1)) {
|
|
print $longdiskw "\0"x4096;
|
|
if ( !($_ % 4) ) {
|
|
system("hdparm -F ".$disks->{$diskid}{path}." 1>/dev/null 2>/dev/null");
|
|
}
|
|
if ( !($_ % 16) ) {
|
|
printf("\r%31.31s%-".($spacer+11).".".($spacer+11)."s%6.2f%% ",
|
|
"Zeroing ".$disks->{$diskid}{path},
|
|
"."x(tell($longdiskw)/$totalBytes*($spacer+11)),
|
|
(tell($longdiskw)/$totalBytes)*100 );
|
|
}
|
|
}
|
|
my $leftover = $totalBytes - tell($longdiskw);
|
|
print $longdiskw "\0"x$leftover;
|
|
system("hdparm -F ".$disks->{$diskid}{path}." 1>/dev/null 2>/dev/null");
|
|
close($longdiskw);
|
|
printf("\r%31.31s...".("."x$spacer)."Zeroing Complete\n", "Zeroing".$disks->{$diskid}{path});
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
# to test a drive
|
|
sub smartcheck {
|
|
# Do our smart disk thing...
|
|
my $diskid = shift;
|
|
my $disks = shift;
|
|
print "Checking overall-health self-assessment test result: ".(" "x19);
|
|
(my $smartassess = `smartctl $disks->{$diskid}{path} -H 2>/dev/null | grep overall`) or (($disks->{$diskid}{failure} = "No Smart Data") and say +"N/A\n$!" and return 1);
|
|
$disks->{$diskid}{selftest} = (split(": ", $smartassess))[1];
|
|
if ( "FAILED" eq $disks->{$diskid}{selftest} ) {
|
|
say "$color{red}Failed$color{reset}";
|
|
$disks->{$diskid}{failure} = "General SMART failure";
|
|
return 0
|
|
} else { say "$color{green}Passed$color{reset}"; }
|
|
say "Checking S.M.A.R.T. variables...";
|
|
# say qq(smartctl $disks->{$diskid}{path} -A -f hex,id | grep "^0x");
|
|
(my $smartcommand = `smartctl $disks->{$diskid}{path} -A -f hex,id 2>/dev/null | grep "^0x"`) or (($disks->{$diskid}{failure} = "No Smart Data") and say +"N/A\n$!" and return 1);
|
|
for (split("\n",$smartcommand)) {
|
|
my @smartdata = split(/\s+/,$_);
|
|
$disks->{$diskid}{smart}{$smartdata[0]}{id} = $smartdata[0];
|
|
$disks->{$diskid}{smart}{$smartdata[0]}{name} = $smartdata[1];
|
|
$disks->{$diskid}{smart}{$smartdata[0]}{flag} = $smartdata[2];
|
|
$disks->{$diskid}{smart}{$smartdata[0]}{value} = $smartdata[3];
|
|
$disks->{$diskid}{smart}{$smartdata[0]}{worst} = $smartdata[4];
|
|
$disks->{$diskid}{smart}{$smartdata[0]}{thresh} = $smartdata[5];
|
|
$disks->{$diskid}{smart}{$smartdata[0]}{type} = $smartdata[6];
|
|
$disks->{$diskid}{smart}{$smartdata[0]}{updated} = $smartdata[7];
|
|
$disks->{$diskid}{smart}{$smartdata[0]}{when_failed} = $smartdata[8];
|
|
$disks->{$diskid}{smart}{$smartdata[0]}{raw_value} = $smartdata[9];
|
|
}
|
|
close( $smartcommand );
|
|
# these are incredibly strict requirements, you probably should be looking at the when_failed field instead...
|
|
for my $smartentry (sort {$a cmp $b} keys %{$disks->{$diskid}{smart}}) {
|
|
if ($disks->{$diskid}{smart}{$smartentry}{id} =~ /0x05|0x07|0xc4|0xc5|0xc6|0xc7/) {
|
|
printf("%-6.6s %-26.26s\t%-26.26s\t", $disks->{$diskid}{smart}{$smartentry}{id}, $disks->{$diskid}{smart}{$smartentry}{name}, $disks->{$diskid}{smart}{$smartentry}{raw_value});
|
|
if ($disks->{$diskid}{smart}{$smartentry}{raw_value} =~ /0|0\/0/) {
|
|
say "$color{green}Passed$color{reset}";
|
|
} else {
|
|
say "$color{red}Failed$color{reset}";
|
|
$disks->{$diskid}{failure} = "failed on $disks->{$diskid}{smart}{$smartentry}{name}";
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
# main loop
|
|
while () {
|
|
my %disks = getdisks();
|
|
my $batchcount = keys %disks;
|
|
say "$color{red}WARNING:$color{reset} This program will completely destroy all data on $batchcount drives with the following serial numbers:";
|
|
foreach my $diskid (sort {$disks{$a}{serial} cmp $disks{$b}{serial}} keys %disks) {
|
|
say $disks{$diskid}{serial};
|
|
}
|
|
# exit unless "\n" eq anykey("Last chance to save your data, hit ENTER to DESTROY ALL DATA, or any other key to abort!\n");
|
|
foreach my $diskid (sort {$a cmp $b}keys %disks) {
|
|
print "="x33;
|
|
printf '[ %-10s ]', $diskid;
|
|
say "="x33;
|
|
$disks{$diskid}{wipe} = wipethemdrives($diskid,\%disks);
|
|
if ($disks{$diskid}{wipe} == 1) {
|
|
$disks{$diskid}{smartpass} = smartcheck($diskid,\%disks);
|
|
if ($disks{$diskid}{smartpass} == 1) {
|
|
push(@good, sprintf("%-26.26s\t%19.19s\t%19.19s",$disks{$diskid}{serial},$disks{$diskid}{model}, ($disks{$diskid}{failure} or "")));
|
|
} else { push(@failed, sprintf("%-26.26s\t%19.19s\t%19.19s",$disks{$diskid}{serial},$disks{$diskid}{model}, ($disks{$diskid}{failure} or "") )); }
|
|
} else { push(@failed, sprintf("%-26.26s\t%19.19s\t%19.19s",$disks{$diskid}{serial}, "-"x19, ($disks{$diskid}{failure} or "") )); }
|
|
print "="x80;
|
|
print "\n"x5;
|
|
}
|
|
say "These drives were successfully wiped and passed SMART, they may be put back into\nproduction:";
|
|
print $color{green}.join("\n",sort {$a cmp $b} uniq(@good)).$color{reset};
|
|
anykey("\nHit any key to see the list of failed drives. Please also note any drives that\ndo not appear on either list. Those should be tried again (if they do not show\nup after three times assume failure).\n");
|
|
say "$color{red}The following drives are bad, RMA or shred:";
|
|
say join("\n",sort {$a cmp $b} uniq(@failed));
|
|
print "$color{green}";
|
|
@failed = ();
|
|
@good = ();
|
|
say $color{green}.("-"x34).$color{red}."\nWipe complete, insert more drives.\n".$color{green}.("-"x34).$color{reset};
|
|
anykey("Hit any key when ready to continue...\n");
|
|
}
|