|
|
6bc3c0 |
From 592820b7a4300cfdc4f85ecd9548f7c2321689fc Mon Sep 17 00:00:00 2001
|
|
|
6bc3c0 |
From: Tomas Bzatek <tbzatek@redhat.com>
|
|
|
6bc3c0 |
Date: Wed, 16 Sep 2020 17:45:07 +0200
|
|
|
6bc3c0 |
Subject: [PATCH 1/5] exec: Fix polling for stdout and stderr
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
The condition of having both the stdout and the stderr fds ready
|
|
|
6bc3c0 |
may never be satisfied in the case of a full stdout buffer waiting
|
|
|
6bc3c0 |
to be read with no output on stderr yet while both fds being still
|
|
|
6bc3c0 |
open. In such case the old code got stuck in an endless loop and
|
|
|
6bc3c0 |
the spawned process being stuck on writing to stdout/stderr. Let's
|
|
|
6bc3c0 |
read data from any fd once available and only react on EOF/HUP.
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
This change also makes use of POSIX poll() instead of g_poll()
|
|
|
6bc3c0 |
as it's more clear what the return values mean - Glib docs are
|
|
|
6bc3c0 |
vague in this regard and one can only guess.
|
|
|
6bc3c0 |
---
|
|
|
6bc3c0 |
src/utils/exec.c | 32 ++++++++++++++++++--------------
|
|
|
6bc3c0 |
1 file changed, 18 insertions(+), 14 deletions(-)
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
diff --git a/src/utils/exec.c b/src/utils/exec.c
|
|
|
6bc3c0 |
index 37bd960..ebbcaf2 100644
|
|
|
6bc3c0 |
--- a/src/utils/exec.c
|
|
|
6bc3c0 |
+++ b/src/utils/exec.c
|
|
|
6bc3c0 |
@@ -22,6 +22,7 @@
|
|
|
6bc3c0 |
#include "extra_arg.h"
|
|
|
6bc3c0 |
#include <syslog.h>
|
|
|
6bc3c0 |
#include <stdlib.h>
|
|
|
6bc3c0 |
+#include <poll.h>
|
|
|
6bc3c0 |
#include <errno.h>
|
|
|
6bc3c0 |
#include <sys/types.h>
|
|
|
6bc3c0 |
#include <sys/wait.h>
|
|
|
6bc3c0 |
@@ -293,7 +294,7 @@ static gboolean _utils_exec_and_report_progress (const gchar **argv, const BDExt
|
|
|
6bc3c0 |
gint poll_status = 0;
|
|
|
6bc3c0 |
guint i = 0;
|
|
|
6bc3c0 |
guint8 completion = 0;
|
|
|
6bc3c0 |
- GPollFD fds[2] = {ZERO_INIT, ZERO_INIT};
|
|
|
6bc3c0 |
+ struct pollfd fds[2] = { ZERO_INIT, ZERO_INIT };
|
|
|
6bc3c0 |
gboolean out_done = FALSE;
|
|
|
6bc3c0 |
gboolean err_done = FALSE;
|
|
|
6bc3c0 |
GString *stdout_data = g_string_new (NULL);
|
|
|
6bc3c0 |
@@ -360,13 +361,16 @@ static gboolean _utils_exec_and_report_progress (const gchar **argv, const BDExt
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
fds[0].fd = out_fd;
|
|
|
6bc3c0 |
fds[1].fd = err_fd;
|
|
|
6bc3c0 |
- fds[0].events = G_IO_IN | G_IO_HUP | G_IO_ERR;
|
|
|
6bc3c0 |
- fds[1].events = G_IO_IN | G_IO_HUP | G_IO_ERR;
|
|
|
6bc3c0 |
+ fds[0].events = POLLIN | POLLHUP | POLLERR;
|
|
|
6bc3c0 |
+ fds[1].events = POLLIN | POLLHUP | POLLERR;
|
|
|
6bc3c0 |
while (!out_done || !err_done) {
|
|
|
6bc3c0 |
- poll_status = g_poll (fds, 2, -1);
|
|
|
6bc3c0 |
+ poll_status = poll (fds, 2, -1);
|
|
|
6bc3c0 |
+ g_warn_if_fail (poll_status != 0); /* no timeout specified, zero should never be returned */
|
|
|
6bc3c0 |
if (poll_status < 0) {
|
|
|
6bc3c0 |
+ if (errno == EAGAIN || errno == EINTR)
|
|
|
6bc3c0 |
+ continue;
|
|
|
6bc3c0 |
g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED,
|
|
|
6bc3c0 |
- "Failed to poll output FDs.");
|
|
|
6bc3c0 |
+ "Failed to poll output FDs: %m");
|
|
|
6bc3c0 |
bd_utils_report_finished (progress_id, (*error)->message);
|
|
|
6bc3c0 |
g_io_channel_shutdown (out_pipe, FALSE, NULL);
|
|
|
6bc3c0 |
g_io_channel_unref (out_pipe);
|
|
|
6bc3c0 |
@@ -375,12 +379,9 @@ static gboolean _utils_exec_and_report_progress (const gchar **argv, const BDExt
|
|
|
6bc3c0 |
g_string_free (stdout_data, TRUE);
|
|
|
6bc3c0 |
g_string_free (stderr_data, TRUE);
|
|
|
6bc3c0 |
return FALSE;
|
|
|
6bc3c0 |
- } else if (poll_status != 2)
|
|
|
6bc3c0 |
- /* both revents fields were not filled yet */
|
|
|
6bc3c0 |
- continue;
|
|
|
6bc3c0 |
- if (!(fds[0].revents & G_IO_IN))
|
|
|
6bc3c0 |
- out_done = TRUE;
|
|
|
6bc3c0 |
- while (!out_done) {
|
|
|
6bc3c0 |
+ }
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ while (!out_done && (fds[0].revents & POLLIN)) {
|
|
|
6bc3c0 |
io_status = g_io_channel_read_line (out_pipe, &line, NULL, NULL, error);
|
|
|
6bc3c0 |
if (io_status == G_IO_STATUS_NORMAL) {
|
|
|
6bc3c0 |
if (prog_extract && prog_extract (line, &completion))
|
|
|
6bc3c0 |
@@ -401,9 +402,10 @@ static gboolean _utils_exec_and_report_progress (const gchar **argv, const BDExt
|
|
|
6bc3c0 |
return FALSE;
|
|
|
6bc3c0 |
}
|
|
|
6bc3c0 |
}
|
|
|
6bc3c0 |
- if (!(fds[1].revents & G_IO_IN))
|
|
|
6bc3c0 |
- err_done = TRUE;
|
|
|
6bc3c0 |
- while (!err_done) {
|
|
|
6bc3c0 |
+ if (fds[0].revents & POLLHUP || fds[0].revents & POLLERR || fds[0].revents & POLLNVAL)
|
|
|
6bc3c0 |
+ out_done = TRUE;
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ while (!err_done && (fds[1].revents & POLLIN)) {
|
|
|
6bc3c0 |
io_status = g_io_channel_read_line (err_pipe, &line, NULL, NULL, error);
|
|
|
6bc3c0 |
if (io_status == G_IO_STATUS_NORMAL) {
|
|
|
6bc3c0 |
g_string_append (stderr_data, line);
|
|
|
6bc3c0 |
@@ -421,6 +423,8 @@ static gboolean _utils_exec_and_report_progress (const gchar **argv, const BDExt
|
|
|
6bc3c0 |
return FALSE;
|
|
|
6bc3c0 |
}
|
|
|
6bc3c0 |
}
|
|
|
6bc3c0 |
+ if (fds[1].revents & POLLHUP || fds[1].revents & POLLERR || fds[1].revents & POLLNVAL)
|
|
|
6bc3c0 |
+ err_done = TRUE;
|
|
|
6bc3c0 |
}
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
child_ret = waitpid (pid, &status, 0);
|
|
|
6bc3c0 |
--
|
|
|
6bc3c0 |
2.26.2
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
From 3025deda9ab670a454bfe373166e49f2acd1c151 Mon Sep 17 00:00:00 2001
|
|
|
6bc3c0 |
From: Tomas Bzatek <tbzatek@redhat.com>
|
|
|
6bc3c0 |
Date: Fri, 25 Sep 2020 18:19:46 +0200
|
|
|
6bc3c0 |
Subject: [PATCH 2/5] exec: Use non-blocking read and process the buffer
|
|
|
6bc3c0 |
manually
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
Waiting for data or a newline character on one fd may create a deadlock
|
|
|
6bc3c0 |
while the other fd is being filled with data, exhausting the pipe buffer.
|
|
|
6bc3c0 |
Setting both stdout and stderr fds non-blocking allows us to get indication
|
|
|
6bc3c0 |
of an empty pipe, continuing with the read cycle over remaining watched fds.
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
This also gets rid of GIOChannel as no extended functionality like GSource
|
|
|
6bc3c0 |
notifications were used, degrading GIOChannel in a simple GObject wrapper
|
|
|
6bc3c0 |
over an fd with just a convenience read_line method that we had to get rid of
|
|
|
6bc3c0 |
anyway. Let's use standard POSIX calls and split the read buffer manually
|
|
|
6bc3c0 |
by matching the newline character. NULL bytes should be handled gracefully
|
|
|
6bc3c0 |
however the stack higher up is not ready for that anyway.
|
|
|
6bc3c0 |
---
|
|
|
6bc3c0 |
src/utils/exec.c | 277 +++++++++++++++++++++++++++--------------------
|
|
|
6bc3c0 |
1 file changed, 159 insertions(+), 118 deletions(-)
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
diff --git a/src/utils/exec.c b/src/utils/exec.c
|
|
|
6bc3c0 |
index ebbcaf2..317fb55 100644
|
|
|
6bc3c0 |
--- a/src/utils/exec.c
|
|
|
6bc3c0 |
+++ b/src/utils/exec.c
|
|
|
6bc3c0 |
@@ -23,6 +23,7 @@
|
|
|
6bc3c0 |
#include <syslog.h>
|
|
|
6bc3c0 |
#include <stdlib.h>
|
|
|
6bc3c0 |
#include <poll.h>
|
|
|
6bc3c0 |
+#include <fcntl.h>
|
|
|
6bc3c0 |
#include <errno.h>
|
|
|
6bc3c0 |
#include <sys/types.h>
|
|
|
6bc3c0 |
#include <sys/wait.h>
|
|
|
6bc3c0 |
@@ -272,6 +273,87 @@ gboolean bd_utils_exec_and_report_status_error (const gchar **argv, const BDExtr
|
|
|
6bc3c0 |
return TRUE;
|
|
|
6bc3c0 |
}
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
+/* buffer size in bytes used to read from stdout and stderr */
|
|
|
6bc3c0 |
+#define _EXEC_BUF_SIZE 64*1024
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+/* similar to g_strstr_len() yet treats 'null' byte as @needle. */
|
|
|
6bc3c0 |
+static gchar *bd_strchr_len_null (const gchar *haystack, gssize haystack_len, const gchar needle) {
|
|
|
6bc3c0 |
+ gchar *ret;
|
|
|
6bc3c0 |
+ gchar *ret_null;
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ ret = memchr (haystack, needle, haystack_len);
|
|
|
6bc3c0 |
+ ret_null = memchr (haystack, 0, haystack_len);
|
|
|
6bc3c0 |
+ if (ret && ret_null)
|
|
|
6bc3c0 |
+ return MIN (ret, ret_null);
|
|
|
6bc3c0 |
+ else
|
|
|
6bc3c0 |
+ return MAX (ret, ret_null);
|
|
|
6bc3c0 |
+}
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+static gboolean
|
|
|
6bc3c0 |
+_process_fd_event (gint fd, struct pollfd *poll_fd, GString *read_buffer, GString *filtered_buffer, gsize *read_buffer_pos, gboolean *done,
|
|
|
6bc3c0 |
+ guint64 progress_id, guint8 *progress, BDUtilsProgExtract prog_extract, GError **error) {
|
|
|
6bc3c0 |
+ gchar buf[_EXEC_BUF_SIZE] = { 0 };
|
|
|
6bc3c0 |
+ ssize_t num_read;
|
|
|
6bc3c0 |
+ gchar *line;
|
|
|
6bc3c0 |
+ gchar *newline_pos;
|
|
|
6bc3c0 |
+ int errno_saved;
|
|
|
6bc3c0 |
+ gboolean eof = FALSE;
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ if (! *done && (poll_fd->revents & POLLIN)) {
|
|
|
6bc3c0 |
+ /* read until we get EOF (0) or error (-1), expecting EAGAIN */
|
|
|
6bc3c0 |
+ while ((num_read = read (fd, buf, _EXEC_BUF_SIZE)) > 0)
|
|
|
6bc3c0 |
+ g_string_append_len (read_buffer, buf, num_read);
|
|
|
6bc3c0 |
+ errno_saved = errno;
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ /* process the fresh data by lines */
|
|
|
6bc3c0 |
+ if (read_buffer->len > *read_buffer_pos) {
|
|
|
6bc3c0 |
+ gchar *buf_ptr;
|
|
|
6bc3c0 |
+ gsize buf_len;
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ while ((buf_ptr = read_buffer->str + *read_buffer_pos,
|
|
|
6bc3c0 |
+ buf_len = read_buffer->len - *read_buffer_pos,
|
|
|
6bc3c0 |
+ newline_pos = bd_strchr_len_null (buf_ptr, buf_len, '\n'))) {
|
|
|
6bc3c0 |
+ line = g_strndup (buf_ptr, newline_pos - buf_ptr + 1);
|
|
|
6bc3c0 |
+ if (prog_extract && prog_extract (line, progress))
|
|
|
6bc3c0 |
+ bd_utils_report_progress (progress_id, *progress, NULL);
|
|
|
6bc3c0 |
+ else
|
|
|
6bc3c0 |
+ g_string_append (filtered_buffer, line);
|
|
|
6bc3c0 |
+ g_free (line);
|
|
|
6bc3c0 |
+ *read_buffer_pos = newline_pos - read_buffer->str + 1;
|
|
|
6bc3c0 |
+ }
|
|
|
6bc3c0 |
+ }
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ /* read error */
|
|
|
6bc3c0 |
+ if (num_read < 0 && errno_saved != EAGAIN && errno_saved != EINTR) {
|
|
|
6bc3c0 |
+ g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED,
|
|
|
6bc3c0 |
+ "Error reading from pipe: %s", g_strerror (errno_saved));
|
|
|
6bc3c0 |
+ return FALSE;
|
|
|
6bc3c0 |
+ }
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ /* EOF */
|
|
|
6bc3c0 |
+ if (num_read == 0)
|
|
|
6bc3c0 |
+ eof = TRUE;
|
|
|
6bc3c0 |
+ }
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ if (poll_fd->revents & POLLHUP || poll_fd->revents & POLLERR || poll_fd->revents & POLLNVAL)
|
|
|
6bc3c0 |
+ eof = TRUE;
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ if (eof) {
|
|
|
6bc3c0 |
+ *done = TRUE;
|
|
|
6bc3c0 |
+ /* process the remaining buffer */
|
|
|
6bc3c0 |
+ line = read_buffer->str + *read_buffer_pos;
|
|
|
6bc3c0 |
+ /* GString guarantees the buffer is always NULL-terminated. */
|
|
|
6bc3c0 |
+ if (strlen (line) > 0) {
|
|
|
6bc3c0 |
+ if (prog_extract && prog_extract (line, progress))
|
|
|
6bc3c0 |
+ bd_utils_report_progress (progress_id, *progress, NULL);
|
|
|
6bc3c0 |
+ else
|
|
|
6bc3c0 |
+ g_string_append (filtered_buffer, line);
|
|
|
6bc3c0 |
+ }
|
|
|
6bc3c0 |
+ }
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ return TRUE;
|
|
|
6bc3c0 |
+}
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
static gboolean _utils_exec_and_report_progress (const gchar **argv, const BDExtraArg **extra, BDUtilsProgExtract prog_extract, gint *proc_status, gchar **stdout, gchar **stderr, GError **error) {
|
|
|
6bc3c0 |
const gchar **args = NULL;
|
|
|
6bc3c0 |
guint args_len = 0;
|
|
|
6bc3c0 |
@@ -283,24 +365,26 @@ static gboolean _utils_exec_and_report_progress (const gchar **argv, const BDExt
|
|
|
6bc3c0 |
gchar *msg = NULL;
|
|
|
6bc3c0 |
GPid pid = 0;
|
|
|
6bc3c0 |
gint out_fd = 0;
|
|
|
6bc3c0 |
- GIOChannel *out_pipe = NULL;
|
|
|
6bc3c0 |
gint err_fd = 0;
|
|
|
6bc3c0 |
- GIOChannel *err_pipe = NULL;
|
|
|
6bc3c0 |
- gchar *line = NULL;
|
|
|
6bc3c0 |
gint child_ret = -1;
|
|
|
6bc3c0 |
gint status = 0;
|
|
|
6bc3c0 |
gboolean ret = FALSE;
|
|
|
6bc3c0 |
- GIOStatus io_status = G_IO_STATUS_NORMAL;
|
|
|
6bc3c0 |
gint poll_status = 0;
|
|
|
6bc3c0 |
guint i = 0;
|
|
|
6bc3c0 |
guint8 completion = 0;
|
|
|
6bc3c0 |
struct pollfd fds[2] = { ZERO_INIT, ZERO_INIT };
|
|
|
6bc3c0 |
+ int flags;
|
|
|
6bc3c0 |
gboolean out_done = FALSE;
|
|
|
6bc3c0 |
gboolean err_done = FALSE;
|
|
|
6bc3c0 |
- GString *stdout_data = g_string_new (NULL);
|
|
|
6bc3c0 |
- GString *stderr_data = g_string_new (NULL);
|
|
|
6bc3c0 |
+ GString *stdout_data;
|
|
|
6bc3c0 |
+ GString *stdout_buffer;
|
|
|
6bc3c0 |
+ GString *stderr_data;
|
|
|
6bc3c0 |
+ GString *stderr_buffer;
|
|
|
6bc3c0 |
+ gsize stdout_buffer_pos = 0;
|
|
|
6bc3c0 |
+ gsize stderr_buffer_pos = 0;
|
|
|
6bc3c0 |
gchar **old_env = NULL;
|
|
|
6bc3c0 |
gchar **new_env = NULL;
|
|
|
6bc3c0 |
+ gboolean success = TRUE;
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
/* TODO: share this code between functions */
|
|
|
6bc3c0 |
if (extra) {
|
|
|
6bc3c0 |
@@ -336,15 +420,13 @@ static gboolean _utils_exec_and_report_progress (const gchar **argv, const BDExt
|
|
|
6bc3c0 |
G_SPAWN_DEFAULT|G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD,
|
|
|
6bc3c0 |
NULL, NULL, &pid, NULL, &out_fd, &err_fd, error);
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
+ g_strfreev (new_env);
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
if (!ret) {
|
|
|
6bc3c0 |
/* error is already populated */
|
|
|
6bc3c0 |
- g_string_free (stdout_data, TRUE);
|
|
|
6bc3c0 |
- g_string_free (stderr_data, TRUE);
|
|
|
6bc3c0 |
- g_strfreev (new_env);
|
|
|
6bc3c0 |
g_free (args);
|
|
|
6bc3c0 |
return FALSE;
|
|
|
6bc3c0 |
}
|
|
|
6bc3c0 |
- g_strfreev (new_env);
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
args_str = g_strjoinv (" ", args ? (gchar **) args : (gchar **) argv);
|
|
|
6bc3c0 |
msg = g_strdup_printf ("Started '%s'", args_str);
|
|
|
6bc3c0 |
@@ -353,18 +435,25 @@ static gboolean _utils_exec_and_report_progress (const gchar **argv, const BDExt
|
|
|
6bc3c0 |
g_free (args);
|
|
|
6bc3c0 |
g_free (msg);
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
- out_pipe = g_io_channel_unix_new (out_fd);
|
|
|
6bc3c0 |
- err_pipe = g_io_channel_unix_new (err_fd);
|
|
|
6bc3c0 |
+ /* set both fds for non-blocking read */
|
|
|
6bc3c0 |
+ flags = fcntl (out_fd, F_GETFL, 0);
|
|
|
6bc3c0 |
+ if (fcntl (out_fd, F_SETFL, flags | O_NONBLOCK))
|
|
|
6bc3c0 |
+ g_warning ("_utils_exec_and_report_progress: Failed to set out_fd non-blocking: %m");
|
|
|
6bc3c0 |
+ flags = fcntl (err_fd, F_GETFL, 0);
|
|
|
6bc3c0 |
+ if (fcntl (err_fd, F_SETFL, flags | O_NONBLOCK))
|
|
|
6bc3c0 |
+ g_warning ("_utils_exec_and_report_progress: Failed to set err_fd non-blocking: %m");
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
- g_io_channel_set_encoding (out_pipe, NULL, NULL);
|
|
|
6bc3c0 |
- g_io_channel_set_encoding (err_pipe, NULL, NULL);
|
|
|
6bc3c0 |
+ stdout_data = g_string_new (NULL);
|
|
|
6bc3c0 |
+ stdout_buffer = g_string_new (NULL);
|
|
|
6bc3c0 |
+ stderr_data = g_string_new (NULL);
|
|
|
6bc3c0 |
+ stderr_buffer = g_string_new (NULL);
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
fds[0].fd = out_fd;
|
|
|
6bc3c0 |
fds[1].fd = err_fd;
|
|
|
6bc3c0 |
fds[0].events = POLLIN | POLLHUP | POLLERR;
|
|
|
6bc3c0 |
fds[1].events = POLLIN | POLLHUP | POLLERR;
|
|
|
6bc3c0 |
- while (!out_done || !err_done) {
|
|
|
6bc3c0 |
- poll_status = poll (fds, 2, -1);
|
|
|
6bc3c0 |
+ while (! (out_done && err_done)) {
|
|
|
6bc3c0 |
+ poll_status = poll (fds, 2, -1 /* timeout */);
|
|
|
6bc3c0 |
g_warn_if_fail (poll_status != 0); /* no timeout specified, zero should never be returned */
|
|
|
6bc3c0 |
if (poll_status < 0) {
|
|
|
6bc3c0 |
if (errno == EAGAIN || errno == EINTR)
|
|
|
6bc3c0 |
@@ -372,140 +461,90 @@ static gboolean _utils_exec_and_report_progress (const gchar **argv, const BDExt
|
|
|
6bc3c0 |
g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED,
|
|
|
6bc3c0 |
"Failed to poll output FDs: %m");
|
|
|
6bc3c0 |
bd_utils_report_finished (progress_id, (*error)->message);
|
|
|
6bc3c0 |
- g_io_channel_shutdown (out_pipe, FALSE, NULL);
|
|
|
6bc3c0 |
- g_io_channel_unref (out_pipe);
|
|
|
6bc3c0 |
- g_io_channel_shutdown (err_pipe, FALSE, NULL);
|
|
|
6bc3c0 |
- g_io_channel_unref (err_pipe);
|
|
|
6bc3c0 |
- g_string_free (stdout_data, TRUE);
|
|
|
6bc3c0 |
- g_string_free (stderr_data, TRUE);
|
|
|
6bc3c0 |
- return FALSE;
|
|
|
6bc3c0 |
+ success = FALSE;
|
|
|
6bc3c0 |
+ break;
|
|
|
6bc3c0 |
}
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
- while (!out_done && (fds[0].revents & POLLIN)) {
|
|
|
6bc3c0 |
- io_status = g_io_channel_read_line (out_pipe, &line, NULL, NULL, error);
|
|
|
6bc3c0 |
- if (io_status == G_IO_STATUS_NORMAL) {
|
|
|
6bc3c0 |
- if (prog_extract && prog_extract (line, &completion))
|
|
|
6bc3c0 |
- bd_utils_report_progress (progress_id, completion, NULL);
|
|
|
6bc3c0 |
- else
|
|
|
6bc3c0 |
- g_string_append (stdout_data, line);
|
|
|
6bc3c0 |
- g_free (line);
|
|
|
6bc3c0 |
- } else if (io_status == G_IO_STATUS_EOF) {
|
|
|
6bc3c0 |
- out_done = TRUE;
|
|
|
6bc3c0 |
- } else if (error && (*error)) {
|
|
|
6bc3c0 |
+ if (!out_done) {
|
|
|
6bc3c0 |
+ if (! _process_fd_event (out_fd, &fds[0], stdout_buffer, stdout_data, &stdout_buffer_pos, &out_done, progress_id, &completion, prog_extract, error)) {
|
|
|
6bc3c0 |
bd_utils_report_finished (progress_id, (*error)->message);
|
|
|
6bc3c0 |
- g_io_channel_shutdown (out_pipe, FALSE, NULL);
|
|
|
6bc3c0 |
- g_io_channel_unref (out_pipe);
|
|
|
6bc3c0 |
- g_io_channel_shutdown (err_pipe, FALSE, NULL);
|
|
|
6bc3c0 |
- g_io_channel_unref (err_pipe);
|
|
|
6bc3c0 |
- g_string_free (stdout_data, TRUE);
|
|
|
6bc3c0 |
- g_string_free (stderr_data, TRUE);
|
|
|
6bc3c0 |
- return FALSE;
|
|
|
6bc3c0 |
+ success = FALSE;
|
|
|
6bc3c0 |
+ break;
|
|
|
6bc3c0 |
}
|
|
|
6bc3c0 |
}
|
|
|
6bc3c0 |
- if (fds[0].revents & POLLHUP || fds[0].revents & POLLERR || fds[0].revents & POLLNVAL)
|
|
|
6bc3c0 |
- out_done = TRUE;
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
- while (!err_done && (fds[1].revents & POLLIN)) {
|
|
|
6bc3c0 |
- io_status = g_io_channel_read_line (err_pipe, &line, NULL, NULL, error);
|
|
|
6bc3c0 |
- if (io_status == G_IO_STATUS_NORMAL) {
|
|
|
6bc3c0 |
- g_string_append (stderr_data, line);
|
|
|
6bc3c0 |
- g_free (line);
|
|
|
6bc3c0 |
- } else if (io_status == G_IO_STATUS_EOF) {
|
|
|
6bc3c0 |
- err_done = TRUE;
|
|
|
6bc3c0 |
- } else if (error && (*error)) {
|
|
|
6bc3c0 |
+ if (!err_done) {
|
|
|
6bc3c0 |
+ if (! _process_fd_event (err_fd, &fds[1], stderr_buffer, stderr_data, &stderr_buffer_pos, &err_done, progress_id, &completion, prog_extract, error)) {
|
|
|
6bc3c0 |
bd_utils_report_finished (progress_id, (*error)->message);
|
|
|
6bc3c0 |
- g_io_channel_shutdown (out_pipe, FALSE, NULL);
|
|
|
6bc3c0 |
- g_io_channel_unref (out_pipe);
|
|
|
6bc3c0 |
- g_io_channel_shutdown (err_pipe, FALSE, NULL);
|
|
|
6bc3c0 |
- g_io_channel_unref (err_pipe);
|
|
|
6bc3c0 |
- g_string_free (stdout_data, TRUE);
|
|
|
6bc3c0 |
- g_string_free (stderr_data, TRUE);
|
|
|
6bc3c0 |
- return FALSE;
|
|
|
6bc3c0 |
+ success = FALSE;
|
|
|
6bc3c0 |
+ break;
|
|
|
6bc3c0 |
}
|
|
|
6bc3c0 |
}
|
|
|
6bc3c0 |
- if (fds[1].revents & POLLHUP || fds[1].revents & POLLERR || fds[1].revents & POLLNVAL)
|
|
|
6bc3c0 |
- err_done = TRUE;
|
|
|
6bc3c0 |
}
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
+ g_string_free (stdout_buffer, TRUE);
|
|
|
6bc3c0 |
+ g_string_free (stderr_buffer, TRUE);
|
|
|
6bc3c0 |
+ close (out_fd);
|
|
|
6bc3c0 |
+ close (err_fd);
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
child_ret = waitpid (pid, &status, 0);
|
|
|
6bc3c0 |
- *proc_status = WEXITSTATUS(status);
|
|
|
6bc3c0 |
- if (child_ret > 0) {
|
|
|
6bc3c0 |
- if (*proc_status != 0) {
|
|
|
6bc3c0 |
- if (stderr_data->str && (g_strcmp0 ("", stderr_data->str) != 0))
|
|
|
6bc3c0 |
- msg = stderr_data->str;
|
|
|
6bc3c0 |
- else
|
|
|
6bc3c0 |
- msg = stdout_data->str;
|
|
|
6bc3c0 |
- g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED,
|
|
|
6bc3c0 |
- "Process reported exit code %d: %s", *proc_status, msg);
|
|
|
6bc3c0 |
- bd_utils_report_finished (progress_id, (*error)->message);
|
|
|
6bc3c0 |
- g_io_channel_shutdown (out_pipe, FALSE, NULL);
|
|
|
6bc3c0 |
- g_io_channel_unref (out_pipe);
|
|
|
6bc3c0 |
- g_io_channel_shutdown (err_pipe, FALSE, NULL);
|
|
|
6bc3c0 |
- g_io_channel_unref (err_pipe);
|
|
|
6bc3c0 |
- g_string_free (stdout_data, TRUE);
|
|
|
6bc3c0 |
- g_string_free (stderr_data, TRUE);
|
|
|
6bc3c0 |
- return FALSE;
|
|
|
6bc3c0 |
- }
|
|
|
6bc3c0 |
- if (WIFSIGNALED(status)) {
|
|
|
6bc3c0 |
- g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED,
|
|
|
6bc3c0 |
- "Process killed with a signal");
|
|
|
6bc3c0 |
- bd_utils_report_finished (progress_id, (*error)->message);
|
|
|
6bc3c0 |
- g_io_channel_shutdown (out_pipe, FALSE, NULL);
|
|
|
6bc3c0 |
- g_io_channel_unref (out_pipe);
|
|
|
6bc3c0 |
- g_io_channel_shutdown (err_pipe, FALSE, NULL);
|
|
|
6bc3c0 |
- g_io_channel_unref (err_pipe);
|
|
|
6bc3c0 |
- g_string_free (stdout_data, TRUE);
|
|
|
6bc3c0 |
- g_string_free (stderr_data, TRUE);
|
|
|
6bc3c0 |
- return FALSE;
|
|
|
6bc3c0 |
- }
|
|
|
6bc3c0 |
- } else if (child_ret == -1) {
|
|
|
6bc3c0 |
- if (errno != ECHILD) {
|
|
|
6bc3c0 |
- errno = 0;
|
|
|
6bc3c0 |
- g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED,
|
|
|
6bc3c0 |
- "Failed to wait for the process");
|
|
|
6bc3c0 |
- bd_utils_report_finished (progress_id, (*error)->message);
|
|
|
6bc3c0 |
- g_io_channel_shutdown (out_pipe, FALSE, NULL);
|
|
|
6bc3c0 |
- g_io_channel_unref (out_pipe);
|
|
|
6bc3c0 |
- g_io_channel_shutdown (err_pipe, FALSE, NULL);
|
|
|
6bc3c0 |
- g_io_channel_unref (err_pipe);
|
|
|
6bc3c0 |
- g_string_free (stdout_data, TRUE);
|
|
|
6bc3c0 |
- g_string_free (stderr_data, TRUE);
|
|
|
6bc3c0 |
- return FALSE;
|
|
|
6bc3c0 |
+ *proc_status = WEXITSTATUS (status);
|
|
|
6bc3c0 |
+ if (success) {
|
|
|
6bc3c0 |
+ if (child_ret > 0) {
|
|
|
6bc3c0 |
+ if (*proc_status != 0) {
|
|
|
6bc3c0 |
+ msg = stderr_data->len > 0 ? stderr_data->str : stdout_data->str;
|
|
|
6bc3c0 |
+ g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED,
|
|
|
6bc3c0 |
+ "Process reported exit code %d: %s", *proc_status, msg);
|
|
|
6bc3c0 |
+ bd_utils_report_finished (progress_id, (*error)->message);
|
|
|
6bc3c0 |
+ success = FALSE;
|
|
|
6bc3c0 |
+ } else if (WIFSIGNALED (status)) {
|
|
|
6bc3c0 |
+ g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED,
|
|
|
6bc3c0 |
+ "Process killed with a signal");
|
|
|
6bc3c0 |
+ bd_utils_report_finished (progress_id, (*error)->message);
|
|
|
6bc3c0 |
+ success = FALSE;
|
|
|
6bc3c0 |
+ }
|
|
|
6bc3c0 |
+ } else if (child_ret == -1) {
|
|
|
6bc3c0 |
+ if (errno != ECHILD) {
|
|
|
6bc3c0 |
+ errno = 0;
|
|
|
6bc3c0 |
+ g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED,
|
|
|
6bc3c0 |
+ "Failed to wait for the process");
|
|
|
6bc3c0 |
+ bd_utils_report_finished (progress_id, (*error)->message);
|
|
|
6bc3c0 |
+ success = FALSE;
|
|
|
6bc3c0 |
+ } else {
|
|
|
6bc3c0 |
+ /* no such process (the child exited before we tried to wait for it) */
|
|
|
6bc3c0 |
+ errno = 0;
|
|
|
6bc3c0 |
+ }
|
|
|
6bc3c0 |
}
|
|
|
6bc3c0 |
- /* no such process (the child exited before we tried to wait for it) */
|
|
|
6bc3c0 |
- errno = 0;
|
|
|
6bc3c0 |
+ if (success)
|
|
|
6bc3c0 |
+ bd_utils_report_finished (progress_id, "Completed");
|
|
|
6bc3c0 |
}
|
|
|
6bc3c0 |
-
|
|
|
6bc3c0 |
- bd_utils_report_finished (progress_id, "Completed");
|
|
|
6bc3c0 |
log_out (task_id, stdout_data->str, stderr_data->str);
|
|
|
6bc3c0 |
log_done (task_id, *proc_status);
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
- /* we don't care about the status here */
|
|
|
6bc3c0 |
- g_io_channel_shutdown (out_pipe, FALSE, error);
|
|
|
6bc3c0 |
- g_io_channel_unref (out_pipe);
|
|
|
6bc3c0 |
- g_io_channel_shutdown (err_pipe, FALSE, error);
|
|
|
6bc3c0 |
- g_io_channel_unref (err_pipe);
|
|
|
6bc3c0 |
-
|
|
|
6bc3c0 |
- if (stdout)
|
|
|
6bc3c0 |
+ if (success && stdout)
|
|
|
6bc3c0 |
*stdout = g_string_free (stdout_data, FALSE);
|
|
|
6bc3c0 |
else
|
|
|
6bc3c0 |
g_string_free (stdout_data, TRUE);
|
|
|
6bc3c0 |
- if (stderr)
|
|
|
6bc3c0 |
+ if (success && stderr)
|
|
|
6bc3c0 |
*stderr = g_string_free (stderr_data, FALSE);
|
|
|
6bc3c0 |
else
|
|
|
6bc3c0 |
g_string_free (stderr_data, TRUE);
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
- return TRUE;
|
|
|
6bc3c0 |
+ return success;
|
|
|
6bc3c0 |
}
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
/**
|
|
|
6bc3c0 |
* bd_utils_exec_and_report_progress:
|
|
|
6bc3c0 |
* @argv: (array zero-terminated=1): the argv array for the call
|
|
|
6bc3c0 |
* @extra: (allow-none) (array zero-terminated=1): extra arguments
|
|
|
6bc3c0 |
- * @prog_extract: (scope notified): function for extracting progress information
|
|
|
6bc3c0 |
+ * @prog_extract: (scope notified) (nullable): function for extracting progress information
|
|
|
6bc3c0 |
* @proc_status: (out): place to store the process exit status
|
|
|
6bc3c0 |
* @error: (out): place to store error (if any)
|
|
|
6bc3c0 |
*
|
|
|
6bc3c0 |
+ * Note that any NULL bytes read from standard output and standard error
|
|
|
6bc3c0 |
+ * output are treated as separators similar to newlines and @prog_extract
|
|
|
6bc3c0 |
+ * will be called with the respective chunk.
|
|
|
6bc3c0 |
+ *
|
|
|
6bc3c0 |
* Returns: whether the @argv was successfully executed (no error and exit code 0) or not
|
|
|
6bc3c0 |
*/
|
|
|
6bc3c0 |
gboolean bd_utils_exec_and_report_progress (const gchar **argv, const BDExtraArg **extra, BDUtilsProgExtract prog_extract, gint *proc_status, GError **error) {
|
|
|
6bc3c0 |
@@ -519,6 +558,9 @@ gboolean bd_utils_exec_and_report_progress (const gchar **argv, const BDExtraArg
|
|
|
6bc3c0 |
* @output: (out): variable to store output to
|
|
|
6bc3c0 |
* @error: (out): place to store error (if any)
|
|
|
6bc3c0 |
*
|
|
|
6bc3c0 |
+ * Note that any NULL bytes read from standard output and standard error
|
|
|
6bc3c0 |
+ * output will be discarded.
|
|
|
6bc3c0 |
+ *
|
|
|
6bc3c0 |
* Returns: whether the @argv was successfully executed capturing the output or not
|
|
|
6bc3c0 |
*/
|
|
|
6bc3c0 |
gboolean bd_utils_exec_and_capture_output (const gchar **argv, const BDExtraArg **extra, gchar **output, GError **error) {
|
|
|
6bc3c0 |
@@ -549,7 +591,6 @@ gboolean bd_utils_exec_and_capture_output (const gchar **argv, const BDExtraArg
|
|
|
6bc3c0 |
g_free (stderr);
|
|
|
6bc3c0 |
return TRUE;
|
|
|
6bc3c0 |
}
|
|
|
6bc3c0 |
-
|
|
|
6bc3c0 |
}
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
/**
|
|
|
6bc3c0 |
--
|
|
|
6bc3c0 |
2.26.2
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
From f5eb61c91ffc6b0d1fd709076a9579105655ff17 Mon Sep 17 00:00:00 2001
|
|
|
6bc3c0 |
From: Tomas Bzatek <tbzatek@redhat.com>
|
|
|
6bc3c0 |
Date: Fri, 25 Sep 2020 18:27:02 +0200
|
|
|
6bc3c0 |
Subject: [PATCH 3/5] exec: Clarify the BDUtilsProgExtract callback
|
|
|
6bc3c0 |
documentation
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
---
|
|
|
6bc3c0 |
src/utils/exec.h | 24 ++++++++++++++++++++++--
|
|
|
6bc3c0 |
1 file changed, 22 insertions(+), 2 deletions(-)
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
diff --git a/src/utils/exec.h b/src/utils/exec.h
|
|
|
6bc3c0 |
index ad169e4..0e262a2 100644
|
|
|
6bc3c0 |
--- a/src/utils/exec.h
|
|
|
6bc3c0 |
+++ b/src/utils/exec.h
|
|
|
6bc3c0 |
@@ -31,10 +31,30 @@ typedef void (*BDUtilsProgFunc) (guint64 task_id, BDUtilsProgStatus status, guin
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
/**
|
|
|
6bc3c0 |
* BDUtilsProgExtract:
|
|
|
6bc3c0 |
- * @line: line from extract progress from
|
|
|
6bc3c0 |
+ * @line: line to extract progress from
|
|
|
6bc3c0 |
* @completion: (out): percentage of completion
|
|
|
6bc3c0 |
*
|
|
|
6bc3c0 |
- * Returns: whether the line was a progress reporting line or not
|
|
|
6bc3c0 |
+ * Callback function used to process a line captured from spawned command's standard
|
|
|
6bc3c0 |
+ * output and standard error output. Typically used to extract completion percentage
|
|
|
6bc3c0 |
+ * of a long-running job.
|
|
|
6bc3c0 |
+ *
|
|
|
6bc3c0 |
+ * Note that both outputs are read simultaneously with no guarantees of message order
|
|
|
6bc3c0 |
+ * this function is called with.
|
|
|
6bc3c0 |
+ *
|
|
|
6bc3c0 |
+ * The value the @completion points to may contain value previously returned from
|
|
|
6bc3c0 |
+ * this callback or zero when called for the first time. This is useful for extractors
|
|
|
6bc3c0 |
+ * where only some kind of a tick mark is printed out as a progress and previous value
|
|
|
6bc3c0 |
+ * is needed to compute an incremented value. It's important to keep in mind that this
|
|
|
6bc3c0 |
+ * function is only called over lines, i.e. progress reporting printing out tick marks
|
|
|
6bc3c0 |
+ * (e.g. dots) without a newline character might not work properly.
|
|
|
6bc3c0 |
+ *
|
|
|
6bc3c0 |
+ * The @line string usually contains trailing newline character, which may be absent
|
|
|
6bc3c0 |
+ * however in case the spawned command exits without printing one. It's guaranteed
|
|
|
6bc3c0 |
+ * this function is called over remaining buffer no matter what the trailing
|
|
|
6bc3c0 |
+ * character is.
|
|
|
6bc3c0 |
+ *
|
|
|
6bc3c0 |
+ * Returns: whether the line was a progress reporting line and should be excluded
|
|
|
6bc3c0 |
+ * from the collected standard output string or not.
|
|
|
6bc3c0 |
*/
|
|
|
6bc3c0 |
typedef gboolean (*BDUtilsProgExtract) (const gchar *line, guint8 *completion);
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
--
|
|
|
6bc3c0 |
2.26.2
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
From 8a7f0de5f63099a3e8bcaca005c4de04df959113 Mon Sep 17 00:00:00 2001
|
|
|
6bc3c0 |
From: Tomas Bzatek <tbzatek@redhat.com>
|
|
|
6bc3c0 |
Date: Fri, 25 Sep 2020 18:27:41 +0200
|
|
|
6bc3c0 |
Subject: [PATCH 4/5] tests: Add bufferbloat exec tests
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
This should test pipe buffer exhaustion as well as potential pipe
|
|
|
6bc3c0 |
read starvation while filling the other fd.
|
|
|
6bc3c0 |
---
|
|
|
6bc3c0 |
tests/utils_test.py | 105 +++++++++++++++++++++++++++++++++++++++++++-
|
|
|
6bc3c0 |
1 file changed, 104 insertions(+), 1 deletion(-)
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
diff --git a/tests/utils_test.py b/tests/utils_test.py
|
|
|
6bc3c0 |
index 2bec5ed..ed13611 100644
|
|
|
6bc3c0 |
--- a/tests/utils_test.py
|
|
|
6bc3c0 |
+++ b/tests/utils_test.py
|
|
|
6bc3c0 |
@@ -1,8 +1,9 @@
|
|
|
6bc3c0 |
import unittest
|
|
|
6bc3c0 |
import re
|
|
|
6bc3c0 |
import os
|
|
|
6bc3c0 |
+import six
|
|
|
6bc3c0 |
import overrides_hack
|
|
|
6bc3c0 |
-from utils import fake_utils, create_sparse_tempfile, create_lio_device, delete_lio_device, run_command, TestTags, tag_test
|
|
|
6bc3c0 |
+from utils import fake_utils, create_sparse_tempfile, create_lio_device, delete_lio_device, run_command, TestTags, tag_test, read_file
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
from gi.repository import BlockDev, GLib
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
@@ -65,6 +66,9 @@ class UtilsExecLoggingTest(UtilsTestCase):
|
|
|
6bc3c0 |
succ = BlockDev.utils_exec_and_report_error(["true"])
|
|
|
6bc3c0 |
self.assertTrue(succ)
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
+ with six.assertRaisesRegex(self, GLib.GError, r"Process reported exit code 1"):
|
|
|
6bc3c0 |
+ succ = BlockDev.utils_exec_and_report_error(["/bin/false"])
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
succ, out = BlockDev.utils_exec_and_capture_output(["echo", "hi"])
|
|
|
6bc3c0 |
self.assertTrue(succ)
|
|
|
6bc3c0 |
self.assertEqual(out, "hi\n")
|
|
|
6bc3c0 |
@@ -178,6 +182,105 @@ class UtilsExecLoggingTest(UtilsTestCase):
|
|
|
6bc3c0 |
self.assertTrue(succ)
|
|
|
6bc3c0 |
self.assertIn("LC_ALL=C", out)
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
+ @tag_test(TestTags.NOSTORAGE, TestTags.CORE)
|
|
|
6bc3c0 |
+ def test_exec_buffer_bloat(self):
|
|
|
6bc3c0 |
+ """Verify that very large output from a command is handled properly"""
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ # easy 64kB of data
|
|
|
6bc3c0 |
+ cnt = 65536
|
|
|
6bc3c0 |
+ succ, out = BlockDev.utils_exec_and_capture_output(["bash", "-c", "for i in {1..%d}; do echo -n .; done" % cnt])
|
|
|
6bc3c0 |
+ self.assertTrue(succ)
|
|
|
6bc3c0 |
+ self.assertEquals(len(out), cnt)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ succ, out = BlockDev.utils_exec_and_capture_output(["bash", "-c", "for i in {1..%d}; do echo -n .; echo -n \# >&2; done" % cnt])
|
|
|
6bc3c0 |
+ self.assertTrue(succ)
|
|
|
6bc3c0 |
+ self.assertEquals(len(out), cnt)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ # now exceed the system pipe buffer size
|
|
|
6bc3c0 |
+ # pipe(7): The maximum size (in bytes) of individual pipes that can be set by users without the CAP_SYS_RESOURCE capability.
|
|
|
6bc3c0 |
+ cnt = int(read_file("/proc/sys/fs/pipe-max-size")) + 100
|
|
|
6bc3c0 |
+ self.assertGreater(cnt, 0)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ succ, out = BlockDev.utils_exec_and_capture_output(["bash", "-c", "for i in {1..%d}; do echo -n .; done" % cnt])
|
|
|
6bc3c0 |
+ self.assertTrue(succ)
|
|
|
6bc3c0 |
+ self.assertEquals(len(out), cnt)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ succ, out = BlockDev.utils_exec_and_capture_output(["bash", "-c", "for i in {1..%d}; do echo -n .; echo -n \# >&2; done" % cnt])
|
|
|
6bc3c0 |
+ self.assertTrue(succ)
|
|
|
6bc3c0 |
+ self.assertEquals(len(out), cnt)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ # make use of some newlines
|
|
|
6bc3c0 |
+ succ, out = BlockDev.utils_exec_and_capture_output(["bash", "-c", "for i in {1..%d}; do echo -n .; if [ $(($i%%500)) -eq 0 ]; then echo ''; fi; done" % cnt])
|
|
|
6bc3c0 |
+ self.assertTrue(succ)
|
|
|
6bc3c0 |
+ self.assertGreater(len(out), cnt)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ succ, out = BlockDev.utils_exec_and_capture_output(["bash", "-c", "for i in {1..%d}; do echo -n .; echo -n \# >&2; if [ $(($i%%500)) -eq 0 ]; then echo ''; echo '' >&2; fi; done" % cnt])
|
|
|
6bc3c0 |
+ self.assertTrue(succ)
|
|
|
6bc3c0 |
+ self.assertGreater(len(out), cnt)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ EXEC_PROGRESS_MSG = "Aloha, I'm the progress line you should match."
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ def my_exec_progress_func_concat(self, line):
|
|
|
6bc3c0 |
+ """Expect an concatenated string"""
|
|
|
6bc3c0 |
+ s = ""
|
|
|
6bc3c0 |
+ for i in range(10):
|
|
|
6bc3c0 |
+ s += self.EXEC_PROGRESS_MSG
|
|
|
6bc3c0 |
+ self.assertEqual(line, s)
|
|
|
6bc3c0 |
+ self.num_matches += 1
|
|
|
6bc3c0 |
+ return 0
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ def my_exec_progress_func(self, line):
|
|
|
6bc3c0 |
+ self.assertTrue(re.match(r".*%s.*" % self.EXEC_PROGRESS_MSG, line))
|
|
|
6bc3c0 |
+ self.num_matches += 1
|
|
|
6bc3c0 |
+ return 0
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ def test_exec_buffer_bloat_progress(self):
|
|
|
6bc3c0 |
+ """Verify that progress reporting can handle large output"""
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ # no newlines, should match just a single occurrence on EOF
|
|
|
6bc3c0 |
+ cnt = 10
|
|
|
6bc3c0 |
+ self.num_matches = 0
|
|
|
6bc3c0 |
+ status = BlockDev.utils_exec_and_report_progress(["bash", "-c", "for i in {1..10}; do echo -n \"%s\"; done" % self.EXEC_PROGRESS_MSG], None, self.my_exec_progress_func_concat)
|
|
|
6bc3c0 |
+ self.assertTrue(status)
|
|
|
6bc3c0 |
+ self.assertEqual(self.num_matches, 1)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ # ten matches
|
|
|
6bc3c0 |
+ self.num_matches = 0
|
|
|
6bc3c0 |
+ status = BlockDev.utils_exec_and_report_progress(["bash", "-c", "for i in {1..%d}; do echo \"%s\"; done" % (cnt, self.EXEC_PROGRESS_MSG)], None, self.my_exec_progress_func)
|
|
|
6bc3c0 |
+ self.assertTrue(status)
|
|
|
6bc3c0 |
+ self.assertEqual(self.num_matches, cnt)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ # 100k matches
|
|
|
6bc3c0 |
+ cnt = 100000
|
|
|
6bc3c0 |
+ self.num_matches = 0
|
|
|
6bc3c0 |
+ status = BlockDev.utils_exec_and_report_progress(["bash", "-c", "for i in {1..%d}; do echo \"%s\"; done" % (cnt, self.EXEC_PROGRESS_MSG)], None, self.my_exec_progress_func)
|
|
|
6bc3c0 |
+ self.assertTrue(status)
|
|
|
6bc3c0 |
+ self.assertEqual(self.num_matches, cnt)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ # 100k matches on stderr
|
|
|
6bc3c0 |
+ self.num_matches = 0
|
|
|
6bc3c0 |
+ status = BlockDev.utils_exec_and_report_progress(["bash", "-c", "for i in {1..%d}; do echo \"%s\" >&2; done" % (cnt, self.EXEC_PROGRESS_MSG)], None, self.my_exec_progress_func)
|
|
|
6bc3c0 |
+ self.assertTrue(status)
|
|
|
6bc3c0 |
+ self.assertEqual(self.num_matches, cnt)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ # 100k matches on stderr and stdout
|
|
|
6bc3c0 |
+ self.num_matches = 0
|
|
|
6bc3c0 |
+ status = BlockDev.utils_exec_and_report_progress(["bash", "-c", "for i in {1..%d}; do echo \"%s\"; echo \"%s\" >&2; done" % (cnt, self.EXEC_PROGRESS_MSG, self.EXEC_PROGRESS_MSG)], None, self.my_exec_progress_func)
|
|
|
6bc3c0 |
+ self.assertTrue(status)
|
|
|
6bc3c0 |
+ self.assertEqual(self.num_matches, cnt * 2)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ # stdout and stderr output, non-zero return from the command and very long exception message
|
|
|
6bc3c0 |
+ self.num_matches = 0
|
|
|
6bc3c0 |
+ with six.assertRaisesRegex(self, GLib.GError, r"Process reported exit code 66"):
|
|
|
6bc3c0 |
+ status = BlockDev.utils_exec_and_report_progress(["bash", "-c", "for i in {1..%d}; do echo \"%s\"; echo \"%s\" >&2; done; exit 66" % (cnt, self.EXEC_PROGRESS_MSG, self.EXEC_PROGRESS_MSG)], None, self.my_exec_progress_func)
|
|
|
6bc3c0 |
+ self.assertEqual(self.num_matches, cnt * 2)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ # no progress reporting callback given, tests slightly different code path
|
|
|
6bc3c0 |
+ status = BlockDev.utils_exec_and_report_progress(["bash", "-c", "for i in {1..%d}; do echo \"%s\"; echo \"%s\" >&2; done" % (cnt, self.EXEC_PROGRESS_MSG, self.EXEC_PROGRESS_MSG)], None, None)
|
|
|
6bc3c0 |
+ self.assertTrue(status)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
class UtilsDevUtilsTestCase(UtilsTestCase):
|
|
|
6bc3c0 |
@tag_test(TestTags.NOSTORAGE, TestTags.CORE)
|
|
|
6bc3c0 |
def test_resolve_device(self):
|
|
|
6bc3c0 |
--
|
|
|
6bc3c0 |
2.26.2
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
From 7a3fd3d32dd325fb5188bcba74966e414e33c343 Mon Sep 17 00:00:00 2001
|
|
|
6bc3c0 |
From: Tomas Bzatek <tbzatek@redhat.com>
|
|
|
6bc3c0 |
Date: Wed, 30 Sep 2020 14:52:27 +0200
|
|
|
6bc3c0 |
Subject: [PATCH 5/5] tests: Add null-byte exec tests
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
Some commands may print out NULL bytes in the middle of their output,
|
|
|
6bc3c0 |
make sure everything works correctly.
|
|
|
6bc3c0 |
---
|
|
|
6bc3c0 |
tests/utils_test.py | 48 +++++++++++++++++++++++++++++++++++++++++++++
|
|
|
6bc3c0 |
1 file changed, 48 insertions(+)
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
diff --git a/tests/utils_test.py b/tests/utils_test.py
|
|
|
6bc3c0 |
index ed13611..1235be3 100644
|
|
|
6bc3c0 |
--- a/tests/utils_test.py
|
|
|
6bc3c0 |
+++ b/tests/utils_test.py
|
|
|
6bc3c0 |
@@ -280,6 +280,54 @@ class UtilsExecLoggingTest(UtilsTestCase):
|
|
|
6bc3c0 |
status = BlockDev.utils_exec_and_report_progress(["bash", "-c", "for i in {1..%d}; do echo \"%s\"; echo \"%s\" >&2; done" % (cnt, self.EXEC_PROGRESS_MSG, self.EXEC_PROGRESS_MSG)], None, None)
|
|
|
6bc3c0 |
self.assertTrue(status)
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
+ def test_exec_null_bytes(self):
|
|
|
6bc3c0 |
+ """Verify that null bytes in the output are processed properly"""
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ TEST_DATA = ["First line", "Second line", "Third line"]
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ status, out = BlockDev.utils_exec_and_capture_output(["bash", "-c", "echo -e \"%s\\0%s\\n%s\"" % (TEST_DATA[0], TEST_DATA[1], TEST_DATA[2])])
|
|
|
6bc3c0 |
+ self.assertTrue(status)
|
|
|
6bc3c0 |
+ self.assertTrue(TEST_DATA[0] in out)
|
|
|
6bc3c0 |
+ self.assertTrue(TEST_DATA[1] in out)
|
|
|
6bc3c0 |
+ self.assertTrue(TEST_DATA[2] in out)
|
|
|
6bc3c0 |
+ self.assertFalse("kuku!" in out)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ # ten matches
|
|
|
6bc3c0 |
+ cnt = 10
|
|
|
6bc3c0 |
+ self.num_matches = 0
|
|
|
6bc3c0 |
+ status = BlockDev.utils_exec_and_report_progress(["bash", "-c", "for i in {1..%d}; do echo -e \"%s\\0%s\"; done" % (cnt, self.EXEC_PROGRESS_MSG, self.EXEC_PROGRESS_MSG)], None, self.my_exec_progress_func)
|
|
|
6bc3c0 |
+ self.assertTrue(status)
|
|
|
6bc3c0 |
+ self.assertEqual(self.num_matches, cnt * 2)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ # 100k matches
|
|
|
6bc3c0 |
+ cnt = 100000
|
|
|
6bc3c0 |
+ self.num_matches = 0
|
|
|
6bc3c0 |
+ status = BlockDev.utils_exec_and_report_progress(["bash", "-c", "for i in {1..%d}; do echo -e \"%s\\0%s\"; done" % (cnt, self.EXEC_PROGRESS_MSG, self.EXEC_PROGRESS_MSG)], None, self.my_exec_progress_func)
|
|
|
6bc3c0 |
+ self.assertTrue(status)
|
|
|
6bc3c0 |
+ self.assertEqual(self.num_matches, cnt * 2)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ # 100k matches on stderr
|
|
|
6bc3c0 |
+ self.num_matches = 0
|
|
|
6bc3c0 |
+ status = BlockDev.utils_exec_and_report_progress(["bash", "-c", "for i in {1..%d}; do echo -e \"%s\\0%s\" >&2; done" % (cnt, self.EXEC_PROGRESS_MSG, self.EXEC_PROGRESS_MSG)], None, self.my_exec_progress_func)
|
|
|
6bc3c0 |
+ self.assertTrue(status)
|
|
|
6bc3c0 |
+ self.assertEqual(self.num_matches, cnt * 2)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ # 100k matches on stderr and stdout
|
|
|
6bc3c0 |
+ self.num_matches = 0
|
|
|
6bc3c0 |
+ status = BlockDev.utils_exec_and_report_progress(["bash", "-c", "for i in {1..%d}; do echo -e \"%s\\0%s\"; echo -e \"%s\\0%s\" >&2; done" % (cnt, self.EXEC_PROGRESS_MSG, self.EXEC_PROGRESS_MSG, self.EXEC_PROGRESS_MSG, self.EXEC_PROGRESS_MSG)], None, self.my_exec_progress_func)
|
|
|
6bc3c0 |
+ self.assertTrue(status)
|
|
|
6bc3c0 |
+ self.assertEqual(self.num_matches, cnt * 4)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ # stdout and stderr output, non-zero return from the command and very long exception message
|
|
|
6bc3c0 |
+ self.num_matches = 0
|
|
|
6bc3c0 |
+ with six.assertRaisesRegex(self, GLib.GError, r"Process reported exit code 66"):
|
|
|
6bc3c0 |
+ status = BlockDev.utils_exec_and_report_progress(["bash", "-c", "for i in {1..%d}; do echo -e \"%s\\0%s\"; echo -e \"%s\\0%s\" >&2; done; exit 66" % (cnt, self.EXEC_PROGRESS_MSG, self.EXEC_PROGRESS_MSG, self.EXEC_PROGRESS_MSG, self.EXEC_PROGRESS_MSG)], None, self.my_exec_progress_func)
|
|
|
6bc3c0 |
+ self.assertEqual(self.num_matches, cnt * 4)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
+ # no progress reporting callback given, tests slightly different code path
|
|
|
6bc3c0 |
+ status = BlockDev.utils_exec_and_report_progress(["bash", "-c", "for i in {1..%d}; do echo -e \"%s\\0%s\"; echo -e \"%s\\0%s\" >&2; done" % (cnt, self.EXEC_PROGRESS_MSG, self.EXEC_PROGRESS_MSG, self.EXEC_PROGRESS_MSG, self.EXEC_PROGRESS_MSG)], None, None)
|
|
|
6bc3c0 |
+ self.assertTrue(status)
|
|
|
6bc3c0 |
+
|
|
|
6bc3c0 |
|
|
|
6bc3c0 |
class UtilsDevUtilsTestCase(UtilsTestCase):
|
|
|
6bc3c0 |
@tag_test(TestTags.NOSTORAGE, TestTags.CORE)
|
|
|
6bc3c0 |
--
|
|
|
6bc3c0 |
2.26.2
|
|
|
6bc3c0 |
|