~emersion/mrsh-dev

jobs(1): complete implementation v1 PROPOSED

I had to make some changes to an earlier patch, so this superceedes them
and includes the completed command.

Drew DeVault (4):
  jobs(1): implement <current> marker
  jobs(1): implement -l, -p flags
  jobs(1): job_state_str: add parenthesized info
  jobs(1): randomly print "Stopped" or "Suspended"

 builtin/bg.c            |  4 +--
 builtin/fg.c            |  4 +--
 builtin/jobs.c          | 73 ++++++++++++++++++++++++++++++++++++-----
 builtin/wait.c          |  2 +-
 include/shell/job.h     |  6 +++-
 include/shell/process.h |  1 +
 shell/job.c             | 26 +++++++++++----
 shell/process.c         |  1 +
 8 files changed, 96 insertions(+), 21 deletions(-)

-- 
2.23.0
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~emersion/mrsh-dev/%3C20190831011709.15287-1-sir%40cmpwn.com%3E/mbox | git am -3
Learn more about email & git

[PATCH 1/4] jobs(1): implement <current> marker Export this patch

---
 builtin/bg.c        |  4 ++--
 builtin/fg.c        |  4 ++--
 builtin/jobs.c      |  7 +++++--
 builtin/wait.c      |  2 +-
 include/shell/job.h |  6 +++++-
 shell/job.c         | 26 ++++++++++++++++++++------
 6 files changed, 35 insertions(+), 14 deletions(-)

