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 (at] 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
30 next_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 usage() {
72 cat << EOF 1>&2
73 Usage: $PROG [-S <setsdir>] [-c <custom-files-dir>] [-h <host-arch>] [-s <size>]
74 EOF
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.
81 getfssize() {
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.
100 sizeone() {
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
121 getffssize() {
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.
147 makeffssize() {
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
176 finish() {
177 cleanup
178 ${sudo} umount ${mnt}
179 ${sudo} vnconfig -u ${vnddev}
180 }
181
182
183 DIR="$(dirname "$0")"
184 PROG="$(basename "$0")"
185 bar="==="
186 sudo=
187 mnt="${TMPDIR:-/tmp}/image.$$"
188 src="/usr/src"
189 obj="/usr/obj"
190
191
192 # Presumable block and fragment size.
193 bsize=16384
194 fsize=2048
195 mtob=$(( 1024 * 1024 ))
196
197 # First pass for options to get the host
198 OPTS="S:c:h:s:x"
199 while getopts "$OPTS" f
200 do
201 case $f in
202 h) h="$OPTARG";;
203 *) ;;
204 esac
205 done
206
207 if [ -z "$h" ]
208 then
209 usage
210 fi
211
212 if [ ! -f "${DIR}/conf/${h}.conf" ]
213 then
214 echo $PROG: ${DIR}/conf/${h}.conf is not present 1>&2
215 exit 1
216 fi
217
218 . "${DIR}/conf/${h}.conf"
219
220 OPTIND=1
221 while getopts "$OPTS" f
222 do
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
231 done
232
233 trap finish 0 1 2 3 15
234
235 shift $(( "$OPTIND" - 1 ))
236 if [ -n "$1" ]; then
237 # take the next argument as being the image name
238 image="$1"
239 shift
240 fi
241
242 # calculate the set bytes
243 setbytes=0
244 echo -n "${bar} computing set sizes ("
245 for s in ${sets}; do
246 one="$(sizeone ${setsdir}/${s}.tgz)"
247 echo -n " $s=$(( ${one} / ${mtob} ))MB"
248 setbytes=$(( ${setbytes} + ${one} ))
249 done
250 echo "): $(( ${setbytes} / ${mtob} ))MB ${bar}"
251
252 # calculate size of custom files
253 custbytes=0
254 if [ -d "${custom}" ]; then
255 custbytes=$(ls -lR "${custom}" | getfssize ${bsize} ${fsize})
256 fi
257 echo "${bar} computing custom sizes: $(( ${custbytes} / ${mtob} ))MB ${bar}"
258
259 # how many bytes
260 rawbytes="$(( ${setbytes} + ${custbytes} ))"
261 echo -n "${bar} computing ffs filesystem size for $(( ${rawbytes} / ${mtob} ))MB: "
262 ffsbytes="$(makeffssize "${rawbytes}")"
263 ffsmb=$(( ${ffsbytes} / ${mtob} ))
264 echo " ${ffsmb}MB ${bar}"
265
266 # total in MB
267 total=$(( ${ffsmb} + ${overhead} ))
268 echo "${bar} overhead: ${overhead}MB ${bar}"
269
270 if [ $size -eq 0 ]; then
271 # auto-size the pkgs fs
272 newsize=${total}
273 else
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}
280 fi
281
282 echo "${bar} making a new ${newsize} MB image in ${image} ${bar}"
283 dd if=/dev/zero of=${image} bs=1m count=${newsize} conv=sparse
284
285 vnddev=$(next_avail vnd)
286 echo "${bar} mounting image via vnd ${vnddev} ${bar}"
287 ${sudo} vnconfig ${vnddev} ${image}
288 ${sudo} mkdir -p ${mnt}
289 make_filesystems
290
291 ${sudo} mkdir -p ${mnt}/etc ${mnt}/dev
292
293 echo -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 )
303 echo " ${bar}"
304
305 echo "${bar} performing customisations ${bar}"
306
307 make_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 #
320 if [ -r /etc/defaults/rc.conf ]; then
321 . /etc/defaults/rc.conf
322 fi
323
324 # If this is not set to YES, the system will drop into single-user mode.
325 #
326 rc_configured=YES
327
328 hostname=${h}
329
330 EOF
331
332 customize
333
334 for d in ${specialdirs}; do
335 ${sudo} mkdir -p ${mnt}/${d}
336 done
337
338 if [ \( -n "${custom}" \) -a \( -d "${custom}" \) ]; then
339 echo "${bar} user customisations from files in ${custom} ${bar}"
340 (cd ${custom} && ${sudo} pax -rwpe . ${mnt})
341 fi
342
343 exit 0
344