Move applets/applet.c into libbb, allows to get rid of --whole-archive
(smaller code). Tested in static and shared mode.

diff --git a/libbb/appletlib.c b/libbb/appletlib.c
index cfa60a9..7808df5 100644
--- a/libbb/appletlib.c
+++ b/libbb/appletlib.c
@@ -1,11 +1,15 @@
 /* vi: set sw=4 ts=4: */
 /*
- * Support for main() which needs to end up in libbusybox, not busybox,
- * if one builds libbusybox.
+ * Utility routines.
  *
- * Copyright (C) 2007 Denys Vlasenko <vda.linux@googlemail.com>
+ * Copyright (C) tons of folks.  Tracking down who wrote what
+ * isn't something I'm going to worry about...  If you wrote something
+ * here, please feel free to acknowledge your work.
  *
- * Licensed under GPLv2, see file License in this tarball for details.
+ * Based in part on code from sash, Copyright (c) 1999 by David I. Bell
+ * Permission has been granted to redistribute this code under the GPL.
+ *
+ * Licensed under GPLv2 or later, see file License in this tarball for details.
  */
 
 #include <assert.h>
@@ -140,3 +144,544 @@
 		bb_show_usage();
 #endif
 }
+
+/* The code below can well be in applets/applets.c, as it is used only
+ * for busybox binary, not "individual" binaries.
+ * However, keeping it here and linking it into libbusybox.so
+ * (together with remaining tiny applets/applets.o)
+ * makes it possible to avoid --whole-archive at link time.
+ * This makes (shared busybox) + libbusybox smaller.
+ * (--gc-sections would be even better....)
+ */
+
+const char *applet_name;
+#if !BB_MMU
+bool re_execed;
+#endif
+
+USE_FEATURE_SUID(static uid_t ruid;)  /* real uid */
+
+#if ENABLE_FEATURE_SUID_CONFIG
+
+/* applets[] is const, so we have to define this "override" structure */
+static struct BB_suid_config {
+	const struct bb_applet *m_applet;
+	uid_t m_uid;
+	gid_t m_gid;
+	mode_t m_mode;
+	struct BB_suid_config *m_next;
+} *suid_config;
+
+static bool suid_cfg_readable;
+
+/* check if u is member of group g */
+static int ingroup(uid_t u, gid_t g)
+{
+	struct group *grp = getgrgid(g);
+
+	if (grp) {
+		char **mem;
+
+		for (mem = grp->gr_mem; *mem; mem++) {
+			struct passwd *pwd = getpwnam(*mem);
+
+			if (pwd && (pwd->pw_uid == u))
+				return 1;
+		}
+	}
+	return 0;
+}
+
+/* This should probably be a libbb routine.  In that case,
+ * I'd probably rename it to something like bb_trimmed_slice.
+ */
+static char *get_trimmed_slice(char *s, char *e)
+{
+	/* First, consider the value at e to be nul and back up until we
+	 * reach a non-space char.  Set the char after that (possibly at
+	 * the original e) to nul. */
+	while (e-- > s) {
+		if (!isspace(*e)) {
+			break;
+		}
+	}
+	e[1] = '\0';
+
+	/* Next, advance past all leading space and return a ptr to the
+	 * first non-space char; possibly the terminating nul. */
+	return skip_whitespace(s);
+}
+
+/* Don't depend on the tools to combine strings. */
+static const char config_file[] ALIGN1 = "/etc/busybox.conf";
+
+/* We don't supply a value for the nul, so an index adjustment is
+ * necessary below.  Also, we use unsigned short here to save some
+ * space even though these are really mode_t values. */
+static const unsigned short mode_mask[] ALIGN2 = {
+	/*  SST     sst                 xxx         --- */
+	S_ISUID,    S_ISUID|S_IXUSR,    S_IXUSR,    0,	/* user */
+	S_ISGID,    S_ISGID|S_IXGRP,    S_IXGRP,    0,	/* group */
+	0,          S_IXOTH,            S_IXOTH,    0	/* other */
+};
+
+#define parse_error(x)  do { errmsg = x; goto pe_label; } while (0)
+
+static void parse_config_file(void)
+{
+	struct BB_suid_config *sct_head;
+	struct BB_suid_config *sct;
+	const struct bb_applet *applet;
+	FILE *f;
+	const char *errmsg;
+	char *s;
+	char *e;
+	int i;
+	unsigned lc;
+	smallint section;
+	char buffer[256];
+	struct stat st;
+
+	assert(!suid_config); /* Should be set to NULL by bss init. */
+
+	ruid = getuid();
+	if (ruid == 0) /* run by root - don't need to even read config file */
+		return;
+
+	if ((stat(config_file, &st) != 0)       /* No config file? */
+	 || !S_ISREG(st.st_mode)                /* Not a regular file? */
+	 || (st.st_uid != 0)                    /* Not owned by root? */
+	 || (st.st_mode & (S_IWGRP | S_IWOTH))  /* Writable by non-root? */
+	 || !(f = fopen(config_file, "r"))      /* Cannot open? */
+	) {
+		return;
+	}
+
+	suid_cfg_readable = 1;
+	sct_head = NULL;
+	section = lc = 0;
+
+	while (1) {
+		s = buffer;
+
+		if (!fgets(s, sizeof(buffer), f)) { /* Are we done? */
+			if (ferror(f)) {   /* Make sure it wasn't a read error. */
+				parse_error("reading");
+			}
+			fclose(f);
+			suid_config = sct_head;	/* Success, so set the pointer. */
+			return;
+		}
+
+		lc++;					/* Got a (partial) line. */
+
+		/* If a line is too long for our buffer, we consider it an error.
+		 * The following test does mistreat one corner case though.
+		 * If the final line of the file does not end with a newline and
+		 * yet exactly fills the buffer, it will be treated as too long
+		 * even though there isn't really a problem.  But it isn't really
+		 * worth adding code to deal with such an unlikely situation, and
+		 * we do err on the side of caution.  Besides, the line would be
+		 * too long if it did end with a newline. */
+		if (!strchr(s, '\n') && !feof(f)) {
+			parse_error("line too long");
+		}
+
+		/* Trim leading and trailing whitespace, ignoring comments, and
+		 * check if the resulting string is empty. */
+		s = get_trimmed_slice(s, strchrnul(s, '#'));
+		if (!*s) {
+			continue;
+		}
+
+		/* Check for a section header. */
+
+		if (*s == '[') {
+			/* Unlike the old code, we ignore leading and trailing
+			 * whitespace for the section name.  We also require that
+			 * there are no stray characters after the closing bracket. */
+			e = strchr(s, ']');
+			if (!e   /* Missing right bracket? */
+			 || e[1] /* Trailing characters? */
+			 || !*(s = get_trimmed_slice(s+1, e)) /* Missing name? */
+			) {
+				parse_error("section header");
+			}
+			/* Right now we only have one section so just check it.
+			 * If more sections are added in the future, please don't
+			 * resort to cascading ifs with multiple strcasecmp calls.
+			 * That kind of bloated code is all too common.  A loop
+			 * and a string table would be a better choice unless the
+			 * number of sections is very small. */
+			if (strcasecmp(s, "SUID") == 0) {
+				section = 1;
+				continue;
+			}
+			section = -1;	/* Unknown section so set to skip. */
+			continue;
+		}
+
+		/* Process sections. */
+
+		if (section == 1) {		/* SUID */
+			/* Since we trimmed leading and trailing space above, we're
+			 * now looking for strings of the form
+			 *    <key>[::space::]*=[::space::]*<value>
+			 * where both key and value could contain inner whitespace. */
+
+			/* First get the key (an applet name in our case). */
+			e = strchr(s, '=');
+			if (e) {
+				s = get_trimmed_slice(s, e);
+			}
+			if (!e || !*s) {	/* Missing '=' or empty key. */
+				parse_error("keyword");
+			}
+
+			/* Ok, we have an applet name.  Process the rhs if this
+			 * applet is currently built in and ignore it otherwise.
+			 * Note: this can hide config file bugs which only pop
+			 * up when the busybox configuration is changed. */
+			applet = find_applet_by_name(s);
+			if (applet) {
+				/* Note: We currently don't check for duplicates!
+				 * The last config line for each applet will be the
+				 * one used since we insert at the head of the list.
+				 * I suppose this could be considered a feature. */
+				sct = xmalloc(sizeof(struct BB_suid_config));
+				sct->m_applet = applet;
+				sct->m_mode = 0;
+				sct->m_next = sct_head;
+				sct_head = sct;
+
+				/* Get the specified mode. */
+
+				e = skip_whitespace(e+1);
+
+				for (i = 0; i < 3; i++) {
+					/* There are 4 chars + 1 nul for each of user/group/other. */
+					static const char mode_chars[] ALIGN1 = "Ssx-\0" "Ssx-\0" "Ttx-";
+
+					const char *q;
+					q = strchrnul(mode_chars + 5*i, *e++);
+					if (!*q) {
+						parse_error("mode");
+					}
+					/* Adjust by -i to account for nul. */
+					sct->m_mode |= mode_mask[(q - mode_chars) - i];
+				}
+
+				/* Now get the the user/group info. */
+
+				s = skip_whitespace(e);
+
+				/* Note: we require whitespace between the mode and the
+				 * user/group info. */
+				if ((s == e) || !(e = strchr(s, '.'))) {
+					parse_error("<uid>.<gid>");
+				}
+				*e++ = '\0';
+
+				/* We can't use get_ug_id here since it would exit()
+				 * if a uid or gid was not found.  Oh well... */
+				sct->m_uid = bb_strtoul(s, NULL, 10);
+				if (errno) {
+					struct passwd *pwd = getpwnam(s);
+					if (!pwd) {
+						parse_error("user");
+					}
+					sct->m_uid = pwd->pw_uid;
+				}
+
+				sct->m_gid = bb_strtoul(e, NULL, 10);
+				if (errno) {
+					struct group *grp;
+					grp = getgrnam(e);
+					if (!grp) {
+						parse_error("group");
+					}
+					sct->m_gid = grp->gr_gid;
+				}
+			}
+			continue;
+		}
+
+		/* Unknown sections are ignored. */
+
+		/* Encountering configuration lines prior to seeing a
+		 * section header is treated as an error.  This is how
+		 * the old code worked, but it may not be desirable.
+		 * We may want to simply ignore such lines in case they
+		 * are used in some future version of busybox. */
+		if (!section) {
+			parse_error("keyword outside section");
+		}
+
+	} /* while (1) */
+
+ pe_label:
+	fprintf(stderr, "Parse error in %s, line %d: %s\n",
+			config_file, lc, errmsg);
+
+	fclose(f);
+	/* Release any allocated memory before returning. */
+	while (sct_head) {
+		sct = sct_head->m_next;
+		free(sct_head);
+		sct_head = sct;
+	}
+}
+#else
+static inline void parse_config_file(void)
+{
+	USE_FEATURE_SUID(ruid = getuid();)
+}
+#endif /* FEATURE_SUID_CONFIG */
+
+
+#if ENABLE_FEATURE_SUID
+static void check_suid(const struct bb_applet *applet)
+{
+	gid_t rgid;  /* real gid */
+
+	if (ruid == 0) /* set by parse_config_file() */
+		return; /* run by root - no need to check more */
+	rgid = getgid();
+
+#if ENABLE_FEATURE_SUID_CONFIG
+	if (suid_cfg_readable) {
+		uid_t uid;
+		struct BB_suid_config *sct;
+		mode_t m;
+
+		for (sct = suid_config; sct; sct = sct->m_next) {
+			if (sct->m_applet == applet)
+				goto found;
+		}
+		/* default: drop all privileges */
+		xsetgid(rgid);
+		xsetuid(ruid);
+		return;
+ found:
+		m = sct->m_mode;
+		if (sct->m_uid == ruid)
+			/* same uid */
+			m >>= 6;
+		else if ((sct->m_gid == rgid) || ingroup(ruid, sct->m_gid))
+			/* same group / in group */
+			m >>= 3;
+
+		if (!(m & S_IXOTH))           /* is x bit not set ? */
+			bb_error_msg_and_die("you have no permission to run this applet!");
+
+		/* _both_ sgid and group_exec have to be set for setegid */
+		if ((sct->m_mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP))
+			rgid = sct->m_gid;
+		/* else (no setegid) we will set egid = rgid */
+
+		/* We set effective AND saved ids. If saved-id is not set
+		 * like we do below, seteiud(0) can still later succeed! */
+		if (setresgid(-1, rgid, rgid))
+			bb_perror_msg_and_die("setresgid");
+
+		/* do we have to set effective uid? */
+		uid = ruid;
+		if (sct->m_mode & S_ISUID)
+			uid = sct->m_uid;
+		/* else (no seteuid) we will set euid = ruid */
+
+		if (setresuid(-1, uid, uid))
+			bb_perror_msg_and_die("setresuid");
+		return;
+	}
+#if !ENABLE_FEATURE_SUID_CONFIG_QUIET
+	{
+		static bool onetime = 0;
+
+		if (!onetime) {
+			onetime = 1;
+			fprintf(stderr, "Using fallback suid method\n");
+		}
+	}
+#endif
+#endif
+
+	if (applet->need_suid == _BB_SUID_ALWAYS) {
+		/* Real uid is not 0. If euid isn't 0 too, suid bit
+		 * is most probably not set on our executable */
+		if (geteuid())
+			bb_error_msg_and_die("applet requires root privileges!");
+	} else if (applet->need_suid == _BB_SUID_NEVER) {
+		xsetgid(rgid);  /* drop all privileges */
+		xsetuid(ruid);
+	}
+}
+#else
+#define check_suid(x) ((void)0)
+#endif /* FEATURE_SUID */
+
+
+#if ENABLE_FEATURE_INSTALLER
+/* create (sym)links for each applet */
+static void install_links(const char *busybox, int use_symbolic_links)
+{
+	/* directory table
+	 * this should be consistent w/ the enum,
+	 * busybox.h::bb_install_loc_t, or else... */
+	static const char usr_bin [] ALIGN1 = "/usr/bin";
+	static const char usr_sbin[] ALIGN1 = "/usr/sbin";
+	static const char *const install_dir[] = {
+		&usr_bin [8], /* "", equivalent to "/" for concat_path_file() */
+		&usr_bin [4], /* "/bin" */
+		&usr_sbin[4], /* "/sbin" */
+		usr_bin,
+		usr_sbin
+	};
+
+	int (*lf)(const char *, const char *) = link;
+	char *fpc;
+	int i;
+	int rc;
+
+	if (use_symbolic_links)
+		lf = symlink;
+
+	for (i = 0; applets[i].name != NULL; i++) {
+		fpc = concat_path_file(
+				install_dir[applets[i].install_loc],
+				applets[i].name);
+		rc = lf(busybox, fpc);
+		if (rc != 0 && errno != EEXIST) {
+			bb_simple_perror_msg(fpc);
+		}
+		free(fpc);
+	}
+}
+#else
+#define install_links(x,y) ((void)0)
+#endif /* FEATURE_INSTALLER */
+
+/* If we were called as "busybox..." */
+static int busybox_main(char **argv)
+{
+	if (!argv[1]) {
+		/* Called without arguments */
+		const struct bb_applet *a;
+		int col, output_width;
+ help:
+		output_width = 80;
+		if (ENABLE_FEATURE_AUTOWIDTH) {
+			/* Obtain the terminal width */
+			get_terminal_width_height(0, &output_width, NULL);
+		}
+		/* leading tab and room to wrap */
+		output_width -= sizeof("start-stop-daemon, ") + 8;
+
+		printf("%s multi-call binary\n", bb_banner); /* reuse const string... */
+		printf("Copyright (C) 1998-2006 Erik Andersen, Rob Landley, and others.\n"
+		       "Licensed under GPLv2. See source distribution for full notice.\n"
+		       "\n"
+		       "Usage: busybox [function] [arguments]...\n"
+		       "   or: [function] [arguments]...\n"
+		       "\n"
+		       "\tBusyBox is a multi-call binary that combines many common Unix\n"
+		       "\tutilities into a single executable.  Most people will create a\n"
+		       "\tlink to busybox for each function they wish to use and BusyBox\n"
+		       "\twill act like whatever it was invoked as!\n"
+		       "\nCurrently defined functions:\n");
+		col = 0;
+		a = applets;
+		while (a->name) {
+			if (col > output_width) {
+				puts(",");
+				col = 0;
+			}
+			col += printf("%s%s", (col ? ", " : "\t"), a->name);
+			a++;
+		}
+		puts("\n");
+		return 0;
+	}
+
+	if (ENABLE_FEATURE_INSTALLER && strcmp(argv[1], "--install") == 0) {
+		const char *busybox;
+		busybox = xmalloc_readlink(bb_busybox_exec_path);
+		if (!busybox)
+			busybox = bb_busybox_exec_path;
+		/* -s makes symlinks */
+		install_links(busybox, argv[2] && strcmp(argv[2], "-s") == 0);
+		return 0;
+	}
+
+	if (strcmp(argv[1], "--help") == 0) {
+		/* "busybox --help [<applet>]" */
+		if (!argv[2])
+			goto help;
+		/* convert to "<applet> --help" */
+		argv[0] = argv[2];
+		argv[2] = NULL;
+	} else {
+		/* "busybox <applet> arg1 arg2 ..." */
+		argv++;
+	}
+	/* We support "busybox /a/path/to/applet args..." too. Allows for
+	 * "#!/bin/busybox"-style wrappers */
+	applet_name = bb_get_last_path_component_nostrip(argv[0]);
+	run_applet_and_exit(applet_name, argv);
+	bb_error_msg_and_die("applet not found");
+}
+
+void run_appletstruct_and_exit(const struct bb_applet *applet, char **argv)
+{
+	int argc = 1;
+
+	while (argv[argc])
+		argc++;
+
+	/* Reinit some shared global data */
+	optind = 1;
+	xfunc_error_retval = EXIT_FAILURE;
+
+	applet_name = applet->name;
+	if (argc == 2 && !strcmp(argv[1], "--help"))
+		bb_show_usage();
+	if (ENABLE_FEATURE_SUID)
+		check_suid(applet);
+	exit(applet->main(argc, argv));
+}
+
+void run_applet_and_exit(const char *name, char **argv)
+{
+	const struct bb_applet *applet = find_applet_by_name(name);
+	if (applet)
+		run_appletstruct_and_exit(applet, argv);
+	if (!strncmp(name, "busybox", 7))
+		exit(busybox_main(argv));
+}
+
+
+#if ENABLE_BUILD_LIBBUSYBOX
+int libbusybox_main(int argc, char **argv)
+#else
+int main(int argc, char **argv)
+#endif
+{
+	bbox_prepare_main(argv);
+
+#if !BB_MMU
+	/* NOMMU re-exec trick sets high-order bit in first byte of name */
+	if (argv[0][0] & 0x80) {
+		re_execed = 1;
+		argv[0][0] &= 0x7f;
+	}
+#endif
+	applet_name = argv[0];
+	if (applet_name[0] == '-')
+		applet_name++;
+	applet_name = bb_basename(applet_name);
+
+	parse_config_file(); /* ...maybe, if FEATURE_SUID_CONFIG */
+
+	run_applet_and_exit(applet_name, argv);
+	bb_error_msg_and_die("applet not found");
+}