diff --git a/lib/util.h b/lib/util.h index efe386bee..07a351a9a 100644 --- a/lib/util.h +++ b/lib/util.h @@ -87,11 +87,12 @@ typedef struct 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. + /* current position in @buf (used by mc_pstream_get_string()) */ + size_t pos; + /* positive: length of data in buf; + * MC_PIPE_STREAM_EOF: EOF of fd; + * MC_PIPE_STREAM_UNREAD: there was not read from fd; + * MC_PIPE_ERROR_READ: reading error from fd. */ ssize_t len; /* whether buf is null-terminated or not */ @@ -207,21 +208,18 @@ const char *get_owner (uid_t uid); /* Returns a copy of *s until a \n is found and is below top */ const char *extract_line (const char *s, const char *top); -/* Error pipes */ -void open_error_pipe (void); -void check_error_pipe (void); -int close_error_pipe (int error, const char *text); - /* Process spawning */ int my_system (int flags, const char *shell, const char *command); 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); +mc_pipe_t *mc_popen (const char *command, gboolean read_out, gboolean read_err, GError ** error); void mc_pread (mc_pipe_t * p, GError ** error); void mc_pclose (mc_pipe_t * p, GError ** error); +GString *mc_pstream_get_string (mc_pipe_stream_t * ps); + void my_exit (int status); void save_stop_handler (void); diff --git a/lib/utilunix.c b/lib/utilunix.c index d1dc91734..865c13813 100644 --- a/lib/utilunix.c +++ b/lib/utilunix.c @@ -53,12 +53,6 @@ #include #endif #include -#ifdef HAVE_SYS_IOCTL_H -#include -#endif -#ifdef HAVE_GET_PROCESS_STATS -#include -#endif #include #include @@ -117,9 +111,6 @@ typedef struct static int_cache uid_cache[UID_CACHE_SIZE]; static int_cache gid_cache[GID_CACHE_SIZE]; -static int error_pipe[2]; /* File descriptors of error pipe */ -static int old_error; /* File descriptor of old standard error */ - /* --------------------------------------------------------------------------------------------- */ /*** file scope functions ************************************************************************/ /* --------------------------------------------------------------------------------------------- */ @@ -277,6 +268,8 @@ mc_pread_stream (mc_pipe_stream_t * ps, const fd_set * fds) if (ps->null_term) ps->buf[(size_t) ps->len] = '\0'; } + + ps->pos = 0; } /* --------------------------------------------------------------------------------------------- */ @@ -503,13 +496,15 @@ my_systemv_flags (int flags, const char *command, char *const argv[]) * Create pipe and run child process. * * @parameter command command line of child process + * @parameter read_out do or don't read the stdout of child process + * @parameter read_err do or don't read the stderr 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_popen (const char *command, gboolean read_out, gboolean read_err, GError ** error) { mc_pipe_t *p; const char *const argv[] = { "/bin/sh", "sh", "-c", command, NULL }; @@ -522,9 +517,13 @@ mc_popen (const char *command, GError ** error) goto ret_err; } + p->out.fd = -1; + p->err.fd = -1; + if (!g_spawn_async_with_pipes - (NULL, (gchar **) argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_FILE_AND_ARGV_ZERO, - NULL, NULL, &p->child_pid, NULL, &p->out.fd, &p->err.fd, error)) + (NULL, (gchar **) argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_FILE_AND_ARGV_ZERO, NULL, + NULL, &p->child_pid, NULL, read_out ? &p->out.fd : NULL, read_err ? &p->err.fd : NULL, + error)) { mc_replace_error (error, MC_PIPE_ERROR_CREATE_PIPE_STREAM, "%s", _("Cannot create pipe streams")); @@ -553,9 +552,9 @@ mc_popen (const char *command, GError ** error) * @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. + * + * Before read, p->xxx.len is an input. It defines the number of data to read. + * Should not be greater than MC_PIPE_BUFSIZE. * * 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; @@ -577,8 +576,8 @@ mc_pread (mc_pipe_t * p, GError ** error) 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; + read_out = p->out.fd >= 0; + read_err = p->err.fd >= 0; if (!read_out && !read_err) { @@ -622,6 +621,50 @@ mc_pread (mc_pipe_t * p, GError ** error) p->err.len = MC_PIPE_STREAM_UNREAD; } +/* --------------------------------------------------------------------------------------------- */ +/** + * Reads a line from @stream. Reading stops after an EOL or a newline. If a newline is read, + * it is appended to the line. + * + * @stream mc_pipe_stream_t object + * + * @return newly created GString or NULL in case of EOL; + */ + +GString * +mc_pstream_get_string (mc_pipe_stream_t * ps) +{ + char *s; + size_t size, i; + gboolean escape = FALSE; + + g_return_val_if_fail (ps != NULL, NULL); + + if (ps->len < 0) + return NULL; + + size = ps->len - ps->pos; + + if (size == 0) + return NULL; + + s = ps->buf + ps->pos; + + if (s[0] == '\0') + return NULL; + + /* find '\0' or unescaped '\n' */ + for (i = 0; i < size && !(s[i] == '\0' || (s[i] == '\n' && !escape)); i++) + escape = s[i] == '\\' ? !escape : FALSE; + + if (i != size && s[i] == '\n') + i++; + + ps->pos += i; + + return g_string_new_len (s, i); +} + /* --------------------------------------------------------------------------------------------- */ /** * Close pipe and destroy pipe descriptor. @@ -705,121 +748,6 @@ tilde_expand (const char *directory) return g_strconcat (passwd->pw_dir, PATH_SEP_STR, q, (char *) NULL); } -/* --------------------------------------------------------------------------------------------- */ -/** - * Creates a pipe to hold standard error for a later analysis. - * The pipe can hold 4096 bytes. Make sure no more is written - * or a deadlock might occur. - */ - -void -open_error_pipe (void) -{ - int error_fd = -1; - - if (pipe (error_pipe) < 0) - message (D_NORMAL, _("Warning"), _("Pipe failed")); - - old_error = dup (STDERR_FILENO); - if (old_error < 0 || close (STDERR_FILENO) != 0 - || (error_fd = dup (error_pipe[1])) != STDERR_FILENO) - { - message (D_NORMAL, _("Warning"), _("Dup failed")); - - close (error_pipe[0]); - error_pipe[0] = -1; - } - else - { - /* - * Settng stderr in nonblocking mode as we close it earlier, than - * program stops. We try to read some error at program startup, - * but we should not block on it. - * - * TODO: make piped stdin/stderr poll()/select()able to get rid - * of following hack. - */ - int fd_flags; - - fd_flags = fcntl (error_pipe[0], F_GETFL, NULL); - if (fd_flags != -1) - { - fd_flags |= O_NONBLOCK; - - if (fcntl (error_pipe[0], F_SETFL, fd_flags) == -1) - { - /* TODO: handle it somehow */ - } - } - } - if (error_fd >= 0) - close (error_fd); - /* we never write there */ - close (error_pipe[1]); - error_pipe[1] = -1; -} - -/* --------------------------------------------------------------------------------------------- */ -/** - * Close a pipe - * - * @param error '-1' - ignore errors, '0' - display warning, '1' - display error - * @param text is prepended to the error message from the pipe - * - * @return not 0 if an error was displayed - */ - -int -close_error_pipe (int error, const char *text) -{ - const char *title; - char msg[MAX_PIPE_SIZE]; - int len = 0; - - /* already closed */ - if (error_pipe[0] == -1) - return 0; - - if (error < 0 || (error > 0 && (error & D_ERROR) != 0)) - title = MSG_ERROR; - else - title = _("Warning"); - if (old_error >= 0) - { - if (dup2 (old_error, STDERR_FILENO) == -1) - { - if (error < 0) - error = D_ERROR; - - message (error, MSG_ERROR, _("Error dup'ing old error pipe")); - return 1; - } - close (old_error); - len = read (error_pipe[0], msg, sizeof (msg) - 1); - - if (len >= 0) - msg[len] = 0; - close (error_pipe[0]); - error_pipe[0] = -1; - } - if (error < 0) - return 0; /* Just ignore error message */ - if (text == NULL) - { - if (len <= 0) - return 0; /* Nothing to show */ - - /* Show message from pipe */ - message (error, title, "%s", msg); - } - else - { - /* Show given text and possible message from pipe */ - message (error, title, "%s\n%s", text, msg); - } - return 1; -} - /* --------------------------------------------------------------------------------------------- */ /** * Canonicalize path, and return a new path. Do everything in place. diff --git a/src/clipboard.c b/src/clipboard.c index c277ae66b..e20a312f2 100644 --- a/src/clipboard.c +++ b/src/clipboard.c @@ -109,7 +109,7 @@ clipboard_file_from_ext_clip (const gchar * event_group_name, const gchar * even if (clipboard_paste_path == NULL || clipboard_paste_path[0] == '\0') return TRUE; - p = mc_popen (clipboard_paste_path, NULL); + p = mc_popen (clipboard_paste_path, TRUE, TRUE, NULL); if (p == NULL) return TRUE; /* don't show error message */ diff --git a/src/filemanager/panelize.c b/src/filemanager/panelize.c index 1fd59c4f9..ddc07508d 100644 --- a/src/filemanager/panelize.c +++ b/src/filemanager/panelize.c @@ -306,15 +306,18 @@ static void do_external_panelize (char *command) { dir_list *list = ¤t_panel->dir; - FILE *external; + mc_pipe_t *external; + GError *error = NULL; + GString *remain_file_name = NULL; - open_error_pipe (); - external = popen (command, "r"); + external = mc_popen (command, TRUE, TRUE, &error); if (external == NULL) { - close_error_pipe (D_ERROR, _("Cannot invoke command.")); + message (D_ERROR, _("External panelize"), "%s", error->message); + g_error_free (error); return; } + /* Clear the counters and the directory list */ panel_clean_dir (current_panel); @@ -324,48 +327,109 @@ do_external_panelize (char *command) while (TRUE) { - char line[MC_MAXPATHLEN]; - size_t len; - char *name; - gboolean link_to_dir, stale_link; - struct stat st; + GString *line; + gboolean ok; - clearerr (external); - if (fgets (line, sizeof (line), external) == NULL) + /* init buffers before call of mc_pread() */ + external->out.len = MC_PIPE_BUFSIZE; + external->err.len = MC_PIPE_BUFSIZE; + + mc_pread (external, &error); + + if (error != NULL) { - if (ferror (external) != 0 && errno == EINTR) - continue; + message (D_ERROR, MSG_ERROR, _("External panelize:\n%s"), error->message); + g_error_free (error); break; } - len = strlen (line); - if (line[len - 1] == '\n') - line[len - 1] = '\0'; - if (line[0] == '\0') - continue; + if (external->err.len > 0) + message (D_ERROR, MSG_ERROR, _("External panelize:\n%s"), external->err.buf); - name = line; - if (line[0] == '.' && IS_PATH_SEP (line[1])) - name += 2; - - if (!handle_path (name, &st, &link_to_dir, &stale_link)) - continue; - - if (!dir_list_append (list, name, &st, link_to_dir, stale_link)) + if (external->out.len == MC_PIPE_STREAM_EOF) break; - file_mark (current_panel, list->len - 1, 0); + if (external->out.len == 0) + continue; - if ((list->len & 31) == 0) - rotate_dash (TRUE); + if (external->out.len == MC_PIPE_ERROR_READ) + { + message (D_ERROR, MSG_ERROR, + _("External panelize:\nfailed to read data from child stdout:\n%s"), + unix_error_string (external->out.error)); + break; + } + + ok = TRUE; + + while (ok && (line = mc_pstream_get_string (&external->out)) != NULL) + { + char *name; + gboolean link_to_dir, stale_link; + struct stat st; + + /* handle a \n-separated file list */ + + if (line->str[line->len - 1] == '\n') + { + /* entire file name or last chunk */ + + g_string_truncate (line, line->len - 1); + + /* join filename chunks */ + if (remain_file_name != NULL) + { + g_string_append_len (remain_file_name, line->str, line->len); + g_string_free (line, TRUE); + line = remain_file_name; + remain_file_name = NULL; + } + } + else + { + /* first or middle chunk of file name */ + + if (remain_file_name == NULL) + remain_file_name = line; + else + { + g_string_append_len (remain_file_name, line->str, line->len); + g_string_free (line, TRUE); + } + + continue; + } + + name = line->str; + + if (name[0] == '.' && IS_PATH_SEP (name[1])) + name += 2; + + if (handle_path (name, &st, &link_to_dir, &stale_link)) + { + ok = dir_list_append (list, name, &st, link_to_dir, stale_link); + + if (ok) + { + file_mark (current_panel, list->len - 1, 0); + + if ((list->len & 31) == 0) + rotate_dash (TRUE); + } + } + + g_string_free (line, TRUE); + } } + if (remain_file_name != NULL) + g_string_free (remain_file_name, TRUE); + + mc_pclose (external, NULL); + current_panel->is_panelized = TRUE; panelize_absolutize_if_needed (current_panel); - if (pclose (external) < 0) - message (D_NORMAL, _("External panelize"), _("Pipe close failed")); - close_error_pipe (D_NORMAL, NULL); try_to_select (current_panel, NULL); panel_re_sort (current_panel); rotate_dash (FALSE); diff --git a/src/vfs/extfs/extfs.c b/src/vfs/extfs/extfs.c index e963ae7d3..3b4c98079 100644 --- a/src/vfs/extfs/extfs.c +++ b/src/vfs/extfs/extfs.c @@ -392,12 +392,128 @@ extfs_skip_leading_dotslash (char *s) /* --------------------------------------------------------------------------------------------- */ -static FILE * -extfs_open_archive (int fstype, const char *name, struct extfs_super_t **pparc) +static int +extfs_add_file (struct extfs_super_t *archive, const char *file_name) +{ + struct vfs_s_super *super = VFS_SUPER (archive); + struct stat hstat; + char *current_file_name = NULL, *current_link_name = NULL; + int ret = 0; + + if (vfs_parse_ls_lga (file_name, &hstat, ¤t_file_name, ¤t_link_name, NULL)) + { + char *cfn = current_file_name; + + if (*cfn != '\0') + { + struct vfs_s_entry *entry; + struct vfs_s_entry *pent = NULL; + struct vfs_s_inode *inode; + char *p, *q; + + cfn = extfs_skip_leading_dotslash (cfn); + if (IS_PATH_SEP (*cfn)) + cfn++; + p = strchr (cfn, '\0'); + if (p != cfn && IS_PATH_SEP (p[-1])) + p[-1] = '\0'; + p = strrchr (cfn, PATH_SEP); + if (p == NULL) + { + p = cfn; + q = strchr (cfn, '\0'); + } + else + { + *(p++) = '\0'; + q = cfn; + } + + if (*q != '\0') + { + pent = extfs_find_entry (super->root, q, FL_MKDIR); + if (pent == NULL) + { + ret = -1; + goto done; + } + } + + if (pent != NULL) + { + entry = extfs_entry_new (super->me, p, pent->ino); + entry->dir = pent->ino; + g_queue_push_tail (pent->ino->subdir, entry); + } + else + { + entry = extfs_entry_new (super->me, p, super->root); + entry->dir = super->root; + g_queue_push_tail (super->root->subdir, entry); + } + + if (!S_ISLNK (hstat.st_mode) && (current_link_name != NULL)) + { + pent = extfs_find_entry (super->root, current_link_name, FL_NONE); + if (pent == NULL) + { + ret = -1; + goto done; + } + + pent->ino->st.st_nlink++; + entry->ino = pent->ino; + } + else + { + struct stat st; + + memset (&st, 0, sizeof (st)); + st.st_ino = super->ino_usage++; + st.st_nlink = 1; + st.st_dev = archive->rdev; + st.st_mode = hstat.st_mode; +#ifdef HAVE_STRUCT_STAT_ST_RDEV + st.st_rdev = hstat.st_rdev; +#endif + st.st_uid = hstat.st_uid; + st.st_gid = hstat.st_gid; + st.st_size = hstat.st_size; + st.st_mtime = hstat.st_mtime; + st.st_atime = hstat.st_atime; + st.st_ctime = hstat.st_ctime; + + if (current_link_name == NULL && S_ISLNK (hstat.st_mode)) + st.st_mode &= ~S_IFLNK; /* You *DON'T* want to do this always */ + + inode = vfs_s_new_inode (super->me, super, &st); + inode->ent = entry; + entry->ino = inode; + + if (current_link_name != NULL && S_ISLNK (hstat.st_mode)) + { + inode->linkname = current_link_name; + current_link_name = NULL; + } + } + } + + done: + g_free (current_file_name); + g_free (current_link_name); + } + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static mc_pipe_t * +extfs_open_archive (int fstype, const char *name, struct extfs_super_t **pparc, GError ** error) { const extfs_plugin_info_t *info; static dev_t archive_counter = 0; - FILE *result = NULL; + mc_pipe_t *result = NULL; mode_t mode; char *cmd; struct stat mystat; @@ -432,12 +548,11 @@ extfs_open_archive (int fstype, const char *name, struct extfs_super_t **pparc) vfs_path_get_last_path_str (local_name_vpath) : tmp, (char *) NULL); g_free (tmp); - open_error_pipe (); - result = popen (cmd, "r"); + result = mc_popen (cmd, TRUE, TRUE, error); g_free (cmd); + if (result == NULL) { - close_error_pipe (D_ERROR, NULL); if (local_name_vpath != NULL) { mc_ungetlocalcopy (name_vpath, local_name_vpath, FALSE); @@ -446,10 +561,6 @@ extfs_open_archive (int fstype, const char *name, struct extfs_super_t **pparc) goto ret; } -#ifdef ___QNXNTO__ - setvbuf (result, NULL, _IONBF, 0); -#endif - current_archive = extfs_super_new (vfs_extfs_ops, name, local_name_vpath, fstype); current_archive->rdev = archive_counter++; vfs_path_free (local_name_vpath); @@ -486,120 +597,96 @@ extfs_open_archive (int fstype, const char *name, struct extfs_super_t **pparc) */ static int -extfs_read_archive (FILE * extfsd, struct extfs_super_t *current_archive) +extfs_read_archive (mc_pipe_t * pip, struct extfs_super_t *archive, GError ** error) { int ret = 0; - char *buffer; - struct vfs_s_super *super = VFS_SUPER (current_archive); + GString *buffer; + GString *err_msg = NULL; + GString *remain_file_name = NULL; - buffer = g_malloc (BUF_4K); - - while (fgets (buffer, BUF_4K, extfsd) != NULL) + while (ret != -1) { - struct stat hstat; - char *current_file_name = NULL, *current_link_name = NULL; + /* init buffers before call of mc_pread() */ + pip->out.len = MC_PIPE_BUFSIZE; + pip->err.len = MC_PIPE_BUFSIZE; - if (vfs_parse_ls_lga (buffer, &hstat, ¤t_file_name, ¤t_link_name, NULL)) + mc_pread (pip, error); + + if (*error != NULL) + return (-1); + + if (pip->err.len > 0) { - struct vfs_s_entry *entry, *pent = NULL; - struct vfs_s_inode *inode; - char *p, *q, *cfn = current_file_name; - - if (*cfn != '\0') + /* join errors/warnings */ + if (err_msg == NULL) + err_msg = g_string_new_len (pip->err.buf, pip->err.len); + else { - cfn = extfs_skip_leading_dotslash (cfn); - if (IS_PATH_SEP (*cfn)) - cfn++; - p = strchr (cfn, '\0'); - if (p != cfn && IS_PATH_SEP (p[-1])) - p[-1] = '\0'; - p = strrchr (cfn, PATH_SEP); - if (p == NULL) + if (err_msg->str[err_msg->len - 1] != '\n') + g_string_append_c (err_msg, '\n'); + g_string_append_len (err_msg, pip->err.buf, pip->err.len); + } + } + + if (pip->out.len == MC_PIPE_STREAM_EOF) + break; + + if (pip->out.len == 0) + continue; + + if (pip->out.len == MC_PIPE_ERROR_READ) + return (-1); + + while (ret != -1 && (buffer = mc_pstream_get_string (&pip->out)) != NULL) + { + /* handle a \n-separated file list */ + + if (buffer->str[buffer->len - 1] == '\n') + { + /* entire file name or last chunk */ + + g_string_truncate (buffer, buffer->len - 1); + + /* join filename chunks */ + if (remain_file_name != NULL) { - p = cfn; - q = strchr (cfn, '\0'); - } - else - { - *(p++) = '\0'; - q = cfn; - } - - if (*q != '\0') - { - pent = extfs_find_entry (super->root, q, FL_MKDIR); - if (pent == NULL) - { - ret = -1; - break; - } - } - - if (pent != NULL) - { - entry = extfs_entry_new (super->me, p, pent->ino); - entry->dir = pent->ino; - g_queue_push_tail (pent->ino->subdir, entry); - } - else - { - entry = extfs_entry_new (super->me, p, super->root); - entry->dir = super->root; - g_queue_push_tail (super->root->subdir, entry); - } - - if (!S_ISLNK (hstat.st_mode) && (current_link_name != NULL)) - { - pent = extfs_find_entry (super->root, current_link_name, FL_NONE); - if (pent == NULL) - { - ret = -1; - break; - } - - pent->ino->st.st_nlink++; - entry->ino = pent->ino; - } - else - { - struct stat st; - - memset (&st, 0, sizeof (st)); - st.st_ino = super->ino_usage++; - st.st_nlink = 1; - st.st_dev = current_archive->rdev; - st.st_mode = hstat.st_mode; -#ifdef HAVE_STRUCT_STAT_ST_RDEV - st.st_rdev = hstat.st_rdev; -#endif - st.st_uid = hstat.st_uid; - st.st_gid = hstat.st_gid; - st.st_size = hstat.st_size; - st.st_mtime = hstat.st_mtime; - st.st_atime = hstat.st_atime; - st.st_ctime = hstat.st_ctime; - - if (current_link_name == NULL && S_ISLNK (hstat.st_mode)) - st.st_mode &= ~S_IFLNK; /* You *DON'T* want to do this always */ - - inode = vfs_s_new_inode (super->me, super, &st); - inode->ent = entry; - entry->ino = inode; - - if (current_link_name != NULL && S_ISLNK (hstat.st_mode)) - { - inode->linkname = current_link_name; - current_link_name = NULL; - } + g_string_append_len (remain_file_name, buffer->str, buffer->len); + g_string_free (buffer, TRUE); + buffer = remain_file_name; + remain_file_name = NULL; } } + else + { + /* first or middle chunk of file name */ - g_free (current_file_name); - g_free (current_link_name); + if (remain_file_name == NULL) + remain_file_name = buffer; + else + { + g_string_append_len (remain_file_name, buffer->str, buffer->len); + g_string_free (buffer, TRUE); + } + + continue; + } + + ret = extfs_add_file (archive, buffer->str); + + g_string_free (buffer, TRUE); } } - g_free (buffer); + if (remain_file_name != NULL) + g_string_free (remain_file_name, TRUE); + + if (err_msg != NULL) + { + if (*error == NULL) + mc_propagate_error (error, 0, "%s", err_msg->str); + + g_string_free (err_msg, TRUE); + } return ret; } @@ -635,35 +722,39 @@ static int extfs_open_and_read_archive (int fstype, const char *name, struct extfs_super_t **archive) { int result = -1; - FILE *extfsd; struct extfs_super_t *a; + mc_pipe_t *pip; + GError *error = NULL; + + pip = extfs_open_archive (fstype, name, archive, &error); - extfsd = extfs_open_archive (fstype, name, archive); a = *archive; - if (extfsd == NULL) + if (pip == NULL) { const extfs_plugin_info_t *info; info = &g_array_index (extfs_plugins, extfs_plugin_info_t, fstype); - message (D_ERROR, MSG_ERROR, _("Cannot open %s archive\n%s"), info->prefix, name); - } - else if (extfs_read_archive (extfsd, a) != 0) - { - pclose (extfsd); - close_error_pipe (D_ERROR, _("Inconsistent extfs archive")); - } - else if (pclose (extfsd) != 0) - { - VFS_SUPER (a)->me->free (VFS_SUPER (a)); - close_error_pipe (D_ERROR, _("Inconsistent extfs archive")); + message (D_ERROR, MSG_ERROR, _("Cannot open %s archive\n%s:\n%s"), info->prefix, name, + error->message); + g_error_free (error); } else { - close_error_pipe (D_ERROR, NULL); - result = 0; + result = extfs_read_archive (pip, a, &error); + + if (result != 0) + VFS_SUPER (a)->me->free (VFS_SUPER (a)); + + if (error != NULL) + { + message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), error->message); + g_error_free (error); + } } + mc_pclose (pip, NULL); + return result; } @@ -827,7 +918,9 @@ extfs_cmd (const char *str_extfs_cmd, const struct extfs_super_t *archive, char *archive_name, *quoted_archive_name; const extfs_plugin_info_t *info; char *cmd; - int retval; + int retval = 0; + GError *error = NULL; + mc_pipe_t *pip; file = extfs_get_path_from_entry (entry); quoted_file = name_quote (file, FALSE); @@ -847,10 +940,29 @@ extfs_cmd (const char *str_extfs_cmd, const struct extfs_super_t *archive, g_free (quoted_localname); g_free (quoted_archive_name); - open_error_pipe (); - retval = my_system (EXECUTE_AS_SHELL, mc_global.shell->path, cmd); + /* don't read stdout */ + pip = mc_popen (cmd, FALSE, TRUE, &error); g_free (cmd); - close_error_pipe (D_ERROR, NULL); + + if (pip == NULL) + { + message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), error->message); + g_error_free (error); + return (-1); + } + + mc_pread (pip, &error); + if (error != NULL) + { + message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), error->message); + g_error_free (error); + retval = -1; + } + else if (pip->err.len > 0) + message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), pip->err.buf); + + mc_pclose (pip, NULL); + return retval; } diff --git a/src/vfs/sfs/sfs.c b/src/vfs/sfs/sfs.c index fafcf32e7..b0d70f46d 100644 --- a/src/vfs/sfs/sfs.c +++ b/src/vfs/sfs/sfs.c @@ -140,6 +140,8 @@ sfs_vfmake (const vfs_path_t * vpath, vfs_path_t * cache_vpath) vfs_path_t *pname; /* name of parent archive */ char *pqname; /* name of parent archive, quoted */ const vfs_path_element_t *path_element; + mc_pipe_t *pip; + GError *error = NULL; path_element = vfs_path_get_by_index (vpath, -1); pname = vfs_path_clone (vpath); @@ -216,14 +218,29 @@ sfs_vfmake (const vfs_path_t * vpath, vfs_path_t * cache_vpath) } g_free (pqname); - open_error_pipe (); - if (my_system (EXECUTE_AS_SHELL, "/bin/sh", pad)) + + /* don't read stdout */ + pip = mc_popen (pad, FALSE, TRUE, &error); + if (pip == NULL) { - close_error_pipe (D_ERROR, NULL); + message (D_ERROR, MSG_ERROR, _("SFS virtual file system:\n%s"), error->message); + g_error_free (error); return (-1); } - close_error_pipe (D_NORMAL, NULL); + mc_pread (pip, &error); + if (error != NULL) + { + message (D_ERROR, MSG_ERROR, _("SFS virtual file system:\n%s"), error->message); + g_error_free (error); + mc_pclose (pip, NULL); + return (-1); + } + + if (pip->err.len > 0) + message (D_ERROR, MSG_ERROR, _("SFS virtual file system:\n%s"), pip->err.buf); + + mc_pclose (pip, NULL); return 0; /* OK */ } diff --git a/src/viewer/datasource.c b/src/viewer/datasource.c index f63556bf5..939243b87 100644 --- a/src/viewer/datasource.c +++ b/src/viewer/datasource.c @@ -385,7 +385,7 @@ mcview_load_command_output (WView * view, const char *command) mcview_close_datasource (view); - p = mc_popen (command, &error); + p = mc_popen (command, TRUE, TRUE, &error); if (p == NULL) { mcview_display (view); diff --git a/tests/lib/Makefile.am b/tests/lib/Makefile.am index 9e8b3d76e..ec7634617 100644 --- a/tests/lib/Makefile.am +++ b/tests/lib/Makefile.am @@ -20,6 +20,7 @@ TESTS = \ mc_build_filename \ name_quote \ serialize \ + utilunix__mc_pstream_get_string \ utilunix__my_system_fork_fail \ utilunix__my_system_fork_child_shell \ utilunix__my_system_fork_child \ @@ -46,6 +47,9 @@ name_quote_SOURCES = \ serialize_SOURCES = \ serialize.c +utilunix__mc_pstream_get_string_SOURCES = \ + utilunix__mc_pstream_get_string.c + utilunix__my_system_fork_fail_SOURCES = \ utilunix__my_system-fork_fail.c diff --git a/tests/lib/utilunix__mc_pstream_get_string.c b/tests/lib/utilunix__mc_pstream_get_string.c new file mode 100644 index 000000000..a942d1ee4 --- /dev/null +++ b/tests/lib/utilunix__mc_pstream_get_string.c @@ -0,0 +1,396 @@ +/* + lib - Read string from mc_pipe_stream + + Copyright (C) 2021 + Free Software Foundation, Inc. + + Written by: + Andrew Borodin , 2021 + + This file is part of the Midnight Commander. + + The Midnight Commander is free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + The Midnight Commander is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#define TEST_SUITE_NAME "/lib/util" + +#include "tests/mctest.h" + +#include "lib/util.h" + +/* --------------------------------------------------------------------------------------------- */ + +#define MAX_CHUNKS 8 + +/* --------------------------------------------------------------------------------------------- */ + +static mc_pipe_stream_t stream; + +static char etalon_long_file_list[BUF_1K]; +static size_t etalon_long_file_list_pos; + +/* --------------------------------------------------------------------------------------------- */ + +/* @Before */ +static void +setup (void) +{ +} + +/* @After */ +static void +teardown (void) +{ +} + +/* --------------------------------------------------------------------------------------------- */ + +/* @DataSource("data_source") */ +/* *INDENT-OFF* */ +static const struct data_source +{ + /* input */ + const char *buf; /* string to read */ + + /* output */ + int pos[MAX_CHUNKS]; /* ps.pos values */ + const char *str[MAX_CHUNKS]; /* chunks */ + size_t len[MAX_CHUNKS]; /* chunk lengths */ +} +data_source[] = +{ + /* 0 */ + { + .buf = "", + .pos = { 0 }, + .str = { "" }, + .len = { 0 } + }, + /* 1 */ + { + .buf = "\n", + .pos = { 0, 1 }, + .str = { "\n" }, + .len = { 1, 0 } + }, + /* 2 */ + { + .buf = "\\\n", + .pos = { 0, 2 }, + .str = { "\\\n" }, + .len = { 2, 0 } + }, + /* 3 */ + { + .buf = "\\\\\n", + .pos = { 0, 3 }, + .str = { "\\\\\n" }, + .len = { 3, 0 } + }, + /* 4 */ + { + .buf = "\\\\\\\n", + .pos = { 0, 4 }, + .str = { "\\\\\\\n" }, + .len = { 4, 0 } + }, + /* 5 */ + { + .buf = "\\\\\\\\\n", + .pos = { 0, 5 }, + .str = { "\\\\\\\\\n" }, + .len = { 5, 0 } + }, + /* 6 */ + { + .buf = "12345", + .pos = { 0, 5 }, + .str = { "12345" }, + .len = { 5, 0 } + }, + /* 7 */ + { + .buf = "12345\n", + .pos = { 0, 6 }, + .str = { "12345\n" }, + .len = { 6, 0 } + }, + /* 8 */ + { + .buf = "12345\\\n", + .pos = { 0, 7 }, + .str = { "12345\\\n" }, + .len = { 7, 0 } + }, + /* 9 */ + { + .buf = "12345\\\\\n", + .pos = { 0, 8 }, + .str = { "12345\\\\\n" }, + .len = { 8, 0 } + }, + /* 10 */ + { + .buf = "12345\nabcd", + .pos = { 0, 6, 10 }, + .str = { "12345\n", "abcd" }, + .len = { 6, 4, 0 } + }, + /* 11 */ + { + .buf = "12345\\\nabcd", + .pos = { 0, 11 }, + .str = { "12345\\\nabcd" }, + .len = { 11, 0 } + }, + /* 12 */ + { + .buf = "12345\\\\\nabcd", + .pos = { 0, 8, 12 }, + .str = { "12345\\\\\n", "abcd" }, + .len = { 8, 4, 0 } + }, + /* 13 */ + { + .buf = "12345\\\\\\\nabcd", + .pos = { 0, 13 }, + .str = { "12345\\\\\\\nabcd" }, + .len = { 13, 0 } + }, + /* 14 */ + { + .buf = "12345\\\\\\\\\nabcd", + .pos = { 0, 10, 14 }, + .str = { "12345\\\\\\\\\n", "abcd" }, + .len = { 10, 4, 0 } + }, + /* 15 */ + { + .buf = "12345\nabcd\n", + .pos = { 0, 6, 11 }, + .str = { "12345\n", "abcd\n" }, + .len = { 6, 5, 0 } + }, + /* 16 */ + { + .buf = "12345\nabcd\n~!@#$%^", + .pos = { 0, 6, 11, 18 }, + .str = { "12345\n", "abcd\n", "~!@#$%^" }, + .len = { 6, 5, 7, 0 } + }, + /* 17 */ + { + .buf = "12345\nabcd\n~!@#$%^\n", + .pos = { 0, 6, 11, 19 }, + .str = { "12345\n", "abcd\n", "~!@#$%^\n" }, + .len = { 6, 5, 8, 0 } + } +}; +/* *INDENT-ON* */ + +/* @Test(dataSource = "data_source") */ +/* *INDENT-OFF* */ +START_PARAMETRIZED_TEST (mc_pstream_get_string_test, data_source) +/* *INDENT-ON* */ +{ + /* given */ + int j = 0; + + /* when */ + memset (&stream, 0, sizeof (stream)); + stream.len = strlen (data->buf); + memmove (&stream.buf, data->buf, stream.len); + + /* then */ + do + { + GString *ret; + + mctest_assert_int_eq (stream.pos, data->pos[j]); + + ret = mc_pstream_get_string (&stream); + if (ret == NULL) + break; + + mctest_assert_int_eq (ret->len, data->len[j]); + mctest_assert_str_eq (ret->str, data->str[j]); + + g_string_free (ret, TRUE); + + j++; + } + while (TRUE); +} +/* *INDENT-OFF* */ +END_PARAMETRIZED_TEST +/* *INDENT-ON* */ + +/* --------------------------------------------------------------------------------------------- */ + +static mc_pipe_t * +test_mc_popen (void) +{ + mc_pipe_t *p; + + p = g_try_new0 (mc_pipe_t, 1); + /* make less than sizeof (etalon_long_file_list) */ + p->out.len = 128; + + etalon_long_file_list_pos = 0; + + return p; +} + +static void +test_mc_pread (mc_pipe_t * p) +{ + size_t len; + + p->out.pos = 0; + + if (etalon_long_file_list_pos >= sizeof (etalon_long_file_list)) + { + etalon_long_file_list_pos = sizeof (etalon_long_file_list); + p->out.len = MC_PIPE_STREAM_EOF; + return; + } + + len = sizeof (etalon_long_file_list) - etalon_long_file_list_pos; + len = MIN (len, (size_t) p->out.len); + memmove (p->out.buf, etalon_long_file_list + etalon_long_file_list_pos, len); + p->out.len = (ssize_t) len; + + etalon_long_file_list_pos += len; +} + +/* *INDENT-OFF* */ +START_TEST (mc_pstream_get_long_file_list_test) +/* *INDENT-ON* */ + +{ + /* given */ + GString *result_long_file_list = NULL; + mc_pipe_t *pip; + GString *remain_file_name = NULL; + + /* when */ + /* fill the list */ + memset (etalon_long_file_list, 'a', sizeof (etalon_long_file_list) - 1); + /* create an \n-separated list */ + etalon_long_file_list[5] = '\n'; + etalon_long_file_list[25] = '\n'; + etalon_long_file_list[50] = '\n'; + etalon_long_file_list[75] = '\n'; + etalon_long_file_list[127] = '\n'; + etalon_long_file_list[200] = '\n'; + etalon_long_file_list[310] = '\n'; + etalon_long_file_list[325] = '\n'; + etalon_long_file_list[360] = '\n'; + etalon_long_file_list[512] = '\n'; + etalon_long_file_list[701] = '\n'; + etalon_long_file_list[725] = '\n'; + etalon_long_file_list[800] = '\n'; + etalon_long_file_list[sizeof (etalon_long_file_list) - 2] = '\n'; + etalon_long_file_list[sizeof (etalon_long_file_list) - 1] = '\0'; + + /* then */ + /* read file list */ + pip = test_mc_popen (); + + while (TRUE) + { + GString *line; + + test_mc_pread (pip); + + if (pip->out.len == MC_PIPE_STREAM_EOF) + break; + + while ((line = mc_pstream_get_string (&pip->out)) != NULL) + { + /* handle an \n-separated file list */ + + if (line->str[line->len - 1] == '\n') + { + /* entire file name or last chunk */ + + g_string_truncate (line, line->len - 1); + + /* join filename chunks */ + if (remain_file_name != NULL) + { + g_string_append_len (remain_file_name, line->str, line->len); + g_string_free (line, TRUE); + line = remain_file_name; + remain_file_name = NULL; + } + } + else + { + /* first or middle chunk of file name */ + if (remain_file_name == NULL) + remain_file_name = line; + else + { + g_string_append_len (remain_file_name, line->str, line->len); + g_string_free (line, TRUE); + } + + line = NULL; + } + + /* collect file names to assemble the result string */ + if (line == NULL) + continue; + + if (result_long_file_list == NULL) + result_long_file_list = line; + else + { + g_string_append_len (result_long_file_list, line->str, line->len); + g_string_free (line, TRUE); + } + + g_string_append_c (result_long_file_list, '\n'); + } + } + + mctest_assert_str_eq (etalon_long_file_list, result_long_file_list->str); + g_string_free (result_long_file_list, TRUE); + +} +/* *INDENT-OFF* */ +END_TEST +/* *INDENT-ON* */ + +/* --------------------------------------------------------------------------------------------- */ + +int +main (void) +{ + TCase *tc_core; + + tc_core = tcase_create ("Core"); + + tcase_add_checked_fixture (tc_core, setup, teardown); + + /* Add new tests here: *************** */ + mctest_add_parameterized_test (tc_core, mc_pstream_get_string_test, data_source); + tcase_add_test (tc_core, mc_pstream_get_long_file_list_test); + /* *********************************** */ + + return mctest_run_all (tc_core); +} + +/* --------------------------------------------------------------------------------------------- */