hush: implement proper SIGHUP handling

function                                             old     new   delta
check_and_run_traps                                  164     229     +65
insert_bg_job                                        376     366     -10
hush_main                                            937     927     -10

diff --git a/shell/hush.c b/shell/hush.c
index f9757a7..72589fd 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -52,11 +52,10 @@
  *      grep for "TODO" and fix (some of them are easy)
  *      change { and } from special chars to reserved words
  *      $var refs in function do not pick up values set by "var=val func"
- *      builtins: return, ulimit
+ *      builtins: ulimit
  *      follow IFS rules more precisely, including update semantics
  *      figure out what to do with backslash-newline
  *      continuation lines, both explicit and implicit - done?
- *      SIGHUP handling
  *      separate job control from interactiveness
  *      (testcase: booting with init=/bin/hush does not show prompt (2009-04))
  *
@@ -1070,8 +1069,9 @@
  * "trap 'cmd' SIGxxx":
  *    set bit in blocked_set (even if 'cmd' is '')
  * after [v]fork, if we plan to be a shell:
- *    nothing for {} child shell (say, "true | { true; true; } | true")
- *    unset all traps if () shell.
+ *    unblock signals with special interactive handling
+ *    (child shell is not interactive),
+ *    unset all traps (note: regardless of child shell's type - {}, (), etc)
  * after [v]fork, if we plan to exec:
  *    POSIX says pending signal mask is cleared in child - no need to clear it.
  *    Restore blocked signal set to one inherited by shell just prior to exec.
@@ -1084,9 +1084,9 @@
 	SPECIAL_INTERACTIVE_SIGS = 0
 #if ENABLE_HUSH_JOB
 		| (1 << SIGTTIN) | (1 << SIGTTOU) | (1 << SIGTSTP)
+		| (1 << SIGHUP)
 #endif
 		| (1 << SIGTERM)
-//TODO		| (1 << SIGHUP)
 		| (1 << SIGINT)
 };
 
@@ -1095,53 +1095,6 @@
 //	G.count_SIGCHLD++;
 //}
 
-static int check_and_run_traps(int sig)
-{
-	static const struct timespec zero_timespec = { 0, 0 };
-	smalluint save_rcode;
-	int last_sig = 0;
-
-	if (sig)
-		goto jump_in;
-	while (1) {
-		sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec);
-		if (sig <= 0)
-			break;
- jump_in:
-		last_sig = sig;
-		if (G.traps && G.traps[sig]) {
-			if (G.traps[sig][0]) {
-				/* We have user-defined handler */
-				char *argv[] = { NULL, xstrdup(G.traps[sig]), NULL };
-				save_rcode = G.last_exitcode;
-				builtin_eval(argv);
-				free(argv[1]);
-				G.last_exitcode = save_rcode;
-			} /* else: "" trap, ignoring signal */
-			continue;
-		}
-		/* not a trap: special action */
-		switch (sig) {
-//		case SIGCHLD:
-//			G.count_SIGCHLD++;
-//			break;
-		case SIGINT:
-//TODO: add putchar('\n') also when we detect that child was killed (sleep 5 + ^C)
-			/* Builtin was ^C'ed, make it look prettier: */
-			bb_putchar('\n');
-			G.flag_SIGINT = 1;
-			break;
-//TODO
-//		case SIGHUP: ...
-//			break;
-		default: /* ignored: */
-			/* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */
-			break;
-		}
-	}
-	return last_sig;
-}
-
 #if ENABLE_HUSH_JOB
 
 /* After [v]fork, in child: do not restore tty pgrp on xfunc death */
@@ -1151,7 +1104,7 @@
 
 /* Restores tty foreground process group, and exits.
  * May be called as signal handler for fatal signal
- * (will faithfully resend signal to itself, producing correct exit state)
+ * (will resend signal to itself, producing correct exit state)
  * or called directly with -EXITCODE.
  * We also call it if xfunc is exiting. */
 static void sigexit(int sig) NORETURN;
@@ -1201,6 +1154,65 @@
 #endif
 }
 
