A. bootvinum Perl Script

The bootvinum Perl script below reads /etc/fstab and current drive partitioning. It then writes several files in the current directory and several variants of /etc/fstab in /etc. These files significantly simplify the installation of Vinum and recovery from spindle failures.

#!/usr/bin/perl -w
use strict;
use FileHandle;

my $config_tag1 = '$Id: article.sgml,v 1.17 2012-03-20 08:56:30 pluknet Exp $';
# Copyright (C) 2001 Robert A. Van Valzah
#
# Bootstrap Vinum
#
# Read /etc/fstab and current partitioning for all spindles mentioned there.
# Generate files needed to mirror all filesystems on root spindle.
#  A new partition table for each spindle
#  Input for the vinum create command to create Vinum objects on each spindle
#  A copy of fstab mounting Vinum volumes instead of BSD partitions
#  Copies of fstab altered for server's degraded modes of operation
# See handbook for instructions on how to use the files generated.
# N.B. This bootstrapping method shrinks size of swap partition by the size
# of Vinum's on-disk configuration (265 sectors).  It embeds existing file
# systems on the root spindle in Vinum objects without having to copy them.
# Thanks to Greg Lehey for suggesting this bootstrapping method.
# Expectations:
#  The root spindle must contain at least root, swap, and /usr partitions
#  The rootback spindle must have matching /rootback and swap partitions
#  Other spindles should only have a /NOFUTURE* filesystem and maybe swap
#  File systems named /NOFUTURE* will be replaced with Vinum drives

# Change configuration variables below to suit your taste
my $vip = 'h';					# VInum Partition
my @drv = ('YouCrazy', 'UpWindow', 'ThruBank',	# Vinum DRiVe names
  'OutSnakes', 'MeWild', 'InMovie', 'HomeJames', 'DownPrices', 'WhileBlind');
# No configuration variables beyond this point

my %vols;		# One entry per Vinum volume to be created
my @spndl;		# One entry per SPiNDLe
my $rsp;		# Root SPindle (as in /dev/$rsp)
my $rbsp;		# RootBack SPindle (as in /dev/$rbsp)
my $cfgsiz = 265;	# Size of Vinum on-disk configuration info in sectors
my $nxtpas = 2;		# Next fsck pass number for non-root filesystems

# Parse fstab, generating the version we'll need for Vinum and noting
# spindles in use.
my $fsin = "/etc/fstab";
#my $fsin = "simu/fstab";
open(FSIN, "$fsin") || die("Couldn't open $fsin: $!\n");

my $fsout = "/etc/fstab.vinum";
open(FSOUT, ">$fsout") || die("Couldn't open $fsout for writing: $!\n");

while (<FSIN>) {
  my ($dev, $mnt, $fstyp, $opt, $dump, $pass) = split;
  next if $dev =~ /^#/;
  if ($mnt eq '/' || $mnt eq '/rootback' || $mnt =~ /^\/NOFUTURE/) {
    my $dn = substr($dev, 5, length($dev)-6);	# Device Name without /dev/
    push(@spndl, $dn) unless grep($_ eq $dn, @spndl);
    $rsp = $dn if $mnt eq '/';
    next if $mnt =~ /^\/NOFUTURE/;
  }
  # Move /rootback from partition e to a
  if ($mnt =~ /^\/rootback/) {
    $dev =~ s/e$/a/;
    $pass = 1;
    $rbsp = substr($dev, 5, length($dev)-6);
    print FSOUT "$dev\t\t$mnt\t$fstyp\t$opt\t\t$dump\t$pass\n";
    next;
  }
  # Move non-root filesystems on smallest spindle into Vinum
  if (defined($rsp) && $dev =~ /^\/dev\/$rsp/ && $dev =~ /[d-h]$/) {
    $pass = $nxtpas++;
    print FSOUT "/dev/vinum$mnt\t\t$mnt\t\t$fstyp\t$opt\t\t$dump\t$pass\n";
    $vols{$dev}->{mnt} = substr($mnt, 1);
    next;
  }
  print FSOUT $_;
}
close(FSOUT);
die("Found more spindles than we have abstract names\n") if $#spndl > $#drv;
die("Didn't find a root partition!\n") if !defined($rsp);
die("Didn't find a /rootback partition!\n") if !defined($rbsp);

# Table of server's Degraded Modes
# One row per mode with hash keys
#   fn	FileName
#   xpr	eXPRession needed to convert fstab lines for this mode
#   cm1	CoMment 1 describing this mode
#   cm2	CoMment 2 describing this mode
#   FH  FileHandle (dynamically initialized below)
my @DM = (
  { cm1 => "When we only have $rsp, comment out lines using $rbsp",
    fn  => "/etc/fstab_only_have_$rsp",
    xpr => "s:^/dev/$rbsp:#\$&:",
  },
  { cm1 => "When we only have $rbsp, comment out lines using $rsp and",
    cm2 => "rootback becomes root",
    fn  => "/etc/fstab_only_have_$rbsp",
    xpr => "s:^/dev/$rsp:#\$&: || s:/rootback:/\t:",
  },
  { cm1 => "When only $rsp root is bad, /rootback becomes root and",
    cm2 => "root becomes /rootbad",
    fn  => "/etc/fstab_${rsp}_root_bad",
    xpr => "s:\t/\t:\t/rootbad: || s:/rootback:/\t:",
  },
);

# Initialize output FileHandles and write comments
foreach my $dm (@DM) {
  my $fh = new FileHandle;
  $fh->open(">$dm->{fn}") || die("Can't write $dm->{fn}: $!\n");
  print $fh "# $dm->{cm1}\n" if $dm->{cm1};
  print $fh "# $dm->{cm2}\n" if $dm->{cm2};
  $dm->{FH} = $fh;
}

