Instead of relying on the udev daemon and GUDev to manage the devices reported by the kernel, we can now run ModemManager relying solely on the kernel events reported via the new ReportKernelEvent() API. Therefore, the '--no-auto-scan' option is implicit for the ModemManager daemon when udev is disabled in the build. Additionally, a new custom implementation of the kernel device object is provided, which uses sysfs to load the properties and attributes required in each kernel device, instead of using a GUdevDevice. The udev rule files are kept in place, and a simple custom parser is provided which preloads all rules in memory once and then applies them to the different kernel objects reported via ReportKernelEvent(), e.g. to set port type hints. A simple unit test setup is prepared to validate the udev rules during the `check' Makefile target.
1211 lines
38 KiB
C
1211 lines
38 KiB
C
/* -*- 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 <aleksander@aleksander.es>
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
#include <gmodule.h>
|
|
|
|
#if WITH_UDEV
|
|
# include "mm-kernel-device-udev.h"
|
|
#endif
|
|
#include "mm-kernel-device-generic.h"
|
|
|
|
#include <ModemManager.h>
|
|
#include <mm-errors-types.h>
|
|
#include <mm-gdbus-manager.h>
|
|
#include <mm-gdbus-test.h>
|
|
|
|
#include "mm-base-manager.h"
|
|
#include "mm-device.h"
|
|
#include "mm-plugin-manager.h"
|
|
#include "mm-auth.h"
|
|
#include "mm-plugin.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_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;
|
|
/* 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 container of devices being prepared */
|
|
GHashTable *devices;
|
|
/* The Object Manager server */
|
|
GDBusObjectManagerServer *object_manager;
|
|
|
|
/* The Test interface support */
|
|
MmGdbusTest *test_skeleton;
|
|
|
|
#if 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;
|
|
|
|
/* 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);
|
|
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);
|
|
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) {
|
|
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");
|
|
mm_device_remove_modem (device);
|
|
g_hash_table_remove (self->priv->devices, mm_device_get_uid (device));
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* 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));
|
|
|
|
if (!mm_kernel_device_is_candidate (port, manual_scan)) {
|
|
/* This could mean that device changed, losing its ID_MM_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;
|
|
}
|
|
|
|
/* 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 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 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);
|
|
}
|
|
|
|
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 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, 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 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 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,
|
|
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_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_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_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 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 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 plugin manager */
|
|
priv->plugin_manager = mm_plugin_manager_new (priv->plugin_dir, 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 WITH_UDEV
|
|
if (priv->udev)
|
|
g_object_unref (priv->udev);
|
|
#endif
|
|
|
|
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_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));
|
|
}
|