diff --git a/lib/util.h b/lib/util.h index 26ba19005..213f7d8fa 100644 --- a/lib/util.h +++ b/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); diff --git a/lib/utilunix.c b/lib/utilunix.c index 338bf2de7..13a2c501c 100644 --- a/lib/utilunix.c +++ b/lib/utilunix.c @@ -50,6 +50,9 @@ #endif #include #include +#ifdef HAVE_SYS_SELECT_H +#include +#endif #include #ifdef HAVE_SYS_IOCTL_H #include @@ -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); +} + /* --------------------------------------------------------------------------------------------- */ /**