#!/usr/bin/perl
#
# Write a DataONTAP V4 'spare' label to the specified disk file
# for the simulator
# This should only be run on the same machine architecture that 
#
# Takes two optional args: 
# 	-n specifies NOT prezeroed; 
# 	-p specifies prezeroed (this is the default)

use strict;
use Getopt::Std;

# The pack template for:
# magic software_version raid_version volume_start core_start disk_size disk_type prezeroed
# all of which are predefined values other than disk_size and prezeroed
my $packstring = "\@0L \@20L \@24L \@28L \@32L \@48L \@52L \@576L";

my %opts;

sub Usage {
    print 
	"Usage: $0 [-p|n|h] <disks>\n" .
	"  Write DataONTAP spare labels to the disk files specified.\n" .
	"  -p : Treat disks as prezeroed spares (default)\n" .
	"  -n : Treat disks as nonzeroed spares\n" .
	"  -h : Print this help message.\n";
}


getopts('nphd', \%opts);

die "Can't mix the nonzeroed and prezeroed options!\n" if ($opts{'n'} && 
							   $opts{'p'});

if ($opts{'h'}) {
    Usage();
    exit();
}

foreach (@ARGV) {
    unless (-e $_) {
	warn "No such file $_\n";
	next;
    }
    
    # From the disk file name, compute the label and where to put the 
    # labels. An ONTAP disk file name has the format:
    # <name>:<maker>:VD-<data capacity>[-FZ][-<sector size>]__:<serialno>:<nsectors>
    # We care about the data capacity -- it is used to set the disk_size
    # field of the label -- and the sector size (as it affects where the
    # labels are actually located within the file
    #
    # Extract this information from the filename
    my $nominal_size = (split /:/)[2];
    my ($capacity,$sector_size) = (split /-/, $nominal_size)[1,-1];

    print "Disk $_ nominal capacity is: $capacity\n" if ($opts{"d"});

    # Determine sector size. Also not easy.
    # If there's only one entry (e.g. "100MB") then the sector size and 
    # capacity are the same string, which means the default sector size of 512
    $sector_size = 512 if ($capacity eq $sector_size);

    # A non-numeric sector size (e.g. "FZ") means it's a fast-zeroing disk
    # which has a 512-byte sector size
    $sector_size = 512 unless (+$sector_size > 0);

    print "Disk $_ sector size appears to be $sector_size\n" if ($opts{"d"});
    
    my $size;
    if ($capacity =~ /([0-9]+)(MB|GB)/) {
	# Computing disk size is interesting.
	# The nominal capacity in the disk file name has to be converted
	# into blocks and then we need to add the 5248 blocks for the reserved
	# space at the beginning of the disk.
	$size = ($1 * 256) + 5248;
    } else {
	warn "Couldn't determine desired disk capacity for disk file $_!\n";
	next;
    }

    unless ($size) {
	warn "$_ has size 0, skipping\n";
	next;
    }

    print "Disk $_ has size $size blocks.\n" if ($opts{'d'});

    my $prezeroed = 1;
    $prezeroed = 0 if ($opts{'n'});

    # Pack the important parts of the label	
    # Things like the magic number, software and RAID versions, etc. are 
    # all fixed. The only variants are the size and the prezeroed status.
    my $label = pack($packstring, 0x4e414353, 100, 4, 5248, 5245, $size, 2, 
		  $prezeroed);
    
    # The label is protected with a checksum that is the one's complement
    # of the additive sum of the whole label as unsigned shorts.
    # Perl will compute the additive checksum for us with unpack
    my $checksum = unpack("%16S*", $label);


    printf ("Label Checksum is %x, inverted is %x\n", $checksum, ((~$checksum) & 0xffff)) if ($opts{'d'}) ;

    # but now we need to take the 16-bit one's complement, and 
    # then put that in front of the label (which we already built above).
    my $label_csum = pack("L a4092", (~$checksum) & 0xffff, $label);

    # Now see if we can try to write to the disk file.
    unless (defined(open(DISK_FILE, "+<" . $_))) {
	warn "Couldn't open disk file $_: $!\n";
	next;
    }

    # Figure out where the labels are located on the disk.
    my $disk_block_size = 8 * $sector_size;
    my $label1_offset = $disk_block_size * 1;
    my $label2_offset = $disk_block_size * 2;

    print "Label1 located at $label1_offset, Label2 located at $label2_offset on a disk with block size $disk_block_size and sector size $sector_size\n" if ($opts{"d"});

    # Finally, write the labels to the disk.
    binmode DISK_FILE or warn "Couldn't set mode on $_\n";
    seek DISK_FILE, $label1_offset, 0 or warn "Couldn't seek to label1: $!\n";
    print DISK_FILE $label_csum or warn "Couldn't write label1: $!\n";
    seek DISK_FILE, $label2_offset, 0 or warn "Couldn't seek to label2: $!\n";
    print DISK_FILE $label_csum or warn "Couldn't write label2: $!\n";
    close DISK_FILE;
}    











