~emersion/mrsh

12e0d0c640eb5fedfe68ceb9c8b1704f487d47a4 — Simon Ser 1 year, 9 months ago e4dbd3b
shell/trap: reset traps in subshells

> When a subshell is entered, traps that are not being ignored shall be set to
> the default actions, except in the case of a command substitution containing
> only a single trap command, when the traps need not be altered.

References: https://github.com/emersion/mrsh/issues/137
4 files changed, 70 insertions(+), 0 deletions(-)

M include/shell/trap.h
M shell/task/task.c
M shell/task/word.c
M shell/trap.c
M include/shell/trap.h => include/shell/trap.h +1 -0
@@ 18,6 18,7 @@ enum mrsh_trap_action {
bool set_trap(struct mrsh_state *state, int sig, enum mrsh_trap_action action,
	struct mrsh_program *program);
bool set_job_control_traps(struct mrsh_state *state);
bool reset_caught_traps(struct mrsh_state *state);
bool run_pending_traps(struct mrsh_state *state);
bool run_exit_trap(struct mrsh_state *state);


M shell/task/task.c => shell/task/task.c +2 -0
@@ 22,6 22,8 @@ static int run_subshell(struct mrsh_context *ctx, struct mrsh_array *array) {
	} else if (pid == 0) {
		priv->child = true;

		reset_caught_traps(ctx->state);

		if (!(ctx->state->options & MRSH_OPT_MONITOR)) {
			// If job control is disabled, stdin is /dev/null
			int fd = open("/dev/null", O_CLOEXEC | O_RDONLY);

M shell/task/word.c => shell/task/word.c +52 -0
@@ 36,6 36,50 @@ static bool buffer_read_from(struct mrsh_buffer *buf, int fd) {
	return true;
}

static bool naive_word_streq(struct mrsh_word *word, const char *str) {
	while (word->type == MRSH_WORD_LIST) {
		struct mrsh_word_list *wl = mrsh_word_get_list(word);
		if (wl->children.len != 1) {
			return false;
		}
		word = wl->children.data[0];
	}
	if (word->type != MRSH_WORD_STRING) {
		return false;
	}
	struct mrsh_word_string *ws = mrsh_word_get_string(word);
	return strcmp(ws->str, "trap") == 0;
}

static bool is_print_traps(struct mrsh_program *program) {
	if (program->body.len != 1) {
		return false;
	}
	struct mrsh_command_list *cl = program->body.data[0];
	if (cl->ampersand || cl->and_or_list->type != MRSH_AND_OR_LIST_PIPELINE) {
		return false;
	}
	struct mrsh_pipeline *pipeline =
		mrsh_and_or_list_get_pipeline(cl->and_or_list);
	if (pipeline->bang || pipeline->commands.len != 1) {
		return false;
	}
	struct mrsh_command *cmd = pipeline->commands.data[0];
	if (cmd->type != MRSH_SIMPLE_COMMAND) {
		return false;
	}
	struct mrsh_simple_command *sc = mrsh_command_get_simple_command(cmd);
	if (sc->name == NULL || !naive_word_streq(sc->name, "trap")) {
		return false;
	}
	if (sc->arguments.len == 1) {
		struct mrsh_word *arg = sc->arguments.data[0];
		return naive_word_streq(arg, "--");
	} else {
		return sc->arguments.len == 0;
	}
}

static void swap_words(struct mrsh_word **word_ptr, struct mrsh_word *new_word) {
	mrsh_word_destroy(*word_ptr);
	*word_ptr = new_word;


@@ 62,6 106,14 @@ static int run_word_command(struct mrsh_context *ctx, struct mrsh_word **word_pt
		dup2(fds[1], STDOUT_FILENO);
		close(fds[1]);

		// When a subshell is entered, traps that are not being ignored shall
		// be set to the default actions, except in the case of a command
		// substitution containing only a single trap command, when the traps
		// need not be altered.
		if (!wc->program || !is_print_traps(wc->program)) {
			reset_caught_traps(ctx->state);
		}

		if (wc->program != NULL) {
			mrsh_run_program(ctx->state, wc->program);
		}

M shell/trap.c => shell/trap.c +15 -0
@@ 94,6 94,21 @@ bool set_job_control_traps(struct mrsh_state *state) {
	return true;
}

bool reset_caught_traps(struct mrsh_state *state) {
	struct mrsh_state_priv *priv = state_get_priv(state);

	for (size_t i = 0; i < MRSH_NSIG; i++) {
		struct mrsh_trap *trap = &priv->traps[i];
		if (trap->set && trap->action != MRSH_TRAP_IGNORE) {
			if (!set_trap(state, i, MRSH_TRAP_DEFAULT, NULL)) {
				return false;
			}
		}
	}

	return true;
}

bool run_pending_traps(struct mrsh_state *state) {
	struct mrsh_state_priv *priv = state_get_priv(state);
	static bool in_trap = false;