[PATCH] Support custom and applet scripts in embed directory

Ron Yorston rmy at pobox.com
Sat Nov 17 13:37:37 UTC 2018


BusyBox has support for embedded shell scripts.  Two types can be
distinguished:  custom scripts and scripts implementing applets.

The required scripts should be present in the 'embed' directory at
build-time.

All embedded scripts can be run by name and are subject to tab
completion in standalone shell mode.

Custom scripts are otherwise unsupported by BusyBox and are intended
as a simple means for application-specific code to be included in
the binary.

Applet scripts are integrated with the BusyBox build system and
are intended to be used to ship standard applets that just happen
to be implemented as scripts.  They can be configured at build time
and appear just like native applets.

A stub C program should be written to provide the usual applet
configuration details and placed in a suitable subsystem directory.  It
may be helpful to have a configuration option to enable any dependencies
the script requires:  see the 'nologin' applet for an example.

Of course, there's nothing stopping developers from including their
own custom scripts as applets if they so choose.

function                                             old     new   delta
scripted_main                                          -      53     +53
packed_usage                                       32951   32997     +46
.rodata                                           168610  168655     +45
applet_names                                        2695    2703      +8
applet_main                                         3128    3136      +8
script_names                                           9       -      -9
find_script_by_name                                   57      26     -31
run_applet_and_exit                                  782     728     -54
------------------------------------------------------------------------------
(add/remove: 1/1 grow/shrink: 4/2 up/down: 160/-94)            Total: 66 bytes

Signed-off-by: Ron Yorston <rmy at pobox.com>
---
 Makefile                     |   2 +-
 applets/busybox.mknotscripts |  16 +++++
 applets/busybox.mkscripts    |  16 +++++
 include/applets.src.h        |  15 +++++
 include/libbb.h              |   1 +
 libbb/appletlib.c            |  55 ++++++++++++-----
 libbb/lineedit.c             |  10 ++--
 scripts/embedded_scripts     | 113 ++++++++++++++++++++++++++++-------
 scripts/gen_build_files.sh   |  12 ++++
 shell/ash.c                  |   6 ++
 util-linux/nologin.c         |  27 +++++++++
 11 files changed, 229 insertions(+), 44 deletions(-)
 create mode 100755 applets/busybox.mknotscripts
 create mode 100755 applets/busybox.mkscripts
 create mode 100644 util-linux/nologin.c

