core: allow building and running without udev

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.
This commit is contained in:
Aleksander Morgado
2016-09-28 19:46:12 +02:00
parent ae9ede926a
commit 58c955f5f2
19 changed files with 1944 additions and 45 deletions

3
.gitignore vendored
View File

@@ -56,6 +56,7 @@ Makefile.in
/src/tests/test-at-serial-port /src/tests/test-at-serial-port
/src/tests/test-sms-part-3gpp /src/tests/test-sms-part-3gpp
/src/tests/test-sms-part-cdma /src/tests/test-sms-part-cdma
/src/tests/test-udev-rules
/cli/mmcli /cli/mmcli
@@ -158,6 +159,7 @@ Makefile.in
/uml290/uml290mode /uml290/uml290mode
/plugins/test-suite.log /plugins/test-suite.log
/plugins/test-udev-rules
/plugins/test-modem-helpers-huawei* /plugins/test-modem-helpers-huawei*
/plugins/test-modem-helpers-altair* /plugins/test-modem-helpers-altair*
/plugins/test-modem-helpers-cinterion* /plugins/test-modem-helpers-cinterion*
@@ -169,6 +171,7 @@ Makefile.in
/test/lsudev /test/lsudev
/test/mmtty /test/mmtty
/test/mmrules
/ModemManager-*-coverage.info /ModemManager-*-coverage.info
/ModemManager-*-coverage/ /ModemManager-*-coverage/

View File

@@ -2,7 +2,6 @@ bin_PROGRAMS = mmcli
mmcli_CPPFLAGS = \ mmcli_CPPFLAGS = \
$(MMCLI_CFLAGS) \ $(MMCLI_CFLAGS) \
$(GUDEV_CFLAGS) \
-I$(top_srcdir) \ -I$(top_srcdir) \
-I$(top_srcdir)/include \ -I$(top_srcdir)/include \
-I$(top_builddir)/include \ -I$(top_builddir)/include \
@@ -35,11 +34,15 @@ mmcli_SOURCES = \
$(NULL) $(NULL)
mmcli_LDADD = \ mmcli_LDADD = \
$(GUDEV_LIBS) \
$(MMCLI_LIBS) \ $(MMCLI_LIBS) \
$(top_builddir)/libmm-glib/libmm-glib.la \ $(top_builddir)/libmm-glib/libmm-glib.la \
$(NULL) $(NULL)
if WITH_UDEV
mmcli_CPPFLAGS += $(GUDEV_CFLAGS)
mmcli_LDADD += $(GUDEV_LIBS)
endif
completiondir = $(datadir)/bash-completion/completions completiondir = $(datadir)/bash-completion/completions
install-data-hook: install-data-hook:

View File

