[systemd-commits] 2 commits - .gitignore Makefile.am Makefile-man.am man/hwdb.xml man/systemd-hwdb.xml man/systemd.network.xml man/udevadm.xml man/udev.xml src/hwdb src/libsystemd src/network src/systemd src/udev units/.gitignore units/systemd-hwdb-update.service.in units/systemd-udev-hwdb-update.service.in

Tom Gundersen tomegun at kemper.freedesktop.org
Thu Dec 18 06:44:13 PST 2014


 .gitignore                                |    1 
 Makefile-man.am                           |    4 
 Makefile.am                               |   70 +-
 man/hwdb.xml                              |   87 +++
 man/systemd-hwdb.xml                      |   94 +++
 man/systemd.network.xml                   |   22 
 man/udev.xml                              |   37 -
 man/udevadm.xml                           |   53 --
 src/hwdb/Makefile                         |    1 
 src/hwdb/hwdb.c                           |  787 ++++++++++++++++++++++++++++++
 src/libsystemd/sd-rtnl/rtnl-message.c     |   56 ++
 src/libsystemd/sd-rtnl/rtnl-types.c       |   15 
 src/network/networkd-fdb.c                |  252 +++++++++
 src/network/networkd-link.c               |   19 
 src/network/networkd-network-gperf.gperf  |    2 
 src/network/networkd-network.c            |   13 
 src/network/networkd.h                    |   29 +
 src/systemd/sd-rtnl.h                     |    4 
 src/udev/udevadm-hwdb.c                   |    1 
 units/.gitignore                          |    2 
 units/systemd-hwdb-update.service.in      |   23 
 units/systemd-udev-hwdb-update.service.in |   23 
 22 files changed, 1445 insertions(+), 150 deletions(-)

New commits:
commit 65eb4378c3e1de25383d8cd606909e64c71edc80
Author: Tom Gundersen <teg at jklm.no>
Date:   Tue Dec 16 00:48:24 2014 +0100

    systemd-hwdb: introduce new tool
    
    This pulls out the hwdb managment from udevadm into an independent tool.
    
    The old code is left in place for backwards compatibility, and easy of
    testing, but all documentation is dropped to encourage use of the new
    tool instead.

diff --git a/.gitignore b/.gitignore
index bd9125d..c849c4d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -81,6 +81,7 @@
 /systemd-hibernate-resume
 /systemd-hibernate-resume-generator
 /systemd-hostnamed
+/systemd-hwdb
 /systemd-inhibit
 /systemd-initctl
 /systemd-journal-gatewayd
diff --git a/Makefile-man.am b/Makefile-man.am
index 6a40b76..45b8238 100644
--- a/Makefile-man.am
+++ b/Makefile-man.am
@@ -14,6 +14,7 @@ MANPAGES += \
 	man/file-hierarchy.7 \
 	man/halt.8 \
 	man/hostname.5 \
+	man/hwdb.7 \
 	man/journalctl.1 \
 	man/journald.conf.5 \
 	man/kernel-command-line.7 \
@@ -72,6 +73,7 @@ MANPAGES += \
 	man/systemd-halt.service.8 \
 	man/systemd-hibernate-resume-generator.8 \
 	man/systemd-hibernate-resume at .service.8 \
+	man/systemd-hwdb.8 \
 	man/systemd-inhibit.1 \
 	man/systemd-initctl.service.8 \
 	man/systemd-journald.service.8 \
@@ -1593,6 +1595,7 @@ EXTRA_DIST += \
 	man/halt.xml \
 	man/hostname.xml \
 	man/hostnamectl.xml \
+	man/hwdb.xml \
 	man/journalctl.xml \
 	man/journald.conf.xml \
 	man/kernel-command-line.xml \
@@ -1702,6 +1705,7 @@ EXTRA_DIST += \
 	man/systemd-hibernate-resume-generator.xml \
 	man/systemd-hibernate-resume at .service.xml \
 	man/systemd-hostnamed.service.xml \
+	man/systemd-hwdb.xml \
 	man/systemd-inhibit.xml \
 	man/systemd-initctl.service.xml \
 	man/systemd-journal-gatewayd.service.xml \
diff --git a/Makefile.am b/Makefile.am
index 6896c4b..2c9c5e3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -542,7 +542,7 @@ nodist_systemunit_DATA = \
 	units/systemd-udevd.service \
 	units/systemd-udev-trigger.service \
 	units/systemd-udev-settle.service \
-	units/systemd-udev-hwdb-update.service \
+	units/systemd-hwdb-update.service \
 	units/debug-shell.service \
 	units/initrd-parse-etc.service \
 	units/initrd-cleanup.service \
@@ -3400,8 +3400,7 @@ libudev_internal_la_CFLAGS = \
 
 # ------------------------------------------------------------------------------
 INSTALL_DIRS += \
-	$(sysconfdir)/udev/rules.d \
-	$(sysconfdir)/udev/hwdb.d
+	$(sysconfdir)/udev/rules.d
 
 dist_network_DATA = \
 	network/99-default.link \
@@ -3429,20 +3428,6 @@ dist_udevrules_DATA += \
 nodist_udevrules_DATA += \
 	rules/99-systemd.rules
 
-dist_udevhwdb_DATA = \
-	hwdb/20-pci-vendor-model.hwdb \
-	hwdb/20-pci-classes.hwdb \
-	hwdb/20-usb-vendor-model.hwdb \
-	hwdb/20-usb-classes.hwdb \
-	hwdb/20-sdio-vendor-model.hwdb \
-	hwdb/20-sdio-classes.hwdb \
-	hwdb/20-bluetooth-vendor-product.hwdb \
-	hwdb/20-acpi-vendor.hwdb \
-	hwdb/20-OUI.hwdb \
-	hwdb/20-net-ifname.hwdb \
-	hwdb/60-keyboard.hwdb \
-	hwdb/70-mouse.hwdb
-
 udevconfdir = $(sysconfdir)/udev
 dist_udevconf_DATA = \
 	src/udev/udev.conf
@@ -3462,14 +3447,12 @@ CLEANFILES += \
 EXTRA_DIST += \
 	units/systemd-udevd.service.in \
 	units/systemd-udev-trigger.service.in \
-	units/systemd-udev-settle.service.in \
-	units/systemd-udev-hwdb-update.service.in
+	units/systemd-udev-settle.service.in
 
 CLEANFILES += \
 	units/systemd-udevd.service \
 	units/systemd-udev-trigger.service \
-	units/systemd-udev-settle.service \
-	units/systemd-udev-hwdb-update.service
+	units/systemd-udev-settle.service
 
 SOCKETS_TARGET_WANTS += \
 	systemd-udevd-control.socket \
@@ -3477,8 +3460,7 @@ SOCKETS_TARGET_WANTS += \
 
 SYSINIT_TARGET_WANTS += \
 	systemd-udevd.service \
-	systemd-udev-trigger.service \
-	systemd-udev-hwdb-update.service
+	systemd-udev-trigger.service
 
 rootbin_PROGRAMS += \
 	udevadm
@@ -3599,10 +3581,49 @@ udevadm_SOURCES = \
 udevadm_LDADD = \
 	libudev-core.la
 
+# ------------------------------------------------------------------------------
+INSTALL_DIRS += \
+	$(sysconfdir)/udev/hwdb.d
+
+systemd_hwdb_SOURCES = \
+	src/libsystemd/sd-hwdb/hwdb-internal.h \
+	src/hwdb/hwdb.c
+
+systemd_hwdb_LDADD = \
+	libsystemd-shared.la \
+	libsystemd-internal.la \
+	libudev-internal.la
+
+rootbin_PROGRAMS += \
+	systemd-hwdb
+
+dist_udevhwdb_DATA = \
+	hwdb/20-pci-vendor-model.hwdb \
+	hwdb/20-pci-classes.hwdb \
+	hwdb/20-usb-vendor-model.hwdb \
+	hwdb/20-usb-classes.hwdb \
+	hwdb/20-sdio-vendor-model.hwdb \
+	hwdb/20-sdio-classes.hwdb \
+	hwdb/20-bluetooth-vendor-product.hwdb \
+	hwdb/20-acpi-vendor.hwdb \
+	hwdb/20-OUI.hwdb \
+	hwdb/20-net-ifname.hwdb \
+	hwdb/60-keyboard.hwdb \
+	hwdb/70-mouse.hwdb
+
+EXTRA_DIST += \
+	units/systemd-hwdb-update.service.in
+
+CLEANFILES += \
+	units/systemd-hwdb-update.service
+
+SYSINIT_TARGET_WANTS += \
+	systemd-hwdb-update.service
+
 # Update hwdb on installation. Do not bother if installing
 # in DESTDIR, since this is likely for packaging purposes.
 hwdb-update-hook:
