/* -*- 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) 2008 - 2009 Novell, Inc. * Copyright (C) 2009 - 2012 Red Hat, Inc. * Copyright (C) 2011 - 2012 Google, Inc * Copyright (C) 2016 Velocloud, Inc. * Copyright (C) 2011 - 2016 Aleksander Morgado */ #include #include #include #include #if defined WITH_UDEV # include "mm-kernel-device-udev.h" #endif #include "mm-kernel-device-generic.h" #include #include #include #include #include #include "mm-base-manager.h" #include "mm-daemon-enums-types.h" #include "mm-device.h" #include "mm-plugin-manager.h" #include "mm-auth.h" #include "mm-plugin.h" #include "mm-filter.h" #include "mm-log.h" static void initable_iface_init (GInitableIface *iface); G_DEFINE_TYPE_EXTENDED (MMBaseManager, mm_base_manager, MM_GDBUS_TYPE_ORG_FREEDESKTOP_MODEM_MANAGER1_SKELETON, 0, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)); enum { PROP_0, PROP_CONNECTION, PROP_AUTO_SCAN, PROP_FILTER_POLICY, PROP_ENABLE_TEST, PROP_PLUGIN_DIR, PROP_INITIAL_KERNEL_EVENTS, LAST_PROP }; struct _MMBaseManagerPrivate { /* The connection to the system bus */ GDBusConnection *connection; /* Whether auto-scanning is enabled */ gboolean auto_scan; /* Filter policy (mask of enabled rules) */ MMFilterRule filter_policy; /* Whether the test interface is enabled */ gboolean enable_test; /* Path to look for plugins */ gchar *plugin_dir; /* Path to the list of initial kernel events */ gchar *initial_kernel_events; /* The authorization provider */ MMAuthProvider *authp; GCancellable *authp_cancellable; /* The Plugin Manager object */ MMPluginManager *plugin_manager; /* The port/device filter */ MMFilter *filter; /* The container of devices being prepared */ GHashTable *devices; /* The Object Manager server */ GDBusObjectManagerServer *object_manager; /* The Test interface support */ MmGdbusTest *test_skeleton; #if defined WITH_UDEV /* The UDev client */ GUdevClient *udev; #endif }; /*****************************************************************************/ static MMDevice * find_device_by_modem (MMBaseManager *manager, MMBaseModem *modem) { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init (&iter, manager->priv->devices); while (g_hash_table_iter_next (&iter, &key, &value)) { MMDevice *candidate = MM_DEVICE (value); if (modem == mm_device_peek_modem (candidate)) return candidate; } return NULL; } static MMDevice * find_device_by_port (MMBaseManager *manager, MMKernelDevice *port) { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init (&iter, manager->priv->devices); while (g_hash_table_iter_next (&iter, &key, &value)) { MMDevice *candidate = MM_DEVICE (value); if (mm_device_owns_port (candidate, port)) return candidate; } return NULL; } static MMDevice * find_device_by_physdev_uid (MMBaseManager *self, const gchar *physdev_uid) { return g_hash_table_lookup (self->priv->devices, physdev_uid); } static MMDevice * find_device_by_kernel_device (MMBaseManager *manager, MMKernelDevice *kernel_device) { return find_device_by_physdev_uid (manager, mm_kernel_device_get_physdev_uid (kernel_device)); } /*****************************************************************************/ typedef struct { MMBaseManager *self; MMDevice *device; } FindDeviceSupportContext; static void find_device_support_context_free (FindDeviceSupportContext *ctx) { g_object_unref (ctx->self); g_object_unref (ctx->device); g_slice_free (FindDeviceSupportContext, ctx); } static void device_support_check_ready (MMPluginManager *plugin_manager, GAsyncResult *res, FindDeviceSupportContext *ctx) { GError *error = NULL; MMPlugin *plugin; /* If the device support check fails, either with an error, or afterwards * when trying to create a modem object, we must remove the MMDevice from * the tracking table of devices, so that a manual scan request afterwards * re-scans all ports. */ /* Receive plugin result from the plugin manager */ plugin = mm_plugin_manager_device_support_check_finish (plugin_manager, res, &error); if (!plugin) { mm_info ("Couldn't check support for device '%s': %s", mm_device_get_uid (ctx->device), error->message); g_error_free (error); g_hash_table_remove (ctx->self->priv->devices, mm_device_get_uid (ctx->device)); find_device_support_context_free (ctx); return; } /* Set the plugin as the one expected in the device */ mm_device_set_plugin (ctx->device, G_OBJECT (plugin)); g_object_unref (plugin); if (!mm_device_create_modem (ctx->device, ctx->self->priv->object_manager, &error)) { mm_warn ("Couldn't create modem for device '%s': %s", mm_device_get_uid (ctx->device), error->message); g_error_free (error); g_hash_table_remove (ctx->self->priv->devices, mm_device_get_uid (ctx->device)); find_device_support_context_free (ctx); return; } /* Modem now created */ mm_info ("Modem for device '%s' successfully created", mm_device_get_uid (ctx->device)); find_device_support_context_free (ctx); } static void device_removed (MMBaseManager *self, MMKernelDevice *kernel_device) { MMDevice *device; const gchar *subsys; const gchar *name; g_return_if_fail (kernel_device != NULL); subsys = mm_kernel_device_get_subsystem (kernel_device); name = mm_kernel_device_get_name (kernel_device); if (!g_str_has_prefix (subsys, "usb") || (name && g_str_has_prefix (name, "cdc-wdm"))) { /* Handle tty/net/wdm port removal */ device = find_device_by_port (self, kernel_device); if (device) { /* The callbacks triggered when the port is released or device support is * cancelled may end up unreffing the device or removing it from the HT, and * so in order to make sure the reference is still valid when we call * support_check_cancel() and g_hash_table_remove(), we hold a full reference * ourselves. */ g_object_ref (device); { mm_info ("(%s/%s): released by device '%s'", subsys, name, mm_device_get_uid (device)); mm_device_release_port (device, kernel_device); /* If port probe list gets empty, remove the device object iself */ if (!mm_device_peek_port_probe_list (device)) { mm_dbg ("Removing empty device '%s'", mm_device_get_uid (device)); if (mm_plugin_manager_device_support_check_cancel (self->priv->plugin_manager, device)) mm_dbg ("Device support check has been cancelled"); /* The device may have already been removed from the tracking HT, we * just try to remove it and if it fails, we ignore it */ mm_device_remove_modem (device); g_hash_table_remove (self->priv->devices, mm_device_get_uid (device)); } } g_object_unref (device); } return; } #if defined WITH_UDEV /* When a USB modem is switching its USB configuration, udev may deliver * the remove events of USB interfaces associated with the old USB * configuration and the add events of USB interfaces associated with the * new USB configuration in an interleaved fashion. As we don't want a * remove event of an USB interface trigger the removal of a MMDevice for * the special case being handled here, we ignore any remove event with * DEVTYPE != usb_device. */ if (g_strcmp0 (mm_kernel_device_get_property (kernel_device, "DEVTYPE"), "usb_device") != 0) return; #endif /* This case is designed to handle the case where, at least with kernel 2.6.31, unplugging * an in-use ttyACMx device results in udev generating remove events for the usb, but the * ttyACMx device (subsystem tty) is not removed, since it was in-use. So if we have not * found a modem for the port (above), we're going to look here to see if we have a modem * associated with the newly removed device. If so, we'll remove the modem, since the * device has been removed. That way, if the device is reinserted later, we'll go through * the process of exporting it. */ device = find_device_by_kernel_device (self, kernel_device); if (device) { mm_dbg ("Removing device '%s'", mm_device_get_uid (device)); mm_device_remove_modem (device); g_hash_table_remove (self->priv->devices, mm_device_get_uid (device)); return; } } static void device_added (MMBaseManager *manager, MMKernelDevice *port, gboolean hotplugged, gboolean manual_scan) { MMDevice *device; const gchar *physdev_uid; g_return_if_fail (port != NULL); mm_dbg ("(%s/%s): adding device at sysfs path: %s", mm_kernel_device_get_subsystem (port), mm_kernel_device_get_name (port), mm_kernel_device_get_sysfs_path (port)); /* Ignore devices that aren't completely configured by udev yet. If * ModemManager is started in parallel with udev, explicitly requesting * devices may return devices for which not all udev rules have yet been * applied (a bug in udev/gudev). Since we often need those rules to match * the device to a specific ModemManager driver, we need to ensure that all * rules have been processed before handling a device. * * This udev tag applies to each port in a device. In other words, the flag * may be set in some ports, but not in others */ if (!mm_kernel_device_get_property_as_boolean (port, ID_MM_CANDIDATE)) { /* This could mean that device changed, losing its candidate * flags (such as Bluetooth RFCOMM devices upon disconnect. * Try to forget it. */ device_removed (manager, port); mm_dbg ("(%s/%s): port not candidate", mm_kernel_device_get_subsystem (port), mm_kernel_device_get_name (port)); return; } /* Run port filter */ if (!mm_filter_port (manager->priv->filter, port, manual_scan)) return; /* If already added, ignore new event */ if (find_device_by_port (manager, port)) { mm_dbg ("(%s/%s): port already added", mm_kernel_device_get_subsystem (port), mm_kernel_device_get_name (port)); 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) { FindDeviceSupportContext *ctx; mm_dbg ("(%s/%s): first port in device %s", mm_kernel_device_get_subsystem (port), mm_kernel_device_get_name (port), physdev_uid); /* Keep the device listed in the Manager */ device = mm_device_new (physdev_uid, hotplugged, FALSE); g_hash_table_insert (manager->priv->devices, g_strdup (physdev_uid), device); /* Launch device support check */ ctx = g_slice_new (FindDeviceSupportContext); ctx->self = g_object_ref (manager); ctx->device = g_object_ref (device); mm_plugin_manager_device_support_check ( manager->priv->plugin_manager, device, (GAsyncReadyCallback) device_support_check_ready, ctx); } else mm_dbg ("(%s/%s): additional port in device %s", mm_kernel_device_get_subsystem (port), mm_kernel_device_get_name (port), physdev_uid); /* Grab the port in the existing device. */ mm_device_grab_port (device, port); } static gboolean handle_kernel_event (MMBaseManager *self, MMKernelEventProperties *properties, GError **error) { MMKernelDevice *kernel_device; const gchar *action; const gchar *subsystem; const gchar *name; const gchar *uid; action = mm_kernel_event_properties_get_action (properties); if (!action) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Missing mandatory parameter 'action'"); return FALSE; } if (g_strcmp0 (action, "add") != 0 && g_strcmp0 (action, "remove") != 0) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Invalid 'action' parameter given: '%s' (expected 'add' or 'remove')", action); return FALSE; } subsystem = mm_kernel_event_properties_get_subsystem (properties); if (!subsystem) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Missing mandatory parameter 'subsystem'"); return FALSE; } name = mm_kernel_event_properties_get_name (properties); if (!name) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Missing mandatory parameter 'name'"); return FALSE; } uid = mm_kernel_event_properties_get_uid (properties); mm_dbg ("Kernel event reported:"); mm_dbg (" action: %s", action); mm_dbg (" subsystem: %s", subsystem); mm_dbg (" name: %s", name); mm_dbg (" uid: %s", uid ? uid : "n/a"); #if defined WITH_UDEV kernel_device = mm_kernel_device_udev_new_from_properties (properties, error); #else kernel_device = mm_kernel_device_generic_new (properties, error); #endif if (!kernel_device) return FALSE; if (g_strcmp0 (action, "add") == 0) device_added (self, kernel_device, TRUE, TRUE); else if (g_strcmp0 (action, "remove") == 0) device_removed (self, kernel_device); else g_assert_not_reached (); g_object_unref (kernel_device); return TRUE; } #if defined WITH_UDEV static void handle_uevent (GUdevClient *client, const char *action, GUdevDevice *device, gpointer user_data) { MMBaseManager *self = MM_BASE_MANAGER (user_data); const gchar *subsys; const gchar *name; MMKernelDevice *kernel_device; g_return_if_fail (action != NULL); /* A bit paranoid */ subsys = g_udev_device_get_subsystem (device); g_return_if_fail (subsys != NULL); g_return_if_fail (g_str_equal (subsys, "tty") || g_str_equal (subsys, "net") || g_str_has_prefix (subsys, "usb")); kernel_device = mm_kernel_device_udev_new (device); /* We only care about tty/net and usb/cdc-wdm devices when adding modem ports, * but for remove, also handle usb parent device remove events */ name = mm_kernel_device_get_name (kernel_device); if ( (g_str_equal (action, "add") || g_str_equal (action, "move") || g_str_equal (action, "change")) && (!g_str_has_prefix (subsys, "usb") || (name && g_str_has_prefix (name, "cdc-wdm")))) device_added (self, kernel_device, TRUE, FALSE); else if (g_str_equal (action, "remove")) device_removed (self, kernel_device); g_object_unref (kernel_device); } typedef struct { MMBaseManager *self; GUdevDevice *device; gboolean manual_scan; } StartDeviceAdded; static gboolean start_device_added_idle (StartDeviceAdded *ctx) { MMKernelDevice *kernel_device; kernel_device = mm_kernel_device_udev_new (ctx->device); device_added (ctx->self, kernel_device, FALSE, ctx->manual_scan); g_object_unref (kernel_device); g_object_unref (ctx->self); g_object_unref (ctx->device); g_slice_free (StartDeviceAdded, ctx); return G_SOURCE_REMOVE; } static void start_device_added (MMBaseManager *self, GUdevDevice *device, gboolean manual_scan) { StartDeviceAdded *ctx; ctx = g_slice_new (StartDeviceAdded); ctx->self = g_object_ref (self); ctx->device = g_object_ref (device); ctx->manual_scan = manual_scan; g_idle_add ((GSourceFunc)start_device_added_idle, ctx); } static void process_scan (MMBaseManager *self, gboolean manual_scan) { GList *devices, *iter; devices = g_udev_client_query_by_subsystem (self->priv->udev, "tty"); for (iter = devices; iter; iter = g_list_next (iter)) { start_device_added (self, G_UDEV_DEVICE (iter->data), manual_scan); g_object_unref (G_OBJECT (iter->data)); } g_list_free (devices); devices = g_udev_client_query_by_subsystem (self->priv->udev, "net"); for (iter = devices; iter; iter = g_list_next (iter)) { start_device_added (self, G_UDEV_DEVICE (iter->data), manual_scan); g_object_unref (G_OBJECT (iter->data)); } g_list_free (devices); devices = g_udev_client_query_by_subsystem (self->priv->udev, "usb"); for (iter = devices; iter; iter = g_list_next (iter)) { const gchar *name; name = g_udev_device_get_name (G_UDEV_DEVICE (iter->data)); if (name && g_str_has_prefix (name, "cdc-wdm")) start_device_added (self, G_UDEV_DEVICE (iter->data), manual_scan); g_object_unref (G_OBJECT (iter->data)); } g_list_free (devices); /* Newer kernels report 'usbmisc' subsystem */ devices = g_udev_client_query_by_subsystem (self->priv->udev, "usbmisc"); for (iter = devices; iter; iter = g_list_next (iter)) { const gchar *name; name = g_udev_device_get_name (G_UDEV_DEVICE (iter->data)); if (name && g_str_has_prefix (name, "cdc-wdm")) start_device_added (self, G_UDEV_DEVICE (iter->data), manual_scan); g_object_unref (G_OBJECT (iter->data)); } g_list_free (devices); } #endif static void process_initial_kernel_events (MMBaseManager *self) { gchar *contents = NULL; gchar *line; GError *error = NULL; if (!self->priv->initial_kernel_events) return; if (!g_file_get_contents (self->priv->initial_kernel_events, &contents, NULL, &error)) { g_warning ("Couldn't load initial kernel events: %s", error->message); g_error_free (error); return; } line = contents; while (line) { gchar *next; next = strchr (line, '\n'); if (next) { *next = '\0'; next++; } /* ignore empty lines */ if (line[0] != '\0') { MMKernelEventProperties *properties; properties = mm_kernel_event_properties_new_from_string (line, &error); if (!properties) { g_warning ("Couldn't parse line '%s' as initial kernel event %s", line, error->message); g_clear_error (&error); } else if (!handle_kernel_event (self, properties, &error)) { g_warning ("Couldn't process line '%s' as initial kernel event %s", line, error->message); g_clear_error (&error); } else g_debug ("Processed initial kernel event:' %s'", line); g_clear_object (&properties); } line = next; } g_free (contents); } void mm_base_manager_start (MMBaseManager *self, gboolean manual_scan) { g_return_if_fail (self != NULL); g_return_if_fail (MM_IS_BASE_MANAGER (self)); if (!self->priv->auto_scan && !manual_scan) { /* If we have a list of initial kernel events, process it now */ process_initial_kernel_events (self); return; } #if defined WITH_UDEV mm_dbg ("Starting %s device scan...", manual_scan ? "manual" : "automatic"); process_scan (self, manual_scan); mm_dbg ("Finished device scan..."); #else mm_dbg ("Unsupported %s device scan...", manual_scan ? "manual" : "automatic"); #endif } /*****************************************************************************/ static void remove_disable_ready (MMBaseModem *modem, GAsyncResult *res, MMBaseManager *self) { MMDevice *device; /* We don't care about errors disabling at this point */ mm_base_modem_disable_finish (modem, res, NULL); device = find_device_by_modem (self, modem); if (device) { g_cancellable_cancel (mm_base_modem_peek_cancellable (modem)); mm_device_remove_modem (device); g_hash_table_remove (self->priv->devices, mm_device_get_uid (device)); } } static void foreach_disable (gpointer key, MMDevice *device, MMBaseManager *self) { MMBaseModem *modem; modem = mm_device_peek_modem (device); if (modem) mm_base_modem_disable (modem, (GAsyncReadyCallback)remove_disable_ready, self); } static gboolean foreach_remove (gpointer key, MMDevice *device, MMBaseManager *self) { MMBaseModem *modem; modem = mm_device_peek_modem (device); if (modem) g_cancellable_cancel (mm_base_modem_peek_cancellable (modem)); mm_device_remove_modem (device); return TRUE; } void mm_base_manager_shutdown (MMBaseManager *self, gboolean disable) { g_return_if_fail (self != NULL); g_return_if_fail (MM_IS_BASE_MANAGER (self)); /* Cancel all ongoing auth requests */ g_cancellable_cancel (self->priv->authp_cancellable); if (disable) { g_hash_table_foreach (self->priv->devices, (GHFunc)foreach_disable, self); /* Disabling may take a few iterations of the mainloop, so the caller * has to iterate the mainloop until all devices have been disabled and * removed. */ return; } /* Otherwise, just remove directly */ g_hash_table_foreach_remove (self->priv->devices, (GHRFunc)foreach_remove, self); } guint32 mm_base_manager_num_modems (MMBaseManager *self) { GHashTableIter iter; gpointer key, value; guint32 n; g_return_val_if_fail (self != NULL, 0); g_return_val_if_fail (MM_IS_BASE_MANAGER (self), 0); n = 0; g_hash_table_iter_init (&iter, self->priv->devices); while (g_hash_table_iter_next (&iter, &key, &value)) { n += !!mm_device_peek_modem (MM_DEVICE (value)); } return n; } /*****************************************************************************/ /* Set logging */ typedef struct { MMBaseManager *self; GDBusMethodInvocation *invocation; gchar *level; } SetLoggingContext; static void set_logging_context_free (SetLoggingContext *ctx) { g_object_unref (ctx->invocation); g_object_unref (ctx->self); g_free (ctx->level); g_free (ctx); } static void set_logging_auth_ready (MMAuthProvider *authp, GAsyncResult *res, SetLoggingContext *ctx) { GError *error = NULL; if (!mm_auth_provider_authorize_finish (authp, res, &error)) g_dbus_method_invocation_take_error (ctx->invocation, error); else if (!mm_log_set_level (ctx->level, &error)) g_dbus_method_invocation_take_error (ctx->invocation, error); else { mm_info ("logging: level '%s'", ctx->level); mm_gdbus_org_freedesktop_modem_manager1_complete_set_logging ( MM_GDBUS_ORG_FREEDESKTOP_MODEM_MANAGER1 (ctx->self), ctx->invocation); } set_logging_context_free (ctx); } static gboolean handle_set_logging (MmGdbusOrgFreedesktopModemManager1 *manager, GDBusMethodInvocation *invocation, const gchar *level) { SetLoggingContext *ctx; ctx = g_new0 (SetLoggingContext, 1); ctx->self = g_object_ref (manager); ctx->invocation = g_object_ref (invocation); ctx->level = g_strdup (level); mm_auth_provider_authorize (ctx->self->priv->authp, invocation, MM_AUTHORIZATION_MANAGER_CONTROL, ctx->self->priv->authp_cancellable, (GAsyncReadyCallback)set_logging_auth_ready, ctx); return TRUE; } /*****************************************************************************/ /* Manual scan */ typedef struct { MMBaseManager *self; GDBusMethodInvocation *invocation; } ScanDevicesContext; static void scan_devices_context_free (ScanDevicesContext *ctx) { g_object_unref (ctx->invocation); g_object_unref (ctx->self); g_free (ctx); } static void scan_devices_auth_ready (MMAuthProvider *authp, GAsyncResult *res, ScanDevicesContext *ctx) { GError *error = NULL; if (!mm_auth_provider_authorize_finish (authp, res, &error)) g_dbus_method_invocation_take_error (ctx->invocation, error); else { #if defined WITH_UDEV /* Otherwise relaunch device scan */ mm_base_manager_start (MM_BASE_MANAGER (ctx->self), TRUE); mm_gdbus_org_freedesktop_modem_manager1_complete_scan_devices ( MM_GDBUS_ORG_FREEDESKTOP_MODEM_MANAGER1 (ctx->self), ctx->invocation); #else g_dbus_method_invocation_return_error_literal ( ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Cannot request manual scan of devices: unsupported"); #endif } scan_devices_context_free (ctx); } static gboolean handle_scan_devices (MmGdbusOrgFreedesktopModemManager1 *manager, GDBusMethodInvocation *invocation) { ScanDevicesContext *ctx; ctx = g_new (ScanDevicesContext, 1); ctx->self = g_object_ref (manager); ctx->invocation = g_object_ref (invocation); mm_auth_provider_authorize (ctx->self->priv->authp, invocation, MM_AUTHORIZATION_MANAGER_CONTROL, ctx->self->priv->authp_cancellable, (GAsyncReadyCallback)scan_devices_auth_ready, ctx); return TRUE; } /*****************************************************************************/ typedef struct { MMBaseManager *self; GDBusMethodInvocation *invocation; GVariant *dictionary; } ReportKernelEventContext; static void report_kernel_event_context_free (ReportKernelEventContext *ctx) { g_object_unref (ctx->invocation); g_object_unref (ctx->self); g_variant_unref (ctx->dictionary); g_slice_free (ReportKernelEventContext, ctx); } static void report_kernel_event_auth_ready (MMAuthProvider *authp, GAsyncResult *res, ReportKernelEventContext *ctx) { GError *error = NULL; MMKernelEventProperties *properties = NULL; if (!mm_auth_provider_authorize_finish (authp, res, &error)) goto out; #if defined WITH_UDEV if (ctx->self->priv->auto_scan) { error = g_error_new_literal (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Cannot report kernel event: " "udev monitoring already in place"); goto out; } #endif properties = mm_kernel_event_properties_new_from_dictionary (ctx->dictionary, &error); if (!properties) goto out; handle_kernel_event (ctx->self, properties, &error); out: if (error) g_dbus_method_invocation_take_error (ctx->invocation, error); else mm_gdbus_org_freedesktop_modem_manager1_complete_report_kernel_event ( MM_GDBUS_ORG_FREEDESKTOP_MODEM_MANAGER1 (ctx->self), ctx->invocation); if (properties) g_object_unref (properties); report_kernel_event_context_free (ctx); } static gboolean handle_report_kernel_event (MmGdbusOrgFreedesktopModemManager1 *manager, GDBusMethodInvocation *invocation, GVariant *dictionary) { ReportKernelEventContext *ctx; ctx = g_slice_new0 (ReportKernelEventContext); ctx->self = g_object_ref (manager); ctx->invocation = g_object_ref (invocation); ctx->dictionary = g_variant_ref (dictionary); mm_auth_provider_authorize (ctx->self->priv->authp, invocation, MM_AUTHORIZATION_MANAGER_CONTROL, ctx->self->priv->authp_cancellable, (GAsyncReadyCallback)report_kernel_event_auth_ready, ctx); return TRUE; } /*****************************************************************************/ /* Test profile setup */ static gboolean handle_set_profile (MmGdbusTest *skeleton, GDBusMethodInvocation *invocation, const gchar *id, const gchar *plugin_name, const gchar *const *ports, MMBaseManager *self) { MMPlugin *plugin; MMDevice *device; gchar *physdev_uid; GError *error = NULL; mm_info ("Test profile set to: '%s'", id); /* Create device and keep it listed in the Manager */ physdev_uid = g_strdup_printf ("/virtual/%s", id); device = mm_device_new (physdev_uid, TRUE, TRUE); g_hash_table_insert (self->priv->devices, physdev_uid, device); /* Grab virtual ports */ mm_device_virtual_grab_ports (device, (const gchar **)ports); /* Set plugin to use */ plugin = mm_plugin_manager_peek_plugin (self->priv->plugin_manager, plugin_name); if (!plugin) { error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, "Requested plugin '%s' not found", plugin_name); mm_warn ("Couldn't set plugin for virtual device '%s': %s", mm_device_get_uid (device), error->message); goto out; } mm_device_set_plugin (device, G_OBJECT (plugin)); /* Create modem */ if (!mm_device_create_modem (device, self->priv->object_manager, &error)) { mm_warn ("Couldn't create modem for virtual device '%s': %s", mm_device_get_uid (device), error->message); goto out; } mm_info ("Modem for virtual device '%s' successfully created", mm_device_get_uid (device)); out: if (error) { mm_device_remove_modem (device); g_hash_table_remove (self->priv->devices, mm_device_get_uid (device)); g_dbus_method_invocation_return_gerror (invocation, error); g_error_free (error); } else mm_gdbus_test_complete_set_profile (skeleton, invocation); return TRUE; } /*****************************************************************************/ MMBaseManager * mm_base_manager_new (GDBusConnection *connection, const gchar *plugin_dir, gboolean auto_scan, MMFilterRule filter_policy, const gchar *initial_kernel_events, gboolean enable_test, GError **error) { g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); return g_initable_new (MM_TYPE_BASE_MANAGER, NULL, /* cancellable */ error, MM_BASE_MANAGER_CONNECTION, connection, MM_BASE_MANAGER_PLUGIN_DIR, plugin_dir, MM_BASE_MANAGER_AUTO_SCAN, auto_scan, MM_BASE_MANAGER_FILTER_POLICY, filter_policy, MM_BASE_MANAGER_INITIAL_KERNEL_EVENTS, initial_kernel_events, MM_BASE_MANAGER_ENABLE_TEST, enable_test, NULL); } static void set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MMBaseManagerPrivate *priv = MM_BASE_MANAGER (object)->priv; switch (prop_id) { case PROP_CONNECTION: { gboolean had_connection = FALSE; if (priv->connection) { had_connection = TRUE; g_object_unref (priv->connection); } priv->connection = g_value_dup_object (value); /* Propagate connection loss to subobjects */ if (had_connection && !priv->connection) { if (priv->object_manager) { mm_dbg ("Stopping connection in object manager server"); g_dbus_object_manager_server_set_connection (priv->object_manager, NULL); } if (priv->test_skeleton && g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (priv->test_skeleton))) { mm_dbg ("Stopping connection in test skeleton"); g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (priv->test_skeleton)); } } break; } case PROP_AUTO_SCAN: priv->auto_scan = g_value_get_boolean (value); break; case PROP_FILTER_POLICY: priv->filter_policy = g_value_get_flags (value); break; case PROP_ENABLE_TEST: priv->enable_test = g_value_get_boolean (value); break; case PROP_PLUGIN_DIR: g_free (priv->plugin_dir); priv->plugin_dir = g_value_dup_string (value); break; case PROP_INITIAL_KERNEL_EVENTS: g_free (priv->initial_kernel_events); priv->initial_kernel_events = 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) { MMBaseManagerPrivate *priv = MM_BASE_MANAGER (object)->priv; switch (prop_id) { case PROP_CONNECTION: g_value_set_object (value, priv->connection); break; case PROP_AUTO_SCAN: g_value_set_boolean (value, priv->auto_scan); break; case PROP_FILTER_POLICY: g_value_set_flags (value, priv->filter_policy); break; case PROP_ENABLE_TEST: g_value_set_boolean (value, priv->enable_test); break; case PROP_PLUGIN_DIR: g_value_set_string (value, priv->plugin_dir); break; case PROP_INITIAL_KERNEL_EVENTS: g_value_set_string (value, priv->initial_kernel_events); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void mm_base_manager_init (MMBaseManager *manager) { MMBaseManagerPrivate *priv; /* Setup private data */ manager->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (manager, MM_TYPE_BASE_MANAGER, MMBaseManagerPrivate); /* Setup authorization provider */ priv->authp = mm_auth_get_provider (); priv->authp_cancellable = g_cancellable_new (); /* Setup internal lists of device objects */ priv->devices = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); #if defined WITH_UDEV { const gchar *subsys[5] = { "tty", "net", "usb", "usbmisc", NULL }; /* Setup UDev client */ priv->udev = g_udev_client_new (subsys); } #endif /* By default, enable autoscan */ priv->auto_scan = TRUE; /* By default, no test interface */ priv->enable_test = FALSE; /* Setup Object Manager Server */ 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), NULL); } static gboolean initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { MMBaseManagerPrivate *priv = MM_BASE_MANAGER (initable)->priv; #if defined WITH_UDEV /* If autoscan enabled, list for udev events */ if (priv->auto_scan) g_signal_connect (priv->udev, "uevent", G_CALLBACK (handle_uevent), initable); #endif /* Create filter */ priv->filter = mm_filter_new (priv->filter_policy, error); if (!priv->filter) return FALSE; /* Create plugin manager */ priv->plugin_manager = mm_plugin_manager_new (priv->plugin_dir, priv->filter, error); if (!priv->plugin_manager) return FALSE; /* Export the manager interface */ if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (initable), priv->connection, MM_DBUS_PATH, error)) return FALSE; /* Export the Object Manager interface */ g_dbus_object_manager_server_set_connection (priv->object_manager, priv->connection); /* Setup the Test skeleton and export the interface */ if (priv->enable_test) { priv->test_skeleton = mm_gdbus_test_skeleton_new (); g_signal_connect (priv->test_skeleton, "handle-set-profile", G_CALLBACK (handle_set_profile), initable); if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (priv->test_skeleton), priv->connection, MM_DBUS_PATH, error)) return FALSE; } /* All good */ return TRUE; } static void finalize (GObject *object) { MMBaseManagerPrivate *priv = MM_BASE_MANAGER (object)->priv; g_free (priv->initial_kernel_events); g_free (priv->plugin_dir); g_hash_table_destroy (priv->devices); #if defined WITH_UDEV if (priv->udev) g_object_unref (priv->udev); #endif if (priv->filter) g_object_unref (priv->filter); if (priv->plugin_manager) g_object_unref (priv->plugin_manager); if (priv->object_manager) g_object_unref (priv->object_manager); if (priv->test_skeleton) g_object_unref (priv->test_skeleton); if (priv->connection) g_object_unref (priv->connection); if (priv->authp) g_object_unref (priv->authp); if (priv->authp_cancellable) g_object_unref (priv->authp_cancellable); G_OBJECT_CLASS (mm_base_manager_parent_class)->finalize (object); } static void initable_iface_init (GInitableIface *iface) { iface->init = initable_init; } static void mm_base_manager_class_init (MMBaseManagerClass *manager_class) { GObjectClass *object_class = G_OBJECT_CLASS (manager_class); g_type_class_add_private (object_class, sizeof (MMBaseManagerPrivate)); /* Virtual methods */ object_class->set_property = set_property; object_class->get_property = get_property; object_class->finalize = finalize; /* Properties */ g_object_class_install_property (object_class, PROP_CONNECTION, g_param_spec_object (MM_BASE_MANAGER_CONNECTION, "Connection", "GDBus connection to the system bus.", G_TYPE_DBUS_CONNECTION, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_AUTO_SCAN, g_param_spec_boolean (MM_BASE_MANAGER_AUTO_SCAN, "Auto scan", "Automatically look for new devices", TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property ( object_class, PROP_FILTER_POLICY, g_param_spec_flags (MM_BASE_MANAGER_FILTER_POLICY, "Filter policy", "Mask of rules enabled in the filter", MM_TYPE_FILTER_RULE, MM_FILTER_RULE_NONE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_ENABLE_TEST, g_param_spec_boolean (MM_BASE_MANAGER_ENABLE_TEST, "Enable tests", "Enable the Test interface", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_PLUGIN_DIR, g_param_spec_string (MM_BASE_MANAGER_PLUGIN_DIR, "Plugin directory", "Where to look for plugins", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_INITIAL_KERNEL_EVENTS, g_param_spec_string (MM_BASE_MANAGER_INITIAL_KERNEL_EVENTS, "Initial kernel events", "Path to a file with the list of initial kernel events", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); }