09 January 2011

Disk Cloning in Linux

A while back, I wrote up how to clone a disk in Solaris and in FreeBSD,
yet somehow managed to overlook writing up how to do so in Linux.
This write up is to rectify just that, how to clone a disk, in this case,
the root disk, in Linux.  The following are our setup details:
        HOST:           tux
        PROMPT:         tux [0]
        OS:             CentOS 5.4 Linux
        MASTER DISK:    sda     current root disk
                                grub disk hd0
                                bios disk 1
        ALTERNATE DISK: sdb     soon to be clone disk
                                grub disk hd1
                                bios disk 2 
Optimally, your clone disk will be the same size and geometry as your
master disk.  In the examples that follow, this is not the case, wherein
the clone disk is 1 cylinder (1 MB) larger than the master.  This isn't
necessarily an issue.  The partition layout created mirrors that of the
master disk.  The important thing to note is that to follow the details
below explicitly, your clone disk needs to have at least enough capacity
to account for the partitions on the master disk.  An understanding of
disk layout, geometry, and partitioning schemes would be useful.

With the disclaimer out of the way, onto the details.  Of note,
'alternate' and 'clone' are used interchangeably in reference to 'sdb',
whereas 'master' references 'sda'.  To start, use 'fdisk' to verify the
current partition tables of both 'sda' and 'sdb'.  This is basically to
verify our size constraints:
        tux [0] /sbin/fdisk -l /dev/sda 

        Disk /dev/sda: 11.0 GB, 11004805120 bytes
        255 heads, 63 sectors/track, 1337 cylinders
        Units = cylinders of 16065 * 512 = 8225280 bytes

           Device Boot      Start         End      Blocks   Id  System
        /dev/sda1   *           1         945     7590681   83  Linux
        /dev/sda2             946        1206     2096482+  83  Linux
        /dev/sda3            1207        1337     1052257+  82  Linux swap / Solaris
        tux [0] /sbin/fdisk -l /dev/sdb

        Disk /dev/sdb: 11.0 GB, 11005853696 bytes
        255 heads, 63 sectors/track, 1338 cylinders
        Units = cylinders of 16065 * 512 = 8225280 bytes

           Device Boot      Start         End      Blocks   Id  System
Since 'sdb' will support the size requirements from 'sda', use 'sfdisk'
to dump the partition table of 'sda' and apply it to 'sdb'.  Verify the
new partition table on 'sdb' with another 'fdisk' command:
        tux [0] /sbin/sfdisk -d /dev/sda | /sbin/sfdisk /dev/sdb
        Checking that no-one is using this disk right now ...
        OK

        Disk /dev/sdb: 1338 cylinders, 255 heads, 63 sectors/track
        Old situation:
        Units = cylinders of 8225280 bytes, blocks of 1024 bytes, counting from 0

           Device Boot Start     End   #cyls    #blocks   Id  System
        /dev/sdb1          0       -       0          0    0  Empty
        /dev/sdb2          0       -       0          0    0  Empty
        /dev/sdb3          0       -       0          0    0  Empty
        /dev/sdb4          0       -       0          0    0  Empty
        New situation:
        Units = sectors of 512 bytes, counting from 0

           Device Boot    Start       End   #sectors  Id  System
        /dev/sdb1   *        63  15181424   15181362  83  Linux
        /dev/sdb2      15181425  19374389    4192965  83  Linux
        /dev/sdb3      19374390  21478904    2104515  82  Linux swap / Solaris
        /dev/sdb4             0         -          0   0  Empty
        Successfully wrote the new partition table

        Re-reading the partition table ...

        If you created or changed a DOS partition, /dev/foo7, say, then use dd(1)
        to zero the first 512 bytes:  dd if=/dev/zero of=/dev/foo7 bs=512 count=1
        (See fdisk(8).)
        tux [0] /sbin/fdisk -l /dev/sdb

        Disk /dev/sdb: 11.0 GB, 11005853696 bytes
        255 heads, 63 sectors/track, 1338 cylinders
        Units = cylinders of 16065 * 512 = 8225280 bytes

           Device Boot      Start         End      Blocks   Id  System
        /dev/sdb1   *           1         945     7590681   83  Linux
        /dev/sdb2             946        1206     2096482+  83  Linux
        /dev/sdb3            1207        1337     1052257+  82  Linux swap / Solaris
