#!/usr/bin/perl
# vim:ts=4:sw=4:ai:cindent:fdm=marker
#
# diskoversikt.pl v0.0.2
#
# OVERVIEW
#
# A simple perl thingy to scan for disks on a Linux system and create an HMTL
# report. Useful in RAID systems, as device naming may change if drives are
# swapped or new drives added - even an USB drive may be placed 'in the middle'
# of the harddrives in the RAID. The RAID doesn't worry about this, but an admin
# may get a slight heartattack when she finds out that sdc is dead, and she no
# longer knows which disk that is. This is meant to be a simple remedy for such
# problems.
#
# CHANGELOG
#
# v0.0.1
# Initial stuff - supports logging to HTML only, no parsing of arguments etc,
# see TODO.
#
# v0.0.2
# Adds support for argument parsing and CSV output. In CSV output format, the
# user capacity is reported in bytes only.
#
# TODO
# I first wrote this to be run as a cron job on a daily or hourly basis, but
# just now realized that if sdc dies completely, and this is run, the data for
# last run will be overwritten, making this rather worthless. Some work must be
# done to compare the previous data with the current. This may be done with some
# sqlite fiddling.
#
# Licensed under GPL v2. See COPYING file or
# http://www.gnu.org/licenses/gpl-2.0.html for details.
#
# Written by Roy Sigurd Karlsbakk <roy@karlsbakk.net>
#

use strict;
use warnings;
use Getopt::Long;

my $partfile = '/proc/partitions';
my $smartctl = '/usr/bin/sudo /usr/sbin/smartctl';
my $hostname = `uname -n`; chomp($hostname);
my %devices;
my %supported_oses = (
	'linux' => 1,
);
my %supported_output_formats = (
	'csv' => 1,
	'html' => 1,
);
my $verbose=undef;
my $output_format='html';

# OS check
die "$^O not supported" unless ($supported_oses{$^O});

# Get commandline options
GetOptions(
	"O=s"				=> \$output_format,
	"output-format=s"	=> \$output_format,
	"H"					=> sub { $output_format = 'html' },
	"html"				=> sub { $output_format = 'html' },
	"C"					=> sub { $output_format = 'csv' },
	"csv"				=> sub { $output_format = 'csv' },
	"T"					=> sub { $output_format = 'text' },
	"text"				=> sub { $output_format = 'text' },
	"v+"				=> \$verbose,
	"verbose+"			=> \$verbose,
) or exit(1);

# Sanity check for options
unless ($supported_output_formats{$output_format}) {
	print STDERR "Unsupported output format: $output_format\n";
	exit(1);
}

# Read partitions file
open P,$partfile || die "Can't read $partfile: $!\n";
while (<P>) {
	if (/\s*(\d+)\s+(\d+)\s+(\d+)\s+([\w\-]+)/) {
		my ($major,$minor,$blocks,$name) = ($1,$2,$3,$4);
		if ($name =~ /^sd\w$/) {
			$devices{$name}{'path'} = "/dev/$name";
		}
	}
}

