← Commit list

dee016821

Denys Vlasenko
Denys Vlasenko
2026-01-27
SUCCESS Duration: 142s

Subject

ash: reorder functions to reduce forward declarations, no code changes

Binary

No binary (build failed)

Architecture & Compiler Flags

Architecture: x86_64-linux-gnu Compiler: gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0
gcc -Wp,-MD,applets/.usage.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer      -Iinclude -Iinclude -o applets/usage applets/usage.c

Build Log

(log yok)

Commit Diff GitHub →

shell/ash.c+496 -493
@@ -76,6 +76,11 @@
//config: bool "Job control"
//config: default y
//config: depends on SHELL_ASH
+//config: help
+//config: Enable 'fg', 'bg', 'jobs' and 'kill' builtins.
+//config: Shell will track whether backgrounded pipes are stopped
+//config: by signals, and allow to restart them by 'fg' or 'bg'.
+//config: Otherwise, it will only track whether they have terminated.
//config:
//config:config ASH_ALIAS
//config: bool "Alias support"
@@ -908,6 +913,7 @@ out2str(const char *p)
#define VSREPLACE 0xc /* ${var/pattern/replacement} */
#define VSREPLACEALL 0xd /* ${var//pattern/replacement} */
#endif
+#if DEBUG || JOBS
static const char vstype_suffix[][3] ALIGN1 = {
[VSNORMAL - VSNORMAL] = "}", // $var or ${var}
[VSMINUS - VSNORMAL] = "-", // ${var-text}
@@ -919,14 +925,15 @@ static const char vstype_suffix[][3] ALIGN1 = {
[VSTRIMLEFT - VSNORMAL] = "#", // ${var#pattern}
[VSTRIMLEFTMAX - VSNORMAL] = "##",// ${var##pattern}
[VSLENGTH - VSNORMAL] = "", // ${#var}
-#if BASH_SUBSTR
+# if BASH_SUBSTR
[VSSUBSTR - VSNORMAL] = ":", // ${var:position:length}
-#endif
-#if BASH_PATTERN_SUBST
+# endif
+# if BASH_PATTERN_SUBST
[VSREPLACE - VSNORMAL] = "/", // ${var/pattern/replacement}
[VSREPLACEALL - VSNORMAL] = "//",// ${var//pattern/replacement}
-#endif
+# endif
};
+#endif
static const char dolatstr[] ALIGN1 = {
CTLQUOTEMARK, CTLVAR, VSNORMAL, '@', '=', CTLQUOTEMARK, '\0'
@@ -3723,63 +3730,6 @@ unaliascmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
#endif /* ASH_ALIAS */
-/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */
-#define FORK_FG 0
-#define FORK_BG 1
-#define FORK_NOJOB 2
-
-/* mode flags for showjob(s) */
-#define SHOW_ONLY_PGID 0x01 /* show only pgid (jobs -p) */
-#define SHOW_PIDS 0x02 /* show individual pids, not just one line per job */
-#define SHOW_CHANGED 0x04 /* only jobs whose state has changed */
-#define SHOW_STDERR 0x08 /* print to stderr (else stdout) */
-
-/*
- * A job structure contains information about a job. A job is either a
- * single process or a set of processes contained in a pipeline. In the
- * latter case, pidlist will be non-NULL, and will point to a -1 terminated
- * array of pids.
- */
-struct procstat {
- pid_t ps_pid; /* process id */
- int ps_status; /* last process status from wait() */
- char *ps_cmd; /* text of command being run */
-};
-
-struct job {
- struct procstat ps0; /* status of process */
- struct procstat *ps; /* status of processes when more than one */
-#if JOBS
- int stopstatus; /* status of a stopped job */
-#endif
- unsigned nprocs; /* number of processes */
-
-#define JOBRUNNING 0 /* at least one proc running */
-#define JOBSTOPPED 1 /* all procs are stopped */
-#define JOBDONE 2 /* all procs are completed */
- unsigned
- state: 8,
-#if JOBS
- sigint: 1, /* job was killed by SIGINT */
- jobctl: 1, /* job running under job control */
-#endif
- waited: 1, /* true if this entry has been waited for */
- used: 1, /* true if this entry is in used */
- changed: 1; /* true if status has changed */
- struct job *prev_job; /* previous job */
-};
-
-static int forkshell(struct job *, union node *, int);
-static int waitforjob(struct job *);
-
-#if !JOBS
-enum { jobctl = 0 };
-#define setjobctl(on) do {} while (0)
-#else
-static smallint jobctl; //references:8
-static void setjobctl(int);
-#endif
-
/*
* Ignore a signal.
*/
@@ -3948,12 +3898,61 @@ setsignal(int signo)
sigaction_set(signo, &act);
}
-/* mode flags for set_curjob */
+/* Mode flags for set_curjob */
#define CUR_DELETE 2
#define CUR_RUNNING 1
#define CUR_STOPPED 0
+/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */
+#define FORK_FG 0
+#define FORK_BG 1
+#define FORK_NOJOB 2
+
+/* Mode flags for showjob(s) */
+#define SHOW_ONLY_PGID 0x01 /* show only pgid (jobs -p) */
+#define SHOW_PIDS 0x02 /* show individual pids, not just one line per job */
+#define SHOW_CHANGED 0x04 /* only jobs whose state has changed */
+#define SHOW_STDERR 0x08 /* print to stderr (else stdout) */
+
+/*
+ * A job structure contains information about a job. A job is either a
+ * single process or a set of processes contained in a pipeline. In the
+ * latter case, pidlist will be non-NULL, and will point to a -1 terminated
+ * array of pids.
+ */
+struct procstat {
+ pid_t ps_pid; /* process id */
+ int ps_status; /* last process status from wait() */
+ char *ps_cmd; /* text of command being run */
+};
+
+struct job {
+ struct procstat ps0; /* status of process */
+ struct procstat *ps; /* status of processes when more than one */
+#if JOBS
+ int stopstatus; /* status of a stopped job */
+#endif
+ unsigned nprocs; /* number of processes */
+
+#define JOBRUNNING 0 /* at least one proc running */
+#define JOBSTOPPED 1 /* all procs are stopped */
+#define JOBDONE 2 /* all procs are completed */
+ unsigned
+ state: 8,
#if JOBS
+ sigint: 1, /* job was killed by SIGINT */
+ jobctl: 1, /* job running under job control */
+#endif
+ waited: 1, /* true if this entry has been waited for */
+ used: 1, /* true if this entry is in used */
+ changed: 1; /* true if status has changed */
+ struct job *prev_job; /* previous job */
+};
+
+#if !JOBS
+enum { jobctl = 0 };
+#else
+static smallint jobctl; //references:8
/* pgrp of shell on invocation */
static int initialpgrp; //references:2
static int ttyfd = -1; //5
@@ -4192,7 +4191,9 @@ freejob(struct job *jp)
INTON;
}
-#if JOBS
+#if !JOBS
+# define setjobctl(on) ((void)0)
+#else
static void
xtcsetpgrp(int fd, pid_t pgrp)
{
@@ -4346,75 +4347,6 @@ killcmd(int argc, char **argv)
}
return kill_main(argc, argv);
}
-
-static void
-showpipe(struct job *jp /*, FILE *out*/)
-{
- struct procstat *ps;
- struct procstat *psend;
-
- psend = jp->ps + jp->nprocs;
- for (ps = jp->ps + 1; ps < psend; ps++)
- printf(" | %s", ps->ps_cmd);
- newline_and_flush(stdout);
- flush_stdout_stderr();
-}
-
-
-static int
-restartjob(struct job *jp, int mode)
-{
- struct procstat *ps;
- int i;
- int status;
- pid_t pgid;
-
- INTOFF;
- if (jp->state == JOBDONE)
- goto out;
- jp->state = JOBRUNNING;
- pgid = jp->ps[0].ps_pid;
- if (mode == FORK_FG) {
- get_tty_state();
- xtcsetpgrp(ttyfd, pgid);
- }
- killpg(pgid, SIGCONT);
- ps = jp->ps;
- i = jp->nprocs;
- do {
- if (WIFSTOPPED(ps->ps_status)) {
- ps->ps_status = -1;
- }
- ps++;
- } while (--i);
- out:
- status = (mode == FORK_FG) ? waitforjob(jp) : 0;
- INTON;
- return status;
-}
-
-static int FAST_FUNC
-fg_bgcmd(int argc UNUSED_PARAM, char **argv)
-{
- struct job *jp;
- int mode;
- int retval;
-
- mode = (**argv == 'f') ? FORK_FG : FORK_BG;
- nextopt(nullstr);
- argv = argptr;
- do {
- jp = getjob(*argv, 1);
- if (mode == FORK_BG) {
- set_curjob(jp, CUR_RUNNING);
- printf("[%d] ", jobno(jp));
- }
- out1str(jp->ps[0].ps_cmd);
- showpipe(jp /*, stdout*/);
- retval = restartjob(jp, mode);
- } while (*argv && *++argv);
- return retval;
-}
#endif
static int
@@ -4451,6 +4383,44 @@ sprint_status48(char *os, int status, int sigonly)
return s - os;
}
+/* Called only on finished or stopped jobs (no members are running) */
+static int
+getstatus(struct job *job)
+{
+ int status;
+ int retval;
+ struct procstat *ps;
+
+ /* Fetch last member's status */
+ ps = job->ps + job->nprocs - 1;
+ status = ps->ps_status;
+ if (pipefail) {
+ /* "set -o pipefail" mode: use last _nonzero_ status */
+ while (status == 0 && --ps >= job->ps)
+ status = ps->ps_status;
+ }
+
+ retval = WEXITSTATUS(status);
+ if (!WIFEXITED(status)) {
+#if JOBS
+ retval = WSTOPSIG(status);
+ if (!WIFSTOPPED(status))
+#endif
+ {
+ /* XXX: limits number of signals */
+ retval = WTERMSIG(status);
+#if JOBS
+ if (retval == SIGINT)
+ job->sigint = 1;
+#endif
+ }
+ retval |= 128;
+ }
+ TRACE(("getstatus: job %d, nproc %d, status 0x%x, retval 0x%x\n",
+ jobno(job), job->nprocs, status, retval));
+ return retval;
+}
+
/* Inside dowait(): */
#define DOWAIT_NONBLOCK 0 /* waitpid() will use WNOHANG and won't wait for signals */
#define DOWAIT_BLOCK 1 /* waitpid() will NOT use WNOHANG */
@@ -4637,23 +4607,107 @@ static int dowait(int block, struct job *jp)
return rpid;
}
-#if JOBS
-static void
-showjob(struct job *jp, int mode)
+/*
+ * Wait for job to finish.
+ *
+ * Under job control we have the problem that while a child process
+ * is running interrupts generated by the user are sent to the child
+ * but not to the shell. This means that an infinite loop started by
+ * an interactive user may be hard to kill. With job control turned off,
+ * an interactive user may place an interactive program inside a loop.
+ * If the interactive program catches interrupts, the user doesn't want
+ * these interrupts to also abort the loop. The approach we take here
+ * is to have the shell ignore interrupt signals while waiting for a
+ * foreground process to terminate, and then send itself an interrupt
+ * signal if the child process was terminated by an interrupt signal.
+ * Unfortunately, some programs want to do a bit of cleanup and then
+ * exit on interrupt; unless these processes terminate themselves by
+ * sending a signal to themselves (instead of calling exit) they will
+ * confuse this approach.
+ *
+ * Called with interrupts off.
+ */
+static int
+waitforjob(struct job *jp)
{
- struct procstat *ps;
- struct procstat *psend;
- int col;
- int indent_col;
- char s[16 + 16 + 48];
- FILE *out = (mode & SHOW_STDERR ? stderr : stdout);
+ int st;
- ps = jp->ps;
+ TRACE(("waitforjob(%%%d) called\n", jp ? jobno(jp) : 0));
- if (mode & SHOW_ONLY_PGID) { /* jobs -p */
- /* just output process (group) id of pipeline */
- fprintf(out, "%d\n", ps->ps_pid);
- return;
+ /* In non-interactive shells, we _can_ get
+ * a keyboard signal here and be EINTRed, but we just loop
+ * inside dowait(), waiting for command to complete.
+ *
+ * man bash:
+ * "If bash is waiting for a command to complete and receives
+ * a signal for which a trap has been set, the trap
+ * will not be executed until the command completes."
+ *
+ * Reality is that even if trap is not set, bash
+ * will not act on the signal until command completes.
+ * Try this. sleep5intoff.c:
+ * #include <signal.h>
+ * #include <unistd.h>
+ * int main() {
+ * sigset_t set;
+ * sigemptyset(&set);
+ * sigaddset(&set, SIGINT);
+ * sigaddset(&set, SIGQUIT);
+ * sigprocmask(SIG_BLOCK, &set, NULL);
+ * sleep(5);
+ * return 0;
+ * }
+ * $ bash -c './sleep5intoff; echo hi'
+ * ^C^C^C^C <--- pressing ^C once a second
+ * $ _
+ * $ bash -c './sleep5intoff; echo hi'
+ * ^\^\^\^\hi <--- pressing ^\ (SIGQUIT)
+ * $ _
+ */
+ dowait(jp ? DOWAIT_BLOCK : DOWAIT_NONBLOCK, jp);
+ if (!jp)
+ return exitstatus;
+
+ st = getstatus(jp);
+#if JOBS
+ if (jp->jobctl) {
+ xtcsetpgrp(ttyfd, rootpid);
+ restore_tty_if_stopped_or_signaled(jp);
+
+ /*
+ * This is truly gross.
+ * If we're doing job control, then we did a TIOCSPGRP which
+ * caused us (the shell) to no longer be in the controlling
+ * session -- so we wouldn't have seen any ^C/SIGINT. So, we
+ * intuit from the subprocess exit status whether a SIGINT
+ * occurred, and if so interrupt ourselves. Yuck. - mycroft
+ */
+ if (jp->sigint) /* TODO: do the same with all signals */
+ raise(SIGINT); /* ... by raise(jp->sig) instead? */
+ }
+ if (jp->state == JOBDONE)
+#endif
+ freejob(jp);
+ return st;
+}
+
+#if JOBS
+static void
+showjob(struct job *jp, int mode)
+{
+ struct procstat *ps;
+ struct procstat *psend;
+ int col;
+ int indent_col;
+ char s[16 + 16 + 48];
+ FILE *out = (mode & SHOW_STDERR ? stderr : stdout);
+
+ ps = jp->ps;
+
+ if (mode & SHOW_ONLY_PGID) { /* jobs -p */
+ /* just output process (group) id of pipeline */
+ fprintf(out, "%d\n", ps->ps_pid);
+ return;
}
col = fmtstr(s, 16, "[%d] ", jobno(jp));
@@ -4758,43 +4812,95 @@ jobscmd(int argc UNUSED_PARAM, char **argv)
return 0;
}
-#endif /* JOBS */
-/* Called only on finished or stopped jobs (no members are running) */
static int
-getstatus(struct job *job)
+restartjob(struct job *jp, int mode)
{
- int status;
- int retval;
struct procstat *ps;
+ int i;
+ int status;
+ pid_t pgid;
- /* Fetch last member's status */
- ps = job->ps + job->nprocs - 1;
- status = ps->ps_status;
- if (pipefail) {
- /* "set -o pipefail" mode: use last _nonzero_ status */
- while (status == 0 && --ps >= job->ps)
- status = ps->ps_status;
+ INTOFF;
+ if (jp->state == JOBDONE)
+ goto out;
+ jp->state = JOBRUNNING;
+ pgid = jp->ps[0].ps_pid;
+ if (mode == FORK_FG) {
+ get_tty_state();
+ xtcsetpgrp(ttyfd, pgid);
}
+ killpg(pgid, SIGCONT);
+ ps = jp->ps;
+ i = jp->nprocs;
+ do {
+ if (WIFSTOPPED(ps->ps_status)) {
+ ps->ps_status = -1;
+ }
+ ps++;
+ } while (--i);
+ out:
+ status = (mode == FORK_FG) ? waitforjob(jp) : 0;
+ INTON;
+ return status;
+}
- retval = WEXITSTATUS(status);
- if (!WIFEXITED(status)) {
-#if JOBS
- retval = WSTOPSIG(status);
- if (!WIFSTOPPED(status))
-#endif
- {
- /* XXX: limits number of signals */
- retval = WTERMSIG(status);
-#if JOBS
- if (retval == SIGINT)
- job->sigint = 1;
-#endif
+static void
+showpipe(struct job *jp /*, FILE *out*/)
+{
+ struct procstat *ps;
+ struct procstat *psend;
+
+ psend = jp->ps + jp->nprocs;
+ for (ps = jp->ps + 1; ps < psend; ps++)
+ printf(" | %s", ps->ps_cmd);
+ newline_and_flush(stdout);
+ flush_stdout_stderr();
+}
+
+static int FAST_FUNC
+fg_bgcmd(int argc UNUSED_PARAM, char **argv)
+{
+ struct job *jp;
+ int mode;
+ int retval;
+
+ mode = (**argv == 'f') ? FORK_FG : FORK_BG;
+ nextopt(nullstr);
+ argv = argptr;
+ do {
+ jp = getjob(*argv, 1);
+ if (mode == FORK_BG) {
+ set_curjob(jp, CUR_RUNNING);
+ printf("[%d] ", jobno(jp));
}
- retval |= 128;
+ out1str(jp->ps[0].ps_cmd);
+ showpipe(jp /*, stdout*/);
+ retval = restartjob(jp, mode);
+ } while (*argv && *++argv);
+ return retval;
+}
+#endif
+
+/*
+ * return 1 if there are stopped jobs, otherwise 0
+ */
+static int
+stoppedjobs(void)
+{
+ struct job *jp;
+ int retval;
+
+ retval = 0;
+ if (!iflag || job_warning)
+ goto out;
+ jp = curjob;
+ if (jp && jp->state == JOBSTOPPED) {
+ out2str("You have stopped jobs.\n");
+ job_warning = 2;
+ retval++;
}
- TRACE(("getstatus: job %d, nproc %d, status 0x%x, retval 0x%x\n",
- jobno(job), job->nprocs, status, retval));
+ out:
return retval;
}
@@ -5526,140 +5632,6 @@ forkshell(struct job *jp, union node *n, int mode)
return pid;
}
-static void shellexec(char *prog, char **argv, const char *path, int idx) NORETURN;
-
-static struct job*
-vforkexec(union node *n, char **argv, const char *path, int idx)
-{
- struct job *jp;
- int pid;
-
- jp = makejob(1);
-
- sigblockall(NULL);
- vforked = 1;
-
- pid = vfork();
-
- if (!pid) {
- forkchild(jp, n, FORK_FG);
- sigclearmask();
- shellexec(argv[0], argv, path, idx);
- /* NOTREACHED */
- }
-
- vforked = 0;
- sigclearmask();
- forkparent(jp, n, FORK_FG, pid);
-
- return jp;
-}
-
-/*
- * Wait for job to finish.
- *
- * Under job control we have the problem that while a child process
- * is running interrupts generated by the user are sent to the child
- * but not to the shell. This means that an infinite loop started by
- * an interactive user may be hard to kill. With job control turned off,
- * an interactive user may place an interactive program inside a loop.
- * If the interactive program catches interrupts, the user doesn't want
- * these interrupts to also abort the loop. The approach we take here
- * is to have the shell ignore interrupt signals while waiting for a
- * foreground process to terminate, and then send itself an interrupt
- * signal if the child process was terminated by an interrupt signal.
- * Unfortunately, some programs want to do a bit of cleanup and then
- * exit on interrupt; unless these processes terminate themselves by
- * sending a signal to themselves (instead of calling exit) they will
- * confuse this approach.
- *
- * Called with interrupts off.
- */
-static int
-waitforjob(struct job *jp)
-{
- int st;
-
- TRACE(("waitforjob(%%%d) called\n", jp ? jobno(jp) : 0));
-
- /* In non-interactive shells, we _can_ get
- * a keyboard signal here and be EINTRed, but we just loop
- * inside dowait(), waiting for command to complete.
- *
- * man bash:
- * "If bash is waiting for a command to complete and receives
- * a signal for which a trap has been set, the trap
- * will not be executed until the command completes."
- *
- * Reality is that even if trap is not set, bash
- * will not act on the signal until command completes.
- * Try this. sleep5intoff.c:
- * #include <signal.h>
- * #include <unistd.h>
- * int main() {
- * sigset_t set;
- * sigemptyset(&set);
- * sigaddset(&set, SIGINT);
- * sigaddset(&set, SIGQUIT);
- * sigprocmask(SIG_BLOCK, &set, NULL);
- * sleep(5);
- * return 0;
- * }
- * $ bash -c './sleep5intoff; echo hi'
- * ^C^C^C^C <--- pressing ^C once a second
- * $ _
- * $ bash -c './sleep5intoff; echo hi'
- * ^\^\^\^\hi <--- pressing ^\ (SIGQUIT)
- * $ _
- */
- dowait(jp ? DOWAIT_BLOCK : DOWAIT_NONBLOCK, jp);
- if (!jp)
- return exitstatus;
-
- st = getstatus(jp);
-#if JOBS
- if (jp->jobctl) {
- xtcsetpgrp(ttyfd, rootpid);
- restore_tty_if_stopped_or_signaled(jp);
-
- /*
- * This is truly gross.
- * If we're doing job control, then we did a TIOCSPGRP which
- * caused us (the shell) to no longer be in the controlling
- * session -- so we wouldn't have seen any ^C/SIGINT. So, we
- * intuit from the subprocess exit status whether a SIGINT
- * occurred, and if so interrupt ourselves. Yuck. - mycroft
- */
- if (jp->sigint) /* TODO: do the same with all signals */
- raise(SIGINT); /* ... by raise(jp->sig) instead? */
- }
- if (jp->state == JOBDONE)
-#endif
- freejob(jp);
- return st;
-}
-
-/*
- * return 1 if there are stopped jobs, otherwise 0
- */
-static int
-stoppedjobs(void)
-{
- struct job *jp;
- int retval;
-
- retval = 0;
- if (!iflag || job_warning)
- goto out;
- jp = curjob;
- if (jp && jp->state == JOBSTOPPED) {
- out2str("You have stopped jobs.\n");
- job_warning = 2;
- retval++;
- }
- out:
- return retval;
-}
/*
* Code for dealing with input/output redirection.
@@ -6191,44 +6163,231 @@ pushredir(union node *redir)
}
/*
- * Undo the effects of the last redirection.
+ * Undo the effects of the last redirection.
+ */
+static void
+popredir(int drop)
+{
+ struct redirtab *rp;
+ int i;
+
+ if (redirlist == NULL)
+ return;
+ INTOFF;
+ rp = redirlist;
+ for (i = 0; i < rp->pair_count; i++) {
+ int fd = rp->two_fd[i].orig_fd;
+ int copy = rp->two_fd[i].moved_to;
+ if (copy == CLOSED) {
+ if (!drop)
+ close(fd);
+ continue;
+ }
+ if (copy != EMPTY) {
+ if (!drop) {
+ /*close(fd);*/
+ dup2_or_raise(copy, fd);
+ }
+ close(copy);
+ }
+ }
+ redirlist = rp->next;
+ free(rp);
+ INTON;
+}
+
+static void
+unwindredir(struct redirtab *stop)
+{
+ while (redirlist != stop)
+ popredir(/*drop:*/ 0);
+}
+
+
+/*
+ * execve wrappers
+ */
+#if ENABLE_FEATURE_SH_STANDALONE
+static void
+tryexec_applet(int applet_no, const char *cmd, char **argv, char **envp)
+{
+ if (!vforked && APPLET_IS_NOEXEC(applet_no)) {
+ dbg_show_dirtymem("dirtymem in NOEXEC tryexec");
+ clearenv();
+ while (*envp)
+ putenv(*envp++);
+ popredir(/*drop:*/ 1);
+//FIXME: exec resets all non-DFL, non-IGN signal handlers to DFL,
+//but we _don't_ exec, such signals will reach ash's handler instead!
+//Maybe add code there to set the handler to DFL, and signal itself?
+// This works for "CMD [| CMD]..." pipes:
+//vforkexec():
+// vfork();
+// forkchild(jp, n, FORK_FG); // this resets TSTP,TTOU,INT,TERM,QUIT to DFL
+// shellexec(argv[0], argv, path, idx)
+// tryexec_applet()
+// we are here
+// And for "exec CMD":
+//execcmd():
+// iflag = 0;
+// mflag = 0;
+// optschanged(); // this resets TSTP,TTOU,INT,TERM to DFL
+// shlvl++;
+// setsignal(SIGQUIT); // this resets QUIT to DFL
+// shellexec() ->
+// tryexec_applet() ->
+// we are here
+// But ash -c 'LAST_CMD_WONT_FORK' does not work!
+//evalcommand():
+// if (!(flags & EV_EXIT) || may_have_traps)
+// // we don't use this branch
+// //else:
+// shellexec() ->
+// tryexec_applet() ->
+// we are here
+//SIGINT works 'by accident' (sets DFL+signals itself)
+//SIGQUIT is IGNORED!
+//Fixed by adding in the evalcommand() before that shellexec():
+// shlvl++;
+// setsignal(SIGQUIT);
+// //TODO: setsignal(TSTP,TTOU,INT,TERM) too?
+//
+// With traps set, this:
+// ash -c 'trap "echo HERE!" INT; exec xargs'
+// didn't work: ^C sets a "run trap later" flag and _returns_,
+// which is not expected by the NOEXEC'ed xargs applet! (It gets EINTR on read).
+// clear_traps() helps with this:
+ clear_traps();
+ run_noexec_applet_and_exit(applet_no, cmd, argv);
+ /* does not return */
+ }
+ /* re-exec ourselves with the new arguments */
+ execve(bb_busybox_exec_path, argv, envp);
+ /* If they called chroot or otherwise made the binary
+ * no longer executable, return.
+ */
+}
+#endif
+
+static void
+tryexec(const char *cmd, char **argv, char **envp)
+{
+ repeat:
+#ifdef SYSV
+ do {
+ execve(cmd, argv, envp);
+ } while (errno == EINTR);
+#else
+ execve(cmd, argv, envp);
+#endif
+ if (cmd != bb_busybox_exec_path && errno == ENOEXEC) {
+ /* Run "cmd" as a shell script:
+ * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
+ * "If the execve() function fails with ENOEXEC, the shell
+ * shall execute a command equivalent to having a shell invoked
+ * with the command name as its first operand,
+ * with any remaining arguments passed to the new shell"
+ *
+ * That is, do not use $SHELL, user's shell, or /bin/sh;
+ * just call ourselves.
+ *
+ * Note that bash reads ~80 chars of the file, and if it sees
+ * a zero byte before it sees newline, it doesn't try to
+ * interpret it, but fails with "cannot execute binary file"
+ * message and exit code 126. For one, this prevents attempts
+ * to interpret foreign ELF binaries as shell scripts.
+ */
+ argv[0] = (char*) cmd;
+ cmd = bb_busybox_exec_path;
+ /* NB: this is only possible because all callers of shellexec()
+ * ensure that the argv[-1] slot exists!
+ */
+ argv--;
+ argv[0] = (char*) "ash";
+ goto repeat;
+ }
+}
+
+/*
+ * Exec a program. Never returns. If you change this routine, you may
+ * have to change the find_command routine as well.
+ * argv[-1] must exist and be writable! See tryexec() for why.
*/
-static void
-popredir(int drop)
+static void shellexec(char *prog, char **argv, const char *path, int idx)
{
- struct redirtab *rp;
- int i;
+ char *cmdname;
+ int e;
+ char **envp;
+ int exerrno;
- if (redirlist == NULL)
- return;
- INTOFF;
- rp = redirlist;
- for (i = 0; i < rp->pair_count; i++) {
- int fd = rp->two_fd[i].orig_fd;
- int copy = rp->two_fd[i].moved_to;
- if (copy == CLOSED) {
- if (!drop)
- close(fd);
- continue;
- }
- if (copy != EMPTY) {
- if (!drop) {
- /*close(fd);*/
- dup2_or_raise(copy, fd);
+ envp = listvars(VEXPORT, VUNSET, /*strlist:*/ NULL, /*end:*/ NULL);
+ if (strchr(prog, '/') != NULL) {
+ tryexec(prog, argv, envp);
+ e = errno;
+ } else {
+#if ENABLE_FEATURE_SH_STANDALONE
+ int applet_no = find_applet_by_name(prog);
+ if (applet_no >= 0)
+ tryexec_applet(applet_no, prog, argv, envp);
+ /* We tried execing ourself, but it didn't work.
+ * Maybe /proc/self/exe doesn't exist?
+ */
+#endif
+ e = ENOENT;
+ while (padvance(&path, argv[0]) >= 0) {
+ cmdname = stackblock();
+ if (--idx < 0 && pathopt == NULL) {
+ tryexec(cmdname, argv, envp);
+ if (errno != ENOENT && errno != ENOTDIR)
+ e = errno;
}
- close(copy);
}
}
- redirlist = rp->next;
- free(rp);
- INTON;
+
+ /* Map to POSIX errors */
+ switch (e) {
+ default:
+ exerrno = 126;
+ break;
+ case ELOOP:
+ case ENAMETOOLONG:
+ case ENOENT:
+ case ENOTDIR:
+ exerrno = 127;
+ break;
+ }
+ exitstatus = exerrno;
+ TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n",
+ prog, e, suppress_int));
+ ash_msg_and_raise(EXEND, "%s: %s", prog, errmsg(e, E_EXEC));
+ /* NOTREACHED */
}
-static void
-unwindredir(struct redirtab *stop)
+static struct job*
+vforkexec(union node *n, char **argv, const char *path, int idx)
{
- while (redirlist != stop)
- popredir(/*drop:*/ 0);
+ struct job *jp;
+ int pid;
+
+ jp = makejob(1);
+
+ sigblockall(NULL);
+ vforked = 1;
+
+ pid = vfork();
+
+ if (!pid) {
+ forkchild(jp, n, FORK_FG);
+ sigclearmask();
+ shellexec(argv[0], argv, path, idx);
+ /* NOTREACHED */
+ }
+
+ vforked = 0;
+ sigclearmask();
+ forkparent(jp, n, FORK_FG, pid);
+
+ return jp;
}
@@ -8476,162 +8635,6 @@ static struct tblentry **cmdtable;
static int builtinloc = -1; /* index in path of %builtin, or -1 */
-#if ENABLE_FEATURE_SH_STANDALONE
-static void
-tryexec_applet(int applet_no, const char *cmd, char **argv, char **envp)
-{
- if (!vforked && APPLET_IS_NOEXEC(applet_no)) {
- dbg_show_dirtymem("dirtymem in NOEXEC tryexec");
- clearenv();
- while (*envp)
- putenv(*envp++);
- popredir(/*drop:*/ 1);
-//FIXME: exec resets all non-DFL, non-IGN signal handlers to DFL,
-//but we _don't_ exec, such signals will reach ash's handler instead!
-//Maybe add code there to set the handler to DFL, and signal itself?
-// This works for "CMD [| CMD]..." pipes:
-//vforkexec():
-// vfork();
-// forkchild(jp, n, FORK_FG); // this resets TSTP,TTOU,INT,TERM,QUIT to DFL
-// shellexec(argv[0], argv, path, idx)
-// tryexec_applet()
-// we are here
-// And for "exec CMD":
-//execcmd():
-// iflag = 0;
-// mflag = 0;
-// optschanged(); // this resets TSTP,TTOU,INT,TERM to DFL
-// shlvl++;
-// setsignal(SIGQUIT); // this resets QUIT to DFL
-// shellexec() ->
-// tryexec_applet() ->
-// we are here
-// But ash -c 'LAST_CMD_WONT_FORK' does not work!
-//evalcommand():
-// if (!(flags & EV_EXIT) || may_have_traps)
-// // we don't use this branch
-// //else:
-// shellexec() ->
-// tryexec_applet() ->
-// we are here
-//SIGINT works 'by accident' (sets DFL+signals itself)
-//SIGQUIT is IGNORED!
-//Fixed by adding in the evalcommand() before that shellexec():
-// shlvl++;
-// setsignal(SIGQUIT);
-// //TODO: setsignal(TSTP,TTOU,INT,TERM) too?
-//
-// With traps set, this:
-// ash -c 'trap "echo HERE!" INT; exec xargs'
-// didn't work: ^C sets a "run trap later" flag and _returns_,
-// which is not expected by the NOEXEC'ed xargs applet! (It gets EINTR on read).
-// clear_traps() helps with this:
- clear_traps();
- run_noexec_applet_and_exit(applet_no, cmd, argv);
- /* does not return */
- }
- /* re-exec ourselves with the new arguments */
- execve(bb_busybox_exec_path, argv, envp);
- /* If they called chroot or otherwise made the binary
- * no longer executable, return.
- */
-}
-#endif
-
-static void
-tryexec(const char *cmd, char **argv, char **envp)
-{
- repeat:
-#ifdef SYSV
- do {
- execve(cmd, argv, envp);
- } while (errno == EINTR);
-#else
- execve(cmd, argv, envp);
-#endif
- if (cmd != bb_busybox_exec_path && errno == ENOEXEC) {
- /* Run "cmd" as a shell script:
- * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
- * "If the execve() function fails with ENOEXEC, the shell
- * shall execute a command equivalent to having a shell invoked
- * with the command name as its first operand,
- * with any remaining arguments passed to the new shell"
- *
- * That is, do not use $SHELL, user's shell, or /bin/sh;
- * just call ourselves.
- *
- * Note that bash reads ~80 chars of the file, and if it sees
- * a zero byte before it sees newline, it doesn't try to
- * interpret it, but fails with "cannot execute binary file"
- * message and exit code 126. For one, this prevents attempts
- * to interpret foreign ELF binaries as shell scripts.
- */
- argv[0] = (char*) cmd;
- cmd = bb_busybox_exec_path;
- /* NB: this is only possible because all callers of shellexec()
- * ensure that the argv[-1] slot exists!
- */
- argv--;
- argv[0] = (char*) "ash";
- goto repeat;
- }
-}
-
-/*
- * Exec a program. Never returns. If you change this routine, you may
- * have to change the find_command routine as well.
- * argv[-1] must exist and be writable! See tryexec() for why.
- */
-static void shellexec(char *prog, char **argv, const char *path, int idx)
-{
- char *cmdname;
- int e;
- char **envp;
- int exerrno;
-
- envp = listvars(VEXPORT, VUNSET, /*strlist:*/ NULL, /*end:*/ NULL);
- if (strchr(prog, '/') != NULL) {
- tryexec(prog, argv, envp);
- e = errno;
- } else {
-#if ENABLE_FEATURE_SH_STANDALONE
- int applet_no = find_applet_by_name(prog);
- if (applet_no >= 0)
- tryexec_applet(applet_no, prog, argv, envp);
- /* We tried execing ourself, but it didn't work.
- * Maybe /proc/self/exe doesn't exist?
- */
-#endif
- e = ENOENT;
- while (padvance(&path, argv[0]) >= 0) {
- cmdname = stackblock();
- if (--idx < 0 && pathopt == NULL) {
- tryexec(cmdname, argv, envp);
- if (errno != ENOENT && errno != ENOTDIR)
- e = errno;
- }
- }
- }
-
- /* Map to POSIX errors */
- switch (e) {
- default:
- exerrno = 126;
- break;
- case ELOOP:
- case ENAMETOOLONG:
- case ENOENT:
- case ENOTDIR:
- exerrno = 127;
- break;
- }
- exitstatus = exerrno;
- TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n",
- prog, e, suppress_int));
- ash_msg_and_raise(EXEND, "%s: %s", prog, errmsg(e, E_EXEC));
- /* NOTREACHED */
-}
-
static void
printentry(struct tblentry *cmdp)
{