diff --git a/Makefile b/Makefile
index 8a0dbdf49..9ee5453d3 100644
--- a/Makefile
+++ b/Makefile
@@ -851,7 +851,7 @@ quiet_cmd_gen_common_bufsiz = GEN     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
+      cmd_gen_embedded_scripts = $(srctree)/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 $(wildcard embed/*) scripts/embedded_scripts
 	$(call cmd,split_autoconf)
diff --git a/applets/busybox.mknotscripts b/applets/busybox.mknotscripts
new file mode 100755
index 000000000..b4ad8666d
--- /dev/null
+++ b/applets/busybox.mknotscripts
@@ -0,0 +1,16 @@
+#!/bin/sh
+# Make a list of disabled busybox scripted applets.
+
+# input $1: full path to Config.h
+# input $2: full path to applets.h
+# output (stdout): list of pathnames that should be linked to busybox
+
+export LC_ALL=POSIX
+export LC_CTYPE=POSIX
+
+CONFIG_H=${1:-include/autoconf.h}
+APPLETS_H=${2:-include/applets.h}
+$HOSTCC -E -DMAKE_NOT_SCRIPTS -include $CONFIG_H $APPLETS_H |
+  awk '/^[ \t]*SCRIPT/{
+	print $2
+  }'
diff --git a/applets/busybox.mkscripts b/applets/busybox.mkscripts
new file mode 100755
index 000000000..b1166a9b9
--- /dev/null
+++ b/applets/busybox.mkscripts
@@ -0,0 +1,16 @@
+#!/bin/sh
+# Make a list of enabled busybox scripted applets.
+
+# input $1: full path to Config.h
+# input $2: full path to applets.h
+# output (stdout): list of pathnames that should be linked to busybox
+
+export LC_ALL=POSIX
+export LC_CTYPE=POSIX
+
+CONFIG_H=${1:-include/autoconf.h}
+APPLETS_H=${2:-include/applets.h}
+$HOSTCC -E -DMAKE_SCRIPTS -include $CONFIG_H $APPLETS_H |
+  awk '/^[ \t]*SCRIPT/{
+	print $2
+  }'
diff --git a/include/applets.src.h b/include/applets.src.h
index 2ddf120ad..eaf9eced8 100644
--- a/include/applets.src.h
+++ b/include/applets.src.h
@@ -27,36 +27,49 @@ s     - suid type:
 # define APPLET_ODDNAME(name,main,l,s,help)  int main##_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 # define APPLET_NOEXEC(name,main,l,s,help)   int main##_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 # define APPLET_NOFORK(name,main,l,s,help)   int main##_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+# define APPLET_SCRIPTED(name,main,l,s,help)
 
 #elif defined(NAME_MAIN)
 # define APPLET(name,l,s)                    name name##_main
 # define APPLET_ODDNAME(name,main,l,s,help)  name main##_main
 # define APPLET_NOEXEC(name,main,l,s,help)   name main##_main
 # define APPLET_NOFORK(name,main,l,s,help)   name main##_main
+# define APPLET_SCRIPTED(name,main,l,s,help) name scripted_main
 
 #elif defined(MAKE_USAGE) && ENABLE_FEATURE_VERBOSE_USAGE
 # define APPLET(name,l,s)                    MAKE_USAGE(#name, name##_trivial_usage name##_full_usage)
 # define APPLET_ODDNAME(name,main,l,s,help)  MAKE_USAGE(#name, help##_trivial_usage help##_full_usage)
 # define APPLET_NOEXEC(name,main,l,s,help)   MAKE_USAGE(#name, help##_trivial_usage help##_full_usage)
 # define APPLET_NOFORK(name,main,l,s,help)   MAKE_USAGE(#name, help##_trivial_usage help##_full_usage)
+# define APPLET_SCRIPTED(name,main,l,s,help) MAKE_USAGE(#name, help##_trivial_usage help##_full_usage)
 
 #elif defined(MAKE_USAGE) && !ENABLE_FEATURE_VERBOSE_USAGE
 # define APPLET(name,l,s)                    MAKE_USAGE(#name, name##_trivial_usage)
 # define APPLET_ODDNAME(name,main,l,s,help)  MAKE_USAGE(#name, help##_trivial_usage)
 # define APPLET_NOEXEC(name,main,l,s,help)   MAKE_USAGE(#name, help##_trivial_usage)
 # define APPLET_NOFORK(name,main,l,s,help)   MAKE_USAGE(#name, help##_trivial_usage)
+# define APPLET_SCRIPTED(name,main,l,s,help) MAKE_USAGE(#name, help##_trivial_usage)
 
 #elif defined(MAKE_LINKS)
 # define APPLET(name,l,c)                    LINK l name
 # define APPLET_ODDNAME(name,main,l,s,help)  LINK l name
 # define APPLET_NOEXEC(name,main,l,s,help)   LINK l name
 # define APPLET_NOFORK(name,main,l,s,help)   LINK l name
+# define APPLET_SCRIPTED(name,main,l,s,help) LINK l name
 
 #elif defined(MAKE_SUID)
 # define APPLET(name,l,s)                    SUID s l name
 # define APPLET_ODDNAME(name,main,l,s,help)  SUID s l name
 # define APPLET_NOEXEC(name,main,l,s,help)   SUID s l name
 # define APPLET_NOFORK(name,main,l,s,help)   SUID s l name
+# define APPLET_SCRIPTED(name,main,l,s,help) SUID s l name
+
+#elif defined(MAKE_SCRIPTS) || defined(MAKE_NOT_SCRIPTS)
+# define APPLET(name,l,s)
+# define APPLET_ODDNAME(name,main,l,s,help)
+# define APPLET_NOEXEC(name,main,l,s,help)
+# define APPLET_NOFORK(name,main,l,s,help)
+# define APPLET_SCRIPTED(name,main,l,s,help) SCRIPT name
 
 #else
   static struct bb_applet applets[] = { /*    name, main, location, need_suid */
@@ -64,6 +77,7 @@ s     - suid type:
 # define APPLET_ODDNAME(name,main,l,s,help)  { #name, #main, l, s },
 # define APPLET_NOEXEC(name,main,l,s,help)   { #name, #main, l, s, 1 },
 # define APPLET_NOFORK(name,main,l,s,help)   { #name, #main, l, s, 1, 1 },
+# define APPLET_SCRIPTED(name,main,l,s,help) { #name, #main, l, s },
 #endif
 
 #if ENABLE_INSTALL_NO_USR
@@ -84,3 +98,4 @@ INSERT
 #undef APPLET_ODDNAME
 #undef APPLET_NOEXEC
 #undef APPLET_NOFORK
+#undef APPLET_SCRIPTED
diff --git a/include/libbb.h b/include/libbb.h
index a32608ebd..3fcff421b 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -1324,6 +1324,7 @@ void bb_logenv_override(void) FAST_FUNC;
 /* Embedded script support */
 int find_script_by_name(const char *name) FAST_FUNC;
 char *get_script_content(unsigned n) FAST_FUNC;
+int scripted_main(int argc UNUSED_PARAM, char** argv) FAST_FUNC;
 
 /* Applets which are useful from another applets */
 int bb_cat(char** argv) FAST_FUNC;
diff --git a/libbb/appletlib.c b/libbb/appletlib.c
index 6dfaf1f41..ceb18e277 100644
--- a/libbb/appletlib.c
+++ b/libbb/appletlib.c
@@ -51,10 +51,12 @@
 #include "usage_compressed.h"
 
 #if ENABLE_ASH_EMBEDDED_SCRIPTS
-# define DEFINE_script_names 1
+# define DEFINE_SCRIPT_DATA 1
 # include "embedded_scripts.h"
 #else
 # define NUM_SCRIPTS 0
+# define NUM_CUSTOM_SCRIPTS 0
+# define NUM_APPLET_SCRIPTS 0
 #endif
 #if NUM_SCRIPTS > 0
 # include "bb_archive.h"
@@ -754,6 +756,14 @@ static void install_links(const char *busybox UNUSED_PARAM,
 }
 # endif
 
+int FAST_FUNC scripted_main(int argc UNUSED_PARAM, char **argv)
+{
+	int script = find_script_by_name(applet_name);
+	if (script >= 0)
+		exit(ash_main(-script - 1, argv));
+	return 0;
+}
+
 static void run_applet_and_exit(const char *name, char **argv) NORETURN;
 
 # if ENABLE_BUSYBOX
@@ -819,7 +829,7 @@ int busybox_main(int argc UNUSED_PARAM, char **argv)
 		output_width--;
 		a = applet_names;
 		{
-#  if NUM_SCRIPTS > 0
+#  if NUM_CUSTOM_SCRIPTS > 0
 			int i;
 			for (i = 0; i < 2; i++, a = script_names)
 #  endif
@@ -949,16 +959,32 @@ void FAST_FUNC run_applet_no_and_exit(int applet_no, const char *name, char **ar
 int FAST_FUNC
 find_script_by_name(const char *name)
 {
-	const char *s = script_names;
-	int i = 0;
+	int i;
+#  if NUM_APPLET_SCRIPTS > 0
+	int applet = find_applet_by_name(name);
+
+	if (applet >= 0) {
+		for (i=0; i<NUM_APPLET_SCRIPTS; ++i)
+			if (applet_numbers[i] == applet)
+				return NUM_CUSTOM_SCRIPTS + i;
+	}
+#  endif
 
-	while (*s) {
-		if (strcmp(name, s) == 0)
-			return i;
-		i++;
-		while (*s++ != '\0')
-			continue;
+#  if NUM_CUSTOM_SCRIPTS > 0
+	{
+		const char *s = script_names;
+
+		i = 0;
+		while (*s) {
+			if (strcmp(name, s) == 0)
+				return i;
+			i++;
+			while (*s++ != '\0')
+				continue;
+		}
 	}
+#  endif
+
 	return -0x10000; /* make it so that NUM_APPLETS + <error> is still < 0 */
 }
 
@@ -993,12 +1019,9 @@ 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));
-	}
+#  if NUM_CUSTOM_SCRIPTS > 0
+	/* returns if script is not found */
+	scripted_main(0, argv);
 #  endif
 
 	/*bb_error_msg_and_die("applet not found"); - links in printf */
diff --git a/libbb/lineedit.c b/libbb/lineedit.c
index 618e7c221..832b9c78a 100644
--- a/libbb/lineedit.c
+++ b/libbb/lineedit.c
@@ -45,6 +45,8 @@
 # include "embedded_scripts.h"
 #else
 # define NUM_SCRIPTS 0
+# define NUM_CUSTOM_SCRIPTS 0
+# define NUM_APPLET_SCRIPTS 0
 #endif
 
 #ifndef _POSIX_VDISABLE
@@ -812,14 +814,14 @@ static NOINLINE unsigned complete_cmd_dir_file(const char *command, int type)
 	}
 	pf_len = strlen(pfind);
 
