[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