~emersion/mrsh

5cbb15ffbf03c959924f2274aed966c85d1b53ed — emersion 2 years ago 50685d2
Add support for IO redirections in builtins
5 files changed, 271 insertions(+), 158 deletions(-)

A include/shell/redir.h
M meson.build
A shell/redir.c
M shell/task/command_builtin.c
M shell/task/command_process.c
A include/shell/redir.h => include/shell/redir.h +8 -0
@@ 0,0 1,8 @@
#ifndef SHELL_REDIR_H
#define SHELL_REDIR_H

#include <mrsh/ast.h>

int process_redir(const struct mrsh_io_redirect *redir, int *redir_fd);

#endif

M meson.build => meson.build +1 -0
@@ 60,6 60,7 @@ lib_mrsh = library(
		'shell/arithm.c',
		'shell/path.c',
		'shell/process.c',
		'shell/redir.c',
		'shell/shell.c',
		'shell/task/assignment.c',
		'shell/task/ast.c',

A shell/redir.c => shell/redir.c +167 -0
@@ 0,0 1,167 @@
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/param.h>
#include "shell/redir.h"

static ssize_t write_here_document_line(int fd, struct mrsh_word *line,
		ssize_t max_size) {
	char *line_str = mrsh_word_str(line);
	size_t line_len = strlen(line_str);
	size_t write_len = line_len + 1; // line + terminating \n
	if (max_size >= 0 && write_len > (size_t)max_size) {
		free(line_str);
		return 0;
	}

	errno = 0;
	ssize_t n = write(fd, line_str, line_len);
	free(line_str);
	if (n < 0 || (size_t)n != line_len) {
		goto err_write;
	}

	if (write(fd, "\n", sizeof(char)) != 1) {
		goto err_write;
	}

	return write_len;

err_write:
	fprintf(stderr, "write() failed: %s\n",
		errno ? strerror(errno) : "short write");
	return -1;
}

static int create_here_document_fd(const struct mrsh_array *lines) {
	int fds[2];
	if (pipe(fds) != 0) {
		fprintf(stderr, "pipe() failed: %s\n", strerror(errno));
		return -1;
	}

	// We can write at most PIPE_BUF bytes without blocking. If we want to write
	// more, we need to fork and continue writing in another process.
	size_t remaining = PIPE_BUF;
	bool more = false;
	size_t i;
	for (i = 0; i < lines->len; ++i) {
		struct mrsh_word *line = lines->data[i];
		ssize_t n = write_here_document_line(fds[1], line, remaining);
		if (n < 0) {
			close(fds[0]);
			close(fds[1]);
			return -1;
		} else if (n == 0) {
			more = true;
			break;
		}
	}

	if (!more) {
		// We could write everything into the pipe buffer
		close(fds[1]);
		return fds[0];
	}

	pid_t pid = fork();
	if (pid < 0) {
		close(fds[0]);
		close(fds[1]);
		fprintf(stderr, "fork() failed: %s\n", strerror(errno));
		return -1;
	} else if (pid == 0) {
		close(fds[0]);

		for (; i < lines->len; ++i) {
			struct mrsh_word *line = lines->data[i];
			ssize_t n = write_here_document_line(fds[1], line, -1);
			if (n < 0) {
				close(fds[1]);
				exit(EXIT_FAILURE);
			}
		}
		close(fds[1]);
		exit(EXIT_SUCCESS);
	}

	close(fds[1]);
	return fds[0];
}

static int parse_fd(const char *str) {
	char *endptr;
	errno = 0;
	int fd = strtol(str, &endptr, 10);
	if (errno != 0) {
		return -1;
	}
	if (endptr[0] != '\0') {
		errno = EINVAL;
		return -1;
	}

	return fd;
}

int process_redir(const struct mrsh_io_redirect *redir, int *redir_fd) {
	// TODO: filename expansions
	char *filename = mrsh_word_str(redir->name);

	int fd, default_redir_fd;
	errno = 0;
	switch (redir->op) {
	case MRSH_IO_LESS: // <
		fd = open(filename, O_CLOEXEC | O_RDONLY);
		default_redir_fd = STDIN_FILENO;
		break;
	case MRSH_IO_GREAT: // >
	case MRSH_IO_CLOBBER: // >|
		fd = open(filename,
			O_CLOEXEC | O_WRONLY | O_CREAT | O_TRUNC, 0644);
		default_redir_fd = STDOUT_FILENO;
		break;
	case MRSH_IO_DGREAT: // >>
		fd = open(filename,
			O_CLOEXEC | O_WRONLY | O_CREAT | O_APPEND, 0644);
		default_redir_fd = STDOUT_FILENO;
		break;
	case MRSH_IO_LESSAND: // <&
		// TODO: parse "-"
		fd = parse_fd(filename);
		default_redir_fd = STDIN_FILENO;
		break;
	case MRSH_IO_GREATAND: // >&
		// TODO: parse "-"
		fd = parse_fd(filename);
		default_redir_fd = STDOUT_FILENO;
		break;
	case MRSH_IO_LESSGREAT: // <>
		fd = open(filename, O_CLOEXEC | O_RDWR | O_CREAT, 0644);
		default_redir_fd = STDIN_FILENO;
		break;
	case MRSH_IO_DLESS: // <<
	case MRSH_IO_DLESSDASH: // <<-
		fd = create_here_document_fd(&redir->here_document);
		default_redir_fd = STDIN_FILENO;
		break;
	}
	if (fd < 0) {
		fprintf(stderr, "cannot open %s: %s\n", filename,
			strerror(errno));
		return -1;
	}

	free(filename);

	*redir_fd = redir->io_number;
	if (*redir_fd < 0) {
		*redir_fd = default_redir_fd;
	}

	return fd;
}

M shell/task/command_builtin.c => shell/task/command_builtin.c +86 -2
@@ 1,15 1,99 @@
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <mrsh/builtin.h>
#include "shell/redir.h"
#include "shell/task_command.h"

struct saved_fd {
	int dup_fd;
	int redir_fd;
};

bool dup_and_save_fd(int fd, int redir_fd, struct saved_fd *saved) {
	saved->redir_fd = redir_fd;

	if (fd == redir_fd) {
		return true;
	}

	saved->dup_fd = dup(redir_fd);
	if (saved->dup_fd < 0) {
		fprintf(stderr, "failed to duplicate file descriptor: %s\n",
			strerror(errno));
		return false;
	}

	if (dup2(fd, redir_fd) < 0) {
		fprintf(stderr, "failed to duplicate file descriptor: %s\n",
			strerror(errno));
		return false;
	}
	close(fd);

	return true;
}

int task_builtin_poll(struct task *task, struct context *ctx) {
	struct task_command *tc = (struct task_command *)task;
	struct mrsh_simple_command *sc = tc->sc;

	assert(!tc->started);
	tc->started = true;

	// TODO: redirections
	// Duplicate old FDs to be able to restore them later
	struct saved_fd fds[2 + sc->io_redirects.len];
	for (size_t i = 0; i < sizeof(fds) / sizeof(fds[0]); ++i) {
		fds[i].dup_fd = fds[i].redir_fd = -1;
	}

	if (ctx->stdin_fileno >= 0) {
		if (!dup_and_save_fd(ctx->stdin_fileno, STDIN_FILENO, &fds[0])) {
			return TASK_STATUS_ERROR;
		}
	}
	if (ctx->stdout_fileno >= 0) {
		if (!dup_and_save_fd(ctx->stdout_fileno, STDOUT_FILENO, &fds[1])) {
			return TASK_STATUS_ERROR;
		}
	}

	for (size_t i = 0; i < sc->io_redirects.len; ++i) {
		struct mrsh_io_redirect *redir = sc->io_redirects.data[i];
		struct saved_fd *saved = &fds[2 + i];

		int redir_fd;
		int fd = process_redir(redir, &redir_fd);
		if (fd < 0) {
			return TASK_STATUS_ERROR;
		}

		if (!dup_and_save_fd(fd, redir_fd, saved)) {
			return TASK_STATUS_ERROR;
		}
	}

	// TODO: environment from assignements

	int argc = tc->args.len - 1;
	char **argv = (char **)tc->args.data;
	return mrsh_run_builtin(ctx->state, argc, argv);
	int ret = mrsh_run_builtin(ctx->state, argc, argv);

	// Restore old FDs
	for (size_t i = 0; i < sizeof(fds) / sizeof(fds[0]); ++i) {
		if (fds[i].dup_fd < 0) {
			continue;
		}

		if (dup2(fds[i].dup_fd, fds[i].redir_fd) < 0) {
			fprintf(stderr, "failed to duplicate file descriptor: %s\n",
				strerror(errno));
			return TASK_STATUS_ERROR;
		}
		close(fds[i].dup_fd);
	}

	return ret;
}

M shell/task/command_process.c => shell/task/command_process.c +9 -156
@@ 1,114 1,12 @@
#define _POSIX_C_SOURCE 200809L
#define _POSIX_C_SOURCE 200112L
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <unistd.h>
#include "shell/path.h"
#include "shell/redir.h"
#include "shell/task_command.h"

static ssize_t write_here_document_line(int fd, struct mrsh_word *line,
		ssize_t max_size) {
	char *line_str = mrsh_word_str(line);
	size_t line_len = strlen(line_str);
	size_t write_len = line_len + 1; // line + terminating \n
	if (max_size >= 0 && write_len > (size_t)max_size) {
		free(line_str);
		return 0;
	}

	errno = 0;
	ssize_t n = write(fd, line_str, line_len);
	free(line_str);
	if (n < 0 || (size_t)n != line_len) {
		goto err_write;
	}

	if (write(fd, "\n", sizeof(char)) != 1) {
		goto err_write;
	}

	return write_len;

err_write:
	fprintf(stderr, "write() failed: %s\n",
		errno ? strerror(errno) : "short write");
	return -1;
}

static int create_here_document_fd(struct mrsh_array *lines) {
	int fds[2];
	if (pipe(fds) != 0) {
		fprintf(stderr, "pipe() failed: %s\n", strerror(errno));
		return -1;
	}

	// We can write at most PIPE_BUF bytes without blocking. If we want to write
	// more, we need to fork and continue writing in another process.
	size_t remaining = PIPE_BUF;
	bool more = false;
	size_t i;
	for (i = 0; i < lines->len; ++i) {
		struct mrsh_word *line = lines->data[i];
		ssize_t n = write_here_document_line(fds[1], line, remaining);
		if (n < 0) {
			close(fds[0]);
			close(fds[1]);
			return -1;
		} else if (n == 0) {
			more = true;
			break;
		}
	}

	if (!more) {
		// We could write everything into the pipe buffer
		close(fds[1]);
		return fds[0];
	}

	pid_t pid = fork();
	if (pid < 0) {
		close(fds[0]);
		close(fds[1]);
		fprintf(stderr, "fork() failed: %s\n", strerror(errno));
		return -1;
	} else if (pid == 0) {
		close(fds[0]);

		for (; i < lines->len; ++i) {
			struct mrsh_word *line = lines->data[i];
			ssize_t n = write_here_document_line(fds[1], line, -1);
			if (n < 0) {
				close(fds[1]);
				exit(EXIT_FAILURE);
			}
		}
		close(fds[1]);
		exit(EXIT_SUCCESS);
	}

	close(fds[1]);
	return fds[0];
}

static int parse_fd(const char *str) {
	char *endptr;
	errno = 0;
	int fd = strtol(str, &endptr, 10);
	if (errno != 0) {
		return -1;
	}
	if (endptr[0] != '\0') {
		errno = EINVAL;
		return -1;
	}

	return fd;
}

static void populate_env_iterator(const char *key, void *_var, void *_) {
	struct mrsh_variable *var = _var;
	if ((var->attribs & MRSH_VAR_ATTRIB_EXPORT)) {


@@ 149,66 47,24 @@ static bool task_process_start(struct task_command *tc, struct context *ctx) {

		if (ctx->stdin_fileno >= 0) {
			dup2(ctx->stdin_fileno, STDIN_FILENO);
			close(ctx->stdin_fileno);
		}
		if (ctx->stdout_fileno >= 0) {
			dup2(ctx->stdout_fileno, STDOUT_FILENO);
			close(ctx->stdout_fileno);
		}

		for (size_t i = 0; i < sc->io_redirects.len; ++i) {
			struct mrsh_io_redirect *redir = sc->io_redirects.data[i];

			// TODO: filename expansions
			char *filename = mrsh_word_str(redir->name);

			int fd, default_redir_fd;
			errno = 0;
			switch (redir->op) {
			case MRSH_IO_LESS: // <
				fd = open(filename, O_CLOEXEC | O_RDONLY);
				default_redir_fd = STDIN_FILENO;
				break;
			case MRSH_IO_GREAT: // >
			case MRSH_IO_CLOBBER: // >|
				fd = open(filename,
					O_CLOEXEC | O_WRONLY | O_CREAT | O_TRUNC, 0644);
				default_redir_fd = STDOUT_FILENO;
				break;
			case MRSH_IO_DGREAT: // >>
				fd = open(filename,
					O_CLOEXEC | O_WRONLY | O_CREAT | O_APPEND, 0644);
				default_redir_fd = STDOUT_FILENO;
				break;
			case MRSH_IO_LESSAND: // <&
				// TODO: parse "-"
				fd = parse_fd(filename);
				default_redir_fd = STDIN_FILENO;
				break;
			case MRSH_IO_GREATAND: // >&
				// TODO: parse "-"
				fd = parse_fd(filename);
				default_redir_fd = STDOUT_FILENO;
				break;
			case MRSH_IO_LESSGREAT: // <>
				fd = open(filename, O_CLOEXEC | O_RDWR | O_CREAT, 0644);
				default_redir_fd = STDIN_FILENO;
				break;
			case MRSH_IO_DLESS: // <<
			case MRSH_IO_DLESSDASH: // <<-
				fd = create_here_document_fd(&redir->here_document);
				default_redir_fd = STDIN_FILENO;
				break;
			}
			int redir_fd;
			int fd = process_redir(redir, &redir_fd);
			if (fd < 0) {
				fprintf(stderr, "cannot open %s: %s\n", filename,
					strerror(errno));
				exit(EXIT_FAILURE);
			}

			free(filename);

			int redir_fd = redir->io_number;
			if (redir_fd < 0) {
				redir_fd = default_redir_fd;
			if (fd == redir_fd) {
				continue;
			}

			errno = 0;


@@ 218,10 74,7 @@ static bool task_process_start(struct task_command *tc, struct context *ctx) {
					strerror(errno));
				exit(EXIT_FAILURE);
			}

			if (fd != redir_fd) {
				close(fd);
			}
			close(fd);
		}

		errno = 0;