mkimage revision 1.13
11.1Sagc#! /bin/sh
21.1Sagc
31.13Schristos# $NetBSD: mkimage,v 1.13 2013/01/16 23:27:34 christos Exp $
41.13Schristos#
51.1Sagc# Copyright (c) 2012 Alistair Crooks <agc@NetBSD.org>
61.1Sagc# All rights reserved.
71.1Sagc#
81.1Sagc# Redistribution and use in source and binary forms, with or without
91.1Sagc# modification, are permitted provided that the following conditions
101.1Sagc# are met:
111.1Sagc# 1. Redistributions of source code must retain the above copyright
121.1Sagc#    notice, this list of conditions and the following disclaimer.
131.1Sagc# 2. Redistributions in binary form must reproduce the above copyright
141.1Sagc#    notice, this list of conditions and the following disclaimer in the
151.1Sagc#    documentation and/or other materials provided with the distribution.
161.1Sagc#
171.1Sagc# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
181.1Sagc# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
191.1Sagc# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
201.1Sagc# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
211.1Sagc# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
221.1Sagc# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
231.1Sagc# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
241.1Sagc# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
251.1Sagc# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
261.1Sagc# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
271.1Sagc#
281.1Sagc
291.1Sagc# find next available vnd, from kre
301.1Sagcnext_avail ()
311.1Sagc{
321.1Sagc	local dev="$1"
331.1Sagc	local N=$(( ${#dev} + 1 ))
341.1Sagc	local unit units
351.1Sagc
361.1Sagc	units=$(
371.1Sagc		sysctl -n hw.disknames		|
381.1Sagc			tr ' ' '\012'		|
391.1Sagc			grep '^'"${dev}"'[0-9]'	|
401.1Sagc			sort -n -k 1.$N			)
411.1Sagc
421.1Sagc	test -z "${units}" && {
431.1Sagc		test -e "/dev/${dev}0a" || {
441.1Sagc			echo >&2 "No ${dev}s available!"
451.1Sagc			return 1
461.1Sagc		}
471.1Sagc		echo "${dev}0"
481.1Sagc		return
491.1Sagc	}
501.1Sagc
511.1Sagc	N=0
521.1Sagc	for unit in ${units}
531.1Sagc	do
541.1Sagc		if [ "${unit}" = "${dev}${N}" ]
551.1Sagc		then
561.1Sagc			N=$(( N + 1 ))
571.1Sagc		else
581.1Sagc			echo "${dev}${N}"
591.1Sagc			return
601.1Sagc		fi
611.1Sagc	done
621.1Sagc
631.1Sagc	test -e /dev/"${dev}${N}a" || {
641.1Sagc		echo >&2 "All ${dev}s in use"
651.1Sagc		return 1
661.1Sagc	}
671.1Sagc
681.1Sagc	echo "${dev}${N}"
691.1Sagc}
701.1Sagc
711.12Schristosusage() {
721.12Schristos	cat << EOF 1>&2
731.12SchristosUsage: $PROG [-S <setsdir>] [-c <custom-files-dir>] [-h <host-arch>] [-s <size>]
741.12SchristosEOF
751.13Schristos	exit 1
761.12Schristos}
771.12Schristos
781.12Schristos# Return the filesystem size for an ls -l or tar -xvf list
791.12Schristos# Directories and symlinks in tar are 0 size, we assume one block
801.12Schristos# (which is too much), we round up by the fragment size the rest.
811.12Schristosgetfssize() {
821.12Schristos	local bsize="$1"
831.12Schristos	local fsize="$2"
841.12Schristos
851.12Schristos	awk -v fsize=${fsize} -v bsize=${bsize} '
861.12Schristos	NF >= 9 && $1 != "tar:" {
871.12Schristos		if ($5 == 0)
881.12Schristos			tot += bsize;
891.12Schristos		else
901.12Schristos			tot += ((int)(($5 + fsize - 1) / fsize)) * fsize;
911.12Schristos	}
921.12Schristos	END {
931.12Schristos		printf("%d\n", tot);
941.12Schristos	}'
951.12Schristos}
961.12Schristos
971.1Sagc# find the size of the gzipped files in a .tgz archive
981.11Schristos# Directories appear as 0, so count them as one block
991.11Schristos# and round up files to a fragment.
1001.1Sagcsizeone() {
1011.4Schristos	if [ ! -f "$1" ]
1021.4Schristos	then
1031.4Schristos		echo "$PROG: Missing set $1" 1>&2
1041.4Schristos		echo 0
1051.4Schristos		return;
1061.4Schristos	fi
1071.12Schristos
1081.1Sagc        case "$1" in 
1091.11Schristos        *.tgz|*.tar.gz|*.tbz|*.tar.bz2|*.txz|*.tar.xz)
1101.12Schristos                tar tvzf "$1" | getfssize ${bsize} ${fsize}
1111.1Sagc                ;;
1121.1Sagc        *)
1131.1Sagc                echo 0
1141.1Sagc                ;; 
1151.1Sagc        esac
1161.1Sagc}
1171.1Sagc
1181.4Schristos
1191.11Schristos# Return the usable filesystem size in bytes, given the total size in bytes,
1201.11Schristos# and optionally block and fragment sizes
1211.11Schristosgetffssize() {
1221.11Schristos	local bytes="$1"
1231.11Schristos	local barg
1241.11Schristos	local farg
1251.11Schristos	local overhead
1261.11Schristos
1271.11Schristos	if [ -n "$2" ]
1281.11Schristos	then
1291.11Schristos		barg="-b $2"
1301.11Schristos		if [ -n "$3" ]
1311.11Schristos		then
1321.11Schristos			farg="-f $3"
1331.11Schristos		fi
1341.11Schristos	fi
1351.11Schristos
1361.11Schristos	overhead=$(newfs -N ${farg} ${barg} -s "${bytes}b" -F /dev/null |
1371.11Schristos	    awk '/using/ {
1381.11Schristos		printf("%d\n", substr($6, 1, length($6) - 3) * 1024 * 1024);
1391.11Schristos	    }'
1401.11Schristos	)
1411.11Schristos	echo $(( ${bytes} - ${overhead} ))
1421.11Schristos}
1431.11Schristos
1441.11Schristos# Compute the size of an ffs filesystem that can fit x bytes.
1451.11Schristos# Instead of duplicating the newfs calculations here we let
1461.11Schristos# it do the job, using binary search.
1471.11Schristosmakeffssize() {
1481.11Schristos	local bytes=$1
1491.11Schristos	local bsize=$2
1501.11Schristos	local fsize=$3
1511.11Schristos	local max=$(( 2 * ${bytes} ))
1521.11Schristos	local min="${bytes}"
1531.11Schristos	local cur
1541.11Schristos	local res
1551.11Schristos	while true; do
1561.11Schristos		cur="$(( ( ${max} + ${min} ) / 2 ))"
1571.11Schristos		res="$(getffssize "${cur}" ${bsize} ${fsize})"
1581.11Schristos#		echo ${min} ${cur} ${max} ${res} ${bytes} 1>&2
1591.11Schristos		if [ "${res}" -eq "${bytes}" ]
1601.11Schristos		then
1611.11Schristos		    break
1621.11Schristos		elif [ "$(( ${min} + 1 ))" -ge "${max}" ]
1631.11Schristos		then
1641.11Schristos		    break
1651.11Schristos		elif [ "${res}" -lt "${bytes}" ]
1661.11Schristos		then
1671.11Schristos		    min="${cur}"
1681.11Schristos		elif [ "${res}" -gt "${bytes}" ]
1691.11Schristos		then
1701.11Schristos		    max="${cur}"
1711.11Schristos		fi
1721.11Schristos	done
1731.11Schristos	echo "${cur}"
1741.11Schristos}
1751.11Schristos
1761.4Schristosfinish() {
1771.13Schristos	cleanup
1781.13Schristos	${sudo} umount ${mnt}
1791.13Schristos	${sudo} vnconfig -u ${vnddev}
1801.13Schristos	rm -fr ${mnt}
1811.4Schristos}
1821.4Schristos
1831.11Schristos
1841.4SchristosDIR="$(dirname "$0")"
1851.4SchristosPROG="$(basename "$0")"
1861.2Sagcbar="==="
1871.4Schristossudo=
1881.7Schristosmnt="${TMPDIR:-/tmp}/image.$$"
1891.4Schristossrc="/usr/src"
1901.4Schristosobj="/usr/obj"
1911.4Schristos
1921.13Schristossets="base comp etc games man misc modules text"
1931.13Schristosxsets="xbase xcomp xetc xfont xserver"
1941.11Schristos
1951.11Schristos# Presumable block and fragment size.
1961.11Schristosbsize=16384
1971.11Schristosfsize=2048
1981.12Schristosmtob=$(( 1024 * 1024 ))
1991.11Schristos
2001.4Schristos# First pass for options to get the host
2011.4SchristosOPTS="S:c:h:s:x"
2021.4Schristoswhile getopts "$OPTS" f
2031.4Schristosdo
2041.4Schristos	case $f in
2051.4Schristos	h)	h="$OPTARG";;
2061.4Schristos	*)	;;
2071.4Schristos	esac
2081.4Schristosdone
2091.4Schristos
2101.4Schristosif [ -z "$h" ]
2111.4Schristosthen
2121.4Schristos	usage
2131.4Schristosfi
2141.4Schristos
2151.5Schristosif [ ! -f "${DIR}/conf/${h}.conf" ]
2161.4Schristosthen
2171.5Schristos	echo $PROG: ${DIR}/conf/${h}.conf is not present 1>&2
2181.4Schristos	exit 1
2191.4Schristosfi
2201.4Schristos
2211.5Schristos. "${DIR}/conf/${h}.conf"
2221.4Schristos
2231.4SchristosOPTIND=1
2241.4Schristoswhile getopts "$OPTS" f
2251.4Schristosdo
2261.4Schristos	case $f in
2271.4Schristos	S)	setsdir="$OPTARG";;
2281.4Schristos	c)	custom="$OPTARG";;
2291.4Schristos	h)	;;
2301.4Schristos	s)	size="$OPTARG";;
2311.13Schristos	x)	sets="$sets $xsets";;
2321.6Schristos	*)	usage;;
2331.1Sagc	esac
2341.1Sagcdone
2351.1Sagc
2361.8Sjmcneilltrap finish 0 1 2 3 15
2371.8Sjmcneill
2381.4Schristosshift $(( "$OPTIND" - 1 ))
2391.4Schristosif [ -n "$1" ]; then
2401.1Sagc	# take the next argument as being the image name
2411.1Sagc	image="$1"
2421.1Sagc	shift
2431.1Sagcfi
2441.1Sagc
2451.11Schristos# calculate the set bytes
2461.11Schristossetbytes=0
2471.11Schristosecho -n "${bar} computing set sizes ("
2481.13Schristosb=
2491.1Sagcfor s in ${sets}; do
2501.4Schristos	one="$(sizeone ${setsdir}/${s}.tgz)"
2511.13Schristos	echo -n "$b$s=$(( ${one} / ${mtob} ))MB"
2521.11Schristos	setbytes=$(( ${setbytes} +  ${one} ))
2531.13Schristos	b=" "
2541.1Sagcdone
2551.12Schristosecho "): $(( ${setbytes} / ${mtob} ))MB ${bar}"
2561.11Schristos
2571.2Sagc# calculate size of custom files
2581.11Schristoscustbytes=0
2591.2Sagcif [ -d "${custom}" ]; then
2601.12Schristos	custbytes=$(ls -lR "${custom}" | getfssize ${bsize} ${fsize})
2611.2Sagcfi
2621.12Schristosecho "${bar} computing custom sizes: $(( ${custbytes} / ${mtob} ))MB ${bar}"
2631.11Schristos
2641.11Schristos# how many bytes
2651.11Schristosrawbytes="$(( ${setbytes} + ${custbytes} ))"
2661.12Schristosecho -n "${bar} computing ffs filesystem size for $(( ${rawbytes} / ${mtob} ))MB: "
2671.11Schristosffsbytes="$(makeffssize "${rawbytes}")"
2681.12Schristosffsmb=$(( ${ffsbytes} / ${mtob} ))
2691.11Schristosecho " ${ffsmb}MB ${bar}"
2701.11Schristos
2711.11Schristos# total in MB
2721.11Schristostotal=$(( ${ffsmb} + ${overhead} ))
2731.11Schristosecho "${bar} overhead: ${overhead}MB ${bar}"
2741.11Schristos
2751.1Sagcif [ $size -eq 0 ]; then
2761.1Sagc        # auto-size the pkgs fs
2771.10Schristos        newsize=${total}
2781.1Sagcelse
2791.1Sagc        # check that we've been given enough space
2801.1Sagc        if [ ${total} -gt ${size} ]; then
2811.4Schristos                echo "$PROG: Given size is ${size} MB, but we need ${total} MB" >&2
2821.1Sagc                exit 1
2831.1Sagc        fi
2841.10Schristos	newsize=${size}
2851.1Sagcfi
2861.1Sagc
2871.10Schristosecho "${bar} making a new ${newsize} MB image in ${image} ${bar}"
2881.10Schristosdd if=/dev/zero of=${image} bs=1m count=${newsize} conv=sparse
2891.1Sagc
2901.1Sagcvnddev=$(next_avail vnd)
2911.1Sagcecho "${bar} mounting image via vnd ${vnddev} ${bar}"
2921.2Sagc${sudo} vnconfig ${vnddev} ${image}
2931.4Schristos${sudo} mkdir -p ${mnt}
2941.4Schristosmake_filesystems
2951.4Schristos
2961.4Schristos${sudo} mkdir -p ${mnt}/etc ${mnt}/dev
2971.1Sagc
2981.10Schristosecho -n "${bar} installing sets:"
2991.4Schristos(cd ${mnt} &&
3001.1Sagc	for s in ${sets}; do
3011.10Schristos		ss="${setsdir}/${s}.tgz"
3021.10Schristos		if [ -f "${ss}" ]; then
3031.10Schristos			echo -n " ${s}"
3041.10Schristos			${sudo} tar xpzf "${ss}"
3051.4Schristos		fi
3061.1Sagc	done
3071.1Sagc)
3081.10Schristosecho " ${bar}"
3091.1Sagc
3101.1Sagcecho "${bar} performing customisations ${bar}"
3111.1Sagc
3121.4Schristosmake_fstab
3131.1Sagc
3141.4Schristos${sudo} cat > ${mnt}/etc/rc.conf << EOF
3151.1Sagc#
3161.1Sagc# see rc.conf(5) for more information.
3171.1Sagc#
3181.1Sagc# Use program=YES to enable program, NO to disable it. program_flags are
3191.1Sagc# passed to the program on the command line.
3201.1Sagc#
3211.1Sagc
3221.1Sagc# Load the defaults in from /etc/defaults/rc.conf (if it's readable).
3231.1Sagc# These can be overridden below.
3241.1Sagc#
3251.1Sagcif [ -r /etc/defaults/rc.conf ]; then
3261.1Sagc        . /etc/defaults/rc.conf
3271.1Sagcfi
3281.1Sagc
3291.1Sagc# If this is not set to YES, the system will drop into single-user mode.
3301.1Sagc#
3311.1Sagcrc_configured=YES
3321.1Sagc
3331.4Schristoshostname=${h}
3341.1Sagc
3351.4SchristosEOF
3361.1Sagc
3371.4Schristoscustomize
3381.1Sagc
3391.1Sagcfor d in ${specialdirs}; do
3401.4Schristos	${sudo} mkdir -p ${mnt}/${d}
3411.1Sagcdone
3421.1Sagc
3431.4Schristosif [ \( -n "${custom}" \) -a \( -d "${custom}" \) ]; then
3441.2Sagc	echo "${bar} user customisations from files in ${custom} ${bar}"
3451.4Schristos	(cd ${custom} && ${sudo} pax -rwpe . ${mnt})
3461.2Sagcfi
3471.1Sagc
3481.1Sagcexit 0
349