56b4bce65c69576885d919e74cd3e954381d07c5 — Drew DeVault 18 days ago 7d1fc69
(Partially) implement job control IDs
5 files changed, 112 insertions(+), 38 deletions(-)

M builtin/bg.c
M builtin/fg.c
M builtin/wait.c
M include/shell/job.h
M shell/job.c
M builtin/bg.c => builtin/bg.c +17 -19
@@ 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 @@ 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;
  }

M builtin/fg.c => builtin/fg.c +11 -16
@@ 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 @@ 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);
  }

M builtin/wait.c => builtin/wait.c +6 -3
@@ 41,9 41,12 @@ } 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);

M include/shell/job.h => include/shell/job.h +5 -0
@@ 22,6 22,7 @@ */
  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 @@ * 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

M shell/job.c => shell/job.c +73 -0
@@ 83,9 83,18 @@ }
  
  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,67 @@ }
  	}
  }
+ 
+ struct mrsh_job *job_by_id(struct mrsh_state *state, const char *id) {
+ 	if (id[0] != '%' || id[1] == '\0') {
+ 		fprintf(stderr, "Invalid job ID specifier\n");
+ 		return NULL;
+ 	}
+ 
+ 	if (id[2] == '\0') {
+ 		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] != '\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;
+ }