Module Name:    src
Committed By:   kre
Date:           Thu Aug 20 23:03:17 UTC 2020

Modified Files:
        src/bin/sh: jobs.c

Log Message:
Add lots of comments explaining what is happening in here.

Also enhance some of the DEBUG mode trace output (nothing visible
in a normal shell build).

A couple of very minor code changes that no-one should ever notice
(eg: one less wait() call in the case that there is nothing pending).


To generate a diff of this commit:
cvs rdiff -u -r1.107 -r1.108 src/bin/sh/jobs.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/bin/sh/jobs.c
diff -u src/bin/sh/jobs.c:1.107 src/bin/sh/jobs.c:1.108
--- src/bin/sh/jobs.c:1.107	Fri Feb  7 02:06:12 2020
+++ src/bin/sh/jobs.c	Thu Aug 20 23:03:17 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: jobs.c,v 1.107 2020/02/07 02:06:12 kre Exp $	*/
+/*	$NetBSD: jobs.c,v 1.108 2020/08/20 23:03:17 kre Exp $	*/
 
 /*-
  * Copyright (c) 1991, 1993
@@ -37,7 +37,7 @@
 #if 0
 static char sccsid[] = "@(#)jobs.c	8.5 (Berkeley) 5/4/95";
 #else
-__RCSID("$NetBSD: jobs.c,v 1.107 2020/02/07 02:06:12 kre Exp $");
+__RCSID("$NetBSD: jobs.c,v 1.108 2020/08/20 23:03:17 kre Exp $");
 #endif
 #endif /* not lint */
 
@@ -380,6 +380,18 @@ restartjob(struct job *jp)
 		return;
 	INTOFF;
 	for (e = i = 0; i < jp->nprocs; i++) {
+		/*
+		 * Don't touch a process we already waited for and collected
+		 * exit status, that pid may have been reused for something
+		 * else - even another of our jobs
+		 */
+		if (jp->ps[i].status != -1 && !WIFSTOPPED(jp->ps[i].status))
+			continue;
+
+		/*
+		 * Otherwise tell it to continue, if it worked, we're done
+		 * (we signal the whole process group)
+		 */
 		if (killpg(jp->ps[i].pid, SIGCONT) != -1)
 			break;
 		if (e == 0 && errno != ESRCH)
@@ -387,6 +399,11 @@ restartjob(struct job *jp)
 	}
 	if (i >= jp->nprocs)
 		error("Cannot continue job (%s)", strerror(e ? e : ESRCH));
