[PATCH 04/10] api: new ReportKernelEvent() api method to report kernel device addition/removals
Aleksander Morgado
aleksander at aleksander.es
Sat Aug 6 13:03:32 UTC 2016
The implementation allows different parameters being reported in the kernel
event, but only 3 of them are mandatory: 'action', 'name' and 'subsystem'. Once
these three parameters are received, ModemManager will load the remaining kernel
device information directly from sysfs.
E.g.:
$ sudo mmcli --report-kernel-event=" \
action=add, \
name=wwan0, \
subsystem=net
The 'physdev-uid' optional parameter may be given to bind together multiple
ports of the same device under a common known name.
The 'physdev-vid', 'physdev-pid' and 'driver' optional parameters may be given
to force a given device to be managed as if it was some other device. This
doesn't have any hard use case in real life, unless for testing devices (e.g. so
that different filters get applied during runtime).
E.g.:
$ sudo mmcli --report-kernel-event=" \
action=add, \
name=wwan0, \
subsystem=net, \
physdev-uid=usb-modem-rear-usb-port, \
physdev-vid=1199, \
physdev-pid=68a2, \
driver=qmi_wwan"
The 'candidate' optional parameter may be given to force treating as candidate
a given port that otherwise would have been ignored by the automatic rules
applied by ModemManager.
E.g.:
$ sudo mmcli --report-kernel-event=" \
action=add, \
name=/dev/ttyS0, \
subsystem=tty,
candidate=yes
---
cli/mmcli-manager.c | 77 +-
docs/reference/libmm-glib/libmm-glib-docs.xml | 1 +
docs/reference/libmm-glib/libmm-glib-sections.txt | 49 +-
introspection/org.freedesktop.ModemManager1.xml | 121 +++
libmm-glib/Makefile.am | 3 +
libmm-glib/libmm-glib.h | 1 +
libmm-glib/mm-common-helpers.c | 43 +
libmm-glib/mm-common-helpers.h | 2 +
libmm-glib/mm-kernel-event-properties.c | 689 ++++++++++++++
libmm-glib/mm-kernel-event-properties.h | 113 +++
libmm-glib/mm-manager.c | 124 +++
libmm-glib/mm-manager.h | 14 +
src/Makefile.am | 6 +
src/kerneldevice/mm-kernel-device-generic.c | 1010 +++++++++++++++++++++
src/kerneldevice/mm-kernel-device-generic.h | 51 ++
src/mm-base-manager.c | 147 ++-
16 files changed, 2444 insertions(+), 7 deletions(-)
create mode 100644 libmm-glib/mm-kernel-event-properties.c
create mode 100644 libmm-glib/mm-kernel-event-properties.h
create mode 100644 src/kerneldevice/mm-kernel-device-generic.c
create mode 100644 src/kerneldevice/mm-kernel-device-generic.h
diff --git a/cli/mmcli-manager.c b/cli/mmcli-manager.c
index 9960412..d78734f 100644
--- a/cli/mmcli-manager.c
+++ b/cli/mmcli-manager.c
@@ -47,6 +47,7 @@ static gboolean list_modems_flag;
static gboolean monitor_modems_flag;
static gboolean scan_modems_flag;
static gchar *set_logging_str;
+static gchar *report_kernel_event_str;
static GOptionEntry entries[] = {
{ "set-logging", 'G', 0, G_OPTION_ARG_STRING, &set_logging_str,
@@ -65,6 +66,10 @@ static GOptionEntry entries[] = {
"Request to re-scan looking for modems",
NULL
},
+ { "report-kernel-event", 'K', 0, G_OPTION_ARG_STRING, &report_kernel_event_str,
+ "Report kernel event",
+ "[\"key=value,...\"]"
+ },
{ NULL }
};
@@ -96,7 +101,8 @@ mmcli_manager_options_enabled (void)
n_actions = (list_modems_flag +
monitor_modems_flag +
scan_modems_flag +
- !!set_logging_str);
+ !!set_logging_str +
+ !!report_kernel_event_str);
if (n_actions > 1) {
g_printerr ("error: too many manager actions requested\n");
@@ -130,6 +136,47 @@ mmcli_manager_shutdown (void)
}
static void
+report_kernel_event_process_reply (gboolean result,
+ const GError *error)
+{
+ if (!result) {
+ g_printerr ("error: couldn't report kernel event: '%s'\n",
+ error ? error->message : "unknown error");
+ exit (EXIT_FAILURE);
+ }
+
+ g_print ("successfully reported kernel event\n");
+}
+
+static void
+report_kernel_event_ready (MMManager *manager,
+ GAsyncResult *result)
+{
+ gboolean operation_result;
+ GError *error = NULL;
+
+ operation_result = mm_manager_report_kernel_event_finish (manager, result, &error);
+ report_kernel_event_process_reply (operation_result, error);
+
+ mmcli_async_operation_done ();
+}
+
+static MMKernelEventProperties *
+build_kernel_event_properties_from_input (const gchar *properties_string)
+{
+ GError *error = NULL;
+ MMKernelEventProperties *properties;
+
+ properties = mm_kernel_event_properties_new_from_string (properties_string, &error);
+ if (!properties) {
+ g_printerr ("error: cannot parse properties string: '%s'\n", error->message);
+ exit (EXIT_FAILURE);
+ }
+
+ return properties;
+}
+
+static void
set_logging_process_reply (gboolean result,
const GError *error)
{
@@ -276,6 +323,20 @@ get_manager_ready (GObject *source,
return;
}
+ /* Request to report kernel event? */
+ if (report_kernel_event_str) {
+ MMKernelEventProperties *properties;
+
+ properties = build_kernel_event_properties_from_input (report_kernel_event_str);
+ mm_manager_report_kernel_event (ctx->manager,
+ properties,
+ ctx->cancellable,
+ (GAsyncReadyCallback)report_kernel_event_ready,
+ NULL);
+ g_object_unref (properties);
+ return;
+ }
+
/* Request to monitor modems? */
if (monitor_modems_flag) {
g_signal_connect (ctx->manager,
@@ -362,6 +423,20 @@ mmcli_manager_run_synchronous (GDBusConnection *connection)
return;
}
+ /* Request to report kernel event? */
+ if (report_kernel_event_str) {
+ MMKernelEventProperties *properties;
+ gboolean result;
+
+ properties = build_kernel_event_properties_from_input (report_kernel_event_str);
+ result = mm_manager_report_kernel_event_sync (ctx->manager,
+ properties,
+ NULL,
+ &error);
+ report_kernel_event_process_reply (result, error);
+ return;
+ }
+
/* Request to list modems? */
if (list_modems_flag) {
list_current_modems (ctx->manager);
diff --git a/docs/reference/libmm-glib/libmm-glib-docs.xml b/docs/reference/libmm-glib/libmm-glib-docs.xml
index 4e1d7fe..f611b72 100644
--- a/docs/reference/libmm-glib/libmm-glib-docs.xml
+++ b/docs/reference/libmm-glib/libmm-glib-docs.xml
@@ -72,6 +72,7 @@
<chapter>
<title>The Manager object</title>
<xi:include href="xml/mm-manager.xml"/>
+ <xi:include href="xml/mm-kernel-event-properties.xml"/>
</chapter>
<chapter>
diff --git a/docs/reference/libmm-glib/libmm-glib-sections.txt b/docs/reference/libmm-glib/libmm-glib-sections.txt
index 27a98cd..804a032 100644
--- a/docs/reference/libmm-glib/libmm-glib-sections.txt
+++ b/docs/reference/libmm-glib/libmm-glib-sections.txt
@@ -20,6 +20,9 @@ mm_manager_scan_devices_sync
mm_manager_set_logging
mm_manager_set_logging_finish
mm_manager_set_logging_sync
+mm_manager_report_kernel_event
+mm_manager_report_kernel_event_finish
+mm_manager_report_kernel_event_sync
<SUBSECTION Standard>
MMManagerClass
MMManagerPrivate
@@ -33,6 +36,46 @@ mm_manager_get_type
</SECTION>
<SECTION>
+<FILE>mm-kernel-event-properties</FILE>
+<TITLE>MMKernelEventProperties</TITLE>
+MMKernelEventProperties
+<SUBSECTION New>
+mm_kernel_event_properties_new
+mm_kernel_event_properties_dup
+<SUBSECTION GettersSetters>
+mm_kernel_event_properties_get_action
+mm_kernel_event_properties_set_action
+mm_kernel_event_properties_get_candidate
+mm_kernel_event_properties_set_candidate
+mm_kernel_event_properties_get_driver
+mm_kernel_event_properties_set_driver
+mm_kernel_event_properties_get_name
+mm_kernel_event_properties_set_name
+mm_kernel_event_properties_get_physdev_pid
+mm_kernel_event_properties_set_physdev_pid
+mm_kernel_event_properties_get_physdev_uid
+mm_kernel_event_properties_set_physdev_uid
+mm_kernel_event_properties_get_physdev_vid
+mm_kernel_event_properties_set_physdev_vid
+mm_kernel_event_properties_get_subsystem
+mm_kernel_event_properties_set_subsystem
+<SUBSECTION Private>
+mm_kernel_event_properties_get_dictionary
+mm_kernel_event_properties_new_from_dictionary
+mm_kernel_event_properties_new_from_string
+<SUBSECTION Standard>
+MMKernelEventPropertiesClass
+MMKernelEventPropertiesPrivate
+MM_IS_KERNEL_EVENT_PROPERTIES
+MM_IS_KERNEL_EVENT_PROPERTIES_CLASS
+MM_KERNEL_EVENT_PROPERTIES
+MM_KERNEL_EVENT_PROPERTIES_CLASS
+MM_KERNEL_EVENT_PROPERTIES_GET_CLASS
+MM_TYPE_KERNEL_EVENT_PROPERTIES
+mm_kernel_event_properties_get_type
+</SECTION>
+
+<SECTION>
<FILE>mm-object</FILE>
<TITLE>MMObject</TITLE>
MMObject
@@ -295,7 +338,6 @@ mm_modem_3gpp_ussd_get_type
<FILE>mm-cdma-manual-activation-properties</FILE>
<TITLE>MMCdmaManualActivationProperties</TITLE>
MMCdmaManualActivationProperties
-
<SUBSECTION Methods>
mm_cdma_manual_activation_properties_new
mm_cdma_manual_activation_properties_get_spc
@@ -896,7 +938,6 @@ MMModemVoice
<SUBSECTION Getters>
mm_modem_voice_get_path
mm_modem_voice_dup_path
-
<SUBSECTION Methods>
mm_modem_voice_create_call
mm_modem_voice_create_call_finish
@@ -1555,10 +1596,14 @@ mm_gdbus_org_freedesktop_modem_manager1_call_scan_devices_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
+mm_gdbus_org_freedesktop_modem_manager1_call_report_kernel_event
+mm_gdbus_org_freedesktop_modem_manager1_call_report_kernel_event_finish
+mm_gdbus_org_freedesktop_modem_manager1_call_report_kernel_event_sync
<SUBSECTION Private>
mm_gdbus_org_freedesktop_modem_manager1_override_properties
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
mm_gdbus_org_freedesktop_modem_manager1_interface_info
<SUBSECTION Standard>
MM_GDBUS_IS_ORG_FREEDESKTOP_MODEM_MANAGER1
diff --git a/introspection/org.freedesktop.ModemManager1.xml b/introspection/org.freedesktop.ModemManager1.xml
index 2ecd026..32c896b 100644
--- a/introspection/org.freedesktop.ModemManager1.xml
+++ b/introspection/org.freedesktop.ModemManager1.xml
@@ -37,5 +37,126 @@
<arg name="level" type="s" direction="in" />
</method>
+ <!--
+ ReportKernelEvent:
+ @properties: event properties.
+
+ Reports a kernel event to ModemManager.
+
+ This method is only available if udev is not being used to report kernel
+ events.
+
+ The @properties dictionary is composed of key/value string pairs. The
+ possible keys are:
+
+ <variablelist>
+
+ <varlistentry><term><literal>action</literal></term>
+ <listitem>
+ <para>
+ The type of action, given as a string value (signature
+ <literal>"s"</literal>).
+ This parameter is MANDATORY.
+ </para>
+ <variablelist>
+ <varlistentry><term><literal>add</literal></term>
+ <listitem>
+ A new kernel device has been added.
+ </listitem>
+ </varlistentry>
+ <varlistentry><term><literal>remove</literal></term>
+ <listitem>
+ An existing kernel device has been removed.
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry><term><literal>name</literal></term>
+ <listitem>
+ <para>
+ The device name, given as a string value (signature
+ <literal>"s"</literal>).
+ This parameter is MANDATORY.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry><term><literal>subsystem</literal></term>
+ <listitem>
+ <para>
+ The device subsystem, given as a string value (signature
+ <literal>"s"</literal>).
+ This parameter is MANDATORY.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry><term><literal>driver</literal></term>
+ <listitem>
+ <para>
+ The device driver, given as a string value (signature
+ <literal>"s"</literal>).
+ This parameter is OPTIONAL, if not given it will be retrieved
+ from sysfs.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry><term><literal>physdev-uid</literal></term>
+ <listitem>
+ <para>
+ The unique ID of the physical device, given as a string value
+ (signature <literal>"s"</literal>).
+ This parameter is OPTIONAL, if not given the sysfs path of the
+ physical device will be used. This parameter must be the same
+ for all devices exposed by the same physical device.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry><term><literal>physdev-vid</literal></term>
+ <listitem>
+ <para>
+ The USB vendor ID of the physical device, given as a 16 bit
+ unsigned integer (signature <literal>"q"</literal>.
+ This parameter is OPTIONAL, if not given it will be retrieved
+ from sysfs. This parameter may be used to override the real one
+ exposed by the device.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry><term><literal>physdev-pid</literal></term>
+ <listitem>
+ <para>
+ The USB product ID of the physical device, given as a 16 bit
+ unsigned integer (signature <literal>"q"</literal>.
+ This parameter is OPTIONAL, if not given it will be retrieved
+ from sysfs. This parameter may be used to override the real one
+ exposed by the device.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry><term><literal>candidate</literal></term>
+ <listitem>
+ <para>
+ Flag to force treating the device as a candidate, despite other
+ rules that ModemManager may have, given as a boolean value
+ (signature <literal>"b"</literal>).
+ This parameter is OPTIONAL, and not specifying it has the same
+ effect as setting it to false.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ -->
+ <method name="ReportKernelEvent">
+ <arg name="properties" type="a{sv}" direction="in" />
+ </method>
+
</interface>
</node>
diff --git a/libmm-glib/Makefile.am b/libmm-glib/Makefile.am
index e257d6f..64bca40 100644
--- a/libmm-glib/Makefile.am
+++ b/libmm-glib/Makefile.am
@@ -81,6 +81,8 @@ libmm_glib_la_SOURCES = \
mm-cdma-manual-activation-properties.c \
mm-signal.h \
mm-signal.c \
+ mm-kernel-event-properties.h \
+ mm-kernel-event-properties.c \
$(NULL)
libmm_glib_la_CPPFLAGS = \
@@ -149,6 +151,7 @@ include_HEADERS = \
mm-firmware-properties.h \
mm-cdma-manual-activation-properties.h \
mm-signal.h \
+ mm-kernel-event-properties.h \
$(NULL)
CLEANFILES =
diff --git a/libmm-glib/libmm-glib.h b/libmm-glib/libmm-glib.h
index fa4c7e2..53f0a19 100644
--- a/libmm-glib/libmm-glib.h
+++ b/libmm-glib/libmm-glib.h
@@ -72,6 +72,7 @@
#include <mm-firmware-properties.h>
#include <mm-cdma-manual-activation-properties.h>
#include <mm-signal.h>
+#include <mm-kernel-event-properties.h>
/* generated */
#include <mm-errors-types.h>
diff --git a/libmm-glib/mm-common-helpers.c b/libmm-glib/mm-common-helpers.c
index c1ddd89..2af5850 100644
--- a/libmm-glib/mm-common-helpers.c
+++ b/libmm-glib/mm-common-helpers.c
@@ -1385,6 +1385,49 @@ mm_get_uint_from_str (const gchar *str,
return FALSE;
}
+/**
+ * mm_get_uint_from_hex_str:
+ * @str: the hex string to convert to an unsigned int
+ * @out: on success, the number
+ *
+ * Converts a string to an unsigned number. All characters in the string
+ * MUST be valid hexadecimal digits (0-9, A-F, a-f), otherwise FALSE is
+ * returned.
+ *
+ * An optional "0x" prefix may be given in @str.
+ *
+ * Returns: %TRUE if the string was converted, %FALSE if it was not or if it
+ * did not contain only digits.
+ */
+gboolean
+mm_get_uint_from_hex_str (const gchar *str,
+ guint *out)
+{
+ gulong num;
+
+ if (!str)
+ return FALSE;
+
+ if (g_str_has_prefix (str, "0x"))
+ str = &str[2];
+
+ if (!str[0])
+ return FALSE;
+
+ for (num = 0; str[num]; num++) {
+ if (!g_ascii_isxdigit (str[num]))
+ return FALSE;
+ }
+
+ errno = 0;
+ num = strtoul (str, NULL, 16);
+ if (!errno && num <= G_MAXUINT) {
+ *out = (guint)num;
+ return TRUE;
+ }
+ return FALSE;
+}
+
gboolean
mm_get_uint_from_match_info (GMatchInfo *match_info,
guint32 match_index,
diff --git a/libmm-glib/mm-common-helpers.h b/libmm-glib/mm-common-helpers.h
index c65d3b9..ab5777d 100644
--- a/libmm-glib/mm-common-helpers.h
+++ b/libmm-glib/mm-common-helpers.h
@@ -143,6 +143,8 @@ gboolean mm_get_int_from_match_info (GMatchInfo *match_info,
gint *out);
gboolean mm_get_uint_from_str (const gchar *str,
guint *out);
+gboolean mm_get_uint_from_hex_str (const gchar *str,
+ guint *out);
gboolean mm_get_uint_from_match_info (GMatchInfo *match_info,
guint32 match_index,
guint *out);
diff --git a/libmm-glib/mm-kernel-event-properties.c b/libmm-glib/mm-kernel-event-properties.c
new file mode 100644
index 0000000..5ff52f9
--- /dev/null
+++ b/libmm-glib/mm-kernel-event-properties.c
@@ -0,0 +1,689 @@
+/* -*- 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) 2016 Velocloud, Inc.
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "mm-errors-types.h"
+#include "mm-enums-types.h"
+#include "mm-common-helpers.h"
+#include "mm-kernel-event-properties.h"
+
+/**
+ * SECTION: mm-kernel-event-properties
+ * @title: MMKernelEventProperties
+ * @short_description: Helper object to handle kernel event properties.
+ *
+ * The #MMKernelEventProperties is an object handling the properties to be set
+ * in reported kernel events.
+ *
+ * This object is created by the user and passed to ModemManager with either
+ * mm_manager_report_kernel_event() or mm_manager_report_kernel_event_sync().
+ */
+
+G_DEFINE_TYPE (MMKernelEventProperties, mm_kernel_event_properties, G_TYPE_OBJECT)
+
+#define PROPERTY_ACTION "action"
+#define PROPERTY_SUBSYSTEM "subsystem"
+#define PROPERTY_NAME "name"
+#define PROPERTY_DRIVER "driver"
+#define PROPERTY_PHYSDEV_UID "physdev-uid"
+#define PROPERTY_PHYSDEV_VID "physdev-vid"
+#define PROPERTY_PHYSDEV_PID "physdev-pid"
+#define PROPERTY_CANDIDATE "candidate"
+
+struct _MMKernelEventPropertiesPrivate {
+ gchar *action;
+ gchar *subsystem;
+ gchar *name;
+ gchar *driver;
+ gchar *physdev_uid;
+ guint16 physdev_vid;
+ guint16 physdev_pid;
+ gboolean candidate;
+};
+
+/*****************************************************************************/
+
+/**
+ * mm_kernel_event_properties_set_action:
+ * @self: A #MMKernelEventProperties.
+ * @action: The action to set.
+ *
+ * Sets the action.
+ */
+void
+mm_kernel_event_properties_set_action (MMKernelEventProperties *self,
+ const gchar *action)
+{
+ g_return_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self));
+
+ g_free (self->priv->action);
+ self->priv->action = g_strdup (action);
+}
+
+/**
+ * mm_kernel_event_properties_get_action:
+ * @self: A #MMKernelEventProperties.
+ *
+ * Gets the action.
+ *
+ * Returns: (transfer none): The action. Do not free the returned value, it is owned by @self.
+ */
+const gchar *
+mm_kernel_event_properties_get_action (MMKernelEventProperties *self)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self), NULL);
+
+ return self->priv->action;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_kernel_event_properties_set_subsystem:
+ * @self: A #MMKernelEventProperties.
+ * @subsystem: The subsystem to set.
+ *
+ * Sets the subsystem.
+ */
+void
+mm_kernel_event_properties_set_subsystem (MMKernelEventProperties *self,
+ const gchar *subsystem)
+{
+ g_return_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self));
+
+ g_free (self->priv->subsystem);
+ self->priv->subsystem = g_strdup (subsystem);
+}
+
+/**
+ * mm_kernel_event_properties_get_subsystem:
+ * @self: A #MMKernelEventProperties.
+ *
+ * Gets the subsystem.
+ *
+ * Returns: (transfer none): The subsystem. Do not free the returned value, it is owned by @self.
+ */
+const gchar *
+mm_kernel_event_properties_get_subsystem (MMKernelEventProperties *self)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self), NULL);
+
+ return self->priv->subsystem;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_kernel_event_properties_set_name:
+ * @self: A #MMKernelEventProperties.
+ * @name: The name to set.
+ *
+ * Sets the name.
+ */
+void
+mm_kernel_event_properties_set_name (MMKernelEventProperties *self,
+ const gchar *name)
+{
+ g_return_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self));
+
+ g_free (self->priv->name);
+ self->priv->name = g_strdup (name);
+}
+
+/**
+ * mm_kernel_event_properties_get_name:
+ * @self: A #MMKernelEventProperties.
+ *
+ * Gets the name.
+ *
+ * Returns: (transfer none): The name. Do not free the returned value, it is owned by @self.
+ */
+const gchar *
+mm_kernel_event_properties_get_name (MMKernelEventProperties *self)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self), NULL);
+
+ return self->priv->name;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_kernel_event_properties_set_driver:
+ * @self: A #MMKernelEventProperties.
+ * @driver: The driver to set.
+ *
+ * Sets the driver.
+ */
+void
+mm_kernel_event_properties_set_driver (MMKernelEventProperties *self,
+ const gchar *driver)
+{
+ g_return_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self));
+
+ g_free (self->priv->driver);
+ self->priv->driver = g_strdup (driver);
+}
+
+/**
+ * mm_kernel_event_properties_get_driver:
+ * @self: A #MMKernelEventProperties.
+ *
+ * Gets the driver.
+ *
+ * Returns: (transfer none): The driver. Do not free the returned value, it is owned by @self.
+ */
+const gchar *
+mm_kernel_event_properties_get_driver (MMKernelEventProperties *self)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self), NULL);
+
+ return self->priv->driver;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_kernel_event_properties_set_physdev_uid:
+ * @self: A #MMKernelEventProperties.
+ * @physdev_uid: The physdev uid to set.
+ *
+ * Sets the unique ID of the physical device.
+ */
+void
+mm_kernel_event_properties_set_physdev_uid (MMKernelEventProperties *self,
+ const gchar *physdev_uid)
+{
+ g_return_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self));
+
+ g_free (self->priv->physdev_uid);
+ self->priv->physdev_uid = g_strdup (physdev_uid);
+}
+
+/**
+ * mm_kernel_event_properties_get_physdev_uid:
+ * @self: A #MMKernelEventProperties.
+ *
+ * Gets the unique ID of the physical device.
+ *
+ * Returns: (transfer none): The physdev uid. Do not free the returned value, it is owned by @self.
+ */
+const gchar *
+mm_kernel_event_properties_get_physdev_uid (MMKernelEventProperties *self)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self), NULL);
+
+ return self->priv->physdev_uid;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_kernel_event_properties_set_physdev_vid:
+ * @self: A #MMKernelEventProperties.
+ * @physdev_vid: The vendor id to set.
+ *
+ * Sets the USB vendor id of the physical device.
+ */
+void
+mm_kernel_event_properties_set_physdev_vid (MMKernelEventProperties *self,
+ guint16 physdev_vid)
+{
+ g_return_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self));
+
+ self->priv->physdev_vid = physdev_vid;
+}
+
+/**
+ * mm_kernel_event_properties_get_physdev_vid:
+ * @self: A #MMKernelEventProperties.
+ *
+ * Gets the USB vendor id of the physical device.
+ *
+ * Returns: The vendor id.
+ */
+guint16
+mm_kernel_event_properties_get_physdev_vid (MMKernelEventProperties *self)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self), 0);
+
+ return self->priv->physdev_vid;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_kernel_event_properties_set_physdev_pid:
+ * @self: A #MMKernelEventProperties.
+ * @physdev_pid: The product id to set.
+ *
+ * Sets the USB product id of the physical device.
+ */
+void
+mm_kernel_event_properties_set_physdev_pid (MMKernelEventProperties *self,
+ guint16 physdev_pid)
+{
+ g_return_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self));
+
+ self->priv->physdev_pid = physdev_pid;
+}
+
+/**
+ * mm_kernel_event_properties_get_physdev_pid:
+ * @self: A #MMKernelEventProperties.
+ *
+ * Gets the USB product id of the physical device.
+ *
+ * Returns: The product id.
+ */
+guint16
+mm_kernel_event_properties_get_physdev_pid (MMKernelEventProperties *self)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self), 0);
+
+ return self->priv->physdev_pid;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_kernel_event_properties_set_candidate:
+ * @self: A #MMKernelEventProperties.
+ * @candidate: %TRUE if candidate, %FALSE otherwise.
+ *
+ * Sets the flag specifying wheter the device should be treated as a candidate
+ * regardless of any internal rules to filter it out.
+ */
+void
+mm_kernel_event_properties_set_candidate (MMKernelEventProperties *self,
+ gboolean candidate)
+{
+ g_return_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self));
+
+ self->priv->candidate = candidate;
+}
+
+/**
+ * mm_kernel_event_properties_get_candidate:
+ * @self: A #MMKernelEventProperties.
+ *
+ * Gets the flag specifying wheter the device should be treated as a candidate
+ * regardless of any internal rules to filter it out.
+ *
+ * Returns: %TRUE if candidate, %FALSE otherwise.
+ */
+gboolean
+mm_kernel_event_properties_get_candidate (MMKernelEventProperties *self)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self), FALSE);
+
+ return self->priv->candidate;
+}
+
+/*****************************************************************************/
+
+GVariant *
+mm_kernel_event_properties_get_dictionary (MMKernelEventProperties *self)
+{
+ GVariantBuilder builder;
+
+ /* We do allow NULL */
+ if (!self)
+ return NULL;
+
+ g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self), NULL);
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+
+ if (self->priv->action)
+ g_variant_builder_add (&builder,
+ "{sv}",
+ PROPERTY_ACTION,
+ g_variant_new_string (self->priv->action));
+
+ if (self->priv->subsystem)
+ g_variant_builder_add (&builder,
+ "{sv}",
+ PROPERTY_SUBSYSTEM,
+ g_variant_new_string (self->priv->subsystem));
+
+ if (self->priv->name)
+ g_variant_builder_add (&builder,
+ "{sv}",
+ PROPERTY_NAME,
+ g_variant_new_string (self->priv->name));
+
+ if (self->priv->driver)
+ g_variant_builder_add (&builder,
+ "{sv}",
+ PROPERTY_DRIVER,
+ g_variant_new_string (self->priv->driver));
+
+ if (self->priv->physdev_uid)
+ g_variant_builder_add (&builder,
+ "{sv}",
+ PROPERTY_PHYSDEV_UID,
+ g_variant_new_string (self->priv->physdev_uid));
+
+ if (self->priv->physdev_vid)
+ g_variant_builder_add (&builder,
+ "{sv}",
+ PROPERTY_PHYSDEV_VID,
+ g_variant_new_uint16 (self->priv->physdev_vid));
+
+ if (self->priv->physdev_pid)
+ g_variant_builder_add (&builder,
+ "{sv}",
+ PROPERTY_PHYSDEV_PID,
+ g_variant_new_uint16 (self->priv->physdev_pid));
+
+ /* Just avoid adding it if FALSE */
+ if (self->priv->candidate)
+ g_variant_builder_add (&builder,
+ "{sv}",
+ PROPERTY_CANDIDATE,
+ g_variant_new_boolean (TRUE));
+
+ return g_variant_ref_sink (g_variant_builder_end (&builder));
+}
+
+/*****************************************************************************/
+
+static guint16
+parse_uint16 (const gchar *str,
+ GError **error)
+{
+ guint num;
+
+ if (!mm_get_uint_from_hex_str (str, &num) || num > G_MAXUINT16) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_INVALID_ARGS,
+ "Invalid properties string, cannot parse '%s' as hexadecimal uint16",
+ str);
+ return 0;
+ }
+
+ return num;
+}
+
+static gboolean
+consume_string (MMKernelEventProperties *self,
+ const gchar *key,
+ const gchar *value,
+ GError **error)
+{
+ if (g_str_equal (key, PROPERTY_ACTION))
+ mm_kernel_event_properties_set_action (self, value);
+ else if (g_str_equal (key, PROPERTY_SUBSYSTEM))
+ mm_kernel_event_properties_set_subsystem (self, value);
+ else if (g_str_equal (key, PROPERTY_NAME))
+ mm_kernel_event_properties_set_name (self, value);
+ else if (g_str_equal (key, PROPERTY_DRIVER))
+ mm_kernel_event_properties_set_driver (self, value);
+ else if (g_str_equal (key, PROPERTY_PHYSDEV_UID))
+ mm_kernel_event_properties_set_physdev_uid (self, value);
+ else if (g_str_equal (key, PROPERTY_PHYSDEV_VID)) {
+ guint16 val;
+ GError *inner_error = NULL;
+
+ val = parse_uint16 (value, &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+ mm_kernel_event_properties_set_physdev_vid (self, val);
+ } else if (g_str_equal (key, PROPERTY_PHYSDEV_PID)) {
+ guint16 val;
+ GError *inner_error = NULL;
+
+ val = parse_uint16 (value, &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+ mm_kernel_event_properties_set_physdev_pid (self, val);
+ } else if (g_str_equal (key, PROPERTY_CANDIDATE)) {
+ gboolean val ;
+ GError *inner_error = NULL;
+
+ val = mm_common_get_boolean_from_string (value, &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+ mm_kernel_event_properties_set_candidate (self, val);
+ } else {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_INVALID_ARGS,
+ "Invalid properties string, unexpected key '%s'",
+ key);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+typedef struct {
+ MMKernelEventProperties *properties;
+ GError *error;
+} ParseKeyValueContext;
+
+static gboolean
+key_value_foreach (const gchar *key,
+ const gchar *value,
+ ParseKeyValueContext *ctx)
+{
+ return consume_string (ctx->properties,
+ key,
+ value,
+ &ctx->error);
+}
+
+MMKernelEventProperties *
+mm_kernel_event_properties_new_from_string (const gchar *str,
+ GError **error)
+{
+ ParseKeyValueContext ctx;
+
+ ctx.properties = mm_kernel_event_properties_new ();
+ ctx.error = NULL;
+
+ mm_common_parse_key_value_string (str,
+ &ctx.error,
+ (MMParseKeyValueForeachFn) key_value_foreach,
+ &ctx);
+
+ /* If error, destroy the object */
+ if (ctx.error) {
+ g_propagate_error (error, ctx.error);
+ g_object_unref (ctx.properties);
+ ctx.properties = NULL;
+ }
+
+ return ctx.properties;
+}
+
+/*****************************************************************************/
+
+static gboolean
+consume_variant (MMKernelEventProperties *properties,
+ const gchar *key,
+ GVariant *value,
+ GError **error)
+{
+ if (g_str_equal (key, PROPERTY_ACTION))
+ mm_kernel_event_properties_set_action (
+ properties,
+ g_variant_get_string (value, NULL));
+ else if (g_str_equal (key, PROPERTY_SUBSYSTEM))
+ mm_kernel_event_properties_set_subsystem (
+ properties,
+ g_variant_get_string (value, NULL));
+ else if (g_str_equal (key, PROPERTY_NAME))
+ mm_kernel_event_properties_set_name (
+ properties,
+ g_variant_get_string (value, NULL));
+ else if (g_str_equal (key, PROPERTY_DRIVER))
+ mm_kernel_event_properties_set_driver (
+ properties,
+ g_variant_get_string (value, NULL));
+ else if (g_str_equal (key, PROPERTY_PHYSDEV_UID))
+ mm_kernel_event_properties_set_physdev_uid (
+ properties,
+ g_variant_get_string (value, NULL));
+ else if (g_str_equal (key, PROPERTY_PHYSDEV_VID))
+ mm_kernel_event_properties_set_physdev_vid (
+ properties,
+ g_variant_get_uint16 (value));
+ else if (g_str_equal (key, PROPERTY_PHYSDEV_PID))
+ mm_kernel_event_properties_set_physdev_pid (
+ properties,
+ g_variant_get_uint16 (value));
+ else {
+ /* Set error */
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_INVALID_ARGS,
+ "Invalid properties dictionary, unexpected key '%s'",
+ key);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+MMKernelEventProperties *
+mm_kernel_event_properties_new_from_dictionary (GVariant *dictionary,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ GVariantIter iter;
+ gchar *key;
+ GVariant *value;
+ MMKernelEventProperties *properties;
+
+ properties = mm_kernel_event_properties_new ();
+ if (!dictionary)
+ return properties;
+
+ if (!g_variant_is_of_type (dictionary, G_VARIANT_TYPE ("a{sv}"))) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_INVALID_ARGS,
+ "Cannot create kernel event properties from dictionary: "
+ "invalid variant type received");
+ g_object_unref (properties);
+ return NULL;
+ }
+
+ g_variant_iter_init (&iter, dictionary);
+ while (!inner_error &&
+ g_variant_iter_next (&iter, "{sv}", &key, &value)) {
+ consume_variant (properties,
+ key,
+ value,
+ &inner_error);
+ g_free (key);
+ g_variant_unref (value);
+ }
+
+ /* If error, destroy the object */
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ g_object_unref (properties);
+ properties = NULL;
+ }
+
+ return properties;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_kernel_event_properties_dup:
+ * @orig: a #MMKernelEventProperties
+ *
+ * Returns a copy of @orig.
+ *
+ * Returns: (transfer full): a #MMKernelEventProperties
+ */
+MMKernelEventProperties *
+mm_kernel_event_properties_dup (MMKernelEventProperties *orig)
+{
+ GVariant *dict;
+ MMKernelEventProperties *copy;
+ GError *error = NULL;
+
+ g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (orig), NULL);
+
+ dict = mm_kernel_event_properties_get_dictionary (orig);
+ copy = mm_kernel_event_properties_new_from_dictionary (dict, &error);
+ g_assert_no_error (error);
+ g_variant_unref (dict);
+
+ return copy;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_kernel_event_properties_new:
+ *
+ * Creates a new empty #MMKernelEventProperties.
+ *
+ * Returns: (transfer full): a #MMKernelEventProperties. The returned value should be freed with g_object_unref().
+ */
+MMKernelEventProperties *
+mm_kernel_event_properties_new (void)
+{
+ return (MM_KERNEL_EVENT_PROPERTIES (g_object_new (MM_TYPE_KERNEL_EVENT_PROPERTIES, NULL)));
+}
+
+static void
+mm_kernel_event_properties_init (MMKernelEventProperties *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ MM_TYPE_KERNEL_EVENT_PROPERTIES,
+ MMKernelEventPropertiesPrivate);
+}
+
+static void
+finalize (GObject *object)
+{
+ MMKernelEventProperties *self = MM_KERNEL_EVENT_PROPERTIES (object);
+
+ g_free (self->priv->action);
+ g_free (self->priv->subsystem);
+ g_free (self->priv->name);
+ g_free (self->priv->driver);
+ g_free (self->priv->physdev_uid);
+
+ G_OBJECT_CLASS (mm_kernel_event_properties_parent_class)->finalize (object);
+}
+
+static void
+mm_kernel_event_properties_class_init (MMKernelEventPropertiesClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMKernelEventPropertiesPrivate));
+
+ object_class->finalize = finalize;
+}
diff --git a/libmm-glib/mm-kernel-event-properties.h b/libmm-glib/mm-kernel-event-properties.h
new file mode 100644
index 0000000..eb52b21
--- /dev/null
+++ b/libmm-glib/mm-kernel-event-properties.h
@@ -0,0 +1,113 @@
+/* -*- 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) 2016 Velocloud, Inc.
+ */
+
+#ifndef MM_KERNEL_EVENT_PROPERTIES_H
+#define MM_KERNEL_EVENT_PROPERTIES_H
+
+#if !defined (__LIBMM_GLIB_H_INSIDE__) && !defined (LIBMM_GLIB_COMPILATION)
+#error "Only <libmm-glib.h> can be included directly."
+#endif
+
+#include <ModemManager.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define MM_TYPE_KERNEL_EVENT_PROPERTIES (mm_kernel_event_properties_get_type ())
+#define MM_KERNEL_EVENT_PROPERTIES(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_KERNEL_EVENT_PROPERTIES, MMKernelEventProperties))
+#define MM_KERNEL_EVENT_PROPERTIES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_KERNEL_EVENT_PROPERTIES, MMKernelEventPropertiesClass))
+#define MM_IS_KERNEL_EVENT_PROPERTIES(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_KERNEL_EVENT_PROPERTIES))
+#define MM_IS_KERNEL_EVENT_PROPERTIES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_KERNEL_EVENT_PROPERTIES))
+#define MM_KERNEL_EVENT_PROPERTIES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_KERNEL_EVENT_PROPERTIES, MMKernelEventPropertiesClass))
+
+typedef struct _MMKernelEventProperties MMKernelEventProperties;
+typedef struct _MMKernelEventPropertiesClass MMKernelEventPropertiesClass;
+typedef struct _MMKernelEventPropertiesPrivate MMKernelEventPropertiesPrivate;
+
+/**
+ * MMKernelEventProperties:
+ *
+ * The #MMKernelEventProperties structure contains private data and should only be
+ * accessed using the provided API.
+ */
+struct _MMKernelEventProperties {
+ /*< private >*/
+ GObject parent;
+ MMKernelEventPropertiesPrivate *priv;
+};
+
+struct _MMKernelEventPropertiesClass {
+ /*< private >*/
+ GObjectClass parent;
+};
+
+GType mm_kernel_event_properties_get_type (void);
+
+MMKernelEventProperties *mm_kernel_event_properties_new (void);
+
+void mm_kernel_event_properties_set_action (MMKernelEventProperties *self,
+ const gchar *action);
+const gchar *mm_kernel_event_properties_get_action (MMKernelEventProperties *self);
+
+void mm_kernel_event_properties_set_subsystem (MMKernelEventProperties *self,
+ const gchar *subsystem);
+const gchar *mm_kernel_event_properties_get_subsystem (MMKernelEventProperties *self);
+
+void mm_kernel_event_properties_set_name (MMKernelEventProperties *self,
+ const gchar *name);
+const gchar *mm_kernel_event_properties_get_name (MMKernelEventProperties *self);
+
+void mm_kernel_event_properties_set_driver (MMKernelEventProperties *self,
+ const gchar *driver);
+const gchar *mm_kernel_event_properties_get_driver (MMKernelEventProperties *self);
+
+void mm_kernel_event_properties_set_physdev_uid (MMKernelEventProperties *self,
+ const gchar *physdev_uid);
+const gchar *mm_kernel_event_properties_get_physdev_uid (MMKernelEventProperties *self);
+
+void mm_kernel_event_properties_set_physdev_vid (MMKernelEventProperties *self,
+ guint16 physdev_vid);
+guint16 mm_kernel_event_properties_get_physdev_vid (MMKernelEventProperties *self);
+
+void mm_kernel_event_properties_set_physdev_pid (MMKernelEventProperties *self,
+ guint16 physdev_pid);
+guint16 mm_kernel_event_properties_get_physdev_pid (MMKernelEventProperties *self);
+
+void mm_kernel_event_properties_set_candidate (MMKernelEventProperties *self,
+ gboolean candidate);
+gboolean mm_kernel_event_properties_get_candidate (MMKernelEventProperties *self);
+
+/*****************************************************************************/
+/* ModemManager/libmm-glib/mmcli specific methods */
+
+#if defined (_LIBMM_INSIDE_MM) || \
+ defined (_LIBMM_INSIDE_MMCLI) || \
+ defined (LIBMM_GLIB_COMPILATION)
+
+MMKernelEventProperties *mm_kernel_event_properties_new_from_string (const gchar *str,
+ GError **error);
+
+MMKernelEventProperties *mm_kernel_event_properties_new_from_dictionary (GVariant *dictionary,
+ GError **error);
+
+MMKernelEventProperties *mm_kernel_event_properties_dup (MMKernelEventProperties *orig);
+
+GVariant *mm_kernel_event_properties_get_dictionary (MMKernelEventProperties *self);
+
+#endif
+
+G_END_DECLS
+
+#endif /* MM_KERNEL_EVENT_PROPERTIES_H */
diff --git a/libmm-glib/mm-manager.c b/libmm-glib/mm-manager.c
index cb196b3..6a9ce5d 100644
--- a/libmm-glib/mm-manager.c
+++ b/libmm-glib/mm-manager.c
@@ -497,6 +497,130 @@ mm_manager_scan_devices_sync (MMManager *manager,
/*****************************************************************************/
+/**
+ * mm_manager_report_kernel_event_finish:
+ * @manager: A #MMManager.
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to mm_manager_report_kernel_event().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with mm_manager_report_kernel_event().
+ *
+ * Returns: %TRUE if the operation succeded, %FALSE if @error is set.
+ */
+gboolean
+mm_manager_report_kernel_event_finish (MMManager *manager,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+report_kernel_event_ready (MmGdbusOrgFreedesktopModemManager1 *manager_iface_proxy,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_gdbus_org_freedesktop_modem_manager1_call_report_kernel_event_finish (
+ manager_iface_proxy,
+ res,
+ &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+/**
+ * mm_manager_report_kernel_event:
+ * @manager: A #MMManager.
+ * @properties: the properties of the kernel event.
+ * @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 report kernel event.
+ *
+ * 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_report_kernel_event_finish() to get the result of the operation.
+ *
+ * See mm_manager_report_kernel_event_sync() for the synchronous, blocking version of this method.
+ */
+void
+mm_manager_report_kernel_event (MMManager *manager,
+ MMKernelEventProperties *properties,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ GError *inner_error = NULL;
+ GVariant *dictionary;
+
+ g_return_if_fail (MM_IS_MANAGER (manager));
+
+ 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;
+ }
+
+ dictionary = mm_kernel_event_properties_get_dictionary (properties);
+ mm_gdbus_org_freedesktop_modem_manager1_call_report_kernel_event (
+ manager->priv->manager_iface_proxy,
+ dictionary,
+ cancellable,
+ (GAsyncReadyCallback)report_kernel_event_ready,
+ task);
+ g_variant_unref (dictionary);
+}
+
+/**
+ * mm_manager_report_kernel_event_sync:
+ * @manager: A #MMManager.
+ * @properties: the properties of the kernel event.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously report kernel event.
+ *
+ * The calling thread is blocked until a reply is received.
+ *
+ * See mm_manager_report_kernel_event() for the asynchronous version of this method.
+ *
+ * Returns: %TRUE if the operation succeded, %FALSE if @error is set.
+ */
+gboolean
+mm_manager_report_kernel_event_sync (MMManager *manager,
+ MMKernelEventProperties *properties,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVariant *dictionary;
+ gboolean result;
+
+ g_return_val_if_fail (MM_IS_MANAGER (manager), FALSE);
+
+ if (!ensure_modem_manager1_proxy (manager, error))
+ return FALSE;
+
+ dictionary = mm_kernel_event_properties_get_dictionary (properties);
+ result = (mm_gdbus_org_freedesktop_modem_manager1_call_report_kernel_event_sync (
+ manager->priv->manager_iface_proxy,
+ dictionary,
+ cancellable,
+ error));
+ g_variant_unref (dictionary);
+ return result;
+}
+
+/*****************************************************************************/
+
static void
register_dbus_errors (void)
{
diff --git a/libmm-glib/mm-manager.h b/libmm-glib/mm-manager.h
index 96c3066..57a129f 100644
--- a/libmm-glib/mm-manager.h
+++ b/libmm-glib/mm-manager.h
@@ -33,6 +33,7 @@
#include <ModemManager.h>
#include "mm-gdbus-modem.h"
+#include "mm-kernel-event-properties.h"
G_BEGIN_DECLS
@@ -108,6 +109,19 @@ gboolean mm_manager_scan_devices_sync (MMManager *manager,
GCancellable *cancellable,
GError **error);
+void mm_manager_report_kernel_event (MMManager *manager,
+ MMKernelEventProperties *properties,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_manager_report_kernel_event_finish (MMManager *manager,
+ GAsyncResult *res,
+ GError **error);
+gboolean mm_manager_report_kernel_event_sync (MMManager *manager,
+ MMKernelEventProperties *properties,
+ GCancellable *cancellable,
+ GError **error);
+
G_END_DECLS
#endif /* _MM_MANAGER_H_ */
diff --git a/src/Makefile.am b/src/Makefile.am
index f64f1c1..e16d059 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -172,9 +172,15 @@ CLEANFILES += $(PORT_ENUMS_GENERATED)
noinst_LTLIBRARIES += libkerneldevice.la
+libkerneldevice_la_CPPFLAGS = \
+ -DUDEVRULESDIR=\"$(udevrulesdir)\" \
+ $(NULL)
+
libkerneldevice_la_SOURCES = \
kerneldevice/mm-kernel-device.h \
kerneldevice/mm-kernel-device.c \
+ kerneldevice/mm-kernel-device-generic.h \
+ kerneldevice/mm-kernel-device-generic.c \
$(NULL)
if WITH_UDEV
diff --git a/src/kerneldevice/mm-kernel-device-generic.c b/src/kerneldevice/mm-kernel-device-generic.c
new file mode 100644
index 0000000..04fce70
--- /dev/null
+++ b/src/kerneldevice/mm-kernel-device-generic.c
@@ -0,0 +1,1010 @@
+/* -*- 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) 2016 Velocloud, Inc.
+ */
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-kernel-device-generic.h"
+#include "mm-log.h"
+
+#if !defined UDEVRULESDIR
+# error UDEVRULESDIR is not defined
+#endif
+
+/* Define the following symbol to enable verbose rule parsing traces */
+#undef TRACE_UDEV_RULES
+
+#if defined TRACE_UDEV_RULES
+# define trace(...) mm_dbg (__VA_ARGS__)
+#else
+# define trace(...)
+#endif
+
+static void initable_iface_init (GInitableIface *iface);
+
+G_DEFINE_TYPE_EXTENDED (MMKernelDeviceGeneric, mm_kernel_device_generic, MM_TYPE_KERNEL_DEVICE, 0,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))
+
+enum {
+ PROP_0,
+ PROP_PROPERTIES,
+ PROP_LAST
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+struct _MMKernelDeviceGenericPrivate {
+ /* Input properties */
+ MMKernelEventProperties *properties;
+
+ /* Contents from sysfs */
+ gchar *driver;
+ gchar *sysfs_path;
+ gchar *parent_sysfs_path;
+ gchar *physdev_sysfs_path;
+ guint16 physdev_vid;
+ guint16 physdev_pid;
+};
+
+static guint
+read_sysfs_property_as_hex (const gchar *path,
+ const gchar *property)
+{
+ gchar *aux;
+ gchar *contents = NULL;
+ guint val = 0;
+
+ aux = g_strdup_printf ("%s/%s", path, property);
+ if (g_file_get_contents (aux, &contents, NULL, NULL)) {
+ g_strdelimit (contents, "\r\n", ' ');
+ g_strstrip (contents);
+ mm_get_uint_from_hex_str (contents, &val);
+ }
+ g_free (contents);
+ g_free (aux);
+ return val;
+}
+
+static gchar *
+read_sysfs_property_as_string (const gchar *path,
+ const gchar *property)
+{
+ gchar *aux;
+ gchar *contents = NULL;
+
+ aux = g_strdup_printf ("%s/%s", path, property);
+ if (g_file_get_contents (aux, &contents, NULL, NULL)) {
+ g_strdelimit (contents, "\r\n", ' ');
+ g_strstrip (contents);
+ }
+ g_free (aux);
+ return contents;
+}
+
+/*****************************************************************************/
+/* Load contents */
+
+static void
+preload_sysfs_path (MMKernelDeviceGeneric *self)
+{
+ gchar *tmp;
+
+ if (self->priv->sysfs_path)
+ return;
+
+ /* sysfs can be built directly using subsystem and name; e.g. for subsystem
+ * usbmisc and name cdc-wdm0:
+ * $ realpath /sys/class/usbmisc/cdc-wdm0
+ * /sys/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.3/4-1.3:1.8/usbmisc/cdc-wdm0
+ */
+ tmp = g_strdup_printf ("/sys/class/%s/%s",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties));
+
+ self->priv->sysfs_path = canonicalize_file_name (tmp);
+ if (!self->priv->sysfs_path || !g_file_test (self->priv->sysfs_path, G_FILE_TEST_EXISTS)) {
+ mm_warn ("Invalid sysfs path read for %s/%s",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties));
+ g_clear_pointer (&self->priv->sysfs_path, g_free);
+ }
+
+ if (self->priv->sysfs_path)
+ mm_dbg ("(%s/%s) sysfs path: %s",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties),
+ self->priv->sysfs_path);
+ g_free (tmp);
+}
+
+static void
+preload_parent_sysfs_path (MMKernelDeviceGeneric *self)
+{
+ gchar *dirpath;
+ gchar *aux;
+
+ if (self->priv->parent_sysfs_path || !self->priv->sysfs_path)
+ return;
+
+ /* parent sysfs can be built directly using subsystem and name; e.g. for
+ * subsystem usbmisc and name cdc-wdm0:
+ * $ realpath /sys/class/usbmisc/cdc-wdm0/device
+ * /sys/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.3/4-1.3:1.8
+ *
+ * This sysfs path will be equal for all devices exported from within the
+ * same interface (e.g. a pair of cdc-wdm/wwan devices).
+ *
+ * The correct parent dir we want to have is the first one with "usb" subsystem.
+ */
+ aux = g_strdup_printf ("%s/device", self->priv->sysfs_path);
+ dirpath = canonicalize_file_name (aux);
+ g_free (aux);
+
+ while (dirpath) {
+ gchar *subsystem_filepath;
+
+ /* Directory must exist */
+ if (!g_file_test (dirpath, G_FILE_TEST_EXISTS))
+ break;
+
+ /* If subsystem file not found, keep looping */
+ subsystem_filepath = g_strdup_printf ("%s/subsystem", dirpath);
+ if (g_file_test (subsystem_filepath, G_FILE_TEST_EXISTS)) {
+ gchar *canonicalized_subsystem;
+ gchar *subsystem_name;
+
+ canonicalized_subsystem = canonicalize_file_name (subsystem_filepath);
+ g_free (subsystem_filepath);
+
+ subsystem_name = g_path_get_basename (canonicalized_subsystem);
+ g_free (canonicalized_subsystem);
+
+ if (subsystem_name && g_str_equal (subsystem_name, "usb")) {
+ self->priv->parent_sysfs_path = dirpath;
+ g_free (subsystem_name);
+ break;
+ }
+ } else
+ g_free (subsystem_filepath);
+
+ /* Just in case */
+ if (g_str_equal (dirpath, "/")) {
+ g_free (dirpath);
+ break;
+ }
+
+ aux = g_path_get_dirname (dirpath);
+ g_free (dirpath);
+ dirpath = aux;
+ }
+
+ if (self->priv->parent_sysfs_path)
+ mm_dbg ("(%s/%s) parent sysfs path: %s",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties),
+ self->priv->parent_sysfs_path);
+}
+
+static void
+preload_physdev_sysfs_path (MMKernelDeviceGeneric *self)
+{
+ /* physdev sysfs is the dirname of the parent sysfs path, e.g.:
+ * /sys/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.3
+ *
+ * This sysfs path will be equal for all devices exported from the same
+ * physical device.
+ */
+ if (!self->priv->physdev_sysfs_path && self->priv->parent_sysfs_path)
+ self->priv->physdev_sysfs_path = g_path_get_dirname (self->priv->parent_sysfs_path);
+
+ if (self->priv->physdev_sysfs_path)
+ mm_dbg ("(%s/%s) physdev sysfs path: %s",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties),
+ self->priv->physdev_sysfs_path);
+}
+
+static void
+preload_driver (MMKernelDeviceGeneric *self)
+{
+ if (!self->priv->driver && self->priv->parent_sysfs_path) {
+ gchar *tmp;
+ gchar *tmp2;
+
+ tmp = g_strdup_printf ("%s/driver", self->priv->parent_sysfs_path);
+ tmp2 = canonicalize_file_name (tmp);
+ if (tmp2 && g_file_test (tmp2, G_FILE_TEST_EXISTS))
+ self->priv->driver = g_path_get_basename (tmp2);
+ g_free (tmp2);
+ g_free (tmp);
+ }
+
+ if (self->priv->driver)
+ mm_dbg ("(%s/%s) driver: %s",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties),
+ self->priv->driver);
+}
+
+static void
+preload_physdev_vid (MMKernelDeviceGeneric *self)
+{
+ if (!self->priv->physdev_vid && self->priv->physdev_sysfs_path) {
+ guint val;
+
+ val = read_sysfs_property_as_hex (self->priv->physdev_sysfs_path, "idVendor");
+ if (val && val <= G_MAXUINT16)
+ self->priv->physdev_vid = val;
+ }
+
+ if (self->priv->physdev_vid)
+ mm_dbg ("(%s/%s) vid: 0x%04x",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties),
+ self->priv->physdev_vid);
+}
+
+static void
+preload_physdev_pid (MMKernelDeviceGeneric *self)
+{
+ if (!self->priv->physdev_pid && self->priv->physdev_sysfs_path) {
+ guint val;
+
+ val = read_sysfs_property_as_hex (self->priv->physdev_sysfs_path, "idProduct");
+ if (val && val <= G_MAXUINT16)
+ self->priv->physdev_pid = val;
+ }
+
+ if (self->priv->physdev_pid)
+ mm_dbg ("(%s/%s) pid: 0x%04x",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties),
+ self->priv->physdev_pid);
+}
+
+static void
+preload_contents (MMKernelDeviceGeneric *self)
+{
+ preload_sysfs_path (self);
+ preload_parent_sysfs_path (self);
+ preload_physdev_sysfs_path (self);
+
+ if (!mm_kernel_event_properties_get_driver (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties))
+ preload_driver (self);
+
+ if (!mm_kernel_event_properties_get_physdev_vid (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties))
+ preload_physdev_vid (self);
+
+ if (!mm_kernel_event_properties_get_physdev_pid (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties))
+ preload_physdev_pid (self);
+}
+
+/*****************************************************************************/
+
+static const gchar *
+kernel_device_get_subsystem (MMKernelDevice *self)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
+
+ return mm_kernel_event_properties_get_subsystem (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties);
+}
+
+static const gchar *
+kernel_device_get_name (MMKernelDevice *self)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
+
+ return mm_kernel_event_properties_get_name (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties);
+}
+
+static const gchar *
+kernel_device_get_sysfs_path (MMKernelDevice *self)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
+
+ return MM_KERNEL_DEVICE_GENERIC (self)->priv->sysfs_path;
+}
+
+static const gchar *
+kernel_device_get_parent_sysfs_path (MMKernelDevice *self)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
+
+ return MM_KERNEL_DEVICE_GENERIC (self)->priv->parent_sysfs_path;
+}
+
+static const gchar *
+kernel_device_get_physdev_uid (MMKernelDevice *self)
+{
+ const gchar *physdev_uid;
+
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
+
+ /* Prefer the one coming in the properties, if any */
+ physdev_uid = mm_kernel_event_properties_get_physdev_uid (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties);
+ if (!physdev_uid)
+ physdev_uid = MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_sysfs_path;
+ /* If there is no physdev sysfs path, e.g. for platform ports, use the device sysfs itself */
+ if (!physdev_uid)
+ physdev_uid = MM_KERNEL_DEVICE_GENERIC (self)->priv->sysfs_path;
+ return physdev_uid;
+}
+
+static const gchar *
+kernel_device_get_driver (MMKernelDevice *self)
+{
+ const gchar *driver;
+
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
+
+ /* Prefer the one coming in the properties, if any */
+ driver = mm_kernel_event_properties_get_driver (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties);
+ if (!driver)
+ driver = MM_KERNEL_DEVICE_GENERIC (self)->priv->driver;
+ return driver;
+}
+
+static guint16
+kernel_device_get_physdev_vid (MMKernelDevice *self)
+{
+ guint16 physdev_vid;
+
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), 0);
+
+ /* Prefer the one coming in the properties, if any */
+ physdev_vid = mm_kernel_event_properties_get_physdev_vid (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties);
+ if (!physdev_vid)
+ physdev_vid = MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_vid;
+
+ return physdev_vid;
+}
+
+static guint16
+kernel_device_get_physdev_pid (MMKernelDevice *self)
+{
+ guint16 physdev_pid;
+
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), 0);
+
+ /* Prefer the one coming in the properties, if any */
+ physdev_pid = mm_kernel_event_properties_get_physdev_pid (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties);
+ if (!physdev_pid)
+ physdev_pid = MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_pid;
+
+ return physdev_pid;
+}
+
+static gboolean
+kernel_device_is_candidate (MMKernelDevice *_self,
+ gboolean manual_scan)
+{
+ MMKernelDeviceGeneric *self;
+ const gchar *name;
+
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (_self), FALSE);
+
+ self = MM_KERNEL_DEVICE_GENERIC (_self);
+
+ /* If being candidate is forced, do it */
+ if (mm_kernel_event_properties_get_candidate (self->priv->properties)) {
+ mm_dbg ("(%s/%s) device forced to be candidate",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties));
+ return TRUE;
+ }
+
+ name = mm_kernel_event_properties_get_name (self->priv->properties);
+
+ /* ignore VTs */
+ if (strncmp (name, "tty", 3) == 0 && g_ascii_isdigit (name[3])) {
+ mm_dbg ("(%s/%s) VT ignored",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties));
+ return FALSE;
+ }
+
+ /* only ports tagged as candidate */
+ if (!mm_kernel_device_get_property_as_boolean (_self, "ID_MM_CANDIDATE")) {
+ mm_dbg ("(%s/%s) device not flagged with ID_MM_CANDIDATE",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties));
+ return FALSE;
+ }
+
+ /* no devices without physical device */
+ if (!self->priv->physdev_sysfs_path) {
+ mm_dbg ("(%s/%s) device without physdev sysfs path",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties));
+ return FALSE;
+ }
+
+ /* ignore ports explicitly ignored; note that in this case the property
+ * is set in this kernel device itself, unlike in the udev backend, that
+ * goes in the parent udev device */
+ if (mm_kernel_device_get_property_as_boolean (_self, "ID_MM_DEVICE_IGNORE")) {
+ mm_dbg ("(%s/%s) device flagged with ID_MM_DEVICE_IGNORE",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties));
+ return FALSE;
+ }
+
+ mm_dbg ("(%s/%s) device is candidate",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties));
+ return TRUE;
+}
+
+static gboolean
+kernel_device_cmp (MMKernelDevice *a,
+ MMKernelDevice *b)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (a), FALSE);
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (b), FALSE);
+
+ return (!g_strcmp0 (mm_kernel_device_get_subsystem (a), mm_kernel_device_get_subsystem (b)) &&
+ !g_strcmp0 (mm_kernel_device_get_name (a), mm_kernel_device_get_name (b)));
+}
+
+/*****************************************************************************/
+
+static const gchar *expected_rules_prefix[] = { "77-mm-", "80-mm-" };
+
+static GList *
+list_rule_files (void)
+{
+ GFile *udevrulesdir;
+ GFileEnumerator *enumerator;
+ GList *children = NULL;
+
+ udevrulesdir = g_file_new_for_path (UDEVRULESDIR);
+ enumerator = g_file_enumerate_children (udevrulesdir,
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+ if (enumerator) {
+ GFileInfo *info;
+
+ /* If we get any kind of error, assume we need to stop enumerating */
+ while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) {
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (expected_rules_prefix); i++) {
+ if (g_str_has_prefix (g_file_info_get_name (info), expected_rules_prefix[i])) {
+ children = g_list_prepend (children, g_build_path (G_DIR_SEPARATOR_S, UDEVRULESDIR, g_file_info_get_name (info), NULL));
+ break;
+ }
+ }
+ g_object_unref (info);
+ }
+ g_object_unref (enumerator);
+ }
+ g_object_unref (udevrulesdir);
+
+ return g_list_sort (children, (GCompareFunc) g_strcmp0);
+}
+
+static gboolean
+apply_result (MMKernelDeviceGeneric *self,
+ gchar *rule,
+ gchar **goto_label)
+{
+ gchar *aux;
+ gchar *right;
+
+ g_strstrip (rule);
+
+ aux = strchr (rule, '=');
+ if (!aux) {
+ mm_warn ("Invalid rule to apply: missing '='");
+ return FALSE;
+ }
+ right = aux + 1;
+ *aux = '\0';
+
+ if (strlen (right) == 0) {
+ mm_warn ("Invalid rule to apply: missing right operand");
+ return FALSE;
+ }
+
+ if (g_str_equal (rule, "GOTO")) {
+ *goto_label = g_strdup (right);
+ return TRUE;
+ }
+
+ /* Just ignore */
+ if (g_str_equal (rule, "LABEL"))
+ return TRUE;
+
+ if (g_str_has_prefix (rule, "ENV")) {
+ rule = &rule[3];
+ g_strdelimit (rule, "{}", ' ');
+ g_strstrip (rule);
+ if (strlen (rule) == 0) {
+ mm_warn ("Invalid rule to apply: empty property name");
+ return FALSE;
+ }
+
+ g_strdelimit (right, "\"'", ' ');
+ g_strstrip (right);
+ if (strlen (right) == 0) {
+ mm_warn ("Invalid rule to apply: empty property value");
+ return FALSE;
+ }
+
+ mm_dbg ("(%s/%s) rule applied: %s=%s",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties),
+ rule, right);
+ g_object_set_data_full (G_OBJECT (self), rule, g_strdup (right), (GDestroyNotify) g_free);
+ return TRUE;
+ }
+
+ mm_warn ("Invalid rule to apply: unknown action '%s'", rule);
+ return FALSE;
+}
+
+static gboolean
+string_match (const gchar *str,
+ const gchar *original_pattern)
+{
+ gchar *pattern;
+ gchar *start;
+ gboolean open_prefix = FALSE;
+ gboolean open_suffix = FALSE;
+ gboolean match;
+
+ pattern = g_strdup (original_pattern);
+ start = pattern;
+
+ if (start[0] == '*') {
+ open_prefix = TRUE;
+ start++;
+ }
+
+ if (start[strlen (start) - 1] == '*') {
+ open_suffix = TRUE;
+ start[strlen (start) - 1] = '\0';
+ }
+
+ if (open_suffix && !open_prefix)
+ match = g_str_has_prefix (str, start);
+ else if (!open_suffix && open_prefix)
+ match = g_str_has_suffix (str, start);
+ else if (open_suffix && open_prefix)
+ match = !!strstr (str, start);
+ else
+ match = g_str_equal (str, start);
+
+ g_free (pattern);
+ return match;
+}
+
+static gboolean
+validate_condition (MMKernelDeviceGeneric *self,
+ gchar *condition)
+{
+ gboolean condition_equal;
+ gchar *aux;
+ gchar *right;
+ guint val;
+
+ g_strstrip (condition);
+
+ if ((aux = strstr (condition, "==")) != NULL)
+ condition_equal = TRUE;
+ else if ((aux = strstr (condition, "!=")) != NULL)
+ condition_equal = FALSE;
+ else {
+ mm_warn ("Invalid rule to apply: unknown condition: %s", condition);
+ return FALSE;
+ }
+
+ *aux = '\0';
+ right = &aux[2];
+ g_strdelimit (right, "\"'", ' ');
+ g_strstrip (right);
+
+ trace ("condition: '%s', operation: '%s', right: '%s'",
+ condition, condition_equal ? "==" : "!=", right);
+
+ /* We only apply 'add' rules */
+ if (g_str_equal (condition, "ACTION"))
+ return ((!!strstr (right, "add")) == condition_equal);
+
+ /* We look for the subsystem string in the whole sysfs path.
+ *
+ * Note that we're not really making a difference between "SUBSYSTEMS"
+ * (where the whole device tree is checked) and "SUBSYSTEM" (where just one
+ * single device is checked), because a lot of the MM udev rules are meant
+ * to just tag the physical device (e.g. with ID_MM_DEVICE_IGNORE) instead
+ * of the single ports. In our case with the custom parsing, we do tag all
+ * independent ports.
+ */
+ if (g_str_equal (condition, "SUBSYSTEMS") || g_str_equal (condition, "SUBSYSTEM"))
+ return ((self->priv->sysfs_path && !!strstr (self->priv->sysfs_path, right)) == condition_equal);
+
+ /* Exact DRIVER match? We also include the check for DRIVERS, even if we
+ * only apply it to this port driver. */
+ if (g_str_equal (condition, "DRIVER") || g_str_equal (condition, "DRIVERS"))
+ return ((!g_strcmp0 (right, mm_kernel_device_get_driver (MM_KERNEL_DEVICE (self)))) == condition_equal);
+
+ /* Device name checks */
+ if (g_str_equal (condition, "KERNEL"))
+ return (string_match (mm_kernel_device_get_name (MM_KERNEL_DEVICE (self)), right) == condition_equal);
+
+ /* Device sysfs path checks */
+ if (g_str_equal (condition, "DEVPATH"))
+ return (string_match (mm_kernel_device_get_sysfs_path (MM_KERNEL_DEVICE (self)), right) == condition_equal);
+
+ /* Attributes checks */
+ if (g_str_has_prefix (condition, "ATTRS")) {
+ gchar *contents;
+ gboolean result;
+
+ condition = &condition[5];
+ g_strdelimit (condition, "{}", ' ');
+ g_strstrip (condition);
+ g_strdelimit (right, "\"'", ' ');
+ g_strstrip (right);
+
+ /* VID/PID directly from our API */
+ if (g_str_equal (condition, "idVendor"))
+ return ((mm_kernel_device_get_physdev_vid (MM_KERNEL_DEVICE (self)) == mm_get_uint_from_hex_str (right, &val)) == condition_equal);
+ if (g_str_equal (condition, "idProduct"))
+ return ((mm_kernel_device_get_physdev_pid (MM_KERNEL_DEVICE (self)) == mm_get_uint_from_hex_str (right, &val)) == condition_equal);
+
+ /* manufacturer/product in the physdev */
+ if (g_str_equal (condition, "manufacturer") || g_str_equal (condition, "product")) {
+ contents = (self->priv->physdev_sysfs_path ? read_sysfs_property_as_string (self->priv->sysfs_path, condition) : NULL);
+ result = ((contents && g_str_equal (contents, right)) == condition_equal);
+ g_free (contents);
+ return result;
+ }
+
+ /* interface class/subclass/number in the exact device */
+ if (g_str_equal (condition, "bInterfaceClass") ||
+ g_str_equal (condition, "bInterfaceSubClass") ||
+ g_str_equal (condition, "bInterfaceProtocol")) {
+ contents = read_sysfs_property_as_string (self->priv->sysfs_path, condition);
+ result = ((contents && g_str_equal (contents, right)) == condition_equal);
+ g_free (contents);
+ return result;
+ }
+
+ mm_warn ("Invalid ATTRS: %s", condition);
+ return FALSE;
+ }
+
+ mm_warn ("Invalid rule to apply: unknown left operand: %s", condition);
+ return FALSE;
+}
+
+static gboolean
+process_rule (MMKernelDeviceGeneric *self,
+ const gchar *line,
+ gchar **goto_label)
+{
+ gchar **split;
+ guint n_items, i;
+ gboolean applied = FALSE;
+ gboolean validated = TRUE;
+
+ split = g_strsplit (line, ",", -1);
+ n_items = g_strv_length (split);
+ for (i = 0; validated && i < n_items; i++) {
+ gchar *rule;
+
+ rule = g_strdup (split[i]);
+ if (i < (n_items - 1)) {
+ /* If one of the condition fails, break the rule */
+ validated = validate_condition (self, rule);
+ trace ("condition %s: %s", split[i], validated ? "valid" : "invalid");
+ }
+ else {
+ /* Last item, the rule to apply */
+ applied = apply_result (self, rule, goto_label);
+ trace ("result %s: %s", split[i], applied ? "applied" : "not applied");
+ }
+
+ g_free (rule);
+ }
+ g_strfreev (split);
+ return applied;
+}
+
+static void
+preload_properties_from_file (MMKernelDeviceGeneric *self,
+ const gchar *path)
+{
+ GFile *file;
+ GFileInputStream *fistream;
+ GDataInputStream *distream = NULL;
+ GError *error = NULL;
+ gchar *line;
+ gchar *goto_label = NULL;
+
+ file = g_file_new_for_path (path);
+ fistream = g_file_read (file, NULL, &error);
+ if (!fistream)
+ goto out;
+
+ distream = g_data_input_stream_new (G_INPUT_STREAM (fistream));
+
+ while ((line = g_data_input_stream_read_line_utf8 (distream, NULL, NULL, &error)) != NULL) {
+ const gchar *aux;
+
+ if (goto_label) {
+ if (strstr (line, goto_label))
+ g_clear_pointer (&goto_label, g_free);
+ goto next;
+ }
+
+ aux = line;
+ while (*aux == ' ')
+ aux++;
+ if (*aux == '#' || *aux == '\0')
+ goto next;
+
+ process_rule (self, line, &goto_label);
+
+ next:
+ g_free (line);
+ }
+
+ g_clear_pointer (&goto_label, g_free);
+
+out:
+ if (error) {
+ mm_dbg ("(%s/%s) couldn't load properties from: %s: %s",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties),
+ path,
+ error->message);
+ g_error_free (error);
+ }
+
+ if (distream)
+ g_object_unref (distream);
+ if (fistream)
+ g_object_unref (fistream);
+ g_object_unref (file);
+}
+
+static void
+preload_properties (MMKernelDeviceGeneric *self)
+{
+ GList *rules, *l;
+
+ rules = list_rule_files ();
+ for (l = rules; l; l = g_list_next (l)) {
+ trace ("(%s/%s) loading properties from: %s",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties),
+ (const gchar *)(l->data));
+ preload_properties_from_file (self, (const gchar *)(l->data));
+ }
+ g_list_free_full (rules, (GDestroyNotify) g_free);
+}
+
+static gboolean
+kernel_device_has_property (MMKernelDevice *self,
+ const gchar *property)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), FALSE);
+
+ return !!g_object_get_data (G_OBJECT (self), property);
+}
+
+static const gchar *
+kernel_device_get_property (MMKernelDevice *self,
+ const gchar *property)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
+
+ return g_object_get_data (G_OBJECT (self), property);
+}
+
+static gboolean
+kernel_device_get_property_as_boolean (MMKernelDevice *self,
+ const gchar *property)
+{
+ const gchar *value;
+
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), FALSE);
+
+ value = g_object_get_data (G_OBJECT (self), property);
+ return (value && mm_common_get_boolean_from_string (value, NULL));
+}
+
+static gint
+kernel_device_get_property_as_int (MMKernelDevice *self,
+ const gchar *property)
+{
+ const gchar *value;
+ gint aux = 0;
+
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), -1);
+
+ value = g_object_get_data (G_OBJECT (self), property);
+ return ((value && mm_get_int_from_str (value, &aux)) ? aux : 0);
+}
+
+/*****************************************************************************/
+
+MMKernelDevice *
+mm_kernel_device_generic_new (MMKernelEventProperties *properties,
+ GError **error)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (properties), NULL);
+
+ return MM_KERNEL_DEVICE (g_initable_new (MM_TYPE_KERNEL_DEVICE_GENERIC,
+ NULL,
+ error,
+ "properties", properties,
+ NULL));
+}
+
+/*****************************************************************************/
+
+static void
+mm_kernel_device_generic_init (MMKernelDeviceGeneric *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_KERNEL_DEVICE_GENERIC, MMKernelDeviceGenericPrivate);
+}
+
+static void
+set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (object);
+
+ switch (prop_id) {
+ case PROP_PROPERTIES:
+ g_assert (!self->priv->properties);
+ self->priv->properties = g_value_dup_object (value);
+ /* Don't preload on "remove" actions, where we don't have the device any more,
+ * or for devices in the 'virtual' subsystem */
+ if (self->priv->properties &&
+ g_strcmp0 (mm_kernel_event_properties_get_action (self->priv->properties), "remove") &&
+ g_strcmp0 (mm_kernel_event_properties_get_subsystem (self->priv->properties), "virtual")) {
+ preload_contents (self);
+ preload_properties (self);
+ }
+ 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)
+{
+ MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (object);
+
+ switch (prop_id) {
+ case PROP_PROPERTIES:
+ g_value_set_object (value, self->priv->properties);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (initable);
+
+ if (!mm_kernel_device_get_subsystem (MM_KERNEL_DEVICE (self))) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "subsystem is mandatory in kernel device");
+ return FALSE;
+ }
+
+ if (!mm_kernel_device_get_name (MM_KERNEL_DEVICE (self))) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "name is mandatory in kernel device");
+ return FALSE;
+ }
+
+ /* sysfs path is mandatory as output, and will only be given if the
+ * specified device exists; but only if this wasn't a 'remove' event
+ * and not a virtual device.
+ */
+ if (self->priv->properties &&
+ g_strcmp0 (mm_kernel_event_properties_get_action (self->priv->properties), "remove") &&
+ g_strcmp0 (mm_kernel_event_properties_get_subsystem (self->priv->properties), "virtual") &&
+ !self->priv->sysfs_path) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "device %s/%s not found",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+dispose (GObject *object)
+{
+ MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (object);
+
+ g_clear_pointer (&self->priv->physdev_sysfs_path, g_free);
+ g_clear_pointer (&self->priv->parent_sysfs_path, g_free);
+ g_clear_pointer (&self->priv->sysfs_path, g_free);
+ g_clear_object (&self->priv->properties);
+
+ G_OBJECT_CLASS (mm_kernel_device_generic_parent_class)->dispose (object);
+}
+
+static void
+initable_iface_init (GInitableIface *iface)
+{
+ iface->init = initable_init;
+}
+
+static void
+mm_kernel_device_generic_class_init (MMKernelDeviceGenericClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMKernelDeviceClass *kernel_device_class = MM_KERNEL_DEVICE_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMKernelDeviceGenericPrivate));
+
+ object_class->dispose = dispose;
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+
+ kernel_device_class->get_subsystem = kernel_device_get_subsystem;
+ kernel_device_class->get_name = kernel_device_get_name;
+ kernel_device_class->get_driver = kernel_device_get_driver;
+ kernel_device_class->get_sysfs_path = kernel_device_get_sysfs_path;
+ kernel_device_class->get_physdev_uid = kernel_device_get_physdev_uid;
+ kernel_device_class->get_physdev_vid = kernel_device_get_physdev_vid;
+ kernel_device_class->get_physdev_pid = kernel_device_get_physdev_pid;
+ kernel_device_class->get_parent_sysfs_path = kernel_device_get_parent_sysfs_path;
+ kernel_device_class->is_candidate = kernel_device_is_candidate;
+ kernel_device_class->cmp = kernel_device_cmp;
+ kernel_device_class->has_property = kernel_device_has_property;
+ kernel_device_class->get_property = kernel_device_get_property;
+ kernel_device_class->get_property_as_boolean = kernel_device_get_property_as_boolean;
+ kernel_device_class->get_property_as_int = kernel_device_get_property_as_int;
+
+ properties[PROP_PROPERTIES] =
+ g_param_spec_object ("properties",
+ "Properties",
+ "Generic kernel event properties",
+ MM_TYPE_KERNEL_EVENT_PROPERTIES,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property (object_class, PROP_PROPERTIES, properties[PROP_PROPERTIES]);
+}
diff --git a/src/kerneldevice/mm-kernel-device-generic.h b/src/kerneldevice/mm-kernel-device-generic.h
new file mode 100644
index 0000000..87f9994
--- /dev/null
+++ b/src/kerneldevice/mm-kernel-device-generic.h
@@ -0,0 +1,51 @@
+/* -*- 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) 2016 Velocloud, Inc.
+ */
+
+#ifndef MM_KERNEL_DEVICE_GENERIC_H
+#define MM_KERNEL_DEVICE_GENERIC_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-kernel-device.h"
+
+#define MM_TYPE_KERNEL_DEVICE_GENERIC (mm_kernel_device_generic_get_type ())
+#define MM_KERNEL_DEVICE_GENERIC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_KERNEL_DEVICE_GENERIC, MMKernelDeviceGeneric))
+#define MM_KERNEL_DEVICE_GENERIC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_KERNEL_DEVICE_GENERIC, MMKernelDeviceGenericClass))
+#define MM_IS_KERNEL_DEVICE_GENERIC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_KERNEL_DEVICE_GENERIC))
+#define MM_IS_KERNEL_DEVICE_GENERIC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_KERNEL_DEVICE_GENERIC))
+#define MM_KERNEL_DEVICE_GENERIC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_KERNEL_DEVICE_GENERIC, MMKernelDeviceGenericClass))
+
+typedef struct _MMKernelDeviceGeneric MMKernelDeviceGeneric;
+typedef struct _MMKernelDeviceGenericClass MMKernelDeviceGenericClass;
+typedef struct _MMKernelDeviceGenericPrivate MMKernelDeviceGenericPrivate;
+
+struct _MMKernelDeviceGeneric {
+ MMKernelDevice parent;
+ MMKernelDeviceGenericPrivate *priv;
+};
+
+struct _MMKernelDeviceGenericClass {
+ MMKernelDeviceClass parent;
+};
+
+GType mm_kernel_device_generic_get_type (void);
+MMKernelDevice *mm_kernel_device_generic_new (MMKernelEventProperties *properties,
+ GError **error);
+
+#endif /* MM_KERNEL_DEVICE_GENERIC_H */
diff --git a/src/mm-base-manager.c b/src/mm-base-manager.c
index 85f66a8..991b7e5 100644
--- a/src/mm-base-manager.c
+++ b/src/mm-base-manager.c
@@ -26,6 +26,8 @@
#if WITH_UDEV
# include "mm-kernel-device-udev.h"
+#else
+# include "mm-kernel-device-generic.h"
#endif
#include <ModemManager.h>
@@ -246,6 +248,11 @@ device_added (MMBaseManager *manager,
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.
@@ -275,6 +282,11 @@ device_added (MMBaseManager *manager,
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,
@@ -290,7 +302,11 @@ device_added (MMBaseManager *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);
@@ -425,9 +441,6 @@ mm_base_manager_start (MMBaseManager *manager,
}
#else
mm_dbg ("Unsupported %s device scan...", manual_scan ? "manual" : "automatic");
-
- /* Make compiler happy */
- device_added (manager, NULL, FALSE, FALSE);
#endif
}
@@ -642,6 +655,128 @@ handle_scan_devices (MmGdbusOrgFreedesktopModemManager1 *manager,
}
/*****************************************************************************/
+
+#if !WITH_UDEV
+
+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;
+ MMKernelDevice *kernel_device;
+ const gchar *action;
+
+ if (!mm_auth_provider_authorize_finish (authp, res, &error))
+ goto out;
+
+ properties = mm_kernel_event_properties_new_from_dictionary (ctx->dictionary, &error);
+ if (!properties)
+ goto out;
+
+ action = mm_kernel_event_properties_get_action (properties);
+ if (g_strcmp0 (action, "add") != 0 && g_strcmp0 (action, "remove") != 0) {
+ error = g_error_new_literal (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Invalid action");
+ goto out;
+ }
+
+ mm_dbg ("Kernel event reported:");
+ mm_dbg (" action: %s", action);
+ if (mm_kernel_event_properties_get_subsystem (properties))
+ mm_dbg (" subsystem: %s", mm_kernel_event_properties_get_subsystem (properties));
+ if (mm_kernel_event_properties_get_name (properties))
+ mm_dbg (" name: %s", mm_kernel_event_properties_get_name (properties));
+ if (mm_kernel_event_properties_get_driver (properties))
+ mm_dbg (" driver: %s", mm_kernel_event_properties_get_driver (properties));
+ if (mm_kernel_event_properties_get_physdev_uid (properties))
+ mm_dbg (" physdev-uid: %s", mm_kernel_event_properties_get_physdev_uid (properties));
+ if (mm_kernel_event_properties_get_physdev_vid (properties))
+ mm_dbg (" physdev-vid: 0x%04X", mm_kernel_event_properties_get_physdev_vid (properties));
+ if (mm_kernel_event_properties_get_physdev_pid (properties))
+ mm_dbg (" physdev-pid: 0x%04X", mm_kernel_event_properties_get_physdev_pid (properties));
+ if (mm_kernel_event_properties_get_candidate (properties))
+ mm_dbg (" candidate: yes");
+
+ kernel_device = mm_kernel_device_generic_new (properties, &error);
+ if (!kernel_device)
+ goto out;
+
+ if (g_strcmp0 (action, "add") == 0)
+ device_added (ctx->self, kernel_device, TRUE, TRUE);
+ else if (g_strcmp0 (action, "remove") == 0)
+ device_removed (ctx->self, kernel_device);
+ else
+ g_assert_not_reached ();
+ g_object_unref (kernel_device);
+
+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;
+}
+
+#else
+
+static gboolean
+handle_report_kernel_event (MmGdbusOrgFreedesktopModemManager1 *manager,
+ GDBusMethodInvocation *invocation,
+ GVariant *dictionary)
+{
+ g_dbus_method_invocation_return_error (invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot report kernel event: "
+ "udev monitoring already in place");
+ return TRUE;
+}
+
+#endif
+
+/*****************************************************************************/
/* Test profile setup */
static gboolean
@@ -844,6 +979,10 @@ mm_base_manager_init (MMBaseManager *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
--
2.9.0
More information about the ModemManager-devel
mailing list