[RFC PATCH 1/1] ash: allow shell scripts to be embedded in the binary
Denys Vlasenko
vda.linux at googlemail.com
Thu Nov 1 09:15:06 UTC 2018
Thanks!
Applied in the minimal form. Scripts behave as if they were applets:
[busybox] SCRIPT [ARGS].
"ash SCRIPT" is not recognized.
Listing of applets (I contemplate extending "Currently defined functions:"
output of busybox), tab completion and standalone mode integration
should be done with follow-up changes, to keep each change more readable.
Please check current git for possible follow-up patches already landing
there.
The commit is:
ash: allow shell scripts to be embedded in the binary
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
On Thu, Oct 25, 2018 at 1:37 PM Ron Yorston <rmy at pobox.com> wrote:
>
> To assist in the deployment of shell scripts it may be convenient
> to embed them in the BusyBox binary.
>
> This patch adds two configuration options to the shell:
>
> - '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.
>
> - 'Allow the contents of embedded scripts to be listed' makes the
> shell argument '-L name' list the contents of the named script
> and allows the scripts to be traced.
>
> Both options are off by default.
>
> When scripts are embedded in the binary:
>
> - The shell argument '-L' lists the names of the scripts.
>
> - Scripts can be run as 'sh name arg...' or 'busybox name arg...'.
>
> - In standalone shell mode scripts can be run by name and are
> subject to tab completion.
>
> Signed-off-by: Ron Yorston <rmy at pobox.com>
> ---
> .gitignore | 5 +
> Makefile | 6 +-
> archival/libarchive/Kbuild.src | 1 +
> include/.gitignore | 1 +
> libbb/appletlib.c | 25 ++++-
> libbb/lineedit.c | 18 ++-
> scripts/embedded_scripts | 68 ++++++++++++
> shell/ash.c | 196 ++++++++++++++++++++++++++++++++-
> 8 files changed, 313 insertions(+), 7 deletions(-)
> create mode 100755 scripts/embedded_scripts
>
> diff --git a/.gitignore b/.gitignore
> index c03c2e8a6..becd9bf6d 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -56,3 +56,8 @@ cscope.po.out
> #
> tags
> TAGS
> +
> +#
> +# user-supplied scripts
> +#
> +/embed
> diff --git a/Makefile b/Makefile
> index 59ec83a6a..8a0dbdf49 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -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
> @@ -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 \
> diff --git a/archival/libarchive/Kbuild.src b/archival/libarchive/Kbuild.src
> index e1a8a7529..12e66a88b 100644
> --- a/archival/libarchive/Kbuild.src
> +++ b/archival/libarchive/Kbuild.src
> @@ -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)
> diff --git a/include/.gitignore b/include/.gitignore
> index 75afff9ca..13a96e018 100644
> --- a/include/.gitignore
> +++ b/include/.gitignore
> @@ -5,6 +5,7 @@
> /autoconf.h
> /bbconfigopts_bz2.h
> /bbconfigopts.h
> +/embedded_scripts.h
> /NUM_APPLETS.h
> /usage_compressed.h
> /usage.h
> diff --git a/libbb/appletlib.c b/libbb/appletlib.c
> index 319bcc263..3f15f663b 100644
> --- a/libbb/appletlib.c
> +++ b/libbb/appletlib.c
> @@ -49,7 +49,11 @@
> #endif
>
> #include "usage_compressed.h"
> -
> +#if ENABLE_ASH_EMBEDDED_SCRIPTS
> +#include "embedded_scripts.h"
> +#else
> +#define NUM_SCRIPTS 0
> +#endif
>
> /* "Do not compress usage text if uncompressed text is small
> * and we don't include bunzip2 code for other reasons"
> @@ -953,7 +957,7 @@ 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 ENABLE_BUSYBOX || NUM_APPLETS > 0 || NUM_SCRIPTS > 0
> static NORETURN void run_applet_and_exit(const char *name, char **argv)
> {
> # if ENABLE_BUSYBOX
> @@ -968,6 +972,23 @@ 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_FEATURE_SH_STANDALONE(, 0));
> + if (script >= 0) {
> + int i, argc = string_array_len(argv);
> + char **new_argv = xmalloc(sizeof(*argv)*(argc+2));
> +
> + new_argv[0] = (char *)"ash";
> + new_argv[1] = (char *)name;
> + for (i=1; i<argc+1; ++i) {
> + new_argv[i+1] = argv[i];
> + }
> +
> + run_applet_no_and_exit(APPLET_NO_ash, name, new_argv);
> + }
> + }
> +# endif
>
> /*bb_error_msg_and_die("applet not found"); - links in printf */
> full_write2_str(applet_name);
> diff --git a/libbb/lineedit.c b/libbb/lineedit.c
> index b1e971f88..d21d8e616 100644
> --- a/libbb/lineedit.c
> +++ b/libbb/lineedit.c
> @@ -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
> @@ -806,10 +812,16 @@ static NOINLINE unsigned complete_cmd_dir_file(const char *command, int type)
> }
> pf_len = strlen(pfind);
>
> -# if ENABLE_FEATURE_SH_STANDALONE && NUM_APPLETS != 1
> +# if ENABLE_FEATURE_SH_STANDALONE && (NUM_APPLETS != 1 || NUM_SCRIPTS > 0)
> if (type == FIND_EXE_ONLY && !dirbuf) {
> - const char *p = applet_names;
> -
> + const char *p;
> +# if NUM_APPLETS != 1 && NUM_SCRIPTS > 0
> + for (i=0,p=applet_names; i<2; i++,p=script_names)
> +# elif NUM_APPLETS != 1
> + p = applet_names;
> +# else /* NUM_SCRIPTS > 0 */
> + p = script_names;
> +# endif
> while (*p) {
> if (strncmp(pfind, p, pf_len) == 0)
> add_match(xstrdup(p));
> diff --git a/scripts/embedded_scripts b/scripts/embedded_scripts
> new file mode 100755
> index 000000000..7d5e80b6f
> --- /dev/null
> +++ b/scripts/embedded_scripts
> @@ -0,0 +1,68 @@
> +#!/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 ASH_MAIN\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'
> + printf 'int find_script_by_name(const char *arg\n'
> + printf '\t\tIF_FEATURE_SH_STANDALONE(, int offset)) FAST_FUNC;\n'
> +fi
> +
> +mv -- "$target.$$" "$target"
> diff --git a/shell/ash.c b/shell/ash.c
> index dc1a55a6b..a27262ab5 100644
> --- a/shell/ash.c
> +++ b/shell/ash.c
> @@ -148,6 +148,31 @@
> //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 n
> +//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:config ASH_LIST_EMBEDDED_SCRIPTS
> +//config: bool "Allow embedded script content to be visible"
> +//config: default n
> +//config: depends on ASH_EMBEDDED_SCRIPTS
> +//config: help
> +//config: Allow the contents of embedded scripts to be listed by the
> +//config: '-L name' shell argument. Also allow embedded scripts to be
> +//config: traced by the 'xtrace' shell option. Select this if you don't
> +//config: mind users seeing what's in your scripts.
> +//config:
> //config:endif # ash options
>
> //applet:IF_ASH(APPLET(ash, BB_DIR_BIN, BB_SUID_DROP))
> @@ -181,6 +206,16 @@
> #include <sys/times.h>
> #include <sys/utsname.h> /* for setting $HOSTNAME */
> #include "busybox.h" /* for applet_names */
> +#if ENABLE_ASH_EMBEDDED_SCRIPTS
> +#define ASH_MAIN
> +#include "embedded_scripts.h"
> +#else
> +#define NUM_SCRIPTS 0
> +#endif
> +
> +#if NUM_SCRIPTS > 0
> +static const char packed_scripts[] ALIGN1 = { PACKED_SCRIPTS };
> +#endif
>
> /* So far, all bash compat is controlled by one config option */
> /* Separate defines document which part of code implements what */
> @@ -8001,6 +8036,13 @@ static void
> tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, char **envp)
> {
> #if ENABLE_FEATURE_SH_STANDALONE
> +# if NUM_SCRIPTS > 0
> + if (applet_no >= NUM_APPLETS) {
> + errno = ENOEXEC;
> + goto run_script;
> + }
> + else
> +# endif
> if (applet_no >= 0) {
> if (APPLET_IS_NOEXEC(applet_no)) {
> clearenv();
> @@ -8023,6 +8065,9 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c
> } while (errno == EINTR);
> #else
> execve(cmd, argv, envp);
> +#endif
> +#if ENABLE_FEATURE_SH_STANDALONE && NUM_SCRIPTS > 0
> + run_script:
> #endif
> if (cmd != bb_busybox_exec_path && errno == ENOEXEC) {
> /* Run "cmd" as a shell script:
> @@ -8052,6 +8097,96 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c
> }
> }
>
> +#if NUM_SCRIPTS > 0
> +# include "bb_archive.h"
> +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) {
> + /* Cannot use xmalloc: will leak bd in NOFORK case! */
> + outbuf = malloc_or_warn(UNPACKED_SCRIPTS_LENGTH);
> + if (outbuf)
> + 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.
> + */
> +int FAST_FUNC
> +find_script_by_name(const char *arg IF_FEATURE_SH_STANDALONE(, int offset))
> +{
> + const char *s = script_names;
> + int i = 0;
> +
> + while (*s) {
> + if (strcmp(arg, s) == 0) {
> + return IF_FEATURE_SH_STANDALONE(offset+)i;
> + }
> + ++i;
> + while (*s++ != '\0')
> + continue;
> + }
> +
> + return -1;
> +}
> +
> +static char *
> +get_script_content(const char *arg)
> +{
> + int i;
> + char *scripts;
> +
> + if ((i=find_script_by_name(arg IF_FEATURE_SH_STANDALONE(, 0))) < 0)
> + return NULL;
> +
> + if ((scripts=unpack_scripts()) != NULL) {
> + char *t = scripts;
> + while (i != 0) {
> + while (*t++ != '\0')
> + continue;
> + --i;
> + }
> + return t;
> + }
> +
> + return NULL;
> +}
> +
> +#if !ENABLE_FEATURE_SH_STANDALONE
> +static void
> +alias_scripts(void)
> +{
> + const char *s = script_names;
> +
> + while (*s) {
> + setalias(s, auto_string(xasprintf("sh %s", s)));
> + while (*s++ != '\0')
> + continue;
> + }
> +}
> +# endif
> +#endif
> +
> /*
> * Exec a program. Never returns. If you change this routine, you may
> * have to change the find_command routine as well.
> @@ -8070,6 +8205,9 @@ static void shellexec(char *prog, char **argv, const char *path, int idx)
> if (strchr(prog, '/') != NULL
> #if ENABLE_FEATURE_SH_STANDALONE
> || (applet_no = find_applet_by_name(prog)) >= 0
> +# if NUM_SCRIPTS > 0
> + || (applet_no = find_script_by_name(prog, NUM_APPLETS)) >= 0
> +# endif
> #endif
> ) {
> tryexec(IF_FEATURE_SH_STANDALONE(applet_no,) prog, argv, envp);
> @@ -10171,6 +10309,10 @@ evalcommand(union node *cmd, int flags)
> */
> /* find_command() encodes applet_no as (-2 - applet_no) */
> int applet_no = (- cmdentry.u.index - 2);
> +#if NUM_SCRIPTS > 0
> + /* ignore embedded scripts */
> + if (applet_no < NUM_APPLETS)
> +#endif
> if (applet_no >= 0 && APPLET_IS_NOFORK(applet_no)) {
> char **sv_environ;
>
> @@ -11083,6 +11225,30 @@ options(int cmdline, int *login_sh)
> *login_sh = 1;
> }
> break;
> +#if NUM_SCRIPTS > 0
> + } else if (cmdline && (c == 'L')) {
> +#if ENABLE_ASH_LIST_EMBEDDED_SCRIPTS
> + /* list contents of an embedded script */
> + if (*argptr) {
> + char *script;
> +
> + if ((script=get_script_content(*argptr)) != NULL) {
> + full_write1_str(script);
> + }
> + } else
> +#endif
> + /* list names of all embedded scripts */
> + {
> + const char *s = script_names;
> +
> + while (*s) {
> + printf("%s\n", s);
> + while (*s++ != '\0')
> + continue;
> + }
> + }
> + exit(0);
> +#endif
> } else {
> setoption(c, val);
> }
> @@ -13353,6 +13519,11 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path)
> #if ENABLE_FEATURE_SH_STANDALONE
> {
> int applet_no = find_applet_by_name(name);
> +# if NUM_SCRIPTS > 0
> + /* embedded script indices are offset by NUM_APPLETS */
> + if (applet_no < 0)
> + applet_no = find_script_by_name(name, NUM_APPLETS);
> +# endif
> if (applet_no >= 0) {
> entry->cmdtype = CMDNORMAL;
> entry->u.index = -2 - applet_no;
> @@ -14003,9 +14174,17 @@ init(void)
> }
> }
>
> -
> //usage:#define ash_trivial_usage
> //usage: "[-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS] / -s [ARGS]]"
> +//usage: IF_ASH_EMBEDDED_SCRIPTS(
> +//usage: " [-L"
> +//usage: )
> +//usage: IF_ASH_LIST_EMBEDDED_SCRIPTS(
> +//usage: " [name]"
> +//usage: )
> +//usage: IF_ASH_EMBEDDED_SCRIPTS(
> +//usage: "]"
> +//usage: )
> //usage:#define ash_full_usage "\n\n"
> //usage: "Unix shell interpreter"
>
> @@ -14048,6 +14227,9 @@ procargs(char **argv)
> optlist[i] = 0;
> #if DEBUG == 2
> debug = 1;
> +#endif
> +#if NUM_SCRIPTS > 0 && !ENABLE_FEATURE_SH_STANDALONE
> + alias_scripts();
> #endif
> /* POSIX 1003.2: first arg after "-c CMD" is $0, remainder $1... */
> if (xminusc) {
> @@ -14055,6 +14237,18 @@ procargs(char **argv)
> if (*xargv)
> goto setarg0;
> } else if (!sflag) {
> +#if NUM_SCRIPTS > 0
> + /* check for an embedded script */
> + char *script;
> + if ((script=get_script_content(*xargv)) != NULL) {
> + setinputstring(script);
> +#if !ENABLE_ASH_LIST_EMBEDDED_SCRIPTS
> + /* prevent tracing */
> + xflag = 0;
> +#endif
> + goto setarg0;
> + }
> +#endif
> setinputfile(*xargv, 0);
> setarg0:
> arg0 = *xargv++;
> --
> 2.17.2
>
> _______________________________________________
> busybox mailing list
> busybox at busybox.net
> http://lists.busybox.net/mailman/listinfo/busybox
More information about the busybox
mailing list