#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); }