# Parse the Vinum version of fstab written above and write versions needed
# for server's degraded modes.
open(FSOUT, "$fsout") || die("Couldn't open $fsout: $!\n");
while (<FSOUT>) {
  my $line = $_;
  foreach my $dm (@DM) {
    $_ = $line;
    eval $dm->{xpr};
    print {$dm->{FH}} $_;
  }
}

# Parse partition table for each spindle and write versions needed for Vinum
my $rootsiz;	# ROOT partition SIZe
my $swapsiz;	# SWAP partition SIZe
my $rspminoff;	# Root SPindle MINimum OFFset of non-root, non-swap, non-c parts
my $rspsiz;	# Root SPindle SIZe
my $rbspsiz;	# RootBack SPindle SIZe
foreach my $i (0..$#spndl) {
  my $dlin = "disklabel $spndl[$i] |";
#  my $dlin = "simu/disklabel.$spndl[$i]";
  open(DLIN, "$dlin") || die("Couldn't open $dlin: $!\n");

  my $dlout = "disklabel.$spndl[$i]";
  open(DLOUT, ">$dlout") || die("Couldn't open $dlout for writing: $!\n");

  my $dlb4 = "$dlout.b4vinum";
  open(DLB4, ">$dlb4") || die("Couldn't open $dlb4 for writing: $!\n");

  my $minoff;		# MINimum OFFset of non-root, non-swap, non-c partitions
  my $totsiz = 0;	# TOTal SIZe of all non-root, non-swap, non-c partitions
  my $swapspndl = 0;	# True if SWAP partition on this SPiNDLe
  while (<DLIN>) {
    print DLB4 $_;
    my ($part, $siz, $off, $fstyp, $fsiz, $bsiz, $bps) = split;

    if ($part && $part eq 'a:' && $spndl[$i] eq $rsp) {
	$rootsiz = $siz;
    }
    if ($part && $part eq 'e:' && $spndl[$i] eq $rbsp) {
      if ($rootsiz != $siz) {
	die("Rootback size ($siz) != root size ($rootsiz)\n");
      }
    }
    if ($part && $part eq 'c:') {
      $rspsiz  = $siz if $spndl[$i] eq $rsp;
      $rbspsiz = $siz if $spndl[$i] eq $rbsp;
    }
    # Make swap partition $cfgsiz sectors smaller
    if ($part && $part eq 'b:') {
      if ($spndl[$i] eq $rsp) {
	$swapsiz = $siz;
      } else {
	if ($swapsiz != $siz) {
	  die("Swap partition sizes unequal across spindles\n");
	}
      }
      printf DLOUT "%4s%9d%9d%10s\n", $part, $siz-$cfgsiz, $off, $fstyp;
      $swapspndl = 1;
      next;
    }
    # Move rootback spindle e partitions to a
    if ($part && $part eq 'e:' && $spndl[$i] eq $rbsp) {
      printf DLOUT "%4s%9d%9d%10s%9d%6d%6d\n", 'a:', $siz, $off, $fstyp,
	$fsiz, $bsiz, $bps;
      next;
    }
    # Delete non-root, non-swap, non-c partitions but note their minimum
    # offset and total size that're needed below.
    if ($part && $part =~ /^[d-h]:$/) {
      $minoff = $off unless $minoff;
      $minoff = $off if $off < $minoff;
      $totsiz += $siz;
      if ($spndl[$i] eq $rsp) {	# If doing spindle containing root
	my $dev = "/dev/$spndl[$i]" . substr($part, 0, 1);
	$vols{$dev}->{siz} = $siz;
	$vols{$dev}->{off} = $off;
	$rspminoff = $minoff;
      }
      next;
    }
    print DLOUT $_;
  }
  if ($swapspndl) {	# If there was a swap partition on this spindle
    # Make a Vinum partition the size of all non-root, non-swap,
    # non-c partitions + the size of Vinum's on-disk configuration.
    # Set its offset so that the start of the first subdisk it contains
    # coincides with the first filesystem we're embedding in Vinum.
    printf DLOUT "%4s%9d%9d%10s\n", "$vip:", $totsiz+$cfgsiz, $minoff-$cfgsiz,
      'vinum';
  } else {
    # No need to mess with size and offset if there was no swap
    printf DLOUT "%4s%9d%9d%10s\n", "$vip:", $totsiz, $minoff,
      'vinum';
  }
}
die("Swap partition not found\n") unless $swapsiz;
die("Swap partition not larger than $cfgsiz blocks\n") unless $swapsiz>$cfgsiz;
die("Rootback spindle size not >= root spindle size\n") unless $rbspsiz>=$rspsiz;

# Generate input to vinum create command needed for each spindle.
foreach my $i (0..$#spndl) {
  my $cfn = "create.$drv[$i]";	# Create File Name
  open(CF, ">$cfn") || die("Can't open $cfn for writing: $!\n");
  print CF "drive $drv[$i] device /dev/$spndl[$i]$vip\n";
  next unless $spndl[$i] eq $rsp || $spndl[$i] eq $rbsp;
  foreach my $dev (keys(%vols)) {
    my $mnt = $vols{$dev}->{mnt};
    my $siz = $vols{$dev}->{siz};
    my $off = $vols{$dev}->{off}-$rspminoff+$cfgsiz;
    print CF "volume $mnt\n" if $spndl[$i] eq $rsp;
    print CF <<EOF;
  plex name $mnt.p$i org concat volume $mnt
    sd name $mnt.p$i.s0 drive $drv[$i] plex $mnt.p$i len ${siz}s driveoffset ${off}s
EOF
  }
}