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.
This commit is contained in:
Richard Hughes 2017-08-09 15:26:56 +01:00
parent 41cbe2aab3
commit 049ccc8f6c
4 changed files with 164 additions and 0 deletions

10
data/tests/spawn.sh Executable file
View File

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

View File

@ -375,3 +375,120 @@ fu_common_firmware_builder (GBytes *bytes,
/* success */ /* success */
return g_steal_pointer (&firmware_blob); 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);
}

View File

@ -24,6 +24,15 @@
#include <glib.h> #include <glib.h>
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, gboolean fu_common_rmtree (const gchar *directory,
GError **error); GError **error);
gboolean fu_common_set_contents_bytes (const gchar *filename, gboolean fu_common_set_contents_bytes (const gchar *filename,

View File

@ -449,6 +449,33 @@ fu_common_firmware_builder_func (void)
g_assert_cmpstr (data, ==, "xobdnas eht ni gninnur"); 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 int
main (int argc, char **argv) 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{delay}", fu_plugin_delay_func);
g_test_add_func ("/fwupd/plugin{module}", fu_plugin_module_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/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); g_test_add_func ("/fwupd/common{firmware-builder}", fu_common_firmware_builder_func);
return g_test_run (); return g_test_run ();
} }