~emersion/mrsh-dev

(Partially) implement job control IDs v1 PROPOSED

Drew DeVault: 1
 (Partially) implement job control IDs

 5 files changed, 111 insertions(+), 38 deletions(-)
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/patches/6051/mbox | git am -3
Learn more about email & git

[PATCH] (Partially) implement job control IDs Export this patch

---
 builtin/bg.c        | 36 +++++++++++------------
 builtin/fg.c        | 27 +++++++----------
 builtin/wait.c      |  9 ++++--
 include/shell/job.h |  5 ++++
 shell/job.c         | 72 +++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 111 insertions(+), 38 deletions(-)

diff --git a/builtin/bg.c b/builtin/bg.c
index 7986bd6..b0c2bee 100644
--- a/builtin/bg.c
@@ -6,8 +6,7 @@
 #include "shell/shell.h"
 #include "shell/task.h"
 
-// TODO: bg [job_id]
-static const char bg_usage[] = "usage: bg\n";
+static const char bg_usage[] = "usage: bg [job_id...]\n";
 
 int builtin_bg(struct mrsh_state *state, int argc, char *argv[]) {
 	mrsh_optind = 0;
@@ -20,26 +19,25 @@ int builtin_bg(struct mrsh_state *state, int argc, char *argv[]) {
 			return EXIT_FAILURE;
 		}
 	}
