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>
This commit is contained in:
Andrew Borodin 2014-07-03 09:58:57 +04:00
parent 5bcc246ac2
commit a31b78003e
2 changed files with 254 additions and 0 deletions

View File

@ -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);

View File

@ -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);
}
/* --------------------------------------------------------------------------------------------- */
/**