#!/bin/sh -
#
#   $Id: mirrorit,v 1.11 2005/12/15 06:36:17 dgregor Exp $
#
# Copyright (c) 1999-2002 Daniel J. Gregor, Jr., All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 
# THIS SOFTWARE IS PROVIDED BY DANIEL J. GREGOR, JR. ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL DANIEL J. GREGOR, JR. BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.

basename="`basename $0`"
warn(){
    echo "$basename: $*" >&2
}
die(){
    echo "$basename: $*" >&2
    exit 1
}

usage="\
usage:\n\
\tmirrorit -a <script> <disk0> <rdisk0> <disk1> <rdisk1> <metadb slice>\n\
\n\
\tRun \"mirrorit -h | more\" for detailed help"

longusage="$usage
n\n\
\n\
where:\n\
\t<script>\tHere you specify the location where a shell script\n\
\t\t\twill be placed.  This script will need to be run after\n\
\t\t\ta reboot to finish the mirror setup process.\n\
\t<disk0>\t\tThe block device of the disk which contains the set\n\
\t\t\tof filesystems you want to mirror onto the second\n\
\t\t\tdisk.  This disk will contain the first set of\n\
\t\t\tsubmirrors.  All of the partitions on this device that\n\
\t\t\tyou want to mirror should be listed in /etc/vfstab.\n\
\t\t\tWhen you specify this device, leave off the slice\n\
\t\t\tnumber, e.g.: \"/dev/dsk/c0t0d0\".\n\
\t<rdisk0>\tThe raw device of <disk0>, e.g.: \"/dev/rdsk/c0t0d0\".\n\
\t<disk1>\t\tThe block device of the disk which will contain the\n\
\t\t\tsecond set of submirrors.  All data on this disk will\n\
\t\t\tbe DESTROYED as it is brought online as a submirror, so\n\
\t\t\tensure that this disk is unused.  Be sure to leave\n\
\t\t\toff the slice number, e.g.: \"/dev/dsk/c1t0d0\".\n\
\t<rdisk1>\tThe raw device of <disk1>, e.g.: \"/dev/rdsk/c1t0d0\".\n\
\t<metadb slice>\tThe slice on <disk0> and <disk1> that will hold the\n\
\t\t\tmetadb files.  This slice only needs to be a few MB\n\
\t\t\t(e.g.: 5MB), and all data on <disk0>s<metadb slice>\n\
\t\t\tand <disk1>s<metadb slice> will be erased when the\n\
\t\t\tmetadb files are created.  You should simply specify\n\
\t\t\tthe slice number as it would appear in a /dev/dsk\n\
\t\t\tdevice file.  To use slice 'f' in format, specify\n\
\t\t\t\"5\".\n\
\n\
Example Usage:\n\
\tmirrorit -a /finishmirroringsetup /dev/dsk/c0t0d0 /dev/rdsk/c0t0d0 \\\\\n\
\t\t/dev/dsk/c1t0d0 /dev/rdsk/c1t0d0 5\n\
\n\
\tThis will mirror the contents of any devices mentioned in /etc/vfstab\n\
\ton /dev/dsk/c0t0d0 to the same slices on /dev/dsk/c1t0d0.  It will use\n\
\tthe free slice 5 on both of those disks to store metadb files.  The\n\
\tscript /finishmirroringsetup will be created, and should be run after\n\
\ta reboot to boot the system with the new metadevices in use and to\n\
\tfinish attaching submirrors."
			 
dryrun=""
afterreboot=""
vfstab="/etc/vfstab"
metapath=""
metapaths="/usr/sbin /usr/opt/SUNWmd/sbin"
dryrun_vfstab_dir="/tmp/mirrorit.$$"

while getopts nt:a:p:h c
do
  case $c in
      n)
	  # XXX dryrun mode doesn't work so well
	  # XXX make sure that you look through this script
	  # XXX before you use it.
	  dryrun="echo"
	  ;;

      t)
	  vfstab="$OPTARG"
	  ;;

      a)
	  afterreboot="$OPTARG"
	  ;;
      
      p)
	  metapath="$OPTARG"
	  ;;

      h)
	  echo -e $longusage
	  exit 0
	  ;;

      '?')
	  die "unknown option.  exiting"
      ;;
  esac
done
shift `expr $OPTIND - 1`

