mkimage revision 1.12
1#! /bin/sh
2
3# $NetBSD: mkimage,v 1.12 2013/01/16 15:58:19 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
75exit 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}
181
182
183DIR="$(dirname "$0")"
184PROG="$(basename "$0")"
185bar="==="
186sudo=
187mnt="${TMPDIR:-/tmp}/image.$$"
188src="/usr/src"
189obj="/usr/obj"
190
191
192# Presumable block and fragment size.
193bsize=16384
194fsize=2048
195mtob=$(( 1024 * 1024 ))
196
197# First pass for options to get the host
198OPTS="S:c:h:s:x"
199while getopts "$OPTS" f
200do
201	case $f in
202	h)	h="$OPTARG";;
203	*)	;;
204	esac
205done
206
207if [ -z "$h" ]
208then
209	usage
210fi
211
212if [ ! -f "${DIR}/conf/${h}.conf" ]
213then
214	echo $PROG: ${DIR}/conf/${h}.conf is not present 1>&2
215	exit 1
216fi
217
218. "${DIR}/conf/${h}.conf"
219
220OPTIND=1
221while getopts "$OPTS" f
222do
223	case $f in
224	S)	setsdir="$OPTARG";;
225	c)	custom="$OPTARG";;
226	h)	;;
227	s)	size="$OPTARG";;
228	x)	set -x;;
229	*)	usage;;
230	esac
231done
232
233trap finish 0 1 2 3 15
234
235shift $(( "$OPTIND" - 1 ))
236if [ -n "$1" ]; then
237	# take the next argument as being the image name
238	image="$1"
239	shift
240fi
241
242# calculate the set bytes
243setbytes=0
244echo -n "${bar} computing set sizes ("
245for s in ${sets}; do
246	one="$(sizeone ${setsdir}/${s}.tgz)"
247	echo -n " $s=$(( ${one} / ${mtob} ))MB"
248	setbytes=$(( ${setbytes} +  ${one} ))
249done
250echo "): $(( ${setbytes} / ${mtob} ))MB ${bar}"
251
252# calculate size of custom files
253custbytes=0
254if [ -d "${custom}" ]; then
255	custbytes=$(ls -lR "${custom}" | getfssize ${bsize} ${fsize})
256fi
257echo "${bar} computing custom sizes: $(( ${custbytes} / ${mtob} ))MB ${bar}"
258
259# how many bytes
260rawbytes="$(( ${setbytes} + ${custbytes} ))"
261echo -n "${bar} computing ffs filesystem size for $(( ${rawbytes} / ${mtob} ))MB: "
262ffsbytes="$(makeffssize "${rawbytes}")"
263ffsmb=$(( ${ffsbytes} / ${mtob} ))
264echo " ${ffsmb}MB ${bar}"
265
266# total in MB
267total=$(( ${ffsmb} + ${overhead} ))
268echo "${bar} overhead: ${overhead}MB ${bar}"
269
270if [ $size -eq 0 ]; then
271        # auto-size the pkgs fs
272        newsize=${total}
273else
274        # check that we've been given enough space
275        if [ ${total} -gt ${size} ]; then
276                echo "$PROG: Given size is ${size} MB, but we need ${total} MB" >&2
277                exit 1
278        fi
279	newsize=${size}
280fi
281
282echo "${bar} making a new ${newsize} MB image in ${image} ${bar}"
283dd if=/dev/zero of=${image} bs=1m count=${newsize} conv=sparse
284
285vnddev=$(next_avail vnd)
286echo "${bar} mounting image via vnd ${vnddev} ${bar}"
287${sudo} vnconfig ${vnddev} ${image}
288${sudo} mkdir -p ${mnt}
289make_filesystems
290
291${sudo} mkdir -p ${mnt}/etc ${mnt}/dev
292
293echo -n "${bar} installing sets:"
294(cd ${mnt} &&
295	for s in ${sets}; do
296		ss="${setsdir}/${s}.tgz"
297		if [ -f "${ss}" ]; then
298			echo -n " ${s}"
299			${sudo} tar xpzf "${ss}"
300		fi
301	done
302)
303echo " ${bar}"
304
305echo "${bar} performing customisations ${bar}"
306
307make_fstab
308
309${sudo} cat > ${mnt}/etc/rc.conf << EOF
310#
311# see rc.conf(5) for more information.
312#
313# Use program=YES to enable program, NO to disable it. program_flags are
314# passed to the program on the command line.
315#
316
317# Load the defaults in from /etc/defaults/rc.conf (if it's readable).
318# These can be overridden below.
319#
320if [ -r /etc/defaults/rc.conf ]; then
321        . /etc/defaults/rc.conf
322fi
323
324# If this is not set to YES, the system will drop into single-user mode.
325#
326rc_configured=YES
327
328hostname=${h}
329
330EOF
331
332customize
333
334for d in ${specialdirs}; do
335	${sudo} mkdir -p ${mnt}/${d}
336done
337
338if [ \( -n "${custom}" \) -a \( -d "${custom}" \) ]; then
339	echo "${bar} user customisations from files in ${custom} ${bar}"
340	(cd ${custom} && ${sudo} pax -rwpe . ${mnt})
341fi
342
343exit 0
344