With the new partition table in place on the alternate disk, we can set
up empty filesystems on 'sdb1' and 'sdb2' using 'mkfs':
        tux [0] for i in 1 2 ; do echo "*** mkfs -V -t ext3 /dev/sdb${i} ***" ;
        >        /sbin/mkfs -V -t ext3 /dev/sdb${i} ; done
        *** mkfs -V -t ext3 /dev/sdb1 ***
        mkfs (util-linux 2.13-pre7)
        mkfs.ext3 /dev/sdb1
        mke2fs 1.39 (29-May-2006)
        Filesystem label=
        OS type: Linux
        Block size=4096 (log=2)
        Fragment size=4096 (log=2)
        950272 inodes, 1897670 blocks
        94883 blocks (5.00%) reserved for the super user
        First data block=0
        Maximum filesystem blocks=1946157056
        58 block groups
        32768 blocks per group, 32768 fragments per group
        16384 inodes per group
        Superblock backups stored on blocks:
                32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632

        Writing inode tables: done
        Creating journal (32768 blocks): done
        Writing superblocks and filesystem accounting information: done

        This filesystem will be automatically checked every 25 mounts or
        180 days, whichever comes first.  Use tune2fs -c or -i to override.
        *** mkfs -V -t ext3 /dev/sdb2 ***
        mkfs (util-linux 2.13-pre7)
        mkfs.ext3 /dev/sdb2
        mke2fs 1.39 (29-May-2006)
        Filesystem label=
        OS type: Linux
        Block size=4096 (log=2)
        Fragment size=4096 (log=2)
        262144 inodes, 524120 blocks
        26206 blocks (5.00%) reserved for the super user
        First data block=0 
        Maximum filesystem blocks=536870912
        16 block groups   
        32768 blocks per group, 32768 fragments per group 
        16384 inodes per group
        Superblock backups stored on blocks: 
                32768, 98304, 163840, 229376, 294912   

        Writing inode tables: done
        Creating journal (8192 blocks): done
        Writing superblocks and filesystem accounting information: done

        This filesystem will be automatically checked every 22 mounts or
        180 days, whichever comes first.  Use tune2fs -c or -i to override.
Though unnecessary, rather than using device entries to manage the
clone disk, we can use volume labels instead.  Turning to 'tune2fs',
we get the volume labels for 'sda1' and 'sda2' to mirror their format
for our clone disk in a separate set of 'tune2fs' commands:
        tux [0] ( /sbin/tune2fs -l /dev/sda1 ; /sbin/tune2fs -l /dev/sda2 ) | /bin/grep name
        Filesystem volume name:   /1
        Filesystem volume name:   /var1
        tux [0] ( /sbin/tune2fs -l /dev/sdb1 ; /sbin/tune2fs -l /dev/sdb2 ) | /bin/grep name
        Filesystem volume name:   <none>
        Filesystem volume name:   <none>
        tux [0] /sbin/tune2fs -L /2 /dev/sdb1
        tune2fs 1.39 (29-May-2006)
        tux [0] /sbin/tune2fs -L /var2 /dev/sdb2 
        tune2fs 1.39 (29-May-2006)
        tux [0] ( /sbin/tune2fs -l /dev/sdb1 ; /sbin/tune2fs -l /dev/sdb2 ) | /bin/grep name
        Filesystem volume name:   /2
        Filesystem volume name:   /var2
Below, we check what the system says about the swap volume on 'sda3'
via 'swapon' and /etc/fstab:
        tux [0] /sbin/swapon -s
        Filename                                Type            Size    Used    Priority
        /dev/sda3                               partition       1052248 0       -1
        tux [0] /bin/grep swap /etc/fstab
        LABEL=SWAP-sda3         swap                    swap    defaults        0 0
Since swap isn't a filesystem, we can't use 'tune2fs' to set a volume
label to 'sdb3'.  As we still need to create the swap area on 'sdb3',
we can tell 'mkswap' to apply a label while setting up the swap area:
        tux [0] /sbin/mkswap -L SWAP-sdb3 /dev/sdb3
        Setting up swapspace version 1, size = 1077506 kB
        LABEL=SWAP-sdb3, no uuid
