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