-	if (mrsh_optind < argc) {
-		fprintf(stderr, bg_usage);
-		return EXIT_FAILURE;
-	}
-
-	struct mrsh_job *stopped = NULL;
-	for (ssize_t i = state->jobs.len - 1; i >= 0; --i) {
-		struct mrsh_job *job = state->jobs.data[i];
-		if (job_poll(job) == TASK_STATUS_STOPPED) {
-			stopped = job;
-			break;
+	if (mrsh_optind == argc) {
+		struct mrsh_job *job = job_by_id(state, "%%");
+		if (!job) {
+			return EXIT_FAILURE;
 		}
-	}
-	if (stopped == NULL) {
-		fprintf(stderr, "bg: no current job");
-		return EXIT_FAILURE;
+		if (!job_set_foreground(job, false, true)) {
+			return EXIT_FAILURE;
+		}
+		return EXIT_SUCCESS;
 	}
 
-	if (!job_set_foreground(stopped, false, true)) {
-		return EXIT_FAILURE;
+	for (int i = mrsh_optind; i < argc; ++i) {
+		struct mrsh_job *job = job_by_id(state, argv[i]);
+		if (!job) {
+			return EXIT_FAILURE;
+		}
+		if (!job_set_foreground(job, false, true)) {
+			return EXIT_FAILURE;
+		}
 	}
 	return EXIT_SUCCESS;
 }
diff --git a/builtin/fg.c b/builtin/fg.c
index 365a67d..b6e3f18 100644
--- a/builtin/fg.c
@@ -5,8 +5,7 @@
 #include "shell/job.h"
 #include "shell/shell.h"
 
-// TODO: fg [job_id]
-static const char fg_usage[] = "usage: fg\n";
+static const char fg_usage[] = "usage: fg [job_id]\n";
 
 int builtin_fg(struct mrsh_state *state, int argc, char *argv[]) {
 	mrsh_optind = 0;
@@ -19,26 +18,22 @@ int builtin_fg(struct mrsh_state *state, int argc, char *argv[]) {
 			return EXIT_FAILURE;
 		}
 	}
-	if (mrsh_optind < argc) {
+
+	struct mrsh_job *job;
+	if (mrsh_optind == argc) {
+		job = job_by_id(state, "%%");
+	} else if (mrsh_optind == argc - 1) {
+		job = job_by_id(state, argv[mrsh_optind]);
+	} else {
 		fprintf(stderr, fg_usage);
 		return EXIT_FAILURE;
 	}
-
-	struct mrsh_job *stopped = NULL;
-	for (ssize_t i = state->jobs.len - 1; i >= 0; --i) {
-		struct mrsh_job *job = state->jobs.data[i];
-		if (job != state->foreground_job) {
-			stopped = job;
-			break;
-		}
-	}
-	if (stopped == NULL) {
-		fprintf(stderr, "fg: no current job");
+	if (!job) {
 		return EXIT_FAILURE;
 	}
 
-	if (!job_set_foreground(stopped, true, true)) {
+	if (!job_set_foreground(job, true, true)) {
 		return EXIT_FAILURE;
 	}
-	return job_wait(stopped);
+	return job_wait(job);
 }
diff --git a/builtin/wait.c b/builtin/wait.c
index 098ae92..66ad7b5 100644
--- a/builtin/wait.c
@@ -41,9 +41,12 @@ int builtin_wait(struct mrsh_state *state, int argc, char *argv[]) {
 	} else {
 		for (int i = 1; i < argc; ++i) {
 			if (argv[i][0] == '%') {
-				// TODO
-				fprintf(stderr, "wait: job control IDs are unimplemented\n");
-				goto failure;
+				struct mrsh_job *job = job_by_id(state, argv[i]);
+				if (!job) {
+					goto failure;
+				}
+				pids[i - 1].pid = job->pgid;
+				pids[i - 1].status = -1;
 			} else {
 				char *endptr;
 				pid_t pid = (pid_t)strtol(argv[i], &endptr, 10);
diff --git a/include/shell/job.h b/include/shell/job.h
index 83f4128..f7e8a37 100644
--- a/include/shell/job.h
+++ b/include/shell/job.h
@@ -22,6 +22,7 @@ struct process;
  */
 struct mrsh_job {
 	pid_t pgid;
+	int job_id;
 	struct termios term_modes; // only valid if stopped
 	struct mrsh_state *state;
 	struct mrsh_array processes; // struct process *
@@ -64,5 +65,9 @@ bool init_job_child_process(struct mrsh_state *state);
  * Update the shell's state with a child process status.
  */
 void update_job(struct mrsh_state *state, pid_t pid, int stat);
+/**
+ * Look up a job by its XBD Job Control Job ID.
+ */
+struct mrsh_job *job_by_id(struct mrsh_state *state, const char *id);
 
 #endif
diff --git a/shell/job.c b/shell/job.c
index 4f54a69..ae9bcd0 100644
--- a/shell/job.c
+++ b/shell/job.c
@@ -83,9 +83,18 @@ static void array_remove(struct mrsh_array *array, size_t i) {
 }
 
 struct mrsh_job *job_create(struct mrsh_state *state, pid_t pgid) {
+	int id = 1;
+	for (size_t i = 0; i < state->jobs.len; ++i) {
+		struct mrsh_job *job = state->jobs.data[i];
+		if (id < job->job_id) {
+			id = job->job_id + 1;
+		}
+	}
+
 	struct mrsh_job *job = calloc(1, sizeof(struct mrsh_job));
 	job->state = state;
 	job->pgid = pgid;
+	job->job_id = id;
 	mrsh_array_add(&state->jobs, job);
 	return job;
 }
@@ -233,3 +242,66 @@ void update_job(struct mrsh_state *state, pid_t pid, int stat) {
 		}
 	}
 }
+
+struct mrsh_job *job_by_id(struct mrsh_state *state, const char *id) {
+	assert(id[0] == '%');
+	if (id[1] == '\0') {
+		fprintf(stderr, "Invalid job ID specifier\n");
+		return NULL;
+	}
+
+	switch (id[1]) {
+	case '%':
+	case '+':
+		for (ssize_t i = state->jobs.len - 1; i >= 0; --i) {
+			struct mrsh_job *job = state->jobs.data[i];
+			if (job_poll(job) == TASK_STATUS_STOPPED) {
+				return job;
+			}
+		}
+		if (state->jobs.len < 1) {
+			fprintf(stderr, "No current job\n");
+		}
+		return state->jobs.data[state->jobs.len - 1];
+	case '-':
+		for (ssize_t i = state->jobs.len - 1, n = 0; i >= 0; --i) {
+			struct mrsh_job *job = state->jobs.data[i];
+			if (job_poll(job) == TASK_STATUS_STOPPED) {
+				if (++n == 2) {
+					return job;
+				}
+			}
+		}
+		if (state->jobs.len < 2) {
+			fprintf(stderr, "No previous job\n");
+			return NULL;
+		}
+		return state->jobs.data[state->jobs.len - 2];
+	}
+
+	if (id[1] >= '0' && id[1] <= '9') {
+		char *endptr;
+		int n = strtol(&id[1], &endptr, 10);
+		if (*endptr != '\0') {
+			fprintf(stderr, "Invalid job number '%s'\n", id);
+			return NULL;
+		}
+		for (size_t i = 0; i < state->jobs.len; ++i) {
+			struct mrsh_job *job = state->jobs.data[i];
+			if (job->job_id == n) {
+				return job;
+			}
+		}
+		fprintf(stderr, "No such job '%s' (%d)\n", id, n);
+		return NULL;
+	}
+
+	if (id[1] == '?') {
+		// TODO
+		fprintf(stderr, "Job lookup by command string is unimplemented\n");
+		return NULL;
+	}
+
+	fprintf(stderr, "Job lookup by command string is unimplemented\n");
+	return NULL;
+}
-- 
2.21.0
That's a cool patch! Applied with two minor edits, see below.

To git.sr.ht:~emersion/mrsh
   7d1fc69ce60f..56b4bce65c69  master -> master
View this thread in the archives