Skip to content

Commit

Permalink
ash: allow shell scripts to be embedded in the binary
Browse files Browse the repository at this point in the history
To assist in the deployment of shell scripts it may be convenient
to embed them in the BusyBox binary.

'Embed scripts in the binary' takes any files in the directory
'embed', concatenates them with null separators, compresses them
and embeds them in the binary.

When scripts are embedded in the binary, scripts can be run as
'busybox SCRIPT [ARGS]' or by usual (sym)link mechanism.

embed/nologin is provided as an example.

function                                             old     new   delta
packed_scripts                                         -     123    +123
unpack_scripts                                         -      87     +87
ash_main                                            1103    1171     +68
run_applet_and_exit                                   78     128     +50
get_script_content                                     -      32     +32
script_names                                           -      10     +10
expmeta                                              663     659      -4
------------------------------------------------------------------------------
(add/remove: 4/0 grow/shrink: 2/1 up/down: 370/-4)            Total: 366 bytes

Signed-off-by: Ron Yorston <[email protected]>
Signed-off-by: Denys Vlasenko <[email protected]>
  • Loading branch information
Denys Vlasenko committed Nov 1, 2018
1 parent 5527967 commit 4f2ef4a
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 3 deletions.
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -850,11 +850,14 @@ quiet_cmd_gen_common_bufsiz = GEN include/common_bufsiz.h
cmd_gen_common_bufsiz = $(srctree)/scripts/generate_BUFSIZ.sh include/common_bufsiz.h
quiet_cmd_split_autoconf = SPLIT include/autoconf.h -> include/config/*
cmd_split_autoconf = scripts/basic/split-include include/autoconf.h include/config
quiet_cmd_gen_embedded_scripts = GEN include/embedded_scripts.h
cmd_gen_embedded_scripts = scripts/embedded_scripts include/embedded_scripts.h embed
#bbox# piggybacked generation of few .h files
include/config/MARKER: scripts/basic/split-include include/autoconf.h
include/config/MARKER: scripts/basic/split-include include/autoconf.h $(wildcard embed/*) scripts/embedded_scripts
$(call cmd,split_autoconf)
$(call cmd,gen_bbconfigopts)
$(call cmd,gen_common_bufsiz)
$(call cmd,gen_embedded_scripts)
@touch $@

# Generate some files
Expand Down Expand Up @@ -974,6 +977,7 @@ MRPROPER_FILES += .config .config.old include/asm .version .old_version \
include/autoconf.h \
include/bbconfigopts.h \
include/bbconfigopts_bz2.h \
include/embedded_scripts.h \
include/usage_compressed.h \
include/applet_tables.h \
include/applets.h \
Expand Down
1 change: 1 addition & 0 deletions archival/libarchive/Kbuild.src
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ lib-$(CONFIG_FEATURE_SEAMLESS_LZMA) += open_transformer.o decompress_unlzma.
lib-$(CONFIG_FEATURE_SEAMLESS_XZ) += open_transformer.o decompress_unxz.o
lib-$(CONFIG_FEATURE_COMPRESS_USAGE) += open_transformer.o decompress_bunzip2.o
lib-$(CONFIG_FEATURE_COMPRESS_BBCONFIG) += open_transformer.o decompress_bunzip2.o
lib-$(CONFIG_ASH_EMBEDDED_SCRIPTS) += open_transformer.o decompress_bunzip2.o

ifneq ($(lib-y),)
lib-y += $(COMMON_FILES)
Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions include/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
/autoconf.h
/bbconfigopts_bz2.h
/bbconfigopts.h
/embedded_scripts.h
/NUM_APPLETS.h
/usage_compressed.h
/usage.h
Expand Down
8 changes: 8 additions & 0 deletions include/libbb.h
Original file line number Diff line number Diff line change
Expand Up @@ -1321,9 +1321,17 @@ void bb_logenv_override(void) FAST_FUNC;
#define MAIN_EXTERNALLY_VISIBLE
#endif

/* Embedded script support */
//int find_script_by_name(const char *arg IF_FEATURE_SH_STANDALONE(, int offset)) FAST_FUNC;
char *get_script_content(unsigned n) FAST_FUNC;

