main: make busybox.conf mode handling less obscure
[oweals/busybox.git] / libbb / appletlib.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Utility routines.
4  *
5  * Copyright (C) tons of folks.  Tracking down who wrote what
6  * isn't something I'm going to worry about...  If you wrote something
7  * here, please feel free to acknowledge your work.
8  *
9  * Based in part on code from sash, Copyright (c) 1999 by David I. Bell
10  * Permission has been granted to redistribute this code under GPL.
11  *
12  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
13  */
14
15 /* We are trying to not use printf, this benefits the case when selected
16  * applets are really simple. Example:
17  *
18  * $ ./busybox
19  * ...
20  * Currently defined functions:
21  *         basename, false, true
22  *
23  * $ size busybox
24  *    text    data     bss     dec     hex filename
25  *    4473      52      72    4597    11f5 busybox
26  *
27  * FEATURE_INSTALLER or FEATURE_SUID will still link printf routines in. :(
28  */
29 #include "busybox.h"
30
31 #if !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \
32         || defined(__APPLE__) \
33     )
34 # include <malloc.h> /* for mallopt */
35 #endif
36
37 /* Try to pull in PAGE_SIZE */
38 #ifdef __linux__
39 # include <sys/user.h>
40 #endif
41 #ifdef __GNU__ /* Hurd */
42 # include <mach/vm_param.h>
43 #endif
44 #ifndef PAGE_SIZE
45 # define PAGE_SIZE (4*1024) /* guess */
46 #endif
47
48
49 /* Declare <applet>_main() */
50 #define PROTOTYPES
51 #include "applets.h"
52 #undef PROTOTYPES
53
54 /* Include generated applet names, pointers to <applet>_main, etc */
55 #include "applet_tables.h"
56 /* ...and if applet_tables generator says we have only one applet... */
57 #ifdef SINGLE_APPLET_MAIN
58 # undef ENABLE_FEATURE_INDIVIDUAL
59 # define ENABLE_FEATURE_INDIVIDUAL 1
60 # undef IF_FEATURE_INDIVIDUAL
61 # define IF_FEATURE_INDIVIDUAL(...) __VA_ARGS__
62 #endif
63
64 #include "usage_compressed.h"
65
66
67 #if ENABLE_SHOW_USAGE && !ENABLE_FEATURE_COMPRESS_USAGE
68 static const char usage_messages[] ALIGN1 = UNPACKED_USAGE;
69 #else
70 # define usage_messages 0
71 #endif
72
73 #if ENABLE_FEATURE_COMPRESS_USAGE
74
75 static const char packed_usage[] ALIGN1 = { PACKED_USAGE };
76 # include "archive.h"
77 static const char *unpack_usage_messages(void)
78 {
79         char *outbuf = NULL;
80         bunzip_data *bd;
81         int i;
82
83         i = start_bunzip(&bd,
84                         /* src_fd: */ -1,
85                         /* inbuf:  */ packed_usage,
86                         /* len:    */ sizeof(packed_usage));
87         /* read_bunzip can longjmp to start_bunzip, and ultimately
88          * end up here with i != 0 on read data errors! Not trivial */
89         if (!i) {
90                 /* Cannot use xmalloc: will leak bd in NOFORK case! */
91                 outbuf = malloc_or_warn(sizeof(UNPACKED_USAGE));
92                 if (outbuf)
93                         read_bunzip(bd, outbuf, sizeof(UNPACKED_USAGE));
94         }
95         dealloc_bunzip(bd);
96         return outbuf;
97 }
98 # define dealloc_usage_messages(s) free(s)
99
100 #else
101
102 # define unpack_usage_messages() usage_messages
103 # define dealloc_usage_messages(s) ((void)(s))
104
105 #endif /* FEATURE_COMPRESS_USAGE */
106
107
108 void FAST_FUNC bb_show_usage(void)
109 {
110         if (ENABLE_SHOW_USAGE) {
111 #ifdef SINGLE_APPLET_STR
112                 /* Imagine that this applet is "true". Dont suck in printf! */
113                 const char *usage_string = unpack_usage_messages();
114
115                 if (*usage_string == '\b') {
116                         full_write2_str("No help available.\n\n");
117                 } else {
118                         full_write2_str("Usage: "SINGLE_APPLET_STR" ");
119                         full_write2_str(usage_string);
120                         full_write2_str("\n\n");
121                 }
122                 if (ENABLE_FEATURE_CLEAN_UP)
123                         dealloc_usage_messages((char*)usage_string);
124 #else
125                 const char *p;
126                 const char *usage_string = p = unpack_usage_messages();
127                 int ap = find_applet_by_name(applet_name);
128
129                 if (ap < 0) /* never happens, paranoia */
130                         xfunc_die();
131                 while (ap) {
132                         while (*p++) continue;
133                         ap--;
134                 }
135                 full_write2_str(bb_banner);
136                 full_write2_str(" multi-call binary.\n");
137                 if (*p == '\b')
138                         full_write2_str("\nNo help available.\n\n");
139                 else {
140                         full_write2_str("\nUsage: ");
141                         full_write2_str(applet_name);
142                         full_write2_str(" ");
143                         full_write2_str(p);
144                         full_write2_str("\n\n");
145                 }
146                 if (ENABLE_FEATURE_CLEAN_UP)
147                         dealloc_usage_messages((char*)usage_string);
148 #endif
149         }
150         xfunc_die();
151 }
152
153 #if NUM_APPLETS > 8
154 /* NB: any char pointer will work as well, not necessarily applet_names */
155 static int applet_name_compare(const void *name, const void *v)
156 {
157         int i = (const char *)v - applet_names;
158         return strcmp(name, APPLET_NAME(i));
159 }
160 #endif
161 int FAST_FUNC find_applet_by_name(const char *name)
162 {
163 #if NUM_APPLETS > 8
164         /* Do a binary search to find the applet entry given the name. */
165         const char *p;
166         p = bsearch(name, applet_names, ARRAY_SIZE(applet_main), 1, applet_name_compare);
167         if (!p)
168                 return -1;
169         return p - applet_names;
170 #else
171         /* A version which does not pull in bsearch */
172         int i = 0;
173         const char *p = applet_names;
174         while (i < NUM_APPLETS) {
175                 if (strcmp(name, p) == 0)
176                         return i;
177                 p += strlen(p) + 1;
178                 i++;
179         }
180         return -1;
181 #endif
182 }
183
184
185 void lbb_prepare(const char *applet
186                 IF_FEATURE_INDIVIDUAL(, char **argv))
187                                 MAIN_EXTERNALLY_VISIBLE;
188 void lbb_prepare(const char *applet
189                 IF_FEATURE_INDIVIDUAL(, char **argv))
190 {
191 #ifdef __GLIBC__
192         (*(int **)&bb_errno) = __errno_location();
193         barrier();
194 #endif
195         applet_name = applet;
196
197         /* Set locale for everybody except 'init' */
198         if (ENABLE_LOCALE_SUPPORT && getpid() != 1)
199                 setlocale(LC_ALL, "");
200
201 #if ENABLE_FEATURE_INDIVIDUAL
202         /* Redundant for busybox (run_applet_and_exit covers that case)
203          * but needed for "individual applet" mode */
204         if (argv[1]
205          && !argv[2]
206          && strcmp(argv[1], "--help") == 0
207          && strncmp(applet, "busybox", 7) != 0
208         ) {
209                 /* Special case. POSIX says "test --help"
210                  * should be no different from e.g. "test --foo".  */
211                 if (!ENABLE_TEST || strcmp(applet_name, "test") != 0)
212                         bb_show_usage();
213         }
214 #endif
215 }
216
217 /* The code below can well be in applets/applets.c, as it is used only
218  * for busybox binary, not "individual" binaries.
219  * However, keeping it here and linking it into libbusybox.so
220  * (together with remaining tiny applets/applets.o)
221  * makes it possible to avoid --whole-archive at link time.
222  * This makes (shared busybox) + libbusybox smaller.
223  * (--gc-sections would be even better....)
224  */
225
226 const char *applet_name;
227 #if !BB_MMU
228 bool re_execed;
229 #endif
230
231
232 /* If not built as a single-applet executable... */
233 #if !defined(SINGLE_APPLET_MAIN)
234
235 IF_FEATURE_SUID(static uid_t ruid;)  /* real uid */
236
237 # if ENABLE_FEATURE_SUID_CONFIG
238
239 static struct suid_config_t {
240         /* next ptr must be first: this struct needs to be llist-compatible */
241         struct suid_config_t *m_next;
242         struct bb_uidgid_t m_ugid;
243         int m_applet;
244         mode_t m_mode;
245 } *suid_config;
246
247 static bool suid_cfg_readable;
248
249 /* check if u is member of group g */
250 static int ingroup(uid_t u, gid_t g)
251 {
252         struct group *grp = getgrgid(g);
253         if (grp) {
254                 char **mem;
255                 for (mem = grp->gr_mem; *mem; mem++) {
256                         struct passwd *pwd = getpwnam(*mem);
257                         if (pwd && (pwd->pw_uid == u))
258                                 return 1;
259                 }
260         }
261         return 0;
262 }
263
264 /* This should probably be a libbb routine.  In that case,
265  * I'd probably rename it to something like bb_trimmed_slice.
266  */
267 static char *get_trimmed_slice(char *s, char *e)
268 {
269         /* First, consider the value at e to be nul and back up until we
270          * reach a non-space char.  Set the char after that (possibly at
271          * the original e) to nul. */
272         while (e-- > s) {
273                 if (!isspace(*e)) {
274                         break;
275                 }
276         }
277         e[1] = '\0';
278
279         /* Next, advance past all leading space and return a ptr to the
280          * first non-space char; possibly the terminating nul. */
281         return skip_whitespace(s);
282 }
283
284 static void parse_config_file(void)
285 {
286         /* Don't depend on the tools to combine strings. */
287         static const char config_file[] ALIGN1 = "/etc/busybox.conf";
288
289         struct suid_config_t *sct_head;
290         int applet_no;
291         FILE *f;
292         const char *errmsg;
293         unsigned lc;
294         smallint section;
295         struct stat st;
296
297         ruid = getuid();
298         if (ruid == 0) /* run by root - don't need to even read config file */
299                 return;
300
301         if ((stat(config_file, &st) != 0)       /* No config file? */
302          || !S_ISREG(st.st_mode)                /* Not a regular file? */
303          || (st.st_uid != 0)                    /* Not owned by root? */
304          || (st.st_mode & (S_IWGRP | S_IWOTH))  /* Writable by non-root? */
305          || !(f = fopen_for_read(config_file))  /* Cannot open? */
306         ) {
307                 return;
308         }
309
310         suid_cfg_readable = 1;
311         sct_head = NULL;
312         section = lc = 0;
313
314         while (1) {
315                 char buffer[256];
316                 char *s;
317
318                 if (!fgets(buffer, sizeof(buffer), f)) { /* Are we done? */
319                         // Looks like bloat
320                         //if (ferror(f)) {   /* Make sure it wasn't a read error. */
321                         //      errmsg = "reading";
322                         //      goto pe_label;
323                         //}
324                         fclose(f);
325                         suid_config = sct_head; /* Success, so set the pointer. */
326                         return;
327                 }
328
329                 s = buffer;
330                 lc++;                                   /* Got a (partial) line. */
331
332                 /* If a line is too long for our buffer, we consider it an error.
333                  * The following test does mistreat one corner case though.
334                  * If the final line of the file does not end with a newline and
335                  * yet exactly fills the buffer, it will be treated as too long
336                  * even though there isn't really a problem.  But it isn't really
337                  * worth adding code to deal with such an unlikely situation, and
338                  * we do err on the side of caution.  Besides, the line would be
339                  * too long if it did end with a newline. */
340                 if (!strchr(s, '\n') && !feof(f)) {
341                         errmsg = "line too long";
342                         goto pe_label;
343                 }
344
345                 /* Trim leading and trailing whitespace, ignoring comments, and
346                  * check if the resulting string is empty. */
347                 s = get_trimmed_slice(s, strchrnul(s, '#'));
348                 if (!*s) {
349                         continue;
350                 }
351
352                 /* Check for a section header. */
353
354                 if (*s == '[') {
355                         /* Unlike the old code, we ignore leading and trailing
356                          * whitespace for the section name.  We also require that
357                          * there are no stray characters after the closing bracket. */
358                         char *e = strchr(s, ']');
359                         if (!e   /* Missing right bracket? */
360                          || e[1] /* Trailing characters? */
361                          || !*(s = get_trimmed_slice(s+1, e)) /* Missing name? */
362                         ) {
363                                 errmsg = "section header";
364                                 goto pe_label;
365                         }
366                         /* Right now we only have one section so just check it.
367                          * If more sections are added in the future, please don't
368                          * resort to cascading ifs with multiple strcasecmp calls.
369                          * That kind of bloated code is all too common.  A loop
370                          * and a string table would be a better choice unless the
371                          * number of sections is very small. */
372                         if (strcasecmp(s, "SUID") == 0) {
373                                 section = 1;
374                                 continue;
375                         }
376                         section = -1;   /* Unknown section so set to skip. */
377                         continue;
378                 }
379
380                 /* Process sections. */
381
382                 if (section == 1) {             /* SUID */
383                         /* Since we trimmed leading and trailing space above, we're
384                          * now looking for strings of the form
385                          *    <key>[::space::]*=[::space::]*<value>
386                          * where both key and value could contain inner whitespace. */
387
388                         /* First get the key (an applet name in our case). */
389                         char *e = strchr(s, '=');
390                         if (e) {
391                                 s = get_trimmed_slice(s, e);
392                         }
393                         if (!e || !*s) {        /* Missing '=' or empty key. */
394                                 errmsg = "keyword";
395                                 goto pe_label;
396                         }
397
398                         /* Ok, we have an applet name.  Process the rhs if this
399                          * applet is currently built in and ignore it otherwise.
400                          * Note: this can hide config file bugs which only pop
401                          * up when the busybox configuration is changed. */
402                         applet_no = find_applet_by_name(s);
403                         if (applet_no >= 0) {
404                                 unsigned i;
405                                 struct suid_config_t *sct;
406
407                                 /* Note: We currently don't check for duplicates!
408                                  * The last config line for each applet will be the
409                                  * one used since we insert at the head of the list.
410                                  * I suppose this could be considered a feature. */
411                                 sct = xzalloc(sizeof(*sct));
412                                 sct->m_applet = applet_no;
413                                 /*sct->m_mode = 0;*/
414                                 sct->m_next = sct_head;
415                                 sct_head = sct;
416
417                                 /* Get the specified mode. */
418
419                                 e = skip_whitespace(e+1);
420
421                                 for (i = 0; i < 3; i++) {
422                                         /* There are 4 chars for each of user/group/other.
423                                          * "x-xx" instead of "x-" are to make
424                                          * "idx > 3" check catch invalid chars.
425                                          */
426                                         static const char mode_chars[] ALIGN1 = "Ssx-" "Ssx-" "x-xx";
427                                         static const unsigned short mode_mask[] ALIGN2 = {
428                                                 S_ISUID, S_ISUID|S_IXUSR, S_IXUSR, 0, /* Ssx- */
429                                                 S_ISGID, S_ISGID|S_IXGRP, S_IXGRP, 0, /* Ssx- */
430                                                                           S_IXOTH, 0  /*   x- */
431                                         };
432                                         const char *q = strchrnul(mode_chars + 4*i, *e);
433                                         unsigned idx = q - (mode_chars + 4*i);
434                                         if (idx > 3) {
435                                                 errmsg = "mode";
436                                                 goto pe_label;
437                                         }
438                                         sct->m_mode |= mode_mask[q - mode_chars];
439                                         e++;
440                                 }
441
442                                 /* Now get the user/group info. */
443
444                                 s = skip_whitespace(e);
445
446                                 /* Note: we require whitespace between the mode and the
447                                  * user/group info. */
448                                 if ((s == e) || !(e = strchr(s, '.'))) {
449                                         errmsg = "uid.gid";
450                                         goto pe_label;
451                                 }
452
453                                 *e = ':'; /* get_uidgid doesn't understand user.group */
454                                 if (get_uidgid(&sct->m_ugid, s, /*allow_numeric:*/ 1) == 0) {
455                                         errmsg = "unknown user/group";
456                                         goto pe_label;
457                                 }
458                         }
459                         continue;
460                 }
461
462                 /* Unknown sections are ignored. */
463
464                 /* Encountering configuration lines prior to seeing a
465                  * section header is treated as an error.  This is how
466                  * the old code worked, but it may not be desirable.
467                  * We may want to simply ignore such lines in case they
468                  * are used in some future version of busybox. */
469                 if (!section) {
470                         errmsg = "keyword outside section";
471                         goto pe_label;
472                 }
473
474         } /* while (1) */
475
476  pe_label:
477         fclose(f);
478         bb_error_msg("parse error in %s, line %u: %s", config_file, lc, errmsg);
479
480         /* Release any allocated memory before returning. */
481         llist_free((llist_t*)sct_head, NULL);
482 }
483 # else
484 static inline void parse_config_file(void)
485 {
486         IF_FEATURE_SUID(ruid = getuid();)
487 }
488 # endif /* FEATURE_SUID_CONFIG */
489
490
491 # if ENABLE_FEATURE_SUID
492 static void check_suid(int applet_no)
493 {
494         gid_t rgid;  /* real gid */
495
496         if (ruid == 0) /* set by parse_config_file() */
497                 return; /* run by root - no need to check more */
498         rgid = getgid();
499
500 #  if ENABLE_FEATURE_SUID_CONFIG
501         if (suid_cfg_readable) {
502                 uid_t uid;
503                 struct suid_config_t *sct;
504                 mode_t m;
505
506                 for (sct = suid_config; sct; sct = sct->m_next) {
507                         if (sct->m_applet == applet_no)
508                                 goto found;
509                 }
510                 goto check_need_suid;
511  found:
512                 /* Is this user allowed to run this applet? */
513                 m = sct->m_mode;
514                 if (sct->m_ugid.uid == ruid)
515                         /* same uid */
516                         m >>= 6;
517                 else if ((sct->m_ugid.gid == rgid) || ingroup(ruid, sct->m_ugid.gid))
518                         /* same group / in group */
519                         m >>= 3;
520                 if (!(m & S_IXOTH)) /* is x bit not set? */
521                         bb_error_msg_and_die("you have no permission to run this applet!");
522
523                 /* We set effective AND saved ids. If saved-id is not set
524                  * like we do below, seteuid(0) can still later succeed! */
525
526                 /* Are we directed to change gid
527                  * (APPLET = *s* USER.GROUP or APPLET = *S* USER.GROUP)?
528                  */
529                 if (sct->m_mode & S_ISGID)
530                         rgid = sct->m_ugid.gid;
531                 /* else: we will set egid = rgid, thus dropping sgid effect */
532                 if (setresgid(-1, rgid, rgid))
533                         bb_perror_msg_and_die("setresgid");
534
535                 /* Are we directed to change uid
536                  * (APPLET = s** USER.GROUP or APPLET = S** USER.GROUP)?
537                  */
538                 uid = ruid;
539                 if (sct->m_mode & S_ISUID)
540                         uid = sct->m_ugid.uid;
541                 /* else: we will set euid = ruid, thus dropping suid effect */
542                 if (setresuid(-1, uid, uid))
543                         bb_perror_msg_and_die("setresuid");
544
545                 goto ret;
546         }
547 #   if !ENABLE_FEATURE_SUID_CONFIG_QUIET
548         {
549                 static bool onetime = 0;
550
551                 if (!onetime) {
552                         onetime = 1;
553                         bb_error_msg("using fallback suid method");
554                 }
555         }
556 #   endif
557  check_need_suid:
558 #  endif
559         if (APPLET_SUID(applet_no) == BB_SUID_REQUIRE) {
560                 /* Real uid is not 0. If euid isn't 0 too, suid bit
561                  * is most probably not set on our executable */
562                 if (geteuid())
563                         bb_error_msg_and_die("must be suid to work properly");
564         } else if (APPLET_SUID(applet_no) == BB_SUID_DROP) {
565                 xsetgid(rgid);  /* drop all privileges */
566                 xsetuid(ruid);
567         }
568  ret: ;
569 #  if ENABLE_FEATURE_SUID_CONFIG
570         llist_free((llist_t*)suid_config, NULL);
571 #  endif
572 }
573 # else
574 #  define check_suid(x) ((void)0)
575 # endif /* FEATURE_SUID */
576
577
578 # if ENABLE_FEATURE_INSTALLER
579 static const char usr_bin [] ALIGN1 = "/usr/bin/";
580 static const char usr_sbin[] ALIGN1 = "/usr/sbin/";
581 static const char *const install_dir[] = {
582         &usr_bin [8], /* "/" */
583         &usr_bin [4], /* "/bin/" */
584         &usr_sbin[4]  /* "/sbin/" */
585 #  if !ENABLE_INSTALL_NO_USR
586         ,usr_bin
587         ,usr_sbin
588 #  endif
589 };
590
591 /* create (sym)links for each applet */
592 static void install_links(const char *busybox, int use_symbolic_links,
593                 char *custom_install_dir)
594 {
595         /* directory table
596          * this should be consistent w/ the enum,
597          * busybox.h::bb_install_loc_t, or else... */
598         int (*lf)(const char *, const char *);
599         char *fpc;
600         unsigned i;
601         int rc;
602
603         lf = link;
604         if (use_symbolic_links)
605                 lf = symlink;
606
607         for (i = 0; i < ARRAY_SIZE(applet_main); i++) {
608                 fpc = concat_path_file(
609                                 custom_install_dir ? custom_install_dir : install_dir[APPLET_INSTALL_LOC(i)],
610                                 APPLET_NAME(i));
611                 // debug: bb_error_msg("%slinking %s to busybox",
612                 //              use_symbolic_links ? "sym" : "", fpc);
613                 rc = lf(busybox, fpc);
614                 if (rc != 0 && errno != EEXIST) {
615                         bb_simple_perror_msg(fpc);
616                 }
617                 free(fpc);
618         }
619 }
620 # else
621 #  define install_links(x,y,z) ((void)0)
622 # endif
623
624 /* If we were called as "busybox..." */
625 static int busybox_main(char **argv)
626 {
627         if (!argv[1]) {
628                 /* Called without arguments */
629                 const char *a;
630                 int col;
631                 unsigned output_width;
632  help:
633                 output_width = 80;
634                 if (ENABLE_FEATURE_AUTOWIDTH) {
635                         /* Obtain the terminal width */
636                         get_terminal_width_height(0, &output_width, NULL);
637                 }
638
639                 dup2(1, 2);
640                 full_write2_str(bb_banner); /* reuse const string */
641                 full_write2_str(" multi-call binary.\n"); /* reuse */
642                 full_write2_str(
643                         "Copyright (C) 1998-2009 Erik Andersen, Rob Landley, Denys Vlasenko\n"
644                         "and others. Licensed under GPLv2.\n"
645                         "See source distribution for full notice.\n"
646                         "\n"
647                         "Usage: busybox [function] [arguments]...\n"
648                         "   or: busybox --list[-full]\n"
649                         "   or: function [arguments]...\n"
650                         "\n"
651                         "\tBusyBox is a multi-call binary that combines many common Unix\n"
652                         "\tutilities into a single executable.  Most people will create a\n"
653                         "\tlink to busybox for each function they wish to use and BusyBox\n"
654                         "\twill act like whatever it was invoked as.\n"
655                         "\n"
656                         "Currently defined functions:\n"
657                 );
658                 col = 0;
659                 a = applet_names;
660                 /* prevent last comma to be in the very last pos */
661                 output_width--;
662                 while (*a) {
663                         int len2 = strlen(a) + 2;
664                         if (col >= (int)output_width - len2) {
665                                 full_write2_str(",\n");
666                                 col = 0;
667                         }
668                         if (col == 0) {
669                                 col = 6;
670                                 full_write2_str("\t");
671                         } else {
672                                 full_write2_str(", ");
673                         }
674                         full_write2_str(a);
675                         col += len2;
676                         a += len2 - 1;
677                 }
678                 full_write2_str("\n\n");
679                 return 0;
680         }
681
682         if (strncmp(argv[1], "--list", 6) == 0) {
683                 unsigned i = 0;
684                 const char *a = applet_names;
685                 dup2(1, 2);
686                 while (*a) {
687 # if ENABLE_FEATURE_INSTALLER
688                         if (argv[1][6]) /* --list-path? */
689                                 full_write2_str(install_dir[APPLET_INSTALL_LOC(i)] + 1);
690 # endif
691                         full_write2_str(a);
692                         full_write2_str("\n");
693                         i++;
694                         a += strlen(a) + 1;
695                 }
696                 return 0;
697         }
698
699         if (ENABLE_FEATURE_INSTALLER && strcmp(argv[1], "--install") == 0) {
700                 int use_symbolic_links;
701                 const char *busybox;
702
703                 busybox = xmalloc_readlink(bb_busybox_exec_path);
704                 if (!busybox) {
705                         /* bb_busybox_exec_path is usually "/proc/self/exe".
706                          * In chroot, readlink("/proc/self/exe") usually fails.
707                          * In such case, better use argv[0] as symlink target
708                          * if it is a full path name.
709                          */
710                         if (argv[0][0] != '/')
711                                 bb_error_msg_and_die("'%s' is not an absolute path", argv[0]);
712                         busybox = argv[0];
713                 }
714                 /* busybox --install [-s] [DIR]:
715                  * -s: make symlinks
716                  * DIR: directory to install links to
717                  */
718                 use_symbolic_links = (argv[2] && strcmp(argv[2], "-s") == 0 && argv++);
719                 install_links(busybox, use_symbolic_links, argv[2]);
720                 return 0;
721         }
722
723         if (strcmp(argv[1], "--help") == 0) {
724                 /* "busybox --help [<applet>]" */
725                 if (!argv[2])
726                         goto help;
727                 /* convert to "<applet> --help" */
728                 argv[0] = argv[2];
729                 argv[2] = NULL;
730         } else {
731                 /* "busybox <applet> arg1 arg2 ..." */
732                 argv++;
733         }
734         /* We support "busybox /a/path/to/applet args..." too. Allows for
735          * "#!/bin/busybox"-style wrappers */
736         applet_name = bb_get_last_path_component_nostrip(argv[0]);
737         run_applet_and_exit(applet_name, argv);
738
739         /*bb_error_msg_and_die("applet not found"); - sucks in printf */
740         full_write2_str(applet_name);
741         full_write2_str(": applet not found\n");
742         xfunc_die();
743 }
744
745 void FAST_FUNC run_applet_no_and_exit(int applet_no, char **argv)
746 {
747         int argc = 1;
748
749         while (argv[argc])
750                 argc++;
751
752         /* Reinit some shared global data */
753         xfunc_error_retval = EXIT_FAILURE;
754
755         applet_name = APPLET_NAME(applet_no);
756         if (argc == 2 && strcmp(argv[1], "--help") == 0) {
757                 /* Special case. POSIX says "test --help"
758                  * should be no different from e.g. "test --foo".  */
759 //TODO: just compare applet_no with APPLET_NO_test
760                 if (!ENABLE_TEST || strcmp(applet_name, "test") != 0)
761                         bb_show_usage();
762         }
763         if (ENABLE_FEATURE_SUID)
764                 check_suid(applet_no);
765         exit(applet_main[applet_no](argc, argv));
766 }
767
768 void FAST_FUNC run_applet_and_exit(const char *name, char **argv)
769 {
770         int applet = find_applet_by_name(name);
771         if (applet >= 0)
772                 run_applet_no_and_exit(applet, argv);
773         if (strncmp(name, "busybox", 7) == 0)
774                 exit(busybox_main(argv));
775 }
776
777 #endif /* !defined(SINGLE_APPLET_MAIN) */
778
779
780
781 #if ENABLE_BUILD_LIBBUSYBOX
782 int lbb_main(char **argv)
783 #else
784 int main(int argc UNUSED_PARAM, char **argv)
785 #endif
786 {
787         /* Tweak malloc for reduced memory consumption */
788 #ifdef M_TRIM_THRESHOLD
789         /* M_TRIM_THRESHOLD is the maximum amount of freed top-most memory
790          * to keep before releasing to the OS
791          * Default is way too big: 256k
792          */
793         mallopt(M_TRIM_THRESHOLD, 2 * PAGE_SIZE);
794 #endif
795 #ifdef M_MMAP_THRESHOLD
796         /* M_MMAP_THRESHOLD is the request size threshold for using mmap()
797          * Default is too big: 256k
798          */
799         mallopt(M_MMAP_THRESHOLD, 8 * PAGE_SIZE - 256);
800 #endif
801
802 #if !BB_MMU
803         /* NOMMU re-exec trick sets high-order bit in first byte of name */
804         if (argv[0][0] & 0x80) {
805                 re_execed = 1;
806                 argv[0][0] &= 0x7f;
807         }
808 #endif
809
810 #if defined(SINGLE_APPLET_MAIN)
811         /* Only one applet is selected in .config */
812         if (argv[1] && strncmp(argv[0], "busybox", 7) == 0) {
813                 /* "busybox <applet> <params>" should still work as expected */
814                 argv++;
815         }
816         /* applet_names in this case is just "applet\0\0" */
817         lbb_prepare(applet_names IF_FEATURE_INDIVIDUAL(, argv));
818         return SINGLE_APPLET_MAIN(argc, argv);
819 #else
820         lbb_prepare("busybox" IF_FEATURE_INDIVIDUAL(, argv));
821
822         applet_name = argv[0];
823         if (applet_name[0] == '-')
824                 applet_name++;
825         applet_name = bb_basename(applet_name);
826
827         parse_config_file(); /* ...maybe, if FEATURE_SUID_CONFIG */
828
829         run_applet_and_exit(applet_name, argv);
830
831         /*bb_error_msg_and_die("applet not found"); - sucks in printf */
832         full_write2_str(applet_name);
833         full_write2_str(": applet not found\n");
834         xfunc_die();
835 #endif
836 }