From 049ccc8f6c29117e6089b51108707b582fbfcad5 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Wed, 9 Aug 2017 15:26:56 +0100 Subject: [PATCH] Add a helper function to spawn a subprocess This allows us to watch the output of a flashing tool and screen-scrape the progress completion. --- data/tests/spawn.sh | 10 ++++ src/fu-common.c | 117 ++++++++++++++++++++++++++++++++++++++++++++ src/fu-common.h | 9 ++++ src/fu-self-test.c | 28 +++++++++++ 4 files changed, 164 insertions(+) create mode 100755 data/tests/spawn.sh diff --git a/data/tests/spawn.sh b/data/tests/spawn.sh new file mode 100755 index 000000000..3c5b04b35 --- /dev/null +++ b/data/tests/spawn.sh @@ -0,0 +1,10 @@ +#/bin/sh +echo "this is a test" +sleep 1 +echo "this is another line1" +echo "this is another line2" +echo "this is another line3" +echo "this is another line4" +sleep 1 +echo "done!" +exit 0 diff --git a/src/fu-common.c b/src/fu-common.c index b84d1fbd0..b331892fc 100644 --- a/src/fu-common.c +++ b/src/fu-common.c @@ -375,3 +375,120 @@ fu_common_firmware_builder (GBytes *bytes, /* success */ return g_steal_pointer (&firmware_blob); } + +typedef struct { + FuOutputHandler handler_cb; + gpointer handler_user_data; + GMainLoop *loop; + GSource *source; + GInputStream *stream; + GCancellable *cancellable; +} FuCommonSpawnHelper; + +static void fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper); + +static gboolean +fu_common_spawn_source_pollable_cb (GObject *stream, gpointer user_data) +{ + FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *) user_data; + gchar buffer[1024]; + gssize sz; + g_auto(GStrv) split = NULL; + g_autoptr(GError) error = NULL; + + /* read from stream */ + sz = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (stream), + buffer, + sizeof(buffer) - 1, + NULL, + &error); + if (sz < 0) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) + g_error ("err=%s", error->message); + return G_SOURCE_REMOVE; + } + + /* no read possible */ + if (sz == 0) + g_main_loop_quit (helper->loop); + + /* emit lines */ + if (helper->handler_cb != NULL) { + buffer[sz] = '\0'; + split = g_strsplit (buffer, "\n", -1); + for (guint i = 0; split[i] != NULL; i++) { + if (split[i][0] == '\0') + continue; + helper->handler_cb (split[i], helper->handler_user_data); + } + } + + /* set up the source for the next read */ + fu_common_spawn_create_pollable_source (helper); + return G_SOURCE_REMOVE; +} + +static void +fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper) +{ + if (helper->source != NULL) + g_source_destroy (helper->source); + helper->source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (helper->stream), + helper->cancellable); + g_source_attach (helper->source, NULL); + g_source_set_callback (helper->source, (GSourceFunc) fu_common_spawn_source_pollable_cb, helper, NULL); +} + +static void +fu_common_spawn_helper_free (FuCommonSpawnHelper *helper) +{ + if (helper->stream != NULL) + g_object_unref (helper->stream); + if (helper->source != NULL) + g_source_destroy (helper->source); + if (helper->loop != NULL) + g_main_loop_unref (helper->loop); + g_free (helper); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCommonSpawnHelper, fu_common_spawn_helper_free) + +/** + * fu_common_spawn_sync: + * @argv: The argument list to run + * @handler_cb: A #FuOutputHandler or %NULL + * @handler_user_data: the user data to pass to @handler + * @cancellable: a #GCancellable, or %NULL + * @error: A #GError or %NULL + * + * Runs a subprocess and waits for it to exit. Any output on standard out or + * standard error will be forwarded to @handler_cb as whole lines. + * + * Returns: %TRUE for success + **/ +gboolean +fu_common_spawn_sync (const gchar * const * argv, + FuOutputHandler handler_cb, + gpointer handler_user_data, + GCancellable *cancellable, GError **error) +{ + g_autoptr(FuCommonSpawnHelper) helper = NULL; + g_autoptr(GSubprocess) subprocess = NULL; + + /* create subprocess */ + subprocess = g_subprocess_newv (argv, G_SUBPROCESS_FLAGS_STDOUT_PIPE | + G_SUBPROCESS_FLAGS_STDERR_MERGE, error); + if (subprocess == NULL) + return FALSE; + + /* watch for process to exit */ + helper = g_new0 (FuCommonSpawnHelper, 1); + helper->handler_cb = handler_cb; + helper->handler_user_data = handler_user_data; + helper->loop = g_main_loop_new (NULL, FALSE); + helper->stream = g_subprocess_get_stdout_pipe (subprocess); + helper->cancellable = cancellable; + fu_common_spawn_create_pollable_source (helper); + g_main_loop_run (helper->loop); + return g_subprocess_wait_check (subprocess, cancellable, error); +} diff --git a/src/fu-common.h b/src/fu-common.h index 4a01dd931..03bddc98e 100644 --- a/src/fu-common.h +++ b/src/fu-common.h @@ -24,6 +24,15 @@ #include +typedef void (*FuOutputHandler) (const gchar *line, + gpointer user_data); + +gboolean fu_common_spawn_sync (const gchar * const *argv, + FuOutputHandler handler, + gpointer handler_user_data, + GCancellable *cancellable, + GError **error); + gboolean fu_common_rmtree (const gchar *directory, GError **error); gboolean fu_common_set_contents_bytes (const gchar *filename, diff --git a/src/fu-self-test.c b/src/fu-self-test.c index 465de45e7..317bebb26 100644 --- a/src/fu-self-test.c +++ b/src/fu-self-test.c @@ -449,6 +449,33 @@ fu_common_firmware_builder_func (void) g_assert_cmpstr (data, ==, "xobdnas eht ni gninnur"); } +static void +fu_test_stdout_cb (const gchar *line, gpointer user_data) +{ + guint *lines = (guint *) user_data; + g_debug ("got '%s'", line); + (*lines)++; +} + +static void +fu_common_spawn_func (void) +{ + gboolean ret; + guint lines = 0; + g_autoptr(GError) error = NULL; + g_autofree gchar *fn = NULL; + gchar *argv[3] = { "replace", "test", NULL }; + + fn = fu_test_get_filename (TESTDATADIR, "spawn.sh"); + g_assert (fn != NULL); + argv[0] = fn; + ret = fu_common_spawn_sync ((const gchar * const *) argv, + fu_test_stdout_cb, &lines, NULL, &error); + g_assert_no_error (error); + g_assert (ret); + g_assert_cmpint (lines, ==, 6); +} + int main (int argc, char **argv) { @@ -466,6 +493,7 @@ main (int argc, char **argv) g_test_add_func ("/fwupd/plugin{delay}", fu_plugin_delay_func); g_test_add_func ("/fwupd/plugin{module}", fu_plugin_module_func); g_test_add_func ("/fwupd/keyring", fu_keyring_func); + g_test_add_func ("/fwupd/common{spawn)", fu_common_spawn_func); g_test_add_func ("/fwupd/common{firmware-builder}", fu_common_firmware_builder_func); return g_test_run (); }