#!/bin/sh # © 2006 Jens Gustedt, INRIA, France # $Revision: 1962 $ # initially based on pages and scripts by Benjamin Smee - # strerror@disciplina.net but mostly rewritten, since. # This is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # This software is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You may have received a copy of the GNU General Public License along # with GNU Emacs; see the file COPYING. If not, write to the Free # Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. # This script is for an early creation of encrypted block devices. It # uses LUKS for that purpose. This is needed if the root filesystem # (not yet tested) and/or the swap device are encrypted. It is capable # to cooperate with suspend2, in that first the encrypted swap device # is mapped, and then suspend2 is triggered to look if there is a # session to resume on that device. Using suspend to disk is always a # bit risky. Be sure to # BACK UP YOUR DATA. # BIG FAT WARNING: # Do not mount any filesystem on a real device prior to resume from # disk. Even a read-only mount, may WRITE TO YOUR DEVICE. If your device is # also mounted inside the image that you what to resume from, your seeking for # trouble. # If you do so, you should be knowing what you are doing. # The script is generic and can be tuned by parameters that we receive # through the kernel parameter command line, as e.g generated by lilo # or grub. # luks=dev1:dev2:... the list of luks device # resume2=swap:devname your swap device of file for # suspend2 resume # noresume2 skip resume2 and boot normally # early=opt1,... pass some options to this skript # [no]manual intermediately get a shell (nomanual) # [no]mntchk let suspend check if root fs is mounted on resume (mntchk) # [no]fsck run fsck on all filesystems that are mounted # on normal boot (fsck) ### The following two options are particulary dangerous, since before ### resume they will mount the partition `dev' and look for files in ### the file system on that devices. Don't use them if you can avoid ### it. And, be very, very careful. # lukskey=dev:path a partition with a file containing a key for luks # modules=dev:path load kernel modules from partition `dev' and path `path' # path defaults to lib/modules/`uname -r` ### Some other standard parameters that we recognize: # root=/dev/yourrootdevice If you have e.g a LUKS encrypted root # on /dev/hda1 put /dev/mapper/hda1 here. # rootfstype=yourtype If you have a type that is not auto detected # rootflags=mountflags Flags to pass to mount for root filesystem # rootdelay=seconds Delay mounting by some seconds (0) # ro Mount root read-only (default) # rw Mount root read-write # init= Overwrite with other script (/sbin/init) # Lilo (or?) also gives a root=something option to the kernel, but # this is in a numerical format. To be able to deal with different # such cases we recognize different formats for mount options. These # maybe a numerical format, a filesystem label or uuid, or just the # name of the device node. See function `device' below. # If you have multiple luks partitions that you want to mount, # separate them with a collon for the `luks' parameter. All these luks # partitions must have one luks pass phrase in common. We map each of # these block devices to a block device in /dev/mapper/ with the same # name, so e.g /dev/hda1 is mapped to /dev/mapper/hda1. # Since a device node like /dev/hda1 does not contain a filesystem as # such, it cannot be identified with a filesystem label. It is simple # to identify a luks partition with its UUID. But integrating this is # not yet implemented. ################# what follows is only for the brave ################# # In the /etc/fstab of the initramfs, you may specify / as a `tmpfs' # filesystem. In any case (real root partition or tmpfs) all other # partitions that are found in fstab (excepting those marked `noauto') # are mounted below that. Then, if there are subdirectories under # these partitions that are given with a `bind' option they will also be # mounted, even if `noauto' is given. For example # #LABEL=VAR /mnt/VAR ext3 noatime 1 2 #/mnt/VAR/etc /etc none noauto,bind 0 0 # # will mount the filesystem labeled `VAR' and then `bind' directory # `etc' in that filesystem to /etc. # # All this is to leave you completely free on how you want to compose # your system, you may want to get more machine specific directories, # such as /etc from somewhere else than your /bin, and with different # mount options. You just have to give a precise description of how # your collection of filesystems should look like in the /etc/fstab # that is found in the initramfs. Mark all `bind' partitions also # `noauto' since other scripts may be messed up otherwise. # # Be careful to adapt your other init scripts according to that new # composition of your system. # Also be careful with an init= option. If you mount a block device # as /, the executable that you name here must live in your root block # device, not in the ramdisk. A good fallback if everything else fails # is init=/bin/sh. ##################################################################### VARIABLES # These are the four locations for executables that busybox knows # about. export PATH="/bin:/sbin:/usr/bin:/usr/sbin" # First set default values for standard kernel boot options that we # know of. export root=${root:-} export rootfstype=${rootfstype:-auto} export rootdelay=${rootdelay:-0} export ro= export noresume2=${noresume2:-0} export init=${init:-/sbin/init} export early=${early:-} export luks=${luks:-} export lukskey=${lukskey:-} export modules=${modules:-} knownopts="resume2=*|init=*|root=*|mounts=*|rootdelay=*|rootfs=*|rootflags=*|early=*|luks=*|lukskey=*|modules=*" # in fact, the boot loader hides some arguments to the environment parseopts="root=*|init=*" # subobtions that can be set or unset manual=0 mntchk=1 fsanity=0 fsck=1 earlyopts="manual|mntchk|fsanity|fsck" # local variables phase=${phase:-0} prompt="early" level=1 PS1="${prompt}> " PS2="${prompt}>> " PS3="${prompt}>>> " PS4="${prompt}>>>> " reroot="" fsckerror= ARGV="$@" nodev=tmpfs ##################### aliases for the case that busybox is picky ##################### when looking at the PATH if ! [ -x "/usr/bin/[" ] ; then if ! /usr/bin/test -x "/usr/bin/[" ; then echo "Severe early execution problem." echo "Be sure to have '/usr/bin/test' and '/usr/bin/[' inside your initramfs." else alias [="/usr/bin/[" alias sh="/bin/sh" alias ln="/bin/ln" alias mount="/bin/mount" alias umount="/bin/umount" alias mdev="/bin/mdev" alias sed="/bin/sed" alias test="/usr/bin/test" alias logger="/usr/bin/logger" echo "Created the following aliases:" alias fi fi ##################################################################### SANITY CHECKS silent () { if [ -c /dev/null ]; then ($* 2>&1) > /dev/null else local null=$($* 2>&1) fi return $? } if silent logger -p kern.info -t early -s "booting"; then alias log="logger -p kern.info -t early -s " else alias log="echo early: " fi goerror () { log "bye." exit 1; } parachute () { local sh=/bin/sh local prompt="${1:-EARLY}" log "Dropping you to a limited shell." log "press CTRL-ALT-Del for reboot" PS1="${prompt}> " PS2="${prompt}>> " PS3="${prompt}>>> " PS4="${prompt}>>>> " exec "${sh}" log "Even ${sh} is not executable..." goerror } trap parachute 1 2 3 4 5 6 7 8 9 10 11 12 if ! busybox sh -n ${0}; then log "${0} appears not to be syntactically correct, cowardly refusing execution." log "Normally at this stage no harm should have been done." parachute "EARLY SYNTAX ERROR" fi inspect () { let $((++phase)) if [ "${manual}" -gt 0 ] ; then log "phase ${phase}, inspect or repair early system manually" log "CTRL-D terminates this shell and continues booting." log "CTRL-ALT-Del reboots." prompt="early phase ${phase}" PS1="${prompt}> " PS2="${prompt}>> " PS3="${prompt}>>> " PS4="${prompt}>>>> " /bin/sh echo fi } # Link hard if possible. lnk () { silent ln $* || ln -s $* return $? } code () { num="${1}" bit="${2}" return $(( (num>>bit)%2 )) } mounterror () { local ret="${1}" local comm="${2}" if [ "${ret}" = 0 ] ; then log "${comm}, success." else code "${ret}" 0 || log "${comm}: incorrect invocation or permissions" code "${ret}" 1 || log "${comm}: system error (out of memory, cannot fork, no more loop devices)" code "${ret}" 2 || log "${comm}: internal mount bug or missing nfs support in mount" code "${ret}" 3 || log "${comm}: user interrupt" code "${ret}" 4 || log "${comm}: problems writing or locking /etc/mtab" code "${ret}" 5 || log "${comm}: mount failure" code "${ret}" 6 || log "${comm}: some mount succeeded" fi } mountit () { local mask="${1}" local comm="${2}" shift 2 silent mount $* local ret=$? local mret=$((${ret}&~${mask})) if [ "${ret}" -eq 0 ]; then mounterror 0 "${comm}" elif [ "${mask}" -lt 0 ]; then return "${ret}" elif [ "${mret}" -ne 0 ]; then mounterror "${mret}" "${comm}" fi return "${mret}" } match () { local str="${1}" local pat="${2}" local comm="case '${str}' in (${pat}) return 0 ;; (*) return 1 ;; esac" eval "${comm}" return $? } count () { echo $# } nth () { let i="${1}"+1 eval 'echo "${'"${i}"'}"' } uname () { case "${1}" in -s) echo $(nth 1 $(cat /proc/version));; -h) echo $(nth 2 $(cat /proc/version));; -r) echo $(nth 3 $(cat /proc/version));; -a) cat /proc/version;; [0-9]*) echo "hui: "$(nth "${1}" $(cat /proc/version));; *) echo $(nth 1 $(cat /proc/version));; esac } # ash has no substitution expansion subst () { echo "${2}" | sed "${1}" } split () { local cls=${2:-:,;} subst "s/[${cls}]/ /g" "${1}" } join () { subst 's/^ *//;s/ */:/g;s/ *$//' "$*" } major () { local num="0x${1}" echo $((num/256)) } minor () { local num="0x${1}" echo $((num%256)) } # With major and minor device number find the # device name of a partition. devname () { local major="${1}" local minor="${2}" local ret=$(sed -n "/^ *${major} *${minor} /s/^.* \([^ ][^ ]*\)\$/\1/g p" /proc/partitions ) echo "${ret}" } # Find the block device DEV for a given NAME. NAME may be # - a device name. # - a number consisting of 3 or 4 digits d2d1d0 or d3d2d1d0 of type # [0-9a-fA-F]. It is then interpreted as hexadecimal major and minor # device number, major is d3d2 and minor d1d0. # - a filesystem label or uuid given by LABEL=label or UUID=uuid. This # feature depends on busybox' implementation of tune2fs/findfs. At # the moment this is tested for ext2/ext3 filesystems, vfat (msdos) # filesystems and `new style' swap filesystems. # # The return value DEV is the name of an existing block device node in # /dev. If no such device node exists the empty string is returned. device () { local name="${1}" local dev= case "${name}" in ([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]|[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) dev=$(devname $(major ${name}) $(minor ${name})) ;; (LABEL=*|UUID=*) dev=$(findfs "${name}") ;; (*) dev="${name}" ;; esac dev=$(subst '/^[^\/].*/s|.*|/dev/&|' "${dev}") if [ ! -b "${dev}" ] ; then dev= fi echo "${dev}" } # Return a mount point MNT for a given NAME. # If NAME is a number or a device name, mount points are of the form # /mnt/XXX, where XXX is the path to the device node without the /dev/ # prefix. # If NAME is a partition label of the form LABEL=THELABEL, the mount # point is /mnt/THELABEL; if it is UUID=THEUUID the point is # /mnt/THEUUID. # In all other cases this is thought to be an nfs mount point. mntpnt () { local name="${1}" local dev=$(device "${name}") local mnt= if [ -n "${dev}" ]; then case "${name}" in (LABEL=*) mnt=$(subst 's|^LABEL=|/mnt/|' "${name}") ;; (UUID=*) mnt=$(subst 's|^UUID=|/mnt/|' "${name}") ;; (*) mnt=$(subst 's|^/dev|/mnt|' "${dev}") ;; esac else mnt=$(subst 's|:/|/|g;s|:|/|g' "/mnt/nfs/${name}") fi echo "${mnt}" } help () { log "Script seems to be syntactically correct." log "It should only be executed as /init process n° 1 from your ramdisk." log "" log "We recognize the following options from the kernel command line:" for opt in noresume2 $(split ${knownopts} '|'); do log " "$(subst 's/=.*//' "${opt}") done log "" log "Where most are interpreted as you find it in the kernel documentation," log "'early', 'luks', 'lukskey' and 'modules' are special." log "luks=* should provide a list of LUKS partitions to be mapped." log "early=* provide us with the following flags (default value) :" for opt in $(split ${earlyopts} '|'); do log " ${opt} ("$((${opt}))")" done log "" log "To set a flag use it with early=FLAG to unset use early=noFLAG." log "Group different flags together in one kernel parameter, e.g" log " early=manual:nocleanup:norootdev" log "" log "With 'lukskey=partion:keyfile' you may indicate a file containing" log "your passphrase, e.g. on a removable device like a memory stick." log "" log "modules=* may name a partition that contains modules for the early boot phase." log "We will do everything we know of to assure that this partition is" log "mounted read-only." return 0 } ####################################################### REAL START # Run through the filesystem and mark all directories so a user sees # that this filesystem is virtual. Use hardlinks for that such that # not too much memory or inodes in the filesystem are wasted. # # A the same time this collects the directory structure and all the # filenames in DLIST and FLIST resp. These my be needed later when we # copy the rootfs ramdisk to a tmpfs. markvirtual () { local top="${1}" local old=$(pwd) [ -n "${top}" ] && cd "${top}" local files=$(ls -A | while read file; do subst "s|^|${top}/|;s|//|/|" "${file}"; done ) local nfiles= local note="${top}/.virtual" note=$(subst 's|//|/|g' "${note}") if [ ! -e "${note}" ]; then echo "This is a virtual filesystem." > "${note}" echo "All files that you modify or create here" >> "${note}" echo "will be lost on the next boot." >> "${note}" chmod 444 "${note}" fi while [ -n "${files}" ]; do nfiles="${files}" files= for file in ${nfiles} ; do # beware test follows symlinks if [ -d "${file}" -a ! -L "${file}" ]; then echo "${file}" >> "${dlist}" cd "${file}" for f in $(ls -A); do files="${files} ${file}/${f}" done local note2="${file}/.virtual" if [ ! -e "${note2}" ]; then lnk "${note}" "${note2}" fi else echo "${file}" >> "${flist}" fi done done cd "${old}" } ################################################ PROCESS COMMANDLINE processCmdline () { log "parameters are: ${ARGV}" CMDLINE=`cat /proc/cmdline` log "kernel cmdline: ${CMDLINE}" export CMDLINE for param in ${CMDLINE} ; do if match "${param}" "${parseopts}"; then # Take care that the contents of the option is never evaluated by the shell # but taken as is. param=$(subst 's/\(^[^=]*=\)\(.*\)/'"\1'\2'/g" "${param}" ) eval "${param}" else case "${param}" in ([0-9Ss]) log "init level of ${param} requested" level="${param}" ;; (noresume2*) noresume2=1 ;; (ro) ro=ro log "request to mount root read-only" ;; (rw) ro=rw log "request to mount root read-write" ;; esac fi done } getEarly () { if [ -n "${early}" ] ; then early=$(split "${early}") for param in ${early} ; do name=$(subst 's/^no//g' "${param}") [ "${param}" != "${name}" ] value=$? if match "${name}" "${earlyopts}"; then eval "${name}=${value}" log "set ${name} to ${value}" else log "unknown option ${param}" fi done fi } goNodev () { # collect the filesystem types that don't use devices. nodev=$(echo $(sed -n '/^nodev/s/nodev // p' /proc/filesystems ) ) # mount other virtual filesystems that the user may have requested. for fst in ${nodev} ; do mountit 1 "mounting ${fst} filesystems" -a -t "${fst}" done nodev=$(subst 's/ /|/g' "${nodev}" ) } ####################################################### FIND KEY key=/tmp/key findKey () { if [ -n "${lukskey}" ]; then lukskey=$(split "${lukskey}") local keypart=$(nth 1 ${lukskey}) local keyfile=$(nth 2 ${lukskey}) local keydir=/tmp/keydir install -d "${keydir}" sleep 2 if [ ! -e "${keypart}" ]; then silent device "${keypart}" sleep "${rootdelay}" export rootdelay=0 keypart=$(device "${keypart}") fi if [ -e "${keypart}" ]; then if mountit 0 "mounting keypartition ${keypart}" "${keypart}" "${keydir}" -r ; then log "Found ${keypart}." keyfile="${keydir}/${keyfile}" if [ -e "${keyfile}" ]; then log "Trying ${keyfile}." passwd=$(cat "${keyfile}") echo -n "${passwd}" > "${key}" lukskey= else log "${keyfile} not found." inspect fi umount "${keydir}" fi else log "Unable to find ${lukskey}, skipped." fi fi } ################################################ GO EARLY LUKS goLuks () { if [ -z "${luks}" -o ! -c /dev/mapper/control ] ; then log "No luks filesystem specified or " log "character device /dev/mapper/control not found." log "Please append a correct \"luks=\" boot option " log "and ensure that /dev/mapper/control is present in your ramdisk." log "Skipping initialization of early luks partition." else # read the pass phrase maps=$(split "${luks}") # iterate over all maps while [ -n "${maps}" ]; do local remaining= findKey if [ ! -e "${key}" ]; then # boot up will halt here until a key is typed in read -s -p "LUKS passphrase for ${maps}: " passwd echo echo -n "${passwd}" > "${key}" fi echo -n "creating maps in /dev/mapper/.." for map in ${maps} ; do dev=$(subst 's|^[^/].*|/dev/&|' "${map}") map=$(basename "${dev}") if ! silent /bin/cryptsetup -q --key-file="${key}" luksOpen "${dev}" "${map}"; then echo -n "[${map}]." remaining="${remaining} ${map}" else echo -n "${map}." fi done echo "done." rm -f "${key}" maps="${remaining}" done fi # To be sure that it is gone, even if we don't succeed in # switching the root afterwards. rm -f "${key}" } ################################################## GO SUSPEND2 goSuspend2 () { if [ -d /sys/power/suspend2 ] ; then suspend2dir="/sys/power/suspend2" else suspend2dir="/proc/suspend2" fi info2="${suspend2dir}/resume2" if [ ${noresume2} == 0 -a -w "${info2}" ]; then resume2=${resume2:-$(cat ${info2})} if [ -z "${resume2}" ]; then log "No resume2= cmd line parameter given, skipped." else # See if the user requested that the root filesystem should not be # checked. mntcntrl=${suspend2dir}/ignore_rootfs if [ ${mntchk} == 0 -a -w "${mntcntrl}" ]; then echo 1 > "${mntcntrl}" fi # Eventually translate resume2 parameter into a real device name. # # Partition labels do not work for swap partitions, since suspend2 # changes the signature on suspending. On resume the label is then # not recognized by busybox' findfs since it doesn't know about # suspend2 signatures, yet. parts=$(split "${resume2}" ':') for part in ${parts}; do dev=$(device ${part}) if [ -n "${dev}" ]; then nresume2="${nresume2} ${dev}" else nresume2="${nresume2} ${part}" fi done nresume2=$(subst 's/^:*//' $(join ':' ${nresume2}) ) if [ "${resume2}" != "${nresume2}" ]; then log "resume2 argument ${resume2} interpreted as ${nresume2}" resume2="${nresume2}" fi sane=1 # Sanity check. With the current patch to suspend2 this should # not be necessary any more. if [ "${fsanity}" = 1 ]; then cat /proc/mounts | while read line; do dev=$(nth 1 ${line}) mnt=$(nth 2 ${line}) fst=$(nth 3 ${line}) opt=$(nth 4 ${line}) case "${opt}" in (rw*) if match "${fst}" "${nodev}"; then log "'${dev}' mounted on ${mnt} is read-write, type is ${fst}, ok." else log "'${dev}' mounted on ${mnt} is read-write, type is ${fst}, refusing resume." sane=0 fi ;; (ro*) log "${devname} on ${mnt} is a ${fst} and mounted read-only, ok." ;; (*) log "${devname} on ${mnt} is a ${fst}. Read state unknown: ${opt}." ;; esac done inspect fi # Go and resume if possible. if [ "${sane}" -eq 1 ] ; then # If resume2 is on a luks partition the initialization # has not yet been performed. echo "${resume2}" > "${info2}" # This is like an `exec' and will not return # if the resume is successful echo -n "resuming suspend2 image..." echo > "${suspend2dir}/do_resume" echo "failed" else log "A rw partition idicates that maybe accidentialy we checked the device." log "Resuming would be too risky." fi log "resuming suspend2 image not possible, going normal boot." fi else log "no suspend2 support found." fi } ################################################## MOUNT # Create all mount points that we find in the fstabs, # if we can. createMountpoints () { fstab="${1}" prefix="${2}" if [ -e "${fstab}" ]; then sed -n '/[ ]*#/!s|^[^ ]* *\([^ ][^ ]*\).*|\1| p' "${fstab}" | while read line; do case "${line}" in (/*) silent install -d "${prefix}${line}" ;; esac done fi } bindAll () { local fstab="${1:-/etc/fstab}" local prefix="${2}" if [ -e "${fstab}" ]; then cp "${fstab}" /.fstab sed -n '/^ *#/! p' /.fstab | while read line; do local dev=$(nth 1 ${line}) local pnt=$(nth 2 ${line}) local tpe=$(nth 3 ${line}) local opt=$(nth 4 ${line}) if match "${opt}" "*bind*"; then bind "${prefix}${dev}" "${prefix}${pnt}" fi done echo "." fi } mountAll () { local fstab="${1:-/etc/fstab}" local prefix="${2}" if [ -e "${fstab}" ]; then cp "${fstab}" /.fstab sed -n '/^ *#/! p' /.fstab | while read line; do local dev=$(device $(nth 1 ${line})) local pnt=$(nth 2 ${line}) local tpe=$(nth 3 ${line}) local opt=$(nth 4 ${line}) local dir="${prefix}${pnt}" if ! match "${opt}" "*noauto*"; then log "mounting: ${dev} ${dir} ${tpe} ${opt}" if mountpoint -q "${dir}" ; then log "${dir} is already mounted" elif [ ! -z "${dev}" ] && ! match "${tpe}" "${nodev}" ; then if [ "${fsck}" = 1 ] && fsck -pv "${dev}"; then fsckerror="${fsckerror} ${dev}" fi install -d "${dir}" mount "${dev}" "${dir}" -r -t "${tpe}" -o "${opt}" fi fi done fi } mountNew () { local fstab="${1:-/etc/fstab}" local prefix="${2}" if [ -e "${fstab}" ]; then cp "${fstab}" /.fstab local line=$(sed -n '/^ *#/! p' /.fstab | sed -n '/ \/ / p') local dev= local tpe= local opt= if [ -n "${line}" ]; then log "Found root partition in /etc/fstab" dev=$(device $(nth 1 ${line})) tpe=$(nth 3 ${line}) opt=$(nth 4 ${line}) [ -n "${opt}" ] && opt="${opt},${ro}" elif [ -n "${root}" ]; then log "Found root partition on commandline" dev=$(device "${root}") tpe="${rootfstype}" opt="defaults" [ -n "${opt}" ] && opt="${opt},${ro}" else log "Assuming tmpfs on root" dev="roottmpfs" tpe="tmpfs" opt="size=33554432,rw" fi if mountpoint -q "${prefix}" ; then log "${prefix} is already mounted" else if [ -z "${dev}" ]; then dev=$(nth 1 ${line}) elif ! match "${tpe}" "${nodev}"; then [ "${fsck}" = 1 ] && fsck -pv "${dev}" || fsckerror="${fsckerror} ${dev}" fi log "mounting: ${dev} ${prefix} ${tpe} ${opt}" install -d "${prefix}" mount "${dev}" "${prefix}" -t "${tpe}" -o "${opt}" log "checking ${tpe} for nodev: ${nodev}" if match "${tpe}" "${nodev}"; then createMountpoints "/etc/fstab" "${pnt}" markvirtual "${pnt}" fi fi fi } chmodAll () { local mode="${1:-ug+w}" local prefix="${2}" cat /proc/partitions | while read line; do local major=$(nth 1 $line) local minor=$(nth 2 $line) local blocks=$(nth 3 $line) local name=$(nth 4 $line) local dev="${prefix}/dev/${name}" [ -b "${dev}" ] && chmod "${mode}" "${dev}" done } moveAll () { local prefix="${1:-/}" cat /proc/mounts > /.mtab sed -n '/ \/ /! p' /.mtab | while read line; do local pnt=$(nth 2 ${line}) local rnt=$(readlink -f ${pnt}) if ! match "${pnt}" "${prefix}*" \ && [ -e "${pnt}" ] \ && [ "${pnt}" = "${rnt}" ] ; then local dir="${prefix}${pnt}" log "moving: ${pnt} -> ${dir}" install -d "${dir}" mount "${pnt}" "${dir}" -o "move" # save the old mountpoint install -d "/.oldmount${pnt}" rmdir "/.oldmount${pnt}" mv "${pnt}" "/.oldmount${pnt}" lnk "${dir}" "${pnt}" fi done } removeAll () { local prefix="${1:-/}" cat /proc/mounts > /.mtab sed -n '/ \/ /! p' /.mtab | while read line; do local pnt=$(nth 2 ${line}) if ! match "${pnt}" "${prefix}*"; then log "removing: ${pnt}" umount "${pnt}" fi done } mountRoot () { local roots="${1}" local fsreq="${2}" local ret=0 for ROOT in $(split "${roots}") ; do dev=$(device "${ROOT}") pnt=$(mntpnt "${ROOT}") if ! mountpoint -q "${pnt}"; then if [ -n "${dev}" ] ; then local fsline=$(sed -n "/^$ROOT/ p" /etc/fstab) local fs=${rootfstype} # Check for special cases. # When we want to go ro we should not use ext3 as a filesystem # because it may write to the journal, even if requested read-only. # So we replace `ext3' by `ext2' in such a case. if [ -n "${fsline}" ]; then local trouble=1 fs=$(nth 3 ${fsline}) case "${fs}" in (ext3) if [ "${fsreq}" = "ext2" ]; then fs="ext2" fi ;; (reiserfs) log "reiserfs still lacks support for this script, be careful" ;; (xfs) log "xfs still lacks support for this script, be careful" ;; (*) trouble=0 ;; esac if [ ${trouble} -gt 0 ]; then if [ -n "${reroot}" ]; then reroot="${reroot}:${ROOT}" else reroot="${ROOT}" fi fi fi install -d "${pnt}" if [ ${rootdelay} -gt 0 ] ; then log "before mounting, power nap of ${rootdelay} seconds requested" sleep "${rootdelay}" export rootdelay=0 else sleep "1" fi mountit 0 "mounting ${dev} on ${pnt}, filesystem ${fs}" -t "${fs}" "${dev}" "${pnt}" -r if ! mountpoint -q "${pnt}" ; then ret=1 fi else log "No device entry for ${ROOT} found, skipped." fi else log "Somethings is already mounted on ${pnt}, skipped." fi done return "${ret}" } umountRoot () { local roots="$1" for ROOT in $(split "${roots}") ; do local pnt=$(mntpnt "${ROOT}") umount "${pnt}" done } remountRoot () { local roots="$1" if [ "${ro}" != "rw" ]; then return 0 fi if [ -n "${rootflags}" ]; then rootflags="${rootflags},remount,rw" else rootflags="remount,rw" fi for ROOT in $(split "${roots}") ; do dev=$(device "${ROOT}") pnt=$(mntpnt "${ROOT}") if mountpoint -q "${pnt}"; then if [ -n "${dev}" ] ; then fstype="${rootfstype}" else log "No device entry for ${ROOT} found, skipped." continue fi mountit 0 "remounting ${dev} on ${pnt}" "${dev}" "${pnt}" -r -t "${fstype}" -o "${rootflags}" fi done } ################################################## COMPOSE SYSTEM # Mount-bind DIR to PNT. # # PNT defaults to /BN, where BN is the basename of DIR. DIR is only # bound when it is not empty and when BN is not one of a forbidden # list of names. If PNT exists at this stage it is backuped and added # to the list of obsoletes. bind () { local dir="${1}" local bn=$(basename "${dir}") # If ${2} is empty, default is the basename of the directory. local pnt=${2:-"/${bn}"} local npnt=".$(basename ${pnt})" local mdir=$(dirname "${pnt}") if [ "${mdir}" != "/" ]; then mdir="${mdir}/" fi while [ -e "${mdir}${npnt}" ] ; do npnt=".${npnt}" done npnt="${mdir}${npnt}" local files=$(count $(ls -A "${dir}")) if [ -d "${dir}" -a "${files}" -gt 0 ]; then case "${bn}" in (proc|sys|mnt|dev|lost\+found) echo -n "[${dir}]." ;; (*) if [ -e "${pnt}" -a ! -e "${pnt}/.virtual" ] ; then echo -n "[${pnt}->${npnt}]." mv "${pnt}" "${npnt}" obsolete="${obsolete} ${npnt}" fi install -d "${pnt}" mount --bind "${dir}" "${pnt}" ret=$? if [ "${ret}" -ne 0 ]; then echo mounterror "${ret}" "When binding ${dir} to ${pnt}" else echo -n "${pnt}." fi ;; esac else echo -n "[${dir}]" fi } goNewroot () { prompt="init" local pnt="/newroot" install -d "${pnt}" mountNew /etc/fstab "${pnt}" inspect mountAll /etc/fstab "${pnt}" if [ "${fsck}" = 1 -a -z "${fsckerror}" ]; then touch "${pnt}/fastboot" else log "The following filesystems had problems: '${fsckerror}'" log "Unmounting all filesystems and rerunning fsck for all these fs." cd /proc umount -a cd / for fs in ${fsckerror} ; do fsck -v "${fs}" done parachute "PLEASE REBOOT (fsck)" fi inspect bindAll /etc/fstab "${pnt}" inspect moveAll "${pnt}" inspect install -d "${pnt}/dev" mv "/dev" "/.dev" ln -s "${pnt}/dev" "/dev" mdev -s inspect chmodAll ug+w "${pnt}" inspect exe="${pnt}${init}" log "Going to ${exe}" if [ -x "${exe}" ]; then log "Going to ${init}" cd "${pnt}" PS1= PS2= PS3= PS4= \ exec /bin/busybox switch_root -c/dev/console "${pnt}" "${init}" "${level}" log "Something went wrong on exec of ${newroot}:${init}." else log "The reclaimed ${newroot}:${init} was not found or is not executable." fi parachute "SWITCH TO NEWROOT FAILED" } discoverEarly () { # Nowadays almost all system tools desperately need /proc and/or /sys. mountit 0 "mounting /proc" -t proc proc /proc mountit 0 "mounting /sys" -t sysfs sysfs /sys # Populate the ramdisk with all this particular busybox knows about. silent busybox --install # This should populate our /dev directory with all block devices that # are exported to /sys. /sbin/mdev -s echo /sbin/mdev > /proc/sys/kernel/hotplug # The /dev/mapper/control does not follow the simple naming scheme # that mdev is able to handle. if [ -c /dev/device-mapper -a ! -c /dev/mapper/control ] ; then lnk /dev/device-mapper /dev/mapper/control fi } findModules () { local moddev=$(nth 1 $(split "${modules}")) local moddir=$(nth 2 $(split "${modules}")) log "requested modules from ${moddev}, ${moddir}" if [ -n "${moddev}" ]; then mountRoot "${moddev}" "ext2" if [ $? -ne 0 ]; then log "unable to mount the modules fs, skipped." return 1; fi fi moddir=${moddir:-"lib/modules/$(uname -r)"} if [ ! -d "/${moddir}" ]; then log "no /${moddir}" local found=0 for dir in $(ls -A /mnt); do if [ -d "/mnt/${dir}/${moddir}" ]; then log "found /mnt/${dir}/${moddir}" install -d /lib/modules lnk "/mnt/${dir}/${moddir}" "/${moddir}" found=1 else log "no /mnt/${dir}/${moddir}" fi done if [ "${found}" -eq 0 ]; then umountRoot "${moddev}" return 1 fi else log "found /${moddir}" fi inspect if [ -e /etc/modprobe.preload ]; then for f in $(sed 's/^[ ]*#.*//' /etc/modprobe.preload); do silent modprobe "${f}" done fi inspect umountRoot "${moddev}" export modules= } ############################# REAL EXECUTION STARTS HERE state=0 if [ "${1}" = "--help" ] ; then let state=$((${state}|1)) fi if [ "${1}" = "--check" ] ; then let state=$((${state}|2)) fi if [ $$ -ne 1 ]; then let state=$((${state}|4)) fi case "${state}" in (1|3|4|5) help ;; esac case "${state}" in (4) goerror ;; (1|2|3|5|6|7) exit 0 ;; esac # keep an inventary of the initial flist=/tmp/flist.txt dlist=/tmp/dlist.txt touch "${dlist}" "${flist}" markvirtual / # doesn't need /proc anymore getEarly discoverEarly inspect processCmdline export mounts=${mounts:-"${root}"} chmodAll a-w inspect if [ -n "${modules}" ]; then findModules fi inspect ############################# MAP PARTITIONS AND GO SUSPEND goNodev goLuks inspect if [ -n "${modules}" ]; then findModules fi inspect goSuspend2 inspect ############################# PERFORM DEVICE MOUNTS AND SWITCH ROOT chmodAll ug+w # should not return goNewroot parachute "SWITCH TO NEWROOT failed"