/* Run command CMD and return statistics on it. Put the statistics in *RESP. */ static void run_command(char *const *cmd, resource_t *resp) { pid_t pid; /* Pid of child. */ void (*interrupt_signal)(int); void (*quit_signal)(int); resp->elapsed_ms = monotonic_ms(); pid = vfork(); /* Run CMD as child process. */ if (pid < 0) bb_perror_msg_and_die("fork"); if (pid == 0) { /* If child. */ /* Don't cast execvp arguments; that causes errors on some systems, versus merely warnings if the cast is left off. */ BB_EXECVP(cmd[0], cmd); xfunc_error_retval = (errno == ENOENT ? 127 : 126); bb_error_msg_and_die("can't run %s", cmd[0]); } /* Have signals kill the child but not self (if possible). */ //TODO: just block all sigs? and reenable them in the very end in main? interrupt_signal = signal(SIGINT, SIG_IGN); quit_signal = signal(SIGQUIT, SIG_IGN); resuse_end(pid, resp); /* Re-enable signals. */ signal(SIGINT, interrupt_signal); signal(SIGQUIT, quit_signal); }
/* Run command CMD and return statistics on it. Put the statistics in *RESP. */ static void run_command(char *const *cmd, resource_t * resp) { pid_t pid; /* Pid of child. */ __sighandler_t interrupt_signal, quit_signal; resp->elapsed_ms = monotonic_us() / 1000; pid = vfork(); /* Run CMD as child process. */ if (pid < 0) bb_error_msg_and_die("cannot fork"); else if (pid == 0) { /* If child. */ /* Don't cast execvp arguments; that causes errors on some systems, versus merely warnings if the cast is left off. */ BB_EXECVP(cmd[0], cmd); bb_error_msg("cannot run %s", cmd[0]); _exit(errno == ENOENT ? 127 : 126); } /* Have signals kill the child but not self (if possible). */ interrupt_signal = signal(SIGINT, SIG_IGN); quit_signal = signal(SIGQUIT, SIG_IGN); if (resuse_end(pid, resp) == 0) bb_error_msg("error waiting for child process"); /* Re-enable signals. */ signal(SIGINT, interrupt_signal); signal(SIGQUIT, quit_signal); }
int openvt_main(int argc, char **argv) { int fd; char vtname[sizeof(VC_FORMAT) + 2]; if (argc < 3) { bb_show_usage(); } /* check for illegal vt number: < 1 or > 63 */ sprintf(vtname, VC_FORMAT, (int)xatoul_range(argv[1], 1, 63)); if (fork() == 0) { /* child */ /* leave current vt (controlling tty) */ setsid(); /* and grab new one */ fd = xopen(vtname, O_RDWR); /* Reassign stdin, stdout and sterr */ dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); while (fd > 2) close(fd--); BB_EXECVP(argv[2], &argv[2]); _exit(1); } return EXIT_SUCCESS; }
int setarch_main(int argc ATTRIBUTE_UNUSED, char **argv) { int pers = -1; /* Figure out what personality we are supposed to switch to ... * we can be invoked as either: * argv[0],argv[1] -> "setarch","personality" * argv[0] -> "personality" */ retry: if (argv[0][5] == '6') /* linux64 */ pers = PER_LINUX; else if (argv[0][5] == '3') /* linux32 */ pers = PER_LINUX32; else if (pers == -1 && argv[1] != NULL) { pers = PER_LINUX32; ++argv; goto retry; } /* make user actually gave us something to do */ ++argv; if (argv[0] == NULL) bb_show_usage(); /* Try to set personality */ if (personality(pers) >= 0) { /* Try to execute the program */ BB_EXECVP(argv[0], argv); } bb_simple_perror_msg_and_die(argv[0]); }
static void runsv(int no, const char *name) { int pid = fork(); if (pid == -1) { warn2_cannot("fork for ", name); return; } if (pid == 0) { /* child */ char *prog[3]; prog[0] = (char*)"runsv"; prog[1] = (char*)name; prog[2] = NULL; if (pgrp) setsid(); signal(SIGHUP, SIG_DFL); signal(SIGTERM, SIG_DFL); BB_EXECVP(prog[0], prog); //pathexec_run(*prog, prog, (char* const*)environ); fatal2_cannot("start runsv ", name); } sv[no].pid = pid; }
void FAST_FUNC BB_EXECVP_or_die(char **argv) { BB_EXECVP(argv[0], argv); /* SUSv3-mandated exit codes */ xfunc_error_retval = (errno == ENOENT) ? 127 : 126; bb_perror_msg_and_die("can't execute '%s'", argv[0]); }
int chpst_main(int argc ATTRIBUTE_UNUSED, char **argv) { INIT_G(); if (applet_name[3] == 'd') envdir(argc, argv); if (applet_name[1] == 'o') softlimit(argc, argv); if (applet_name[0] == 's') setuidgid(argc, argv); if (applet_name[0] == 'e') envuidgid(argc, argv); // otherwise we are chpst { char *m,*d,*o,*p,*f,*c,*r,*t,*n; getopt32(argv, "+u:U:e:m:d:o:p:f:c:r:t:/:n:vP012", &set_user,&env_user,&env_dir, &m,&d,&o,&p,&f,&c,&r,&t,&root,&n); // if (option_mask32 & 0x1) // -u // if (option_mask32 & 0x2) // -U // if (option_mask32 & 0x4) // -e if (option_mask32 & 0x8) limits = limitl = limita = limitd = xatoul(m); // -m if (option_mask32 & 0x10) limitd = xatoul(d); // -d if (option_mask32 & 0x20) limito = xatoul(o); // -o if (option_mask32 & 0x40) limitp = xatoul(p); // -p if (option_mask32 & 0x80) limitf = xatoul(f); // -f if (option_mask32 & 0x100) limitc = xatoul(c); // -c if (option_mask32 & 0x200) limitr = xatoul(r); // -r if (option_mask32 & 0x400) limitt = xatoul(t); // -t // if (option_mask32 & 0x800) // -/ if (option_mask32 & 0x1000) nicelvl = xatoi(n); // -n // The below consts should match #defines at top! //if (option_mask32 & 0x2000) OPT_verbose = 1; // -v //if (option_mask32 & 0x4000) OPT_pgrp = 1; // -P //if (option_mask32 & 0x8000) OPT_nostdin = 1; // -0 //if (option_mask32 & 0x10000) OPT_nostdout = 1; // -1 //if (option_mask32 & 0x20000) OPT_nostderr = 1; // -2 } argv += optind; if (!argv || !*argv) bb_show_usage(); if (OPT_pgrp) setsid(); if (env_dir) edir(env_dir); if (root) { xchdir(root); xchroot("."); } slimit(); if (nicelvl) { errno = 0; if (nice(nicelvl) == -1) bb_perror_msg_and_die("nice"); } if (env_user) euidgid(env_user); if (set_user) suidgid(set_user); if (OPT_nostdin) close(0); if (OPT_nostdout) close(1); if (OPT_nostderr) close(2); BB_EXECVP(argv[0], argv); bb_perror_msg_and_die("exec %s", argv[0]); }
static void envuidgid(int argc, char **argv) { const char *account; account = *++argv; if (!account) bb_show_usage(); if (!*++argv) bb_show_usage(); euidgid((char*)account); BB_EXECVP(argv[0], argv); bb_perror_msg_and_die("exec %s", argv[0]); }
static void setuidgid(int argc ATTRIBUTE_UNUSED, char **argv) { const char *account; account = *++argv; if (!account) bb_show_usage(); if (!*++argv) bb_show_usage(); suidgid((char*)account); BB_EXECVP(argv[0], argv); bb_perror_msg_and_die("exec %s", argv[0]); }
static void envdir(int argc, char **argv) { const char *dir; dir = *++argv; if (!dir) bb_show_usage(); if (!*++argv) bb_show_usage(); edir(dir); BB_EXECVP(argv[0], argv); bb_perror_msg_and_die("exec %s", argv[0]); }
int taskset_main(int argc, char** argv) { cpu_set_t mask, new_mask; pid_t pid = 0; unsigned opt; const char *state = "current\0new"; char *p_opt = NULL, *aff = NULL; opt = getopt32(argc, argv, "+p:", &p_opt); if (opt & OPT_p) { if (argc == optind+1) { /* -p <aff> <pid> */ aff = p_opt; p_opt = argv[optind]; } argv += optind; /* me -p <arg> */ pid = xatoul_range(p_opt, 1, ULONG_MAX); /* -p <pid> */ } else aff = *++argv; /* <aff> <cmd...> */ if (aff) { unsigned i = 0; unsigned long l = xstrtol_range(aff, 0, 1, LONG_MAX); CPU_ZERO(&new_mask); while (i < CPU_SETSIZE && l >= (1<<i)) { if ((1<<i) & l) CPU_SET(i, &new_mask); ++i; } } if (opt & OPT_p) { print_aff: if (sched_getaffinity(pid, sizeof(mask), &mask) < 0) bb_perror_msg_and_die("failed to %cet pid %d's affinity", 'g', pid); printf("pid %d's %s affinity mask: "TASKSET_PRINTF_MASK"\n", pid, state, from_cpuset(mask)); if (!*argv) /* no new affinity given or we did print already, done. */ return EXIT_SUCCESS; } if (sched_setaffinity(pid, sizeof(new_mask), &new_mask)) bb_perror_msg_and_die("failed to %cet pid %d's affinity", 's', pid); if (opt & OPT_p) { state += 8; ++argv; goto print_aff; } ++argv; BB_EXECVP(*argv, argv); bb_perror_msg_and_die("%s", *argv); }
int setsid_main(int argc, char **argv) { if (argc < 2) bb_show_usage(); /* Comment why is this necessary? */ if (getpgrp() == getpid()) forkexit_or_rexec(argv); setsid(); /* no error possible */ BB_EXECVP(argv[1], argv + 1); bb_perror_msg_and_die("%s", argv[1]); }
int env_main(int argc UNUSED_PARAM, char **argv) { char **ep; unsigned opt; llist_t *unset_env = NULL; opt_complementary = "u::"; #if ENABLE_FEATURE_ENV_LONG_OPTIONS applet_long_options = env_longopts; #endif opt = getopt32(argv, "+iu:", &unset_env); argv += optind; if (*argv && LONE_DASH(argv[0])) { opt |= 1; ++argv; } if (opt & 1) { clearenv(); } while (unset_env) { char *var = llist_pop(&unset_env); /* This does not handle -uVAR=VAL * (coreutils _sets_ the variable in that case): */ /*unsetenv(var);*/ /* This does, but uses somewhan undocumented feature that * putenv("name_without_equal_sign") unsets the variable: */ putenv(var); } while (*argv && (strchr(*argv, '=') != NULL)) { if (putenv(*argv) < 0) { bb_perror_msg_and_die("putenv"); } ++argv; } if (*argv) { BB_EXECVP(*argv, argv); /* SUSv3-mandated exit codes. */ xfunc_error_retval = (errno == ENOENT) ? 127 : 126; bb_simple_perror_msg_and_die(*argv); } for (ep = environ; *ep; ep++) { puts(*ep); } fflush_stdout_and_exit(EXIT_SUCCESS); }
/* vfork scares gcc, it generates bigger code. * Keep it away from main program. * TODO: move to libbb; or adapt existing libbb's spawn(). */ static NOINLINE void vfork_child(char **argv) { if (vfork() == 0) { /* CHILD */ /* Try to make this VT our controlling tty */ setsid(); /* lose old ctty */ ioctl(STDIN_FILENO, TIOCSCTTY, 0 /* 0: don't forcibly steal */); //bb_error_msg("our sid %d", getsid(0)); //bb_error_msg("our pgrp %d", getpgrp()); //bb_error_msg("VT's sid %d", tcgetsid(0)); //bb_error_msg("VT's pgrp %d", tcgetpgrp(0)); BB_EXECVP(argv[0], argv); bb_perror_msg_and_die("exec %s", argv[0]); } }
int env_main(int argc ATTRIBUTE_UNUSED, char **argv) { /* cleanenv was static - why? */ char *cleanenv[1]; char **ep; unsigned opt; llist_t *unset_env = NULL; opt_complementary = "u::"; #if ENABLE_FEATURE_ENV_LONG_OPTIONS applet_long_options = env_longopts; #endif opt = getopt32(argv, "+iu:", &unset_env); argv += optind; if (*argv && LONE_DASH(argv[0])) { opt |= 1; ++argv; } if (opt & 1) { cleanenv[0] = NULL; environ = cleanenv; } else { while (unset_env) { unsetenv(unset_env->data); unset_env = unset_env->link; } } while (*argv && (strchr(*argv, '=') != NULL)) { if (putenv(*argv) < 0) { bb_perror_msg_and_die("putenv"); } ++argv; } if (*argv) { BB_EXECVP(*argv, argv); /* SUSv3-mandated exit codes. */ xfunc_error_retval = (errno == ENOENT) ? 127 : 126; bb_simple_perror_msg_and_die(*argv); } for (ep = environ; *ep; ep++) { puts(*ep); } fflush_stdout_and_exit(0); }
int nohup_main(int argc UNUSED_PARAM, char **argv) { const char *nohupout; char *home; xfunc_error_retval = 127; if (!argv[1]) { bb_show_usage(); } /* If stdin is a tty, detach from it. */ if (isatty(STDIN_FILENO)) { /* bb_error_msg("ignoring input"); */ close(STDIN_FILENO); xopen(bb_dev_null, O_RDONLY); /* will be fd 0 (STDIN_FILENO) */ } nohupout = "nohup.out"; /* Redirect stdout to nohup.out, either in "." or in "$HOME". */ if (isatty(STDOUT_FILENO)) { close(STDOUT_FILENO); if (open(nohupout, O_CREAT|O_WRONLY|O_APPEND, S_IRUSR|S_IWUSR) < 0) { home = getenv("HOME"); if (home) { nohupout = concat_path_file(home, nohupout); xopen3(nohupout, O_CREAT|O_WRONLY|O_APPEND, S_IRUSR|S_IWUSR); } else { xopen(bb_dev_null, O_RDONLY); /* will be fd 1 */ } } bb_error_msg("appending output to %s", nohupout); } /* If we have a tty on stderr, redirect to stdout. */ if (isatty(STDERR_FILENO)) { /* if (stdout_wasnt_a_tty) bb_error_msg("redirecting stderr to stdout"); */ dup2(STDOUT_FILENO, STDERR_FILENO); } signal(SIGHUP, SIG_IGN); BB_EXECVP(argv[1], argv+1); bb_simple_perror_msg_and_die(argv[1]); }
int setsid_main(int argc UNUSED_PARAM, char **argv) { if (!argv[1]) bb_show_usage(); /* setsid() is allowed only when we are not a process group leader. * Otherwise our PID serves as PGID of some existing process group * and cannot be used as PGID of a new process group. */ if (getpgrp() == getpid()) if (fork_or_rexec(argv)) exit(EXIT_SUCCESS); /* parent */ setsid(); /* no error possible */ BB_EXECVP(argv[1], argv + 1); bb_simple_perror_msg_and_die(argv[1]); }
void FAST_FUNC open_transformer(int fd, const char *transform_prog) #endif { struct fd_pair fd_pipe; int pid; xpiped_pair(fd_pipe); pid = BB_MMU ? xfork() : xvfork(); if (pid == 0) { /* Child */ close(fd_pipe.rd); /* we don't want to read from the parent */ // FIXME: error check? #if BB_MMU { transformer_aux_data_t aux; init_transformer_aux_data(&aux); aux.check_signature = check_signature; transformer(&aux, fd, fd_pipe.wr); if (ENABLE_FEATURE_CLEAN_UP) { close(fd_pipe.wr); /* send EOF */ close(fd); } /* must be _exit! bug was actually seen here */ _exit(EXIT_SUCCESS); } #else { char *argv[4]; xmove_fd(fd, 0); xmove_fd(fd_pipe.wr, 1); argv[0] = (char*)transform_prog; argv[1] = (char*)"-cf"; argv[2] = (char*)"-"; argv[3] = NULL; BB_EXECVP(transform_prog, argv); bb_perror_msg_and_die("can't execute '%s'", transform_prog); } #endif /* notreached */ } /* parent process */ close(fd_pipe.wr); /* don't want to write to the child */ xmove_fd(fd_pipe.rd, fd); }
int nohup_main(int argc, char **argv) { int nullfd; const char *nohupout; char *home = NULL; xfunc_error_retval = 127; if (argc < 2) bb_show_usage(); nullfd = xopen(bb_dev_null, O_WRONLY|O_APPEND); /* If stdin is a tty, detach from it. */ if (isatty(STDIN_FILENO)) dup2(nullfd, STDIN_FILENO); nohupout = "nohup.out"; /* Redirect stdout to nohup.out, either in "." or in "$HOME". */ if (isatty(STDOUT_FILENO)) { close(STDOUT_FILENO); if (open(nohupout, O_CREAT|O_WRONLY|O_APPEND, S_IRUSR|S_IWUSR) < 0) { home = getenv("HOME"); if (home) { nohupout = concat_path_file(home, nohupout); xopen3(nohupout, O_CREAT|O_WRONLY|O_APPEND, S_IRUSR|S_IWUSR); } } } else dup2(nullfd, STDOUT_FILENO); /* If we have a tty on stderr, announce filename and redirect to stdout. * Else redirect to /dev/null. */ if (isatty(STDERR_FILENO)) { bb_error_msg("appending to %s", nohupout); dup2(STDOUT_FILENO, STDERR_FILENO); } else dup2(nullfd, STDERR_FILENO); if (nullfd > 2) close(nullfd); signal(SIGHUP, SIG_IGN); BB_EXECVP(argv[1], argv+1); if (ENABLE_FEATURE_CLEAN_UP && home) free((char*)nohupout); bb_simple_perror_msg_and_die(argv[1]); }
int nice_main(int argc, char **argv) { int old_priority, adjustment; old_priority = getpriority(PRIO_PROCESS, 0); if (!*++argv) { /* No args, so (GNU) output current nice value. */ printf("%d\n", old_priority); fflush_stdout_and_exit(EXIT_SUCCESS); } adjustment = 10; /* Set default adjustment. */ if (argv[0][0] == '-') { if (argv[0][1] == 'n') { /* -n */ if (argv[0][2]) { /* -nNNNN (w/o space) */ argv[0] += 2; argv--; argc++; } } else { /* -NNN (NNN may be negative) == -n NNN */ argv[0] += 1; argv--; argc++; } if (argc < 4) { /* Missing priority and/or utility! */ bb_show_usage(); } adjustment = xatoi_range(argv[1], INT_MIN/2, INT_MAX/2); argv += 2; } { /* Set our priority. */ int prio = old_priority + adjustment; if (setpriority(PRIO_PROCESS, 0, prio) < 0) { bb_perror_msg_and_die("setpriority(%d)", prio); } } BB_EXECVP(*argv, argv); /* Now exec the desired program. */ /* The exec failed... */ xfunc_error_retval = (errno == ENOENT) ? 127 : 126; /* SUSv3 */ bb_perror_msg_and_die("%s", *argv); }
int openvt_main(int argc, char **argv) { char vtname[sizeof(VC_FORMAT) + 2]; if (argc < 3) bb_show_usage(); /* check for illegal vt number: < 1 or > 63 */ sprintf(vtname, VC_FORMAT, (int)xatou_range(argv[1], 1, 63)); bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv); /* grab new one */ close(0); xopen(vtname, O_RDWR); xdup2(0, STDOUT_FILENO); xdup2(0, STDERR_FILENO); argv += 2; BB_EXECVP(argv[0], argv); _exit(1); }
int chroot_main(int argc UNUSED_PARAM, char **argv) { ++argv; if (!*argv) bb_show_usage(); xchroot(*argv); xchdir("/"); ++argv; if (!*argv) { /* no 2nd param (PROG), use shell */ argv -= 2; argv[0] = getenv("SHELL"); if (!argv[0]) { argv[0] = (char *) DEFAULT_SHELL; } argv[1] = (char *) "-i"; } BB_EXECVP(*argv, argv); bb_perror_msg_and_die("can't execute '%s'", *argv); }
static void softlimit(int argc, char **argv) { char *a,*c,*d,*f,*l,*m,*o,*p,*r,*s,*t; getopt32(argv, "+a:c:d:f:l:m:o:p:r:s:t:", &a,&c,&d,&f,&l,&m,&o,&p,&r,&s,&t); if (option_mask32 & 0x001) limita = xatoul(a); // -a if (option_mask32 & 0x002) limitc = xatoul(c); // -c if (option_mask32 & 0x004) limitd = xatoul(d); // -d if (option_mask32 & 0x008) limitf = xatoul(f); // -f if (option_mask32 & 0x010) limitl = xatoul(l); // -l if (option_mask32 & 0x020) limits = limitl = limita = limitd = xatoul(m); // -m if (option_mask32 & 0x040) limito = xatoul(o); // -o if (option_mask32 & 0x080) limitp = xatoul(p); // -p if (option_mask32 & 0x100) limitr = xatoul(r); // -r if (option_mask32 & 0x200) limits = xatoul(s); // -s if (option_mask32 & 0x400) limitt = xatoul(t); // -t argv += optind; if (!argv[0]) bb_show_usage(); slimit(); BB_EXECVP(argv[0], argv); bb_perror_msg_and_die("exec %s", argv[0]); }
/* This does a fork/exec in one call, using vfork(). Returns PID of new child, * -1 for failure. Runs argv[0], searching path if that has no / in it. */ pid_t FAST_FUNC spawn(char **argv) { /* Compiler should not optimize stores here */ volatile int failed; pid_t pid; fflush_all(); /* Be nice to nommu machines. */ failed = 0; pid = vfork(); if (pid < 0) /* error */ return pid; if (!pid) { /* child */ /* This macro is ok - it doesn't do NOEXEC/NOFORK tricks */ BB_EXECVP(argv[0], argv); /* We are (maybe) sharing a stack with blocked parent, * let parent know we failed and then exit to unblock parent * (but don't run atexit() stuff, which would screw up parent.) */ failed = errno; /* mount, for example, does not want the message */ /*bb_perror_msg("can't execute '%s'", argv[0]);*/ _exit(111); } /* parent */ /* Unfortunately, this is not reliable: according to standards * vfork() can be equivalent to fork() and we won't see value * of 'failed'. * Interested party can wait on pid and learn exit code. * If 111 - then it (most probably) failed to exec */ if (failed) { safe_waitpid(pid, NULL, 0); /* prevent zombie */ errno = failed; return -1; } return pid; }
/* This does a fork/exec in one call, using vfork(). Returns PID of new child, * -1 for failure. Runs argv[0], searching path if that has no / in it. */ pid_t spawn(char **argv) { /* Compiler should not optimize stores here */ volatile int failed; pid_t pid; // Ain't it a good place to fflush(NULL)? /* Be nice to nommu machines. */ failed = 0; pid = vfork(); if (pid < 0) /* error */ return pid; if (!pid) { /* child */ /* This macro is ok - it doesn't do NOEXEC/NOFORK tricks */ BB_EXECVP(argv[0], argv); /* We are (maybe) sharing a stack with blocked parent, * let parent know we failed and then exit to unblock parent * (but don't run atexit() stuff, which would screw up parent.) */ failed = errno; _exit(111); } /* parent */ /* Unfortunately, this is not reliable: according to standards * vfork() can be equivalent to fork() and we won't see value * of 'failed'. * Interested party can wait on pid and learn exit code. * If 111 - then it (most probably) failed to exec */ if (failed) { errno = failed; return -1; } return pid; }
int chrt_main(int argc, char **argv) { pid_t pid = 0; unsigned opt; struct sched_param sp; char *p_opt = NULL, *priority = NULL; const char *state = "current\0new"; int prio = 0, policy = SCHED_RR; opt_complementary = "r--fo:f--ro:r--fo"; /* only one policy accepted */ opt = getopt32(argv, "+mp:rfo", &p_opt); if (opt & OPT_r) policy = SCHED_RR; if (opt & OPT_f) policy = SCHED_FIFO; if (opt & OPT_o) policy = SCHED_OTHER; if (opt & OPT_m) { /* print min/max */ show_min_max(SCHED_FIFO); show_min_max(SCHED_RR); show_min_max(SCHED_OTHER); fflush_stdout_and_exit(EXIT_SUCCESS); } if (opt & OPT_p) { if (argc == optind+1) { /* -p <priority> <pid> */ priority = p_opt; p_opt = argv[optind]; } argv += optind; /* me -p <arg> */ pid = xatoul_range(p_opt, 1, ULONG_MAX); /* -p <pid> */ } else { argv += optind; /* me -p <arg> */ priority = *argv; } if (priority) { /* from the manpage of sched_getscheduler: [...] sched_priority can have a value in the range 0 to 99. [...] SCHED_OTHER or SCHED_BATCH must be assigned the static priority 0. [...] SCHED_FIFO or SCHED_RR can have a static priority in the range 1 to 99. */ prio = xstrtol_range(priority, 0, policy == SCHED_OTHER ? 0 : 1, 99); } if (opt & OPT_p) { int pol = 0; print_rt_info: pol = sched_getscheduler(pid); if (pol < 0) bb_perror_msg_and_die("failed to %cet pid %d's policy", 'g', pid); printf("pid %d's %s scheduling policy: %s\n", pid, state, policies[pol].name); if (sched_getparam(pid, &sp)) bb_perror_msg_and_die("failed to get pid %d's attributes", pid); printf("pid %d's %s scheduling priority: %d\n", pid, state, sp.sched_priority); if (!*argv) /* no new prio given or we did print already, done. */ return EXIT_SUCCESS; } sp.sched_priority = prio; if (sched_setscheduler(pid, policy, &sp) < 0) bb_perror_msg_and_die("failed to %cet pid %d's policy", 's', pid); if (opt & OPT_p) { state += 8; ++argv; goto print_rt_info; } ++argv; BB_EXECVP(*argv, argv); bb_simple_perror_msg_and_die(*argv); }
int timeout_main(int argc UNUSED_PARAM, char **argv) { int signo; int status; int parent = 0; int timeout = 10; pid_t pid; #if !BB_MMU char *sv1, *sv2; #endif const char *opt_s = "TERM"; /* -p option is not documented, it is needed to support NOMMU. */ /* -t SECONDS; -p PARENT_PID */ opt_complementary = "t+" USE_FOR_NOMMU(":p+"); /* '+': stop at first non-option */ getopt32(argv, "+s:t:" USE_FOR_NOMMU("p:"), &opt_s, &timeout, &parent); /*argv += optind; - no, wait for bb_daemonize_or_rexec! */ signo = get_signum(opt_s); if (signo < 0) bb_error_msg_and_die("unknown signal '%s'", opt_s); /* We want to create a grandchild which will watch * and kill the grandparent. Other methods: * making parent watch child disrupts parent<->child link * (example: "tcpsvd 0.0.0.0 1234 timeout service_prog" - * it's better if service_prog is a child of tcpsvd!), * making child watch parent results in programs having * unexpected children. */ if (parent) /* we were re-execed, already grandchild */ goto grandchild; if (!argv[optind]) /* no PROG? */ bb_show_usage(); #if !BB_MMU sv1 = argv[optind]; sv2 = argv[optind + 1]; #endif pid = vfork(); if (pid < 0) bb_perror_msg_and_die("vfork"); if (pid == 0) { /* Child: spawn grandchild and exit */ parent = getppid(); #if !BB_MMU argv[optind] = xasprintf("-p%u", parent); argv[optind + 1] = NULL; #endif /* NB: exits with nonzero on error: */ bb_daemonize_or_rexec(0, argv); /* Here we are grandchild. Sleep, then kill grandparent */ grandchild: /* Just sleep(NUGE_NUM); kill(parent) may kill wrong process! */ while (1) { sleep(1); if (--timeout <= 0) break; if (kill(parent, 0)) { /* process is gone */ return EXIT_SUCCESS; } } kill(parent, signo); return EXIT_SUCCESS; } /* Parent */ wait(&status); /* wait for child to die */ /* Did intermediate [v]fork or exec fail? */ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) return EXIT_FAILURE; /* Ok, exec a program as requested */ argv += optind; #if !BB_MMU argv[0] = sv1; argv[1] = sv2; #endif BB_EXECVP(argv[0], argv); bb_perror_msg_and_die("exec '%s'", argv[0]); }
int tcpudpsvd_main(int argc ATTRIBUTE_UNUSED, char **argv) { char *str_C, *str_t; char *user; struct hcc *hccp; const char *instructs; char *msg_per_host = NULL; unsigned len_per_host = len_per_host; /* gcc */ #ifndef SSLSVD struct bb_uidgid_t ugid; #endif bool tcp; uint16_t local_port; char *preset_local_hostname = NULL; char *remote_hostname = remote_hostname; /* for compiler */ char *remote_addr = remote_addr; /* for compiler */ len_and_sockaddr *lsa; len_and_sockaddr local, remote; socklen_t sa_len; int pid; int sock; int conn; unsigned backlog = 20; INIT_G(); tcp = (applet_name[0] == 't'); /* 3+ args, -i at most once, -p implies -h, -v is counter, -b N, -c N */ opt_complementary = "-3:i--i:ph:vv:b+:c+"; #ifdef SSLSVD getopt32(argv, "+c:C:i:x:u:l:Eb:hpt:vU:/:Z:K:", &cmax, &str_C, &instructs, &instructs, &user, &preset_local_hostname, &backlog, &str_t, &ssluser, &root, &cert, &key, &verbose ); #else /* "+": stop on first non-option */ getopt32(argv, "+c:C:i:x:u:l:Eb:hpt:v", &cmax, &str_C, &instructs, &instructs, &user, &preset_local_hostname, &backlog, &str_t, &verbose ); #endif if (option_mask32 & OPT_C) { /* -C n[:message] */ max_per_host = bb_strtou(str_C, &str_C, 10); if (str_C[0]) { if (str_C[0] != ':') bb_show_usage(); msg_per_host = str_C + 1; len_per_host = strlen(msg_per_host); } } if (max_per_host > cmax) max_per_host = cmax; if (option_mask32 & OPT_u) { if (!get_uidgid(&ugid, user, 1)) bb_error_msg_and_die("unknown user/group: %s", user); } #ifdef SSLSVD if (option_mask32 & OPT_U) ssluser = optarg; if (option_mask32 & OPT_slash) root = optarg; if (option_mask32 & OPT_Z) cert = optarg; if (option_mask32 & OPT_K) key = optarg; #endif argv += optind; if (!argv[0][0] || LONE_CHAR(argv[0], '0')) argv[0] = (char*)"0.0.0.0"; /* Per-IP flood protection is not thought-out for UDP */ if (!tcp) max_per_host = 0; bb_sanitize_stdio(); /* fd# 0,1,2 must be opened */ #ifdef SSLSVD sslser = user; client = 0; if ((getuid() == 0) && !(option_mask32 & OPT_u)) { xfunc_exitcode = 100; bb_error_msg_and_die("-U ssluser must be set when running as root"); } if (option_mask32 & OPT_u) if (!uidgid_get(&sslugid, ssluser, 1)) { if (errno) { bb_perror_msg_and_die("fatal: cannot get user/group: %s", ssluser); } bb_error_msg_and_die("unknown user/group '%s'", ssluser); } if (!cert) cert = "./cert.pem"; if (!key) key = cert; if (matrixSslOpen() < 0) fatal("cannot initialize ssl"); if (matrixSslReadKeys(&keys, cert, key, 0, ca) < 0) { if (client) fatal("cannot read cert, key, or ca file"); fatal("cannot read cert or key file"); } if (matrixSslNewSession(&ssl, keys, 0, SSL_FLAGS_SERVER) < 0) fatal("cannot create ssl session"); #endif sig_block(SIGCHLD); signal(SIGCHLD, sig_child_handler); bb_signals(BB_FATAL_SIGS, sig_term_handler); signal(SIGPIPE, SIG_IGN); if (max_per_host) ipsvd_perhost_init(cmax); local_port = bb_lookup_port(argv[1], tcp ? "tcp" : "udp", 0); lsa = xhost2sockaddr(argv[0], local_port); argv += 2; sock = xsocket(lsa->u.sa.sa_family, tcp ? SOCK_STREAM : SOCK_DGRAM, 0); setsockopt_reuseaddr(sock); sa_len = lsa->len; /* I presume sockaddr len stays the same */ xbind(sock, &lsa->u.sa, sa_len); if (tcp) xlisten(sock, backlog); else /* udp: needed for recv_from_to to work: */ socket_want_pktinfo(sock); /* ndelay_off(sock); - it is the default I think? */ #ifndef SSLSVD if (option_mask32 & OPT_u) { /* drop permissions */ xsetgid(ugid.gid); xsetuid(ugid.uid); } #endif if (verbose) { char *addr = xmalloc_sockaddr2dotted(&lsa->u.sa); bb_error_msg("listening on %s, starting", addr); free(addr); #ifndef SSLSVD if (option_mask32 & OPT_u) printf(", uid %u, gid %u", (unsigned)ugid.uid, (unsigned)ugid.gid); #endif } /* Main accept() loop */ again: hccp = NULL; while (cnum >= cmax) wait_for_any_sig(); /* expecting SIGCHLD */ /* Accept a connection to fd #0 */ again1: close(0); again2: sig_unblock(SIGCHLD); local.len = remote.len = sa_len; if (tcp) { conn = accept(sock, &remote.u.sa, &remote.len); } else { /* In case recv_from_to won't be able to recover local addr. * Also sets port - recv_from_to is unable to do it. */ local = *lsa; conn = recv_from_to(sock, NULL, 0, MSG_PEEK, &remote.u.sa, &local.u.sa, sa_len); } sig_block(SIGCHLD); if (conn < 0) { if (errno != EINTR) bb_perror_msg(tcp ? "accept" : "recv"); goto again2; } xmove_fd(tcp ? conn : sock, 0); if (max_per_host) { /* Drop connection immediately if cur_per_host > max_per_host * (minimizing load under SYN flood) */ remote_addr = xmalloc_sockaddr2dotted_noport(&remote.u.sa); cur_per_host = ipsvd_perhost_add(remote_addr, max_per_host, &hccp); if (cur_per_host > max_per_host) { /* ipsvd_perhost_add detected that max is exceeded * (and did not store ip in connection table) */ free(remote_addr); if (msg_per_host) { /* don't block or test for errors */ send(0, msg_per_host, len_per_host, MSG_DONTWAIT); } goto again1; } /* NB: remote_addr is not leaked, it is stored in conn table */ } if (!tcp) { /* Voodoo magic: making udp sockets each receive its own * packets is not trivial, and I still not sure * I do it 100% right. * 1) we have to do it before fork() * 2) order is important - is it right now? */ /* Open new non-connected UDP socket for further clients... */ sock = xsocket(lsa->u.sa.sa_family, SOCK_DGRAM, 0); setsockopt_reuseaddr(sock); /* Make plain write/send work for old socket by supplying default * destination address. This also restricts incoming packets * to ones coming from this remote IP. */ xconnect(0, &remote.u.sa, sa_len); /* hole? at this point we have no wildcard udp socket... * can this cause clients to get "port unreachable" icmp? * Yup, time window is very small, but it exists (is it?) */ /* ..."open new socket", continued */ xbind(sock, &lsa->u.sa, sa_len); socket_want_pktinfo(sock); /* Doesn't work: * we cannot replace fd #0 - we will lose pending packet * which is already buffered for us! And we cannot use fd #1 * instead - it will "intercept" all following packets, but child * does not expect data coming *from fd #1*! */ #if 0 /* Make it so that local addr is fixed to localp->u.sa * and we don't accidentally accept packets to other local IPs. */ /* NB: we possibly bind to the _very_ same_ address & port as the one * already bound in parent! This seems to work in Linux. * (otherwise we can move socket to fd #0 only if bind succeeds) */ close(0); set_nport(localp, htons(local_port)); xmove_fd(xsocket(localp->u.sa.sa_family, SOCK_DGRAM, 0), 0); setsockopt_reuseaddr(0); /* crucial */ xbind(0, &localp->u.sa, localp->len); #endif } pid = vfork(); if (pid == -1) { bb_perror_msg("vfork"); goto again; } if (pid != 0) { /* Parent */ cnum++; if (verbose) connection_status(); if (hccp) hccp->pid = pid; /* clean up changes done by vforked child */ undo_xsetenv(); goto again; } /* Child: prepare env, log, and exec prog */ /* Closing tcp listening socket */ if (tcp) close(sock); { /* vfork alert! every xmalloc in this block should be freed! */ char *local_hostname = local_hostname; /* for compiler */ char *local_addr = NULL; char *free_me0 = NULL; char *free_me1 = NULL; char *free_me2 = NULL; if (verbose || !(option_mask32 & OPT_E)) { if (!max_per_host) /* remote_addr is not yet known */ free_me0 = remote_addr = xmalloc_sockaddr2dotted(&remote.u.sa); if (option_mask32 & OPT_h) { free_me1 = remote_hostname = xmalloc_sockaddr2host_noport(&remote.u.sa); if (!remote_hostname) { bb_error_msg("cannot look up hostname for %s", remote_addr); remote_hostname = remote_addr; } } /* Find out local IP peer connected to. * Errors ignored (I'm not paranoid enough to imagine kernel * which doesn't know local IP). */ if (tcp) getsockname(0, &local.u.sa, &local.len); /* else: for UDP it is done earlier by parent */ local_addr = xmalloc_sockaddr2dotted(&local.u.sa); if (option_mask32 & OPT_h) { local_hostname = preset_local_hostname; if (!local_hostname) { free_me2 = local_hostname = xmalloc_sockaddr2host_noport(&local.u.sa); if (!local_hostname) bb_error_msg_and_die("cannot look up hostname for %s", local_addr); } /* else: local_hostname is not NULL, but is NOT malloced! */ } } if (verbose) { pid = getpid(); if (max_per_host) { bb_error_msg("concurrency %s %u/%u", remote_addr, cur_per_host, max_per_host); } bb_error_msg((option_mask32 & OPT_h) ? "start %u %s-%s (%s-%s)" : "start %u %s-%s", pid, local_addr, remote_addr, local_hostname, remote_hostname); } if (!(option_mask32 & OPT_E)) { /* setup ucspi env */ const char *proto = tcp ? "TCP" : "UDP"; /* Extract "original" destination addr:port * from Linux firewall. Useful when you redirect * an outbond connection to local handler, and it needs * to know where it originally tried to connect */ if (tcp && getsockopt(0, SOL_IP, SO_ORIGINAL_DST, &local.u.sa, &local.len) == 0) { char *addr = xmalloc_sockaddr2dotted(&local.u.sa); xsetenv_plain("TCPORIGDSTADDR", addr); free(addr); } xsetenv_plain("PROTO", proto); xsetenv_proto(proto, "LOCALADDR", local_addr); xsetenv_proto(proto, "REMOTEADDR", remote_addr); if (option_mask32 & OPT_h) { xsetenv_proto(proto, "LOCALHOST", local_hostname); xsetenv_proto(proto, "REMOTEHOST", remote_hostname); } //compat? xsetenv_proto(proto, "REMOTEINFO", ""); /* additional */ if (cur_per_host > 0) /* can not be true for udp */ xsetenv_plain("TCPCONCURRENCY", utoa(cur_per_host)); } free(local_addr); free(free_me0); free(free_me1); free(free_me2); } xdup2(0, 1); signal(SIGTERM, SIG_DFL); signal(SIGPIPE, SIG_DFL); signal(SIGCHLD, SIG_DFL); sig_unblock(SIGCHLD); #ifdef SSLSVD strcpy(id, utoa(pid)); ssl_io(0, argv); #else BB_EXECVP(argv[0], argv); #endif bb_perror_msg_and_die("exec '%s'", argv[0]); }
int lpd_main(int argc UNUSED_PARAM, char *argv[]) { int spooling = spooling; // for compiler char *s, *queue; char *filenames[2]; // goto spool directory if (*++argv) xchdir(*argv++); // error messages of xfuncs will be sent over network xdup2(STDOUT_FILENO, STDERR_FILENO); // nullify ctrl/data filenames memset(filenames, 0, sizeof(filenames)); // read command s = queue = xmalloc_read_stdin(); // we understand only "receive job" command if (2 != *queue) { unsupported_cmd: printf("Command %02x %s\n", (unsigned char)s[0], "is not supported"); goto err_exit; } // parse command: "2 | QUEUE_NAME | '\n'" queue++; // protect against "/../" attacks // *strchrnul(queue, '\n') = '\0'; - redundant, sane() will do if (!*sane(queue)) return EXIT_FAILURE; // queue is a directory -> chdir to it and enter spooling mode spooling = chdir(queue) + 1; // 0: cannot chdir, 1: done // we don't free(s), we might need "queue" var later while (1) { char *fname; int fd; // int is easier than ssize_t: can use xatoi_u, // and can correctly display error returns (-1) int expected_len, real_len; // signal OK safe_write(STDOUT_FILENO, "", 1); // get subcommand // valid s must be of form: "SUBCMD | LEN | space | FNAME" // N.B. we bail out on any error s = xmalloc_read_stdin(); if (!s) { // (probably) EOF char *p, *q, var[2]; // non-spooling mode or no spool helper specified if (!spooling || !*argv) return EXIT_SUCCESS; // the only non-error exit // spooling mode but we didn't see both ctrlfile & datafile if (spooling != 7) goto err_exit; // reject job // spooling mode and spool helper specified -> exec spool helper // (we exit 127 if helper cannot be executed) var[1] = '\0'; // read and delete ctrlfile q = xmalloc_xopen_read_close(filenames[0], NULL); unlink(filenames[0]); // provide datafile name // we can use leaky setenv since we are about to exec or exit xsetenv("DATAFILE", filenames[1]); // parse control file by "\n" while ((p = strchr(q, '\n')) != NULL && isalpha(*q)) { *p++ = '\0'; // q is a line of <SYM><VALUE>, // we are setting environment string <SYM>=<VALUE>. // Ignoring "l<datafile>", exporting others: if (*q != 'l') { var[0] = *q++; xsetenv(var, q); } q = p; // next line } // helper should not talk over network. // this call reopens stdio fds to "/dev/null" // (no daemonization is done) bb_daemonize_or_rexec(DAEMON_DEVNULL_STDIO | DAEMON_ONLY_SANITIZE, NULL); BB_EXECVP(*argv, argv); exit(127); } // validate input. // we understand only "control file" or "data file" cmds if (2 != s[0] && 3 != s[0]) goto unsupported_cmd; if (spooling & (1 << (s[0]-1))) { printf("Duplicated subcommand\n"); goto err_exit; } // get filename *strchrnul(s, '\n') = '\0'; fname = strchr(s, ' '); if (!fname) { // bad_fname: printf("No or bad filename\n"); goto err_exit; } *fname++ = '\0'; // // s[0]==2: ctrlfile, must start with 'c' // // s[0]==3: datafile, must start with 'd' // if (fname[0] != s[0] + ('c'-2)) // goto bad_fname; // get length expected_len = bb_strtou(s + 1, NULL, 10); if (errno || expected_len < 0) { printf("Bad length\n"); goto err_exit; } if (2 == s[0] && expected_len > 16 * 1024) { // SECURITY: // ctrlfile can't be big (we want to read it back later!) printf("File is too big\n"); goto err_exit; } // open the file if (spooling) { // spooling mode: dump both files // job in flight has mode 0200 "only writable" sane(fname); fd = open3_or_warn(fname, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0200); if (fd < 0) goto err_exit; filenames[s[0] - 2] = xstrdup(fname); } else { // non-spooling mode: // 2: control file (ignoring), 3: data file fd = -1; if (3 == s[0]) fd = xopen(queue, O_RDWR | O_APPEND); } // signal OK safe_write(STDOUT_FILENO, "", 1); // copy the file real_len = bb_copyfd_size(STDIN_FILENO, fd, expected_len); if (real_len != expected_len) { printf("Expected %d but got %d bytes\n", expected_len, real_len); goto err_exit; } // get EOF indicator, see whether it is NUL (ok) // (and don't trash s[0]!) if (safe_read(STDIN_FILENO, &s[1], 1) != 1 || s[1] != 0) { // don't send error msg to peer - it obviously // doesn't follow the protocol, so probably // it can't understand us either goto err_exit; } if (spooling) { // chmod completely downloaded file as "readable+writable" fchmod(fd, 0600); // accumulate dump state // N.B. after all files are dumped spooling should be 1+2+4==7 spooling |= (1 << (s[0]-1)); // bit 1: ctrlfile; bit 2: datafile } free(s); close(fd); // NB: can do close(-1). Who cares? // NB: don't do "signal OK" write here, it will be done // at the top of the loop } // while (1) err_exit: // don't keep corrupted files if (spooling) { #define i spooling for (i = 2; --i >= 0; ) if (filenames[i]) unlink(filenames[i]); } return EXIT_FAILURE; }
static int parse(const char *boundary, char **argv) { char *line, *s, *p; const char *type; int boundary_len = strlen(boundary); const char *delims = " ;\"\t\r\n"; const char *uniq; int ntokens; const char *tokens[32]; // 32 is enough // prepare unique string pattern uniq = xasprintf("%%llu.%u.%s", (unsigned)getpid(), safe_gethostname()); //bb_info_msg("PARSE[%s]", terminator); while ((line = xmalloc_fgets_str(stdin, "\r\n\r\n")) != NULL) { // seek to start of MIME section // N.B. to avoid false positives let us seek to the _last_ occurance p = NULL; s = line; while ((s=strcasestr(s, "Content-Type:")) != NULL) p = s++; if (!p) goto next; //bb_info_msg("L[%s]", p); // split to tokens // TODO: strip of comments which are of form: (comment-text) ntokens = 0; tokens[ntokens] = NULL; for (s = strtok(p, delims); s; s = strtok(NULL, delims)) { tokens[ntokens] = s; if (ntokens < ARRAY_SIZE(tokens) - 1) ntokens++; //bb_info_msg("L[%d][%s]", ntokens, s); } tokens[ntokens] = NULL; //bb_info_msg("N[%d]", ntokens); // analyse tokens type = find_token(tokens, "Content-Type:", "text/plain"); //bb_info_msg("T[%s]", type); if (0 == strncasecmp(type, "multipart/", 10)) { if (0 == strcasecmp(type+10, "mixed")) { parse(xfind_token(tokens, "boundary="), argv); } else bb_error_msg_and_die("no support of content type '%s'", type); } else { pid_t pid = pid; int rc; FILE *fp; // fetch charset const char *charset = find_token(tokens, "charset=", CONFIG_FEATURE_MIME_CHARSET); // fetch encoding const char *encoding = find_token(tokens, "Content-Transfer-Encoding:", "7bit"); // compose target filename char *filename = (char *)find_token(tokens, "filename=", NULL); if (!filename) filename = xasprintf(uniq, monotonic_us()); else filename = bb_get_last_path_component_strip(xstrdup(filename)); // start external helper, if any if (opts & OPT_X) { int fd[2]; xpipe(fd); pid = vfork(); if (0 == pid) { // child reads from fd[0] xdup2(fd[0], STDIN_FILENO); close(fd[0]); close(fd[1]); xsetenv("CONTENT_TYPE", type); xsetenv("CHARSET", charset); xsetenv("ENCODING", encoding); xsetenv("FILENAME", filename); BB_EXECVP(*argv, argv); _exit(EXIT_FAILURE); } // parent dumps to fd[1] close(fd[0]); fp = fdopen(fd[1], "w"); signal(SIGPIPE, SIG_IGN); // ignore EPIPE // or create a file for dump } else { char *fname = xasprintf("%s%s", *argv, filename); fp = xfopen_for_write(fname); free(fname); } // housekeeping free(filename); // dump to fp if (0 == strcasecmp(encoding, "base64")) { decode_base64(stdin, fp); } else if (0 != strcasecmp(encoding, "7bit") && 0 != strcasecmp(encoding, "8bit")) { // quoted-printable, binary, user-defined are unsupported so far bb_error_msg_and_die("no support of encoding '%s'", encoding); } else { // N.B. we have written redundant \n. so truncate the file // The following weird 2-tacts reading technique is due to // we have to not write extra \n at the end of the file // In case of -x option we could truncate the resulting file as // fseek(fp, -1, SEEK_END); // if (ftruncate(fileno(fp), ftell(fp))) // bb_perror_msg("ftruncate"); // But in case of -X we have to be much more careful. There is // no means to truncate what we already have sent to the helper. p = xmalloc_fgets_str(stdin, "\r\n"); while (p) { if ((s = xmalloc_fgets_str(stdin, "\r\n")) == NULL) break; if ('-' == s[0] && '-' == s[1] && 0 == strncmp(s+2, boundary, boundary_len)) break; fputs(p, fp); p = s; } /* while ((s = xmalloc_fgetline_str(stdin, "\r\n")) != NULL) { if ('-' == s[0] && '-' == s[1] && 0 == strncmp(s+2, boundary, boundary_len)) break; fprintf(fp, "%s\n", s); } // N.B. we have written redundant \n. so truncate the file fseek(fp, -1, SEEK_END); if (ftruncate(fileno(fp), ftell(fp))) bb_perror_msg("ftruncate"); */ } fclose(fp); // finalize helper if (opts & OPT_X) { signal(SIGPIPE, SIG_DFL); // exit if helper exited >0 rc = wait4pid(pid); if (rc) return rc+20; } // check multipart finalized if (s && '-' == s[2+boundary_len] && '-' == s[2+boundary_len+1]) { free(line); break; } } next: free(line); } //bb_info_msg("ENDPARSE[%s]", boundary); return EXIT_SUCCESS; }