[PATCH 5/5] core: allow building and running without udev
Dan Williams
dcbw at redhat.com
Fri Sep 30 04:10:11 UTC 2016
On Thu, 2016-09-29 at 15:39 +0200, Aleksander Morgado wrote:
> Instead of relying on the udev daemon and GUDev to manage the devices
> reported
> by the kernel, we can now run ModemManager relying solely on the
> kernel events
> reported via the new ReportKernelEvent() API. Therefore, the '--no-
> auto-scan'
> option is implicit for the ModemManager daemon when udev is disabled
> in the
> build.
>
> Additionally, a new custom implementation of the kernel device object
> is
> provided, which uses sysfs to load the properties and attributes
> required in
> each kernel device, instead of using a GUdevDevice.
>
> The udev rule files are kept in place, and a simple custom parser is
> provided
> which preloads all rules in memory once and then applies them to the
> different
> kernel objects reported via ReportKernelEvent(), e.g. to set port
> type hints.
> A simple unit test setup is prepared to validate the udev rules
> during the
> `check' Makefile target.
>
Generally LGTM; though perhaps load_rules_file() should check the file
owner and permissions and refuse to parse the file if it's not UID 0
(or a different one for testcases) and if it's not 0644. The udev
files are potentially untrusted input and MM runs as root.
Dan
> ---
> .gitignore | 3 +
> cli/Makefile.am | 7 +-
> cli/mmcli-manager.c | 31 +-
> configure.ac | 30 +-
> libmm-glib/mm-common-helpers.c | 43 ++
> libmm-glib/mm-common-helpers.h | 2 +
> plugins/Makefile.am | 38 ++
> plugins/tests/test-udev-rules.c | 164 +++++
> src/Makefile.am | 15 +
> src/kerneldevice/mm-kernel-device-generic-rules.c | 446
> +++++++++++++
> src/kerneldevice/mm-kernel-device-generic-rules.h | 62 ++
> src/kerneldevice/mm-kernel-device-generic.c | 772
> +++++++++++++++++++++-
> src/kerneldevice/mm-kernel-device-generic.h | 9 +-
> src/mm-base-manager.c | 50 +-
> src/mm-context.c | 27 +-
> src/tests/Makefile.am | 5 +-
> src/tests/test-udev-rules.c | 79 +++
> test/Makefile.am | 29 +
> test/mmrules.c | 173 +++++
> 19 files changed, 1942 insertions(+), 43 deletions(-)
> create mode 100644 plugins/tests/test-udev-rules.c
> create mode 100644 src/kerneldevice/mm-kernel-device-generic-rules.c
> create mode 100644 src/kerneldevice/mm-kernel-device-generic-rules.h
> create mode 100644 src/tests/test-udev-rules.c
> create mode 100644 test/mmrules.c
>
> diff --git a/.gitignore b/.gitignore
> index 36fe6c9..123ce20 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 7915f28..6c2d791 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 4e078f1..24ecc4e 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 dd3111d..c74d015 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 4b784da..3a5883b 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 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/plugins/Makefile.am b/plugins/Makefile.am
> index 435d0ed..2dcde28 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 0000000..369e22b
> --- /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 at 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 b1f1a40..91b4e17 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 0000000..608ca9f
> --- /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 at 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_STAND
> ARD_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 0000000..db570d1
> --- /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 at 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 a6dbc2d..32ac5ed 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 *r
> ules,
> + GError **e
> rror)
> {
> 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 87f9994..0eb471f 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 *propert
> ies,
> - GError
> **error);
> +GType mm_kernel_device_generic_get_type (void);
> +MMKernelDevice
> *mm_kernel_device_generic_new (MMKernelEventProperties *p
> roperties,
> + 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 3e03d55..6609613 100644
> --- a/src/mm-base-manager.c
> +++ b/src/mm-base-manager.c
> @@ -17,11 +17,17 @@
> * Copyright (C) 2011 - 2016 Aleksander Morgado <aleksander at aleksand
> er.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_device
> s (
> 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 cf8025b..c00fa54 100644
> --- a/src/mm-context.c
> +++ b/src/mm-context.c
> @@ -13,6 +13,7 @@
> * Copyright (C) 2012 Aleksander Morgado <aleksander at 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 97adac6..c5bb61d 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 0000000..469885d
> --- /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 at 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 ae526cb..cd2b5d5 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 0000000..f8c4c3e
> --- /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 at 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;
> +}
More information about the ModemManager-devel
mailing list