We now proceed to actually mirroring the data content of our filesystems
on our master disk (sda).  Below, we test for a mount point (/mnt) and if
necessary create it, mount 'sdb1' to '/mnt', and use 'dump' and 'restore'
to copy the data from 'sda1' (/) to 'sdb1' (/mnt).  Once the dump /
restore has completed, we remove 'restoresymtable' as it won't be needed:
        tux [0] [ ! -d /mnt ] && /bin/mkdir /mnt
        tux [1] /bin/mount /dev/sdb1 /mnt
        tux [0] cd /mnt ; /sbin/dump 0uf - / | /sbin/restore rf -
          DUMP: Date of this level 0 dump: Sun Jan  9 23:08:52 2011
          DUMP: Dumping /dev/sda1 (/) to standard output
          DUMP: Label: /1
          DUMP: Writing 10 Kilobyte records
          DUMP: mapping (Pass I) [regular files]
          DUMP: mapping (Pass II) [directories]
          DUMP: estimated 2040405 blocks.
          DUMP: Volume 1 started with block 1 at: Sun Jan  9 23:08:54 2011
          DUMP: dumping (Pass III) [directories]
          DUMP: dumping (Pass IV) [regular files]
        /sbin/restore: ./lost+found: File exists
        ./tmp/rstdir1294632532: (inode 1276706) not found on tape
        ./tmp/rstmode1294632532: (inode 1276711) not found on tape
          DUMP: 61.29% done at 4168 kB/s, finished in 0:03
          DUMP: Volume 1 completed at: Sun Jan  9 23:18:43 2011
          DUMP: Volume 1 2372690 blocks (2317.08MB)
          DUMP: Volume 1 took 0:09:49
          DUMP: Volume 1 transfer rate: 4028 kB/s
          DUMP: 2372690 blocks (2317.08MB)
          DUMP: finished in 589 seconds, throughput 4028 kBytes/sec
          DUMP: Date of this level 0 dump: Sun Jan  9 23:08:52 2011
          DUMP: Date this dump completed:  Sun Jan  9 23:18:43 2011
          DUMP: Average transfer rate: 4028 kB/s
          DUMP: DUMP IS DONE
        tux [0] /bin/rm restoresymtable
With 'sdb1' complete, we move on to 'sdb2', using the same procedure as
above to copy from 'sda2' (/var) to 'sdb2' (/mnt/var):
        tux [0] [ ! -d /mnt/var ] && /bin/mkdir /mnt/var
        tux [1] /bin/mount /dev/sdb2 /mnt/var
        tux [0] cd /mnt/var ; /sbin/dump 0uf - /var | /sbin/restore rf -
          DUMP: Date of this level 0 dump: Sun Jan  9 23:33:17 2011
          DUMP: Dumping /dev/sda2 (/var) to standard output
          DUMP: Label: /var1
          DUMP: Writing 10 Kilobyte records
          DUMP: mapping (Pass I) [regular files]
          DUMP: mapping (Pass II) [directories]
          DUMP: estimated 37035 blocks.
          DUMP: Volume 1 started with block 1 at: Sun Jan  9 23:33:18 2011
          DUMP: dumping (Pass III) [directories]
          DUMP: dumping (Pass IV) [regular files]
        /sbin/restore: ./lost+found: File exists
          DUMP: Volume 1 completed at: Sun Jan  9 23:33:30 2011
          DUMP: Volume 1 40990 blocks (40.03MB)
          DUMP: Volume 1 took 0:00:12
          DUMP: Volume 1 transfer rate: 3415 kB/s
          DUMP: 40990 blocks (40.03MB)
          DUMP: finished in 12 seconds, throughput 3415 kBytes/sec
          DUMP: Date of this level 0 dump: Sun Jan  9 23:33:17 2011
          DUMP: Date this dump completed:  Sun Jan  9 23:33:30 2011
          DUMP: Average transfer rate: 3415 kB/s
          DUMP: DUMP IS DONE
        tux [0] /bin/rm restoresymtable
        tux [0] cd /
Now that our filesystems have been cloned, we need to update 'fstab',
'grub.conf', and potentially 'device.map' on our clone disk.  For 'fstab',
we update from this (the values of our master disk (sda)):
        tux [0] /bin/cat /mnt/etc/fstab
        LABEL=/1                /                       ext3    defaults        1 1
        LABEL=/var1             /var                    ext3    defaults        1 2
        tmpfs                   /dev/shm                tmpfs   defaults        0 0
        devpts                  /dev/pts                devpts  gid=5,mode=620  0 0
        sysfs                   /sys                    sysfs   defaults        0 0
        proc                    /proc                   proc    defaults        0 0
        LABEL=SWAP-sda3         swap                    swap    defaults        0 0
to this (using the values of our clone disk (sdb)):
        tux [0] /bin/cat /mnt/etc/fstab
        LABEL=/2                /                       ext3    defaults        1 1
        LABEL=/var2             /var                    ext3    defaults        1 2
        tmpfs                   /dev/shm                tmpfs   defaults        0 0
        devpts                  /dev/pts                devpts  gid=5,mode=620  0 0
        sysfs                   /sys                    sysfs   defaults        0 0
        proc                    /proc                   proc    defaults        0 0
        LABEL=SWAP-sdb3         swap                    swap    defaults        0 0
