about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlyssa Ross <hi@alyssa.is>2019-12-29 21:58:50 +0000
committerAlyssa Ross <hi@alyssa.is>2019-12-29 21:58:50 +0000
commitc2ba79364f1fce5a7b62a07fffbf19cee710955b (patch)
tree3eb91c665d6742d5dcae7ccb082c2aa5bb554ba1
downloadmbase-master.tar
mbase-master.tar.gz
mbase-master.tar.bz2
mbase-master.tar.lz
mbase-master.tar.xz
mbase-master.tar.zst
mbase-master.zip
Initial commit HEAD master
-rw-r--r--.gitignore9
-rw-r--r--Makefile67
-rw-r--r--README3
-rw-r--r--configure.ac6
-rw-r--r--mbase.c372
-rw-r--r--skip.c2
6 files changed, 459 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4fb8a70
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+Makefile.in
+autom4te.cache
+/config.log
+/config.status
+/configure
+/install-sh
+
+/skip
+/mbase
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d9bbf31
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,67 @@
+all: mbase skip
+.PHONY: all
+
+.SUFFIXES:
+.SUFFIXES: .c .o
+
+prefix = /home/src/mbase/prefix
+exec_prefix = ${prefix}
+bindir = ${exec_prefix}/bin
+libexecdir = ${exec_prefix}/libexec
+
+SHELL = /bin/sh
+
+INSTALL = /nix/store/k8lhqzpaaymshchz8ky3z4653h4kln9d-coreutils-8.31/bin/install -c
+INSTALL_PROGRAM = $(INSTALL)
+MKDIR_P = /nix/store/k8lhqzpaaymshchz8ky3z4653h4kln9d-coreutils-8.31/bin/mkdir -p
+
+CFLAGS = -g
+
+.c.o:
+	$(CC) -c -DLIBEXECDIR='"$(libexecdir)"' $(CPPFLAGS) $(CFLAGS) $<
+
+%: %.c
+
+configure: configure.ac
+	autoconf
+
+Makefile: Makefile.in config.status
+	./config.status
+
+config.status: configure
+	./config.status --recheck
+
+install-dirs:
+	# TODO
+
+install: mbase skip install-dirs
+	$(MKDIR_P) $(DESTDIR)$(bindir)
+	$(INSTALL_PROGRAM) mbase $(DESTDIR)$(bindir)/
+
+	$(MKDIR_P) $(DESTDIR)$(libexecdir)/mbase
+	$(INSTALL_PROGRAM) skip $(DESTDIR)$(libexecdir)/mbase/
+.PHONY: install
+
+uninstall:
+	rm -f $(DESTDIR)$(bindir)/mbase
+	rm -fr $(DESTDIR)$(libexecdir)/mbase
+.PHONY: uninstall
+
+install-strip:
+	$(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' install
+.PHONY: install-strip
+
+clean:
+	rm -f mbase mbase.o skip skip.o
+.PHONY: clean
+
+distclean: clean
+	rm -f config.status config.log Makefile
+.PHONY: distclean
+
+mbase-1.0.tar.gz:
+	-mkdir mbase-1.0
+	cp install-sh configure.ac configure Makefile.in README mbase.c skip.c mbase-1.0
+	tar czf mbase-1.0.tar.gz mbase-1.0
+dist: mbase-1.0.tar.gz
+.PHONY: dist
diff --git a/README b/README
new file mode 100644
index 0000000..5807f04
--- /dev/null
+++ b/README
@@ -0,0 +1,3 @@
+mbase -- editor-oriented interactive mail user agent
+
+mlist ~/Mail/INBOX | mbase
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..a230de6
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,6 @@
+AC_INIT([mbase], [0.1.0])
+AC_PROG_INSTALL
+AC_PROG_MKDIR_P
+AC_PROG_CPP
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
diff --git a/mbase.c b/mbase.c
new file mode 100644
index 0000000..1d88242
--- /dev/null
+++ b/mbase.c
@@ -0,0 +1,372 @@
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+enum error_code
+{
+	SUCCESS = 0,
+
+	// XXX: Only add to the bottom of this list to preserve indices.
+	PATH_TOO_LONG,
+	ALLOC_FAILED,
+	FORK_FAILED,
+	EXEC_FAILED,
+	CHILD_ERROR,
+	ENCODING_ERROR,
+	IO_ERROR,
+	PIPE_FAILED,
+	IOCTL_FAILED,
+	SETENV_FAILED,
+};
+
+int mbase_verror(const char *format, va_list ap)
+{
+	char *prefix = "mbase: ";
+	char *suffix = "\n";
+
+	size_t prefix_len = strlen(prefix);
+	size_t format_len = strlen(format);
+	size_t suffix_len = strlen(suffix);
+
+	size_t len = prefix_len + format_len + suffix_len;
+
+	char *mbase_format = calloc(len + 1, sizeof(*mbase_format));
+	if (NULL == mbase_format) {
+		return -1;
+	}
+
+	strcpy(mbase_format, prefix);
+	strcpy(mbase_format + prefix_len, format);
+	strcpy(mbase_format + prefix_len + format_len, suffix);
+
+	int res = vfprintf(stderr, mbase_format, ap);
+
+	free(mbase_format);
+
+	return res;
+}
+
+int mbase_error(const char *format, ...)
+{
+	va_list ap;
+	va_start(ap, format);
+	int res = mbase_verror(format, ap);
+	va_end(ap);
+	return res;
+}
+
+void die(int status, char *message)
+{
+	mbase_error("%s", message);
+	exit(status);
+}
+
+int run_command(char *cmd, char *arg)
+{
+	pid_t cmdpid = fork();
+
+	if (-1 == cmdpid) {
+		die(FORK_FAILED, strerror(errno));
+	}
+
+	if (cmdpid == 0) {
+		size_t cmdlen = strlen(cmd);
+		char cmdsh[cmdlen + 6];
+		strcpy(cmdsh, cmd);
+		strcpy(cmdsh + cmdlen, " \"$0\"");
+		char *cmdargv[] = { "/bin/sh", "-c", cmdsh, arg, NULL };
+
+		execv("/bin/sh", cmdargv);
+		die(EXEC_FAILED, strerror(errno));
+	}
+
+	int exit;
+	assert(waitpid(cmdpid, &exit, 0) != -1);
+
+	return exit;
+}
+
+/*
+ * Reads a newline-separated list of paths from standard input into
+ * the string array pointer given. The caller must later free each
+ * path, as well as the array itself.
+ *
+ * Returns the number of paths read.
+ */
+size_t read_paths(char ***paths)
+{
+	size_t paths_len = 256;
+	size_t npaths = 0;
+	char buf[PATH_MAX + 2];
+
+	*paths = calloc(paths_len, sizeof(**paths));
+	if (NULL == paths) {
+		die(ALLOC_FAILED, strerror(errno));
+	}
+
+	// fgets can read a zero-length string (one byte), in which case
+	// the loop should be terminated immediately to avoid having to
+	// worry about indexing -1 into the array to get the last character,
+	// etc.
+	while (fgets(buf, sizeof(buf), stdin) == buf && buf[0] != '\0') {
+		size_t len = strlen(buf);
+
+		if (buf[len - 1] == '\n') {
+			len--;
+		} else {
+			int peek = getc(stdin);
+			ungetc(peek, stdin);
+			if (peek != EOF) {
+				die(PATH_TOO_LONG, "path too long");
+			}
+		}
+
+		if (npaths + 1 >= paths_len) {
+			paths_len *= 2;
+			*paths = realloc(*paths, paths_len * sizeof(**paths));
+		}
+
+		(*paths)[npaths] = calloc(len + 1, sizeof(***paths));
+		if (NULL == (*paths)[npaths]) {
+			die(ALLOC_FAILED, strerror(errno));
+		}
+
+		strncpy((*paths)[npaths], buf, len);
+		npaths++;
+	}
+
+	return npaths;
+}
+
+pid_t mscan(char **paths, size_t n, FILE **mscan_out)
+{
+	int mscan_in_fds[2], mscan_out_fds[2];
+	if (-1 == pipe(mscan_in_fds) || -1 == pipe(mscan_out_fds)) {
+		die(PIPE_FAILED, strerror(errno));
+	}
+
+	pid_t mscan_pid = fork();
+
+	if (-1 == mscan_pid) {
+		die(FORK_FAILED, strerror(errno));
+	}
+
+	if (0 == mscan_pid) {
+		if (-1 == ioctl(STDIN_FILENO, TIOCNOTTY)) {
+			die(IO_ERROR, strerror(errno));
+		}
+
+		if (-1 == setenv("COLUMNS", "10000", 1)) {
+			die(SETENV_FAILED, strerror(errno));
+		}
+
+		if (-1 == dup2(mscan_in_fds[0], STDIN_FILENO)) {
+			die(IO_ERROR, strerror(errno));
+		}
+		if (-1 == dup2(mscan_out_fds[1], STDOUT_FILENO)) {
+			die(IO_ERROR, strerror(errno));
+		}
+
+		if (-1 == close(mscan_in_fds[0])) {
+			die(IO_ERROR, strerror(errno));
+		}
+		if (-1 == close(mscan_in_fds[1])) {
+			die(IO_ERROR, strerror(errno));
+		}
+		if (-1 == close(mscan_out_fds[0])) {
+			die(IO_ERROR, strerror(errno));
+		}
+		if (-1 == close(mscan_out_fds[1])) {
+			die(IO_ERROR, strerror(errno));
+		}
+
+		execlp("mscan", "mscan", NULL);
+		die(EXEC_FAILED, strerror(errno));
+	}
+
+	if (-1 == close(mscan_in_fds[0])) {
+		die(IO_ERROR, strerror(errno));
+	}
+	if (-1 == close(mscan_out_fds[1])) {
+		die(IO_ERROR, strerror(errno));
+	}
+	FILE *mscan_in = fdopen(mscan_in_fds[1], "w");
+	*mscan_out = fdopen(mscan_out_fds[0], "r");
+	if (NULL == mscan_in || NULL == *mscan_out) {
+		die(IO_ERROR, strerror(errno));
+	}
+
+	for (size_t i = 0; i < n; i++) {
+		if (fprintf(mscan_in, "%s\n", paths[i]) < 0) {
+			die(IO_ERROR, strerror(errno));
+		}
+	}
+	if (fclose(mscan_in) == EOF) {
+		die(IO_ERROR, strerror(errno));
+	}
+
+	return mscan_pid;
+}
+
+/*
+ * Caller must free return value.
+ */
+char *tmppath(char *name)
+{
+	char *tmpdir = getenv("TMPDIR");
+	if (NULL == tmpdir) {
+		tmpdir = "/tmp";
+	}
+
+	char *tmppath = calloc(PATH_MAX + 1, sizeof(*tmppath));
+	if (NULL == tmppath) {
+		die(ALLOC_FAILED, strerror(errno));
+	}
+
+	strncpy(tmppath, tmpdir, PATH_MAX);
+	size_t len = strlen(tmppath);
+	if (tmppath[len - 1] != '/') tmppath[len] = '/';
+	len++;
+	strncpy(tmppath + len, "mbase.XXXXXX", PATH_MAX - len);
+
+	return tmppath;
+}
+
+void write_todo_file(FILE *summaries, size_t lines, FILE *out)
+{
+	char buf[256];
+	int maxiwidth = snprintf(NULL, 0, "%zu", lines - 1);
+	assert(maxiwidth > 0);
+	size_t i = 0;
+	bool start_of_line = true;
+	while (fgets(buf, sizeof(buf), summaries) == buf) {
+		if (start_of_line) {
+			if (fprintf(out, "skip \t %*zu \t ", maxiwidth, i++) < 0) {
+				die(IO_ERROR, strerror(errno));
+			}
+		}
+
+		if (fputs(buf, out) == EOF) {
+			die(IO_ERROR, strerror(errno));
+		}
+
+		start_of_line = buf != '\0' && buf[strlen(buf) - 1] == '\n';
+	}
+}
+
+void run_todo(FILE *todo_in, char **paths, size_t npaths)
+{
+	for (size_t line_no = 1;; line_no++) {
+		char *cmdstr = NULL;
+		unsigned int n;
+		int res = fscanf(todo_in, "%ms%*[ ]%*[\t]%*[ ]%u%*[ ]%*[\t]", &cmdstr, &n);
+
+		if (res != EOF && n < npaths) {
+			run_command(cmdstr, paths[n]);
+		} else if (!feof(todo_in) || n >= npaths) {
+			mbase_error("mbase: parse error on line %zu", line_no);
+		}
+
+		if (fscanf(todo_in, "%*[^\n]") == EOF) {
+			die(IO_ERROR, strerror(errno));
+		}
+
+		free(cmdstr);
+	}
+}
+
+/* Caller must free */
+char *get_path(void)
+{
+	char *path;
+	char *env_path = getenv("PATH");
+
+	if (env_path) {
+		path = calloc(strlen(env_path) + 1, sizeof(*path));
+		strcpy(path, env_path);
+	} else {
+		size_t path_len = confstr(_CS_PATH, NULL, 0);
+		confstr(_CS_PATH, path, path_len);
+	}
+
+	return path;
+}
+
+void prepend_path(char *prefix)
+{
+	char *path = get_path();
+	if (strlen(path) == 0) {
+		if (setenv("PATH", prefix, 1) == -1) {
+			die(SETENV_FAILED, strerror(errno));
+		}
+	} else {
+		char *new_path = calloc(strlen(prefix) + 1 + strlen(path) + 1, sizeof(*new_path));
+		if (NULL == new_path) {
+			die(ALLOC_FAILED, strerror(errno));
+		}
+		if (sprintf(new_path, "%s:%s", prefix, path) < 1) {
+			die(ENCODING_ERROR, strerror(errno));
+		}
+		if (setenv("PATH", new_path, 1) == -1) {
+			die(SETENV_FAILED, strerror(errno));
+		}
+		free(new_path);
+	}
+	free(path);
+}
+
+int main(void)
+{
+	prepend_path(LIBEXECDIR "/mbase");
+
+	char **paths;
+	size_t npaths = read_paths(&paths);
+
+	int tty = open("/dev/tty", O_RDWR);
+	dup2(tty, STDIN_FILENO);
+	close(tty);
+
+	FILE *mscan_out;
+	pid_t mscan_pid = mscan(paths, npaths, &mscan_out);
+	char *todo_path = tmppath("mbase.XXXXXX");
+
+	int todo_fd = mkstemp(todo_path);
+	FILE *todo_out = fdopen(todo_fd, "w");
+	write_todo_file(mscan_out, npaths, todo_out);
+	fclose(todo_out);
+
+	fclose(mscan_out);
+	int mscan_exit;
+	assert(waitpid(mscan_pid, &mscan_exit, 0) != -1);
+	if (mscan_exit != 0) {
+		// die(CHILD_ERROR, "mscan exited %i", mscan_exit);
+	}
+
+	char *editor = getenv("EDITOR");
+	if (NULL == editor) {
+		editor = "vi";
+	}
+	int editor_exit = run_command(editor, todo_path);
+	if (editor_exit != 0) {
+		// die(CHILD_ERROR, "editor exited %i", editor_exit);
+	}
+
+	FILE *todo_in = fopen(todo_path, "r");
+	run_todo(todo_in, paths, npaths);
+	fclose(todo_in);
+
+	for (size_t i = 0; i < npaths; i++) {
+		free(paths[i]);
+	}
+	free(paths);
+	free(todo_path);
+}
diff --git a/skip.c b/skip.c
new file mode 100644
index 0000000..e8599dc
--- /dev/null
+++ b/skip.c
@@ -0,0 +1,2 @@
+int main(void) {
+}