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