← Commit list

df154028d

Denys Vlasenko
Denys Vlasenko
2025-08-09
SUCCESS Duration: 153s

Subject

ash: eval: Add vfork support

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

—

Build Log

(log yok)

Commit Diff GitHub →

include/libbb.h+2 -0
@@ -643,6 +643,8 @@ int sigaction_set(int sig, const struct sigaction *act) FAST_FUNC;
int sigprocmask_allsigs(int how) FAST_FUNC;
/* Return old set in the same set: */
int sigprocmask2(int how, sigset_t *set) FAST_FUNC;
+/* SIG_BLOCK all signals, return old set: */
+int sigblockall(sigset_t *set) FAST_FUNC;
/* Standard handler which just records signo */
extern smallint bb_got_signal;
void record_signo(int signo); /* not FAST_FUNC! */
libbb/signals.c+12 -0
@@ -41,6 +41,18 @@ int FAST_FUNC sigprocmask2(int how, sigset_t *set)
return sigprocmask(how, set, oset);
}
+int FAST_FUNC sigblockall(sigset_t *set)
+{
+#if 0 /* nope. set can be NULL */
+ sigfillset(set);
+ return sigprocmask2(SIG_SETMASK, set);
+#else
+ sigset_t mask;
+ sigfillset(&mask);
+ return sigprocmask(SIG_SETMASK, &mask, set);
+#endif
+}
+
void FAST_FUNC bb_signals(int sigs, void (*f)(int))
{
int sig_no = 0;
shell/ash.c+102 -35
@@ -408,6 +408,7 @@ struct globals_misc {
uint8_t back_exitstatus;/* exit status of backquoted command */
smallint job_warning; /* user was warned about stopped jobs (can be 2, 1 or 0). */
smallint inps4; /* Prevent PS4 nesting. */
+ smallint vforked;
int savestatus; /* exit status of last command outside traps */
int rootpid; /* pid of main shell */
/* shell level: 0 for the main shell, 1 for its children, and so on */
@@ -431,7 +432,7 @@ struct globals_misc {
* but we do read it async.
*/
volatile /*sig_atomic_t*/ smallint pending_int; /* 1 = got SIGINT */
- volatile /*sig_atomic_t*/ smallint gotsigchld; /* 1 = got SIGCHLD */
+ volatile /*sig_atomic_t*/ smallint gotsigchld; /* 1 = got SIGCHLD */
volatile /*sig_atomic_t*/ smallint pending_sig; /* last pending signal */
smallint exception_type; /* kind of exception: */
#define EXINT 0 /* SIGINT received */
@@ -506,6 +507,7 @@ extern struct globals_misc *BB_GLOBAL_CONST ash_ptr_to_globals_misc;
#define back_exitstatus (G_misc.back_exitstatus )
#define job_warning (G_misc.job_warning)
#define inps4 (G_misc.inps4 )
+#define vforked (G_misc.vforked )
#define savestatus (G_misc.savestatus )
#define rootpid (G_misc.rootpid )
#define shlvl (G_misc.shlvl )
@@ -580,6 +582,17 @@ var_end(const char *var)
return var;
}
+/* Our signal logic never blocks individual signals
+ * using signal mask - only by setting SIG_IGN handler.
+ * Therefore just unmasking all of them instead of "restore old mask"
+ * approach is safe, modulo a case where the shell itself inherited
+ * a _masked_ signal.
+ */
+static void
+sigclearmask(void)
+{
+ sigprocmask_allsigs(SIG_UNBLOCK);
+}
/* ============ Parser data */
@@ -678,6 +691,10 @@ raise_exception(int e)
if (exception_handler == NULL)
abort();
#endif
+
+ if (vforked)
+ _exit(exitstatus);
+
INTOFF;
exception_type = e;
longjmp(exception_handler->loc, 1);
@@ -1481,6 +1498,7 @@ ash_vmsg_and_raise(int cond, const char *msg, va_list ap)
/* NOTREACHED */
}
+/* This function is called sh_error() in dash */
static void ash_msg_and_raise_error(const char *, ...) NORETURN;
static void
ash_msg_and_raise_error(const char *msg, ...)
@@ -3665,7 +3683,6 @@ struct job {
struct job *prev_job; /* previous job */
};
-static struct job *makejob(int);
static int forkshell(struct job *, union node *, int);
static int waitforjob(struct job *);
@@ -3688,15 +3705,23 @@ ignoresig(int signo)
/* No, need to do it */
signal(signo, SIG_IGN);
}
- sigmode[signo - 1] = S_HARD_IGN;
+ if (!vforked)
+ sigmode[signo - 1] = S_HARD_IGN;
}
/*
* Only one usage site - in setsignal()
+ * This function is called onsig() in dash
*/
static void
signal_handler(int signo)
{
+ // parent momentarily has vforked == 1 too, but it masks
+ // all signals until after it resets vforked to 0.
+ if (vforked)
+ /* We are vfork child, DO NOT MODIFY ANY VARIABLES! */
+ return;
+
if (signo == SIGCHLD) {
gotsigchld = 1;
if (!trap[SIGCHLD])
@@ -3724,10 +3749,13 @@ signal_handler(int signo)
static void
setsignal(int signo)
{
+ int lvforked;
char *t;
char cur_act, new_act;
struct sigaction act;
+ lvforked = vforked;
+
t = trap[signo];
new_act = S_DFL;
if (t != NULL) { /* trap for this sig is set */
@@ -3736,7 +3764,7 @@ setsignal(int signo)
new_act = S_IGN;
}
- if (rootshell && new_act == S_DFL) {
+ if (rootshell && new_act == S_DFL && !lvforked) {
switch (signo) {
case SIGINT:
if (iflag || minusc || sflag == 0)
@@ -3806,8 +3834,8 @@ setsignal(int signo)
if (cur_act == S_HARD_IGN || cur_act == new_act)
return;
- *t = new_act;
-
+ if (!lvforked)
+ *t = new_act;
act.sa_handler = SIG_DFL;
switch (new_act) {
case S_CATCH:
@@ -4342,15 +4370,16 @@ waitproc(int block, int *status)
if (err || (err = -!block))
break;
- sigfillset(&oldmask);
- sigprocmask2(SIG_SETMASK, &oldmask); /* mask is updated */
- while (!gotsigchld && !pending_sig)
- sigsuspend(&oldmask);
- sigprocmask(SIG_SETMASK, &oldmask, NULL);
//simpler, but unsafe: a signal can set pending_sig after check, but before pause():
//while (!gotsigchld && !pending_sig)
// pause();
+ sigblockall(&oldmask);
+
+ while (!gotsigchld && !pending_sig)
+ sigsuspend(&oldmask);
+
+ sigclearmask();
} while (gotsigchld);
return err;
@@ -5187,18 +5216,28 @@ static void closescript(void);
static NOINLINE void
forkchild(struct job *jp, union node *n, int mode)
{
+ int lvforked;
int oldlvl;
TRACE(("Child shell %d\n", getpid()));
oldlvl = shlvl;
- shlvl++;
+ lvforked = vforked;
+
+ if (!lvforked) {
+ shlvl++;
+
+ closescript();
+
+#if JOBS
+ /* do job control only in root shell */
+ jobctl = 0;
+#endif
+ }
/* man bash: "Non-builtin commands run by bash have signal handlers
* set to the values inherited by the shell from its parent".
* Do we do it correctly? */
- closescript();
-
if (n && n->type == NCMD /* is it single cmd? */
/* && n->ncmd.args->type == NARG - always true? */
&& n->ncmd.args && strcmp(n->ncmd.args->narg.text, "trap") == 0
@@ -5244,10 +5283,9 @@ forkchild(struct job *jp, union node *n, int mode)
trap_ptr = xmemdup(trap, sizeof(trap));
/* Fall through into clearing traps */
}
- clear_traps();
+ if (!lvforked)
+ clear_traps();
#if JOBS
- /* do job control only in root shell */
- jobctl = 0;
if (mode != FORK_NOJOB && jp->jobctl && oldlvl == 0) {
pid_t pgrp;
@@ -5302,6 +5340,10 @@ forkchild(struct job *jp, union node *n, int mode)
return;
}
#endif
+
+ if (lvforked)
+ return;
+
for (jp = curjob; jp; jp = jp->prev_job)
freejob(jp);
}
@@ -5313,7 +5355,15 @@ forkchild(struct job *jp, union node *n, int mode)
static void
forkparent(struct job *jp, union node *n, int mode, pid_t pid)
{
- TRACE(("In parent shell: child = %d\n", pid));
+ if (pid < 0) {
+ TRACE(("Fork failed, errno=%d", errno));
+ if (jp)
+ freejob(jp);
+ ash_msg_and_raise_perror("can't fork");
+ /* NOTREACHED */
+ }
+
+ TRACE(("In parent shell: child = %d\n", pid));
if (!jp) /* jp is NULL when called by openhere() for heredoc support */
return;
#if JOBS
@@ -5352,21 +5402,44 @@ forkshell(struct job *jp, union node *n, int mode)
TRACE(("forkshell(%%%d, %p, %d) called\n", jobno(jp), n, mode));
pid = fork();
- if (pid < 0) {
- TRACE(("Fork failed, errno=%d", errno));
- if (jp)
- freejob(jp);
- ash_msg_and_raise_perror("can't fork");
- }
if (pid == 0) {
CLEAR_RANDOM_T(&random_gen); /* or else $RANDOM repeats in child */
forkchild(jp, n, mode);
- } else {
+ } else
forkparent(jp, n, mode, pid);
- }
+
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.
*
@@ -8341,7 +8414,6 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c
* 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) NORETURN;
static void shellexec(char *prog, char **argv, const char *path, int idx)
{
char *cmdname;
@@ -10612,6 +10684,7 @@ evalcommand(union node *cmd, int flags)
break;
}
#endif
+ /* Fork off a child process if necessary. */
/* Can we avoid forking? For example, very last command
* in a script or a subshell does not need forking,
* we can just exec it.
@@ -10620,14 +10693,8 @@ evalcommand(union node *cmd, int flags)
/* No, forking off a child is necessary */
INTOFF;
get_tty_state();
- jp = makejob(1);
- if (forkshell(jp, cmd, FORK_FG) != 0) {
- /* parent */
- break;
- }
- /* child */
- FORCEINTON;
- /* fall through to exec'ing external program */
+ jp = vforkexec(cmd, argv, path, cmdentry.u.index);
+ break;
}
shellexec(argv[0], argv, path, cmdentry.u.index);
/* NOTREACHED */
shell/hush.c+1 -2
@@ -12160,8 +12160,7 @@ static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid
* Or else we may race with SIGCHLD, lose it,
* and get stuck in sigsuspend...
*/
- sigfillset(&oldset); /* block all signals, remember old set */
- sigprocmask2(SIG_SETMASK, &oldset);
+ sigblockall(&oldset); /* block all signals, remember old set */
if (!sigisemptyset(&G.pending_set)) {
/* Crap! we raced with some signal! */