if [ $# -ne 5 ]; then
    echo -e $usage >&2
    exit 1
fi

# XXX I really wish that "-e" was available in standard sh(1)
if [ -r "$afterreboot" ]; then
    warn "A file already exists in \"$afterreboot\".  Please remove"
    die "it or specify a different after reboot script location."
fi

# Find where they put the meta* tools, if not chosen on command line

if [ x"$metapath" = x"" ]; then
    for a in $metapaths; do
	if [ -x $a/metaroot ]; then
	    metapath="$a"
	    break
	fi
    done
fi

if [ ! -x $metapath/metaroot ]; then
    warn "Cannot find the meta tools."
    die "Please use the -p argument with a path."
fi

PATH=$PATH:$metapath

if [ x"$afterreboot" = x"" ]; then
    die "you must specify the -a option"
fi

disk0=$1; shift
rdisk0=$1; shift
disk1=$1; shift
rdisk1=$1; shift
metadbslice=$1; shift

#
# Sanity checking, devices
#

if [ ! -b "${disk0}s2" ]; then
    cat <<EOF >&2
Could not find overlay slice ("s2") for disk0, ${disk0}.
${disk0}s2 is not a block special disk device.

The <disk0> argument is supposed to be a block special disk device with
the slice part removed, i.e.: "/dev/dsk/c0t0d".

EOF
    echo -e $usage >&2
    exit 1
fi

if [ ! -b "${disk1}s2" ]; then
    cat <<EOF >&2
Could not find overlay slice ("s2") for disk1, ${disk1}.
${disk1}s2 is not a block special disk device.

The <disk1> argument is supposed to be a block special disk device with
the slice part removed, i.e.: "/dev/dsk/c0t1d".

EOF
    echo -e $usage >&2
    exit 1
fi

if [ ! -c "${rdisk0}s2" ]; then
    cat <<EOF >&2
Could not find overlay slice ("s2") for rdisk0, ${rdisk0}.
${rdisk0}s2 is not a character special disk device.

The <rdisk0> argument is supposed to be a character special disk device with
the slice part removed, i.e.: "/dev/dsk/c0t0d".

EOF
    echo -e $usage >&2
    exit 1
fi

if [ ! -c "${rdisk1}s2" ]; then
    cat <<EOF >&2
Could not find overlay slice ("s2") for rdisk1, ${rdisk1}.
${rdisk1}s2 is not a character special disk device.

The <rdisk1> argument is supposed to be a character special disk device with
the slice part removed, i.e.: "/dev/rdsk/c0t1d".

EOF
    echo -e $usage >&2
    exit 1
fi

#
# Sanity checking, partitions
#

grep '^[^#]*'"$disk0" $vfstab > /dev/null
if [ $? -ne 0 ]; then
    cat <<EOF >&2
No partitions on disk0 found to mirror.

There are no partitions on disk0 ($disk0)
listed in ${vfstab}.  Commented entries are ignored.

Please check that the disk specified for disk0 ($disk0)
is correct and that there are partitions on disk0 listed in
${vfstab}
EOF

    echo -e $usage >&2
    exit 1
fi

grep '^[^#]*'"$disk1" $vfstab > /dev/null
if [ $? -ne 1 ]; then
    cat <<EOF >&2
Partitions found on disk1 in $vfstab that would be destroyed.

All data on disk1 will be destroyed as part of the mirroring
process and there are partitions on disk1 ($disk1)
listed in ${vfstab}.

Please check that the disk specified for disk1 ($disk1)
is correct and that there are no partitions on disk1 listed
in ${vfstab} (it is okay if they are commented out).
EOF

    echo -e $usage >&2
    exit 1
fi

df -k | grep "^$disk1" > /dev/null
if [ $? -ne 1 ]; then
    cat <<EOF >&2
Mounted partitions found on disk1 that would be destroyed.

All data on disk1 will be destroyed as part of the mirroring
process and there are partitions on disk1 ($disk1)
which are mounted (as reported by "df -k").

Please check that the disk specified for disk1 ($disk1)
is correct and that there are no partitions on disk1 listed
in the output of "df -k".
EOF

    echo -e $usage >&2
    exit 1
fi

# XXX should we match one or more digits (as we are now), or
# XXX exactly one?
echo "$metadbslice" | grep '^[0-9][0-9]*$' > /dev/null
if [ $? -ne 0 ]; then
    cat <<EOF >&2
Invalid metadb slice number "$metadbslice" specified.

The value for <metadb slice> must be a numeric value.
EOF

    echo -e $usage >&2
    exit 1
fi

# XXX Check to make sure that the devices for r?disk[01] that the
# XXX user specified are actually different disks.  yes, we are trying
# XXX to make sure that a dumb user doesn't shoot their own foot off.

# If we are in dry run mode, make a copy of the vfstab and play with that copy.

if [ x"$dryrun" != x"" ]; then
    mkdir -m 700 $dryrun_vfstab_dir
    if [ $? -ne 0 ]; then
	die "Could not make temporary directory $dryrun_vfstab_dir"
    fi
    trap "cd / ; rm -rf $dryrun_vfstab_dir" 0

    cp $vfstab $dryrun_vfstab_dir/vfstab
    if [ $? -ne 0 ]; then
	die "Could not copy $vfstab to $dryrun_vfstab_dir/vfstab"
    fi
    vfstab_orig=$vfstab
    vfstab=$dryrun_vfstab_dir/vfstab
fi

# Real work.

$dryrun sh -c "prtvtoc ${rdisk0}s2 | fmthard -s -  ${rdisk1}s2 > /dev/null"
if [ $? -ne 0 ]; then
    die "Could not use prtvtoc/fmthard to write partition table to ${rdisk1}s2"
fi
$dryrun metadb -a -f -c 2 ${rdisk0}s${metadbslice} ${rdisk1}s${metadbslice}
if [ $? -ne 0 ]; then
    warn "Could not create metadevice state tables on these devices:"
    die "    ${rdisk0}s${metadbslice} ${rdisk1}s${metadbslice}"
fi

lastmetadb="`metastat | \
	grep '^d[0-9][0-9]*:' | \
	sed 's/:.*//;s/^d//' | \
	sort -n | \
	tail -1`"

if [ x"$lastmetadb" != x"" ]; then
    metadevicecount=`expr $lastmetadb + 1`
else
    metadevicecount=0
fi

echo "#!/bin/sh -" > $afterreboot
echo "PATH=\$PATH:$metapath" >> $afterreboot
chmod 755 $afterreboot

grep '^[^#]*'"$disk0" $vfstab | \
    sort | \
    while read devicemount devicefsck mountpoint fstype fsckpass \
    mountatboot mountoptions
  do
    mirror=d$metadevicecount
    submirror0=d`expr $metadevicecount + 1`
    submirror1=d`expr $metadevicecount + 2`
  
    devicemirror="`echo $devicemount | sed s%${disk0}%${disk1}%`"

    $dryrun metainit -f $submirror0 1 1 $devicemount
    if [ $? -ne 0 ]; then
	die "could not metainit $devicemount as $submirror0"
    fi

    $dryrun metainit $submirror1 1 1 $devicemirror
    if [ $? -ne 0 ]; then
	die "could not metainit $devicemirror as $submirror1"
    fi

    $dryrun metainit $mirror -m $submirror0
    if [ $? -ne 0 ]; then
	die "could not metainit $submirror0 into $mirror"
    fi

    if [ x"$mountpoint" = x"/" ]; then
	$dryrun metaroot $mirror
	if [ $? -ne 0 ]; then
	    die "could not metainit $submirror0 into $mirror"
	fi

	$dryrun lockfs -fa
	if [ $? -ne 0 ]; then
	    die "could not run lockfs"
	fi
    else
	if [ x"$devicemount" != x"-" ]; then
	    echo $devicemount | grep "^$disk0" > /dev/null && \
		echo "1,\$s%$devicemount%/dev/md/dsk/$mirror%\nw\nq" \
		| ed $vfstab > /dev/null
	    if [ $? -ne 0 ]; then
		die "could not change vfstab entry for $devicemount to /dev/md/dsk/$mirror"
	    fi
	fi

	if [ x"$devicefsck" != x"-" ]; then
	    echo $devicefsck | grep "^$rdisk0" > /dev/null && \
		echo "1,\$s%$devicefsck%/dev/md/rdsk/$mirror%\nw\nq" \
		| ed $vfstab > /dev/null
	    if [ $? -ne 0 ]; then
		die "could not change vfstab entry for $devicefsck to /dev/md/rdsk/$mirror"
	    fi
	fi
    fi

    echo metattach $mirror $submirror1 >> $afterreboot

    metadevicecount=`expr $metadevicecount + 3`
done

# If we are in dry run mode, print out a diff of our vfstab changes
if [ x"$dryrun" != x"" ]; then
	diff $vfstab_orig $vfstab
fi
