11. The Scripts

See the notes in the beginning of each script for a summary of what it does.

11.1. First Stage

11.1.1. make.fdisk

This script, run at backup time, creates scripts similar to make.dev.hda and mount.dev.x, below, for you to run at restore time. It also produces data files similar to dev.hda and dev.hda.sfd, below. The names of the scripts and data files produced depend on the device given this script as a a parameter. Those script, run at restore time, build and mount the partitions on the hard drive. make.fdisk is called from save.metadata, below.

#! /usr/bin/perl

# A perl script to create a script and input file for fdisk to
# re-create the partitions on the hard disk, and format the Linux and
# Linux swap partitions. The first parameter is the fully qualified
# path of the device of the hard disk, e.g. /dev/hda. The two
# resulting files are the script make.dev.x and the data file dev.x
# (where x is the hard drive described, e.g. hda, sdc). make.dev.x is
# run at restore time to rebuild hard drive x, prior to running
# restore.metadata. dev.x is the input file for fdisk.

# The directory tree where everything is put must already exist and be
# specified in the environment variable $zip.

# Copyright 2001 through the last date of modification Charles Curley
# except for the subroutine cut2fmt.

# cut2fmt Copyright (c) 1998 Tom Christiansen, Nathan Torkington and
# O'Reilly & Associates, Inc.  Permission is granted to use this code
# freely EXCEPT for book publication.  You may use this code for book
# publication only with the explicit permission of O'Reilly &
# Associates, Inc.

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA

# In addition, as a special exception, Tom Christiansen, Nathan
# Torkington and O'Reilly & Associates, Inc.  give permission to use
# the code of this program with the subroutine cut2fmt (or with
# modified versions of the subroutine cut2fmt that use the same
# license as the subroutine cut2fmt), and distribute linked
# combinations including the two.  You must obey the GNU General
# Public License in all respects for all of the code used other than
# the subroutine cut2fmt.  If you modify this file, you may extend
# this exception to your version of the file, but you are not
# obligated to do so.  If you do not wish to do so, delete this
# exception statement and the subroutine cut2fmt from your version.

# You can also contact the Free Software Foundation at
# http://www.fsf.org/

# Changes:

# 2011-09-07: Per email from Michael Coffman: getextopts was leaving
# the "needs_recovery" flag in the ext? options, and mke2fs throws up
# on it. Added a line to remove it.

# 2010-03-22: We now use dumpe2fs to recover ext[3|4] partitions'
# features and use the -O option to mke2fs. So custom partitions' file
# systems will be laid down correctly.

# 2010-03-20: Improved handling of ext4. (There's more to do.) Fixed a
# bug with line endings in the output of "mount -l".

# 2010-01-12: An even better way to get UUIDs, which simplified the
# code and removed a bug, a false return if there was no UUID.

# 2009-12-24: Found a better way to get UUIDs. So we now provide them
# for swap partitions.

# 2007-11-05: Preserve UUIDs (Ubuntu). There is no way to set a UUID
# on a swap partition, so we punt: fix them up manually after the
# fact. The easiest fix is for you to edit fstab to refer to swap
# partitions by device name. Alternatively, see
# https://bugs.launchpad.net/ubuntu/+source/util-linux/+bug/66637,
# http://www.thinkwiki.org/wiki/Installing_Ubuntu_7.04_on_a_ThinkPad_T43#Swap_and_Hibernation_problem
# and
# https://bugs.launchpad.net/ubuntu/+source/util-linux/+bug/66637/comments/23
# et seq. for a list of things to do. Gnrrr.

# 2007-06-10: In addition to scanning /etc/fstab for LVM partitions
# (logical volumes), we also check the device files in /dev. This is
# because some logical volumes may be mounted by label, and scanning
# fstab won't pick those up.

# 2007-05-22: Changes for FHS compliance. Removed commented out
# references to ZIP drives. N.B.: we now take the location of where to
# put things as an environment variable, $zip.

# 2006-04-15: Added support for partition type 0x12, "Compaq
# diagnostic". This type is used for so-called "hidden diagnostics"
# partitions, e.g. on Lenovo/IBM computers.

# 2006-04-08: Primitive LVM support. It is kludgy in that it uses
# first stage restoration distribution (finnix) specific code to turn
# LVM on and off, but otherwise seems to work.

# 2006-03-28: We have a problem if swap partitions have
# labels. There's no way to retrieve the label from a swap
# partition. If we have one & only one swap partition, then we can
# pull it out of /etc/fstab. Otherwise the user is on her own. We scan
# fstab for swap mount points that have labels for their devices. If
# there is one and only one, we assume that's it, otherwise pass.

# 2005-10-29: We now provide the geometry as an argument to fdisk
# (which does not work on tomsrtbt). We also save data for sfdisk, and
# write out make.dev.xxx so that it will use sfdisk if it finds it.

# 2005-08-14: Due to experience on Knoppix, we now add the code to
# change the partition types to the end of the fdisk input file
# instead of right after creating the partition.

# 2004 04 10: fdisk v > 2.11 has wider columns. Added code to select
# the appropriate cut string based on fdisk's version.

# 2004 04 09: Added support for Mandrake's idea of devfs. On Mandrake,
# everything is mounted with devfs. So the mount devices are buried
# deep in places like /dev/ide/host0/bus0/target0/lun0/part1 instead
# of places like /dev/hda1, where $DEITY intended they should be. We
# have to reverse from the long devfs device to the shorter old style
# that tomsrtbt uses. The alternative is to keep track in an array of
# which devfs device belongs to which short device.

# 2003 12 29: Changed the regex for detecting whether a file system is
# read-write in the code that builds the mount file(s). The old test
# does not work if mount returns multiple parameters in the 5th field,
# e.g. (rw,errors=remount-ro) on some debian systems. This regex
# assumes that the rw parameter is always listed first, which may not
# always be the case. If it fails, take out the '\('. Thanks to Pasi
# Oja-Nisula <pon at iki dot fi> for pointing this out.

# 2003 01 09: Added support for FAT32. We now create two scripts for
# each hard drive, make.dev.[as]dx and mount.dev.[as]dx. These create
# and make file systems on each partition, and make mount points and
# mount them.

# 2002 12 25: added support to handle W95 extended (LBA) (f) and W95
# FAT 32 partitions. I have tested this for primary but not logical
# partitions.

# 2002 09 08: Added minimal support for ext3fs. We now detect mounted
# ext3fs partitions & rebuild but with no options. The detection
# depends on the command line "dumpe2fs <device> 2>/dev/null | grep -i
# journal" producing no output for an ext2fs, and output (we don't
# care what) for an ext3fs.

# This could stand extension to support non-default ext3 options such
# as the type of journaling. Volunteers?

# 2002 07 25: Bad block checking is now a command line option (-c) at
# the time the product script is run.

# 2002 07 03: Corrected the mechanism for specifying the default
# drive.

# 2001 11 25: Changed the way mke2fs gets its bad block
# list. badblocks does not guess at the block size, so you have to get
# it (from dumpe2fs) and feed it to badblocks. It is simpler to just
# have mke2fs call badblocks, but you do loose the ability to have a
# writing test easily. -- C^2

# 2001 11 25: Changed the regex that extracts partition labels from
# the mount command. This change does not affect the results at all,
# it just makes it possible to use Emacs' perl mode to indent
# correctly. I just escaped the left bracket in the regex. -- C^2

# Discussion:

# fdisk will spit out a file of the form below if you run it as "fdisk
# -l".

# root@tester ~/bin $ fdisk -l /dev/hda

# Disk /dev/hda: 64 heads, 63 sectors, 1023 cylinders
# Units = cylinders of 4032 * 512 bytes

#    Device Boot    Start       End    Blocks   Id  System
# /dev/hda1             1         9     18112+  83  Linux
# /dev/hda2            10      1023   2044224    5  Extended
# /dev/hda5            10       368    723712+  83  Linux
# /dev/hda6           369       727    723712+  83  Linux
# /dev/hda7           728       858    264064+  83  Linux
# /dev/hda8           859       989    264064+  83  Linux
# /dev/hda9           990      1022     66496+  82  Linux swap

# What fdisk does not do is provide output suitable for later
# importing into fdisk, a la sfdisk. This script parses the output
# from fdisk and creates an input file for fdisk. Use the input file
# like so:

# fdisk /dev/hdx < dev.hdx

# For the bare metal restore package, this script also builds a script
# that will execute the above command so you can run it from your zip
# disk. Because the bare metal restore scripts all are in /root/bin,
# the data file and script created by this script are also placed
# there. The same script also creates appropriate Linux file systems,
# either ext2fs, or Linux swap. There is limited support for FAT12,
# FAT16 and FAT32. For anything else, you're on your own.

# Note for FAT32: According to the MS KB, there are more than one
# reserved sectors for FAT32, usually 32, but it can vary. Do a search
# in M$'s KB for "boot sector" or BPB for the gory details. For more
# info than you really need on how boot sectors are used, see
# http://support.microsoft.com/support/kb/articles/Q140/4/18.asp

# You can also edit dev.x to change the sizes of partitions. Don't
# forget, if you change the size of a FAT partition across the 32MB
# boundary, you need to change the type as well! Run "fdisk /dev/hda"
# or some such, then the l command to see the available partition
# types. Then go ahead and edit dev.x appropriately. Also, when moving
# partition boundarys with hand edits, make sure you move both logical
# and extended partition boundaries appropriately.

# Bad block checking right now is a quick read of the partition. A
# writing check is also possible but more difficult. You have to run
# badblocks as a separate command, and pass the bad block list to
# mke2fs in a file (in /tmp, which is a ram disk). You also have to
# know how large the blocks are, which you learn by running
# dumpe2fs. It gets messy and I haven't done it yet. You probably
# don't need it for a new hard drive, but if you have had a hard drive
# crash on you and you are reusing it (while you are waiting for its
# replacement to come in, I presume), then I highly recommend it. Let
# me know how you do it.

# For more information contact the author, Charles Curley, at
# http://www.charlescurley.com/.

use UUID;			# for validating uuids.

# cut2fmt figures out the format string for the unpack function we use
# to slice and dice the output from fdisk. From Christiansen and
# Torkington, Perl Cookbook 5.

