summaryrefslogtreecommitdiff
path: root/examples/dte/spawn.c
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2023-11-09 23:19:53 +0100
committerMitja Felicijan <mitja.felicijan@gmail.com>2023-11-09 23:19:53 +0100
commit1566b6faa8534118c3566188181367cd0868468f (patch)
tree1de8d4b369efb5e592685a31088f798a6b63ffa1 /examples/dte/spawn.c
parent349991bf6efe473ab9a5cbdae0a8114d72b997e3 (diff)
downloadcrep-1566b6faa8534118c3566188181367cd0868468f.tar.gz
Added partial matching and introduced threads
Diffstat (limited to 'examples/dte/spawn.c')
-rw-r--r--examples/dte/spawn.c396
1 files changed, 396 insertions, 0 deletions
diff --git a/examples/dte/spawn.c b/examples/dte/spawn.c
new file mode 100644
index 0000000..0d9e0d6
--- /dev/null
+++ b/examples/dte/spawn.c
@@ -0,0 +1,396 @@
+#include <errno.h>
+#include <poll.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include "spawn.h"
+#include "error.h"
+#include "regexp.h"
+#include "terminal/mode.h"
+#include "util/debug.h"
+#include "util/fd.h"
+#include "util/fork-exec.h"
+#include "util/ptr-array.h"
+#include "util/str-util.h"
+#include "util/strtonum.h"
+#include "util/xmalloc.h"
+#include "util/xreadwrite.h"
+#include "util/xstdio.h"
+
+static void handle_error_msg(const Compiler *c, MessageArray *msgs, char *str)
+{
+ if (str[0] == '\0' || str[0] == '\n') {
+ return;
+ }
+
+ size_t str_len = str_replace_byte(str, '\t', ' ');
+ if (str[str_len - 1] == '\n') {
+ str[--str_len] = '\0';
+ }
+
+ for (size_t i = 0, n = c->error_formats.count; i < n; i++) {
+ const ErrorFormat *p = c->error_formats.ptrs[i];
+ regmatch_t m[ERRORFMT_CAPTURE_MAX];
+ if (!regexp_exec(&p->re, str, str_len, ARRAYLEN(m), m, 0)) {
+ continue;
+ }
+ if (p->ignore) {
+ return;
+ }
+
+ int8_t mi = p->capture_index[ERRFMT_MESSAGE];
+ if (m[mi].rm_so < 0) {
+ mi = 0;
+ }
+
+ Message *msg = new_message(str + m[mi].rm_so, m[mi].rm_eo - m[mi].rm_so);
+ msg->loc = xnew0(FileLocation, 1);
+
+ int8_t fi = p->capture_index[ERRFMT_FILE];
+ if (fi >= 0 && m[fi].rm_so >= 0) {
+ msg->loc->filename = xstrslice(str, m[fi].rm_so, m[fi].rm_eo);
+
+ unsigned long *const ptrs[] = {
+ [ERRFMT_LINE] = &msg->loc->line,
+ [ERRFMT_COLUMN] = &msg->loc->column,
+ };
+
+ static_assert(ARRAYLEN(ptrs) == 3);
+ static_assert(ERRFMT_LINE == 1);
+ static_assert(ERRFMT_COLUMN == 2);
+
+ for (size_t j = ERRFMT_LINE; j < ARRAYLEN(ptrs); j++) {
+ int8_t ci = p->capture_index[j];
+ if (ci >= 0 && m[ci].rm_so >= 0) {
+ size_t len = m[ci].rm_eo - m[ci].rm_so;
+ unsigned long val;
+ if (len == buf_parse_ulong(str + m[ci].rm_so, len, &val)) {
+ *ptrs[j] = val;
+ }
+ }
+ }
+ }
+
+ add_message(msgs, msg);
+ return;
+ }
+
+ add_message(msgs, new_message(str, str_len));
+}
+
+static void read_errors(const Compiler *c, MessageArray *msgs, int fd, bool quiet)
+{
+ FILE *f = fdopen(fd, "r");
+ if (unlikely(!f)) {
+ return;
+ }
+ char line[4096];
+ while (xfgets(line, sizeof(line), f)) {
+ if (!quiet) {
+ xfputs(line, stderr);
+ }
+ handle_error_msg(c, msgs, line);
+ }
+ fclose(f);
+}
+
+static void handle_piped_data(int f[3], SpawnContext *ctx)
+{
+ BUG_ON(f[0] < 0 && f[1] < 0 && f[2] < 0);
+ BUG_ON(f[0] >= 0 && f[0] <= 2);
+ BUG_ON(f[1] >= 0 && f[1] <= 2);
+ BUG_ON(f[2] >= 0 && f[2] <= 2);
+
+ if (ctx->input.length == 0) {
+ xclose(f[0]);
+ f[0] = -1;
+ if (f[1] < 0 && f[2] < 0) {
+ return;
+ }
+ }
+
+ struct pollfd fds[] = {
+ {.fd = f[0], .events = POLLOUT},
+ {.fd = f[1], .events = POLLIN},
+ {.fd = f[2], .events = POLLIN},
+ };
+
+ size_t wlen = 0;
+ while (1) {
+ if (unlikely(poll(fds, ARRAYLEN(fds), -1) < 0)) {
+ if (errno == EINTR) {
+ continue;
+ }
+ error_msg_errno("poll");
+ return;
+ }
+
+ for (size_t i = 0; i < ARRAYLEN(ctx->outputs); i++) {
+ struct pollfd *pfd = fds + i + 1;
+ if (pfd->revents & POLLIN) {
+ String *output = &ctx->outputs[i];
+ char *buf = string_reserve_space(output, 4096);
+ ssize_t rc = xread(pfd->fd, buf, output->alloc - output->len);
+ if (unlikely(rc < 0)) {
+ error_msg_errno("read");
+ return;
+ }
+ if (rc == 0) { // EOF
+ if (xclose(pfd->fd)) {
+ error_msg_errno("close");
+ return;
+ }
+ pfd->fd = -1;
+ continue;
+ }
+ output->len += rc;
+ }
+ }
+
+ if (fds[0].revents & POLLOUT) {
+ ssize_t rc = xwrite(fds[0].fd, ctx->input.data + wlen, ctx->input.length - wlen);
+ if (unlikely(rc < 0)) {
+ error_msg_errno("write");
+ return;
+ }
+ wlen += (size_t) rc;
+ if (wlen == ctx->input.length) {
+ if (xclose(fds[0].fd)) {
+ error_msg_errno("close");
+ return;
+ }
+ fds[0].fd = -1;
+ }
+ }
+
+ size_t active_fds = ARRAYLEN(fds);
+ for (size_t i = 0; i < ARRAYLEN(fds); i++) {
+ int rev = fds[i].revents;
+ if (fds[i].fd < 0 || rev & POLLNVAL) {
+ fds[i].fd = -1;
+ active_fds--;
+ continue;
+ }
+ if (rev & POLLERR || (rev & (POLLHUP | POLLIN)) == POLLHUP) {
+ if (xclose(fds[i].fd)) {
+ error_msg_errno("close");
+ }
+ fds[i].fd = -1;
+ active_fds--;
+ }
+ }
+ if (active_fds == 0) {
+ return;
+ }
+ }
+}
+
+static int open_dev_null(int flags)
+{
+ int fd = xopen("/dev/null", flags | O_CLOEXEC, 0);
+ if (unlikely(fd < 0)) {
+ error_msg_errno("Error opening /dev/null");
+ }
+ return fd;
+}
+
+static int handle_child_error(pid_t pid)
+{
+ int ret = wait_child(pid);
+ if (ret < 0) {
+ error_msg_errno("waitpid");
+ } else if (ret >= 256) {
+ int sig = ret >> 8;
+ const char *str = strsignal(sig);
+ error_msg("Child received signal %d (%s)", sig, str ? str : "??");
+ } else if (ret) {
+ error_msg("Child returned %d", ret);
+ }
+ return ret;
+}
+
+static void yield_terminal(EditorState *e, bool quiet)
+{
+ if (quiet) {
+ term_raw_isig();
+ } else {
+ e->child_controls_terminal = true;
+ ui_end(e);
+ }
+}
+
+static void resume_terminal(EditorState *e, bool quiet, bool prompt)
+{
+ term_raw();
+ if (!quiet && e->child_controls_terminal) {
+ if (prompt) {
+ any_key(&e->terminal, e->options.esc_timeout);
+ }
+ ui_start(e);
+ e->child_controls_terminal = false;
+ }
+}
+
+static void exec_error(const char *argv0)
+{
+ error_msg("Unable to exec '%s': %s", argv0, strerror(errno));
+}
+
+bool spawn_compiler(SpawnContext *ctx, const Compiler *c, MessageArray *msgs)
+{
+ BUG_ON(!ctx->editor);
+ BUG_ON(!ctx->argv[0]);
+
+ int fd[3];
+ fd[0] = open_dev_null(O_RDONLY);
+ if (fd[0] < 0) {
+ return false;
+ }
+
+ int dev_null = open_dev_null(O_WRONLY);
+ if (dev_null < 0) {
+ xclose(fd[0]);
+ return false;
+ }
+
+ int p[2];
+ if (xpipe2(p, O_CLOEXEC) != 0) {
+ error_msg_errno("pipe");
+ xclose(dev_null);
+ xclose(fd[0]);
+ return false;
+ }
+
+ SpawnFlags flags = ctx->flags;
+ bool read_stdout = !!(flags & SPAWN_READ_STDOUT);
+ bool quiet = !!(flags & SPAWN_QUIET);
+ bool prompt = !!(flags & SPAWN_PROMPT);
+ if (read_stdout) {
+ fd[1] = p[1];
+ fd[2] = quiet ? dev_null : 2;
+ } else {
+ fd[1] = quiet ? dev_null : 1;
+ fd[2] = p[1];
+ }
+
+ yield_terminal(ctx->editor, quiet);
+ pid_t pid = fork_exec(ctx->argv, NULL, fd, quiet);
+ if (pid == -1) {
+ exec_error(ctx->argv[0]);
+ xclose(p[1]);
+ prompt = false;
+ } else {
+ // Must close write end of the pipe before read_errors() or
+ // the read end never gets EOF!
+ xclose(p[1]);
+ read_errors(c, msgs, p[0], quiet);
+ handle_child_error(pid);
+ }
+ resume_terminal(ctx->editor, quiet, prompt);
+
+ xclose(p[0]);
+ xclose(dev_null);
+ xclose(fd[0]);
+ return (pid != -1);
+}
+
+// Close fd only if valid (positive) and not stdin/stdout/stderr
+static int safe_xclose(int fd)
+{
+ return (fd > STDERR_FILENO) ? xclose(fd) : 0;
+}
+
+static void safe_xclose_all(int fds[], size_t nr_fds)
+{
+ for (size_t i = 0; i < nr_fds; i++) {
+ safe_xclose(fds[i]);
+ fds[i] = -1;
+ }
+}
+
+UNITTEST {
+ int fds[] = {-2, -3, -4};
+ safe_xclose_all(fds, 2);
+ BUG_ON(fds[0] != -1);
+ BUG_ON(fds[1] != -1);
+ BUG_ON(fds[2] != -4);
+ safe_xclose_all(fds, 3);
+ BUG_ON(fds[2] != -1);
+}
+
+int spawn(SpawnContext *ctx)
+{
+ BUG_ON(!ctx->editor);
+ BUG_ON(!ctx->argv[0]);
+
+ int child_fds[3] = {-1, -1, -1};
+ int parent_fds[3] = {-1, -1, -1};
+ bool quiet = !!(ctx->flags & SPAWN_QUIET);
+ size_t nr_pipes = 0;
+
+ for (size_t i = 0; i < ARRAYLEN(child_fds); i++) {
+ switch (ctx->actions[i]) {
+ case SPAWN_TTY:
+ if (!quiet) {
+ child_fds[i] = i;
+ break;
+ }
+ // Fallthrough
+ case SPAWN_NULL:
+ child_fds[i] = open_dev_null(O_RDWR);
+ if (child_fds[i] < 0) {
+ goto error_close;
+ }
+ break;
+ case SPAWN_PIPE: {
+ int p[2];
+ if (xpipe2(p, O_CLOEXEC) != 0) {
+ error_msg_errno("pipe");
+ goto error_close;
+ }
+ BUG_ON(p[0] <= STDERR_FILENO);
+ BUG_ON(p[1] <= STDERR_FILENO);
+ child_fds[i] = i ? p[1] : p[0];
+ parent_fds[i] = i ? p[0] : p[1];
+ if (!fd_set_nonblock(parent_fds[i], true)) {
+ error_msg_errno("fcntl");
+ goto error_close;
+ }
+ nr_pipes++;
+ break;
+ }
+ default:
+ BUG("unhandled action type");
+ goto error_close;
+ }
+ }
+
+ yield_terminal(ctx->editor, quiet);
+ pid_t pid = fork_exec(ctx->argv, ctx->env, child_fds, quiet);
+ if (pid == -1) {
+ exec_error(ctx->argv[0]);
+ goto error_resume;
+ }
+
+ safe_xclose_all(child_fds, ARRAYLEN(child_fds));
+ if (nr_pipes > 0) {
+ handle_piped_data(parent_fds, ctx);
+ }
+
+ safe_xclose_all(parent_fds, ARRAYLEN(parent_fds));
+ int err = wait_child(pid);
+ if (err < 0) {
+ error_msg_errno("waitpid");
+ }
+
+ resume_terminal(ctx->editor, quiet, !!(ctx->flags & SPAWN_PROMPT));
+ return err;
+
+error_resume:
+ resume_terminal(ctx->editor, quiet, false);
+error_close:
+ safe_xclose_all(child_fds, ARRAYLEN(child_fds));
+ safe_xclose_all(parent_fds, ARRAYLEN(parent_fds));
+ return -1;
+}