~emersion/mrsh

7b73bb8bd1ec4c201a25abb9368937d04744e4dd — Simon Ser 2 years ago a0b7bd3
job: init shell for job control

This includes putting the shell into its own process group, waiting for it to
be in the foreground, overriding the default signal handlers and grabbing
control of the terminal. We need to restore the default signal handlers in
children processes.
7 files changed, 124 insertions(+), 4 deletions(-)

M include/mrsh/shell.h
A include/shell/job.h
M main.c
M meson.build
A shell/job.c
M shell/shell.c
M shell/task/command_process.c
M include/mrsh/shell.h => include/mrsh/shell.h +7 -0
@@ 6,6 6,7 @@
#include <mrsh/hashtable.h>
#include <stdint.h>
#include <stdio.h>
#include <termios.h>

enum mrsh_option {
	// -a: When this option is on, the export attribute shall be set for each


@@ 81,6 82,7 @@ struct mrsh_call_frame {

struct mrsh_state {
	int exit;
	int fd;
	uint32_t options; // enum mrsh_option
	struct mrsh_call_frame *args;
	bool interactive;


@@ 88,6 90,10 @@ struct mrsh_state {
	struct mrsh_hashtable aliases; // char *
	struct mrsh_hashtable functions; // mrsh_function *
	int last_status;

	bool job_control;
	pid_t pgid;
	struct termios term_modes;
};

void mrsh_function_destroy(struct mrsh_function *fn);


@@ 108,5 114,6 @@ void mrsh_pop_args(struct mrsh_state *state);
int mrsh_run_program(struct mrsh_state *state, struct mrsh_program *prog);
int mrsh_run_word(struct mrsh_state *state, struct mrsh_word **word);
bool mrsh_run_arithm_expr(struct mrsh_arithm_expr *expr, long *result);
bool mrsh_set_job_control(struct mrsh_state *state, bool enabled);

#endif

A include/shell/job.h => include/shell/job.h +10 -0
@@ 0,0 1,10 @@
#ifndef SHELL_JOB_H
#define SHELL_JOB_H

#include <stdbool.h>

struct mrsh_state;

bool job_init_process(struct mrsh_state *state);

#endif

M main.c => main.c +10 -3
@@ 40,17 40,19 @@ int main(int argc, char *argv[]) {
		}
	}

	int fd = -1;
	state.fd = -1;
	struct mrsh_buffer parser_buffer = {0};
	struct mrsh_parser *parser;
	if (state.interactive) {
		interactive_init(&state);
		parser = mrsh_parser_with_buffer(&parser_buffer);
		state.fd = STDIN_FILENO;
	} else {
		if (init_args.command_str) {
			parser = mrsh_parser_with_data(init_args.command_str,
				strlen(init_args.command_str));
		} else {
			int fd;
			if (init_args.command_file) {
				fd = open(init_args.command_file, O_RDONLY | O_CLOEXEC);
				if (fd < 0) {


@@ 63,10 65,15 @@ int main(int argc, char *argv[]) {
			}

			parser = mrsh_parser_with_fd(fd);
			state.fd = fd;
		}
	}
	mrsh_state_set_parser_alias_func(&state, parser);

	if (state.interactive) {
		mrsh_set_job_control(&state, true);
	}

	struct mrsh_buffer read_buffer = {0};
	while (state.exit == -1) {
		if (state.interactive) {


@@ 137,8 144,8 @@ int main(int argc, char *argv[]) {
	mrsh_parser_destroy(parser);
	mrsh_buffer_finish(&parser_buffer);
	mrsh_state_finish(&state);
	if (fd >= 0) {
		close(fd);
	if (state.fd >= 0) {
		close(state.fd);
	}

	return state.exit;

M meson.build => meson.build +1 -0
@@ 102,6 102,7 @@ lib_mrsh = library(
		'parser/word.c',
		'shell/arithm.c',
		'shell/entry.c',
		'shell/job.c',
		'shell/path.c',
		'shell/process.c',
		'shell/redir.c',

A shell/job.c => shell/job.c +92 -0
@@ 0,0 1,92 @@
#define _POSIX_C_SOURCE 1
#include <assert.h>
#include <errno.h>
#include <mrsh/array.h>
#include <mrsh/shell.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "shell/job.h"

static const int ignored_signals[] = {
	SIGINT,
	SIGQUIT,
	SIGTSTP,
	SIGTTIN,
	SIGTTOU,
};

static const size_t IGNORED_SIGNALS_LEN =
	sizeof(ignored_signals) / sizeof(ignored_signals[0]);

bool mrsh_set_job_control(struct mrsh_state *state, bool enabled) {
	assert(state->fd >= 0);

	if (state->job_control == enabled) {
		return true;
	}

	if (enabled) {
		// Loop until we are in the foreground
		while (true) {
			pid_t pgid = getpgrp();
			if (tcgetpgrp(state->fd) == pgid) {
				break;
			}
			kill(-pgid, SIGTTIN);
		}

		// Ignore interactive and job-control signals
		struct sigaction sa = { .sa_handler = SIG_IGN };
		sigemptyset(&sa.sa_mask);
		for (size_t i = 0; i < IGNORED_SIGNALS_LEN; ++i) {
			if (sigaction(ignored_signals[i], &sa, NULL) != 0) {
				fprintf(stderr, "sigaction failed: %s\n", strerror(errno));
				return false;
			}
		}

		// Put ourselves in our own process group
		state->pgid = getpid();
		if (setpgid(state->pgid, state->pgid) != 0) {
			fprintf(stderr, "setpgid failed: %s\n", strerror(errno));
			return false;
		}

		// Grab control of the terminal
		if (tcsetpgrp(state->fd, state->pgid) != 0) {
			fprintf(stderr, "tcsetpgrp failed: %s\n", strerror(errno));
			return false;
		}
		// Save default terminal attributes for the shell
		if (tcgetattr(state->fd, &state->term_modes) != 0) {
			fprintf(stderr, "tcgetattr failed: %s\n", strerror(errno));
			return false;
		}
	} else {
		return false; // TODO
	}

	state->job_control = enabled;
	return true;
}

bool job_init_process(struct mrsh_state *state) {
	if (!state->job_control) {
		return true;
	}

	struct sigaction sa = { .sa_handler = SIG_DFL };
	sigemptyset(&sa.sa_mask);
	for (size_t i = 0; i < IGNORED_SIGNALS_LEN; ++i) {
		if (sigaction(ignored_signals[i], &sa, NULL) != 0) {
			fprintf(stderr, "sigaction failed: %s\n", strerror(errno));
			return false;
		}
	}

	return true;
}

M shell/shell.c => shell/shell.c +1 -0
@@ 18,6 18,7 @@ void mrsh_function_destroy(struct mrsh_function *fn) {

void mrsh_state_init(struct mrsh_state *state) {
	state->exit = -1;
	state->fd = -1;
	state->interactive = isatty(STDIN_FILENO);
	state->options = state->interactive ? MRSH_OPT_INTERACTIVE : 0;
	state->args = calloc(1, sizeof(struct mrsh_call_frame));

M shell/task/command_process.c => shell/task/command_process.c +3 -1
@@ 3,6 3,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "shell/job.h"
#include "shell/path.h"
#include "shell/redir.h"
#include "shell/task_command.h"


@@ 40,7 41,8 @@ static bool task_process_start(struct task_command *tc, struct context *ctx) {
		return false;
	} else if (pid == 0) {
		put_into_process_group(ctx, getpid());
		// TODO: give the process group the terminal, if foreground job
		job_init_process(ctx->state);
		// TODO: give the terminal to the process group, if foreground job

		for (size_t i = 0; i < sc->assignments.len; ++i) {
			struct mrsh_assignment *assign = sc->assignments.data[i];