sub cut2fmt {
    my (@positions) = @_;
    my $template    = '';
    my $lastpos     = 1;

    foreach $place (@positions) {
        $template .= "A" . ($place - $lastpos) . " ";
        $lastpos = $place;
    }

    $template .= "A*";
    return $template;
}


# Sub gpl, a subroutine to ship the GPL and other header information
# to the current output file.

sub gpl {
    my $FILE = shift;
    my $year = shift;

    print $FILE <<FINIS;

# Copyright $year through the last date of modification Charles Curley.

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA

# You can also contact the Free Software Foundation at http://www.fsf.org/

# For more information contact the author, Charles Curley, at
# http://www.charlescurley.com/.

FINIS

}

sub getBootSector {
    my $infile = $_[0];
    my $outfile = $_[1];

    $systemcmd = "dd if=$infile of=$outfile bs=512 count=1 &> /dev/null ";
    system ($systemcmd);
}


# If we have one & only one swap partition, then this must be
# it. Otherwise the user is on her own. We scan fstab for swap mount
# points that have labels for their devices. If there is one and only
# one, we assume that's it, otherwise pass.

sub getswaplabel {
    my $dev = $_[0];

    open (FSTAB, "< /etc/fstab")
        or die "Couldn't fork: $!\n";
    while (defined (my $line = <FSTAB>)) {
        chop ($line);
        @fstabs = split (" ", $line);
        if (@fstabs[1] eq "swap") {
            $swaplabel = @fstabs[0];
            if ($swaplabel =~ /LABEL/) {
                $swaps++;
                $sl = substr ($swaplabel, 6);
            }
#           print ("\"@fstabs[0]\", \"@fstabs[1]\", \"$sl\", $swaps.\n");
            break;
        }
    }
    close (FSTAB);

#   print "label is $sl.\n";

    if ($swaps == 1) {
        $ret = "-L $sl $dev\n";
    } else {
        $ret = "$dev\n";
    }

#   print ("Returning :$ret\n");

    return $ret;
}

# dolvm is a subroutine to handle LVM partitions.

$lvms = 0;          # true if we've been here before

sub dolvm {

#     print ("In dolvm ()...\n");

    if ($lvms == 0) {
        $lvms = 1;

        # Scan /etc/fstab for the logical volumes and write a script to
        # make file systems on them and another to mount 'em later on.

        $mklvs = open (MKLVS, "> ${outputfilepath}bin/make.lvs")
            or die "Couldn't fork: $!\n";

        print MKLVS <<FINIS;
#! /bin/sh

# A script to create file systems on logical volumes. Created at bare
# metal backup time by the Perl script make.fdisk.
FINIS

        &gpl (*MKLVS, "2006");


        print MKLVS <<FINIS;

export blockcheck=\$1;

if [ "\$blockcheck" != "-c" ] && [ -n "\$blockcheck" ]
then
    echo "\${0}: Build file systems on logical volumes."
    echo "\${0}: -c: block check during file system making."
    exit 1;
fi

FINIS

        $mtlvs = open (MTLVS, "> ${outputfilepath}bin/mount.lvs")
            or die "Couldn't fork: $!\n";

        print MTLVS <<FINIS;
#! /bin/sh

# A script to mount file systems on logical volumes. Created at bare
# metal backup time by the Perl script make.fdisk.
FINIS

        &gpl (*MTLVS, "2007");


        # Now cycle through all the known logical volumes & set them
        # up. N.B.: This has been tested on machines with only one LV
        # and with two. It picks up both on that test machine.

        $pvdisp = open (PVDISP, "pvdisplay -c |")
            or die ("Can't open LVM display.\n");
        while (defined (my $pv = <PVDISP>)) {
            chop ($pv);
#             print ("$pv\n");
            @pv = split (":", $pv);
            $uid = @pv[11];
            $pvname = @pv[1];
            $phv = @pv[0];
#             print ("pv $pvname has uid $uid.\n");

            # back up the LVM's lvm details. Get the config files.
            system ("vgcfgbackup -f ${outputfilepath}metadata/LVM.backs.$pvname $pvname");

            print (MKLVS "echo \"y\\n\" | pvcreate -ff --uuid \"$uid\"\\\n");
            print (MKLVS "    --restorefile ../metadata/lvm/backup/${pvname} $phv\n");
            print (MKLVS "vgcfgrestore --file ../metadata/LVM.backs.$pvname $pvname\n\n");
        }

        print (MKLVS "# Hideously disty dependent! turn on LVM.\n");
        print (MKLVS "if [ -e /etc/init.d/lvm ] ; then\n");
        print (MKLVS "    /etc/init.d/lvm start\nfi\n\n");

        # Now walk fstab in search of logical volumes. This is
        # necessary to pick up swap partitions, and it may pick up
        # others. We need fstab below to match the partitions up with
        # their mount points, so we keep the array around.

        %volsfound = ();
        open (FSTAB, "< /etc/fstab")
            or die "Couldn't fork: $!\n";
        @fstab = <FSTAB>;
        foreach $line (@fstab) {
            @fstabs = split (" ", $line);
            #                   Red Hat|Ubuntu
            if (@fstabs[0] =~ /VolGroup|mapper/ ) {
                # print ("$line");
                if (@fstabs[2] eq "swap") {
                    print (MKLVS "echo\necho making LV @fstabs[0] a swap partition.\n");
                    print (MKLVS "mkswap \$blockcheck @fstabs[0]\n\n");
                } elsif (@fstabs[2] == "ext4") {
                    print (MKLVS "echo\necho making LV @fstabs[0], @fstabs[1],");
                    print (MKLVS " an ext4 partition.\n");
                    print (MKLVS "mke2fs -t ext4" . getextopts (@fstabs[0]));
                    print (MKLVS "\$blockcheck @fstabs[0]\n\n");

                    print (MTLVS "mkdir -p /target$fstabs[1]\n");
                    print (MTLVS "mount @fstabs[0] /target$fstabs[1]\n\n");
                    $volsfound{@fstabs[0]} = 4;
                } elsif (@fstabs[2] == "ext3") {
                    print (MKLVS "echo\necho making LV @fstabs[0], @fstabs[1],");
                    print (MKLVS " an ext3 partition.\n");
                    print (MKLVS "mke2fs -t ext3" . getextopts (@fstabs[0]));
                    print (MKLVS "\$blockcheck @fstabs[0]\n\n");

                    print (MTLVS "mkdir -p /target$fstabs[1]\n");
                    print (MTLVS "mount @fstabs[0] /target$fstabs[1]\n\n");
                    $volsfound{@fstabs[0]} = 3;
                } elsif (@fstabs[2] == "ext2") {
                    print (MKLVS "echo\necho making LV @fstabs[0], @fstabs[1],");
                    print (MKLVS " an ext2 partition.\n");
                    print (MKLVS "mke2fs \$blockcheck @fstabs[0]\n\n");

                    print (MTLVS "mkdir -p /target$fstabs[1]\n");
                    print (MTLVS "mount @fstabs[0] /target$fstabs[1]\n\n");
                    $volsfound{@fstabs[0]} = 2;
                } else {
                    print ("Opps, unknown type of logical volume, @fstabs[0]\n");
                }
            }
        }

#         print ("Volumes already found are: ");
#         while ( ($k, $v) = each %volsfound ) {
#             print ("$k ==> $v ");
#         }
#         print ("\n");

        # Now walk the logical volume devices and pick up any
        # partitions formated ext3/ext2. This may result in duplicates
        # if the partitions have labels but are mounted by device name
        # rather than by label.

        # FIX ME: This loop doesn't distinguish between ext3 and
        # ext4. It may not need to, as we pull the options from the
        # partition itself. If so, that would be nice.

        opendir (DEVHANDLE, "/dev") or die ("Can't open /dev!!\n");
        while ( defined ($fname = readdir (DEVHANDLE))) {
            @fnames = (@fnames, $fname);
        }
        @sorted = sort (@fnames);
        foreach $fname (@sorted) {
            #                Red Hat|Ubuntu
            if ($fname =~ /^VolGroup|^mapper/ && -d "/dev/$fname") {
                # print ("Inside /dev is $fname.\n");
                opendir (VOLHANDLE, "/dev/$fname")
                    or die ("Can't open /dev/$fname!!\n");
                while ( defined ($vname = readdir (VOLHANDLE))) {
                    @vnames = (@vnames, $vname);
                }
                @vsorted = sort (@vnames);
                foreach $vname (@vsorted) {
#                     print "/dev/$fname/$vname: " . $volsfound{"/dev/$fname/$vname"} . "\n";
                    if($vname ne "." && $vname ne ".."
                       && $volsfound{"/dev/$fname/$vname"} < 1) {
#                         print ("Inside /dev/$fname is $vname.\n");
                        my $journal = 0;

                        # FIX ME: add tests to be sure it's a symlink
                        # to a block device.

                        # Is it extX?
                        open (DUMP, "dumpe2fs /dev/$fname/$vname 2> /dev/null|");

                        @lines = <DUMP>;

                        if (scalar (@lines) > 1) {

                            # If we've gotten here we have a valid
                            # ext[2|3|4] file system. Now prepare to
                            # spit out the commands to recreate
                            # it. Get the label, if any, and whether
                            # there is a journal or not.

                            foreach $_ (@lines) {
                                if (/Filesystem volume name:/) {
                                    $label = substr ($_, 26);
                                    chop ($label);
#                                     print ("\$label is \"$label\".\n");
                                }
                                if (/has_journal/) {
                                    $journal = 1;
                                }

                            }

                            # get the mount point from fstab so we can mount it.
                            $mountpoint = "none!";
                            foreach $fstab (@fstab) {
                                @fstabs = split (" ", $line);
                                if (@fstabs[0] eq "LABEL=$label" ) {
                                    $mountpoint = @fstabs[1];
#                                     print ("mount point is \"$mountpoint\".\n");
                                    last;
                                }
                            }
                            if (length ($label) ) {
                                $label = "-L \"" . $label . "\"";
                            }

                            if ($journal > 0) {
                                print (MKLVS "echo\necho making LV /dev/$fname/$vname");
                                print (MKLVS " an ext3/4 partition.\n");
                                print (MKLVS "mke2fs" . getextopts ("/dev/$fname/$vname"));
                                print (MKLVS "$label \$blockcheck /dev/$fname/$vname\n\n");

                                if ($mountpoint ne "none!") {
                                  print (MTLVS "mkdir -p /target$mountpoint\n");
                                  print (MTLVS "mount /dev/$fname/$vname /target$mountpoint\n\n");
                                }
                              } else {
                                print (MKLVS "echo\necho making LV /dev/$fname/$vname");
                                print (MKLVS " an ext2 partition.\n");
                                print (MKLVS "mke2fs $label \$blockcheck /dev/$fname/$vname\n\n");

                                if ($mountpoint ne "none!") {
                                  print (MTLVS "mkdir -p /target$mountpoint\n");
                                  print (MTLVS "mount /dev/$fname/$vname /target$mountpoint\n\n");
                                }
                              }
                        }
                    }

                }
                closedir (VOLHANDLE);
            }
        }
        print (MTLVS "mount | grep -i \"/target\"\n");

        closedir (DEVHANDLE);

        close (FSTAB);
        close (MKLVS);
        close (MTLVS);

        chmod 0700, "${outputfilepath}bin/make.lvs";
        chmod 0700, "${outputfilepath}bin/mount.lvs";

        # Copy the LVM configuration to where we can get at it...
        system ("cp -rp /etc/lvm ${outputfilepath}metadata/");

    }

#     print ("Leaving dolvm ()...\n");

    return ($ret);
}

