aboutsummaryrefslogtreecommitdiff
path: root/src/mm-dispatcher.c
diff options
context:
space:
mode:
authorAleksander Morgado <aleksander@aleksander.es>2022-02-16 23:12:17 +0100
committerAleksander Morgado <aleksander@aleksander.es>2022-04-05 08:14:53 +0000
commit01c8edf68cdd32582e7fbc32a0d83f71811e31d6 (patch)
treeb1df4b2b1140ef3f7132a9d67c93c4abc8f49ec7 /src/mm-dispatcher.c
parent1fec1cd3c225fd418a2a20f726c93b570a187805 (diff)
dispatcher: new generic object to handle script dispatchers
Based on the FCC unlock specific one, but without any FCC unlock specific constraint.
Diffstat (limited to 'src/mm-dispatcher.c')
-rw-r--r--src/mm-dispatcher.c351
1 files changed, 351 insertions, 0 deletions
diff --git a/src/mm-dispatcher.c b/src/mm-dispatcher.c
new file mode 100644
index 00000000..9b45eca0
--- /dev/null
+++ b/src/mm-dispatcher.c
@@ -0,0 +1,351 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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:
+ *
+ * Copyright (C) 2021-2022 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <config.h>
+#include <sys/stat.h>
+
+#include <ModemManager.h>
+#include "mm-errors-types.h"
+#include "mm-utils.h"
+#include "mm-log-object.h"
+#include "mm-dispatcher.h"
+
+static void log_object_iface_init (MMLogObjectInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (MMDispatcher, mm_dispatcher, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_LOG_OBJECT, log_object_iface_init))
+
+enum {
+ PROP_0,
+ PROP_OPERATION_DESCRIPTION,
+ PROP_LAST
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+struct _MMDispatcherPrivate {
+ gchar *operation_description;
+ GSubprocessLauncher *launcher;
+};
+
+/*****************************************************************************/
+
+static gchar *
+log_object_build_id (MMLogObject *_self)
+{
+ MMDispatcher *self = MM_DISPATCHER (_self);
+
+ return g_strdup_printf ("%s dispatcher", self->priv->operation_description);
+}
+
+/*****************************************************************************/
+
+static gboolean
+validate_file (const gchar *path,
+ GError **error)
+{
+ g_autoptr(GFile) file = NULL;
+ g_autoptr(GFileInfo) file_info = NULL;
+ guint32 file_mode;
+ guint32 file_uid;
+
+ file = g_file_new_for_path (path);
+ file_info = g_file_query_info (file,
+ (G_FILE_ATTRIBUTE_STANDARD_SIZE ","
+ G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET ","
+ G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK ","
+ G_FILE_ATTRIBUTE_UNIX_MODE ","
+ G_FILE_ATTRIBUTE_UNIX_UID),
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ error);
+ if (!file_info)
+ return FALSE;
+
+ if (g_file_info_get_is_symlink (file_info)) {
+ const gchar *link_target;
+
+ link_target = g_file_info_get_symlink_target (file_info);
+ if (g_strcmp0 (link_target, "/dev/null") == 0) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
+ "Link '%s' to /dev/null is not executable", path);
+ return FALSE;
+ }
+ }
+
+ if (g_file_info_get_size (file_info) == 0) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
+ "File '%s' is empty", path);
+ return FALSE;
+ }
+
+ file_uid = g_file_info_get_attribute_uint32 (file_info, G_FILE_ATTRIBUTE_UNIX_UID);
+ if (file_uid != 0) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
+ "File '%s' not owned by root", path);
+ return FALSE;
+ }
+
+ file_mode = g_file_info_get_attribute_uint32 (file_info, G_FILE_ATTRIBUTE_UNIX_MODE);
+ if (!S_ISREG (file_mode)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
+ "File '%s' is not regular", path);
+ return FALSE;
+ }
+ if (file_mode & (S_IWGRP | S_IWOTH)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
+ "File '%s' is writable by group or other", path);
+ return FALSE;
+ }
+ if (file_mode & S_ISUID) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
+ "File '%s' is set-UID", path);
+ return FALSE;
+ }
+ if (!(file_mode & S_IXUSR)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
+ "File '%s' is not executable by the owner", path);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+ GSubprocess *subprocess;
+ guint timeout_id;
+} RunContext;
+
+static void
+run_context_free (RunContext *ctx)
+{
+ g_assert (!ctx->timeout_id);
+ g_clear_object (&ctx->subprocess);
+ g_slice_free (RunContext, ctx);
+}
+
+gboolean
+mm_dispatcher_run_finish (MMDispatcher *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static gboolean
+subprocess_wait_timed_out (GTask *task)
+{
+ MMDispatcher *self;
+ RunContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ mm_obj_warn (self, "forcing exit on %s operation", self->priv->operation_description);
+ g_subprocess_force_exit (ctx->subprocess);
+
+ ctx->timeout_id = 0;
+ return G_SOURCE_REMOVE;
+}
+
+static void
+subprocess_wait_ready (GSubprocess *subprocess,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ MMDispatcher *self;
+ RunContext *ctx;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ /* cleanup timeout before any return */
+ if (ctx->timeout_id) {
+ g_source_remove (ctx->timeout_id);
+ ctx->timeout_id = 0;
+ }
+
+ if (!g_subprocess_wait_finish (subprocess, res, &error)) {
+ g_prefix_error (&error, "%s operation wait failed: ", self->priv->operation_description);
+ g_task_return_error (task, error);
+ } else if (!g_subprocess_get_successful (subprocess)) {
+ if (g_subprocess_get_if_signaled (subprocess))
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "%s operation aborted with signal %d",
+ self->priv->operation_description,
+ g_subprocess_get_term_sig (subprocess));
+ else if (g_subprocess_get_if_exited (subprocess))
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "%s operation finished with status %d",
+ self->priv->operation_description,
+ g_subprocess_get_exit_status (subprocess));
+ else
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "%s operation failed", self->priv->operation_description);
+ } else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_dispatcher_run (MMDispatcher *self,
+ const GStrv argv,
+ guint timeout_secs,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ RunContext *ctx;
+ GError *error = NULL;
+
+ g_assert (g_strv_length (argv) >= 1);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ ctx = g_slice_new0 (RunContext);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) run_context_free);
+
+ /* Validation checks to see if we should run it or not */
+ if (!validate_file (argv[0], &error)) {
+ g_prefix_error (&error, "Cannot run %s operation from %s: ",
+ self->priv->operation_description, argv[0]);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* create and launch subprocess */
+ ctx->subprocess = g_subprocess_launcher_spawnv (self->priv->launcher,
+ (const gchar * const *)argv,
+ &error);
+ if (!ctx->subprocess) {
+ g_prefix_error (&error, "%s operation launch from %s failed: ",
+ self->priv->operation_description, argv[0]);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* setup timeout */
+ ctx->timeout_id = g_timeout_add_seconds (timeout_secs,
+ (GSourceFunc)subprocess_wait_timed_out,
+ task);
+
+ /* wait for subprocess exit */
+ g_subprocess_wait_async (ctx->subprocess,
+ cancellable,
+ (GAsyncReadyCallback)subprocess_wait_ready,
+ task);
+}
+
+/*****************************************************************************/
+
+static void
+mm_dispatcher_init (MMDispatcher *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_DISPATCHER, MMDispatcherPrivate);
+
+ /* Create launcher and inherit parent's environment */
+ self->priv->launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_SILENCE | G_SUBPROCESS_FLAGS_STDERR_SILENCE);
+ g_subprocess_launcher_set_environ (self->priv->launcher, NULL);
+}
+
+static void
+set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MMDispatcher *self = MM_DISPATCHER (object);
+
+ switch (prop_id) {
+ case PROP_OPERATION_DESCRIPTION:
+ /* construct only */
+ self->priv->operation_description = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MMDispatcher *self = MM_DISPATCHER (object);
+
+ switch (prop_id) {
+ case PROP_OPERATION_DESCRIPTION:
+ g_value_set_string (value, self->priv->operation_description);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ MMDispatcher *self = MM_DISPATCHER (object);
+
+ g_clear_object (&self->priv->launcher);
+
+ G_OBJECT_CLASS (mm_dispatcher_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ MMDispatcher *self = MM_DISPATCHER (object);
+
+ g_free (self->priv->operation_description);
+
+ G_OBJECT_CLASS (mm_dispatcher_parent_class)->finalize (object);
+}
+
+static void
+log_object_iface_init (MMLogObjectInterface *iface)
+{
+ iface->build_id = log_object_build_id;
+}
+
+static void
+mm_dispatcher_class_init (MMDispatcherClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ g_type_class_add_private (object_class, sizeof (MMDispatcherPrivate));
+
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ properties[PROP_OPERATION_DESCRIPTION] =
+ g_param_spec_string (MM_DISPATCHER_OPERATION_DESCRIPTION,
+ "Operation description",
+ "String describing the operation, to be used in logging",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property (object_class, PROP_OPERATION_DESCRIPTION, properties[PROP_OPERATION_DESCRIPTION]);
+}