-	-test -n "$(DESTDIR)" || $(rootbindir)/udevadm hwdb --update
+	-test -n "$(DESTDIR)" || $(rootbindir)/systemd-hwdb update
 
 INSTALL_DATA_HOOKS += \
 	hwdb-update-hook
diff --git a/man/hwdb.xml b/man/hwdb.xml
new file mode 100644
index 0000000..9f2aff3
--- /dev/null
+++ b/man/hwdb.xml
@@ -0,0 +1,87 @@
+<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<refentry id="hwdb">
+  <refentryinfo>
+    <title>hwdb</title>
+    <productname>systemd</productname>
+    <authorgroup>
+      <author>
+        <contrib>Developer</contrib>
+        <firstname>Kay</firstname>
+        <surname>Sievers</surname>
+        <email>kay at vrfy.org</email>
+      </author>
+      <author>
+        <contrib>Developer</contrib>
+        <firstname>Tom</firstname>
+        <surname>Gundersen</surname>
+        <email>teg at jklm.no</email>
+      </author>
+    </authorgroup>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>hwdb</refentrytitle>
+    <manvolnum>7</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>hwdb</refname>
+    <refpurpose>Hardware Database</refpurpose>
+  </refnamediv>
+
+  <refsect1><title>Description</title>
+    <para>The hardware database is a key-value store for associating modalias-like keys to
+    udev-properties-like values. It is used primarily by udev to add the relevant properties
+    to matching devices, but it can also be queried directly.</para>
+  </refsect1>
+
+  <refsect1><title>Hardware Database Files</title>
+      <para>The hwdb files are read from the files located in the
+      system hwdb directory <filename>/usr/lib/udev/hwdb.d</filename>,
+      the volatile runtime directory <filename>/run/udev/hwdb.d</filename>
+      and the local administration directory <filename>/etc/udev/hwdb.d</filename>.
+      All hwdb files are collectively sorted and processed in lexical order,
+      regardless of the directories in which they live. However, files with
+      identical filenames replace each other. Files in <filename>/etc</filename>
+      have the highest priority, files in <filename>/run</filename> take precedence
+      over files with the same name in <filename>/usr/lib</filename>. This can be
+      used to override a system-supplied hwdb file with a local file if needed;
+      a symlink in <filename>/etc</filename> with the same name as a hwdb file in
+      <filename>/usr/lib</filename>, pointing to <filename>/dev/null</filename>,
+      disables the hwdb file entirely. hwdb files must have the extension
+      <filename>.hwdb</filename>; other extensions are ignored.</para>
+
+      <para>The hwdb file contains data records consisting of matches and
+      associated key-value pairs. Every record in the hwdb starts with one or
+      more match string, specifying a shell glob to compare the database
+      lookup string against. Multiple match lines are specified in additional
+      consecutive lines. Every match line is compared indivdually, they are
+      combined by OR. Every match line must start at the first character of
+      the line.</para>
+
+      <para>The match lines are followed by one or more key-value pair lines, which
+      are recognized by a leading space character. The key name and value are separated
+      by <literal>=</literal>. An empty line signifies the end
+      of a record. Lines beginning with <literal>#</literal> are ignored.</para>
+
+      <para>The content of all hwdb files is read by
+      <citerefentry><refentrytitle>systemd-hwdb</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+      and compiled to a binary database located at <filename>/etc/udev/hwdb.bin</filename>,
+      or alternatively <filename>/usr/lib/udev/hwdb.bin</filename> if you want ship the compiled
+      database in an immutable image.
+      During runtime only the binary database is used.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para>
+      <citerefentry>
+        <refentrytitle>systemd-hwdb</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>
+    </para>
+  </refsect1>
+</refentry>
diff --git a/man/systemd-hwdb.xml b/man/systemd-hwdb.xml
new file mode 100644
index 0000000..09f4d55
--- /dev/null
+++ b/man/systemd-hwdb.xml
@@ -0,0 +1,94 @@
+<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<refentry id="systemd-hwdb">
+  <refentryinfo>
+    <title>systemd-hwdb</title>
+    <productname>systemd</productname>
+    <authorgroup>
+      <author>
+        <contrib>Developer</contrib>
+        <firstname>Kay</firstname>
+        <surname>Sievers</surname>
+        <email>kay at vrfy.org</email>
+      </author>
+      <author>
+        <contrib>Developer</contrib>
+        <firstname>Tom</firstname>
+        <surname>Gundersen</surname>
+        <email>teg at jklm.no</email>
+      </author>
+    </authorgroup>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>systemd-hwdb</refentrytitle>
+    <manvolnum>8</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>systemd-hwdb</refname><refpurpose>hardware database management tool</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>systemd-hwdb <optional>options</optional> update</command>
+    </cmdsynopsis>
+    <cmdsynopsis>
+      <command>systemd-hwdb <optional>options</optional> query <replaceable>modalias</replaceable></command>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1><title>Description</title>
+    <para><command>systemd-hwdb</command> expects a command and command
+    specific arguments.  It manages the binary hardware database.</para>
+  </refsect1>
+
+  <refsect1><title>Options</title>
+    <variablelist>
+      <varlistentry>
+        <term><option>-h</option></term>
+        <term><option>--help</option></term>
+        <listitem>
+          <para>Print help text.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>--usr</option></term>
+        <listitem>
+          <para>Generate in /usr/lib/udev instead of /etc/udev.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>-r</option></term>
+        <term><option>--root=<replaceable>PATH</replaceable></option></term>
+        <listitem>
+          <para>Alternative root path in the filesystem.</para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+
+    <refsect2><title>systemd-hwdb
+      <arg choice="opt"><replaceable>options</replaceable></arg>
+      update</title>
+      <para>Update the binary database.</para>
+    </refsect2>
+
+    <refsect2><title>systemd-hwdb
+      <arg choice="opt"><replaceable>options</replaceable></arg>
+      query
+      <arg><replaceable>MODALIAS</replaceable></arg>
+    </title>
+      <para>Query database and print result.</para>
+    </refsect2>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para><citerefentry>
+        <refentrytitle>hwdb</refentrytitle><manvolnum>7</manvolnum>
+    </citerefentry></para>
+  </refsect1>
+</refentry>
diff --git a/man/udev.xml b/man/udev.xml
index 1113a66..34b2e12 100644
--- a/man/udev.xml
+++ b/man/udev.xml
@@ -735,43 +735,6 @@
       </variablelist>
   </refsect1>
 
-  <refsect1><title>Hardware Database Files</title>
-      <para>The hwdb files are read from the files located in the
-      system hwdb directory <filename>/usr/lib/udev/hwdb.d</filename>,
-      the volatile runtime directory <filename>/run/udev/hwdb.d</filename>
-      and the local administration directory <filename>/etc/udev/hwdb.d</filename>.
-      All hwdb files are collectively sorted and processed in lexical order,
-      regardless of the directories in which they live. However, files with
-      identical filenames replace each other. Files in <filename>/etc</filename>
-      have the highest priority, files in <filename>/run</filename> take precedence
-      over files with the same name in <filename>/usr/lib</filename>. This can be
-      used to override a system-supplied hwdb file with a local file if needed;
-      a symlink in <filename>/etc</filename> with the same name as a hwdb file in
-      <filename>/usr/lib</filename>, pointing to <filename>/dev/null</filename>,
-      disables the hwdb file entirely. hwdb files must have the extension
-      <filename>.hwdb</filename>; other extensions are ignored.</para>
-
-      <para>The hwdb file contains data records consisting of matches and
-      associated key-value pairs. Every record in the hwdb starts with one or
-      more match string, specifying a shell glob to compare the database
-      lookup string against. Multiple match lines are specified in additional
-      consecutive lines. Every match line is compared indivdually, they are
-      combined by OR. Every match line must start at the first character of
-      the line.</para>
-
-      <para>The match lines are followed by one or more key-value pair lines, which
-      are recognized by a leading space character. The key name and value are separated
-      by <literal>=</literal>. An empty line signifies the end
-      of a record. Lines beginning with <literal>#</literal> are ignored.</para>
-
-      <para>The content of all hwdb files is read by
-      <citerefentry><refentrytitle>udevadm</refentrytitle><manvolnum>8</manvolnum></citerefentry>
-      and compiled to a binary database located at <filename>/etc/udev/hwdb.bin</filename>,
-      or alternatively <filename>/usr/lib/udev/hwdb.bin</filename> if you want ship the compiled
-      database in an immutable image.
-      During runtime only the binary database is used.</para>
-  </refsect1>
-
   <refsect1>
     <title>See Also</title>
     <para>