diff --git a/builtin/bg.c b/builtin/bg.c
index b0c2bee..f61fed0 100644
--- a/builtin/bg.c
@@ -20,7 +20,7 @@ int builtin_bg(struct mrsh_state *state, int argc, char *argv[]) {
 		}
 	}
 	if (mrsh_optind == argc) {
-		struct mrsh_job *job = job_by_id(state, "%%");
+		struct mrsh_job *job = job_by_id(state, "%%", true);
 		if (!job) {
 			return EXIT_FAILURE;
 		}
@@ -31,7 +31,7 @@ int builtin_bg(struct mrsh_state *state, int argc, char *argv[]) {
 	}
 
 	for (int i = mrsh_optind; i < argc; ++i) {
-		struct mrsh_job *job = job_by_id(state, argv[i]);
+		struct mrsh_job *job = job_by_id(state, argv[i], true);
 		if (!job) {
 			return EXIT_FAILURE;
 		}
diff --git a/builtin/fg.c b/builtin/fg.c
index b6e3f18..3bc5992 100644
--- a/builtin/fg.c
@@ -21,9 +21,9 @@ int builtin_fg(struct mrsh_state *state, int argc, char *argv[]) {
 
 	struct mrsh_job *job;
 	if (mrsh_optind == argc) {
-		job = job_by_id(state, "%%");
+		job = job_by_id(state, "%%", true);
 	} else if (mrsh_optind == argc - 1) {
-		job = job_by_id(state, argv[mrsh_optind]);
+		job = job_by_id(state, argv[mrsh_optind], true);
 	} else {
 		fprintf(stderr, fg_usage);
 		return EXIT_FAILURE;
diff --git a/builtin/jobs.c b/builtin/jobs.c
index f6be2f8..17dc6c2 100644
--- a/builtin/jobs.c
@@ -38,15 +38,18 @@ int builtin_jobs(struct mrsh_state *state, int argc, char *argv[]) {
 		}
 	}
 
+	struct mrsh_job *current = job_by_id(state, "%+", false);
+
 	for (size_t i = 0; i < state->jobs.len; i++) {
 		struct mrsh_job *job = state->jobs.data[i];
 		if (job_poll(job) >= 0) {
 			continue;
 		}
-		// TODO: <current>
 		char *cmd = mrsh_node_format(job->node);
-		printf("[%d] %c %s %s\n", job->job_id, ' ', job_state_str(job), cmd);
+		printf("[%d] %c %s %s\n", job->job_id, job == current ? '+' : ' ',
+				job_state_str(job), cmd);
 		free(cmd);
 	}
+
 	return EXIT_SUCCESS;
 }
diff --git a/builtin/wait.c b/builtin/wait.c
index d6c005f..7f793a7 100644
--- a/builtin/wait.c
@@ -41,7 +41,7 @@ int builtin_wait(struct mrsh_state *state, int argc, char *argv[]) {
 	} else {
 		for (int i = 1; i < argc; ++i) {
 			if (argv[i][0] == '%') {
-				struct mrsh_job *job = job_by_id(state, argv[i]);
+				struct mrsh_job *job = job_by_id(state, argv[i], true);
 				if (!job) {
 					goto failure;
 				}
diff --git a/include/shell/job.h b/include/shell/job.h
index 59e2efc..390cbb6 100644
--- a/include/shell/job.h
+++ b/include/shell/job.h
@@ -82,7 +82,11 @@ bool init_job_child_process(struct mrsh_state *state);
 void update_job(struct mrsh_state *state, pid_t pid, int stat);
 /**
  * Look up a job by its XBD Job Control Job ID.
+ *
+ * When using this to look up jobs internally, set interactive to false. This
+ * suppresses error reporting.
  */
-struct mrsh_job *job_by_id(struct mrsh_state *state, const char *id);
+struct mrsh_job *job_by_id(struct mrsh_state *state,
+		const char *id, bool interactive);
 
 #endif
diff --git a/shell/job.c b/shell/job.c
index 3156f5b..f75d71c 100644
--- a/shell/job.c
+++ b/shell/job.c
@@ -298,9 +298,12 @@ void update_job(struct mrsh_state *state, pid_t pid, int stat) {
 }
 
 // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_204
-struct mrsh_job *job_by_id(struct mrsh_state *state, const char *id) {
+struct mrsh_job *job_by_id(struct mrsh_state *state,
+		const char *id, bool interactive) {
 	if (id[0] != '%' || id[1] == '\0') {
-		fprintf(stderr, "Invalid job ID specifier\n");
+		if (interactive) {
+			fprintf(stderr, "Invalid job ID specifier\n");
+		}
 		return NULL;
 	}
 
@@ -321,7 +324,9 @@ struct mrsh_job *job_by_id(struct mrsh_state *state, const char *id) {
 					return job;
 				}
 			}
-			fprintf(stderr, "No current job\n");
+			if (interactive) {
+				fprintf(stderr, "No current job\n");
+			}
 			return NULL;
 		case '-':
 			// Previous job
@@ -344,7 +349,9 @@ struct mrsh_job *job_by_id(struct mrsh_state *state, const char *id) {
 					return job;
 				}
 			}
-			fprintf(stderr, "No previous job\n");
+			if (interactive) {
+				fprintf(stderr, "No previous job\n");
+			}
 			return NULL;
 		}
 	}
@@ -353,7 +360,9 @@ struct mrsh_job *job_by_id(struct mrsh_state *state, const char *id) {
 		char *endptr;
 		int n = strtol(&id[1], &endptr, 10);
 		if (endptr[0] != '\0') {
-			fprintf(stderr, "Invalid job number '%s'\n", id);
+			if (interactive) {
+				fprintf(stderr, "Invalid job number '%s'\n", id);
+			}
 			return NULL;
 		}
 		for (size_t i = 0; i < state->jobs.len; ++i) {
@@ -362,7 +371,9 @@ struct mrsh_job *job_by_id(struct mrsh_state *state, const char *id) {
 				return job;
 			}
 		}
-		fprintf(stderr, "No such job '%s' (%d)\n", id, n);
+		if (interactive) {
+			fprintf(stderr, "No such job '%s' (%d)\n", id, n);
+		}
 		return NULL;
 	}
 
@@ -384,5 +395,8 @@ struct mrsh_job *job_by_id(struct mrsh_state *state, const char *id) {
 		}
 	}
 
+	if (interactive) {
+		fprintf(stderr, "No such job '%s'\n", id);
+	}
 	return NULL;
 }
-- 
2.23.0
This is missing "-" for the previous job, but can be added later.

[PATCH 2/4] jobs(1): implement -l, -p flags Export this patch

---
 builtin/jobs.c | 44 ++++++++++++++++++++++++++++++++++++++------
 1 file changed, 38 insertions(+), 6 deletions(-)

diff --git a/builtin/jobs.c b/builtin/jobs.c
index 17dc6c2..f86606c 100644
--- a/builtin/jobs.c
@@ -1,13 +1,14 @@
 #include <assert.h>
+#include <limits.h>
 #include <mrsh/getopt.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include "builtin.h"
 #include "shell/job.h"
+#include "shell/process.h"
 #include "shell/shell.h"
 #include "shell/task.h"
 
-// TODO: [-l|-p] [job_id...]
 static const char jobs_usage[] = "usage: jobs\n";
 
 static char *job_state_str(struct mrsh_job *job) {
@@ -27,10 +28,28 @@ static char *job_state_str(struct mrsh_job *job) {
 }
 
 int builtin_jobs(struct mrsh_state *state, int argc, char *argv[]) {
+	bool pids = false, pgids = false;
+
 	mrsh_optind = 0;
 	int opt;
-	while ((opt = mrsh_getopt(argc, argv, ":")) != -1) {
+	while ((opt = mrsh_getopt(argc, argv, ":lp")) != -1) {
 		switch (opt) {
+		case 'l':
+			if (pids) {
+				fprintf(stderr, "jobs: the -p and -l options are "
+						"mutually exclusive\n");
+				return EXIT_FAILURE;
+			}
+			pgids = true;
+			break;
+		case 'p':
+			if (pgids) {
+				fprintf(stderr, "jobs: the -p and -l options are "
+						"mutually exclusive\n");
+				return EXIT_FAILURE;
+			}
+			pids = true;
+			break;
 		default:
 			fprintf(stderr, "jobs: unknown option -- %c\n", mrsh_optopt);
 			fprintf(stderr, jobs_usage);
@@ -45,10 +64,23 @@ int builtin_jobs(struct mrsh_state *state, int argc, char *argv[]) {
 		if (job_poll(job) >= 0) {
 			continue;
 		}
-		char *cmd = mrsh_node_format(job->node);
-		printf("[%d] %c %s %s\n", job->job_id, job == current ? '+' : ' ',
-				job_state_str(job), cmd);
-		free(cmd);
+		if (pids) {
+			for (size_t j = 0; j < job->processes.len; ++j) {
+				struct process *proc = job->processes.data[j];
+				printf("%d\n", proc->pid);
+			}
+		} else if (pgids) {
+			char *cmd = mrsh_node_format(job->node);
+			printf("[%d] %c %d %s %s\n", job->job_id,
+					job == current ? '+' : ' ', job->pgid,
+					job_state_str(job), cmd);
+			free(cmd);
+		} else {
+			char *cmd = mrsh_node_format(job->node);
+			printf("[%d] %c %s %s\n", job->job_id, job == current ? '+' : ' ',
+					job_state_str(job), cmd);
+			free(cmd);
+		}
 	}
 
 	return EXIT_SUCCESS;
-- 
2.23.0

[PATCH 3/4] jobs(1): job_state_str: add parenthesized info Export this patch

This completes the jobs(1) implementation.
---
 builtin/jobs.c          | 21 ++++++++++++++++++++-
 include/shell/process.h |  1 +
 shell/process.c         |  1 +
 3 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/builtin/jobs.c b/builtin/jobs.c
index f86606c..420c39f 100644
--- a/builtin/jobs.c
@@ -3,6 +3,7 @@
 #include <mrsh/getopt.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <signal.h>
 #include "builtin.h"
 #include "shell/job.h"
 #include "shell/process.h"
@@ -13,15 +14,33 @@ static const char jobs_usage[] = "usage: jobs\n";
 
 static char *job_state_str(struct mrsh_job *job) {
 	int status = job_poll(job);
-	// TODO code in parentheses for Done and Stopped
 	switch (status) {
 	case TASK_STATUS_WAIT:
 		return "Running";
 	case TASK_STATUS_ERROR:
 		return "Error";
 	case TASK_STATUS_STOPPED:
+		if (job->processes.len > 0) {
+			struct process *proc = job->processes.data[0];
+			switch (proc->signal) {
+			case SIGSTOP:
+				return "Stopped (SIGSTOP)";
+			case SIGTTIN:
+				return "Stopped (SIGTTIN)";
+			case SIGTTOU:
+				return "Stopped (SIGTTOU)";
+			}
+		}
 		return "Stopped";
 	default:
+		if (job->processes.len > 0) {
+			struct process *proc = job->processes.data[0];
+			if (proc->stat != 0) {
+				static char stat[128];
+				snprintf(stat, sizeof(stat), "Done(%d)", proc->stat);
+				return stat;
+			}
+		}
 		assert(status >= 0);
 		return "Done";
 	}
diff --git a/include/shell/process.h b/include/shell/process.h
index dbde82a..4fe5453 100644
--- a/include/shell/process.h
+++ b/include/shell/process.h
@@ -18,6 +18,7 @@ struct process {
 	bool stopped;
 	bool terminated;
 	int stat; // only valid if terminated
+	int signal; // only valid if stopped is true
 };
 
 /**
diff --git a/shell/process.c b/shell/process.c
index 9d6e913..04bd3be 100644
--- a/shell/process.c
+++ b/shell/process.c
@@ -68,6 +68,7 @@ void update_process(struct mrsh_state *state, pid_t pid, int stat) {
 		proc->stat = stat;
 	} else if (WIFSTOPPED(stat)) {
 		proc->stopped = true;
+		proc->signal = WSTOPSIG(stat);
 	} else {
 		assert(false);
 	}
-- 
2.23.0

[PATCH 4/4] jobs(1): randomly print "Stopped" or "Suspended" Export this patch

The string used is unspecified between these two options, so this
prevents a particular one from being relied upon.
---
 builtin/jobs.c | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/builtin/jobs.c b/builtin/jobs.c
index 420c39f..f083f00 100644
--- a/builtin/jobs.c
@@ -12,7 +12,7 @@
 
 static const char jobs_usage[] = "usage: jobs\n";
 
-static char *job_state_str(struct mrsh_job *job) {
+static char *job_state_str(struct mrsh_job *job, bool r) {
 	int status = job_poll(job);
 	switch (status) {
 	case TASK_STATUS_WAIT:
@@ -24,14 +24,14 @@ static char *job_state_str(struct mrsh_job *job) {
 			struct process *proc = job->processes.data[0];
 			switch (proc->signal) {
 			case SIGSTOP:
-				return "Stopped (SIGSTOP)";
+				return r ? "Stopped (SIGSTOP)" : "Suspended (SIGSTOP)";
 			case SIGTTIN:
-				return "Stopped (SIGTTIN)";
+				return r ? "Stopped (SIGTTIN)" : "Suspended (SIGTTIN)";
 			case SIGTTOU:
-				return "Stopped (SIGTTOU)";
+				return r ? "Stopped (SIGTTOU)" : "Suspended (SIGTTOU)";
 			}
 		}
-		return "Stopped";
+		return r ? "Stopped" : "Suspended";
 	default:
 		if (job->processes.len > 0) {
 			struct process *proc = job->processes.data[0];
@@ -77,6 +77,7 @@ int builtin_jobs(struct mrsh_state *state, int argc, char *argv[]) {
 	}
 
 	struct mrsh_job *current = job_by_id(state, "%+", false);
+	int r = rand() % 2 == 0;
 
 	for (size_t i = 0; i < state->jobs.len; i++) {
 		struct mrsh_job *job = state->jobs.data[i];
@@ -92,12 +93,12 @@ int builtin_jobs(struct mrsh_state *state, int argc, char *argv[]) {
 			char *cmd = mrsh_node_format(job->node);
 			printf("[%d] %c %d %s %s\n", job->job_id,
 					job == current ? '+' : ' ', job->pgid,
-					job_state_str(job), cmd);
+					job_state_str(job, r), cmd);
 			free(cmd);
 		} else {
 			char *cmd = mrsh_node_format(job->node);
 			printf("[%d] %c %s %s\n", job->job_id, job == current ? '+' : ' ',
-					job_state_str(job), cmd);
+					job_state_str(job, r), cmd);
 			free(cmd);
 		}
 	}
-- 
2.23.0
This series LGTM.

To git.sr.ht:~emersion/mrsh
   57e24f4c4561..64d1985175c1  master -> master

Thanks!
View this thread in the archives