Browse Source

Ticket #2966: fix view of broken manpages.

Initial commit: create MC pipe class to capture stdout and stderr of
spawn processes.

Signed-off-by: Andrew Borodin <aborodin@vmail.ru>
Andrew Borodin 10 years ago
parent
commit
a31b78003e
2 changed files with 254 additions and 0 deletions
  1. 42 0
      lib/util.h
  2. 212 0
      lib/utilunix.c

+ 42 - 0
lib/util.h

@@ -27,6 +27,13 @@
 #define mc_return_if_error(mcerror) do { if (mcerror != NULL && *mcerror != NULL) return; } while (0)
 #define mc_return_val_if_error(mcerror, mcvalue) do { if (mcerror != NULL && *mcerror != NULL) return mcvalue; } while (0)
 
+#define MC_PIPE_BUFSIZE BUF_8K
+#define MC_PIPE_STREAM_EOF 0
+#define MC_PIPE_STREAM_UNREAD -1
+#define MC_PIPE_ERROR_CREATE_PIPE -2
+#define MC_PIPE_ERROR_PARSE_COMMAND -3
+#define MC_PIPE_ERROR_CREATE_PIPE_STREAM -4
+#define MC_PIPE_ERROR_READ -5
 
 /*** enums ***************************************************************************************/
 
@@ -51,6 +58,37 @@ enum compression_type
     COMPRESSION_XZ
 };
 
+/* stdout or stderr stream of child process */
+typedef struct
+{
+    /* file descriptor */
+    int fd;
+    /* data read from fd */
+    char buf[MC_PIPE_BUFSIZE];
+    /* positive: length of data in buf as before read as after;
+     * zero or negative before read: do not read drom fd;
+     * MC_PIPE_STREAM_EOF after read: EOF of fd;
+     * MC_PIPE_STREAM_UNREAD after read: there was not read from fd;
+     * MC_PIPE_ERROR_READ after read: reading error from fd.
+     */
+    ssize_t len;
+    /* whether buf is null-terminated or not */
+    gboolean null_term;
+    /* error code in case of len == MC_PIPE_ERROR_READ */
+    int error;
+} mc_pipe_stream_t;
+
+/* Pipe descriptor for child process */
+typedef struct
+{
+    /* PID of child process */
+    GPid child_pid;
+    /* stdout of child process */
+    mc_pipe_stream_t out;
+    /* stderr of child process */
+    mc_pipe_stream_t err;
+} mc_pipe_t;
+
 /*** structures declarations (and typedefs of structures)*****************************************/
 
 /* keys are set only during sorting */
@@ -157,6 +195,10 @@ int my_systeml (int flags, const char *shell, ...);
 int my_systemv (const char *command, char *const argv[]);
 int my_systemv_flags (int flags, const char *command, char *const argv[]);
 
+mc_pipe_t *mc_popen (const char *command, GError ** error);
+void mc_pread (mc_pipe_t * p, GError ** error);
+void mc_pclose (mc_pipe_t * p, GError ** error);
+
 void my_exit (int status);
 void save_stop_handler (void);
 

+ 212 - 0
lib/utilunix.c

@@ -50,6 +50,9 @@
 #endif
 #include <sys/types.h>
 #include <sys/stat.h>
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
 #include <sys/wait.h>
 #ifdef HAVE_SYS_IOCTL_H
 #include <sys/ioctl.h>
@@ -232,6 +235,50 @@ my_system_make_arg_array (int flags, const char *shell, char **execute_name)
     return args_array;
 }
 
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_pread_stream (mc_pipe_stream_t * ps, const fd_set * fds)
+{
+    size_t buf_len;
+    ssize_t read_len;
+
+    if (!FD_ISSET (ps->fd, fds))
+    {
+        ps->len = MC_PIPE_STREAM_UNREAD;
+        return;
+    }
+
+    buf_len = (size_t) ps->len;
+
+    if (buf_len >= MC_PIPE_BUFSIZE)
+        buf_len = ps->null_term ? MC_PIPE_BUFSIZE - 1 : MC_PIPE_BUFSIZE;
+
+    do
+    {
+        read_len = read (ps->fd, ps->buf, buf_len);
+    }
+    while (read_len < 0 && errno == EINTR);
+
+    if (read_len < 0)
+    {
+        /* reading error */
+        ps->len = MC_PIPE_ERROR_READ;
+        ps->error = errno;
+    }
+    else if (read_len == 0)
+        /* EOF */
+        ps->len = MC_PIPE_STREAM_EOF;
+    else
+    {
+        /* success */
+        ps->len = read_len;
+
+        if (ps->null_term)
+            ps->buf[(size_t) ps->len] = '\0';
+    }
+}
+
 /* --------------------------------------------------------------------------------------------- */
 /*** public functions ****************************************************************************/
 /* --------------------------------------------------------------------------------------------- */
