diff options
Diffstat (limited to 'src/mm-dispatcher.c')
-rw-r--r-- | src/mm-dispatcher.c | 351 |
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]); +} |