sub getuuid {
    my $device = shift;

    # print ("In getuuid. \$device = $device.\n");

    my $rs = open (UUID, "blkid -o value -s UUID $device |")
        or die ("Can't open volume id tool.\n");
    while (defined (my $uuid = <UUID>)) {
        chop ($uuid);
        # print ("$uuid\n");

	if (UUID::parse($uuid, my $rawUuid) != -1) {
	    # print ("Device $device has uuid '$uuid'.\n");
	    return ($uuid);
	}
    }
    return ('');
}

# scan the partition for the options to mke2fs.

sub getextopts {
  my $device = $_[0];
  # print ("Device is $device\n");

  open (DUMP, "dumpe2fs $device 2> /dev/null|") or
    die ("Can't open a dump.\n");

  my @lines = <DUMP>;
  if (scalar (@lines) > 1) {

    foreach $_ (@lines) {
      # print ("line: $_");

      if (/Filesystem features/) {
        $_ = substr ($_, 26);
        chop ();
        $_ =~ s/needs_recovery //g;
        $_ =~ s/ /,/g;
        # print ("Features are: ${_}.\n");
        return ($_ = ' -O ' . $_ . ' ');
      }
    }
    die ("Couldn't get features from device $device\n");
  } else {
    die ("Couldn't run dumpe2fs on $device.\n");
  }
}

# Begin main line code.

# $outputfilepath = "/root/bin/";
$outputfilepath = $ENV{"zip"} . "/";
if ($outputfilepath eq "/") {
    $outputfilepath =  "./";
}
# print "\$outputfilepath is ${outputfilepath}\n";

# These will "fail" silently if the directory is already there, which
# it should be if we're called from save.metadata. These simplify
# testing, though.

mkdir ("${outputfilepath}metadata", 0700);
mkdir ("${outputfilepath}bin", 0700);

# Provide a default device.

# print "\$ARGV[0] is $ARGV[0].\n";

$device = defined ($ARGV[0]) ? $ARGV[0] : "/dev/hda";

# Need to check to see if $device is a sym link. If it is, the mount
# point is the target of the link. (Mandrake) Otherwise we search for
# mount points on $device. Fedora, Red Hat.

if ( -l $device) {

    # It is a sym link. Get the target of the link, then make it into
    # an absolute path, preserving the numbering.

    $mountdev = '/dev/' . readlink ($device);
    $mountdev =~ s|ide/host(\d+)/bus(\d+)/target(\d+)/lun(\d+)/disc
        |ide/host\1/bus\2/target\3/lun\4|x;
} else {
    # not a sym link; just assign it.
    $mountdev = $device;
}

# print "Device is $device; mount device is $mountdev.\n";

# Prepare format string. Here are two format strings I have found
# useful. Here, column numbers are 1 based, i.e. the leftmost column
# is column 1, not column 0 as in Emacs.

# We select a format string according to fdisk's version.

$fdpid = open (FDVER, "fdisk -v |") or die "Couldn't fork: $!\n";
while (<FDVER>) {
    @_ = unpack ("A7 A*", $_);
    $fdver=$_[1];
    $fdver =~ s/[^\d.]//g; # strip non-numbers, non-periods, as in "2.12pre".
}

# print "fdisk version is $fdver\n";

if ($fdver < 2.12) {
# fdisk to 2.11?? Red Hat, Fedora Core 1
    $fmt = cut2fmt (11, 19, 24, 34, 45, 49);
} else {
# fdisk 2.12 & up?? Mandrake 10.0, Fedora Core 2
    $fmt = cut2fmt (12, 14, 26, 38, 50, 55);
}
# print "Format string is $fmt.\n";

# define fields in the array @_.
$dev = 0;
$bootable = 1;
$firstcyl = 2;
$lastcyl = 3;
$parttype = 5;
$partstring = 6;

$target = "\/target";

$outputfilename = $device;
$outputfilename =~ s/\//./g;
$outputfilename = substr ($outputfilename, 1, 100);

