Unexport fu_common_spawn_sync()

This isn't something we want plugins to use.
This commit is contained in:
Richard Hughes 2022-06-06 14:43:59 +01:00 committed by Mario Limonciello
parent 425fedcca1
commit 25dccf09f7
12 changed files with 293 additions and 267 deletions

View File

@ -90,6 +90,8 @@ if __name__ == "__main__":
"fu_common_read_uint32": "fu_memread_uint32",
"fu_common_read_uint64": "fu_memread_uint64",
"fu_common_bytes_compare_raw": "fu_memcmp_safe",
"FuOutputHandler": "FuSpawnOutputHandler",
"fu_common_spawn_sync": "fu_spawn_sync",
}.items():
if buf.find(old) == -1:
continue

View File

@ -70,3 +70,4 @@ Remember: Plugins should be upstream!
* `fu_common_read*`: Use `fu_memread` prefix, i.e. replace the `_common` with `_mem`
* `fu_common_write*`: Use `fu_memwrite` prefix, i.e. replace the `_common` with `_mem`
* `fu_common_bytes_compare_raw()`: Use `fu_memcmp_safe()` instead
* `fu_common_spawn_sync()`: Use `g_spawn_sync()` instead, or ideally not at all!

View File

@ -503,196 +503,6 @@ fu_common_firmware_builder(GBytes *bytes,
return g_steal_pointer(&firmware_blob);
}
typedef struct {
FuOutputHandler handler_cb;
gpointer handler_user_data;
GMainLoop *loop;
GSource *source;
GInputStream *stream;
GCancellable *cancellable;
guint timeout_id;
} 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_warning("failed to get read from nonblocking fd: %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)
{
g_object_unref(helper->cancellable);
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);
if (helper->timeout_id != 0)
g_source_remove(helper->timeout_id);
g_free(helper);
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCommonSpawnHelper, fu_common_spawn_helper_free)
#pragma clang diagnostic pop
#ifndef _WIN32
static gboolean
fu_common_spawn_timeout_cb(gpointer user_data)
{
FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *)user_data;
g_cancellable_cancel(helper->cancellable);
g_main_loop_quit(helper->loop);
helper->timeout_id = 0;
return G_SOURCE_REMOVE;
}
static void
fu_common_spawn_cancelled_cb(GCancellable *cancellable, FuCommonSpawnHelper *helper)
{
/* just propagate */
g_cancellable_cancel(helper->cancellable);
}
#endif
/**
* fu_common_spawn_sync:
* @argv: the argument list to run
* @handler_cb: (scope call) (nullable): optional #FuOutputHandler
* @handler_user_data: (nullable): the user data to pass to @handler_cb
* @timeout_ms: a timeout in ms, or 0 for no limit
* @cancellable: (nullable): optional #GCancellable
* @error: (nullable): optional return location for an error
*
* 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
*
* Since: 0.9.7
**/
gboolean
fu_common_spawn_sync(const gchar *const *argv,
FuOutputHandler handler_cb,
gpointer handler_user_data,
guint timeout_ms,
GCancellable *cancellable,
GError **error)
{
g_autoptr(FuCommonSpawnHelper) helper = NULL;
g_autoptr(GSubprocess) subprocess = NULL;
g_autofree gchar *argv_str = NULL;
#ifndef _WIN32
gulong cancellable_id = 0;
#endif
g_return_val_if_fail(argv != NULL, FALSE);
g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* create subprocess */
argv_str = g_strjoinv(" ", (gchar **)argv);
g_debug("running '%s'", argv_str);
subprocess =
g_subprocess_newv(argv,
G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_MERGE,
error);
if (subprocess == NULL)
return FALSE;
#ifndef _WIN32
/* 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);
/* always create a cancellable, and connect up the parent */
helper->cancellable = g_cancellable_new();
if (cancellable != NULL) {
cancellable_id = g_cancellable_connect(cancellable,
G_CALLBACK(fu_common_spawn_cancelled_cb),
helper,
NULL);
}
/* allow timeout */
if (timeout_ms > 0) {
helper->timeout_id = g_timeout_add(timeout_ms, fu_common_spawn_timeout_cb, helper);
}
fu_common_spawn_create_pollable_source(helper);
g_main_loop_run(helper->loop);
g_cancellable_disconnect(cancellable, cancellable_id);
#endif
if (!g_subprocess_wait_check(subprocess, cancellable, error))
return FALSE;
#ifndef _WIN32
if (g_cancellable_set_error_if_cancelled(helper->cancellable, error))
return FALSE;
#endif
/* success */
return TRUE;
}
static const GError *
fu_common_error_array_find(GPtrArray *errors, FwupdError error_code)
{

View File

@ -169,23 +169,6 @@ typedef enum {
FU_LID_STATE_LAST
} FuLidState;
/**
* FuOutputHandler:
* @line: text data
* @user_data: user data
*
* The process spawn iteration callback.
*/
typedef void (*FuOutputHandler)(const gchar *line, gpointer user_data);
gboolean
fu_common_spawn_sync(const gchar *const *argv,
FuOutputHandler handler_cb,
gpointer handler_user_data,
guint timeout_ms,
GCancellable *cancellable,
GError **error) G_GNUC_WARN_UNUSED_RESULT;
gchar *
fu_common_get_path(FuPathKind path_kind);
gchar *

View File

@ -948,14 +948,6 @@ 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 gboolean
_open_cb(GObject *device, GError **error)
{
@ -1020,50 +1012,6 @@ fu_device_locker_fail_func(void)
g_assert_false(fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_IS_OPEN));
}
static void
fu_common_spawn_func(void)
{
gboolean ret;
guint lines = 0;
g_autoptr(GError) error = NULL;
g_autofree gchar *fn = NULL;
const gchar *argv[4] = {"/bin/sh", "replace", "test", NULL};
#ifdef _WIN32
g_test_skip("Known failures on Windows right now, skipping spawn func test");
return;
#endif
fn = g_test_build_filename(G_TEST_DIST, "tests", "spawn.sh", NULL);
argv[1] = fn;
ret = fu_common_spawn_sync(argv, fu_test_stdout_cb, &lines, 0, NULL, &error);
g_assert_no_error(error);
g_assert_true(ret);
g_assert_cmpint(lines, ==, 6);
}
static void
fu_common_spawn_timeout_func(void)
{
gboolean ret;
guint lines = 0;
g_autoptr(GError) error = NULL;
g_autofree gchar *fn = NULL;
const gchar *argv[4] = {"/bin/sh", "replace", "test", NULL};
#ifdef _WIN32
g_test_skip("Known failures on Windows right now, skipping spawn timeout test");
return;
#endif
fn = g_test_build_filename(G_TEST_DIST, "tests", "spawn.sh", NULL);
argv[1] = fn;
ret = fu_common_spawn_sync(argv, fu_test_stdout_cb, &lines, 500, NULL, &error);
g_assert_error(error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
g_assert_false(ret);
g_assert_cmpint(lines, ==, 1);
}
static void
fu_common_endian_func(void)
{
@ -4070,8 +4018,6 @@ main(int argc, char **argv)
fu_common_store_cab_error_missing_file_func);
g_test_add_func("/fwupd/common{cab-error-size}", fu_common_store_cab_error_size_func);
g_test_add_func("/fwupd/common{bytes-get-data}", fu_common_bytes_get_data_func);
g_test_add_func("/fwupd/common{spawn)", fu_common_spawn_func);
g_test_add_func("/fwupd/common{spawn-timeout)", fu_common_spawn_timeout_func);
g_test_add_func("/fwupd/common{firmware-builder}", fu_common_firmware_builder_func);
g_test_add_func("/fwupd/common{kernel-lockdown}", fu_common_kernel_lockdown_func);
g_test_add_func("/fwupd/common{strsafe}", fu_strsafe_func);

View File

@ -74,7 +74,6 @@ LIBFWUPDPLUGIN_0.9.7 {
fu_common_firmware_builder;
fu_common_mkdir_parent;
fu_common_rmtree;
fu_common_spawn_sync;
fu_device_get_metadata_boolean;
fu_device_get_metadata_integer;
fu_device_set_metadata_boolean;

View File

@ -14,6 +14,7 @@
#include "fu-history.h"
#include "fu-plugin-private.h"
#include "fu-spawn.h"
#include "fu-util-common.h"
typedef enum {
@ -46,7 +47,7 @@ fu_offline_set_splash_progress(FuUtilPrivate *priv, guint percentage, GError **e
argv[1] = "display-message";
argv[2] = "--text";
}
return fu_common_spawn_sync(argv, NULL, NULL, 200, NULL, error);
return fu_spawn_sync(argv, NULL, NULL, 200, NULL, error);
}
static gboolean
@ -63,9 +64,9 @@ fu_offline_set_splash_mode(FuUtilPrivate *priv, GError **error)
}
/* try the new fancy mode, then fall back to really old mode */
if (!fu_common_spawn_sync(argv, NULL, NULL, 1500, NULL, &error_local)) {
if (!fu_spawn_sync(argv, NULL, NULL, 1500, NULL, &error_local)) {
argv[2] = "--updates";
if (!fu_common_spawn_sync(argv, NULL, NULL, 1500, NULL, error)) {
if (!fu_spawn_sync(argv, NULL, NULL, 1500, NULL, error)) {
g_prefix_error(error, "%s: ", error_local->message);
return FALSE;
}
@ -92,10 +93,10 @@ fu_offline_set_splash_reboot(FuUtilPrivate *priv, GError **error)
}
/* try the new fancy mode, then fall back to really old mode */
if (!fu_common_spawn_sync(argv, NULL, NULL, 200, NULL, &error_local)) {
if (!fu_spawn_sync(argv, NULL, NULL, 200, NULL, &error_local)) {
/* fall back to really old mode that should be supported */
argv[2] = "--shutdown";
if (!fu_common_spawn_sync(argv, NULL, NULL, 200, NULL, error)) {
if (!fu_spawn_sync(argv, NULL, NULL, 200, NULL, error)) {
g_prefix_error(error, "%s: ", error_local->message);
return FALSE;
}

View File

@ -29,6 +29,7 @@
#include "fu-progressbar.h"
#include "fu-security-attr.h"
#include "fu-smbios-private.h"
#include "fu-spawn.h"
typedef struct {
FuPlugin *plugin;
@ -3802,6 +3803,58 @@ fu_release_compare_func_cb(gconstpointer a, gconstpointer b)
return fu_release_compare(release1, release2);
}
static void
fu_spawn_stdout_cb(const gchar *line, gpointer user_data)
{
guint *lines = (guint *)user_data;
g_debug("got '%s'", line);
(*lines)++;
}
static void
fu_spawn_func(void)
{
gboolean ret;
guint lines = 0;
g_autoptr(GError) error = NULL;
g_autofree gchar *fn = NULL;
const gchar *argv[4] = {"/bin/sh", "replace", "test", NULL};
#ifdef _WIN32
g_test_skip("Known failures on Windows right now, skipping spawn func test");
return;
#endif
fn = g_test_build_filename(G_TEST_DIST, "tests", "spawn.sh", NULL);
argv[1] = fn;
ret = fu_spawn_sync(argv, fu_spawn_stdout_cb, &lines, 0, NULL, &error);
g_assert_no_error(error);
g_assert_true(ret);
g_assert_cmpint(lines, ==, 6);
}
static void
fu_spawn_timeout_func(void)
{
gboolean ret;
guint lines = 0;
g_autoptr(GError) error = NULL;
g_autofree gchar *fn = NULL;
const gchar *argv[4] = {"/bin/sh", "replace", "test", NULL};
#ifdef _WIN32
g_test_skip("Known failures on Windows right now, skipping spawn timeout test");
return;
#endif
fn = g_test_build_filename(G_TEST_DIST, "tests", "spawn.sh", NULL);
argv[1] = fn;
ret = fu_spawn_sync(argv, fu_spawn_stdout_cb, &lines, 500, NULL, &error);
g_assert_error(error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
g_assert_false(ret);
g_assert_cmpint(lines, ==, 1);
}
static void
fu_release_compare_func(gconstpointer user_data)
{
@ -3994,5 +4047,7 @@ main(int argc, char **argv)
g_test_add_data_func("/fwupd/history{migrate}", self, fu_history_migrate_func);
g_test_add_data_func("/fwupd/plugin-list", self, fu_plugin_list_func);
g_test_add_data_func("/fwupd/plugin-list{depsolve}", self, fu_plugin_list_depsolve_func);
g_test_add_func("/fwupd/spawn", fu_spawn_func);
g_test_add_func("/fwupd/spawn-timeou)", fu_spawn_timeout_func);
return g_test_run();
}

201
src/fu-spawn.c Normal file
View File

@ -0,0 +1,201 @@
/*
* Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#define G_LOG_DOMAIN "FuSpawn"
#include "config.h"
#include "fu-spawn.h"
typedef struct {
FuSpawnOutputHandler handler_cb;
gpointer handler_user_data;
GMainLoop *loop;
GSource *source;
GInputStream *stream;
GCancellable *cancellable;
guint timeout_id;
} FuSpawnHelper;
static void
fu_spawn_create_pollable_source(FuSpawnHelper *helper);
static gboolean
fu_spawn_source_pollable_cb(GObject *stream, gpointer user_data)
{
FuSpawnHelper *helper = (FuSpawnHelper *)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_warning("failed to get read from nonblocking fd: %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_spawn_create_pollable_source(helper);
return G_SOURCE_REMOVE;
}
static void
fu_spawn_create_pollable_source(FuSpawnHelper *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_spawn_source_pollable_cb,
helper,
NULL);
}
static void
fu_spawn_helper_free(FuSpawnHelper *helper)
{
g_object_unref(helper->cancellable);
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);
if (helper->timeout_id != 0)
g_source_remove(helper->timeout_id);
g_free(helper);
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuSpawnHelper, fu_spawn_helper_free)
#pragma clang diagnostic pop
#ifndef _WIN32
static gboolean
fu_spawn_timeout_cb(gpointer user_data)
{
FuSpawnHelper *helper = (FuSpawnHelper *)user_data;
g_cancellable_cancel(helper->cancellable);
g_main_loop_quit(helper->loop);
helper->timeout_id = 0;
return G_SOURCE_REMOVE;
}
static void
fu_spawn_cancelled_cb(GCancellable *cancellable, FuSpawnHelper *helper)
{
/* just propagate */
g_cancellable_cancel(helper->cancellable);
}
#endif
/**
* fu_spawn_sync:
* @argv: the argument list to run
* @handler_cb: (scope call) (nullable): optional #FuSpawnOutputHandler
* @handler_user_data: (nullable): the user data to pass to @handler_cb
* @timeout_ms: a timeout in ms, or 0 for no limit
* @cancellable: (nullable): optional #GCancellable
* @error: (nullable): optional return location for an error
*
* 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
*
* Since: 0.9.7
**/
gboolean
fu_spawn_sync(const gchar *const *argv,
FuSpawnOutputHandler handler_cb,
gpointer handler_user_data,
guint timeout_ms,
GCancellable *cancellable,
GError **error)
{
g_autoptr(FuSpawnHelper) helper = NULL;
g_autoptr(GSubprocess) subprocess = NULL;
g_autofree gchar *argv_str = NULL;
#ifndef _WIN32
gulong cancellable_id = 0;
#endif
g_return_val_if_fail(argv != NULL, FALSE);
g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* create subprocess */
argv_str = g_strjoinv(" ", (gchar **)argv);
g_debug("running '%s'", argv_str);
subprocess =
g_subprocess_newv(argv,
G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_MERGE,
error);
if (subprocess == NULL)
return FALSE;
#ifndef _WIN32
/* watch for process to exit */
helper = g_new0(FuSpawnHelper, 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);
/* always create a cancellable, and connect up the parent */
helper->cancellable = g_cancellable_new();
if (cancellable != NULL) {
cancellable_id = g_cancellable_connect(cancellable,
G_CALLBACK(fu_spawn_cancelled_cb),
helper,
NULL);
}
/* allow timeout */
if (timeout_ms > 0) {
helper->timeout_id = g_timeout_add(timeout_ms, fu_spawn_timeout_cb, helper);
}
fu_spawn_create_pollable_source(helper);
g_main_loop_run(helper->loop);
g_cancellable_disconnect(cancellable, cancellable_id);
#endif
if (!g_subprocess_wait_check(subprocess, cancellable, error))
return FALSE;
#ifndef _WIN32
if (g_cancellable_set_error_if_cancelled(helper->cancellable, error))
return FALSE;
#endif
/* success */
return TRUE;
}

26
src/fu-spawn.h Normal file
View File

@ -0,0 +1,26 @@
/*
* Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include <gio/gio.h>
/**
* FuSpawnOutputHandler:
* @line: text data
* @user_data: user data
*
* The process spawn iteration callback.
*/
typedef void (*FuSpawnOutputHandler)(const gchar *line, gpointer user_data);
gboolean
fu_spawn_sync(const gchar *const *argv,
FuSpawnOutputHandler handler_cb,
gpointer handler_user_data,
guint timeout_ms,
GCancellable *cancellable,
GError **error) G_GNUC_WARN_UNUSED_RESULT;

View File

@ -122,6 +122,7 @@ fwupdoffline = executable(
sources : [
'fu-history.c',
'fu-offline.c',
'fu-spawn.c',
'fu-security-attr.c',
'fu-util-common.c',
systemd_src
@ -269,6 +270,7 @@ if get_option('tests')
fu_hash,
sources : [
'fu-progressbar.c',
'fu-spawn.c',
'fu-self-test.c',
daemon_src,
],