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