[prev in list] [next in list] [prev in thread] [next in thread] 

List:       busybox
Subject:    [RFC PATCH 1/1] ash: allow shell scripts to be embedded in the binary
From:       Ron Yorston <rmy () pobox ! com>
Date:       2018-10-25 11:28:46
Message-ID: 5bd1a8ee.fj9g2+EE/qvwmBAE%rmy () pobox ! com
[Download RAW message or body]

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@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@busybox.net
http://lists.busybox.net/mailman/listinfo/busybox


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic