Saturday, September 29, 2012

My All-Purpose Init Script for initrd / initramfs Boot Images

Here I post my init script. It is used in my initrd/initramfs boot images for the following purposes.

  • Boot from a live CD / DVD
  • Boot from a read-only filesystem image, compressed in SquashFS
  • Copy the filesystem image to a RAM disk and run Linux entirely on memory
  • Boot Linux from a USB flash drive
  • Boot Linux from a local disk partition
  • Run a rescue shell without booting Linux

I have yet to implement network booting. Right now it is good enough for my current needs. It takes the following boot parameters.

  • root=
    Specifies the root partition to boot Linux from. For example, /dev/sda2.
  • label=
    Specifies the label of the device to boot from. For example, label=DEBIAN.
  • uuid=
    Specifies the UUID of the device to boot from. For example, uuid=dcfd6a0a-2a0f-4b3d-8a1a-5e7d642ebfbd
  • boot=
    Can be cdrom, loop, ram, usb or ata.
  • vmode=
    Specifies the screen resolution of the framebuffer video. For example, vmode=640x480
  • single
    Boot into the single-user mode.
  • nox
    Boot into the console mode in runlevel 2
#!/bin/dash

# Define a function to parse kernel command line options.
get_opt() {
  echo $@ | cut -d "=" -f 2-
}

# Define a function to load drivers.
loadmod() {
  for i in $@ ; do
    for j in $(grep $i /tmp/pcimodules.txt); do
      modprobe $j
    done
  done
}

# Define a function to load drivers for USB controllers
modusb() {
  lspci -n | grep "0c03: " &&
  modprobe -a ohci-hcd uhci-hcd ehci-hcd xhci-hcd
}

# Define a function to guess the partition type.
gpart() {
  for i in $(blkid | grep $1); do
    case $i in
      *\=*)
        eval $i
        ;;
      *)
        true
        ;;
    esac
  done
}

# Define a function for mounting the root partition.
mountr() {
  if [ $uuid ]; then
    [ $# = 1 ] && mount -r -U $uuid $1 || mount -r -U $uuid /mnt
  elif [ $label ]; then
    [ $# = 1 ] && mount -r -L $label $1 || mount -r -L $label /mnt
  else
    gpart $1
    case $TYPE in
      ext*)
        e2fsck -p $1
        [ $# = 2 ] && mount $1 $2 || mount $1 /mnt
        ;;
      jfs)
        jfs_fsck $1
        if [ $# = 2 ]; then
          mount -t jfs -o ro,iocharset=utf8 $1 $2
        else mount -t jfs -o ro,iocharset=utf8 $1 /mnt
        fi
        ;;
      vfat)
        if [ $# = 2 ]; then
          mount -t vfat -o ro,gid=100,dmask=2,fmask=113 $1 $2
        else mount -t vfat -o ro,gid=100,dmask=2,fmask=113 $1 /mnt
        fi
        ;;
      *)
        [ $# = 2 ] && mount -r $1 $2 || mount -r $1 /mnt
        ;;
    esac
  fi
}

# Define a function for creating a union filesystem
union() {
  mount -t tmpfs none /opt/tmp
  modinfo unionfs > /dev/null 2>&1 &&
  mount -t unionfs -o dirs=/opt/tmp=rw:/opt=ro none /mnt ||
  ( mkdir /opt/tmp/.change
    modprobe fuse
    unionfs-fuse -o allow_other,use_ino,suid,dev,nonempty,kernel_cache \
     -o cow,chroot=/opt,max_files=32768 /tmp/.change=RW:/=RO /mnt )
  mount --rbind /opt /mnt/opt
}

# Mount proc and sysfs.
mount -t proc none /proc
mount -t sysfs none /sys

# Populate /dev (Needs kernel >= 2.6.32)
mount -t devtmpfs none /dev
mkdir -m 755 /dev/pts
mount -t devpts -o gid=5,mode=620 none /dev/pts

# Find modules required to use available PCI hardware
mount -t tmpfs none /tmp
for f in $(lspci -n | awk '{print toupper($3)}' | sed -e 's/:/d0000/g')
  do grep $f /lib/modules/$(uname -r)/modules.alias \
    | awk '{print $3}' >> /tmp/pcimodules.txt
done
lspci -n | awk '$2 == "0c00:" {print "firewire-ohci"}' >> /tmp/modules.txt

# Find the root=, label=, uuid= and boot= values on kernel command line.
for i in $(cat /proc/cmdline); do
  case $i in
    root\=*)
        root=$(get_opt $i)
        case $root in
          /dev/cdr* | /dev/dvd* | /dev/sr* | /dev/scd*)
            boot=cdrom
            ;;
          0x200)
            root=/dev/fd0
            ;;
          label\=* | LABEL\=* | uuid\=* | UUID\=* )
            eval $(echo $root | tr 'A-Z' 'a-z')
            ;;
        esac
        ;;
    label\=* | uuid\=* | boot\=* | vmode\=* )
        eval $i
        ;;
    single)
        RUNLEVEL=single
        ;;
    nox)
        RUNLEVEL=2
        ;;
  esac
done

# Set default values
[ "$boot" ] || boot=ata
[ "$uuid$label$root" ] || root=/dev/sda6

# Activate framebuffer display devices.
grep -q i915 /tmp/pcimodules.txt ||
if [ $vmode -a $boot = cdrom ]; then
    modprobe uvesafb scroll=ywrap mode_option=$vmode-16
