Third attempt, and these are the changes done since the first version of this diff.
1. Check for existence of the command to be executed and also the permissions. 2. Replace popen(3) which messed stdout with socketpair(2), fork(2) and execl(2). 3. IO multiplexing using poll(2) to prevent deadlock while transferring large text to and from external command. 4. Fixed a memory leak while using strdup(3). 5. style(9) consistencies. In emacs, if the output from the command is less than 12 lines, it shows the output in minibuffer otherwise emacs pops up a *Shell Command Output* buffer and displays in it. Since mg's minibuffer isn't multi-line aware, this diff pops up *Shell Command Output* and displays output in that buffer irrespective of output's size. Comments? Index: def.h =================================================================== RCS file: /cvs/src/usr.bin/mg/def.h,v retrieving revision 1.118 diff -u -p -r1.118 def.h --- def.h 10 Dec 2011 14:09:48 -0000 1.118 +++ def.h 16 Mar 2012 04:59:14 -0000 @@ -567,6 +567,8 @@ int prefixregion(int, int); int setprefix(int, int); int region_get_data(struct region *, char *, int); void region_put_data(const char *, int); +int markbuffer(int, int); +int piperegion(int, int); /* search.c X */ int forwsearch(int, int); Index: funmap.c =================================================================== RCS file: /cvs/src/usr.bin/mg/funmap.c,v retrieving revision 1.36 diff -u -p -r1.36 funmap.c --- funmap.c 14 Mar 2012 13:56:35 -0000 1.36 +++ funmap.c 16 Mar 2012 04:59:14 -0000 @@ -113,6 +113,7 @@ static struct funmap functnames[] = { {localbind, "local-set-key",}, {localunbind, "local-unset-key",}, {makebkfile, "make-backup-files",}, + {markbuffer, "mark-whole-buffer",}, {do_meta, "meta-key-mode",}, /* better name, anyone? */ {negative_argument, "negative-argument",}, {newline, "newline",}, @@ -166,6 +167,7 @@ static struct funmap functnames[] = { {setfillcol, "set-fill-column",}, {setmark, "set-mark-command",}, {setprefix, "set-prefix-string",}, + {piperegion, "shell-command-on-region",}, {shrinkwind, "shrink-window",}, #ifdef NOTAB {space_to_tabstop, "space-to-tabstop",}, Index: keymap.c =================================================================== RCS file: /cvs/src/usr.bin/mg/keymap.c,v retrieving revision 1.47 diff -u -p -r1.47 keymap.c --- keymap.c 14 Mar 2012 13:56:35 -0000 1.47 +++ keymap.c 16 Mar 2012 04:59:14 -0000 @@ -135,7 +135,7 @@ static PF cXcar[] = { #endif /* !NO_MACRO */ setfillcol, /* f */ gotoline, /* g */ - rescan, /* h */ + markbuffer, /* h */ fileinsert, /* i */ rescan, /* j */ killbuffer_cmd, /* k */ @@ -257,7 +257,7 @@ static PF metal[] = { rescan, /* y */ rescan, /* z */ gotobop, /* { */ - rescan, /* | */ + piperegion, /* | */ gotoeop /* } */ }; Index: mg.1 =================================================================== RCS file: /cvs/src/usr.bin/mg/mg.1,v retrieving revision 1.58 diff -u -p -r1.58 mg.1 --- mg.1 9 Feb 2012 09:00:14 -0000 1.58 +++ mg.1 16 Mar 2012 04:59:14 -0000 @@ -196,6 +196,8 @@ call-last-kbd-macro set-fill-column .It C-x g goto-line +.It C-x h +mark-whole-buffer .It C-x i insert-file .It C-x k @@ -260,6 +262,8 @@ copy-region-as-kill execute-extended-command .It M-{ backward-paragraph +.It M-| +shell-command-on-region .It M-} forward-paragraph .It M-~ @@ -572,6 +576,9 @@ Bind a key mapping in the local (topmost Unbind a key mapping in the local (topmost) mode. .It make-backup-files Toggle generation of backup files. +.It mark-whole-buffer +Marks whole buffer as a region by putting dot at the beginning and mark +at the end of buffer. .It meta-key-mode When disabled, the meta key can be used to insert extended-ascii (8-bit) characters. @@ -734,6 +741,8 @@ Used by auto-fill-mode. Sets the mark in the current window to the current dot location. .It set-prefix-string Sets the prefix string to be used by the 'prefix-region' command. +.It shell-command-on-region +Provide the text in region to the shell command as input. .It shrink-window Shrink current window by one line. The window immediately below is expanded to pick up the slack. Index: region.c =================================================================== RCS file: /cvs/src/usr.bin/mg/region.c,v retrieving revision 1.29 diff -u -p -r1.29 region.c --- region.c 5 Jun 2009 18:02:06 -0000 1.29 +++ region.c 18 Mar 2012 17:39:32 -0000 @@ -9,9 +9,26 @@ * internal use. */ +#include <sys/types.h> +#include <sys/socket.h> + +#include <poll.h> +#include <string.h> +#include <unistd.h> + #include "def.h" +#define TIMEOUT 10000 + +static char leftover[BUFSIZ]; + +static int exists(const char *); +static char *get_line(struct line *, int *); static int getregion(struct region *); +static int iomux(int); +static int pipeio(const char *); +static int preadin(int,struct buffer *); +static void pwriteout(int,struct region *); static int setsize(struct region *, RSIZE); /* @@ -366,4 +383,302 @@ region_put_data(const char *buf, int len else linsert(1, buf[i]); } +} + +/* + * Mark whole buffer by first traversing to end-of-buffer + * and then to beginning-of-buffer. Mark, dot are implicitly + * set to eob, bob respectively during traversal. + */ +int +markbuffer(int f, int n) +{ + if (gotoeob(f,n) == FALSE) + return (FALSE); + if (gotobob(f,n) == FALSE) + return (FALSE); + return (TRUE); +} + +/* + * Pipe text from current region to external command. + */ +/*ARGSUSED */ +int +piperegion(int f, int n) +{ + char *cmd, *cmdcpy, cmdbuf[NFILEN]; + + /* C-u M-| is not supported yet */ + if (n > 1) + return (ABORT); + + if (curwp->w_markp == NULL) { + ewprintf("The mark is not set now, so there is no region"); + return (FALSE); + } + if ((cmd = eread("Shell command on region: ", cmdbuf, sizeof(cmdbuf), + EFNEW | EFCR)) == NULL) + return (FALSE); + else if (cmd[0] == '\0') + return (ABORT); + + if ((cmdcpy = strdup(cmdbuf)) == NULL) { + ewprintf("out of memory"); + return (FALSE); + } + /* If cmd contains arguments then consider only the cmd part + * while checking for existence. + */ + if (strchr(cmdcpy, ' ') != NULL) + cmdcpy = strsep(&cmdcpy, " "); + if (exists(cmdcpy) == FALSE) + return (FALSE); + free(cmdcpy); + return (pipeio(cmdbuf)); +} + +/* + * Check if the command exists and has necessary permissions. + */ +int +exists(const char *cmd) +{ + char fname[MAXPATHLEN], *dir, *path, *tmp; + int cmdlen, dlen, found; + + /* Special case if prog contains '/' */ + if (strchr(cmd, '/')) { + if (access(cmd, F_OK) == -1) { + ewprintf("%s not found", cmd); + return (FALSE); + } + else if (access(cmd, X_OK) == -1) { + ewprintf("%s cannot execute - Permission denied", cmd); + return (FALSE); + } else + return (TRUE); + } + if ((tmp = getenv("PATH")) == NULL) + return (FALSE); + if ((path = strdup(tmp)) == NULL) { + ewprintf("out of memory"); + return (FALSE); + } + found = FALSE; + cmdlen = strlen(cmd); + while ((dir = strsep(&path, ":")) != NULL) { + if (*dir == '\0') + *dir = '.'; + + dlen = strlen(dir); + while (dir[dlen-1] == '/') + dir[--dlen] = '\0'; /* Strip trailing '/' */ + + if (dlen + 1 + cmdlen >= sizeof(fname)) { + ewprintf("path too long"); + return (FALSE); + } + snprintf(fname, sizeof(fname), "%s/%s", dir, cmd); + if(access(fname, F_OK) == 0) { + found = TRUE; + break; + } + } + if (found) { + if (access(fname, X_OK) == -1) { + ewprintf("%s cannot execute - Permission denied", cmd); + return (FALSE); + } else + return (TRUE); + } else { + ewprintf("%s not found", cmd); + return (FALSE); + } + free(path); + return (FALSE); +} + +/* + * Create a socketpair, fork and execl cmd passed. STDIN, STDOUT + * and STDERR of child process are redirected to socket. + */ +int +pipeio(const char* const cmd) +{ + int s[2]; + char *shellp; + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s) == -1) { + ewprintf("socketpair error"); + return (FALSE); + } + switch(fork()) { + case -1: + ewprintf("Can't fork"); + return (FALSE); + case 0: + /* Child process */ + close(s[0]); + if (dup2(s[1], STDIN_FILENO) == -1) + _exit(1); + if (dup2(s[1], STDOUT_FILENO) == -1) + _exit(1); + if (dup2(s[1], STDERR_FILENO) == -1) + _exit(1); + if ((shellp = getenv("SHELL")) == NULL) + _exit(1); + execl(shellp, "sh", "-c", cmd, (char *)NULL); + _exit(1); + default: + /* Parent process */ + close(s[1]); + return iomux(s[0]); + } + return (FALSE); +} + +/* + * Multiplex read, write on socket fd passed. First get the region, + * find/create *Shell Command Output* buffer and clear it's contents. + * Poll on the fd for both read and write readiness. + */ +int +iomux(int fd) +{ + struct region region; + struct buffer *bp; + struct pollfd pfd[1]; + int nfds; + + if (getregion(®ion) != TRUE) + return (FALSE); + + /* There is nothing to write if r_size is zero + * but the cmd's output should be read so shutdown + * the socket for writing only. + */ + if (region.r_size == 0) + shutdown(fd, SHUT_WR); + + bp = bfind("*Shell Command Output*", TRUE); + bp->b_flag |= BFREADONLY; + if (bclear(bp) != TRUE) + return (FALSE); + + pfd[0].fd = fd; + pfd[0].events = POLLIN | POLLOUT; + while ((nfds = poll(pfd, 1, TIMEOUT)) != -1 || + (pfd[0].revents & (POLLERR | POLLHUP | POLLNVAL))) { + if (pfd[0].revents & POLLOUT && region.r_size > 0) { + pwriteout(fd, ®ion); + region.r_linep = lforw(region.r_linep); + } else if (pfd[0].revents & POLLIN) + if (preadin(fd, bp) == FALSE) + break; + } + close(fd); + /* In case if last line doesn't have a '\n' add the leftover + * characters to buffer. + */ + if (leftover[0] != '\0') + addline(bp, leftover); + if (nfds == 0) { + ewprintf("poll timed out"); + return (FALSE); + } else if (nfds == -1) { + ewprintf("poll error"); + return (FALSE); + } + return (popbuftop(bp, WNONE)); +} + +/* + * Write each line from region to fd. Once done shutdown the + * write end. + */ +void +pwriteout(int fd, struct region *region) +{ + struct line *linep; + char *l; + int len, loffs; + + linep = region->r_linep; + loffs = region->r_offset; + /* Reset the offset, needed only for first line of region. */ + region->r_offset = 0; + if ((l = get_line(linep, &len)) == NULL) + return; + /* Take care of region's offset on first and last lines */ + len -= loffs; + len = (len < region->r_size) ? len : region->r_size; + if ((send(fd, l + loffs, len, MSG_NOSIGNAL) == -1) + && (errno == EPIPE)) + region->r_size = -1; + else + region->r_size -= len; + if (region->r_size <= 0) + shutdown(fd, SHUT_WR); + free(l); +} + +/* + * Since struct line doesn't have a terminating '\n', + * make a copy and append '\n'. + */ +char * +get_line(struct line *ln, int *lenp) +{ + int len; + char *line; + + len = llength(ln); + if (len == INT_MAX) + return (NULL); + + if ((line = malloc(len + 1)) == NULL) + return (NULL); + + (void)memmove(line, ltext(ln), len); + line[len] = '\n'; + *lenp = len + 1; + return (line); +} + +/* + * Read some data from socket fd, break on '\n' and add + * to buffer. If couldn't break on newline hold leftover + * characters and append in next iteration. + */ +int +preadin(int fd, struct buffer *bp) +{ + char buf[BUFSIZ], *p, *q; + + bzero(buf, BUFSIZ); + if (read(fd, buf, BUFSIZ - 2) == 0) + return (FALSE); + buf[BUFSIZ - 1] = '\0'; + p = q = buf; + if (leftover[0] != '\0' && ((q = strchr(p, '\n')) != NULL)) { + *q++ = '\0'; + if (strlcat(leftover, p, sizeof(leftover)) >= sizeof(leftover)) { + ewprintf("line too long"); + return (FALSE); + } + addline(bp, leftover); + leftover[0] = '\0'; + p = q; + } + while ((q = strchr(p, '\n')) != NULL) { + *q++ = '\0'; + addline(bp, p); + p = q; + } + if (strlcpy(leftover, p, sizeof(leftover)) >= sizeof(leftover)) { + ewprintf("line too long"); + return (FALSE); + } + return (TRUE); }