Thursday, 11 March 2010

Booting with /etc in a separate Partition

A friend of mine(Tejas Surve) had this question. "Is it possible to boot a linux system with having /etc in a separate partition?" He buzzed about it. A friend of his replied that it can't be done and he even blogged the reasons for it. I chose to disagree and found a solution, after all the beauty of opensource is that it can be bent and moulded to do what you want, the way you want.

So how is it done? This requires the use of initramfs or initrd. Most of the Ubuntu users may be using initrd by default. You can verify this by checking your menu.lst and looking for the line "initrd="

Initramfs or initrd are very commonly used with small kernels which do not have al the modules preloaded so that they can be loaded before additional tasks can be performed. This sometimes also includes loading the filesystems modules for mounting partitions. At this point the root (/) of the system is residing in memory and hence they do not need the hard drive's / or /etc for anything. The kernel has booted and hence it passes control to the init script on the initramfs which it loaded during booting and this script does the additional admin work that we need to do before passing control over to the real init which lies in /sbin/init on our Hard Drive, which in turn would require the /etc/inittab to function. This is how many systems without HardDrive boot off a network drive.

With the theory behind this done, let me explain how I did it. As most of you know, I use Slackware-current and my choice of kernel has always been the huge-smp flavour. For those who do not know, the huge-smp kernel is a kernel compiled with all or most of the modules as built-in and not as a module. This causes the size of the kernel to be huge(hence the name). Hence I usually do not need an initrd/initramfs. Just in case you do not have a huge-smp kernel, you can get it from http://ftp.ua.freebsd.org/pub/slackware/slackware-current/slackware/a/kernel-huge-smp-2.6.33_smp-i686-2.txz This is a slackware archive but you can extract with tar xvf filename command. Copy the vmlinuz.* file to your boot and use it accordingly.

Although I could use the mkinitrd to create my intramfs image, I chose not to and used a guide from http://jootamam.net/howto-initramfs-image.htm with some modifications.

Creating the folder structure

As discussed in the blog link above I created the folder structure, but I chose to create a /dev folder as well.


$ mkdir -p work/initramfs/{bin,sbin,dev,etc,proc,sys,newroot}
$ cd work
$ touch initramfs/etc/mdev.conf

Installing Busybox

We need a shell to do anything, hence we can install busybox for this.

$ wget http://jootamam.net/initramfs-files/busybox-1.10.1-static.bz2
$ bunzip2 busybox-1.10.1-static.bz2
$ mv busybox-1.10.1-static initramfs/bin/busybox
$ chmod +x initramfs/bin/busybox
$ ln -s busybox initramfs/bin/sh

The /init script

The real magic is played by the init script. It will first mount the root and etc partition and then switch to the real root and pass on control to the real init script located at /sbin/init on our Hard Drive

Let us setup the script by running the following commands

$ touch initramfs/init
$ chmod +x initramfs/init


The init script which I created is different from the init script given in the blog above, So I will paste my copy here with some explanations:


#!/bin/sh
busybox --install -s
#Mount things needed by this script
mount -t proc none /proc
mount -n devtmpfs -t devtmpfs /dev && mkdir /dev/pts
mount -t devpts devpts /dev/pts
mount -t sysfs sysfs /sys

echo /sbin/mdev > /proc/sys/kernel/hotplug

#Function for parsing command line options with "=" in them
# get_opt("init=/sbin/init") will return "/sbin/init"
get_opt() {
echo "$@" | cut -d "=" -f 2
}


#Defaults
init="/sbin/init"
root="/dev/hda1"

#Process command line options
for i in $(cat /proc/cmdline); do
case $i in
root\=*)
root=$(get_opt $i)
;;
init\=*)
init=$(get_opt $i)
;;
etc\=*)
etc=$(get_opt $i)
;;
home\=*)
home=$(get_opt $i)
;;
esac
done

#Mount the root device
if [ -n "${root}" ]; then
mount "${root}" /newroot -o ro
echo mounted real_root
fi
if [ -n "${etc}" ]; then
mount "${etc}" /newroot/etc
echo mounted etc
fi
if [ -n "${home}" ]; then
mount "${home}" /newroot/home
echo mounted home
fi

#Check if $init exists and is executable
if [[ -x "/newroot/${init}" ]] ; then
#Unmount all other mounts so that the ram used by
#the initramfs can be cleared after switch_root
umount /sys /proc

#Switch to the new root and execute init
exec switch_root /newroot "${init}"
fi

#This will only be run if the exec above failed
echo "Failed to switch_root, dropping to a shell"
exec sh


If you compare this script with the original script from the blog you will notice many differences.
I haven't used mdev at all. Instead I mount /dev as devtmpfs.
You will notice some additional cases in the for loop for parsing command lines
You will notice the section where I'm mounting the additional partitions, if they have been defined.

Also note that I have set the default root as /dev/hda1 although my root is on /dev/sda1. This is intended to demonstrate the variable being replaced when it is passed at the boot command.

Creating the initramfs.igz file
Since I was editting the init file a lot and rebuilding the initramfs frequently, I decided to make a script instead. I called it geninitramfs.sh

The script is as follows:

#!/bin/bash
cd initramfs
find . | cpio -H newc -o > ../initramfs.cpio
cd ..
cat initramfs.cpio | gzip > initramfs.igz


Once you run this script from the work folder it will create a file initramfs.igz. Move/copy it to the /boot folder

$ mv initramfs.igz /boot/

You could have added this line to the geninitrams.sh script, but I chose not to.

Now add a new bootloader entry for this. I use LILO, so my entry in lilo.conf was as follows:


image = /boot/vmlinuz
initrd = /boot/initramfs.igz
root = /dev/sda1
label = Testopia
append = "home=/dev/sda6 etc=/dev/sda6 root=/dev/sda1"
read-only

If anyone uses grub and is following my post paste the grub entry and I'll add it here.

With this done I simple rerun the lilo command to install the changes.

Preparing the new /etc

To demo this, I need /etc on a different partition. So I moved my /etc contents to /home as it is a separate partition on my system. I used the following command to do this in order to preserve the permissions


$ cd /etc && tar -cf – . | (cd /home && tar -xpvf -)

After this I moved my /etc to /etc.old to remove the real /etc completely and made a blank directory called /etc


$ mv /etc /etc.old
$ mkdir /etc

With this done I rebooted the PC and selected Testopia as my boot choice from the Lilo prompt. The system booted completely with the /etc in a different partition.

Note: I did not have to touch my fstab for this and the newly mounted /etc wont be seen by the mount output.

1 comment:

Unknown said...

These parameters would be a good thing to be implemented in the oficial kernel parameters. How about contribute it?