about summary refs log tree commit diff
path: root/mbase.c
diff options
context:
space:
mode:
Diffstat (limited to 'mbase.c')
-rw-r--r--mbase.c372
1 files changed, 372 insertions, 0 deletions
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);
+}