diff options
-rw-r--r-- | cli/mmcli-manager.c | 54 | ||||
-rw-r--r-- | docs/reference/libmm-glib/libmm-glib-sections.txt | 10 | ||||
-rw-r--r-- | introspection/org.freedesktop.ModemManager1.xml | 21 | ||||
-rw-r--r-- | libmm-glib/mm-manager.c | 236 | ||||
-rw-r--r-- | libmm-glib/mm-manager.h | 26 | ||||
-rw-r--r-- | src/mm-base-manager.c | 383 | ||||
-rw-r--r-- | src/mm-device.c | 103 | ||||
-rw-r--r-- | src/mm-device.h | 13 |
8 files changed, 829 insertions, 17 deletions
diff --git a/cli/mmcli-manager.c b/cli/mmcli-manager.c index 810b0aa3..dac107c9 100644 --- a/cli/mmcli-manager.c +++ b/cli/mmcli-manager.c @@ -56,6 +56,7 @@ static gboolean list_modems_flag; static gboolean monitor_modems_flag; static gboolean scan_modems_flag; static gchar *set_logging_str; +static gchar *inhibit_device_str; static gchar *report_kernel_event_str; #if defined WITH_UDEV @@ -83,6 +84,10 @@ static GOptionEntry entries[] = { "Request to re-scan looking for modems", NULL }, + { "inhibit-device", 'I', 0, G_OPTION_ARG_STRING, &inhibit_device_str, + "Inhibit device given a unique device identifier", + "[UID]" + }, { "report-kernel-event", 'K', 0, G_OPTION_ARG_STRING, &report_kernel_event_str, "Report kernel event", "[\"key=value,...\"]" @@ -126,6 +131,7 @@ mmcli_manager_options_enabled (void) monitor_modems_flag + scan_modems_flag + !!set_logging_str + + !!inhibit_device_str + !!report_kernel_event_str); #if defined WITH_UDEV @@ -145,7 +151,8 @@ mmcli_manager_options_enabled (void) exit (EXIT_FAILURE); } mmcli_force_async_operation (); - } + } else if (inhibit_device_str) + mmcli_force_async_operation (); #if defined WITH_UDEV if (report_kernel_event_auto_scan) @@ -181,6 +188,41 @@ mmcli_manager_shutdown (void) } static void +inhibition_cancelled (GCancellable *cancellable) +{ + GError *error = NULL; + + if (!mm_manager_uninhibit_device_sync (ctx->manager, inhibit_device_str, NULL, &error)) { + g_printerr ("error: couldn't uninhibit device: '%s'\n", + error ? error->message : "unknown error"); + } else + g_print ("successfully uninhibited device with uid '%s'\n", inhibit_device_str); + + mmcli_async_operation_done (); +} + +static void +inhibit_device_ready (MMManager *manager, + GAsyncResult *result) +{ + GError *error = NULL; + + if (!mm_manager_inhibit_device_finish (manager, result, &error)) { + g_printerr ("error: couldn't inhibit device: '%s'\n", + error ? error->message : "unknown error"); + exit (EXIT_FAILURE); + } + + g_print ("successfully inhibited device with uid '%s'\n", inhibit_device_str); + g_print ("type Ctrl+C to abort this program and remove the inhibition\n"); + + g_cancellable_connect (ctx->cancellable, + G_CALLBACK (inhibition_cancelled), + NULL, + NULL); +} + +static void report_kernel_event_process_reply (gboolean result, const GError *error) { @@ -463,6 +505,16 @@ get_manager_ready (GObject *source, return; } + /* Request to inhibit device? */ + if (inhibit_device_str) { + mm_manager_inhibit_device (ctx->manager, + inhibit_device_str, + ctx->cancellable, + (GAsyncReadyCallback)inhibit_device_ready, + NULL); + return; + } + g_warn_if_reached (); } diff --git a/docs/reference/libmm-glib/libmm-glib-sections.txt b/docs/reference/libmm-glib/libmm-glib-sections.txt index 08c58973..41f3b0d6 100644 --- a/docs/reference/libmm-glib/libmm-glib-sections.txt +++ b/docs/reference/libmm-glib/libmm-glib-sections.txt @@ -18,6 +18,12 @@ mm_manager_get_version mm_manager_scan_devices mm_manager_scan_devices_finish mm_manager_scan_devices_sync +mm_manager_inhibit_device +mm_manager_inhibit_device_finish +mm_manager_inhibit_device_sync +mm_manager_uninhibit_device +mm_manager_uninhibit_device_finish +mm_manager_uninhibit_device_sync mm_manager_set_logging mm_manager_set_logging_finish mm_manager_set_logging_sync @@ -1731,6 +1737,9 @@ mm_gdbus_org_freedesktop_modem_manager1_get_version mm_gdbus_org_freedesktop_modem_manager1_call_scan_devices mm_gdbus_org_freedesktop_modem_manager1_call_scan_devices_finish mm_gdbus_org_freedesktop_modem_manager1_call_scan_devices_sync +mm_gdbus_org_freedesktop_modem_manager1_call_inhibit_device +mm_gdbus_org_freedesktop_modem_manager1_call_inhibit_device_finish +mm_gdbus_org_freedesktop_modem_manager1_call_inhibit_device_sync mm_gdbus_org_freedesktop_modem_manager1_call_set_logging mm_gdbus_org_freedesktop_modem_manager1_call_set_logging_finish mm_gdbus_org_freedesktop_modem_manager1_call_set_logging_sync @@ -1740,6 +1749,7 @@ mm_gdbus_org_freedesktop_modem_manager1_call_report_kernel_event_sync <SUBSECTION Private> mm_gdbus_org_freedesktop_modem_manager1_set_version mm_gdbus_org_freedesktop_modem_manager1_override_properties +mm_gdbus_org_freedesktop_modem_manager1_complete_inhibit_device mm_gdbus_org_freedesktop_modem_manager1_complete_scan_devices mm_gdbus_org_freedesktop_modem_manager1_complete_set_logging mm_gdbus_org_freedesktop_modem_manager1_complete_report_kernel_event diff --git a/introspection/org.freedesktop.ModemManager1.xml b/introspection/org.freedesktop.ModemManager1.xml index 4d7a4e84..85b62134 100644 --- a/introspection/org.freedesktop.ModemManager1.xml +++ b/introspection/org.freedesktop.ModemManager1.xml @@ -107,6 +107,27 @@ </method> <!-- + InhibitDevice: + @uid: the unique ID of the physical device, given in the + #org.freedesktop.ModemManager1.Modem:Device property. + @inhibit: %TRUE to inhibit the modem and %FALSE to uninhibit it. + + Inhibit or uninhibit the device. + + When the modem is inhibited ModemManager will close all its ports and + unexport it from the bus, so that users of the interface are no longer + able to operate with it. + + This operation binds the inhibition request to the existence of the + caller in the DBus bus. If the caller disappears from the bus, the + inhibition will automatically removed. + --> + <method name="InhibitDevice"> + <arg name="uid" type="s" direction="in" /> + <arg name="inhibit" type="b" direction="in" /> + </method> + + <!-- Version: The runtime version of the ModemManager daemon. diff --git a/libmm-glib/mm-manager.c b/libmm-glib/mm-manager.c index 975f13ca..fe2932db 100644 --- a/libmm-glib/mm-manager.c +++ b/libmm-glib/mm-manager.c @@ -638,6 +638,242 @@ mm_manager_report_kernel_event_sync (MMManager *manager, /*****************************************************************************/ +static gboolean +common_inhibit_device_finish (MMManager *manager, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +inhibit_ready (MmGdbusOrgFreedesktopModemManager1 *manager_iface_proxy, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_gdbus_org_freedesktop_modem_manager1_call_inhibit_device_finish (manager_iface_proxy, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +common_inhibit_device (MMManager *manager, + const gchar *uid, + gboolean inhibit, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + GError *inner_error = NULL; + + task = g_task_new (manager, cancellable, callback, user_data); + + if (!ensure_modem_manager1_proxy (manager, &inner_error)) { + g_task_return_error (task, inner_error); + g_object_unref (task); + return; + } + + mm_gdbus_org_freedesktop_modem_manager1_call_inhibit_device ( + manager->priv->manager_iface_proxy, + uid, + inhibit, + cancellable, + (GAsyncReadyCallback)inhibit_ready, + task); +} + +static gboolean +common_inhibit_device_sync (MMManager *manager, + const gchar *uid, + gboolean inhibit, + GCancellable *cancellable, + GError **error) +{ + if (!ensure_modem_manager1_proxy (manager, error)) + return FALSE; + + return (mm_gdbus_org_freedesktop_modem_manager1_call_inhibit_device_sync ( + manager->priv->manager_iface_proxy, + uid, + inhibit, + cancellable, + error)); +} + +/** + * mm_manager_inhibit_device_finish: + * @manager: A #MMManager. + * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to mm_manager_inhibit_device(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with mm_manager_inhibit_device(). + * + * Returns: %TRUE if the call succeeded, %FALSE if @error is set. + */ +gboolean +mm_manager_inhibit_device_finish (MMManager *manager, + GAsyncResult *res, + GError **error) +{ + g_return_val_if_fail (MM_IS_MANAGER (manager), FALSE); + return common_inhibit_device_finish (manager, res, error); +} + +/** + * mm_manager_inhibit_device: + * @manager: A #MMManager. + * @uid: the unique ID of the physical device. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL. + * @user_data: User data to pass to @callback. + * + * Asynchronously requests to add an inhibition on the device identified by @uid. + * + * The @uid must be the unique ID retrieved from an existing #MMModem using + * mm_modem_get_device(). The caller should keep track of this @uid and use it + * in the mm_manager_uninhibit_device() call when the inhibition is no longer required. + * + * The inhibition added with this method may also be automatically removed when + * the caller program disappears from the bus (e.g. if the program ends before + * having called mm_manager_uninhibit_device() explicitly). + * + * When the operation is finished, @callback will be invoked in the + * <link linkend="g-main-context-push-thread-default">thread-default main loop</link> + * of the thread you are calling this method from. You can then call + * mm_manager_inhibit_device_finish() to get the result of the operation. + * + * See mm_manager_inhibit_device_sync() for the synchronous, blocking version of this method. + */ +void +mm_manager_inhibit_device (MMManager *manager, + const gchar *uid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MANAGER (manager)); + common_inhibit_device (manager, uid, TRUE, cancellable, callback, user_data); +} + +/** + * mm_manager_inhibit_device_sync: + * @manager: A #MMManager. + * @uid: the unique ID of the physical device. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously requests to add an inhibition on the device identified by @uid. + * + * The @uid must be the unique ID retrieved from an existing #MMModem using + * mm_modem_get_device(). The caller should keep track of this @uid and use it + * in the mm_manager_uninhibit_device_sync() call when the inhibition is no longer required. + * + * The inhibition added with this method may also be automatically removed when + * the caller program disappears from the bus (e.g. if the program ends before + * having called mm_manager_uninhibit_device_sync() explicitly). + * + * See mm_manager_inhibit_device() for the asynchronous version of this method. + * + * Returns: %TRUE if the call succeeded, %FALSE if @error is set. + */ +gboolean +mm_manager_inhibit_device_sync (MMManager *manager, + const gchar *uid, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (MM_IS_MANAGER (manager), FALSE); + return common_inhibit_device_sync (manager, uid, TRUE, cancellable, error); +} + +/** + * mm_manager_uninhibit_device_finish: + * @manager: A #MMManager. + * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to mm_manager_uninhibit_device(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with mm_manager_uninhibit_device(). + * + * Returns: %TRUE if the call succeeded, %FALSE if @error is set. + */ +gboolean +mm_manager_uninhibit_device_finish (MMManager *manager, + GAsyncResult *res, + GError **error) +{ + g_return_val_if_fail (MM_IS_MANAGER (manager), FALSE); + return common_inhibit_device_finish (manager, res, error); +} + +/** + * mm_manager_uninhibit_device: + * @manager: A #MMManager. + * @uid: the unique ID of the physical device. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL. + * @user_data: User data to pass to @callback. + * + * Asynchronously requests to remove an inhibition on the device identified by @uid. + * + * The @uid must be the same unique ID that was sent in the inhibition request. + * + * Only the same program that placed an inhibition on a given device is able to remove + * the inhibition. + * + * When the operation is finished, @callback will be invoked in the + * <link linkend="g-main-context-push-thread-default">thread-default main loop</link> + * of the thread you are calling this method from. You can then call + * mm_manager_uninhibit_device_finish() to get the result of the operation. + * + * See mm_manager_uninhibit_device_sync() for the synchronous, blocking version of this method. + */ +void +mm_manager_uninhibit_device (MMManager *manager, + const gchar *uid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MANAGER (manager)); + common_inhibit_device (manager, uid, FALSE, cancellable, callback, user_data); +} + +/** + * mm_manager_uninhibit_device_sync: + * @manager: A #MMManager. + * @uid: the unique ID of the physical device. + * @cancellable: (allow-none): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously requests to remove an inhibition on the device identified by @uid. + * + * The @uid must be the same unique ID that was sent in the inhibition request. + * + * Only the same program that placed an inhibition on a given device is able to remove + * the inhibition. + * + * See mm_manager_uninhibit_device() for the asynchronous version of this method. + * + * Returns: %TRUE if the call succeeded, %FALSE if @error is set. + */ +gboolean +mm_manager_uninhibit_device_sync (MMManager *manager, + const gchar *uid, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (MM_IS_MANAGER (manager), FALSE); + return common_inhibit_device_sync (manager, uid, FALSE, cancellable, error); +} + +/*****************************************************************************/ + static void register_dbus_errors (void) { diff --git a/libmm-glib/mm-manager.h b/libmm-glib/mm-manager.h index f133c9f7..c215cfd9 100644 --- a/libmm-glib/mm-manager.h +++ b/libmm-glib/mm-manager.h @@ -128,6 +128,32 @@ gboolean mm_manager_report_kernel_event_sync (MMManager *manage GCancellable *cancellable, GError **error); +void mm_manager_inhibit_device (MMManager *manager, + const gchar *uid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_manager_inhibit_device_finish (MMManager *manager, + GAsyncResult *res, + GError **error); +gboolean mm_manager_inhibit_device_sync (MMManager *manager, + const gchar *uid, + GCancellable *cancellable, + GError **error); + +void mm_manager_uninhibit_device (MMManager *manager, + const gchar *uid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_manager_uninhibit_device_finish (MMManager *manager, + GAsyncResult *res, + GError **error); +gboolean mm_manager_uninhibit_device_sync (MMManager *manager, + const gchar *uid, + GCancellable *cancellable, + GError **error); + G_END_DECLS #endif /* _MM_MANAGER_H_ */ diff --git a/src/mm-base-manager.c b/src/mm-base-manager.c index 72a15688..62c586c0 100644 --- a/src/mm-base-manager.c +++ b/src/mm-base-manager.c @@ -87,6 +87,8 @@ struct _MMBaseManagerPrivate { GHashTable *devices; /* The Object Manager server */ GDBusObjectManagerServer *object_manager; + /* The map of inhibited devices */ + GHashTable *inhibited_devices; /* The Test interface support */ MmGdbusTest *test_skeleton; @@ -205,6 +207,15 @@ device_support_check_ready (MMPluginManager *plugin_manager, find_device_support_context_free (ctx); } +static gboolean is_device_inhibited (MMBaseManager *self, + const gchar *physdev_uid); +static void device_inhibited_track_port (MMBaseManager *self, + const gchar *physdev_uid, + MMKernelDevice *port, + gboolean manual_scan); +static void device_inhibited_untrack_port (MMBaseManager *self, + MMKernelDevice *port); + static void device_removed (MMBaseManager *self, MMKernelDevice *kernel_device) @@ -246,8 +257,14 @@ device_removed (MMBaseManager *self, } } g_object_unref (device); + return; } + /* If the device was inhibited and the port is gone, untrack it. + * This is only needed for ports that were tracked out of device objects. + * In this case we don't rely on the physdev uid, as API-reported + * remove kernel events may not include uid. */ + device_inhibited_untrack_port (self, kernel_device); return; } @@ -318,6 +335,20 @@ device_added (MMBaseManager *manager, return; } + /* Get the port's physical device's uid. All ports of the same physical + * device will share the same uid. */ + physdev_uid = mm_kernel_device_get_physdev_uid (port); + g_assert (physdev_uid); + + /* If the device is inhibited, do nothing else */ + if (is_device_inhibited (manager, physdev_uid)) { + /* Note: we will not report as hotplugged an inhibited device port + * because we don't know what was done with the port out of our + * context. */ + device_inhibited_track_port (manager, physdev_uid, port, manual_scan); + return; + } + /* Run port filter */ if (!mm_filter_port (manager->priv->filter, port, manual_scan)) return; @@ -328,11 +359,6 @@ device_added (MMBaseManager *manager, return; } - /* Get the port's physical device's uid. All ports of the same physical - * device will share the same uid. */ - physdev_uid = mm_kernel_device_get_physdev_uid (port); - g_assert (physdev_uid); - /* See if we already created an object to handle ports in this device */ device = find_device_by_physdev_uid (manager, physdev_uid); if (!device) { @@ -899,6 +925,333 @@ handle_report_kernel_event (MmGdbusOrgFreedesktopModemManager1 *manager, } /*****************************************************************************/ +/* Inhibit or uninhibit device */ + +typedef struct { + MMKernelDevice *kernel_port; + gboolean manual_scan; +} InhibitedDevicePortInfo; + +static void +inhibited_device_port_info_free (InhibitedDevicePortInfo *port_info) +{ + g_object_unref (port_info->kernel_port); + g_slice_free (InhibitedDevicePortInfo, port_info); +} + +typedef struct { + gchar *sender; + guint name_lost_id; + GList *port_infos; +} InhibitedDeviceInfo; + +static void +inhibited_device_info_free (InhibitedDeviceInfo *info) +{ + g_list_free_full (info->port_infos, (GDestroyNotify)inhibited_device_port_info_free); + g_bus_unwatch_name (info->name_lost_id); + g_free (info->sender); + g_slice_free (InhibitedDeviceInfo, info); +} + +static InhibitedDeviceInfo * +find_inhibited_device_info_by_physdev_uid (MMBaseManager *self, + const gchar *physdev_uid) +{ + return (physdev_uid ? g_hash_table_lookup (self->priv->inhibited_devices, physdev_uid) : NULL); +} + +static gboolean +is_device_inhibited (MMBaseManager *self, + const gchar *physdev_uid) +{ + return !!find_inhibited_device_info_by_physdev_uid (self, physdev_uid); +} + +static void +device_inhibited_untrack_port (MMBaseManager *self, + MMKernelDevice *kernel_port) +{ + GHashTableIter iter; + gchar *uid; + InhibitedDeviceInfo *info; + + g_hash_table_iter_init (&iter, self->priv->inhibited_devices); + while (g_hash_table_iter_next (&iter, (gpointer)&uid, (gpointer)&info)) { + GList *l; + + for (l = info->port_infos; l; l = g_list_next (l)) { + InhibitedDevicePortInfo *port_info; + + port_info = (InhibitedDevicePortInfo *)(l->data); + if (mm_kernel_device_cmp (port_info->kernel_port, kernel_port)) { + mm_dbg ("(%s/%s): released while inhibited", + mm_kernel_device_get_subsystem (kernel_port), + mm_kernel_device_get_name (kernel_port)); + inhibited_device_port_info_free (port_info); + info->port_infos = g_list_delete_link (info->port_infos, l); + return; + } + } + } +} + +static void +device_inhibited_track_port (MMBaseManager *self, + const gchar *physdev_uid, + MMKernelDevice *kernel_port, + gboolean manual_scan) +{ + InhibitedDevicePortInfo *port_info; + InhibitedDeviceInfo *info; + GList *l; + + info = find_inhibited_device_info_by_physdev_uid (self, physdev_uid); + g_assert (info); + + for (l = info->port_infos; l; l = g_list_next (l)) { + /* If device is already tracked, just overwrite the manual scan info */ + port_info = (InhibitedDevicePortInfo *)(l->data); + if (mm_kernel_device_cmp (port_info->kernel_port, kernel_port)) { + port_info->manual_scan = manual_scan; + return; + } + } + + mm_dbg ("(%s/%s): added while inhibited", + mm_kernel_device_get_subsystem (kernel_port), + mm_kernel_device_get_name (kernel_port)); + + port_info = g_slice_new0 (InhibitedDevicePortInfo); + port_info->kernel_port = g_object_ref (kernel_port); + port_info->manual_scan = manual_scan; + info->port_infos = g_list_append (info->port_infos, port_info); +} + +typedef struct { + MMBaseManager *self; + gchar *uid; +} InhibitSenderLostContext; + +static void +inhibit_sender_lost_context_free (InhibitSenderLostContext *lost_ctx) +{ + g_free (lost_ctx->uid); + g_slice_free (InhibitSenderLostContext, lost_ctx); +} + +static void +remove_device_inhibition (MMBaseManager *self, + const gchar *uid) +{ + InhibitedDeviceInfo *info; + MMDevice *device; + GList *port_infos; + + info = find_inhibited_device_info_by_physdev_uid (self, uid); + g_assert (info); + + device = find_device_by_physdev_uid (self, uid); + port_infos = info->port_infos; + info->port_infos = NULL; + g_hash_table_remove (self->priv->inhibited_devices, uid); + + if (port_infos) { + GList *l; + + /* Note that a device can only be inhibited if it had an existing + * modem exposed in the bus. And so, inhibition can only be placed + * AFTER all port probes have finished for a given device. This means + * that we either have a device tracked, or we have a list of port + * infos. Both at the same time should never happen. */ + g_assert (!device); + + /* Report as added all port infos that we had tracked while the + * device was inhibited. We can only report the added port after + * having removed the entry from the inhibited devices tracking + * table. */ + for (l = port_infos; l; l = g_list_next (l)) { + InhibitedDevicePortInfo *port_info; + + port_info = (InhibitedDevicePortInfo *)(l->data); + device_added (self, port_info->kernel_port, FALSE, port_info->manual_scan); + } + g_list_free_full (port_infos, (GDestroyNotify)inhibited_device_port_info_free); + } + /* The device may be totally gone from the system while we were + * keeping the inhibition, so do not error out if not found. */ + else if (device) { + GError *error = NULL; + + /* Uninhibit device, which will create and expose the modem object */ + if (!mm_device_uninhibit (device, self->priv->object_manager, &error)) { + mm_warn ("Couldn't uninhibit device: %s", error->message); + g_error_free (error); + } + } +} + +static void +inhibit_sender_lost (GDBusConnection *connection, + const gchar *sender_name, + InhibitSenderLostContext *lost_ctx) +{ + mm_info ("Device inhibition teardown for uid '%s' (owner disappeared from bus)", lost_ctx->uid); + remove_device_inhibition (lost_ctx->self, lost_ctx->uid); +} + +typedef struct { + MMBaseManager *self; + GDBusMethodInvocation *invocation; + gchar *uid; + gboolean inhibit; +} InhibitDeviceContext; + +static void +inhibit_device_context_free (InhibitDeviceContext *ctx) +{ + g_object_unref (ctx->invocation); + g_object_unref (ctx->self); + g_free (ctx->uid); + g_slice_free (InhibitDeviceContext, ctx); +} + +static void +device_inhibit_ready (MMDevice *device, + GAsyncResult *res, + InhibitDeviceContext *ctx) +{ + InhibitSenderLostContext *lost_ctx; + InhibitedDeviceInfo *info; + GError *error = NULL; + + if (!mm_device_inhibit_finish (device, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + inhibit_device_context_free (ctx); + return; + } + + info = g_slice_new0 (InhibitedDeviceInfo); + info->sender = g_strdup (g_dbus_method_invocation_get_sender (ctx->invocation)); + + /* This context will exist as long as the sender name watcher exists, + * i.e. as long as the associated InhibitDeviceInfo exists. We don't need + * an extra reference of self here because these contexts are stored within + * self, and therefore bound to its lifetime. */ + lost_ctx = g_slice_new0 (InhibitSenderLostContext); + lost_ctx->self = ctx->self; + lost_ctx->uid = g_strdup (ctx->uid); + info->name_lost_id = g_bus_watch_name_on_connection (g_dbus_method_invocation_get_connection (ctx->invocation), + info->sender, + G_BUS_NAME_WATCHER_FLAGS_NONE, + NULL, + (GBusNameVanishedCallback)inhibit_sender_lost, + lost_ctx, + (GDestroyNotify)inhibit_sender_lost_context_free); + + g_hash_table_insert (ctx->self->priv->inhibited_devices, g_strdup (ctx->uid), info); + + mm_info ("Device inhibition setup for uid '%s'", ctx->uid); + + mm_gdbus_org_freedesktop_modem_manager1_complete_inhibit_device ( + MM_GDBUS_ORG_FREEDESKTOP_MODEM_MANAGER1 (ctx->self), + ctx->invocation); + inhibit_device_context_free (ctx); +} + +static void +base_manager_inhibit_device (InhibitDeviceContext *ctx) +{ + MMDevice *device; + + device = find_device_by_physdev_uid (ctx->self, ctx->uid); + if (!device) { + g_dbus_method_invocation_return_error (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, + "No device found with uid '%s'", ctx->uid); + inhibit_device_context_free (ctx); + return; + } + + if (mm_device_get_inhibited (device)) { + g_dbus_method_invocation_return_error (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_IN_PROGRESS, + "Device '%s' is already inhibited", ctx->uid); + inhibit_device_context_free (ctx); + return; + } + + mm_device_inhibit (device, + (GAsyncReadyCallback) device_inhibit_ready, + ctx); +} + +static void +base_manager_uninhibit_device (InhibitDeviceContext *ctx) +{ + InhibitedDeviceInfo *info; + const gchar *sender; + + /* Validate uninhibit request */ + sender = g_dbus_method_invocation_get_sender (ctx->invocation); + info = find_inhibited_device_info_by_physdev_uid (ctx->self, ctx->uid); + if (!info || (g_strcmp0 (info->sender, sender) != 0)) { + g_dbus_method_invocation_return_error (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, + "No inhibition found for uid '%s'", ctx->uid); + inhibit_device_context_free (ctx); + return; + } + + mm_info ("Device inhibition teardown for uid '%s'", ctx->uid); + remove_device_inhibition (ctx->self, ctx->uid); + + mm_gdbus_org_freedesktop_modem_manager1_complete_inhibit_device ( + MM_GDBUS_ORG_FREEDESKTOP_MODEM_MANAGER1 (ctx->self), + ctx->invocation); + inhibit_device_context_free (ctx); +} + +static void +inhibit_device_auth_ready (MMAuthProvider *authp, + GAsyncResult *res, + InhibitDeviceContext *ctx) +{ + GError *error = NULL; + + if (!mm_auth_provider_authorize_finish (authp, res, &error)) { + g_dbus_method_invocation_take_error (ctx->invocation, error); + inhibit_device_context_free (ctx); + return; + } + + if (ctx->inhibit) + base_manager_inhibit_device (ctx); + else + base_manager_uninhibit_device (ctx); +} + +static gboolean +handle_inhibit_device (MmGdbusOrgFreedesktopModemManager1 *manager, + GDBusMethodInvocation *invocation, + const gchar *uid, + gboolean inhibit) +{ + InhibitDeviceContext *ctx; + + ctx = g_new0 (InhibitDeviceContext, 1); + ctx->self = g_object_ref (manager); + ctx->invocation = g_object_ref (invocation); + ctx->uid = g_strdup (uid); + ctx->inhibit = inhibit; + + mm_auth_provider_authorize (ctx->self->priv->authp, + invocation, + MM_AUTHORIZATION_MANAGER_CONTROL, + ctx->self->priv->authp_cancellable, + (GAsyncReadyCallback)inhibit_device_auth_ready, + ctx); + return TRUE; +} + +/*****************************************************************************/ /* Test profile setup */ static gboolean @@ -1092,6 +1445,9 @@ mm_base_manager_init (MMBaseManager *manager) /* Setup internal lists of device objects */ priv->devices = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + /* Setup internal list of inhibited devices */ + priv->inhibited_devices = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)inhibited_device_info_free); + #if defined WITH_UDEV { const gchar *subsys[5] = { "tty", "net", "usb", "usbmisc", NULL }; @@ -1111,17 +1467,11 @@ mm_base_manager_init (MMBaseManager *manager) priv->object_manager = g_dbus_object_manager_server_new (MM_DBUS_PATH); /* Enable processing of input DBus messages */ - g_signal_connect (manager, - "handle-set-logging", - G_CALLBACK (handle_set_logging), - NULL); - g_signal_connect (manager, - "handle-scan-devices", - G_CALLBACK (handle_scan_devices), - NULL); - g_signal_connect (manager, - "handle-report-kernel-event", - G_CALLBACK (handle_report_kernel_event), + g_object_connect (manager, + "signal::handle-set-logging", G_CALLBACK (handle_set_logging), NULL, + "signal::handle-scan-devices", G_CALLBACK (handle_scan_devices), NULL, + "signal::handle-report-kernel-event", G_CALLBACK (handle_report_kernel_event), NULL, + "signal::handle-inhibit-device", G_CALLBACK (handle_inhibit_device), NULL, NULL); } @@ -1185,6 +1535,7 @@ finalize (GObject *object) g_free (priv->initial_kernel_events); g_free (priv->plugin_dir); + g_hash_table_destroy (priv->inhibited_devices); g_hash_table_destroy (priv->devices); #if defined WITH_UDEV diff --git a/src/mm-device.c b/src/mm-device.c index 1e53870a..4b3d3066 100644 --- a/src/mm-device.c +++ b/src/mm-device.c @@ -36,6 +36,7 @@ enum { PROP_MODEM, PROP_HOTPLUGGED, PROP_VIRTUAL, + PROP_INHIBITED, PROP_LAST }; @@ -79,6 +80,9 @@ struct _MMDevicePrivate { /* Whether the device was hot-plugged. */ gboolean hotplugged; + /* Whether the device is inhibited. */ + gboolean inhibited; + /* Virtual ports */ gchar **virtual_ports; }; @@ -370,6 +374,12 @@ mm_device_create_modem (MMDevice *self, g_assert (self->priv->modem == NULL); g_assert (self->priv->object_manager == NULL); + if (self->priv->inhibited) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Device is inhibited"); + return FALSE; + } + if (!self->priv->virtual) { if (!self->priv->port_probes) { g_set_error (error, @@ -520,6 +530,85 @@ mm_device_get_hotplugged (MMDevice *self) return self->priv->hotplugged; } +gboolean +mm_device_get_inhibited (MMDevice *self) +{ + return self->priv->inhibited; +} + +/*****************************************************************************/ + +gboolean +mm_device_inhibit_finish (MMDevice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +inhibit_disable_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMDevice *self; + GError *error = NULL; + + self = g_task_get_source_object (task); + + if (!mm_base_modem_disable_finish (modem, res, &error)) + g_task_return_error (task, error); + else { + g_cancellable_cancel (mm_base_modem_peek_cancellable (modem)); + mm_device_remove_modem (self); + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +void +mm_device_inhibit (MMDevice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* We want to allow inhibiting only devices that are currently + * exported in the bus, because otherwise we may be inhibiting + * in the middle of port probing and that may lead to some ports + * tracked inside the device object during inhibition and some + * other ports tracked in the base manager. So, if the device + * does not have a valid modem created and exposed, do not allow + * the inhibition. */ + if (!self->priv->modem || !g_dbus_object_get_object_path (G_DBUS_OBJECT (self->priv->modem))) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, + "Modem not exported in the bus"); + g_object_unref (task); + return; + } + + /* Flag as inhibited right away */ + g_assert (!self->priv->inhibited); + self->priv->inhibited = TRUE; + + /* Make sure modem is disabled while inhibited */ + mm_base_modem_disable (self->priv->modem, + (GAsyncReadyCallback)inhibit_disable_ready, + task); +} + +gboolean +mm_device_uninhibit (MMDevice *self, + GDBusObjectManagerServer *object_manager, + GError **error) +{ + g_assert (self->priv->inhibited); + self->priv->inhibited = FALSE; + return mm_device_create_modem (self, object_manager, error); +} + /*****************************************************************************/ void @@ -602,6 +691,9 @@ set_property (GObject *object, case PROP_VIRTUAL: self->priv->virtual = g_value_get_boolean (value); break; + case PROP_INHIBITED: + self->priv->inhibited = g_value_get_boolean (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -632,6 +724,9 @@ get_property (GObject *object, case PROP_VIRTUAL: g_value_set_boolean (value, self->priv->virtual); break; + case PROP_INHIBITED: + g_value_set_boolean (value, self->priv->inhibited); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -719,6 +814,14 @@ mm_device_class_init (MMDeviceClass *klass) G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_property (object_class, PROP_VIRTUAL, properties[PROP_VIRTUAL]); + properties[PROP_INHIBITED] = + g_param_spec_boolean (MM_DEVICE_INHIBITED, + "Inhibited", + "Whether the modem is inhibited", + FALSE, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_INHIBITED, properties[PROP_INHIBITED]); + signals[SIGNAL_PORT_GRABBED] = g_signal_new (MM_DEVICE_PORT_GRABBED, G_OBJECT_CLASS_TYPE (object_class), diff --git a/src/mm-device.h b/src/mm-device.h index 6e6ff0bf..b00d1314 100644 --- a/src/mm-device.h +++ b/src/mm-device.h @@ -38,6 +38,7 @@ typedef struct _MMDevicePrivate MMDevicePrivate; #define MM_DEVICE_MODEM "modem" #define MM_DEVICE_HOTPLUGGED "hotplugged" #define MM_DEVICE_VIRTUAL "virtual" +#define MM_DEVICE_INHIBITED "inhibited" #define MM_DEVICE_PORT_GRABBED "port-grabbed" #define MM_DEVICE_PORT_RELEASED "port-released" @@ -77,6 +78,17 @@ gboolean mm_device_create_modem (MMDevice *self, GError **error); void mm_device_remove_modem (MMDevice *self); +void mm_device_inhibit (MMDevice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_device_inhibit_finish (MMDevice *self, + GAsyncResult *res, + GError **error); +gboolean mm_device_uninhibit (MMDevice *self, + GDBusObjectManagerServer *object_manager, + GError **error); + + const gchar *mm_device_get_uid (MMDevice *self); const gchar **mm_device_get_drivers (MMDevice *self); guint16 mm_device_get_vendor (MMDevice *self); @@ -94,6 +106,7 @@ GObject *mm_device_get_port_probe (MMDevice *self, GList *mm_device_peek_port_probe_list (MMDevice *self); GList *mm_device_get_port_probe_list (MMDevice *self); gboolean mm_device_get_hotplugged (MMDevice *self); +gboolean mm_device_get_inhibited (MMDevice *self); /* For testing purposes */ void mm_device_virtual_grab_ports (MMDevice *self, |