1#!/bin/sh 2# 3# Copyright (c) 2015 Antti Kantee. All Rights Reserved. 4# Copyright (c) 2015 Martin Lucina. All Rights Reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions 8# are met: 9# 1. Redistributions of source code must retain the above copyright 10# notice, this list of conditions and the following disclaimer. 11# 2. Redistributions in binary form must reproduce the above copyright 12# notice, this list of conditions and the following disclaimer in the 13# documentation and/or other materials provided with the distribution. 14# 15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 16# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25# SUCH DAMAGE. 26# 27 28_RUMPBAKE_VERSION=20160209 29 30# 31# rumprun-bake: script for final stage linking ("baking") of a unikernel image 32# 33 34unset runcmd 35unset CONF 36unset CFGFILE 37 38if [ "$(basename $0)" = "rumpbake" ]; then 39 echo '>>' 40 echo '>> name "rumpbake" is deprecated. use rumprun-bake instead' 41 echo '>> (waiting 5s for you to see this)' 42 echo '>>' 43 sleep 5 44fi 45 46if [ "${RUMPRUN_WARNING_STFU}" != 'please' ]; then 47 exec 3>&1 1>&2 48 echo 49 echo !!! 50 echo !!! NOTE: rumprun-bake is experimental. syntax may change in the future 51 echo !!! 52 echo 53 exec 1>&3 3>&- 54fi 55 56_die() 57{ 58 59 echo ">> ERROR: $*" 60 exit 1 61} 62 63# 64# configuration management 65# 66 67_filter () 68{ 69 local filtee vname tmplist 70 71 filtee=$1 72 vname=$2 73 for x in $(eval echo \${${vname}}); do 74 [ "${filtee}" = "${x}" ] || tmplist="${tmplist} ${x}" 75 done 76 77 eval ${vname}="\${tmplist}" 78} 79 80_haveconf () 81{ 82 83 for x in ${ALLCONFIGS}; do 84 [ "${x}" != "${1}" ] || return 85 done 86 _die "config \"${1}\" not found (${CFGFILE})" 87} 88 89_nothaveconf () 90{ 91 92 for x in ${ALLCONFIGS}; do 93 [ "${x}" != "${1}" ] \ 94 || _die "config ${1} already exists (${CFGFILE})" 95 done 96} 97 98# Implement "sort | uniq" ourselves so that we don't completely 99# screw up the order of the arguments. not 100% sure it matters, but it's 100# easy enough. Also, notably, doing it locally is ~50% faster (which 101# starts to matter when you have enough configs). 102_uniq () 103{ 104 local listname newlist 105 106 listname=$1 107 shift || _die need listname for _uniq 108 109 eval _UNIQREMAINING=\${$listname} 110 set -- ${_UNIQREMAINING} 111 while [ $# -gt 0 ]; do 112 newlist="${newlist} $1" 113 _filter $1 _UNIQREMAINING 114 set -- ${_UNIQREMAINING} 115 done 116 eval ${listname}=\${newlist} 117} 118 119ALLCONFIGS= 120version () 121{ 122 123 [ "${1}" = "${_RUMPBAKE_VERSION}" ] \ 124 || _die ${CFGFILE} mismatch: expect ${_RUMPBAKE_VERSION}, got \"$1\" 125 _VERSOK=true 126} 127 128conf () 129{ 130 131 if ! echo ${1} | egrep -q '^(xen|hw|sel4)?_'; then 132 _die "conf: invalid \"$1\" (${CFGFILE})" 133 fi 134 CONF=$1 135} 136 137fnoc () 138{ 139 140 unset CONF 141} 142 143create () 144{ 145 146 [ -n "$*" ] || _die "create: need description (${CFGFILE})" 147 148 _nothaveconf ${CONF} 149 150 ALLCONFIGS="${ALLCONFIGS} ${CONF}" 151 eval CONFDESCR_${CONF}=\"${*}\" 152} 153 154assimilate () 155{ 156 local from 157 158 _haveconf ${CONF} 159 160 for from; do 161 _haveconf ${from} 162 eval CONFIG_${CONF}=\"\${CONFIG_${CONF}} \${CONFIG_${from}}\" 163 done 164} 165 166nuke () 167{ 168 169 [ $# -eq 0 ] || _die "nuke: wrong number of args (${CFGFILE})" 170 _haveconf ${CONF} 171 _filter ${CONF} ALLCONFIGS 172} 173 174add () 175{ 176 177 _haveconf ${CONF} 178 eval CONFIG_${CONF}=\"\${CONFIG_${CONF}} $@\" 179} 180 181remove () 182{ 183 local compvar 184 185 _haveconf ${CONF} 186 187 compvar=CONFIG_${CONF} 188 for x; do 189 _filter ${x} ${compvar} 190 done 191} 192 193# debug routine 194debugdump () 195{ 196 197 _haveconf ${CONF} 198 199 _uniq CONFIG_${CONF} 200 eval echo \${CONFIG_${CONF}} 201} 202 203_usage () 204{ 205 cat <<EOM 206rumprun-bake version: ${_RUMPBAKE_VERSION} 207 208usage: rumprun-bake [-c conffile ...] list 209 rumprun-bake [-c conffile ...] [-m cmd ...] describe config 210 rumprun-bake [-c conffile ...] [-m cmd ...] config out in [in ...] 211 212"list" outputs available configs. 213 214"describe" outputs details for the given config. 215 216Final usage creates a unikernel image: 217 config : rumprun board configuration to use. 218 output : output file name for the unikernel image. 219 input : executable(s) to bake. 220EOM 221 exit 1 222} 223 224_nuketmpdir () 225{ 226 227 nukeme="${TMPDIR}" 228 TMPDIR='' 229 rm -rf ${nukeme} 230} 231 232_readconfig () 233{ 234 local x 235 236 CFGFILE="$1" 237 if [ ! -f "${CFGFILE}" ]; then 238 echo "rumprun-bake: error: Configuration file ${CFGFILE} not found" 239 exit 1 240 fi 241 242 _VERSOK=false 243 244 # ". foo" doesn't work everywhere/always, so do a dance here. 245 # Note: CFGFILE needs to remain as what the user gave. 246 case "$1" in 247 /*) 248 . "${CFGFILE}" 249 ;; 250 *) 251 . "$(pwd)/${CFGFILE}" 252 ;; 253 esac 254 ${_VERSOK} || _die "config version not specified (${CFGFILE})" 255 256 unset CFGFILE 257 258 # Publish configs which are not private 259 for x in ${ALLCONFIGS}; do 260 [ "${x#_}" = "${x}" ] || _filter ${x} ALLCONFIGS 261 done 262} 263 264_getoneinfo () 265{ 266 267 bin="$1" 268 var="$2" 269 unset tmp 270 271 notesect=.note.rumprun.bakerecipe 272 tmp="$(!LIBEXEC_READELF! -p ${notesect} ${bin} 2>/dev/null \ 273 | sed -n '/.*rumprun_'"${var}"': /p')" 274 [ -n "${tmp}" ] \ 275 || _die "Could not extract \"${var}\" from ${bin}. Not rumprun bin?" 276 277 # now that we've verified the entry is present, reduce to 278 # contents (which may be empty) 279 tmp="${tmp#*rumprun_${var}: }" 280 281 cvar=$(echo ${var} | tr '[a-z]' '[A-Z]') 282 283 eval [ \"\${RUMPBAKE_${cvar}:=${tmp}}\" = \"${tmp}\" ] || \ 284 _die ${var} mismatch in binaries 285} 286 287_getbininfo () 288{ 289 290 # extract bake recipe 291 for x in tuple tooldir backingcc cflags; do 292 _getoneinfo "${1}" ${x} 293 done 294} 295 296# does not respect runcmd. let's not mope and whine over it 297TMPDIR=$(mktemp -d /tmp/rumprun-bake.XXXXXX) 298trap _nuketmpdir 0 INT TERM 299 300_readconfig "!DESTDIR!/etc/rumprun-bake.conf" 301 302while getopts "c:m:n" opt; do 303 case "${opt}" in 304 c) 305 _readconfig "${OPTARG}" 306 ;; 307 m) 308 # save. we have to process them after configs are processed 309 echo "${OPTARG}" >> ${TMPDIR}/manualcmds 310 ;; 311 n) 312 runcmd=echo 313 ;; 314 *) 315 _usage 316 ;; 317 esac 318done 319 320shift $((${OPTIND}-1)) 321TARGET="${1}" 322 323if [ "${TARGET}" = "list" ]; then 324 for x in ${ALLCONFIGS}; do 325 eval mydesc="\${CONFDESCR_${x}}" 326 printf '%-16s' "${x}" 327 printf ': %s' "${mydesc}" 328 printf '\n' 329 done 330 exit 0 331fi 332 333if [ "${TARGET}" = "describe" ]; then 334 CONFIG=$2 335else 336 CONFIG=$1 337fi 338 339# process potential manual commands 340if [ -f ${TMPDIR}/manualcmds ]; then 341 printf "version %s\n" ${_RUMPBAKE_VERSION} > ${TMPDIR}/cmdconfig 342 printf "conf %s\n" ${CONFIG} >> ${TMPDIR}/cmdconfig 343 cat ${TMPDIR}/manualcmds >> ${TMPDIR}/cmdconfig 344 printf 'fnoc\n' >> ${TMPDIR}/cmdconfig 345 _readconfig ${TMPDIR}/cmdconfig 346fi 347 348if [ "${TARGET}" = "describe" ]; then 349 [ $# -eq 2 ] || _die \"describe\" needs exactly one config. 350 CONF=$2 351 debugdump 352 exit 0 353fi 354 355OUTPUT="${2}" 356[ $# -gt 2 ] || _usage 357shift 2 358 359unset RUMPBAKE_BACKINGCC 360unset RUMPBAKE_TUPLE 361unset RUMPBAKE_CFLAGS 362 363# XXX: TOOLDIR is just dirname $0, so can simplify that bit 364unset RUMPBAKE_TOOLDIR 365 366[ $# -le 8 ] || { echo '>> max 8 binaries supported currently' ; exit 1; } 367 368# Santize the config argument passed in to remove shell 369# metacharacters 370config="$(echo ${TARGET} | sed -e 's/-/_/g' -e 's/[^A-Za-z0-9_]//g')" 371for c in ${ALLCONFIGS}; do 372 [ "$c" = "$config" ] && break 373done 374if [ "$c" != "$config" ]; then 375 echo "rumprun-bake: error: unsupported config \"$config\"" 376 exit 1 377fi 378 379_uniq CONFIG_${config} 380 381PLATFORM=${config%%_*} 382eval LIBS="\${CONFIG_${config}}" 383 384# Check if the file is a relocatable object produced by a rumprun toolchain. 385# Create a temporary object with a unique "main" 386objnum=1 387allobjs= 388for f in "$@"; do 389 _getbininfo ${f} 390 391 ${runcmd} ${RUMPBAKE_TOOLDIR}/bin/${RUMPBAKE_TUPLE}-objcopy \ 392 --redefine-sym main=rumprun_main${objnum} \ 393 ${f} ${TMPDIR}/tmp${objnum}.obj 394 allobjs="${allobjs} ${TMPDIR}/tmp${objnum}.obj" 395 objnum=$((${objnum}+1)) 396done 397 398MACHINE_GNU_ARCH=${RUMPBAKE_TUPLE%%-*} 399 400 401# Final link using cc to produce the unikernel image. 402${runcmd} ${RUMPBAKE_BACKINGCC} ${RUMPBAKE_CFLAGS} !EXTRACCFLAGS! \ 403 --sysroot ${RUMPBAKE_TOOLDIR}/rumprun-${MACHINE_GNU_ARCH} \ 404 -specs=${RUMPBAKE_TOOLDIR}/rumprun-${MACHINE_GNU_ARCH}/lib/rumprun-${PLATFORM}/specs-bake \ 405 -o ${OUTPUT} ${allobjs} \ 406 -Wl,--whole-archive ${LIBS} || exit 1 407 408exit 0 409