elif [ $vmode ]; then
  for i in $(grep fb /tmp/pcimodules.txt); do
    case $i in
      atyfb)
        modprobe $i mode=$vmode-16
        ;;
      nvidiafb | rivafb)
        modprobe nvidiafb mode_option=$vmode bpp=16 hwcur=1
        ;;
      radeonfb | savagefb)
        modprobe $i mode_option=$vmode-16
        ;;
      sisfb)
        modprobe $i mode=$vmodex16 mem=12288 font=SUN12x22
        ;;
      viafb | vt8623fb)
        modprobe viafb viafb_mode=$vmode viafb_bpp=16
        ;;
      *)
        modprobe $i
        ;;
    esac
  done
  [ -c /dev/fb0 ] || modprobe uvesafb scroll=ywrap mode_option=$vmode-16
fi

case $boot in
    cdrom)
        # Boot Linux from a live CD.
        loadmod ata_ ahci pdc_adma
        modusb
        modprobe usb-storage &&
        modprobe sr_mod &&
        sleep 7
        modprobe isofs
        mount -t iso9660 /dev/sr0 /media
        [ -d /media/isolinux -o -d /media/boot/isolinux ] || 
          mount -t iso9660 /dev/sr1 /media
        if [ -f /media/*.[Ss][Qq]* ]; then
          SQF=$(ls -t /media/*.[Ss][Qq]* | head -n 1)
          if [ $root = /dev/ram ]; then 
               echo "Please wait until the RAM disk is ready."
               dd if=$SQF of=/dev/ram1 bs=2048 &&
               mount -t squashfs /dev/ram1 /opt
          else modprobe loop
               mount -t squashfs -o loop $SQF /opt
          fi
        else
          mount --move /media /opt
        fi
        union
        ;;
    loop)
        # Boot Linux from an image file.
        loadmod ata_ ahci pdc_adma
        modusb
        modprobe usb-storage &&
        modprobe sd_mod &&
        sleep 7
        [ "$uuid$label" ] && mountr /media || mountr $root /media
        modprobe loop
        if [ -f /media/*.[Ss][Qq]* ]; then
          SQF=$(ls -t /media/*.[Ss][Qq]* | head -n 1)
          mount -t squashfs -o loop $SQF /opt
        elif [ -f /media/*.[Ii][Ss][Oo] ]; then
          ISO=$(ls -t /media/*.[Ii][Ss][Oo] | head -n 1)
          modprobe isofs
          mount -t iso9660 -o loop $ISO /opt
        fi
        union
        ;;
    ram)
        # Boot Linux from ramdisk.
        loadmod ata_ ahci pdc_adma
        modusb
        modprobe usb-storage &&
        modprobe sd_mod &&
        sleep 7
        [ "$uuid$label" ] && mountr /media || mountr $root /media
        echo "Please wait until the RAM disk is ready."
        if [ -f /media/*.[Ss][Qq]* ]; then
          SQF=$(ls -t /media/*.[Ss][Qq]* | head -n 1)
          dd if=$SQF of=/dev/ram1 &&
          mount -t squashfs /dev/ram1 /opt
        elif [ -f /media/*.[Ii][Ss][Oo] ]; then
          ISO=$(ls -t /media/*.[Ii][Ss][Oo] | head -n 1)
          dd if=$ISO of=/dev/ram1 bs=2048 &&
          modprobe isofs
          mount -t iso9660 /dev/ram1 /opt
        fi
        union
        ;;
    usb*)
        # Boot Linux from a USB drive.
        modusb
        modprobe usb-storage &&
        modprobe sd_mod &&
        sleep 7
        [ "$uuid$label" ] && mountr || mountr $root
        ;;
    ata*)
        loadmod ata_ ahci pdc_adma &&
        modprobe sd_mod &&
        [ "$uuid$label" ] && mountr || mountr $root
        ;;
esac

# Make sure that init exists and is executable.
if [ -x /mnt/sbin/init ]; then
  mount --move /dev /mnt/dev
  mount --move /proc /mnt/proc
  mount --move /sys /mnt/sys
  umount /tmp

# Start init from the root filesystem.
  cd /mnt
  [ -f /media/updates.zip ] && unzip -o /media/updates.zip
  case $boot in
    cdrom)
      [ $root = /dev/ram ] && umount /media
      [ $RUNLEVEL ] || RUNLEVEL=3
      ;;
    loop | ram)
      umount /media
      [ $RUNLEVEL ] || RUNLEVEL=3
      ;;
    *)
      [ $RUNLEVEL ] || RUNLEVEL=5
      ;;
  esac
  [ -d initrd ] && pivot_root . initrd
  exec switch_root . /sbin/init $RUNLEVEL dev/console 2>&1
fi

# Start a shell as a last resort.
echo "Error booting from the root filesystem. Starting a shell."
exec /bin/dash

The following are examples of boot parameters that can be used with my init script.

  • Boot Linux from the local hard drive partition /dev/sda8
    boot=ata root=/dev/sda8
  • Boot Linux from the latest squashfs file (*.sq*) on /dev/sda1
    boot=loop root=/dev/sda1
  • Boot Linux from the CD-ROM with 1024x768 video resolution
    boot=cdrom vmode=1024x768
  • Copy the squashfs image from CD-ROM into memory and run Linux on memory
    boot=cdrom root=/dev/ram ramdisk_size=573440 vmode=800x600
  • Boot Linux from the second partition of a USB drive
    boot=usb root=/dev/sda2

Also read:

2 comments:

  1. Thank you. For me kernel 3.6.1 enumerates usb devices first so I've finally had to admit defeat and use a initrd. Your script got me up and going quickly.

    Perhaps root=uuid=... is a bit more standard.

    Thanks.

    ReplyDelete
    Replies
    1. You're welcome. The script was updated to support root=uuid= and root=label= ...

      KJY

      Delete

About This Blog

KBlog logo This blog seeks to provide useful information to people, based on the author's knowledge and experience. Thanks for visiting the blog and posting your comments.

© Contents by KBlog

© Blogger template by Emporium Digital 2008

Total Pageviews