Bittorrent block devices as "poor mans multicast" disk cloning

Reece Arnott <[email protected]>
Newsgroups gmane.org.user-groups.linux.dunedin.general
Message-ID <[email protected]>
Caveat: This has been proven in principle by cloning a single 100MB 
partition from one machine to another on an 'airgapped' network only. 
There may be issues (that I hope to have worked out tomorrow) expanding 
this to multiple partitions, specifically a Windows 7 install on 2 NTFS 
partitions, and there may be issues with doing this on the live Otago 
Uni network (I'll have to talk to the ITS networking and security teams 
in a couple of days).

I had a bit of time last week to work on a little project that I'd been 
meaning to do: get bittorrent based disk cloning via a USB Live RAM 
installation of Ubuntu (13.10). The main motivation for getting this 
working is that standard multicast techniques are 'contra-indicated' by 
the Otago Uni network (unregulated multicast traffic gets delivered to 
all machines on the LAN by definition and some machines *really* don't 
like multicast traffic and die a horrible death). If this doesn't make 
it onto the Uni network and stays just on a separate network then its 
only advantage over a multicast solution on the same separate network is 
the option to be a bit more ad-hoc as you can add new machines to the 
torrent swarm at any time (compared to multicasting where you get all 
your ducks in a row before you start).

First, the streamlined boot process:
=====================

This is kicked off by the following syslinux config option:

LABEL Reece
     Menu label ^0) Run Reece's Menu from 64-bit Ubuntu 13.10 Live terminal
     TEXT HELP
         home-rw contains .bash_profile login script to run /cdrom/rmenu 
which unmounts it
     ENDTEXT
     KERNEL /ubuntu/casper/vmlinuz.efi
#Copied from syslinux "Run Ubuntu 13.10 (64-bit)" and added casper 
option 'text' + home-rw loop filesystem in the root of the USB
     append  persistent file=/cdrom/ubuntu/preseed/ubuntu.seed 
boot=casper text initrd=/ubuntu/casper/initrd.lz ignore_uuid 
live-media-path=/ubuntu/casper locale=en_US.UTF-8 --

There is a 1MB loop filesystem in the root of the USB that is called 
home-rw that if it exists is mounted as /home. This contains one file of 
importance, the one line /home/linux/.bash_profile:
. /cdrom/rmenu

