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(&region) != 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, &region);
+                       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);
 }

Reply via email to