+static int check_and_run_traps(int sig)
+{
+	static const struct timespec zero_timespec = { 0, 0 };
+	smalluint save_rcode;
+	int last_sig = 0;
+
+	if (sig)
+		goto jump_in;
+	while (1) {
+		sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec);
+		if (sig <= 0)
+			break;
+ jump_in:
+		last_sig = sig;
+		if (G.traps && G.traps[sig]) {
+			if (G.traps[sig][0]) {
+				/* We have user-defined handler */
+				char *argv[] = { NULL, xstrdup(G.traps[sig]), NULL };
+				save_rcode = G.last_exitcode;
+				builtin_eval(argv);
+				free(argv[1]);
+				G.last_exitcode = save_rcode;
+			} /* else: "" trap, ignoring signal */
+			continue;
+		}
+		/* not a trap: special action */
+		switch (sig) {
+//		case SIGCHLD:
+//			G.count_SIGCHLD++;
+//			break;
+		case SIGINT:
+//TODO: add putchar('\n') also when we detect that child was killed (sleep 5 + ^C)
+			/* Builtin was ^C'ed, make it look prettier: */
+			bb_putchar('\n');
+			G.flag_SIGINT = 1;
+			break;
+#if ENABLE_HUSH_JOB
+		case SIGHUP: {
+			struct pipe *job;
+			/* bash is observed to signal whole process groups,
+			 * not individual processes */
+			for (job = G.job_list; job; job = job->next) {
+				if (job->pgrp <= 0)
+					continue;
+				debug_printf_exec("HUPing pgrp %d\n", job->pgrp);
+				if (kill(- job->pgrp, SIGHUP) == 0)
+					kill(- job->pgrp, SIGCONT);
+			}
+			sigexit(SIGHUP);
+		}
+#endif
+		default: /* ignored: */
+			/* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */
+			break;
+		}
+	}
+	return last_sig;
+}
+
 
 static const char *set_cwd(void)
 {
@@ -2061,7 +2073,9 @@
 		case '`': /* <SPECIAL_VAR_SYMBOL>`cmd<SPECIAL_VAR_SYMBOL> */
 			*p = '\0';
 			arg++;
-//TODO: can we just stuff it into "output" directly?
+			/* Can't just stuff it into output o_string,
+			 * expanded result may need to be globbed
+			 * and $IFS-splitted */
 			debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch);
 			process_command_subs(&subst_result, arg);
 			debug_printf_subst("SUBST RES '%s'\n", subst_result.data);
@@ -2777,7 +2791,8 @@
 		}
 		funcp = funcp->next;
 	}
-	debug_printf_exec("found function '%s'\n", name);
+	if (funcp)
+		debug_printf_exec("found function '%s'\n", name);
 	return funcp;
 }
 
@@ -2819,7 +2834,7 @@
 		}
 		goto skip;
 	}
-	debug_printf_exec("remembering new function '%s'\n", command->argv[0]);
+	debug_printf_exec("remembering new function '%s'\n", name);
 	funcp = *funcpp = xzalloc(sizeof(*funcp));
 	/*funcp->next = NULL;*/
  skip:
@@ -3059,8 +3074,11 @@
 	}
 
 	len = 0;
