ash: allow shell scripts to be embedded in the binary
authorDenys Vlasenko <vda.linux@googlemail.com>
Thu, 1 Nov 2018 08:53:25 +0000 (09:53 +0100)
committerDenys Vlasenko <vda.linux@googlemail.com>
Thu, 1 Nov 2018 09:15:13 +0000 (10:15 +0100)
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 <rmy@pobox.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
Makefile
applets_sh/nologin [deleted file]
archival/libarchive/Kbuild.src
embed/nologin [new file with mode: 0755]
include/.gitignore
include/libbb.h
libbb/appletlib.c
libbb/lineedit.c
scripts/embedded_scripts [new file with mode: 0755]
shell/ash.c

index 59ec83a6a1a6ec94f0dc157b05208fe440b3b2b5..8a0dbdf4903ffc07512b6d1a6ff371d3db83f0e8 100644 (file)
--- 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/applets_sh/nologin b/applets_sh/nologin
deleted file mode 100755 (executable)
index 3768eaa..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-cat /etc/nologin.txt 2>/dev/null || echo "This account is not available"
-sleep 5
-exit 1
index e1a8a75291763daedb9c30986ac1346c35b6b579..12e66a88b812f3bcd8acccfa8847d89762bcecc4 100644 (file)
@@ -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/embed/nologin b/embed/nologin
new file mode 100755 (executable)
index 0000000..3768eaa
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+cat /etc/nologin.txt 2>/dev/null || echo "This account is not available"
+sleep 5
+exit 1
index 75afff9ca46b4cfaa3bc2ac117536e729290c93a..13a96e0182c8a822a24fc85ab32a80b5fca08424 100644 (file)
@@ -5,6 +5,7 @@
 /autoconf.h
 /bbconfigopts_bz2.h
 /bbconfigopts.h
+/embedded_scripts.h
 /NUM_APPLETS.h
 /usage_compressed.h
 /usage.h
index 140404ff5c40d34553669c01d5ebc71389543bf2..affff58748629b0d6adfb4b49e56035130ee866f 100644 (file)
@@ -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);
index 319bcc2632be25998bc80bce9802300d7d9e270d..08720082e16c6d7323cd9a8d38dd71db6bc31048 100644 (file)
 
 #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"
@@ -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
@@ -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);
index b1e971f88da7d64f51947141c946a4bf43f9465e..aef1911d9ff5be8861be1a1e8f2fce140bff4454 100644 (file)
 #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
diff --git a/scripts/embedded_scripts b/scripts/embedded_scripts
new file mode 100755 (executable)
index 0000000..986e851
--- /dev/null
@@ -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"
index dc1a55a6bf8c143b22c6fdf2d78293a5e8d85850..25468d7965e3550868ce18bebc736ab2ed2ea0b9 100644 (file)
 //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))
 #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 */
@@ -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);
@@ -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;
@@ -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: "));