#!/usr/sepp/bin/perl -w # ISG Tool Chest # # name : mirror_root # maint: dws # vers : 1.5 # path : tardis:/home/support/data/tools/system/mirror_root/mirror_root # # date ver cksum who description # ---------- ---- -------- --- ------------------------------------------ # 2000/07/27 1.0 2ecb31eb ds added ISGTC header # 2000/09/08 1.1 8800f50c ds check if the mounted root matches 'disk' # and added -r option. # 2000/09/08 1.2 1c09c5fc ds if 'host' is not specified, it won't be # checked (was documented, but not implemented) # 2000/09/08 1.3 4e3009e7 ds installboot didn't work properly if bootblk # wasn't specified # 2000/12/04 1.4 f2c9807f ds check that mirror is not mounted. # # 2001/03/07 1.5 734a6a74 ds added copyright and license information # to calculate cksum, use: # cat mirror_root | grep -v '^#' | cksum | gawk '{printf "%08x\n", $1}' use strict; my $DEBUG=0; my $cmd_out = ''; my $cmd_error; $ENV{PATH}="/usr/sbin:/sbin:/bin"; sub trim_space($) { my $x = shift; $$x =~ s/^\s+//; $$x =~ s/\s+$//; } sub read_conf($$) { my $conffile = shift; my $mandatory = shift; my %conf = (); open(CONF, "<$conffile") or die "ERROR: can't open $conffile.\n"; my $line=1; while() { chomp; # trim space s/^\s+//; s/\s+$//; # comment or empty line /^(#|$)/ and next; # key = val /^(\S*?)\s*=\s*(.*)$/ or die "ERROR: syntax error in config file $conffile, line $line\n"; $conf{$1}=$2; $line++; } my $m; foreach $m (@$mandatory) { if(!$conf{$m}) { die "ERROR: parameter '$m' must be configured in $conffile.\n"; } } close(CONF); return \%conf; } sub sys($) { if($DEBUG) { print "cmd: $_[0]\n"; return; } return system "$_[0]"; } sub cd($) { if($DEBUG) { print "cmd: cd $_[0]\n"; return; } chdir "$_[0]"; } sub cmd($) { if($DEBUG) { print "cmd: $_[0]\n"; return 1; } open(CMD, "$_[0] 2>&1 ) { $cmd_out .= $_; } close(CMD) or do { $cmd_error = $? >> 8; return 0; }; return 1; } sub disk_slices($) { my $disk = shift; my %slices = (); open(DF, "df -lk |"); while() { if(m|^/dev/dsk/(${disk}s.)\s+.*?(\S+)$|) { $slices{$2} = $1; } } return \%slices; } sub read_partition_map($) { my $disk = shift; my %dims = (); my @pmap = (); open(PRTVTOC, "prtvtoc /dev/rdsk/${disk}s0 |"); while() { if(/^\s*$/) { next; } elsif(/^\*/) { if(/(\d+)\s+bytes\/sector\s*$/) { $dims{'b_s'} = $1; } elsif(/(\d+)\s+sectors\/track\s*$/) { $dims{'s_t'} = $1; } elsif(/(\d+)\s+tracks\/cylinder\s*$/) { $dims{'t_c'} = $1; } elsif(/(\d+)\s+sectors\/cylinder\s*$/) { $dims{'s_c'} = $1; } elsif(/(\d+)\s+cylinders\s*$/) { $dims{'c'} = $1; } elsif(/(\d+)\s+accessible cylinders\s*$/) { $dims{'ac'} = $1; } } else { my @p = split; push @pmap, \@p; } } close(PRTVTOC); return (\%dims, \@pmap); } sub make_partition_map($$$) { my $disk = shift; my $dims = shift; my $pmap = shift; my $str; $str = <{b_s}; $str .= sprintf "* %7u sectors/track\n", $dims->{s_t}; $str .= sprintf "* %7u tracks/cylinder\n", $dims->{t_c}; $str .= sprintf "* %7u sectors/cylinder\n", $dims->{s_c}; $str .= sprintf "* %7u cylinders\n", $dims->{c}; $str .= sprintf "* %7u accessible cylinders\n", $dims->{ac}; $str .= <$tmpfile"); print TMPFILE $str; close(TMPFILE); cmd "fmthard -s $tmpfile /dev/rdsk/$_[0]s0"; } sub round($$) { $_[0] = int $_[0]; return $_[0] - ($_[0] % $_[1]); } sub translate_partition_map($$$) { my $a_dims = shift; my $a_pmap = shift; my $b_dims = shift; my @b_pmap = (); my $a_s = $a_dims->{ac} * $a_dims->{s_c}; my $b_s = $b_dims->{ac} * $b_dims->{s_c}; # copy pmap foreach(@$a_pmap) { push @b_pmap, [@$_]; } # scale map { $_->[3] = $_->[3] * $b_s / $a_s; $_->[5] = $_->[5] * $b_s / $a_s; } @b_pmap; # fit to cylinder boundaries map { $_->[3] = round($_->[3], $b_dims->{s_c}); $_->[5] = round($_->[5]+1, $b_dims->{s_c}) - 1; } @b_pmap; # count map { $_->[4] = $_->[5] - $_->[3] + 1; } @b_pmap; return \@b_pmap; } ################# MAIN ################# # arguments my $conffile = shift @ARGV or die "usage: $0 config-file\n"; my $reverse=0; if($conffile eq '-r') { $reverse=1; $conffile = shift @ARGV or die "usage: $0 config-file\n"; } # read config my $conf = read_conf($conffile, ['disk', 'mirror']); my $disk = $conf->{disk}; my $mirror = $conf->{mirror}; # -r ($disk,$mirror) = ($mirror,$disk) if $reverse; # check host if(defined $conf->{host}) { my $host=`uname -n`; chomp $host; $host =~ s/\..*$//; die "ERROR: $0 can only be used on $conf->{host}.\n" unless $host eq $conf->{host}; } # check that 'mirror' is not mounted my $mirrordisk=`mount | grep $mirror`; die "ERROR: the mirror-disk $mirror seems to be mounted!\n" if $mirrordisk !~ /^\s*$/; # check that 'disk' is the mounted root disk. my $rootdisk=`mount -V /`; $rootdisk=~m|/dev/dsk/(\w*)|; $rootdisk=$1; die "ERROR: / isn't mounted from $disk.\n" unless $rootdisk =~ /$disk/; # make temporary mount point (make early so that we catch an error early) my $tmp_mnt = '/root_backup'; rmdir $tmp_mnt; mkdir $tmp_mnt, 755 or die "ERROR: can't make $tmp_mnt directory!\n"; # find partitions my $slices = disk_slices($disk); # is there / (on slice 0)? $slices->{'/'} or die "ERROR: couldn't find root partition!\n"; $slices->{'/'} =~ /.*s0/ or die "ERROR: root partition not on slice 0!\n"; # translate and write partition map my ($d_dims, $d_pmap) = read_partition_map($disk); my ($m_dims, $m_pmap) = read_partition_map($mirror); $m_pmap = translate_partition_map($d_dims, $d_pmap, $m_dims); write_partition_map($mirror, $m_dims, $m_pmap); foreach(sort keys (%$slices)) { my $dir = $_; my $d = $slices->{$dir}; $d =~ /s(.)$/; my $s = $1; # slice my $m = "${mirror}s$s"; if($DEBUG) { print "\n"; } # format cmd "newfs /dev/rdsk/$m"; # mount cmd "mount /dev/dsk/$m $tmp_mnt" or die "ERROR: couldn't mount /dev/dsk/$m.\n"; # copy if(defined $conf->{fastfs}) { cmd "$conf->{fastfs} $tmp_mnt fast"; } cd "$tmp_mnt"; cmd "(ufsdump 0f - $dir | ufsrestore rf -)"; cmd "rm restoresymtable"; # if it's / then we need to modify some things... if($dir eq '/') { cd "etc"; # modify vfstab cmd "mv vfstab vfstab.DISK0"; sys "sed -e 's/${disk}s/${mirror}s/g' vfstab.DISK0 >vfstab 2>/dev/null"; cd ".."; # make it bootable my $bootblk; if(defined $conf->{bootblk}) { $bootblk = $conf->{bootblk}; } else { my $platform = `uname -i`; chomp $platform; $bootblk = "/usr/platform/$platform/lib/fs/ufs/bootblk"; } cmd "installboot $bootblk /dev/rdsk/$m"; } cd "/"; # umount cmd "umount $tmp_mnt"; sleep 1; } # remove temp mount point cmd "rmdir $tmp_mnt"; if($cmd_error) { print "$0:\n"; print "WARNING: one of the command did not exit with exit-state 0 (output follows).\n\n"; print $cmd_out; } __END__ =head1 NAME mirror_root - make a bootable mirror copy of the root disk on another disk =head1 SYNOPSIS mirror_root [B<-r>] I =head1 DESCRIPTION mirror_root makes a bootable mirror copy of the root disk on another disk. It uses a external configuration file to be specified with I and can adapt geometrical differences of the two disks. To make a mirror copy, it will make the following: =over 4 =item * Read the partition map of the root disk using prtvtoc(1M). =item * Read the geometry of the mirror disk and, if needed, adapt the partition map of the root disk by scaling each partition and fitting the them to cylinder boundaries. =item * Write the partition map of the mirror disk using fmthard(1M). =item * Format the mirror disk using newfs(1M). =item * For each slice: =over 4 =item - Mount the slice of the mirror disk on F =item - Execute fastfs(1) on F to speedup the copying of data (if configured so in the config file). =item - Copy the data using ufsdump(1M) and ufsrestore(1M) =item - If it is the root (F) slice, modify F and execute installboot(1M) to make the mirror disk bootable. =item - Umount F =back =back =head1 OPTIONS -r copy from mirror back to disk instead of from disk to mirror =head1 CONFIGURATION The configuration file contains 'parameter = value' pairs (space/tabs are not fixed) and may contain empty lines or lines beginning with a '#', that denote a comment line. The following parameters can be set: =over 10 =item B Host-name of the machine this configuration is for. If defined, mirror_root will check if it is the correct host before performing the mirroring. =item B Disk-name of the root disk to be backed up. The name follows the syntax 'cNtNdN' where the N are numbers. =item B Disk-name of the mirror disk, same syntax as for B. =item B Absolute path of fastfs(1). If not defined, fastfs won't be executed. =item B Absolute path of the bootblock to be passed to installboot(1M). If not defined, C<"/usr/platform/".`uname -i`."/lib/fs/ufs/bootblk"> will be taken. =head1 EXAMPLE host = bigbang fastfs = /home/support/data/tools/system/mirror_root/fastfs disk = c0t0d0 mirror = c0t1d0 =head1 NOTES This is a rewrite of the backup_root script of Christoph Wicki (the original was in sh and the fitting of partition maps for non-exactly-matching disks was added). To boot from the mirrored disk, type 'C' at the OK prompt (press Stop-A). =head1 SEE ALSO fastfs: ftp://www.wins.uva.nl/pub/solaris/fastfs.c.gz =head1 COPYRIGHT Copyright (c) 2000 ETH Zurich, All rights reserved. =head1 LICENSE This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA =head1 AUTHOR David Schweikert , Christoph Wicki