@@ -29,7 +29,9 @@
#include <glib.h> #include <glib.h>
#include <gio/gio.h> #include <gio/gio.h>
#include <gudev/gudev.h> #if WITH_UDEV
# include <gudev/gudev.h>
#endif
#define _LIBMM_INSIDE_MMCLI #define _LIBMM_INSIDE_MMCLI
#include "libmm-glib.h" #include "libmm-glib.h"
@@ -41,7 +43,9 @@
typedef struct { typedef struct {
MMManager *manager; MMManager *manager;
GCancellable *cancellable; GCancellable *cancellable;
#if WITH_UDEV
GUdevClient *udev; GUdevClient *udev;
#endif
} Context; } Context;
static Context *ctx; static Context *ctx;
@@ -51,7 +55,10 @@ static gboolean monitor_modems_flag;
static gboolean scan_modems_flag; static gboolean scan_modems_flag;
static gchar *set_logging_str; static gchar *set_logging_str;
static gchar *report_kernel_event_str; static gchar *report_kernel_event_str;
#if WITH_UDEV
static gboolean report_kernel_event_auto_scan; static gboolean report_kernel_event_auto_scan;
#endif
static GOptionEntry entries[] = { static GOptionEntry entries[] = {
{ "set-logging", 'G', 0, G_OPTION_ARG_STRING, &set_logging_str, { "set-logging", 'G', 0, G_OPTION_ARG_STRING, &set_logging_str,
@@ -74,10 +81,12 @@ static GOptionEntry entries[] = {
"Report kernel event", "Report kernel event",
"[\"key=value,...\"]" "[\"key=value,...\"]"
}, },
#if WITH_UDEV
{ "report-kernel-event-auto-scan", 0, 0, G_OPTION_ARG_NONE, &report_kernel_event_auto_scan, { "report-kernel-event-auto-scan", 0, 0, G_OPTION_ARG_NONE, &report_kernel_event_auto_scan,
"Automatically report kernel events based on udev notifications", "Automatically report kernel events based on udev notifications",
NULL NULL
}, },
#endif
{ NULL } { NULL }
}; };
@@ -110,8 +119,11 @@ mmcli_manager_options_enabled (void)
monitor_modems_flag + monitor_modems_flag +
scan_modems_flag + scan_modems_flag +
!!set_logging_str + !!set_logging_str +
!!report_kernel_event_str + !!report_kernel_event_str);
report_kernel_event_auto_scan);
#if WITH_UDEV
n_actions += report_kernel_event_auto_scan;
#endif
if (n_actions > 1) { if (n_actions > 1) {
g_printerr ("error: too many manager actions requested\n"); g_printerr ("error: too many manager actions requested\n");
@@ -121,8 +133,10 @@ mmcli_manager_options_enabled (void)
if (monitor_modems_flag) if (monitor_modems_flag)
mmcli_force_async_operation (); mmcli_force_async_operation ();
#if WITH_UDEV
if (report_kernel_event_auto_scan) if (report_kernel_event_auto_scan)
mmcli_force_async_operation (); mmcli_force_async_operation ();
#endif
checked = TRUE; checked = TRUE;
return !!n_actions; return !!n_actions;
@@ -134,8 +148,11 @@ context_free (Context *ctx)
if (!ctx) if (!ctx)
return; return;
#if WITH_UDEV
if (ctx->udev) if (ctx->udev)
g_object_unref (ctx->udev); g_object_unref (ctx->udev);
#endif
if (ctx->manager) if (ctx->manager)
g_object_unref (ctx->manager); g_object_unref (ctx->manager);
if (ctx->cancellable) if (ctx->cancellable)
@@ -308,6 +325,8 @@ cancelled (GCancellable *cancellable)
mmcli_async_operation_done (); mmcli_async_operation_done ();
} }
#if WITH_UDEV
static void static void
handle_uevent (GUdevClient *client, handle_uevent (GUdevClient *client,
const char *action, const char *action,
@@ -324,6 +343,8 @@ handle_uevent (GUdevClient *client,
g_object_unref (properties); g_object_unref (properties);
} }
#endif
static void static void
get_manager_ready (GObject *source, get_manager_ready (GObject *source,
GAsyncResult *result, GAsyncResult *result,
@@ -367,6 +388,7 @@ get_manager_ready (GObject *source,
return; return;
} }
#if WITH_UDEV
if (report_kernel_event_auto_scan) { if (report_kernel_event_auto_scan) {
const gchar *subsys[] = { "tty", "usbmisc", "net", NULL }; const gchar *subsys[] = { "tty", "usbmisc", "net", NULL };
guint i; guint i;
@@ -400,6 +422,7 @@ get_manager_ready (GObject *source,
NULL); NULL);
return; return;
} }
#endif
/* Request to monitor modems? */ /* Request to monitor modems? */
if (monitor_modems_flag) { if (monitor_modems_flag) {
@@ -457,10 +480,12 @@ mmcli_manager_run_synchronous (GDBusConnection *connection)
exit (EXIT_FAILURE); exit (EXIT_FAILURE);
} }
#if WITH_UDEV
if (report_kernel_event_auto_scan) { if (report_kernel_event_auto_scan) {
g_printerr ("error: monitoring udev events cannot be done synchronously\n"); g_printerr ("error: monitoring udev events cannot be done synchronously\n");
exit (EXIT_FAILURE); exit (EXIT_FAILURE);
} }
#endif
/* Initialize context */ /* Initialize context */
ctx = g_new0 (Context, 1); ctx = g_new0 (Context, 1);

View File

@@ -95,7 +95,6 @@ dnl Build dependencies
dnl dnl
GLIB_MIN_VERSION=2.36.0 GLIB_MIN_VERSION=2.36.0
GUDEV_MIN_VERSION=147
PKG_CHECK_MODULES(MM, PKG_CHECK_MODULES(MM,
glib-2.0 >= $GLIB_MIN_VERSION glib-2.0 >= $GLIB_MIN_VERSION
@@ -121,10 +120,6 @@ PKG_CHECK_MODULES(MMCLI,
AC_SUBST(MMCLI_CFLAGS) AC_SUBST(MMCLI_CFLAGS)
AC_SUBST(MMCLI_LIBS) 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 dnl Some required utilities
GLIB_MKENUMS=`$PKG_CONFIG --variable=glib_mkenums glib-2.0` GLIB_MKENUMS=`$PKG_CONFIG --variable=glib_mkenums glib-2.0`
AC_SUBST(GLIB_MKENUMS) AC_SUBST(GLIB_MKENUMS)
@@ -182,6 +177,30 @@ if test "x$with_systemdsystemunitdir" != xno; then
fi fi
AM_CONDITIONAL(HAVE_SYSTEMD, [test -n "$SYSTEMD_UNIT_DIR" -a "$SYSTEMD_UNIT_DIR" != xno ]) 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-----------------------------------------------------------------------------
dnl Suspend/resume support dnl Suspend/resume support
dnl dnl
@@ -396,6 +415,7 @@ echo "
systemd unit directory: ${with_systemdsystemunitdir} systemd unit directory: ${with_systemdsystemunitdir}
Features: Features:
udev support: ${with_udev}
policykit support: ${with_polkit} policykit support: ${with_polkit}
mbim support: ${with_mbim} mbim support: ${with_mbim}
qmi support: ${with_qmi} qmi support: ${with_qmi}

View File

@@ -1386,6 +1386,49 @@ mm_get_uint_from_str (const gchar *str,
return FALSE; 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 gboolean
mm_get_uint_from_match_info (GMatchInfo *match_info, mm_get_uint_from_match_info (GMatchInfo *match_info,
guint32 match_index, guint32 match_index,

View File

@@ -143,6 +143,8 @@ gboolean mm_get_int_from_match_info (GMatchInfo *match_info,
gint *out); gint *out);
gboolean mm_get_uint_from_str (const gchar *str, gboolean mm_get_uint_from_str (const gchar *str,
guint *out); guint *out);
gboolean mm_get_uint_from_hex_str (const gchar *str,
guint *out);
gboolean mm_get_uint_from_match_info (GMatchInfo *match_info, gboolean mm_get_uint_from_match_info (GMatchInfo *match_info,
guint32 match_index, guint32 match_index,
guint *out); guint *out);

View File

@@ -306,6 +306,8 @@ libmm_plugin_huawei_la_LIBADD = $(builddir)/libhelpers-huawei.la
dist_udevrules_DATA += huawei/77-mm-huawei-net-port-types.rules dist_udevrules_DATA += huawei/77-mm-huawei-net-port-types.rules
AM_CFLAGS += -DTESTUDEVRULESDIR_HUAWEI=\"${srcdir}/huawei\"
################################################################################ ################################################################################
# plugin: ericsson mbm # 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 dist_udevrules_DATA += mbm/77-mm-ericsson-mbm.rules
AM_CFLAGS += -DTESTUDEVRULESDIR_MBM=\"${srcdir}/mbm\"
################################################################################ ################################################################################
# plugin: option # 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 dist_udevrules_DATA += nokia/77-mm-nokia-port-types.rules
AM_CFLAGS += -DTESTUDEVRULESDIR_NOKIA=\"${srcdir}/nokia\"
################################################################################ ################################################################################
# plugin: zte # 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 dist_udevrules_DATA += zte/77-mm-zte-port-types.rules
AM_CFLAGS += -DTESTUDEVRULESDIR_ZTE=\"${srcdir}/zte\"
################################################################################ ################################################################################
# plugin: longcheer (and rebranded dongles) # 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 dist_udevrules_DATA += longcheer/77-mm-longcheer-port-types.rules
AM_CFLAGS += -DTESTUDEVRULESDIR_LONGCHEER=\"${srcdir}/longcheer\"
################################################################################ ################################################################################
# plugin: anydata cdma # 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 dist_udevrules_DATA += simtech/77-mm-simtech-port-types.rules
AM_CFLAGS += -DTESTUDEVRULESDIR_SIMTECH=\"${srcdir}/simtech\"
################################################################################ ################################################################################
# plugin: alcatel/TCT/JRD x220D and possibly others # 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 dist_udevrules_DATA += x22x/77-mm-x22x-port-types.rules
AM_CFLAGS += -DTESTUDEVRULESDIR_X22X=\"${srcdir}/x22x\"
################################################################################ ################################################################################
# plugin: pantech # 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 dist_udevrules_DATA += cinterion/77-mm-cinterion-port-types.rules
AM_CFLAGS += -DTESTUDEVRULESDIR_CINTERION=\"${srcdir}/cinterion\"
################################################################################ ################################################################################
# plugin: iridium # 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 dist_udevrules_DATA += dell/77-mm-dell-port-types.rules
AM_CFLAGS += -DTESTUDEVRULESDIR_DELL=\"${srcdir}/dell\"
################################################################################ ################################################################################
# plugin: altair lte # 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 dist_udevrules_DATA += telit/77-mm-telit-port-types.rules
AM_CFLAGS += -DTESTUDEVRULESDIR_TELIT=\"${srcdir}/telit\"
################################################################################ ################################################################################
# plugin: mtk # 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 dist_udevrules_DATA += mtk/77-mm-mtk-port-types.rules
AM_CFLAGS += -DTESTUDEVRULESDIR_MTK=\"${srcdir}/mtk\"
################################################################################ ################################################################################
# plugin: haier # 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 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) TEST_PROGS += $(noinst_PROGRAMS)

View File

@@ -0,0 +1,164 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
* Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
*/
#include <glib.h>
#include <glib-object.h>
#include <string.h>
#include <stdio.h>
#include <locale.h>
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
/* Define symbol to enable test message traces */
#undef ENABLE_TEST_MESSAGE_TRACES
#include "mm-kernel-device-generic-rules.h"
#include "mm-log.h"
/************************************************************/
static void
common_test (const gchar *plugindir)
{
GArray *rules;
GError *error = NULL;
rules = mm_kernel_device_generic_rules_load (plugindir, &error);
g_assert_no_error (error);
g_assert (rules);
g_assert (rules->len > 0);
g_array_unref (rules);
}
/************************************************************/
static void
test_huawei (void)
{
common_test (TESTUDEVRULESDIR_HUAWEI);
}
static void
test_mbm (void)
{
common_test (TESTUDEVRULESDIR_MBM);
}
static void
test_nokia (void)
{
common_test (TESTUDEVRULESDIR_NOKIA);
}
static void
test_zte (void)
{
common_test (TESTUDEVRULESDIR_ZTE);
}
static void
test_longcheer (void)
{
common_test (TESTUDEVRULESDIR_LONGCHEER);
}
static void
test_simtech (void)
{
common_test (TESTUDEVRULESDIR_SIMTECH);
}
static void
test_x22x (void)
{
common_test (TESTUDEVRULESDIR_X22X);
}
static void
test_cinterion (void)
{
common_test (TESTUDEVRULESDIR_CINTERION);
}
static void
test_dell (void)
{
common_test (TESTUDEVRULESDIR_DELL);
}
static void
test_telit (void)
{
common_test (TESTUDEVRULESDIR_TELIT);
}
static void
test_mtk (void)
{
common_test (TESTUDEVRULESDIR_MTK);
}
static void
test_haier (void)
{
common_test (TESTUDEVRULESDIR_HAIER);
}
/************************************************************/
void
_mm_log (const char *loc,
const char *func,
guint32 level,
const char *fmt,
...)
{
#if defined ENABLE_TEST_MESSAGE_TRACES
/* Dummy log function */
va_list args;
gchar *msg;
va_start (args, fmt);
msg = g_strdup_vprintf (fmt, args);
va_end (args);
g_print ("%s\n", msg);
g_free (msg);
#endif
}
int main (int argc, char **argv)
{
setlocale (LC_ALL, "");
g_type_init ();
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/MM/test-udev-rules/huawei", test_huawei);
g_test_add_func ("/MM/test-udev-rules/mbm", test_mbm);
g_test_add_func ("/MM/test-udev-rules/nokia", test_nokia);
g_test_add_func ("/MM/test-udev-rules/zte", test_zte);
g_test_add_func ("/MM/test-udev-rules/longcheer", test_longcheer);
g_test_add_func ("/MM/test-udev-rules/simtech", test_simtech);
g_test_add_func ("/MM/test-udev-rules/x22x", test_x22x);
g_test_add_func ("/MM/test-udev-rules/cinterion", test_cinterion);
g_test_add_func ("/MM/test-udev-rules/dell", test_dell);
g_test_add_func ("/MM/test-udev-rules/telit", test_telit);
g_test_add_func ("/MM/test-udev-rules/mtk", test_mtk);
g_test_add_func ("/MM/test-udev-rules/haier", test_haier);
return g_test_run ();
}

View File

@@ -102,14 +102,29 @@ endif
noinst_LTLIBRARIES += libkerneldevice.la noinst_LTLIBRARIES += libkerneldevice.la
libkerneldevice_la_CPPFLAGS = \
-DUDEVRULESDIR=\"$(udevrulesdir)\" \
$(NULL)
libkerneldevice_la_SOURCES = \ libkerneldevice_la_SOURCES = \
kerneldevice/mm-kernel-device.h \ kerneldevice/mm-kernel-device.h \
kerneldevice/mm-kernel-device.c \ kerneldevice/mm-kernel-device.c \
kerneldevice/mm-kernel-device-generic.h \ kerneldevice/mm-kernel-device-generic.h \
kerneldevice/mm-kernel-device-generic.c \ 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.h \
kerneldevice/mm-kernel-device-udev.c \ kerneldevice/mm-kernel-device-udev.c \
$(NULL) $(NULL)
endif
libkerneldevice_la_LIBADD = \
$(top_builddir)/libmm-glib/libmm-glib.la \
$(NULL)
################################################################################ ################################################################################
# ports library # ports library

View File

@@ -0,0 +1,446 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
*/
#include "config.h"
#include <string.h>
#include <ModemManager.h>
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
#include "mm-log.h"
#include "mm-kernel-device-generic-rules.h"
static void
udev_rule_match_clear (MMUdevRuleMatch *rule_match)
{
g_free (rule_match->parameter);
g_free (rule_match->value);
}
static void
udev_rule_clear (MMUdevRule *rule)
{
switch (rule->result.type) {
case MM_UDEV_RULE_RESULT_TYPE_PROPERTY:
g_free (rule->result.content.property.name);
g_free (rule->result.content.property.value);
break;
case MM_UDEV_RULE_RESULT_TYPE_GOTO_TAG:
case MM_UDEV_RULE_RESULT_TYPE_LABEL:
g_free (rule->result.content.tag);
break;
case MM_UDEV_RULE_RESULT_TYPE_GOTO_INDEX:
case MM_UDEV_RULE_RESULT_TYPE_UNKNOWN:
break;
}
if (rule->conditions)
g_array_unref (rule->conditions);
}
static gboolean
split_item (const gchar *item,
gchar **out_left,
gchar **out_operator,
gchar **out_right,
GError **error)
{
const gchar *aux;
gchar *left = NULL;
gchar *operator = NULL;
gchar *right = NULL;
GError *inner_error = NULL;
g_assert (item && out_left && out_operator && out_right);
/* Get left/operator/right */
if (((aux = strstr (item, "==")) != NULL) || ((aux = strstr (item, "!=")) != NULL)) {
operator = g_strndup (aux, 2);
right = g_strdup (aux + 2);
} else if ((aux = strstr (item, "=")) != NULL) {
operator = g_strndup (aux, 1);
right = g_strdup (aux + 1);
} else {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Invalid rule item, missing operator: '%s'", item);
goto out;
}
left = g_strndup (item, (aux - item));
g_strstrip (left);
if (!left[0]) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Invalid rule item, missing left field: '%s'", item);
goto out;
}
g_strdelimit (right, "\"", ' ');
g_strstrip (right);
if (!right[0]) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Invalid rule item, missing right field: '%s'", item);
goto out;
}
out:
if (inner_error) {
g_free (left);
g_free (operator);
g_free (right);
g_propagate_error (error, inner_error);
return FALSE;
}
*out_left = left;
*out_operator = operator;
*out_right = right;
return TRUE;
}
static gboolean
load_rule_result (MMUdevRuleResult *rule_result,
const gchar *item,
GError **error)
{
gchar *left;
gchar *operator;
gchar *right;
GError *inner_error = NULL;
gsize left_len;
if (!split_item (item, &left, &operator, &right, error))
return FALSE;
if (!g_str_equal (operator, "=")) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Invalid rule result operator: '%s'", item);
goto out;
}
if (g_str_equal (left, "LABEL")) {
rule_result->type = MM_UDEV_RULE_RESULT_TYPE_LABEL;
rule_result->content.tag = right;
right = NULL;
goto out;
}
if (g_str_equal (left, "GOTO")) {
rule_result->type = MM_UDEV_RULE_RESULT_TYPE_GOTO_TAG;
rule_result->content.tag = right;
right = NULL;
goto out;
}
left_len = strlen (left);
if (g_str_has_prefix (left, "ENV{") && left[left_len - 1] == '}') {
rule_result->type = MM_UDEV_RULE_RESULT_TYPE_PROPERTY;
rule_result->content.property.name = g_strndup (left + 4, left_len - 5);
rule_result->content.property.value = right;
right = NULL;
goto out;
}
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Invalid rule result parameter: '%s'", item);
out:
g_free (left);
g_free (operator);
g_free (right);
if (inner_error) {
g_propagate_error (error, inner_error);
return FALSE;
}
return TRUE;
}
static gboolean
load_rule_match (MMUdevRuleMatch *rule_match,
const gchar *item,
GError **error)
{
gchar *left;
gchar *operator;
gchar *right;
if (!split_item (item, &left, &operator, &right, error))
return FALSE;
if (g_str_equal (operator, "=="))
rule_match->type = MM_UDEV_RULE_MATCH_TYPE_EQUAL;
else if (g_str_equal (operator, "!="))
rule_match->type = MM_UDEV_RULE_MATCH_TYPE_NOT_EQUAL;
else {
g_free (left);
g_free (operator);
g_free (right);
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Invalid rule match, wrong match type: '%s'", item);
return FALSE;
}
g_free (operator);
rule_match->parameter = left;
rule_match->value = right;
return TRUE;
}
static gboolean
load_rule_from_line (MMUdevRule *rule,
const gchar *line,
GError **error)
{
gchar **split;
guint n_items;
GError *inner_error = NULL;
split = g_strsplit (line, ",", -1);
n_items = g_strv_length (split);
/* Conditions */
if (n_items > 1) {
guint i;
rule->conditions = g_array_sized_new (FALSE, FALSE, sizeof (MMUdevRuleMatch), n_items - 1);
g_array_set_clear_func (rule->conditions, (GDestroyNotify) udev_rule_match_clear);
/* All items except for the last one are conditions */
for (i = 0; !inner_error && i < (n_items - 1); i++) {
MMUdevRuleMatch rule_match = { 0 };
/* If condition correctly preloaded, add it to the rule */
if (!load_rule_match (&rule_match, split[i], &inner_error))
goto out;
g_assert (rule_match.type != MM_UDEV_RULE_MATCH_TYPE_UNKNOWN);
g_assert (rule_match.parameter);
g_assert (rule_match.value);
g_array_append_val (rule->conditions, rule_match);
}
}
/* Last item, the result */
if (!load_rule_result (&rule->result, split[n_items - 1], &inner_error))
goto out;
g_assert ((rule->result.type == MM_UDEV_RULE_RESULT_TYPE_GOTO_TAG && rule->result.content.tag) ||
(rule->result.type == MM_UDEV_RULE_RESULT_TYPE_LABEL && rule->result.content.tag) ||
(rule->result.type == MM_UDEV_RULE_RESULT_TYPE_PROPERTY && rule->result.content.property.name && rule->result.content.property.value));
out:
g_strfreev (split);
if (inner_error) {
g_propagate_error (error, inner_error);
return FALSE;
}
return TRUE;
}
static gboolean
process_goto_tags (GArray *rules,
guint first_rule_index,
GError **error)
{
guint i;
for (i = first_rule_index; i < rules->len; i++) {
MMUdevRule *rule;
rule = &g_array_index (rules, MMUdevRule, i);
if (rule->result.type == MM_UDEV_RULE_RESULT_TYPE_GOTO_TAG) {
guint j;
guint label_index = 0;
for (j = i + 1; j < rules->len; j++) {
MMUdevRule *walker;
walker = &g_array_index (rules, MMUdevRule, j);
if (walker->result.type == MM_UDEV_RULE_RESULT_TYPE_LABEL &&
g_str_equal (rule->result.content.tag, walker->result.content.tag)) {
if (label_index) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"More than one label '%s' found", rule->result.content.tag);
return FALSE;
}
label_index = j;
}
}
if (!label_index) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Couldn't find label '%s'", rule->result.content.tag);
return FALSE;
}
rule->result.type = MM_UDEV_RULE_RESULT_TYPE_GOTO_INDEX;
g_free (rule->result.content.tag);
rule->result.content.index = label_index;
}
}
return TRUE;
}
static gboolean
load_rules_from_file (GArray *rules,
const gchar *path,
GError **error)
{
GFile *file;
GFileInputStream *fistream;
GDataInputStream *distream = NULL;
GError *inner_error = NULL;
gchar *line;
guint first_rule_index;
mm_dbg ("[rules] loading rules from: %s", path);
first_rule_index = rules->len;
file = g_file_new_for_path (path);
fistream = g_file_read (file, NULL, &inner_error);
if (!fistream)
goto out;
distream = g_data_input_stream_new (G_INPUT_STREAM (fistream));
while (((line = g_data_input_stream_read_line_utf8 (distream, NULL, NULL, &inner_error)) != NULL) && !inner_error) {
const gchar *aux;
aux = line;
while (*aux == ' ')
aux++;
if (*aux != '#' && *aux != '\0') {
MMUdevRule rule = { 0 };
if (load_rule_from_line (&rule, aux, &inner_error))
g_array_append_val (rules, rule);
else
udev_rule_clear (&rule);
}
g_free (line);
}
out:
if (distream)
g_object_unref (distream);
if (fistream)
g_object_unref (fistream);
g_object_unref (file);
if (inner_error) {
g_propagate_error (error, inner_error);
return FALSE;
}
if (first_rule_index < rules->len && !process_goto_tags (rules, first_rule_index, error))
return FALSE;
return TRUE;
}
static GList *
list_rule_files (const gchar *rules_dir_path)
{
static const gchar *expected_rules_prefix[] = { "77-mm-", "78-mm-", "79-mm-", "80-mm-" };
GFile *udevrulesdir;
GFileEnumerator *enumerator;
GList *children = NULL;
udevrulesdir = g_file_new_for_path (rules_dir_path);
enumerator = g_file_enumerate_children (udevrulesdir,
G_FILE_ATTRIBUTE_STANDARD_NAME,
G_FILE_QUERY_INFO_NONE,
NULL,
NULL);
if (enumerator) {
GFileInfo *info;
/* If we get any kind of error, assume we need to stop enumerating */
while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) {
guint i;
for (i = 0; i < G_N_ELEMENTS (expected_rules_prefix); i++) {
if (g_str_has_prefix (g_file_info_get_name (info), expected_rules_prefix[i])) {
children = g_list_prepend (children, g_build_path (G_DIR_SEPARATOR_S, rules_dir_path, g_file_info_get_name (info), NULL));
break;
}
}
g_object_unref (info);
}
g_object_unref (enumerator);
}
g_object_unref (udevrulesdir);
return g_list_sort (children, (GCompareFunc) g_strcmp0);
}
GArray *
mm_kernel_device_generic_rules_load (const gchar *rules_dir,
GError **error)
{
GList *rule_files, *l;
GArray *rules;
GError *inner_error = NULL;
mm_dbg ("[rules] rules directory set to '%s'...", rules_dir);
rules = g_array_new (FALSE, FALSE, sizeof (MMUdevRule));
g_array_set_clear_func (rules, (GDestroyNotify) udev_rule_clear);
/* List rule files in rules dir */
rule_files = list_rule_files (rules_dir);
if (!rule_files) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"No rule files found in '%s'", rules_dir);
goto out;
}
/* Iterate over rule files */
for (l = rule_files; l; l = g_list_next (l)) {
if (!load_rules_from_file (rules, (const gchar *)(l->data), &inner_error))
goto out;
}
/* Fail if no rules were loaded */
if (rules->len == 0) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "No rules loaded");
goto out;
}
mm_dbg ("[rules] %u loaded", rules->len);
out:
if (rule_files)
g_list_free_full (rule_files, (GDestroyNotify) g_free);
if (inner_error) {
g_propagate_error (error, inner_error);
g_array_unref (rules);
return NULL;
}
return rules;
}

