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 (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 rm -fr ${mnt}
181 }
182
183
184 DIR="$(dirname "$0")"
185 PROG="$(basename "$0")"
186 bar="==="
187 sudo=
188 mnt="${TMPDIR:-/tmp}/image.$$"
189 src="/usr/src"
190 obj="/usr/obj"
191
192 sets="base comp etc games man misc modules text"
193 xsets="xbase xcomp xetc xfont xserver"
194
195 # Presumable block and fragment size.
196 bsize=16384
197 fsize=2048
198 mtob=$(( 1024 * 1024 ))
199
200 # First pass for options to get the host
201 OPTS="S:c:h:s:x"
202 while getopts "$OPTS" f
203 do
204 case $f in
205 h) h="$OPTARG";;
206 *) ;;
207 esac
208 done
209
210 if [ -z "$h" ]
211 then
212 usage
213 fi
214
215 if [ ! -f "${DIR}/conf/${h}.conf" ]
216 then
217 echo $PROG: ${DIR}/conf/${h}.conf is not present 1>&2
218 exit 1
219 fi
220
221 . "${DIR}/conf/${h}.conf"
222
223 OPTIND=1
224 while getopts "$OPTS" f
225 do
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
234 done
235
236 trap finish 0 1 2 3 15
237
238 shift $(( "$OPTIND" - 1 ))
239 if [ -n "$1" ]; then
240 # take the next argument as being the image name
241 image="$1"
242 shift
243 fi
244
245 # calculate the set bytes
246 setbytes=0
247 echo -n "${bar} computing set sizes ("
248 b=
249 for s in ${sets}; do
250 one="$(sizeone ${setsdir}/${s}.tgz)"
251 echo -n "$b$s=$(( ${one} / ${mtob} ))MB"
252 setbytes=$(( ${setbytes} + ${one} ))
253 b=" "
254 done
255 echo "): $(( ${setbytes} / ${mtob} ))MB ${bar}"
256
257 # calculate size of custom files
258 custbytes=0
259 if [ -d "${custom}" ]; then
260 custbytes=$(ls -lR "${custom}" | getfssize ${bsize} ${fsize})
261 fi
262 echo "${bar} computing custom sizes: $(( ${custbytes} / ${mtob} ))MB ${bar}"
263
264 # how many bytes
265 rawbytes="$(( ${setbytes} + ${custbytes} ))"
266 echo -n "${bar} computing ffs filesystem size for $(( ${rawbytes} / ${mtob} ))MB: "
267 ffsbytes="$(makeffssize "${rawbytes}")"
268 ffsmb=$(( ${ffsbytes} / ${mtob} ))
269 echo " ${ffsmb}MB ${bar}"
270
271 # total in MB
272 total=$(( ${ffsmb} + ${overhead} ))
273 echo "${bar} overhead: ${overhead}MB ${bar}"
274
275 if [ $size -eq 0 ]; then
276 # auto-size the pkgs fs
277 newsize=${total}
278 else
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}
285 fi
286
287 echo "${bar} making a new ${newsize} MB image in ${image} ${bar}"
288 dd if=/dev/zero of=${image} bs=1m count=${newsize} conv=sparse
289
290 vnddev=$(next_avail vnd)
291 echo "${bar} mounting image via vnd ${vnddev} ${bar}"
292 ${sudo} vnconfig ${vnddev} ${image}
293 ${sudo} mkdir -p ${mnt}
294 make_filesystems
295
296 ${sudo} mkdir -p ${mnt}/etc ${mnt}/dev
297
298 echo -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 )
308 echo " ${bar}"
309
310 echo "${bar} performing customisations ${bar}"
311
312 make_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 #
325 if [ -r /etc/defaults/rc.conf ]; then
326 . /etc/defaults/rc.conf
327 fi
328
329 # If this is not set to YES, the system will drop into single-user mode.
330 #
331 rc_configured=YES
332
333 hostname=${h}
334
335 EOF
336
337 customize
338
339 for d in ${specialdirs}; do
340 ${sudo} mkdir -p ${mnt}/${d}
341 done
342
343 if [ \( -n "${custom}" \) -a \( -d "${custom}" \) ]; then
344 echo "${bar} user customisations from files in ${custom} ${bar}"
345 (cd ${custom} && ${sudo} pax -rwpe . ${mnt})
346 fi
347
348 exit 0
349