# Make a hash of the labels.
$mpid = open (MOUNT, "mount -l |") or die "Couldn't fork: $!\n";
while (<MOUNT>) {
    if ($_ =~ /^$mountdev/i) { # is this a line with a partition in it?
        chop;
        # print "mount line is \"$_\"\n"; # print it just for grins
        split;
        if ($_[6] ne "") {      # only process if there actually is a label
            $_[6] =~ s/[\[\]]//g; # strike [ and ].
            $labels{$_[0]} = $_[6];
#           print "The label of file device $_[0] is $labels{$_[0]}.\n";
        }


        # We only mount if it's ext2fs, ext3fs, or ext4fs, and read
        # and write.

        if ($_[4] =~ /ext[234]/ and $_[5] =~ /\(rw/ ) {
            if ($_[0] =~ /ide/i) {

                # We have a devfs system, e.g. Mandrake. This code
                # converts back from the devfs designation to the old
                # /dev/hd* designation for tomsrtb. I have NOT checked
                # this out for drives other than /dev/hda. Also, this
                # code does not handle SCSI drives.

                if ( $_[0] =~ /target0/ && $_[0] =~ /bus0/ ) {
                    $letter = 'a';
                } elsif ( $_[0] =~ /target1/ && $_[0] =~ /bus0/) {
                    $letter = 'b';
                } elsif ( $_[0] =~ /target0/ && $_[0] =~ /bus1/) {
                    $letter = 'c';
                } else {
                    $letter = 'd';
                }
                $_[0] =~ s|/ide/host\d+/bus\d+/target\d+/lun\d+/part|/hd|g;
                $_[0] =~ s/hd/hd$letter/;
            }
            $mountpoints{$_[2]} = $_[0];
            # print "$_[2] is the mountpoint for tomsrtbt|finnix";
            # print " device $mountpoints{$_[2]}.\n";
        }
    }
}
close (MOUNT);

# Get sfdisk output. If we have sfdisk at restore time (e.g. Knoppix),
# we'll use it.

system "sfdisk -d $device > ${outputfilepath}metadata/${outputfilename}.sfd";

# Otherwise we'll use the output from fdisk, which may or may not be
# any more accurate.

$fpid = open (FDISK, "fdisk -l $device |") or die "Couldn't fork: $!\n";

open (OUTPUT, "> ${outputfilepath}metadata/${outputfilename}")
    or die "Couldn't open output file ${outputfilepath}metadata/${outputfilename}.\n";

while (<FDISK>) {
    if ($_ =~ /^$device/i) {    # is this a line with a partition in it?
#       print $_;               # print it just for grins
        chop;                   # kill trailing \r
        @_ = unpack ($fmt, $_);

        # Now strip white spaces from cylinder numbers, white space &
        # leading plus signs from partition type.
        @_[$firstcyl] =~ s/[ \t]+//;
        @_[$lastcyl] =~ s/[ \t]+//;
        @_[$parttype] =~ s/[+ \t]+//;

        $partnumber = substr(@_[$dev], 8, 10); # get partition number for this line
        # just for grins
#         print "  $partnumber, @_[$firstcyl], @_[$lastcyl],";
#         print " @_[$parttype], @_[$partstring]\n";

        # Here we start creating the input to recreate the partition
        # this line represents.

        print OUTPUT "n\n";
        if ($partnumber < 5) {
            # primary Linux partition
            if (@_[$parttype] == 83) {
                print OUTPUT "p\n$partnumber\n@_[$firstcyl]\n";
                # in case it's all on one cylinder
                if (@_[$firstcyl] ne @_[$lastcyl]) {
                    print OUTPUT "@_[$lastcyl]\n";
                }

#                 # Now detect if this is an ext3 (journaling)
#                 # partition. We do this using dumpe2fs to dump the
#                 # partition and grepping on "journal". If the
#                 # partition is ext2, there will be no output. If it is
#                 # ext3, there will be output, and we use that fact to
#                 # set a command line switch. The command line switch
#                 # goes into an associative array (hash) so we don't
#                 # have to remember to reset it to the null string when
#                 # we're done.

#                 $dpid = open (DUMPE2FS,
#                               "dumpe2fs @_[$dev] 2>/dev/null | grep -i journal |")
#                     or die "Couldn't fork: $!\n";
#                 while (<DUMPE2FS>) {
# #                   print "Dumpe2fs: $_";
#                     $ext3{$_[$dev]} = "-j ";
#                     last;
#                 }
#                 close (DUMPE2FS);

                if ($labels{@_[$dev]}) { # do we have a label?
                    $format .= "echo\necho formatting $checking@_[$dev]\n";
                    # $format .= "mke2fs $ext3{$_[$dev]}\$blockcheck";
                    # $format .= " -L $labels{@_[$dev]} @_[$dev]\n";
                    $format .= "mke2fs" . getextopts ($_[$dev]) . "\$blockcheck";
                    $format .= " -L $labels{@_[$dev]} @_[$dev]\n";
                } else {
                    $format .= "echo\necho formatting $checking@_[$dev]\n";
                    # $format .= "mke2fs $ext3{$_[$dev]}\$blockcheck @_[$dev]\n";
                    $format .= "mke2fs" . getextopts ($_[$dev]) . "\$blockcheck @_[$dev]\n";
                }
                $uuid = getuuid ($_[$dev]);
                if (length ($uuid) > 0) {
                    $format .= "tune2fs -U $uuid @_[$dev]\n";
                }

                $format .= "\n";

                # extended partition
            } elsif (@_[$parttype] == 5) {
                # print ("Creating Extended Partition.\n");
                print OUTPUT "e\n$partnumber\n@_[$firstcyl]\n";
                if (@_[$firstcyl] ne @_[$lastcyl]) {
                    print OUTPUT "@_[$lastcyl]\n";
                }

                # extended partition, Win95 Ext'd (LBA)
            } elsif (@_[$parttype] eq "f") {
                # print ("Creating Extended LBA Partition.\n");
                print OUTPUT "e\n$partnumber\n@_[$firstcyl]\n";
                if (@_[$firstcyl] ne @_[$lastcyl]) {
                    print OUTPUT "@_[$lastcyl]\n";
                }
                $typechanges .= "t\n$partnumber\nf\n";

                # primary Linux swap partition
            } elsif (@_[$parttype] == 82) {
                print OUTPUT "p\n$partnumber\n@_[$firstcyl]\n";
                if (@_[$firstcyl] ne @_[$lastcyl]) {
                    print OUTPUT "@_[$lastcyl]\n";
                }
                $typechanges .= "t\n$partnumber\n82\n";
                $format .= "echo\necho Making @_[$dev] a swap partition.\n";
		$format .= "mkswap \$blockcheck ";
                $uuid = getuuid ($_[$dev]);
                if (length ($uuid) > 0) {
		    $format .= "-U $uuid ";
                }
                if ($labels{@_[$dev]}) { # do we have a label?
                    $format .= "-L $labels{@_[$dev]} ";
                    $format .= "@_[$dev]\n";
                } else {
                    $format .= getswaplabel (@_[$dev]);
                }

                $format .= "\n";

                # Primary mess-dos partition. We don't handle hidden
                # partitions.

            } elsif ( @_[$parttype] == 1 || @_[$parttype] == 4 || @_[$parttype] == 6
                      || @_[$parttype] eq "b" || @_[$parttype] eq "c"
                      || @_[$parttype] eq "e" || @_[$parttype] eq "12" ) {
                # print ("Making DOS primary partition.\n");

                getBootSector (@_[$dev], "${outputfilepath}metadata/$outputfilename$partnumber");

                print OUTPUT "p\n$partnumber\n@_[$firstcyl]\n";
                # in case it's all on one cylinder
                if (@_[$firstcyl] ne @_[$lastcyl]) {
                    print OUTPUT "@_[$lastcyl]\n";
                }

                $typechanges .= "t\n$partnumber\n@_[$parttype]\n";
                $format .= "echo\necho formatting $checking@_[$dev]\n";
                $format .= "mkdosfs \$blockcheck";
                if ( @_[$parttype] == b || @_[$parttype] == c
                     || @_[$parttype] eq "12" ) {
                    # We have a W9x FAT32 partition. Add a command line switch.
                    $format .= " -F 32";
                }
                $format .= " @_[$dev]\n";
                $format .= "# restore FAT boot sector.\n";
                $format .= "dd if=$outputfilename$partnumber";
                $format .= " of=@_[$dev] bs=512 count=1\n\n";

            } elsif ( @_[$parttype] == "8e") {
                $format .= dolvm ();
            } else {
                # anything else partition
                print OUTPUT "p\n@_[$firstcyl]\n";
                if (@_[$firstcyl] ne @_[$lastcyl]) {
                    print OUTPUT "@_[$lastcyl]\n";
                }
                $typechanges .= "t\n$partnumber\n@_[$parttype]\n";
            }

        } else {
            # logical Linux partition
            if (@_[$parttype] == 83) {
                print OUTPUT "l\n@_[$firstcyl]\n";
                if (@_[$firstcyl] ne @_[$lastcyl]) {
                    print OUTPUT "@_[$lastcyl]\n";
                }

#                 # Now detect if this is an ext3 (journaling)
#                 # partition. We do this using dumpe2fs to dump the
#                 # partition and grepping on "journal". If the
#                 # partition is ext2, there will be no output. If it is
#                 # ext3, there will be output, and we use that fact to
#                 # set a command line switch. The command line switch
#                 # goes into an associative array (hash) so we don't
#                 # have to remember to reset it to the null string when
#                 # we're done.

#                 $dpid = open (DUMPE2FS,
#                               "dumpe2fs @_[$dev] 2>/dev/null | grep -i journal |")
#                     or die "Couldn't fork: $!\n";
#                 while (<DUMPE2FS>) {
# #                   print "Dumpe2fs: $_";
#                     $ext3{$_[$dev]} = "-j ";
#                     last;
#                 }
#                 close (DUMPE2FS);

                if ($labels{@_[$dev]}) { # do we have a label?
                    $format .= "echo\necho formatting $checking@_[$dev]\n";
                    # $format .= "mke2fs $ext3{@_[$dev]}\$blockcheck";
                    # $format .= " -L $labels{@_[$dev]} @_[$dev]\n";
                    $format .= "mke2fs" . getextopts ($_[$dev]) . "\$blockcheck";
                    $format .= " -L $labels{@_[$dev]} @_[$dev]\n";
                } else {
                    $format .= "echo\necho formatting $checking@_[$dev]\n";
                    # $format .= "mke2fs $ext3{@_[$dev]}\$blockcheck @_[$dev]\n";
                    $format .= "mke2fs" . getextopts ($_[$dev]) . "\$blockcheck @_[$dev]\n";
                }
                $uuid = getuuid ($_[$dev]);
                if (length ($uuid) > 0) {
                    $format .= "tune2fs -U $uuid @_[$dev]\n";
                }

                $format .= "\n";

                # logical Linux swap partition
            } elsif (@_[$parttype] == 82 ) {
                print OUTPUT "l\n@_[$firstcyl]\n";
                if (@_[$firstcyl] ne @_[$lastcyl]) {
                    print OUTPUT "@_[$lastcyl]\n";
                }
                $typechanges .= "t\n$partnumber\n82\n";
                $format .= "echo\necho Making @_[$dev] a swap partition.\n";
		$format .= "mkswap \$blockcheck ";
                $uuid = getuuid ($_[$dev]);
                if (length ($uuid) > 0) {
		    $format .= "-U $uuid ";
                }
                if ($labels{@_[$dev]}) { # do we have a label?
                    $format .= "-L $labels{@_[$dev]} ";
                    $format .= "@_[$dev]\n";
                } else {
                    $format .= getswaplabel (@_[$dev]);
                }

                $format .= "\n";

                # Logical mess-dos partition. We don't handle hidden
                # partitions.

            } elsif ( @_[$parttype] == 1 || @_[$parttype] == 4 || @_[$parttype] == 6
                      || @_[$parttype] eq "b" || @_[$parttype] eq "c"
                      || @_[$parttype] eq "e" || @_[$parttype] eq "12" ) {
#               print ("Making DOS logical partition.\n");

                getBootSector (@_[$dev], "${outputfilepath}metadata/$outputfilename$partnumber");

                print OUTPUT "l\n$partnumber\n@_[$firstcyl]\n";
                # in case it's all on one cylinder
                if (@_[$firstcyl] ne @_[$lastcyl]) {
                    print OUTPUT "@_[$lastcyl]\n";
                }
                $typechanges .= "t\n$partnumber\n@_[$parttype]\n";
                $format .= "echo\necho formatting $checking@_[$dev]\n";
                $format .= "mkdosfs \$blockcheck";
                if ( @_[$parttype] == b || @_[$parttype] == c
                     || @_[$parttype] eq "12" ) {
                    # We have a W9x FAT32 partition. Add a command line switch.
                    $format .= " -F 32";
                }
                $format .= " @_[$dev]\n";
                $format .= "# restore FAT boot sector.\n";
                $format .= "dd if=$outputfilename$partnumber";
                $format .= " of=@_[$dev] bs=512 count=1\n\n";

            } elsif ( @_[$parttype] == "8e") {
                $format .= dolvm ();
            } else {
                # anything else partition
                print OUTPUT "l\n@_[$firstcyl]\n";
                if (@_[$firstcyl] ne @_[$lastcyl]) {
                    print OUTPUT "@_[$lastcyl]\n";
                }
                $typechanges .= "t\n$partnumber\n@_[$parttype]\n";
            }
        }

        # handle bootable partitions
        if (@_[$bootable] =~ /\*/) {
            print OUTPUT "a\n$partnumber\n";
        }
    } else {
        # If we got here, the current line does not have a partition in it.

        # Get the geometry for fdisk. Force fdisk to use the current
        # geometry at restoration time. Comment this out for
        # tomstrbt's fdisk; it doesn't like it.

        if ($_ =~ /heads.*sectors.*cylinders/i) {
#           print $_;               # again, for grins.
            chop;
            @geometry = split (/ /, $_);
            $geometry = "-H $geometry[0] -S $geometry[2] -C $geometry[4]";
#           print $geometry;
        }
    }
}

# Append all the partition type changes, validate, and print out the
# results.

print OUTPUT "${typechanges}v\nw\n";

close (OUTPUT);
close (FDISK);


open (OUTPUT, "> ${outputfilepath}bin/make.$outputfilename")
    or die "Couldn't open output file ${outputfilepath}bin/make.$outputfilename.\n";

print OUTPUT <<FINIS;
#! /bin/sh

# A script to restore the partition data of a hard drive and format
# the partitions. Created at bare metal backup time by the Perl script
# make.fdisk.
FINIS

&gpl (*OUTPUT, "2001");

print OUTPUT <<FINIS;

swapoff -a
# Hideously disty dependent! Turn off LVM.
if [ -e /etc/init.d/lvm ] ; then
    /etc/init.d/lvm stop
fi

export blockcheck=\$1;

if [ "\$blockcheck" != "-c" ] && [ -n "\$blockcheck" ]
then
    echo "\${0}: automated restore with no human interaction."
    echo "\${0}: -c: block check during file system making."
    exit 1;
fi

FINIS

# Clean the old partition table out. Turn off swap in case we're using
# it.

# print OUTPUT "dd if=/dev/zero of=$device bs=512 count=2\n\nsync\n\n";
print OUTPUT "dd if=/dev/zero of=$device bs=1024 count=2000\n\nsync\n\n";


# command for fdisk

$fdiskcmd .= "# see if we have sfdisk & if so use it.\n";
$fdiskcmd .= "if which sfdisk ; then\n";
$fdiskcmd .= "  echo \"Using sfdisk.\"\n";
$fdiskcmd .= "  sfdisk $geometry $device < ../metadata/${outputfilename}.sfd\n";
$fdiskcmd .= "else\n";
$fdiskcmd .= "  echo \"using fdisk.\"\n";
$fdiskcmd .= "  fdisk $geometry $device \< ../metadata/$outputfilename\n";
$fdiskcmd .= "fi\n\nsync\n\n";


print OUTPUT $fdiskcmd;
print OUTPUT $format;

print OUTPUT "fdisk -l \"$device\"\n";

close (OUTPUT);

# Now build the script that will build the mount points on the root
# and other partitions.

open (OUTPUT, "> ${outputfilepath}bin/mount.$outputfilename")
    or die "Couldn't open output file ${outputfilepath}bin/make.$outputfilename.\n";

print OUTPUT <<FINIS;
#! /bin/sh

# A script to create a minimal directory tree on the target hard drive
# and mount the partitions on it. Created at bare metal backup time by
# the Perl script make.fdisk.
FINIS

&gpl (*OUTPUT, "2001");

print OUTPUT <<FINIS;

# WARNING: If your Linux system mount partitions across hard drive
# boundaries, you will have multiple "mount.dev.* scripts. You must
# ensure that they run in the proper order. The root partition should
# be mounted first, then the rest in the order they cascade. If they
# cross mount, you'll have to handle that manually.

FINIS


# We have a hash of mount points and devices in %mountpoints. However,
# we have to process them such that directories are built on the
# appropriate target partition. E.g. where /usr/local is on its own
# partition, we have to mount /usr before we build /usr/local. We can
# ensure this by sorting them. Shorter mount point paths will be built
# first. We can't sort a hash directly, so we use an array.

# We build commands to create the appropriate mount points and then
# mount the partitions to the mount points. This is in preparation for
# untarring the contents of the ZIP disk, done in restore.metadata.

# In case it's all one big partition...
print OUTPUT "mkdir -p $target\n";

foreach $point ( sort keys %mountpoints) {
    print OUTPUT "\n# $point is the mountpoint for";
    print OUTPUT " device $mountpoints{$point}.\n";
    print OUTPUT "mkdir -p $target$point\n";
    print OUTPUT "mount $mountpoints{$point} $target$point\n";
}

print OUTPUT "\nmount | grep -i \"/target\"\n";

close (OUTPUT);

# These scripts are dangerous & should only be visible to root.

chmod 0700, "${outputfilepath}bin/make.$outputfilename";
chmod 0700, "${outputfilepath}bin/mount.$outputfilename";
chmod 0600, "${outputfilepath}metadata/${outputfilename}*";

11.1.2. make.dev.hda

This script is a sample of the sort produced by make.fdisk, above. It uses data files like dev.hda, below. It builds partitions and puts file systems on some of them. This is the first script run at restore time.

If you are brave enough to edit dev.hda or dev.hda.sfd (q.v.), say, to add a new partition, you may need to edit this script as well.

If you want make.dev.hda to check for bad blocks when it puts a file system on the partitions, use a "-c" command line option.

#! /bin/sh

# A script to restore the partition data of a hard drive and format
# the partitions. Created at bare metal backup time by the Perl script
# make.fdisk.

# Copyright 2001 through the last date of modification Charles Curley.

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA

# You can also contact the Free Software Foundation at http://www.fsf.org/

# For more information contact the author, Charles Curley, at
# http://www.charlescurley.com/.


export blockcheck=$1;

if [ "$blockcheck" != "-c" ] && [ -n "$blockcheck" ]
then
    echo "${0}: automated restore with no human interaction."
    echo "${0}: -c: block check during file system making."
    exit 1;
fi

dd if=/dev/zero of=/dev/hda bs=512 count=2

swapoff -a
sync

# see if we have sfdisk & if so use it.
if which sfdisk ; then
  echo "Using sfdisk."
  sfdisk  -H 128 -S 63 -C 523 /dev/hda < dev.hda.sfd
else
  echo "using fdisk."
  fdisk  -H 128 -S 63 -C 523 /dev/hda < dev.hda
fi

sync

echo
echo formatting /dev/hda1
mkdosfs $blockcheck /dev/hda1
# restore FAT boot sector.
dd if=dev.hda1 of=/dev/hda1 bs=512 count=1

echo
echo formatting /dev/hda2
mke2fs -j $blockcheck -L /boot /dev/hda2

echo
echo formatting /dev/hda3
mke2fs -j $blockcheck -L / /dev/hda3

echo Making /dev/hda5 a swap partition.
mkswap $blockcheck /dev/hda5

fdisk -l "/dev/hda"

11.1.3. make.lvs

make.lvs is generated by make.fdisk, but only if logical volumes are present. As the name suggests, it builds the logical volumes and makes file systems on them.

#! /bin/sh

# A script to create file systems on logical volumes. Created at bare
# metal backup time by the Perl script make.fdisk.

# Copyright 2006 through the last date of modification Charles Curley.

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA

# You can also contact the Free Software Foundation at http://www.fsf.org/

# For more information contact the author, Charles Curley, at
# http://www.charlescurley.com/.


export blockcheck=$1;

if [ "$blockcheck" != "-c" ] && [ -n "$blockcheck" ]
then
    echo "${0}: Build file systems on logical volumes."
    echo "${0}: -c: block check during file system making."
    exit 1;
fi

export LVM_SYSTEM_DIR=$(pwd)/lvm.cfg

echo "y\n" | pvcreate -ff --uuid "CCmw0N-0We2-HzRS-jRZa-FkC7-NxTc-oAfvpX"\
     --restorefile lvm.cfg/archive/VolGroup00_*.vg   /dev/hda3
vgcfgrestore --file LVM.backs VolGroup00

# Hideously disty dependent!
if [ -e /etc/init.d/lvm ] ; then
     /etc/init.d/lvm start
fi

echo
echo making LV /dev/VolGroup00/LogVol00 an ext3 partition.
mke2fs -j $blockcheck /dev/VolGroup00/LogVol00

echo
echo making LV /dev/VolGroup00/LogVol02 an ext3 partition.
mke2fs -j $blockcheck /dev/VolGroup00/LogVol02

echo
echo making LV /dev/VolGroup00/LogVol01 a swap partition.
mkswap $blockcheck /dev/VolGroup00/LogVol01

11.1.4. mount.dev.hda

This script is a sample of the sort produced by make.fdisk, above. It builds mount points and mounts partitions on them, making the target file system ready for restoring files. This is the second script run at restore time.

If you are brave enough to edit dev.hda (q.v.), say, to add a new partition, you may need to edit this script as well.

#! /bin/sh

# A script to create a minimal directory tree on the target hard drive
# and mount the partitions on it. Created at bare metal backup time by
# the Perl script make.fdisk.

# Copyright 2001 through the last date of modification Charles Curley.

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA

# You can also contact the Free Software Foundation at http://www.fsf.org/

# For more information contact the author, Charles Curley, at
# http://www.charlescurley.com/.


# WARNING: If your Linux system mount partitions across hard drive
# boundaries, you will have multiple "mount.dev.* scripts. You must
# ensure that they run in the proper order. The root partition should
# be mounted first, then the rest in the order they cascade. If they
# cross mount, you'll have to handle that manually.


# / is the mountpoint for tomsrtbt device /dev/hda3.
mkdir /target/
mount /dev/hda3 /target/

# /boot is the mountpoint for tomsrtbt device /dev/hda2.
mkdir /target/boot
mount /dev/hda2 /target/boot

mount | grep -i "/dev/hda"

11.1.5. mount.lvs

mount.lvs is generated by make.fdisk, but only if logical volumes are present. As the name suggests, it mounts the logical volumes ready for restoration.

#! /bin/sh

# A script to mount file systems on logical volumes. Created at bare
# metal backup time by the Perl script make.fdisk.

# Copyright 2006 through the last date of modification Charles Curley.

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA

# You can also contact the Free Software Foundation at http://www.fsf.org/

# For more information contact the author, Charles Curley, at
# http://www.charlescurley.com/.

mkdir -p /target/
mount /dev/VolGroup00/LogVol00 /target/

mkdir -p /target/home
mount /dev/VolGroup00/LogVol02 /target/home

mount | grep -i "/target"

11.1.6. dev.hda

This data file is used at restore time if sfdisk is not present on the restoration Linux. It is fed to fdisk by the script make.dev.hda. It is produced at backup time by make.fdisk. Those familiar with fdisk will recognize that each line is an fdisk command or value, such as a cylinder number. Thus, it is possible to change the partition sizes and add new partitions by editing this file. That's why the penultimate command is v, to verify the partition table before it is written.

n
p
1
1
29
a
1
n
p
2
30
44
n
e
3
45
1023
n
l
45
944
n
l
945
1023
t
1
6
t
6
82
v
w

11.1.7. dev.hda.sfd

This data file is used at restore time if sfdisk is present on the restoration Linux system. It is fed to sfdisk by the script make.dev.hda. It is produced at backup time by make.fdisk. Each line represents a partition. Thus, it is possible to change the partition sizes and add new partitions by editing this file.

# partition table of /dev/hda
unit: sectors

/dev/hda1 : start=       63, size=   116865, Id= 6, bootable
/dev/hda2 : start=   116928, size=   153216, Id=83
/dev/hda3 : start=   270144, size=   286272, Id=82
/dev/hda4 : start=   556416, size=  3568320, Id= 5
/dev/hda5 : start=   556479, size=  3568257, Id=83

11.1.8. save.metadata

This is the first script to run as part of the backup process. It calls make.fdisk, above. If you have a SCSI hard drive or multiple hard drives to back up, edit the call to make.fdisk appropriately.

NoteWARNING
 

Recent kernels have incorporated a new ATA (IDE) hard drive driver, libata. Because of this, parallel ATA (PATA) drives now show up as SCSI drives, as serial ATA (SATA) have always done. However, not all rescue distributions (e.g. Finnix) use this new driver. There is a line toward the bottom of save.metadata wich very carefully replaces "/dev/sda" with "/dev/hda". Use this as a template if you have multiple IDE hard drives. Comment it out or delete it if this is not an issue for you.

Note that there is no guaranteed mapping! Systems with multiple hard drives may have confusing mappings. Be sure to edit this line carefully. Check it if you add or remove a hard drive of any interface type to or from your system!

N.B: if you have libata IDE drive issues, the grub-install line at the end of restore.metadata won't work. If it doesn't, use your rescue disk to do the same. Or burn and boot to the boot image that is made as part of this script. Boot to it and do the second state restore as usual. The second state restore should re-run grub-install.

#! /bin/bash

# A script to save certain meta-data off to the boot partition. Useful for
# restoration.

# Copyright 2000 through the last date of modification, Charles Curley.

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA

# You can also contact the Free Software Foundation at
# http://www.fsf.org/

# 2013-06-14: Preliminary work on preserving GPT information. We have
# not automated restoring, but at least we preserve the partition
# data.

# 2013-05-09: We now accumulate LSB release data into the README.txt
# file.

# 2012-07-18: Added creation of a list of packages, for debian systems
# only.

# 2007-10-29: added support for Debian systems.

# 2007-05-22: Changes for FHS compliance. Removed commented out
# references to ZIP drives. Added a line to deal with the fact that
# libata (in newer kernels) maps IDE drives to SCSI device names, but
# not all rescue distributions use libata. So we have to change the
# device names from SCSI to IDE, e.g. /dev/sda to /dev/hda.

# 2006-03-26: had a deprecated option in the sort options; fixed that.

# 2005-09-09: Added a line to create a boot disk ISO in the ZIP drive.

# 2005-08-30: Modernized sub-shell calls, a few other tweaks.

# 2005-07-29: Fedora Core 4 mods. Name of the directory to be saved
# has to be last. Also, we now specify --numeric-owner so as to avoid
# UID problems when using some live CD systems. And we now save to
# /var instead of a mounted ZIP disk.

# 2005-02-19: Fedora Core 3 mods.

# 2003 01 08: We now age the output from rpm -VA to make back
# comparisons easier.

# The loop that creates directories now has the -p option for mkdir,
# which means you can create parents on the fly if they don't already
# exist.

# initrd is now in the list of directories to create automatically.

# We now exclude more stuff when building the tarballs.

# 2002 07 01: Went to bzip2 to compress the archives, for smaller
# results. This is important in a 100MB ZIP disk. Also some general
# code cleanup.

# 2002 07 01: The function crunch will tar and bzip2 the
# archives. This is cleaner than the old code, and has better safety
# checking.


# For more information contact the author, Charles Curley, at
# http://www.charlescurley.com/.

# Exclude: given a fully qualified path to an ambiguous file name,
# expand it to the base name plus, e.g. any version numbering in the
# base name. E.g. "/usr/lib/python*" becomes "python2.5" if that's
# what's in the directory. We then prepend "--exclude" and return
# it. Use it to prepare ambiguous excludes for crunch's benefit. If
# the file doesn't exist, we return nothing.

function exclude {

if [ -z "$1" ]; then            # 0 length parameter check.
   echo "-Parameter #1 is missing.-" # Also if no parameter is passed.
   exit 1
else
   local file=$1
   local t
   for t in $(ls -d $file 2>/dev/null ) ; do
     if [ -e $t ]; then
       echo -n '--exclude ' ${t#/} ' ' ;
     fi
   done
fi
}


# Crunch: A function to compress the contents of a directory and put
# the archive onto the ZIP disk.

# The first parameter is the name of the archive file to be
# created. The backup location, $zip, will be prepended and the
# extension, "tar.bz2" will be appended.

# All following parameters will be taken as additional directories or
# files to be put into the archive.

function crunch {

if [ -z "$1" ] || [ -z "$2" ]	# Checks if parameter #1 or #2 is zero length.
then
   echo "-Parameter #1 or #2 is missing.-"  # Also if no parameter is passed.
   return 1
else
   local file=$1		# The archive file to create
   shift			# Discard the file name
   local dirs=$@		# The director[y|ies] to archive
   local tarcmd="tar --numeric-owner -cjf"	# The tar command.

   local tarit="$tarcmd  ${zip}/$data/$file.tar.bz2 $dirs"
   echo $tarit
   $tarit			# do it!!

   error=$?			# Preserve the exit code

   if [ $error != 0 ]		# Did we fail?
   then				# Yes
      echo "Tar failed with error $error"
      echo $tarcmd ${zip}/$data/$file.tar.bz2 $dirs
      exit $error		# return tar's exit code as ours
   fi

   return 0			# For error testing if needed.
fi
}

# Begin the main line code
export data="data";             # Name of the data directory in the archive
export today=$(date +%Y%m%d);   # Today's archive
export zip="/var/lib/bare.metal.recovery/${today}";

if [ -d ${zip} ] ; then
  echo deleting metadata from earlier today,
  echo ${zip}
  echo
  rm -r ${zip}
fi
mkdir -p ${zip}/metadata ${zip}/bin ${zip}/data

NEW=${zip}/metadata/rpmVa.txt       # name for the rpm -Va output file.

# Now we save hard drive information. Run make.fdisk on each hard
# drive in the order in which it mounted from the root partition. That
# is, run it first on the hard drive with your root partition, then
# any hard drives that mount to the first hard drive, then any hard
# drives that mount to those. For example, if your root partition is
# on /dev/sdc, run "make.fdisk /dev/sdc" first.

# The reason for this is that make.fdisk produces a script to make
# mount points and then mount the appropriate partition to them during
# first stage restore. Mount points must be created on the partition
# where they will reside. The partitions must be mounted in this
# order. For example, if your /var and /var/ftp are both separate
# partitions, then you must mount /, create /var, then mount /var,
# then create /var/ftp. The order in which the script "first.stage"
# runs the mounting scripts is based on their time of creation.

# If necessary, put a line, "sleep 1" between calls to make.fdisk.

echo "Saving hard drive info"

# List all your hard drives here. Put them in the order you want
# things done at restore time.

for drive in sda ; do
    fdisk -l /dev/${drive} | grep GPT > /dev/null
    if [ $? = '0' ] ; then
        echo "GPT found."
	sgd=$(which sgdisk)
        if [ $? = '1' ] ; then
            echo "sgdisk required ; install package gdisk!"
            exit 1
        fi
	sgdisk -p /dev/${drive} > ${zip}/sgdisk.${drive}.txt
	sgdisk --backup=${zip}/metadata/sgdisk.${drive} /dev/${drive}
    else
        echo "GPT not found. Handling as usual."
        make.fdisk /dev/${drive}
        fdisk -l /dev/${drive} > ${zip}/fdisk.${drive}
    fi
done

echo -e "$(hostname) bare metal archive, created $(date)" > ${zip}/README.txt
uname -a >> ${zip}/README.txt

# Preserve the release information. Tested with Red Hat/Fedora, should
# work with SuSE, Mandrake and other RPM based systems. Also Ubuntu
# 7.10, Gutsy Gibbon.

for releasefile in $(ls /etc/*release*  /etc/debian_version) ; do
  # echo $releasefile
  if [ -e $releasefile ] && [ ! -L $releasefile ] ; then
    cat $releasefile >> ${zip}/README.txt
  fi
done

# Linux Standard Base information.

lsb=$(which lsb_release)

if [ $? = '0' ] ; then
  echo >> ${zip}/README.txt
  echo LSB data: >> ${zip}/README.txt
  lsb_release -a >> ${zip}/README.txt
fi

# end Linux Standard Base information.

# Are we on an RPM based system? Suse?? Other RPM systems?
REDHAT=$(grep -iq \(fedora\|redhat\) ${zip}/README.txt ; echo $?)

if [ $REDHAT == 0 ] ; then

    # back up RPM metadata
    echo "Verifying RPMs."
    rpm -Va | sort -t ' ' -k 3 | uniq > ${NEW}
    echo "Finished verifying RPMs."

else
    echo "Non-RPM System."
    dpkg-query --showformat='${Package}\t${Version}\t${Revision}\t${Architecture}\n' --show | sort > ${zip}/packages.txt
    if [ -z $(which debsums) ] ; then
        echo debsums is *not* intalled.
    else
        echo Running debsums...
        debsums -csa > ${zip}/metadata/debsums.txt 2>&1
        echo Done running debsums.
    fi
fi

echo "Building the ZIP drive backups."

# These are in case we need to refer to them while rebuilding. The
# rebuilding process should be mostly automated, but you never
# know....

ls -al /mnt > ${zip}/ls.mnt.txt
ls -al / > ${zip}/ls.root.txt
ls -al /var > ${zip}/ls.var.txt

cd /

# Build our minimal archives on the ZIP disk. These appear to be
# required so we can restore later on.

crunch usr.lib $(exclude '/usr/lib/perl*') --exclude usr/lib/openoffice\
  --exclude usr/lib/libreoffice \
  --exclude usr/lib/Adobe  $(exclude '/usr/lib/python*')\
  $(exclude '/usr/lib/firefox*') $(exclude '/usr/lib/gimp*') --exclude dri\
  --exclude X11 $(exclude '/usr/lib/qt*')\
  --exclude xorg --exclude gconv --exclude locale $(exclude '/usr/lib/evolution*')\
  --exclude isdn $(exclude '/usr/lib/anaconda*') $(exclude '/usr/lib/ImageMagick*')\
  --exclude usr/lib/wine usr/lib

# crunch usr.share --exclude icons --exclude selinux\
#  --exclude man --exclude doc --exclude locale --exclude X11\
#  --exclude fonts --exclude gnome --exclude foomatic\
#  --exclude gnome-applets --exclude man --exclude pixmaps usr/share
# crunch usr.share.locale /usr/share/locale/en_US/

# if [ -e /usr/share/fonts/default ]\
#   && [ -e /usr/share/fonts/ISO8859-2 ]\
#   && [ -e /usr/share/fonts/bitmap-fonts ]; then
#     crunch usr.share.fonts /usr/share/fonts/default /usr/share/fonts/ISO8859-2 \
#     /usr/share/fonts/bitmap-fonts
# fi

crunch root --exclude root/tmp --exclude root/.cpan --exclude root/.mozilla --exclude root/down root
crunch boot boot
crunch etc --exclude etc/samba --exclude etc/X11 --exclude etc/gconf etc
crunch lib lib

crunch usr.sbin usr/sbin
# crunch usr.local usr/local
# crunch usr.libexec usr/libexec

if [ $REDHAT == 0 ] ; then
  crunch usr.kerberos usr/kerberos
fi

crunch usr.bin --exclude usr/bin/emacs-x\
 --exclude usr/bin/emacsclient --exclude usr/bin/emacs-nox --exclude\
  usr/bin/gs --exclude usr/bin/pine $(exclude 'usr/bin/gimp-*')\
   --exclude usr/bin/doxygen --exclude usr/bin/postgres --exclude\
    usr/bin/gdb --exclude usr/bin/kmail --exclude usr/bin/splint\
	 --exclude usr/bin/odbctest --exclude usr/bin/php --exclude \
	 usr/bin/xchat --exclude usr/bin/gnucash --exclude usr/bin/pdfetex\
	  --exclude usr/bin/pdftex --exclude usr/bin/smbcacls\
	   --exclude usr/bin/evolution-calendar --exclude usr/bin/xpdf\
	    --exclude usr/bin/xmms usr/bin
crunch sbin sbin
crunch bin bin
crunch dev dev

# RH8. Fedora 1 puts them in /lib
# crunch kerberos usr/kerberos/lib/

# Now optional saves.

# arkeia specific:
# crunch arkeia usr/knox

# save these so we can use ssh for restore. *crack* for RH 7.0 login
# authentication.
# RH 8.0
# crunch usr.lib usr/lib/*crack* usr/lib/libz* usr/lib/libssl* usr/lib/libcrypto*
# Fedora 1
# crunch usr.lib usr/lib/*crack* usr/lib/libz* usr/lib/libwrap*\
#  usr/lib/libk* usr/lib/*krb5* /usr/lib/libgss*
# Fedora 3
# crunch usr.lib usr/lib/*crack* usr/lib/libz* usr/lib/libwrap*\
#  usr/lib/libk* usr/lib/*krb5* usr/lib/libgss*
# Fedora 7
# crunch usr.lib usr/lib/*crack*\
#  usr/lib/libk* usr/lib/*krb5* usr/lib/libgss*

# Grub requires these at installation time.
if [ $REDHAT == 0 ] ; then
  crunch usr.share.grub usr/share/grub
else
  crunch usr.lib.grub usr/lib/grub
fi

# save the scripts we will use to restore.
cp -p /etc/bare.metal.recovery/* ${zip}/bin

echo "Testing our results."
find ${zip} -iname "*.bz2" | xargs bunzip2 -t

# Since we're doing system stuff anyway, make a boot disk ISO image
# suitable for burning. It uses the current kernel.

if [ $REDHAT == 0 ] ; then
  mkbootdisk --iso --device ${zip}/bootdisk.$(uname -r).iso $(uname -r)
fi

# Recent kernels have incorporated a new ATA (IDE) hard drive
# driver. Because of this, parallel ATA drives now show up as SCSI
# drives, as serial ATA have always done. However, not all rescue
# distributions (e.g. finix) use this new driver. So the following
# line very carefully replaces "/dev/sda" with "/dev/hda". Use this as
# a template if you have multiple IDE hard drives.

# Note that there is no guaranteed mapping! Systems with multiple hard
# drives may have confusing mappings. Be sure to edit this line
# carefully. Check it if you add or remove a hard drive of any
# interface type to or from your system!

find ${zip} -type f | grep -v bz2$ | xargs sed -i 's|/dev/sda|/dev/hda|g'

du -hs ${zip}
df -h

11.1.9. restore.metadata

This script restores metadata from the ZIP disk as a first stage restore.

N.B: if you have libata IDE drive issues, the grub-install line at the end of this script won't work. If it doesn't, use your rescue disk to do the same.

#! /bin/sh

# A script to restore the meta-data from the ZIP disk. This runs under
# tomsrtbt only after partitions have been rebuilt, file systems made,
# and mounted. It also assumes the ZIP disk has already been
# mounted. Mounting the ZIP disk read only is probably a good idea.

# Copyright 2000 through the last date of modification Charles Curley.

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA

# You can also contact the Free Software Foundation at http://www.fsf.org/

# 2007-07-08: We now force inittab to run level 3, which may be disty
# dependent. FHS compliance changes as well.

# 2005-08-03: We now use a relative path, so you can load from
# different places depending on the first stage system you are
# using. Also added some FC4 tricks, and some changes to better
# reproduce the permissions and ownerships.

# 2003 08 23: Oops: tar on tomsrtbt does not respect -p. Try setting
# umask to 0000 instead.

# 2003 02 13: Tar was not preserving permissions on restore. Fixed
# that.

# 2002 07 01: Went to bzip2 to compress the archives, for smaller
# results. This is important in a 100MB ZIP disk. Also some general
# code cleanup.

# For more information contact the author, Charles Curley, at
# http://www.charlescurley.com/.

umask 0000

cd ..                           # Assume we are in /bin
zip=$(pwd)/data;              # Where we find the tarballs to restore.
target="/target";        # Where the hard drive to restore is mounted.

ls -lt $zip/*.bz2               # Warm fuzzies for the user.

cd $target

# Restore the archived metadata files.
for archive in $( ls $zip/*.bz2 ); do
  echo $archive
  ls -al $archive
  bzip2 -dc $archive | tar -xf -
done

# Build the mount points for our second stage restoration and other
# things.

# If you boot via an initrd, make sure you build a directory here so
# the kernel can mount the initrd at boot. tmp/.font-unix is for the
# xfs font server.

for dir in\
   back\
   dev\
   initrd\
   media\
   mnt/dosc\
   mnt/imports\
   mnt/nfs\
   mnt/zip\
   proc\
   selinux\
   sys\
   tmp/.font-unix\
   var/cache/yum\
   var/empty/sshd/etc\
   var/lib/bare.metal.recovery\
   var/lock/subsys\
   var/log\
   var/run\
   var/spool\
 ; do

  mkdir -p $target/$dir
done

for dir in mnt usr usr/share $(ls -d var/*) selinux usr/lib var\
  var/cache/yum var/lock/subsys var/run var/empty/sshd/etc\
  var/spool media ; do
  chmod go-w $target/$dir
done

# Set modes
chmod 0111 $target/var/empty/sshd
chown root:lock $target/var/lock
chmod 775 $target/var/lock
chmod 711 $target/var/empty/sshd
chmod 700 $target/var/lib/bare.metal.recovery

# For Fedora. First two for xfs.
# chroot $target chown xfs:xfs /tmp/.font-unix
# chmod 1777 $target/tmp/.font-unix # set the sticky bit.
chmod 1777 $target/tmp

# Now install the boot sector. N.B: if you have libata IDE drive
# issues, it won't work. If it doesn't, use your rescue disk to do the
# same.

# chroot $target /sbin/lilo -C /etc/lilo.conf
chroot $target /sbin/grub-install /dev/hda

# Set the system to boot to run level 3 regardless of the current run
# level. Be sure to set it back to the normal value.

sed -i s/id:.:initdefault:/id:3:initdefault:/g $target/etc/inittab

df -m

11.1.10. first.stage

This script runs the entire first stage restore with no operator intervention.

If you want to check for bad blocks when it puts a file system on the partitions, use a "-c" command line option.

#! /bin/bash

# A master script to run the other, detailed scripts. Use this script
# only if you want no human intervention in the restore process. The
# only option is -c, which forces bad block checking during formatting
# of the partitions.

# Copyright 2002 through the last date of modification Charles Curley.

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA

# You can also contact the Free Software Foundation at http://www.fsf.org/

# For more information contact the author, Charles Curley, at
# http://www.charlescurley.com/.

# 2005-08-07 We no longer assume the working directory. This is
# because the working directory will vary greatly according to which
# Linux disty you use and how you are doing your restoration.

export blockcheck=$1;

if [ "$blockcheck" != "-c" ] && [ -n "$blockcheck" ]
then
    echo "${0}: automated restore with no human interaction."
    echo "${0}: -c: block check during file system making."
    exit 1;
fi

for drive in $( ls make.dev.* ); do
    echo $drive$'\a'
    sleep 2
    ./$drive $blockcheck;
done

# If there are any LVM volumes, now is the time to restore them.

if [ -e make.lvs ] && [ -e mount.lvs ]
then
    echo make.lvs$'\a'
    sleep 2
    ./make.lvs

    echo mount.lvs$'\a'
    ./mount.lvs
fi


# WARNING: If your Linux system mount partitions across hard drive
# boundaries, you will have multiple "mount.dev.* scripts. You must
# ensure that they run in the proper order, which the loop below may
# not do. The root partition should be mounted first, then the rest in
# the order they cascade. If they cross mount, you'll have to handle
# that manually. If you have LVMs to deal with, that's a whole 'nother
# kettle of fish.

# The "ls -tr" will list the scripts in the order they are created, so
# it might be a good idea to create them (in the script save.metadata)
# in the order in which you should run them.

for drive in $( ls -tr mount.dev.* ); do
    echo $drive$'\a'
    sleep 2
    ./$drive;
done

./restore.metadata

# People who are really confident may comment this line in.
# reboot

11.2. Second Stage

These scripts run on the computer being backed up or restored.

11.2.1. back.up.all

This script saves to another computer via an NFS mount. You can adapt it to save to tape drives or other media.

#! /bin/bash

# Back up the entire system to another computer's drive. To make this
# work, we need a convenient chunk of disk space on the remote computer we
# can nfs mount as /mnt/save.

# Copyright 2000 through the last date of modification Charles Curley.

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA

# You can also contact the Free Software Foundation at http://www.fsf.org/

# For more information contact the author, Charles Curley, at
# http://www.charlescurley.com/.

save="/mnt/save"

# Make sure it's there
umount $save
mount $save

cd /

rm $save/tester.tar.old.gz
mv $save/tester.tar.gz $save/tester.tar.old.gz

# save everything except /mnt, /proc, and nfs mounted directories.

time tar cf - / --exclude /mnt --exclude /proc --exclude $save\
    | gzip -c > $save/tester.tar.gz

11.2.2. back.up.all.ssh

This script does exactly what back.up.all does, but it uses SSH instead of NFS.

#! /bin/bash

# Back up the entire system to another computer's drive. To make this
# work, we need a convenient chunk of disk space on the remote
# computer. This version uses ssh to do its transfer, and compresses
# using bz2. This means this script has to know more about the other
# computer, which does not make for good modularization.

# Copyright 2000 through the last date of modification Charles Curley.

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA

# You can also contact the Free Software Foundation at http://www.fsf.org/

# For more information contact the author, Charles Curley, at
# http://www.charlescurley.com/.

save="/backs/tester"
backup_server="charlesc"

# rotate the old backups. Do it all in one line to minimze authentication overhead.
ssh $backup_server "rm $save/tester.tar.old.bz2; mv $save/tester.tar.bz2 \
    $save/tester.tar.old.bz2"

# save everything except /mnt, /proc, and squid directories.

time tar cf - / --exclude /mnt --exclude /proc --exclude /var/spool/squid\
    | ssh $backup_server "bzip2 -9 > $save/tester.tar.bz2"

11.2.3. restore.all

This is the restore script to use if you backed up using back.up.all.

#! /bin/bash

# A script to restore all of the data from an nfs mount. This is our final
# stage restore.

# Copyright 2000 through the last date of modification Charles Curley.

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA

# You can also contact the Free Software Foundation at http://www.fsf.org/

# For more information contact the author, Charles Curley, at
# http://www.charlescurley.com/.

export save="/mnt/save"

mount $save

cd /
gunzip -dc $save/tester.tar.gz | tar -xpkf -

rm /var/run/*.pid

lilo

11.2.4. restore.all.ssh

This is the restoration script to use if you used back.up.all.ssh to back up.

#! /bin/bash

# A script to restore all of the data using ssh and bunzip2. This is
# our final stage restore.

# Copyright 2000 through the last date of modification Charles Curley.

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA

# You can also contact the Free Software Foundation at http://www.fsf.org/

# For more information contact the author, Charles Curley, at
# http://www.charlescurley.com/.

save="/backs/tester/"
backup_server="charlesc"

cd /

ssh $backup_server "cat $save/tester.tar.bz2" | bunzip2 | tar -xpkf -

rm /var/run/*.pid

lilo

11.3. Backup Server Scripts

The SSH scripts above have a possible security problem. If you run them on a firewall, the firewall has to have access via SSH to the backup server. In that case, a clever cracker might also be able to crack the backup server. It would be more secure to run backup and restore scripts on the backup server, and let the backup server have access to the firewall. That is what these scripts are for.

These scripts backup and restore the target completely, not just the stage one backup and restore. get backs up the bare metal archive separately so that you can make a CD-ROM ir NFS mount from it.

I use these scripts routinely.

11.3.1. get

#! /bin/bash

# Back up another computer's drive to this system. To make this work,
# we need a convenient chunk of disk space on this computer. This
# version uses ssh to do its transfer, and compresses using bz2. This
# version was developed so that the system to be backed up won't be
# authenticated to log onto the backup computer. This script is
# intended to be used on a firewall. You don't want the firewall to be
# authenticated to the backup system in case the firewall is cracked.

# Copyright 2000 through the last date of modification Charles Curley.

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA

# You can also contact the Free Software Foundation at http://www.fsf.org/

# For more information contact the author, Charles Curley, at
# http://www.charlescurley.com/.

# 2007-05-22: Changes for FHS compliance. Removed commented out
# references to ZIP drives. Now, if the archive does not exist on the
# client, we run save.metadata there.

# 2006-04-14: Use the script ../scripts/get.target to determine the
# host name. This makes the script more general, and maybe we can run
# it from one place & determine the host name from the directory
# name. Also, we now test to see if old backups exist before deleting
# them.

# 2004 04 03: added /sys to the list of excludes. It is a read-only
# pseudo-file system like /proc.

# 2002 07 01: We now set the path on the target to the zip drive with
# a variable. This fixes a bug in the command to eject the zip disk.

# 2002 07 01: The zip disk archives are now in bzip2 format, so this
# script has been changed to reflect that.

# Get the host name of the computer to be backed up and other info.
. ../scripts/get.target

# The "--anchored" option is there to prevent --exclude from excluding
# all files with that name. E.g. we only want to exclude /sys, not
# some other sys elsewhere in the file system.

ssh $host "cd / ; tar -cf - --anchored --exclude media --exclude mnt\
 --exclude selinux --exclude sys --exclude proc --exclude var/spool/squid\
 --exclude var/cache/yum --exclude var/named/chroot/proc\
 --exclude var/lib/bare.metal.recovery * " | bzip2 -9 | cat > $host.$DATE.tar.bz2

# if [ -e $host.dos.$DATE.old.tar.bz2 ] ; then
#     rm $host.dos.$DATE.old.tar.bz2
# fi

# echo Backing up $host dos to the backup server.
# ssh $host "cd / ; mount mnt/dosc ; tar -cf - mnt/dosc "\
# | bzip2 -9 | cat > $host.dos.$DATE.tar.bz2

echo Testing the results.
find $host.$DATE* -iname "*.bz2" | xargs bunzip2 -t

11.3.2. restore

#! /bin/bash

# A script to restore all of the data to tester via ssh. This is our final
# stage restore.

# Copyright 2000 Charles Curley.

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA

# You can also contact the Free Software Foundation at http://www.fsf.org/

# For more information contact the author, Charles Curley, at
# http://www.charlescurley.com/.

# 2007-05-22: Changes for FHS compliance. Removed commented out
# references to ZIP drives.

# 2006-04-14: Use the script ../scripts/get.target to determine the
# host name. This makes the script more general, and maybe we can run
# it from one place & determine the host name from the directory
# name.

# Get the host name of the computer to be backed up and other info.
. ../scripts/get.target

bunzip2 -dc $TARGET.tar.bz2 | ssh $host "umask 000 ; cd / ; tar -xpkf - "

# bunzip2 -dc $host.dos.$DATE.tar.bz2 | ssh $host "umask 000 ;\
# mount /mnt/dosc ; cd / ; tar -xpkf - "

# Note libata issue! We boot to /dev/sda, not /dev/hda, as IDE drives
# now show up as SCSI drives.

ssh $host "chown root:lock /var/lock ; grub-install /dev/sda"
# ; chown -R amanda:disk /var/lib/amanda

11.3.3. get.target

# -*- shell-script -*-

# Copyright 2000 through the last date of modification, Charles Curley.

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA

# You can also contact the Free Software Foundation at
# http://www.fsf.org/

# How to determine the target: the directory structure is
# /..../back/hostname.OS.etc So we have to get the name of the
# directory. That gives us host name and OS.etc. From that we extract
# the host name.

path=`pwd`

target=${path##/*/}
host=${target%%.*}

# Run "info date" for more information.

DATE=`date +%Y%m%d`
# echo "Today's date is $DATE."

echo "\$target is $target. \$host is $host. Today's date is $DATE."

name=$0
echo "This is script $name"

if [ $(echo $name | grep -i get > /dev/null) ] ; then
    # Do functions common to all restores.

    # Which archive do we restore and is this a valid target name?

    TARGET=$1

    if [ -z $TARGET ] ; then
        echo Please specify a target from one of:
        for dir in $(ls -d $host.*) ; do
            if [ -d $dir ] ; then
                echo -n "$dir "
            fi
        done
        exit 2;
    fi

    if [ -z $TARGET ] || [ ! -d $TARGET ] ; then
        echo $TARGET does not exist!
        exit 2;
    fi

    ssh $host rm -r /var/lib/rpm
else
    # Do functions common to all gets.

    # Where we will get the archives on the target.

    #zip=/mnt/zip
    export zip="/var/lib/bare.metal.recovery";

    # If it does not already exist, build the archive.

    ssh $host "if [ ! -d ${zip}/$DATE ] ; then echo Saving metadata... ; save.metadata ; fi"

    echo Backing up $host

    if [ -e $host.$DATE ] ; then
	    rm -r $host.$DATE
    fi

    echo Copying the bare metal recovery archive.

    # -r for recursive copy, -p to preserve times and permissions, -q
    # for quiet: no progress meter.

    scp -qpr $host:$zip/$DATE $host.$DATE

    du -hs $host.*

    echo Cleaning out old yum packages
    ssh $host "yum clean packages"

    echo Backing up $host to the backup server.

    if [ -e $host.$DATE.tar.bz2 ] ; then
        rm $host.$DATE.tar.bz2
    fi
fi

11.4. Miscellaneous Files

11.4.1. install

This little script just installs things and sets up a few directories. It tests for the presence of the perl UUID module.

It would be a useful basis for an RPM or deb package. The placement of files is based on the Filesystem Hierarchy Standard, version 2.3, announced on January 29, 2004.

#! /bin/bash

# A script to install the bare metal recovery scripts. With any luck,
# this will comply with the "Filesystem Hierarchy Standard",
# http://www.pathname.com/fhs/, version 2.3, announced on January 29,
# 2004.

# Copyright 2007 through the last date of modification, Charles Curley.

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA

# You can also contact the Free Software Foundation at
# http://www.fsf.org/

# 2017-07-12: We now check to see if the perl UUID module is installed
# and in perl's module path.

# 2014-01-06: we now install a set of excludes for use in general
# backing up of stuff not backed up by the general backup system
# (amanda, e.g., or by the metatdata/minimal system backups.

# Begin the actual code.

# Check for the presence of perl's UUID module (and, indirectly, perl
# itself).

perl -e 'use UUID;'
uuid=$?

if [ "$uuid" != "0" ] ; then
    echo Oops! Please install perl module UUID.
    echo E.g.: apt install libuuid-perl
    echo Perl exited with a return value of $uuid.
    exit $uuid
fi

# Where we put the archives ready for making CDs, etc.
mkdir -p /var/lib/bare.metal.recovery

# Keep them secure from pesky snooping users who don't need to be able
# to hack a copy of, say, /etc/shadow.
chmod 700 /var/lib/bare.metal.recovery

# Excludes for use by the "get" script on the bare metal backup
# server.
cp ../bare.metal.backup.excludes /var/lib/bare.metal.recovery

# Backup time executables.
for i in save.metadata make.fdisk ; do
  cp -rp $i /usr/sbin
  chown root:root /usr/sbin/$i
done

# Save the recovery time executables we provide. The archiving
# programs look for them here and save them into the archives.
mkdir -p /etc/bare.metal.recovery
for i in first.stage restore.metadata ; do
  cp -rp $i /etc/bare.metal.recovery
  chown root:root /etc/bare.metal.recovery/$i
done