mkimage revision 1.11
11.1Sagc#! /bin/sh
21.1Sagc
31.11Schristos# $NetBSD: mkimage,v 1.11 2013/01/15 21:04:41 christos Exp $
41.1Sagc
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.1Sagc# find the size of the gzipped files in a .tgz archive
721.11Schristos# Directories appear as 0, so count them as one block
731.11Schristos# and round up files to a fragment.
741.1Sagcsizeone() {
751.4Schristos	if [ ! -f "$1" ]
761.4Schristos	then
771.4Schristos		echo "$PROG: Missing set $1" 1>&2
781.4Schristos		echo 0
791.4Schristos		return;
801.4Schristos	fi
811.1Sagc        case "$1" in 
821.11Schristos        *.tgz|*.tar.gz|*.tbz|*.tar.bz2|*.txz|*.tar.xz)
831.11Schristos                tar tvzf "$1" |
841.11Schristos		awk -v fsize=${fsize} -v bsize=${bsize} '
851.11Schristos		{
861.11Schristos			if ($5 == 0)
871.11Schristos				tot += bsize;
881.11Schristos			else
891.11Schristos				tot += ((int)(($5 + fsize - 1) / fsize)) * fsize;
901.11Schristos		}
911.11Schristos		END {
921.11Schristos			printf("%d\n", tot);
931.11Schristos		}'
941.1Sagc                ;;
951.1Sagc        *)
961.1Sagc                echo 0
971.1Sagc                ;; 
981.1Sagc        esac
991.1Sagc}
1001.1Sagc
1011.4Schristosusage() {
1021.4Schristos	cat << EOF 1>&2
1031.4SchristosUsage: $PROG [-S <setsdir>] [-c <custom-files-dir>] [-h <host-arch>] [-s <size>]
1041.4SchristosEOF
1051.4Schristosexit 1
1061.4Schristos}
1071.4Schristos
1081.11Schristos# Return the usable filesystem size in bytes, given the total size in bytes,
1091.11Schristos# and optionally block and fragment sizes
1101.11Schristosgetffssize() {
1111.11Schristos	local bytes="$1"
1121.11Schristos	local barg
1131.11Schristos	local farg
1141.11Schristos	local overhead
1151.11Schristos
1161.11Schristos	if [ -n "$2" ]
1171.11Schristos	then
1181.11Schristos		barg="-b $2"
1191.11Schristos		if [ -n "$3" ]
1201.11Schristos		then
1211.11Schristos			farg="-f $3"
1221.11Schristos		fi
1231.11Schristos	fi
1241.11Schristos
1251.11Schristos	overhead=$(newfs -N ${farg} ${barg} -s "${bytes}b" -F /dev/null |
1261.11Schristos	    awk '/using/ {
1271.11Schristos		printf("%d\n", substr($6, 1, length($6) - 3) * 1024 * 1024);
1281.11Schristos	    }'
1291.11Schristos	)
1301.11Schristos	echo $(( ${bytes} - ${overhead} ))
1311.11Schristos}
1321.11Schristos
1331.11Schristos# Compute the size of an ffs filesystem that can fit x bytes.
1341.11Schristos# Instead of duplicating the newfs calculations here we let
1351.11Schristos# it do the job, using binary search.
1361.11Schristosmakeffssize() {
1371.11Schristos	local bytes=$1
1381.11Schristos	local bsize=$2
1391.11Schristos	local fsize=$3
1401.11Schristos	local max=$(( 2 * ${bytes} ))
1411.11Schristos	local min="${bytes}"
1421.11Schristos	local cur
1431.11Schristos	local res
1441.11Schristos	while true; do
1451.11Schristos		cur="$(( ( ${max} + ${min} ) / 2 ))"
1461.11Schristos		res="$(getffssize "${cur}" ${bsize} ${fsize})"
1471.11Schristos#		echo ${min} ${cur} ${max} ${res} ${bytes} 1>&2
1481.11Schristos		if [ "${res}" -eq "${bytes}" ]
1491.11Schristos		then
1501.11Schristos		    break
1511.11Schristos		elif [ "$(( ${min} + 1 ))" -ge "${max}" ]
1521.11Schristos		then
1531.11Schristos		    break
1541.11Schristos		elif [ "${res}" -lt "${bytes}" ]
1551.11Schristos		then
1561.11Schristos		    min="${cur}"
1571.11Schristos		elif [ "${res}" -gt "${bytes}" ]
1581.11Schristos		then
1591.11Schristos		    max="${cur}"
1601.11Schristos		fi
1611.11Schristos	done
1621.11Schristos	echo "${cur}"
1631.11Schristos}
1641.11Schristos
1651.4Schristosfinish() {
1661.4Schristos    cleanup
1671.4Schristos    ${sudo} umount ${mnt}
1681.4Schristos    ${sudo} vnconfig -u ${vnddev}
1691.4Schristos}
1701.4Schristos
1711.11Schristos
1721.4SchristosDIR="$(dirname "$0")"
1731.4SchristosPROG="$(basename "$0")"
1741.2Sagcbar="==="
1751.4Schristossudo=
1761.7Schristosmnt="${TMPDIR:-/tmp}/image.$$"
1771.4Schristossrc="/usr/src"
1781.4Schristosobj="/usr/obj"
1791.4Schristos
1801.11Schristos
1811.11Schristos# Presumable block and fragment size.
1821.11Schristosbsize=16384
1831.11Schristosfsize=2048
1841.11Schristos
1851.4Schristos# First pass for options to get the host
1861.4SchristosOPTS="S:c:h:s:x"
1871.4Schristoswhile getopts "$OPTS" f
1881.4Schristosdo
1891.4Schristos	case $f in
1901.4Schristos	h)	h="$OPTARG";;
1911.4Schristos	*)	;;
1921.4Schristos	esac
1931.4Schristosdone
1941.4Schristos
1951.4Schristosif [ -z "$h" ]
1961.4Schristosthen
1971.4Schristos	usage
1981.4Schristosfi
1991.4Schristos
2001.5Schristosif [ ! -f "${DIR}/conf/${h}.conf" ]
2011.4Schristosthen
2021.5Schristos	echo $PROG: ${DIR}/conf/${h}.conf is not present 1>&2
2031.4Schristos	exit 1
2041.4Schristosfi
2051.4Schristos
2061.5Schristos. "${DIR}/conf/${h}.conf"
2071.4Schristos
2081.4SchristosOPTIND=1
2091.4Schristoswhile getopts "$OPTS" f
2101.4Schristosdo
2111.4Schristos	case $f in
2121.4Schristos	S)	setsdir="$OPTARG";;
2131.4Schristos	c)	custom="$OPTARG";;
2141.4Schristos	h)	;;
2151.4Schristos	s)	size="$OPTARG";;
2161.4Schristos	x)	set -x;;
2171.6Schristos	*)	usage;;
2181.1Sagc	esac
2191.1Sagcdone
2201.1Sagc
2211.8Sjmcneilltrap finish 0 1 2 3 15
2221.8Sjmcneill
2231.4Schristosshift $(( "$OPTIND" - 1 ))
2241.4Schristosif [ -n "$1" ]; then
2251.1Sagc	# take the next argument as being the image name
2261.1Sagc	image="$1"
2271.1Sagc	shift
2281.1Sagcfi
2291.1Sagc
2301.11Schristos# calculate the set bytes
2311.11Schristossetbytes=0
2321.11Schristosecho -n "${bar} computing set sizes ("
2331.1Sagcfor s in ${sets}; do
2341.4Schristos	one="$(sizeone ${setsdir}/${s}.tgz)"
2351.11Schristos	echo -n " $s=$(( ${one} / 1024 / 1024 ))MB"
2361.11Schristos	setbytes=$(( ${setbytes} +  ${one} ))
2371.1Sagcdone
2381.11Schristosecho "): $(( ${setbytes} / 1024 / 1024 ))MB ${bar}"
2391.11Schristos
2401.2Sagc# calculate size of custom files
2411.11Schristoscustbytes=0
2421.2Sagcif [ -d "${custom}" ]; then
2431.11Schristos	custbytes=$(ls -lR "${custom}" | 
2441.11Schristos	    awk 'NF == 9 { tot += $5 } END { print tot }')
2451.2Sagcfi
2461.11Schristosecho "${bar} computing custom sizes: $(( ${custbytes} / 1024 / 1024 ))MB ${bar}"
2471.11Schristos
2481.11Schristos# how many bytes
2491.11Schristosrawbytes="$(( ${setbytes} + ${custbytes} ))"
2501.11Schristosecho -n "${bar} computing ffs filesystem size for $(( ${rawbytes} / 1024 / 1024 ))MB: "
2511.11Schristosffsbytes="$(makeffssize "${rawbytes}")"
2521.11Schristosffsmb=$(( ${ffsbytes} / 1024 / 1024 ))
2531.11Schristosecho " ${ffsmb}MB ${bar}"
2541.11Schristos
2551.11Schristos# total in MB
2561.11Schristostotal=$(( ${ffsmb} + ${overhead} ))
2571.11Schristosecho "${bar} overhead: ${overhead}MB ${bar}"
2581.11Schristos
2591.1Sagcif [ $size -eq 0 ]; then
2601.1Sagc        # auto-size the pkgs fs
2611.10Schristos        newsize=${total}
2621.1Sagcelse
2631.1Sagc        # check that we've been given enough space
2641.1Sagc        if [ ${total} -gt ${size} ]; then
2651.4Schristos                echo "$PROG: Given size is ${size} MB, but we need ${total} MB" >&2
2661.1Sagc                exit 1
2671.1Sagc        fi
2681.10Schristos	newsize=${size}
2691.1Sagcfi
2701.1Sagc
2711.10Schristosecho "${bar} making a new ${newsize} MB image in ${image} ${bar}"
2721.10Schristosdd if=/dev/zero of=${image} bs=1m count=${newsize} conv=sparse
2731.1Sagc
2741.1Sagcvnddev=$(next_avail vnd)
2751.1Sagcecho "${bar} mounting image via vnd ${vnddev} ${bar}"
2761.2Sagc${sudo} vnconfig ${vnddev} ${image}
2771.4Schristos${sudo} mkdir -p ${mnt}
2781.4Schristosmake_filesystems
2791.4Schristos
2801.4Schristos${sudo} mkdir -p ${mnt}/etc ${mnt}/dev
2811.1Sagc
2821.10Schristosecho -n "${bar} installing sets:"
2831.4Schristos(cd ${mnt} &&
2841.1Sagc	for s in ${sets}; do
2851.10Schristos		ss="${setsdir}/${s}.tgz"
2861.10Schristos		if [ -f "${ss}" ]; then
2871.10Schristos			echo -n " ${s}"
2881.10Schristos			${sudo} tar xpzf "${ss}"
2891.4Schristos		fi
2901.1Sagc	done
2911.1Sagc)
2921.10Schristosecho " ${bar}"
2931.1Sagc
2941.1Sagcecho "${bar} performing customisations ${bar}"
2951.1Sagc
2961.4Schristosmake_fstab
2971.1Sagc
2981.4Schristos${sudo} cat > ${mnt}/etc/rc.conf << EOF
2991.1Sagc#
3001.1Sagc# see rc.conf(5) for more information.
3011.1Sagc#
3021.1Sagc# Use program=YES to enable program, NO to disable it. program_flags are
3031.1Sagc# passed to the program on the command line.
3041.1Sagc#
3051.1Sagc
3061.1Sagc# Load the defaults in from /etc/defaults/rc.conf (if it's readable).
3071.1Sagc# These can be overridden below.
3081.1Sagc#
3091.1Sagcif [ -r /etc/defaults/rc.conf ]; then
3101.1Sagc        . /etc/defaults/rc.conf
3111.1Sagcfi
3121.1Sagc
3131.1Sagc# If this is not set to YES, the system will drop into single-user mode.
3141.1Sagc#
3151.1Sagcrc_configured=YES
3161.1Sagc
3171.4Schristoshostname=${h}
3181.1Sagc
3191.4SchristosEOF
3201.1Sagc
3211.4Schristoscustomize
3221.1Sagc
3231.1Sagcfor d in ${specialdirs}; do
3241.4Schristos	${sudo} mkdir -p ${mnt}/${d}
3251.1Sagcdone
3261.1Sagc
3271.4Schristosif [ \( -n "${custom}" \) -a \( -d "${custom}" \) ]; then
3281.2Sagc	echo "${bar} user customisations from files in ${custom} ${bar}"
3291.4Schristos	(cd ${custom} && ${sudo} pax -rwpe . ${mnt})
3301.2Sagcfi
3311.1Sagc
3321.1Sagcexit 0
333