/* Applets which are useful from another applets */
int bb_cat(char** argv) FAST_FUNC;
int ash_main(int argc, char** argv)
#if ENABLE_ASH || ENABLE_SH_IS_ASH || ENABLE_BASH_IS_ASH
MAIN_EXTERNALLY_VISIBLE
#endif
;
/* If shell needs them, they exist even if not enabled as applets */
int echo_main(int argc, char** argv) IF_ECHO(MAIN_EXTERNALLY_VISIBLE);
int printf_main(int argc, char **argv) IF_PRINTF(MAIN_EXTERNALLY_VISIBLE);
Expand Down
83 changes: 82 additions & 1 deletion libbb/appletlib.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@

#include "usage_compressed.h"

#if ENABLE_ASH_EMBEDDED_SCRIPTS
# define DEFINE_script_names 1
# include "embedded_scripts.h"
#else
# define NUM_SCRIPTS 0
#endif
#if NUM_SCRIPTS > 0
# include "bb_archive.h"
static const char packed_scripts[] ALIGN1 = { PACKED_SCRIPTS };
#endif

/* "Do not compress usage text if uncompressed text is small
* and we don't include bunzip2 code for other reasons"
Expand Down Expand Up @@ -953,7 +963,71 @@ void FAST_FUNC run_applet_no_and_exit(int applet_no, const char *name, char **ar
}
# endif /* NUM_APPLETS > 0 */

# if ENABLE_BUSYBOX || NUM_APPLETS > 0
# if NUM_SCRIPTS > 0
static char *
unpack_scripts(void)
{
char *outbuf = NULL;
bunzip_data *bd;
int i;
jmp_buf jmpbuf;

/* Setup for I/O error handling via longjmp */
i = setjmp(jmpbuf);
if (i == 0) {
i = start_bunzip(&jmpbuf,
&bd,
/* src_fd: */ -1,
/* inbuf: */ packed_scripts,
/* len: */ sizeof(packed_scripts)
);
}
/* read_bunzip can longjmp and end up here with i != 0
* on read data errors! Not trivial */
if (i == 0) {
outbuf = xmalloc(UNPACKED_SCRIPTS_LENGTH);
read_bunzip(bd, outbuf, UNPACKED_SCRIPTS_LENGTH);
}
dealloc_bunzip(bd);
return outbuf;
}

/*
* In standalone shell mode we sometimes want the index of the script
* and sometimes the index offset by NUM_APPLETS.
*/
static int
find_script_by_name(const char *arg)
{
const char *s = script_names;
int i = 0;

while (*s) {
if (strcmp(arg, s) == 0)
return i;
i++;
while (*s++ != '\0')
continue;
}
return -1;
}

char* FAST_FUNC
get_script_content(unsigned n)
{
char *t = unpack_scripts();
if (t) {
while (n != 0) {
while (*t++ != '\0')
continue;
n--;
}
}
return t;
}
# endif /* NUM_SCRIPTS > 0 */

# if ENABLE_BUSYBOX || NUM_APPLETS > 0 || NUM_SCRIPTS > 0
static NORETURN void run_applet_and_exit(const char *name, char **argv)
{
# if ENABLE_BUSYBOX
Expand All @@ -968,6 +1042,13 @@ static NORETURN void run_applet_and_exit(const char *name, char **argv)
run_applet_no_and_exit(applet, name, argv);
}
# endif
# if NUM_SCRIPTS > 0
{
int script = find_script_by_name(name);
if (script >= 0)
exit(ash_main(-script - 1, argv));
}
# endif

/*bb_error_msg_and_die("applet not found"); - links in printf */
full_write2_str(applet_name);
Expand Down
6 changes: 6 additions & 0 deletions libbb/lineedit.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@
#include "busybox.h"
#include "NUM_APPLETS.h"
#include "unicode.h"
#if ENABLE_ASH_EMBEDDED_SCRIPTS
# include "embedded_scripts.h"
#else
# define NUM_SCRIPTS 0
#endif

#ifndef _POSIX_VDISABLE
# define _POSIX_VDISABLE '\0'
#endif
Expand Down
66 changes: 66 additions & 0 deletions scripts/embedded_scripts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/bin/sh

target="$1"
loc="$2"

test "$target" || exit 1
test "$SED" || SED=sed
test "$DD" || DD=dd

