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 (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 # 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.
74 sizeone() {
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
101 usage() {
102 cat << EOF 1>&2
103 Usage: $PROG [-S <setsdir>] [-c <custom-files-dir>] [-h <host-arch>] [-s <size>]
104 EOF
105 exit 1
106 }
107
108 # Return the usable filesystem size in bytes, given the total size in bytes,
109 # and optionally block and fragment sizes
110 getffssize() {
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.
136 makeffssize() {
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
165 finish() {
166 cleanup
167 ${sudo} umount ${mnt}
168 ${sudo} vnconfig -u ${vnddev}
169 }
170
171
172 DIR="$(dirname "$0")"
173 PROG="$(basename "$0")"
174 bar="==="
175 sudo=
176 mnt="${TMPDIR:-/tmp}/image.$$"
177 src="/usr/src"
178 obj="/usr/obj"
179
180
181 # Presumable block and fragment size.
182 bsize=16384
183 fsize=2048
184
185 # First pass for options to get the host
186 OPTS="S:c:h:s:x"
187 while getopts "$OPTS" f
188 do
189 case $f in
190 h) h="$OPTARG";;
191 *) ;;
192 esac
193 done
194
195 if [ -z "$h" ]
196 then
197 usage
198 fi
199
200 if [ ! -f "${DIR}/conf/${h}.conf" ]
201 then
202 echo $PROG: ${DIR}/conf/${h}.conf is not present 1>&2
203 exit 1
204 fi
205
206 . "${DIR}/conf/${h}.conf"
207
208 OPTIND=1
209 while getopts "$OPTS" f
210 do
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
219 done
220
221 trap finish 0 1 2 3 15
222
223 shift $(( "$OPTIND" - 1 ))
224 if [ -n "$1" ]; then
225 # take the next argument as being the image name
226 image="$1"
227 shift
228 fi
229
230 # calculate the set bytes
231 setbytes=0
232 echo -n "${bar} computing set sizes ("
233 for s in ${sets}; do
234 one="$(sizeone ${setsdir}/${s}.tgz)"
235 echo -n " $s=$(( ${one} / 1024 / 1024 ))MB"
236 setbytes=$(( ${setbytes} + ${one} ))
237 done
238 echo "): $(( ${setbytes} / 1024 / 1024 ))MB ${bar}"
239
240 # calculate size of custom files
241 custbytes=0
242 if [ -d "${custom}" ]; then
243 custbytes=$(ls -lR "${custom}" |
244 awk 'NF == 9 { tot += $5 } END { print tot }')
245 fi
246 echo "${bar} computing custom sizes: $(( ${custbytes} / 1024 / 1024 ))MB ${bar}"
247
248 # how many bytes
249 rawbytes="$(( ${setbytes} + ${custbytes} ))"
250 echo -n "${bar} computing ffs filesystem size for $(( ${rawbytes} / 1024 / 1024 ))MB: "
251 ffsbytes="$(makeffssize "${rawbytes}")"
252 ffsmb=$(( ${ffsbytes} / 1024 / 1024 ))
253 echo " ${ffsmb}MB ${bar}"
254
255 # total in MB
256 total=$(( ${ffsmb} + ${overhead} ))
257 echo "${bar} overhead: ${overhead}MB ${bar}"
258
259 if [ $size -eq 0 ]; then
260 # auto-size the pkgs fs
261 newsize=${total}
262 else
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}
269 fi
270
271 echo "${bar} making a new ${newsize} MB image in ${image} ${bar}"
272 dd if=/dev/zero of=${image} bs=1m count=${newsize} conv=sparse
273
274 vnddev=$(next_avail vnd)
275 echo "${bar} mounting image via vnd ${vnddev} ${bar}"
276 ${sudo} vnconfig ${vnddev} ${image}
277 ${sudo} mkdir -p ${mnt}
278 make_filesystems
279
280 ${sudo} mkdir -p ${mnt}/etc ${mnt}/dev
281
282 echo -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 )
292 echo " ${bar}"
293
294 echo "${bar} performing customisations ${bar}"
295
296 make_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 #
309 if [ -r /etc/defaults/rc.conf ]; then
310 . /etc/defaults/rc.conf
311 fi
312
313 # If this is not set to YES, the system will drop into single-user mode.
314 #
315 rc_configured=YES
316
317 hostname=${h}
318
319 EOF
320
321 customize
322
323 for d in ${specialdirs}; do
324 ${sudo} mkdir -p ${mnt}/${d}
325 done
326
327 if [ \( -n "${custom}" \) -a \( -d "${custom}" \) ]; then
328 echo "${bar} user customisations from files in ${custom} ${bar}"
329 (cd ${custom} && ${sudo} pax -rwpe . ${mnt})
330 fi
331
332 exit 0
333