View File

@@ -0,0 +1,62 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
* Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
*/
#include <glib.h>
G_BEGIN_DECLS
typedef enum {
MM_UDEV_RULE_MATCH_TYPE_UNKNOWN,
MM_UDEV_RULE_MATCH_TYPE_EQUAL,
MM_UDEV_RULE_MATCH_TYPE_NOT_EQUAL,
} MMUdevRuleMatchType;
typedef struct {
MMUdevRuleMatchType type;
gchar *parameter;
gchar *value;
} MMUdevRuleMatch;
typedef enum {
MM_UDEV_RULE_RESULT_TYPE_UNKNOWN,
MM_UDEV_RULE_RESULT_TYPE_PROPERTY,
MM_UDEV_RULE_RESULT_TYPE_LABEL,
MM_UDEV_RULE_RESULT_TYPE_GOTO_INDEX,
MM_UDEV_RULE_RESULT_TYPE_GOTO_TAG, /* internal use only */
} MMUdevRuleResultType;
typedef struct {
gchar *name;
gchar *value;
} MMUdevRuleResultProperty;
typedef struct {
MMUdevRuleResultType type;
union {
MMUdevRuleResultProperty property;
gchar *tag;
guint index;
} content;
} MMUdevRuleResult;
typedef struct {
GArray *conditions;
MMUdevRuleResult result;
} MMUdevRule;
GArray *mm_kernel_device_generic_rules_load (const gchar *rules_dir,
GError **error);
G_END_DECLS