-# if ENABLE_FEATURE_SH_STANDALONE && (NUM_APPLETS != 1 || NUM_SCRIPTS > 0)
+# if ENABLE_FEATURE_SH_STANDALONE && (NUM_APPLETS != 1 || NUM_CUSTOM_SCRIPTS > 0)
 	if (type == FIND_EXE_ONLY && !dirbuf) {
 		const char *p;
-#  if NUM_APPLETS != 1 && NUM_SCRIPTS > 0
+#  if NUM_APPLETS != 1 && NUM_CUSTOM_SCRIPTS > 0
 		for (i = 0, p = applet_names; i < 2; i++, p = script_names)
-#  elif NUM_APPLETS != 1 /* and NUM_SCRIPTS == 0 */
+#  elif NUM_APPLETS != 1 /* and NUM_CUSTOM_SCRIPTS == 0 */
 		p = applet_names;
-#  else /* NUM_APPLETS == 1 && NUM_SCRIPTS > 0 */
+#  else /* NUM_APPLETS == 1 && NUM_CUSTOM_SCRIPTS > 0 */
 		p = script_names;
 #  endif
 		while (*p) {
diff --git a/scripts/embedded_scripts b/scripts/embedded_scripts
index 7245ba6e0..13318d0ce 100755
--- a/scripts/embedded_scripts
+++ b/scripts/embedded_scripts
@@ -14,46 +14,113 @@ if test $? != 0; then
 	exit 1
 fi
 
-exec >"$target.$$"
-
-scripts=""
+all_scripts=""
 if [ -d "$loc" ]
 then
-	scripts=$(cd $loc; ls * 2>/dev/null)
+	all_scripts=$(cd $loc; ls * 2>/dev/null)
 fi
+applet_scripts=$(applets/busybox.mkscripts)
+not_applet_scripts=$(applets/busybox.mknotscripts)
 
-n=$(echo $scripts | wc -w)
+# check for presence of applet scripts, even if disabled
+for i in $applet_scripts $not_applet_scripts
+do
+	if [ ! -f $loc/$i ]
+	then
+		echo "missing applet script $i"
+		exit 1
+	fi
+done
 
-if [ $n -ne 0 ]
+custom_scripts=""
+for i in $all_scripts
+do
+	found=0
+	for j in $applet_scripts $not_applet_scripts
+	do
+		if [ "$i" = "$j" ]
+		then
+			found=1
+			break;
+		fi
+	done
+	if [ $found -eq 0 ]
+	then
+		# anything that isn't an applet script is a custom script
+		custom_scripts="$custom_scripts $i"
+	fi
+done
+
+n=$(echo $custom_scripts $applet_scripts | wc -w)
+nall1=$(echo $all_scripts | wc -w)
+nall2=$(echo $custom_scripts $applet_scripts $not_applet_scripts | wc -w)
+num_cscripts=$(echo $custom_scripts | wc -w)
+num_ascripts=$(echo $applet_scripts | wc -w)
+
+if [ $nall1 -ne $nall2 ]
 then
-	printf '#ifdef DEFINE_script_names\n'
-	printf 'const char script_names[] ALIGN1 = '
-	for i in $scripts
+	echo "script mismatch $n != $nall"
+	exit 1
+fi
+
+concatenate_scripts() {
+	for i in $custom_scripts $applet_scripts
 	do
-		printf '"%s\\0"' $i
+		cat $loc/$i
+		printf '\000'
 	done
-	printf ';\n'
+}
+
+exec >"$target.$$"
+
+if [ $n -ne 0 ]
+then
+	printf '#ifdef DEFINE_SCRIPT_DATA\n'
+	if [ $num_cscripts -ne 0 ]
+	then
+		printf 'const char script_names[] ALIGN1 = ""\n'
+		for i in $custom_scripts
+		do
+			printf '"%s\\0"\n' $i
+		done
+		printf ';\n'
+	fi
+	if [ $num_ascripts -ne 0 ]
+	then
+		printf 'const uint16_t applet_numbers[] = {\n'
+		for i in $applet_scripts
+		do
+			# TODO support applets with names including invalid characters
+			printf '\tAPPLET_NO_%s,\n' $i
+		done
+		printf '};\n'
+	fi
 	printf '#else\n'
-	printf 'extern const char script_names[] ALIGN1;\n'
+	if [ $num_cscripts -ne 0 ]
+	then
+		printf 'extern const char script_names[] ALIGN1;\n'
+	fi
+	if [ $num_ascripts -ne 0 ]
+	then
+		printf 'extern const uint16_t applet_numbers[];\n'
+	fi
 	printf '#endif\n'
 fi
-printf "#define NUM_SCRIPTS $n\n\n"
+
+printf "\n"
+printf '#define NUM_SCRIPTS %d\n' $n
+printf '#define NUM_CUSTOM_SCRIPTS %d\n' $num_cscripts
+printf '#define NUM_APPLET_SCRIPTS %d\n' $num_ascripts
+printf "\n"
 
 if [ $n -ne 0 ]
 then
 	printf '#define UNPACKED_SCRIPTS_LENGTH '
-	for i in $scripts
-	do
-		cat $loc/$i
-		printf '\000'
-	done | wc -c
+	concatenate_scripts | 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 \
+	concatenate_scripts | bzip2 -1 | $DD bs=2 skip=1 2>/dev/null | \
+	od -v -b \
 	| grep -v '^ ' \
 	| $SED -e 's/^[^ ]*//' \
 		-e 's/ //g' \
diff --git a/scripts/gen_build_files.sh b/scripts/gen_build_files.sh
index f79fa2f83..d9f9db64e 100755
--- a/scripts/gen_build_files.sh
+++ b/scripts/gen_build_files.sh
@@ -50,6 +50,18 @@ generate()
 
 # (Re)generate include/applets.h
 sed -n 's@^//applet:@@p' "$srctree"/*/*.c "$srctree"/*/*/*.c \
+| awk '
+	/APPLET_SCRIPTED/ {
+		printf("#if defined(MAKE_NOT_SCRIPTS)\n");
+		not = $0;
+		sub(/^IF_/, "IF_NOT_", not);
+		print not;
+		printf("#else\n");
+		print;
+		printf("#endif\n");
+		next;
+	}
+	{print;}' \
 | generate \
 	"$srctree/include/applets.src.h" \
 	"include/applets.h" \
diff --git a/shell/ash.c b/shell/ash.c
index b1f8f15d2..cab675316 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -158,6 +158,10 @@
 //config:	at build time. Like applets, scripts can be run as
 //config:	'busybox SCRIPT ...' or by linking their name to the binary.
 //config:
+//config:	This also allows applets to be implemented as scripts: place
+//config:	the script in the 'embed' directory and a stub C file containing
+//config:	configuration in the appropriate subsystem directory.
+//config:
 //config:endif # ash options
 
 //applet:IF_ASH(APPLET(ash, BB_DIR_BIN, BB_SUID_DROP))
@@ -195,6 +199,8 @@
 # include "embedded_scripts.h"
 #else
 # define NUM_SCRIPTS 0
+# define NUM_CUSTOM_SCRIPTS 0
+# define NUM_APPLET_SCRIPTS 0
 #endif
 
 /* So far, all bash compat is controlled by one config option */
diff --git a/util-linux/nologin.c b/util-linux/nologin.c
new file mode 100644
index 000000000..52c640784
--- /dev/null
+++ b/util-linux/nologin.c
@@ -0,0 +1,27 @@
+//config:config NOLOGIN
+//config:   bool "nologin"
+//config:   default y
+//config:   depends on ASH_EMBEDDED_SCRIPTS
+//config:   help
+//config:   Politely refuse a login
+//config:
+//config:config NOLOGIN_DEPENDENCIES
+//config:   bool "Dependencies for nologin"
+//config:   default y
+//config:   depends on NOLOGIN
+//config:   select CAT
+//config:   select ECHO
+//config:   select SLEEP
+//config:   help
+//config:   nologin is implemented as a shell script. It requires the
+//config:   following in the runtime environment:
+//config:      cat echo sleep
+//config:   If you know these will be available externally you can
+//config:   disable this option.
+
+//applet:IF_NOLOGIN(APPLET_SCRIPTED(nologin, scripted, BB_DIR_USR_SBIN, BB_SUID_DROP, nologin))
+
+//usage:#define nologin_trivial_usage
+//usage:    ""
+//usage:#define nologin_full_usage "\n\n"
+//usage:    "Display a message that an account is not available and exit non-zero."
-- 
2.19.1



More information about the busybox mailing list