#! /usr/bin/perl -w
# $Header: /global/src/usr.bin/src/RCS/admin.pl,v 1.40 2017/11/22 12:32:25 pb22 Exp $

# Use "pod2text" for info

=head1 NAME

cl-admin - various useful admin commands

=head1 SYNOPSIS

B<cl-admin> [--help] [--list] [--defaults] [--yes | --ask] [--priv B<cmd>] [B<-->] B<command> [args]*

=head1 OPTIONS

=over 4

=item B<--verbose>

show what it's doing

=item B<--defaults>

Use the default values without asking for confirmation.

=item B<--yes>

Assume "B<yes>" to all questions as yo whether run a command.

=item B<--ask>

Ask before doing anything, even things which are "safe".

=item B<--help>

generate help info.

=item B<--list>

list the available commands

=item B<--priv> I<cmd>

The command to raise priv.
Normally B<cl-asuser>,
but could be B<sudo> or on older systems, B<really>.

=back

=head1 DESCRIPTION

Various commands to perform useful admin tasks.

=over 4

=item B<linux-clean>

clean up B<linux-headers> and B<linux-images> packages on Ubuntu systemd

=item B<raid-fix>

fix up any broken RAID arrays

=item B<mkraid1> [device] [size]

create a RAID1 partition (and create the /dev/md* device and set the partition types)

=item B<pvcreate> [device]

create an LVM Physical Volume (and the RAID1 partition if needed)

=item B<vgcreate> [vg [device]]

create an LVM Volume Group (and the PV if needed)

=item B<vgextend> [vg [device]]

extend an LVM Volume Group (and create the PV if needed)

=item B<lvcreate> [vg [name [size [mddev]]]]

create an LVM Logival Volume (and the VG if needed)

=item B<mkfs> [device [opts [size [mddev]]]]

create a filesystem on device (and create the LV if needed)

=item B<mkswap> [B<-L> I<label>] [I<device> | I<letter> [I<digit>]]

create a swap partition

=item B<resize2fs> [fs [size]]

extend a file system. Currently will extend an enclosing RAID or LVM partition.

=item B<repos>

show the repositories which are enabled by default

=item B<smart-log>

log the SMART info

=back

=cut

use Getopt::Long;
use Getopt::Long qw(:config require_order);
use Pod::Usage;
%opts = (
	"verbose|v"	=> \$verbose,
	"help|h"	=> \$help,
	"list|l"	=> \$list,
	"defaults|u"	=> \$defaults,
	"yes|y"		=> \$yes,
	"ask|a"		=> \$always_ask,
	"priv|p:s"	=> \$priv,
);

# Commands to raise privs if not already done
$priv		= "cl-asuser";

%sub = (
	"mkraid1"	=> \&sub_mkraid1,
	"mkswap"	=> \&sub_mkswap,
	"mkfs" 		=> \&sub_mkfs,
	"pvcreate" 	=> \&sub_pvcreate,
	"vgcreate" 	=> \&sub_vgcreate,
	"vgextend" 	=> \&sub_vgextend,
	"lvcreate" 	=> \&sub_lvcreate,
	"raid1-fix"	=> \&sub_raid1fix,
	"raid-fix"	=> \&sub_raid1fix,
	"linux-clean"	=> \&sub_linuxclean,
	"clean-linux"	=> \&sub_linuxclean,
	"smart-log"	=> \&sub_smartlog,
	"repos" 	=> \&sub_repos,
	"resize2fs" 	=> \&sub_resize2fs,
);

$cmd = $0;
$cmd =~ s=^/usr/bin/==;

# @ORIG_ARGV = @ARGV;
GetOptions (%opts) || pod2usage();
pod2usage(-verbose => 2) if $help;
sub_list(), exit(0) if $list;

$PART_TYPE_UNUSED	= "ff";		# CL convention for "unused partition"
$PART_TYPE_RAID		= "fd";		# std "RAID" partition type
$PART_TYPE_SWAP		= "82";		# std "swap" partition type
$MISSING		= "missing";	# RAID device not present

$defmd			= "/dev/md5";

# Possible pairings of discs for RAID
@HDS	= ("/dev/sda", "/dev/sdb");
@HDH	= ("/dev/hda", "/dev/hdc");

%bitmap = ();

pod2usage unless $command = shift;
&list_subs("Unknown command '$command'") unless $sub = $sub{$command};
@argv = @ARGV;
@ARGV=();
print STDERR "# Use $command to run $sub with $#argv (@argv)\n" if $verbose;
&$sub(@argv);

# sudo bash -c "mdn=1 vg=vg01bash sh/setup-scratch"

# =============================================================================

# List the available commands
sub sub_list {
	&list_subs("");
}

# -----------------------------------------------------------------------------

