mkimage revision 1.11
1#! /bin/sh
2
3# $NetBSD: mkimage,v 1.11 2013/01/15 21:04:41 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
71# find the size of the gzipped files in a .tgz archive
72# Directories appear as 0, so count them as one block
73# and round up files to a fragment.
74sizeone() {
75	if [ ! -f "$1" ]
76	then
77		echo "$PROG: Missing set $1" 1>&2
78		echo 0
79		return;
80	fi
81        case "$1" in 
82        *.tgz|*.tar.gz|*.tbz|*.tar.bz2|*.txz|*.tar.xz)
83                tar tvzf "$1" |
84		awk -v fsize=${fsize} -v bsize=${bsize} '
85		{
86			if ($5 == 0)
87				tot += bsize;
88			else
89				tot += ((int)(($5 + fsize - 1) / fsize)) * fsize;
90		}
91		END {
92			printf("%d\n", tot);
93		}'
94                ;;
95        *)
96                echo 0
97                ;; 
98        esac
99}
100
101usage() {
102	cat << EOF 1>&2
103Usage: $PROG [-S <setsdir>] [-c <custom-files-dir>] [-h <host-arch>] [-s <size>]
104EOF
105exit 1
106}
107
108# Return the usable filesystem size in bytes, given the total size in bytes,
109# and optionally block and fragment sizes
110getffssize() {
111	local bytes="$1"
112	local barg
113	local farg
114	local overhead
115
116	if [ -n "$2" ]
117	then
118		barg="-b $2"
119		if [ -n "$3" ]
120		then
121			farg="-f $3"
122		fi
123	fi
124
125	overhead=$(newfs -N ${farg} ${barg} -s "${bytes}b" -F /dev/null |
126	    awk '/using/ {
127		printf("%d\n", substr($6, 1, length($6) - 3) * 1024 * 1024);
128	    }'
129	)
130	echo $(( ${bytes} - ${overhead} ))
131}
132
133# Compute the size of an ffs filesystem that can fit x bytes.
134# Instead of duplicating the newfs calculations here we let
135# it do the job, using binary search.
136makeffssize() {
137	local bytes=$1
138	local bsize=$2
139	local fsize=$3
140	local max=$(( 2 * ${bytes} ))
141	local min="${bytes}"
142	local cur
143	local res
144	while true; do
145		cur="$(( ( ${max} + ${min} ) / 2 ))"
146		res="$(getffssize "${cur}" ${bsize} ${fsize})"
147#		echo ${min} ${cur} ${max} ${res} ${bytes} 1>&2
148		if [ "${res}" -eq "${bytes}" ]
149		then
150		    break
151		elif [ "$(( ${min} + 1 ))" -ge "${max}" ]
152		then
153		    break
154		elif [ "${res}" -lt "${bytes}" ]
155		then
156		    min="${cur}"
157		elif [ "${res}" -gt "${bytes}" ]
158		then
159		    max="${cur}"
160		fi
161	done
162	echo "${cur}"
163}
164
165finish() {
166    cleanup
167    ${sudo} umount ${mnt}
168    ${sudo} vnconfig -u ${vnddev}
169}
170
171
172DIR="$(dirname "$0")"
173PROG="$(basename "$0")"
174bar="==="
175sudo=
176mnt="${TMPDIR:-/tmp}/image.$$"
177src="/usr/src"
178obj="/usr/obj"
179
180
181# Presumable block and fragment size.
182bsize=16384
183fsize=2048
184
185# First pass for options to get the host
186OPTS="S:c:h:s:x"
187while getopts "$OPTS" f
188do
189	case $f in
190	h)	h="$OPTARG";;
191	*)	;;
192	esac
193done
194
195if [ -z "$h" ]
196then
197	usage
198fi
199
200if [ ! -f "${DIR}/conf/${h}.conf" ]
201then
202	echo $PROG: ${DIR}/conf/${h}.conf is not present 1>&2
203	exit 1
204fi
205
206. "${DIR}/conf/${h}.conf"
207
208OPTIND=1
209while getopts "$OPTS" f
210do
211	case $f in
212	S)	setsdir="$OPTARG";;
213	c)	custom="$OPTARG";;
214	h)	;;
215	s)	size="$OPTARG";;
216	x)	set -x;;
217	*)	usage;;
218	esac
219done
220
221trap finish 0 1 2 3 15
222
223shift $(( "$OPTIND" - 1 ))
224if [ -n "$1" ]; then
225	# take the next argument as being the image name
226	image="$1"
227	shift
228fi
229
230# calculate the set bytes
231setbytes=0
232echo -n "${bar} computing set sizes ("
233for s in ${sets}; do
234	one="$(sizeone ${setsdir}/${s}.tgz)"
235	echo -n " $s=$(( ${one} / 1024 / 1024 ))MB"
236	setbytes=$(( ${setbytes} +  ${one} ))
237done
238echo "): $(( ${setbytes} / 1024 / 1024 ))MB ${bar}"
239
240# calculate size of custom files
241custbytes=0
242if [ -d "${custom}" ]; then
243	custbytes=$(ls -lR "${custom}" | 
244	    awk 'NF == 9 { tot += $5 } END { print tot }')
245fi
246echo "${bar} computing custom sizes: $(( ${custbytes} / 1024 / 1024 ))MB ${bar}"
247
248# how many bytes
249rawbytes="$(( ${setbytes} + ${custbytes} ))"
250echo -n "${bar} computing ffs filesystem size for $(( ${rawbytes} / 1024 / 1024 ))MB: "
251ffsbytes="$(makeffssize "${rawbytes}")"
252ffsmb=$(( ${ffsbytes} / 1024 / 1024 ))
253echo " ${ffsmb}MB ${bar}"
254
255# total in MB
256total=$(( ${ffsmb} + ${overhead} ))
257echo "${bar} overhead: ${overhead}MB ${bar}"
258
259if [ $size -eq 0 ]; then
260        # auto-size the pkgs fs
261        newsize=${total}
262else
263        # check that we've been given enough space
264        if [ ${total} -gt ${size} ]; then
265                echo "$PROG: Given size is ${size} MB, but we need ${total} MB" >&2
266                exit 1
267        fi
268	newsize=${size}
269fi
270
271echo "${bar} making a new ${newsize} MB image in ${image} ${bar}"
272dd if=/dev/zero of=${image} bs=1m count=${newsize} conv=sparse
273
274vnddev=$(next_avail vnd)
275echo "${bar} mounting image via vnd ${vnddev} ${bar}"
276${sudo} vnconfig ${vnddev} ${image}
277${sudo} mkdir -p ${mnt}
278make_filesystems
279
280${sudo} mkdir -p ${mnt}/etc ${mnt}/dev
281
282echo -n "${bar} installing sets:"
283(cd ${mnt} &&
284	for s in ${sets}; do
285		ss="${setsdir}/${s}.tgz"
286		if [ -f "${ss}" ]; then
287			echo -n " ${s}"
288			${sudo} tar xpzf "${ss}"
289		fi
290	done
291)
292echo " ${bar}"
293
294echo "${bar} performing customisations ${bar}"
295
296make_fstab
297
298${sudo} cat > ${mnt}/etc/rc.conf << EOF
299#
300# see rc.conf(5) for more information.
301#
302# Use program=YES to enable program, NO to disable it. program_flags are
303# passed to the program on the command line.
304#
305
306# Load the defaults in from /etc/defaults/rc.conf (if it's readable).
307# These can be overridden below.
308#
309if [ -r /etc/defaults/rc.conf ]; then
310        . /etc/defaults/rc.conf
311fi
312
313# If this is not set to YES, the system will drop into single-user mode.
314#
315rc_configured=YES
316
317hostname=${h}
318
319EOF
320
321customize
322
323for d in ${specialdirs}; do
324	${sudo} mkdir -p ${mnt}/${d}
325done
326
327if [ \( -n "${custom}" \) -a \( -d "${custom}" \) ]; then
328	echo "${bar} user customisations from files in ${custom} ${bar}"
329	(cd ${custom} && ${sudo} pax -rwpe . ${mnt})
330fi
331
332exit 0
333