Some time ago I inherited an old Laptop, an OmniBook XE3GC. I had installed FreeBSD on it quite some time ago and tried connecting using different means. First I tried a USB2USB cable. This somewhat worked but unfortunately it was too unreliable. After some time the connection would drop and I could not re-establish the connection without rebooting both ends. Next I tried a parallel port laplink cable. Unfortunately the parallel port on the laptop is not interrupt driven making this non-functional. So I reverted to the last resort: SLIP while this worked ok when connecting to my K6 gateway it wouldn’t work so well with the new Soekris 5501 that replaced the K6 recently. The problem here was that the connection would only work on the baudrate set in the console although conlock was not enabled.
Since I recently moved all my stuff to the living room I had a spare WLAN stick and right away stuck that one in the laptops USB port. Although the laptop only has USB1.1 it would still be much better than SLIP’s 12kb/s max. Now I could do some real work and install all the good stuff. Unfortunately I hit another wall soon. The laptop only has a 5GB harddisk so was soon running out of space. So I started with the usualy space saving measures. I had already NFS mounted my gateway for /usr/src and /usr/ports. So cleaned up the ports builddirectory and deleted /usr/obj. Then I reverted to make all portbuild happen on the NFS share. Nonetheless it became clear that in order to be able to actually do anything with the machine after install one would need some space to work with. So I looked at compression options. The first thing I tried was a fusefs-gunzip but that didn’t work so I searched some more and found geom_uzip. This is somewhat similar to squashfs but works a little different. Instead of providing a complete filesystem it takes a filesystem image and compresses it block by block. The blocksize can be specified, default is 16k I chose 64k for better compression ratio. Here is the script I use for setting up the empty image:
#!/bin/sh
# create empty diskimage and mount it
SIZE=7G
FILE=/home/usr_local
rm $FILE && \
truncate -s $SIZE $FILE && \
MDDEV=$(mdconfig -a -t vnode -f $FILE)
if [ "$MDDEV" ]
then newfs -b 65536 -m 0 -n -U -o time /dev/$MDDEV
mount /dev/$MDDEV /mnt
fi
The truncate command creates a sparse file. This is much faster than doing a “dd if=/dev/zero of=/home/usr_local” as the 7GB of zeroes aren’t actually written. (that way you can also create multi petabyte volumes requiring only small amounts of diskspace and impress your friends with it) Then I turn the file into a blockdevice (similar to losetup under Linux) and create a filesystem on it. I chose the same blocksize as I will use later on for uzip, select no reserved space as the volume will be readonly anyways, for the same reason I also forego snapshots (-n) and I set the optimization to time. This will cause the filesystem not to use “tails”, that is if a datablock doesn’t fill the 64k completely use the remaining space for other files (by default 2k chunks are used). The idea is that the zeroes will be compressed “away” anyways so I rather have the speed advantage (and zeroes decompress really really fast). I used the default UFS2 because I don’t know any better and I enabled softupdates (-U) because write performance sucked without it. Maybe I’ll do some comparison later on to find out the optimal combination of UFS1/2 with or without softupdates. I could imagine that softupdates writes some kind of journal data that impacts compression ratio and UFS2 adds some additional structures to increase failure resilience.
Then I replicate the content of /usr/local from the laptop into /mnt. After that I run:
#!/bin/sh
# umount imagefile and create uzip file
FILE=/home/usr_local
UZIP=/export/usr_local.uzip
MDDEV=$(mdconfig -l -v|grep $FILE|awk '{print $1}')
MNT=$(mount|grep $MDDEV|awk '{print $3}')
umount $MNT &&\
mdconfig -d -u $MDDEV
mkuzip -v -o $UZIP -s 65536 $FILE
After that I can delete /usr/local on the laptop and put the following in /etc/rc.conf:
mdconfig_md0="-t vnode -f /usr_local.uzip"
and this in /etc/fstab:
/dev/md0.uzip /uzip ufs ro,noauto 0 0
This causes rc to automatically activate /dev/md0, load geom_uzip and that automatically creates /dev/md0.uzip which is then in turn mounted. It took me a while to figure out that FreeBSD will invariantly attempt a “mount /dev/md0.uzip” after creating it and that I just have to put in the above fstab entry to make it work.
After that the only remaining piece was to unionfs mount /uzip under the existing, writeable /usr/local. But that again was a single line in the fstab:
/uzip /usr/local unionfs rw,noatime,below,copymode=transparent 0 0
Since this is a laptop and I don’t want each access to a file cause a write so I also added the noatime option to this and the “/” filesystem.
And as a last comment I thought on how to replicate /usr/local as quick a possible and this what I came up with:
tar -C /usr/local -cf - .|buffer -S 1M -m 5M|lzop|buffer|nc gateway 1234
and on the gateway:
nc -l 1234|buffer -m 5M|lzop -d|tar -C /mnt -xvf -
The buffer commands are there to balance out speed variances caused by different filesizes. Many small files will cause tar to slow down compared to large files when looking at the bytes per second throughput. This applies to both ends. An additional buffer after lzop compression to ensure saturation of the the link even when we encounter lots of well compressable data.