Differences between revisions 1 and 5 (spanning 4 versions)
Revision 1 as of 2012-12-27 23:05:36
Size: 17965
Editor: RogerLeigh
Comment: Initial description
Revision 5 as of 2013-05-09 09:50:43
Size: 3414
Editor: RogerLeigh
Comment: Replace initramfs-tools patches with gitweb link
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
<<TableOfContents(3)>>
Line 28: Line 30:
=== Mounting /etc in the initramfs ===

One use case identified in previous discussion was the need to support encrypted filesystems at boot. It's common to keep /usr separate and just encrypt the rootfs for performance reasons. However, the real need is for /etc to be encrypted. With a separate /usr mount no longer making sense, it would therefore be useful to be able to mount /etc encrypted directly in the initramfs, leaving the rootfs and /usr unencrypted.
Line 45: Line 43:
 1. Update util-linux to skip /usr with the -R option  1. Update util-linux to skip /usr with the -R option (DebianBug:697002)
Line 48: Line 46:
== Additional changes == == Bug reports ==
Line 50: Line 48:
 1. Update initramfs to mount /etc
 1. Update util-linux to skip /etc with the -R option
 1. Update initscripts to fsck /etc in checkroot and remount r/o when shutting down
When filing bugs, please use
{{{
User: rleigh@debian.org
Usertags: usrinitramfs
}}}
 so they are properly tagged.
[[http://bugs.debian.org/cgi-bin/pkgreport.cgi?users=rleigh@debian.org;tag=usrinitramfs|User tagged bugs]]
Line 59: Line 61:
{{{#!highlight diff
commit afe1bf2fcbb6e8ef56b99a224b44533811cc6c5a
Author: Roger Leigh <rleigh@debian.org>
Date: Tue Dec 25 13:25:48 2012 +0000

    Mount /usr in the initramfs

diff --git a/init b/init
index cb832ff..7a0fc0f 100755
--- a/init
+++ b/init
@@ -217,12 +217,18 @@ run_scripts /scripts/init-premount
 
 maybe_break mount
 log_begin_msg "Mounting root file system"
-. /scripts/${BOOT}
+. /scripts/local
+. /scripts/nfs
 parse_numeric ${ROOT}
 maybe_break mountroot
 mountroot
 log_end_msg
 
+if read_fstab_entry /usr; then
+ log_begin_msg "Mounting /usr file system"
+ domount unknown /usr
+fi
+
 maybe_break bottom
 [ "$quiet" != "y" ] && log_begin_msg "Running /scripts/init-bottom"
 run_scripts /scripts/init-bottom
diff --git a/scripts/functions b/scripts/functions
index 6e74ade..79506e7 100644
--- a/scripts/functions
+++ b/scripts/functions
@@ -423,3 +423,55 @@ wait_for_udev()
  command -v udevadm >/dev/null 2>&1 || return 0
  udevadm settle ${1:+--timeout=$1}
 }
+
+# Find a specific fstab entry
+# $1=mountpoint
+# $2=fstype (optional)
+# returns 0 on success, 1 on failure (not found or no fstab)
+read_fstab_entry () {
+ # Not found by default.
+ found=1
+
+ for file in ${rootmnt}/etc/fstab; do
+ if [ -f "$file" ]; then
+ while read MNT_FSNAME MNT_DIR MNT_TYPE MNT_OPTS MNT_FREQ MNT_PASS MNT_JUNK; do
+ case "$MNT_FSNAME" in
+ ""|\#*)
+ continue;
+ ;;
+ esac
+ if [ "$MNT_DIR" = "$1" ]; then
+ if [ -n "$2" ]; then
+ [ "$MNT_TYPE" = "$2" ] || continue;
+ fi
+ found=0
+ break 2
+ fi
+ done < "$file"
+ fi
+ done
+
+ return $found
+}
+
+# Mount a file system. We parse the information from the fstab.
+# $1=type local|nfs|unknown $2=mountpoint mount location
+domount()
+{
+ read_fstab_entry "$2"
+ mount "-t${MNT_TYPE}" "-o${MNT_OPTS}" "$MNT_FSNAME" "${rootmnt}${MNT_DIR}"
+}
+
+# Mount the root file system. This is delegated to the
+# mount(local|nfs)root functions.
+# $1=type local|nfs|unknown
+mountroot()
+{
+ case $boot in
+ nfs) : ;;
+ *) boot=local ;;
+ esac
+
+ echo "Mounting ${boot} root filesystem"
+ mount${boot}root
+}
\ No newline at end of file
diff --git a/scripts/local b/scripts/local
index 521e69a..f6f023f 100644
--- a/scripts/local
+++ b/scripts/local
@@ -74,7 +74,7 @@ pre_mountroot()
  done
 }
 
-mountroot()
+mountlocalroot()
 {
  pre_mountroot
 
diff --git a/scripts/nfs b/scripts/nfs
index 6fa0c43..a0764a3 100644
--- a/scripts/nfs
+++ b/scripts/nfs
@@ -49,7 +49,7 @@ do_nfsmount()
 }
 
 # NFS root mounting
-mountroot()
+mountnfsroot()
 {
  [ "$quiet" != "y" ] && log_begin_msg "Running /scripts/nfs-top"
  run_scripts /scripts/nfs-top
}}}

