~emersion/mrsh

507244cb6a98882a34b12191d722cdefa701f12a — Simon Ser 8 months ago 2ddc157
Print background job status notifications

Closes: https://github.com/emersion/mrsh/issues/108
4 files changed, 90 insertions(+), 36 deletions(-)

M builtin/jobs.c
M include/shell/job.h
M shell/job.c
M shell/task/task.c
M builtin/jobs.c => builtin/jobs.c +1 -35
@@ 12,45 12,11 @@

static const char jobs_usage[] = "usage: jobs [-l|-p] [job_id...]\n";

static char *job_state_str(struct mrsh_job *job, bool r) {
	int status = job_poll(job);
	switch (status) {
	case TASK_STATUS_WAIT:
		return "Running";
	case TASK_STATUS_ERROR:
		return "Error";
	case TASK_STATUS_STOPPED:
		if (job->processes.len > 0) {
			struct mrsh_process *proc = job->processes.data[0];
			switch (proc->signal) {
			case SIGSTOP:
				return r ? "Stopped (SIGSTOP)" : "Suspended (SIGSTOP)";
			case SIGTTIN:
				return r ? "Stopped (SIGTTIN)" : "Suspended (SIGTTIN)";
			case SIGTTOU:
				return r ? "Stopped (SIGTTOU)" : "Suspended (SIGTTOU)";
			}
		}
		return r ? "Stopped" : "Suspended";
	default:
		if (job->processes.len > 0) {
			struct mrsh_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";
	}
}

struct jobs_context {
	struct mrsh_job *current, *previous;
	bool pids;
	bool pgids;
	int r;
	bool r;
};

static void show_job(struct mrsh_job *job, const struct jobs_context *ctx) {

M include/shell/job.h => include/shell/job.h +7 -0
@@ 28,6 28,9 @@ struct mrsh_job {
	struct termios term_modes; // only valid if stopped
	struct mrsh_state *state;
	struct mrsh_array processes; // struct mrsh_process *

	bool pending_notification; // need to print a job status notification
	int last_status;
};

/**


@@ 88,5 91,9 @@ 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, bool interactive);
/**
 * Return a string describing the process' state. `r` is a random boolean.
 */
const char *job_state_str(struct mrsh_job *job, bool r);

#endif

M shell/job.c => shell/job.c +53 -0
@@ 104,6 104,7 @@ struct mrsh_job *job_create(struct mrsh_state *state,
	job->node = mrsh_node_copy(node);
	job->pgid = -1;
	job->job_id = id;
	job->last_status = TASK_STATUS_WAIT;
	mrsh_array_add(&priv->jobs, job);
	return job;
}


@@ 146,6 147,20 @@ void job_add_process(struct mrsh_job *job, struct mrsh_process *proc) {
	mrsh_array_add(&job->processes, proc);
}

static void queue_job_notifications(struct mrsh_state *state) {
	struct mrsh_state_priv *priv = state_get_priv(state);

	for (size_t i = 0; i < priv->jobs.len; ++i) {
		struct mrsh_job *job = priv->jobs.data[i];
		int status = job_poll(job);
		if (status != job->last_status && job->pgid > 0 &&
				priv->foreground_job != job) {
			job->pending_notification = true;
		}
		job->last_status = status;
	}
}

bool job_set_foreground(struct mrsh_job *job, bool foreground, bool cont) {
	struct mrsh_state *state = job->state;
	struct mrsh_state_priv *priv = state_get_priv(state);


@@ 191,6 206,8 @@ bool job_set_foreground(struct mrsh_job *job, bool foreground, bool cont) {
		}
	}

	queue_job_notifications(state);

	return true;
}



@@ 313,6 330,8 @@ void update_job(struct mrsh_state *state, pid_t pid, int stat) {
			}
		}
	}

	queue_job_notifications(state);
}

// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_204


@@ 420,3 439,37 @@ struct mrsh_job *job_by_id(struct mrsh_state *state,
	}
	return NULL;
}

const char *job_state_str(struct mrsh_job *job, bool r) {
	int status = job_poll(job);
	switch (status) {
	case TASK_STATUS_WAIT:
		return "Running";
	case TASK_STATUS_ERROR:
		return "Error";
	case TASK_STATUS_STOPPED:
		if (job->processes.len > 0) {
			struct mrsh_process *proc = job->processes.data[0];
			switch (proc->signal) {
			case SIGSTOP:
				return r ? "Stopped (SIGSTOP)" : "Suspended (SIGSTOP)";
			case SIGTTIN:
				return r ? "Stopped (SIGTTIN)" : "Suspended (SIGTTIN)";
			case SIGTTOU:
				return r ? "Stopped (SIGTTOU)" : "Suspended (SIGTTOU)";
			}
		}
		return r ? "Stopped" : "Suspended";
	default:
		if (job->processes.len > 0) {
			struct mrsh_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";
	}
}

M shell/task/task.c => shell/task/task.c +29 -1
@@ 382,16 382,44 @@ int run_command_list_array(struct mrsh_context *ctx, struct mrsh_array *array) {
	return ret;
}

static void show_job(struct mrsh_job *job, struct mrsh_job *current,
		struct mrsh_job *previous, bool r) {
	char curprev = ' ';
	if (job == current) {
		curprev = '+';
	} else if (job == previous) {
		curprev = '-';
	}
	char *cmd = mrsh_node_format(job->node);
	fprintf(stderr, "[%d] %c %s %s\n", job->job_id, curprev,
		job_state_str(job, r), cmd);
	free(cmd);
}

static void destroy_terminated_jobs(struct mrsh_state *state) {
	struct mrsh_state_priv *priv = state_get_priv(state);

	struct mrsh_job *current = job_by_id(state, "%+", false),
		*previous = job_by_id(state, "%-", false);
	bool r = rand() % 2 == 0;

	for (size_t i = 0; i < priv->jobs.len; ++i) {
		struct mrsh_job *job = priv->jobs.data[i];
		if (job_poll(job) >= 0) {

		int status = job_poll(job);

		if (state->options & MRSH_OPT_NOTIFY && job->pending_notification) {
			show_job(job, current, previous, r);
			job->pending_notification = false;
		}

		if (status >= 0) {
			job_destroy(job);
			--i;
		}
	}

	fflush(stderr);
}

int mrsh_run_program(struct mrsh_state *state, struct mrsh_program *prog) {