# create a RAID1 partition
sub sub_mkraid1 {
	if ($#_ < 0) { warn("$0: mkraid1 requires a device\n"); return; }
	if ($#_ > 1) { warn("$0: mkraid1 requires just a device and an optionsl size\n"); return; }
	$md = shift @_;
	$size = shift @_ || 0;
	$md = "md$md" if $md =~ /^\d+$/;
	$md = "/dev/$md" if $md =~ /^md\d+$/;
	$metadata = ($md =~ /md[1-3]$/) ? "1.0" : "";
	$bitmap = "internal";
	unless ($md =~ /^\/dev\/md(\d+)$/) { warn("$0: mkraid1 requires a device /dev/md<n> not $md\n"); return; }
	$mdn = $1;
	print STDERR "# $cmd: in sub_mkraid1: $md\n" if $verbose;
	@raid = &read_mdstat;
	return warn("$0: $md already exists (", $raid_devs{"md$mdn"}, ")\n") if $raid_devs{"md$mdn"};
	
	&run(1, 0, "$priv mknod $md b 9 $mdn") unless -e $md;

	($dev1, $dev2) = @HDS;
	($dev1, $dev2) = @HDH if -e $HDH[0] && -e $HDH[1] && ! (-e $dev1 && -e $dev2);

	chomp($dev1 = &read_response("$cmd: First  device for $md [%s]: ", "$dev1$mdn"));
	chomp($dev2 = &read_response("$cmd: Second device for $md [%s]: ", "$dev2$mdn"));
	my(@devices) = ($dev1, $dev2);
	foreach $dev (@devices) {
		next if ($dev eq $MISSING);
		chomp($type = &part_type_of($dev));
		if ($type eq $PART_TYPE_RAID) { ; }
		elsif ($type eq $PART_TYPE_UNUSED) {
			&part_type_of($dev, $PART_TYPE_RAID)
		}
		elsif ($type eq "") {
			warn("$0: WARNING: type of $dev cannot be determined. Something may be wrong.\n");
			&part_type_of($dev, $PART_TYPE_RAID)
		}
		else {
			warn("$0: WARNING: $dev is type 0x$type, so may be in use. Think hard before changing it\n");
			&part_type_of($dev, $PART_TYPE_RAID, -1)
		}
	}
	chomp($size = &read_response("$cmd: size (unset or 0 for all) [%s]: ", $size));
	chomp($metadata = &read_response("$cmd: metadata [%s]: ", $metadata));
	chomp($bitmap = &read_response("$cmd: bitmap (normally 'internal' or 'none' [%s]: ", $bitmap));
	$size = ($size) ? " -z$size" : "";
	$metadata = ($metadata) ? " -e$metadata" : "";
	$bitmap = ($bitmap) ? " -b$bitmap" : "";
	&run(0, 0, "$priv mdadm -C -n2 -l1$size$metadata$bitmap $md @devices");
	&run(0, 0, "$priv mdadm-E");
}

# -----------------------------------------------------------------------------

# create a swap partition
sub sub_mkswap {
	my ($dev, $name, $extra) = @_;
	# User typed: cl-admin mswap -L swap /dev/sda6
	if ($dev eq "-L") { ($dev, $extra) = ($extra); }
	else {
		if ($dev && $dev =~ /^[a-z]$/ && $name && $name =~ /^\d+$/) {
			$dev = "sd$dev$name";
			$name = $extra;
			undef $extra;
	    	}
		die("$0: extra argument '$name' for mkswap\n") if defined $name;
		$name = "swap";
	}
	die("$0: extra argument '$extra' for mkswap\n") if defined $extra;
	chomp($dev = &read_response("$cmd: device to setup as swap [%s]: ", '')) unless $dev;
	$dev = "/dev/$dev" if $dev =~ /^dev\d+$/;
	$dev =~ s/^\/dev\///;
	$devf = "/dev/$dev";
	print STDERR "# $cmd: in sub_mkswap: $dev, $devf\n" if $verbose;
	
	chomp($type = &part_type_of($devf));
	if ($type eq $PART_TYPE_SWAP) { ; }
	elsif ($type eq $PART_TYPE_UNUSED) {
		&part_type_of($devf, $PART_TYPE_SWAP)
	}
	elsif ($type eq "") {
		warn("$0: WARNING: type of $devf cannot be determined. Something may be wrong.\n");
		&part_type_of($devf, $PART_TYPE_SWAP)
	}
	else {
		warn("$0: WARNING: $devf is type 0x$type, so may be in use. Think hard before changing it\n");
		&part_type_of($devf, $PART_TYPE_SWAP, -1)
	}
	chomp($label = &read_response("$cmd: Label for $devf [%s]: ", $name));
	die("$0: label must start with 'swap'\n") unless $label =~ /^swap/i;
	&run(0, 0, "$priv mkswap -L $label $devf");
	$fstab = `grep $label /etc/fstab`;
	printf "%s: run: sudo bash -c \"echo 'LABEL=%-16s  swap                    swap    defaults        0 0' >> /etc/fstab\"\n", $0, $label unless $fstab;
}

# -----------------------------------------------------------------------------

# sort by number - need to expand every number to N digits
sub bynum ($$)  {
	my ($a, $b) = @_;
	$a =~ s/(\d+)/sprintf("%04d", $1)/eg;
	$b =~ s/(\d+)/sprintf("%04d", $1)/eg;
        $a cmp $b;
}

# -----------------------------------------------------------------------------

# clean out unneeded linux-* packages on Ubuntu systems
sub sub_linuxclean {
	my $seen_running = 0;
	my @headers = ();
	my @kernels = ();
	chomp($running = `uname -r`);
	die("$0: no kernel release found ($running)\n") unless $running =~ /^[0-9]/;

	my $issue = `cat /etc/issue`;
	
	my @pkgs = sort bynum ($issue =~ /ubuntu/i) ? `dpkg -l linux-\\*` : `rpm -q kernel kernel-xen`;
	my $pkgcmd = ($issue =~ /ubuntu/i) ? "apt-get remove" : "yum remove";

	for $line (@pkgs) {
		chomp(my $pkg = $line);
	        if ($line =~ /^ii\s+(linux-(headers-|image-)([\d]\S+))/) {
			$pkg = $1;
			next if $line =~ /\slinux-headers-\S+-generic/;
			$kern = $3;
		} elsif ($line =~ /^(kernel-xen)(-)(\S+)$/) {
			$kern = "${3}xen";
		} elsif ($line =~ /^(kernel)(-)(\S+)$/) {
			$kern = $3;
		} else { next }

		print "# Process $pkg $kern ...\n" if $verbose;
		if ($pkg =~ /headers-/) {
			push @headers, $pkg;
		} else {
			if ($kern eq $running) {
				$seen_running = 1;
				print STDERR "# Running $pkg\n" if $verbose;
				$pkg = '';
			}
			push @kernels, $pkg;
		}
	}
	print STDERR "# <@kernels> <@headers> ...\n" if $verbose;
	pop @kernels;
	$latest = pop @headers || "<None>";
	print STDERR "# <@kernels> $latest <@headers> ...\n" if $verbose;
	if ($seen_running) {
		push @headers, @kernels;
	} else {
		warn("$0: failed to find running kernel $running - just @kernels $latest\n");
	}
	exit 0 if ($#headers < 0) || (($#headers == 0) && ($headers[0] eq ''));
	my $force = ($yes) ? "-y " : "";
	&run(0, 0, "$priv $pkgcmd $force@headers");
}

# -----------------------------------------------------------------------------

# Fix up RAID1 errors. Look in /proc/mdstat for problems, then use mdadm to find matching UUIDs
sub sub_raid1fix {
	my($errs) = 0;
	my($dev) = "";

	@raid = &read_mdstat;
	#	$raid_devs{$dev} = $devs;
	#	chomp($raid_Uline{$dev} = <FILE>);
	#	chomp($raid_lastline{$dev} = <FILE>);
	foreach $dev (@raid) {
		#print STDERR "# try entry $errs: $dev: <<$raid_lastline{$dev}>> <<$raid_Uline{$dev}>>\n" if $verbose;
		unless ($raid_lastline{$dev} =~ /^\s*$/) {
			print STDERR "# SKIP $dev $devs as: <$raid_Uline{$dev}> <$raid_lastline{$dev}>\n" if $verbose || ! ($raid_lastline{$dev} =~ /resync=DELAYED/ || $raid_lastline{$dev} =~ /recovery\s*=/);
			next;
		}
		next unless $raid_Uline{$dev} =~ /U_/ || $raid_Uline{$dev} =~ /_U/;
		$raid_Uline{$dev} =~ m/^\s*(\d+)/;
		$size[$errs] = $1;
		$md[$errs] = $dev;
		$devs[$errs] = $raid_devs{$dev};
		print STDERR "# fix entry $errs: $dev: $size[$errs], $md[$errs], $devs[$errs]\n" if $verbose;
		if ($devs[$errs] =~ s/\s*(\S+)\[\d+\]\(F\)//) {
			# "man mdadm" suggests that there can be multiple operations on a command line.
			# It doesn't always seem to work, so don't risk it
			&run(1, 0, "$priv mdadm /dev/$md[$errs] -r /dev/$1 \&\& $priv mdadm /dev/$md[$errs] -a /dev/$1");
		} else {
			$errs ++;
		}
	}

	# If no (pending) errors, all done
	return unless $errs;

	@partitions = &read_partitions;
	foreach $name (@partitions) {
		next if $name =~ /^md/ || $name =~ /dm-/;
		$args .= " /dev/$name";
	}

	# With any luck there are some partitions to inspect.
	return unless $args;
	print STDERR "# Run: $priv mdadm -E $args\n" if $verbose;
	@res = `$priv mdadm -E $args 2>&1`;
	# Note the UUIDs of each device
	foreach $_ (@res) {
		if (m=^/dev/(\S+)=) { $dev = $1; $dev =~ s/:$//; next; }
		elsif (/Device UUID\s*:\s*(\S+)/) { ; }
		elsif (/UUID\s*:\s*(\S+)/) { $UUID{$dev} = $1; $dev{$1} .= " $dev"; }
	}
	foreach $UUID (sort keys %dev) {
		print STDERR "# $UUID is $dev{$UUID}\n" if $verbose;
	}
	for $i (0 .. $errs-1) {
		print STDERR "# process $i $size[$i] $md[$i] $devs[$i]\n" if $verbose;
		$devs[$i] =~ m/(\S+)\[\d+\]/;
		$dev = $1;
		$UUID = $UUID{$dev};
		$devs = $dev{$UUID};
		print STDERR "# $i is $devs[$i] => $dev, $UUID, $devs\n" if $verbose;
		foreach $one (split(/ /, $raid_devs{$md[$i]})) {
			$one =~ s/\[\d+\]$//;
			$devs =~ s/$one//;
		}
		$devs =~ s/$dev//;
		$devs =~ s/  */ /g;
		$devs =~ s/^ //;
		$devs =~ s/ $//;
		# Do we have <1, 1 or >1 options?
		if (! $devs) {
			warn("$0: no previous UUID $UUID for $md[$i] / $dev found\n");
		} elsif ($devs =~ / /) {
			warn("$0: multiple UUID $UUID for $md[$i] / $dev found: $devs\n");
		} else {
			&run(1, 0, "$priv mdadm --re-add /dev/$md[$i] /dev/$devs") &&
			&run(0, 0, "$priv mdadm -a /dev/$md[$i] /dev/$devs");
		}
	}
}

# -----------------------------------------------------------------------------

# read /proc/partitions
sub read_partitions {
	my(@partitions) = ();

	# Now process /proc/partitions deciding what to do
	$file = "/proc/partitions";
	print STDERR "# open $file\n" if $verbose;
	open(FILE, $file) || die("$0: failed to open $file: $!\n");
	$args = "";
	while (<FILE>) {
		print STDERR "# $_" if $verbose;
		($j, $j, $size, $name, $j) = split;
		next unless defined $name;
		next unless $size =~ /^\d+$/;
		$size{$name} = $size;
		push @partitions, $name;
	}
	close(FILE) || die("$0: failed to close $file: $!\n");
	print STDERR "# close $file and return $#partitions: @partitions\n" if $verbose;
	return @partitions;
}

# -----------------------------------------------------------------------------

# read LV list
sub read_lv {
	my(@lvs) = ();
	open(LVSCAN, "$priv lvm lvs|") || return;
	while (<LVSCAN>) {
		#  LV      VG   Attr   LSize  Origin Snap%  Move Log Copy% 
		#  scratch vg01 -wi-ao 32.00G              
		next if /^\s*LV\s/;
		if (/\s*(\S+)\s+(\S+)/) {
			push @lvs, $1;
			$lv{$1} = 1;
			$lv{"$2/$1"} = 1;
			$lv{"/dev/$2/$1"} = 1;
		}
	}
	close(LVSCAN);
	return @lvs;
}

# -----------------------------------------------------------------------------

# read VG list
sub read_vg {
	my(@vgs) = ();
	open(VGSCAN, "$priv lvm vgs|") || return;
	while (<VGSCAN>) {
		#   VG   #PV #LV #SN Attr   VSize  VFree  
		#   vg01   1   1   0 wz--n- 32.11G 112.00M
		next if /^\s*VG\s/;
		if (/\s*(\S+)\s+(\d+)/) {
			push @vgs, $1;
			$vg{$1} = $2;
		}
	}
	close(VGSCAN);
	return @vgs;
}

# -----------------------------------------------------------------------------

# read PV list
sub read_pv {
	my(@pvs) = ();
	open(PVSCAN, "$priv lvm pvs|") || return;
	#   PV         VG   Fmt  Attr PSize  PFree  
	#   /dev/md6   vg01 lvm2 a-   32.11G 112.00M

	while (<PVSCAN>) {
		next if /^\s*PV\s/;
		if (/^\s*(\/dev\/\S+)\s+(\S+)/) {
			push @pvs, $1;
			$pv{$1} = $2;
		}
	}
	close(PVSCAN);
	return @pvs;
}

# -----------------------------------------------------------------------------

# read /proc/mdstat
sub read_mdstat {
	my(@partitions) = ();

	# Process /proc/mdstat, fixing "failed" devices, and noting degraded devices and their sizes
	$file = "/proc/mdstat";
	print STDERR "# open $file\n" if $verbose;
	open(FILE, $file) || die("$0: failed to open $file: $!\n");
	while (<FILE>) {
		print STDERR "# $_" if $verbose;
		# md10 : active raid1 hdc10[1] hda10[0]
		#      2104384 blocks [2/2] [UU]
		# md1 : active raid1 hdc1[2](F) hda1[0]
		#      8417920 blocks [2/1] [U_]
		# md3 : active raid1 hda3[0]
		#      8417984 blocks [2/1] [U_]
		#      bitmap: 0/153 pages [0KB], 16KB chunk
		if (/^(md\d+)\s*:\s*active\s+raid\d+\s+(.*)/) {
			my($dev, $devs) = ($1, $2);
			push @partitions, $dev;
			$raid_devs{$dev} = $devs;
			$raid_devs{"/dev/$dev"} = $devs;
			print STDERR "# $dev is $devs\n" if $verbose;
			# e.g.: 6000000 blocks [2/2] [UU]
			chomp($raid_Uline{$dev} = <FILE>);
			# blank or e.g.: bitmap: 1/184 pages [4KB], 16KB chunk
			chomp($raid_lastline{$dev} = <FILE>);
			if ($raid_lastline{$dev} =~ /bitmap:/) {
				$bitmap{$dev} = 1;
				$bitmap{"/dev/$dev"} = 1;
				chomp($raid_lastline{$dev} = <FILE>);
			}
		}
	}
	close(FILE) || die("$0: failed to close $file: $!\n");
	print STDERR "# close $file and return $#partitions: @partitions\n" if $verbose;
	$read_mdstat = 1;
	return @partitions;
}

# -----------------------------------------------------------------------------

# list the available subroutines
sub list_subs {
	my($why)	= @_;
	print STDERR "$0: $why\n" if $why;
	print STDERR "$0: valid commands are:\n";
	# foreach $cmd (sort keys %sub) { print STDERR "\t$cmd\n"; }
	print STDERR &format(1,16,80, sort keys %sub);
	exit(!!$why);
}

# -----------------------------------------------------------------------------

# format output of an array into virtual columns
sub format {
	my($offset, $item, $max, @items) = @_;
	my($pref) = " " x $offset;
	my($res) = $pref;
	my($len) = $offset;
	foreach $val (@items) {
		$ilen = length($val);
		if (($len + $ilen) > $max) {
			$res =~ s/  *$/\n$pref/;
			$len = $offset;
		} 
		$ilen = (int ($ilen / $item) +1) * $item;
		$len += $ilen;
		$res .= sprintf("%-${ilen}s", $val);
	}
	$res =~ s/  *$/\n/;
	return $res;
}

# -----------------------------------------------------------------------------

# get or set the type of a partition
sub part_type_of {
	my($part, $type, $ask) = @_;
	$part =~ s/(\d+)$//;
	my($n) = $1;
	print STDERR "# part_type_of($part, $n, $type)\n" if $verbose;
	if (!defined($type)) {
		return `$priv sfdisk --print-id $part $n`;
	} else {
		return &run((defined $ask) ? $ask : 0, 0, "$priv sfdisk --change-id $part $n $type");

	}
}

# -----------------------------------------------------------------------------

# Run a command, asking the user whether to run the command.
sub run {
	my ($n, $quiet, $rcmd) = @_;
	if ((($n < 1) || ($always_ask)) && ! ($yes)) {
		my($repl) = &read_response("$cmd: run: $rcmd [%s]? ", "y");
		if ($repl && ($repl =~ /^[Nn]/)) { print STDERR "(skip)\n"; return; }
	} elsif (!$quiet) {
		print STDERR "$cmd: run: $rcmd\n";
	}
	$! = 0;
	$rc = system($rcmd);
	print STDERR "# <$rcmd> gave $rc ($!)\n" if$rc || $verbose;
	die("$0: exiting automated run due to failure\n") if $rc && $yes;
	return $rc;
}

# -----------------------------------------------------------------------------

# create an LVM Physical Partition
sub sub_pvcreate {
	my ($dev) = @_;
	print STDERR "# pvcreate(@_)\n" if $verbose;
	chomp($dev = &read_response("$cmd: device to pvcreate [%s]: ", $defmd)) unless $dev;
	&read_pv;
	if ($pv{$dev}) { print STDERR "$cmd: $dev exists\n"; return; }
	if ($dev =~ /md\d+$/) {
		&read_mdstat;
		&sub_mkraid1($dev) unless $raid_devs{$dev};
	}
	&run(0, 0, "$priv lvm pvcreate --metadatasize 994k --dataalignment 1m $dev");
	&run(1, 0, "$priv lvm pvs -o+pe_start $dev");
}

# -----------------------------------------------------------------------------

# create an LVM Volume Group
sub sub_vgcreate {
	my ($vg, $dev) = @_;
	print STDERR "# vgcreate(@_)\n" if $verbose;
	chomp($vg = &read_response("$cmd: volume group to create [%s]: ", "vg01")) unless $vg;
	if ($vg{$vg}) { print STDERR "$cmd: $vg already exists\n"; return; }
	&read_vg;
	if ($vg{$vg}) { print STDERR "$cmd: $vg already exists\n"; return; }
	chomp($dev = &read_response("$cmd: device to pvcreate [%s]: ", $defmd)) unless $dev;
	&read_pv;
	&sub_pvcreate($dev) unless $pv{$dev};
	# &sub_mkraid1($dev) if $dev =~ /md\d+$/;
	&run(0, 0, "$priv lvm vgcreate $vg $dev");
	&run(1, 0, "$priv lvm vgscan");
}

# -----------------------------------------------------------------------------

# extend an LVM Volume Group
sub sub_vgextend {
	my ($vg, $dev) = @_;
	print STDERR "# vgextend(@_)\n" if $verbose;
	chomp($vg = &read_response("$cmd: volume group to extend [%s]: ", "vg01")) unless $vg;
	&read_vg;
	if (!$vg{$vg}) { print STDERR "$cmd: $vg does not exist\n"; return; }
	chomp($dev = &read_response("$cmd: device to pvcreate [%s]: ", $defmd)) unless $dev;
	&read_pv;
	&sub_pvcreate($dev) unless $pv{$dev};
	# &sub_mkraid1($dev) if $dev =~ /md\d+$/;
	&run(0, 0, "$priv lvm vgextend $vg $dev");
}

# -----------------------------------------------------------------------------

# create a FS
sub sub_mkfs {
	my ($dev, $opts, $size, $md) = @_;
	$label = '';
	print STDERR "# mkfs(@_)\n" if $verbose;

	# Process /dev/<VG>/<LV>
	my ($vfs, $mfs, $vg, $lv) = fs_names($dev);

	print STDERR "# mkfs(@_) gives ($vfs, $mfs, $vg, $lv) from $dev\n" if $verbose;
	&sub_lvcreate($vg, $dev, $size, $md) if $vg && ! -e $dev;
	if ($dev =~ /^\/dev\/md(\d+)$/) {
		my $n = $1;
		my $os = $n < 4 && $n > 0;
		&sub_mkraid1($dev, ($os) ? 1000 : '');
		$label = "OS_partion${n}_md$n" if $os;
	}
	unless ($opts) {
	  $label = $lv if $label eq '';
	  chomp($label	= &read_response("$cmd: label [%s]: ", "-L '$label'"));
	  chomp($type	= &read_response("$cmd: type [%s]: ", "-text4"));
	  chomp($stripe	= &read_response("$cmd: stripe [%s]: ", "-Estripe-width=32"));
	  chomp($resize	= &read_response("$cmd: resize [%s]: ", "-Eresize=512G"));
	  chomp($journal= &read_response("$cmd: journal [%s]: ", ($type =~ /ext3/) ? "-j" : ""));
	  chomp($block	= &read_response("$cmd: bytes per block [%s]: ", "-b4096"));
	  chomp($inode	= &read_response("$cmd: bytes per inode [%s]: ", "-i16000"));
	  chomp($sparse	= &read_response("$cmd: spares superblocks [%s]: ", "-s 1"));
	  chomp($extra	= &read_response("$cmd: extra args [%s]: ", ""));
	  chomp($opts	= &read_response("$cmd: mkfs opts [%s]: ", "$type $sparse $inode $block $journal $stripe $resize $label $extra"));
	}
	&run(0, 0, "$priv mkfs $opts $dev");
}

# -----------------------------------------------------------------------------

# create an LVM Logical Partition
sub sub_lvcreate {
	my ($vg, $name, $size, $md) = @_;
	my ($vfs, $mfs, $pvg, $plv) = fs_names($vg);
	# Allow /dev/<VG>/<LV>
	if ($plv) { $size = $name; $label = $name = $plv; }
	print STDERR "# lvcreate(@_)\n" if $verbose;
	chomp($vg = &read_response("$cmd: volume group to use [%s]: ", "vg01")) unless $vg;
	chomp($name = &read_response("$cmd: logical volume to create [%s]: ", "scratch")) unless $name;
	&read_lv;
	if ($lv{"$vg/$name"}) { print STDERR "$cmd: $vg/$name already exists\n"; return; }
	&read_vg;
	&sub_vgcreate($vg, $md) unless $vg{$vg};
	chomp($size = &read_response("$cmd: size of logical volume [%s]: ", "32G")) unless $size;
	&run(0, 0, "$priv lvm lvcreate -L $size -n $name $vg");
}

sub read_response {
	my($prompt, $default) = @_;
	printf STDERR $prompt, ($default) ? $default : "";
	if ($defaults) { print STDERR "\n" } else { $_ = <>; }
	return ($_ && $_ ne "\n") ? $_ : $default;
}

# -----------------------------------------------------------------------------

# display repositories
sub sub_repos {
	my(@files) = @ARGV;

	$all =0;
	$repow = 16;
	$all++, shift if $#ARGV >= 0 && $ARGV[0] eq "-a";
	@files = </etc/yum.repos.d/*.repo> unless $#files >= 0;
	@files = </etc/yum.conf> unless $#files >= 0;
	$name = ''; &dump_repo;		# Clear all vars
	foreach $repo (@files) {
		&read_repo($repo);
	}
}

sub read_repo {
	my($repo) =@_;
	print STDERR "# Open $repo\n" if $verbose;
	open(REPO, $repo) || die("$0: failed to open $repo: $!\n");
	while (<REPO>) {
		if (! /^\s*#/ && /=$/) { chomp; $_ .= <REPO>; }
		if (/^\s*\[(.*)\]/) {
			$newname = $1;
			&dump_repo;
			$name = $newname;
		} elsif (/^\s*([^\s=]+)\s*=\s*(.*)/) {
			($key,$val) = ($1, $2);
			print "# <$key>=<$val>\n" if $verbose;
			if ($key eq "enabled") { $enabled = $val }
			elsif ($key eq "baseurl") { $baseurl = $val }
			elsif ($key eq "mirrorlist") { $mirrorlist = $val }
		}
	}
	close(REPO) || die("$0: failed to close $repo: $!\n");
	&dump_repo;
}

sub dump_repo {
	if ($name && ($enabled || $all)) {
		$mirrorlist =~ s=http://mirrors.fedoraproject.org/mirrorlist\?=<mirrors.fc>?=;
		$baseurl =~ s=^http://=h:=;
		$baseurl =~ s=^h:www.cl.cam.ac.uk/=h:cl/=;
		$baseurl =~ s=^h:www-uxsup.csx.cam.ac.uk/=h:csx/=;
		printf "%-${repow}s %s %s\n", $name, $enabled, $baseurl . $mirrorlist;
	}
	$mirrorlist = $baseurl = $name = '';
	$enabled = 1;
}

# -----------------------------------------------------------------------------

# resize a FS, e.g. extending a RAID1 or LV and its contents
# /dev/mapper/$VG-$LV is use readable, but /dev/$VG/$LV is not :-(
sub sub_resize2fs {
	my ($fs, $size) = @_;
	my ($delta) = 0;
	print STDERR "# resize2fs(@_) $fs, $size\n" if $verbose;
	chomp($fs = &read_response("$cmd: FS to extend [%s]: ", "/")) unless $fs;
	unless ($fs =~ /^\/dev\//) {
		open(DF, "df $fs|") || die("$0: failed to open 'df $fs': $!\n");
		$got = "";
		while (<DF>) {
			if (/^(\/dev\/\S+)/) {
				print STDERR "# $_" if $verbose;
				$got = $1; last;
			}
		}
		close(DF);
		die("$0: failed to locate device for $fs\n") unless $got;
		$fs = $got;
	}
	my ($vfs, $mfs) = fs_names($fs);
	unless ($size) {
		$bs = &blocksize_of_fs($mfs, 4096);
		$size = (&size_of_part($mfs, 0) * 1024) /  $bs;
		chomp($size = &read_response("$cmd: size in blocks or \$n[KMG] (+size for increase) [%s]: ", $size))
	}
	die("$0: no size given for $fs\n") unless $size;
	$delta = 1 if $size =~ s/^\+//;
	if ($size =~ /^(\d+)([kKmMgG])$/) {
		$size = $1;
		$unit = $2;
		if ($unit =~ /k/i) { $size *= 1; }
		elsif ($unit =~ /m/i) { $size *= 1024; }
		elsif ($unit =~ /g/i) { $size *= 1024 * 1024; }
		$bs = &blocksize_of_fs($mfs, 4096) unless $bs;
		$size = int(($size * 1024) / $bs);
	}
	$bs = &blocksize_of_fs($mfs, 4096) unless $bs;
	# If all else fails, use the partition size
	$curr = int((&size_of_part($mfs, 0) * 1024) /  $bs);
	# better is to use the FS size.
	$curr = &blocks_in_fs($mfs, $curr);
	$size += $curr if $delta;
	if ($curr == $size) {
		$quit = &read_response("$cmd: new size $size is unchanged. Quit? [%s]: ", "yes");
		die("$0: size $size == $curr\n") unless $quit =~ /^[nN]/;
	}
	if ($curr > $size) {
		$diff = $curr - $size;
		$quit = &read_response("$cmd: new size $size appears to be smaller than current $curr by $diff. This script can currently only enlarge filesystems. Quit? [%s]: ", "yes");
		die("$0: size $size < $curr\n") unless $quit =~ /^[nN]/;
	}
	unless ($curr == $size) {
		$k = ($size * $bs) / 1024;
		$part = &name_of_fs($mfs);
		print "# part is $part ($mfs)\n" if $verbose;
		if ($fs =~ /^\/dev\/md\d+$/) {
			&read_mdstat unless $read_mdstat;
			&run(0, 0, "$priv mdadm --grow -b none $fs") if $bitmap{$fs};
			&run(0, 0, "$priv mdadm --grow -z $k $fs");
			&run(0, 0, "$priv mdadm --grow -b internal $fs") if $bitmap{$fs};
		}
		elsif ($part =~ /dev\/dm-\d/) {
			if ($curr < $size) {
				&run(0, 0, "$priv lvm lvextend -L ${k}k $vfs");
			}
		}
		elsif ($part =~ /dev\/mapper/) {
			if ($curr < $size) {
				&run(0, 0, "$priv lvm lvextend -L ${k}k $vfs");
			}
		}
		else {
			print "# Need to resize $fs to $k KB\n";
		}
	}
	&run(0, 0, "$priv resize2fs -p $fs $size");
}

sub blocksize_of_fs {
	my ($fs, $bs) = @_;
	open(T2F, "$priv tune2fs -l $fs|") || return $bs;
	while (<T2F>) {
		if (/^Block size:\s*(\d+)$/) {
			print STDERR "# $_" if $verbose;
			$bs = $1; last
		}
	}
	close(T2F);
	return $bs;
}

sub blocks_in_fs {
	my ($fs, $size) = @_;
	open(T2F, "$priv tune2fs -l $fs|") || return $size;
	while (<T2F>) {
		if (/^Block count:\s*(\d+)$/) {
			print STDERR "# $_" if $verbose;
			$size = $1; last
		}
	}
	close(T2F);
	return $size;
}

sub size_of_part {
	my ($fs, $size) = @_;
	my ($name) = &name_of_fs($fs);
	my ($tdev, $bdev) = (-1, -1);
	print STDERR "# size_of_part($fs,$size) $name\n" if $verbose;
	if (@stat = stat($name)) {
		my ($dev) = $stat[6];
		$tdev = $dev >> 8;
		$bdev = $dev & 255;
		print STDERR "# $name is ($tdev, $bdev) <", join("> <", @stat), ">\n" if $verbose;
	}
	$name =~ s/^\/dev\///;
	open(PART, "/proc/partitions") || return $size;
	while (<PART>) {
		if (/^\s*\d+\s+\d+\s+(\d+)\s+$name$/) {
			print STDERR "# $_" if $verbose;
			$size = $1; last;
		}
		if (/^\s*$tdev\s+$bdev\s+(\d+)\s+/) {
			print STDERR "##  $_" if $verbose;
			$size = $1; last;
		}
	}
	close(PART);
	return $size;
}

sub name_of_fs {
	my ($fs) = @_;
	return $fs if $fs =~ /^\/dev\/[shm]d/;
	$ls = `ls -lLd $fs`;
	# brw-------  1 root system 253, 0 Apr 19 22:52 /dev/m/vg05-scratch
	$fs = "/dev/dm-$1" if $ls =~ /\s+253,\s*(\d+)/;
	return $fs;
}

# -----------------------------------------------------------------------------

# dump the SMART info
sub sub_smartlog {
	my ($vg, $dev) = @_;
	$twe = "twe0";
	print STDERR "# smart_log(@_)\n" if $verbose;

	$partitions	= "/proc/partitions";
	$modules	= "/proc/modules";

	%stdname = (
		"ih0b0t0l0"	=> "hda",
		"ih0b1t0l0"	=> "hdc",
		"sh0b0t0l0"	=> "sda",
	);

	@discs = ();
	open(PARTITIONS, $partitions) || die("$0: Failed to open $partitions: $!\n");
	while (<PARTITIONS>) {
		#    8     0 1394032640 sda 55160 587095 3831104 1967710 1889686 ...
		if (/^\s*\d+\s+\d+\s+\d+\s+(([sh]d[a-z][a-z]?)|(\S+\/disc))\s+/) {
			push @discs, $1;
		}
		elsif ($verbose && $verbose > 3) { print STDERR ": Ignore part: $_\n"; }
	}
	close(PARTITIONS) || die("$0: Failed to close $partitions: $!\n");
	
	foreach $disc (sort @discs) {
		&process($disc, '', '');
	}
}

sub process {
	my($dev, $opts, $tag) = @_;
	my($pend) = "";
	my($devn) = &shortname($dev);
	print STDERR ": process($dev, $opts, $tag)\n" if $verbose && $verbose > 2;
	if ($opts eq '' && ($dev =~ /^sd/ || $dev =~ /scsi/) && &has_3ware) {
		return "" if &has_3ware < 0;	# Don't probe!!
		for $i (0 .. 7) { &process($twe, "-d 3ware,$i", ",$i"); }
		$twe++;
	}
	if ($opts eq '' && ($dev =~ /^sd/ || $dev =~ /scsi/) && &has_megaraid) {
		return "" if &has_megaraid < 0;	# Don't probe!!
		for $i (0 .. 15) { &process($dev, "-d megaraid,$i", ""); }
	}
	if (($dev =~ /^sd/ || $dev =~ /scsi/) && &has_libata && $opts !~ /-d 3ware/ && $opts !~ /-d ata/) { 
		if (&has_libscsi) { &process($dev, "-d ata", ""); }
		else { $opts .= " -d ata"; }
	}
	print ": process($dev, $opts, $tag)\n";
	&run(1, 0, "$priv smartctl -x $opts /dev/$dev");
}

# Is the machine using libata
sub has_libata {
	return $has_libata if defined $has_libata;
	unless (open(MODULES, $modules)) { return $has_libata = 1; }
	while (<MODULES>) { return $has_libata = 1 if /libata/; }
	close(MODULES) || return $has_libata = 1;
	return $has_libata = 0;
}


# Is the machine using real scsi drivers
sub has_libscsi {
	return $has_libscsi if defined $has_libscsi;
	unless (open(MODULES, $modules)) { return $has_libscsi = 1; }
	while (<MODULES>) { return $has_libscsi = 1 if /scsi_transport_spi/; }
	close(MODULES) || return $has_libscsi = 1;
	return $has_libscsi = 0;
}

# Does this machine have a 3ware controller ? Assume so if unsure.
sub has_3ware {
	return $has_3ware if defined $has_3ware;
	return $has_3ware = 1 if -e "/dev/twe0";
	unless (open(MODULES, $modules)) { return $has_3ware = 1; }
	while (<MODULES>) {
		return $has_3ware = -1 if /3w_xxxx/ && ! -d "/proc/scsi/3w-xxxx";	# KILLS grafham under 2.6.10-1.737_FC3smp (OK under 2.6.9-1.6_FC2smp)
		return $has_3ware = 1 if /3ware/ || /3w[_-]xxx/;
	}
	close(MODULES) || return $has_3ware = 1;
	return $has_3ware = 0;
}

# Does this machine have a megaraid controller ? Assume so if unsure.
sub has_megaraid {
	return $has_megaraid if defined $has_megaraid;
	return $has_megaraid = 1 if -e "/dev/megaraid_sas_ioctl_node";
	unless (open(MODULES, $modules)) { return $has_megaraid = 1; }
	while (<MODULES>) {
		return $has_megaraid = 1 if /megaraid_sas/
	}
	close(MODULES) || return $has_megaraid = 1;
	return $has_megaraid = 0;
}

# Contract a devfs path name
sub shortname {
	my($devn) = @_;
	$devn =~ s=/host(\d+)/=h$1/=;
	$devn =~ s=/bus(\d+)/=b$1/=;
	$devn =~ s=/target(\d+)/=t$1/=;
	$devn =~ s=/lun(\d+)/=l$1/=;
	$devn =~ s=/disc==;
	$devn =~ s=^ideh=ih=;
	$devn =~ s=^scsih=sh=;
	$devn = $stdname{$devn} if defined $stdname{$devn};
	return $devn;
}

# Get the two FS names for LVs, and VG and LV
sub fs_names {
	my($fs) = @_;
	my ($vfs, $mfs);
	my ($vg, $lv) = ('', '');
	$vfs = $mfs = $fs;
	if ($vfs =~ /^\/dev\/mapper\/(\S+)-(\S+)/) { $vfs = "/dev/$1/$2"; $vg=$1; $lv=$2; }
	if ($mfs =~ /^\/dev\/(vg[^\/]+)\/(\S+)/) { $mfs = "/dev/mapper/$1-$2"; $vg=$1; $lv=$2; }
	return ($vfs, $mfs, $vg, $lv);
}