-	do len += strlen(*argv) + 1; while (*++argv);
-	pi->cmdtext = p = xmalloc(len);
+	do {
+		len += strlen(*argv) + 1;
+	} while (*++argv);
+	p = xmalloc(len);
+	pi->cmdtext = p;// = xmalloc(len);
 	argv = pi->cmds[0].argv;
 	do {
 		len = strlen(*argv);
@@ -3074,44 +3092,36 @@
 
 static void insert_bg_job(struct pipe *pi)
 {
-	struct pipe *thejob;
+	struct pipe *job, **jobp;
 	int i;
 
 	/* Linear search for the ID of the job to use */
 	pi->jobid = 1;
-	for (thejob = G.job_list; thejob; thejob = thejob->next)
-		if (thejob->jobid >= pi->jobid)
-			pi->jobid = thejob->jobid + 1;
+	for (job = G.job_list; job; job = job->next)
+		if (job->jobid >= pi->jobid)
+			pi->jobid = job->jobid + 1;
 
-	/* Add thejob to the list of running jobs */
-	if (!G.job_list) {
-		thejob = G.job_list = xmalloc(sizeof(*thejob));
-	} else {
-		for (thejob = G.job_list; thejob->next; thejob = thejob->next)
-			continue;
-		thejob->next = xmalloc(sizeof(*thejob));
-		thejob = thejob->next;
-	}
+	/* Add job to the list of running jobs */
+	jobp = &G.job_list;
+	while ((job = *jobp) != NULL)
+		jobp = &job->next;
+	job = *jobp = xmalloc(sizeof(*job));
 
-	/* Physically copy the struct job */
-	memcpy(thejob, pi, sizeof(struct pipe));
-	thejob->cmds = xzalloc(sizeof(pi->cmds[0]) * pi->num_cmds);
-	/* We cannot copy entire pi->cmds[] vector! Double free()s will happen */
+	*job = *pi; /* physical copy */
+	job->next = NULL;
+	job->cmds = xzalloc(sizeof(pi->cmds[0]) * pi->num_cmds);
+	/* Cannot copy entire pi->cmds[] vector! This causes double frees */
 	for (i = 0; i < pi->num_cmds; i++) {
-// TODO: do we really need to have so many fields which are just dead weight
-// at execution stage?
-		thejob->cmds[i].pid = pi->cmds[i].pid;
+		job->cmds[i].pid = pi->cmds[i].pid;
 		/* all other fields are not used and stay zero */
 	}
-	thejob->next = NULL;
-	thejob->cmdtext = xstrdup(get_cmdtext(pi));
+	job->cmdtext = xstrdup(get_cmdtext(pi));
 
-	/* We don't wait for background thejobs to return -- append it
-	   to the list of backgrounded thejobs and leave it alone */
 	if (G_interactive_fd)
-		printf("[%d] %d %s\n", thejob->jobid, thejob->cmds[0].pid, thejob->cmdtext);
-	G.last_bg_pid = thejob->cmds[0].pid;
-	G.last_jobid = thejob->jobid;
+		printf("[%d] %d %s\n", job->jobid, job->cmds[0].pid, job->cmdtext);
+	/* Last command's pid goes to $! */
+	G.last_bg_pid = job->cmds[job->num_cmds - 1].pid;
+	G.last_jobid = job->jobid;
 }
 
 static void remove_bg_job(struct pipe *pi)
@@ -3306,7 +3316,7 @@
  * cmd || ...  { list } || ...
  * If it is, then we can run cmd as a builtin, NOFORK [do we do this?],
  * or (if SH_STANDALONE) an applet, and we can run the { list }
- * with run_list(). If it isn't one of these, we fork and exec cmd.
+ * with run_list. If it isn't one of these, we fork and exec cmd.
  *
  * Cases when we must fork:
  * non-single:   cmd | cmd
@@ -3942,7 +3952,11 @@
 				check_and_run_traps(0);
 #if ENABLE_HUSH_JOB
 				if (G.run_list_level == 1)
+{
+debug_printf_exec("insert_bg_job1\n");
 					insert_bg_job(pi);
+debug_printf_exec("insert_bg_job2\n");
+}
 #endif
 				G.last_exitcode = rcode = EXIT_SUCCESS;
 				debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n");
@@ -5853,11 +5867,10 @@
 	/* bash 3.2 seems to handle these just like 'fatal' ones */
 	maybe_set_to_sigexit(SIGPIPE);
 	maybe_set_to_sigexit(SIGALRM);
-//TODO: disable and move down when proper SIGHUP handling is added
-	maybe_set_to_sigexit(SIGHUP );
-	/* if we are interactive, [SIGHUP,] SIGTERM and SIGINT are masked.
+	/* if we are interactive, SIGHUP, SIGTERM and SIGINT are masked.
 	 * if we aren't interactive... but in this case
 	 * we never want to restore pgrp on exit, and this fn is not called */
+	/*maybe_set_to_sigexit(SIGHUP );*/
 	/*maybe_set_to_sigexit(SIGTERM);*/
 	/*maybe_set_to_sigexit(SIGINT );*/
 }
@@ -6350,7 +6363,7 @@
 #if !BB_MMU
 		nommu_save_t dummy;
 #endif
-// TODO: if exec fails, bash does NOT exit! We do...
+		/* TODO: if exec fails, bash does NOT exit! We do... */
 		pseudo_exec_argv(&dummy, argv, 0, NULL);
 		/* never returns */
 	}
@@ -6472,8 +6485,8 @@
 	bb_error_msg("%s: %d: no such job", argv[0], jobnum);
 	return EXIT_FAILURE;
  found:
-	// TODO: bash prints a string representation
-	// of job being foregrounded (like "sleep 1 | cat")
+	/* TODO: bash prints a string representation
+	 * of job being foregrounded (like "sleep 1 | cat") */
 	if (argv[0][0] == 'f') {
 		/* Put the job into the foreground.  */
 		tcsetpgrp(G_interactive_fd, pi->pgrp);
@@ -6705,7 +6718,7 @@
 	if (*++argv == NULL)
 		return EXIT_FAILURE;
 
-	/* TODO: search through $PATH is missing */
+// TODO: search through $PATH is missing
 	input = fopen_or_warn(*argv, "r");
 	if (!input) {
 		/* bb_perror_msg("%s", *argv); - done by fopen_or_warn */