summaryrefslogtreecommitdiff
path: root/udev
diff options
context:
space:
mode:
authorKay Sievers <kay.sievers@vrfy.org>2011-08-05 02:00:30 +0200
committerKay Sievers <kay.sievers@vrfy.org>2011-08-05 02:00:30 +0200
commitd7867b31836173d1a943ecb1cab6484536126411 (patch)
tree2ad9da94abfa9b5d12dc47a623601b8ea8640c7f /udev
parent617746e09795575c6258dd075ee7f0a44ce61e1e (diff)
implement path_id, usb_id, input_id as built-in command
Diffstat (limited to 'udev')
-rw-r--r--udev/udev-builtin-input_id.c218
-rw-r--r--udev/udev-builtin-path_id.c486
-rw-r--r--udev/udev-builtin-usb_id.c482
-rw-r--r--udev/udev-builtin.c76
-rw-r--r--udev/udev-rules.c99
-rw-r--r--udev/udev.h25
-rw-r--r--udev/udev.xml3
-rw-r--r--udev/udevadm-test-builtin.c123
-rw-r--r--udev/udevadm-test.c2
-rw-r--r--udev/udevadm.c5
-rw-r--r--udev/udevadm.xml15
11 files changed, 1501 insertions, 33 deletions
diff --git a/udev/udev-builtin-input_id.c b/udev/udev-builtin-input_id.c
new file mode 100644
index 0000000000..84ca0f75e4
--- /dev/null
+++ b/udev/udev-builtin-input_id.c
@@ -0,0 +1,218 @@
+/*
+ * compose persistent device path
+ *
+ * Copyright (C) 2009 Martin Pitt <martin.pitt@ubuntu.com>
+ * Portions Copyright (C) 2004 David Zeuthen, <david@fubar.dk>
+ * Copyright (C) 2011 Kay Sievers <kay.sievers@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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <linux/limits.h>
+#include <linux/input.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)
+
+/*
+ * 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)
+{
+ struct udev *udev = udev_device_get_udev(dev);
+ char text[4096];
+ unsigned i;
+ char* word;
+ unsigned long val;
+
+ snprintf(text, sizeof(text), "%s", udev_device_get_sysattr_value(pdev, attr));
+ info(udev, "%s raw kernel attribute: %s\n", attr, text);
+
+ memset (bitmask, 0, 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
+ info(udev, "ignoring %s block %lX which is larger than maximum size\n", attr, val);
+ *word = '\0';
+ ++i;
+ }
+ val = strtoul (text, NULL, 16);
+ if (i < bitmask_size / sizeof(unsigned long))
+ bitmask[i] = val;
+ else
+ info(udev, "ignoring %s block %lX which is larger than maximum size\n", attr, val);
+
+ if (test) {
+ /* printf pattern with the right unsigned long number of hex chars */
+ snprintf(text, sizeof(text), " bit %%4u: %%0%zilX\n", 2 * sizeof(unsigned long));
+ info(udev, "%s decoded bit map:\n", 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)
+ info(udev, text, i * BITS_PER_LONG, bitmask[i]);
+ }
+}
+
+/* pointer devices */
+static void 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,
+ bool test)
+{
+ int is_mouse = 0;
+ int is_touchpad = 0;
+
+ if (!test_bit (EV_KEY, bitmask_ev)) {
+ if (test_bit (EV_ABS, bitmask_ev) &&
+ test_bit (ABS_X, bitmask_abs) &&
+ test_bit (ABS_Y, bitmask_abs) &&
+ test_bit (ABS_Z, bitmask_abs))
+ udev_builtin_add_property(dev, test, "ID_INPUT_ACCELEROMETER", "1");
+ return;
+ }
+
+ if (test_bit (EV_ABS, bitmask_ev) &&
+ test_bit (ABS_X, bitmask_abs) && test_bit (ABS_Y, bitmask_abs)) {
+ if (test_bit (BTN_STYLUS, bitmask_key) || test_bit (BTN_TOOL_PEN, bitmask_key))
+ udev_builtin_add_property(dev, test, "ID_INPUT_TABLET", "1");
+ else if (test_bit (BTN_TOOL_FINGER, bitmask_key) && !test_bit (BTN_TOOL_PEN, bitmask_key))
+ is_touchpad = 1;
+ else if (test_bit (BTN_TRIGGER, bitmask_key) ||
+ test_bit (BTN_A, bitmask_key) ||
+ test_bit (BTN_1, bitmask_key))
+ udev_builtin_add_property(dev, test, "ID_INPUT_JOYSTICK", "1");
+ else if (test_bit (BTN_MOUSE, bitmask_key))
+ /* This path is taken by VMware's USB mouse, which has
+ * absolute axes, but no touch/pressure button. */
+ is_mouse = 1;
+ else if (test_bit (BTN_TOUCH, bitmask_key))
+ udev_builtin_add_property(dev, test, "ID_INPUT_TOUCHSCREEN", "1");
+ }
+
+ if (test_bit (EV_REL, bitmask_ev) &&
+ test_bit (REL_X, bitmask_rel) && test_bit (REL_Y, bitmask_rel) &&
+ test_bit (BTN_MOUSE, bitmask_key))
+ is_mouse = 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");
+}
+
+/* key like devices */
+static void test_key (struct udev_device *dev,
+ const unsigned long* bitmask_ev,
+ const unsigned long* bitmask_key,
+ bool test)
+{
+ struct udev *udev = udev_device_get_udev(dev);
+ unsigned i;
+ unsigned long found;
+ unsigned long mask;
+
+ /* do we have any KEY_* capability? */
+ if (!test_bit (EV_KEY, bitmask_ev)) {
+ info(udev, "test_key: no EV_KEY capability\n");
+ return;
+ }
+
+ /* only consider KEY_* here, not BTN_* */
+ found = 0;
+ for (i = 0; i < BTN_MISC/BITS_PER_LONG; ++i) {
+ found |= bitmask_key[i];
+ info(udev, "test_key: checking bit block %lu for any keys; found=%i\n", 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)) {
+ info(udev, "test_key: Found key %x in high block\n", i);
+ found = 1;
+ break;
+ }
+ }
+ }
+
+ if (found > 0)
+ udev_builtin_add_property(dev, test, "ID_INPUT_KEY", "1");
+
+ /* 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");
+}
+
+static int builtin_input_id(struct udev_device *dev, 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)];
+
+ /* 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);
+
+ /* not an "input" class device */
+ if (pdev == NULL)
+ return EXIT_SUCCESS;
+
+ /* 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);
+ test_pointers(dev, bitmask_ev, bitmask_abs, bitmask_key, bitmask_rel, test);
+ test_key(dev, bitmask_ev, bitmask_key, 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/udev/udev-builtin-path_id.c b/udev/udev-builtin-path_id.c
new file mode 100644
index 0000000000..61c50d77bd
--- /dev/null
+++ b/udev/udev-builtin-path_id.c
@@ -0,0 +1,486 @@
+/*
+ * compose persistent device path
+ *
+ * Copyright (C) 2009-2011 Kay Sievers <kay.sievers@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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+#include <getopt.h>
+
+#include "udev.h"
+
+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-%d", lun);
+ /* handle all other lun addressing methods by using a variant of the original lun format */
+ return path_prepend(path, "lun-0x%04x%04x00000000", (lun & 0xffff), (lun >> 16) & 0xffff);
+}
+
+static struct udev_device *skip_subsystem(struct udev_device *dev, const char *subsys)
+{
+ struct udev_device *parent = dev;
+
+ while (parent != NULL) {
+ const char *subsystem;
+
+ subsystem = udev_device_get_subsystem(parent);
+ if (subsystem == NULL || strcmp(subsystem, subsys) != 0)
+ 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;;
+
+ 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);
+ if (lun)
+ free(lun);
+out:
+ udev_device_unref(fcdev);
+ 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 *sasdev;
+ const char *sas_address;
+ char *lun = NULL;
+
+ 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);
+ if (lun)
+ free(lun);
+out:
+ udev_device_unref(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;
+
+ /* find iscsi session */
+ transportdev = parent;
+ for (;;) {
+ transportdev = udev_device_get_parent(transportdev);
+ if (transportdev == NULL)
+ return NULL;
+ if (strncmp(udev_device_get_sysname(transportdev), "session", 7) == 0)
+ 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);
+ if (lun)
+ free(lun);
+out:
+ udev_device_unref(sessiondev);
+ udev_device_unref(conndev);
+ 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;
+
+ 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 */
+ 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 (strncmp(dent->d_name, "host", 4) != 0)
+ continue;
+ i = strtoul(&dent->d_name[4], &rest, 10);
+ if (rest[0] != '\0')
+ continue;
+ 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(struct udev_device *parent, char **path)
+{
+ const char *devtype;
+ const char *name;
+ const char *id;
+
+ devtype = udev_device_get_devtype(parent);
+ if (devtype == NULL || strcmp(devtype, "scsi_device") != 0)
+ 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);
+ goto out;
+ }
+
+ /* lousy 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);
+ goto out;
+ }
+
+ if (strstr(name, "/end_device-") != NULL) {
+ parent = handle_scsi_sas(parent, path);
+ goto out;
+ }
+
+ if (strstr(name, "/session") != NULL) {
+ parent = handle_scsi_iscsi(parent, path);
+ goto out;
+ }
+
+ parent = handle_scsi_default(parent, path);
+out:
+ 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 (strncmp(name, "nst", 3) == 0 && strchr("lma", name[3]) != NULL)
+ path_prepend(path, "nst%c", name[3]);
+ else if (strncmp(name, "st", 2) == 0 && 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 (strcmp(devtype, "usb_interface") != 0 && strcmp(devtype, "usb_device") != 0)
+ 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_cciss(struct udev_device *parent, char **path)
+{
+ return NULL;
+}
+
+static struct udev_device *handle_ccw(struct udev_device *parent, struct udev_device *dev, char **path)
+{
+ struct udev_device *scsi_dev;
+
+ scsi_dev = udev_device_get_parent_with_subsystem_devtype(dev, "scsi", "scsi_device");
+ if (scsi_dev != NULL) {
+ const char *wwpn;
+ const char *lun;
+ const char *hba_id;
+
+ hba_id = udev_device_get_sysattr_value(scsi_dev, "hba_id");
+ wwpn = udev_device_get_sysattr_value(scsi_dev, "wwpn");
+ lun = udev_device_get_sysattr_value(scsi_dev, "fcp_lun");
+ if (hba_id != NULL && lun != NULL && wwpn != NULL) {
+ path_prepend(path, "ccw-%s-zfcp-%s:%s", hba_id, wwpn, lun);
+ goto out;
+ }
+ }
+
+ path_prepend(path, "ccw-%s", udev_device_get_sysname(parent));
+out:
+ parent = skip_subsystem(parent, "ccw");
+ return parent;
+}
+
+static int builtin_path_id(struct udev_device *dev, bool test)
+{
+ struct udev_device *parent;
+ char *path = NULL;
+
+ /* S390 ccw bus */
+ parent = udev_device_get_parent_with_subsystem_devtype(dev, "ccw", NULL);
+ if (parent != NULL) {
+ handle_ccw(parent, dev, &path);
+ goto out;
+ }
+
+ /* 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 (strcmp(subsys, "scsi_tape") == 0) {
+ handle_scsi_tape(parent, &path);
+ } else if (strcmp(subsys, "scsi") == 0) {
+ parent = handle_scsi(parent, &path);
+ } else if (strcmp(subsys, "cciss") == 0) {
+ handle_cciss(parent, &path);
+ } else if (strcmp(subsys, "usb") == 0) {
+ parent = handle_usb(parent, &path);
+ } else if (strcmp(subsys, "serio") == 0) {
+ path_prepend(&path, "serio-%s", udev_device_get_sysnum(parent));
+ parent = skip_subsystem(parent, "serio");
+ } else if (strcmp(subsys, "pci") == 0) {
+ path_prepend(&path, "pci-%s", udev_device_get_sysname(parent));
+ parent = skip_subsystem(parent, "pci");
+ } else if (strcmp(subsys, "platform") == 0) {
+ path_prepend(&path, "platform-%s", udev_device_get_sysname(parent));
+ parent = skip_subsystem(parent, "platform");
+ } else if (strcmp(subsys, "acpi") == 0) {
+ path_prepend(&path, "acpi-%s", udev_device_get_sysname(parent));
+ parent = skip_subsystem(parent, "acpi");
+ } else if (strcmp(subsys, "xen") == 0) {
+ path_prepend(&path, "xen-%s", udev_device_get_sysname(parent));
+ parent = skip_subsystem(parent, "xen");
+ } else if (strcmp(subsys, "virtio") == 0) {
+ path_prepend(&path, "virtio-pci-%s", udev_device_get_sysname(parent));
+ parent = skip_subsystem(parent, "virtio");
+ }
+
+ parent = udev_device_get_parent(parent);
+ }
+out:
+ 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",
+};
diff --git a/udev/udev-builtin-usb_id.c b/udev/udev-builtin-usb_id.c
new file mode 100644
index 0000000000..95368ecf22
--- /dev/null
+++ b/udev/udev-builtin-usb_id.c
@@ -0,0 +1,482 @@
+/*
+ * 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.sievers@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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "udev.h"
+
+static char vendor_str[64];
+static char vendor_str_enc[256];
+static const char *vendor_id = "";
+static char model_str[64];
+static char model_str_enc[256];
+static const char *product_id = "";
+static char serial_str[UTIL_NAME_SIZE];
+static char packed_if_str[UTIL_NAME_SIZE];
+static char revision_str[64];
+static char type_str[64];
+static char instance_str[64];
+static const char *ifnum;
+static const char *driver;
+
+static void set_usb_iftype(char *to, int if_class_num, size_t len)
+{
+ 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;
+ char *type = "generic";
+
+ type_num = strtoul(from, &eptr, 0);
+ if (eptr != from) {
+ switch (type_num) {
+ case 2:
+ type = "atapi";
+ break;
+ case 3:
+ type = "tape";
+ break;
+ case 4: /* UFI */
+ case 5: /* SFF-8070i */
+ type = "floppy";
+ break;
+ case 1: /* RBC devices */
+ type = "rbc";
+ break;
+ case 6: /* Transparent SPC-2 devices */
+ type = "scsi";
+ break;
+ default:
+ break;
+ }
+ }
+ util_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;
+ 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;
+ }
+ }
+ util_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)
+{
+ char *filename = NULL;
+ int fd;
+ ssize_t size;
+ unsigned char buf[18 + 65535];
+ unsigned int pos, strpos;
+ struct usb_interface_descriptor {
+ u_int8_t bLength;
+ u_int8_t bDescriptorType;
+ u_int8_t bInterfaceNumber;
+ u_int8_t bAlternateSetting;
+ u_int8_t bNumEndpoints;
+ u_int8_t bInterfaceClass;
+ u_int8_t bInterfaceSubClass;
+ u_int8_t bInterfaceProtocol;
+ u_int8_t iInterface;
+ } __attribute__((packed));
+ int err = 0;
+
+ if (asprintf(&filename, "%s/descriptors", udev_device_get_syspath(dev)) < 0) {
+ err = -1;
+ goto out;
+ }
+ fd = open(filename, O_RDONLY|O_CLOEXEC);
+ if (fd < 0) {
+ fprintf(stderr, "error opening USB device 'descriptors' file\n");
+ err = -1;
+ goto out;
+ }
+ size = read(fd, buf, sizeof(buf));
+ close(fd);
+ if (size < 18 || size == sizeof(buf)) {
+ err = -1;
+ goto out;
+ }
+
+ pos = 0;
+ strpos = 0;
+ ifs_str[0] = '\0';
+ while (pos < sizeof(buf) && 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';
+ }
+out:
+ free(filename);
+ return err;
+}
+
+/*
+ * 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 usb_id(struct udev_device *dev)
+{
+ struct udev *udev = udev_device_get_udev(dev);
+ 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;
+
+ dbg(udev, "syspath %s\n", udev_device_get_syspath(dev));
+
+ /* shortcut, if we are called directly for a "usb_device" type */
+ if (udev_device_get_devtype(dev) != NULL && strcmp(udev_device_get_devtype(dev), "usb_device") == 0) {
+ 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) {
+ info(udev, "unable to access usb_interface device of '%s'\n",
+ udev_device_get_syspath(dev));
+ return 1;
+ }
+
+ 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) {
+ info(udev, "%s: cannot get bInterfaceClass attribute\n",
+ udev_device_get_sysname(dev));
+ return 1;
+ }
+
+ 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);
+ }
+
+ info(udev, "%s: if_class %d protocol %d\n",
+ 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) {
+ info(udev, "unable to find parent 'usb' device of '%s'\n",
+ udev_device_get_syspath(dev));
+ return 1;
+ }
+
+ /* 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) {
+ info(udev, "unable to find parent 'scsi' device of '%s'\n",
+ udev_device_get_syspath(dev));
+ goto fallback;
+ }
+ if (sscanf(udev_device_get_sysname(dev_scsi), "%d:%d:%d:%d", &host, &bus, &target, &lun) != 4) {
+ info(udev, "invalid scsi device '%s'\n", 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) {
+ info(udev, "%s: cannot get SCSI vendor attribute\n",
+ udev_device_get_sysname(dev_scsi));
+ goto fallback;
+ }
+ udev_util_encode_string(scsi_vendor, vendor_str_enc, sizeof(vendor_str_enc));
+ udev_util_replace_whitespace(scsi_vendor, vendor_str, sizeof(vendor_str)-1);
+ udev_util_replace_chars(vendor_str, NULL);
+
+ scsi_model = udev_device_get_sysattr_value(dev_scsi, "model");
+ if (!scsi_model) {
+ info(udev, "%s: cannot get SCSI model attribute\n",
+ udev_device_get_sysname(dev_scsi));
+ goto fallback;
+ }
+ udev_util_encode_string(scsi_model, model_str_enc, sizeof(model_str_enc));
+ udev_util_replace_whitespace(scsi_model, model_str, sizeof(model_str)-1);
+ udev_util_replace_chars(model_str, NULL);
+
+ scsi_type = udev_device_get_sysattr_value(dev_scsi, "type");
+ if (!scsi_type) {
+ info(udev, "%s: cannot get SCSI type attribute\n",
+ 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) {
+ info(udev, "%s: cannot get SCSI revision attribute\n",
+ udev_device_get_sysname(dev_scsi));
+ goto fallback;
+ }
+ udev_util_replace_whitespace(scsi_rev, revision_str, sizeof(revision_str)-1);
+ udev_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) {
+ info(udev, "No USB vendor information available\n");
+ return 1;
+ }
+ udev_util_encode_string(usb_vendor, vendor_str_enc, sizeof(vendor_str_enc));
+ udev_util_replace_whitespace(usb_vendor, vendor_str, sizeof(vendor_str)-1);
+ udev_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) {
+ dbg(udev, "No USB model information available\n");
+ return 1;
+ }
+ udev_util_encode_string(usb_model, model_str_enc, sizeof(model_str_enc));
+ udev_util_replace_whitespace(usb_model, model_str, sizeof(model_str)-1);
+ udev_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) {
+ udev_util_replace_whitespace(usb_rev, revision_str, sizeof(revision_str)-1);
+ udev_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) {
+ udev_util_replace_whitespace(usb_serial, serial_str, sizeof(serial_str)-1);
+ udev_util_replace_chars(serial_str, NULL);
+ }
+ }
+ return 0;
+}
+
+static int builtin_usb_id(struct udev_device *dev, bool test)
+{
+ char serial[256];
+ size_t l;
+ char *s;
+ int err;
+
+ err = usb_id(dev);
+ if (err)
+ return EXIT_FAILURE;
+
+ s = serial;
+ l = util_strpcpyl(&s, sizeof(serial), vendor_str, "_", model_str, NULL);
+ if (serial_str[0] != '\0')
+ l = util_strpcpyl(&s, l, "_", serial_str, NULL);
+ if (instance_str[0] != '\0')
+ util_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 (serial_str[0] != '\0')
+ udev_builtin_add_property(dev, test, "ID_SERIAL_SHORT", serial_str);
+ if (type_str[0] != '\0')
+ udev_builtin_add_property(dev, test, "ID_TYPE", type_str);
+ if (instance_str[0] != '\0')
+ udev_builtin_add_property(dev, test, "ID_INSTANCE", instance_str);
+ udev_builtin_add_property(dev, test, "ID_BUS", "usb");
+ if (packed_if_str[0] != '\0')
+ 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",
+};
diff --git a/udev/udev-builtin.c b/udev/udev-builtin.c
new file mode 100644
index 0000000000..eeec6e990d
--- /dev/null
+++ b/udev/udev-builtin.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2007-2009 Kay Sievers <kay.sievers@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 <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+
+#include "udev.h"
+
+static const struct udev_builtin *builtins[] = {
+ [UDEV_BUILTIN_PATH_ID] = &udev_builtin_path_id,
+ [UDEV_BUILTIN_USB_ID] = &udev_builtin_usb_id,
+ [UDEV_BUILTIN_INPUT_ID] = &udev_builtin_input_id,
+};
+
+int udev_builtin_list(struct udev *udev)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(builtins); i++)
+ fprintf(stderr, " %-12s %s\n", builtins[i]->name, builtins[i]->help);
+ return 0;
+}
+
+const char *udev_builtin_name(enum udev_builtin_cmd cmd)
+{
+ return builtins[cmd]->name;
+}
+
+enum udev_builtin_cmd udev_builtin_lookup(const char *name)
+{
+ enum udev_builtin_cmd i;
+
+ for (i = 0; i < ARRAY_SIZE(builtins); i++)
+ if (strcmp(builtins[i]->name, name) == 0)
+ return i;
+ return UDEV_BUILTIN_MAX;
+}
+
+int udev_builtin_run(struct udev_device *dev, enum udev_builtin_cmd cmd, bool test)
+{
+ return builtins[cmd]->cmd(dev, test);
+}
+
+int udev_builtin_add_property(struct udev_device *dev, bool test, const char *key, const char *val, ...)
+{
+ struct udev_list_entry *entry;
+
+ entry = udev_device_add_property(dev, key, val);
+ /* store in db, skip private keys */
+ if (key[0] != '.')
+ udev_list_entry_set_num(entry, true);
+
+ info(udev_device_get_udev(dev), "%s=%s\n", key, val);
+ if (test)
+ printf("%s=%s\n", key, val);
+ return 0;
+}
diff --git a/udev/udev-rules.c b/udev/udev-rules.c
index f345e8977d..742d88b3d5 100644
--- a/udev/udev-rules.c
+++ b/udev/udev-rules.c
@@ -144,6 +144,7 @@ enum token_type {
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 */
@@ -205,6 +206,7 @@ struct token {
int devlink_prio;
int event_timeout;
int watch;
+ enum udev_builtin_cmd builtin_cmd;
};
} key;
};
@@ -280,6 +282,7 @@ static const char *token_str(enum token_type type)
[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",
@@ -361,6 +364,9 @@ static void dump_token(struct udev_rules *rules, struct token *token)
dbg(rules->udev, "%s %s '%s'(%s)\n",
token_str(type), operation_str(op), value, string_glob_str(glob));
break;
+ case TK_M_IMPORT_BUILTIN:
+ dbg(rules->udev, "%s %i\n", token_str(type), token->key.builtin_cmd);
+ break;
case TK_M_ATTR:
case TK_M_ATTRS:
case TK_M_ENV:
@@ -1033,6 +1039,9 @@ static int rule_add_key(struct rule_tmp *rule_tmp, enum token_type type,
case TK_A_TAG:
token->key.value_off = add_string(rule_tmp->rules, value);
break;
+ case TK_M_IMPORT_BUILTIN:
+ token->key.builtin_cmd = *(enum udev_builtin_cmd *)data;
+ break;
case TK_M_ENV:
case TK_M_ATTR:
case TK_M_ATTRS:
@@ -1435,43 +1444,50 @@ static int add_rule(struct udev_rules *rules, char *line,
if (strncmp(key, "IMPORT", sizeof("IMPORT")-1) == 0) {
attr = get_key_attribute(rules->udev, key + sizeof("IMPORT")-1);
- if (attr != NULL && strstr(attr, "program")) {
+ if (attr == NULL) {
+ err(rules->udev, "IMPORT{} type missing, ignoring IMPORT %s:%u\n", filename, lineno);
+ continue;
+ }
+ if (strstr(attr, "program")) {
+ /* find known built-in command */
+ if (value[0] != '/') {
+ char file[UTIL_PATH_SIZE];
+ char *pos;
+ enum udev_builtin_cmd cmd;
+
+ util_strscpy(file, sizeof(file), value);
+ pos = strchr(file, ' ');
+ if (pos)
+ pos[0] = '\0';
+ cmd = udev_builtin_lookup(file);
+ if (cmd < UDEV_BUILTIN_MAX) {
+ info(rules->udev, "IMPORT found builtin '%s', replacing %s:%u\n", file, filename, lineno);
+ rule_add_key(&rule_tmp, TK_M_IMPORT_BUILTIN, op, NULL, &cmd);
+ continue;
+ }
+ }
dbg(rules->udev, "IMPORT will be executed\n");
rule_add_key(&rule_tmp, TK_M_IMPORT_PROG, op, value, NULL);
- } else if (attr != NULL && strstr(attr, "file")) {
+ } else if (strstr(attr, "builtin")) {
+ enum udev_builtin_cmd cmd = udev_builtin_lookup(value);
+
+ dbg(rules->udev, "IMPORT execute builtin\n");
+ if (cmd < UDEV_BUILTIN_MAX)
+ rule_add_key(&rule_tmp, TK_M_IMPORT_BUILTIN, op, NULL, &cmd);
+ else
+ err(rules->udev, "IMPORT{builtin}: '%s' unknown %s:%u\n", value, filename, lineno);
+ } else if (strstr(attr, "file")) {
dbg(rules->udev, "IMPORT will be included as file\n");
rule_add_key(&rule_tmp, TK_M_IMPORT_FILE, op, value, NULL);
- } else if (attr != NULL && strstr(attr, "db")) {
+ } else if (strstr(attr, "db")) {
dbg(rules->udev, "IMPORT will include db values\n");
rule_add_key(&rule_tmp, TK_M_IMPORT_DB, op, value, NULL);
- } else if (attr != NULL && strstr(attr, "cmdline")) {
+ } else if (strstr(attr, "cmdline")) {
dbg(rules->udev, "IMPORT will include db values\n");
rule_add_key(&rule_tmp, TK_M_IMPORT_CMDLINE, op, value, NULL);
- } else if (attr != NULL && strstr(attr, "parent")) {
+ } else if (strstr(attr, "parent")) {
dbg(rules->udev, "IMPORT will include the parent values\n");
rule_add_key(&rule_tmp, TK_M_IMPORT_PARENT, op, value, NULL);
- } else {
- /* figure it out if it is executable */
- char file[UTIL_PATH_SIZE];
- char *pos;
- struct stat statbuf;
-
- /* allow programs in /lib/udev called without the path */
- if (value[0] != '/')
- util_strscpyl(file, sizeof(file), LIBEXECDIR "/", value, NULL);
- else
- util_strscpy(file, sizeof(file), value);
- pos = strchr(file, ' ');
- if (pos)
- pos[0] = '\0';
- dbg(rules->udev, "IMPORT auto mode for '%s'\n", file);
- if (stat(file, &statbuf) == 0 && (statbuf.st_mode & S_IXUSR)) {
- dbg(rules->udev, "IMPORT will be executed (autotype)\n");
- rule_add_key(&rule_tmp, TK_M_IMPORT_PROG, op, value, NULL);
- } else {
- dbg(rules->udev, "IMPORT will be included as file (autotype)\n");
- rule_add_key(&rule_tmp, TK_M_IMPORT_FILE, op, value, NULL);
- }
}
continue;
}
@@ -2349,6 +2365,35 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event
goto nomatch;
break;
}
+ case TK_M_IMPORT_BUILTIN: {
+ /* check if we ran already */
+ if (event->builtin_run & (1 << cur->key.builtin_cmd)) {
+ info(event->udev, "IMPORT builtin skip '%s' %s:%u\n",
+ udev_builtin_name(cur->key.builtin_cmd),
+ &rules->buf[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);
+ info(event->udev, "IMPORT builtin '%s' %s:%u\n",
+ udev_builtin_name(cur->key.builtin_cmd),
+ &rules->buf[rule->rule.filename_off],
+ rule->rule.filename_line);
+ if (udev_builtin_run(event->dev, cur->key.builtin_cmd, false) != 0) {
+ /* remember failure */
+ info(rules->udev, "IMPORT builtin '%s' returned non-zero\n",
+ 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->buf[cur->key.value_off];
const char *value;
diff --git a/udev/udev.h b/udev/udev.h
index c1e2814504..1f9650fc42 100644
--- a/udev/udev.h
+++ b/udev/udev.h
@@ -44,6 +44,8 @@ struct udev_event {
unsigned long long birth_usec;
unsigned long long timeout_usec;
int fd_signal;
+ unsigned int builtin_run;
+ unsigned int builtin_ret;
bool sigterm;
bool inotify_watch;
bool inotify_watch_final;
@@ -141,4 +143,27 @@ extern const struct udevadm_cmd udevadm_control;
extern const struct udevadm_cmd udevadm_trigger;
extern const struct udevadm_cmd udevadm_settle;
extern const struct udevadm_cmd udevadm_test;
+extern const struct udevadm_cmd udevadm_test_builtin;
+
+/* built-in commands */
+enum udev_builtin_cmd {
+ UDEV_BUILTIN_PATH_ID,
+ UDEV_BUILTIN_USB_ID,
+ UDEV_BUILTIN_INPUT_ID,
+ UDEV_BUILTIN_MODALIAS_MATCH,
+ UDEV_BUILTIN_MAX
+};
+struct udev_builtin {
+ const char *name;
+ int (*cmd)(struct udev_device *dev, bool test);
+ const char *help;
+};
+extern const struct udev_builtin udev_builtin_path_id;
+extern const struct udev_builtin udev_builtin_usb_id;
+extern const struct udev_builtin udev_builtin_input_id;
+enum udev_builtin_cmd udev_builtin_lookup(const char *name);
+const char *udev_builtin_name(enum udev_builtin_cmd cmd);
+int udev_builtin_run(struct udev_device *dev, enum udev_builtin_cmd cmd, bool test);
+int udev_builtin_list(struct udev *udev);
+int udev_builtin_add_property(struct udev_device *dev, bool test, const char *key, const char *val, ...);
#endif
diff --git a/udev/udev.xml b/udev/udev.xml
index abbfee7ad1..a785348551 100644
--- a/udev/udev.xml
+++ b/udev/udev.xml
@@ -463,9 +463,6 @@
</listitem>
</varlistentry>
</variablelist>
- <para>If no option is given, udev chooses between <option>program</option>
- and <option>file</option> based on the executable bit of the file
- permissions.</para>
</listitem>
</varlistentry>
diff --git a/udev/udevadm-test-builtin.c b/udev/udevadm-test-builtin.c
new file mode 100644
index 0000000000..a2be77683a
--- /dev/null
+++ b/udev/udevadm-test-builtin.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2011 Kay Sievers <kay.sievers@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 <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <getopt.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/inotify.h>
+#include <sys/poll.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "udev.h"
+
+static void help(struct udev *udev)
+{
+ fprintf(stderr, "\n");
+ fprintf(stderr, "Usage: udevadm builtin [--help] <command> <syspath>\n");
+ udev_builtin_list(udev);
+ fprintf(stderr, "\n");
+}
+
+static int adm_builtin(struct udev *udev, int argc, char *argv[])
+{
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ {}
+ };
+ char *command = NULL;
+ char *syspath = NULL;
+ char filename[UTIL_PATH_SIZE];
+ struct udev_device *dev = NULL;
+ enum udev_builtin_cmd cmd;
+ int rc = EXIT_SUCCESS;
+
+ dbg(udev, "version %s\n", VERSION);
+
+ for (;;) {
+ int option;
+
+ option = getopt_long(argc, argv, "h", options, NULL);
+ if (option == -1)
+ break;
+
+ switch (option) {
+ case 'h':
+ help(udev);
+ goto out;
+ }
+ }
+ command = argv[optind++];
+ if (command == NULL) {
+ fprintf(stderr, "command missing\n");
+ help(udev);
+ rc = 2;
+ goto out;
+ }
+
+ syspath = argv[optind++];
+ if (syspath == NULL) {
+ fprintf(stderr, "syspath missing\n\n");
+ rc = 3;
+ goto out;
+ }
+
+ /* add /sys if needed */
+ if (strncmp(syspath, udev_get_sys_path(udev), strlen(udev_get_sys_path(udev))) != 0)
+ util_strscpyl(filename, sizeof(filename), udev_get_sys_path(udev), syspath, NULL);
+ else
+ util_strscpy(filename, sizeof(filename), syspath);
+ util_remove_trailing_chars(filename, '/');
+
+ dev = udev_device_new_from_syspath(udev, filename);
+ if (dev == NULL) {
+ fprintf(stderr, "unable to open device '%s'\n\n", filename);
+ rc = 4;
+ goto out;
+ }
+
+ cmd = udev_builtin_lookup(command);
+ if (cmd >= UDEV_BUILTIN_MAX) {
+ fprintf(stderr, "unknown command '%s'\n", command);
+ help(udev);
+ rc = 5;
+ goto out;
+ }
+
+ if (udev_builtin_run(dev, cmd, true) < 0) {
+ fprintf(stderr, "error executing '%s'\n\n", command);
+ rc = 6;
+ }
+out:
+ udev_device_unref(dev);
+ return rc;
+}
+
+const struct udevadm_cmd udevadm_test_builtin = {
+ .name = "test-builtin",
+ .cmd = adm_builtin,
+ .help = "test a built-in command",
+};
diff --git a/udev/udevadm-test.c b/udev/udevadm-test.c
index c2c377c3d7..e807fc05a6 100644
--- a/udev/udevadm-test.c
+++ b/udev/udevadm-test.c
@@ -165,6 +165,6 @@ out:
const struct udevadm_cmd udevadm_test = {
.name = "test",
.cmd = adm_test,
- .help = "simulation run",
+ .help = "test an event run",
.debug = true,
};
diff --git a/udev/udevadm.c b/udev/udevadm.c
index d3810e73e0..178981eb3e 100644
--- a/udev/udevadm.c
+++ b/udev/udevadm.c
@@ -67,6 +67,7 @@ static const struct udevadm_cmd *udevadm_cmds[] = {
&udevadm_control,
&udevadm_monitor,
&udevadm_test,
+ &udevadm_test_builtin,
&udevadm_version,
&udevadm_help,
};
@@ -75,11 +76,11 @@ static int adm_help(struct udev *udev, int argc, char *argv[])
{
unsigned int i;
- printf("Usage: udevadm [--help] [--version] [--debug] COMMAND [COMMAND OPTIONS]\n");
+ fprintf(stderr, "Usage: udevadm [--help] [--version] [--debug] COMMAND [COMMAND OPTIONS]\n");
for (i = 0; i < ARRAY_SIZE(udevadm_cmds); i++)
if (udevadm_cmds[i]->help != NULL)
printf(" %-12s %s\n", udevadm_cmds[i]->name, udevadm_cmds[i]->help);
- printf("\n");
+ fprintf(stderr, "\n");
return 0;
}
diff --git a/udev/udevadm.xml b/udev/udevadm.xml
index a106093569..2fdbb42b2b 100644
--- a/udev/udevadm.xml
+++ b/udev/udevadm.xml
@@ -44,6 +44,9 @@
<cmdsynopsis>
<command>udevadm test <optional>options</optional> <replaceable>devpath</replaceable></command>
</cmdsynopsis>
+ <cmdsynopsis>
+ <command>udevadm test-builtin <optional>options</optional> <replaceable>command</replaceable> <replaceable>devpath</replaceable></command>
+ </cmdsynopsis>
</refsynopsisdiv>
<refsect1><title>Description</title>
@@ -440,6 +443,18 @@
</varlistentry>
</variablelist>
</refsect2>
+
+ <refsect2><title>udevadm test-builtin <optional>options</optional> <replaceable>command</replaceable> <replaceable>devpath</replaceable></title>
+ <para>Run a built-in command for the given device, and print debug output.</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
</refsect1>
<refsect1><title>Author</title>