Mount /etc (work in progress):

{{{#!highlight diff
diff --git a/init b/init
index 7a0fc0f..d3ee2b9 100755
--- a/init
+++ b/init
@@ -40,10 +40,18 @@ export ROOT=
 export ROOTDELAY=
 export ROOTFLAGS=
 export ROOTFSTYPE=
+export ETC=
+export ETCDELAY=
+export ETCFLAGS=
+export ETCFSTYPE=
 export IP=
 export BOOT=
 export BOOTIF=
 export UBIMTD=
+export localtopused=no
+export localpremountused=no
+export nfstopused=no
+export nfspremountused=no
 export break=
 export init=/sbin/init
 export quiet=n
@@ -119,6 +127,57 @@ for x in $(cat /proc/cmdline); do
    ;;
   esac
   ;;
+ etc=*)
+ ETC=${x#etc=}
+ case $ETC in
+ LABEL=*)
+ ETC="${ETC#LABEL=}"
+
+ # support any / in LABEL= path (escape to \x2f)
+ case "${ETC}" in
+ */*)
+ if command -v sed >/dev/null 2>&1; then
+ ETC="$(echo ${ETC} | sed 's,/,\\x2f,g')"
+ else
+ if [ "${ETC}" != "${ETC#/}" ]; then
+ ETC="\x2f${ETC#/}"
+ fi
+ if [ "${ETC}" != "${ETC%/}" ]; then
+ ETC="${ETC%/}\x2f"
+ fi
+ IFS='/'
+ newetc=
+ for s in $ETC; do
+ newetc="${newetc:+${newetc}\\x2f}${s}"
+ done
+ unset IFS
+ ETC="${newetc}"
+ fi
+ esac
+ ETC="/dev/disk/by-label/${ETC}"
+ ;;
+ UUID=*)
+ ETC="/dev/disk/by-uuid/${ETC#UUID=}"
+ ;;
+ /dev/nfs)
+ [ -z "${BOOT}" ] && BOOT=nfs
+ ;;
+ esac
+ ;;
+ etcflags=*)
+ ETCFLAGS="-o ${x#etcflags=}"
+ ;;
+ etcfstype=*)
+ ETCFSTYPE="${x#etcfstype=}"
+ ;;
+ etcdelay=*)
+ ETCDELAY="${x#etcdelay=}"
+ case ${ETCDELAY} in
+ *[![:digit:].]*)
+ ETCDELAY=
+ ;;
+ esac
+ ;;
  nfsroot=*)
   NFSROOT="${x#nfsroot=}"
   ;;
@@ -224,9 +283,23 @@ maybe_break mountroot
 mountroot
 log_end_msg
 
+if [ -n "${ETC}" ]; then
+ log_begin_msg "Mounting /usr file system"
+ mountetc
+ log_end_msg
+fi
+
 if read_fstab_entry /usr; then
- log_begin_msg "Mounting /usr file system"
- domount unknown /usr
+ log_begin_msg "Mounting /usr file system"
+ domount unknown /usr
+ log_end_msg
+fi
+
+if [ "${localtopused}" = "yes" ] || [ "${localpremountused}" = "yes" ]; then
+ mountlocalend
+fi
+if [ "${nfstopused}" = "yes" ] || [ "${nfspremountused}" = "yes" ]; then
+ mountnfsend
 fi
 
 maybe_break bottom
@@ -308,10 +381,18 @@ unset ROOTFLAGS
 unset ROOTFSTYPE
 unset ROOTDELAY
 unset ROOT
+unset ETCFLAGS
+unset ETCFSTYPE
+unset ETCDELAY
+unset ETC
 unset IP
 unset BOOT
 unset BOOTIF
 unset UBIMTD
+unset localtopused
+unset localpremountused
+unset nfstopused
+unset nfspremountused
 unset blacklist
 unset break
 unset noresume
diff --git a/initramfs-tools.8 b/initramfs-tools.8
index bc6a030..5728146 100644
--- a/initramfs-tools.8
+++ b/initramfs-tools.8
@@ -42,6 +42,24 @@ The default is 180 seconds.
 set the file system mount option string.
 
 .TP
+\fB\fI etc= "<path to blockdevice>"
+the device node to mount as the etc file system.
+The recommended usage is to specify the UUID as followed "etc=UUID=xxx".
+
+.TP
+\fB\fI etcfstype
+set the etc file system type.
+
+.TP
+\fB\fI etcdelay
+set delay in seconds. Determines how long mountetc waits for etc to appear.
+The default is 180 seconds.
+
+.TP
+\fB\fI etcflags
+set the file system mount option string.
+
+.TP
 \fB\fI nfsroot
 can be either "auto" to try to get the relevant information from DHCP or a
 string of the form NFSSERVER:NFSPATH or NFSSERVER:NFSPATH:NFSOPTS.
diff --git a/scripts/functions b/scripts/functions
index 79506e7..3c4f7af 100644
--- a/scripts/functions
+++ b/scripts/functions
@@ -472,6 +472,18 @@ mountroot()
   *) boot=local ;;
  esac
 
- echo "Mounting ${boot} root filesystem"
  mount${boot}root
+}
+
+# Mount the etc file system. This is delegated to the
+# mountlocaletc function; not currently supported by nfs.
+# $1=type local|nfs|unknown
+mountetc()
+{
+ case $boot in
+ nfs) return ;;
+ *) boot=local ;;
+ esac
+
+ mount${boot}etc
 }
\ No newline at end of file
diff --git a/scripts/local b/scripts/local
index f6f023f..2df27ab 100644
--- a/scripts/local
+++ b/scripts/local
@@ -1,11 +1,9 @@
 # Local filesystem mounting -*- shell-script -*-
 
-pre_mountroot()
+# $1=device to mount
+# $2=optionname (for root and etc)
+pre_localmount()
 {
- [ "$quiet" != "y" ] && log_begin_msg "Running /scripts/local-top"
- run_scripts /scripts/local-top
- [ "$quiet" != "y" ] && log_end_msg
-
  wait_for_udev 10
 
  # Load ubi with the correct MTD partition and return since fstype
@@ -17,21 +15,21 @@ pre_mountroot()
 
  # Don't wait for a root device that doesn't have a corresponding
  # device in /dev (ie, mtd0)
- if [ "${ROOT#/dev}" = "${ROOT}" ]; then
+ if [ "${ROOT#/dev}" = "$1" ]; then
   return
  fi
 
  # If the root device hasn't shown up yet, give it a little while
  # to deal with removable devices
- if [ ! -e "${ROOT}" ] || ! $(get_fstype "${ROOT}" >/dev/null); then
+ if [ ! -e "$1" ] || ! $(get_fstype "$1" >/dev/null); then
   log_begin_msg "Waiting for root file system"
 
   # Default delay is 30s
   slumber=${ROOTDELAY:-30}
 
   slumber=$(( ${slumber} * 10 ))
- while [ ! -e "${ROOT}" ] \
- || ! $(get_fstype "${ROOT}" >/dev/null); do
+ while [ ! -e "$1" ] \
+ || ! $(get_fstype "$1" >/dev/null); do
    /bin/sleep 0.1
    slumber=$(( ${slumber} - 1 ))
    [ ${slumber} -gt 0 ] || break
@@ -45,15 +43,15 @@ pre_mountroot()
  fi
 
  # We've given up, but we'll let the user fix matters if they can
- while [ ! -e "${ROOT}" ]; do
+ while [ ! -e "$1" ]; do
   # give hint about renamed root
- case "${ROOT}" in
+ case "$1" in
   /dev/hd*)
    suffix="${ROOT#/dev/hd}"
    major="${suffix%[[:digit:]]}"
    major="${major%[[:digit:]]}"
    if [ -d "/sys/block/sd${major}" ]; then
- echo "WARNING bootdevice may be renamed. Try root=/dev/sd${suffix}"
+ echo "WARNING bootdevice may be renamed. Try $2=/dev/sd${suffix}"
    fi
    ;;
   /dev/sd*)
@@ -61,22 +59,43 @@ pre_mountroot()
    major="${suffix%[[:digit:]]}"
    major="${major%[[:digit:]]}"
    if [ -d "/sys/block/hd${major}" ]; then
- echo "WARNING bootdevice may be renamed. Try root=/dev/hd${suffix}"
+ echo "WARNING bootdevice may be renamed. Try $2=/dev/hd${suffix}"
    fi
    ;;
   esac
- echo "Gave up waiting for root device. Common problems:"
+ echo "Gave up waiting for $2 device. Common problems:"
   echo " - Boot args (cat /proc/cmdline)"
   echo " - Check rootdelay= (did the system wait long enough?)"
- echo " - Check root= (did the system wait for the right device?)"
+ echo " - Check $2= (did the system wait for the right device?)"
   echo " - Missing modules (cat /proc/modules; ls /dev)"
- panic "ALERT! ${ROOT} does not exist. Dropping to a shell!"
+ panic "ALERT! $1 does not exist. Dropping to a shell!"
  done
 }
 
+mountlocaltop()
+{
+ if [ "${localtopused}" != "yes" ]; then
+ [ "$quiet" != "y" ] && log_begin_msg "Running /scripts/local-top"
+ run_scripts /scripts/local-top
+ [ "$quiet" != "y" ] && log_end_msg
+ fi
+ localtopused=yes
+}
+
+mountlocalpremount()
+{
+ if [ "${localpremountused}" != "yes" ]; then
+ [ "$quiet" != "y" ] && log_begin_msg "Running /scripts/local-premount"
+ run_scripts /scripts/local-premount
+ [ "$quiet" != "y" ] && log_end_msg
+ fi
+ localpremountused=yes
+}
+
 mountlocalroot()
 {
- pre_mountroot
+ mountlocaltop
+ pre_localmount "${ROOT}" root
 
  # Get the root filesystem type if not set
  if [ -z "${ROOTFSTYPE}" ]; then
@@ -85,9 +104,7 @@ mountlocalroot()
   FSTYPE=${ROOTFSTYPE}
  fi
 
- [ "$quiet" != "y" ] && log_begin_msg "Running /scripts/local-premount"
- run_scripts /scripts/local-premount
- [ "$quiet" != "y" ] && log_end_msg
+ mountlocalpremount
 
  if [ "${readonly}" = "y" ]; then
   roflag=-r
@@ -105,7 +122,41 @@ mountlocalroot()
  else
   mount ${roflag} ${ROOTFLAGS} ${ROOT} ${rootmnt}
  fi
+}
+
+mountlocaletc()
+{
+ pre_localmount "${ETC}" etc
+
+ # Get the etc filesystem type if not set
+ if [ -z "${ETCFSTYPE}" ]; then
+ FSTYPE=$(get_fstype "${ETC}")
+ else
+ FSTYPE=${ETCFSTYPE}
+ fi
 
+ mountlocalstart
+
+ if [ "${readonly}" = "y" ]; then
+ roflag=-r
+ else
+ roflag=-w
+ fi
+
+ # FIXME This has no error checking
+ modprobe ${FSTYPE}
+
+ # FIXME This has no error checking
+ # Mount etc
+ if [ "${FSTYPE}" != "unknown" ]; then
+ mount ${roflag} -t ${FSTYPE} ${ETCFLAGS} ${ETC} ${rootmnt}/etc
+ else
+ mount ${roflag} ${ETCFLAGS} ${ETC} ${rootmnt}/etc
+ fi
+}
+
+mountlocalend()
+{
  [ "$quiet" != "y" ] && log_begin_msg "Running /scripts/local-bottom"
  run_scripts /scripts/local-bottom
  [ "$quiet" != "y" ] && log_end_msg
diff --git a/scripts/nfs b/scripts/nfs
index a0764a3..d93f5fb 100644
--- a/scripts/nfs
+++ b/scripts/nfs
@@ -35,9 +35,7 @@ do_nfsmount()
   NFSOPTS="-o retrans=10"
  fi
 
- [ "$quiet" != "y" ] && log_begin_msg "Running /scripts/nfs-premount"
- run_scripts /scripts/nfs-premount
- [ "$quiet" != "y" ] && log_end_msg
+ mountnfspremount
 
  if [ ${readonly} = y ]; then
   roflag="-o ro"
@@ -48,12 +46,31 @@ do_nfsmount()
  nfsmount -o nolock ${roflag} ${NFSOPTS} ${NFSROOT} ${rootmnt}
 }
 
+mountnfstop()
+{
+ if [ "${nfstopused}" != "yes" ]; then
+ [ "$quiet" != "y" ] && log_begin_msg "Running /scripts/nfs-top"
+ run_scripts /scripts/nfs-top
+ [ "$quiet" != "y" ] && log_end_msg
+ fi
+ nfstopused=yes
+}
+
+mountnfspremount()
+{
+ if [ "${nfspremountused}" != "yes" ]; then
+ [ "$quiet" != "y" ] && log_begin_msg "Running /scripts/nfs-premount"
+ run_scripts /scripts/nfs-premount
+ [ "$quiet" != "y" ] && log_end_msg
+ fi
+ nfspremountused=yes
+}
+
+
 # NFS root mounting
 mountnfsroot()
 {
- [ "$quiet" != "y" ] && log_begin_msg "Running /scripts/nfs-top"
- run_scripts /scripts/nfs-top
- [ "$quiet" != "y" ] && log_end_msg
+ mountnfstop
 
  modprobe nfs
  # For DHCP
@@ -73,7 +90,10 @@ mountnfsroot()
   retry_nr=$(( ${retry_nr} + 1 ))
   [ "$quiet" != "y" ] && log_end_msg
  done
+}
 
+mountnfsend()
+{
  [ "$quiet" != "y" ] && log_begin_msg "Running /scripts/nfs-bottom"
  run_scripts /scripts/nfs-bottom
  [ "$quiet" != "y" ] && log_end_msg
}}}

=== util-linux ===

{{{#!highlight diff
--- util-linux-2.20.1.original/fsck/fsck.8 2012-12-25 15:14:40.000000000 +0000
+++ util-linux-2.20.1/fsck/fsck.8 2012-12-27 22:19:18.000000000 +0000
@@ -273,8 +273,8 @@
 .B \-R
 When checking all filesystems with the
 .B \-A
-flag, skip the root filesystem. (This is useful in case the root
-filesystem has already been mounted read-write.)
+flag, skip the root, /etc and /usr filesystems. (This is useful in case the root,
+/etc and/or /usr filesystems have already been mounted read-write.)
 .TP
 .B \-T
 Don't show the title on startup.
--- util-linux-2.20.1.original/fsck/fsck.c 2012-12-25 15:14:40.000000000 +0000
+++ util-linux-2.20.1/fsck/fsck.c 2012-12-27 22:18:20.000000000 +0000
@@ -1132,11 +1132,11 @@
  }
  /*
   * This is for the bone-headed user who enters the root
- * filesystem twice. Skip root will skep all root entries.
+ * filesystem twice. Skip root will skep all root, /etc and /usr entries.
   */
  if (skip_root)
   for (fs = filesys_info; fs; fs = fs->next)
- if (!strcmp(fs->mountpt, "/"))
+ if (!strcmp(fs->mountpt, "/") || !strcmp(fs->mountpt, "/etc") || !strcmp(fs->mountpt, "/usr"))
     fs->flags |= FLAG_DONE;
 
  while (not_done_yet) {
}}}
[[http://anonscm.debian.org/gitweb/?p=users/rleigh/initramfs-tools.git;a=shortlog;h=refs/heads/usrmount|Work in progress (git)]]

Mount /usr in the initramfs

Introduction

Traditionally, /usr has been mounted in mountall, as for any filesystem in /etc/fstab other than the root filesystem (or special filesystems like /proc and /dev). This has several implications:

  • Programs, libraries and data under /usr are not available in the early stages of booting
  • Programs in /bin and /sbin can not assume /usr is available
  • init scripts need to cope with /usr being unavailable in early boot
  • This causes particular problems with shared libraries, PAM modules, NSS module dependencies, etc. as well as locale availability.

If /usr could be guaranteed to be available by mounting it in the initramfs, these problems can also be overcome. This permits all shared libraries, PAM modules, NSS modules etc. to function at once. It also allows locales to be used at once as well. Additionally, it is possible to use C++ (libstdc++ is under /usr), and interpreters such as perl and python, etc. In short, it makes a large number of things possible which were previously not possible, and this will both allow greater flexibility in what can run in the early stages of booting, as well as removing a large number of special cases and hairy logic which existed solely to cope with /usr not being available.

Initial implementation

The change to the initramfs is fairly simple. Once the root filesystem is mounted, the /etc/fstab on the root filesystem can be read, and then if it contains an entry for /usr, we mount it.

The -R option of /sbin/fsck, used to check all filesystems except the root filesystem needs updating so that it will also ignore /usr, or else at boot the checkfs script will try to fsck a mounted /usr.

The initscripts themselves need updating to treat /usr like the root filesystem

  • Remount r/o
  • Check
  • Remount r/w
  • Remount r/o at shutdown and don't umount

Caveats

The main caveat is that if you use a separately mounted /usr, you must use an initramfs to mount it. While not using an initramfs will continue to be supported, this will only work if you have /usr on the root filesystem (i.e. it's a single mount). This is because mounting /usr in the traditional way will cause startup to fail since it's mounted too late (this will only happen once init scripts start relying on /usr to be available immediately).

initramfs

No initramfs

root only

Supported

Supported

root and /usr

Supported

Broken

The initramfs will support / and /usr mounts on local/local and nfs/nfs. However, it it not yet clear if this will extend to local/nfs and/or nfs/local.

Required changes

  1. Update initramfs to mount /usr
  2. Update util-linux to skip /usr with the -R option (697002)

  3. Update initscripts to fsck /usr in checkroot and remount r/o when shutting down

Bug reports

When filing bugs, please use

User: rleigh@debian.org
Usertags: usrinitramfs
  • so they are properly tagged.

User tagged bugs

Patches

initramfs-tools

Work in progress (git)

initscripts (sysvinit)

TODO