Skip to content

Instantly share code, notes, and snippets.

@technosaurus
Created November 24, 2017 05:18
Show Gist options
  • Save technosaurus/de0311f5f1c5c3b43a4e84c394cfee1e to your computer and use it in GitHub Desktop.
Save technosaurus/de0311f5f1c5c3b43a4e84c394cfee1e to your computer and use it in GitHub Desktop.
#!/bin/sh
addline()
{
NEXT="$(printf "%s% $((50-${#LASTNAME}))d% 10d %10d" "$LASTNAME" "$OLD" "$NEW" "$DELTA")"
[ -z "$STUFF" ] &&
STUFF="$NEXT" ||
STUFF="$(printf "%s\n%s" "$STUFF" "$NEXT")"
}
bloatcheck()
{
if [ $# -ne 2 ]
then
echo "usage: bloatcheck old new"
exit 1
fi
DIFF1=`mktemp base.XXXXXXX`
DIFF2=`mktemp bloat.XXXXXXX`
trap "rm $DIFF1 $DIFF2" EXIT
nm --size-sort "$1" | sort -k3,3 > $DIFF1
nm --size-sort "$2" | sort -k3,3 > $DIFF2
diff -U 0 $DIFF1 $DIFF2 | tail -n +3 | sed -n 's/^\([-+]\)/\1 /p' \
| sort -k4,4 | do_bloatcheck
}
change_sh()
{
# build each command as a standalone executable
NOBUILD=1 make_sh > /dev/null &&
${HOSTCC:-cc} -I . scripts/install.c -o generated/instlist &&
export PREFIX=${PREFIX:-change/} &&
mkdir -p "$PREFIX" || exit 1
# Build all the commands standalone except:
# sh - shell builtins like "cd" and "exit" need the multiplexer
# help - needs to know what other commands are enabled (use command --help)
for i in $(generated/instlist | egrep -vw "sh|help")
do
echo -n " $i" &&
single_sh $i > /dev/null 2>$PREFIX/${i}.bad &&
rm $PREFIX/${i}.bad || echo -n '*'
done
echo
}
configure(){
# Toybox configuration file.
# This sets environment variables used by scripts/make.sh
# A synonym.
[ -z "$CROSS_COMPILE" ] && CROSS_COMPILE="$CROSS"
# CFLAGS and OPTIMIZE are different so you can add extra CFLAGS without
# disabling default optimizations
[ -z "$CFLAGS" ] && CFLAGS="-Wall -Wundef -Wno-char-subscripts -Werror=implicit-function-declaration"
# Required for our expected ABI. we're 8-bit clean thus "char" must be unsigned.
CFLAGS="$CFLAGS -funsigned-char"
[ -z "$OPTIMIZE" ] && OPTIMIZE="-Os -ffunction-sections -fdata-sections -fno-asynchronous-unwind-tables -fno-strict-aliasing"
# We accept LDFLAGS, but by default don't have anything in it
[ -z "$LDOPTIMIZE" ] && LDOPTIMIZE="-Wl,--gc-sections"
# The makefile provides defaults for these, so this only gets used if
# you call scripts/make.sh and friends directly.
[ -z "$CC" ] && CC=cc
# If HOSTCC needs CFLAGS or LDFLAGS, just add them to the variable
# ala HOSTCC="blah-cc --static"
[ -z "$HOSTCC" ] && HOSTCC=cc
}
do_bloatcheck()
{
LASTNAME=
DELTA=0
TOTAL=0
OLD=0
NEW=0
STUFF=
printf "name% 46s% 10s% 11s\n" old new delta
echo "-----------------------------------------------------------------------"
while read a b c d
do
THISNAME=$(echo "$d" | sed 's/[.][0-9]*$//')
if [ "$LASTNAME" != "$THISNAME" ]
then
TOTAL=$(($TOTAL+$DELTA))
[ $DELTA -ne 0 ] && addline
LASTNAME="$THISNAME"
DELTA=0
OLD=0
NEW=0
fi
SIZE=$(printf "%d" "0x$b")
if [ "$a" == "-" ]
then
OLD=$(($OLD+$SIZE))
SIZE=$((-1*$SIZE))
else
NEW=$(($NEW+$SIZE))
fi
DELTA=$(($DELTA+$SIZE))
done
TOTAL=$(($TOTAL+$DELTA))
[ $DELTA -ne 0 ] && addline
echo "$STUFF" | sort -k4,4nr
echo "-----------------------------------------------------------------------"
printf "% 71d total\n" "$TOTAL"
}
# Set up a chroot environment and run commands within it.
# Needed commands listed on command line
# Script fed to stdin.
dochroot()
{
mkdir tmpdir4chroot
mount -t ramfs tmpdir4chroot tmpdir4chroot
mkdir -p tmpdir4chroot/{etc,sys,proc,tmp,dev}
cp -L testing.sh tmpdir4chroot
# Copy utilities from command line arguments
echo -n "Setup chroot"
mkchroot tmpdir4chroot $*
echo
mknod tmpdir4chroot/dev/tty c 5 0
mknod tmpdir4chroot/dev/null c 1 3
mknod tmpdir4chroot/dev/zero c 1 5
# Copy script from stdin
cat > tmpdir4chroot/test.sh
chmod +x tmpdir4chroot/test.sh
chroot tmpdir4chroot /test.sh
umount -l tmpdir4chroot
rmdir tmpdir4chroot
}
do_loudly()
{
[ ! -z "$V" ] && echo "$@" || echo -n "$DOTPROG"
"$@"
}
do_test()
{
CMDNAME="${1##*/}"
CMDNAME="${CMDNAME%.test}"
if [ -z "$TEST_HOST" ]
then
[ -z "$2" ] && C="$(readlink -f ../$CMDNAME)" || C="$(which $CMDNAME)"
else
C="$CMDNAME"
fi
if [ ! -z "$C" ]
then
. "$1"
else
echo "$CMDNAME disabled"
fi
}
findglobals_sh()
{
# Quick and dirty check to see if anybody's leaked global variables.
# We should have this, toy_list, toybuf, and toys.
nm toybox_unstripped | grep '[0-9A-Fa-f]* [BCDGRS]' | cut -d ' ' -f 3
}
genbuildsh()
{
# Write a canned build line for use on crippled build machines.
echo "#!/bin/sh"
echo
echo "BUILD='$BUILD'"
echo
echo "FILES='$LIBFILES $TOYFILES'"
echo
echo "LINK='$LINK'"
echo
echo
echo '$BUILD $FILES $LINK'
}
genconfig()
{
# Reverse sort puts posix first, examples last.
for j in $(ls toys/*/README | sort -s -r)
do
DIR="$(dirname "$j")"
[ $(ls "$DIR" | wc -l) -lt 2 ] && continue
echo "menu \"$(head -n 1 $j)\""
echo
# extract config stanzas from each source file, in alphabetical order
for i in $(ls -1 $DIR/*.c)
do
# Grab the config block for Config.in
echo "# $i"
sed -n '/^\*\//q;/^config [A-Z]/,$p' $i || return 1
echo
done
echo endmenu
done
}
genconfig_sh()
{
# This has to be a separate function from make_sh so it can be called
# before menuconfig. (It's called again from make_sh just to be sure.)
mkdir -p generated
. configure
probeconfig > generated/Config.probed || rm generated/Config.probed
genconfig > generated/Config.in || rm generated/Config.in
WORKING=
PENDING=
toys toys/*/*.c | (
while IFS=":" read FILE NAME
do
[ "$NAME" == help ] && continue
[ "$NAME" == install ] && continue
echo -e "$NAME: $FILE *.[ch] lib/*.[ch]\n\tscripts/toybox.sh single_sh $NAME\n"
echo -e "test_$NAME:\n\tscripts/toybox.sh test_sh $NAME\n"
[ "${FILE/pending//}" != "$FILE" ] &&
PENDING="$PENDING $NAME" ||
WORKING="$WORKING $NAME"
done &&
echo -e "clean::\n\trm -f $WORKING $PENDING" &&
echo -e "list:\n\t@echo $(echo $WORKING | tr ' ' '\n' | sort | xargs)" &&
echo -e "list_pending:\n\t@echo $(echo $PENDING | tr ' ' '\n' | sort | xargs)" &&
echo -e ".PHONY: $WORKING $PENDING" | sed 's/ \([^ ]\)/ test_\1/g'
) > .singlemake
}
# Extract global structure definitions and flag definitions from toys/*/*.c
getglobals()
{
for i in toys/*/*.c
do
NAME="$(echo $i | $SED 's@.*/\(.*\)\.c@\1@')"
DATA="$($SED -n -e '/^GLOBALS(/,/^)/b got;b;:got' \
-e 's/^GLOBALS(/struct '"$NAME"'_data {/' \
-e 's/^)/};/' -e 'p' $i)"
[ ! -z "$DATA" ] && echo -e "// $i\n\n$DATA\n"
done
}
install_sh()
{
# Grab default values for $CFLAGS and such.
. ./configure
[ -z "$PREFIX" ] && PREFIX="/usr/toybox"
# Parse command line arguments.
LONG_PATH=""
while [ ! -z "$1" ]
do
# Create symlinks instead of hardlinks?
[ "$1" == "--symlink" ] && LINK_TYPE="-s"
# Uninstall?
[ "$1" == "--uninstall" ] && UNINSTALL=Uninstall
# Delete destination command if it exists?
[ "$1" == "--force" ] && DO_FORCE="-f"
# Use {,usr}/{bin,sbin} paths instead of all files in one directory?
[ "$1" == "--long" ] && LONG_PATH="bin/"
# Symlink host toolchain binaries to destination to create cross compile $PATH
[ "$1" == "--airlock" ] && AIRLOCK=1
shift
done
echo "Compile instlist..."
NOBUILD=1 make_sh
$DEBUG $HOSTCC -I . scripts/install.c -o generated/instlist || exit 1
COMMANDS="$(generated/instlist $LONG_PATH)"
echo "${UNINSTALL:-Install} commands..."
# Copy toybox itself
if [ -z "$UNINSTALL" ]
then
mkdir -p "${PREFIX}/${LONG_PATH}" &&
rm -f "${PREFIX}/${LONG_PATH}/toybox" &&
cp toybox ${PREFIX}/${LONG_PATH} || exit 1
else
rm -f "${PREFIX}/${LONG_PATH}/toybox" 2>/dev/null
fi
cd "$PREFIX" || exit 1
# Make links to toybox
EXIT=0
for i in $COMMANDS
do
# Figure out target of link
if [ -z "$LONG_PATH" ]
then
DOTPATH=""
else
# Create subdirectory for command to go in (if necessary)
DOTPATH="$(dirname "$i")"/
if [ -z "$UNINSTALL" ]
then
mkdir -p "$DOTPATH" || exit 1
fi
if [ -z "$LINK_TYPE" ]
then
DOTPATH="bin/"
else
if [ "$DOTPATH" != "$LONG_PATH" ]
then
# For symlinks we need ../../bin style relative paths
DOTPATH="$(echo $DOTPATH | sed -e 's@[^/]*/@../@g')"$LONG_PATH
else
DOTPATH=""
fi
fi
fi
# Create link
if [ -z "$UNINSTALL" ]
then
ln $DO_FORCE $LINK_TYPE ${DOTPATH}toybox $i || EXIT=1
else
rm -f $i || EXIT=1
fi
done
[ -z "$AIRLOCK" ] && exit 0
# --airlock creates a single directory you can point the $PATH to for cross
# compiling, which contains just toybox and symlinks to toolchain binaries.
# This not only means you're building with a known set of tools (insulated from
# variations in the host distro), but that everything else is NOT in your PATH
# and thus various configure stages won't find things on thie host that won't
# be there on the target (such as the distcc build noticing the host has
# python and deciding to #include Python.h).
# The following are commands toybox should provide, but doesn't yet.
# For now symlink the host version. This list must go away by 1.0.
PENDING="bunzip2 bzcat dd diff expr ftpd ftpget ftpput gunzip less ping route tar test tr vi wget zcat awk bzip2 fdisk gzip sh sha512sum unxz xzcat bc"
# "gcc" should go away for llvm, but some things still hardwire it
TOOLCHAIN="ar as nm cc make ld gcc objdump"
if [ ! -z "$AIRLOCK" ]
then
# Tools needed to build packages
for i in $TOOLCHAIN $PENDING $HOST_EXTRA
do
if [ ! -f "$i" ]
then
# Loop through each instance, populating fallback directories (used by
# things like distcc, which require multiple instances of the same binary
# in a known order in the $PATH).
X=0
FALLBACK="$PREFIX"
which -a "$i" | while read j
do
if [ ! -e "$FALLBACK/$i" ]
then
mkdir -p "$FALLBACK" &&
ln -sf "$j" "$FALLBACK/$i" || exit 1
fi
X=$[$X+1]
FALLBACK="$PREFIX/fallback-$X"
done
if [ ! -f "$PREFIX/$i" ]
then
echo "Toolchain component missing: $i" >&2
[ -z "$PEDANTIC" ] || EXIT=1
fi
fi
done
fi
exit $EXIT
}
# Is anything under directory $2 newer than file $1
isnewer()
{
CHECK="$1"
shift
[ ! -z "$(find "$@" -newer "$CHECK" 2>/dev/null || echo yes)" ]
}
# Process config.h and newtoys.h to generate FLAG_x macros. Note we must
# always #define the relevant macro, even when it's disabled, because we
# allow multiple NEWTOY() in the same C file. (When disabled the FLAG is 0,
# so flags&0 becomes a constant 0 allowing dead code elimination.)
make_flagsh()
{
# Parse files through C preprocessor twice, once to get flags for current
# .config and once to get flags for allyesconfig
for I in A B
do
(
# define macros and select header files with option string data
echo "#define NEWTOY(aa,bb,cc) aa $I bb"
echo '#define OLDTOY(...)'
if [ "$I" == A ]
then
cat generated/config.h
else
$SED '/USE_.*([^)]*)$/s/$/ __VA_ARGS__/' generated/config.h
fi
echo '#include "lib/toyflags.h"'
cat generated/newtoys.h
# Run result through preprocessor, glue together " " gaps leftover from USE
# macros, delete comment lines, print any line with a quoted optstring,
# turn any non-quoted opstring (NULL or 0) into " " (because fscanf can't
# handle "" with nothing in it, and mkflags uses that).
) | ${CROSS_COMPILE}${CC} -E - | \
$SED -n -e 's/" *"//g;/^#/d;t clear;:clear;s/"/"/p;t;s/\( [AB] \).*/\1 " "/p'
# Sort resulting line pairs and glue them together into triplets of
# command "flags" "allflags"
# to feed into mkflags C program that outputs actual flag macros
# If no pair (because command's disabled in config), use " " for flags
# so allflags can define the appropriate zero macros.
done | sort -s | $SED -n -e 's/ A / /;t pair;h;s/\([^ ]*\).*/\1 " "/;x' \
-e 'b single;:pair;h;n;:single;s/[^ ]* B //;H;g;s/\n/ /;p' | \
tee generated/flags.raw | generated/mkflags > generated/flags.h || exit 1
}
make_sh()
{
# Grab default values for $CFLAGS and such.
export LANG=c
export LC_ALL=C
set -o pipefail
. ./configure
[ -z "$KCONFIG_CONFIG" ] && KCONFIG_CONFIG=.config
[ -z "$OUTNAME" ] && OUTNAME=toybox
UNSTRIPPED="generated/unstripped/$(basename "$OUTNAME")"
# Since each cc invocation is short, launch half again as many processes
# as we have processors so they don't exit faster than we can start them.
[ -z "$CPUS" ] && CPUS=$(($(nproc)+1))
if [ -z "$SED" ]
then
[ ! -z "$(which gsed 2>/dev/null)" ] && SED=gsed || SED=sed
fi
# Respond to V= by echoing command lines as well as running them
DOTPROG=
echo "Generate headers from toys/*/*.c..."
mkdir -p generated/unstripped
if isnewer generated/Config.in toys
then
echo "Extract configuration information from toys/*.c files..."
genconfig_sh
fi
# Create a list of all the commands toybox can provide. Note that the first
# entry is out of order on purpose (the toybox multiplexer command must be the
# first element of the array). The rest must be sorted in alphabetical order
# for fast binary search.
if isnewer generated/newtoys.h toys
then
echo -n "generated/newtoys.h "
echo "USE_TOYBOX(NEWTOY(toybox, NULL, TOYFLAG_STAYROOT))" > generated/newtoys.h
$SED -n -e 's/^USE_[A-Z0-9_]*(/&/p' toys/*/*.c \
| $SED 's/\(.*TOY(\)\([^,]*\),\(.*\)/\2 \1\2,\3/' | sort -s -k 1,1 \
| $SED 's/[^ ]* //' >> generated/newtoys.h
[ $? -ne 0 ] && exit 1
fi
[ ! -z "$V" ] && echo "Which C files to build..."
# Extract a list of toys/*/*.c files to compile from the data in $KCONFIG_CONFIG
# (First command names, then filenames with relevant {NEW,OLD}TOY() macro.)
[ -d ".git" ] && GITHASH="$(git describe --tags --abbrev=12 2>/dev/null)"
[ ! -z "$GITHASH" ] && GITHASH="-DTOYBOX_VERSION=\"$GITHASH\""
TOYFILES="$($SED -n 's/^CONFIG_\([^=]*\)=.*/\1/p' "$KCONFIG_CONFIG" | xargs | tr ' [A-Z]' '|[a-z]')"
TOYFILES="$(egrep -l "TOY[(]($TOYFILES)[ ,]" toys/*/*.c)"
CFLAGS="$CFLAGS $(cat generated/cflags)"
BUILD="$(echo ${CROSS_COMPILE}${CC} $CFLAGS -I . $OPTIMIZE $GITHASH)"
LIBFILES="$(ls lib/*.c | grep -v lib/help.c)"
TOYFILES="lib/help.c main.c $TOYFILES"
if [ "${TOYFILES/pending//}" != "$TOYFILES" ]
then
echo -e "\n\033[1;31mwarning: using unfinished code from toys/pending\033[0m"
fi
if ! cmp -s <(genbuildsh | head -n 3) \
<(head -n 3 generated/build.sh 2>/dev/null)
then
echo -n "Library probe"
# We trust --as-needed to remove each library if we don't use any symbols
# out of it, this loop is because the compiler has no way to ignore a library
# that doesn't exist, so we have to detect and skip nonexistent libraries
# for it.
> generated/optlibs.dat
for i in util crypt m resolv selinux smack attr rt crypto z log
do
echo "int main(int argc, char *argv[]) {return 0;}" | \
${CROSS_COMPILE}${CC} $CFLAGS -xc - -o generated/libprobe -Wl,--as-needed -l$i > /dev/null 2>/dev/null &&
echo -l$i >> generated/optlibs.dat
echo -n .
done
rm -f generated/libprobe
echo
fi
# LINK needs optlibs.dat, above
LINK="$(echo $LDOPTIMIZE $LDFLAGS -o "$UNSTRIPPED" -Wl,--as-needed $(cat generated/optlibs.dat))"
genbuildsh > generated/build.sh && chmod +x generated/build.sh || exit 1
#TODO: "make $SED && make" doesn't regenerate config.h because diff .config
if true #isnewer generated/config.h "$KCONFIG_CONFIG"
then
echo "Make generated/config.h from $KCONFIG_CONFIG."
# This long and roundabout sed invocation is to make old versions of sed
# happy. New ones have '\n' so can replace one line with two without all
# the branches and tedious mucking about with hold space.
$SED -n \
-e 's/^# CONFIG_\(.*\) is not set.*/\1/' \
-e 't notset' \
-e 's/^CONFIG_\(.*\)=y.*/\1/' \
-e 't isset' \
-e 's/^CONFIG_\([^=]*\)=\(.*\)/#define CFG_\1 \2/p' \
-e 'd' \
-e ':notset' \
-e 'h' \
-e 's/.*/#define CFG_& 0/p' \
-e 'g' \
-e 's/.*/#define USE_&(...)/p' \
-e 'd' \
-e ':isset' \
-e 'h' \
-e 's/.*/#define CFG_& 1/p' \
-e 'g' \
-e 's/.*/#define USE_&(...) __VA_ARGS__/p' \
$KCONFIG_CONFIG > generated/config.h || exit 1
fi
if [ generated/mkflags -ot scripts/mkflags.c ]
then
do_loudly $HOSTCC scripts/mkflags.c -o generated/mkflags || exit 1
fi
if isnewer generated/flags.h toys "$KCONFIG_CONFIG"
then
echo -n "generated/flags.h "
make_flagsh
fi
if isnewer generated/globals.h toys
then
echo -n "generated/globals.h "
GLOBSTRUCT="$(getglobals)"
(
echo "$GLOBSTRUCT"
echo
echo "extern union global_union {"
echo "$GLOBSTRUCT" | \
$SED -n 's/struct \(.*\)_data {/ struct \1_data \1;/p'
echo "} this;"
) > generated/globals.h
fi
if [ generated/mktags -ot scripts/mktags.c ]
then
do_loudly $HOSTCC scripts/mktags.c -o generated/mktags || exit 1
fi
if isnewer generated/tags.h toys
then
echo -n "generated/tags.h "
$SED -n '/TAGGED_ARRAY(/,/^)/{s/.*TAGGED_ARRAY[(]\([^,]*\),/\1/;p}' \
toys/*/*.c lib/*.c | generated/mktags > generated/tags.h
fi
if [ generated/config2help -ot scripts/config2help.c ]
then
do_loudly $HOSTCC scripts/config2help.c -I . lib/xwrap.c lib/llist.c \
lib/lib.c lib/portability.c -o generated/config2help || exit 1
fi
if isnewer generated/help.h generated/Config.in
then
echo "generated/help.h"
generated/config2help Config.in $KCONFIG_CONFIG > generated/help.h || exit 1
fi
[ ! -z "$NOBUILD" ] && exit 0
echo -n "Compile toybox"
[ ! -z "$V" ] && echo
DOTPROG=.
# This is a parallel version of: do_loudly $BUILD $FILES $LINK || exit 1
# Any headers newer than the oldest generated/obj file?
X="$(ls -1t generated/obj/* 2>/dev/null | tail -n 1)"
# TODO: redo this
if [ ! -e "$X" ] || [ ! -z "$(find toys -name "*.h" -newer "$X")" ]
then
rm -rf generated/obj && mkdir -p generated/obj || exit 1
else
rm -f generated/obj/{main,lib_help}.o || exit 1
fi
# build each generated/obj/*.o file in parallel
PENDING=
LNKFILES=
DONE=0
COUNT=0
CLICK=
for i in $LIBFILES click $TOYFILES
do
[ "$i" == click ] && CLICK=1 && continue
X=${i/lib\//lib_}
X=${X##*/}
OUT="generated/obj/${X%%.c}.o"
LNKFILES="$LNKFILES $OUT"
# $LIBFILES doesn't need to be rebuilt if newer than .config, $TOYFILES does
[ "$OUT" -nt "$i" ] && [ -z "$CLICK" -o "$OUT" -nt "$KCONFIG_CONFIG" ] &&
continue
do_loudly $BUILD -c $i -o $OUT &
PENDING="$PENDING $!"
COUNT=$(($COUNT+1))
# ratelimit to $CPUS many parallel jobs, detecting errors
for j in $PENDING
do
[ "$COUNT" -lt "$CPUS" ] && break;
wait $j
DONE=$(($DONE+$?))
COUNT=$(($COUNT-1))
PENDING="${PENDING## $j}"
done
[ $DONE -ne 0 ] && break
done
# wait for all background jobs, detecting errors
for i in $PENDING
do
wait $i
DONE=$(($DONE+$?))
done
[ $DONE -ne 0 ] && exit 1
do_loudly $BUILD $LNKFILES $LINK || exit 1
if [ ! -z "$NOSTRIP" ] ||
! do_loudly ${CROSS_COMPILE}strip "$UNSTRIPPED" -o "$OUTNAME"
then
echo "strip failed, using unstripped" && cp "$UNSTRIPPED" "$OUTNAME" ||
exit 1
fi
# gcc 4.4's strip command is buggy, and doesn't set the executable bit on
# its output the way SUSv4 suggests it do so. While we're at it, make sure
# we don't have the "w" bit set so things like bzip2's "cp -f" install don't
# overwrite our binary through the symlink.
do_loudly chmod 555 "$OUTNAME" || exit 1
echo
}
minicom_sh()
{
# If you want to use toybox netcat to talk to a serial port, use this.
if [ ! -c "$1" ]
then
echo "Usage: minicom_sh /dev/ttyS0"
exit 1
fi
SPEED="$2"
[ -z "$SPEED" ] && SPEED=115200
stty $SPEED -F "$1"
stty raw -echo -ctlecho -F "$1"
stty raw -echo # Need to do it on stdin, too.
./toybox netcat -f "$1"
stty cooked echo # Put stdin back.
}
# Recursively grab an executable and all the libraries needed to run it.
# Source paths beginning with / will be copied into destpath, otherwise
# the file is assumed to already be there and only its library dependencies
# are copied.
mkchroot()
{
[ $# -lt 2 ] && return
echo -n .
dest=$1
shift
for i in "$@"
do
case "$i" in
/*);;
*)i=`which $i`;;
esac
[ -f "$dest/$i" ] && continue
if [ -e "$i" ]
then
d=`echo "$i" | grep -o '.*/'` &&
mkdir -p "$dest/$d" &&
cat "$i" > "$dest/$i" &&
chmod +x "$dest/$i"
else
echo "Not found: $i"
fi
mkchroot "$dest" $(ldd "$i" | egrep -o '/.* ')
done
}
optional()
{
option=`echo "$OPTIONFLAGS" | egrep "(^|:)$1(:|\$)"`
# Not set?
if [ -z "$1" ] || [ -z "$OPTIONFLAGS" ] || [ ${#option} -ne 0 ]
then
SKIP=""
return
fi
SKIP=1
}
probecc()
{
${CROSS_COMPILE}${CC} $CFLAGS -xc -o /dev/null $1 -
}
probeconfig()
{
> generated/cflags
# llvm produces its own really stupid warnings about things that aren't wrong,
# and although you can turn the warning off, gcc reacts badly to command line
# arguments it doesn't understand. So probe.
[ -z "$(probecc -Wno-string-plus-int <<< \#warn warn 2>&1 | grep string-plus-int)" ] &&
echo -Wno-string-plus-int >> generated/cflags
# Probe for container support on target
probesymbol TOYBOX_CONTAINER << EOF
#include <linux/sched.h>
int x=CLONE_NEWNS|CLONE_NEWUTS|CLONE_NEWIPC|CLONE_NEWNET;
int main(int argc, char *argv[]) { setns(0,0); return unshare(x); }
EOF
probesymbol TOYBOX_FIFREEZE -c << EOF
#include <linux/fs.h>
#ifndef FIFREEZE
#error nope
#endif
EOF
# Work around some uClibc limitations
probesymbol TOYBOX_ICONV -c << EOF
#include "iconv.h"
EOF
probesymbol TOYBOX_FALLOCATE << EOF
#include <fcntl.h>
int main(int argc, char *argv[]) { return posix_fallocate(0,0,0); }
EOF
# Android and some other platforms miss utmpx
probesymbol TOYBOX_UTMPX -c << EOF
#include <utmpx.h>
#ifndef BOOT_TIME
#error nope
#endif
int main(int argc, char *argv[]) {
struct utmpx *a;
if (0 != (a = getutxent())) return 0;
return 1;
}
EOF
# Android is missing shadow.h
probesymbol TOYBOX_SHADOW -c << EOF
#include <shadow.h>
int main(int argc, char *argv[]) {
struct spwd *a = getspnam("root"); return 0;
}
EOF
# Some commands are android-specific
probesymbol TOYBOX_ON_ANDROID -c << EOF
#ifndef __ANDROID__
#error nope
#endif
EOF
probesymbol TOYBOX_ANDROID_SCHEDPOLICY << EOF
#include <cutils/sched_policy.h>
int main(int argc,char *argv[]) { get_sched_policy_name(0); }
EOF
# nommu support
probesymbol TOYBOX_FORK << EOF
#include <unistd.h>
int main(int argc, char *argv[]) { return fork(); }
EOF
echo -e '\tdepends on !TOYBOX_MUSL_NOMMU_IS_BROKEN'
probesymbol TOYBOX_PRLIMIT << EOF
#include <sys/time.h>
#include <sys/resource.h>
int main(int argc, char *argv[]) { prlimit(0, 0, 0, 0); }
EOF
}
# Probe for a single config symbol with a "compiles or not" test.
# Symbol name is first argument, flags second, feed C file to stdin
probesymbol()
{
probecc $2 2>/dev/null && DEFAULT=y || DEFAULT=n
rm a.out 2>/dev/null
echo -e "config $1\n\tbool" || exit 1
echo -e "\tdefault $DEFAULT\n" || exit 1
}
runtest_sh()
{
# Simple test harness infrastructure
#
# Copyright 2005 by Rob Landley
# This file defines two main functions, "testcmd" and "optional". The
# first performs a test, the second enables/disables tests based on
# configuration options.
# The following environment variables enable optional behavior in "testing":
# DEBUG - Show every command run by test script.
# VERBOSE - Print the diff -u of each failed test case.
# If equal to "fail", stop after first failed test.
#
# The "testcmd" function takes five arguments:
# $1) Description to display when running command
# $2) Command line arguments to command
# $3) Expected result (on stdout)
# $4) Data written to file "input"
# $5) Data written to stdin
#
# The "testing" function is like testcmd but takes a complete command line
# (I.E. you have to include the command name.) The variable $C is an absolute
# path to the command being tested, which can bypass shell builtins.
#
# The exit value of testcmd is the exit value of the command it ran.
#
# The environment variable "FAILCOUNT" contains a cumulative total of the
# number of failed tests.
#
# The "optional" function is used to skip certain tests (by setting the
# environment variable SKIP), ala:
# optional CFG_THINGY
#
# The "optional" function checks the environment variable "OPTIONFLAGS",
# which is either empty (in which case it always clears SKIP) or
# else contains a colon-separated list of features (in which case the function
# clears SKIP if the flag was found, or sets it to 1 if the flag was not found).
export FAILCOUNT=0
export SKIP=
# Helper functions
# Check config to see if option is enabled, set SKIP if not.
SHOWPASS=PASS
SHOWFAIL=FAIL
SHOWSKIP=SKIP
if tty -s <&1
then
SHOWPASS="$(echo -e "\033[1;32m${SHOWPASS}\033[0m")"
SHOWFAIL="$(echo -e "\033[1;31m${SHOWFAIL}\033[0m")"
SHOWSKIP="$(echo -e "\033[1;33m${SHOWSKIP}\033[0m")"
fi
}
showasm()
{
# Copyright 2006 Rob Landley <rob@landley.net>
# Dumb little utility function to print out the assembly dump of a single
# function, or list the functions so dumpable in an executable. You'd think
# there would be a way to get objdump to do this, but I can't find it.
[ $# -lt 1 ] || [ $# -gt 2 ] && { echo "usage: showasm file function"; exit 1; }
[ ! -f $1 ] && { echo "File $1 not found"; exit 1; }
if [ $# -eq 1 ]
then
objdump -d $1 | sed -n -e 's/^[0-9a-fA-F]* <\(.*\)>:$/\1/p'
exit 0
fi
objdump -d $1 | sed -n -e '/./{H;$!d}' -e "x;/^.[0-9a-fA-F]* <$2>:/p"
}
# Build a standalone toybox command
single_sh(){
if [ -z "$1" ]
then
echo "usage: single_sh command..." >&2
exit 1
fi
# Harvest TOYBOX_* symbols from .config
if [ ! -e .config ]
then
echo "Need .config for toybox global settings. Run defconfig/menuconfig." >&2
exit 1
fi
# Force dependencies to rebuild headers if we build multiplexer after this.
touch -c .config
export KCONFIG_CONFIG=.singleconfig
for i in "$@"
do
echo -n "$i:"
TOYFILE="$(egrep -l "TOY[(]($i)[ ,]" toys/*/*.c)"
if [ -z "$TOYFILE" ]
then
echo "Unknown command '$i'" >&2
exit 1
fi
# Enable stuff this command depends on
DEPENDS="$(sed -n "/^config *$i"'$/,/^$/{s/^[ \t]*depends on //;T;s/[!][A-Z0-9_]*//g;s/ *&& */|/g;p}' $TOYFILE | xargs | tr ' ' '|')"
NAME=$(echo $i | tr a-z- A-Z_)
make allnoconfig > /dev/null &&
sed -ri -e '/CONFIG_TOYBOX/d' \
-e "s/# (CONFIG_($NAME|${NAME}_.*${DEPENDS:+|$DEPENDS})) is not set/\1=y/" \
"$KCONFIG_CONFIG" &&
echo "# CONFIG_TOYBOX is not set" >> "$KCONFIG_CONFIG" &&
grep "CONFIG_TOYBOX_" .config >> "$KCONFIG_CONFIG" &&
rm -f "$PREFIX$i" &&
OUTNAME="$PREFIX$i" make_sh || exit 1
done
}
test_sh(){
TOPDIR="$PWD"
FILES="$PWD"/tests/files
trap 'kill $(jobs -p) 2>/dev/null; exit 1' INT
rm -rf generated/testdir
mkdir -p generated/testdir/testdir
if [ -z "$TEST_HOST" ]
then
if [ $# -ne 0 ]
then
PREFIX=generated/testdir/ single_sh "$@" || exit 1
else
make install_flat PREFIX=generated/testdir || exit 1
fi
fi
cd generated/testdir
PATH="$PWD:$PATH"
cd testdir
export LC_COLLATE=C
runtest_sh
[ -f "$TOPDIR/generated/config.h" ] && export OPTIONFLAGS=:$(echo $(sed -nr 's/^#define CFG_(.*) 1/\1/p' "$TOPDIR/generated/config.h") | sed 's/ /:/g')
if [ $# -ne 0 ]
then
for i in "$@"
do
do_test "$TOPDIR"/tests/$i.test
done
else
for i in "$TOPDIR"/tests/*.test
do
if [ -z "$TEST_HOST" ]
then
do_test "$i" 1
else
rm -rf testdir && mkdir testdir && cd testdir || exit 1
do_test "$i"
cd ..
fi
done
fi
}
testcmd()
{
wrong_args "$@"
testing "$1" "$C $2" "$3" "$4" "$5"
}
# The testing function
testing()
{
wrong_args "$@"
NAME="$CMDNAME $1"
[ -z "$1" ] && NAME=$2
[ -n "$DEBUG" ] && set -x
if [ -n "$SKIP" ] || ( [ -n "$SKIP_HOST" ] && [ -n "$TEST_HOST" ])
then
[ ! -z "$VERBOSE" ] && echo "$SHOWSKIP: $NAME"
return 0
fi
echo -ne "$3" > expected
echo -ne "$4" > input
echo -ne "$5" | ${EVAL:-eval} "$2" > actual
RETVAL=$?
# Catch segfaults
[ $RETVAL -gt 128 ] && [ $RETVAL -lt 255 ] &&
echo "exited with signal (or returned $RETVAL)" >> actual
DIFF="$(diff -au${NOSPACE:+b} expected actual)"
if [ ! -z "$DIFF" ]
then
FAILCOUNT=$[$FAILCOUNT+1]
echo "$SHOWFAIL: $NAME"
if [ -n "$VERBOSE" ]
then
[ ! -z "$4" ] && echo "echo -ne \"$4\" > input"
echo "echo -ne '$5' |$EVAL $2"
echo "$DIFF"
[ "$VERBOSE" == fail ] && exit 1
fi
else
echo "$SHOWPASS: $NAME"
fi
rm -f input expected actual
[ -n "$DEBUG" ] && set +x
return 0
}
# Find names of commands that can be built standalone in these C files
toys()
{
grep 'TOY(.*)' "$@" | grep -v TOYFLAG_NOFORK | grep -v "0))" | \
sed -rn 's/([^:]*):.*(OLD|NEW)TOY\( *([a-zA-Z][^,]*) *,.*/\1:\3/p'
}
wrong_args()
{
if [ $# -ne 5 ]
then
echo "Test $NAME has the wrong number of arguments ($# $*)" >&2
exit
fi
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment