mkimage revision 1.13
1#! /bin/sh
2
3# $NetBSD: mkimage,v 1.13 2013/01/16 23:27:34 christos Exp $
4#
5# Copyright (c) 2012 Alistair Crooks <agc@NetBSD.org>
6# All rights reserved.
7#
8# Redistribution and use in source and binary forms, with or without
9# modification, are permitted provided that the following conditions
10# are met:
11# 1. Redistributions of source code must retain the above copyright
12#    notice, this list of conditions and the following disclaimer.
13# 2. Redistributions in binary form must reproduce the above copyright
14#    notice, this list of conditions and the following disclaimer in the
15#    documentation and/or other materials provided with the distribution.
16#
17# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27#
28
29# find next available vnd, from kre
30next_avail ()
31{
32	local dev="$1"
33	local N=$(( ${#dev} + 1 ))
34	local unit units
35
36	units=$(
37		sysctl -n hw.disknames		|
38			tr ' ' '\012'		|
39			grep '^'"${dev}"'[0-9]'	|
40			sort -n -k 1.$N			)
41
42	test -z "${units}" && {
43		test -e "/dev/${dev}0a" || {
44			echo >&2 "No ${dev}s available!"
45			return 1
46		}
47		echo "${dev}0"
48		return
49	}
50
51	N=0
52	for unit in ${units}
53	do
54		if [ "${unit}" = "${dev}${N}" ]
55		then
56			N=$(( N + 1 ))
57		else
58			echo "${dev}${N}"
59			return
60		fi
61	done
62
63	test -e /dev/"${dev}${N}a" || {
64		echo >&2 "All ${dev}s in use"
65		return 1
66	}
67
68	echo "${dev}${N}"
69}
70
71usage() {
72	cat << EOF 1>&2
73Usage: $PROG [-S <setsdir>] [-c <custom-files-dir>] [-h <host-arch>] [-s <size>]
74EOF
75	exit 1
76}
77
78# Return the filesystem size for an ls -l or tar -xvf list
79# Directories and symlinks in tar are 0 size, we assume one block
80# (which is too much), we round up by the fragment size the rest.
81getfssize() {
82	local bsize="$1"
83	local fsize="$2"
84
85	awk -v fsize=${fsize} -v bsize=${bsize} '
86	NF >= 9 && $1 != "tar:" {
87		if ($5 == 0)
88			tot += bsize;
89		else
90			tot += ((int)(($5 + fsize - 1) / fsize)) * fsize;
91	}
92	END {
93		printf("%d\n", tot);
94	}'
95}
96
97# find the size of the gzipped files in a .tgz archive
98# Directories appear as 0, so count them as one block
99# and round up files to a fragment.
100sizeone() {
101	if [ ! -f "$1" ]
102	then
103		echo "$PROG: Missing set $1" 1>&2
104		echo 0
105		return;
106	fi
107
108        case "$1" in 
109        *.tgz|*.tar.gz|*.tbz|*.tar.bz2|*.txz|*.tar.xz)
110                tar tvzf "$1" | getfssize ${bsize} ${fsize}
111                ;;
112        *)
113                echo 0
114                ;; 
115        esac
116}
117
118
119# Return the usable filesystem size in bytes, given the total size in bytes,
120# and optionally block and fragment sizes
121getffssize() {
122	local bytes="$1"
123	local barg
124	local farg
125	local overhead
126
127	if [ -n "$2" ]
128	then
129		barg="-b $2"
130		if [ -n "$3" ]
131		then
132			farg="-f $3"
133		fi
134	fi
135
136	overhead=$(newfs -N ${farg} ${barg} -s "${bytes}b" -F /dev/null |
137	    awk '/using/ {
138		printf("%d\n", substr($6, 1, length($6) - 3) * 1024 * 1024);
139	    }'
140	)
141	echo $(( ${bytes} - ${overhead} ))
142}
143
144# Compute the size of an ffs filesystem that can fit x bytes.
145# Instead of duplicating the newfs calculations here we let
146# it do the job, using binary search.
147makeffssize() {
148	local bytes=$1
149	local bsize=$2
150	local fsize=$3
151	local max=$(( 2 * ${bytes} ))
152	local min="${bytes}"
153	local cur
154	local res
155	while true; do
156		cur="$(( ( ${max} + ${min} ) / 2 ))"
157		res="$(getffssize "${cur}" ${bsize} ${fsize})"
158#		echo ${min} ${cur} ${max} ${res} ${bytes} 1>&2
159		if [ "${res}" -eq "${bytes}" ]
160		then
161		    break
162		elif [ "$(( ${min} + 1 ))" -ge "${max}" ]
163		then
164		    break
165		elif [ "${res}" -lt "${bytes}" ]
166		then
167		    min="${cur}"
168		elif [ "${res}" -gt "${bytes}" ]
169		then
170		    max="${cur}"
171		fi
172	done
173	echo "${cur}"
174}
175
176finish() {
177	cleanup
178	${sudo} umount ${mnt}
179	${sudo} vnconfig -u ${vnddev}
180	rm -fr ${mnt}
181}
182
183
184DIR="$(dirname "$0")"
185PROG="$(basename "$0")"
186bar="==="
187sudo=
188mnt="${TMPDIR:-/tmp}/image.$$"
189src="/usr/src"
190obj="/usr/obj"
191
192sets="base comp etc games man misc modules text"
193xsets="xbase xcomp xetc xfont xserver"
194
195# Presumable block and fragment size.
196bsize=16384
197fsize=2048
198mtob=$(( 1024 * 1024 ))
199
200# First pass for options to get the host
201OPTS="S:c:h:s:x"
202while getopts "$OPTS" f
203do
204	case $f in
205	h)	h="$OPTARG";;
206	*)	;;
207	esac
208done
209
210if [ -z "$h" ]
211then
212	usage
213fi
214
215if [ ! -f "${DIR}/conf/${h}.conf" ]
216then
217	echo $PROG: ${DIR}/conf/${h}.conf is not present 1>&2
218	exit 1
219fi
220
221. "${DIR}/conf/${h}.conf"
222
223OPTIND=1
224while getopts "$OPTS" f
225do
226	case $f in
227	S)	setsdir="$OPTARG";;
228	c)	custom="$OPTARG";;
229	h)	;;
230	s)	size="$OPTARG";;
231	x)	sets="$sets $xsets";;
232	*)	usage;;
233	esac
234done
235
236trap finish 0 1 2 3 15
237
238shift $(( "$OPTIND" - 1 ))
239if [ -n "$1" ]; then
240	# take the next argument as being the image name
241	image="$1"
242	shift
243fi
244
245# calculate the set bytes
246setbytes=0
247echo -n "${bar} computing set sizes ("
248b=
249for s in ${sets}; do
250	one="$(sizeone ${setsdir}/${s}.tgz)"
251	echo -n "$b$s=$(( ${one} / ${mtob} ))MB"
252	setbytes=$(( ${setbytes} +  ${one} ))
253	b=" "
254done
255echo "): $(( ${setbytes} / ${mtob} ))MB ${bar}"
256
257# calculate size of custom files
258custbytes=0
259if [ -d "${custom}" ]; then
260	custbytes=$(ls -lR "${custom}" | getfssize ${bsize} ${fsize})
261fi
262echo "${bar} computing custom sizes: $(( ${custbytes} / ${mtob} ))MB ${bar}"
263
264# how many bytes
265rawbytes="$(( ${setbytes} + ${custbytes} ))"
266echo -n "${bar} computing ffs filesystem size for $(( ${rawbytes} / ${mtob} ))MB: "
267ffsbytes="$(makeffssize "${rawbytes}")"
268ffsmb=$(( ${ffsbytes} / ${mtob} ))
269echo " ${ffsmb}MB ${bar}"
270
271# total in MB
272total=$(( ${ffsmb} + ${overhead} ))
273echo "${bar} overhead: ${overhead}MB ${bar}"
274
275if [ $size -eq 0 ]; then
276        # auto-size the pkgs fs
277        newsize=${total}
278else
279        # check that we've been given enough space
280        if [ ${total} -gt ${size} ]; then
281                echo "$PROG: Given size is ${size} MB, but we need ${total} MB" >&2
282                exit 1
283        fi
284	newsize=${size}
285fi
286
287echo "${bar} making a new ${newsize} MB image in ${image} ${bar}"
288dd if=/dev/zero of=${image} bs=1m count=${newsize} conv=sparse
289
290vnddev=$(next_avail vnd)
291echo "${bar} mounting image via vnd ${vnddev} ${bar}"
292${sudo} vnconfig ${vnddev} ${image}
293${sudo} mkdir -p ${mnt}
294make_filesystems
295
296${sudo} mkdir -p ${mnt}/etc ${mnt}/dev
297
298echo -n "${bar} installing sets:"
299(cd ${mnt} &&
300	for s in ${sets}; do
301		ss="${setsdir}/${s}.tgz"
302		if [ -f "${ss}" ]; then
303			echo -n " ${s}"
304			${sudo} tar xpzf "${ss}"
305		fi
306	done
307)
308echo " ${bar}"
309
310echo "${bar} performing customisations ${bar}"
311
312make_fstab
313
314${sudo} cat > ${mnt}/etc/rc.conf << EOF
315#
316# see rc.conf(5) for more information.
317#
318# Use program=YES to enable program, NO to disable it. program_flags are
319# passed to the program on the command line.
320#
321
322# Load the defaults in from /etc/defaults/rc.conf (if it's readable).
323# These can be overridden below.
324#
325if [ -r /etc/defaults/rc.conf ]; then
326        . /etc/defaults/rc.conf
327fi
328
329# If this is not set to YES, the system will drop into single-user mode.
330#
331rc_configured=YES
332
333hostname=${h}
334
335EOF
336
337customize
338
339for d in ${specialdirs}; do
340	${sudo} mkdir -p ${mnt}/${d}
341done
342
343if [ \( -n "${custom}" \) -a \( -d "${custom}" \) ]; then
344	echo "${bar} user customisations from files in ${custom} ${bar}"
345	(cd ${custom} && ${sudo} pax -rwpe . ${mnt})
346fi
347
348exit 0
349