@@ -450,6 +497,171 @@ my_systemv_flags (int flags, const char *command, char *const argv[])
     return status;
 }
 
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create pipe and run child process.
+ *
+ * @parameter command command line of child process
+ * @paremeter error contains pointer to object to handle error code and message
+ *
+ * @return newly created object of mc_pipe_t class in success, NULL otherwise
+ */
+
+mc_pipe_t *
+mc_popen (const char *command, GError ** error)
+{
+    mc_pipe_t *p;
+    char **argv;
+
+    p = g_try_new (mc_pipe_t, 1);
+    if (p == NULL)
+    {
+        mc_replace_error (error, MC_PIPE_ERROR_CREATE_PIPE, "%s",
+                          _("Cannot create pipe descriptor"));
+        goto ret_err;
+    }
+
+    if (!g_shell_parse_argv (command, NULL, &argv, error))
+    {
+        mc_replace_error (error, MC_PIPE_ERROR_PARSE_COMMAND, "%s",
+                          _("Cannot parse command for pipe"));
+        goto ret_err;
+    }
+
+    if (!g_spawn_async_with_pipes (NULL, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL,
+                                   &p->child_pid, NULL, &p->out.fd, &p->err.fd, error))
+    {
+        mc_replace_error (error, MC_PIPE_ERROR_CREATE_PIPE_STREAM, "%s",
+                          _("Cannot create pipe streams"));
+        goto ret_err;
+    }
+
+    g_strfreev (argv);
+
+    p->out.buf[0] = '\0';
+    p->out.len = MC_PIPE_BUFSIZE;
+    p->out.null_term = FALSE;
+
+    p->err.buf[0] = '\0';
+    p->err.len = MC_PIPE_BUFSIZE;
+    p->err.null_term = FALSE;
+
+    return p;
+
+  ret_err:
+    g_free (p);
+    return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Read stdout and stderr of pipe asynchronously.
+ *
+ * @parameter p pipe descriptor
+ *
+ * The lengths of read data contain in p->out.len and p->err.len.
+ * Before read, p->xxx.len is an input:
+ *   p->xxx.len > 0:  do read stream p->xxx and store data in p->xxx.buf;
+ *   p->xxx.len <= 0: do not read stream p->xxx.
+ *
+ * After read, p->xxx.len is an output and contains the following:
+ *   p->xxx.len > 0: an actual length of read data stored in p->xxx.buf;
+ *   p->xxx.len == MC_PIPE_STREAM_EOF: EOF of stream p->xxx;
+ *   p->xxx.len == MC_PIPE_STREAM_UNREAD: stream p->xxx was not read;
+ *   p->xxx.len == MC_PIPE_ERROR_READ: reading error, and p->xxx.errno is set appropriately.
+ *
+ * @paremeter error contains pointer to object to handle error code and message
+ */
+
+void
+mc_pread (mc_pipe_t * p, GError ** error)
+{
+    gboolean read_out, read_err;
+    fd_set fds;
+    int maxfd = 0;
+    int res;
+
+    if (error != NULL)
+        *error = NULL;
+
+    read_out = p->out.fd >= 0 && p->out.len > 0;
+    read_err = p->err.fd >= 0 && p->err.len > 0;
+
+    if (!read_out && !read_err)
+    {
+        p->out.len = MC_PIPE_STREAM_UNREAD;
+        p->err.len = MC_PIPE_STREAM_UNREAD;
+        return;
+    }
+
+    FD_ZERO (&fds);
+    if (read_out)
+    {
+        FD_SET (p->out.fd, &fds);
+        maxfd = p->out.fd;
+    }
+
+    if (read_err)
+    {
+        FD_SET (p->err.fd, &fds);
+        maxfd = max (maxfd, p->err.fd);
+    }
+
+    /* no timeout */
+    res = select (maxfd + 1, &fds, NULL, NULL, NULL);
+    if (res < 0 && errno != EINTR)
+    {
+        mc_propagate_error (error, MC_PIPE_ERROR_READ,
+                            _
+                            ("Unexpected error in select() reading data from a child process:\n%s"),
+                            unix_error_string (errno));
+        return;
+    }
+
+    if (read_out)
+        mc_pread_stream (&p->out, &fds);
+    else
+        p->out.len = MC_PIPE_STREAM_UNREAD;
+
+    if (read_err)
+        mc_pread_stream (&p->err, &fds);
+    else
+        p->err.len = MC_PIPE_STREAM_UNREAD;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Close pipe and destroy pipe descriptor.
+ *
+ * @paremeter p pipe descriptor
+ * @paremeter error contains pointer to object to handle error code and message
+ */
+
+void
+mc_pclose (mc_pipe_t * p, GError ** error)
+{
+    int res;
+
+    if (p->out.fd >= 0)
+        res = close (p->out.fd);
+    if (p->err.fd >= 0)
+        res = close (p->err.fd);
+
+    do
+    {
+        int status;
+
+        res = waitpid (p->child_pid, &status, 0);
+    }
+    while (res < 0 && errno == EINTR);
+
+    if (res < 0)
+        mc_replace_error (error, MC_PIPE_ERROR_READ, _("Unexpected error in waitpid():\n%s"),
+                          unix_error_string (errno));
+
+    g_free (p);
+}
+
 /* --------------------------------------------------------------------------------------------- */
 
 /**