/* number of presumed living untracked jobs */
static int jobless; //4
+#if 0
+/* Bash has a feature: it restores termios after a successful wait for
+ * a foreground job which had at least one stopped or sigkilled member.
+ * The probable rationale is that SIGSTOP and SIGKILL can preclude task from
+ * properly restoring tty state. Should we do this too?
+ * A reproducer: ^Z an interactive python:
+ *
+ * # python
+ * Python 2.7.12 (...)
+ * >>> ^Z
+ * { python leaves tty in -icanon -echo state. We do survive that... }
+ * [1]+ Stopped python
+ * { ...however, next program (python #2) does not survive it well: }
+ * # python
+ * Python 2.7.12 (...)
+ * >>> Traceback (most recent call last):
+ * { above, I typed "qwerty<CR>", but -echo state is still in effect }
+ * File "<stdin>", line 1, in <module>
+ * NameError: name 'qwerty' is not defined
+ *
+ * The implementation below is modeled on bash code and seems to work.
+ * However, I'm not sure we should do this. For one: what if I'd fg
+ * the stopped python instead? It'll be confused by "restored" tty state.
+ */
+static struct termios shell_tty_info;
+static void
+get_tty_state(void)
+{
+ if (rootshell && ttyfd >= 0)
+ tcgetattr(ttyfd, &shell_tty_info);
+}
+static void
+set_tty_state(void)
+{
+ /* if (rootshell) - caller ensures this */
+ if (ttyfd >= 0)
+ tcsetattr(ttyfd, TCSADRAIN, &shell_tty_info);
+}
+static int
+job_signal_status(struct job *jp)
+{
+ int status;
+ unsigned i;
+ struct procstat *ps = jp->ps;
+ for (i = 0; i < jp->nprocs; i++) {
+ status = ps[i].ps_status;
+ if (WIFSIGNALED(status) || WIFSTOPPED(status))
+ return status;
+ }
+ return 0;
+}
+static void
+restore_tty_if_stopped_or_signaled(struct job *jp)
+{
+//TODO: check what happens if we come from waitforjob() in expbackq()
+ if (rootshell) {
+ int s = job_signal_status(jp);
+ if (s) /* WIFSIGNALED(s) || WIFSTOPPED(s) */
+ set_tty_state();
+ }
+}
+#else
+# define get_tty_state() ((void)0)
+# define restore_tty_if_stopped_or_signaled(jp) ((void)0)
+#endif
+
static void
set_curjob(struct job *jp, unsigned mode)
{
goto out;
jp->state = JOBRUNNING;
pgid = jp->ps[0].ps_pid;
- if (mode == FORK_FG)
+ if (mode == FORK_FG) {
+ get_tty_state();
xtcsetpgrp(ttyfd, pgid);
+ }
killpg(pgid, SIGCONT);
ps = jp->ps;
i = jp->nprocs;
memset(jp, 0, sizeof(*jp));
#if JOBS
/* jp->jobctl is a bitfield.
- * "jp->jobctl |= jobctl" likely to give awful code */
+ * "jp->jobctl |= doing_jobctl" likely to give awful code */
if (doing_jobctl)
jp->jobctl = 1;
#endif
#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
evalsubshell(union node *n, int flags)
{
struct job *jp;
- int backgnd = (n->type == NBACKGND);
+ int backgnd = (n->type == NBACKGND); /* FORK_BG(1) if yes, else FORK_FG(0) */
int status;
expredir(n->nredir.redirect);
if (!backgnd && (flags & EV_EXIT) && !may_have_traps)
goto nofork;
INT_OFF;
+ if (backgnd == FORK_FG)
+ get_tty_state();
jp = makejob(/*n,*/ 1);
if (forkshell(jp, n, backgnd) == 0) {
/* child */
}
/* parent */
status = 0;
- if (!backgnd)
+ if (backgnd == FORK_FG)
status = waitforjob(jp);
INT_ON;
return status;
pipelen++;
flags |= EV_EXIT;
INT_OFF;
+ if (n->npipe.pipe_backgnd == 0)
+ get_tty_state();
jp = makejob(/*n,*/ pipelen);
prevfd = -1;
for (lp = n->npipe.cmdlist; lp; lp = lp->next) {
if (!(flags & EV_EXIT) || may_have_traps) {
/* No, forking off a child is necessary */
INT_OFF;
+ get_tty_state();
jp = makejob(/*cmd,*/ 1);
if (forkshell(jp, cmd, FORK_FG) != 0) {
/* parent */