summaryrefslogtreecommitdiff
path: root/src/grp-udev/libudev-core
diff options
context:
space:
mode:
Diffstat (limited to 'src/grp-udev/libudev-core')
-rw-r--r--src/grp-udev/libudev-core/Makefile99
l---------src/grp-udev/libudev-core/logind-acl.c1
l---------src/grp-udev/libudev-core/logind-acl.h1
-rw-r--r--src/grp-udev/libudev-core/net/.gitignore1
-rw-r--r--src/grp-udev/libudev-core/net/Makefile29
-rw-r--r--src/grp-udev/libudev-core/net/ethtool-util.c327
-rw-r--r--src/grp-udev/libudev-core/net/ethtool-util.h65
-rw-r--r--src/grp-udev/libudev-core/net/link-config-gperf.gperf44
-rw-r--r--src/grp-udev/libudev-core/net/link-config.c517
-rw-r--r--src/grp-udev/libudev-core/net/link-config.h100
l---------src/grp-udev/libudev-core/sd-login.c1
-rw-r--r--src/grp-udev/libudev-core/udev-builtin-blkid.c337
-rw-r--r--src/grp-udev/libudev-core/udev-builtin-btrfs.c58
-rw-r--r--src/grp-udev/libudev-core/udev-builtin-hwdb.c222
-rw-r--r--src/grp-udev/libudev-core/udev-builtin-input_id.c341
-rw-r--r--src/grp-udev/libudev-core/udev-builtin-keyboard.c278
-rw-r--r--src/grp-udev/libudev-core/udev-builtin-kmod.c123
-rw-r--r--src/grp-udev/libudev-core/udev-builtin-net_id.c639
-rw-r--r--src/grp-udev/libudev-core/udev-builtin-net_setup_link.c107
-rw-r--r--src/grp-udev/libudev-core/udev-builtin-path_id.c770
-rw-r--r--src/grp-udev/libudev-core/udev-builtin-uaccess.c89
-rw-r--r--src/grp-udev/libudev-core/udev-builtin-usb_id.c473
-rw-r--r--src/grp-udev/libudev-core/udev-builtin.c142
-rw-r--r--src/grp-udev/libudev-core/udev-ctrl.c461
-rw-r--r--src/grp-udev/libudev-core/udev-event.c943
-rw-r--r--src/grp-udev/libudev-core/udev-node.c375
-rw-r--r--src/grp-udev/libudev-core/udev-rules.c2582
-rw-r--r--src/grp-udev/libudev-core/udev-watch.c152
28 files changed, 9277 insertions, 0 deletions
diff --git a/src/grp-udev/libudev-core/Makefile b/src/grp-udev/libudev-core/Makefile
new file mode 100644
index 0000000000..14d4e827a0
--- /dev/null
+++ b/src/grp-udev/libudev-core/Makefile
@@ -0,0 +1,99 @@
+# -*- Mode: makefile; indent-tabs-mode: t -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010-2012 Lennart Poettering
+# Copyright 2010-2012 Kay Sievers
+# Copyright 2013 Zbigniew Jędrzejewski-Szmek
+# Copyright 2013 David Strauss
+# Copyright 2016 Luke Shumaker
+#
+# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk
+include $(topsrcdir)/build-aux/Makefile.head.mk
+
+noinst_LTLIBRARIES += \
+ libudev-core.la
+
+$(outdir)/keyboard-keys-list.txt:
+ $(AM_V_GEN)$(CPP) $(sd.ALL_CPPFLAGS) -dM -include linux/input.h - < /dev/null | $(AWK) '/^#define[ \t]+KEY_[^ ]+[ \t]+[0-9K]/ { if ($$2 != "KEY_MAX") { print $$2 } }' > $@
+
+$(outdir)/keyboard-keys-from-name.gperf: $(outdir)/keyboard-keys-list.txt
+ $(AM_V_GEN)$(AWK) 'BEGIN{ print "struct key { const char* name; unsigned short id; };"; print "%null-strings"; print "%%";} { print tolower(substr($$1 ,5)) ", " $$1 }' < $< > $@
+
+$(outdir)/keyboard-keys-from-name.h: $(outdir)/keyboard-keys-from-name.gperf
+ $(AM_V_GPERF)$(GPERF) -L ANSI-C -t -N keyboard_lookup_key -H hash_key_name -p -C < $< > $@
+
+gperf_txt_sources += \
+ src/udev/keyboard-keys-list.txt
+
+libudev_core_la_SOURCES = \
+ src/udev/udev.h \
+ src/udev/udev-event.c \
+ src/udev/udev-watch.c \
+ src/udev/udev-node.c \
+ src/udev/udev-rules.c \
+ src/udev/udev-ctrl.c \
+ src/udev/udev-builtin.c \
+ src/udev/udev-builtin-btrfs.c \
+ src/udev/udev-builtin-hwdb.c \
+ src/udev/udev-builtin-input_id.c \
+ src/udev/udev-builtin-keyboard.c \
+ src/udev/udev-builtin-net_id.c \
+ src/udev/udev-builtin-net_setup_link.c \
+ src/udev/udev-builtin-path_id.c \
+ src/udev/udev-builtin-usb_id.c \
+ src/udev/net/link-config.h \
+ src/udev/net/link-config.c \
+ src/udev/net/ethtool-util.h \
+ src/udev/net/ethtool-util.c
+
+nodist_libudev_core_la_SOURCES = \
+ src/udev/keyboard-keys-from-name.h \
+ src/udev/net/link-config-gperf.c
+
+gperf_gperf_sources += \
+ src/udev/net/link-config-gperf.gperf
+
+libudev_core_la_CFLAGS = \
+ $(BLKID_CFLAGS) \
+ $(KMOD_CFLAGS)
+
+libudev_core_la_LIBADD = \
+ libsystemd-network.la \
+ libsystemd-shared.la \
+ $(BLKID_LIBS) \
+ $(KMOD_LIBS)
+
+ifneq ($(HAVE_KMOD),)
+libudev_core_la_SOURCES += \
+ src/udev/udev-builtin-kmod.c
+endif # HAVE_KMOD
+
+ifneq ($(HAVE_BLKID),)
+libudev_core_la_SOURCES += \
+ src/udev/udev-builtin-blkid.c
+endif # HAVE_BLKID
+
+ifneq ($(HAVE_ACL),)
+libudev_core_la_SOURCES += \
+ src/udev/udev-builtin-uaccess.c \
+ src/login/logind-acl.c \
+ src/libsystemd/sd-login/sd-login.c \
+ src/systemd/sd-login.h
+endif # HAVE_ACL
+
+nested.subdirs += net
+
+include $(topsrcdir)/build-aux/Makefile.tail.mk
diff --git a/src/grp-udev/libudev-core/logind-acl.c b/src/grp-udev/libudev-core/logind-acl.c
new file mode 120000
index 0000000000..dd15b7973f
--- /dev/null
+++ b/src/grp-udev/libudev-core/logind-acl.c
@@ -0,0 +1 @@
+../../grp-login/systemd-logind/logind-acl.c \ No newline at end of file
diff --git a/src/grp-udev/libudev-core/logind-acl.h b/src/grp-udev/libudev-core/logind-acl.h
new file mode 120000
index 0000000000..6065dde301
--- /dev/null
+++ b/src/grp-udev/libudev-core/logind-acl.h
@@ -0,0 +1 @@
+../../grp-login/systemd-logind/logind-acl.h \ No newline at end of file
diff --git a/src/grp-udev/libudev-core/net/.gitignore b/src/grp-udev/libudev-core/net/.gitignore
new file mode 100644
index 0000000000..9ca85bacc9
--- /dev/null
+++ b/src/grp-udev/libudev-core/net/.gitignore
@@ -0,0 +1 @@
+/link-config-gperf.c
diff --git a/src/grp-udev/libudev-core/net/Makefile b/src/grp-udev/libudev-core/net/Makefile
new file mode 100644
index 0000000000..ac615aad3b
--- /dev/null
+++ b/src/grp-udev/libudev-core/net/Makefile
@@ -0,0 +1,29 @@
+# -*- Mode: makefile; indent-tabs-mode: t -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010-2012 Lennart Poettering
+# Copyright 2010-2012 Kay Sievers
+# Copyright 2013 Zbigniew Jędrzejewski-Szmek
+# Copyright 2013 David Strauss
+# Copyright 2016 Luke Shumaker
+#
+# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../../config.mk
+include $(topsrcdir)/build-aux/Makefile.head.mk
+
+sd.CPPFLAGS += $(libsystemd-shared.CPPFLAGS)
+sd.CPPFLAGS += $(libsystemd-network.CPPFLAGS)
+
+include $(topsrcdir)/build-aux/Makefile.tail.mk
diff --git a/src/grp-udev/libudev-core/net/ethtool-util.c b/src/grp-udev/libudev-core/net/ethtool-util.c
new file mode 100644
index 0000000000..05d6ecb953
--- /dev/null
+++ b/src/grp-udev/libudev-core/net/ethtool-util.c
@@ -0,0 +1,327 @@
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2013 Tom Gundersen <teg@jklm.no>
+
+ 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 <net/if.h>
+#include <sys/ioctl.h>
+
+#include <linux/ethtool.h>
+#include <linux/sockios.h>
+
+#include "systemd-basic/log.h"
+#include "systemd-basic/socket-util.h"
+#include "systemd-basic/string-table.h"
+#include "systemd-basic/strxcpyx.h"
+#include "systemd-basic/util.h"
+#include "systemd-shared/conf-parser.h"
+
+#include "ethtool-util.h"
+
+static const char* const duplex_table[_DUP_MAX] = {
+ [DUP_FULL] = "full",
+ [DUP_HALF] = "half"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(duplex, Duplex);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_duplex, duplex, Duplex, "Failed to parse duplex setting");
+
+static const char* const wol_table[_WOL_MAX] = {
+ [WOL_PHY] = "phy",
+ [WOL_MAGIC] = "magic",
+ [WOL_OFF] = "off"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(wol, WakeOnLan);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_wol, wol, WakeOnLan, "Failed to parse WakeOnLan setting");
+
+static const char* const netdev_feature_table[_NET_DEV_FEAT_MAX] = {
+ [NET_DEV_FEAT_GSO] = "tx-generic-segmentation",
+ [NET_DEV_FEAT_GRO] = "rx-gro",
+ [NET_DEV_FEAT_LRO] = "rx-lro",
+ [NET_DEV_FEAT_TSO] = "tx-tcp-segmentation",
+ [NET_DEV_FEAT_UFO] = "tx-udp-fragmentation",
+};
+
+int ethtool_connect(int *ret) {
+ int fd;
+
+ assert_return(ret, -EINVAL);
+
+ fd = socket_ioctl_fd();
+ if (fd < 0)
+ return fd;
+ *ret = fd;
+
+ return 0;
+}
+
+int ethtool_get_driver(int *fd, const char *ifname, char **ret) {
+ struct ethtool_drvinfo ecmd = {
+ .cmd = ETHTOOL_GDRVINFO
+ };
+ struct ifreq ifr = {
+ .ifr_data = (void*) &ecmd
+ };
+ char *d;
+ int r;
+
+ if (*fd < 0) {
+ r = ethtool_connect(fd);
+ if (r < 0)
+ return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
+ }
+
+ strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
+
+ r = ioctl(*fd, SIOCETHTOOL, &ifr);
+ if (r < 0)
+ return -errno;
+
+ d = strdup(ecmd.driver);
+ if (!d)
+ return -ENOMEM;
+
+ *ret = d;
+ return 0;
+}
+
+int ethtool_set_speed(int *fd, const char *ifname, unsigned int speed, Duplex duplex) {
+ struct ethtool_cmd ecmd = {
+ .cmd = ETHTOOL_GSET
+ };
+ struct ifreq ifr = {
+ .ifr_data = (void*) &ecmd
+ };
+ bool need_update = false;
+ int r;
+
+ if (speed == 0 && duplex == _DUP_INVALID)
+ return 0;
+
+ if (*fd < 0) {
+ r = ethtool_connect(fd);
+ if (r < 0)
+ return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
+ }
+
+ strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
+
+ r = ioctl(*fd, SIOCETHTOOL, &ifr);
+ if (r < 0)
+ return -errno;
+
+ if (ethtool_cmd_speed(&ecmd) != speed) {
+ ethtool_cmd_speed_set(&ecmd, speed);
+ need_update = true;
+ }
+
+ switch (duplex) {
+ case DUP_HALF:
+ if (ecmd.duplex != DUPLEX_HALF) {
+ ecmd.duplex = DUPLEX_HALF;
+ need_update = true;
+ }
+ break;
+ case DUP_FULL:
+ if (ecmd.duplex != DUPLEX_FULL) {
+ ecmd.duplex = DUPLEX_FULL;
+ need_update = true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (need_update) {
+ ecmd.cmd = ETHTOOL_SSET;
+
+ r = ioctl(*fd, SIOCETHTOOL, &ifr);
+ if (r < 0)
+ return -errno;
+ }
+
+ return 0;
+}
+
+int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol) {
+ struct ethtool_wolinfo ecmd = {
+ .cmd = ETHTOOL_GWOL
+ };
+ struct ifreq ifr = {
+ .ifr_data = (void*) &ecmd
+ };
+ bool need_update = false;
+ int r;
+
+ if (wol == _WOL_INVALID)
+ return 0;
+
+ if (*fd < 0) {
+ r = ethtool_connect(fd);
+ if (r < 0)
+ return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
+ }
+
+ strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
+
+ r = ioctl(*fd, SIOCETHTOOL, &ifr);
+ if (r < 0)
+ return -errno;
+
+ switch (wol) {
+ case WOL_PHY:
+ if (ecmd.wolopts != WAKE_PHY) {
+ ecmd.wolopts = WAKE_PHY;
+ need_update = true;
+ }
+ break;
+ case WOL_MAGIC:
+ if (ecmd.wolopts != WAKE_MAGIC) {
+ ecmd.wolopts = WAKE_MAGIC;
+ need_update = true;
+ }
+ break;
+ case WOL_OFF:
+ if (ecmd.wolopts != 0) {
+ ecmd.wolopts = 0;
+ need_update = true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (need_update) {
+ ecmd.cmd = ETHTOOL_SWOL;
+
+ r = ioctl(*fd, SIOCETHTOOL, &ifr);
+ if (r < 0)
+ return -errno;
+ }
+
+ return 0;
+}
+
+static int ethtool_get_stringset(int *fd, struct ifreq *ifr, int stringset_id, struct ethtool_gstrings **gstrings) {
+ _cleanup_free_ struct ethtool_gstrings *strings = NULL;
+ struct {
+ struct ethtool_sset_info info;
+ uint32_t space;
+ } buffer = {
+ .info = {
+ .cmd = ETHTOOL_GSSET_INFO,
+ .sset_mask = UINT64_C(1) << stringset_id,
+ },
+ };
+ unsigned len;
+ int r;
+
+ ifr->ifr_data = (void *) &buffer.info;
+
+ r = ioctl(*fd, SIOCETHTOOL, ifr);
+ if (r < 0)
+ return -errno;
+
+ if (!buffer.info.sset_mask)
+ return -EINVAL;
+
+ len = buffer.info.data[0];
+
+ strings = malloc0(sizeof(struct ethtool_gstrings) + len * ETH_GSTRING_LEN);
+ if (!strings)
+ return -ENOMEM;
+
+ strings->cmd = ETHTOOL_GSTRINGS;
+ strings->string_set = stringset_id;
+ strings->len = len;
+
+ ifr->ifr_data = (void *) strings;
+
+ r = ioctl(*fd, SIOCETHTOOL, ifr);
+ if (r < 0)
+ return -errno;
+
+ *gstrings = strings;
+ strings = NULL;
+
+ return 0;
+}
+
+static int find_feature_index(struct ethtool_gstrings *strings, const char *feature) {
+ unsigned i;
+
+ for (i = 0; i < strings->len; i++) {
+ if (streq((char *) &strings->data[i * ETH_GSTRING_LEN], feature))
+ return i;
+ }
+
+ return -1;
+}
+
+int ethtool_set_features(int *fd, const char *ifname, NetDevFeature *features) {
+ _cleanup_free_ struct ethtool_gstrings *strings = NULL;
+ struct ethtool_sfeatures *sfeatures;
+ int block, bit, i, r;
+ struct ifreq ifr = {};
+
+ if (*fd < 0) {
+ r = ethtool_connect(fd);
+ if (r < 0)
+ return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
+ }
+
+ strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
+
+ r = ethtool_get_stringset(fd, &ifr, ETH_SS_FEATURES, &strings);
+ if (r < 0)
+ return log_warning_errno(r, "link_config: could not get ethtool features for %s", ifname);
+
+ sfeatures = alloca0(sizeof(struct ethtool_gstrings) + DIV_ROUND_UP(strings->len, 32U) * sizeof(sfeatures->features[0]));
+ sfeatures->cmd = ETHTOOL_SFEATURES;
+ sfeatures->size = DIV_ROUND_UP(strings->len, 32U);
+
+ for (i = 0; i < _NET_DEV_FEAT_MAX; i++) {
+
+ if (features[i] != -1) {
+
+ r = find_feature_index(strings, netdev_feature_table[i]);
+ if (r < 0) {
+ log_warning_errno(r, "link_config: could not find feature: %s", netdev_feature_table[i]);
+ continue;
+ }
+
+ block = r / 32;
+ bit = r % 32;
+
+ sfeatures->features[block].valid |= 1 << bit;
+
+ if (features[i])
+ sfeatures->features[block].requested |= 1 << bit;
+ else
+ sfeatures->features[block].requested &= ~(1 << bit);
+ }
+ }
+
+ ifr.ifr_data = (void *) sfeatures;
+
+ r = ioctl(*fd, SIOCETHTOOL, &ifr);
+ if (r < 0)
+ return log_warning_errno(r, "link_config: could not set ethtool features for %s", ifname);
+
+ return 0;
+}
diff --git a/src/grp-udev/libudev-core/net/ethtool-util.h b/src/grp-udev/libudev-core/net/ethtool-util.h
new file mode 100644
index 0000000000..6b5dfdd513
--- /dev/null
+++ b/src/grp-udev/libudev-core/net/ethtool-util.h
@@ -0,0 +1,65 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2013 Tom Gundersen <teg@jklm.no>
+
+ 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 "systemd-basic/macro.h"
+
+/* we can't use DUPLEX_ prefix, as it
+ * clashes with <linux/ethtool.h> */
+typedef enum Duplex {
+ DUP_FULL,
+ DUP_HALF,
+ _DUP_MAX,
+ _DUP_INVALID = -1
+} Duplex;
+
+typedef enum WakeOnLan {
+ WOL_PHY,
+ WOL_MAGIC,
+ WOL_OFF,
+ _WOL_MAX,
+ _WOL_INVALID = -1
+} WakeOnLan;
+
+typedef enum NetDevFeature {
+ NET_DEV_FEAT_GSO,
+ NET_DEV_FEAT_GRO,
+ NET_DEV_FEAT_LRO,
+ NET_DEV_FEAT_TSO,
+ NET_DEV_FEAT_UFO,
+ _NET_DEV_FEAT_MAX,
+ _NET_DEV_FEAT_INVALID = -1
+} NetDevFeature;
+
+int ethtool_connect(int *ret);
+
+int ethtool_get_driver(int *fd, const char *ifname, char **ret);
+int ethtool_set_speed(int *fd, const char *ifname, unsigned int speed, Duplex duplex);
+int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol);
+int ethtool_set_features(int *fd, const char *ifname, NetDevFeature *features);
+
+const char *duplex_to_string(Duplex d) _const_;
+Duplex duplex_from_string(const char *d) _pure_;
+
+const char *wol_to_string(WakeOnLan wol) _const_;
+WakeOnLan wol_from_string(const char *wol) _pure_;
+
+int config_parse_duplex(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_wol(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);
diff --git a/src/grp-udev/libudev-core/net/link-config-gperf.gperf b/src/grp-udev/libudev-core/net/link-config-gperf.gperf
new file mode 100644
index 0000000000..89184782ed
--- /dev/null
+++ b/src/grp-udev/libudev-core/net/link-config-gperf.gperf
@@ -0,0 +1,44 @@
+%{
+#include <stddef.h>
+
+#include "systemd-network/network-internal.h"
+#include "systemd-shared/conf-parser.h"
+
+#include "ethtool-util.h"
+#include "link-config.h"
+%}
+struct ConfigPerfItem;
+%null_strings
+%language=ANSI-C
+%define slot-name section_and_lvalue
+%define hash-function-name link_config_gperf_hash
+%define lookup-function-name link_config_gperf_lookup
+%readonly-tables
+%omit-struct-type
+%struct-type
+%includes
+%%
+Match.MACAddress, config_parse_hwaddr, 0, offsetof(link_config, match_mac)
+Match.OriginalName, config_parse_ifnames, 0, offsetof(link_config, match_name)
+Match.Path, config_parse_strv, 0, offsetof(link_config, match_path)
+Match.Driver, config_parse_strv, 0, offsetof(link_config, match_driver)
+Match.Type, config_parse_strv, 0, offsetof(link_config, match_type)
+Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(link_config, match_host)
+Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(link_config, match_virt)
+Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(link_config, match_kernel)
+Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(link_config, match_arch)
+Link.Description, config_parse_string, 0, offsetof(link_config, description)
+Link.MACAddressPolicy, config_parse_mac_policy, 0, offsetof(link_config, mac_policy)
+Link.MACAddress, config_parse_hwaddr, 0, offsetof(link_config, mac)
+Link.NamePolicy, config_parse_name_policy, 0, offsetof(link_config, name_policy)
+Link.Name, config_parse_ifname, 0, offsetof(link_config, name)
+Link.Alias, config_parse_ifalias, 0, offsetof(link_config, alias)
+Link.MTUBytes, config_parse_iec_size, 0, offsetof(link_config, mtu)
+Link.BitsPerSecond, config_parse_si_size, 0, offsetof(link_config, speed)
+Link.Duplex, config_parse_duplex, 0, offsetof(link_config, duplex)
+Link.WakeOnLan, config_parse_wol, 0, offsetof(link_config, wol)
+Link.GenericSegmentationOffload, config_parse_tristate, 0, offsetof(link_config, features[NET_DEV_FEAT_GSO])
+Link.TCPSegmentationOffload, config_parse_tristate, 0, offsetof(link_config, features[NET_DEV_FEAT_TSO])
+Link.UDPSegmentationOffload, config_parse_tristate, 0, offsetof(link_config, features[NET_DEV_FEAT_UFO])
+Link.GenericReceiveOffload, config_parse_tristate, 0, offsetof(link_config, features[NET_DEV_FEAT_GRO])
+Link.LargeReceiveOffload, config_parse_tristate, 0, offsetof(link_config, features[NET_DEV_FEAT_LRO])
diff --git a/src/grp-udev/libudev-core/net/link-config.c b/src/grp-udev/libudev-core/net/link-config.c
new file mode 100644
index 0000000000..df37c63169
--- /dev/null
+++ b/src/grp-udev/libudev-core/net/link-config.c
@@ -0,0 +1,517 @@
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2013 Tom Gundersen <teg@jklm.no>
+
+ 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 <netinet/ether.h>
+
+#include "libudev-private.h"
+#include "sd-netlink/netlink-util.h"
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/conf-files.h"
+#include "systemd-basic/fd-util.h"
+#include "systemd-basic/log.h"
+#include "systemd-basic/missing.h"
+#include "systemd-basic/parse-util.h"
+#include "systemd-basic/path-util.h"
+#include "systemd-basic/proc-cmdline.h"
+#include "systemd-basic/random-util.h"
+#include "systemd-basic/stat-util.h"
+#include "systemd-basic/string-table.h"
+#include "systemd-basic/string-util.h"
+#include "systemd-basic/strv.h"
+#include "systemd-basic/util.h"
+#include "systemd-network/network-internal.h"
+#include "systemd-shared/conf-parser.h"
+#include "systemd-staging/sd-netlink.h"
+
+#include "ethtool-util.h"
+#include "link-config.h"
+
+struct link_config_ctx {
+ LIST_HEAD(link_config, links);
+
+ int ethtool_fd;
+
+ bool enable_name_policy;
+
+ sd_netlink *rtnl;
+
+ usec_t link_dirs_ts_usec;
+};
+
+static const char* const link_dirs[] = {
+ "/etc/systemd/network",
+ "/run/systemd/network",
+ "/usr/lib/systemd/network",
+#ifdef HAVE_SPLIT_USR
+ "/lib/systemd/network",
+#endif
+ NULL};
+
+static void link_config_free(link_config *link) {
+ if (!link)
+ return;
+
+ free(link->filename);
+
+ free(link->match_mac);
+ strv_free(link->match_path);
+ strv_free(link->match_driver);
+ strv_free(link->match_type);
+ free(link->match_name);
+ free(link->match_host);
+ free(link->match_virt);
+ free(link->match_kernel);
+ free(link->match_arch);
+
+ free(link->description);
+ free(link->mac);
+ free(link->name_policy);
+ free(link->name);
+ free(link->alias);
+
+ free(link);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(link_config*, link_config_free);
+
+static void link_configs_free(link_config_ctx *ctx) {
+ link_config *link, *link_next;
+
+ if (!ctx)
+ return;
+
+ LIST_FOREACH_SAFE(links, link, link_next, ctx->links)
+ link_config_free(link);
+}
+
+void link_config_ctx_free(link_config_ctx *ctx) {
+ if (!ctx)
+ return;
+
+ safe_close(ctx->ethtool_fd);
+
+ sd_netlink_unref(ctx->rtnl);
+
+ link_configs_free(ctx);
+
+ free(ctx);
+
+ return;
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(link_config_ctx*, link_config_ctx_free);
+
+int link_config_ctx_new(link_config_ctx **ret) {
+ _cleanup_(link_config_ctx_freep) link_config_ctx *ctx = NULL;
+
+ if (!ret)
+ return -EINVAL;
+
+ ctx = new0(link_config_ctx, 1);
+ if (!ctx)
+ return -ENOMEM;
+
+ LIST_HEAD_INIT(ctx->links);
+
+ ctx->ethtool_fd = -1;
+
+ ctx->enable_name_policy = true;
+
+ *ret = ctx;
+ ctx = NULL;
+
+ return 0;
+}
+
+static int load_link(link_config_ctx *ctx, const char *filename) {
+ _cleanup_(link_config_freep) link_config *link = NULL;
+ _cleanup_fclose_ FILE *file = NULL;
+ int r;
+
+ assert(ctx);
+ assert(filename);
+
+ file = fopen(filename, "re");
+ if (!file) {
+ if (errno == ENOENT)
+ return 0;
+ else
+ return -errno;
+ }
+
+ if (null_or_empty_fd(fileno(file))) {
+ log_debug("Skipping empty file: %s", filename);
+ return 0;
+ }
+
+ link = new0(link_config, 1);
+ if (!link)
+ return log_oom();
+
+ link->mac_policy = _MACPOLICY_INVALID;
+ link->wol = _WOL_INVALID;
+ link->duplex = _DUP_INVALID;
+
+ memset(&link->features, -1, _NET_DEV_FEAT_MAX);
+
+ r = config_parse(NULL, filename, file,
+ "Match\0Link\0Ethernet\0",
+ config_item_perf_lookup, link_config_gperf_lookup,
+ false, false, true, link);
+ if (r < 0)
+ return r;
+ else
+ log_debug("Parsed configuration file %s", filename);
+
+ if (link->mtu > UINT_MAX || link->speed > UINT_MAX)
+ return -ERANGE;
+
+ link->filename = strdup(filename);
+
+ LIST_PREPEND(links, ctx->links, link);
+ link = NULL;
+
+ return 0;
+}
+
+static bool enable_name_policy(void) {
+ _cleanup_free_ char *value = NULL;
+ int r;
+
+ r = get_proc_cmdline_key("net.ifnames=", &value);
+ if (r > 0 && streq(value, "0"))
+ return false;
+
+ return true;
+}
+
+int link_config_load(link_config_ctx *ctx) {
+ int r;
+ _cleanup_strv_free_ char **files;
+ char **f;
+
+ link_configs_free(ctx);
+
+ if (!enable_name_policy()) {
+ ctx->enable_name_policy = false;
+ log_info("Network interface NamePolicy= disabled on kernel command line, ignoring.");
+ }
+
+ /* update timestamp */
+ paths_check_timestamp(link_dirs, &ctx->link_dirs_ts_usec, true);
+
+ r = conf_files_list_strv(&files, ".link", NULL, link_dirs);
+ if (r < 0)
+ return log_error_errno(r, "failed to enumerate link files: %m");
+
+ STRV_FOREACH_BACKWARDS(f, files) {
+ r = load_link(ctx, *f);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+bool link_config_should_reload(link_config_ctx *ctx) {
+ return paths_check_timestamp(link_dirs, &ctx->link_dirs_ts_usec, false);
+}
+
+int link_config_get(link_config_ctx *ctx, struct udev_device *device,
+ link_config **ret) {
+ link_config *link;
+
+ assert(ctx);
+ assert(device);
+ assert(ret);
+
+ LIST_FOREACH(links, link, ctx->links) {
+ const char* attr_value;
+
+ attr_value = udev_device_get_sysattr_value(device, "address");
+
+ if (net_match_config(link->match_mac, link->match_path, link->match_driver,
+ link->match_type, link->match_name, link->match_host,
+ link->match_virt, link->match_kernel, link->match_arch,
+ attr_value ? ether_aton(attr_value) : NULL,
+ udev_device_get_property_value(device, "ID_PATH"),
+ udev_device_get_driver(udev_device_get_parent(device)),
+ udev_device_get_property_value(device, "ID_NET_DRIVER"),
+ udev_device_get_devtype(device),
+ udev_device_get_sysname(device))) {
+ if (link->match_name) {
+ unsigned char name_assign_type = NET_NAME_UNKNOWN;
+
+ attr_value = udev_device_get_sysattr_value(device, "name_assign_type");
+ if (attr_value)
+ (void) safe_atou8(attr_value, &name_assign_type);
+
+ if (name_assign_type == NET_NAME_ENUM) {
+ log_warning("Config file %s applies to device based on potentially unpredictable interface name '%s'",
+ link->filename, udev_device_get_sysname(device));
+ *ret = link;
+
+ return 0;
+ } else if (name_assign_type == NET_NAME_RENAMED) {
+ log_warning("Config file %s matches device based on renamed interface name '%s', ignoring",
+ link->filename, udev_device_get_sysname(device));
+
+ continue;
+ }
+ }
+
+ log_debug("Config file %s applies to device %s",
+ link->filename, udev_device_get_sysname(device));
+
+ *ret = link;
+
+ return 0;
+ }
+ }
+
+ *ret = NULL;
+
+ return -ENOENT;
+}
+
+static bool mac_is_random(struct udev_device *device) {
+ const char *s;
+ unsigned type;
+ int r;
+
+ /* if we can't get the assign type, assume it is not random */
+ s = udev_device_get_sysattr_value(device, "addr_assign_type");
+ if (!s)
+ return false;
+
+ r = safe_atou(s, &type);
+ if (r < 0)
+ return false;
+
+ return type == NET_ADDR_RANDOM;
+}
+
+static bool should_rename(struct udev_device *device, bool respect_predictable) {
+ const char *s;
+ unsigned type;
+ int r;
+
+ /* if we can't get the assgin type, assume we should rename */
+ s = udev_device_get_sysattr_value(device, "name_assign_type");
+ if (!s)
+ return true;
+
+ r = safe_atou(s, &type);
+ if (r < 0)
+ return true;
+
+ switch (type) {
+ case NET_NAME_USER:
+ case NET_NAME_RENAMED:
+ /* these were already named by userspace, do not touch again */
+ return false;
+ case NET_NAME_PREDICTABLE:
+ /* the kernel claims to have given a predictable name */
+ if (respect_predictable)
+ return false;
+ /* fall through */
+ case NET_NAME_ENUM:
+ default:
+ /* the name is known to be bad, or of an unknown type */
+ return true;
+ }
+}
+
+static int get_mac(struct udev_device *device, bool want_random,
+ struct ether_addr *mac) {
+ int r;
+
+ if (want_random)
+ random_bytes(mac->ether_addr_octet, ETH_ALEN);
+ else {
+ uint64_t result;
+
+ r = net_get_unique_predictable_data(device, &result);
+ if (r < 0)
+ return r;
+
+ assert_cc(ETH_ALEN <= sizeof(result));
+ memcpy(mac->ether_addr_octet, &result, ETH_ALEN);
+ }
+
+ /* see eth_random_addr in the kernel */
+ mac->ether_addr_octet[0] &= 0xfe; /* clear multicast bit */
+ mac->ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */
+
+ return 0;
+}
+
+int link_config_apply(link_config_ctx *ctx, link_config *config,
+ struct udev_device *device, const char **name) {
+ const char *old_name;
+ const char *new_name = NULL;
+ struct ether_addr generated_mac;
+ struct ether_addr *mac = NULL;
+ bool respect_predictable = false;
+ int r, ifindex;
+
+ assert(ctx);
+ assert(config);
+ assert(device);
+ assert(name);
+
+ old_name = udev_device_get_sysname(device);
+ if (!old_name)
+ return -EINVAL;
+
+ r = ethtool_set_speed(&ctx->ethtool_fd, old_name, config->speed / 1024, config->duplex);
+ if (r < 0)
+ log_warning_errno(r, "Could not set speed or duplex of %s to %zu Mbps (%s): %m",
+ old_name, config->speed / 1024,
+ duplex_to_string(config->duplex));
+
+ r = ethtool_set_wol(&ctx->ethtool_fd, old_name, config->wol);
+ if (r < 0)
+ log_warning_errno(r, "Could not set WakeOnLan of %s to %s: %m",
+ old_name, wol_to_string(config->wol));
+
+ r = ethtool_set_features(&ctx->ethtool_fd, old_name, config->features);
+ if (r < 0)
+ log_warning_errno(r, "Could not set offload features of %s: %m", old_name);
+
+ ifindex = udev_device_get_ifindex(device);
+ if (ifindex <= 0) {
+ log_warning("Could not find ifindex");
+ return -ENODEV;
+ }
+
+ if (ctx->enable_name_policy && config->name_policy) {
+ NamePolicy *policy;
+
+ for (policy = config->name_policy;
+ !new_name && *policy != _NAMEPOLICY_INVALID; policy++) {
+ switch (*policy) {
+ case NAMEPOLICY_KERNEL:
+ respect_predictable = true;
+ break;
+ case NAMEPOLICY_DATABASE:
+ new_name = udev_device_get_property_value(device, "ID_NET_NAME_FROM_DATABASE");
+ break;
+ case NAMEPOLICY_ONBOARD:
+ new_name = udev_device_get_property_value(device, "ID_NET_NAME_ONBOARD");
+ break;
+ case NAMEPOLICY_SLOT:
+ new_name = udev_device_get_property_value(device, "ID_NET_NAME_SLOT");
+ break;
+ case NAMEPOLICY_PATH:
+ new_name = udev_device_get_property_value(device, "ID_NET_NAME_PATH");
+ break;
+ case NAMEPOLICY_MAC:
+ new_name = udev_device_get_property_value(device, "ID_NET_NAME_MAC");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (should_rename(device, respect_predictable)) {
+ /* if not set by policy, fall back manually set name */
+ if (!new_name)
+ new_name = config->name;
+ } else
+ new_name = NULL;
+
+ switch (config->mac_policy) {
+ case MACPOLICY_PERSISTENT:
+ if (mac_is_random(device)) {
+ r = get_mac(device, false, &generated_mac);
+ if (r == -ENOENT) {
+ log_warning_errno(r, "Could not generate persistent MAC address for %s: %m", old_name);
+ break;
+ } else if (r < 0)
+ return r;
+ mac = &generated_mac;
+ }
+ break;
+ case MACPOLICY_RANDOM:
+ if (!mac_is_random(device)) {
+ r = get_mac(device, true, &generated_mac);
+ if (r == -ENOENT) {
+ log_warning_errno(r, "Could not generate random MAC address for %s: %m", old_name);
+ break;
+ } else if (r < 0)
+ return r;
+ mac = &generated_mac;
+ }
+ break;
+ case MACPOLICY_NONE:
+ default:
+ mac = config->mac;
+ }
+
+ r = rtnl_set_link_properties(&ctx->rtnl, ifindex, config->alias, mac, config->mtu);
+ if (r < 0)
+ return log_warning_errno(r, "Could not set Alias, MACAddress or MTU on %s: %m", old_name);
+
+ *name = new_name;
+
+ return 0;
+}
+
+int link_get_driver(link_config_ctx *ctx, struct udev_device *device, char **ret) {
+ const char *name;
+ char *driver = NULL;
+ int r;
+
+ name = udev_device_get_sysname(device);
+ if (!name)
+ return -EINVAL;
+
+ r = ethtool_get_driver(&ctx->ethtool_fd, name, &driver);
+ if (r < 0)
+ return r;
+
+ *ret = driver;
+ return 0;
+}
+
+static const char* const mac_policy_table[_MACPOLICY_MAX] = {
+ [MACPOLICY_PERSISTENT] = "persistent",
+ [MACPOLICY_RANDOM] = "random",
+ [MACPOLICY_NONE] = "none"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(mac_policy, MACPolicy);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_mac_policy, mac_policy, MACPolicy,
+ "Failed to parse MAC address policy");
+
+static const char* const name_policy_table[_NAMEPOLICY_MAX] = {
+ [NAMEPOLICY_KERNEL] = "kernel",
+ [NAMEPOLICY_DATABASE] = "database",
+ [NAMEPOLICY_ONBOARD] = "onboard",
+ [NAMEPOLICY_SLOT] = "slot",
+ [NAMEPOLICY_PATH] = "path",
+ [NAMEPOLICY_MAC] = "mac"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(name_policy, NamePolicy);
+DEFINE_CONFIG_PARSE_ENUMV(config_parse_name_policy, name_policy, NamePolicy,
+ _NAMEPOLICY_INVALID,
+ "Failed to parse interface name policy");
diff --git a/src/grp-udev/libudev-core/net/link-config.h b/src/grp-udev/libudev-core/net/link-config.h
new file mode 100644
index 0000000000..0040dd2b42
--- /dev/null
+++ b/src/grp-udev/libudev-core/net/link-config.h
@@ -0,0 +1,100 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2013 Tom Gundersen <teg@jklm.no>
+
+ 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 <libudev.h>
+
+#include "systemd-basic/list.h"
+#include "systemd-shared/condition.h"
+
+#include "ethtool-util.h"
+
+typedef struct link_config_ctx link_config_ctx;
+typedef struct link_config link_config;
+
+typedef enum MACPolicy {
+ MACPOLICY_PERSISTENT,
+ MACPOLICY_RANDOM,
+ MACPOLICY_NONE,
+ _MACPOLICY_MAX,
+ _MACPOLICY_INVALID = -1
+} MACPolicy;
+
+typedef enum NamePolicy {
+ NAMEPOLICY_KERNEL,
+ NAMEPOLICY_DATABASE,
+ NAMEPOLICY_ONBOARD,
+ NAMEPOLICY_SLOT,
+ NAMEPOLICY_PATH,
+ NAMEPOLICY_MAC,
+ _NAMEPOLICY_MAX,
+ _NAMEPOLICY_INVALID = -1
+} NamePolicy;
+
+struct link_config {
+ char *filename;
+
+ struct ether_addr *match_mac;
+ char **match_path;
+ char **match_driver;
+ char **match_type;
+ char **match_name;
+ Condition *match_host;
+ Condition *match_virt;
+ Condition *match_kernel;
+ Condition *match_arch;
+
+ char *description;
+ struct ether_addr *mac;
+ MACPolicy mac_policy;
+ NamePolicy *name_policy;
+ char *name;
+ char *alias;
+ size_t mtu;
+ size_t speed;
+ Duplex duplex;
+ WakeOnLan wol;
+ NetDevFeature features[_NET_DEV_FEAT_MAX];
+
+ LIST_FIELDS(link_config, links);
+};
+
+int link_config_ctx_new(link_config_ctx **ret);
+void link_config_ctx_free(link_config_ctx *ctx);
+
+int link_config_load(link_config_ctx *ctx);
+bool link_config_should_reload(link_config_ctx *ctx);
+
+int link_config_get(link_config_ctx *ctx, struct udev_device *device, struct link_config **ret);
+int link_config_apply(link_config_ctx *ctx, struct link_config *config, struct udev_device *device, const char **name);
+
+int link_get_driver(link_config_ctx *ctx, struct udev_device *device, char **ret);
+
+const char *name_policy_to_string(NamePolicy p) _const_;
+NamePolicy name_policy_from_string(const char *p) _pure_;
+
+const char *mac_policy_to_string(MACPolicy p) _const_;
+MACPolicy mac_policy_from_string(const char *p) _pure_;
+
+/* gperf lookup function */
+const struct ConfigPerfItem* link_config_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
+
+int config_parse_mac_policy(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_name_policy(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);
diff --git a/src/grp-udev/libudev-core/sd-login.c b/src/grp-udev/libudev-core/sd-login.c
new file mode 120000
index 0000000000..913dcedc6a
--- /dev/null
+++ b/src/grp-udev/libudev-core/sd-login.c
@@ -0,0 +1 @@
+../../libsystemd/src/sd-login/sd-login.c \ No newline at end of file
diff --git a/src/grp-udev/libudev-core/udev-builtin-blkid.c b/src/grp-udev/libudev-core/udev-builtin-blkid.c
new file mode 100644
index 0000000000..b56ffa62a3
--- /dev/null
+++ b/src/grp-udev/libudev-core/udev-builtin-blkid.c
@@ -0,0 +1,337 @@
+/*
+ * probe disks for filesystems and partitions
+ *
+ * Copyright (C) 2011 Kay Sievers <kay@vrfy.org>
+ * Copyright (C) 2011 Karel Zak <kzak@redhat.com>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <blkid/blkid.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <systemd/sd-id128.h>
+
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/fd-util.h"
+#include "systemd-basic/string-util.h"
+#include "systemd-shared/efivars.h"
+#include "systemd-shared/gpt.h"
+#include "udev.h"
+
+static void print_property(struct udev_device *dev, bool test, const char *name, const char *value) {
+ char s[256];
+
+ s[0] = '\0';
+
+ if (streq(name, "TYPE")) {
+ udev_builtin_add_property(dev, test, "ID_FS_TYPE", value);
+
+ } else if (streq(name, "USAGE")) {
+ udev_builtin_add_property(dev, test, "ID_FS_USAGE", value);
+
+ } else if (streq(name, "VERSION")) {
+ udev_builtin_add_property(dev, test, "ID_FS_VERSION", value);
+
+ } else if (streq(name, "UUID")) {
+ blkid_safe_string(value, s, sizeof(s));
+ udev_builtin_add_property(dev, test, "ID_FS_UUID", s);
+ blkid_encode_string(value, s, sizeof(s));
+ udev_builtin_add_property(dev, test, "ID_FS_UUID_ENC", s);
+
+ } else if (streq(name, "UUID_SUB")) {
+ blkid_safe_string(value, s, sizeof(s));
+ udev_builtin_add_property(dev, test, "ID_FS_UUID_SUB", s);
+ blkid_encode_string(value, s, sizeof(s));
+ udev_builtin_add_property(dev, test, "ID_FS_UUID_SUB_ENC", s);
+
+ } else if (streq(name, "LABEL")) {
+ blkid_safe_string(value, s, sizeof(s));
+ udev_builtin_add_property(dev, test, "ID_FS_LABEL", s);
+ blkid_encode_string(value, s, sizeof(s));
+ udev_builtin_add_property(dev, test, "ID_FS_LABEL_ENC", s);
+
+ } else if (streq(name, "PTTYPE")) {
+ udev_builtin_add_property(dev, test, "ID_PART_TABLE_TYPE", value);
+
+ } else if (streq(name, "PTUUID")) {
+ udev_builtin_add_property(dev, test, "ID_PART_TABLE_UUID", value);
+
+ } else if (streq(name, "PART_ENTRY_NAME")) {
+ blkid_encode_string(value, s, sizeof(s));
+ udev_builtin_add_property(dev, test, "ID_PART_ENTRY_NAME", s);
+
+ } else if (streq(name, "PART_ENTRY_TYPE")) {
+ blkid_encode_string(value, s, sizeof(s));
+ udev_builtin_add_property(dev, test, "ID_PART_ENTRY_TYPE", s);
+
+ } else if (startswith(name, "PART_ENTRY_")) {
+ strscpyl(s, sizeof(s), "ID_", name, NULL);
+ udev_builtin_add_property(dev, test, s, value);
+
+ } else if (streq(name, "SYSTEM_ID")) {
+ blkid_encode_string(value, s, sizeof(s));
+ udev_builtin_add_property(dev, test, "ID_FS_SYSTEM_ID", s);
+
+ } else if (streq(name, "PUBLISHER_ID")) {
+ blkid_encode_string(value, s, sizeof(s));
+ udev_builtin_add_property(dev, test, "ID_FS_PUBLISHER_ID", s);
+
+ } else if (streq(name, "APPLICATION_ID")) {
+ blkid_encode_string(value, s, sizeof(s));
+ udev_builtin_add_property(dev, test, "ID_FS_APPLICATION_ID", s);
+
+ } else if (streq(name, "BOOT_SYSTEM_ID")) {
+ blkid_encode_string(value, s, sizeof(s));
+ udev_builtin_add_property(dev, test, "ID_FS_BOOT_SYSTEM_ID", s);
+ }
+}
+
+static int find_gpt_root(struct udev_device *dev, blkid_probe pr, bool test) {
+
+#if defined(GPT_ROOT_NATIVE) && defined(ENABLE_EFI)
+
+ _cleanup_free_ char *root_id = NULL;
+ bool found_esp = false;
+ blkid_partlist pl;
+ int i, nvals, r;
+
+ assert(pr);
+
+ /* Iterate through the partitions on this disk, and see if the
+ * EFI ESP we booted from is on it. If so, find the first root
+ * disk, and add a property indicating its partition UUID. */
+
+ errno = 0;
+ pl = blkid_probe_get_partitions(pr);
+ if (!pl)
+ return errno > 0 ? -errno : -ENOMEM;
+
+ nvals = blkid_partlist_numof_partitions(pl);
+ for (i = 0; i < nvals; i++) {
+ blkid_partition pp;
+ const char *stype, *sid;
+ sd_id128_t type;
+
+ pp = blkid_partlist_get_partition(pl, i);
+ if (!pp)
+ continue;
+
+ sid = blkid_partition_get_uuid(pp);
+ if (!sid)
+ continue;
+
+ stype = blkid_partition_get_type_string(pp);
+ if (!stype)
+ continue;
+
+ if (sd_id128_from_string(stype, &type) < 0)
+ continue;
+
+ if (sd_id128_equal(type, GPT_ESP)) {
+ sd_id128_t id, esp;
+
+ /* We found an ESP, let's see if it matches
+ * the ESP we booted from. */
+
+ if (sd_id128_from_string(sid, &id) < 0)
+ continue;
+
+ r = efi_loader_get_device_part_uuid(&esp);
+ if (r < 0)
+ return r;
+
+ if (sd_id128_equal(id, esp))
+ found_esp = true;
+
+ } else if (sd_id128_equal(type, GPT_ROOT_NATIVE)) {
+ unsigned long long flags;
+
+ flags = blkid_partition_get_flags(pp);
+ if (flags & GPT_FLAG_NO_AUTO)
+ continue;
+
+ /* We found a suitable root partition, let's
+ * remember the first one. */
+
+ if (!root_id) {
+ root_id = strdup(sid);
+ if (!root_id)
+ return -ENOMEM;
+ }
+ }
+ }
+
+ /* We found the ESP on this disk, and also found a root
+ * partition, nice! Let's export its UUID */
+ if (found_esp && root_id)
+ udev_builtin_add_property(dev, test, "ID_PART_GPT_AUTO_ROOT_UUID", root_id);
+#endif
+
+ return 0;
+}
+
+static int probe_superblocks(blkid_probe pr) {
+ struct stat st;
+ int rc;
+
+ if (fstat(blkid_probe_get_fd(pr), &st))
+ return -1;
+
+ blkid_probe_enable_partitions(pr, 1);
+
+ if (!S_ISCHR(st.st_mode) &&
+ blkid_probe_get_size(pr) <= 1024 * 1440 &&
+ blkid_probe_is_wholedisk(pr)) {
+ /*
+ * check if the small disk is partitioned, if yes then
+ * don't probe for filesystems.
+ */
+ blkid_probe_enable_superblocks(pr, 0);
+
+ rc = blkid_do_fullprobe(pr);
+ if (rc < 0)
+ return rc; /* -1 = error, 1 = nothing, 0 = success */
+
+ if (blkid_probe_lookup_value(pr, "PTTYPE", NULL, NULL) == 0)
+ return 0; /* partition table detected */
+ }
+
+ blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS);
+ blkid_probe_enable_superblocks(pr, 1);
+
+ return blkid_do_safeprobe(pr);
+}
+
+static int builtin_blkid(struct udev_device *dev, int argc, char *argv[], bool test) {
+ const char *root_partition;
+ int64_t offset = 0;
+ bool noraid = false;
+ _cleanup_close_ int fd = -1;
+ blkid_probe pr;
+ const char *data;
+ const char *name;
+ const char *prtype = NULL;
+ int nvals;
+ int i;
+ int err = 0;
+ bool is_gpt = false;
+
+ static const struct option options[] = {
+ { "offset", optional_argument, NULL, 'o' },
+ { "noraid", no_argument, NULL, 'R' },
+ {}
+ };
+
+ for (;;) {
+ int option;
+
+ option = getopt_long(argc, argv, "oR", options, NULL);
+ if (option == -1)
+ break;
+
+ switch (option) {
+ case 'o':
+ offset = strtoull(optarg, NULL, 0);
+ break;
+ case 'R':
+ noraid = true;
+ break;
+ }
+ }
+
+ pr = blkid_new_probe();
+ if (!pr)
+ return EXIT_FAILURE;
+
+ blkid_probe_set_superblocks_flags(pr,
+ BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID |
+ BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE |
+ BLKID_SUBLKS_USAGE | BLKID_SUBLKS_VERSION |
+ BLKID_SUBLKS_BADCSUM);
+
+ if (noraid)
+ blkid_probe_filter_superblocks_usage(pr, BLKID_FLTR_NOTIN, BLKID_USAGE_RAID);
+
+ fd = open(udev_device_get_devnode(dev), O_RDONLY|O_CLOEXEC);
+ if (fd < 0) {
+ err = log_debug_errno(errno, "Failure opening block device %s: %m", udev_device_get_devnode(dev));
+ goto out;
+ }
+
+ err = blkid_probe_set_device(pr, fd, offset, 0);
+ if (err < 0)
+ goto out;
+
+ log_debug("probe %s %sraid offset=%"PRIi64,
+ udev_device_get_devnode(dev),
+ noraid ? "no" : "", offset);
+
+ err = probe_superblocks(pr);
+ if (err < 0)
+ goto out;
+ if (blkid_probe_has_value(pr, "SBBADCSUM")) {
+ if (!blkid_probe_lookup_value(pr, "TYPE", &prtype, NULL))
+ log_warning("incorrect %s checksum on %s",
+ prtype, udev_device_get_devnode(dev));
+ else
+ log_warning("incorrect checksum on %s",
+ udev_device_get_devnode(dev));
+ goto out;
+ }
+
+ /* If we are a partition then our parent passed on the root
+ * partition UUID to us */
+ root_partition = udev_device_get_property_value(dev, "ID_PART_GPT_AUTO_ROOT_UUID");
+
+ nvals = blkid_probe_numof_values(pr);
+ for (i = 0; i < nvals; i++) {
+ if (blkid_probe_get_value(pr, i, &name, &data, NULL))
+ continue;
+
+ print_property(dev, test, name, data);
+
+ /* Is this a disk with GPT partition table? */
+ if (streq(name, "PTTYPE") && streq(data, "gpt"))
+ is_gpt = true;
+
+ /* Is this a partition that matches the root partition
+ * property we inherited from our parent? */
+ if (root_partition && streq(name, "PART_ENTRY_UUID") && streq(data, root_partition))
+ udev_builtin_add_property(dev, test, "ID_PART_GPT_AUTO_ROOT", "1");
+ }
+
+ if (is_gpt)
+ find_gpt_root(dev, pr, test);
+
+ blkid_free_probe(pr);
+out:
+ if (err < 0)
+ return EXIT_FAILURE;
+
+ return EXIT_SUCCESS;
+}
+
+const struct udev_builtin udev_builtin_blkid = {
+ .name = "blkid",
+ .cmd = builtin_blkid,
+ .help = "Filesystem and partition probing",
+ .run_once = true,
+};
diff --git a/src/grp-udev/libudev-core/udev-builtin-btrfs.c b/src/grp-udev/libudev-core/udev-builtin-btrfs.c
new file mode 100644
index 0000000000..dbaf65d8f9
--- /dev/null
+++ b/src/grp-udev/libudev-core/udev-builtin-btrfs.c
@@ -0,0 +1,58 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Kay Sievers <kay@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 <fcntl.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+
+#ifdef HAVE_LINUX_BTRFS_H
+#include <linux/btrfs.h>
+#endif
+
+#include "systemd-basic/fd-util.h"
+#include "systemd-basic/missing.h"
+#include "systemd-basic/string-util.h"
+#include "udev.h"
+
+static int builtin_btrfs(struct udev_device *dev, int argc, char *argv[], bool test) {
+ struct btrfs_ioctl_vol_args args = {};
+ _cleanup_close_ int fd = -1;
+ int err;
+
+ if (argc != 3 || !streq(argv[1], "ready"))
+ return EXIT_FAILURE;
+
+ fd = open("/dev/btrfs-control", O_RDWR|O_CLOEXEC);
+ if (fd < 0)
+ return EXIT_FAILURE;
+
+ strscpy(args.name, sizeof(args.name), argv[2]);
+ err = ioctl(fd, BTRFS_IOC_DEVICES_READY, &args);
+ if (err < 0)
+ return EXIT_FAILURE;
+
+ udev_builtin_add_property(dev, test, "ID_BTRFS_READY", one_zero(err == 0));
+ return EXIT_SUCCESS;
+}
+
+const struct udev_builtin udev_builtin_btrfs = {
+ .name = "btrfs",
+ .cmd = builtin_btrfs,
+ .help = "btrfs volume management",
+};
diff --git a/src/grp-udev/libudev-core/udev-builtin-hwdb.c b/src/grp-udev/libudev-core/udev-builtin-hwdb.c
new file mode 100644
index 0000000000..9587845ac2
--- /dev/null
+++ b/src/grp-udev/libudev-core/udev-builtin-hwdb.c
@@ -0,0 +1,222 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Kay Sievers <kay@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 <fnmatch.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "sd-hwdb/hwdb-util.h"
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/string-util.h"
+#include "systemd-shared/udev-util.h"
+#include "systemd-staging/sd-hwdb.h"
+#include "udev.h"
+
+static sd_hwdb *hwdb;
+
+int udev_builtin_hwdb_lookup(struct udev_device *dev,
+ const char *prefix, const char *modalias,
+ const char *filter, bool test) {
+ _cleanup_free_ char *lookup = NULL;
+ const char *key, *value;
+ int n = 0;
+
+ if (!hwdb)
+ return -ENOENT;
+
+ if (prefix) {
+ lookup = strjoin(prefix, modalias, NULL);
+ if (!lookup)
+ return -ENOMEM;
+ modalias = lookup;
+ }
+
+ SD_HWDB_FOREACH_PROPERTY(hwdb, modalias, key, value) {
+ if (filter && fnmatch(filter, key, FNM_NOESCAPE) != 0)
+ continue;
+
+ if (udev_builtin_add_property(dev, test, key, value) < 0)
+ return -ENOMEM;
+ n++;
+ }
+ return n;
+}
+
+static const char *modalias_usb(struct udev_device *dev, char *s, size_t size) {
+ const char *v, *p;
+ int vn, pn;
+
+ v = udev_device_get_sysattr_value(dev, "idVendor");
+ if (!v)
+ return NULL;
+ p = udev_device_get_sysattr_value(dev, "idProduct");
+ if (!p)
+ return NULL;
+ vn = strtol(v, NULL, 16);
+ if (vn <= 0)
+ return NULL;
+ pn = strtol(p, NULL, 16);
+ if (pn <= 0)
+ return NULL;
+ snprintf(s, size, "usb:v%04Xp%04X*", vn, pn);
+ return s;
+}
+
+static int udev_builtin_hwdb_search(struct udev_device *dev, struct udev_device *srcdev,
+ const char *subsystem, const char *prefix,
+ const char *filter, bool test) {
+ struct udev_device *d;
+ char s[16];
+ bool last = false;
+ int r = 0;
+
+ assert(dev);
+
+ if (!srcdev)
+ srcdev = dev;
+
+ for (d = srcdev; d && !last; d = udev_device_get_parent(d)) {
+ const char *dsubsys;
+ const char *modalias = NULL;
+
+ dsubsys = udev_device_get_subsystem(d);
+ if (!dsubsys)
+ continue;
+
+ /* look only at devices of a specific subsystem */
+ if (subsystem && !streq(dsubsys, subsystem))
+ continue;
+
+ modalias = udev_device_get_property_value(d, "MODALIAS");
+
+ if (streq(dsubsys, "usb") && streq_ptr(udev_device_get_devtype(d), "usb_device")) {
+ /* if the usb_device does not have a modalias, compose one */
+ if (!modalias)
+ modalias = modalias_usb(d, s, sizeof(s));
+
+ /* avoid looking at any parent device, they are usually just a USB hub */
+ last = true;
+ }
+
+ if (!modalias)
+ continue;
+
+ r = udev_builtin_hwdb_lookup(dev, prefix, modalias, filter, test);
+ if (r > 0)
+ break;
+ }
+
+ return r;
+}
+
+static int builtin_hwdb(struct udev_device *dev, int argc, char *argv[], bool test) {
+ static const struct option options[] = {
+ { "filter", required_argument, NULL, 'f' },
+ { "device", required_argument, NULL, 'd' },
+ { "subsystem", required_argument, NULL, 's' },
+ { "lookup-prefix", required_argument, NULL, 'p' },
+ {}
+ };
+ const char *filter = NULL;
+ const char *device = NULL;
+ const char *subsystem = NULL;
+ const char *prefix = NULL;
+ _cleanup_udev_device_unref_ struct udev_device *srcdev = NULL;
+
+ if (!hwdb)
+ return EXIT_FAILURE;
+
+ for (;;) {
+ int option;
+
+ option = getopt_long(argc, argv, "f:d:s:p:", options, NULL);
+ if (option == -1)
+ break;
+
+ switch (option) {
+ case 'f':
+ filter = optarg;
+ break;
+
+ case 'd':
+ device = optarg;
+ break;
+
+ case 's':
+ subsystem = optarg;
+ break;
+
+ case 'p':
+ prefix = optarg;
+ break;
+ }
+ }
+
+ /* query a specific key given as argument */
+ if (argv[optind]) {
+ if (udev_builtin_hwdb_lookup(dev, prefix, argv[optind], filter, test) > 0)
+ return EXIT_SUCCESS;
+ return EXIT_FAILURE;
+ }
+
+ /* read data from another device than the device we will store the data */
+ if (device) {
+ srcdev = udev_device_new_from_device_id(udev_device_get_udev(dev), device);
+ if (!srcdev)
+ return EXIT_FAILURE;
+ }
+
+ if (udev_builtin_hwdb_search(dev, srcdev, subsystem, prefix, filter, test) > 0)
+ return EXIT_SUCCESS;
+ return EXIT_FAILURE;
+}
+
+/* called at udev startup and reload */
+static int builtin_hwdb_init(struct udev *udev) {
+ int r;
+
+ if (hwdb)
+ return 0;
+
+ r = sd_hwdb_new(&hwdb);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+/* called on udev shutdown and reload request */
+static void builtin_hwdb_exit(struct udev *udev) {
+ hwdb = sd_hwdb_unref(hwdb);
+}
+
+/* called every couple of seconds during event activity; 'true' if config has changed */
+static bool builtin_hwdb_validate(struct udev *udev) {
+ return hwdb_validate(hwdb);
+}
+
+const struct udev_builtin udev_builtin_hwdb = {
+ .name = "hwdb",
+ .cmd = builtin_hwdb,
+ .init = builtin_hwdb_init,
+ .exit = builtin_hwdb_exit,
+ .validate = builtin_hwdb_validate,
+ .help = "Hardware database",
+};
diff --git a/src/grp-udev/libudev-core/udev-builtin-input_id.c b/src/grp-udev/libudev-core/udev-builtin-input_id.c
new file mode 100644
index 0000000000..d4ba49addd
--- /dev/null
+++ b/src/grp-udev/libudev-core/udev-builtin-input_id.c
@@ -0,0 +1,341 @@
+/*
+ * expose input properties via udev
+ *
+ * Copyright (C) 2009 Martin Pitt <martin.pitt@ubuntu.com>
+ * Portions Copyright (C) 2004 David Zeuthen, <david@fubar.dk>
+ * Copyright (C) 2011 Kay Sievers <kay@vrfy.org>
+ * Copyright (C) 2014 Carlos Garnacho <carlosg@gnome.org>
+ * Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <linux/limits.h>
+#include <linux/input.h>
+
+#include "systemd-basic/fd-util.h"
+#include "systemd-basic/stdio-util.h"
+#include "systemd-basic/string-util.h"
+#include "systemd-basic/util.h"
+#include "udev.h"
+
+/* we must use this kernel-compatible implementation */
+#define BITS_PER_LONG (sizeof(unsigned long) * 8)
+#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1)
+#define OFF(x) ((x)%BITS_PER_LONG)
+#define BIT(x) (1UL<<OFF(x))
+#define LONG(x) ((x)/BITS_PER_LONG)
+#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1)
+
+static inline int abs_size_mm(const struct input_absinfo *absinfo) {
+ /* Resolution is defined to be in units/mm for ABS_X/Y */
+ return (absinfo->maximum - absinfo->minimum) / absinfo->resolution;
+}
+
+static void extract_info(struct udev_device *dev, const char *devpath, bool test) {
+ char width[DECIMAL_STR_MAX(int)], height[DECIMAL_STR_MAX(int)];
+ struct input_absinfo xabsinfo = {}, yabsinfo = {};
+ _cleanup_close_ int fd = -1;
+
+ fd = open(devpath, O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ return;
+
+ if (ioctl(fd, EVIOCGABS(ABS_X), &xabsinfo) < 0 ||
+ ioctl(fd, EVIOCGABS(ABS_Y), &yabsinfo) < 0)
+ return;
+
+ if (xabsinfo.resolution <= 0 || yabsinfo.resolution <= 0)
+ return;
+
+ xsprintf(width, "%d", abs_size_mm(&xabsinfo));
+ xsprintf(height, "%d", abs_size_mm(&yabsinfo));
+
+ udev_builtin_add_property(dev, test, "ID_INPUT_WIDTH_MM", width);
+ udev_builtin_add_property(dev, test, "ID_INPUT_HEIGHT_MM", height);
+}
+
+/*
+ * Read a capability attribute and return bitmask.
+ * @param dev udev_device
+ * @param attr sysfs attribute name (e. g. "capabilities/key")
+ * @param bitmask: Output array which has a sizeof of bitmask_size
+ */
+static void get_cap_mask(struct udev_device *dev,
+ struct udev_device *pdev, const char* attr,
+ unsigned long *bitmask, size_t bitmask_size,
+ bool test) {
+ const char *v;
+ char text[4096];
+ unsigned i;
+ char* word;
+ unsigned long val;
+
+ v = udev_device_get_sysattr_value(pdev, attr);
+ if (!v)
+ v = "";
+
+ xsprintf(text, "%s", v);
+ log_debug("%s raw kernel attribute: %s", attr, text);
+
+ memzero(bitmask, bitmask_size);
+ i = 0;
+ while ((word = strrchr(text, ' ')) != NULL) {
+ val = strtoul (word+1, NULL, 16);
+ if (i < bitmask_size/sizeof(unsigned long))
+ bitmask[i] = val;
+ else
+ log_debug("ignoring %s block %lX which is larger than maximum size", attr, val);
+ *word = '\0';
+ ++i;
+ }
+ val = strtoul (text, NULL, 16);
+ if (i < bitmask_size / sizeof(unsigned long))
+ bitmask[i] = val;
+ else
+ log_debug("ignoring %s block %lX which is larger than maximum size", attr, val);
+
+ if (test) {
+ /* printf pattern with the right unsigned long number of hex chars */
+ xsprintf(text, " bit %%4u: %%0%zulX\n",
+ 2 * sizeof(unsigned long));
+ log_debug("%s decoded bit map:", attr);
+ val = bitmask_size / sizeof (unsigned long);
+ /* skip over leading zeros */
+ while (bitmask[val-1] == 0 && val > 0)
+ --val;
+ for (i = 0; i < val; ++i) {
+ DISABLE_WARNING_FORMAT_NONLITERAL;
+ log_debug(text, i * BITS_PER_LONG, bitmask[i]);
+ REENABLE_WARNING;
+ }
+ }
+}
+
+/* pointer devices */
+static bool test_pointers(struct udev_device *dev,
+ const unsigned long* bitmask_ev,
+ const unsigned long* bitmask_abs,
+ const unsigned long* bitmask_key,
+ const unsigned long* bitmask_rel,
+ const unsigned long* bitmask_props,
+ bool test) {
+ bool has_abs_coordinates = false;
+ bool has_rel_coordinates = false;
+ bool has_mt_coordinates = false;
+ bool has_joystick_axes_or_buttons = false;
+ bool is_direct = false;
+ bool has_touch = false;
+ bool has_3d_coordinates = false;
+ bool has_keys = false;
+ bool stylus_or_pen = false;
+ bool finger_but_no_pen = false;
+ bool has_mouse_button = false;
+ bool is_mouse = false;
+ bool is_touchpad = false;
+ bool is_touchscreen = false;
+ bool is_tablet = false;
+ bool is_joystick = false;
+ bool is_accelerometer = false;
+ bool is_pointing_stick= false;
+
+ has_keys = test_bit(EV_KEY, bitmask_ev);
+ has_abs_coordinates = test_bit(ABS_X, bitmask_abs) && test_bit(ABS_Y, bitmask_abs);
+ has_3d_coordinates = has_abs_coordinates && test_bit(ABS_Z, bitmask_abs);
+ is_accelerometer = test_bit(INPUT_PROP_ACCELEROMETER, bitmask_props);
+
+ if (!has_keys && has_3d_coordinates)
+ is_accelerometer = true;
+
+ if (is_accelerometer) {
+ udev_builtin_add_property(dev, test, "ID_INPUT_ACCELEROMETER", "1");
+ return true;
+ }
+
+ is_pointing_stick = test_bit(INPUT_PROP_POINTING_STICK, bitmask_props);
+ stylus_or_pen = test_bit(BTN_STYLUS, bitmask_key) || test_bit(BTN_TOOL_PEN, bitmask_key);
+ finger_but_no_pen = test_bit(BTN_TOOL_FINGER, bitmask_key) && !test_bit(BTN_TOOL_PEN, bitmask_key);
+ has_mouse_button = test_bit(BTN_LEFT, bitmask_key);
+ has_rel_coordinates = test_bit(EV_REL, bitmask_ev) && test_bit(REL_X, bitmask_rel) && test_bit(REL_Y, bitmask_rel);
+ has_mt_coordinates = test_bit(ABS_MT_POSITION_X, bitmask_abs) && test_bit(ABS_MT_POSITION_Y, bitmask_abs);
+
+ /* unset has_mt_coordinates if devices claims to have all abs axis */
+ if (has_mt_coordinates && test_bit(ABS_MT_SLOT, bitmask_abs) && test_bit(ABS_MT_SLOT - 1, bitmask_abs))
+ has_mt_coordinates = false;
+ is_direct = test_bit(INPUT_PROP_DIRECT, bitmask_props);
+ has_touch = test_bit(BTN_TOUCH, bitmask_key);
+ /* joysticks don't necessarily have buttons; e. g.
+ * rudders/pedals are joystick-like, but buttonless; they have
+ * other fancy axes */
+ has_joystick_axes_or_buttons = test_bit(BTN_TRIGGER, bitmask_key) ||
+ test_bit(BTN_A, bitmask_key) ||
+ test_bit(BTN_1, bitmask_key) ||
+ test_bit(ABS_RX, bitmask_abs) ||
+ test_bit(ABS_RY, bitmask_abs) ||
+ test_bit(ABS_RZ, bitmask_abs) ||
+ test_bit(ABS_THROTTLE, bitmask_abs) ||
+ test_bit(ABS_RUDDER, bitmask_abs) ||
+ test_bit(ABS_WHEEL, bitmask_abs) ||
+ test_bit(ABS_GAS, bitmask_abs) ||
+ test_bit(ABS_BRAKE, bitmask_abs);
+
+ if (has_abs_coordinates) {
+ if (stylus_or_pen)
+ is_tablet = true;
+ else if (finger_but_no_pen && !is_direct)
+ is_touchpad = true;
+ else if (has_mouse_button)
+ /* This path is taken by VMware's USB mouse, which has
+ * absolute axes, but no touch/pressure button. */
+ is_mouse = true;
+ else if (has_touch || is_direct)
+ is_touchscreen = true;
+ else if (has_joystick_axes_or_buttons)
+ is_joystick = true;
+ }
+ if (has_mt_coordinates) {
+ if (stylus_or_pen)
+ is_tablet = true;
+ else if (finger_but_no_pen && !is_direct)
+ is_touchpad = true;
+ else if (has_touch || is_direct)
+ is_touchscreen = true;
+ }
+
+ if (has_rel_coordinates && has_mouse_button)
+ is_mouse = true;
+
+ if (is_pointing_stick)
+ udev_builtin_add_property(dev, test, "ID_INPUT_POINTINGSTICK", "1");
+ if (is_mouse)
+ udev_builtin_add_property(dev, test, "ID_INPUT_MOUSE", "1");
+ if (is_touchpad)
+ udev_builtin_add_property(dev, test, "ID_INPUT_TOUCHPAD", "1");
+ if (is_touchscreen)
+ udev_builtin_add_property(dev, test, "ID_INPUT_TOUCHSCREEN", "1");
+ if (is_joystick)
+ udev_builtin_add_property(dev, test, "ID_INPUT_JOYSTICK", "1");
+ if (is_tablet)
+ udev_builtin_add_property(dev, test, "ID_INPUT_TABLET", "1");
+
+ return is_tablet || is_mouse || is_touchpad || is_touchscreen || is_joystick || is_pointing_stick;
+}
+
+/* key like devices */
+static bool test_key(struct udev_device *dev,
+ const unsigned long* bitmask_ev,
+ const unsigned long* bitmask_key,
+ bool test) {
+ unsigned i;
+ unsigned long found;
+ unsigned long mask;
+ bool ret = false;
+
+ /* do we have any KEY_* capability? */
+ if (!test_bit(EV_KEY, bitmask_ev)) {
+ log_debug("test_key: no EV_KEY capability");
+ return false;
+ }
+
+ /* only consider KEY_* here, not BTN_* */
+ found = 0;
+ for (i = 0; i < BTN_MISC/BITS_PER_LONG; ++i) {
+ found |= bitmask_key[i];
+ log_debug("test_key: checking bit block %lu for any keys; found=%i", (unsigned long)i*BITS_PER_LONG, found > 0);
+ }
+ /* If there are no keys in the lower block, check the higher block */
+ if (!found) {
+ for (i = KEY_OK; i < BTN_TRIGGER_HAPPY; ++i) {
+ if (test_bit(i, bitmask_key)) {
+ log_debug("test_key: Found key %x in high block", i);
+ found = 1;
+ break;
+ }
+ }
+ }
+
+ if (found > 0) {
+ udev_builtin_add_property(dev, test, "ID_INPUT_KEY", "1");
+ ret = true;
+ }
+
+ /* the first 32 bits are ESC, numbers, and Q to D; if we have all of
+ * those, consider it a full keyboard; do not test KEY_RESERVED, though */
+ mask = 0xFFFFFFFE;
+ if ((bitmask_key[0] & mask) == mask) {
+ udev_builtin_add_property(dev, test, "ID_INPUT_KEYBOARD", "1");
+ ret = true;
+ }
+
+ return ret;
+}
+
+static int builtin_input_id(struct udev_device *dev, int argc, char *argv[], bool test) {
+ struct udev_device *pdev;
+ unsigned long bitmask_ev[NBITS(EV_MAX)];
+ unsigned long bitmask_abs[NBITS(ABS_MAX)];
+ unsigned long bitmask_key[NBITS(KEY_MAX)];
+ unsigned long bitmask_rel[NBITS(REL_MAX)];
+ unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)];
+ const char *sysname, *devnode;
+ bool is_pointer;
+ bool is_key;
+
+ assert(dev);
+
+ /* walk up the parental chain until we find the real input device; the
+ * argument is very likely a subdevice of this, like eventN */
+ pdev = dev;
+ while (pdev != NULL && udev_device_get_sysattr_value(pdev, "capabilities/ev") == NULL)
+ pdev = udev_device_get_parent_with_subsystem_devtype(pdev, "input", NULL);
+
+ if (pdev) {
+ /* Use this as a flag that input devices were detected, so that this
+ * program doesn't need to be called more than once per device */
+ udev_builtin_add_property(dev, test, "ID_INPUT", "1");
+ get_cap_mask(dev, pdev, "capabilities/ev", bitmask_ev, sizeof(bitmask_ev), test);
+ get_cap_mask(dev, pdev, "capabilities/abs", bitmask_abs, sizeof(bitmask_abs), test);
+ get_cap_mask(dev, pdev, "capabilities/rel", bitmask_rel, sizeof(bitmask_rel), test);
+ get_cap_mask(dev, pdev, "capabilities/key", bitmask_key, sizeof(bitmask_key), test);
+ get_cap_mask(dev, pdev, "properties", bitmask_props, sizeof(bitmask_props), test);
+ is_pointer = test_pointers(dev, bitmask_ev, bitmask_abs,
+ bitmask_key, bitmask_rel,
+ bitmask_props, test);
+ is_key = test_key(dev, bitmask_ev, bitmask_key, test);
+ /* Some evdev nodes have only a scrollwheel */
+ if (!is_pointer && !is_key && test_bit(EV_REL, bitmask_ev) &&
+ (test_bit(REL_WHEEL, bitmask_rel) || test_bit(REL_HWHEEL, bitmask_rel)))
+ udev_builtin_add_property(dev, test, "ID_INPUT_KEY", "1");
+ }
+
+ devnode = udev_device_get_devnode(dev);
+ sysname = udev_device_get_sysname(dev);
+ if (devnode && sysname && startswith(sysname, "event"))
+ extract_info(dev, devnode, test);
+
+ return EXIT_SUCCESS;
+}
+
+const struct udev_builtin udev_builtin_input_id = {
+ .name = "input_id",
+ .cmd = builtin_input_id,
+ .help = "Input device properties",
+};
diff --git a/src/grp-udev/libudev-core/udev-builtin-keyboard.c b/src/grp-udev/libudev-core/udev-builtin-keyboard.c
new file mode 100644
index 0000000000..32792260c8
--- /dev/null
+++ b/src/grp-udev/libudev-core/udev-builtin-keyboard.c
@@ -0,0 +1,278 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Kay Sievers <kay@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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+
+#include <linux/input.h>
+
+#include "systemd-basic/fd-util.h"
+#include "systemd-basic/parse-util.h"
+#include "systemd-basic/stdio-util.h"
+#include "systemd-basic/string-util.h"
+#include "udev.h"
+
+static const struct key *keyboard_lookup_key(const char *str, GPERF_LEN_TYPE len);
+#include "keyboard-keys-from-name.h"
+
+static int install_force_release(struct udev_device *dev, const unsigned *release, unsigned release_count) {
+ struct udev_device *atkbd;
+ const char *cur;
+ char codes[4096];
+ char *s;
+ size_t l;
+ unsigned i;
+ int ret;
+
+ assert(dev);
+ assert(release);
+
+ atkbd = udev_device_get_parent_with_subsystem_devtype(dev, "serio", NULL);
+ if (!atkbd)
+ return -ENODEV;
+
+ cur = udev_device_get_sysattr_value(atkbd, "force_release");
+ if (!cur)
+ return -ENODEV;
+
+ s = codes;
+ l = sizeof(codes);
+
+ /* copy current content */
+ l = strpcpy(&s, l, cur);
+
+ /* append new codes */
+ for (i = 0; i < release_count; i++)
+ l = strpcpyf(&s, l, ",%u", release[i]);
+
+ log_debug("keyboard: updating force-release list with '%s'", codes);
+ ret = udev_device_set_sysattr_value(atkbd, "force_release", codes);
+ if (ret < 0)
+ log_error_errno(ret, "Error writing force-release attribute: %m");
+ return ret;
+}
+
+static void map_keycode(int fd, const char *devnode, int scancode, const char *keycode)
+{
+ struct {
+ unsigned scan;
+ unsigned key;
+ } map;
+ char *endptr;
+ const struct key *k;
+ unsigned keycode_num;
+
+ /* translate identifier to key code */
+ k = keyboard_lookup_key(keycode, strlen(keycode));
+ if (k) {
+ keycode_num = k->id;
+ } else {
+ /* check if it's a numeric code already */
+ keycode_num = strtoul(keycode, &endptr, 0);
+ if (endptr[0] !='\0') {
+ log_error("Unknown key identifier '%s'", keycode);
+ return;
+ }
+ }
+
+ map.scan = scancode;
+ map.key = keycode_num;
+
+ log_debug("keyboard: mapping scan code %d (0x%x) to key code %d (0x%x)",
+ map.scan, map.scan, map.key, map.key);
+
+ if (ioctl(fd, EVIOCSKEYCODE, &map) < 0)
+ log_error_errno(errno, "Error calling EVIOCSKEYCODE on device node '%s' (scan code 0x%x, key code %d): %m", devnode, map.scan, map.key);
+}
+
+static inline char* parse_token(const char *current, int32_t *val_out) {
+ char *next;
+ int32_t val;
+
+ if (!current)
+ return NULL;
+
+ val = strtol(current, &next, 0);
+ if (*next && *next != ':')
+ return NULL;
+
+ if (next != current)
+ *val_out = val;
+
+ if (*next)
+ next++;
+
+ return next;
+}
+
+static void override_abs(int fd, const char *devnode,
+ unsigned evcode, const char *value) {
+ struct input_absinfo absinfo;
+ int rc;
+ char *next;
+
+ rc = ioctl(fd, EVIOCGABS(evcode), &absinfo);
+ if (rc < 0) {
+ log_error_errno(errno, "Unable to EVIOCGABS device \"%s\"", devnode);
+ return;
+ }
+
+ next = parse_token(value, &absinfo.minimum);
+ next = parse_token(next, &absinfo.maximum);
+ next = parse_token(next, &absinfo.resolution);
+ next = parse_token(next, &absinfo.fuzz);
+ next = parse_token(next, &absinfo.flat);
+ if (!next) {
+ log_error("Unable to parse EV_ABS override '%s' for '%s'", value, devnode);
+ return;
+ }
+
+ log_debug("keyboard: %x overridden with %"PRIi32"/%"PRIi32"/%"PRIi32"/%"PRIi32"/%"PRIi32" for \"%s\"",
+ evcode,
+ absinfo.minimum, absinfo.maximum, absinfo.resolution, absinfo.fuzz, absinfo.flat,
+ devnode);
+ rc = ioctl(fd, EVIOCSABS(evcode), &absinfo);
+ if (rc < 0)
+ log_error_errno(errno, "Unable to EVIOCSABS device \"%s\"", devnode);
+}
+
+static void set_trackpoint_sensitivity(struct udev_device *dev, const char *value)
+{
+ struct udev_device *pdev;
+ char val_s[DECIMAL_STR_MAX(int)];
+ int r, val_i;
+
+ assert(dev);
+ assert(value);
+
+ /* The sensitivity sysfs attr belongs to the serio parent device */
+ pdev = udev_device_get_parent_with_subsystem_devtype(dev, "serio", NULL);
+ if (!pdev) {
+ log_warning("Failed to get serio parent for '%s'", udev_device_get_devnode(dev));
+ return;
+ }
+
+ r = safe_atoi(value, &val_i);
+ if (r < 0) {
+ log_error("Unable to parse POINTINGSTICK_SENSITIVITY '%s' for '%s'", value, udev_device_get_devnode(dev));
+ return;
+ }
+
+ xsprintf(val_s, "%d", val_i);
+
+ r = udev_device_set_sysattr_value(pdev, "sensitivity", val_s);
+ if (r < 0)
+ log_error_errno(r, "Failed to write 'sensitivity' attribute for '%s': %m", udev_device_get_devnode(pdev));
+}
+
+static int open_device(const char *devnode) {
+ int fd;
+
+ fd = open(devnode, O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
+ if (fd < 0)
+ return log_error_errno(errno, "Error opening device \"%s\": %m", devnode);
+
+ return fd;
+}
+
+static int builtin_keyboard(struct udev_device *dev, int argc, char *argv[], bool test) {
+ struct udev_list_entry *entry;
+ unsigned release[1024];
+ unsigned release_count = 0;
+ _cleanup_close_ int fd = -1;
+ const char *node;
+
+ node = udev_device_get_devnode(dev);
+ if (!node) {
+ log_error("No device node for \"%s\"", udev_device_get_syspath(dev));
+ return EXIT_FAILURE;
+ }
+
+ udev_list_entry_foreach(entry, udev_device_get_properties_list_entry(dev)) {
+ const char *key;
+ char *endptr;
+
+ key = udev_list_entry_get_name(entry);
+ if (startswith(key, "KEYBOARD_KEY_")) {
+ const char *keycode;
+ unsigned scancode;
+
+ /* KEYBOARD_KEY_<hex scan code>=<key identifier string> */
+ scancode = strtoul(key + 13, &endptr, 16);
+ if (endptr[0] != '\0') {
+ log_warning("Unable to parse scan code from \"%s\"", key);
+ continue;
+ }
+
+ keycode = udev_list_entry_get_value(entry);
+
+ /* a leading '!' needs a force-release entry */
+ if (keycode[0] == '!') {
+ keycode++;
+
+ release[release_count] = scancode;
+ if (release_count < ELEMENTSOF(release)-1)
+ release_count++;
+
+ if (keycode[0] == '\0')
+ continue;
+ }
+
+ if (fd == -1) {
+ fd = open_device(node);
+ if (fd < 0)
+ return EXIT_FAILURE;
+ }
+
+ map_keycode(fd, node, scancode, keycode);
+ } else if (startswith(key, "EVDEV_ABS_")) {
+ unsigned evcode;
+
+ /* EVDEV_ABS_<EV_ABS code>=<min>:<max>:<res>:<fuzz>:<flat> */
+ evcode = strtoul(key + 10, &endptr, 16);
+ if (endptr[0] != '\0') {
+ log_warning("Unable to parse EV_ABS code from \"%s\"", key);
+ continue;
+ }
+
+ if (fd == -1) {
+ fd = open_device(node);
+ if (fd < 0)
+ return EXIT_FAILURE;
+ }
+
+ override_abs(fd, node, evcode, udev_list_entry_get_value(entry));
+ } else if (streq(key, "POINTINGSTICK_SENSITIVITY"))
+ set_trackpoint_sensitivity(dev, udev_list_entry_get_value(entry));
+ }
+
+ /* install list of force-release codes */
+ if (release_count > 0)
+ install_force_release(dev, release, release_count);
+
+ return EXIT_SUCCESS;
+}
+
+const struct udev_builtin udev_builtin_keyboard = {
+ .name = "keyboard",
+ .cmd = builtin_keyboard,
+ .help = "Keyboard scan code to key mapping",
+};
diff --git a/src/grp-udev/libudev-core/udev-builtin-kmod.c b/src/grp-udev/libudev-core/udev-builtin-kmod.c
new file mode 100644
index 0000000000..91732bbfa4
--- /dev/null
+++ b/src/grp-udev/libudev-core/udev-builtin-kmod.c
@@ -0,0 +1,123 @@
+/*
+ * load kernel modules
+ *
+ * Copyright (C) 2011-2012 Kay Sievers <kay@vrfy.org>
+ * Copyright (C) 2011 ProFUSION embedded systems
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <libkmod.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "systemd-basic/string-util.h"
+#include "udev.h"
+
+static struct kmod_ctx *ctx = NULL;
+
+static int load_module(struct udev *udev, const char *alias) {
+ struct kmod_list *list = NULL;
+ struct kmod_list *l;
+ int err;
+
+ err = kmod_module_new_from_lookup(ctx, alias, &list);
+ if (err < 0)
+ return err;
+
+ if (list == NULL)
+ log_debug("No module matches '%s'", alias);
+
+ kmod_list_foreach(l, list) {
+ struct kmod_module *mod = kmod_module_get_module(l);
+
+ err = kmod_module_probe_insert_module(mod, KMOD_PROBE_APPLY_BLACKLIST, NULL, NULL, NULL, NULL);
+ if (err == KMOD_PROBE_APPLY_BLACKLIST)
+ log_debug("Module '%s' is blacklisted", kmod_module_get_name(mod));
+ else if (err == 0)
+ log_debug("Inserted '%s'", kmod_module_get_name(mod));
+ else
+ log_debug("Failed to insert '%s'", kmod_module_get_name(mod));
+
+ kmod_module_unref(mod);
+ }
+
+ kmod_module_unref_list(list);
+ return err;
+}
+
+_printf_(6,0) static void udev_kmod_log(void *data, int priority, const char *file, int line, const char *fn, const char *format, va_list args) {
+ log_internalv(priority, 0, file, line, fn, format, args);
+}
+
+static int builtin_kmod(struct udev_device *dev, int argc, char *argv[], bool test) {
+ struct udev *udev = udev_device_get_udev(dev);
+ int i;
+
+ if (!ctx)
+ return 0;
+
+ if (argc < 3 || !streq(argv[1], "load")) {
+ log_error("expect: %s load <module>", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ for (i = 2; argv[i]; i++) {
+ log_debug("Execute '%s' '%s'", argv[1], argv[i]);
+ load_module(udev, argv[i]);
+ }
+
+ return EXIT_SUCCESS;
+}
+
+/* called at udev startup and reload */
+static int builtin_kmod_init(struct udev *udev) {
+ if (ctx)
+ return 0;
+
+ ctx = kmod_new(NULL, NULL);
+ if (!ctx)
+ return -ENOMEM;
+
+ log_debug("Load module index");
+ kmod_set_log_fn(ctx, udev_kmod_log, udev);
+ kmod_load_resources(ctx);
+ return 0;
+}
+
+/* called on udev shutdown and reload request */
+static void builtin_kmod_exit(struct udev *udev) {
+ log_debug("Unload module index");
+ ctx = kmod_unref(ctx);
+}
+
+/* called every couple of seconds during event activity; 'true' if config has changed */
+static bool builtin_kmod_validate(struct udev *udev) {
+ log_debug("Validate module index");
+ if (!ctx)
+ return false;
+ return (kmod_validate_resources(ctx) != KMOD_RESOURCES_OK);
+}
+
+const struct udev_builtin udev_builtin_kmod = {
+ .name = "kmod",
+ .cmd = builtin_kmod,
+ .init = builtin_kmod_init,
+ .exit = builtin_kmod_exit,
+ .validate = builtin_kmod_validate,
+ .help = "Kernel module loader",
+ .run_once = false,
+};
diff --git a/src/grp-udev/libudev-core/udev-builtin-net_id.c b/src/grp-udev/libudev-core/udev-builtin-net_id.c
new file mode 100644
index 0000000000..3bd321ceeb
--- /dev/null
+++ b/src/grp-udev/libudev-core/udev-builtin-net_id.c
@@ -0,0 +1,639 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Kay Sievers <kay@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/>.
+***/
+
+/*
+ * Predictable network interface device names based on:
+ * - firmware/bios-provided index numbers for on-board devices
+ * - firmware-provided pci-express hotplug slot index number
+ * - physical/geographical location of the hardware
+ * - the interface's MAC address
+ *
+ * http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames
+ *
+ * Two character prefixes based on the type of interface:
+ * en — Ethernet
+ * sl — serial line IP (slip)
+ * wl — wlan
+ * ww — wwan
+ *
+ * Type of names:
+ * b<number> — BCMA bus core number
+ * c<bus_id> — CCW bus group name, without leading zeros [s390]
+ * o<index>[n<phys_port_name>|d<dev_port>]
+ * — on-board device index number
+ * s<slot>[f<function>][n<phys_port_name>|d<dev_port>]
+ * — hotplug slot index number
+ * x<MAC> — MAC address
+ * [P<domain>]p<bus>s<slot>[f<function>][n<phys_port_name>|d<dev_port>]
+ * — PCI geographical location
+ * [P<domain>]p<bus>s<slot>[f<function>][u<port>][..][c<config>][i<interface>]
+ * — USB port number chain
+ *
+ * All multi-function PCI devices will carry the [f<function>] number in the
+ * device name, including the function 0 device.
+ *
+ * When using PCI geography, The PCI domain is only prepended when it is not 0.
+ *
+ * For USB devices the full chain of port numbers of hubs is composed. If the
+ * name gets longer than the maximum number of 15 characters, the name is not
+ * exported.
+ * The usual USB configuration == 1 and interface == 0 values are suppressed.
+ *
+ * PCI Ethernet card with firmware index "1":
+ * ID_NET_NAME_ONBOARD=eno1
+ * ID_NET_NAME_ONBOARD_LABEL=Ethernet Port 1
+ *
+ * PCI Ethernet card in hotplug slot with firmware index number:
+ * /sys/devices/pci0000:00/0000:00:1c.3/0000:05:00.0/net/ens1
+ * ID_NET_NAME_MAC=enx000000000466
+ * ID_NET_NAME_PATH=enp5s0
+ * ID_NET_NAME_SLOT=ens1
+ *
+ * PCI Ethernet multi-function card with 2 ports:
+ * /sys/devices/pci0000:00/0000:00:1c.0/0000:02:00.0/net/enp2s0f0
+ * ID_NET_NAME_MAC=enx78e7d1ea46da
+ * ID_NET_NAME_PATH=enp2s0f0
+ * /sys/devices/pci0000:00/0000:00:1c.0/0000:02:00.1/net/enp2s0f1
+ * ID_NET_NAME_MAC=enx78e7d1ea46dc
+ * ID_NET_NAME_PATH=enp2s0f1
+ *
+ * PCI wlan card:
+ * /sys/devices/pci0000:00/0000:00:1c.1/0000:03:00.0/net/wlp3s0
+ * ID_NET_NAME_MAC=wlx0024d7e31130
+ * ID_NET_NAME_PATH=wlp3s0
+ *
+ * USB built-in 3G modem:
+ * /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.4/2-1.4:1.6/net/wwp0s29u1u4i6
+ * ID_NET_NAME_MAC=wwx028037ec0200
+ * ID_NET_NAME_PATH=wwp0s29u1u4i6
+ *
+ * USB Android phone:
+ * /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/net/enp0s29u1u2
+ * ID_NET_NAME_MAC=enxd626b3450fb5
+ * ID_NET_NAME_PATH=enp0s29u1u2
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <linux/pci_regs.h>
+
+#include "systemd-basic/fd-util.h"
+#include "systemd-basic/fileio.h"
+#include "systemd-basic/stdio-util.h"
+#include "systemd-basic/string-util.h"
+#include "udev.h"
+
+#define ONBOARD_INDEX_MAX (16*1024-1)
+
+enum netname_type{
+ NET_UNDEF,
+ NET_PCI,
+ NET_USB,
+ NET_BCMA,
+ NET_VIRTIO,
+ NET_CCWGROUP,
+};
+
+struct netnames {
+ enum netname_type type;
+
+ uint8_t mac[6];
+ bool mac_valid;
+
+ struct udev_device *pcidev;
+ char pci_slot[IFNAMSIZ];
+ char pci_path[IFNAMSIZ];
+ char pci_onboard[IFNAMSIZ];
+ const char *pci_onboard_label;
+
+ char usb_ports[IFNAMSIZ];
+ char bcma_core[IFNAMSIZ];
+ char ccw_group[IFNAMSIZ];
+};
+
+/* retrieve on-board index number and label from firmware */
+static int dev_pci_onboard(struct udev_device *dev, struct netnames *names) {
+ unsigned dev_port = 0;
+ size_t l;
+ char *s;
+ const char *attr, *port_name;
+ int idx;
+
+ /* ACPI _DSM — device specific method for naming a PCI or PCI Express device */
+ attr = udev_device_get_sysattr_value(names->pcidev, "acpi_index");
+ /* SMBIOS type 41 — Onboard Devices Extended Information */
+ if (!attr)
+ attr = udev_device_get_sysattr_value(names->pcidev, "index");
+ if (!attr)
+ return -ENOENT;
+
+ idx = strtoul(attr, NULL, 0);
+ if (idx <= 0)
+ return -EINVAL;
+
+ /* Some BIOSes report rubbish indexes that are excessively high (2^24-1 is an index VMware likes to report for
+ * example). Let's define a cut-off where we don't consider the index reliable anymore. We pick some arbitrary
+ * cut-off, which is somewhere beyond the realistic number of physical network interface a system might
+ * have. Ideally the kernel would already filter his crap for us, but it doesn't currently. */
+ if (idx > ONBOARD_INDEX_MAX)
+ return -ENOENT;
+
+ /* kernel provided port index for multiple ports on a single PCI function */
+ attr = udev_device_get_sysattr_value(dev, "dev_port");
+ if (attr)
+ dev_port = strtol(attr, NULL, 10);
+
+ /* kernel provided front panel port name for multiple port PCI device */
+ port_name = udev_device_get_sysattr_value(dev, "phys_port_name");
+
+ s = names->pci_onboard;
+ l = sizeof(names->pci_onboard);
+ l = strpcpyf(&s, l, "o%d", idx);
+ if (port_name)
+ l = strpcpyf(&s, l, "n%s", port_name);
+ else if (dev_port > 0)
+ l = strpcpyf(&s, l, "d%d", dev_port);
+ if (l == 0)
+ names->pci_onboard[0] = '\0';
+
+ names->pci_onboard_label = udev_device_get_sysattr_value(names->pcidev, "label");
+
+ return 0;
+}
+
+/* read the 256 bytes PCI configuration space to check the multi-function bit */
+static bool is_pci_multifunction(struct udev_device *dev) {
+ _cleanup_close_ int fd = -1;
+ const char *filename;
+ uint8_t config[64];
+
+ filename = strjoina(udev_device_get_syspath(dev), "/config");
+ fd = open(filename, O_RDONLY | O_CLOEXEC);
+ if (fd < 0)
+ return false;
+ if (read(fd, &config, sizeof(config)) != sizeof(config))
+ return false;
+
+ /* bit 0-6 header type, bit 7 multi/single function device */
+ if ((config[PCI_HEADER_TYPE] & 0x80) != 0)
+ return true;
+
+ return false;
+}
+
+static int dev_pci_slot(struct udev_device *dev, struct netnames *names) {
+ struct udev *udev = udev_device_get_udev(names->pcidev);
+ unsigned domain, bus, slot, func, dev_port = 0;
+ size_t l;
+ char *s;
+ const char *attr, *port_name;
+ struct udev_device *pci = NULL;
+ char slots[PATH_MAX];
+ _cleanup_closedir_ DIR *dir = NULL;
+ struct dirent *dent;
+ int hotplug_slot = 0, err = 0;
+
+ if (sscanf(udev_device_get_sysname(names->pcidev), "%x:%x:%x.%u", &domain, &bus, &slot, &func) != 4)
+ return -ENOENT;
+
+ /* kernel provided port index for multiple ports on a single PCI function */
+ attr = udev_device_get_sysattr_value(dev, "dev_port");
+ if (attr)
+ dev_port = strtol(attr, NULL, 10);
+
+ /* kernel provided front panel port name for multiple port PCI device */
+ port_name = udev_device_get_sysattr_value(dev, "phys_port_name");
+
+ /* compose a name based on the raw kernel's PCI bus, slot numbers */
+ s = names->pci_path;
+ l = sizeof(names->pci_path);
+ if (domain > 0)
+ l = strpcpyf(&s, l, "P%u", domain);
+ l = strpcpyf(&s, l, "p%us%u", bus, slot);
+ if (func > 0 || is_pci_multifunction(names->pcidev))
+ l = strpcpyf(&s, l, "f%u", func);
+ if (port_name)
+ l = strpcpyf(&s, l, "n%s", port_name);
+ else if (dev_port > 0)
+ l = strpcpyf(&s, l, "d%u", dev_port);
+ if (l == 0)
+ names->pci_path[0] = '\0';
+
+ /* ACPI _SUN — slot user number */
+ pci = udev_device_new_from_subsystem_sysname(udev, "subsystem", "pci");
+ if (!pci) {
+ err = -ENOENT;
+ goto out;
+ }
+
+ snprintf(slots, sizeof slots, "%s/slots", udev_device_get_syspath(pci));
+ dir = opendir(slots);
+ if (!dir) {
+ err = -errno;
+ goto out;
+ }
+
+ for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+ int i;
+ char *rest, *address, str[PATH_MAX];
+
+ if (dent->d_name[0] == '.')
+ continue;
+ i = strtol(dent->d_name, &rest, 10);
+ if (rest[0] != '\0')
+ continue;
+ if (i < 1)
+ continue;
+
+ snprintf(str, sizeof str, "%s/%s/address", slots, dent->d_name);
+ if (read_one_line_file(str, &address) >= 0) {
+ /* match slot address with device by stripping the function */
+ if (strneq(address, udev_device_get_sysname(names->pcidev), strlen(address)))
+ hotplug_slot = i;
+ free(address);
+ }
+
+ if (hotplug_slot > 0)
+ break;
+ }
+
+ if (hotplug_slot > 0) {
+ s = names->pci_slot;
+ l = sizeof(names->pci_slot);
+ if (domain > 0)
+ l = strpcpyf(&s, l, "P%d", domain);
+ l = strpcpyf(&s, l, "s%d", hotplug_slot);
+ if (func > 0 || is_pci_multifunction(names->pcidev))
+ l = strpcpyf(&s, l, "f%d", func);
+ if (port_name)
+ l = strpcpyf(&s, l, "n%s", port_name);
+ else if (dev_port > 0)
+ l = strpcpyf(&s, l, "d%d", dev_port);
+ if (l == 0)
+ names->pci_slot[0] = '\0';
+ }
+out:
+ udev_device_unref(pci);
+ return err;
+}
+
+static int names_pci(struct udev_device *dev, struct netnames *names) {
+ struct udev_device *parent;
+
+ assert(dev);
+ assert(names);
+
+ parent = udev_device_get_parent(dev);
+
+ /* there can only ever be one virtio bus per parent device, so we can
+ safely ignore any virtio buses. see
+ <http://lists.linuxfoundation.org/pipermail/virtualization/2015-August/030331.html> */
+ while (parent && streq_ptr("virtio", udev_device_get_subsystem(parent)))
+ parent = udev_device_get_parent(parent);
+
+ if (!parent)
+ return -ENOENT;
+
+ /* check if our direct parent is a PCI device with no other bus in-between */
+ if (streq_ptr("pci", udev_device_get_subsystem(parent))) {
+ names->type = NET_PCI;
+ names->pcidev = parent;
+ } else {
+ names->pcidev = udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL);
+ if (!names->pcidev)
+ return -ENOENT;
+ }
+ dev_pci_onboard(dev, names);
+ dev_pci_slot(dev, names);
+ return 0;
+}
+
+static int names_usb(struct udev_device *dev, struct netnames *names) {
+ struct udev_device *usbdev;
+ char name[256];
+ char *ports;
+ char *config;
+ char *interf;
+ size_t l;
+ char *s;
+
+ assert(dev);
+ assert(names);
+
+ usbdev = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_interface");
+ if (!usbdev)
+ return -ENOENT;
+
+ /* get USB port number chain, configuration, interface */
+ strscpy(name, sizeof(name), udev_device_get_sysname(usbdev));
+ s = strchr(name, '-');
+ if (!s)
+ return -EINVAL;
+ ports = s+1;
+
+ s = strchr(ports, ':');
+ if (!s)
+ return -EINVAL;
+ s[0] = '\0';
+ config = s+1;
+
+ s = strchr(config, '.');
+ if (!s)
+ return -EINVAL;
+ s[0] = '\0';
+ interf = s+1;
+
+ /* prefix every port number in the chain with "u" */
+ s = ports;
+ while ((s = strchr(s, '.')))
+ s[0] = 'u';
+ s = names->usb_ports;
+ l = strpcpyl(&s, sizeof(names->usb_ports), "u", ports, NULL);
+
+ /* append USB config number, suppress the common config == 1 */
+ if (!streq(config, "1"))
+ l = strpcpyl(&s, sizeof(names->usb_ports), "c", config, NULL);
+
+ /* append USB interface number, suppress the interface == 0 */
+ if (!streq(interf, "0"))
+ l = strpcpyl(&s, sizeof(names->usb_ports), "i", interf, NULL);
+ if (l == 0)
+ return -ENAMETOOLONG;
+
+ names->type = NET_USB;
+ return 0;
+}
+
+static int names_bcma(struct udev_device *dev, struct netnames *names) {
+ struct udev_device *bcmadev;
+ unsigned int core;
+
+ assert(dev);
+ assert(names);
+
+ bcmadev = udev_device_get_parent_with_subsystem_devtype(dev, "bcma", NULL);
+ if (!bcmadev)
+ return -ENOENT;
+
+ /* bus num:core num */
+ if (sscanf(udev_device_get_sysname(bcmadev), "bcma%*u:%u", &core) != 1)
+ return -EINVAL;
+ /* suppress the common core == 0 */
+ if (core > 0)
+ xsprintf(names->bcma_core, "b%u", core);
+
+ names->type = NET_BCMA;
+ return 0;
+}
+
+static int names_ccw(struct udev_device *dev, struct netnames *names) {
+ struct udev_device *cdev;
+ const char *bus_id;
+ size_t bus_id_len;
+ int rc;
+
+ assert(dev);
+ assert(names);
+
+ /* Retrieve the associated CCW device */
+ cdev = udev_device_get_parent(dev);
+ if (!cdev)
+ return -ENOENT;
+
+ /* Network devices are always grouped CCW devices */
+ if (!streq_ptr("ccwgroup", udev_device_get_subsystem(cdev)))
+ return -ENOENT;
+
+ /* Retrieve bus-ID of the grouped CCW device. The bus-ID uniquely
+ * identifies the network device on the Linux on System z channel
+ * subsystem. Note that the bus-ID contains lowercase characters.
+ */
+ bus_id = udev_device_get_sysname(cdev);
+ if (!bus_id)
+ return -ENOENT;
+
+ /* Check the length of the bus-ID. Rely on that the kernel provides
+ * a correct bus-ID; alternatively, improve this check and parse and
+ * verify each bus-ID part...
+ */
+ bus_id_len = strlen(bus_id);
+ if (!bus_id_len || bus_id_len < 8 || bus_id_len > 9)
+ return -EINVAL;
+
+ /* Strip leading zeros from the bus id for aesthetic purposes. This
+ * keeps the ccw names stable, yet much shorter in general case of
+ * bus_id 0.0.0600 -> 600. This is similar to e.g. how PCI domain is
+ * not prepended when it is zero.
+ */
+ bus_id += strspn(bus_id, ".0");
+
+ /* Store the CCW bus-ID for use as network device name */
+ rc = snprintf(names->ccw_group, sizeof(names->ccw_group), "c%s", bus_id);
+ if (rc >= 0 && rc < (int)sizeof(names->ccw_group))
+ names->type = NET_CCWGROUP;
+ return 0;
+}
+
+static int names_mac(struct udev_device *dev, struct netnames *names) {
+ const char *s;
+ unsigned int i;
+ unsigned int a1, a2, a3, a4, a5, a6;
+
+ /* check for NET_ADDR_PERM, skip random MAC addresses */
+ s = udev_device_get_sysattr_value(dev, "addr_assign_type");
+ if (!s)
+ return EXIT_FAILURE;
+ i = strtoul(s, NULL, 0);
+ if (i != 0)
+ return 0;
+
+ s = udev_device_get_sysattr_value(dev, "address");
+ if (!s)
+ return -ENOENT;
+ if (sscanf(s, "%x:%x:%x:%x:%x:%x", &a1, &a2, &a3, &a4, &a5, &a6) != 6)
+ return -EINVAL;
+
+ /* skip empty MAC addresses */
+ if (a1 + a2 + a3 + a4 + a5 + a6 == 0)
+ return -EINVAL;
+
+ names->mac[0] = a1;
+ names->mac[1] = a2;
+ names->mac[2] = a3;
+ names->mac[3] = a4;
+ names->mac[4] = a5;
+ names->mac[5] = a6;
+ names->mac_valid = true;
+ return 0;
+}
+
+/* IEEE Organizationally Unique Identifier vendor string */
+static int ieee_oui(struct udev_device *dev, struct netnames *names, bool test) {
+ char str[32];
+
+ if (!names->mac_valid)
+ return -ENOENT;
+ /* skip commonly misused 00:00:00 (Xerox) prefix */
+ if (memcmp(names->mac, "\0\0\0", 3) == 0)
+ return -EINVAL;
+ xsprintf(str, "OUI:%02X%02X%02X%02X%02X%02X", names->mac[0],
+ names->mac[1], names->mac[2], names->mac[3], names->mac[4],
+ names->mac[5]);
+ udev_builtin_hwdb_lookup(dev, NULL, str, NULL, test);
+ return 0;
+}
+
+static int builtin_net_id(struct udev_device *dev, int argc, char *argv[], bool test) {
+ const char *s;
+ const char *p;
+ unsigned int i;
+ const char *devtype;
+ const char *prefix = "en";
+ struct netnames names = {};
+ int err;
+
+ /* handle only ARPHRD_ETHER and ARPHRD_SLIP devices */
+ s = udev_device_get_sysattr_value(dev, "type");
+ if (!s)
+ return EXIT_FAILURE;
+ i = strtoul(s, NULL, 0);
+ switch (i) {
+ case ARPHRD_ETHER:
+ prefix = "en";
+ break;
+ case ARPHRD_SLIP:
+ prefix = "sl";
+ break;
+ default:
+ return 0;
+ }
+
+ /* skip stacked devices, like VLANs, ... */
+ s = udev_device_get_sysattr_value(dev, "ifindex");
+ if (!s)
+ return EXIT_FAILURE;
+ p = udev_device_get_sysattr_value(dev, "iflink");
+ if (!p)
+ return EXIT_FAILURE;
+ if (!streq(s, p))
+ return 0;
+
+ devtype = udev_device_get_devtype(dev);
+ if (devtype) {
+ if (streq("wlan", devtype))
+ prefix = "wl";
+ else if (streq("wwan", devtype))
+ prefix = "ww";
+ }
+
+ err = names_mac(dev, &names);
+ if (err >= 0 && names.mac_valid) {
+ char str[IFNAMSIZ];
+
+ xsprintf(str, "%sx%02x%02x%02x%02x%02x%02x", prefix,
+ names.mac[0], names.mac[1], names.mac[2],
+ names.mac[3], names.mac[4], names.mac[5]);
+ udev_builtin_add_property(dev, test, "ID_NET_NAME_MAC", str);
+
+ ieee_oui(dev, &names, test);
+ }
+
+ /* get path names for Linux on System z network devices */
+ err = names_ccw(dev, &names);
+ if (err >= 0 && names.type == NET_CCWGROUP) {
+ char str[IFNAMSIZ];
+
+ if (snprintf(str, sizeof(str), "%s%s", prefix, names.ccw_group) < (int)sizeof(str))
+ udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str);
+ goto out;
+ }
+
+ /* get PCI based path names, we compose only PCI based paths */
+ err = names_pci(dev, &names);
+ if (err < 0)
+ goto out;
+
+ /* plain PCI device */
+ if (names.type == NET_PCI) {
+ char str[IFNAMSIZ];
+
+ if (names.pci_onboard[0])
+ if (snprintf(str, sizeof(str), "%s%s", prefix, names.pci_onboard) < (int)sizeof(str))
+ udev_builtin_add_property(dev, test, "ID_NET_NAME_ONBOARD", str);
+
+ if (names.pci_onboard_label)
+ if (snprintf(str, sizeof(str), "%s%s", prefix, names.pci_onboard_label) < (int)sizeof(str))
+ udev_builtin_add_property(dev, test, "ID_NET_LABEL_ONBOARD", str);
+
+ if (names.pci_path[0])
+ if (snprintf(str, sizeof(str), "%s%s", prefix, names.pci_path) < (int)sizeof(str))
+ udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str);
+
+ if (names.pci_slot[0])
+ if (snprintf(str, sizeof(str), "%s%s", prefix, names.pci_slot) < (int)sizeof(str))
+ udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str);
+ goto out;
+ }
+
+ /* USB device */
+ err = names_usb(dev, &names);
+ if (err >= 0 && names.type == NET_USB) {
+ char str[IFNAMSIZ];
+
+ if (names.pci_path[0])
+ if (snprintf(str, sizeof(str), "%s%s%s", prefix, names.pci_path, names.usb_ports) < (int)sizeof(str))
+ udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str);
+
+ if (names.pci_slot[0])
+ if (snprintf(str, sizeof(str), "%s%s%s", prefix, names.pci_slot, names.usb_ports) < (int)sizeof(str))
+ udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str);
+ goto out;
+ }
+
+ /* Broadcom bus */
+ err = names_bcma(dev, &names);
+ if (err >= 0 && names.type == NET_BCMA) {
+ char str[IFNAMSIZ];
+
+ if (names.pci_path[0])
+ if (snprintf(str, sizeof(str), "%s%s%s", prefix, names.pci_path, names.bcma_core) < (int)sizeof(str))
+ udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str);
+
+ if (names.pci_slot[0])
+ if (snprintf(str, sizeof(str), "%s%s%s", prefix, names.pci_slot, names.bcma_core) < (int)sizeof(str))
+ udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str);
+ goto out;
+ }
+out:
+ return EXIT_SUCCESS;
+}
+
+const struct udev_builtin udev_builtin_net_id = {
+ .name = "net_id",
+ .cmd = builtin_net_id,
+ .help = "Network device properties",
+};
diff --git a/src/grp-udev/libudev-core/udev-builtin-net_setup_link.c b/src/grp-udev/libudev-core/udev-builtin-net_setup_link.c
new file mode 100644
index 0000000000..38d0955f3d
--- /dev/null
+++ b/src/grp-udev/libudev-core/udev-builtin-net_setup_link.c
@@ -0,0 +1,107 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Tom Gundersen
+
+ 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 "link-config.h"
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/log.h"
+#include "udev.h"
+
+static link_config_ctx *ctx = NULL;
+
+static int builtin_net_setup_link(struct udev_device *dev, int argc, char **argv, bool test) {
+ _cleanup_free_ char *driver = NULL;
+ const char *name = NULL;
+ link_config *link;
+ int r;
+
+ if (argc > 1) {
+ log_error("This program takes no arguments.");
+ return EXIT_FAILURE;
+ }
+
+ r = link_get_driver(ctx, dev, &driver);
+ if (r >= 0)
+ udev_builtin_add_property(dev, test, "ID_NET_DRIVER", driver);
+
+ r = link_config_get(ctx, dev, &link);
+ if (r < 0) {
+ if (r == -ENOENT) {
+ log_debug("No matching link configuration found.");
+ return EXIT_SUCCESS;
+ } else {
+ log_error_errno(r, "Could not get link config: %m");
+ return EXIT_FAILURE;
+ }
+ }
+
+ r = link_config_apply(ctx, link, dev, &name);
+ if (r < 0) {
+ log_error_errno(r, "Could not apply link config to %s: %m", udev_device_get_sysname(dev));
+ return EXIT_FAILURE;
+ }
+
+ udev_builtin_add_property(dev, test, "ID_NET_LINK_FILE", link->filename);
+
+ if (name)
+ udev_builtin_add_property(dev, test, "ID_NET_NAME", name);
+
+ return EXIT_SUCCESS;
+}
+
+static int builtin_net_setup_link_init(struct udev *udev) {
+ int r;
+
+ if (ctx)
+ return 0;
+
+ r = link_config_ctx_new(&ctx);
+ if (r < 0)
+ return r;
+
+ r = link_config_load(ctx);
+ if (r < 0)
+ return r;
+
+ log_debug("Created link configuration context.");
+ return 0;
+}
+
+static void builtin_net_setup_link_exit(struct udev *udev) {
+ link_config_ctx_free(ctx);
+ ctx = NULL;
+ log_debug("Unloaded link configuration context.");
+}
+
+static bool builtin_net_setup_link_validate(struct udev *udev) {
+ log_debug("Check if link configuration needs reloading.");
+ if (!ctx)
+ return false;
+
+ return link_config_should_reload(ctx);
+}
+
+const struct udev_builtin udev_builtin_net_setup_link = {
+ .name = "net_setup_link",
+ .cmd = builtin_net_setup_link,
+ .init = builtin_net_setup_link_init,
+ .exit = builtin_net_setup_link_exit,
+ .validate = builtin_net_setup_link_validate,
+ .help = "Configure network link",
+ .run_once = false,
+};
diff --git a/src/grp-udev/libudev-core/udev-builtin-path_id.c b/src/grp-udev/libudev-core/udev-builtin-path_id.c
new file mode 100644
index 0000000000..df1a999683
--- /dev/null
+++ b/src/grp-udev/libudev-core/udev-builtin-path_id.c
@@ -0,0 +1,770 @@
+/*
+ * compose persistent device path
+ *
+ * Copyright (C) 2009-2011 Kay Sievers <kay@vrfy.org>
+ *
+ * Logic based on Hannes Reinecke's shell script.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/string-util.h"
+#include "udev.h"
+
+_printf_(2,3)
+static int path_prepend(char **path, const char *fmt, ...) {
+ va_list va;
+ char *pre;
+ int err = 0;
+
+ va_start(va, fmt);
+ err = vasprintf(&pre, fmt, va);
+ va_end(va);
+ if (err < 0)
+ goto out;
+
+ if (*path != NULL) {
+ char *new;
+
+ err = asprintf(&new, "%s-%s", pre, *path);
+ free(pre);
+ if (err < 0)
+ goto out;
+ free(*path);
+ *path = new;
+ } else {
+ *path = pre;
+ }
+out:
+ return err;
+}
+
+/*
+** Linux only supports 32 bit luns.
+** See drivers/scsi/scsi_scan.c::scsilun_to_int() for more details.
+*/
+static int format_lun_number(struct udev_device *dev, char **path) {
+ unsigned long lun = strtoul(udev_device_get_sysnum(dev), NULL, 10);
+
+ /* address method 0, peripheral device addressing with bus id of zero */
+ if (lun < 256)
+ return path_prepend(path, "lun-%lu", lun);
+ /* handle all other lun addressing methods by using a variant of the original lun format */
+ return path_prepend(path, "lun-0x%04lx%04lx00000000", lun & 0xffff, (lun >> 16) & 0xffff);
+}
+
+static struct udev_device *skip_subsystem(struct udev_device *dev, const char *subsys) {
+ struct udev_device *parent = dev;
+
+ assert(dev);
+ assert(subsys);
+
+ while (parent != NULL) {
+ const char *subsystem;
+
+ subsystem = udev_device_get_subsystem(parent);
+ if (subsystem == NULL || !streq(subsystem, subsys))
+ break;
+ dev = parent;
+ parent = udev_device_get_parent(parent);
+ }
+ return dev;
+}
+
+static struct udev_device *handle_scsi_fibre_channel(struct udev_device *parent, char **path) {
+ struct udev *udev = udev_device_get_udev(parent);
+ struct udev_device *targetdev;
+ struct udev_device *fcdev = NULL;
+ const char *port;
+ char *lun = NULL;
+
+ assert(parent);
+ assert(path);
+
+ targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target");
+ if (targetdev == NULL)
+ return NULL;
+
+ fcdev = udev_device_new_from_subsystem_sysname(udev, "fc_transport", udev_device_get_sysname(targetdev));
+ if (fcdev == NULL)
+ return NULL;
+ port = udev_device_get_sysattr_value(fcdev, "port_name");
+ if (port == NULL) {
+ parent = NULL;
+ goto out;
+ }
+
+ format_lun_number(parent, &lun);
+ path_prepend(path, "fc-%s-%s", port, lun);
+ free(lun);
+out:
+ udev_device_unref(fcdev);
+ return parent;
+}
+
+static struct udev_device *handle_scsi_sas_wide_port(struct udev_device *parent, char **path) {
+ struct udev *udev = udev_device_get_udev(parent);
+ struct udev_device *targetdev;
+ struct udev_device *target_parent;
+ struct udev_device *sasdev;
+ const char *sas_address;
+ char *lun = NULL;
+
+ assert(parent);
+ assert(path);
+
+ targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target");
+ if (targetdev == NULL)
+ return NULL;
+
+ target_parent = udev_device_get_parent(targetdev);
+ if (target_parent == NULL)
+ return NULL;
+
+ sasdev = udev_device_new_from_subsystem_sysname(udev, "sas_device",
+ udev_device_get_sysname(target_parent));
+ if (sasdev == NULL)
+ return NULL;
+
+ sas_address = udev_device_get_sysattr_value(sasdev, "sas_address");
+ if (sas_address == NULL) {
+ parent = NULL;
+ goto out;
+ }
+
+ format_lun_number(parent, &lun);
+ path_prepend(path, "sas-%s-%s", sas_address, lun);
+ free(lun);
+out:
+ udev_device_unref(sasdev);
+ return parent;
+}
+
+static struct udev_device *handle_scsi_sas(struct udev_device *parent, char **path)
+{
+ struct udev *udev = udev_device_get_udev(parent);
+ struct udev_device *targetdev;
+ struct udev_device *target_parent;
+ struct udev_device *port;
+ struct udev_device *expander;
+ struct udev_device *target_sasdev = NULL;
+ struct udev_device *expander_sasdev = NULL;
+ struct udev_device *port_sasdev = NULL;
+ const char *sas_address = NULL;
+ const char *phy_id;
+ const char *phy_count;
+ char *lun = NULL;
+
+ assert(parent);
+ assert(path);
+
+ targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target");
+ if (targetdev == NULL)
+ return NULL;
+
+ target_parent = udev_device_get_parent(targetdev);
+ if (target_parent == NULL)
+ return NULL;
+
+ /* Get sas device */
+ target_sasdev = udev_device_new_from_subsystem_sysname(udev,
+ "sas_device", udev_device_get_sysname(target_parent));
+ if (target_sasdev == NULL)
+ return NULL;
+
+ /* The next parent is sas port */
+ port = udev_device_get_parent(target_parent);
+ if (port == NULL) {
+ parent = NULL;
+ goto out;
+ }
+
+ /* Get port device */
+ port_sasdev = udev_device_new_from_subsystem_sysname(udev,
+ "sas_port", udev_device_get_sysname(port));
+
+ phy_count = udev_device_get_sysattr_value(port_sasdev, "num_phys");
+ if (phy_count == NULL) {
+ parent = NULL;
+ goto out;
+ }
+
+ /* Check if we are simple disk */
+ if (strncmp(phy_count, "1", 2) != 0) {
+ parent = handle_scsi_sas_wide_port(parent, path);
+ goto out;
+ }
+
+ /* Get connected phy */
+ phy_id = udev_device_get_sysattr_value(target_sasdev, "phy_identifier");
+ if (phy_id == NULL) {
+ parent = NULL;
+ goto out;
+ }
+
+ /* The port's parent is either hba or expander */
+ expander = udev_device_get_parent(port);
+ if (expander == NULL) {
+ parent = NULL;
+ goto out;
+ }
+
+ /* Get expander device */
+ expander_sasdev = udev_device_new_from_subsystem_sysname(udev,
+ "sas_device", udev_device_get_sysname(expander));
+ if (expander_sasdev != NULL) {
+ /* Get expander's address */
+ sas_address = udev_device_get_sysattr_value(expander_sasdev,
+ "sas_address");
+ if (sas_address == NULL) {
+ parent = NULL;
+ goto out;
+ }
+ }
+
+ format_lun_number(parent, &lun);
+ if (sas_address)
+ path_prepend(path, "sas-exp%s-phy%s-%s", sas_address, phy_id, lun);
+ else
+ path_prepend(path, "sas-phy%s-%s", phy_id, lun);
+
+ free(lun);
+out:
+ udev_device_unref(target_sasdev);
+ udev_device_unref(expander_sasdev);
+ udev_device_unref(port_sasdev);
+ return parent;
+}
+
+static struct udev_device *handle_scsi_iscsi(struct udev_device *parent, char **path) {
+ struct udev *udev = udev_device_get_udev(parent);
+ struct udev_device *transportdev;
+ struct udev_device *sessiondev = NULL;
+ const char *target;
+ char *connname;
+ struct udev_device *conndev = NULL;
+ const char *addr;
+ const char *port;
+ char *lun = NULL;
+
+ assert(parent);
+ assert(path);
+
+ /* find iscsi session */
+ transportdev = parent;
+ for (;;) {
+ transportdev = udev_device_get_parent(transportdev);
+ if (transportdev == NULL)
+ return NULL;
+ if (startswith(udev_device_get_sysname(transportdev), "session"))
+ break;
+ }
+
+ /* find iscsi session device */
+ sessiondev = udev_device_new_from_subsystem_sysname(udev, "iscsi_session", udev_device_get_sysname(transportdev));
+ if (sessiondev == NULL)
+ return NULL;
+ target = udev_device_get_sysattr_value(sessiondev, "targetname");
+ if (target == NULL) {
+ parent = NULL;
+ goto out;
+ }
+
+ if (asprintf(&connname, "connection%s:0", udev_device_get_sysnum(transportdev)) < 0) {
+ parent = NULL;
+ goto out;
+ }
+ conndev = udev_device_new_from_subsystem_sysname(udev, "iscsi_connection", connname);
+ free(connname);
+ if (conndev == NULL) {
+ parent = NULL;
+ goto out;
+ }
+ addr = udev_device_get_sysattr_value(conndev, "persistent_address");
+ port = udev_device_get_sysattr_value(conndev, "persistent_port");
+ if (addr == NULL || port == NULL) {
+ parent = NULL;
+ goto out;
+ }
+
+ format_lun_number(parent, &lun);
+ path_prepend(path, "ip-%s:%s-iscsi-%s-%s", addr, port, target, lun);
+ free(lun);
+out:
+ udev_device_unref(sessiondev);
+ udev_device_unref(conndev);
+ return parent;
+}
+
+static struct udev_device *handle_scsi_ata(struct udev_device *parent, char **path) {
+ struct udev *udev = udev_device_get_udev(parent);
+ struct udev_device *targetdev;
+ struct udev_device *target_parent;
+ struct udev_device *atadev;
+ const char *port_no;
+
+ assert(parent);
+ assert(path);
+
+ targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host");
+ if (!targetdev)
+ return NULL;
+
+ target_parent = udev_device_get_parent(targetdev);
+ if (!target_parent)
+ return NULL;
+
+ atadev = udev_device_new_from_subsystem_sysname(udev, "ata_port", udev_device_get_sysname(target_parent));
+ if (!atadev)
+ return NULL;
+
+ port_no = udev_device_get_sysattr_value(atadev, "port_no");
+ if (!port_no) {
+ parent = NULL;
+ goto out;
+ }
+ path_prepend(path, "ata-%s", port_no);
+out:
+ udev_device_unref(atadev);
+ return parent;
+}
+
+static struct udev_device *handle_scsi_default(struct udev_device *parent, char **path) {
+ struct udev_device *hostdev;
+ int host, bus, target, lun;
+ const char *name;
+ char *base;
+ char *pos;
+ DIR *dir;
+ struct dirent *dent;
+ int basenum;
+
+ assert(parent);
+ assert(path);
+
+ hostdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host");
+ if (hostdev == NULL)
+ return NULL;
+
+ name = udev_device_get_sysname(parent);
+ if (sscanf(name, "%d:%d:%d:%d", &host, &bus, &target, &lun) != 4)
+ return NULL;
+
+ /*
+ * Rebase host offset to get the local relative number
+ *
+ * Note: This is by definition racy, unreliable and too simple.
+ * Please do not copy this model anywhere. It's just a left-over
+ * from the time we had no idea how things should look like in
+ * the end.
+ *
+ * Making assumptions about a global in-kernel counter and use
+ * that to calculate a local offset is a very broken concept. It
+ * can only work as long as things are in strict order.
+ *
+ * The kernel needs to export the instance/port number of a
+ * controller directly, without the need for rebase magic like
+ * this. Manual driver unbind/bind, parallel hotplug/unplug will
+ * get into the way of this "I hope it works" logic.
+ */
+ basenum = -1;
+ base = strdup(udev_device_get_syspath(hostdev));
+ if (base == NULL)
+ return NULL;
+ pos = strrchr(base, '/');
+ if (pos == NULL) {
+ parent = NULL;
+ goto out;
+ }
+ pos[0] = '\0';
+ dir = opendir(base);
+ if (dir == NULL) {
+ parent = NULL;
+ goto out;
+ }
+ for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+ char *rest;
+ int i;
+
+ if (dent->d_name[0] == '.')
+ continue;
+ if (dent->d_type != DT_DIR && dent->d_type != DT_LNK)
+ continue;
+ if (!startswith(dent->d_name, "host"))
+ continue;
+ i = strtoul(&dent->d_name[4], &rest, 10);
+ if (rest[0] != '\0')
+ continue;
+ /*
+ * find the smallest number; the host really needs to export its
+ * own instance number per parent device; relying on the global host
+ * enumeration and plainly rebasing the numbers sounds unreliable
+ */
+ if (basenum == -1 || i < basenum)
+ basenum = i;
+ }
+ closedir(dir);
+ if (basenum == -1) {
+ parent = NULL;
+ goto out;
+ }
+ host -= basenum;
+
+ path_prepend(path, "scsi-%u:%u:%u:%u", host, bus, target, lun);
+out:
+ free(base);
+ return hostdev;
+}
+
+static struct udev_device *handle_scsi_hyperv(struct udev_device *parent, char **path) {
+ struct udev_device *hostdev;
+ struct udev_device *vmbusdev;
+ const char *guid_str;
+ char *lun = NULL;
+ char guid[38];
+ size_t i, k;
+
+ assert(parent);
+ assert(path);
+
+ hostdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host");
+ if (!hostdev)
+ return NULL;
+
+ vmbusdev = udev_device_get_parent(hostdev);
+ if (!vmbusdev)
+ return NULL;
+
+ guid_str = udev_device_get_sysattr_value(vmbusdev, "device_id");
+ if (!guid_str)
+ return NULL;
+
+ if (strlen(guid_str) < 37 || guid_str[0] != '{' || guid_str[36] != '}')
+ return NULL;
+
+ for (i = 1, k = 0; i < 36; i++) {
+ if (guid_str[i] == '-')
+ continue;
+ guid[k++] = guid_str[i];
+ }
+ guid[k] = '\0';
+
+ format_lun_number(parent, &lun);
+ path_prepend(path, "vmbus-%s-%s", guid, lun);
+ free(lun);
+ return parent;
+}
+
+static struct udev_device *handle_scsi(struct udev_device *parent, char **path, bool *supported_parent) {
+ const char *devtype;
+ const char *name;
+ const char *id;
+
+ devtype = udev_device_get_devtype(parent);
+ if (devtype == NULL || !streq(devtype, "scsi_device"))
+ return parent;
+
+ /* firewire */
+ id = udev_device_get_sysattr_value(parent, "ieee1394_id");
+ if (id != NULL) {
+ parent = skip_subsystem(parent, "scsi");
+ path_prepend(path, "ieee1394-0x%s", id);
+ *supported_parent = true;
+ goto out;
+ }
+
+ /* scsi sysfs does not have a "subsystem" for the transport */
+ name = udev_device_get_syspath(parent);
+
+ if (strstr(name, "/rport-") != NULL) {
+ parent = handle_scsi_fibre_channel(parent, path);
+ *supported_parent = true;
+ goto out;
+ }
+
+ if (strstr(name, "/end_device-") != NULL) {
+ parent = handle_scsi_sas(parent, path);
+ *supported_parent = true;
+ goto out;
+ }
+
+ if (strstr(name, "/session") != NULL) {
+ parent = handle_scsi_iscsi(parent, path);
+ *supported_parent = true;
+ goto out;
+ }
+
+ if (strstr(name, "/ata") != NULL) {
+ parent = handle_scsi_ata(parent, path);
+ goto out;
+ }
+
+ if (strstr(name, "/vmbus_") != NULL) {
+ parent = handle_scsi_hyperv(parent, path);
+ goto out;
+ }
+
+ parent = handle_scsi_default(parent, path);
+out:
+ return parent;
+}
+
+static struct udev_device *handle_cciss(struct udev_device *parent, char **path) {
+ const char *str;
+ unsigned int controller, disk;
+
+ str = udev_device_get_sysname(parent);
+ if (sscanf(str, "c%ud%u%*s", &controller, &disk) != 2)
+ return NULL;
+
+ path_prepend(path, "cciss-disk%u", disk);
+ parent = skip_subsystem(parent, "cciss");
+ return parent;
+}
+
+static void handle_scsi_tape(struct udev_device *dev, char **path) {
+ const char *name;
+
+ /* must be the last device in the syspath */
+ if (*path != NULL)
+ return;
+
+ name = udev_device_get_sysname(dev);
+ if (startswith(name, "nst") && strchr("lma", name[3]) != NULL)
+ path_prepend(path, "nst%c", name[3]);
+ else if (startswith(name, "st") && strchr("lma", name[2]) != NULL)
+ path_prepend(path, "st%c", name[2]);
+}
+
+static struct udev_device *handle_usb(struct udev_device *parent, char **path) {
+ const char *devtype;
+ const char *str;
+ const char *port;
+
+ devtype = udev_device_get_devtype(parent);
+ if (devtype == NULL)
+ return parent;
+ if (!streq(devtype, "usb_interface") && !streq(devtype, "usb_device"))
+ return parent;
+
+ str = udev_device_get_sysname(parent);
+ port = strchr(str, '-');
+ if (port == NULL)
+ return parent;
+ port++;
+
+ parent = skip_subsystem(parent, "usb");
+ path_prepend(path, "usb-0:%s", port);
+ return parent;
+}
+
+static struct udev_device *handle_bcma(struct udev_device *parent, char **path) {
+ const char *sysname;
+ unsigned int core;
+
+ sysname = udev_device_get_sysname(parent);
+ if (sscanf(sysname, "bcma%*u:%u", &core) != 1)
+ return NULL;
+
+ path_prepend(path, "bcma-%u", core);
+ return parent;
+}
+
+/* Handle devices of AP bus in System z platform. */
+static struct udev_device *handle_ap(struct udev_device *parent, char **path) {
+ const char *type, *func;
+
+ assert(parent);
+ assert(path);
+
+ type = udev_device_get_sysattr_value(parent, "type");
+ func = udev_device_get_sysattr_value(parent, "ap_functions");
+
+ if (type != NULL && func != NULL) {
+ path_prepend(path, "ap-%s-%s", type, func);
+ goto out;
+ }
+ path_prepend(path, "ap-%s", udev_device_get_sysname(parent));
+out:
+ parent = skip_subsystem(parent, "ap");
+ return parent;
+}
+
+static int builtin_path_id(struct udev_device *dev, int argc, char *argv[], bool test) {
+ struct udev_device *parent;
+ char *path = NULL;
+ bool supported_transport = false;
+ bool supported_parent = false;
+
+ assert(dev);
+
+ /* walk up the chain of devices and compose path */
+ parent = dev;
+ while (parent != NULL) {
+ const char *subsys;
+
+ subsys = udev_device_get_subsystem(parent);
+ if (subsys == NULL) {
+ ;
+ } else if (streq(subsys, "scsi_tape")) {
+ handle_scsi_tape(parent, &path);
+ } else if (streq(subsys, "scsi")) {
+ parent = handle_scsi(parent, &path, &supported_parent);
+ supported_transport = true;
+ } else if (streq(subsys, "cciss")) {
+ parent = handle_cciss(parent, &path);
+ supported_transport = true;
+ } else if (streq(subsys, "usb")) {
+ parent = handle_usb(parent, &path);
+ supported_transport = true;
+ } else if (streq(subsys, "bcma")) {
+ parent = handle_bcma(parent, &path);
+ supported_transport = true;
+ } else if (streq(subsys, "serio")) {
+ path_prepend(&path, "serio-%s", udev_device_get_sysnum(parent));
+ parent = skip_subsystem(parent, "serio");
+ } else if (streq(subsys, "pci")) {
+ path_prepend(&path, "pci-%s", udev_device_get_sysname(parent));
+ parent = skip_subsystem(parent, "pci");
+ supported_parent = true;
+ } else if (streq(subsys, "platform")) {
+ path_prepend(&path, "platform-%s", udev_device_get_sysname(parent));
+ parent = skip_subsystem(parent, "platform");
+ supported_transport = true;
+ supported_parent = true;
+ } else if (streq(subsys, "acpi")) {
+ path_prepend(&path, "acpi-%s", udev_device_get_sysname(parent));
+ parent = skip_subsystem(parent, "acpi");
+ supported_parent = true;
+ } else if (streq(subsys, "xen")) {
+ path_prepend(&path, "xen-%s", udev_device_get_sysname(parent));
+ parent = skip_subsystem(parent, "xen");
+ supported_parent = true;
+ } else if (streq(subsys, "virtio")) {
+ while (parent && streq_ptr("virtio", udev_device_get_subsystem(parent)))
+ parent = udev_device_get_parent(parent);
+ path_prepend(&path, "virtio-pci-%s", udev_device_get_sysname(parent));
+ supported_transport = true;
+ supported_parent = true;
+ } else if (streq(subsys, "scm")) {
+ path_prepend(&path, "scm-%s", udev_device_get_sysname(parent));
+ parent = skip_subsystem(parent, "scm");
+ supported_transport = true;
+ supported_parent = true;
+ } else if (streq(subsys, "ccw")) {
+ path_prepend(&path, "ccw-%s", udev_device_get_sysname(parent));
+ parent = skip_subsystem(parent, "ccw");
+ supported_transport = true;
+ supported_parent = true;
+ } else if (streq(subsys, "ccwgroup")) {
+ path_prepend(&path, "ccwgroup-%s", udev_device_get_sysname(parent));
+ parent = skip_subsystem(parent, "ccwgroup");
+ supported_transport = true;
+ supported_parent = true;
+ } else if (streq(subsys, "ap")) {
+ parent = handle_ap(parent, &path);
+ supported_transport = true;
+ supported_parent = true;
+ } else if (streq(subsys, "iucv")) {
+ path_prepend(&path, "iucv-%s", udev_device_get_sysname(parent));
+ parent = skip_subsystem(parent, "iucv");
+ supported_transport = true;
+ supported_parent = true;
+ } else if (streq(subsys, "nvme")) {
+ const char *nsid = udev_device_get_sysattr_value(dev, "nsid");
+
+ if (nsid) {
+ path_prepend(&path, "nvme-%s", nsid);
+ parent = skip_subsystem(parent, "nvme");
+ supported_parent = true;
+ supported_transport = true;
+ }
+ }
+
+ if (parent)
+ parent = udev_device_get_parent(parent);
+ }
+
+ /*
+ * Do not return devices with an unknown parent device type. They
+ * might produce conflicting IDs if the parent does not provide a
+ * unique and predictable name.
+ */
+ if (!supported_parent)
+ path = mfree(path);
+
+ /*
+ * Do not return block devices without a well-known transport. Some
+ * devices do not expose their buses and do not provide a unique
+ * and predictable name that way.
+ */
+ if (streq_ptr(udev_device_get_subsystem(dev), "block") && !supported_transport)
+ path = mfree(path);
+
+ if (path != NULL) {
+ char tag[UTIL_NAME_SIZE];
+ size_t i;
+ const char *p;
+
+ /* compose valid udev tag name */
+ for (p = path, i = 0; *p; p++) {
+ if ((*p >= '0' && *p <= '9') ||
+ (*p >= 'A' && *p <= 'Z') ||
+ (*p >= 'a' && *p <= 'z') ||
+ *p == '-') {
+ tag[i++] = *p;
+ continue;
+ }
+
+ /* skip all leading '_' */
+ if (i == 0)
+ continue;
+
+ /* avoid second '_' */
+ if (tag[i-1] == '_')
+ continue;
+
+ tag[i++] = '_';
+ }
+ /* strip trailing '_' */
+ while (i > 0 && tag[i-1] == '_')
+ i--;
+ tag[i] = '\0';
+
+ udev_builtin_add_property(dev, test, "ID_PATH", path);
+ udev_builtin_add_property(dev, test, "ID_PATH_TAG", tag);
+ free(path);
+ return EXIT_SUCCESS;
+ }
+ return EXIT_FAILURE;
+}
+
+const struct udev_builtin udev_builtin_path_id = {
+ .name = "path_id",
+ .cmd = builtin_path_id,
+ .help = "Compose persistent device path",
+ .run_once = true,
+};
diff --git a/src/grp-udev/libudev-core/udev-builtin-uaccess.c b/src/grp-udev/libudev-core/udev-builtin-uaccess.c
new file mode 100644
index 0000000000..64773b90f5
--- /dev/null
+++ b/src/grp-udev/libudev-core/udev-builtin-uaccess.c
@@ -0,0 +1,89 @@
+/*
+ * manage device node user ACL
+ *
+ * Copyright 2010-2012 Kay Sievers <kay@vrfy.org>
+ * Copyright 2010 Lennart Poettering
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <systemd/sd-login.h>
+
+#include "systemd-basic/login-util.h"
+#include "systemd-basic/util.h"
+#include "udev.h"
+
+#include "logind-acl.h"
+
+static int builtin_uaccess(struct udev_device *dev, int argc, char *argv[], bool test) {
+ int r;
+ const char *path = NULL, *seat;
+ bool changed_acl = false;
+ uid_t uid;
+
+ umask(0022);
+
+ /* don't muck around with ACLs when the system is not running systemd */
+ if (!logind_running())
+ return 0;
+
+ path = udev_device_get_devnode(dev);
+ seat = udev_device_get_property_value(dev, "ID_SEAT");
+ if (!seat)
+ seat = "seat0";
+
+ r = sd_seat_get_active(seat, NULL, &uid);
+ if (r == -ENXIO || r == -ENODATA) {
+ /* No active session on this seat */
+ r = 0;
+ goto finish;
+ } else if (r < 0) {
+ log_error("Failed to determine active user on seat %s.", seat);
+ goto finish;
+ }
+
+ r = devnode_acl(path, true, false, 0, true, uid);
+ if (r < 0) {
+ log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_ERR, r, "Failed to apply ACL on %s: %m", path);
+ goto finish;
+ }
+
+ changed_acl = true;
+ r = 0;
+
+finish:
+ if (path && !changed_acl) {
+ int k;
+
+ /* Better be safe than sorry and reset ACL */
+ k = devnode_acl(path, true, false, 0, false, 0);
+ if (k < 0) {
+ log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, k, "Failed to apply ACL on %s: %m", path);
+ if (r >= 0)
+ r = k;
+ }
+ }
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
+const struct udev_builtin udev_builtin_uaccess = {
+ .name = "uaccess",
+ .cmd = builtin_uaccess,
+ .help = "Manage device node user ACL",
+};
diff --git a/src/grp-udev/libudev-core/udev-builtin-usb_id.c b/src/grp-udev/libudev-core/udev-builtin-usb_id.c
new file mode 100644
index 0000000000..68989580f2
--- /dev/null
+++ b/src/grp-udev/libudev-core/udev-builtin-usb_id.c
@@ -0,0 +1,473 @@
+/*
+ * USB device properties and persistent device path
+ *
+ * Copyright (c) 2005 SUSE Linux Products GmbH, Germany
+ * Author: Hannes Reinecke <hare@suse.de>
+ *
+ * Copyright (C) 2005-2011 Kay Sievers <kay@vrfy.org>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/fd-util.h"
+#include "systemd-basic/string-util.h"
+#include "udev.h"
+
+static void set_usb_iftype(char *to, int if_class_num, size_t len) {
+ const char *type = "generic";
+
+ switch (if_class_num) {
+ case 1:
+ type = "audio";
+ break;
+ case 2: /* CDC-Control */
+ break;
+ case 3:
+ type = "hid";
+ break;
+ case 5: /* Physical */
+ break;
+ case 6:
+ type = "media";
+ break;
+ case 7:
+ type = "printer";
+ break;
+ case 8:
+ type = "storage";
+ break;
+ case 9:
+ type = "hub";
+ break;
+ case 0x0a: /* CDC-Data */
+ break;
+ case 0x0b: /* Chip/Smart Card */
+ break;
+ case 0x0d: /* Content Security */
+ break;
+ case 0x0e:
+ type = "video";
+ break;
+ case 0xdc: /* Diagnostic Device */
+ break;
+ case 0xe0: /* Wireless Controller */
+ break;
+ case 0xfe: /* Application-specific */
+ break;
+ case 0xff: /* Vendor-specific */
+ break;
+ default:
+ break;
+ }
+ strncpy(to, type, len);
+ to[len-1] = '\0';
+}
+
+static int set_usb_mass_storage_ifsubtype(char *to, const char *from, size_t len) {
+ int type_num = 0;
+ char *eptr;
+ const char *type = "generic";
+
+ type_num = strtoul(from, &eptr, 0);
+ if (eptr != from) {
+ switch (type_num) {
+ case 1: /* RBC devices */
+ type = "rbc";
+ break;
+ case 2:
+ type = "atapi";
+ break;
+ case 3:
+ type = "tape";
+ break;
+ case 4: /* UFI */
+ type = "floppy";
+ break;
+ case 6: /* Transparent SPC-2 devices */
+ type = "scsi";
+ break;
+ default:
+ break;
+ }
+ }
+ strscpy(to, len, type);
+ return type_num;
+}
+
+static void set_scsi_type(char *to, const char *from, size_t len) {
+ int type_num;
+ char *eptr;
+ const char *type = "generic";
+
+ type_num = strtoul(from, &eptr, 0);
+ if (eptr != from) {
+ switch (type_num) {
+ case 0:
+ case 0xe:
+ type = "disk";
+ break;
+ case 1:
+ type = "tape";
+ break;
+ case 4:
+ case 7:
+ case 0xf:
+ type = "optical";
+ break;
+ case 5:
+ type = "cd";
+ break;
+ default:
+ break;
+ }
+ }
+ strscpy(to, len, type);
+}
+
+#define USB_DT_DEVICE 0x01
+#define USB_DT_INTERFACE 0x04
+
+static int dev_if_packed_info(struct udev_device *dev, char *ifs_str, size_t len) {
+ _cleanup_free_ char *filename = NULL;
+ _cleanup_close_ int fd = -1;
+ ssize_t size;
+ unsigned char buf[18 + 65535];
+ size_t pos = 0;
+ unsigned strpos = 0;
+ struct usb_interface_descriptor {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bInterfaceNumber;
+ uint8_t bAlternateSetting;
+ uint8_t bNumEndpoints;
+ uint8_t bInterfaceClass;
+ uint8_t bInterfaceSubClass;
+ uint8_t bInterfaceProtocol;
+ uint8_t iInterface;
+ } _packed_;
+
+ if (asprintf(&filename, "%s/descriptors", udev_device_get_syspath(dev)) < 0)
+ return log_oom();
+
+ fd = open(filename, O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ return log_debug_errno(errno, "Error opening USB device 'descriptors' file: %m");
+
+ size = read(fd, buf, sizeof(buf));
+ if (size < 18 || size == sizeof(buf))
+ return -EIO;
+
+ ifs_str[0] = '\0';
+ while (pos + sizeof(struct usb_interface_descriptor) < (size_t) size &&
+ strpos + 7 < len - 2) {
+
+ struct usb_interface_descriptor *desc;
+ char if_str[8];
+
+ desc = (struct usb_interface_descriptor *) &buf[pos];
+ if (desc->bLength < 3)
+ break;
+ pos += desc->bLength;
+
+ if (desc->bDescriptorType != USB_DT_INTERFACE)
+ continue;
+
+ if (snprintf(if_str, 8, ":%02x%02x%02x",
+ desc->bInterfaceClass,
+ desc->bInterfaceSubClass,
+ desc->bInterfaceProtocol) != 7)
+ continue;
+
+ if (strstr(ifs_str, if_str) != NULL)
+ continue;
+
+ memcpy(&ifs_str[strpos], if_str, 8),
+ strpos += 7;
+ }
+
+ if (strpos > 0) {
+ ifs_str[strpos++] = ':';
+ ifs_str[strpos++] = '\0';
+ }
+
+ return 0;
+}
+
+/*
+ * A unique USB identification is generated like this:
+ *
+ * 1.) Get the USB device type from InterfaceClass and InterfaceSubClass
+ * 2.) If the device type is 'Mass-Storage/SPC-2' or 'Mass-Storage/RBC',
+ * use the SCSI vendor and model as USB-Vendor and USB-model.
+ * 3.) Otherwise, use the USB manufacturer and product as
+ * USB-Vendor and USB-model. Any non-printable characters
+ * in those strings will be skipped; a slash '/' will be converted
+ * into a full stop '.'.
+ * 4.) If that fails, too, we will use idVendor and idProduct
+ * as USB-Vendor and USB-model.
+ * 5.) The USB identification is the USB-vendor and USB-model
+ * string concatenated with an underscore '_'.
+ * 6.) If the device supplies a serial number, this number
+ * is concatenated with the identification with an underscore '_'.
+ */
+static int builtin_usb_id(struct udev_device *dev, int argc, char *argv[], bool test) {
+ char vendor_str[64] = "";
+ char vendor_str_enc[256];
+ const char *vendor_id;
+ char model_str[64] = "";
+ char model_str_enc[256];
+ const char *product_id;
+ char serial_str[UTIL_NAME_SIZE] = "";
+ char packed_if_str[UTIL_NAME_SIZE] = "";
+ char revision_str[64] = "";
+ char type_str[64] = "";
+ char instance_str[64] = "";
+ const char *ifnum = NULL;
+ const char *driver = NULL;
+ char serial[256];
+
+ struct udev_device *dev_interface = NULL;
+ struct udev_device *dev_usb = NULL;
+ const char *if_class, *if_subclass;
+ int if_class_num;
+ int protocol = 0;
+ size_t l;
+ char *s;
+
+ assert(dev);
+
+ /* shortcut, if we are called directly for a "usb_device" type */
+ if (udev_device_get_devtype(dev) != NULL && streq(udev_device_get_devtype(dev), "usb_device")) {
+ dev_if_packed_info(dev, packed_if_str, sizeof(packed_if_str));
+ dev_usb = dev;
+ goto fallback;
+ }
+
+ /* usb interface directory */
+ dev_interface = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_interface");
+ if (dev_interface == NULL) {
+ log_debug("unable to access usb_interface device of '%s'",
+ udev_device_get_syspath(dev));
+ return EXIT_FAILURE;
+ }
+
+ ifnum = udev_device_get_sysattr_value(dev_interface, "bInterfaceNumber");
+ driver = udev_device_get_sysattr_value(dev_interface, "driver");
+
+ if_class = udev_device_get_sysattr_value(dev_interface, "bInterfaceClass");
+ if (!if_class) {
+ log_debug("%s: cannot get bInterfaceClass attribute",
+ udev_device_get_sysname(dev));
+ return EXIT_FAILURE;
+ }
+
+ if_class_num = strtoul(if_class, NULL, 16);
+ if (if_class_num == 8) {
+ /* mass storage */
+ if_subclass = udev_device_get_sysattr_value(dev_interface, "bInterfaceSubClass");
+ if (if_subclass != NULL)
+ protocol = set_usb_mass_storage_ifsubtype(type_str, if_subclass, sizeof(type_str)-1);
+ } else {
+ set_usb_iftype(type_str, if_class_num, sizeof(type_str)-1);
+ }
+
+ log_debug("%s: if_class %d protocol %d",
+ udev_device_get_syspath(dev_interface), if_class_num, protocol);
+
+ /* usb device directory */
+ dev_usb = udev_device_get_parent_with_subsystem_devtype(dev_interface, "usb", "usb_device");
+ if (!dev_usb) {
+ log_debug("unable to find parent 'usb' device of '%s'",
+ udev_device_get_syspath(dev));
+ return EXIT_FAILURE;
+ }
+
+ /* all interfaces of the device in a single string */
+ dev_if_packed_info(dev_usb, packed_if_str, sizeof(packed_if_str));
+
+ /* mass storage : SCSI or ATAPI */
+ if (protocol == 6 || protocol == 2) {
+ struct udev_device *dev_scsi;
+ const char *scsi_model, *scsi_vendor, *scsi_type, *scsi_rev;
+ int host, bus, target, lun;
+
+ /* get scsi device */
+ dev_scsi = udev_device_get_parent_with_subsystem_devtype(dev, "scsi", "scsi_device");
+ if (dev_scsi == NULL) {
+ log_debug("unable to find parent 'scsi' device of '%s'",
+ udev_device_get_syspath(dev));
+ goto fallback;
+ }
+ if (sscanf(udev_device_get_sysname(dev_scsi), "%d:%d:%d:%d", &host, &bus, &target, &lun) != 4) {
+ log_debug("invalid scsi device '%s'", udev_device_get_sysname(dev_scsi));
+ goto fallback;
+ }
+
+ /* Generic SPC-2 device */
+ scsi_vendor = udev_device_get_sysattr_value(dev_scsi, "vendor");
+ if (!scsi_vendor) {
+ log_debug("%s: cannot get SCSI vendor attribute",
+ udev_device_get_sysname(dev_scsi));
+ goto fallback;
+ }
+ udev_util_encode_string(scsi_vendor, vendor_str_enc, sizeof(vendor_str_enc));
+ util_replace_whitespace(scsi_vendor, vendor_str, sizeof(vendor_str)-1);
+ util_replace_chars(vendor_str, NULL);
+
+ scsi_model = udev_device_get_sysattr_value(dev_scsi, "model");
+ if (!scsi_model) {
+ log_debug("%s: cannot get SCSI model attribute",
+ udev_device_get_sysname(dev_scsi));
+ goto fallback;
+ }
+ udev_util_encode_string(scsi_model, model_str_enc, sizeof(model_str_enc));
+ util_replace_whitespace(scsi_model, model_str, sizeof(model_str)-1);
+ util_replace_chars(model_str, NULL);
+
+ scsi_type = udev_device_get_sysattr_value(dev_scsi, "type");
+ if (!scsi_type) {
+ log_debug("%s: cannot get SCSI type attribute",
+ udev_device_get_sysname(dev_scsi));
+ goto fallback;
+ }
+ set_scsi_type(type_str, scsi_type, sizeof(type_str)-1);
+
+ scsi_rev = udev_device_get_sysattr_value(dev_scsi, "rev");
+ if (!scsi_rev) {
+ log_debug("%s: cannot get SCSI revision attribute",
+ udev_device_get_sysname(dev_scsi));
+ goto fallback;
+ }
+ util_replace_whitespace(scsi_rev, revision_str, sizeof(revision_str)-1);
+ util_replace_chars(revision_str, NULL);
+
+ /*
+ * some broken devices have the same identifiers
+ * for all luns, export the target:lun number
+ */
+ sprintf(instance_str, "%d:%d", target, lun);
+ }
+
+fallback:
+ vendor_id = udev_device_get_sysattr_value(dev_usb, "idVendor");
+ product_id = udev_device_get_sysattr_value(dev_usb, "idProduct");
+
+ /* fallback to USB vendor & device */
+ if (vendor_str[0] == '\0') {
+ const char *usb_vendor = NULL;
+
+ usb_vendor = udev_device_get_sysattr_value(dev_usb, "manufacturer");
+ if (!usb_vendor)
+ usb_vendor = vendor_id;
+ if (!usb_vendor) {
+ log_debug("No USB vendor information available");
+ return EXIT_FAILURE;
+ }
+ udev_util_encode_string(usb_vendor, vendor_str_enc, sizeof(vendor_str_enc));
+ util_replace_whitespace(usb_vendor, vendor_str, sizeof(vendor_str)-1);
+ util_replace_chars(vendor_str, NULL);
+ }
+
+ if (model_str[0] == '\0') {
+ const char *usb_model = NULL;
+
+ usb_model = udev_device_get_sysattr_value(dev_usb, "product");
+ if (!usb_model)
+ usb_model = product_id;
+ if (!usb_model)
+ return EXIT_FAILURE;
+ udev_util_encode_string(usb_model, model_str_enc, sizeof(model_str_enc));
+ util_replace_whitespace(usb_model, model_str, sizeof(model_str)-1);
+ util_replace_chars(model_str, NULL);
+ }
+
+ if (revision_str[0] == '\0') {
+ const char *usb_rev;
+
+ usb_rev = udev_device_get_sysattr_value(dev_usb, "bcdDevice");
+ if (usb_rev) {
+ util_replace_whitespace(usb_rev, revision_str, sizeof(revision_str)-1);
+ util_replace_chars(revision_str, NULL);
+ }
+ }
+
+ if (serial_str[0] == '\0') {
+ const char *usb_serial;
+
+ usb_serial = udev_device_get_sysattr_value(dev_usb, "serial");
+ if (usb_serial) {
+ const unsigned char *p;
+
+ /* http://msdn.microsoft.com/en-us/library/windows/hardware/gg487321.aspx */
+ for (p = (unsigned char *)usb_serial; *p != '\0'; p++)
+ if (*p < 0x20 || *p > 0x7f || *p == ',') {
+ usb_serial = NULL;
+ break;
+ }
+ }
+
+ if (usb_serial) {
+ util_replace_whitespace(usb_serial, serial_str, sizeof(serial_str)-1);
+ util_replace_chars(serial_str, NULL);
+ }
+ }
+
+ s = serial;
+ l = strpcpyl(&s, sizeof(serial), vendor_str, "_", model_str, NULL);
+ if (!isempty(serial_str))
+ l = strpcpyl(&s, l, "_", serial_str, NULL);
+
+ if (!isempty(instance_str))
+ strpcpyl(&s, l, "-", instance_str, NULL);
+
+ udev_builtin_add_property(dev, test, "ID_VENDOR", vendor_str);
+ udev_builtin_add_property(dev, test, "ID_VENDOR_ENC", vendor_str_enc);
+ udev_builtin_add_property(dev, test, "ID_VENDOR_ID", vendor_id);
+ udev_builtin_add_property(dev, test, "ID_MODEL", model_str);
+ udev_builtin_add_property(dev, test, "ID_MODEL_ENC", model_str_enc);
+ udev_builtin_add_property(dev, test, "ID_MODEL_ID", product_id);
+ udev_builtin_add_property(dev, test, "ID_REVISION", revision_str);
+ udev_builtin_add_property(dev, test, "ID_SERIAL", serial);
+ if (!isempty(serial_str))
+ udev_builtin_add_property(dev, test, "ID_SERIAL_SHORT", serial_str);
+ if (!isempty(type_str))
+ udev_builtin_add_property(dev, test, "ID_TYPE", type_str);
+ if (!isempty(instance_str))
+ udev_builtin_add_property(dev, test, "ID_INSTANCE", instance_str);
+ udev_builtin_add_property(dev, test, "ID_BUS", "usb");
+ if (!isempty(packed_if_str))
+ udev_builtin_add_property(dev, test, "ID_USB_INTERFACES", packed_if_str);
+ if (ifnum != NULL)
+ udev_builtin_add_property(dev, test, "ID_USB_INTERFACE_NUM", ifnum);
+ if (driver != NULL)
+ udev_builtin_add_property(dev, test, "ID_USB_DRIVER", driver);
+ return EXIT_SUCCESS;
+}
+
+const struct udev_builtin udev_builtin_usb_id = {
+ .name = "usb_id",
+ .cmd = builtin_usb_id,
+ .help = "USB device properties",
+ .run_once = true,
+};
diff --git a/src/grp-udev/libudev-core/udev-builtin.c b/src/grp-udev/libudev-core/udev-builtin.c
new file mode 100644
index 0000000000..fb80e715ad
--- /dev/null
+++ b/src/grp-udev/libudev-core/udev-builtin.c
@@ -0,0 +1,142 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2007-2012 Kay Sievers <kay@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 <getopt.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "systemd-basic/string-util.h"
+#include "udev.h"
+
+static bool initialized;
+
+static const struct udev_builtin *builtins[] = {
+#ifdef HAVE_BLKID
+ [UDEV_BUILTIN_BLKID] = &udev_builtin_blkid,
+#endif
+ [UDEV_BUILTIN_BTRFS] = &udev_builtin_btrfs,
+ [UDEV_BUILTIN_HWDB] = &udev_builtin_hwdb,
+ [UDEV_BUILTIN_INPUT_ID] = &udev_builtin_input_id,
+ [UDEV_BUILTIN_KEYBOARD] = &udev_builtin_keyboard,
+#ifdef HAVE_KMOD
+ [UDEV_BUILTIN_KMOD] = &udev_builtin_kmod,
+#endif
+ [UDEV_BUILTIN_NET_ID] = &udev_builtin_net_id,
+ [UDEV_BUILTIN_NET_LINK] = &udev_builtin_net_setup_link,
+ [UDEV_BUILTIN_PATH_ID] = &udev_builtin_path_id,
+ [UDEV_BUILTIN_USB_ID] = &udev_builtin_usb_id,
+#ifdef HAVE_ACL
+ [UDEV_BUILTIN_UACCESS] = &udev_builtin_uaccess,
+#endif
+};
+
+void udev_builtin_init(struct udev *udev) {
+ unsigned int i;
+
+ if (initialized)
+ return;
+
+ for (i = 0; i < ELEMENTSOF(builtins); i++)
+ if (builtins[i] && builtins[i]->init)
+ builtins[i]->init(udev);
+
+ initialized = true;
+}
+
+void udev_builtin_exit(struct udev *udev) {
+ unsigned int i;
+
+ if (!initialized)
+ return;
+
+ for (i = 0; i < ELEMENTSOF(builtins); i++)
+ if (builtins[i] && builtins[i]->exit)
+ builtins[i]->exit(udev);
+
+ initialized = false;
+}
+
+bool udev_builtin_validate(struct udev *udev) {
+ unsigned int i;
+
+ for (i = 0; i < ELEMENTSOF(builtins); i++)
+ if (builtins[i] && builtins[i]->validate && builtins[i]->validate(udev))
+ return true;
+ return false;
+}
+
+void udev_builtin_list(struct udev *udev) {
+ unsigned int i;
+
+ for (i = 0; i < ELEMENTSOF(builtins); i++)
+ if (builtins[i])
+ fprintf(stderr, " %-14s %s\n", builtins[i]->name, builtins[i]->help);
+}
+
+const char *udev_builtin_name(enum udev_builtin_cmd cmd) {
+ if (!builtins[cmd])
+ return NULL;
+
+ return builtins[cmd]->name;
+}
+
+bool udev_builtin_run_once(enum udev_builtin_cmd cmd) {
+ if (!builtins[cmd])
+ return false;
+
+ return builtins[cmd]->run_once;
+}
+
+enum udev_builtin_cmd udev_builtin_lookup(const char *command) {
+ char name[UTIL_PATH_SIZE];
+ enum udev_builtin_cmd i;
+ char *pos;
+
+ strscpy(name, sizeof(name), command);
+ pos = strchr(name, ' ');
+ if (pos)
+ pos[0] = '\0';
+ for (i = 0; i < ELEMENTSOF(builtins); i++)
+ if (builtins[i] && streq(builtins[i]->name, name))
+ return i;
+ return UDEV_BUILTIN_MAX;
+}
+
+int udev_builtin_run(struct udev_device *dev, enum udev_builtin_cmd cmd, const char *command, bool test) {
+ char arg[UTIL_PATH_SIZE];
+ int argc;
+ char *argv[128];
+
+ if (!builtins[cmd])
+ return -EOPNOTSUPP;
+
+ /* we need '0' here to reset the internal state */
+ optind = 0;
+ strscpy(arg, sizeof(arg), command);
+ udev_build_argv(udev_device_get_udev(dev), arg, &argc, argv);
+ return builtins[cmd]->cmd(dev, argc, argv, test);
+}
+
+int udev_builtin_add_property(struct udev_device *dev, bool test, const char *key, const char *val) {
+ udev_device_add_property(dev, key, val);
+
+ if (test)
+ printf("%s=%s\n", key, val);
+ return 0;
+}
diff --git a/src/grp-udev/libudev-core/udev-ctrl.c b/src/grp-udev/libudev-core/udev-ctrl.c
new file mode 100644
index 0000000000..616ba7d199
--- /dev/null
+++ b/src/grp-udev/libudev-core/udev-ctrl.c
@@ -0,0 +1,461 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2008 Kay Sievers <kay@vrfy.org>
+ *
+ * This library 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.
+ */
+
+#include <errno.h>
+#include <poll.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/fd-util.h"
+#include "systemd-basic/formats-util.h"
+#include "systemd-basic/socket-util.h"
+#include "udev.h"
+
+/* wire protocol magic must match */
+#define UDEV_CTRL_MAGIC 0xdead1dea
+
+enum udev_ctrl_msg_type {
+ UDEV_CTRL_UNKNOWN,
+ UDEV_CTRL_SET_LOG_LEVEL,
+ UDEV_CTRL_STOP_EXEC_QUEUE,
+ UDEV_CTRL_START_EXEC_QUEUE,
+ UDEV_CTRL_RELOAD,
+ UDEV_CTRL_SET_ENV,
+ UDEV_CTRL_SET_CHILDREN_MAX,
+ UDEV_CTRL_PING,
+ UDEV_CTRL_EXIT,
+};
+
+struct udev_ctrl_msg_wire {
+ char version[16];
+ unsigned int magic;
+ enum udev_ctrl_msg_type type;
+ union {
+ int intval;
+ char buf[256];
+ };
+};
+
+struct udev_ctrl_msg {
+ int refcount;
+ struct udev_ctrl_connection *conn;
+ struct udev_ctrl_msg_wire ctrl_msg_wire;
+};
+
+struct udev_ctrl {
+ int refcount;
+ struct udev *udev;
+ int sock;
+ union sockaddr_union saddr;
+ socklen_t addrlen;
+ bool bound;
+ bool cleanup_socket;
+ bool connected;
+};
+
+struct udev_ctrl_connection {
+ int refcount;
+ struct udev_ctrl *uctrl;
+ int sock;
+};
+
+struct udev_ctrl *udev_ctrl_new_from_fd(struct udev *udev, int fd) {
+ struct udev_ctrl *uctrl;
+ const int on = 1;
+ int r;
+
+ uctrl = new0(struct udev_ctrl, 1);
+ if (uctrl == NULL)
+ return NULL;
+ uctrl->refcount = 1;
+ uctrl->udev = udev;
+
+ if (fd < 0) {
+ uctrl->sock = socket(AF_LOCAL, SOCK_SEQPACKET|SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
+ if (uctrl->sock < 0) {
+ log_error_errno(errno, "error getting socket: %m");
+ udev_ctrl_unref(uctrl);
+ return NULL;
+ }
+ } else {
+ uctrl->bound = true;
+ uctrl->sock = fd;
+ }
+
+ /*
+ * FIXME: remove it as soon as we can depend on this:
+ * http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=90c6bd34f884cd9cee21f1d152baf6c18bcac949
+ */
+ r = setsockopt(uctrl->sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
+ if (r < 0)
+ log_warning_errno(errno, "could not set SO_PASSCRED: %m");
+
+ uctrl->saddr.un.sun_family = AF_LOCAL;
+ strscpy(uctrl->saddr.un.sun_path, sizeof(uctrl->saddr.un.sun_path), "/run/udev/control");
+ uctrl->addrlen = SOCKADDR_UN_LEN(uctrl->saddr.un);
+ return uctrl;
+}
+
+struct udev_ctrl *udev_ctrl_new(struct udev *udev) {
+ return udev_ctrl_new_from_fd(udev, -1);
+}
+
+int udev_ctrl_enable_receiving(struct udev_ctrl *uctrl) {
+ int err;
+
+ if (!uctrl->bound) {
+ err = bind(uctrl->sock, &uctrl->saddr.sa, uctrl->addrlen);
+ if (err < 0 && errno == EADDRINUSE) {
+ unlink(uctrl->saddr.un.sun_path);
+ err = bind(uctrl->sock, &uctrl->saddr.sa, uctrl->addrlen);
+ }
+
+ if (err < 0)
+ return log_error_errno(errno, "bind failed: %m");
+
+ err = listen(uctrl->sock, 0);
+ if (err < 0)
+ return log_error_errno(errno, "listen failed: %m");
+
+ uctrl->bound = true;
+ uctrl->cleanup_socket = true;
+ }
+ return 0;
+}
+
+struct udev *udev_ctrl_get_udev(struct udev_ctrl *uctrl) {
+ return uctrl->udev;
+}
+
+static struct udev_ctrl *udev_ctrl_ref(struct udev_ctrl *uctrl) {
+ if (uctrl)
+ uctrl->refcount++;
+
+ return uctrl;
+}
+
+struct udev_ctrl *udev_ctrl_unref(struct udev_ctrl *uctrl) {
+ if (uctrl && -- uctrl->refcount == 0) {
+ if (uctrl->sock >= 0)
+ close(uctrl->sock);
+ free(uctrl);
+ }
+
+ return NULL;
+}
+
+int udev_ctrl_cleanup(struct udev_ctrl *uctrl) {
+ if (uctrl == NULL)
+ return 0;
+ if (uctrl->cleanup_socket)
+ unlink(uctrl->saddr.un.sun_path);
+ return 0;
+}
+
+int udev_ctrl_get_fd(struct udev_ctrl *uctrl) {
+ if (uctrl == NULL)
+ return -EINVAL;
+ return uctrl->sock;
+}
+
+struct udev_ctrl_connection *udev_ctrl_get_connection(struct udev_ctrl *uctrl) {
+ struct udev_ctrl_connection *conn;
+ struct ucred ucred = {};
+ const int on = 1;
+ int r;
+
+ conn = new(struct udev_ctrl_connection, 1);
+ if (conn == NULL)
+ return NULL;
+ conn->refcount = 1;
+ conn->uctrl = uctrl;
+
+ conn->sock = accept4(uctrl->sock, NULL, NULL, SOCK_CLOEXEC|SOCK_NONBLOCK);
+ if (conn->sock < 0) {
+ if (errno != EINTR)
+ log_error_errno(errno, "unable to receive ctrl connection: %m");
+ goto err;
+ }
+
+ /* check peer credential of connection */
+ r = getpeercred(conn->sock, &ucred);
+ if (r < 0) {
+ log_error_errno(r, "unable to receive credentials of ctrl connection: %m");
+ goto err;
+ }
+ if (ucred.uid > 0) {
+ log_error("sender uid="UID_FMT", message ignored", ucred.uid);
+ goto err;
+ }
+
+ /* enable receiving of the sender credentials in the messages */
+ r = setsockopt(conn->sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
+ if (r < 0)
+ log_warning_errno(errno, "could not set SO_PASSCRED: %m");
+
+ udev_ctrl_ref(uctrl);
+ return conn;
+err:
+ if (conn->sock >= 0)
+ close(conn->sock);
+ return mfree(conn);
+}
+
+struct udev_ctrl_connection *udev_ctrl_connection_ref(struct udev_ctrl_connection *conn) {
+ if (conn == NULL)
+ return NULL;
+ conn->refcount++;
+ return conn;
+}
+
+struct udev_ctrl_connection *udev_ctrl_connection_unref(struct udev_ctrl_connection *conn) {
+ if (conn && -- conn->refcount == 0) {
+ if (conn->sock >= 0)
+ close(conn->sock);
+
+ udev_ctrl_unref(conn->uctrl);
+
+ free(conn);
+ }
+
+ return NULL;
+}
+
+static int ctrl_send(struct udev_ctrl *uctrl, enum udev_ctrl_msg_type type, int intval, const char *buf, int timeout) {
+ struct udev_ctrl_msg_wire ctrl_msg_wire;
+ int err = 0;
+
+ memzero(&ctrl_msg_wire, sizeof(struct udev_ctrl_msg_wire));
+ strcpy(ctrl_msg_wire.version, "udev-" VERSION);
+ ctrl_msg_wire.magic = UDEV_CTRL_MAGIC;
+ ctrl_msg_wire.type = type;
+
+ if (buf != NULL)
+ strscpy(ctrl_msg_wire.buf, sizeof(ctrl_msg_wire.buf), buf);
+ else
+ ctrl_msg_wire.intval = intval;
+
+ if (!uctrl->connected) {
+ if (connect(uctrl->sock, &uctrl->saddr.sa, uctrl->addrlen) < 0) {
+ err = -errno;
+ goto out;
+ }
+ uctrl->connected = true;
+ }
+ if (send(uctrl->sock, &ctrl_msg_wire, sizeof(ctrl_msg_wire), 0) < 0) {
+ err = -errno;
+ goto out;
+ }
+
+ /* wait for peer message handling or disconnect */
+ for (;;) {
+ struct pollfd pfd[1];
+ int r;
+
+ pfd[0].fd = uctrl->sock;
+ pfd[0].events = POLLIN;
+ r = poll(pfd, 1, timeout * MSEC_PER_SEC);
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+ err = -errno;
+ break;
+ }
+
+ if (r > 0 && pfd[0].revents & POLLERR) {
+ err = -EIO;
+ break;
+ }
+
+ if (r == 0)
+ err = -ETIMEDOUT;
+ break;
+ }
+out:
+ return err;
+}
+
+int udev_ctrl_send_set_log_level(struct udev_ctrl *uctrl, int priority, int timeout) {
+ return ctrl_send(uctrl, UDEV_CTRL_SET_LOG_LEVEL, priority, NULL, timeout);
+}
+
+int udev_ctrl_send_stop_exec_queue(struct udev_ctrl *uctrl, int timeout) {
+ return ctrl_send(uctrl, UDEV_CTRL_STOP_EXEC_QUEUE, 0, NULL, timeout);
+}
+
+int udev_ctrl_send_start_exec_queue(struct udev_ctrl *uctrl, int timeout) {
+ return ctrl_send(uctrl, UDEV_CTRL_START_EXEC_QUEUE, 0, NULL, timeout);
+}
+
+int udev_ctrl_send_reload(struct udev_ctrl *uctrl, int timeout) {
+ return ctrl_send(uctrl, UDEV_CTRL_RELOAD, 0, NULL, timeout);
+}
+
+int udev_ctrl_send_set_env(struct udev_ctrl *uctrl, const char *key, int timeout) {
+ return ctrl_send(uctrl, UDEV_CTRL_SET_ENV, 0, key, timeout);
+}
+
+int udev_ctrl_send_set_children_max(struct udev_ctrl *uctrl, int count, int timeout) {
+ return ctrl_send(uctrl, UDEV_CTRL_SET_CHILDREN_MAX, count, NULL, timeout);
+}
+
+int udev_ctrl_send_ping(struct udev_ctrl *uctrl, int timeout) {
+ return ctrl_send(uctrl, UDEV_CTRL_PING, 0, NULL, timeout);
+}
+
+int udev_ctrl_send_exit(struct udev_ctrl *uctrl, int timeout) {
+ return ctrl_send(uctrl, UDEV_CTRL_EXIT, 0, NULL, timeout);
+}
+
+struct udev_ctrl_msg *udev_ctrl_receive_msg(struct udev_ctrl_connection *conn) {
+ struct udev_ctrl_msg *uctrl_msg;
+ ssize_t size;
+ struct cmsghdr *cmsg;
+ struct iovec iov;
+ char cred_msg[CMSG_SPACE(sizeof(struct ucred))];
+ struct msghdr smsg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = cred_msg,
+ .msg_controllen = sizeof(cred_msg),
+ };
+ struct ucred *cred;
+
+ uctrl_msg = new0(struct udev_ctrl_msg, 1);
+ if (uctrl_msg == NULL)
+ return NULL;
+ uctrl_msg->refcount = 1;
+ uctrl_msg->conn = conn;
+ udev_ctrl_connection_ref(conn);
+
+ /* wait for the incoming message */
+ for (;;) {
+ struct pollfd pfd[1];
+ int r;
+
+ pfd[0].fd = conn->sock;
+ pfd[0].events = POLLIN;
+
+ r = poll(pfd, 1, 10000);
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+ goto err;
+ } else if (r == 0) {
+ log_error("timeout waiting for ctrl message");
+ goto err;
+ } else {
+ if (!(pfd[0].revents & POLLIN)) {
+ log_error_errno(errno, "ctrl connection error: %m");
+ goto err;
+ }
+ }
+
+ break;
+ }
+
+ iov.iov_base = &uctrl_msg->ctrl_msg_wire;
+ iov.iov_len = sizeof(struct udev_ctrl_msg_wire);
+
+ size = recvmsg(conn->sock, &smsg, 0);
+ if (size < 0) {
+ log_error_errno(errno, "unable to receive ctrl message: %m");
+ goto err;
+ }
+
+ cmsg_close_all(&smsg);
+
+ cmsg = CMSG_FIRSTHDR(&smsg);
+
+ if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) {
+ log_error("no sender credentials received, message ignored");
+ goto err;
+ }
+
+ cred = (struct ucred *) CMSG_DATA(cmsg);
+
+ if (cred->uid != 0) {
+ log_error("sender uid="UID_FMT", message ignored", cred->uid);
+ goto err;
+ }
+
+ if (uctrl_msg->ctrl_msg_wire.magic != UDEV_CTRL_MAGIC) {
+ log_error("message magic 0x%08x doesn't match, ignore it", uctrl_msg->ctrl_msg_wire.magic);
+ goto err;
+ }
+
+ return uctrl_msg;
+err:
+ udev_ctrl_msg_unref(uctrl_msg);
+ return NULL;
+}
+
+struct udev_ctrl_msg *udev_ctrl_msg_unref(struct udev_ctrl_msg *ctrl_msg) {
+ if (ctrl_msg && -- ctrl_msg->refcount == 0) {
+ udev_ctrl_connection_unref(ctrl_msg->conn);
+ free(ctrl_msg);
+ }
+
+ return NULL;
+}
+
+int udev_ctrl_get_set_log_level(struct udev_ctrl_msg *ctrl_msg) {
+ if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_SET_LOG_LEVEL)
+ return ctrl_msg->ctrl_msg_wire.intval;
+ return -1;
+}
+
+int udev_ctrl_get_stop_exec_queue(struct udev_ctrl_msg *ctrl_msg) {
+ if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_STOP_EXEC_QUEUE)
+ return 1;
+ return -1;
+}
+
+int udev_ctrl_get_start_exec_queue(struct udev_ctrl_msg *ctrl_msg) {
+ if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_START_EXEC_QUEUE)
+ return 1;
+ return -1;
+}
+
+int udev_ctrl_get_reload(struct udev_ctrl_msg *ctrl_msg) {
+ if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_RELOAD)
+ return 1;
+ return -1;
+}
+
+const char *udev_ctrl_get_set_env(struct udev_ctrl_msg *ctrl_msg) {
+ if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_SET_ENV)
+ return ctrl_msg->ctrl_msg_wire.buf;
+ return NULL;
+}
+
+int udev_ctrl_get_set_children_max(struct udev_ctrl_msg *ctrl_msg) {
+ if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_SET_CHILDREN_MAX)
+ return ctrl_msg->ctrl_msg_wire.intval;
+ return -1;
+}
+
+int udev_ctrl_get_ping(struct udev_ctrl_msg *ctrl_msg) {
+ if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_PING)
+ return 1;
+ return -1;
+}
+
+int udev_ctrl_get_exit(struct udev_ctrl_msg *ctrl_msg) {
+ if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_EXIT)
+ return 1;
+ return -1;
+}
diff --git a/src/grp-udev/libudev-core/udev-event.c b/src/grp-udev/libudev-core/udev-event.c
new file mode 100644
index 0000000000..f553b8b73d
--- /dev/null
+++ b/src/grp-udev/libudev-core/udev-event.c
@@ -0,0 +1,943 @@
+/*
+ * Copyright (C) 2003-2013 Kay Sievers <kay@vrfy.org>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <net/if.h>
+#include <poll.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/prctl.h>
+#include <sys/signalfd.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "sd-netlink/netlink-util.h"
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/fd-util.h"
+#include "systemd-basic/formats-util.h"
+#include "systemd-basic/process-util.h"
+#include "systemd-basic/signal-util.h"
+#include "systemd-basic/string-util.h"
+#include "udev.h"
+
+typedef struct Spawn {
+ const char *cmd;
+ pid_t pid;
+ usec_t timeout_warn;
+ usec_t timeout;
+ bool accept_failure;
+} Spawn;
+
+struct udev_event *udev_event_new(struct udev_device *dev) {
+ struct udev *udev = udev_device_get_udev(dev);
+ struct udev_event *event;
+
+ event = new0(struct udev_event, 1);
+ if (event == NULL)
+ return NULL;
+ event->dev = dev;
+ event->udev = udev;
+ udev_list_init(udev, &event->run_list, false);
+ udev_list_init(udev, &event->seclabel_list, false);
+ event->birth_usec = clock_boottime_or_monotonic();
+ return event;
+}
+
+void udev_event_unref(struct udev_event *event) {
+ if (event == NULL)
+ return;
+ sd_netlink_unref(event->rtnl);
+ udev_list_cleanup(&event->run_list);
+ udev_list_cleanup(&event->seclabel_list);
+ free(event->program_result);
+ free(event->name);
+ free(event);
+}
+
+size_t udev_event_apply_format(struct udev_event *event, const char *src, char *dest, size_t size) {
+ struct udev_device *dev = event->dev;
+ enum subst_type {
+ SUBST_UNKNOWN,
+ SUBST_DEVNODE,
+ SUBST_ATTR,
+ SUBST_ENV,
+ SUBST_KERNEL,
+ SUBST_KERNEL_NUMBER,
+ SUBST_DRIVER,
+ SUBST_DEVPATH,
+ SUBST_ID,
+ SUBST_MAJOR,
+ SUBST_MINOR,
+ SUBST_RESULT,
+ SUBST_PARENT,
+ SUBST_NAME,
+ SUBST_LINKS,
+ SUBST_ROOT,
+ SUBST_SYS,
+ };
+ static const struct subst_map {
+ const char *name;
+ const char fmt;
+ enum subst_type type;
+ } map[] = {
+ { .name = "devnode", .fmt = 'N', .type = SUBST_DEVNODE },
+ { .name = "tempnode", .fmt = 'N', .type = SUBST_DEVNODE },
+ { .name = "attr", .fmt = 's', .type = SUBST_ATTR },
+ { .name = "sysfs", .fmt = 's', .type = SUBST_ATTR },
+ { .name = "env", .fmt = 'E', .type = SUBST_ENV },
+ { .name = "kernel", .fmt = 'k', .type = SUBST_KERNEL },
+ { .name = "number", .fmt = 'n', .type = SUBST_KERNEL_NUMBER },
+ { .name = "driver", .fmt = 'd', .type = SUBST_DRIVER },
+ { .name = "devpath", .fmt = 'p', .type = SUBST_DEVPATH },
+ { .name = "id", .fmt = 'b', .type = SUBST_ID },
+ { .name = "major", .fmt = 'M', .type = SUBST_MAJOR },
+ { .name = "minor", .fmt = 'm', .type = SUBST_MINOR },
+ { .name = "result", .fmt = 'c', .type = SUBST_RESULT },
+ { .name = "parent", .fmt = 'P', .type = SUBST_PARENT },
+ { .name = "name", .fmt = 'D', .type = SUBST_NAME },
+ { .name = "links", .fmt = 'L', .type = SUBST_LINKS },
+ { .name = "root", .fmt = 'r', .type = SUBST_ROOT },
+ { .name = "sys", .fmt = 'S', .type = SUBST_SYS },
+ };
+ const char *from;
+ char *s;
+ size_t l;
+
+ assert(dev);
+
+ from = src;
+ s = dest;
+ l = size;
+
+ for (;;) {
+ enum subst_type type = SUBST_UNKNOWN;
+ char attrbuf[UTIL_PATH_SIZE];
+ char *attr = NULL;
+
+ while (from[0] != '\0') {
+ if (from[0] == '$') {
+ /* substitute named variable */
+ unsigned int i;
+
+ if (from[1] == '$') {
+ from++;
+ goto copy;
+ }
+
+ for (i = 0; i < ELEMENTSOF(map); i++) {
+ if (startswith(&from[1], map[i].name)) {
+ type = map[i].type;
+ from += strlen(map[i].name)+1;
+ goto subst;
+ }
+ }
+ } else if (from[0] == '%') {
+ /* substitute format char */
+ unsigned int i;
+
+ if (from[1] == '%') {
+ from++;
+ goto copy;
+ }
+
+ for (i = 0; i < ELEMENTSOF(map); i++) {
+ if (from[1] == map[i].fmt) {
+ type = map[i].type;
+ from += 2;
+ goto subst;
+ }
+ }
+ }
+copy:
+ /* copy char */
+ if (l == 0)
+ goto out;
+ s[0] = from[0];
+ from++;
+ s++;
+ l--;
+ }
+
+ goto out;
+subst:
+ /* extract possible $format{attr} */
+ if (from[0] == '{') {
+ unsigned int i;
+
+ from++;
+ for (i = 0; from[i] != '}'; i++) {
+ if (from[i] == '\0') {
+ log_error("missing closing brace for format '%s'", src);
+ goto out;
+ }
+ }
+ if (i >= sizeof(attrbuf))
+ goto out;
+ memcpy(attrbuf, from, i);
+ attrbuf[i] = '\0';
+ from += i+1;
+ attr = attrbuf;
+ } else {
+ attr = NULL;
+ }
+
+ switch (type) {
+ case SUBST_DEVPATH:
+ l = strpcpy(&s, l, udev_device_get_devpath(dev));
+ break;
+ case SUBST_KERNEL:
+ l = strpcpy(&s, l, udev_device_get_sysname(dev));
+ break;
+ case SUBST_KERNEL_NUMBER:
+ if (udev_device_get_sysnum(dev) == NULL)
+ break;
+ l = strpcpy(&s, l, udev_device_get_sysnum(dev));
+ break;
+ case SUBST_ID:
+ if (event->dev_parent == NULL)
+ break;
+ l = strpcpy(&s, l, udev_device_get_sysname(event->dev_parent));
+ break;
+ case SUBST_DRIVER: {
+ const char *driver;
+
+ if (event->dev_parent == NULL)
+ break;
+
+ driver = udev_device_get_driver(event->dev_parent);
+ if (driver == NULL)
+ break;
+ l = strpcpy(&s, l, driver);
+ break;
+ }
+ case SUBST_MAJOR: {
+ char num[UTIL_PATH_SIZE];
+
+ sprintf(num, "%u", major(udev_device_get_devnum(dev)));
+ l = strpcpy(&s, l, num);
+ break;
+ }
+ case SUBST_MINOR: {
+ char num[UTIL_PATH_SIZE];
+
+ sprintf(num, "%u", minor(udev_device_get_devnum(dev)));
+ l = strpcpy(&s, l, num);
+ break;
+ }
+ case SUBST_RESULT: {
+ char *rest;
+ int i;
+
+ if (event->program_result == NULL)
+ break;
+ /* get part of the result string */
+ i = 0;
+ if (attr != NULL)
+ i = strtoul(attr, &rest, 10);
+ if (i > 0) {
+ char result[UTIL_PATH_SIZE];
+ char tmp[UTIL_PATH_SIZE];
+ char *cpos;
+
+ strscpy(result, sizeof(result), event->program_result);
+ cpos = result;
+ while (--i) {
+ while (cpos[0] != '\0' && !isspace(cpos[0]))
+ cpos++;
+ while (isspace(cpos[0]))
+ cpos++;
+ if (cpos[0] == '\0')
+ break;
+ }
+ if (i > 0) {
+ log_error("requested part of result string not found");
+ break;
+ }
+ strscpy(tmp, sizeof(tmp), cpos);
+ /* %{2+}c copies the whole string from the second part on */
+ if (rest[0] != '+') {
+ cpos = strchr(tmp, ' ');
+ if (cpos)
+ cpos[0] = '\0';
+ }
+ l = strpcpy(&s, l, tmp);
+ } else {
+ l = strpcpy(&s, l, event->program_result);
+ }
+ break;
+ }
+ case SUBST_ATTR: {
+ const char *value = NULL;
+ char vbuf[UTIL_NAME_SIZE];
+ size_t len;
+ int count;
+
+ if (attr == NULL) {
+ log_error("missing file parameter for attr");
+ break;
+ }
+
+ /* try to read the value specified by "[dmi/id]product_name" */
+ if (util_resolve_subsys_kernel(event->udev, attr, vbuf, sizeof(vbuf), 1) == 0)
+ value = vbuf;
+
+ /* try to read the attribute the device */
+ if (value == NULL)
+ value = udev_device_get_sysattr_value(event->dev, attr);
+
+ /* try to read the attribute of the parent device, other matches have selected */
+ if (value == NULL && event->dev_parent != NULL && event->dev_parent != event->dev)
+ value = udev_device_get_sysattr_value(event->dev_parent, attr);
+
+ if (value == NULL)
+ break;
+
+ /* strip trailing whitespace, and replace unwanted characters */
+ if (value != vbuf)
+ strscpy(vbuf, sizeof(vbuf), value);
+ len = strlen(vbuf);
+ while (len > 0 && isspace(vbuf[--len]))
+ vbuf[len] = '\0';
+ count = util_replace_chars(vbuf, UDEV_ALLOWED_CHARS_INPUT);
+ if (count > 0)
+ log_debug("%i character(s) replaced" , count);
+ l = strpcpy(&s, l, vbuf);
+ break;
+ }
+ case SUBST_PARENT: {
+ struct udev_device *dev_parent;
+ const char *devnode;
+
+ dev_parent = udev_device_get_parent(event->dev);
+ if (dev_parent == NULL)
+ break;
+ devnode = udev_device_get_devnode(dev_parent);
+ if (devnode != NULL)
+ l = strpcpy(&s, l, devnode + strlen("/dev/"));
+ break;
+ }
+ case SUBST_DEVNODE:
+ if (udev_device_get_devnode(dev) != NULL)
+ l = strpcpy(&s, l, udev_device_get_devnode(dev));
+ break;
+ case SUBST_NAME:
+ if (event->name != NULL)
+ l = strpcpy(&s, l, event->name);
+ else if (udev_device_get_devnode(dev) != NULL)
+ l = strpcpy(&s, l, udev_device_get_devnode(dev) + strlen("/dev/"));
+ else
+ l = strpcpy(&s, l, udev_device_get_sysname(dev));
+ break;
+ case SUBST_LINKS: {
+ struct udev_list_entry *list_entry;
+
+ list_entry = udev_device_get_devlinks_list_entry(dev);
+ if (list_entry == NULL)
+ break;
+ l = strpcpy(&s, l, udev_list_entry_get_name(list_entry) + strlen("/dev/"));
+ udev_list_entry_foreach(list_entry, udev_list_entry_get_next(list_entry))
+ l = strpcpyl(&s, l, " ", udev_list_entry_get_name(list_entry) + strlen("/dev/"), NULL);
+ break;
+ }
+ case SUBST_ROOT:
+ l = strpcpy(&s, l, "/dev");
+ break;
+ case SUBST_SYS:
+ l = strpcpy(&s, l, "/sys");
+ break;
+ case SUBST_ENV:
+ if (attr == NULL) {
+ break;
+ } else {
+ const char *value;
+
+ value = udev_device_get_property_value(event->dev, attr);
+ if (value == NULL)
+ break;
+ l = strpcpy(&s, l, value);
+ break;
+ }
+ default:
+ log_error("unknown substitution type=%i", type);
+ break;
+ }
+ }
+
+out:
+ s[0] = '\0';
+ return l;
+}
+
+static int spawn_exec(struct udev_event *event,
+ const char *cmd, char *const argv[], char **envp,
+ int fd_stdout, int fd_stderr) {
+ _cleanup_close_ int fd = -1;
+ int r;
+
+ /* discard child output or connect to pipe */
+ fd = open("/dev/null", O_RDWR);
+ if (fd >= 0) {
+ r = dup2(fd, STDIN_FILENO);
+ if (r < 0)
+ log_warning_errno(errno, "redirecting stdin failed: %m");
+
+ if (fd_stdout < 0) {
+ r = dup2(fd, STDOUT_FILENO);
+ if (r < 0)
+ log_warning_errno(errno, "redirecting stdout failed: %m");
+ }
+
+ if (fd_stderr < 0) {
+ r = dup2(fd, STDERR_FILENO);
+ if (r < 0)
+ log_warning_errno(errno, "redirecting stderr failed: %m");
+ }
+ } else
+ log_warning_errno(errno, "open /dev/null failed: %m");
+
+ /* connect pipes to std{out,err} */
+ if (fd_stdout >= 0) {
+ r = dup2(fd_stdout, STDOUT_FILENO);
+ if (r < 0)
+ log_warning_errno(errno, "redirecting stdout failed: %m");
+
+ fd_stdout = safe_close(fd_stdout);
+ }
+
+ if (fd_stderr >= 0) {
+ r = dup2(fd_stderr, STDERR_FILENO);
+ if (r < 0)
+ log_warning_errno(errno, "redirecting stdout failed: %m");
+
+ fd_stderr = safe_close(fd_stderr);
+ }
+
+ /* terminate child in case parent goes away */
+ prctl(PR_SET_PDEATHSIG, SIGTERM);
+
+ /* restore sigmask before exec */
+ (void) reset_signal_mask();
+
+ execve(argv[0], argv, envp);
+
+ /* exec failed */
+ return log_error_errno(errno, "failed to execute '%s' '%s': %m", argv[0], cmd);
+}
+
+static void spawn_read(struct udev_event *event,
+ usec_t timeout_usec,
+ const char *cmd,
+ int fd_stdout, int fd_stderr,
+ char *result, size_t ressize) {
+ _cleanup_close_ int fd_ep = -1;
+ struct epoll_event ep_outpipe = {
+ .events = EPOLLIN,
+ .data.ptr = &fd_stdout,
+ };
+ struct epoll_event ep_errpipe = {
+ .events = EPOLLIN,
+ .data.ptr = &fd_stderr,
+ };
+ size_t respos = 0;
+ int r;
+
+ /* read from child if requested */
+ if (fd_stdout < 0 && fd_stderr < 0)
+ return;
+
+ fd_ep = epoll_create1(EPOLL_CLOEXEC);
+ if (fd_ep < 0) {
+ log_error_errno(errno, "error creating epoll fd: %m");
+ return;
+ }
+
+ if (fd_stdout >= 0) {
+ r = epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_stdout, &ep_outpipe);
+ if (r < 0) {
+ log_error_errno(errno, "fail to add stdout fd to epoll: %m");
+ return;
+ }
+ }
+
+ if (fd_stderr >= 0) {
+ r = epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_stderr, &ep_errpipe);
+ if (r < 0) {
+ log_error_errno(errno, "fail to add stderr fd to epoll: %m");
+ return;
+ }
+ }
+
+ /* read child output */
+ while (fd_stdout >= 0 || fd_stderr >= 0) {
+ int timeout;
+ int fdcount;
+ struct epoll_event ev[4];
+ int i;
+
+ if (timeout_usec > 0) {
+ usec_t age_usec;
+
+ age_usec = clock_boottime_or_monotonic() - event->birth_usec;
+ if (age_usec >= timeout_usec) {
+ log_error("timeout '%s'", cmd);
+ return;
+ }
+ timeout = ((timeout_usec - age_usec) / USEC_PER_MSEC) + MSEC_PER_SEC;
+ } else {
+ timeout = -1;
+ }
+
+ fdcount = epoll_wait(fd_ep, ev, ELEMENTSOF(ev), timeout);
+ if (fdcount < 0) {
+ if (errno == EINTR)
+ continue;
+ log_error_errno(errno, "failed to poll: %m");
+ return;
+ } else if (fdcount == 0) {
+ log_error("timeout '%s'", cmd);
+ return;
+ }
+
+ for (i = 0; i < fdcount; i++) {
+ int *fd = (int *)ev[i].data.ptr;
+
+ if (*fd < 0)
+ continue;
+
+ if (ev[i].events & EPOLLIN) {
+ ssize_t count;
+ char buf[4096];
+
+ count = read(*fd, buf, sizeof(buf)-1);
+ if (count <= 0)
+ continue;
+ buf[count] = '\0';
+
+ /* store stdout result */
+ if (result != NULL && *fd == fd_stdout) {
+ if (respos + count < ressize) {
+ memcpy(&result[respos], buf, count);
+ respos += count;
+ } else {
+ log_error("'%s' ressize %zu too short", cmd, ressize);
+ }
+ }
+
+ /* log debug output only if we watch stderr */
+ if (fd_stderr >= 0) {
+ char *pos;
+ char *line;
+
+ pos = buf;
+ while ((line = strsep(&pos, "\n"))) {
+ if (pos != NULL || line[0] != '\0')
+ log_debug("'%s'(%s) '%s'", cmd, *fd == fd_stdout ? "out" : "err" , line);
+ }
+ }
+ } else if (ev[i].events & EPOLLHUP) {
+ r = epoll_ctl(fd_ep, EPOLL_CTL_DEL, *fd, NULL);
+ if (r < 0) {
+ log_error_errno(errno, "failed to remove fd from epoll: %m");
+ return;
+ }
+ *fd = -1;
+ }
+ }
+ }
+
+ /* return the child's stdout string */
+ if (result != NULL)
+ result[respos] = '\0';
+}
+
+static int on_spawn_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
+ Spawn *spawn = userdata;
+ char timeout[FORMAT_TIMESTAMP_RELATIVE_MAX];
+
+ assert(spawn);
+
+ kill_and_sigcont(spawn->pid, SIGKILL);
+
+ log_error("spawned process '%s' ["PID_FMT"] timed out after %s, killing", spawn->cmd, spawn->pid,
+ format_timestamp_relative(timeout, sizeof(timeout), spawn->timeout));
+
+ return 1;
+}
+
+static int on_spawn_timeout_warning(sd_event_source *s, uint64_t usec, void *userdata) {
+ Spawn *spawn = userdata;
+ char timeout[FORMAT_TIMESTAMP_RELATIVE_MAX];
+
+ assert(spawn);
+
+ log_warning("spawned process '%s' ["PID_FMT"] is taking longer than %s to complete", spawn->cmd, spawn->pid,
+ format_timestamp_relative(timeout, sizeof(timeout), spawn->timeout));
+
+ return 1;
+}
+
+static int on_spawn_sigchld(sd_event_source *s, const siginfo_t *si, void *userdata) {
+ Spawn *spawn = userdata;
+
+ assert(spawn);
+
+ switch (si->si_code) {
+ case CLD_EXITED:
+ if (si->si_status == 0) {
+ log_debug("Process '%s' succeeded.", spawn->cmd);
+ sd_event_exit(sd_event_source_get_event(s), 0);
+
+ return 1;
+ } else if (spawn->accept_failure)
+ log_debug("Process '%s' failed with exit code %i.", spawn->cmd, si->si_status);
+ else
+ log_warning("Process '%s' failed with exit code %i.", spawn->cmd, si->si_status);
+
+ break;
+ case CLD_KILLED:
+ case CLD_DUMPED:
+ log_warning("Process '%s' terminated by signal %s.", spawn->cmd, signal_to_string(si->si_status));
+
+ break;
+ default:
+ log_error("Process '%s' failed due to unknown reason.", spawn->cmd);
+ }
+
+ sd_event_exit(sd_event_source_get_event(s), -EIO);
+
+ return 1;
+}
+
+static int spawn_wait(struct udev_event *event,
+ usec_t timeout_usec,
+ usec_t timeout_warn_usec,
+ const char *cmd, pid_t pid,
+ bool accept_failure) {
+ Spawn spawn = {
+ .cmd = cmd,
+ .pid = pid,
+ .accept_failure = accept_failure,
+ };
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ int r, ret;
+
+ r = sd_event_new(&e);
+ if (r < 0)
+ return r;
+
+ if (timeout_usec > 0) {
+ usec_t usec, age_usec;
+
+ usec = now(clock_boottime_or_monotonic());
+ age_usec = usec - event->birth_usec;
+ if (age_usec < timeout_usec) {
+ if (timeout_warn_usec > 0 && timeout_warn_usec < timeout_usec && age_usec < timeout_warn_usec) {
+ spawn.timeout_warn = timeout_warn_usec - age_usec;
+
+ r = sd_event_add_time(e, NULL, clock_boottime_or_monotonic(),
+ usec + spawn.timeout_warn, USEC_PER_SEC,
+ on_spawn_timeout_warning, &spawn);
+ if (r < 0)
+ return r;
+ }
+
+ spawn.timeout = timeout_usec - age_usec;
+
+ r = sd_event_add_time(e, NULL, clock_boottime_or_monotonic(),
+ usec + spawn.timeout, USEC_PER_SEC, on_spawn_timeout, &spawn);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ r = sd_event_add_child(e, NULL, pid, WEXITED, on_spawn_sigchld, &spawn);
+ if (r < 0)
+ return r;
+
+ r = sd_event_loop(e);
+ if (r < 0)
+ return r;
+
+ r = sd_event_get_exit_code(e, &ret);
+ if (r < 0)
+ return r;
+
+ return ret;
+}
+
+int udev_build_argv(struct udev *udev, char *cmd, int *argc, char *argv[]) {
+ int i = 0;
+ char *pos;
+
+ if (strchr(cmd, ' ') == NULL) {
+ argv[i++] = cmd;
+ goto out;
+ }
+
+ pos = cmd;
+ while (pos != NULL && pos[0] != '\0') {
+ if (pos[0] == '\'') {
+ /* do not separate quotes */
+ pos++;
+ argv[i] = strsep(&pos, "\'");
+ if (pos != NULL)
+ while (pos[0] == ' ')
+ pos++;
+ } else {
+ argv[i] = strsep(&pos, " ");
+ if (pos != NULL)
+ while (pos[0] == ' ')
+ pos++;
+ }
+ i++;
+ }
+out:
+ argv[i] = NULL;
+ if (argc)
+ *argc = i;
+ return 0;
+}
+
+int udev_event_spawn(struct udev_event *event,
+ usec_t timeout_usec,
+ usec_t timeout_warn_usec,
+ bool accept_failure,
+ const char *cmd,
+ char *result, size_t ressize) {
+ int outpipe[2] = {-1, -1};
+ int errpipe[2] = {-1, -1};
+ pid_t pid;
+ int err = 0;
+
+ /* pipes from child to parent */
+ if (result != NULL || log_get_max_level() >= LOG_INFO) {
+ if (pipe2(outpipe, O_NONBLOCK) != 0) {
+ err = log_error_errno(errno, "pipe failed: %m");
+ goto out;
+ }
+ }
+ if (log_get_max_level() >= LOG_INFO) {
+ if (pipe2(errpipe, O_NONBLOCK) != 0) {
+ err = log_error_errno(errno, "pipe failed: %m");
+ goto out;
+ }
+ }
+
+ pid = fork();
+ switch(pid) {
+ case 0:
+ {
+ char arg[UTIL_PATH_SIZE];
+ char *argv[128];
+ char program[UTIL_PATH_SIZE];
+
+ /* child closes parent's ends of pipes */
+ outpipe[READ_END] = safe_close(outpipe[READ_END]);
+ errpipe[READ_END] = safe_close(errpipe[READ_END]);
+
+ strscpy(arg, sizeof(arg), cmd);
+ udev_build_argv(event->udev, arg, NULL, argv);
+
+ /* allow programs in /usr/lib/udev/ to be called without the path */
+ if (argv[0][0] != '/') {
+ strscpyl(program, sizeof(program), UDEVLIBEXECDIR "/", argv[0], NULL);
+ argv[0] = program;
+ }
+
+ log_debug("starting '%s'", cmd);
+
+ spawn_exec(event, cmd, argv, udev_device_get_properties_envp(event->dev),
+ outpipe[WRITE_END], errpipe[WRITE_END]);
+
+ _exit(2);
+ }
+ case -1:
+ log_error_errno(errno, "fork of '%s' failed: %m", cmd);
+ err = -1;
+ goto out;
+ default:
+ /* parent closed child's ends of pipes */
+ outpipe[WRITE_END] = safe_close(outpipe[WRITE_END]);
+ errpipe[WRITE_END] = safe_close(errpipe[WRITE_END]);
+
+ spawn_read(event,
+ timeout_usec,
+ cmd,
+ outpipe[READ_END], errpipe[READ_END],
+ result, ressize);
+
+ err = spawn_wait(event, timeout_usec, timeout_warn_usec, cmd, pid, accept_failure);
+ }
+
+out:
+ if (outpipe[READ_END] >= 0)
+ close(outpipe[READ_END]);
+ if (outpipe[WRITE_END] >= 0)
+ close(outpipe[WRITE_END]);
+ if (errpipe[READ_END] >= 0)
+ close(errpipe[READ_END]);
+ if (errpipe[WRITE_END] >= 0)
+ close(errpipe[WRITE_END]);
+ return err;
+}
+
+static int rename_netif(struct udev_event *event) {
+ struct udev_device *dev = event->dev;
+ char name[IFNAMSIZ];
+ const char *oldname;
+ int r;
+
+ oldname = udev_device_get_sysname(dev);
+
+ strscpy(name, IFNAMSIZ, event->name);
+
+ r = rtnl_set_link_name(&event->rtnl, udev_device_get_ifindex(dev), name);
+ if (r < 0)
+ return log_error_errno(r, "Error changing net interface name '%s' to '%s': %m", oldname, name);
+
+ log_debug("renamed network interface '%s' to '%s'", oldname, name);
+
+ return 0;
+}
+
+void udev_event_execute_rules(struct udev_event *event,
+ usec_t timeout_usec, usec_t timeout_warn_usec,
+ struct udev_list *properties_list,
+ struct udev_rules *rules) {
+ struct udev_device *dev = event->dev;
+
+ if (udev_device_get_subsystem(dev) == NULL)
+ return;
+
+ if (streq(udev_device_get_action(dev), "remove")) {
+ udev_device_read_db(dev);
+ udev_device_tag_index(dev, NULL, false);
+ udev_device_delete_db(dev);
+
+ if (major(udev_device_get_devnum(dev)) != 0)
+ udev_watch_end(event->udev, dev);
+
+ udev_rules_apply_to_event(rules, event,
+ timeout_usec, timeout_warn_usec,
+ properties_list);
+
+ if (major(udev_device_get_devnum(dev)) != 0)
+ udev_node_remove(dev);
+ } else {
+ event->dev_db = udev_device_clone_with_db(dev);
+ if (event->dev_db != NULL) {
+ /* disable watch during event processing */
+ if (major(udev_device_get_devnum(dev)) != 0)
+ udev_watch_end(event->udev, event->dev_db);
+
+ if (major(udev_device_get_devnum(dev)) == 0 &&
+ streq(udev_device_get_action(dev), "move"))
+ udev_device_copy_properties(dev, event->dev_db);
+ }
+
+ udev_rules_apply_to_event(rules, event,
+ timeout_usec, timeout_warn_usec,
+ properties_list);
+
+ /* rename a new network interface, if needed */
+ if (udev_device_get_ifindex(dev) > 0 && streq(udev_device_get_action(dev), "add") &&
+ event->name != NULL && !streq(event->name, udev_device_get_sysname(dev))) {
+ int r;
+
+ r = rename_netif(event);
+ if (r < 0)
+ log_warning_errno(r, "could not rename interface '%d' from '%s' to '%s': %m", udev_device_get_ifindex(dev),
+ udev_device_get_sysname(dev), event->name);
+ else {
+ r = udev_device_rename(dev, event->name);
+ if (r < 0)
+ log_warning_errno(r, "renamed interface '%d' from '%s' to '%s', but could not update udev_device: %m",
+ udev_device_get_ifindex(dev), udev_device_get_sysname(dev), event->name);
+ else
+ log_debug("changed devpath to '%s'", udev_device_get_devpath(dev));
+ }
+ }
+
+ if (major(udev_device_get_devnum(dev)) > 0) {
+ bool apply;
+
+ /* remove/update possible left-over symlinks from old database entry */
+ if (event->dev_db != NULL)
+ udev_node_update_old_links(dev, event->dev_db);
+
+ if (!event->owner_set)
+ event->uid = udev_device_get_devnode_uid(dev);
+
+ if (!event->group_set)
+ event->gid = udev_device_get_devnode_gid(dev);
+
+ if (!event->mode_set) {
+ if (udev_device_get_devnode_mode(dev) > 0) {
+ /* kernel supplied value */
+ event->mode = udev_device_get_devnode_mode(dev);
+ } else if (event->gid > 0) {
+ /* default 0660 if a group is assigned */
+ event->mode = 0660;
+ } else {
+ /* default 0600 */
+ event->mode = 0600;
+ }
+ }
+
+ apply = streq(udev_device_get_action(dev), "add") || event->owner_set || event->group_set || event->mode_set;
+ udev_node_add(dev, apply, event->mode, event->uid, event->gid, &event->seclabel_list);
+ }
+
+ /* preserve old, or get new initialization timestamp */
+ udev_device_ensure_usec_initialized(event->dev, event->dev_db);
+
+ /* (re)write database file */
+ udev_device_tag_index(dev, event->dev_db, true);
+ udev_device_update_db(dev);
+ udev_device_set_is_initialized(dev);
+
+ event->dev_db = udev_device_unref(event->dev_db);
+ }
+}
+
+void udev_event_execute_run(struct udev_event *event, usec_t timeout_usec, usec_t timeout_warn_usec) {
+ struct udev_list_entry *list_entry;
+
+ udev_list_entry_foreach(list_entry, udev_list_get_entry(&event->run_list)) {
+ char command[UTIL_PATH_SIZE];
+ const char *cmd = udev_list_entry_get_name(list_entry);
+ enum udev_builtin_cmd builtin_cmd = udev_list_entry_get_num(list_entry);
+
+ udev_event_apply_format(event, cmd, command, sizeof(command));
+
+ if (builtin_cmd < UDEV_BUILTIN_MAX)
+ udev_builtin_run(event->dev, builtin_cmd, command, false);
+ else {
+ if (event->exec_delay > 0) {
+ log_debug("delay execution of '%s'", command);
+ sleep(event->exec_delay);
+ }
+
+ udev_event_spawn(event, timeout_usec, timeout_warn_usec, false, command, NULL, 0);
+ }
+ }
+}
diff --git a/src/grp-udev/libudev-core/udev-node.c b/src/grp-udev/libudev-core/udev-node.c
new file mode 100644
index 0000000000..9577ae069e
--- /dev/null
+++ b/src/grp-udev/libudev-core/udev-node.c
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2003-2013 Kay Sievers <kay@vrfy.org>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "systemd-basic/formats-util.h"
+#include "systemd-basic/fs-util.h"
+#include "systemd-basic/selinux-util.h"
+#include "systemd-basic/smack-util.h"
+#include "systemd-basic/stdio-util.h"
+#include "systemd-basic/string-util.h"
+#include "udev.h"
+
+static int node_symlink(struct udev_device *dev, const char *node, const char *slink) {
+ struct stat stats;
+ char target[UTIL_PATH_SIZE];
+ char *s;
+ size_t l;
+ char slink_tmp[UTIL_PATH_SIZE + 32];
+ int i = 0;
+ int tail = 0;
+ int err = 0;
+
+ /* use relative link */
+ target[0] = '\0';
+ while (node[i] && (node[i] == slink[i])) {
+ if (node[i] == '/')
+ tail = i+1;
+ i++;
+ }
+ s = target;
+ l = sizeof(target);
+ while (slink[i] != '\0') {
+ if (slink[i] == '/')
+ l = strpcpy(&s, l, "../");
+ i++;
+ }
+ l = strscpy(s, l, &node[tail]);
+ if (l == 0) {
+ err = -EINVAL;
+ goto exit;
+ }
+
+ /* preserve link with correct target, do not replace node of other device */
+ if (lstat(slink, &stats) == 0) {
+ if (S_ISBLK(stats.st_mode) || S_ISCHR(stats.st_mode)) {
+ log_error("conflicting device node '%s' found, link to '%s' will not be created", slink, node);
+ goto exit;
+ } else if (S_ISLNK(stats.st_mode)) {
+ char buf[UTIL_PATH_SIZE];
+ int len;
+
+ len = readlink(slink, buf, sizeof(buf));
+ if (len > 0 && len < (int)sizeof(buf)) {
+ buf[len] = '\0';
+ if (streq(target, buf)) {
+ log_debug("preserve already existing symlink '%s' to '%s'", slink, target);
+ label_fix(slink, true, false);
+ utimensat(AT_FDCWD, slink, NULL, AT_SYMLINK_NOFOLLOW);
+ goto exit;
+ }
+ }
+ }
+ } else {
+ log_debug("creating symlink '%s' to '%s'", slink, target);
+ do {
+ err = mkdir_parents_label(slink, 0755);
+ if (err != 0 && err != -ENOENT)
+ break;
+ mac_selinux_create_file_prepare(slink, S_IFLNK);
+ err = symlink(target, slink);
+ if (err != 0)
+ err = -errno;
+ mac_selinux_create_file_clear();
+ } while (err == -ENOENT);
+ if (err == 0)
+ goto exit;
+ }
+
+ log_debug("atomically replace '%s'", slink);
+ strscpyl(slink_tmp, sizeof(slink_tmp), slink, ".tmp-", udev_device_get_id_filename(dev), NULL);
+ unlink(slink_tmp);
+ do {
+ err = mkdir_parents_label(slink_tmp, 0755);
+ if (err != 0 && err != -ENOENT)
+ break;
+ mac_selinux_create_file_prepare(slink_tmp, S_IFLNK);
+ err = symlink(target, slink_tmp);
+ if (err != 0)
+ err = -errno;
+ mac_selinux_create_file_clear();
+ } while (err == -ENOENT);
+ if (err != 0) {
+ log_error_errno(errno, "symlink '%s' '%s' failed: %m", target, slink_tmp);
+ goto exit;
+ }
+ err = rename(slink_tmp, slink);
+ if (err != 0) {
+ log_error_errno(errno, "rename '%s' '%s' failed: %m", slink_tmp, slink);
+ unlink(slink_tmp);
+ }
+exit:
+ return err;
+}
+
+/* find device node of device with highest priority */
+static const char *link_find_prioritized(struct udev_device *dev, bool add, const char *stackdir, char *buf, size_t bufsize) {
+ struct udev *udev = udev_device_get_udev(dev);
+ DIR *dir;
+ int priority = 0;
+ const char *target = NULL;
+
+ if (add) {
+ priority = udev_device_get_devlink_priority(dev);
+ strscpy(buf, bufsize, udev_device_get_devnode(dev));
+ target = buf;
+ }
+
+ dir = opendir(stackdir);
+ if (dir == NULL)
+ return target;
+ for (;;) {
+ struct udev_device *dev_db;
+ struct dirent *dent;
+
+ dent = readdir(dir);
+ if (dent == NULL || dent->d_name[0] == '\0')
+ break;
+ if (dent->d_name[0] == '.')
+ continue;
+
+ log_debug("found '%s' claiming '%s'", dent->d_name, stackdir);
+
+ /* did we find ourself? */
+ if (streq(dent->d_name, udev_device_get_id_filename(dev)))
+ continue;
+
+ dev_db = udev_device_new_from_device_id(udev, dent->d_name);
+ if (dev_db != NULL) {
+ const char *devnode;
+
+ devnode = udev_device_get_devnode(dev_db);
+ if (devnode != NULL) {
+ if (target == NULL || udev_device_get_devlink_priority(dev_db) > priority) {
+ log_debug("'%s' claims priority %i for '%s'",
+ udev_device_get_syspath(dev_db), udev_device_get_devlink_priority(dev_db), stackdir);
+ priority = udev_device_get_devlink_priority(dev_db);
+ strscpy(buf, bufsize, devnode);
+ target = buf;
+ }
+ }
+ udev_device_unref(dev_db);
+ }
+ }
+ closedir(dir);
+ return target;
+}
+
+/* manage "stack of names" with possibly specified device priorities */
+static void link_update(struct udev_device *dev, const char *slink, bool add) {
+ char name_enc[UTIL_PATH_SIZE];
+ char filename[UTIL_PATH_SIZE * 2];
+ char dirname[UTIL_PATH_SIZE];
+ const char *target;
+ char buf[UTIL_PATH_SIZE];
+
+ util_path_encode(slink + strlen("/dev"), name_enc, sizeof(name_enc));
+ strscpyl(dirname, sizeof(dirname), "/run/udev/links/", name_enc, NULL);
+ strscpyl(filename, sizeof(filename), dirname, "/", udev_device_get_id_filename(dev), NULL);
+
+ if (!add && unlink(filename) == 0)
+ rmdir(dirname);
+
+ target = link_find_prioritized(dev, add, dirname, buf, sizeof(buf));
+ if (target == NULL) {
+ log_debug("no reference left, remove '%s'", slink);
+ if (unlink(slink) == 0)
+ rmdir_parents(slink, "/");
+ } else {
+ log_debug("creating link '%s' to '%s'", slink, target);
+ node_symlink(dev, target, slink);
+ }
+
+ if (add) {
+ int err;
+
+ do {
+ int fd;
+
+ err = mkdir_parents(filename, 0755);
+ if (err != 0 && err != -ENOENT)
+ break;
+ fd = open(filename, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC|O_NOFOLLOW, 0444);
+ if (fd >= 0)
+ close(fd);
+ else
+ err = -errno;
+ } while (err == -ENOENT);
+ }
+}
+
+void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev_old) {
+ struct udev_list_entry *list_entry;
+
+ /* update possible left-over symlinks */
+ udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev_old)) {
+ const char *name = udev_list_entry_get_name(list_entry);
+ struct udev_list_entry *list_entry_current;
+ int found;
+
+ /* check if old link name still belongs to this device */
+ found = 0;
+ udev_list_entry_foreach(list_entry_current, udev_device_get_devlinks_list_entry(dev)) {
+ const char *name_current = udev_list_entry_get_name(list_entry_current);
+
+ if (streq(name, name_current)) {
+ found = 1;
+ break;
+ }
+ }
+ if (found)
+ continue;
+
+ log_debug("update old name, '%s' no longer belonging to '%s'",
+ name, udev_device_get_devpath(dev));
+ link_update(dev, name, false);
+ }
+}
+
+static int node_permissions_apply(struct udev_device *dev, bool apply,
+ mode_t mode, uid_t uid, gid_t gid,
+ struct udev_list *seclabel_list) {
+ const char *devnode = udev_device_get_devnode(dev);
+ dev_t devnum = udev_device_get_devnum(dev);
+ struct stat stats;
+ struct udev_list_entry *entry;
+ int err = 0;
+
+ if (streq(udev_device_get_subsystem(dev), "block"))
+ mode |= S_IFBLK;
+ else
+ mode |= S_IFCHR;
+
+ if (lstat(devnode, &stats) != 0) {
+ err = log_debug_errno(errno, "can not stat() node '%s' (%m)", devnode);
+ goto out;
+ }
+
+ if (((stats.st_mode & S_IFMT) != (mode & S_IFMT)) || (stats.st_rdev != devnum)) {
+ err = -EEXIST;
+ log_debug("found node '%s' with non-matching devnum %s, skip handling",
+ udev_device_get_devnode(dev), udev_device_get_id_filename(dev));
+ goto out;
+ }
+
+ if (apply) {
+ bool selinux = false;
+ bool smack = false;
+
+ if ((stats.st_mode & 0777) != (mode & 0777) || stats.st_uid != uid || stats.st_gid != gid) {
+ log_debug("set permissions %s, %#o, uid=%u, gid=%u", devnode, mode, uid, gid);
+ err = chmod(devnode, mode);
+ if (err < 0)
+ log_warning_errno(errno, "setting mode of %s to %#o failed: %m", devnode, mode);
+ err = chown(devnode, uid, gid);
+ if (err < 0)
+ log_warning_errno(errno, "setting owner of %s to uid=%u, gid=%u failed: %m", devnode, uid, gid);
+ } else {
+ log_debug("preserve permissions %s, %#o, uid=%u, gid=%u", devnode, mode, uid, gid);
+ }
+
+ /* apply SECLABEL{$module}=$label */
+ udev_list_entry_foreach(entry, udev_list_get_entry(seclabel_list)) {
+ const char *name, *label;
+ int r;
+
+ name = udev_list_entry_get_name(entry);
+ label = udev_list_entry_get_value(entry);
+
+ if (streq(name, "selinux")) {
+ selinux = true;
+
+ r = mac_selinux_apply(devnode, label);
+ if (r < 0)
+ log_error_errno(r, "SECLABEL: failed to set SELinux label '%s': %m", label);
+ else
+ log_debug("SECLABEL: set SELinux label '%s'", label);
+
+ } else if (streq(name, "smack")) {
+ smack = true;
+
+ r = mac_smack_apply(devnode, SMACK_ATTR_ACCESS, label);
+ if (r < 0)
+ log_error_errno(r, "SECLABEL: failed to set SMACK label '%s': %m", label);
+ else
+ log_debug("SECLABEL: set SMACK label '%s'", label);
+
+ } else
+ log_error("SECLABEL: unknown subsystem, ignoring '%s'='%s'", name, label);
+ }
+
+ /* set the defaults */
+ if (!selinux)
+ mac_selinux_fix(devnode, true, false);
+ if (!smack)
+ mac_smack_apply(devnode, SMACK_ATTR_ACCESS, NULL);
+ }
+
+ /* always update timestamp when we re-use the node, like on media change events */
+ utimensat(AT_FDCWD, devnode, NULL, 0);
+out:
+ return err;
+}
+
+void udev_node_add(struct udev_device *dev, bool apply,
+ mode_t mode, uid_t uid, gid_t gid,
+ struct udev_list *seclabel_list) {
+ char filename[sizeof("/dev/block/:") + 2*DECIMAL_STR_MAX(unsigned)];
+ struct udev_list_entry *list_entry;
+
+ log_debug("handling device node '%s', devnum=%s, mode=%#o, uid="UID_FMT", gid="GID_FMT,
+ udev_device_get_devnode(dev), udev_device_get_id_filename(dev), mode, uid, gid);
+
+ if (node_permissions_apply(dev, apply, mode, uid, gid, seclabel_list) < 0)
+ return;
+
+ /* always add /dev/{block,char}/$major:$minor */
+ xsprintf(filename, "/dev/%s/%u:%u",
+ streq(udev_device_get_subsystem(dev), "block") ? "block" : "char",
+ major(udev_device_get_devnum(dev)),
+ minor(udev_device_get_devnum(dev)));
+ node_symlink(dev, udev_device_get_devnode(dev), filename);
+
+ /* create/update symlinks, add symlinks to name index */
+ udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev))
+ link_update(dev, udev_list_entry_get_name(list_entry), true);
+}
+
+void udev_node_remove(struct udev_device *dev) {
+ struct udev_list_entry *list_entry;
+ char filename[sizeof("/dev/block/:") + 2*DECIMAL_STR_MAX(unsigned)];
+
+ /* remove/update symlinks, remove symlinks from name index */
+ udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev))
+ link_update(dev, udev_list_entry_get_name(list_entry), false);
+
+ /* remove /dev/{block,char}/$major:$minor */
+ xsprintf(filename, "/dev/%s/%u:%u",
+ streq(udev_device_get_subsystem(dev), "block") ? "block" : "char",
+ major(udev_device_get_devnum(dev)),
+ minor(udev_device_get_devnum(dev)));
+ unlink(filename);
+}
diff --git a/src/grp-udev/libudev-core/udev-rules.c b/src/grp-udev/libudev-core/udev-rules.c
new file mode 100644
index 0000000000..68b5ffc90d
--- /dev/null
+++ b/src/grp-udev/libudev-core/udev-rules.c
@@ -0,0 +1,2582 @@
+/*
+ * Copyright (C) 2003-2012 Kay Sievers <kay@vrfy.org>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/conf-files.h"
+#include "systemd-basic/escape.h"
+#include "systemd-basic/fd-util.h"
+#include "systemd-basic/fs-util.h"
+#include "systemd-basic/glob-util.h"
+#include "systemd-basic/path-util.h"
+#include "systemd-basic/stat-util.h"
+#include "systemd-basic/stdio-util.h"
+#include "systemd-basic/strbuf.h"
+#include "systemd-basic/string-util.h"
+#include "systemd-basic/strv.h"
+#include "systemd-basic/user-util.h"
+#include "systemd-basic/util.h"
+#include "systemd-shared/sysctl-util.h"
+#include "udev.h"
+
+#define PREALLOC_TOKEN 2048
+
+struct uid_gid {
+ unsigned int name_off;
+ union {
+ uid_t uid;
+ gid_t gid;
+ };
+};
+
+static const char* const rules_dirs[] = {
+ "/etc/udev/rules.d",
+ "/run/udev/rules.d",
+ UDEVLIBEXECDIR "/rules.d",
+ NULL
+};
+
+struct udev_rules {
+ struct udev *udev;
+ usec_t dirs_ts_usec;
+ int resolve_names;
+
+ /* every key in the rules file becomes a token */
+ struct token *tokens;
+ unsigned int token_cur;
+ unsigned int token_max;
+
+ /* all key strings are copied and de-duplicated in a single continuous string buffer */
+ struct strbuf *strbuf;
+
+ /* during rule parsing, uid/gid lookup results are cached */
+ struct uid_gid *uids;
+ unsigned int uids_cur;
+ unsigned int uids_max;
+ struct uid_gid *gids;
+ unsigned int gids_cur;
+ unsigned int gids_max;
+};
+
+static char *rules_str(struct udev_rules *rules, unsigned int off) {
+ return rules->strbuf->buf + off;
+}
+
+static unsigned int rules_add_string(struct udev_rules *rules, const char *s) {
+ return strbuf_add_string(rules->strbuf, s, strlen(s));
+}
+
+/* KEY=="", KEY!="", KEY+="", KEY-="", KEY="", KEY:="" */
+enum operation_type {
+ OP_UNSET,
+
+ OP_MATCH,
+ OP_NOMATCH,
+ OP_MATCH_MAX,
+
+ OP_ADD,
+ OP_REMOVE,
+ OP_ASSIGN,
+ OP_ASSIGN_FINAL,
+};
+
+enum string_glob_type {
+ GL_UNSET,
+ GL_PLAIN, /* no special chars */
+ GL_GLOB, /* shell globs ?,*,[] */
+ GL_SPLIT, /* multi-value A|B */
+ GL_SPLIT_GLOB, /* multi-value with glob A*|B* */
+ GL_SOMETHING, /* commonly used "?*" */
+};
+
+enum string_subst_type {
+ SB_UNSET,
+ SB_NONE,
+ SB_FORMAT,
+ SB_SUBSYS,
+};
+
+/* tokens of a rule are sorted/handled in this order */
+enum token_type {
+ TK_UNSET,
+ TK_RULE,
+
+ TK_M_ACTION, /* val */
+ TK_M_DEVPATH, /* val */
+ TK_M_KERNEL, /* val */
+ TK_M_DEVLINK, /* val */
+ TK_M_NAME, /* val */
+ TK_M_ENV, /* val, attr */
+ TK_M_TAG, /* val */
+ TK_M_SUBSYSTEM, /* val */
+ TK_M_DRIVER, /* val */
+ TK_M_WAITFOR, /* val */
+ TK_M_ATTR, /* val, attr */
+ TK_M_SYSCTL, /* val, attr */
+
+ TK_M_PARENTS_MIN,
+ TK_M_KERNELS, /* val */
+ TK_M_SUBSYSTEMS, /* val */
+ TK_M_DRIVERS, /* val */
+ TK_M_ATTRS, /* val, attr */
+ TK_M_TAGS, /* val */
+ TK_M_PARENTS_MAX,
+
+ TK_M_TEST, /* val, mode_t */
+ TK_M_PROGRAM, /* val */
+ TK_M_IMPORT_FILE, /* val */
+ TK_M_IMPORT_PROG, /* val */
+ TK_M_IMPORT_BUILTIN, /* val */
+ TK_M_IMPORT_DB, /* val */
+ TK_M_IMPORT_CMDLINE, /* val */
+ TK_M_IMPORT_PARENT, /* val */
+ TK_M_RESULT, /* val */
+ TK_M_MAX,
+
+ TK_A_STRING_ESCAPE_NONE,
+ TK_A_STRING_ESCAPE_REPLACE,
+ TK_A_DB_PERSIST,
+ TK_A_INOTIFY_WATCH, /* int */
+ TK_A_DEVLINK_PRIO, /* int */
+ TK_A_OWNER, /* val */
+ TK_A_GROUP, /* val */
+ TK_A_MODE, /* val */
+ TK_A_OWNER_ID, /* uid_t */
+ TK_A_GROUP_ID, /* gid_t */
+ TK_A_MODE_ID, /* mode_t */
+ TK_A_TAG, /* val */
+ TK_A_STATIC_NODE, /* val */
+ TK_A_SECLABEL, /* val, attr */
+ TK_A_ENV, /* val, attr */
+ TK_A_NAME, /* val */
+ TK_A_DEVLINK, /* val */
+ TK_A_ATTR, /* val, attr */
+ TK_A_SYSCTL, /* val, attr */
+ TK_A_RUN_BUILTIN, /* val, bool */
+ TK_A_RUN_PROGRAM, /* val, bool */
+ TK_A_GOTO, /* size_t */
+
+ TK_END,
+};
+
+/* we try to pack stuff in a way that we take only 12 bytes per token */
+struct token {
+ union {
+ unsigned char type; /* same in rule and key */
+ struct {
+ enum token_type type:8;
+ bool can_set_name:1;
+ bool has_static_node:1;
+ unsigned int unused:6;
+ unsigned short token_count;
+ unsigned int label_off;
+ unsigned short filename_off;
+ unsigned short filename_line;
+ } rule;
+ struct {
+ enum token_type type:8;
+ enum operation_type op:8;
+ enum string_glob_type glob:8;
+ enum string_subst_type subst:4;
+ enum string_subst_type attrsubst:4;
+ unsigned int value_off;
+ union {
+ unsigned int attr_off;
+ unsigned int rule_goto;
+ mode_t mode;
+ uid_t uid;
+ gid_t gid;
+ int devlink_prio;
+ int watch;
+ enum udev_builtin_cmd builtin_cmd;
+ };
+ } key;
+ };
+};
+
+#define MAX_TK 64
+struct rule_tmp {
+ struct udev_rules *rules;
+ struct token rule;
+ struct token token[MAX_TK];
+ unsigned int token_cur;
+};
+
+#ifdef DEBUG
+static const char *operation_str(enum operation_type type) {
+ static const char *operation_strs[] = {
+ [OP_UNSET] = "UNSET",
+ [OP_MATCH] = "match",
+ [OP_NOMATCH] = "nomatch",
+ [OP_MATCH_MAX] = "MATCH_MAX",
+
+ [OP_ADD] = "add",
+ [OP_REMOVE] = "remove",
+ [OP_ASSIGN] = "assign",
+ [OP_ASSIGN_FINAL] = "assign-final",
+} ;
+
+ return operation_strs[type];
+}
+
+static const char *string_glob_str(enum string_glob_type type) {
+ static const char *string_glob_strs[] = {
+ [GL_UNSET] = "UNSET",
+ [GL_PLAIN] = "plain",
+ [GL_GLOB] = "glob",
+ [GL_SPLIT] = "split",
+ [GL_SPLIT_GLOB] = "split-glob",
+ [GL_SOMETHING] = "split-glob",
+ };
+
+ return string_glob_strs[type];
+}
+
+static const char *token_str(enum token_type type) {
+ static const char *token_strs[] = {
+ [TK_UNSET] = "UNSET",
+ [TK_RULE] = "RULE",
+
+ [TK_M_ACTION] = "M ACTION",
+ [TK_M_DEVPATH] = "M DEVPATH",
+ [TK_M_KERNEL] = "M KERNEL",
+ [TK_M_DEVLINK] = "M DEVLINK",
+ [TK_M_NAME] = "M NAME",
+ [TK_M_ENV] = "M ENV",
+ [TK_M_TAG] = "M TAG",
+ [TK_M_SUBSYSTEM] = "M SUBSYSTEM",
+ [TK_M_DRIVER] = "M DRIVER",
+ [TK_M_WAITFOR] = "M WAITFOR",
+ [TK_M_ATTR] = "M ATTR",
+ [TK_M_SYSCTL] = "M SYSCTL",
+
+ [TK_M_PARENTS_MIN] = "M PARENTS_MIN",
+ [TK_M_KERNELS] = "M KERNELS",
+ [TK_M_SUBSYSTEMS] = "M SUBSYSTEMS",
+ [TK_M_DRIVERS] = "M DRIVERS",
+ [TK_M_ATTRS] = "M ATTRS",
+ [TK_M_TAGS] = "M TAGS",
+ [TK_M_PARENTS_MAX] = "M PARENTS_MAX",
+
+ [TK_M_TEST] = "M TEST",
+ [TK_M_PROGRAM] = "M PROGRAM",
+ [TK_M_IMPORT_FILE] = "M IMPORT_FILE",
+ [TK_M_IMPORT_PROG] = "M IMPORT_PROG",
+ [TK_M_IMPORT_BUILTIN] = "M IMPORT_BUILTIN",
+ [TK_M_IMPORT_DB] = "M IMPORT_DB",
+ [TK_M_IMPORT_CMDLINE] = "M IMPORT_CMDLINE",
+ [TK_M_IMPORT_PARENT] = "M IMPORT_PARENT",
+ [TK_M_RESULT] = "M RESULT",
+ [TK_M_MAX] = "M MAX",
+
+ [TK_A_STRING_ESCAPE_NONE] = "A STRING_ESCAPE_NONE",
+ [TK_A_STRING_ESCAPE_REPLACE] = "A STRING_ESCAPE_REPLACE",
+ [TK_A_DB_PERSIST] = "A DB_PERSIST",
+ [TK_A_INOTIFY_WATCH] = "A INOTIFY_WATCH",
+ [TK_A_DEVLINK_PRIO] = "A DEVLINK_PRIO",
+ [TK_A_OWNER] = "A OWNER",
+ [TK_A_GROUP] = "A GROUP",
+ [TK_A_MODE] = "A MODE",
+ [TK_A_OWNER_ID] = "A OWNER_ID",
+ [TK_A_GROUP_ID] = "A GROUP_ID",
+ [TK_A_STATIC_NODE] = "A STATIC_NODE",
+ [TK_A_SECLABEL] = "A SECLABEL",
+ [TK_A_MODE_ID] = "A MODE_ID",
+ [TK_A_ENV] = "A ENV",
+ [TK_A_TAG] = "A ENV",
+ [TK_A_NAME] = "A NAME",
+ [TK_A_DEVLINK] = "A DEVLINK",
+ [TK_A_ATTR] = "A ATTR",
+ [TK_A_SYSCTL] = "A SYSCTL",
+ [TK_A_RUN_BUILTIN] = "A RUN_BUILTIN",
+ [TK_A_RUN_PROGRAM] = "A RUN_PROGRAM",
+ [TK_A_GOTO] = "A GOTO",
+
+ [TK_END] = "END",
+ };
+
+ return token_strs[type];
+}
+
+static void dump_token(struct udev_rules *rules, struct token *token) {
+ enum token_type type = token->type;
+ enum operation_type op = token->key.op;
+ enum string_glob_type glob = token->key.glob;
+ const char *value = rules_str(rules, token->key.value_off);
+ const char *attr = &rules->strbuf->buf[token->key.attr_off];
+
+ switch (type) {
+ case TK_RULE:
+ {
+ const char *tks_ptr = (char *)rules->tokens;
+ const char *tk_ptr = (char *)token;
+ unsigned int idx = (tk_ptr - tks_ptr) / sizeof(struct token);
+
+ log_debug("* RULE %s:%u, token: %u, count: %u, label: '%s'",
+ &rules->strbuf->buf[token->rule.filename_off], token->rule.filename_line,
+ idx, token->rule.token_count,
+ &rules->strbuf->buf[token->rule.label_off]);
+ break;
+ }
+ case TK_M_ACTION:
+ case TK_M_DEVPATH:
+ case TK_M_KERNEL:
+ case TK_M_SUBSYSTEM:
+ case TK_M_DRIVER:
+ case TK_M_WAITFOR:
+ case TK_M_DEVLINK:
+ case TK_M_NAME:
+ case TK_M_KERNELS:
+ case TK_M_SUBSYSTEMS:
+ case TK_M_DRIVERS:
+ case TK_M_TAGS:
+ case TK_M_PROGRAM:
+ case TK_M_IMPORT_FILE:
+ case TK_M_IMPORT_PROG:
+ case TK_M_IMPORT_DB:
+ case TK_M_IMPORT_CMDLINE:
+ case TK_M_IMPORT_PARENT:
+ case TK_M_RESULT:
+ case TK_A_NAME:
+ case TK_A_DEVLINK:
+ case TK_A_OWNER:
+ case TK_A_GROUP:
+ case TK_A_MODE:
+ case TK_A_RUN_BUILTIN:
+ case TK_A_RUN_PROGRAM:
+ log_debug("%s %s '%s'(%s)",
+ token_str(type), operation_str(op), value, string_glob_str(glob));
+ break;
+ case TK_M_IMPORT_BUILTIN:
+ log_debug("%s %i '%s'", token_str(type), token->key.builtin_cmd, value);
+ break;
+ case TK_M_ATTR:
+ case TK_M_SYSCTL:
+ case TK_M_ATTRS:
+ case TK_M_ENV:
+ case TK_A_ATTR:
+ case TK_A_SYSCTL:
+ case TK_A_ENV:
+ log_debug("%s %s '%s' '%s'(%s)",
+ token_str(type), operation_str(op), attr, value, string_glob_str(glob));
+ break;
+ case TK_M_TAG:
+ case TK_A_TAG:
+ log_debug("%s %s '%s'", token_str(type), operation_str(op), value);
+ break;
+ case TK_A_STRING_ESCAPE_NONE:
+ case TK_A_STRING_ESCAPE_REPLACE:
+ case TK_A_DB_PERSIST:
+ log_debug("%s", token_str(type));
+ break;
+ case TK_M_TEST:
+ log_debug("%s %s '%s'(%s) %#o",
+ token_str(type), operation_str(op), value, string_glob_str(glob), token->key.mode);
+ break;
+ case TK_A_INOTIFY_WATCH:
+ log_debug("%s %u", token_str(type), token->key.watch);
+ break;
+ case TK_A_DEVLINK_PRIO:
+ log_debug("%s %u", token_str(type), token->key.devlink_prio);
+ break;
+ case TK_A_OWNER_ID:
+ log_debug("%s %s %u", token_str(type), operation_str(op), token->key.uid);
+ break;
+ case TK_A_GROUP_ID:
+ log_debug("%s %s %u", token_str(type), operation_str(op), token->key.gid);
+ break;
+ case TK_A_MODE_ID:
+ log_debug("%s %s %#o", token_str(type), operation_str(op), token->key.mode);
+ break;
+ case TK_A_STATIC_NODE:
+ log_debug("%s '%s'", token_str(type), value);
+ break;
+ case TK_A_SECLABEL:
+ log_debug("%s %s '%s' '%s'", token_str(type), operation_str(op), attr, value);
+ break;
+ case TK_A_GOTO:
+ log_debug("%s '%s' %u", token_str(type), value, token->key.rule_goto);
+ break;
+ case TK_END:
+ log_debug("* %s", token_str(type));
+ break;
+ case TK_M_PARENTS_MIN:
+ case TK_M_PARENTS_MAX:
+ case TK_M_MAX:
+ case TK_UNSET:
+ log_debug("unknown type %u", type);
+ break;
+ }
+}
+
+static void dump_rules(struct udev_rules *rules) {
+ unsigned int i;
+
+ log_debug("dumping %u (%zu bytes) tokens, %zu (%zu bytes) strings",
+ rules->token_cur,
+ rules->token_cur * sizeof(struct token),
+ rules->strbuf->nodes_count,
+ rules->strbuf->len);
+ for (i = 0; i < rules->token_cur; i++)
+ dump_token(rules, &rules->tokens[i]);
+}
+#else
+static inline void dump_token(struct udev_rules *rules, struct token *token) {}
+static inline void dump_rules(struct udev_rules *rules) {}
+#endif /* DEBUG */
+
+static int add_token(struct udev_rules *rules, struct token *token) {
+ /* grow buffer if needed */
+ if (rules->token_cur+1 >= rules->token_max) {
+ struct token *tokens;
+ unsigned int add;
+
+ /* double the buffer size */
+ add = rules->token_max;
+ if (add < 8)
+ add = 8;
+
+ tokens = realloc(rules->tokens, (rules->token_max + add ) * sizeof(struct token));
+ if (tokens == NULL)
+ return -1;
+ rules->tokens = tokens;
+ rules->token_max += add;
+ }
+ memcpy(&rules->tokens[rules->token_cur], token, sizeof(struct token));
+ rules->token_cur++;
+ return 0;
+}
+
+static uid_t add_uid(struct udev_rules *rules, const char *owner) {
+ unsigned int i;
+ uid_t uid = 0;
+ unsigned int off;
+ int r;
+
+ /* lookup, if we know it already */
+ for (i = 0; i < rules->uids_cur; i++) {
+ off = rules->uids[i].name_off;
+ if (streq(rules_str(rules, off), owner)) {
+ uid = rules->uids[i].uid;
+ return uid;
+ }
+ }
+ r = get_user_creds(&owner, &uid, NULL, NULL, NULL);
+ if (r < 0) {
+ if (r == -ENOENT || r == -ESRCH)
+ log_error("specified user '%s' unknown", owner);
+ else
+ log_error_errno(r, "error resolving user '%s': %m", owner);
+ }
+
+ /* grow buffer if needed */
+ if (rules->uids_cur+1 >= rules->uids_max) {
+ struct uid_gid *uids;
+ unsigned int add;
+
+ /* double the buffer size */
+ add = rules->uids_max;
+ if (add < 1)
+ add = 8;
+
+ uids = realloc(rules->uids, (rules->uids_max + add ) * sizeof(struct uid_gid));
+ if (uids == NULL)
+ return uid;
+ rules->uids = uids;
+ rules->uids_max += add;
+ }
+ rules->uids[rules->uids_cur].uid = uid;
+ off = rules_add_string(rules, owner);
+ if (off <= 0)
+ return uid;
+ rules->uids[rules->uids_cur].name_off = off;
+ rules->uids_cur++;
+ return uid;
+}
+
+static gid_t add_gid(struct udev_rules *rules, const char *group) {
+ unsigned int i;
+ gid_t gid = 0;
+ unsigned int off;
+ int r;
+
+ /* lookup, if we know it already */
+ for (i = 0; i < rules->gids_cur; i++) {
+ off = rules->gids[i].name_off;
+ if (streq(rules_str(rules, off), group)) {
+ gid = rules->gids[i].gid;
+ return gid;
+ }
+ }
+ r = get_group_creds(&group, &gid);
+ if (r < 0) {
+ if (r == -ENOENT || r == -ESRCH)
+ log_error("specified group '%s' unknown", group);
+ else
+ log_error_errno(r, "error resolving group '%s': %m", group);
+ }
+
+ /* grow buffer if needed */
+ if (rules->gids_cur+1 >= rules->gids_max) {
+ struct uid_gid *gids;
+ unsigned int add;
+
+ /* double the buffer size */
+ add = rules->gids_max;
+ if (add < 1)
+ add = 8;
+
+ gids = realloc(rules->gids, (rules->gids_max + add ) * sizeof(struct uid_gid));
+ if (gids == NULL)
+ return gid;
+ rules->gids = gids;
+ rules->gids_max += add;
+ }
+ rules->gids[rules->gids_cur].gid = gid;
+ off = rules_add_string(rules, group);
+ if (off <= 0)
+ return gid;
+ rules->gids[rules->gids_cur].name_off = off;
+ rules->gids_cur++;
+ return gid;
+}
+
+static int import_property_from_string(struct udev_device *dev, char *line) {
+ char *key;
+ char *val;
+ size_t len;
+
+ /* find key */
+ key = line;
+ while (isspace(key[0]))
+ key++;
+
+ /* comment or empty line */
+ if (key[0] == '#' || key[0] == '\0')
+ return -1;
+
+ /* split key/value */
+ val = strchr(key, '=');
+ if (val == NULL)
+ return -1;
+ val[0] = '\0';
+ val++;
+
+ /* find value */
+ while (isspace(val[0]))
+ val++;
+
+ /* terminate key */
+ len = strlen(key);
+ if (len == 0)
+ return -1;
+ while (isspace(key[len-1]))
+ len--;
+ key[len] = '\0';
+
+ /* terminate value */
+ len = strlen(val);
+ if (len == 0)
+ return -1;
+ while (isspace(val[len-1]))
+ len--;
+ val[len] = '\0';
+
+ if (len == 0)
+ return -1;
+
+ /* unquote */
+ if (val[0] == '"' || val[0] == '\'') {
+ if (val[len-1] != val[0]) {
+ log_debug("inconsistent quoting: '%s', skip", line);
+ return -1;
+ }
+ val[len-1] = '\0';
+ val++;
+ }
+
+ udev_device_add_property(dev, key, val);
+
+ return 0;
+}
+
+static int import_file_into_properties(struct udev_device *dev, const char *filename) {
+ FILE *f;
+ char line[UTIL_LINE_SIZE];
+
+ f = fopen(filename, "re");
+ if (f == NULL)
+ return -1;
+ while (fgets(line, sizeof(line), f) != NULL)
+ import_property_from_string(dev, line);
+ fclose(f);
+ return 0;
+}
+
+static int import_program_into_properties(struct udev_event *event,
+ usec_t timeout_usec,
+ usec_t timeout_warn_usec,
+ const char *program) {
+ char result[UTIL_LINE_SIZE];
+ char *line;
+ int err;
+
+ err = udev_event_spawn(event, timeout_usec, timeout_warn_usec, true, program, result, sizeof(result));
+ if (err < 0)
+ return err;
+
+ line = result;
+ while (line != NULL) {
+ char *pos;
+
+ pos = strchr(line, '\n');
+ if (pos != NULL) {
+ pos[0] = '\0';
+ pos = &pos[1];
+ }
+ import_property_from_string(event->dev, line);
+ line = pos;
+ }
+ return 0;
+}
+
+static int import_parent_into_properties(struct udev_device *dev, const char *filter) {
+ struct udev_device *dev_parent;
+ struct udev_list_entry *list_entry;
+
+ assert(dev);
+ assert(filter);
+
+ dev_parent = udev_device_get_parent(dev);
+ if (dev_parent == NULL)
+ return -1;
+
+ udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(dev_parent)) {
+ const char *key = udev_list_entry_get_name(list_entry);
+ const char *val = udev_list_entry_get_value(list_entry);
+
+ if (fnmatch(filter, key, 0) == 0)
+ udev_device_add_property(dev, key, val);
+ }
+ return 0;
+}
+
+static void attr_subst_subdir(char *attr, size_t len) {
+ const char *pos, *tail, *path;
+ _cleanup_closedir_ DIR *dir = NULL;
+ struct dirent *dent;
+
+ pos = strstr(attr, "/*/");
+ if (!pos)
+ return;
+
+ tail = pos + 2;
+ path = strndupa(attr, pos - attr + 1); /* include slash at end */
+ dir = opendir(path);
+ if (dir == NULL)
+ return;
+
+ for (dent = readdir(dir); dent != NULL; dent = readdir(dir))
+ if (dent->d_name[0] != '.') {
+ char n[strlen(dent->d_name) + strlen(tail) + 1];
+
+ strscpyl(n, sizeof n, dent->d_name, tail, NULL);
+ if (faccessat(dirfd(dir), n, F_OK, 0) == 0) {
+ strscpyl(attr, len, path, n, NULL);
+ break;
+ }
+ }
+}
+
+static int get_key(struct udev *udev, char **line, char **key, enum operation_type *op, char **value) {
+ char *linepos;
+ char *temp;
+
+ linepos = *line;
+ if (linepos == NULL || linepos[0] == '\0')
+ return -1;
+
+ /* skip whitespace */
+ while (isspace(linepos[0]) || linepos[0] == ',')
+ linepos++;
+
+ /* get the key */
+ if (linepos[0] == '\0')
+ return -1;
+ *key = linepos;
+
+ for (;;) {
+ linepos++;
+ if (linepos[0] == '\0')
+ return -1;
+ if (isspace(linepos[0]))
+ break;
+ if (linepos[0] == '=')
+ break;
+ if ((linepos[0] == '+') || (linepos[0] == '-') || (linepos[0] == '!') || (linepos[0] == ':'))
+ if (linepos[1] == '=')
+ break;
+ }
+
+ /* remember end of key */
+ temp = linepos;
+
+ /* skip whitespace after key */
+ while (isspace(linepos[0]))
+ linepos++;
+ if (linepos[0] == '\0')
+ return -1;
+
+ /* get operation type */
+ if (linepos[0] == '=' && linepos[1] == '=') {
+ *op = OP_MATCH;
+ linepos += 2;
+ } else if (linepos[0] == '!' && linepos[1] == '=') {
+ *op = OP_NOMATCH;
+ linepos += 2;
+ } else if (linepos[0] == '+' && linepos[1] == '=') {
+ *op = OP_ADD;
+ linepos += 2;
+ } else if (linepos[0] == '-' && linepos[1] == '=') {
+ *op = OP_REMOVE;
+ linepos += 2;
+ } else if (linepos[0] == '=') {
+ *op = OP_ASSIGN;
+ linepos++;
+ } else if (linepos[0] == ':' && linepos[1] == '=') {
+ *op = OP_ASSIGN_FINAL;
+ linepos += 2;
+ } else
+ return -1;
+
+ /* terminate key */
+ temp[0] = '\0';
+
+ /* skip whitespace after operator */
+ while (isspace(linepos[0]))
+ linepos++;
+ if (linepos[0] == '\0')
+ return -1;
+
+ /* get the value */
+ if (linepos[0] == '"')
+ linepos++;
+ else
+ return -1;
+ *value = linepos;
+
+ /* terminate */
+ temp = strchr(linepos, '"');
+ if (!temp)
+ return -1;
+ temp[0] = '\0';
+ temp++;
+
+ /* move line to next key */
+ *line = temp;
+ return 0;
+}
+
+/* extract possible KEY{attr} */
+static const char *get_key_attribute(struct udev *udev, char *str) {
+ char *pos;
+ char *attr;
+
+ attr = strchr(str, '{');
+ if (attr != NULL) {
+ attr++;
+ pos = strchr(attr, '}');
+ if (pos == NULL) {
+ log_error("missing closing brace for format");
+ return NULL;
+ }
+ pos[0] = '\0';
+ return attr;
+ }
+ return NULL;
+}
+
+static void rule_add_key(struct rule_tmp *rule_tmp, enum token_type type,
+ enum operation_type op,
+ const char *value, const void *data) {
+ struct token *token = rule_tmp->token + rule_tmp->token_cur;
+ const char *attr = NULL;
+
+ assert(rule_tmp->token_cur < ELEMENTSOF(rule_tmp->token));
+ memzero(token, sizeof(struct token));
+
+ switch (type) {
+ case TK_M_ACTION:
+ case TK_M_DEVPATH:
+ case TK_M_KERNEL:
+ case TK_M_SUBSYSTEM:
+ case TK_M_DRIVER:
+ case TK_M_WAITFOR:
+ case TK_M_DEVLINK:
+ case TK_M_NAME:
+ case TK_M_KERNELS:
+ case TK_M_SUBSYSTEMS:
+ case TK_M_DRIVERS:
+ case TK_M_TAGS:
+ case TK_M_PROGRAM:
+ case TK_M_IMPORT_FILE:
+ case TK_M_IMPORT_PROG:
+ case TK_M_IMPORT_DB:
+ case TK_M_IMPORT_CMDLINE:
+ case TK_M_IMPORT_PARENT:
+ case TK_M_RESULT:
+ case TK_A_OWNER:
+ case TK_A_GROUP:
+ case TK_A_MODE:
+ case TK_A_DEVLINK:
+ case TK_A_NAME:
+ case TK_A_GOTO:
+ case TK_M_TAG:
+ case TK_A_TAG:
+ case TK_A_STATIC_NODE:
+ token->key.value_off = rules_add_string(rule_tmp->rules, value);
+ break;
+ case TK_M_IMPORT_BUILTIN:
+ token->key.value_off = rules_add_string(rule_tmp->rules, value);
+ token->key.builtin_cmd = *(enum udev_builtin_cmd *)data;
+ break;
+ case TK_M_ENV:
+ case TK_M_ATTR:
+ case TK_M_SYSCTL:
+ case TK_M_ATTRS:
+ case TK_A_ATTR:
+ case TK_A_SYSCTL:
+ case TK_A_ENV:
+ case TK_A_SECLABEL:
+ attr = data;
+ token->key.value_off = rules_add_string(rule_tmp->rules, value);
+ token->key.attr_off = rules_add_string(rule_tmp->rules, attr);
+ break;
+ case TK_M_TEST:
+ token->key.value_off = rules_add_string(rule_tmp->rules, value);
+ if (data != NULL)
+ token->key.mode = *(mode_t *)data;
+ break;
+ case TK_A_STRING_ESCAPE_NONE:
+ case TK_A_STRING_ESCAPE_REPLACE:
+ case TK_A_DB_PERSIST:
+ break;
+ case TK_A_RUN_BUILTIN:
+ case TK_A_RUN_PROGRAM:
+ token->key.builtin_cmd = *(enum udev_builtin_cmd *)data;
+ token->key.value_off = rules_add_string(rule_tmp->rules, value);
+ break;
+ case TK_A_INOTIFY_WATCH:
+ case TK_A_DEVLINK_PRIO:
+ token->key.devlink_prio = *(int *)data;
+ break;
+ case TK_A_OWNER_ID:
+ token->key.uid = *(uid_t *)data;
+ break;
+ case TK_A_GROUP_ID:
+ token->key.gid = *(gid_t *)data;
+ break;
+ case TK_A_MODE_ID:
+ token->key.mode = *(mode_t *)data;
+ break;
+ case TK_RULE:
+ case TK_M_PARENTS_MIN:
+ case TK_M_PARENTS_MAX:
+ case TK_M_MAX:
+ case TK_END:
+ case TK_UNSET:
+ assert_not_reached("wrong type");
+ }
+
+ if (value != NULL && type < TK_M_MAX) {
+ /* check if we need to split or call fnmatch() while matching rules */
+ enum string_glob_type glob;
+ int has_split;
+ int has_glob;
+
+ has_split = (strchr(value, '|') != NULL);
+ has_glob = string_is_glob(value);
+ if (has_split && has_glob) {
+ glob = GL_SPLIT_GLOB;
+ } else if (has_split) {
+ glob = GL_SPLIT;
+ } else if (has_glob) {
+ if (streq(value, "?*"))
+ glob = GL_SOMETHING;
+ else
+ glob = GL_GLOB;
+ } else {
+ glob = GL_PLAIN;
+ }
+ token->key.glob = glob;
+ }
+
+ if (value != NULL && type > TK_M_MAX) {
+ /* check if assigned value has substitution chars */
+ if (value[0] == '[')
+ token->key.subst = SB_SUBSYS;
+ else if (strchr(value, '%') != NULL || strchr(value, '$') != NULL)
+ token->key.subst = SB_FORMAT;
+ else
+ token->key.subst = SB_NONE;
+ }
+
+ if (attr != NULL) {
+ /* check if property/attribute name has substitution chars */
+ if (attr[0] == '[')
+ token->key.attrsubst = SB_SUBSYS;
+ else if (strchr(attr, '%') != NULL || strchr(attr, '$') != NULL)
+ token->key.attrsubst = SB_FORMAT;
+ else
+ token->key.attrsubst = SB_NONE;
+ }
+
+ token->key.type = type;
+ token->key.op = op;
+ rule_tmp->token_cur++;
+}
+
+static int sort_token(struct udev_rules *rules, struct rule_tmp *rule_tmp) {
+ unsigned int i;
+ unsigned int start = 0;
+ unsigned int end = rule_tmp->token_cur;
+
+ for (i = 0; i < rule_tmp->token_cur; i++) {
+ enum token_type next_val = TK_UNSET;
+ unsigned int next_idx = 0;
+ unsigned int j;
+
+ /* find smallest value */
+ for (j = start; j < end; j++) {
+ if (rule_tmp->token[j].type == TK_UNSET)
+ continue;
+ if (next_val == TK_UNSET || rule_tmp->token[j].type < next_val) {
+ next_val = rule_tmp->token[j].type;
+ next_idx = j;
+ }
+ }
+
+ /* add token and mark done */
+ if (add_token(rules, &rule_tmp->token[next_idx]) != 0)
+ return -1;
+ rule_tmp->token[next_idx].type = TK_UNSET;
+
+ /* shrink range */
+ if (next_idx == start)
+ start++;
+ if (next_idx+1 == end)
+ end--;
+ }
+ return 0;
+}
+
+#define LOG_RULE_ERROR(fmt, ...) log_error("Invalid rule %s:%u: " fmt, filename, lineno, ##__VA_ARGS__)
+#define LOG_RULE_WARNING(fmt, ...) log_warning("%s:%u: " fmt, filename, lineno, ##__VA_ARGS__)
+#define LOG_RULE_DEBUG(fmt, ...) log_debug("%s:%u: " fmt, filename, lineno, ##__VA_ARGS__)
+#define LOG_AND_RETURN(fmt, ...) { LOG_RULE_ERROR(fmt, __VA_ARGS__); return; }
+
+static void add_rule(struct udev_rules *rules, char *line,
+ const char *filename, unsigned int filename_off, unsigned int lineno) {
+ char *linepos;
+ const char *attr;
+ struct rule_tmp rule_tmp = {
+ .rules = rules,
+ .rule.type = TK_RULE,
+ };
+
+ /* the offset in the rule is limited to unsigned short */
+ if (filename_off < USHRT_MAX)
+ rule_tmp.rule.rule.filename_off = filename_off;
+ rule_tmp.rule.rule.filename_line = lineno;
+
+ linepos = line;
+ for (;;) {
+ char *key;
+ char *value;
+ enum operation_type op;
+
+ if (get_key(rules->udev, &linepos, &key, &op, &value) != 0) {
+ /* Avoid erroring on trailing whitespace. This is probably rare
+ * so save the work for the error case instead of always trying
+ * to strip the trailing whitespace with strstrip(). */
+ while (isblank(*linepos))
+ linepos++;
+
+ /* If we aren't at the end of the line, this is a parsing error.
+ * Make a best effort to describe where the problem is. */
+ if (!strchr(NEWLINE, *linepos)) {
+ char buf[2] = {*linepos};
+ _cleanup_free_ char *tmp;
+
+ tmp = cescape(buf);
+ log_error("invalid key/value pair in file %s on line %u, starting at character %tu ('%s')",
+ filename, lineno, linepos - line + 1, tmp);
+ if (*linepos == '#')
+ log_error("hint: comments can only start at beginning of line");
+ }
+ break;
+ }
+
+ if (rule_tmp.token_cur >= ELEMENTSOF(rule_tmp.token))
+ LOG_AND_RETURN("temporary rule array too small, aborting event processing with %u items", rule_tmp.token_cur);
+
+ if (streq(key, "ACTION")) {
+ if (op > OP_MATCH_MAX)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ rule_add_key(&rule_tmp, TK_M_ACTION, op, value, NULL);
+
+ } else if (streq(key, "DEVPATH")) {
+ if (op > OP_MATCH_MAX)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ rule_add_key(&rule_tmp, TK_M_DEVPATH, op, value, NULL);
+
+ } else if (streq(key, "KERNEL")) {
+ if (op > OP_MATCH_MAX)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ rule_add_key(&rule_tmp, TK_M_KERNEL, op, value, NULL);
+
+ } else if (streq(key, "SUBSYSTEM")) {
+ if (op > OP_MATCH_MAX)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ /* bus, class, subsystem events should all be the same */
+ if (STR_IN_SET(value, "subsystem", "bus", "class")) {
+ if (!streq(value, "subsystem"))
+ LOG_RULE_WARNING("'%s' must be specified as 'subsystem'; please fix", value);
+
+ rule_add_key(&rule_tmp, TK_M_SUBSYSTEM, op, "subsystem|class|bus", NULL);
+ } else
+ rule_add_key(&rule_tmp, TK_M_SUBSYSTEM, op, value, NULL);
+
+ } else if (streq(key, "DRIVER")) {
+ if (op > OP_MATCH_MAX)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ rule_add_key(&rule_tmp, TK_M_DRIVER, op, value, NULL);
+
+ } else if (startswith(key, "ATTR{")) {
+ attr = get_key_attribute(rules->udev, key + strlen("ATTR"));
+ if (attr == NULL)
+ LOG_AND_RETURN("error parsing %s attribute", "ATTR");
+
+ if (op == OP_REMOVE)
+ LOG_AND_RETURN("invalid %s operation", "ATTR");
+
+ if (op < OP_MATCH_MAX)
+ rule_add_key(&rule_tmp, TK_M_ATTR, op, value, attr);
+ else
+ rule_add_key(&rule_tmp, TK_A_ATTR, op, value, attr);
+
+ } else if (startswith(key, "SYSCTL{")) {
+ attr = get_key_attribute(rules->udev, key + strlen("SYSCTL"));
+ if (attr == NULL)
+ LOG_AND_RETURN("error parsing %s attribute", "ATTR");
+
+ if (op == OP_REMOVE)
+ LOG_AND_RETURN("invalid %s operation", "ATTR");
+
+ if (op < OP_MATCH_MAX)
+ rule_add_key(&rule_tmp, TK_M_SYSCTL, op, value, attr);
+ else
+ rule_add_key(&rule_tmp, TK_A_SYSCTL, op, value, attr);
+
+ } else if (startswith(key, "SECLABEL{")) {
+ attr = get_key_attribute(rules->udev, key + strlen("SECLABEL"));
+ if (attr == NULL)
+ LOG_AND_RETURN("error parsing %s attribute", "SECLABEL");
+
+ if (op == OP_REMOVE)
+ LOG_AND_RETURN("invalid %s operation", "SECLABEL");
+
+ rule_add_key(&rule_tmp, TK_A_SECLABEL, op, value, attr);
+
+ } else if (streq(key, "KERNELS")) {
+ if (op > OP_MATCH_MAX)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ rule_add_key(&rule_tmp, TK_M_KERNELS, op, value, NULL);
+
+ } else if (streq(key, "SUBSYSTEMS")) {
+ if (op > OP_MATCH_MAX)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ rule_add_key(&rule_tmp, TK_M_SUBSYSTEMS, op, value, NULL);
+
+ } else if (streq(key, "DRIVERS")) {
+ if (op > OP_MATCH_MAX)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ rule_add_key(&rule_tmp, TK_M_DRIVERS, op, value, NULL);
+
+ } else if (startswith(key, "ATTRS{")) {
+ if (op > OP_MATCH_MAX)
+ LOG_AND_RETURN("invalid %s operation", "ATTRS");
+
+ attr = get_key_attribute(rules->udev, key + strlen("ATTRS"));
+ if (attr == NULL)
+ LOG_AND_RETURN("error parsing %s attribute", "ATTRS");
+
+ if (startswith(attr, "device/"))
+ LOG_RULE_WARNING("'device' link may not be available in future kernels; please fix");
+ if (strstr(attr, "../") != NULL)
+ LOG_RULE_WARNING("direct reference to parent sysfs directory, may break in future kernels; please fix");
+ rule_add_key(&rule_tmp, TK_M_ATTRS, op, value, attr);
+
+ } else if (streq(key, "TAGS")) {
+ if (op > OP_MATCH_MAX)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ rule_add_key(&rule_tmp, TK_M_TAGS, op, value, NULL);
+
+ } else if (startswith(key, "ENV{")) {
+ attr = get_key_attribute(rules->udev, key + strlen("ENV"));
+ if (attr == NULL)
+ LOG_AND_RETURN("error parsing %s attribute", "ENV");
+
+ if (op == OP_REMOVE)
+ LOG_AND_RETURN("invalid %s operation", "ENV");
+
+ if (op < OP_MATCH_MAX)
+ rule_add_key(&rule_tmp, TK_M_ENV, op, value, attr);
+ else {
+ if (STR_IN_SET(attr,
+ "ACTION",
+ "SUBSYSTEM",
+ "DEVTYPE",
+ "MAJOR",
+ "MINOR",
+ "DRIVER",
+ "IFINDEX",
+ "DEVNAME",
+ "DEVLINKS",
+ "DEVPATH",
+ "TAGS"))
+ LOG_AND_RETURN("invalid ENV attribute, '%s' cannot be set", attr);
+
+ rule_add_key(&rule_tmp, TK_A_ENV, op, value, attr);
+ }
+
+ } else if (streq(key, "TAG")) {
+ if (op < OP_MATCH_MAX)
+ rule_add_key(&rule_tmp, TK_M_TAG, op, value, NULL);
+ else
+ rule_add_key(&rule_tmp, TK_A_TAG, op, value, NULL);
+
+ } else if (streq(key, "PROGRAM")) {
+ if (op == OP_REMOVE)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ rule_add_key(&rule_tmp, TK_M_PROGRAM, op, value, NULL);
+
+ } else if (streq(key, "RESULT")) {
+ if (op > OP_MATCH_MAX)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ rule_add_key(&rule_tmp, TK_M_RESULT, op, value, NULL);
+
+ } else if (startswith(key, "IMPORT")) {
+ attr = get_key_attribute(rules->udev, key + strlen("IMPORT"));
+ if (attr == NULL) {
+ LOG_RULE_WARNING("ignoring IMPORT{} with missing type");
+ continue;
+ }
+ if (op == OP_REMOVE)
+ LOG_AND_RETURN("invalid %s operation", "IMPORT");
+
+ if (streq(attr, "program")) {
+ /* find known built-in command */
+ if (value[0] != '/') {
+ const enum udev_builtin_cmd cmd = udev_builtin_lookup(value);
+
+ if (cmd < UDEV_BUILTIN_MAX) {
+ LOG_RULE_DEBUG("IMPORT found builtin '%s', replacing", value);
+ rule_add_key(&rule_tmp, TK_M_IMPORT_BUILTIN, op, value, &cmd);
+ continue;
+ }
+ }
+ rule_add_key(&rule_tmp, TK_M_IMPORT_PROG, op, value, NULL);
+ } else if (streq(attr, "builtin")) {
+ const enum udev_builtin_cmd cmd = udev_builtin_lookup(value);
+
+ if (cmd >= UDEV_BUILTIN_MAX)
+ LOG_RULE_WARNING("IMPORT{builtin} '%s' unknown", value);
+ else
+ rule_add_key(&rule_tmp, TK_M_IMPORT_BUILTIN, op, value, &cmd);
+ } else if (streq(attr, "file"))
+ rule_add_key(&rule_tmp, TK_M_IMPORT_FILE, op, value, NULL);
+ else if (streq(attr, "db"))
+ rule_add_key(&rule_tmp, TK_M_IMPORT_DB, op, value, NULL);
+ else if (streq(attr, "cmdline"))
+ rule_add_key(&rule_tmp, TK_M_IMPORT_CMDLINE, op, value, NULL);
+ else if (streq(attr, "parent"))
+ rule_add_key(&rule_tmp, TK_M_IMPORT_PARENT, op, value, NULL);
+ else
+ LOG_RULE_ERROR("ignoring unknown %s{} type '%s'", "IMPORT", attr);
+
+ } else if (startswith(key, "TEST")) {
+ mode_t mode = 0;
+
+ if (op > OP_MATCH_MAX)
+ LOG_AND_RETURN("invalid %s operation", "TEST");
+
+ attr = get_key_attribute(rules->udev, key + strlen("TEST"));
+ if (attr != NULL) {
+ mode = strtol(attr, NULL, 8);
+ rule_add_key(&rule_tmp, TK_M_TEST, op, value, &mode);
+ } else
+ rule_add_key(&rule_tmp, TK_M_TEST, op, value, NULL);
+
+ } else if (startswith(key, "RUN")) {
+ attr = get_key_attribute(rules->udev, key + strlen("RUN"));
+ if (attr == NULL)
+ attr = "program";
+ if (op == OP_REMOVE)
+ LOG_AND_RETURN("invalid %s operation", "RUN");
+
+ if (streq(attr, "builtin")) {
+ const enum udev_builtin_cmd cmd = udev_builtin_lookup(value);
+
+ if (cmd < UDEV_BUILTIN_MAX)
+ rule_add_key(&rule_tmp, TK_A_RUN_BUILTIN, op, value, &cmd);
+ else
+ LOG_RULE_ERROR("RUN{builtin}: '%s' unknown", value);
+ } else if (streq(attr, "program")) {
+ const enum udev_builtin_cmd cmd = UDEV_BUILTIN_MAX;
+
+ rule_add_key(&rule_tmp, TK_A_RUN_PROGRAM, op, value, &cmd);
+ } else
+ LOG_RULE_ERROR("ignoring unknown %s{} type '%s'", "RUN", attr);
+
+ } else if (streq(key, "LABEL")) {
+ if (op == OP_REMOVE)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ rule_tmp.rule.rule.label_off = rules_add_string(rules, value);
+
+ } else if (streq(key, "GOTO")) {
+ if (op == OP_REMOVE)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ rule_add_key(&rule_tmp, TK_A_GOTO, 0, value, NULL);
+
+ } else if (startswith(key, "NAME")) {
+ if (op == OP_REMOVE)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ if (op < OP_MATCH_MAX)
+ rule_add_key(&rule_tmp, TK_M_NAME, op, value, NULL);
+ else {
+ if (streq(value, "%k")) {
+ LOG_RULE_WARNING("NAME=\"%%k\" is ignored, because it breaks kernel supplied names; please remove");
+ continue;
+ }
+ if (isempty(value)) {
+ LOG_RULE_DEBUG("NAME=\"\" is ignored, because udev will not delete any device nodes; please remove");
+ continue;
+ }
+ rule_add_key(&rule_tmp, TK_A_NAME, op, value, NULL);
+ }
+ rule_tmp.rule.rule.can_set_name = true;
+
+ } else if (streq(key, "SYMLINK")) {
+ if (op == OP_REMOVE)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ if (op < OP_MATCH_MAX)
+ rule_add_key(&rule_tmp, TK_M_DEVLINK, op, value, NULL);
+ else
+ rule_add_key(&rule_tmp, TK_A_DEVLINK, op, value, NULL);
+ rule_tmp.rule.rule.can_set_name = true;
+
+ } else if (streq(key, "OWNER")) {
+ uid_t uid;
+ char *endptr;
+
+ if (op == OP_REMOVE)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ uid = strtoul(value, &endptr, 10);
+ if (endptr[0] == '\0')
+ rule_add_key(&rule_tmp, TK_A_OWNER_ID, op, NULL, &uid);
+ else if (rules->resolve_names > 0 && strchr("$%", value[0]) == NULL) {
+ uid = add_uid(rules, value);
+ rule_add_key(&rule_tmp, TK_A_OWNER_ID, op, NULL, &uid);
+ } else if (rules->resolve_names >= 0)
+ rule_add_key(&rule_tmp, TK_A_OWNER, op, value, NULL);
+
+ rule_tmp.rule.rule.can_set_name = true;
+
+ } else if (streq(key, "GROUP")) {
+ gid_t gid;
+ char *endptr;
+
+ if (op == OP_REMOVE)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ gid = strtoul(value, &endptr, 10);
+ if (endptr[0] == '\0')
+ rule_add_key(&rule_tmp, TK_A_GROUP_ID, op, NULL, &gid);
+ else if ((rules->resolve_names > 0) && strchr("$%", value[0]) == NULL) {
+ gid = add_gid(rules, value);
+ rule_add_key(&rule_tmp, TK_A_GROUP_ID, op, NULL, &gid);
+ } else if (rules->resolve_names >= 0)
+ rule_add_key(&rule_tmp, TK_A_GROUP, op, value, NULL);
+
+ rule_tmp.rule.rule.can_set_name = true;
+
+ } else if (streq(key, "MODE")) {
+ mode_t mode;
+ char *endptr;
+
+ if (op == OP_REMOVE)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ mode = strtol(value, &endptr, 8);
+ if (endptr[0] == '\0')
+ rule_add_key(&rule_tmp, TK_A_MODE_ID, op, NULL, &mode);
+ else
+ rule_add_key(&rule_tmp, TK_A_MODE, op, value, NULL);
+ rule_tmp.rule.rule.can_set_name = true;
+
+ } else if (streq(key, "OPTIONS")) {
+ const char *pos;
+
+ if (op == OP_REMOVE)
+ LOG_AND_RETURN("invalid %s operation", key);
+
+ pos = strstr(value, "link_priority=");
+ if (pos != NULL) {
+ int prio = atoi(pos + strlen("link_priority="));
+
+ rule_add_key(&rule_tmp, TK_A_DEVLINK_PRIO, op, NULL, &prio);
+ }
+
+ pos = strstr(value, "string_escape=");
+ if (pos != NULL) {
+ pos += strlen("string_escape=");
+ if (startswith(pos, "none"))
+ rule_add_key(&rule_tmp, TK_A_STRING_ESCAPE_NONE, op, NULL, NULL);
+ else if (startswith(pos, "replace"))
+ rule_add_key(&rule_tmp, TK_A_STRING_ESCAPE_REPLACE, op, NULL, NULL);
+ }
+
+ pos = strstr(value, "db_persist");
+ if (pos != NULL)
+ rule_add_key(&rule_tmp, TK_A_DB_PERSIST, op, NULL, NULL);
+
+ pos = strstr(value, "nowatch");
+ if (pos != NULL) {
+ const int off = 0;
+
+ rule_add_key(&rule_tmp, TK_A_INOTIFY_WATCH, op, NULL, &off);
+ } else {
+ pos = strstr(value, "watch");
+ if (pos != NULL) {
+ const int on = 1;
+
+ rule_add_key(&rule_tmp, TK_A_INOTIFY_WATCH, op, NULL, &on);
+ }
+ }
+
+ pos = strstr(value, "static_node=");
+ if (pos != NULL) {
+ pos += strlen("static_node=");
+ rule_add_key(&rule_tmp, TK_A_STATIC_NODE, op, pos, NULL);
+ rule_tmp.rule.rule.has_static_node = true;
+ }
+
+ } else
+ LOG_AND_RETURN("unknown key '%s'", key);
+ }
+
+ /* add rule token and sort tokens */
+ rule_tmp.rule.rule.token_count = 1 + rule_tmp.token_cur;
+ if (add_token(rules, &rule_tmp.rule) != 0 || sort_token(rules, &rule_tmp) != 0)
+ LOG_RULE_ERROR("failed to add rule token");
+}
+
+static int parse_file(struct udev_rules *rules, const char *filename) {
+ _cleanup_fclose_ FILE *f = NULL;
+ unsigned int first_token;
+ unsigned int filename_off;
+ char line[UTIL_LINE_SIZE];
+ int line_nr = 0;
+ unsigned int i;
+
+ f = fopen(filename, "re");
+ if (!f) {
+ if (errno == ENOENT)
+ return 0;
+ else
+ return -errno;
+ }
+
+ if (null_or_empty_fd(fileno(f))) {
+ log_debug("Skipping empty file: %s", filename);
+ return 0;
+ } else
+ log_debug("Reading rules file: %s", filename);
+
+ first_token = rules->token_cur;
+ filename_off = rules_add_string(rules, filename);
+
+ while (fgets(line, sizeof(line), f) != NULL) {
+ char *key;
+ size_t len;
+
+ /* skip whitespace */
+ line_nr++;
+ key = line;
+ while (isspace(key[0]))
+ key++;
+
+ /* comment */
+ if (key[0] == '#')
+ continue;
+
+ len = strlen(line);
+ if (len < 3)
+ continue;
+
+ /* continue reading if backslash+newline is found */
+ while (line[len-2] == '\\') {
+ if (fgets(&line[len-2], (sizeof(line)-len)+2, f) == NULL)
+ break;
+ if (strlen(&line[len-2]) < 2)
+ break;
+ line_nr++;
+ len = strlen(line);
+ }
+
+ if (len+1 >= sizeof(line)) {
+ log_error("line too long '%s':%u, ignored", filename, line_nr);
+ continue;
+ }
+ add_rule(rules, key, filename, filename_off, line_nr);
+ }
+
+ /* link GOTOs to LABEL rules in this file to be able to fast-forward */
+ for (i = first_token+1; i < rules->token_cur; i++) {
+ if (rules->tokens[i].type == TK_A_GOTO) {
+ char *label = rules_str(rules, rules->tokens[i].key.value_off);
+ unsigned int j;
+
+ for (j = i+1; j < rules->token_cur; j++) {
+ if (rules->tokens[j].type != TK_RULE)
+ continue;
+ if (rules->tokens[j].rule.label_off == 0)
+ continue;
+ if (!streq(label, rules_str(rules, rules->tokens[j].rule.label_off)))
+ continue;
+ rules->tokens[i].key.rule_goto = j;
+ break;
+ }
+ if (rules->tokens[i].key.rule_goto == 0)
+ log_error("GOTO '%s' has no matching label in: '%s'", label, filename);
+ }
+ }
+ return 0;
+}
+
+struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names) {
+ struct udev_rules *rules;
+ struct udev_list file_list;
+ struct token end_token;
+ char **files, **f;
+ int r;
+
+ rules = new0(struct udev_rules, 1);
+ if (rules == NULL)
+ return NULL;
+ rules->udev = udev;
+ rules->resolve_names = resolve_names;
+ udev_list_init(udev, &file_list, true);
+
+ /* init token array and string buffer */
+ rules->tokens = malloc(PREALLOC_TOKEN * sizeof(struct token));
+ if (rules->tokens == NULL)
+ return udev_rules_unref(rules);
+ rules->token_max = PREALLOC_TOKEN;
+
+ rules->strbuf = strbuf_new();
+ if (!rules->strbuf)
+ return udev_rules_unref(rules);
+
+ udev_rules_check_timestamp(rules);
+
+ r = conf_files_list_strv(&files, ".rules", NULL, rules_dirs);
+ if (r < 0) {
+ log_error_errno(r, "failed to enumerate rules files: %m");
+ return udev_rules_unref(rules);
+ }
+
+ /*
+ * The offset value in the rules strct is limited; add all
+ * rules file names to the beginning of the string buffer.
+ */
+ STRV_FOREACH(f, files)
+ rules_add_string(rules, *f);
+
+ STRV_FOREACH(f, files)
+ parse_file(rules, *f);
+
+ strv_free(files);
+
+ memzero(&end_token, sizeof(struct token));
+ end_token.type = TK_END;
+ add_token(rules, &end_token);
+ log_debug("rules contain %zu bytes tokens (%u * %zu bytes), %zu bytes strings",
+ rules->token_max * sizeof(struct token), rules->token_max, sizeof(struct token), rules->strbuf->len);
+
+ /* cleanup temporary strbuf data */
+ log_debug("%zu strings (%zu bytes), %zu de-duplicated (%zu bytes), %zu trie nodes used",
+ rules->strbuf->in_count, rules->strbuf->in_len,
+ rules->strbuf->dedup_count, rules->strbuf->dedup_len, rules->strbuf->nodes_count);
+ strbuf_complete(rules->strbuf);
+
+ /* cleanup uid/gid cache */
+ rules->uids = mfree(rules->uids);
+ rules->uids_cur = 0;
+ rules->uids_max = 0;
+ rules->gids = mfree(rules->gids);
+ rules->gids_cur = 0;
+ rules->gids_max = 0;
+
+ dump_rules(rules);
+ return rules;
+}
+
+struct udev_rules *udev_rules_unref(struct udev_rules *rules) {
+ if (rules == NULL)
+ return NULL;
+ free(rules->tokens);
+ strbuf_cleanup(rules->strbuf);
+ free(rules->uids);
+ free(rules->gids);
+ return mfree(rules);
+}
+
+bool udev_rules_check_timestamp(struct udev_rules *rules) {
+ if (!rules)
+ return false;
+
+ return paths_check_timestamp(rules_dirs, &rules->dirs_ts_usec, true);
+}
+
+static int match_key(struct udev_rules *rules, struct token *token, const char *val) {
+ char *key_value = rules_str(rules, token->key.value_off);
+ char *pos;
+ bool match = false;
+
+ if (val == NULL)
+ val = "";
+
+ switch (token->key.glob) {
+ case GL_PLAIN:
+ match = (streq(key_value, val));
+ break;
+ case GL_GLOB:
+ match = (fnmatch(key_value, val, 0) == 0);
+ break;
+ case GL_SPLIT:
+ {
+ const char *s;
+ size_t len;
+
+ s = rules_str(rules, token->key.value_off);
+ len = strlen(val);
+ for (;;) {
+ const char *next;
+
+ next = strchr(s, '|');
+ if (next != NULL) {
+ size_t matchlen = (size_t)(next - s);
+
+ match = (matchlen == len && strneq(s, val, matchlen));
+ if (match)
+ break;
+ } else {
+ match = (streq(s, val));
+ break;
+ }
+ s = &next[1];
+ }
+ break;
+ }
+ case GL_SPLIT_GLOB:
+ {
+ char value[UTIL_PATH_SIZE];
+
+ strscpy(value, sizeof(value), rules_str(rules, token->key.value_off));
+ key_value = value;
+ while (key_value != NULL) {
+ pos = strchr(key_value, '|');
+ if (pos != NULL) {
+ pos[0] = '\0';
+ pos = &pos[1];
+ }
+ match = (fnmatch(key_value, val, 0) == 0);
+ if (match)
+ break;
+ key_value = pos;
+ }
+ break;
+ }
+ case GL_SOMETHING:
+ match = (val[0] != '\0');
+ break;
+ case GL_UNSET:
+ return -1;
+ }
+
+ if (match && (token->key.op == OP_MATCH))
+ return 0;
+ if (!match && (token->key.op == OP_NOMATCH))
+ return 0;
+ return -1;
+}
+
+static int match_attr(struct udev_rules *rules, struct udev_device *dev, struct udev_event *event, struct token *cur) {
+ const char *name;
+ char nbuf[UTIL_NAME_SIZE];
+ const char *value;
+ char vbuf[UTIL_NAME_SIZE];
+ size_t len;
+
+ name = rules_str(rules, cur->key.attr_off);
+ switch (cur->key.attrsubst) {
+ case SB_FORMAT:
+ udev_event_apply_format(event, name, nbuf, sizeof(nbuf));
+ name = nbuf;
+ /* fall through */
+ case SB_NONE:
+ value = udev_device_get_sysattr_value(dev, name);
+ if (value == NULL)
+ return -1;
+ break;
+ case SB_SUBSYS:
+ if (util_resolve_subsys_kernel(event->udev, name, vbuf, sizeof(vbuf), 1) != 0)
+ return -1;
+ value = vbuf;
+ break;
+ default:
+ return -1;
+ }
+
+ /* remove trailing whitespace, if not asked to match for it */
+ len = strlen(value);
+ if (len > 0 && isspace(value[len-1])) {
+ const char *key_value;
+ size_t klen;
+
+ key_value = rules_str(rules, cur->key.value_off);
+ klen = strlen(key_value);
+ if (klen > 0 && !isspace(key_value[klen-1])) {
+ if (value != vbuf) {
+ strscpy(vbuf, sizeof(vbuf), value);
+ value = vbuf;
+ }
+ while (len > 0 && isspace(vbuf[--len]))
+ vbuf[len] = '\0';
+ }
+ }
+
+ return match_key(rules, cur, value);
+}
+
+enum escape_type {
+ ESCAPE_UNSET,
+ ESCAPE_NONE,
+ ESCAPE_REPLACE,
+};
+
+void udev_rules_apply_to_event(struct udev_rules *rules,
+ struct udev_event *event,
+ usec_t timeout_usec,
+ usec_t timeout_warn_usec,
+ struct udev_list *properties_list) {
+ struct token *cur;
+ struct token *rule;
+ enum escape_type esc = ESCAPE_UNSET;
+ bool can_set_name;
+
+ if (rules->tokens == NULL)
+ return;
+
+ can_set_name = ((!streq(udev_device_get_action(event->dev), "remove")) &&
+ (major(udev_device_get_devnum(event->dev)) > 0 ||
+ udev_device_get_ifindex(event->dev) > 0));
+
+ /* loop through token list, match, run actions or forward to next rule */
+ cur = &rules->tokens[0];
+ rule = cur;
+ for (;;) {
+ dump_token(rules, cur);
+ switch (cur->type) {
+ case TK_RULE:
+ /* current rule */
+ rule = cur;
+ /* possibly skip rules which want to set NAME, SYMLINK, OWNER, GROUP, MODE */
+ if (!can_set_name && rule->rule.can_set_name)
+ goto nomatch;
+ esc = ESCAPE_UNSET;
+ break;
+ case TK_M_ACTION:
+ if (match_key(rules, cur, udev_device_get_action(event->dev)) != 0)
+ goto nomatch;
+ break;
+ case TK_M_DEVPATH:
+ if (match_key(rules, cur, udev_device_get_devpath(event->dev)) != 0)
+ goto nomatch;
+ break;
+ case TK_M_KERNEL:
+ if (match_key(rules, cur, udev_device_get_sysname(event->dev)) != 0)
+ goto nomatch;
+ break;
+ case TK_M_DEVLINK: {
+ struct udev_list_entry *list_entry;
+ bool match = false;
+
+ udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(event->dev)) {
+ const char *devlink;
+
+ devlink = udev_list_entry_get_name(list_entry) + strlen("/dev/");
+ if (match_key(rules, cur, devlink) == 0) {
+ match = true;
+ break;
+ }
+ }
+ if (!match)
+ goto nomatch;
+ break;
+ }
+ case TK_M_NAME:
+ if (match_key(rules, cur, event->name) != 0)
+ goto nomatch;
+ break;
+ case TK_M_ENV: {
+ const char *key_name = rules_str(rules, cur->key.attr_off);
+ const char *value;
+
+ value = udev_device_get_property_value(event->dev, key_name);
+
+ /* check global properties */
+ if (!value && properties_list) {
+ struct udev_list_entry *list_entry;
+
+ list_entry = udev_list_get_entry(properties_list);
+ list_entry = udev_list_entry_get_by_name(list_entry, key_name);
+ if (list_entry != NULL)
+ value = udev_list_entry_get_value(list_entry);
+ }
+
+ if (!value)
+ value = "";
+ if (match_key(rules, cur, value))
+ goto nomatch;
+ break;
+ }
+ case TK_M_TAG: {
+ struct udev_list_entry *list_entry;
+ bool match = false;
+
+ udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(event->dev)) {
+ if (streq(rules_str(rules, cur->key.value_off), udev_list_entry_get_name(list_entry))) {
+ match = true;
+ break;
+ }
+ }
+ if ((!match && (cur->key.op != OP_NOMATCH)) ||
+ (match && (cur->key.op == OP_NOMATCH)))
+ goto nomatch;
+ break;
+ }
+ case TK_M_SUBSYSTEM:
+ if (match_key(rules, cur, udev_device_get_subsystem(event->dev)) != 0)
+ goto nomatch;
+ break;
+ case TK_M_DRIVER:
+ if (match_key(rules, cur, udev_device_get_driver(event->dev)) != 0)
+ goto nomatch;
+ break;
+ case TK_M_ATTR:
+ if (match_attr(rules, event->dev, event, cur) != 0)
+ goto nomatch;
+ break;
+ case TK_M_SYSCTL: {
+ char filename[UTIL_PATH_SIZE];
+ _cleanup_free_ char *value = NULL;
+ size_t len;
+
+ udev_event_apply_format(event, rules_str(rules, cur->key.attr_off), filename, sizeof(filename));
+ sysctl_normalize(filename);
+ if (sysctl_read(filename, &value) < 0)
+ goto nomatch;
+
+ len = strlen(value);
+ while (len > 0 && isspace(value[--len]))
+ value[len] = '\0';
+ if (match_key(rules, cur, value) != 0)
+ goto nomatch;
+ break;
+ }
+ case TK_M_KERNELS:
+ case TK_M_SUBSYSTEMS:
+ case TK_M_DRIVERS:
+ case TK_M_ATTRS:
+ case TK_M_TAGS: {
+ struct token *next;
+
+ /* get whole sequence of parent matches */
+ next = cur;
+ while (next->type > TK_M_PARENTS_MIN && next->type < TK_M_PARENTS_MAX)
+ next++;
+
+ /* loop over parents */
+ event->dev_parent = event->dev;
+ for (;;) {
+ struct token *key;
+
+ /* loop over sequence of parent match keys */
+ for (key = cur; key < next; key++ ) {
+ dump_token(rules, key);
+ switch(key->type) {
+ case TK_M_KERNELS:
+ if (match_key(rules, key, udev_device_get_sysname(event->dev_parent)) != 0)
+ goto try_parent;
+ break;
+ case TK_M_SUBSYSTEMS:
+ if (match_key(rules, key, udev_device_get_subsystem(event->dev_parent)) != 0)
+ goto try_parent;
+ break;
+ case TK_M_DRIVERS:
+ if (match_key(rules, key, udev_device_get_driver(event->dev_parent)) != 0)
+ goto try_parent;
+ break;
+ case TK_M_ATTRS:
+ if (match_attr(rules, event->dev_parent, event, key) != 0)
+ goto try_parent;
+ break;
+ case TK_M_TAGS: {
+ bool match = udev_device_has_tag(event->dev_parent, rules_str(rules, cur->key.value_off));
+
+ if (match && key->key.op == OP_NOMATCH)
+ goto try_parent;
+ if (!match && key->key.op == OP_MATCH)
+ goto try_parent;
+ break;
+ }
+ default:
+ goto nomatch;
+ }
+ }
+ break;
+
+ try_parent:
+ event->dev_parent = udev_device_get_parent(event->dev_parent);
+ if (event->dev_parent == NULL)
+ goto nomatch;
+ }
+ /* move behind our sequence of parent match keys */
+ cur = next;
+ continue;
+ }
+ case TK_M_TEST: {
+ char filename[UTIL_PATH_SIZE];
+ struct stat statbuf;
+ int match;
+
+ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), filename, sizeof(filename));
+ if (util_resolve_subsys_kernel(event->udev, filename, filename, sizeof(filename), 0) != 0) {
+ if (filename[0] != '/') {
+ char tmp[UTIL_PATH_SIZE];
+
+ strscpy(tmp, sizeof(tmp), filename);
+ strscpyl(filename, sizeof(filename),
+ udev_device_get_syspath(event->dev), "/", tmp, NULL);
+ }
+ }
+ attr_subst_subdir(filename, sizeof(filename));
+
+ match = (stat(filename, &statbuf) == 0);
+ if (match && cur->key.mode > 0)
+ match = ((statbuf.st_mode & cur->key.mode) > 0);
+ if (match && cur->key.op == OP_NOMATCH)
+ goto nomatch;
+ if (!match && cur->key.op == OP_MATCH)
+ goto nomatch;
+ break;
+ }
+ case TK_M_PROGRAM: {
+ char program[UTIL_PATH_SIZE];
+ char result[UTIL_LINE_SIZE];
+
+ event->program_result = mfree(event->program_result);
+ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), program, sizeof(program));
+ log_debug("PROGRAM '%s' %s:%u",
+ program,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
+
+ if (udev_event_spawn(event, timeout_usec, timeout_warn_usec, true, program, result, sizeof(result)) < 0) {
+ if (cur->key.op != OP_NOMATCH)
+ goto nomatch;
+ } else {
+ int count;
+
+ util_remove_trailing_chars(result, '\n');
+ if (esc == ESCAPE_UNSET || esc == ESCAPE_REPLACE) {
+ count = util_replace_chars(result, UDEV_ALLOWED_CHARS_INPUT);
+ if (count > 0)
+ log_debug("%i character(s) replaced" , count);
+ }
+ event->program_result = strdup(result);
+ if (cur->key.op == OP_NOMATCH)
+ goto nomatch;
+ }
+ break;
+ }
+ case TK_M_IMPORT_FILE: {
+ char import[UTIL_PATH_SIZE];
+
+ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), import, sizeof(import));
+ if (import_file_into_properties(event->dev, import) != 0)
+ if (cur->key.op != OP_NOMATCH)
+ goto nomatch;
+ break;
+ }
+ case TK_M_IMPORT_PROG: {
+ char import[UTIL_PATH_SIZE];
+
+ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), import, sizeof(import));
+ log_debug("IMPORT '%s' %s:%u",
+ import,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
+
+ if (import_program_into_properties(event, timeout_usec, timeout_warn_usec, import) != 0)
+ if (cur->key.op != OP_NOMATCH)
+ goto nomatch;
+ break;
+ }
+ case TK_M_IMPORT_BUILTIN: {
+ char command[UTIL_PATH_SIZE];
+
+ if (udev_builtin_run_once(cur->key.builtin_cmd)) {
+ /* check if we ran already */
+ if (event->builtin_run & (1 << cur->key.builtin_cmd)) {
+ log_debug("IMPORT builtin skip '%s' %s:%u",
+ udev_builtin_name(cur->key.builtin_cmd),
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
+ /* return the result from earlier run */
+ if (event->builtin_ret & (1 << cur->key.builtin_cmd))
+ if (cur->key.op != OP_NOMATCH)
+ goto nomatch;
+ break;
+ }
+ /* mark as ran */
+ event->builtin_run |= (1 << cur->key.builtin_cmd);
+ }
+
+ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), command, sizeof(command));
+ log_debug("IMPORT builtin '%s' %s:%u",
+ udev_builtin_name(cur->key.builtin_cmd),
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
+
+ if (udev_builtin_run(event->dev, cur->key.builtin_cmd, command, false) != 0) {
+ /* remember failure */
+ log_debug("IMPORT builtin '%s' returned non-zero",
+ udev_builtin_name(cur->key.builtin_cmd));
+ event->builtin_ret |= (1 << cur->key.builtin_cmd);
+ if (cur->key.op != OP_NOMATCH)
+ goto nomatch;
+ }
+ break;
+ }
+ case TK_M_IMPORT_DB: {
+ const char *key = rules_str(rules, cur->key.value_off);
+ const char *value;
+
+ value = udev_device_get_property_value(event->dev_db, key);
+ if (value != NULL)
+ udev_device_add_property(event->dev, key, value);
+ else {
+ if (cur->key.op != OP_NOMATCH)
+ goto nomatch;
+ }
+ break;
+ }
+ case TK_M_IMPORT_CMDLINE: {
+ _cleanup_fclose_ FILE *f = NULL;
+ bool imported = false;
+
+ f = fopen("/proc/cmdline", "re");
+ if (f != NULL) {
+ char cmdline[4096];
+
+ if (fgets(cmdline, sizeof(cmdline), f) != NULL) {
+ const char *key = rules_str(rules, cur->key.value_off);
+ char *pos;
+
+ pos = strstr(cmdline, key);
+ if (pos != NULL) {
+ imported = true;
+ pos += strlen(key);
+ if (pos[0] == '\0' || isspace(pos[0]))
+ /* we import simple flags as 'FLAG=1' */
+ udev_device_add_property(event->dev, key, "1");
+ else if (pos[0] == '=') {
+ const char *value;
+
+ pos++;
+ value = pos;
+ while (pos[0] != '\0' && !isspace(pos[0]))
+ pos++;
+ pos[0] = '\0';
+ udev_device_add_property(event->dev, key, value);
+ }
+ }
+ }
+ }
+ if (!imported && cur->key.op != OP_NOMATCH)
+ goto nomatch;
+ break;
+ }
+ case TK_M_IMPORT_PARENT: {
+ char import[UTIL_PATH_SIZE];
+
+ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), import, sizeof(import));
+ if (import_parent_into_properties(event->dev, import) != 0)
+ if (cur->key.op != OP_NOMATCH)
+ goto nomatch;
+ break;
+ }
+ case TK_M_RESULT:
+ if (match_key(rules, cur, event->program_result) != 0)
+ goto nomatch;
+ break;
+ case TK_A_STRING_ESCAPE_NONE:
+ esc = ESCAPE_NONE;
+ break;
+ case TK_A_STRING_ESCAPE_REPLACE:
+ esc = ESCAPE_REPLACE;
+ break;
+ case TK_A_DB_PERSIST:
+ udev_device_set_db_persist(event->dev);
+ break;
+ case TK_A_INOTIFY_WATCH:
+ if (event->inotify_watch_final)
+ break;
+ if (cur->key.op == OP_ASSIGN_FINAL)
+ event->inotify_watch_final = true;
+ event->inotify_watch = cur->key.watch;
+ break;
+ case TK_A_DEVLINK_PRIO:
+ udev_device_set_devlink_priority(event->dev, cur->key.devlink_prio);
+ break;
+ case TK_A_OWNER: {
+ char owner[UTIL_NAME_SIZE];
+ const char *ow = owner;
+ int r;
+
+ if (event->owner_final)
+ break;
+ if (cur->key.op == OP_ASSIGN_FINAL)
+ event->owner_final = true;
+ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), owner, sizeof(owner));
+ event->owner_set = true;
+ r = get_user_creds(&ow, &event->uid, NULL, NULL, NULL);
+ if (r < 0) {
+ if (r == -ENOENT || r == -ESRCH)
+ log_error("specified user '%s' unknown", owner);
+ else
+ log_error_errno(r, "error resolving user '%s': %m", owner);
+
+ event->uid = 0;
+ }
+ log_debug("OWNER %u %s:%u",
+ event->uid,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
+ break;
+ }
+ case TK_A_GROUP: {
+ char group[UTIL_NAME_SIZE];
+ const char *gr = group;
+ int r;
+
+ if (event->group_final)
+ break;
+ if (cur->key.op == OP_ASSIGN_FINAL)
+ event->group_final = true;
+ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), group, sizeof(group));
+ event->group_set = true;
+ r = get_group_creds(&gr, &event->gid);
+ if (r < 0) {
+ if (r == -ENOENT || r == -ESRCH)
+ log_error("specified group '%s' unknown", group);
+ else
+ log_error_errno(r, "error resolving group '%s': %m", group);
+
+ event->gid = 0;
+ }
+ log_debug("GROUP %u %s:%u",
+ event->gid,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
+ break;
+ }
+ case TK_A_MODE: {
+ char mode_str[UTIL_NAME_SIZE];
+ mode_t mode;
+ char *endptr;
+
+ if (event->mode_final)
+ break;
+ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), mode_str, sizeof(mode_str));
+ mode = strtol(mode_str, &endptr, 8);
+ if (endptr[0] != '\0') {
+ log_error("ignoring invalid mode '%s'", mode_str);
+ break;
+ }
+ if (cur->key.op == OP_ASSIGN_FINAL)
+ event->mode_final = true;
+ event->mode_set = true;
+ event->mode = mode;
+ log_debug("MODE %#o %s:%u",
+ event->mode,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
+ break;
+ }
+ case TK_A_OWNER_ID:
+ if (event->owner_final)
+ break;
+ if (cur->key.op == OP_ASSIGN_FINAL)
+ event->owner_final = true;
+ event->owner_set = true;
+ event->uid = cur->key.uid;
+ log_debug("OWNER %u %s:%u",
+ event->uid,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
+ break;
+ case TK_A_GROUP_ID:
+ if (event->group_final)
+ break;
+ if (cur->key.op == OP_ASSIGN_FINAL)
+ event->group_final = true;
+ event->group_set = true;
+ event->gid = cur->key.gid;
+ log_debug("GROUP %u %s:%u",
+ event->gid,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
+ break;
+ case TK_A_MODE_ID:
+ if (event->mode_final)
+ break;
+ if (cur->key.op == OP_ASSIGN_FINAL)
+ event->mode_final = true;
+ event->mode_set = true;
+ event->mode = cur->key.mode;
+ log_debug("MODE %#o %s:%u",
+ event->mode,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
+ break;
+ case TK_A_SECLABEL: {
+ char label_str[UTIL_LINE_SIZE] = {};
+ const char *name, *label;
+
+ name = rules_str(rules, cur->key.attr_off);
+ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), label_str, sizeof(label_str));
+ if (label_str[0] != '\0')
+ label = label_str;
+ else
+ label = rules_str(rules, cur->key.value_off);
+
+ if (cur->key.op == OP_ASSIGN || cur->key.op == OP_ASSIGN_FINAL)
+ udev_list_cleanup(&event->seclabel_list);
+ udev_list_entry_add(&event->seclabel_list, name, label);
+ log_debug("SECLABEL{%s}='%s' %s:%u",
+ name, label,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
+ break;
+ }
+ case TK_A_ENV: {
+ const char *name = rules_str(rules, cur->key.attr_off);
+ char *value = rules_str(rules, cur->key.value_off);
+ char value_new[UTIL_NAME_SIZE];
+ const char *value_old = NULL;
+
+ if (value[0] == '\0') {
+ if (cur->key.op == OP_ADD)
+ break;
+ udev_device_add_property(event->dev, name, NULL);
+ break;
+ }
+
+ if (cur->key.op == OP_ADD)
+ value_old = udev_device_get_property_value(event->dev, name);
+ if (value_old) {
+ char temp[UTIL_NAME_SIZE];
+
+ /* append value separated by space */
+ udev_event_apply_format(event, value, temp, sizeof(temp));
+ strscpyl(value_new, sizeof(value_new), value_old, " ", temp, NULL);
+ } else
+ udev_event_apply_format(event, value, value_new, sizeof(value_new));
+
+ udev_device_add_property(event->dev, name, value_new);
+ break;
+ }
+ case TK_A_TAG: {
+ char tag[UTIL_PATH_SIZE];
+ const char *p;
+
+ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), tag, sizeof(tag));
+ if (cur->key.op == OP_ASSIGN || cur->key.op == OP_ASSIGN_FINAL)
+ udev_device_cleanup_tags_list(event->dev);
+ for (p = tag; *p != '\0'; p++) {
+ if ((*p >= 'a' && *p <= 'z') ||
+ (*p >= 'A' && *p <= 'Z') ||
+ (*p >= '0' && *p <= '9') ||
+ *p == '-' || *p == '_')
+ continue;
+ log_error("ignoring invalid tag name '%s'", tag);
+ break;
+ }
+ if (cur->key.op == OP_REMOVE)
+ udev_device_remove_tag(event->dev, tag);
+ else
+ udev_device_add_tag(event->dev, tag);
+ break;
+ }
+ case TK_A_NAME: {
+ const char *name = rules_str(rules, cur->key.value_off);
+
+ char name_str[UTIL_PATH_SIZE];
+ int count;
+
+ if (event->name_final)
+ break;
+ if (cur->key.op == OP_ASSIGN_FINAL)
+ event->name_final = true;
+ udev_event_apply_format(event, name, name_str, sizeof(name_str));
+ if (esc == ESCAPE_UNSET || esc == ESCAPE_REPLACE) {
+ count = util_replace_chars(name_str, "/");
+ if (count > 0)
+ log_debug("%i character(s) replaced", count);
+ }
+ if (major(udev_device_get_devnum(event->dev)) &&
+ !streq(name_str, udev_device_get_devnode(event->dev) + strlen("/dev/"))) {
+ log_error("NAME=\"%s\" ignored, kernel device nodes cannot be renamed; please fix it in %s:%u\n",
+ name,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
+ break;
+ }
+ if (free_and_strdup(&event->name, name_str) < 0) {
+ log_oom();
+ return;
+ }
+ log_debug("NAME '%s' %s:%u",
+ event->name,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
+ break;
+ }
+ case TK_A_DEVLINK: {
+ char temp[UTIL_PATH_SIZE];
+ char filename[UTIL_PATH_SIZE];
+ char *pos, *next;
+ int count = 0;
+
+ if (event->devlink_final)
+ break;
+ if (major(udev_device_get_devnum(event->dev)) == 0)
+ break;
+ if (cur->key.op == OP_ASSIGN_FINAL)
+ event->devlink_final = true;
+ if (cur->key.op == OP_ASSIGN || cur->key.op == OP_ASSIGN_FINAL)
+ udev_device_cleanup_devlinks_list(event->dev);
+
+ /* allow multiple symlinks separated by spaces */
+ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), temp, sizeof(temp));
+ if (esc == ESCAPE_UNSET)
+ count = util_replace_chars(temp, "/ ");
+ else if (esc == ESCAPE_REPLACE)
+ count = util_replace_chars(temp, "/");
+ if (count > 0)
+ log_debug("%i character(s) replaced" , count);
+ pos = temp;
+ while (isspace(pos[0]))
+ pos++;
+ next = strchr(pos, ' ');
+ while (next != NULL) {
+ next[0] = '\0';
+ log_debug("LINK '%s' %s:%u", pos,
+ rules_str(rules, rule->rule.filename_off), rule->rule.filename_line);
+ strscpyl(filename, sizeof(filename), "/dev/", pos, NULL);
+ udev_device_add_devlink(event->dev, filename);
+ while (isspace(next[1]))
+ next++;
+ pos = &next[1];
+ next = strchr(pos, ' ');
+ }
+ if (pos[0] != '\0') {
+ log_debug("LINK '%s' %s:%u", pos,
+ rules_str(rules, rule->rule.filename_off), rule->rule.filename_line);
+ strscpyl(filename, sizeof(filename), "/dev/", pos, NULL);
+ udev_device_add_devlink(event->dev, filename);
+ }
+ break;
+ }
+ case TK_A_ATTR: {
+ const char *key_name = rules_str(rules, cur->key.attr_off);
+ char attr[UTIL_PATH_SIZE];
+ char value[UTIL_NAME_SIZE];
+ _cleanup_fclose_ FILE *f = NULL;
+
+ if (util_resolve_subsys_kernel(event->udev, key_name, attr, sizeof(attr), 0) != 0)
+ strscpyl(attr, sizeof(attr), udev_device_get_syspath(event->dev), "/", key_name, NULL);
+ attr_subst_subdir(attr, sizeof(attr));
+
+ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), value, sizeof(value));
+ log_debug("ATTR '%s' writing '%s' %s:%u", attr, value,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
+ f = fopen(attr, "we");
+ if (f == NULL)
+ log_error_errno(errno, "error opening ATTR{%s} for writing: %m", attr);
+ else if (fprintf(f, "%s", value) <= 0)
+ log_error_errno(errno, "error writing ATTR{%s}: %m", attr);
+ break;
+ }
+ case TK_A_SYSCTL: {
+ char filename[UTIL_PATH_SIZE];
+ char value[UTIL_NAME_SIZE];
+ int r;
+
+ udev_event_apply_format(event, rules_str(rules, cur->key.attr_off), filename, sizeof(filename));
+ sysctl_normalize(filename);
+ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), value, sizeof(value));
+ log_debug("SYSCTL '%s' writing '%s' %s:%u", filename, value,
+ rules_str(rules, rule->rule.filename_off), rule->rule.filename_line);
+ r = sysctl_write(filename, value);
+ if (r < 0)
+ log_error_errno(r, "error writing SYSCTL{%s}='%s': %m", filename, value);
+ break;
+ }
+ case TK_A_RUN_BUILTIN:
+ case TK_A_RUN_PROGRAM: {
+ struct udev_list_entry *entry;
+
+ if (cur->key.op == OP_ASSIGN || cur->key.op == OP_ASSIGN_FINAL)
+ udev_list_cleanup(&event->run_list);
+ log_debug("RUN '%s' %s:%u",
+ rules_str(rules, cur->key.value_off),
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
+ entry = udev_list_entry_add(&event->run_list, rules_str(rules, cur->key.value_off), NULL);
+ udev_list_entry_set_num(entry, cur->key.builtin_cmd);
+ break;
+ }
+ case TK_A_GOTO:
+ if (cur->key.rule_goto == 0)
+ break;
+ cur = &rules->tokens[cur->key.rule_goto];
+ continue;
+ case TK_END:
+ return;
+
+ case TK_M_PARENTS_MIN:
+ case TK_M_PARENTS_MAX:
+ case TK_M_MAX:
+ case TK_UNSET:
+ log_error("wrong type %u", cur->type);
+ goto nomatch;
+ }
+
+ cur++;
+ continue;
+ nomatch:
+ /* fast-forward to next rule */
+ cur = rule + rule->rule.token_count;
+ }
+}
+
+int udev_rules_apply_static_dev_perms(struct udev_rules *rules) {
+ struct token *cur;
+ struct token *rule;
+ uid_t uid = 0;
+ gid_t gid = 0;
+ mode_t mode = 0;
+ _cleanup_strv_free_ char **tags = NULL;
+ char **t;
+ FILE *f = NULL;
+ _cleanup_free_ char *path = NULL;
+ int r;
+
+ if (rules->tokens == NULL)
+ return 0;
+
+ cur = &rules->tokens[0];
+ rule = cur;
+ for (;;) {
+ switch (cur->type) {
+ case TK_RULE:
+ /* current rule */
+ rule = cur;
+
+ /* skip rules without a static_node tag */
+ if (!rule->rule.has_static_node)
+ goto next;
+
+ uid = 0;
+ gid = 0;
+ mode = 0;
+ tags = strv_free(tags);
+ break;
+ case TK_A_OWNER_ID:
+ uid = cur->key.uid;
+ break;
+ case TK_A_GROUP_ID:
+ gid = cur->key.gid;
+ break;
+ case TK_A_MODE_ID:
+ mode = cur->key.mode;
+ break;
+ case TK_A_TAG:
+ r = strv_extend(&tags, rules_str(rules, cur->key.value_off));
+ if (r < 0)
+ goto finish;
+
+ break;
+ case TK_A_STATIC_NODE: {
+ char device_node[UTIL_PATH_SIZE];
+ char tags_dir[UTIL_PATH_SIZE];
+ char tag_symlink[UTIL_PATH_SIZE];
+ struct stat stats;
+
+ /* we assure, that the permissions tokens are sorted before the static token */
+
+ if (mode == 0 && uid == 0 && gid == 0 && tags == NULL)
+ goto next;
+
+ strscpyl(device_node, sizeof(device_node), "/dev/", rules_str(rules, cur->key.value_off), NULL);
+ if (stat(device_node, &stats) != 0)
+ break;
+ if (!S_ISBLK(stats.st_mode) && !S_ISCHR(stats.st_mode))
+ break;
+
+ /* export the tags to a directory as symlinks, allowing otherwise dead nodes to be tagged */
+ if (tags) {
+ STRV_FOREACH(t, tags) {
+ _cleanup_free_ char *unescaped_filename = NULL;
+
+ strscpyl(tags_dir, sizeof(tags_dir), "/run/udev/static_node-tags/", *t, "/", NULL);
+ r = mkdir_p(tags_dir, 0755);
+ if (r < 0)
+ return log_error_errno(r, "failed to create %s: %m", tags_dir);
+
+ unescaped_filename = xescape(rules_str(rules, cur->key.value_off), "/.");
+
+ strscpyl(tag_symlink, sizeof(tag_symlink), tags_dir, unescaped_filename, NULL);
+ r = symlink(device_node, tag_symlink);
+ if (r < 0 && errno != EEXIST)
+ return log_error_errno(errno, "failed to create symlink %s -> %s: %m",
+ tag_symlink, device_node);
+ }
+ }
+
+ /* don't touch the permissions if only the tags were set */
+ if (mode == 0 && uid == 0 && gid == 0)
+ break;
+
+ if (mode == 0) {
+ if (gid > 0)
+ mode = 0660;
+ else
+ mode = 0600;
+ }
+ if (mode != (stats.st_mode & 01777)) {
+ r = chmod(device_node, mode);
+ if (r < 0) {
+ log_error("failed to chmod '%s' %#o", device_node, mode);
+ return -errno;
+ } else
+ log_debug("chmod '%s' %#o", device_node, mode);
+ }
+
+ if ((uid != 0 && uid != stats.st_uid) || (gid != 0 && gid != stats.st_gid)) {
+ r = chown(device_node, uid, gid);
+ if (r < 0) {
+ log_error("failed to chown '%s' %u %u ", device_node, uid, gid);
+ return -errno;
+ } else
+ log_debug("chown '%s' %u %u", device_node, uid, gid);
+ }
+
+ utimensat(AT_FDCWD, device_node, NULL, 0);
+ break;
+ }
+ case TK_END:
+ goto finish;
+ }
+
+ cur++;
+ continue;
+next:
+ /* fast-forward to next rule */
+ cur = rule + rule->rule.token_count;
+ continue;
+ }
+
+finish:
+ if (f) {
+ fflush(f);
+ fchmod(fileno(f), 0644);
+ if (ferror(f) || rename(path, "/run/udev/static_node-tags") < 0) {
+ unlink_noerrno("/run/udev/static_node-tags");
+ unlink_noerrno(path);
+ return -errno;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/grp-udev/libudev-core/udev-watch.c b/src/grp-udev/libudev-core/udev-watch.c
new file mode 100644
index 0000000000..ba3e7b979b
--- /dev/null
+++ b/src/grp-udev/libudev-core/udev-watch.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2004-2012 Kay Sievers <kay@vrfy.org>
+ * Copyright (C) 2009 Canonical Ltd.
+ * Copyright (C) 2009 Scott James Remnant <scott@netsplit.com>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <sys/inotify.h>
+#include <unistd.h>
+
+#include "systemd-basic/stdio-util.h"
+#include "udev.h"
+
+static int inotify_fd = -1;
+
+/* inotify descriptor, will be shared with rules directory;
+ * set to cloexec since we need our children to be able to add
+ * watches for us
+ */
+int udev_watch_init(struct udev *udev) {
+ inotify_fd = inotify_init1(IN_CLOEXEC);
+ if (inotify_fd < 0)
+ log_error_errno(errno, "inotify_init failed: %m");
+ return inotify_fd;
+}
+
+/* move any old watches directory out of the way, and then restore
+ * the watches
+ */
+void udev_watch_restore(struct udev *udev) {
+ if (inotify_fd < 0)
+ return;
+
+ if (rename("/run/udev/watch", "/run/udev/watch.old") == 0) {
+ DIR *dir;
+ struct dirent *ent;
+
+ dir = opendir("/run/udev/watch.old");
+ if (dir == NULL) {
+ log_error_errno(errno, "unable to open old watches dir /run/udev/watch.old; old watches will not be restored: %m");
+ return;
+ }
+
+ for (ent = readdir(dir); ent != NULL; ent = readdir(dir)) {
+ char device[UTIL_PATH_SIZE];
+ ssize_t len;
+ struct udev_device *dev;
+
+ if (ent->d_name[0] == '.')
+ continue;
+
+ len = readlinkat(dirfd(dir), ent->d_name, device, sizeof(device));
+ if (len <= 0 || len == (ssize_t)sizeof(device))
+ goto unlink;
+ device[len] = '\0';
+
+ dev = udev_device_new_from_device_id(udev, device);
+ if (dev == NULL)
+ goto unlink;
+
+ log_debug("restoring old watch on '%s'", udev_device_get_devnode(dev));
+ udev_watch_begin(udev, dev);
+ udev_device_unref(dev);
+unlink:
+ unlinkat(dirfd(dir), ent->d_name, 0);
+ }
+
+ closedir(dir);
+ rmdir("/run/udev/watch.old");
+
+ } else if (errno != ENOENT)
+ log_error_errno(errno, "unable to move watches dir /run/udev/watch; old watches will not be restored: %m");
+}
+
+void udev_watch_begin(struct udev *udev, struct udev_device *dev) {
+ char filename[sizeof("/run/udev/watch/") + DECIMAL_STR_MAX(int)];
+ int wd;
+ int r;
+
+ if (inotify_fd < 0)
+ return;
+
+ log_debug("adding watch on '%s'", udev_device_get_devnode(dev));
+ wd = inotify_add_watch(inotify_fd, udev_device_get_devnode(dev), IN_CLOSE_WRITE);
+ if (wd < 0) {
+ log_error_errno(errno, "inotify_add_watch(%d, %s, %o) failed: %m",
+ inotify_fd, udev_device_get_devnode(dev), IN_CLOSE_WRITE);
+ return;
+ }
+
+ xsprintf(filename, "/run/udev/watch/%d", wd);
+ mkdir_parents(filename, 0755);
+ unlink(filename);
+ r = symlink(udev_device_get_id_filename(dev), filename);
+ if (r < 0)
+ log_error_errno(errno, "Failed to create symlink %s: %m", filename);
+
+ udev_device_set_watch_handle(dev, wd);
+}
+
+void udev_watch_end(struct udev *udev, struct udev_device *dev) {
+ int wd;
+ char filename[sizeof("/run/udev/watch/") + DECIMAL_STR_MAX(int)];
+
+ if (inotify_fd < 0)
+ return;
+
+ wd = udev_device_get_watch_handle(dev);
+ if (wd < 0)
+ return;
+
+ log_debug("removing watch on '%s'", udev_device_get_devnode(dev));
+ inotify_rm_watch(inotify_fd, wd);
+
+ xsprintf(filename, "/run/udev/watch/%d", wd);
+ unlink(filename);
+
+ udev_device_set_watch_handle(dev, -1);
+}
+
+struct udev_device *udev_watch_lookup(struct udev *udev, int wd) {
+ char filename[sizeof("/run/udev/watch/") + DECIMAL_STR_MAX(int)];
+ char device[UTIL_NAME_SIZE];
+ ssize_t len;
+
+ if (inotify_fd < 0 || wd < 0)
+ return NULL;
+
+ xsprintf(filename, "/run/udev/watch/%d", wd);
+ len = readlink(filename, device, sizeof(device));
+ if (len <= 0 || (size_t)len == sizeof(device))
+ return NULL;
+ device[len] = '\0';
+
+ return udev_device_new_from_device_id(udev, device);
+}