From c2ba79364f1fce5a7b62a07fffbf19cee710955b Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Sun, 29 Dec 2019 21:58:50 +0000 Subject: Initial commit --- .gitignore | 9 ++ Makefile | 67 +++++++++++ README | 3 + configure.ac | 6 + mbase.c | 372 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ skip.c | 2 + 6 files changed, 459 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README create mode 100644 configure.ac create mode 100644 mbase.c create mode 100644 skip.c 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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) { +} -- cgit 1.4.1