diff --git a/man/udevadm.xml b/man/udevadm.xml
index 38c1935..4aa8964 100644
--- a/man/udevadm.xml
+++ b/man/udevadm.xml
@@ -49,9 +49,6 @@
       <command>udevadm monitor <optional>options</optional></command>
     </cmdsynopsis>
     <cmdsynopsis>
-      <command>udevadm hwdb <optional>options</optional></command>
-    </cmdsynopsis>
-    <cmdsynopsis>
       <command>udevadm test <optional>options</optional> <replaceable>devpath</replaceable></command>
     </cmdsynopsis>
     <cmdsynopsis>
@@ -512,56 +509,6 @@
       </variablelist>
     </refsect2>
 
-    <refsect2><title>udevadm hwdb
-      <arg choice="opt"><replaceable>options</replaceable></arg>
-    </title>
-      <para>Maintain the hardware database index in <filename>/etc/udev/hwdb.bin</filename>.</para>
-      <variablelist>
-        <varlistentry>
-          <term><option>-u</option></term>
-          <term><option>--update</option></term>
-          <listitem>
-            <para>Compile the hardware database information located in /usr/lib/udev/hwdb.d/,
-            /etc/udev/hwdb.d/ and store it in <filename>/etc/udev/hwdb.bin</filename>. This should be done after
-            any update to the source files; it will not be called automatically. The running
-            udev daemon will detect a new database on its own and does not need to be
-            notified about it.</para>
-          </listitem>
-        </varlistentry>
-        <varlistentry>
-          <term><option>--usr</option></term>
-          <listitem>
-            <para>Put the compiled database into <filename>/usr/lib/udev/hwdb.bin</filename> instead.
-            Use this if you want to ship a pre-compiled database in immutable system images, or
-            don't use <filename>/etc/udev/hwdb.d</filename> and want to avoid large binary files in
-            <filename>/etc</filename>.</para>
-          </listitem>
-        </varlistentry>
-        <varlistentry>
-          <term><option>-t</option></term>
-          <term><option>--test=<replaceable>string</replaceable></option></term>
-          <listitem>
-            <para>Query the database with a modalias string, and print the
-            retrieved properties.</para>
-          </listitem>
-        </varlistentry>
-        <varlistentry>
-          <term><option>-r</option></term>
-          <term><option>--root=<replaceable>string</replaceable></option></term>
-          <listitem>
-            <para>Alternative root path in the file system for reading and writing files.</para>
-          </listitem>
-        </varlistentry>
-        <varlistentry>
-          <term><option>-h</option></term>
-          <term><option>--help</option></term>
-          <listitem>
-            <para>Print help text.</para>
-          </listitem>
-        </varlistentry>
-      </variablelist>
-    </refsect2>
-
     <refsect2><title>udevadm test
       <arg choice="opt"><replaceable>options</replaceable></arg>
       <arg><replaceable>devpath</replaceable></arg>