View File

@@ -22,8 +22,13 @@
#include <libmm-glib.h> #include <libmm-glib.h>
#include "mm-kernel-device-generic.h" #include "mm-kernel-device-generic.h"
#include "mm-kernel-device-generic-rules.h"
#include "mm-log.h" #include "mm-log.h"
#if !defined UDEVRULESDIR
# error UDEVRULESDIR is not defined
#endif
static void initable_iface_init (GInitableIface *iface); static void initable_iface_init (GInitableIface *iface);
G_DEFINE_TYPE_EXTENDED (MMKernelDeviceGeneric, mm_kernel_device_generic, MM_TYPE_KERNEL_DEVICE, 0, 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 { enum {
PROP_0, PROP_0,
PROP_PROPERTIES, PROP_PROPERTIES,
PROP_RULES,
PROP_LAST PROP_LAST
}; };
@@ -40,8 +46,330 @@ static GParamSpec *properties[PROP_LAST];
struct _MMKernelDeviceGenericPrivate { struct _MMKernelDeviceGenericPrivate {
/* Input properties */ /* Input properties */
MMKernelEventProperties *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 * 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); 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 * 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); 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 * static const gchar *
kernel_device_get_physdev_uid (MMKernelDevice *self) kernel_device_get_physdev_uid (MMKernelDevice *self)
{ {
const gchar *uid;
g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL); g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
/* Prefer the one coming in the properties, if any */ /* 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 * 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); g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
return NULL; return MM_KERNEL_DEVICE_GENERIC (self)->priv->driver;
} }
static guint16 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); 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 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); 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 static gboolean
kernel_device_is_candidate (MMKernelDevice *_self, kernel_device_is_candidate (MMKernelDevice *_self,
gboolean manual_scan) gboolean manual_scan)
{ {
MMKernelDeviceGeneric *self;
const gchar *name;
g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (_self), FALSE); 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; 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))); !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 static gboolean
kernel_device_has_property (MMKernelDevice *self, kernel_device_has_property (MMKernelDevice *self,
const gchar *property) const gchar *property)
{ {
g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), FALSE); 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 * 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); 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 static gboolean
kernel_device_get_property_as_boolean (MMKernelDevice *self, kernel_device_get_property_as_boolean (MMKernelDevice *self,
const gchar *property) const gchar *property)
{ {
const gchar *value;
g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), FALSE); 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 static gint
kernel_device_get_property_as_int (MMKernelDevice *self, kernel_device_get_property_as_int (MMKernelDevice *self,
const gchar *property) const gchar *property)
{ {
const gchar *value;
gint aux = 0;
g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), -1); 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 * MMKernelDevice *
mm_kernel_device_generic_new (MMKernelEventProperties *properties, mm_kernel_device_generic_new_with_rules (MMKernelEventProperties *properties,
GError **error) GArray *rules,
GError **error)
{ {
g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (properties), NULL); 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, return MM_KERNEL_DEVICE (g_initable_new (MM_TYPE_KERNEL_DEVICE_GENERIC,
NULL, NULL,
error, error,
"properties", properties, "properties", properties,
"rules", rules,
NULL)); 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 static void
@@ -201,6 +907,12 @@ set_property (GObject *object,
case PROP_PROPERTIES: case PROP_PROPERTIES:
g_assert (!self->priv->properties); g_assert (!self->priv->properties);
self->priv->properties = g_value_dup_object (value); 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; break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -220,6 +932,9 @@ get_property (GObject *object,
case PROP_PROPERTIES: case PROP_PROPERTIES:
g_value_set_object (value, self->priv->properties); g_value_set_object (value, self->priv->properties);
break; break;
case PROP_RULES:
g_value_set_boxed (value, self->priv->rules);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@@ -241,18 +956,27 @@ initable_init (GInitable *initable,
return FALSE; return FALSE;
} }
if (!g_str_equal (subsystem, "virtual")) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
"only virtual subsystem supported");
return FALSE;
}
if (!mm_kernel_device_get_name (MM_KERNEL_DEVICE (self))) { if (!mm_kernel_device_get_name (MM_KERNEL_DEVICE (self))) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
"name is mandatory in kernel device"); "name is mandatory in kernel device");
return FALSE; return FALSE;
} }
/* sysfs path is mandatory as output, and will only be given if the
* specified device exists; but only if this wasn't a 'remove' event
* and not a virtual device.
*/
if (self->priv->properties &&
g_strcmp0 (mm_kernel_event_properties_get_action (self->priv->properties), "remove") &&
g_strcmp0 (mm_kernel_event_properties_get_subsystem (self->priv->properties), "virtual") &&
!self->priv->sysfs_path) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
"device %s/%s not found",
mm_kernel_event_properties_get_subsystem (self->priv->properties),
mm_kernel_event_properties_get_name (self->priv->properties));
return FALSE;
}
return TRUE; return TRUE;
} }
@@ -261,7 +985,13 @@ dispose (GObject *object)
{ {
MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (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); 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, MM_TYPE_KERNEL_EVENT_PROPERTIES,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_property (object_class, PROP_PROPERTIES, properties[PROP_PROPERTIES]); 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]);
} }