# Informasjonssanking
foreach my $dev (sort keys %devices) {
# {{{
#
# === START OF INFORMATION SECTION ===
# Model Family:     Crucial RealSSD C300
# Device Model:     C300-CTFDDAC256MAG
# Serial Number:    00000000102502FBE1C7
# LU WWN Device Id: 5 00a075 102fbe1c7
# Firmware Version: 0002
# User Capacity:    256,060,514,304 bytes [256 GB]
# Sector Size:      512 bytes logical/physical
# Device is:        In smartctl database [for details use: -P show]
# ATA Version is:   8
# ATA Standard is:  ATA-8-ACS revision 6
# Local Time is:    Wed Sep 26 12:46:33 2012 CEST
# SMART support is: Available - device has SMART capability.
# SMART support is: Enabled
#
# }}}
	open SMARTCTL,"$smartctl -i $devices{$dev}{'path'}|" ||
		warn "Can't open command \"$smartctl -i $devices{$dev}{'path'}|\": $!\n";
	while (<SMARTCTL>) {
		if (/Model Family:\s+(\w.*)/) {
			$devices{$dev}{'model_family'} = $1;
		} elsif (/Device Model:\s+(\w.*)/) {
			$devices{$dev}{'device_model'} = $1;
		} elsif (/Serial Number:\s+(\w.*)/) {
			$devices{$dev}{'serial_number'} = $1;
		} elsif (/LU WWN Device Id:\s+(\w.*)/) {
			$devices{$dev}{'wwn'} = $1;
			$devices{$dev}{'wwn'} =~ s/\s//g;
		} elsif (/Firmware Version:\s+(\w.*)/) {
			$devices{$dev}{'firmware_version'} = $1;
		} elsif (/User Capacity:\s+(\w.*)/) {
			$devices{$dev}{'user_capacity'} = $1;
		} elsif (/Sector Size:\s+([\d\,]+)/) {
			$devices{$dev}{'sector_size'} = $1;
		}
	}
	close SMARTCTL;

# {{{
#
# === START OF READ SMART DATA SECTION ===
# SMART overall-health self-assessment test result: PASSED
#
# }}}
	open SMARTCTL,"$smartctl -H $devices{$dev}{'path'}|" ||
		warn "Can't open command \"$smartctl -i $devices{$dev}{'path'}|\": $!\n";
	while (<SMARTCTL>) {
		if (/SMART overall-health self-assessment test result:\s+(\w.*)/) {
			$devices{$dev}{'smart_status'} = $1;
		}
	}
	close SMARTCTL;
}

# Datavask - om noen felter mangler, bare sett dem til &nbsp;
foreach my $dev (sort keys %devices) {
	foreach my $key ('model_family','device_model','serial_number','wwn','firmware_version',
			'user_capacity','sector_size','smart_status',) {
		$devices{$dev}{$key} = '<i>unknown</i>' unless defined ($devices{$dev}{$key});
	}
}

# Rapportering
if ($output_format eq 'csv') {
	print "deviec;model_family;device_model;serial_number;wwn;firmware_version;user_capacity;sector_size;smart_status\n";
	foreach my $dev (sort keys %devices) {
		$devices{$dev}{'user_capacity'} =~ s/\s.*//g;
		$devices{$dev}{'user_capacity'} =~ s/,//g;
		my $devline = "$dev;";
		foreach my $key ('model_family','device_model','serial_number','wwn','firmware_version',
				'user_capacity','sector_size','smart_status',) {
			$devline .= "$devices{$dev}{$key};";
		}
		$devline =~ s/;$/\n/;
		print "$devline";
	}
} elsif ($output_format eq 'html') {
	print <<EOT;
<html>
	<head>
		<title>Disk status for $hostname</title>
		<style type="text/css">
		table, td, th {
			border:1px solid green;
		}
		th {
			background-color:green;
			color:white;
		}
		</style>
	</head>
	<body>
		<h1>Disk status for $hostname</h1>
		<table>
			<tr>
				<th>Device Name</th>
				<th>Model Family</th>
				<th>Device Model</th>
				<th>Serial Number</th>
				<th>WWN</th>
				<th>Firmware Version</th>
				<th>User Capacity</th>
				<th>Sector Size</th>
				<th>S.M.A.R.T. Status</th>
			</tr>
EOT

	foreach my $dev (sort keys %devices) {
		print <<EOT
			<tr>
				<td>$devices{$dev}{'path'}</td>
				<td>$devices{$dev}{'model_family'}</td>
				<td>$devices{$dev}{'device_model'}</td>
				<td>$devices{$dev}{'serial_number'}</td>
				<td>$devices{$dev}{'wwn'}</td>
				<td>$devices{$dev}{'firmware_version'}</td>
				<td>$devices{$dev}{'user_capacity'}</td>
				<td>$devices{$dev}{'sector_size'}</td>
				<td>$devices{$dev}{'smart_status'}</td>
			</tr>
EOT
}

	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
	my $last_updated = sprintf("Last updated %04d-%02d-%02d %02d:%02d:%02d",  $year+1900,$mon,$mday,$hour,$min,$sec);
	print <<EOT;
		</table>
		<p>
		<small>$last_updated
		Exported by <a href="diskoversikt.pl">diskoversikt.pl</a>, written by <a href="mailto:roy\@karlsbakk.net">Roy Sigurd Karlsbakk</a></small>
	</body>
</html>
EOT
} else {
	print "WTF?";
	exit -42;
}
0