The USB is mounted as /cdrom so the rmenu mentioned is a script in the 
root of the cd that just copies a series of scripts from the USB and 
displays a menu so that the scripts can be easily changed.:
#!/bin/bash
#In this case the USB drive is mounted as /cdrom by the Ubuntu Live boot 
process as it is the same one the squashfs is on.
mntpoint=/cdrom
#This is where we copy the scripts off the USB stick to (assumed to be 
in the path)
pth=/usr/local/bin
sudo cp $mntpoint/scripts/* $pth
#Note that the r and u scripts must be run as in the same environment as 
the standard command line so add an alias
alias r='. r'
alias u='. u'
echo "Copied scripts from $mntpoint/scripts to $pth"
menu

The u script ("Unmount 1MB persistent home folder") is used to unmount 
the /home loop filesystem when it isn't needed:
#!/bin/bash
# Note for this to work correctly it needs to be called as '. u' rather 
than just 'u'
# The rmenu script sets up an alias for this reason.
cd /
sudo umount -l /home
echo Sleeping for 5 seconds after lazy unmount of persistent home loop 
file system
sleep 5
sudo mkdir /home/linux
sudo chown linux:linux /home/linux
cd ~

The r script ("Reset persistent home folder") is used to reset the 
persistent home folder to the pristine copy, also stored in the root of 
the USB:
u
sudo rm /cdrom/home-rw
sudo cp /cdrom/home-rw-copy /cdrom/home-rw

Now, the actual bittorrent stuff:
===================
Initially I thought about trying to mount the file system and create a 
standard torrent for all the files on the partition. This worked fine 
for files and folders but not for other types such as symlinks and FIFO 
pipes. When a Windows 7 NTFS partition is mounted, <JUNCTION> folders 
such as Application Data (linked to AppData) are shown as symlinks and 
some files under a users CryptnetUrlCache folder are shown as FIFO 
pipes. There is also the added disadvantage that NTFS formatted 
partitions contain ACLs for each file/folder and so they would also have 
to be recorded and applied separately.

Instead I went for the option of creating a torrent for the block device 
(so far only tested on a simple 100MB /dev/sda1). Most, if not all, 
torrent programs deal only with files and folders, not block devices, 
but I found a diff from 2003 that added the capability to BitTornado, a 
python bittorrent suite: 
http://osdir.com/ml/network.bit-torrent.general/2003-12/msg00356.html

In the case of python at least, the main difference between standard 
files and block devices is the way to get the size. From this I found a 
fork of bittornado on GitHub: https://github.com/effigies/BitTornado (a 
couple of commits have happened in the last few days so the exact link 
is 
https://github.com/effigies/BitTornado/tree/73abe68ac13bda31db5307db8c2d0ba9e58bbfac) 
and changed 2 files:

1)To create a torrent of a block device I had to change the BTTree.py file

BTTree.py changes, in context:
==================
under def __init__

if os.path.isfile(loc):
             self.size = os.path.getsize(loc)
         #This elif added by Reece
         elif S_ISBLK(os.stat(loc).st_mode):
             f = open(loc, 'rb');
             f.seek(0,2)
             self.size= f.tell()
             print("Block device detected. Size is %s" % self.size)
             f.close()
         # We'll need to know the size of all subfiles
         elif os.path.isdir(loc):
             for sub in sorted(os.listdir(self.loc)):
                 # Ignore .* (glob, not regex)
                 if sub[0] == '.':
                     continue
                 sloc = os.path.join(loc, sub)
                 spath = self.path + [sub]
                 try:
                     self.subs.append(BTTree(sloc, spath))

                 # Notify, but ignore entries that are neither
                 # files nor directories
                 except IOError as problem:
                     print problem

             # For bittorrent's purposes, size(dir) = size(subs)
             self.size = sum(sub.size for sub in self.subs)
         else:
             raise IOError("Entry is neither file nor directory: " + loc)


Also, just for cosmetic purposes:

     def addFileToInfos(self, infos):
         """Add file information and data hash to a sequence of Info
         structures"""
         with open(self.loc, 'rb') as fhandle:
             pos = 0L
             piece_length = 0
             for info in infos:
                 piece_length = max(piece_length, info.hasher.pieceLength)
                 info.add_file_info(self.size, self.path)

             while pos < self.size:
                 nbytes = min(piece_length, self.size - pos)
                 buf = fhandle.read(nbytes)
                 pos += nbytes
                 #Added by Reece so can tell where in the file we are as 
the ordinary complete percentage only updates after each file is completed
                 #Note that there are four tabs here so the standard % 
complete still shows
                 print("                Current file is %.1f%% complete. 
%s out of %s bytes\r" % ((float(pos)*100)/float(self.size),pos,self.size)),
                 for info in infos:
                     info.add_data(buf)


2) To allow for checking when running the bittorrent client I changed 
the BT1/Storage.py file:

Storage.py change in context:
==================
Under def __init__
                     if exists(file):
                         #Added by Reece
                         f=open(file,'rb')
                         f.seek(0,2)
                         l=f.tell()
                         f.close()
                         #Reece commented the below out
                         #l = getsize(file)
                         if l > length:
                             with open(file, 'rb+') as h:
                                 h.truncate(length)
                                 h.flush()
                             l = length

The actual scripts for bittorrenting:
======================
For the first master/seed machine, set it up as the tracker as well, so 
run b+e+g+h
For all but the first run b+g+h
(script names will change once I get it working so I can delete the 
non-used scripts and consolidate the working ones but I needed some 
names to put here)

------ b ----

# Copy the bittorrent folder with all the files from the USB drive to 
the /tmp folder
cp -R /cdrom/bittorrent /tmp

------ e -----
#Should have been able to get BitTornado tracker working but instead 
went with
# the one in the Ubuntu repos (downloaded by a 13.04 desktop, copied the 
.deb files from /var/cache/apt)
sudo dpkg -i /tmp/bittorrent/python-bittorrent_3.4.2-11.4ubuntu2_all.deb
sudo dpkg -i /tmp/bittorrent/bittorrent_3.4.2-11.4ubuntu2_all.deb

ipaddy=`ifconfig eth0 | grep "inet addr:" | cut -f 2 -d ":" | cut -f 1 
-d " "`

echo Creating torrent with tracker $ipaddy
cd /tmp/bittorrent/BitTornado-master/
sudo ./btmakemetafile.py http://$ipaddy:80/announce /dev/sda1 --target 
/tmp/currentimage.torrent

echo Starting Tracker on $ipaddy
sudo bttrack --bind $ipaddy --port 80 --dfile /tmp/dstate --logfile 
/tmp/tracker.log &

echo Copying torrent file to USB and RAM cache
sudo cp /tmp/currentimage.torrent /cdrom/bittorrent/currentimage.torrent
sudo cp /tmp/currentimage.torrent /tmp/bittorrent/currentimage.torrent
sudo rm /tmp/currentimage.torrent
sudo sync

------ g ------
# Unmount the USB so can take it to a different machine
sudo sync
sudo umount -l /cdrom
u

------ h ------
cd /tmp/bittorrent/BitTornado-master/
#For some reason the Python script looks for the icons in this folder 
rather than in the icons subfolder
# even though we aren't actually using any of them anyway
cp icons/* .
sudo ./btdownloadheadless.py /tmp/bittorrent/currentimage.torrent 
--saveas /dev/sda1


Other things I will be testing tomorrow:
========================
These are mainly specific to my context of cloning 2 NTFS partitions 
that are a Windows Sysprep'd install...
For the master/seed machine:
- Copy the Windows 7 Autounattend.xml file and $OEM$ additional files to 
the C: drive
- resize /dev/sda2 to minimum size using ntfsresize and fdisk script
- Copy sfdisk info and mbr to files for later restore (under 
/cdrom/bittorrent folder)
------------------------------------
For the rest:
- Restore sfdisk partitions + mbr from /tmp/bittorrent folder after 
wiping current partition table
------------------------------------
And the final thing to do for all machines:
- Resize /dev/sda2 to maximum size + shutdown

(For future-travellers looking at this on an archived forum, you will be 
able to find the followup email by putting in the current email title + 
followup into a search engine)

-- 
"Believing men would act in their own interest was not cynicism, it turned out, but sheerest optimism; in reality men do not meet so high a standard."
-- Harry Potter and the Methods of Rationality (Chapter 84)
http://hpmor.com/

Reece Arnott
Dunedin
New Zealand


_______________________________________________
DunLUG mailing list
[email protected]
http://lists.ethernal.org/listinfo/dunlug
DunLUG Wiki - http://dunlug.kallisti.net.nz/
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.