View File

@@ -44,8 +44,11 @@ struct _MMKernelDeviceGenericClass {
MMKernelDeviceClass parent; MMKernelDeviceClass parent;
}; };
GType mm_kernel_device_generic_get_type (void); GType mm_kernel_device_generic_get_type (void);
MMKernelDevice *mm_kernel_device_generic_new (MMKernelEventProperties *properties, MMKernelDevice *mm_kernel_device_generic_new (MMKernelEventProperties *properties,
GError **error); GError **error);
MMKernelDevice *mm_kernel_device_generic_new_with_rules (MMKernelEventProperties *properties,
GArray *rules,
GError **error);
#endif /* MM_KERNEL_DEVICE_GENERIC_H */ #endif /* MM_KERNEL_DEVICE_GENERIC_H */

View File

@@ -17,11 +17,17 @@
* Copyright (C) 2011 - 2016 Aleksander Morgado <aleksander@aleksander.es> * Copyright (C) 2011 - 2016 Aleksander Morgado <aleksander@aleksander.es>
*/ */
#include <config.h>
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
#include <gmodule.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 <ModemManager.h>
#include <mm-errors-types.h> #include <mm-errors-types.h>
@@ -62,8 +68,6 @@ struct _MMBaseManagerPrivate {
gchar *plugin_dir; gchar *plugin_dir;
/* Path to the list of initial kernel events */ /* Path to the list of initial kernel events */
gchar *initial_kernel_events; gchar *initial_kernel_events;
/* The UDev client */
GUdevClient *udev;
/* The authorization provider */ /* The authorization provider */
MMAuthProvider *authp; MMAuthProvider *authp;
GCancellable *authp_cancellable; GCancellable *authp_cancellable;
@@ -76,6 +80,11 @@ struct _MMBaseManagerPrivate {
/* The Test interface support */ /* The Test interface support */
MmGdbusTest *test_skeleton; 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 (" name: %s", name);
mm_dbg (" uid: %s", uid ? uid : "n/a"); mm_dbg (" uid: %s", uid ? uid : "n/a");
#if WITH_UDEV
kernel_device = mm_kernel_device_udev_new_from_properties (properties, error); 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) if (!kernel_device)
return FALSE; return FALSE;
@@ -362,6 +375,8 @@ handle_kernel_event (MMBaseManager *self,
return TRUE; return TRUE;
} }
#if WITH_UDEV
static void static void
handle_uevent (GUdevClient *client, handle_uevent (GUdevClient *client,
const char *action, const char *action,
@@ -474,6 +489,8 @@ process_scan (MMBaseManager *self,
g_list_free (devices); g_list_free (devices);
} }
#endif
static void static void
process_initial_kernel_events (MMBaseManager *self) process_initial_kernel_events (MMBaseManager *self)
{ {
@@ -534,9 +551,13 @@ mm_base_manager_start (MMBaseManager *self,
return; return;
} }
#if WITH_UDEV
mm_dbg ("Starting %s device scan...", manual_scan ? "manual" : "automatic"); mm_dbg ("Starting %s device scan...", manual_scan ? "manual" : "automatic");
process_scan (self, manual_scan); process_scan (self, manual_scan);
mm_dbg ("Finished device 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)) if (!mm_auth_provider_authorize_finish (authp, res, &error))
g_dbus_method_invocation_take_error (ctx->invocation, error); g_dbus_method_invocation_take_error (ctx->invocation, error);
else { else {
#if WITH_UDEV
/* Otherwise relaunch device scan */ /* Otherwise relaunch device scan */
mm_base_manager_start (MM_BASE_MANAGER (ctx->self), TRUE); mm_base_manager_start (MM_BASE_MANAGER (ctx->self), TRUE);
mm_gdbus_org_freedesktop_modem_manager1_complete_scan_devices ( mm_gdbus_org_freedesktop_modem_manager1_complete_scan_devices (
MM_GDBUS_ORG_FREEDESKTOP_MODEM_MANAGER1 (ctx->self), MM_GDBUS_ORG_FREEDESKTOP_MODEM_MANAGER1 (ctx->self),
ctx->invocation); 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); 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)) if (!mm_auth_provider_authorize_finish (authp, res, &error))
goto out; goto out;
#if WITH_UDEV
if (ctx->self->priv->auto_scan) { if (ctx->self->priv->auto_scan) {
error = g_error_new_literal (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, error = g_error_new_literal (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
"Cannot report kernel event: " "Cannot report kernel event: "
"udev monitoring already in place"); "udev monitoring already in place");
goto out; goto out;
} }
#endif
properties = mm_kernel_event_properties_new_from_dictionary (ctx->dictionary, &error); properties = mm_kernel_event_properties_new_from_dictionary (ctx->dictionary, &error);
if (!properties) if (!properties)
@@ -990,7 +1019,6 @@ static void
mm_base_manager_init (MMBaseManager *manager) mm_base_manager_init (MMBaseManager *manager)
{ {
MMBaseManagerPrivate *priv; MMBaseManagerPrivate *priv;
const gchar *subsys[5] = { "tty", "net", "usb", "usbmisc", NULL };
/* Setup private data */ /* Setup private data */
manager->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (manager, 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 */ /* Setup internal lists of device objects */
priv->devices = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); priv->devices = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
/* Setup UDev client */ #if WITH_UDEV
priv->udev = g_udev_client_new (subsys); {
const gchar *subsys[5] = { "tty", "net", "usb", "usbmisc", NULL };
/* Setup UDev client */
priv->udev = g_udev_client_new (subsys);
}
#endif
/* By default, enable autoscan */ /* By default, enable autoscan */
priv->auto_scan = TRUE; priv->auto_scan = TRUE;
@@ -1038,9 +1072,11 @@ initable_init (GInitable *initable,
{ {
MMBaseManagerPrivate *priv = MM_BASE_MANAGER (initable)->priv; MMBaseManagerPrivate *priv = MM_BASE_MANAGER (initable)->priv;
#if WITH_UDEV
/* If autoscan enabled, list for udev events */ /* If autoscan enabled, list for udev events */
if (priv->auto_scan) if (priv->auto_scan)
g_signal_connect (priv->udev, "uevent", G_CALLBACK (handle_uevent), initable); g_signal_connect (priv->udev, "uevent", G_CALLBACK (handle_uevent), initable);
#endif
/* Create plugin manager */ /* Create plugin manager */
priv->plugin_manager = mm_plugin_manager_new (priv->plugin_dir, error); priv->plugin_manager = mm_plugin_manager_new (priv->plugin_dir, error);
@@ -1086,8 +1122,10 @@ finalize (GObject *object)
g_hash_table_destroy (priv->devices); g_hash_table_destroy (priv->devices);
#if WITH_UDEV
if (priv->udev) if (priv->udev)
g_object_unref (priv->udev); g_object_unref (priv->udev);
#endif
if (priv->plugin_manager) if (priv->plugin_manager)
g_object_unref (priv->plugin_manager); g_object_unref (priv->plugin_manager);

View File

@@ -13,6 +13,7 @@
* Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
*/ */
#include <config.h>
#include <stdlib.h> #include <stdlib.h>
#include "mm-context.h" #include "mm-context.h"
@@ -20,14 +21,20 @@
/*****************************************************************************/ /*****************************************************************************/
/* Application context */ /* Application context */
static gboolean version_flag; static gboolean version_flag;
static gboolean debug; static gboolean debug;
static const gchar *log_level; static const gchar *log_level;
static const gchar *log_file; static const gchar *log_file;
static gboolean show_ts; static gboolean show_ts;
static gboolean rel_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 const gchar *initial_kernel_events;
static gboolean no_auto_scan;
static const GOptionEntry entries[] = { static const GOptionEntry entries[] = {
{ "version", 'V', 0, G_OPTION_ARG_NONE, &version_flag, "Print version", NULL }, { "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]" }, { "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 }, { "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 }, { "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 }, { "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 } { NULL }
}; };
@@ -176,8 +189,10 @@ mm_context_init (gint argc,
print_version (); print_version ();
/* Initial kernel events processing may only be used if autoscan is disabled */ /* Initial kernel events processing may only be used if autoscan is disabled */
#if WITH_UDEV
if (!no_auto_scan && initial_kernel_events) { if (!no_auto_scan && initial_kernel_events) {
g_warning ("error: --initial-kernel-events must be used only if --no-auto-scan is also used"); g_warning ("error: --initial-kernel-events must be used only if --no-auto-scan is also used");
exit (1); exit (1);
} }
#endif
} }

View File

@@ -17,6 +17,7 @@ AM_CFLAGS = \
-I${top_srcdir}/src/ \ -I${top_srcdir}/src/ \
-I${top_builddir}/src/ \ -I${top_builddir}/src/ \
-I${top_srcdir}/src/kerneldevice \ -I${top_srcdir}/src/kerneldevice \
-DTESTUDEVRULESDIR=\"${top_srcdir}/src/\" \
$(NULL) $(NULL)
AM_LDFLAGS = \ AM_LDFLAGS = \
@@ -24,6 +25,7 @@ AM_LDFLAGS = \
$(CODE_COVERAGE_LDFLAGS) \ $(CODE_COVERAGE_LDFLAGS) \
$(top_builddir)/src/libhelpers.la \ $(top_builddir)/src/libhelpers.la \
$(top_builddir)/src/libport.la \ $(top_builddir)/src/libport.la \
$(top_builddir)/src/libkerneldevice.la \
-lutil \ -lutil \
$(NULL) $(NULL)
@@ -39,7 +41,7 @@ endif
################################################################################ ################################################################################
# tests # tests
# note: we abuse AM_LDFLAGS to include libhelpers and libport. # note: we abuse AM_LDFLAGS to include the libraries being tested
################################################################################ ################################################################################
noinst_PROGRAMS = \ noinst_PROGRAMS = \
@@ -49,6 +51,7 @@ noinst_PROGRAMS = \
test-at-serial-port \ test-at-serial-port \
test-sms-part-3gpp \ test-sms-part-3gpp \
test-sms-part-cdma \ test-sms-part-cdma \
test-udev-rules \
$(NULL) $(NULL)
if WITH_QMI if WITH_QMI

View File

@@ -0,0 +1,79 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
* Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
*/
#include <glib.h>
#include <glib-object.h>
#include <string.h>
#include <stdio.h>
#include <locale.h>
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
/* Define symbol to enable test message traces */
#undef ENABLE_TEST_MESSAGE_TRACES
#include "mm-kernel-device-generic-rules.h"
#include "mm-log.h"
/************************************************************/
static void
test_load_cleanup_core (void)
{
GArray *rules;
GError *error = NULL;
rules = mm_kernel_device_generic_rules_load (TESTUDEVRULESDIR, &error);
g_assert_no_error (error);
g_assert (rules);
g_assert (rules->len > 0);
g_array_unref (rules);
}
/************************************************************/
void
_mm_log (const char *loc,
const char *func,
guint32 level,
const char *fmt,
...)
{
#if defined ENABLE_TEST_MESSAGE_TRACES
/* Dummy log function */
va_list args;
gchar *msg;
va_start (args, fmt);
msg = g_strdup_vprintf (fmt, args);
va_end (args);
g_print ("%s\n", msg);
g_free (msg);
#endif
}
int main (int argc, char **argv)
{
setlocale (LC_ALL, "");
g_type_init ();
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/MM/test-udev-rules/load-cleanup-core", test_load_cleanup_core);
return g_test_run ();
}

View File

@@ -6,12 +6,16 @@ EXTRA_DIST =
# lsudev # lsudev
################################################################################ ################################################################################
if WITH_UDEV
noinst_PROGRAMS += lsudev noinst_PROGRAMS += lsudev
lsudev_SOURCES = lsudev.c lsudev_SOURCES = lsudev.c
lsudev_CPPFLAGS = $(GUDEV_CFLAGS) lsudev_CPPFLAGS = $(GUDEV_CFLAGS)
lsudev_LDADD = $(GUDEV_LIBS) lsudev_LDADD = $(GUDEV_LIBS)
endif
################################################################################ ################################################################################
# mmtty # mmtty
################################################################################ ################################################################################
@@ -37,6 +41,31 @@ mmtty_LDADD = \
$(top_builddir)/src/libport.la \ $(top_builddir)/src/libport.la \
$(NULL) $(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 # mmcli-test-sms
################################################################################ ################################################################################

173
test/mmrules.c Normal file
View File

@@ -0,0 +1,173 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
* Copyright (C) 2015 Aleksander Morgado <aleksander@aleksander.es>
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <string.h>
#include <glib.h>
#include <gio/gio.h>
#include <mm-log.h>
#include <mm-kernel-device-generic-rules.h>
#define PROGRAM_NAME "mmrules"
#define PROGRAM_VERSION PACKAGE_VERSION
/* Context */
static gchar *path;
static gboolean verbose_flag;
static gboolean version_flag;
static GOptionEntry main_entries[] = {
{ "path", 'p', 0, G_OPTION_ARG_FILENAME, &path,
"Specify path to udev rules directory",
"[PATH]"
},
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose_flag,
"Run action with verbose logs",
NULL
},
{ "version", 'V', 0, G_OPTION_ARG_NONE, &version_flag,
"Print version",
NULL
},
{ NULL }
};
void
_mm_log (const char *loc,
const char *func,
guint32 level,
const char *fmt,
...)
{
va_list args;
gchar *msg;
if (!verbose_flag)
return;
va_start (args, fmt);
msg = g_strdup_vprintf (fmt, args);
va_end (args);
g_print ("%s\n", msg);
g_free (msg);
}
static void
print_version_and_exit (void)
{
g_print ("\n"
PROGRAM_NAME " " PROGRAM_VERSION "\n"
"Copyright (2015) Aleksander Morgado\n"
"License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl-2.0.html>\n"
"This is free software: you are free to change and redistribute it.\n"
"There is NO WARRANTY, to the extent permitted by law.\n"
"\n");
exit (EXIT_SUCCESS);
}
static void
print_rule (MMUdevRule *rule)
{
/* Process conditions */
if (rule->conditions) {
guint i;
for (i = 0; i < rule->conditions->len; i++) {
MMUdevRuleMatch *rule_match;
rule_match = &g_array_index (rule->conditions, MMUdevRuleMatch, i);
switch (rule_match->type) {
case MM_UDEV_RULE_MATCH_TYPE_EQUAL:
g_print (" [condition %u] %s == %s\n",
i, rule_match->parameter, rule_match->value);
break;
case MM_UDEV_RULE_MATCH_TYPE_NOT_EQUAL:
g_print (" [condition %u] %s != %s\n",
i, rule_match->parameter, rule_match->value);
break;
case MM_UDEV_RULE_MATCH_TYPE_UNKNOWN:
g_assert_not_reached ();
}
}
}
/* Process result */
switch (rule->result.type) {
case MM_UDEV_RULE_RESULT_TYPE_LABEL:
g_print (" [result] label %s\n", rule->result.content.tag);
break;
case MM_UDEV_RULE_RESULT_TYPE_GOTO_INDEX:
g_print (" [result] jump to rule %u\n", rule->result.content.index);
break;
case MM_UDEV_RULE_RESULT_TYPE_PROPERTY:
g_print (" [result] set property %s = %s\n",
rule->result.content.property.name, rule->result.content.property.value);
break;
case MM_UDEV_RULE_RESULT_TYPE_GOTO_TAG:
case MM_UDEV_RULE_RESULT_TYPE_UNKNOWN:
g_assert_not_reached ();
}
}
int main (int argc, char **argv)
{
GOptionContext *context;
GArray *rules;
guint i;
GError *error = NULL;
setlocale (LC_ALL, "");
g_type_init ();
/* Setup option context, process it and destroy it */
context = g_option_context_new ("- ModemManager udev rules testing");
g_option_context_add_main_entries (context, main_entries, NULL);
g_option_context_parse (context, &argc, &argv, NULL);
g_option_context_free (context);
if (version_flag)
print_version_and_exit ();
/* No device path given? */
if (!path) {
g_printerr ("error: no path specified\n");
exit (EXIT_FAILURE);
}
/* Load rules from directory */
rules = mm_kernel_device_generic_rules_load (path, &error);
if (!rules) {
g_printerr ("error: couldn't load rules: %s", error->message);
exit (EXIT_FAILURE);
}
/* Print loaded rules */
for (i = 0; i < rules->len; i++) {
g_print ("-----------------------------------------\n");
g_print ("rule [%u]:\n", i);
print_rule (&g_array_index (rules, MMUdevRule, i));
}
g_array_unref (rules);
return EXIT_SUCCESS;
}