You will likely need to update 'device.map' on your clone disk, as
otherwise, 'grub-install' may kick back the following error:
        tux [0] /sbin/grub-install --root-directory=/mnt /dev/sdb
        /dev/sdb does not have any corresponding BIOS drive.
So using your favorite editor (vi?), update 'device.map' from this:
        tux [0] /bin/cat /mnt/boot/grub/device.map
        # this device map was generated by anaconda
        (hd0)     /dev/sda
to this to account for our alternate disk as well:
        tux [0] /bin/cat /mnt/boot/grub/device.map
        # this device map was generated by anaconda 
        (hd0)     /dev/sda
        (hd1)     /dev/sdb
A subsequent run of 'grub-install' now completes without issue, writing
the stage1, stage1_5, and stage2 files to '/mnt/boot/grub' and installing
'GRUB' to the master boot record (MBR) on 'sdb':
        tux [0] /sbin/grub-install --root-directory=/mnt /dev/sdb
        Installation finished. No error reported.
        This is the contents of the device map /mnt/boot/grub/device.map.
        Check if this is correct or not. If any of the lines is incorrect,
        fix it and re-run the script `grub-install'.

        # this device map was generated by anaconda
        (hd0)     /dev/sda
        (hd1)     /dev/sdb
The last file to update is 'grub.conf' on 'sdb' to tell 'GRUB' about our
alternate disk.  You should also update 'grub.conf' on 'sda' as well.
On our clone disk (sdb) below, we update from:
        tux [0] grep -v ^# /mnt/boot/grub/grub.conf
        default=0
        timeout=5
        splashimage=(hd0,0)/boot/grub/splash.xpm.gz
        title CentOS (2.6.18-164.el5)
                root (hd0,0)
                kernel /boot/vmlinuz-2.6.18-164.el5 ro root=LABEL=/1
                initrd /boot/initrd-2.6.18-164.el5.img
to the following, setting the default boot entry to our clone disk and
adding an entry to boot into the Linux install now cloned to 'sdb':
        tux [0] grep -v ^# /mnt/boot/grub/grub.conf
        default=1
        timeout=5
        splashimage=(hd1,0)/boot/grub/splash.xpm.gz
        title CentOS (2.6.18-164.el5)
                root (hd0,0)
                kernel /boot/vmlinuz-2.6.18-164.el5 ro root=LABEL=/1
                initrd /boot/initrd-2.6.18-164.el5.img
        title CentOS Mirror (2.6.18-164.el5)
                root (hd1,0)
                kernel /boot/vmlinuz-2.6.18-164.el5 ro root=LABEL=/2
                initrd /boot/initrd-2.6.18-164.el5.img
Of note, updating 'grub.conf' on the master disk (sda) would only
include adding an entry for our clone disk, leaving the default entry to
the master disk.  With our work complete, the only things left are to
unmount our clone disk filesystems, check for any FS errors via 'fsck',
and reboot:
        tux [0] /bin/umount /mnt/var
        tux [0] /bin/umount /mnt
        tux [0] /sbin/fsck -n /dev/sdb2
        fsck 1.39 (29-May-2006)
        e2fsck 1.39 (29-May-2006)
        /var2: clean, 991/262144 files, 26347/524120 blocks
        tux [0] /sbin/fsck -n /dev/sdb1
        fsck 1.39 (29-May-2006)
        e2fsck 1.39 (29-May-2006)
        /2: clean, 71367/950272 files, 562330/1897670 blocks
        tux [0] /sbin/reboot
After the host has reset, tell the BIOS to boot from disk 2 (sdb).
Once Linux has finished booting, log in and verify that we are indeed
booted from our clone disk:
        tux [0] /bin/cat /proc/cmdline
        ro root=LABEL=/2
        tux [0] /bin/df -h
        Filesystem            Size  Used Avail Use% Mounted on
        /dev/sdb1             7.2G  2.1G  4.8G  31% /
        /dev/sdb2             2.0G   71M  1.8G   4% /var
        tmpfs                 506M     0  506M   0% /dev/shm
        tux [0] /sbin/swapon -s
        Filename                                Type            Size    Used    Priority
        /dev/sdb3                               partition       1052248 0       -1

see also:
    Creating an MD Root Mirror in Linux
    Disk Cloning in Solaris
    Disk Cloning in FreeBSD
    Breaking and Syncing an MD Root Mirror