diff --git a/src/hwdb/Makefile b/src/hwdb/Makefile
new file mode 120000
index 0000000..d0b0e8e
--- /dev/null
+++ b/src/hwdb/Makefile
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/hwdb/hwdb.c b/src/hwdb/hwdb.c
new file mode 100644
index 0000000..9f8cbf6
--- /dev/null
+++ b/src/hwdb/hwdb.c
@@ -0,0 +1,787 @@
+/***
+  This file is part of systemd.
+
+  Copyright 2012 Kay Sievers <kay at vrfy.org>
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "util.h"
+#include "strbuf.h"
+#include "conf-files.h"
+#include "strv.h"
+#include "mkdir.h"
+#include "fileio.h"
+
+#include "hwdb-internal.h"
+#include "hwdb-util.h"
+
+/*
+ * Generic udev properties, key/value database based on modalias strings.
+ * Uses a Patricia/radix trie to index all matches for efficient lookup.
+ */
+
+static const char *arg_hwdb_bin_dir = "/etc/udev";
+static const char *arg_root = "";
+
+static const char * const conf_file_dirs[] = {
+        "/etc/udev/hwdb.d",
+        UDEVLIBEXECDIR "/hwdb.d",
+        NULL
+};
+
+/* in-memory trie objects */
+struct trie {
+        struct trie_node *root;
+        struct strbuf *strings;
+
+        size_t nodes_count;
+        size_t children_count;
+        size_t values_count;
+};
+
+struct trie_node {
+        /* prefix, common part for all children of this node */
+        size_t prefix_off;
+
+        /* sorted array of pointers to children nodes */
+        struct trie_child_entry *children;
+        uint8_t children_count;
+
+        /* sorted array of key/value pairs */
+        struct trie_value_entry *values;
+        size_t values_count;
+};
+
+/* children array item with char (0-255) index */
+struct trie_child_entry {
+        uint8_t c;
+        struct trie_node *child;
+};
+
+/* value array item with key/value pairs */
+struct trie_value_entry {
+        size_t key_off;
+        size_t value_off;
+};
+
+static int trie_children_cmp(const void *v1, const void *v2) {
+        const struct trie_child_entry *n1 = v1;
+        const struct trie_child_entry *n2 = v2;
+
+        return n1->c - n2->c;
+}
+
+static int node_add_child(struct trie *trie, struct trie_node *node, struct trie_node *node_child, uint8_t c) {
+        struct trie_child_entry *child;
+
+        /* extend array, add new entry, sort for bisection */
+        child = realloc(node->children, (node->children_count + 1) * sizeof(struct trie_child_entry));
+        if (!child)
+                return -ENOMEM;
+
+        node->children = child;
+        trie->children_count++;
+        node->children[node->children_count].c = c;
+        node->children[node->children_count].child = node_child;
+        node->children_count++;
+        qsort(node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp);
+        trie->nodes_count++;
+
+        return 0;
+}
+
+static struct trie_node *node_lookup(const struct trie_node *node, uint8_t c) {
+        struct trie_child_entry *child;
+        struct trie_child_entry search;
+
+        search.c = c;
+        child = bsearch(&search, node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp);
+        if (child)
+                return child->child;
+        return NULL;
+}
+
+static void trie_node_cleanup(struct trie_node *node) {
+        size_t i;
+
+        for (i = 0; i < node->children_count; i++)
+                trie_node_cleanup(node->children[i].child);
+        free(node->children);
+        free(node->values);
+        free(node);
+}
+
+static void trie_free(struct trie *trie) {
+        if (!trie)
+                return;
+
+        if (trie->root)
+                trie_node_cleanup(trie->root);
+
+        strbuf_cleanup(trie->strings);
+        free(trie);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct trie*, trie_free);
+
+static int trie_values_cmp(const void *v1, const void *v2, void *arg) {
+        const struct trie_value_entry *val1 = v1;
+        const struct trie_value_entry *val2 = v2;
+        struct trie *trie = arg;
+
+        return strcmp(trie->strings->buf + val1->key_off,
+                      trie->strings->buf + val2->key_off);
+}
+
+static int trie_node_add_value(struct trie *trie, struct trie_node *node,
+                          const char *key, const char *value) {
+        ssize_t k, v;
+        struct trie_value_entry *val;
+
+        k = strbuf_add_string(trie->strings, key, strlen(key));
+        if (k < 0)
+                return k;
+        v = strbuf_add_string(trie->strings, value, strlen(value));
+        if (v < 0)
+                return v;
+
+        if (node->values_count) {
+                struct trie_value_entry search = {
+                        .key_off = k,
+                        .value_off = v,
+                };
+
+                val = xbsearch_r(&search, node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie);
+                if (val) {
+                        /* replace existing earlier key with new value */
+                        val->value_off = v;
+                        return 0;
+                }
+        }
+
+        /* extend array, add new entry, sort for bisection */
+        val = realloc(node->values, (node->values_count + 1) * sizeof(struct trie_value_entry));
+        if (!val)
+                return -ENOMEM;
+        trie->values_count++;
+        node->values = val;
+        node->values[node->values_count].key_off = k;
+        node->values[node->values_count].value_off = v;
+        node->values_count++;
+        qsort_r(node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie);
+        return 0;
+}
+
+static int trie_insert(struct trie *trie, struct trie_node *node, const char *search,
+                       const char *key, const char *value) {
+        size_t i = 0;
+        int err = 0;
+
+        for (;;) {
+                size_t p;
+                uint8_t c;
+                struct trie_node *child;
+
+                for (p = 0; (c = trie->strings->buf[node->prefix_off + p]); p++) {
+                        _cleanup_free_ char *s = NULL;
+                        ssize_t off;
+                        _cleanup_free_ struct trie_node *new_child = NULL;
+
+                        if (c == search[i + p])
+                                continue;
+
+                        /* split node */
+                        new_child = new0(struct trie_node, 1);
+                        if (!new_child)
+                                return -ENOMEM;
+
+                        /* move values from parent to child */
+                        new_child->prefix_off = node->prefix_off + p+1;
+                        new_child->children = node->children;
+                        new_child->children_count = node->children_count;
+                        new_child->values = node->values;
+                        new_child->values_count = node->values_count;
+
+                        /* update parent; use strdup() because the source gets realloc()d */
+                        s = strndup(trie->strings->buf + node->prefix_off, p);
+                        if (!s)
+                                return -ENOMEM;
+
+                        off = strbuf_add_string(trie->strings, s, p);
+                        if (off < 0)
+                                return off;
+
+                        node->prefix_off = off;
+                        node->children = NULL;
+                        node->children_count = 0;
+                        node->values = NULL;
+                        node->values_count = 0;
+                        err = node_add_child(trie, node, new_child, c);
+                        if (err < 0)
+                                return err;
+
+                        new_child = NULL; /* avoid cleanup */
+                        break;
+                }
+                i += p;
+
+                c = search[i];
+                if (c == '\0')
+                        return trie_node_add_value(trie, node, key, value);
+
+                child = node_lookup(node, c);
+                if (!child) {
+                        ssize_t off;
+
+                        /* new child */
+                        child = new0(struct trie_node, 1);
+                        if (!child)
+                                return -ENOMEM;
+
+                        off = strbuf_add_string(trie->strings, search + i+1, strlen(search + i+1));
+                        if (off < 0) {
+                                free(child);
+                                return off;
+                        }
+
+                        child->prefix_off = off;
+                        err = node_add_child(trie, node, child, c);
+                        if (err < 0) {
+                                free(child);
+                                return err;
+                        }
+
+                        return trie_node_add_value(trie, child, key, value);
+                }
+
+                node = child;
+                i++;
+        }
+}
+
+struct trie_f {
+        FILE *f;
+        struct trie *trie;
+        uint64_t strings_off;
+
+        uint64_t nodes_count;
+        uint64_t children_count;
+        uint64_t values_count;
+};
+
+/* calculate the storage space for the nodes, children arrays, value arrays */
+static void trie_store_nodes_size(struct trie_f *trie, struct trie_node *node) {
+        uint64_t i;
+
+        for (i = 0; i < node->children_count; i++)
+                trie_store_nodes_size(trie, node->children[i].child);
+
+        trie->strings_off += sizeof(struct trie_node_f);
+        for (i = 0; i < node->children_count; i++)
+                trie->strings_off += sizeof(struct trie_child_entry_f);
+        for (i = 0; i < node->values_count; i++)
+                trie->strings_off += sizeof(struct trie_value_entry_f);
+}
+
+static int64_t trie_store_nodes(struct trie_f *trie, struct trie_node *node) {
+        uint64_t i;
+        struct trie_node_f n = {
+                .prefix_off = htole64(trie->strings_off + node->prefix_off),
+                .children_count = node->children_count,
+                .values_count = htole64(node->values_count),
+        };
+        struct trie_child_entry_f *children = NULL;
+        int64_t node_off;
+
+        if (node->children_count) {
+                children = new0(struct trie_child_entry_f, node->children_count);
+                if (!children)
+                        return -ENOMEM;
+        }
+
+        /* post-order recursion */
+        for (i = 0; i < node->children_count; i++) {
+                int64_t child_off;
+
+                child_off = trie_store_nodes(trie, node->children[i].child);
+                if (child_off < 0) {
+                        free(children);
+                        return child_off;
+                }
+                children[i].c = node->children[i].c;
+                children[i].child_off = htole64(child_off);
+        }
+
+        /* write node */
+        node_off = ftello(trie->f);
+        fwrite(&n, sizeof(struct trie_node_f), 1, trie->f);
+        trie->nodes_count++;
+
+        /* append children array */
+        if (node->children_count) {
+                fwrite(children, sizeof(struct trie_child_entry_f), node->children_count, trie->f);
+                trie->children_count += node->children_count;
+                free(children);
+        }
+
+        /* append values array */
+        for (i = 0; i < node->values_count; i++) {
+                struct trie_value_entry_f v = {
+                        .key_off = htole64(trie->strings_off + node->values[i].key_off),
+                        .value_off = htole64(trie->strings_off + node->values[i].value_off),
+                };
+
+                fwrite(&v, sizeof(struct trie_value_entry_f), 1, trie->f);
+                trie->values_count++;
+        }
+
+        return node_off;
+}
+
+static int trie_store(struct trie *trie, const char *filename) {
+        struct trie_f t = {
+                .trie = trie,
+        };
+        _cleanup_free_ char *filename_tmp = NULL;
+        int64_t pos;
+        int64_t root_off;
+        int64_t size;
+        struct trie_header_f h = {
+                .signature = HWDB_SIG,
+                .tool_version = htole64(atoi(VERSION)),
+                .header_size = htole64(sizeof(struct trie_header_f)),
+                .node_size = htole64(sizeof(struct trie_node_f)),
+                .child_entry_size = htole64(sizeof(struct trie_child_entry_f)),
+                .value_entry_size = htole64(sizeof(struct trie_value_entry_f)),
+        };
+        int err;
+
+        /* calculate size of header, nodes, children entries, value entries */
+        t.strings_off = sizeof(struct trie_header_f);
+        trie_store_nodes_size(&t, trie->root);
+
+        err = fopen_temporary(filename , &t.f, &filename_tmp);
+        if (err < 0)
+                return err;
+        fchmod(fileno(t.f), 0444);
+
+        /* write nodes */
+        err = fseeko(t.f, sizeof(struct trie_header_f), SEEK_SET);
+        if (err < 0) {
+                fclose(t.f);
+                unlink_noerrno(filename_tmp);
+                return -errno;
+        }
+        root_off = trie_store_nodes(&t, trie->root);
+        h.nodes_root_off = htole64(root_off);
+        pos = ftello(t.f);
+        h.nodes_len = htole64(pos - sizeof(struct trie_header_f));
+
+        /* write string buffer */
+        fwrite(trie->strings->buf, trie->strings->len, 1, t.f);
+        h.strings_len = htole64(trie->strings->len);
+
+        /* write header */
+        size = ftello(t.f);
+        h.file_size = htole64(size);
+        err = fseeko(t.f, 0, SEEK_SET);
+        if (err < 0) {
+                fclose(t.f);
+                unlink_noerrno(filename_tmp);
+                return -errno;
+        }
+        fwrite(&h, sizeof(struct trie_header_f), 1, t.f);
+        err = ferror(t.f);
+        if (err)
+                err = -errno;
+        fclose(t.f);
+        if (err < 0 || rename(filename_tmp, filename) < 0) {
+                unlink_noerrno(filename_tmp);
+                return err < 0 ? err : -errno;
+        }
+
+        log_debug("=== trie on-disk ===");
+        log_debug("size:             %8"PRIu64" bytes", size);
+        log_debug("header:           %8zu bytes", sizeof(struct trie_header_f));
+        log_debug("nodes:            %8"PRIu64" bytes (%8"PRIu64")",
+                  t.nodes_count * sizeof(struct trie_node_f), t.nodes_count);
+        log_debug("child pointers:   %8"PRIu64" bytes (%8"PRIu64")",
+                  t.children_count * sizeof(struct trie_child_entry_f), t.children_count);
+        log_debug("value pointers:   %8"PRIu64" bytes (%8"PRIu64")",
+                  t.values_count * sizeof(struct trie_value_entry_f), t.values_count);
+        log_debug("string store:     %8zu bytes", trie->strings->len);
+        log_debug("strings start:    %8"PRIu64, t.strings_off);
+
+        return 0;
+}
+
+static int insert_data(struct trie *trie, char **match_list, char *line, const char *filename) {
+        char *value, **entry;
+
+        value = strchr(line, '=');
+        if (!value) {
+                log_error("Error, key/value pair expected but got '%s' in '%s':", line, filename);
+                return -EINVAL;
+        }
+
+        value[0] = '\0';
+        value++;
+
+        /* libudev requires properties to start with a space */
+        while (isblank(line[0]) && isblank(line[1]))
+                line++;
+
+        if (line[0] == '\0' || value[0] == '\0') {
+                log_error("Error, empty key or value '%s' in '%s':", line, filename);
+                return -EINVAL;
+        }
+
+        STRV_FOREACH(entry, match_list)
+                trie_insert(trie, trie->root, *entry, line, value);
+
+        return 0;
+}
+
+static int import_file(struct trie *trie, const char *filename) {
+        enum {
+                HW_NONE,
+                HW_MATCH,
+                HW_DATA,
+        } state = HW_NONE;
+        _cleanup_fclose_ FILE *f = NULL;
+        char line[LINE_MAX];
+        _cleanup_strv_free_ char **match_list = NULL;
+        char *match = NULL;
+        int r;
+
+        f = fopen(filename, "re");
+        if (!f)
+                return -errno;
+
+        while (fgets(line, sizeof(line), f)) {
+                size_t len;
+                char *pos;
+
+                /* comment line */
+                if (line[0] == '#')
+                        continue;
+
+                /* strip trailing comment */
+                pos = strchr(line, '#');
+                if (pos)
+                        pos[0] = '\0';
+
+                /* strip trailing whitespace */
+                len = strlen(line);
+                while (len > 0 && isspace(line[len-1]))
+                        len--;
+                line[len] = '\0';
+
+                switch (state) {
+                case HW_NONE:
+                        if (len == 0)
+                                break;
+
+                        if (line[0] == ' ') {
+                                log_error("Error, MATCH expected but got '%s' in '%s':", line, filename);
+                                break;
+                        }
+
+                        /* start of record, first match */
+                        state = HW_MATCH;
+
+                        match = strdup(line);
+                        if (!match)
+                                return -ENOMEM;
+
+                        r = strv_consume(&match_list, match);
+                        if (r < 0)
+                                return r;
+
+                        break;
+
+                case HW_MATCH:
+                        if (len == 0) {
+                                log_error("Error, DATA expected but got empty line in '%s':", filename);
+                                state = HW_NONE;
+                                strv_clear(match_list);
+                                break;
+                        }
+
+                        /* another match */
+                        if (line[0] != ' ') {
+                                match = strdup(line);
+                                if (!match)
+                                        return -ENOMEM;
+
+                                r = strv_consume(&match_list, match);
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        /* first data */
+                        state = HW_DATA;
+                        insert_data(trie, match_list, line, filename);
+                        break;
+
+                case HW_DATA:
+                        /* end of record */
+                        if (len == 0) {
+                                state = HW_NONE;
+                                strv_clear(match_list);
+                                break;
+                        }
+
+                        if (line[0] != ' ') {
+                                log_error("Error, DATA expected but got '%s' in '%s':", line, filename);
+                                state = HW_NONE;
+                                strv_clear(match_list);
+                                break;
+                        }
+
+                        insert_data(trie, match_list, line, filename);
+                        break;
+                };
+        }
+
+        return 0;
+}
+
+static int hwdb_query(char **args, unsigned n) {
+        _cleanup_hwdb_unref_ sd_hwdb *hwdb = NULL;
+        const char *key, *value;
+        const char *modalias;
+        int r;
+
+        assert(args);
+        assert(n == 2);
+
+        modalias = args[1];
+
+        r = sd_hwdb_new(&hwdb);
+        if (r < 0)
+                return r;
+
+        SD_HWDB_FOREACH_PROPERTY(hwdb, modalias, key, value)
+                printf("%s=%s\n", key, value);
+
+        return 0;
+}
+
+static int hwdb_update(char **args, unsigned n) {
+        _cleanup_free_ char *hwdb_bin = NULL;
+        _cleanup_(trie_freep) struct trie *trie = NULL;
+        char **files, **f;
+        int r;
+
+        trie = new0(struct trie, 1);
+        if (!trie)
+                return -ENOMEM;
+
+        /* string store */
+        trie->strings = strbuf_new();
+        if (!trie->strings)
+                return -ENOMEM;
+
+        /* index */
+        trie->root = new0(struct trie_node, 1);
+        if (!trie->root)
+                return -ENOMEM;
+
+        trie->nodes_count++;
+
+        r = conf_files_list_strv(&files, ".hwdb", arg_root, conf_file_dirs);
+        if (r < 0)
+                return log_error_errno(r, "failed to enumerate hwdb files: %m");
+
+        STRV_FOREACH(f, files) {
+                log_debug("reading file '%s'", *f);
+                import_file(trie, *f);
+        }
+        strv_free(files);
+
+        strbuf_complete(trie->strings);
+
+        log_debug("=== trie in-memory ===");
+        log_debug("nodes:            %8zu bytes (%8zu)",
+                  trie->nodes_count * sizeof(struct trie_node), trie->nodes_count);
+        log_debug("children arrays:  %8zu bytes (%8zu)",
+                  trie->children_count * sizeof(struct trie_child_entry), trie->children_count);
+        log_debug("values arrays:    %8zu bytes (%8zu)",
+                  trie->values_count * sizeof(struct trie_value_entry), trie->values_count);
+        log_debug("strings:          %8zu bytes",
+                  trie->strings->len);
+        log_debug("strings incoming: %8zu bytes (%8zu)",
+                  trie->strings->in_len, trie->strings->in_count);
+        log_debug("strings dedup'ed: %8zu bytes (%8zu)",
+                  trie->strings->dedup_len, trie->strings->dedup_count);
+
+        hwdb_bin = strjoin(arg_root, "/", arg_hwdb_bin_dir, "/hwdb.bin", NULL);
+        if (!hwdb_bin)
+                return -ENOMEM;
+
+        mkdir_parents(hwdb_bin, 0755);
+        r = trie_store(trie, hwdb_bin);
+        if (r < 0)
+                return log_error_errno(r, "Failure writing database %s: %m", hwdb_bin);
+
+        return 0;
+}
+
+static void help(void) {
+        printf("Usage: %s OPTIONS COMMAND\n\n"
+               "Options:\n"
+               "  --usr                generate in " UDEVLIBEXECDIR " instead of /etc/udev\n"
+               "  -r,--root=PATH       alternative root path in the filesystem\n"
+               "  -h,--help\n\n"
+               "Commands:\n"
+               "  update               update the hwdb database\n"
+               "  query MODALIAS       query database and print result\n\n",
+               program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+        enum {
+                ARG_USR = 0x100,
+        };
+
+        static const struct option options[] = {
+                { "usr",    no_argument,       NULL, ARG_USR },
+                { "root",   required_argument, NULL, 'r' },
+                { "help",   no_argument,       NULL, 'h' },
+                {}
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "ut:r:h", options, NULL)) >= 0) {
+                switch(c) {
+                case ARG_USR:
+                        arg_hwdb_bin_dir = UDEVLIBEXECDIR;
+                        break;
+                case 'r':
+                        arg_root = optarg;
+                        break;
+                case 'h':
+                        help();
+                        return 0;
+                case '?':
+                        return -EINVAL;
+                default:
+                        assert_not_reached("Unknown option");
+                }
+        }
+
+        return 1;
+}
+
+static int hwdb_main(int argc, char *argv[]) {
+        static const struct {
+                const char *verb;
+                const enum {
+                        MORE,
+                        LESS,
+                        EQUAL,
+                } argc_cmp;
+                const int argc;
+                int (*const dispatch)(char **args, unsigned n);
+        } verbs[] = {
+                { "update", EQUAL, 1, hwdb_update },
+                { "query", EQUAL, 2, hwdb_query },
+        };
+
+        int left;
+        unsigned i;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        left = argc - optind;
+
+        if (left <= 0) {
+                log_error("Missing command.");
+                help();
+                return -EINVAL;
+        }
+
+        for (i = 0; i < ELEMENTSOF(verbs); i++)
+                if (streq(argv[optind], verbs[i].verb))
+                        break;
+
+        if (i >= ELEMENTSOF(verbs)) {
+                log_error("Unknown command %s.", argv[optind]);
+                help();
+                return -EINVAL;
+        }
+
+        switch (verbs[i].argc_cmp) {
+        case EQUAL:
+                if (left != verbs[i].argc) {
+                        log_error("Invalid number of arguments.");
+                        help();
+                        return -EINVAL;
+                }
+
+                break;
+
+        case MORE:
+                if (left < verbs[i].argc) {
+                        log_error("Too few arguments.");
+                        help();
+                        return -EINVAL;
+                }
+
+                break;
+        case LESS:
+                if (left > verbs[i].argc) {
+                        log_error("Too many arguments.");
+                        help();
+                        return -EINVAL;
+                }
+
+                break;
+        default:
+                assert_not_reached("Unknown comparison operator.");
+        }
+
+        return verbs[i].dispatch(argv + optind, left);
+}
+
+int main (int argc, char *argv[]) {
+        int r;
+
+        log_parse_environment();
+        log_open();
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                goto finish;
+
+        r = hwdb_main(argc, argv);
+
+finish:
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/udev/udevadm-hwdb.c b/src/udev/udevadm-hwdb.c
index eb300ff..7a2114a 100644
--- a/src/udev/udevadm-hwdb.c
+++ b/src/udev/udevadm-hwdb.c
@@ -687,5 +687,4 @@ out:
 const struct udevadm_cmd udevadm_hwdb = {
         .name = "hwdb",
         .cmd = adm_hwdb,
-        .help = "maintain the hardware database index",
 };
diff --git a/units/.gitignore b/units/.gitignore
index b8d6247..e44ccfe 100644
--- a/units/.gitignore
+++ b/units/.gitignore
@@ -66,7 +66,7 @@
 /systemd-tmpfiles-setup-dev.service
 /systemd-tmpfiles-setup.service
 /systemd-tmpfiles.service
-/systemd-udev-hwdb-update.service
+/systemd-hwdb-update.service
 /systemd-udev-settle.service
 /systemd-udev-trigger.service
 /systemd-udevd.service
diff --git a/units/systemd-hwdb-update.service.in b/units/systemd-hwdb-update.service.in
new file mode 100644
index 0000000..791528e
--- /dev/null
+++ b/units/systemd-hwdb-update.service.in
@@ -0,0 +1,23 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Rebuild Hardware Database
+Documentation=man:hwdb(7) man:systemd-hwdb(8)
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=systemd-remount-fs.service
+Before=sysinit.target shutdown.target systemd-update-done.service
+ConditionNeedsUpdate=/etc
+ConditionPathExists=|!@udevlibexecdir@/hwdb.bin
+ConditionPathExists=|/etc/udev/hwdb.bin
+ConditionDirectoryNotEmpty=|/etc/udev/hwdb.d/
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootbindir@/systemd-hwdb update
diff --git a/units/systemd-udev-hwdb-update.service.in b/units/systemd-udev-hwdb-update.service.in
deleted file mode 100644
index 5b1f75d..0000000
--- a/units/systemd-udev-hwdb-update.service.in
+++ /dev/null
@@ -1,23 +0,0 @@
-#  This file is part of systemd.
-#
-#  systemd is free software; you can redistribute it and/or modify it
-#  under the terms of the GNU Lesser General Public License as published by
-#  the Free Software Foundation; either version 2.1 of the License, or
-#  (at your option) any later version.
-
-[Unit]
-Description=Rebuild Hardware Database
-Documentation=man:udev(7) man:systemd-udevd.service(8)
-DefaultDependencies=no
-Conflicts=shutdown.target
-After=systemd-remount-fs.service
-Before=sysinit.target shutdown.target systemd-update-done.service
-ConditionNeedsUpdate=/etc
-ConditionPathExists=|!@udevlibexecdir@/hwdb.bin
-ConditionPathExists=|/etc/udev/hwdb.bin
-ConditionDirectoryNotEmpty=|/etc/udev/hwdb.d/
-
-[Service]
-Type=oneshot
-RemainAfterExit=yes
-ExecStart=@rootbindir@/udevadm hwdb --update

commit b98b483bac585af754e8a22ea890db8486905d8a
Author: Alin Rauta <alin.rauta at intel.com>
Date:   Wed Dec 17 07:35:36 2014 -0800

    networkd: add FDB support

diff --git a/Makefile.am b/Makefile.am
index ab07d3b..6896c4b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -5281,6 +5281,7 @@ libsystemd_networkd_core_la_SOURCES = \
 	src/network/networkd-address.c \
 	src/network/networkd-route.c \
 	src/network/networkd-manager.c \
+	src/network/networkd-fdb.c \
 	src/network/networkd-address-pool.c
 
 nodist_libsystemd_networkd_core_la_SOURCES = \
diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 79c7a23..360c57c 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -549,6 +549,28 @@
         </refsect1>
 
         <refsect1>
+                <title>[BridgeFDB] Section Options</title>
+                        <para>The <literal>[BridgeFDB]</literal> section manages the forwarding database table of a port and accepts the following keys. Specify
+                        several <literal>[BridgeFDB]</literal> sections to configure several static MAC table entries.</para>
+
+                        <variablelist class='network-directives'>
+                                <varlistentry>
+                                        <term><varname>MACAddress=</varname></term>
+                                        <listitem>
+                                                <para>As in the <literal>[Network]</literal> section. This key is mandatory.</para>
+                                        </listitem>
+                                </varlistentry>
+                                <varlistentry>
+                                        <term><varname>VLANId=</varname></term>
+                                        <listitem>
+                                                <para>The VLAN Id for the new static MAC table entry.
+                                                If omitted, no VLAN Id info is appended to the new static MAC table entry.</para>
+                                        </listitem>
+                                </varlistentry>
+                        </variablelist>
+        </refsect1>
+
+        <refsect1>
                 <title>Example</title>
                 <example>
                         <title>/etc/systemd/network/50-static.network</title>
diff --git a/src/libsystemd/sd-rtnl/rtnl-message.c b/src/libsystemd/sd-rtnl/rtnl-message.c
index 165e84d..9099440 100644
--- a/src/libsystemd/sd-rtnl/rtnl-message.c
+++ b/src/libsystemd/sd-rtnl/rtnl-message.c
@@ -220,6 +220,58 @@ int sd_rtnl_message_new_route(sd_rtnl *rtnl, sd_rtnl_message **ret,
         return 0;
 }
 
+int sd_rtnl_message_neigh_set_flags(sd_rtnl_message *m, uint8_t flags) {
+        struct ndmsg *ndm;
+
+        assert_return(m, -EINVAL);
+        assert_return(m->hdr, -EINVAL);
+        assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL);
+
+        ndm = NLMSG_DATA(m->hdr);
+        ndm->ndm_flags |= flags;
+
+        return 0;
+}
+
+int sd_rtnl_message_neigh_set_state(sd_rtnl_message *m, uint16_t state) {
+        struct ndmsg *ndm;
+
+        assert_return(m, -EINVAL);
+        assert_return(m->hdr, -EINVAL);
+        assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL);
+
+        ndm = NLMSG_DATA(m->hdr);
+        ndm->ndm_state |= state;
+
+        return 0;
+}
+
+int sd_rtnl_message_neigh_get_flags(sd_rtnl_message *m, uint8_t *flags) {
+        struct ndmsg *ndm;
+
+        assert_return(m, -EINVAL);
+        assert_return(m->hdr, -EINVAL);
+        assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL);
+
+        ndm = NLMSG_DATA(m->hdr);
+        *flags = ndm->ndm_flags;
+
+        return 0;
+}
+
+int sd_rtnl_message_neigh_get_state(sd_rtnl_message *m, uint16_t *state) {
+        struct ndmsg *ndm;
+
+        assert_return(m, -EINVAL);
+        assert_return(m->hdr, -EINVAL);
+        assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL);
+
+        ndm = NLMSG_DATA(m->hdr);
+        *state = ndm->ndm_state;
+
+        return 0;
+}
+
 int sd_rtnl_message_neigh_get_family(sd_rtnl_message *m, int *family) {
         struct ndmsg *ndm;
 
@@ -255,7 +307,9 @@ int sd_rtnl_message_new_neigh(sd_rtnl *rtnl, sd_rtnl_message **ret, uint16_t nlm
         int r;
 
         assert_return(rtnl_message_type_is_neigh(nlmsg_type), -EINVAL);
-        assert_return(ndm_family == AF_INET || ndm_family == AF_INET6, -EINVAL);
+        assert_return(ndm_family == AF_INET  ||
+                      ndm_family == AF_INET6 ||
+                      ndm_family == PF_BRIDGE, -EINVAL);
         assert_return(ret, -EINVAL);
 
         r = message_new(rtnl, ret, nlmsg_type);
diff --git a/src/libsystemd/sd-rtnl/rtnl-types.c b/src/libsystemd/sd-rtnl/rtnl-types.c
index a1db2ab..735ad75 100644
--- a/src/libsystemd/sd-rtnl/rtnl-types.c
+++ b/src/libsystemd/sd-rtnl/rtnl-types.c
@@ -332,15 +332,12 @@ static const NLTypeSystem rtnl_route_type_system = {
 static const NLType rtnl_neigh_types[NDA_MAX + 1] = {
         [NDA_DST]               = { .type = NLA_IN_ADDR },
         [NDA_LLADDR]            = { .type = NLA_ETHER_ADDR },
-/*
-        NDA_CACHEINFO,
-        NDA_PROBES,
-        NDA_VLAN,
-        NDA_PORT
-        NDA_VNI
-        NDA_IFINDEX
-        NDA_MASTER
-*/
+        [NDA_CACHEINFO]         = { .type = NLA_CACHE_INFO, .size = sizeof(struct nda_cacheinfo) },
+        [NDA_PROBES]            = { .type = NLA_U32 },
+        [NDA_VLAN]              = { .type = NLA_U16 },
+        [NDA_PORT]              = { .type = NLA_U16 },
+        [NDA_VNI]               = { .type = NLA_U32 },
+        [NDA_IFINDEX]           = { .type = NLA_U32 },
 };
 
 static const NLTypeSystem rtnl_neigh_type_system = {
diff --git a/src/network/networkd-fdb.c b/src/network/networkd-fdb.c
new file mode 100644
index 0000000..9bb45e3
--- /dev/null
+++ b/src/network/networkd-fdb.c
@@ -0,0 +1,252 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <ctype.h>
+#include <net/if.h>
+#include <net/ethernet.h>
+
+#include "networkd.h"
+#include "networkd-netdev.h"
+#include "networkd-link.h"
+#include "network-internal.h"
+#include "path-util.h"
+#include "conf-files.h"
+#include "conf-parser.h"
+#include "util.h"
+
+/* create a new FDB entry or get an existing one. */
+int fdb_entry_new_static(Network *const network,
+                         const unsigned section,
+                         FdbEntry **ret) {
+        _cleanup_fdbentry_free_ FdbEntry *fdb_entry = NULL;
+        struct ether_addr *mac_addr = NULL;
+
+        assert(network);
+
+        /* search entry in hashmap first. */
+        if(section) {
+                fdb_entry = hashmap_get(network->fdb_entries_by_section, UINT_TO_PTR(section));
+                if (fdb_entry) {
+                        *ret = fdb_entry;
+                        fdb_entry = NULL;
+
+                        return 0;
+                }
+        }
+
+        /* allocate space for MAC address. */
+        mac_addr = new0(struct ether_addr, 1);
+        if (!mac_addr)
+                return -ENOMEM;
+
+        /* allocate space for and FDB entry. */
+        fdb_entry = new0(FdbEntry, 1);
+
+        if (!fdb_entry) {
+                /* free previously allocated space for mac_addr. */
+                free(mac_addr);
+                return -ENOMEM;
+        }
+
+        /* init FDB structure. */
+        fdb_entry->network = network;
+        fdb_entry->mac_addr = mac_addr;
+
+        LIST_PREPEND(static_fdb_entries, network->static_fdb_entries, fdb_entry);
+
+        if (section) {
+                fdb_entry->section = section;
+                hashmap_put(network->fdb_entries_by_section,
+                            UINT_TO_PTR(fdb_entry->section), fdb_entry);
+        }
+
+        /* return allocated FDB structure. */
+        *ret = fdb_entry;
+        fdb_entry = NULL;
+
+        return 0;
+}
+
+static int set_fdb_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
+        _cleanup_fdbentry_free_ FdbEntry *fdb_entry = userdata;
+        int r;
+
+        assert(fdb_entry);
+
+        r = sd_rtnl_message_get_errno(m);
+        if ((r < 0) && (r != (-EEXIST)))
+                log_error("Could not add FDB entry for interface: %s error: %s",
+                          fdb_entry->network->match_name, strerror(-r));
+
+        return 1;
+}
+
+/* send a request to the kernel to add a FDB entry in its static MAC table. */
+int fdb_entry_configure(sd_rtnl *const rtnl,
+                        FdbEntry *const fdb_entry,
+                        const int ifindex) {
+        _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL;
+        int r;
+
+        assert(fdb_entry);
+        assert(rtnl);
+
+        /* create new RTM message */
+        r = sd_rtnl_message_new_neigh(rtnl, &req, RTM_NEWNEIGH, ifindex, PF_BRIDGE);
+        if (r < 0)
+                return rtnl_log_create_error(r);
+
+        /* only NTF_SELF flag supported. */
+        r = sd_rtnl_message_neigh_set_flags(req, NTF_SELF);
+        if (r < 0)
+                return rtnl_log_create_error(r);
+
+        /* only NUD_PERMANENT state supported. */
+        r = sd_rtnl_message_neigh_set_state(req, NUD_NOARP | NUD_PERMANENT);
+        if (r < 0)
+                return rtnl_log_create_error(r);
+
+        r = sd_rtnl_message_append_ether_addr(req, NDA_LLADDR, fdb_entry->mac_addr);
+        if (r < 0)
+                return rtnl_log_create_error(r);
+
+        /* VLAN Id is optional. We'll add VLAN Id only if it's specified. */
+        if (0 != fdb_entry->vlan_id) {
+                r = sd_rtnl_message_append_u16(req, NDA_VLAN, fdb_entry->vlan_id);
+                if (r < 0)
+                        return rtnl_log_create_error(r);
+        }
+
+        /* send message to the kernel to update its internal static MAC table. */
+        r = sd_rtnl_call_async(rtnl, req, set_fdb_handler, fdb_entry, 0, NULL);
+        if (r < 0) {
+                log_error("Could not send rtnetlink message: %s", strerror(-r));
+                return r;
+        }
+
+        return 0;
+}
+
+/* remove and FDB entry. */
+void fdb_entry_free(FdbEntry *fdb_entry) {
+        if(!fdb_entry)
+                return;
+
+        if(fdb_entry->network) {
+                LIST_REMOVE(static_fdb_entries, fdb_entry->network->static_fdb_entries,
+                            fdb_entry);
+
+                if(fdb_entry->section)
+                    hashmap_remove(fdb_entry->network->fdb_entries_by_section,
+                                   UINT_TO_PTR(fdb_entry->section));
+        }
+
+        free(fdb_entry->mac_addr);
+
+        free(fdb_entry);
+}
+
+/* parse the HW address from config files. */
+int config_parse_fdb_hwaddr(const char *unit,
+                            const char *filename,
+                            unsigned line,
+                            const char *section,
+                            unsigned section_line,
+                            const char *lvalue,
+                            int ltype,
+                            const char *rvalue,
+                            void *data,
+                            void *userdata) {
+        Network *network = userdata;
+        _cleanup_fdbentry_free_ FdbEntry *fdb_entry = NULL;
+        int r;
+
+        assert(filename);
+        assert(section);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        r = fdb_entry_new_static(network, section_line, &fdb_entry);
+        if (r < 0) {
+                log_error("Failed to allocate a new FDB entry: %s", strerror(-r));
+                return r;
+        }
+
+        /* read in the MAC address for the FDB table. */
+        r = sscanf(rvalue, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
+                   &fdb_entry->mac_addr->ether_addr_octet[0],
+                   &fdb_entry->mac_addr->ether_addr_octet[1],
+                   &fdb_entry->mac_addr->ether_addr_octet[2],
+                   &fdb_entry->mac_addr->ether_addr_octet[3],
+                   &fdb_entry->mac_addr->ether_addr_octet[4],
+                   &fdb_entry->mac_addr->ether_addr_octet[5]);
+
+        if (ETHER_ADDR_LEN !=  r) {
+                log_syntax(unit, LOG_ERR, filename, line, EINVAL,
+                           "Not a valid MAC address, ignoring assignment: %s", rvalue);
+                return 0;
+        }
+
+        fdb_entry = NULL;
+
+        return 0;
+}
+
+/* parse the VLAN Id from config files. */
+int config_parse_fdb_vlan_id(const char *unit,
+                             const char *filename,
+                             unsigned line,
+                             const char *section,
+                             unsigned section_line,
+                             const char *lvalue,
+                             int ltype,
+                             const char *rvalue,
+                             void *data,
+                             void *userdata) {
+        Network *network = userdata;
+        _cleanup_fdbentry_free_ FdbEntry *fdb_entry = NULL;
+        int r;
+
+        assert(filename);
+        assert(section);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        r = fdb_entry_new_static(network, section_line, &fdb_entry);
+        if (r < 0) {
+                log_error("Failed to allocate a new FDB entry: %s", strerror(-r));
+                return r;
+        }
+
+        r = config_parse_unsigned(unit, filename, line, section,
+                                  section_line, lvalue, ltype,
+                                  rvalue, &fdb_entry->vlan_id, userdata);
+        if (r < 0) {
+                log_error("Failed to parse the unsigned integer: %s", strerror(-r));
+                return r;
+        }
+
+        fdb_entry = NULL;
+
+        return 0;
+}
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 08f724e..341ae88 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -656,6 +656,21 @@ int link_address_drop_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata)
         return 1;
 }
 
+static int link_set_bridge_fdb(const Link *const link) {
+        FdbEntry *fdb_entry;
+        int r = 0;
+
+        LIST_FOREACH(static_fdb_entries, fdb_entry, link->network->static_fdb_entries) {
+                r = fdb_entry_configure(link->manager->rtnl, fdb_entry, link->ifindex);
+                if(r < 0) {
+                        log_link_error(link, "Failed to add MAC entry to static MAC table: %s", strerror(-r));
+                        break;
+                }
+        }
+
+        return r;
+}
+
 static int link_set_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
         _cleanup_link_unref_ Link *link = userdata;
         int r;
@@ -1147,6 +1162,10 @@ static int link_configure(Link *link) {
         assert(link->network);
         assert(link->state == LINK_STATE_PENDING);
 
+        r = link_set_bridge_fdb(link);
+        if (r < 0)
+                return r;
+
         if (link_ipv4ll_enabled(link)) {
                 r = ipv4ll_configure(link);
                 if (r < 0)
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 640a3a2..fb0a209 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -62,6 +62,8 @@ DHCP.CriticalConnection,     config_parse_bool,                  0,
 DHCP.VendorClassIdentifier,  config_parse_string,                0,                             offsetof(Network, dhcp_vendor_class_identifier)
 DHCP.RouteMetric,            config_parse_unsigned,              0,                             offsetof(Network, dhcp_route_metric)
 Bridge.Cost,                 config_parse_unsigned,              0,                             offsetof(Network, cost)
+BridgeFDB.MACAddress,        config_parse_fdb_hwaddr,            0,                             0
+BridgeFDB.VLANId,            config_parse_fdb_vlan_id,           0,                             0
 /* backwards compatibility: do not add new entries to this section */
 DHCPv4.UseDNS,               config_parse_bool,                  0,                             offsetof(Network, dhcp_dns)
 DHCPv4.UseMTU,               config_parse_bool,                  0,                             offsetof(Network, dhcp_mtu)
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index ef9e0a8..966b59b 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -62,6 +62,7 @@ static int network_load_one(Manager *manager, const char *filename) {
 
         LIST_HEAD_INIT(network->static_addresses);
         LIST_HEAD_INIT(network->static_routes);
+        LIST_HEAD_INIT(network->static_fdb_entries);
 
         network->stacked_netdevs = hashmap_new(&string_hash_ops);
         if (!network->stacked_netdevs)
@@ -75,6 +76,10 @@ static int network_load_one(Manager *manager, const char *filename) {
         if (!network->routes_by_section)
                 return log_oom();
 
+        network->fdb_entries_by_section = hashmap_new(NULL);
+        if (!network->fdb_entries_by_section)
+                return log_oom();
+
         network->filename = strdup(filename);
         if (!network->filename)
                 return log_oom();
@@ -97,7 +102,8 @@ static int network_load_one(Manager *manager, const char *filename) {
                          "Route\0"
                          "DHCP\0"
                          "DHCPv4\0"
-                         "Bridge\0",
+                         "Bridge\0"
+                         "BridgeFDB\0",
                          config_item_perf_lookup, network_network_gperf_lookup,
                          false, false, true, network);
         if (r < 0)
@@ -154,6 +160,7 @@ void network_free(Network *network) {
         NetDev *netdev;
         Route *route;
         Address *address;
+        FdbEntry *fdb_entry;
         Iterator i;
 
         if (!network)
@@ -192,8 +199,12 @@ void network_free(Network *network) {
         while ((address = network->static_addresses))
                 address_free(address);
 
+        while ((fdb_entry = network->static_fdb_entries))
+                fdb_entry_free(fdb_entry);
+
         hashmap_free(network->addresses_by_section);
         hashmap_free(network->routes_by_section);
+        hashmap_free(network->fdb_entries_by_section);
 
         if (network->manager && network->manager->networks)
                 LIST_REMOVE(networks, network->manager->networks, network);
diff --git a/src/network/networkd.h b/src/network/networkd.h
index 4cdcd73..a5c5b08 100644
--- a/src/network/networkd.h
+++ b/src/network/networkd.h
@@ -51,6 +51,7 @@ typedef struct Address Address;
 typedef struct Route Route;
 typedef struct Manager Manager;
 typedef struct AddressPool AddressPool;
+typedef struct FdbEntry FdbEntry;
 
 typedef enum DHCPSupport {
         DHCP_SUPPORT_NONE,
@@ -69,6 +70,16 @@ typedef enum LLMNRSupport {
         _LLMNR_SUPPORT_INVALID = -1,
 } LLMNRSupport;
 
+struct FdbEntry {
+        Network *network;
+        unsigned section;
+
+        struct ether_addr *mac_addr;
+        uint16_t vlan_id;
+
+        LIST_FIELDS(FdbEntry, static_fdb_entries);
+};
+
 struct Network {
         Manager *manager;
 
@@ -113,9 +124,11 @@ struct Network {
 
         LIST_HEAD(Address, static_addresses);
         LIST_HEAD(Route, static_routes);
+        LIST_HEAD(FdbEntry, static_fdb_entries);
 
         Hashmap *addresses_by_section;
         Hashmap *routes_by_section;
+        Hashmap *fdb_entries_by_section;
 
         bool wildcard_domain;
         char **domains, **dns, **ntp;
@@ -327,6 +340,22 @@ int config_parse_label(const char *unit, const char *filename, unsigned line,
                        const char *section, unsigned section_line, const char *lvalue,
                        int ltype, const char *rvalue, void *data, void *userdata);
 
+/* Forwarding database table. */
+int fdb_entry_configure(sd_rtnl *const rtnl, FdbEntry *const fdb_entry, const int ifindex);
+void fdb_entry_free(FdbEntry *fdb_entry);
+int fdb_entry_new_static(Network *const network, const unsigned section, FdbEntry **ret);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(FdbEntry*, fdb_entry_free);
+#define _cleanup_fdbentry_free_ _cleanup_(fdb_entry_freep)
+
+int config_parse_fdb_hwaddr(const char *unit, const char *filename, unsigned line,
+                            const char *section, unsigned section_line, const char *lvalue,
+                            int ltype, const char *rvalue, void *data, void *userdata);
+
+int config_parse_fdb_vlan_id(const char *unit, const char *filename, unsigned line,
+                             const char *section, unsigned section_line, const char *lvalue,
+                             int ltype, const char *rvalue, void *data, void *userdata);
+
 /* DHCP support */
 
 const char* dhcp_support_to_string(DHCPSupport i) _const_;
diff --git a/src/systemd/sd-rtnl.h b/src/systemd/sd-rtnl.h
index b05f83c..b8836e2 100644
--- a/src/systemd/sd-rtnl.h
+++ b/src/systemd/sd-rtnl.h
@@ -109,8 +109,12 @@ int sd_rtnl_message_route_get_family(sd_rtnl_message *m, int *family);
 int sd_rtnl_message_route_get_dst_prefixlen(sd_rtnl_message *m, unsigned char *dst_len);
 int sd_rtnl_message_route_get_src_prefixlen(sd_rtnl_message *m, unsigned char *src_len);
 
+int sd_rtnl_message_neigh_set_flags(sd_rtnl_message *m, uint8_t flags);
+int sd_rtnl_message_neigh_set_state(sd_rtnl_message *m, uint16_t state);
 int sd_rtnl_message_neigh_get_family(sd_rtnl_message *m, int *family);
 int sd_rtnl_message_neigh_get_ifindex(sd_rtnl_message *m, int *family);
+int sd_rtnl_message_neigh_get_state(sd_rtnl_message *m, uint16_t *state);
+int sd_rtnl_message_neigh_get_flags(sd_rtnl_message *m, uint8_t *flags);
 
 int sd_rtnl_message_append_string(sd_rtnl_message *m, unsigned short type, const char *data);
 int sd_rtnl_message_append_u8(sd_rtnl_message *m, unsigned short type, uint8_t data);



More information about the systemd-commits mailing list