1#!/bin/sh 2# 3# Copyright (c) 1999 Matt Dillon 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions 8# are met: 9# 1. Redistributions of source code must retain the above copyright 10# notice, this list of conditions and the following disclaimer. 11# 2. Redistributions in binary form must reproduce the above copyright 12# notice, this list of conditions and the following disclaimer in the 13# documentation and/or other materials provided with the distribution. 14# 15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25# SUCH DAMAGE. 26# 27 28# On entry to this script the entire system consists of a read-only root 29# mounted via NFS. The kernel has run BOOTP and configured an interface 30# (otherwise it would not have been able to mount the NFS root!) 31# 32# We use the contents of /conf to create and populate memory filesystems 33# that are mounted on top of this root to implement the writable 34# (and host-specific) parts of the root filesystem, and other volatile 35# filesystems. 36# 37# The hierarchy in /conf has the form /conf/T/M/ where M are directories 38# for which memory filesystems will be created and filled, 39# and T is one of the "template" directories below: 40# 41# base universal base, typically a replica of the original root; 42# default secondary universal base, typically overriding some 43# of the files in the original root; 44# ${ipba} where ${ipba} is the assigned broadcast IP address 45# bcast/${ipba} same as above 46# ${class} where ${class} is a list of directories supplied by 47# bootp/dhcp through the T134 option. 48# ${ipba} and ${class} are typically used to configure features 49# for group of diskless clients, or even individual features; 50# ${ip} where ${ip} is the machine's assigned IP address, typically 51# used to set host-specific features; 52# ip/${ip} same as above 53# 54# Template directories are scanned in the order they are listed above, 55# with each successive directory overriding (merged into) the previous one; 56# non-existing directories are ignored. The subdirectory forms exist to 57# help keep the top level /conf manageable in large installations. 58# 59# The existence of a directory /conf/T/M causes this script to create a 60# memory filesystem mounted as /M on the client. 61# 62# Some files in /conf have special meaning, namely: 63# 64# Filename Action 65# ---------------------------------------------------------------- 66# /conf/T/M/remount 67# The contents of the file is a mount command. E.g. if 68# /conf/1.2.3.4/foo/remount contains "mount -o ro /dev/ad0s3", 69# then /dev/ad0s3 will be mounted on /conf/1.2.3.4/foo/ 70# 71# /conf/T/M/remount_optional 72# If this file exists, then failure to execute the mount 73# command contained in /conf/T/M/remount is non-fatal. 74# 75# /conf/T/M/remount_subdir 76# If this file exists, then the behaviour of /conf/T/M/remount 77# changes as follows: 78# 1. /conf/T/M/remount is invoked to mount the root of the 79# filesystem where the configuration data exists on a 80# temporary mountpoint. 81# 2. /conf/T/M/remount_subdir is then invoked to mount a 82# *subdirectory* of the filesystem mounted by 83# /conf/T/M/remount on /conf/T/M/. 84# 85# /conf/T/M/diskless_remount 86# The contents of the file points to an NFS filesystem, 87# possibly followed by mount_nfs options. If the server name 88# is omitted, the script will prepend the root path used when 89# booting. E.g. if you booted from foo.com:/path/to/root, 90# an entry for /conf/base/etc/diskless_remount could be any of 91# foo.com:/path/to/root/etc 92# /etc -o ro 93# Because mount_nfs understands ".." in paths, it is 94# possible to mount from locations above the NFS root with 95# paths such as "/../../etc". 96# 97# /conf/T/M/md_size 98# The contents of the file specifies the size of the memory 99# filesystem to be created, in 512 byte blocks. 100# The default size is 10240 blocks (5MB). E.g. if 101# /conf/base/etc/md_size contains "30000" then a 15MB MFS 102# will be created. In case of multiple entries for the same 103# directory M, the last one in the scanning order is used. 104# NOTE: If you only need to create a memory filesystem but not 105# initialize it from a template, it is preferable to specify 106# it in fstab e.g. as "md /tmp mfs -s=30m,rw 0 0" 107# 108# /conf/T/SUBDIR.cpio.gz 109# The file is cpio'd into /SUBDIR (and a memory filesystem is 110# created for /SUBDIR if necessary). The presence of this file 111# prevents the copy from /conf/T/SUBDIR/ 112# 113# /conf/T/M/extract 114# This is alternative to SUBDIR.cpio.gz and remount. 115# Similar to remount case, a memory filesystem is created 116# for /M and initialized from a template but no mounting 117# performed. Instead, this file is run passing /M as single 118# argument. It is expected to extract template override to /M 119# using auxiliary storage found in some embedded systems 120# having NVRAM too small to hold mountable file system. 121# 122# /conf/T/SUBDIR.remove 123# The list of paths contained in the file are rm -rf'd 124# relative to /SUBDIR. 125# 126# /conf/diskless_remount 127# Similar to /conf/T/M/diskless_remount above, but allows 128# all of /conf to be remounted. This can be used to allow 129# multiple roots to share the same /conf. 130# 131# 132# You will almost universally want to create the following files under /conf 133# 134# File Content 135# ---------------------------- ---------------------------------- 136# /conf/base/etc/md_size size of /etc filesystem 137# /conf/base/etc/diskless_remount "/etc" 138# /conf/default/etc/rc.conf generic diskless config parameters 139# /conf/default/etc/fstab generic diskless fstab e.g. like this 140# 141# foo:/root_part / nfs ro 0 0 142# foo:/usr_part /usr nfs ro 0 0 143# foo:/home_part /home nfs rw 0 0 144# md /tmp mfs -s=30m,rw 0 0 145# md /var mfs -s=30m,rw 0 0 146# proc /proc procfs rw 0 0 147# 148# plus, possibly, overrides for password files etc. 149# 150# NOTE! /var, /tmp, and /dev will be typically created elsewhere, e.g. 151# as entries in the fstab as above. 152# Those filesystems should not be specified in /conf. 153# 154# (end of documentation, now get to the real code) 155 156dlv=`/sbin/sysctl -n vfs.nfs.diskless_valid 2> /dev/null` 157 158# DEBUGGING 159# log something on stdout if verbose. 160o_verbose=0 # set to 1 or 2 if you want more debugging 161log() { 162 [ ${o_verbose} -gt 0 ] && echo "*** $* ***" 163 [ ${o_verbose} -gt 1 ] && read -p "=== Press enter to continue" foo 164} 165 166# chkerr: 167# 168# Routine to check for error 169# 170# checks error code and drops into shell on failure. 171# if shell exits, terminates script as well as /etc/rc. 172# if remount_optional exists under the mountpoint, skip this check. 173# 174chkerr() { 175 lastitem () ( n=$(($# - 1)) ; shift $n ; echo $1 ) 176 mountpoint="$(lastitem $2)" 177 [ -r $mountpoint/remount_optional ] && ( echo "$2 failed: ignoring due to remount_optional" ; return ) 178 case $1 in 179 0) 180 ;; 181 *) 182 echo "$2 failed: dropping into /bin/sh" 183 /bin/sh 184 # RESUME 185 ;; 186 esac 187} 188 189# The list of filesystems to umount after the copy 190to_umount="" 191 192handle_remount() { # $1 = mount point 193 local nfspt mountopts b 194 b=$1 195 log handle_remount $1 196 [ -d $b -a -f $b/diskless_remount ] || return 197 read nfspt mountopts < $b/diskless_remount 198 log "nfspt ${nfspt} mountopts ${mountopts}" 199 # prepend the nfs root if not present 200 [ `expr "$nfspt" : '\(.\)'` = "/" ] && nfspt="${nfsroot}${nfspt}" 201 mount_nfs $mountopts $nfspt $b 202 chkerr $? "mount_nfs $nfspt $b" 203 to_umount="$b ${to_umount}" 204} 205 206# Create a generic memory disk. 207# The 'auto' parameter will attempt to use tmpfs(4), falls back to md(4). 208# $1 is size in 512-byte sectors, $2 is the mount point. 209mount_md() { 210 if [ ${o_verbose} -gt 0 ] ; then 211 /sbin/mdmfs -XL -S -s $1 auto $2 212 else 213 /sbin/mdmfs -S -s $1 auto $2 214 fi 215} 216 217# Create the memory filesystem if it has not already been created 218# 219create_md() { 220 [ "x`eval echo \\$md_created_$1`" = "x" ] || return # only once 221 if [ "x`eval echo \\$md_size_$1`" = "x" ]; then 222 md_size=10240 223 else 224 md_size=`eval echo \\$md_size_$1` 225 fi 226 log create_md $1 with size $md_size 227 mount_md $md_size /$1 228 /bin/chmod 755 /$1 229 eval md_created_$1=created 230} 231 232# DEBUGGING 233# 234# set -v 235 236# Figure out our interface and IP. 237# 238bootp_ifc="" 239bootp_ipa="" 240bootp_ipbca="" 241class="" 242if [ ${dlv:=0} -ne 0 ] ; then 243 iflist=`ifconfig -l` 244 for i in ${iflist} ; do 245 set -- `ifconfig ${i}` 246 while [ $# -ge 1 ] ; do 247 if [ "${bootp_ifc}" = "" -a "$1" = "inet" ] ; then 248 bootp_ifc=${i} ; bootp_ipa=${2} ; shift 249 fi 250 if [ "${bootp_ipbca}" = "" -a "$1" = "broadcast" ] ; then 251 bootp_ipbca=$2; shift 252 fi 253 shift 254 done 255 if [ "${bootp_ifc}" != "" ] ; then 256 break 257 fi 258 done 259 # Get the values passed with the T134 bootp cookie. 260 class="`/sbin/sysctl -qn kern.bootp_cookie`" 261 262 echo "Interface ${bootp_ifc} IP-Address ${bootp_ipa} Broadcast ${bootp_ipbca} ${class}" 263fi 264 265log Figure out our NFS root path 266# 267set -- `mount -t nfs` 268while [ $# -ge 1 ] ; do 269 if [ "$2" = "on" -a "$3" = "/" ]; then 270 nfsroot="$1" 271 break 272 fi 273 shift 274done 275 276# The list of directories with template files 277templates="base default" 278if [ -n "${bootp_ipbca}" ]; then 279 templates="${templates} ${bootp_ipbca} bcast/${bootp_ipbca}" 280fi 281if [ -n "${class}" ]; then 282 templates="${templates} ${class}" 283fi 284if [ -n "${bootp_ipa}" ]; then 285 templates="${templates} ${bootp_ipa} ip/${bootp_ipa}" 286fi 287 288# If /conf/diskless_remount exists, remount all of /conf. 289handle_remount /conf 290 291# Resolve templates in /conf/base, /conf/default, /conf/${bootp_ipbca}, 292# and /conf/${bootp_ipa}. For each subdirectory found within these 293# directories: 294# 295# - calculate memory filesystem sizes. If the subdirectory (prior to 296# NFS remounting) contains the file 'md_size', the contents specified 297# in 512 byte sectors will be used to size the memory filesystem. Otherwise 298# 8192 sectors (4MB) is used. 299# 300# - handle NFS remounts. If the subdirectory contains the file 301# diskless_remount, the contents of the file is NFS mounted over 302# the directory. For example /conf/base/etc/diskless_remount 303# might contain 'myserver:/etc'. NFS remounts allow you to avoid 304# having to dup your system directories in /conf. Your server must 305# be sure to export those filesystems -alldirs, however. 306# If the diskless_remount file contains a string beginning with a 307# '/' it is assumed that the local nfsroot should be prepended to 308# it before attempting to the remount. This allows the root to be 309# relocated without needing to change the remount files. 310# 311log "templates are ${templates}" 312for i in ${templates} ; do 313 for j in /conf/$i/* ; do 314 [ -d $j ] || continue 315 316 # memory filesystem size specification 317 subdir=${j##*/} 318 [ -f $j/md_size ] && eval md_size_$subdir=`cat $j/md_size` 319 320 # remount. Beware, the command is in the file itself! 321 if [ -f $j/remount ]; then 322 if [ -f $j/remount_subdir ]; then 323 k="/conf.tmp/$i/$subdir" 324 [ -d $k ] || continue 325 326 # Mount the filesystem root where the config data is 327 # on the temporary mount point. 328 nfspt=`/bin/cat $j/remount` 329 $nfspt $k 330 chkerr $? "$nfspt $k" 331 332 # Now use a nullfs mount to get the data where we 333 # really want to see it. 334 remount_subdir=`/bin/cat $j/remount_subdir` 335 remount_subdir_cmd="mount -t nullfs $k/$remount_subdir" 336 337 $remount_subdir_cmd $j 338 chkerr $? "$remount_subdir_cmd $j" 339 340 # XXX check order -- we must force $k to be unmounted 341 # after j, as j depends on k. 342 to_umount="$j $k ${to_umount}" 343 else 344 nfspt=`/bin/cat $j/remount` 345 $nfspt $j 346 chkerr $? "$nfspt $j" 347 to_umount="$j ${to_umount}" # XXX hope it is really a mount! 348 fi 349 fi 350 351 # NFS remount 352 handle_remount $j 353 done 354done 355 356# - Create all required MFS filesystems and populate them from 357# our templates. Support both a direct template and a dir.cpio.gz 358# archive. Support for auxiliary NVRAM. Support dir.remove files containing 359# a list of relative paths to remove. 360# 361# The dir.cpio.gz form is there to make the copy process more efficient, 362# so if the cpio archive is present, it prevents the files from dir/ 363# from being copied. 364 365PATH=${PATH}:/rescue 366 367for i in ${templates} ; do 368 for j in /conf/$i/* ; do 369 subdir=${j##*/} 370 if [ -d $j -a ! -f $j.cpio.gz ]; then 371 create_md $subdir 372 cp -Rp $j/ /$subdir 373 fi 374 done 375 for j in /conf/$i/*.cpio.gz ; do 376 subdir=${j%*.cpio.gz} 377 subdir=${subdir##*/} 378 if [ -f $j ]; then 379 create_md $subdir 380 echo "Loading /$subdir from cpio archive $j" 381 (cd / ; tar -xpf $j) 382 fi 383 done 384 for j in /conf/$i/*/extract ; do 385 if [ -x $j ]; then 386 subdir=${j%*/extract} 387 subdir=${subdir##*/} 388 create_md $subdir 389 echo "Loading /$subdir using auxiliary command $j" 390 $j /$subdir 391 fi 392 done 393 for j in /conf/$i/*.remove ; do 394 subdir=${j%*.remove} 395 subdir=${subdir##*/} 396 if [ -f $j ]; then 397 # doubly sure it is a memory disk before rm -rf'ing 398 create_md $subdir 399 (cd /$subdir; rm -rf `/bin/cat $j`) 400 fi 401 done 402done 403 404# umount partitions used to fill the memory filesystems 405[ -n "${to_umount}" ] && umount $to_umount 406