+
+	/*
+	 * Now change state of all stopped processes in the job to running
+	 * If there were any, the job is now running as well.
+	 */
 	for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) {
 		if (WIFSTOPPED(ps->status)) {
 			VTRACE(DBG_JOBS, (
@@ -570,10 +587,10 @@ showjobs(struct output *out, int mode)
 
 	CTRACE(DBG_JOBS, ("showjobs(%x) called\n", mode));
 
-	/* If not even one one job changed, there is nothing to do */
-	gotpid = dowait(WSILENT, NULL, NULL);
-	while (dowait(WSILENT, NULL, NULL) > 0)
-		continue;
+	/*  Collect everything pending in the kernel */
+	if ((gotpid = dowait(WSILENT, NULL, NULL)) > 0)
+		while (dowait(WSILENT, NULL, NULL) > 0)
+			continue;
 #ifdef JOBS
 	/*
 	 * Check if we are not in our foreground group, and if not
@@ -813,19 +830,25 @@ waitcmd(int argc, char **argv)
 
 		/*
 		 * There is at least 1 job running, so we can
-		 * safely wait() for something to exit.
+		 * safely wait() (blocking) for something to exit.
 		 */
 		if (jp->state == JOBRUNNING) {
 			job = NULL;
 			if ((i = dowait(WBLOCK|WNOFREE, NULL, &job)) == -1)
 			       return 128 + lastsig();
 
-			if (job == NULL)	/* an interloper */
+			/*
+			 * This happens if an interloper has died
+			 * (eg: a child of the executable that exec'd us)
+			 * Simply go back and start all over again
+			 * (this is rare).
+			 */ 
+			if (job == NULL)
 				continue;
 
 			/*
-			 * one of the job's processes exited,
-			 * but there are more
+			 * one of the reported job's processes exited,
+			 * but there are more still running, back for more
 			 */
 			if (job->state == JOBRUNNING)
 				continue;
@@ -1314,7 +1337,17 @@ waitforjob(struct job *jp)
 
 
 /*
- * Wait for a process to terminate.
+ * Wait for a process (any process) to terminate.
+ *
+ * If "job" is given (not NULL), then its jobcontrol status (and mflag)
+ * are used to determine if we wait for stopping/continuing processes or
+ * only terminating ones, and the decision whether to report to stdout
+ * or not varies depending what happened, and whether the affected job
+ * is the one that was requested or not.
+ *
+ * If "changed" is not NULL, then the job which changed because a
+ * process terminated/stopped will be reported by setting *changed,
+ * if there is any such job, otherwise we set *changed = NULL.
  */
 
 STATIC int
@@ -1327,53 +1360,123 @@ dowait(int flags, struct job *job, struc
 	struct job *thisjob;
 	int done;
 	int stopped;
+	int err;
 
-	VTRACE(DBG_JOBS|DBG_PROCS, ("dowait(%x) called\n", flags));
+	VTRACE(DBG_JOBS|DBG_PROCS, ("dowait(%x) called for job %d%s\n",
+	    flags, (job ? job-jobtab+1 : 0), changed ? " [report change]":""));
 
 	if (changed != NULL)
 		*changed = NULL;
 
+	/*
+	 * First deal with the kernel, collect info on any (one) of our
+	 * children that has changed state since we last asked.
+	 * (loop if we're interrupted by a signal that we aren't processing)
+	 */
 	do {
+		err = 0;
 		pid = waitproc(flags & WBLOCK, job, &status);
-		VTRACE(DBG_JOBS|DBG_PROCS, ("wait returns pid %d, status %#x\n",
-		    pid, status));
-	} while (pid == -1 && errno == EINTR && pendingsigs == 0);
+		if (pid == -1)
+			err = errno;
+		VTRACE(DBG_JOBS|DBG_PROCS,
+		    ("wait returns pid %d (e:%d), status %#x (ps=%d)\n",
+		    pid, err, status, pendingsigs));
+	} while (pid == -1 && err == EINTR && pendingsigs == 0);
+
+	/*
+	 * if nothing exited/stopped/..., we have nothing else to do
+	 */
 	if (pid <= 0)
 		return pid;
+
+	/*
+	 * Otherwise, try to find the process, somewhere in our job table
+	 */
 	INTOFF;
 	thisjob = NULL;
 	for (jp = jobtab ; jp < jobtab + njobs ; jp++) {
 		if (jp->used) {
-			done = 1;
-			stopped = 1;
+			/*
+			 * For each job that is in use (this is one)
+			 */
+			done = 1;	/* assume it is finished */
+			stopped = 1;	/* and has stopped */
+
+			/*
+			 * Now scan all our child processes of the job
+			 */
 			for (sp = jp->ps ; sp < jp->ps + jp->nprocs ; sp++) {
 				if (sp->pid == -1)
 					continue;
+				/*
+				 * If the process that changed is the one
+				 * we're looking at, and it was previously
+				 * running (-1) or was stopped (anything else
+				 * and it must have already finished earlier,
+				 * so cannot be the process that just changed)
+				 * then we update its status
+				 */
 				if (sp->pid == pid &&
 				  (sp->status==-1 || WIFSTOPPED(sp->status))) {
 					VTRACE(DBG_JOBS | DBG_PROCS,
 			("Job %d: changing status of proc %d from %#x to %#x\n",
 						jp - jobtab + 1, pid,
 						sp->status, status));
+
+					/*
+					 * If the process continued,
+					 * then update its status to running
+					 * and mark the job running as well.
+					 *
+					 * If it was anything but running
+					 * before, flag it as a change for
+					 * reporting purposes later
+					 */
 					if (WIFCONTINUED(status)) {
 						if (sp->status != -1)
 							jp->flags |= JOBCHANGED;
 						sp->status = -1;
 						jp->state = 0;
-					} else
+					} else {
+						/* otherwise update status */
 						sp->status = status;
+					}
+
+					/*
+					 * We now know the affected job
+					 */
 					thisjob = jp;
 					if (changed != NULL)
 						*changed = jp;
 				}
+				/*
+				 * After any update that might have just
+				 * happened, if this process is running,
+				 * the job is not stopped, or if the process
+				 * simply stopped (not terminated) then the
+				 * job is certainly not completed (done).
+				 */
 				if (sp->status == -1)
 					stopped = 0;
 				else if (WIFSTOPPED(sp->status))
 					done = 0;
 			}
+
+			/*
+			 * Once we have examined all processes for the
+			 * job, if we still show it as stopped, then...
+			 */
 			if (stopped) {		/* stopped or done */
+				/*
+				 * it might be stopped, or finished, decide:
+				 */
 				int state = done ? JOBDONE : JOBSTOPPED;
 
+				/*
+				 * If that wasn't the same as it was before
+				 * then update its state, and if it just
+				 * completed, make it be the current job (%%)
+				 */
 				if (jp->state != state) {
 					VTRACE(DBG_JOBS,
 				("Job %d: changing state from %d to %d\n",
@@ -1388,6 +1491,15 @@ dowait(int flags, struct job *job, struc
 		}
 	}
 
+	/*
+	 * Now we have scanned all jobs.   If we found the job that
+	 * the process that changed state belonged to (we occasionally
+	 * fork processes without associating them with a job, when one
+	 * of those finishes, we simply ignore it, the zombie has been
+	 * cleaned up, which is all that matters) then we need to
+	 * determine if we should say something about it to stdout
+	 */
+
 	if (thisjob &&
 	    (thisjob->state != JOBRUNNING || thisjob->flags & JOBCHANGED)) {
 		int mode = 0;
@@ -1401,13 +1513,21 @@ dowait(int flags, struct job *job, struc
 			showjob(out2, thisjob, mode);
 		else {
 			VTRACE(DBG_JOBS,
-			    ("Not printing status, rootshell=%d, job=%p\n",
-			    rootshell, job));
+			    ("Not printing status for %p [%d], "
+			     "mode=%#x rootshell=%d, job=%p [%d]\n",
+			    thisjob, (thisjob ? (thisjob-jobtab+1) : 0),
+			    mode, rootshell, job, (job ? (job-jobtab+1) : 0)));
 			thisjob->flags |= JOBCHANGED;
 		}
 	}
 
 	INTON;
+	/*
+	 * Finally tell our caller that something happened (in general all
+	 * anyone tests for is <= 0 (or >0) so the actual pid value here
+	 * doesn't matter much, but we know it is >0 and we may as well
+	 * give back something meaningful
+	 */
 	return pid;
 }
 

Reply via email to