# Some people were bitten by their system lacking a (proper) od
od -v -b </dev/null >/dev/null
if test $? != 0; then
echo 'od tool is not installed or cannot accept "-v -b" options'
exit 1
fi

exec >"$target.$$"

scripts=""
if [ -d "$loc" ]
then
scripts=$(cd $loc; ls * 2>/dev/null)
fi

n=$(echo $scripts | wc -w)

if [ $n -ne 0 ]
then
printf '#ifdef DEFINE_script_names\n'
printf 'const char script_names[] ALIGN1 = '
for i in $scripts
do
printf '"%s\\0"' $i
done
printf '"\\0";\n'
printf '#else\n'
printf 'extern const char script_names[] ALIGN1;\n'
printf '#endif\n'
fi
printf "#define NUM_SCRIPTS $n\n\n"

if [ $n -ne 0 ]
then
printf '#define UNPACKED_SCRIPTS_LENGTH '
for i in $scripts
do
cat $loc/$i
printf '\000'
done | wc -c

printf '#define PACKED_SCRIPTS \\\n'
for i in $scripts
do
cat $loc/$i
printf '\000'
done | bzip2 -1 | $DD bs=2 skip=1 2>/dev/null | od -v -b \
| grep -v '^ ' \
| $SED -e 's/^[^ ]*//' \
-e 's/ //g' \
-e '/^$/d' \
-e 's/\(...\)/0\1,/g' \
-e 's/$/ \\/'
printf '\n'
fi

mv -- "$target.$$" "$target"
33 changes: 32 additions & 1 deletion shell/ash.c
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,21 @@
//config: you to run the specified command or builtin,
//config: even when there is a function with the same name.
//config:
//config:config ASH_EMBEDDED_SCRIPTS
//config: bool "Embed scripts in the binary"
//config: default y
//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
//config: help
//config: Allow scripts to be compressed and embedded in the BusyBox
//config: binary. The scripts should be placed in the 'embed' directory
//config: at build time. In standalone shell mode such scripts can be
//config: run directly and are subject to tab completion; otherwise they
//config: can be run by giving their name as an argument to the shell.
//config: For convenience shell aliases are created. The '-L' shell
//config: argument lists the names of the scripts. Like applets scripts
//config: can be run as 'busybox name ...' or by linking their name to
//config: the binary.
//config:
//config:endif # ash options

//applet:IF_ASH(APPLET(ash, BB_DIR_BIN, BB_SUID_DROP))
Expand Down Expand Up @@ -181,6 +196,11 @@
#include <sys/times.h>
#include <sys/utsname.h> /* for setting $HOSTNAME */
#include "busybox.h" /* for applet_names */
#if ENABLE_ASH_EMBEDDED_SCRIPTS
# include "embedded_scripts.h"
#else
# define NUM_SCRIPTS 0
#endif

/* So far, all bash compat is controlled by one config option */
/* Separate defines document which part of code implements what */
Expand Down Expand Up @@ -14021,13 +14041,17 @@ procargs(char **argv)
int login_sh;

xargv = argv;
#if NUM_SCRIPTS > 0
if (minusc)
goto setarg0;
#endif
login_sh = xargv[0] && xargv[0][0] == '-';
arg0 = xargv[0];
/* if (xargv[0]) - mmm, this is always true! */
xargv++;
argptr = xargv;
for (i = 0; i < NOPTS; i++)
optlist[i] = 2;
argptr = xargv;
if (options(/*cmdline:*/ 1, &login_sh)) {
/* it already printed err message */
raise_exception(EXERROR);
Expand Down Expand Up @@ -14130,6 +14154,7 @@ extern int etext();
*/
int ash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int ash_main(int argc UNUSED_PARAM, char **argv)
/* note: 'argc' is used only if embedded scripts are enabled */
{
volatile smallint state;
struct jmploc jmploc;
Expand Down Expand Up @@ -14183,6 +14208,12 @@ int ash_main(int argc UNUSED_PARAM, char **argv)

init();
setstackmark(&smark);

#if NUM_SCRIPTS > 0
if (argc < 0)
/* Non-NULL minusc tells procargs that an embedded script is being run */
minusc = get_script_content(-argc - 1);
#endif
login_sh = procargs(argv);
#if DEBUG
TRACE(("Shell args: "));
Expand Down

0 comments on commit 4f2ef4a

Please sign in to comment.