aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--cli/Makefile.am7
-rw-r--r--cli/mmcli-manager.c31
-rw-r--r--configure.ac30
-rw-r--r--libmm-glib/mm-common-helpers.c43
-rw-r--r--libmm-glib/mm-common-helpers.h2
-rw-r--r--plugins/Makefile.am38
-rw-r--r--plugins/tests/test-udev-rules.c164
-rw-r--r--src/Makefile.am15
-rw-r--r--src/kerneldevice/mm-kernel-device-generic-rules.c446
-rw-r--r--src/kerneldevice/mm-kernel-device-generic-rules.h62
-rw-r--r--src/kerneldevice/mm-kernel-device-generic.c772
-rw-r--r--src/kerneldevice/mm-kernel-device-generic.h9
-rw-r--r--src/mm-base-manager.c50
-rw-r--r--src/mm-context.c27
-rw-r--r--src/tests/Makefile.am5
-rw-r--r--src/tests/test-udev-rules.c79
-rw-r--r--test/Makefile.am29
-rw-r--r--test/mmrules.c173
19 files changed, 1942 insertions, 43 deletions
diff --git a/.gitignore b/.gitignore
index 36fe6c96..123ce208 100644
--- a/.gitignore
+++ b/.gitignore
@@ -56,6 +56,7 @@ Makefile.in
/src/tests/test-at-serial-port
/src/tests/test-sms-part-3gpp
/src/tests/test-sms-part-cdma
+/src/tests/test-udev-rules
/cli/mmcli
@@ -158,6 +159,7 @@ Makefile.in
/uml290/uml290mode
/plugins/test-suite.log
+/plugins/test-udev-rules
/plugins/test-modem-helpers-huawei*
/plugins/test-modem-helpers-altair*
/plugins/test-modem-helpers-cinterion*
@@ -169,6 +171,7 @@ Makefile.in
/test/lsudev
/test/mmtty
+/test/mmrules
/ModemManager-*-coverage.info
/ModemManager-*-coverage/
diff --git a/cli/Makefile.am b/cli/Makefile.am
index 7915f289..6c2d7912 100644
--- a/cli/Makefile.am
+++ b/cli/Makefile.am
@@ -2,7 +2,6 @@ bin_PROGRAMS = mmcli
mmcli_CPPFLAGS = \
$(MMCLI_CFLAGS) \
- $(GUDEV_CFLAGS) \
-I$(top_srcdir) \
-I$(top_srcdir)/include \
-I$(top_builddir)/include \
@@ -35,11 +34,15 @@ mmcli_SOURCES = \
$(NULL)
mmcli_LDADD = \
- $(GUDEV_LIBS) \
$(MMCLI_LIBS) \
$(top_builddir)/libmm-glib/libmm-glib.la \
$(NULL)
+if WITH_UDEV
+mmcli_CPPFLAGS += $(GUDEV_CFLAGS)
+mmcli_LDADD += $(GUDEV_LIBS)
+endif
+
completiondir = $(datadir)/bash-completion/completions
install-data-hook:
diff --git a/cli/mmcli-manager.c b/cli/mmcli-manager.c
index 4e078f17..24ecc4eb 100644
--- a/cli/mmcli-manager.c
+++ b/cli/mmcli-manager.c
@@ -29,7 +29,9 @@
#include <glib.h>
#include <gio/gio.h>
-#include <gudev/gudev.h>
+#if WITH_UDEV
+# include <gudev/gudev.h>
+#endif
#define _LIBMM_INSIDE_MMCLI
#include "libmm-glib.h"
@@ -41,7 +43,9 @@
typedef struct {
MMManager *manager;
GCancellable *cancellable;
+#if WITH_UDEV
GUdevClient *udev;
+#endif
} Context;
static Context *ctx;
@@ -51,7 +55,10 @@ static gboolean monitor_modems_flag;
static gboolean scan_modems_flag;
static gchar *set_logging_str;
static gchar *report_kernel_event_str;
+
+#if WITH_UDEV
static gboolean report_kernel_event_auto_scan;
+#endif
static GOptionEntry entries[] = {
{ "set-logging", 'G', 0, G_OPTION_ARG_STRING, &set_logging_str,
@@ -74,10 +81,12 @@ static GOptionEntry entries[] = {
"Report kernel event",
"[\"key=value,...\"]"
},
+#if WITH_UDEV
{ "report-kernel-event-auto-scan", 0, 0, G_OPTION_ARG_NONE, &report_kernel_event_auto_scan,
"Automatically report kernel events based on udev notifications",
NULL
},
+#endif
{ NULL }
};
@@ -110,8 +119,11 @@ mmcli_manager_options_enabled (void)
monitor_modems_flag +
scan_modems_flag +
!!set_logging_str +
- !!report_kernel_event_str +
- report_kernel_event_auto_scan);
+ !!report_kernel_event_str);
+
+#if WITH_UDEV
+ n_actions += report_kernel_event_auto_scan;
+#endif
if (n_actions > 1) {
g_printerr ("error: too many manager actions requested\n");
@@ -121,8 +133,10 @@ mmcli_manager_options_enabled (void)
if (monitor_modems_flag)
mmcli_force_async_operation ();
+#if WITH_UDEV
if (report_kernel_event_auto_scan)
mmcli_force_async_operation ();
+#endif
checked = TRUE;
return !!n_actions;
@@ -134,8 +148,11 @@ context_free (Context *ctx)
if (!ctx)
return;
+#if WITH_UDEV
if (ctx->udev)
g_object_unref (ctx->udev);
+#endif
+
if (ctx->manager)
g_object_unref (ctx->manager);
if (ctx->cancellable)
@@ -308,6 +325,8 @@ cancelled (GCancellable *cancellable)
mmcli_async_operation_done ();
}
+#if WITH_UDEV
+
static void
handle_uevent (GUdevClient *client,
const char *action,
@@ -324,6 +343,8 @@ handle_uevent (GUdevClient *client,
g_object_unref (properties);
}
+#endif
+
static void
get_manager_ready (GObject *source,
GAsyncResult *result,
@@ -367,6 +388,7 @@ get_manager_ready (GObject *source,
return;
}
+#if WITH_UDEV
if (report_kernel_event_auto_scan) {
const gchar *subsys[] = { "tty", "usbmisc", "net", NULL };
guint i;
@@ -400,6 +422,7 @@ get_manager_ready (GObject *source,
NULL);
return;
}
+#endif
/* Request to monitor modems? */
if (monitor_modems_flag) {
@@ -457,10 +480,12 @@ mmcli_manager_run_synchronous (GDBusConnection *connection)
exit (EXIT_FAILURE);
}
+#if WITH_UDEV
if (report_kernel_event_auto_scan) {
g_printerr ("error: monitoring udev events cannot be done synchronously\n");
exit (EXIT_FAILURE);
}
+#endif
/* Initialize context */
ctx = g_new0 (Context, 1);
diff --git a/configure.ac b/configure.ac
index dd3111d7..c74d0153 100644
--- a/configure.ac
+++ b/configure.ac
@@ -95,7 +95,6 @@ dnl Build dependencies
dnl
GLIB_MIN_VERSION=2.36.0
-GUDEV_MIN_VERSION=147
PKG_CHECK_MODULES(MM,
glib-2.0 >= $GLIB_MIN_VERSION
@@ -121,10 +120,6 @@ PKG_CHECK_MODULES(MMCLI,
AC_SUBST(MMCLI_CFLAGS)
AC_SUBST(MMCLI_LIBS)
-PKG_CHECK_MODULES(GUDEV, gudev-1.0 >= $GUDEV_MIN_VERSION)
-AC_SUBST(GUDEV_CFLAGS)
-AC_SUBST(GUDEV_LIBS)
-
dnl Some required utilities
GLIB_MKENUMS=`$PKG_CONFIG --variable=glib_mkenums glib-2.0`
AC_SUBST(GLIB_MKENUMS)
@@ -183,6 +178,30 @@ fi
AM_CONDITIONAL(HAVE_SYSTEMD, [test -n "$SYSTEMD_UNIT_DIR" -a "$SYSTEMD_UNIT_DIR" != xno ])
dnl-----------------------------------------------------------------------------
+dnl udev support (enabled by default)
+dnl
+
+GUDEV_VERSION=147
+
+AC_ARG_WITH(udev, AS_HELP_STRING([--without-udev], [Build without udev support]), [], [with_udev=yes])
+AM_CONDITIONAL(WITH_UDEV, test "x$with_udev" = "xyes")
+case $with_udev in
+ yes)
+ PKG_CHECK_MODULES(GUDEV, [gudev-1.0 >= $GUDEV_VERSION], [have_gudev=yes],[have_gudev=no])
+ if test "x$have_gudev" = "xno"; then
+ AC_MSG_ERROR([Couldn't find gudev >= $GUDEV_VERSION. Install it, or otherwise configure using --without-udev to disable udev support.])
+ else
+ AC_DEFINE(WITH_UDEV, 1, [Define if you want udev support])
+ AC_SUBST(GUDEV_CFLAGS)
+ AC_SUBST(GUDEV_LIBS)
+ fi
+ ;;
+ *)
+ with_udev=no
+ ;;
+esac
+
+dnl-----------------------------------------------------------------------------
dnl Suspend/resume support
dnl
@@ -396,6 +415,7 @@ echo "
systemd unit directory: ${with_systemdsystemunitdir}
Features:
+ udev support: ${with_udev}
policykit support: ${with_polkit}
mbim support: ${with_mbim}
qmi support: ${with_qmi}
diff --git a/libmm-glib/mm-common-helpers.c b/libmm-glib/mm-common-helpers.c
index 4b784da0..3a5883b2 100644
--- a/libmm-glib/mm-common-helpers.c
+++ b/libmm-glib/mm-common-helpers.c
@@ -1386,6 +1386,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 c65d3b98..ab5777d1 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/plugins/Makefile.am b/plugins/Makefile.am
index 435d0ed2..2dcde280 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -306,6 +306,8 @@ libmm_plugin_huawei_la_LIBADD = $(builddir)/libhelpers-huawei.la
dist_udevrules_DATA += huawei/77-mm-huawei-net-port-types.rules
+AM_CFLAGS += -DTESTUDEVRULESDIR_HUAWEI=\"${srcdir}/huawei\"
+
################################################################################
# plugin: ericsson mbm
################################################################################
@@ -321,6 +323,8 @@ libmm_plugin_ericsson_mbm_la_LIBADD = $(MBM_COMMON_LIBADD_FLAGS)
dist_udevrules_DATA += mbm/77-mm-ericsson-mbm.rules
+AM_CFLAGS += -DTESTUDEVRULESDIR_MBM=\"${srcdir}/mbm\"
+
################################################################################
# plugin: option
################################################################################
@@ -423,6 +427,8 @@ libmm_plugin_nokia_icera_la_LIBADD = $(ICERA_COMMON_LIBADD_FLAGS)
dist_udevrules_DATA += nokia/77-mm-nokia-port-types.rules
+AM_CFLAGS += -DTESTUDEVRULESDIR_NOKIA=\"${srcdir}/nokia\"
+
################################################################################
# plugin: zte
################################################################################
@@ -444,6 +450,8 @@ libmm_plugin_zte_la_LIBADD = $(ICERA_COMMON_LIBADD_FLAGS)
dist_udevrules_DATA += zte/77-mm-zte-port-types.rules
+AM_CFLAGS += -DTESTUDEVRULESDIR_ZTE=\"${srcdir}/zte\"
+
################################################################################
# plugin: longcheer (and rebranded dongles)
################################################################################
@@ -460,6 +468,8 @@ libmm_plugin_longcheer_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
dist_udevrules_DATA += longcheer/77-mm-longcheer-port-types.rules
+AM_CFLAGS += -DTESTUDEVRULESDIR_LONGCHEER=\"${srcdir}/longcheer\"
+
################################################################################
# plugin: anydata cdma
################################################################################
@@ -504,6 +514,8 @@ libmm_plugin_simtech_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
dist_udevrules_DATA += simtech/77-mm-simtech-port-types.rules
+AM_CFLAGS += -DTESTUDEVRULESDIR_SIMTECH=\"${srcdir}/simtech\"
+
################################################################################
# plugin: alcatel/TCT/JRD x220D and possibly others
################################################################################
@@ -520,6 +532,8 @@ libmm_plugin_x22x_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
dist_udevrules_DATA += x22x/77-mm-x22x-port-types.rules
+AM_CFLAGS += -DTESTUDEVRULESDIR_X22X=\"${srcdir}/x22x\"
+
################################################################################
# plugin: pantech
################################################################################
@@ -595,6 +609,8 @@ libmm_plugin_cinterion_la_LIBADD = $(builddir)/libhelpers-cinterion.la
dist_udevrules_DATA += cinterion/77-mm-cinterion-port-types.rules
+AM_CFLAGS += -DTESTUDEVRULESDIR_CINTERION=\"${srcdir}/cinterion\"
+
################################################################################
# plugin: iridium
################################################################################
@@ -694,6 +710,8 @@ libmm_plugin_dell_la_LIBADD = $(NOVATEL_COMMON_LIBADD_FLAGS) $(SIERRA_COMMON_L
dist_udevrules_DATA += dell/77-mm-dell-port-types.rules
+AM_CFLAGS += -DTESTUDEVRULESDIR_DELL=\"${srcdir}/dell\"
+
################################################################################
# plugin: altair lte
################################################################################
@@ -778,6 +796,8 @@ libmm_plugin_telit_la_LIBADD = $(builddir)/libhelpers-telit.la $(TELIT_COMMON_
dist_udevrules_DATA += telit/77-mm-telit-port-types.rules
+AM_CFLAGS += -DTESTUDEVRULESDIR_TELIT=\"${srcdir}/telit\"
+
################################################################################
# plugin: mtk
################################################################################
@@ -794,6 +814,8 @@ libmm_plugin_mtk_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
dist_udevrules_DATA += mtk/77-mm-mtk-port-types.rules
+AM_CFLAGS += -DTESTUDEVRULESDIR_MTK=\"${srcdir}/mtk\"
+
################################################################################
# plugin: haier
################################################################################
@@ -808,6 +830,22 @@ libmm_plugin_haier_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
dist_udevrules_DATA += haier/77-mm-haier-port-types.rules
+AM_CFLAGS += -DTESTUDEVRULESDIR_HAIER=\"${srcdir}/haier\"
+
+################################################################################
+# udev rules tester
+################################################################################
+
+noinst_PROGRAMS += test-udev-rules
+test_udev_rules_SOURCES = \
+ tests/test-udev-rules.c \
+ $(NULL)
+test_udev_rules_LDADD = \
+ $(top_builddir)/src/libkerneldevice.la \
+ $(top_builddir)/libmm-glib/libmm-glib.la \
+ $(NULL)
+
+
################################################################################
TEST_PROGS += $(noinst_PROGRAMS)
diff --git a/plugins/tests/test-udev-rules.c b/plugins/tests/test-udev-rules.c
new file mode 100644
index 00000000..369e22bb
--- /dev/null
+++ b/plugins/tests/test-udev-rules.c
@@ -0,0 +1,164 @@
+/* -*- 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 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+#include <string.h>
+#include <stdio.h>
+#include <locale.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+/* Define symbol to enable test message traces */
+#undef ENABLE_TEST_MESSAGE_TRACES
+
+#include "mm-kernel-device-generic-rules.h"
+#include "mm-log.h"
+
+/************************************************************/
+
+static void
+common_test (const gchar *plugindir)
+{
+ GArray *rules;
+ GError *error = NULL;
+
+ rules = mm_kernel_device_generic_rules_load (plugindir, &error);
+ g_assert_no_error (error);
+ g_assert (rules);
+ g_assert (rules->len > 0);
+
+ g_array_unref (rules);
+}
+
+/************************************************************/
+
+static void
+test_huawei (void)
+{
+ common_test (TESTUDEVRULESDIR_HUAWEI);
+}
+
+static void
+test_mbm (void)
+{
+ common_test (TESTUDEVRULESDIR_MBM);
+}
+
+static void
+test_nokia (void)
+{
+ common_test (TESTUDEVRULESDIR_NOKIA);
+}
+
+static void
+test_zte (void)
+{
+ common_test (TESTUDEVRULESDIR_ZTE);
+}
+
+static void
+test_longcheer (void)
+{
+ common_test (TESTUDEVRULESDIR_LONGCHEER);
+}
+
+static void
+test_simtech (void)
+{
+ common_test (TESTUDEVRULESDIR_SIMTECH);
+}
+
+static void
+test_x22x (void)
+{
+ common_test (TESTUDEVRULESDIR_X22X);
+}
+
+static void
+test_cinterion (void)
+{
+ common_test (TESTUDEVRULESDIR_CINTERION);
+}
+
+static void
+test_dell (void)
+{
+ common_test (TESTUDEVRULESDIR_DELL);
+}
+
+static void
+test_telit (void)
+{
+ common_test (TESTUDEVRULESDIR_TELIT);
+}
+
+static void
+test_mtk (void)
+{
+ common_test (TESTUDEVRULESDIR_MTK);
+}
+
+static void
+test_haier (void)
+{
+ common_test (TESTUDEVRULESDIR_HAIER);
+}
+
+/************************************************************/
+
+void
+_mm_log (const char *loc,
+ const char *func,
+ guint32 level,
+ const char *fmt,
+ ...)
+{
+#if defined ENABLE_TEST_MESSAGE_TRACES
+ /* Dummy log function */
+ va_list args;
+ gchar *msg;
+
+ va_start (args, fmt);
+ msg = g_strdup_vprintf (fmt, args);
+ va_end (args);
+ g_print ("%s\n", msg);
+ g_free (msg);
+#endif
+}
+
+int main (int argc, char **argv)
+{
+ setlocale (LC_ALL, "");
+
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/MM/test-udev-rules/huawei", test_huawei);
+ g_test_add_func ("/MM/test-udev-rules/mbm", test_mbm);
+ g_test_add_func ("/MM/test-udev-rules/nokia", test_nokia);
+ g_test_add_func ("/MM/test-udev-rules/zte", test_zte);
+ g_test_add_func ("/MM/test-udev-rules/longcheer", test_longcheer);
+ g_test_add_func ("/MM/test-udev-rules/simtech", test_simtech);
+ g_test_add_func ("/MM/test-udev-rules/x22x", test_x22x);
+ g_test_add_func ("/MM/test-udev-rules/cinterion", test_cinterion);
+ g_test_add_func ("/MM/test-udev-rules/dell", test_dell);
+ g_test_add_func ("/MM/test-udev-rules/telit", test_telit);
+ g_test_add_func ("/MM/test-udev-rules/mtk", test_mtk);
+ g_test_add_func ("/MM/test-udev-rules/haier", test_haier);
+
+ return g_test_run ();
+}
diff --git a/src/Makefile.am b/src/Makefile.am
index b1f1a401..91b4e17a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -102,14 +102,29 @@ endif
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 \
+ kerneldevice/mm-kernel-device-generic-rules.h \
+ kerneldevice/mm-kernel-device-generic-rules.c \
+ $(NULL)
+
+if WITH_UDEV
+libkerneldevice_la_SOURCES += \
kerneldevice/mm-kernel-device-udev.h \
kerneldevice/mm-kernel-device-udev.c \
$(NULL)
+endif
+
+libkerneldevice_la_LIBADD = \
+ $(top_builddir)/libmm-glib/libmm-glib.la \
+ $(NULL)
################################################################################
# ports library
diff --git a/src/kerneldevice/mm-kernel-device-generic-rules.c b/src/kerneldevice/mm-kernel-device-generic-rules.c
new file mode 100644
index 00000000..608ca9f1
--- /dev/null
+++ b/src/kerneldevice/mm-kernel-device-generic-rules.c
@@ -0,0 +1,446 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log.h"
+#include "mm-kernel-device-generic-rules.h"
+
+static void
+udev_rule_match_clear (MMUdevRuleMatch *rule_match)
+{
+ g_free (rule_match->parameter);
+ g_free (rule_match->value);
+}
+
+static void
+udev_rule_clear (MMUdevRule *rule)
+{
+ switch (rule->result.type) {
+ case MM_UDEV_RULE_RESULT_TYPE_PROPERTY:
+ g_free (rule->result.content.property.name);
+ g_free (rule->result.content.property.value);
+ break;
+ case MM_UDEV_RULE_RESULT_TYPE_GOTO_TAG:
+ case MM_UDEV_RULE_RESULT_TYPE_LABEL:
+ g_free (rule->result.content.tag);
+ break;
+ case MM_UDEV_RULE_RESULT_TYPE_GOTO_INDEX:
+ case MM_UDEV_RULE_RESULT_TYPE_UNKNOWN:
+ break;
+ }
+
+ if (rule->conditions)
+ g_array_unref (rule->conditions);
+}
+
+static gboolean
+split_item (const gchar *item,
+ gchar **out_left,
+ gchar **out_operator,
+ gchar **out_right,
+ GError **error)
+{
+ const gchar *aux;
+ gchar *left = NULL;
+ gchar *operator = NULL;
+ gchar *right = NULL;
+ GError *inner_error = NULL;
+
+ g_assert (item && out_left && out_operator && out_right);
+
+ /* Get left/operator/right */
+ if (((aux = strstr (item, "==")) != NULL) || ((aux = strstr (item, "!=")) != NULL)) {
+ operator = g_strndup (aux, 2);
+ right = g_strdup (aux + 2);
+ } else if ((aux = strstr (item, "=")) != NULL) {
+ operator = g_strndup (aux, 1);
+ right = g_strdup (aux + 1);
+ } else {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Invalid rule item, missing operator: '%s'", item);
+ goto out;
+ }
+
+ left = g_strndup (item, (aux - item));
+ g_strstrip (left);
+ if (!left[0]) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Invalid rule item, missing left field: '%s'", item);
+ goto out;
+ }
+
+ g_strdelimit (right, "\"", ' ');
+ g_strstrip (right);
+ if (!right[0]) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Invalid rule item, missing right field: '%s'", item);
+ goto out;
+ }
+
+out:
+ if (inner_error) {
+ g_free (left);
+ g_free (operator);
+ g_free (right);
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ *out_left = left;
+ *out_operator = operator;
+ *out_right = right;
+ return TRUE;
+}
+
+static gboolean
+load_rule_result (MMUdevRuleResult *rule_result,
+ const gchar *item,
+ GError **error)
+{
+ gchar *left;
+ gchar *operator;
+ gchar *right;
+ GError *inner_error = NULL;
+ gsize left_len;
+
+ if (!split_item (item, &left, &operator, &right, error))
+ return FALSE;
+
+ if (!g_str_equal (operator, "=")) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Invalid rule result operator: '%s'", item);
+ goto out;
+ }
+
+ if (g_str_equal (left, "LABEL")) {
+ rule_result->type = MM_UDEV_RULE_RESULT_TYPE_LABEL;
+ rule_result->content.tag = right;
+ right = NULL;
+ goto out;
+ }
+
+ if (g_str_equal (left, "GOTO")) {
+ rule_result->type = MM_UDEV_RULE_RESULT_TYPE_GOTO_TAG;
+ rule_result->content.tag = right;
+ right = NULL;
+ goto out;
+ }
+
+ left_len = strlen (left);
+ if (g_str_has_prefix (left, "ENV{") && left[left_len - 1] == '}') {
+ rule_result->type = MM_UDEV_RULE_RESULT_TYPE_PROPERTY;
+ rule_result->content.property.name = g_strndup (left + 4, left_len - 5);
+ rule_result->content.property.value = right;
+ right = NULL;
+ goto out;
+ }
+
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Invalid rule result parameter: '%s'", item);
+
+out:
+ g_free (left);
+ g_free (operator);
+ g_free (right);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+load_rule_match (MMUdevRuleMatch *rule_match,
+ const gchar *item,
+ GError **error)
+{
+ gchar *left;
+ gchar *operator;
+ gchar *right;
+
+ if (!split_item (item, &left, &operator, &right, error))
+ return FALSE;
+
+ if (g_str_equal (operator, "=="))
+ rule_match->type = MM_UDEV_RULE_MATCH_TYPE_EQUAL;
+ else if (g_str_equal (operator, "!="))
+ rule_match->type = MM_UDEV_RULE_MATCH_TYPE_NOT_EQUAL;
+ else {
+ g_free (left);
+ g_free (operator);
+ g_free (right);
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Invalid rule match, wrong match type: '%s'", item);
+
+ return FALSE;
+ }
+
+ g_free (operator);
+ rule_match->parameter = left;
+ rule_match->value = right;
+ return TRUE;
+}
+
+static gboolean
+load_rule_from_line (MMUdevRule *rule,
+ const gchar *line,
+ GError **error)
+{
+ gchar **split;
+ guint n_items;
+ GError *inner_error = NULL;
+
+ split = g_strsplit (line, ",", -1);
+ n_items = g_strv_length (split);
+
+ /* Conditions */
+ if (n_items > 1) {
+ guint i;
+
+ rule->conditions = g_array_sized_new (FALSE, FALSE, sizeof (MMUdevRuleMatch), n_items - 1);
+ g_array_set_clear_func (rule->conditions, (GDestroyNotify) udev_rule_match_clear);
+
+ /* All items except for the last one are conditions */
+ for (i = 0; !inner_error && i < (n_items - 1); i++) {
+ MMUdevRuleMatch rule_match = { 0 };
+
+ /* If condition correctly preloaded, add it to the rule */
+ if (!load_rule_match (&rule_match, split[i], &inner_error))
+ goto out;
+ g_assert (rule_match.type != MM_UDEV_RULE_MATCH_TYPE_UNKNOWN);
+ g_assert (rule_match.parameter);
+ g_assert (rule_match.value);
+ g_array_append_val (rule->conditions, rule_match);
+ }
+ }
+
+ /* Last item, the result */
+ if (!load_rule_result (&rule->result, split[n_items - 1], &inner_error))
+ goto out;
+
+ g_assert ((rule->result.type == MM_UDEV_RULE_RESULT_TYPE_GOTO_TAG && rule->result.content.tag) ||
+ (rule->result.type == MM_UDEV_RULE_RESULT_TYPE_LABEL && rule->result.content.tag) ||
+ (rule->result.type == MM_UDEV_RULE_RESULT_TYPE_PROPERTY && rule->result.content.property.name && rule->result.content.property.value));
+
+out:
+ g_strfreev (split);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gboolean
+process_goto_tags (GArray *rules,
+ guint first_rule_index,
+ GError **error)
+{
+ guint i;
+
+ for (i = first_rule_index; i < rules->len; i++) {
+ MMUdevRule *rule;
+
+ rule = &g_array_index (rules, MMUdevRule, i);
+ if (rule->result.type == MM_UDEV_RULE_RESULT_TYPE_GOTO_TAG) {
+ guint j;
+ guint label_index = 0;
+
+ for (j = i + 1; j < rules->len; j++) {
+ MMUdevRule *walker;
+
+ walker = &g_array_index (rules, MMUdevRule, j);
+ if (walker->result.type == MM_UDEV_RULE_RESULT_TYPE_LABEL &&
+ g_str_equal (rule->result.content.tag, walker->result.content.tag)) {
+
+ if (label_index) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "More than one label '%s' found", rule->result.content.tag);
+ return FALSE;
+ }
+
+ label_index = j;
+ }
+ }
+
+ if (!label_index) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't find label '%s'", rule->result.content.tag);
+ return FALSE;
+ }
+
+ rule->result.type = MM_UDEV_RULE_RESULT_TYPE_GOTO_INDEX;
+ g_free (rule->result.content.tag);
+ rule->result.content.index = label_index;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+load_rules_from_file (GArray *rules,
+ const gchar *path,
+ GError **error)
+{
+ GFile *file;
+ GFileInputStream *fistream;
+ GDataInputStream *distream = NULL;
+ GError *inner_error = NULL;
+ gchar *line;
+ guint first_rule_index;
+
+ mm_dbg ("[rules] loading rules from: %s", path);
+ first_rule_index = rules->len;
+
+ file = g_file_new_for_path (path);
+ fistream = g_file_read (file, NULL, &inner_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, &inner_error)) != NULL) && !inner_error) {
+ const gchar *aux;
+
+ aux = line;
+ while (*aux == ' ')
+ aux++;
+ if (*aux != '#' && *aux != '\0') {
+ MMUdevRule rule = { 0 };
+
+ if (load_rule_from_line (&rule, aux, &inner_error))
+ g_array_append_val (rules, rule);
+ else
+ udev_rule_clear (&rule);
+ }
+ g_free (line);
+ }
+
+out:
+
+ if (distream)
+ g_object_unref (distream);
+ if (fistream)
+ g_object_unref (fistream);
+ g_object_unref (file);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (first_rule_index < rules->len && !process_goto_tags (rules, first_rule_index, error))
+ return FALSE;
+
+ return TRUE;
+}
+
+static GList *
+list_rule_files (const gchar *rules_dir_path)
+{
+ static const gchar *expected_rules_prefix[] = { "77-mm-", "78-mm-", "79-mm-", "80-mm-" };
+ GFile *udevrulesdir;
+ GFileEnumerator *enumerator;
+ GList *children = NULL;
+
+ udevrulesdir = g_file_new_for_path (rules_dir_path);
+ 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, rules_dir_path, 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);
+}
+
+GArray *
+mm_kernel_device_generic_rules_load (const gchar *rules_dir,
+ GError **error)
+{
+ GList *rule_files, *l;
+ GArray *rules;
+ GError *inner_error = NULL;
+
+ mm_dbg ("[rules] rules directory set to '%s'...", rules_dir);
+
+ rules = g_array_new (FALSE, FALSE, sizeof (MMUdevRule));
+ g_array_set_clear_func (rules, (GDestroyNotify) udev_rule_clear);
+
+ /* List rule files in rules dir */
+ rule_files = list_rule_files (rules_dir);
+ if (!rule_files) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No rule files found in '%s'", rules_dir);
+ goto out;
+ }
+
+ /* Iterate over rule files */
+ for (l = rule_files; l; l = g_list_next (l)) {
+ if (!load_rules_from_file (rules, (const gchar *)(l->data), &inner_error))
+ goto out;
+ }
+
+ /* Fail if no rules were loaded */
+ if (rules->len == 0) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "No rules loaded");
+ goto out;
+ }
+
+ mm_dbg ("[rules] %u loaded", rules->len);
+
+out:
+ if (rule_files)
+ g_list_free_full (rule_files, (GDestroyNotify) g_free);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ g_array_unref (rules);
+ return NULL;
+ }
+
+ return rules;
+}
diff --git a/src/kerneldevice/mm-kernel-device-generic-rules.h b/src/kerneldevice/mm-kernel-device-generic-rules.h
new file mode 100644
index 00000000..db570d19
--- /dev/null
+++ b/src/kerneldevice/mm-kernel-device-generic-rules.h
@@ -0,0 +1,62 @@
+/* -*- 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 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ MM_UDEV_RULE_MATCH_TYPE_UNKNOWN,
+ MM_UDEV_RULE_MATCH_TYPE_EQUAL,
+ MM_UDEV_RULE_MATCH_TYPE_NOT_EQUAL,
+} MMUdevRuleMatchType;
+
+typedef struct {
+ MMUdevRuleMatchType type;
+ gchar *parameter;
+ gchar *value;
+} MMUdevRuleMatch;
+
+typedef enum {
+ MM_UDEV_RULE_RESULT_TYPE_UNKNOWN,
+ MM_UDEV_RULE_RESULT_TYPE_PROPERTY,
+ MM_UDEV_RULE_RESULT_TYPE_LABEL,
+ MM_UDEV_RULE_RESULT_TYPE_GOTO_INDEX,
+ MM_UDEV_RULE_RESULT_TYPE_GOTO_TAG, /* internal use only */
+} MMUdevRuleResultType;
+
+typedef struct {
+ gchar *name;
+ gchar *value;
+} MMUdevRuleResultProperty;
+
+typedef struct {
+ MMUdevRuleResultType type;
+ union {
+ MMUdevRuleResultProperty property;
+ gchar *tag;
+ guint index;
+ } content;
+} MMUdevRuleResult;
+
+typedef struct {
+ GArray *conditions;
+ MMUdevRuleResult result;
+} MMUdevRule;
+
+GArray *mm_kernel_device_generic_rules_load (const gchar *rules_dir,
+ GError **error);
+
+G_END_DECLS
diff --git a/src/kerneldevice/mm-kernel-device-generic.c b/src/kerneldevice/mm-kernel-device-generic.c
index a6dbc2d7..32ac5ed1 100644
--- a/src/kerneldevice/mm-kernel-device-generic.c
+++ b/src/kerneldevice/mm-kernel-device-generic.c
@@ -22,8 +22,13 @@
#include <libmm-glib.h>
#include "mm-kernel-device-generic.h"
+#include "mm-kernel-device-generic-rules.h"
#include "mm-log.h"
+#if !defined UDEVRULESDIR
+# error UDEVRULESDIR is not defined
+#endif
+
static void initable_iface_init (GInitableIface *iface);
G_DEFINE_TYPE_EXTENDED (MMKernelDeviceGeneric, mm_kernel_device_generic, MM_TYPE_KERNEL_DEVICE, 0,
@@ -32,6 +37,7 @@ G_DEFINE_TYPE_EXTENDED (MMKernelDeviceGeneric, mm_kernel_device_generic, MM_TYP
enum {
PROP_0,
PROP_PROPERTIES,
+ PROP_RULES,
PROP_LAST
};
@@ -40,8 +46,330 @@ static GParamSpec *properties[PROP_LAST];
struct _MMKernelDeviceGenericPrivate {
/* Input properties */
MMKernelEventProperties *properties;
+ /* Rules to apply */
+ GArray *rules;
+
+ /* Contents from sysfs */
+ gchar *driver;
+ gchar *sysfs_path;
+ gchar *interface_sysfs_path;
+ guint8 interface_class;
+ guint8 interface_subclass;
+ guint8 interface_protocol;
+ guint8 interface_number;
+ gchar *physdev_sysfs_path;
+ guint16 physdev_vid;
+ guint16 physdev_pid;
+ gchar *physdev_manufacturer;
+ gchar *physdev_product;
};
+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_interface_sysfs_path (MMKernelDeviceGeneric *self)
+{
+ gchar *dirpath;
+ gchar *aux;
+
+ if (self->priv->interface_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->interface_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->interface_sysfs_path)
+ mm_dbg ("(%s/%s) interface sysfs path: %s",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties),
+ self->priv->interface_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->interface_sysfs_path)
+ self->priv->physdev_sysfs_path = g_path_get_dirname (self->priv->interface_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->interface_sysfs_path) {
+ gchar *tmp;
+ gchar *tmp2;
+
+ tmp = g_strdup_printf ("%s/driver", self->priv->interface_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);
+ else
+ mm_dbg ("(%s/%s) vid: unknown",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties));
+
+}
+
+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);
+ else
+ mm_dbg ("(%s/%s) pid: unknown",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties));
+}
+
+static void
+preload_manufacturer (MMKernelDeviceGeneric *self)
+{
+ if (!self->priv->physdev_manufacturer)
+ self->priv->physdev_manufacturer = (self->priv->physdev_sysfs_path ? read_sysfs_property_as_string (self->priv->physdev_sysfs_path, "manufacturer") : NULL);
+
+ mm_dbg ("(%s/%s) manufacturer: %s",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties),
+ self->priv->physdev_manufacturer ? self->priv->physdev_manufacturer : "unknown");
+}
+
+static void
+preload_product (MMKernelDeviceGeneric *self)
+{
+ if (!self->priv->physdev_product)
+ self->priv->physdev_product = (self->priv->physdev_sysfs_path ? read_sysfs_property_as_string (self->priv->physdev_sysfs_path, "product") : NULL);
+
+ mm_dbg ("(%s/%s) product: %s",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties),
+ self->priv->physdev_product ? self->priv->physdev_product : "unknown");
+}
+
+static void
+preload_interface_class (MMKernelDeviceGeneric *self)
+{
+ self->priv->interface_class = (self->priv->interface_sysfs_path ? read_sysfs_property_as_hex (self->priv->interface_sysfs_path, "bInterfaceClass") : 0x00);
+ mm_dbg ("(%s/%s) interface class: 0x%02x",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties),
+ self->priv->interface_class);
+}
+
+static void
+preload_interface_subclass (MMKernelDeviceGeneric *self)
+{
+ self->priv->interface_subclass = (self->priv->interface_sysfs_path ? read_sysfs_property_as_hex (self->priv->interface_sysfs_path, "bInterfaceSubClass") : 0x00);
+ mm_dbg ("(%s/%s) interface subclass: 0x%02x",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties),
+ self->priv->interface_subclass);
+}
+
+static void
+preload_interface_protocol (MMKernelDeviceGeneric *self)
+{
+ self->priv->interface_protocol = (self->priv->interface_sysfs_path ? read_sysfs_property_as_hex (self->priv->interface_sysfs_path, "bInterfaceProtocol") : 0x00);
+ mm_dbg ("(%s/%s) interface protocol: 0x%02x",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties),
+ self->priv->interface_protocol);
+}
+
+static void
+preload_interface_number (MMKernelDeviceGeneric *self)
+{
+ self->priv->interface_number = (self->priv->interface_sysfs_path ? read_sysfs_property_as_hex (self->priv->interface_sysfs_path, "bInterfaceNumber") : 0x00);
+ mm_dbg ("(%s/%s) interface number: 0x%02x",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties),
+ self->priv->interface_number);
+}
+
+static void
+preload_contents (MMKernelDeviceGeneric *self)
+{
+ preload_sysfs_path (self);
+ preload_interface_sysfs_path (self);
+ preload_interface_class (self);
+ preload_interface_subclass (self);
+ preload_interface_protocol (self);
+ preload_interface_number (self);
+ preload_physdev_sysfs_path (self);
+ preload_manufacturer (self);
+ preload_product (self);
+ preload_driver (self);
+ preload_physdev_vid (self);
+ preload_physdev_pid (self);
+}
+
/*****************************************************************************/
static const gchar *
@@ -65,7 +393,7 @@ kernel_device_get_sysfs_path (MMKernelDevice *self)
{
g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
- return NULL;
+ return MM_KERNEL_DEVICE_GENERIC (self)->priv->sysfs_path;
}
static const gchar *
@@ -73,16 +401,30 @@ kernel_device_get_parent_sysfs_path (MMKernelDevice *self)
{
g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
- return NULL;
+ return MM_KERNEL_DEVICE_GENERIC (self)->priv->interface_sysfs_path;
}
static const gchar *
kernel_device_get_physdev_uid (MMKernelDevice *self)
{
+ const gchar *uid;
+
g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
/* Prefer the one coming in the properties, if any */
- return mm_kernel_event_properties_get_uid (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties);
+ if ((uid = mm_kernel_event_properties_get_uid (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties)) != NULL)
+ return uid;
+
+ /* Try to load from properties set */
+ if ((uid = mm_kernel_device_get_property (self, "ID_MM_PHYSDEV_UID")) != NULL)
+ return uid;
+
+ /* Use physical device path, if any */
+ if (MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_sysfs_path)
+ return 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 */
+ return MM_KERNEL_DEVICE_GENERIC (self)->priv->sysfs_path;
}
static const gchar *
@@ -90,7 +432,7 @@ kernel_device_get_driver (MMKernelDevice *self)
{
g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
- return NULL;
+ return MM_KERNEL_DEVICE_GENERIC (self)->priv->driver;
}
static guint16
@@ -98,7 +440,7 @@ kernel_device_get_physdev_vid (MMKernelDevice *self)
{
g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), 0);
- return 0;
+ return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_vid;
}
static guint16
@@ -106,15 +448,59 @@ kernel_device_get_physdev_pid (MMKernelDevice *self)
{
g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), 0);
- return 0;
+ return MM_KERNEL_DEVICE_GENERIC (self)->priv->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);
+
+ 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;
}
@@ -129,13 +515,305 @@ kernel_device_cmp (MMKernelDevice *a,
!g_strcmp0 (mm_kernel_device_get_name (a), mm_kernel_device_get_name (b)));
}
+/*****************************************************************************/
+
+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
+check_condition (MMKernelDeviceGeneric *self,
+ MMUdevRuleMatch *match)
+{
+ gboolean condition_equal;
+
+ condition_equal = (match->type == MM_UDEV_RULE_MATCH_TYPE_EQUAL);
+
+ /* We only apply 'add' rules */
+ if (g_str_equal (match->parameter, "ACTION"))
+ return ((!!strstr (match->value, "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 (match->parameter, "SUBSYSTEMS") || g_str_equal (match->parameter, "SUBSYSTEM"))
+ return ((self->priv->sysfs_path && !!strstr (self->priv->sysfs_path, match->value)) == 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 (match->parameter, "DRIVER") || g_str_equal (match->parameter, "DRIVERS"))
+ return ((!g_strcmp0 (match->value, mm_kernel_device_get_driver (MM_KERNEL_DEVICE (self)))) == condition_equal);
+
+ /* Device name checks */
+ if (g_str_equal (match->parameter, "KERNEL"))
+ return (string_match (mm_kernel_device_get_name (MM_KERNEL_DEVICE (self)), match->value) == condition_equal);
+
+ /* Device sysfs path checks; we allow both a direct match and a prefix patch */
+ if (g_str_equal (match->parameter, "DEVPATH")) {
+ const gchar *sysfs_path;
+ gchar *prefix_match = NULL;
+ gboolean result = FALSE;
+
+ sysfs_path = mm_kernel_device_get_sysfs_path (MM_KERNEL_DEVICE (self));
+
+ /* If not already doing a prefix match, do an implicit one. This is so that
+ * we can add properties to the usb_device owning all ports, and then apply
+ * the property to all ports individually processed here. */
+ if (match->value[0] && match->value[strlen (match->value) - 1] != '*')
+ prefix_match = g_strdup_printf ("%s/*", match->value);
+
+ if (string_match (sysfs_path, match->value) == condition_equal) {
+ result = TRUE;
+ goto out;
+ }
+
+ if (prefix_match && string_match (sysfs_path, prefix_match) == condition_equal) {
+ result = TRUE;
+ goto out;
+ }
+
+ if (g_str_has_prefix (sysfs_path, "/sys")) {
+ if (string_match (&sysfs_path[4], match->value) == condition_equal) {
+ result = TRUE;
+ goto out;
+ }
+ if (prefix_match && string_match (&sysfs_path[4], prefix_match) == condition_equal) {
+ result = TRUE;
+ goto out;
+ }
+ }
+ out:
+ g_free (prefix_match);
+ return result;
+ }
+
+ /* Attributes checks */
+ if (g_str_has_prefix (match->parameter, "ATTRS")) {
+ gchar *attribute;
+ gchar *contents = NULL;
+ gboolean result = FALSE;
+ guint val;
+
+ attribute = g_strdup (&match->parameter[5]);
+ g_strdelimit (attribute, "{}", ' ');
+ g_strstrip (attribute);
+
+ /* VID/PID directly from our API */
+ if (g_str_equal (attribute, "idVendor"))
+ result = ((mm_get_uint_from_hex_str (match->value, &val)) &&
+ ((mm_kernel_device_get_physdev_vid (MM_KERNEL_DEVICE (self)) == val) == condition_equal));
+ else if (g_str_equal (attribute, "idProduct"))
+ result = ((mm_get_uint_from_hex_str (match->value, &val)) &&
+ ((mm_kernel_device_get_physdev_pid (MM_KERNEL_DEVICE (self)) == val) == condition_equal));
+ /* manufacturer in the physdev */
+ else if (g_str_equal (attribute, "manufacturer"))
+ result = ((self->priv->physdev_manufacturer && g_str_equal (self->priv->physdev_manufacturer, match->value)) == condition_equal);
+ /* product in the physdev */
+ else if (g_str_equal (attribute, "product"))
+ result = ((self->priv->physdev_product && g_str_equal (self->priv->physdev_product, match->value)) == condition_equal);
+ /* interface class/subclass/protocol/number in the interface */
+ else if (g_str_equal (attribute, "bInterfaceClass"))
+ result = (g_str_equal (match->value, "?*") || ((mm_get_uint_from_hex_str (match->value, &val)) &&
+ ((self->priv->interface_class == val) == condition_equal)));
+ else if (g_str_equal (attribute, "bInterfaceSubClass"))
+ result = (g_str_equal (match->value, "?*") || ((mm_get_uint_from_hex_str (match->value, &val)) &&
+ ((self->priv->interface_subclass == val) == condition_equal)));
+ else if (g_str_equal (attribute, "bInterfaceProtocol"))
+ result = (g_str_equal (match->value, "?*") || ((mm_get_uint_from_hex_str (match->value, &val)) &&
+ ((self->priv->interface_protocol == val) == condition_equal)));
+ else if (g_str_equal (attribute, "bInterfaceNumber"))
+ result = (g_str_equal (match->value, "?*") || ((mm_get_uint_from_hex_str (match->value, &val)) &&
+ ((self->priv->interface_number == val) == condition_equal)));
+ else
+ mm_warn ("Unknown attribute: %s", attribute);
+
+ g_free (contents);
+ g_free (attribute);
+ return result;
+ }
+
+ /* Previously set property checks */
+ if (g_str_has_prefix (match->parameter, "ENV")) {
+ gchar *property;
+ gboolean result = FALSE;
+
+ property = g_strdup (&match->parameter[3]);
+ g_strdelimit (property, "{}", ' ');
+ g_strstrip (property);
+
+ result = ((!g_strcmp0 ((const gchar *) g_object_get_data (G_OBJECT (self), property), match->value)) == condition_equal);
+
+ g_free (property);
+ return result;
+ }
+
+ mm_warn ("Unknown match condition parameter: %s", match->parameter);
+ return FALSE;
+}
+
+static guint
+check_rule (MMKernelDeviceGeneric *self,
+ guint rule_i)
+{
+ MMUdevRule *rule;
+ gboolean apply = TRUE;
+
+ g_assert (rule_i < self->priv->rules->len);
+
+ rule = &g_array_index (self->priv->rules, MMUdevRule, rule_i);
+ if (rule->conditions) {
+ guint condition_i;
+
+ for (condition_i = 0; condition_i < rule->conditions->len; condition_i++) {
+ MMUdevRuleMatch *match;
+
+ match = &g_array_index (rule->conditions, MMUdevRuleMatch, condition_i);
+ if (!check_condition (self, match)) {
+ apply = FALSE;
+ break;
+ }
+ }
+ }
+
+ if (apply) {
+ switch (rule->result.type) {
+ case MM_UDEV_RULE_RESULT_TYPE_PROPERTY: {
+ gchar *property_value_read = NULL;
+
+ if (g_str_equal (rule->result.content.property.value, "$attr{bInterfaceClass}"))
+ property_value_read = g_strdup_printf ("%02x", self->priv->interface_class);
+ else if (g_str_equal (rule->result.content.property.value, "$attr{bInterfaceSubClass}"))
+ property_value_read = g_strdup_printf ("%02x", self->priv->interface_subclass);
+ else if (g_str_equal (rule->result.content.property.value, "$attr{bInterfaceProtocol}"))
+ property_value_read = g_strdup_printf ("%02x", self->priv->interface_protocol);
+ else if (g_str_equal (rule->result.content.property.value, "$attr{bInterfaceNumber}"))
+ property_value_read = g_strdup_printf ("%02x", self->priv->interface_number);
+
+ /* add new property */
+ mm_dbg ("(%s/%s) property added: %s=%s",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties),
+ rule->result.content.property.name,
+ property_value_read ? property_value_read : rule->result.content.property.value);
+
+ if (!property_value_read)
+ /* NOTE: we keep a reference to the list of rules ourselves, so it isn't
+ * an issue if we re-use the same string (i.e. without g_strdup-ing it)
+ * as a property value. */
+ g_object_set_data (G_OBJECT (self),
+ rule->result.content.property.name,
+ rule->result.content.property.value);
+ else
+ g_object_set_data_full (G_OBJECT (self),
+ rule->result.content.property.name,
+ property_value_read,
+ g_free);
+ break;
+ }
+
+ case MM_UDEV_RULE_RESULT_TYPE_LABEL:
+ /* noop */
+ break;
+
+ case MM_UDEV_RULE_RESULT_TYPE_GOTO_INDEX:
+ /* Jump to a new index */
+ return rule->result.content.index;
+
+ case MM_UDEV_RULE_RESULT_TYPE_GOTO_TAG:
+ case MM_UDEV_RULE_RESULT_TYPE_UNKNOWN:
+ g_assert_not_reached ();
+ }
+ }
+
+ /* Go to the next rule */
+ return rule_i + 1;
+}
+
+static void
+preload_properties (MMKernelDeviceGeneric *self)
+{
+ guint i;
+
+ g_assert (self->priv->rules);
+ g_assert (self->priv->rules->len > 0);
+
+ /* Start to process rules */
+ i = 0;
+ while (i < self->priv->rules->len) {
+ guint next_rule;
+
+ next_rule = check_rule (self, i);
+ i = next_rule;
+ }
+}
+
+static void
+check_preload (MMKernelDeviceGeneric *self)
+{
+ /* Only preload when properties and rules are set */
+ if (!self->priv->properties || !self->priv->rules)
+ return;
+
+ /* Don't preload on "remove" actions, where we don't have the device any more */
+ if (g_strcmp0 (mm_kernel_event_properties_get_action (self->priv->properties), "remove") == 0)
+ return;
+
+ /* Don't preload for devices in the 'virtual' subsystem */
+ if (g_strcmp0 (mm_kernel_event_properties_get_subsystem (self->priv->properties), "virtual") == 0)
+ return;
+
+ mm_dbg ("(%s/%s) preloading contents and properties...",
+ mm_kernel_event_properties_get_subsystem (self->priv->properties),
+ mm_kernel_event_properties_get_name (self->priv->properties));
+ preload_contents (self);
+ preload_properties (self);
+}
+
static gboolean
kernel_device_has_property (MMKernelDevice *self,
const gchar *property)
{
g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), FALSE);
- return FALSE;
+ return !!g_object_get_data (G_OBJECT (self), property);
}
static const gchar *
@@ -144,42 +822,70 @@ kernel_device_get_property (MMKernelDevice *self,
{
g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
- return 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);
- return 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);
- return 0;
+ 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)
+mm_kernel_device_generic_new_with_rules (MMKernelEventProperties *properties,
+ GArray *rules,
+ GError **error)
{
g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (properties), NULL);
+ g_return_val_if_fail (rules != NULL, NULL);
return MM_KERNEL_DEVICE (g_initable_new (MM_TYPE_KERNEL_DEVICE_GENERIC,
NULL,
error,
"properties", properties,
+ "rules", rules,
NULL));
}
+MMKernelDevice *
+mm_kernel_device_generic_new (MMKernelEventProperties *properties,
+ GError **error)
+{
+ static GArray *rules = NULL;
+
+ g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (properties), NULL);
+
+ /* We only try to load the default list of rules once */
+ if (G_UNLIKELY (!rules)) {
+ rules = mm_kernel_device_generic_rules_load (UDEVRULESDIR, error);
+ if (!rules)
+ return NULL;
+ }
+
+ return mm_kernel_device_generic_new_with_rules (properties, rules, error);
+}
+
/*****************************************************************************/
static void
@@ -201,6 +907,12 @@ set_property (GObject *object,
case PROP_PROPERTIES:
g_assert (!self->priv->properties);
self->priv->properties = g_value_dup_object (value);
+ check_preload (self);
+ break;
+ case PROP_RULES:
+ g_assert (!self->priv->rules);
+ self->priv->rules = g_value_dup_boxed (value);
+ check_preload (self);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -220,6 +932,9 @@ get_property (GObject *object,
case PROP_PROPERTIES:
g_value_set_object (value, self->priv->properties);
break;
+ case PROP_RULES:
+ g_value_set_boxed (value, self->priv->rules);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -241,15 +956,24 @@ initable_init (GInitable *initable,
return FALSE;
}
- if (!g_str_equal (subsystem, "virtual")) {
+ if (!mm_kernel_device_get_name (MM_KERNEL_DEVICE (self))) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
- "only virtual subsystem supported");
+ "name is mandatory in kernel device");
return FALSE;
}
- if (!mm_kernel_device_get_name (MM_KERNEL_DEVICE (self))) {
+ /* 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,
- "name is mandatory in kernel device");
+ "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;
}
@@ -261,7 +985,13 @@ dispose (GObject *object)
{
MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (object);
- g_clear_object (&self->priv->properties);
+ g_clear_pointer (&self->priv->physdev_product, g_free);
+ g_clear_pointer (&self->priv->physdev_manufacturer, g_free);
+ g_clear_pointer (&self->priv->physdev_sysfs_path, g_free);
+ g_clear_pointer (&self->priv->interface_sysfs_path, g_free);
+ g_clear_pointer (&self->priv->sysfs_path, g_free);
+ g_clear_pointer (&self->priv->rules, g_array_unref);
+ g_clear_object (&self->priv->properties);
G_OBJECT_CLASS (mm_kernel_device_generic_parent_class)->dispose (object);
}
@@ -306,4 +1036,12 @@ mm_kernel_device_generic_class_init (MMKernelDeviceGenericClass *klass)
MM_TYPE_KERNEL_EVENT_PROPERTIES,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_property (object_class, PROP_PROPERTIES, properties[PROP_PROPERTIES]);
+
+ properties[PROP_RULES] =
+ g_param_spec_boxed ("rules",
+ "Rules",
+ "List of rules to apply",
+ G_TYPE_ARRAY,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property (object_class, PROP_RULES, properties[PROP_RULES]);
}
diff --git a/src/kerneldevice/mm-kernel-device-generic.h b/src/kerneldevice/mm-kernel-device-generic.h
index 87f99941..0eb471fc 100644
--- a/src/kerneldevice/mm-kernel-device-generic.h
+++ b/src/kerneldevice/mm-kernel-device-generic.h
@@ -44,8 +44,11 @@ struct _MMKernelDeviceGenericClass {
MMKernelDeviceClass parent;
};
-GType mm_kernel_device_generic_get_type (void);
-MMKernelDevice *mm_kernel_device_generic_new (MMKernelEventProperties *properties,
- GError **error);
+GType mm_kernel_device_generic_get_type (void);
+MMKernelDevice *mm_kernel_device_generic_new (MMKernelEventProperties *properties,
+ GError **error);
+MMKernelDevice *mm_kernel_device_generic_new_with_rules (MMKernelEventProperties *properties,
+ GArray *rules,
+ GError **error);
#endif /* MM_KERNEL_DEVICE_GENERIC_H */
diff --git a/src/mm-base-manager.c b/src/mm-base-manager.c
index 3e03d552..66096130 100644
--- a/src/mm-base-manager.c
+++ b/src/mm-base-manager.c
@@ -17,11 +17,17 @@
* Copyright (C) 2011 - 2016 Aleksander Morgado <aleksander@aleksander.es>
*/
+#include <config.h>
+
#include <string.h>
#include <ctype.h>
#include <gmodule.h>
-#include "mm-kernel-device-udev.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>
@@ -62,8 +68,6 @@ struct _MMBaseManagerPrivate {
gchar *plugin_dir;
/* Path to the list of initial kernel events */
gchar *initial_kernel_events;
- /* The UDev client */
- GUdevClient *udev;
/* The authorization provider */
MMAuthProvider *authp;
GCancellable *authp_cancellable;
@@ -76,6 +80,11 @@ struct _MMBaseManagerPrivate {
/* The Test interface support */
MmGdbusTest *test_skeleton;
+
+#if WITH_UDEV
+ /* The UDev client */
+ GUdevClient *udev;
+#endif
};
/*****************************************************************************/
@@ -346,7 +355,11 @@ handle_kernel_event (MMBaseManager *self,
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;
@@ -362,6 +375,8 @@ handle_kernel_event (MMBaseManager *self,
return TRUE;
}
+#if WITH_UDEV
+
static void
handle_uevent (GUdevClient *client,
const char *action,
@@ -474,6 +489,8 @@ process_scan (MMBaseManager *self,
g_list_free (devices);
}
+#endif
+
static void
process_initial_kernel_events (MMBaseManager *self)
{
@@ -534,9 +551,13 @@ mm_base_manager_start (MMBaseManager *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
}
/*****************************************************************************/
@@ -714,11 +735,17 @@ scan_devices_auth_ready (MMAuthProvider *authp,
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);
@@ -771,12 +798,14 @@ report_kernel_event_auth_ready (MMAuthProvider *authp,
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)
@@ -990,7 +1019,6 @@ static void
mm_base_manager_init (MMBaseManager *manager)
{
MMBaseManagerPrivate *priv;
- const gchar *subsys[5] = { "tty", "net", "usb", "usbmisc", NULL };
/* Setup private data */
manager->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (manager,
@@ -1004,8 +1032,14 @@ mm_base_manager_init (MMBaseManager *manager)
/* Setup internal lists of device objects */
priv->devices = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
- /* Setup UDev client */
- priv->udev = g_udev_client_new (subsys);
+#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;
@@ -1038,9 +1072,11 @@ initable_init (GInitable *initable,
{
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);
@@ -1086,8 +1122,10 @@ finalize (GObject *object)
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);
diff --git a/src/mm-context.c b/src/mm-context.c
index cf8025bc..c00fa544 100644
--- a/src/mm-context.c
+++ b/src/mm-context.c
@@ -13,6 +13,7 @@
* Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
*/
+#include <config.h>
#include <stdlib.h>
#include "mm-context.h"
@@ -20,14 +21,20 @@
/*****************************************************************************/
/* Application context */
-static gboolean version_flag;
-static gboolean debug;
+static gboolean version_flag;
+static gboolean debug;
static const gchar *log_level;
static const gchar *log_file;
-static gboolean show_ts;
-static gboolean rel_ts;
+static gboolean show_ts;
+static gboolean rel_ts;
+
+#if WITH_UDEV
+static gboolean no_auto_scan = FALSE;
+#else
+static gboolean no_auto_scan = TRUE;
+#endif
+
static const gchar *initial_kernel_events;
-static gboolean no_auto_scan;
static const GOptionEntry entries[] = {
{ "version", 'V', 0, G_OPTION_ARG_NONE, &version_flag, "Print version", NULL },
@@ -36,8 +43,14 @@ static const GOptionEntry entries[] = {
{ "log-file", 0, 0, G_OPTION_ARG_FILENAME, &log_file, "Path to log file", "[PATH]" },
{ "timestamps", 0, 0, G_OPTION_ARG_NONE, &show_ts, "Show timestamps in log output", NULL },
{ "relative-timestamps", 0, 0, G_OPTION_ARG_NONE, &rel_ts, "Use relative timestamps (from MM start)", NULL },
+#if WITH_UDEV
{ "no-auto-scan", 0, 0, G_OPTION_ARG_NONE, &no_auto_scan, "Don't auto-scan looking for devices", NULL },
- { "initial-kernel-events", 0, 0, G_OPTION_ARG_FILENAME, &initial_kernel_events, "Path to initial kernel events file (requires --no-auto-scan)", "[PATH]" },
+#else
+ /* Keep the option when udev disabled, just so that the unit test setup can
+ * unconditionally use --no-auto-scan */
+ { "no-auto-scan", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &no_auto_scan, NULL, NULL },
+#endif
+ { "initial-kernel-events", 0, 0, G_OPTION_ARG_FILENAME, &initial_kernel_events, "Path to initial kernel events file", "[PATH]" },
{ NULL }
};
@@ -176,8 +189,10 @@ mm_context_init (gint argc,
print_version ();
/* Initial kernel events processing may only be used if autoscan is disabled */
+#if WITH_UDEV
if (!no_auto_scan && initial_kernel_events) {
g_warning ("error: --initial-kernel-events must be used only if --no-auto-scan is also used");
exit (1);
}
+#endif
}
diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
index 97adac6a..c5bb61d8 100644
--- a/src/tests/Makefile.am
+++ b/src/tests/Makefile.am
@@ -17,6 +17,7 @@ AM_CFLAGS = \
-I${top_srcdir}/src/ \
-I${top_builddir}/src/ \
-I${top_srcdir}/src/kerneldevice \
+ -DTESTUDEVRULESDIR=\"${top_srcdir}/src/\" \
$(NULL)
AM_LDFLAGS = \
@@ -24,6 +25,7 @@ AM_LDFLAGS = \
$(CODE_COVERAGE_LDFLAGS) \
$(top_builddir)/src/libhelpers.la \
$(top_builddir)/src/libport.la \
+ $(top_builddir)/src/libkerneldevice.la \
-lutil \
$(NULL)
@@ -39,7 +41,7 @@ endif
################################################################################
# tests
-# note: we abuse AM_LDFLAGS to include libhelpers and libport.
+# note: we abuse AM_LDFLAGS to include the libraries being tested
################################################################################
noinst_PROGRAMS = \
@@ -49,6 +51,7 @@ noinst_PROGRAMS = \
test-at-serial-port \
test-sms-part-3gpp \
test-sms-part-cdma \
+ test-udev-rules \
$(NULL)
if WITH_QMI
diff --git a/src/tests/test-udev-rules.c b/src/tests/test-udev-rules.c
new file mode 100644
index 00000000..469885df
--- /dev/null
+++ b/src/tests/test-udev-rules.c
@@ -0,0 +1,79 @@
+/* -*- 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 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+#include <string.h>
+#include <stdio.h>
+#include <locale.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+/* Define symbol to enable test message traces */
+#undef ENABLE_TEST_MESSAGE_TRACES
+
+#include "mm-kernel-device-generic-rules.h"
+#include "mm-log.h"
+
+/************************************************************/
+
+static void
+test_load_cleanup_core (void)
+{
+ GArray *rules;
+ GError *error = NULL;
+
+ rules = mm_kernel_device_generic_rules_load (TESTUDEVRULESDIR, &error);
+ g_assert_no_error (error);
+ g_assert (rules);
+ g_assert (rules->len > 0);
+
+ g_array_unref (rules);
+}
+
+/************************************************************/
+
+void
+_mm_log (const char *loc,
+ const char *func,
+ guint32 level,
+ const char *fmt,
+ ...)
+{
+#if defined ENABLE_TEST_MESSAGE_TRACES
+ /* Dummy log function */
+ va_list args;
+ gchar *msg;
+
+ va_start (args, fmt);
+ msg = g_strdup_vprintf (fmt, args);
+ va_end (args);
+ g_print ("%s\n", msg);
+ g_free (msg);
+#endif
+}
+
+int main (int argc, char **argv)
+{
+ setlocale (LC_ALL, "");
+
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/MM/test-udev-rules/load-cleanup-core", test_load_cleanup_core);
+
+ return g_test_run ();
+}
diff --git a/test/Makefile.am b/test/Makefile.am
index ae526cbb..cd2b5d56 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -6,12 +6,16 @@ EXTRA_DIST =
# lsudev
################################################################################
+if WITH_UDEV
+
noinst_PROGRAMS += lsudev
lsudev_SOURCES = lsudev.c
lsudev_CPPFLAGS = $(GUDEV_CFLAGS)
lsudev_LDADD = $(GUDEV_LIBS)
+endif
+
################################################################################
# mmtty
################################################################################
@@ -38,6 +42,31 @@ mmtty_LDADD = \
$(NULL)
################################################################################
+# mmrules
+################################################################################
+
+noinst_PROGRAMS += mmrules
+
+mmrules_SOURCES = mmrules.c
+
+mmrules_CPPFLAGS = \
+ $(MM_CFLAGS) \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/src \
+ -I$(top_srcdir)/src/kerneldevice \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir)/include \
+ -I$(top_srcdir)/libmm-glib \
+ -I$(top_srcdir)/libmm-glib/generated \
+ -I$(top_builddir)/libmm-glib/generated
+ $(NULL)
+
+mmrules_LDADD = \
+ $(MM_LIBS) \
+ $(top_builddir)/src/libkerneldevice.la \
+ $(NULL)
+
+################################################################################
# mmcli-test-sms
################################################################################
diff --git a/test/mmrules.c b/test/mmrules.c
new file mode 100644
index 00000000..f8c4c3e4
--- /dev/null
+++ b/test/mmrules.c
@@ -0,0 +1,173 @@
+/* -*- 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) 2015 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <locale.h>
+#include <string.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#include <mm-log.h>
+#include <mm-kernel-device-generic-rules.h>
+
+#define PROGRAM_NAME "mmrules"
+#define PROGRAM_VERSION PACKAGE_VERSION
+
+/* Context */
+static gchar *path;
+static gboolean verbose_flag;
+static gboolean version_flag;
+
+static GOptionEntry main_entries[] = {
+ { "path", 'p', 0, G_OPTION_ARG_FILENAME, &path,
+ "Specify path to udev rules directory",
+ "[PATH]"
+ },
+ { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose_flag,
+ "Run action with verbose logs",
+ NULL
+ },
+ { "version", 'V', 0, G_OPTION_ARG_NONE, &version_flag,
+ "Print version",
+ NULL
+ },
+ { NULL }
+};
+
+void
+_mm_log (const char *loc,
+ const char *func,
+ guint32 level,
+ const char *fmt,
+ ...)
+{
+ va_list args;
+ gchar *msg;
+
+ if (!verbose_flag)
+ return;
+
+ va_start (args, fmt);
+ msg = g_strdup_vprintf (fmt, args);
+ va_end (args);
+ g_print ("%s\n", msg);
+ g_free (msg);
+}
+
+static void
+print_version_and_exit (void)
+{
+ g_print ("\n"
+ PROGRAM_NAME " " PROGRAM_VERSION "\n"
+ "Copyright (2015) Aleksander Morgado\n"
+ "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl-2.0.html>\n"
+ "This is free software: you are free to change and redistribute it.\n"
+ "There is NO WARRANTY, to the extent permitted by law.\n"
+ "\n");
+ exit (EXIT_SUCCESS);
+}
+
+static void
+print_rule (MMUdevRule *rule)
+{
+ /* Process conditions */
+ if (rule->conditions) {
+ guint i;
+
+ for (i = 0; i < rule->conditions->len; i++) {
+ MMUdevRuleMatch *rule_match;
+
+ rule_match = &g_array_index (rule->conditions, MMUdevRuleMatch, i);
+ switch (rule_match->type) {
+ case MM_UDEV_RULE_MATCH_TYPE_EQUAL:
+ g_print (" [condition %u] %s == %s\n",
+ i, rule_match->parameter, rule_match->value);
+ break;
+ case MM_UDEV_RULE_MATCH_TYPE_NOT_EQUAL:
+ g_print (" [condition %u] %s != %s\n",
+ i, rule_match->parameter, rule_match->value);
+ break;
+ case MM_UDEV_RULE_MATCH_TYPE_UNKNOWN:
+ g_assert_not_reached ();
+ }
+ }
+ }
+
+ /* Process result */
+ switch (rule->result.type) {
+ case MM_UDEV_RULE_RESULT_TYPE_LABEL:
+ g_print (" [result] label %s\n", rule->result.content.tag);
+ break;
+ case MM_UDEV_RULE_RESULT_TYPE_GOTO_INDEX:
+ g_print (" [result] jump to rule %u\n", rule->result.content.index);
+ break;
+ case MM_UDEV_RULE_RESULT_TYPE_PROPERTY:
+ g_print (" [result] set property %s = %s\n",
+ rule->result.content.property.name, rule->result.content.property.value);
+ break;
+ case MM_UDEV_RULE_RESULT_TYPE_GOTO_TAG:
+ case MM_UDEV_RULE_RESULT_TYPE_UNKNOWN:
+ g_assert_not_reached ();
+ }
+}
+
+int main (int argc, char **argv)
+{
+ GOptionContext *context;
+ GArray *rules;
+ guint i;
+ GError *error = NULL;
+
+ setlocale (LC_ALL, "");
+
+ g_type_init ();
+
+ /* Setup option context, process it and destroy it */
+ context = g_option_context_new ("- ModemManager udev rules testing");
+ g_option_context_add_main_entries (context, main_entries, NULL);
+ g_option_context_parse (context, &argc, &argv, NULL);
+ g_option_context_free (context);
+
+ if (version_flag)
+ print_version_and_exit ();
+
+ /* No device path given? */
+ if (!path) {
+ g_printerr ("error: no path specified\n");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Load rules from directory */
+ rules = mm_kernel_device_generic_rules_load (path, &error);
+ if (!rules) {
+ g_printerr ("error: couldn't load rules: %s", error->message);
+ exit (EXIT_FAILURE);
+ }
+
+ /* Print loaded rules */
+ for (i = 0; i < rules->len; i++) {
+ g_print ("-----------------------------------------\n");
+ g_print ("rule [%u]:\n", i);
+ print_rule (&g_array_index (rules, MMUdevRule, i));
+ }
+
+ g_array_unref (rules);
+
+ return EXIT_SUCCESS;
+}