| @@ -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 */ |