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