diff options
38 files changed, 1555 insertions, 1494 deletions
diff --git a/Makefile.am b/Makefile.am index 7686ca74e1..563b09b97b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -160,7 +160,11 @@ udev_common_sources =\ udev/udev-watch.c \ udev/udev-node.c \ udev/udev-rules.c \ - udev/udev-ctrl.c + udev/udev-ctrl.c \ + udev/udev-builtin.c \ + udev/udev-builtin-path_id.c \ + udev/udev-builtin-usb_id.c \ + udev/udev-builtin-input_id.c udev_udevd_SOURCES = \ $(udev_common_sources) \ @@ -174,10 +178,11 @@ udev_udevadm_SOURCES = \ udev/udevadm.c \ udev/udevadm-info.c \ udev/udevadm-control.c \ - udev/udevadm-test.c \ udev/udevadm-monitor.c \ udev/udevadm-settle.c \ - udev/udevadm-trigger.c + udev/udevadm-trigger.c \ + udev/udevadm-test.c \ + udev/udevadm-test-builtin.c udev_udevadm_LDADD = libudev/libudev-private.la # ------------------------------------------------------------------------------ @@ -253,20 +258,6 @@ extras_collect_collect_LDADD = libudev/libudev-private.la libexec_PROGRAMS += extras/collect/collect # ------------------------------------------------------------------------------ -# input_id - import input device class -# ------------------------------------------------------------------------------ -extras_input_id_input_id_SOURCES = extras/input_id/input_id.c -extras_input_id_input_id_LDADD = libudev/libudev-private.la -libexec_PROGRAMS += extras/input_id/input_id - -# ------------------------------------------------------------------------------ -# path_id - compose identifier of persistent elements of the parent buses -# ------------------------------------------------------------------------------ -extras_path_id_path_id_SOURCES = extras/path_id/path_id.c -extras_path_id_path_id_LDADD = libudev/libudev-private.la -libexec_PROGRAMS += extras/path_id/path_id - -# ------------------------------------------------------------------------------ # scsi_id - SCSI inquiry to get various serial numbers # ------------------------------------------------------------------------------ extras_scsi_id_scsi_id_SOURCES =\ @@ -280,13 +271,6 @@ dist_man_MANS += extras/scsi_id/scsi_id.8 EXTRA_DIST += extras/scsi_id/README # ------------------------------------------------------------------------------ -# usb_id - USB device property import -# ------------------------------------------------------------------------------ -extras_usb_id_usb_id_SOURCES = extras/usb_id/usb_id.c -extras_usb_id_usb_id_LDADD = libudev/libudev-private.la -libexec_PROGRAMS += extras/usb_id/usb_id - -# ------------------------------------------------------------------------------ # v4l_id - video4linux capabilities # ------------------------------------------------------------------------------ extras_v4l_id_v4l_id_SOURCES = extras/v4l_id/v4l_id.c @@ -1,3 +1,11 @@ +udev 174 +======== +Bugfixes. + +The path_id, usb_id, input_id tools are built-in commands now +and the stand-alone tools do not exist anymore. For testing, +the commands can be run with 'udevadm test-builtin <cmd>'. + udev 173 ======== Bugfixes. @@ -4,6 +4,8 @@ - test (now fixed) /dev/tape/ links + - warn about fallback of IMPORT{program} for built-in commands + - /run/udev/control socket (add ConditionVirtualization=!pidns) diff --git a/extras/gudev/docs/.gitignore b/extras/gudev/docs/.gitignore index a68789bd13..a24184ac83 100644 --- a/extras/gudev/docs/.gitignore +++ b/extras/gudev/docs/.gitignore @@ -1,3 +1,4 @@ +gudev-overrides.txt gudev-decl-list.txt gudev-decl.txt gudev-undeclared.txt diff --git a/extras/input_id/.gitignore b/extras/input_id/.gitignore deleted file mode 100644 index 4f33cba4ad..0000000000 --- a/extras/input_id/.gitignore +++ /dev/null @@ -1 +0,0 @@ -input_id diff --git a/extras/input_id/input_id b/extras/input_id/input_id Binary files differnew file mode 100755 index 0000000000..e215aad729 --- /dev/null +++ b/extras/input_id/input_id diff --git a/extras/input_id/input_id.c b/extras/input_id/input_id.c index ba53df01b0..e69de29bb2 100644 --- a/extras/input_id/input_id.c +++ b/extras/input_id/input_id.c @@ -1,281 +0,0 @@ -/* - * input_id - input device classification - * - * Copyright (C) 2009 Martin Pitt <martin.pitt@ubuntu.com> - * Portions Copyright (C) 2004 David Zeuthen, <david@fubar.dk> - * - * 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 keymap; if not, write to the Free Software Foundation, - * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - */ - -#include <stdio.h> -#include <string.h> -#include <stdlib.h> -#include <getopt.h> -#include <limits.h> -#include <linux/limits.h> -#include <linux/input.h> - -#include "libudev.h" -#include "libudev-private.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 int debug = 0; - -static void log_fn(struct udev *udev, int priority, - const char *file, int line, const char *fn, - const char *format, va_list args) -{ - if (debug) { - fprintf(stderr, "%s: ", fn); - vfprintf(stderr, format, args); - } else { - vsyslog(priority, format, args); - } -} - -/* - * 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, const char* attr, - unsigned long *bitmask, size_t bitmask_size) -{ - 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(dev, 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 (debug) { - /* 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 (const unsigned long* bitmask_ev, - const unsigned long* bitmask_abs, - const unsigned long* bitmask_key, - const unsigned long* bitmask_rel) -{ - 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)) - puts("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)) - puts("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)) - puts("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)) - puts("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) - puts("ID_INPUT_MOUSE=1"); - if (is_touchpad) - puts("ID_INPUT_TOUCHPAD=1"); -} - -/* key like devices */ -static void test_key (struct udev *udev, - const unsigned long* bitmask_ev, - const unsigned long* bitmask_key) -{ - 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) - puts("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) - puts("ID_INPUT_KEYBOARD=1"); -} - -static void help(void) -{ - printf("Usage: input_id [options] <device path>\n" - " --debug debug to stderr\n" - " --help print this help text\n\n"); -} - -int main (int argc, char** argv) -{ - struct udev *udev; - struct udev_device *dev; - - static const struct option options[] = { - { "debug", no_argument, NULL, 'd' }, - { "help", no_argument, NULL, 'h' }, - {} - }; - - char devpath[PATH_MAX]; - 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)]; - - udev = udev_new(); - if (udev == NULL) - return 1; - - udev_log_init("input_id"); - udev_set_log_fn(udev, log_fn); - - /* CLI argument parsing */ - while (1) { - int option; - - option = getopt_long(argc, argv, "dxh", options, NULL); - if (option == -1) - break; - - switch (option) { - case 'd': - debug = 1; - if (udev_get_log_priority(udev) < LOG_INFO) - udev_set_log_priority(udev, LOG_INFO); - break; - case 'h': - help(); - exit(0); - default: - exit(1); - } - } - - if (argv[optind] == NULL) { - help(); - exit(1); - } - - /* get the device */ - snprintf(devpath, sizeof(devpath), "%s/%s", udev_get_sys_path(udev), argv[optind]); - dev = udev_device_new_from_syspath(udev, devpath); - if (dev == NULL) { - fprintf(stderr, "unable to access '%s'\n", devpath); - return 1; - } - - /* walk up the parental chain until we find the real input device; the - * argument is very likely a subdevice of this, like eventN */ - while (dev != NULL && udev_device_get_sysattr_value(dev, "capabilities/ev") == NULL) - dev = udev_device_get_parent_with_subsystem_devtype(dev, "input", NULL); - - /* not an "input" class device */ - if (dev == NULL) - return 0; - - /* 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 */ - puts("ID_INPUT=1"); - - get_cap_mask (dev, "capabilities/ev", bitmask_ev, sizeof (bitmask_ev)); - get_cap_mask (dev, "capabilities/abs", bitmask_abs, sizeof (bitmask_abs)); - get_cap_mask (dev, "capabilities/rel", bitmask_rel, sizeof (bitmask_rel)); - get_cap_mask (dev, "capabilities/key", bitmask_key, sizeof (bitmask_key)); - - test_pointers(bitmask_ev, bitmask_abs, bitmask_key, bitmask_rel); - - test_key(udev, bitmask_ev, bitmask_key); - - return 0; -} diff --git a/extras/keymap/95-keymap.rules b/extras/keymap/95-keymap.rules index 6f1b99555e..0d9b771e2e 100644 --- a/extras/keymap/95-keymap.rules +++ b/extras/keymap/95-keymap.rules @@ -10,7 +10,7 @@ KERNEL!="event*", GOTO="keyboard_end" ENV{ID_INPUT_KEY}=="", GOTO="keyboard_end" SUBSYSTEMS=="bluetooth", GOTO="keyboard_end" -SUBSYSTEMS=="usb", ENV{ID_VENDOR}=="", IMPORT{program}="usb_id --export %p" +SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id" SUBSYSTEMS=="usb", GOTO="keyboard_usbcheck" GOTO="keyboard_modulecheck" diff --git a/extras/path_id/.gitignore b/extras/path_id/.gitignore deleted file mode 100644 index 6fd2f89761..0000000000 --- a/extras/path_id/.gitignore +++ /dev/null @@ -1 +0,0 @@ -path_id diff --git a/extras/path_id/path_id b/extras/path_id/path_id Binary files differnew file mode 100755 index 0000000000..567d508f1d --- /dev/null +++ b/extras/path_id/path_id diff --git a/extras/path_id/path_id.c b/extras/path_id/path_id.c index 928aa6737f..e69de29bb2 100644 --- a/extras/path_id/path_id.c +++ b/extras/path_id/path_id.c @@ -1,554 +0,0 @@ -/* - * compose persistent device path - * - * Copyright (C) 2009 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 "libudev.h" -#include "libudev-private.h" - -static int debug; - -static void log_fn(struct udev *udev, int priority, - const char *file, int line, const char *fn, - const char *format, va_list args) -{ - if (debug) { - fprintf(stderr, "%s: ", fn != NULL ? fn : file); - vfprintf(stderr, format, args); - } else { - vsyslog(priority, format, args); - } -} - -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; -} - -int main(int argc, char **argv) -{ - static const struct option options[] = { - { "debug", no_argument, NULL, 'd' }, - { "help", no_argument, NULL, 'h' }, - {} - }; - struct udev *udev; - struct udev_device *dev; - struct udev_device *parent; - char syspath[UTIL_PATH_SIZE]; - const char *devpath; - char *path = NULL; - int rc = EXIT_FAILURE; - - udev = udev_new(); - if (udev == NULL) - goto exit; - - udev_log_init("path_id"); - udev_set_log_fn(udev, log_fn); - - while (1) { - int option; - - option = getopt_long(argc, argv, "dh", options, NULL); - if (option == -1) - break; - - switch (option) { - case 'd': - debug = 1; - if (udev_get_log_priority(udev) < LOG_INFO) - udev_set_log_priority(udev, LOG_INFO); - break; - case 'h': - printf("Usage: path_id [--debug] [--help] <devpath>\n" - " --debug print debug information\n" - " --help print this help text\n\n"); - goto exit; - } - } - - devpath = argv[optind]; - if (devpath == NULL) { - fprintf(stderr, "No device specified\n"); - rc = 2; - goto exit; - } - - util_strscpyl(syspath, sizeof(syspath), udev_get_sys_path(udev), devpath, NULL); - dev = udev_device_new_from_syspath(udev, syspath); - if (dev == NULL) { - fprintf(stderr, "unable to access '%s'\n", devpath); - rc = 3; - goto exit; - } - - /* 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'; - - printf("ID_PATH=%s\n", path); - printf("ID_PATH_TAG=%s\n", tag); - free(path); - rc = EXIT_SUCCESS; - } - - udev_device_unref(dev); -exit: - udev_unref(udev); - udev_log_close(); - return rc; -} diff --git a/extras/usb_id/.gitignore b/extras/usb_id/.gitignore deleted file mode 100644 index 309b59fc3a..0000000000 --- a/extras/usb_id/.gitignore +++ /dev/null @@ -1 +0,0 @@ -usb_id diff --git a/extras/usb_id/usb_id b/extras/usb_id/usb_id Binary files differnew file mode 100755 index 0000000000..903702e876 --- /dev/null +++ b/extras/usb_id/usb_id diff --git a/extras/usb_id/usb_id.c b/extras/usb_id/usb_id.c index 3a7a0ffd49..e69de29bb2 100644 --- a/extras/usb_id/usb_id.c +++ b/extras/usb_id/usb_id.c @@ -1,576 +0,0 @@ -/* - * usb_id - identify an USB device - * - * Copyright (c) 2005 SUSE Linux Products GmbH, Germany - * - * Author: Hannes Reinecke <hare@suse.de> - * - * 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 <getopt.h> - -#include "libudev.h" -#include "libudev-private.h" - -int debug; - -static void log_fn(struct udev *udev, int priority, - const char *file, int line, const char *fn, - const char *format, va_list args) -{ - if (debug) { - fprintf(stderr, "%s: ", fn != NULL ? fn : file); - vfprintf(stderr, format, args); - } else { - vsyslog(priority, format, args); - } -} - -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 int use_usb_info; -static int use_num_info; - -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); - 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; - while (pos < sizeof(buf) && strpos+7 < len) { - 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; - } -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) && !use_usb_info) { - 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; - - if (!use_num_info) - 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; - - if (!use_num_info) - 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; -} - -int main(int argc, char **argv) -{ - static const struct option options[] = { - { "usb-info", no_argument, NULL, 'u' }, - { "num-info", no_argument, NULL, 'n' }, - { "export", no_argument, NULL, 'x' }, - { "debug", no_argument, NULL, 'd' }, - { "help", no_argument, NULL, 'h' }, - {} - }; - struct udev *udev; - struct udev_device *dev = NULL; - static int export; - int retval = 0; - - udev = udev_new(); - if (udev == NULL) - goto exit; - - udev_log_init("usb_id"); - udev_set_log_fn(udev, log_fn); - - while (1) { - int option; - - option = getopt_long(argc, argv, "dnuxh", options, NULL); - if (option == -1) - break; - - switch (option) { - case 'd': - debug = 1; - if (udev_get_log_priority(udev) < LOG_INFO) - udev_set_log_priority(udev, LOG_INFO); - break; - case 'n': - use_num_info = 1; - use_usb_info = 1; - break; - case 'u': - use_usb_info = 1; - break; - case 'x': - export = 1; - break; - case 'h': - printf("Usage: usb_id [--usb-info] [--num-info] [--export] [--help] [<devpath>]\n" - " --usb-info use usb strings instead\n" - " --num-info use numerical values\n" - " --export print values as environment keys\n" - " --help print this help text\n\n"); - goto exit; - } - } - - dev = udev_device_new_from_environment(udev); - if (dev == NULL) { - char syspath[UTIL_PATH_SIZE]; - const char *devpath; - - devpath = argv[optind]; - if (devpath == NULL) { - fprintf(stderr, "missing device\n"); - retval = 1; - goto exit; - } - - util_strscpyl(syspath, sizeof(syspath), udev_get_sys_path(udev), devpath, NULL); - dev = udev_device_new_from_syspath(udev, syspath); - if (dev == NULL) { - err(udev, "unable to access '%s'\n", devpath); - retval = 1; - goto exit; - return 1; - } - } - - retval = usb_id(dev); - if (retval == 0) { - char serial[256]; - size_t l; - char *s; - - 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); - - if (export) { - printf("ID_VENDOR=%s\n", vendor_str); - printf("ID_VENDOR_ENC=%s\n", vendor_str_enc); - printf("ID_VENDOR_ID=%s\n", vendor_id); - printf("ID_MODEL=%s\n", model_str); - printf("ID_MODEL_ENC=%s\n", model_str_enc); - printf("ID_MODEL_ID=%s\n", product_id); - printf("ID_REVISION=%s\n", revision_str); - printf("ID_SERIAL=%s\n", serial); - if (serial_str[0] != '\0') - printf("ID_SERIAL_SHORT=%s\n", serial_str); - if (type_str[0] != '\0') - printf("ID_TYPE=%s\n", type_str); - if (instance_str[0] != '\0') - printf("ID_INSTANCE=%s\n", instance_str); - printf("ID_BUS=usb\n"); - if (packed_if_str[0] != '\0') - printf("ID_USB_INTERFACES=:%s\n", packed_if_str); - if (ifnum != NULL) - printf("ID_USB_INTERFACE_NUM=%s\n", ifnum); - if (driver != NULL) - printf("ID_USB_DRIVER=%s\n", driver); - } else - printf("%s\n", serial); - } - -exit: - udev_device_unref(dev); - udev_unref(udev); - udev_log_close(); - return retval; -} diff --git a/extras/v4l_id/60-persistent-v4l.rules b/extras/v4l_id/60-persistent-v4l.rules index b6e1313a31..edfd844753 100644 --- a/extras/v4l_id/60-persistent-v4l.rules +++ b/extras/v4l_id/60-persistent-v4l.rules @@ -6,14 +6,14 @@ ENV{MAJOR}=="", GOTO="persistent_v4l_end" IMPORT{program}="v4l_id $tempnode" -SUBSYSTEMS=="usb", IMPORT{program}="usb_id --export %p" +SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id" KERNEL=="video*", ENV{ID_SERIAL}=="?*", SYMLINK+="v4l/by-id/$env{ID_BUS}-$env{ID_SERIAL}-video-index$attr{index}" # check for valid "index" number TEST!="index", GOTO="persistent_v4l_end" ATTR{index}!="?*", GOTO="persistent_v4l_end" -IMPORT{program}="path_id %p" +IMPORT{builtin}="path_id" ENV{ID_PATH}=="?*", KERNEL=="video*|vbi*", SYMLINK+="v4l/by-path/$env{ID_PATH}-video-index$attr{index}" ENV{ID_PATH}=="?*", KERNEL=="audio*", SYMLINK+="v4l/by-path/$env{ID_PATH}-audio-index$attr{index}" diff --git a/libudev/docs/.gitignore b/libudev/docs/.gitignore index 6fe7fd15d0..286f244fa2 100644 --- a/libudev/docs/.gitignore +++ b/libudev/docs/.gitignore @@ -1,3 +1,4 @@ +libudev-overrides.txt html/ tmpl/ xml/ diff --git a/libudev/docs/libudev-overrides.txt b/libudev/docs/libudev-overrides.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/libudev/docs/libudev-overrides.txt diff --git a/rules/rules.d/50-udev-default.rules b/rules/rules.d/50-udev-default.rules index 30267fee63..7c4046284e 100644 --- a/rules/rules.d/50-udev-default.rules +++ b/rules/rules.d/50-udev-default.rules @@ -20,7 +20,7 @@ KERNEL=="null|zero|full|random|urandom", MODE="0666" KERNEL=="mem|kmem|port|nvram", GROUP="kmem", MODE="0640" # input -SUBSYSTEM=="input", ENV{ID_INPUT}=="", IMPORT{program}="input_id %p" +SUBSYSTEM=="input", ENV{ID_INPUT}=="", IMPORT{builtin}="input_id" KERNEL=="mouse*|mice|event*", MODE="0640" KERNEL=="ts[0-9]*|uinput", MODE="0640" KERNEL=="js[0-9]*", MODE="0644" @@ -53,7 +53,7 @@ SUBSYSTEM=="firewire", ATTR{units}=="*0x00a02d:0x014001*", GROUP="video" # 'libusb' device nodes SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", MODE="0664" -SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{ID_USB_INTERFACES}=="", IMPORT{program}="usb_id --export %p" +SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", IMPORT{builtin}="usb_id" # printer KERNEL=="parport[0-9]*", GROUP="lp" diff --git a/rules/rules.d/60-persistent-alsa.rules b/rules/rules.d/60-persistent-alsa.rules index 748c1bae59..8154e2dbb5 100644 --- a/rules/rules.d/60-persistent-alsa.rules +++ b/rules/rules.d/60-persistent-alsa.rules @@ -4,11 +4,11 @@ ACTION=="remove", GOTO="persistent_alsa_end" SUBSYSTEM!="sound", GOTO="persistent_alsa_end" KERNEL!="controlC[0-9]*", GOTO="persistent_alsa_end" -SUBSYSTEMS=="usb", ENV{ID_MODEL}=="", IMPORT{program}="usb_id --export %p" +SUBSYSTEMS=="usb", ENV{ID_MODEL}=="", IMPORT{builtin}="usb_id" ENV{ID_SERIAL}=="?*", ENV{ID_USB_INTERFACE_NUM}=="?*", SYMLINK+="snd/by-id/$env{ID_BUS}-$env{ID_SERIAL}-$env{ID_USB_INTERFACE_NUM}" ENV{ID_SERIAL}=="?*", ENV{ID_USB_INTERFACE_NUM}=="", SYMLINK+="snd/by-id/$env{ID_BUS}-$env{ID_SERIAL}" -ENV{ID_PATH}=="", IMPORT{program}="path_id %p" +IMPORT{builtin}="path_id" ENV{ID_PATH}=="?*", SYMLINK+="snd/by-path/$env{ID_PATH}" LABEL="persistent_alsa_end" diff --git a/rules/rules.d/60-persistent-input.rules b/rules/rules.d/60-persistent-input.rules index e7611f5706..fb798ddb05 100644 --- a/rules/rules.d/60-persistent-input.rules +++ b/rules/rules.d/60-persistent-input.rules @@ -4,7 +4,7 @@ ACTION=="remove", GOTO="persistent_input_end" SUBSYSTEM!="input", GOTO="persistent_input_end" SUBSYSTEMS=="bluetooth", GOTO="persistent_input_end" -SUBSYSTEMS=="usb", ENV{ID_BUS}=="", IMPORT{program}="usb_id --export %p" +SUBSYSTEMS=="usb", ENV{ID_BUS}=="", IMPORT{builtin}="usb_id" # determine class name for persistent symlinks ENV{ID_INPUT_KEYBOARD}=="?*", ENV{.INPUT_CLASS}="kbd" @@ -28,7 +28,7 @@ SUBSYSTEMS=="usb", ENV{ID_BUS}=="?*", KERNEL=="event*", ENV{.INPUT_CLASS}=="", A SYMLINK+="input/by-id/$env{ID_BUS}-$env{ID_SERIAL}-event-if$attr{bInterfaceNumber}" # by-path -SUBSYSTEMS=="pci|usb|platform|acpi", IMPORT{program}="path_id %p" +SUBSYSTEMS=="pci|usb|platform|acpi", IMPORT{builtin}="path_id" ENV{ID_PATH}=="?*", KERNEL=="mouse*|js*", ENV{.INPUT_CLASS}=="?*", SYMLINK+="input/by-path/$env{ID_PATH}-$env{.INPUT_CLASS}" ENV{ID_PATH}=="?*", KERNEL=="event*", ENV{.INPUT_CLASS}=="?*", SYMLINK+="input/by-path/$env{ID_PATH}-event-$env{.INPUT_CLASS}" # allow empty class for platform and usb devices; platform supports only a single interface that way diff --git a/rules/rules.d/60-persistent-serial.rules b/rules/rules.d/60-persistent-serial.rules index 90816be679..2948200c53 100644 --- a/rules/rules.d/60-persistent-serial.rules +++ b/rules/rules.d/60-persistent-serial.rules @@ -6,11 +6,11 @@ KERNEL!="ttyUSB[0-9]*|ttyACM[0-9]*", GOTO="persistent_serial_end" SUBSYSTEMS=="usb-serial", ENV{.ID_PORT}="$attr{port_number}" -IMPORT="path_id %p" +IMPORT{builtin}="path_id" ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="", SYMLINK+="serial/by-path/$env{ID_PATH}" ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="?*", SYMLINK+="serial/by-path/$env{ID_PATH}-port$env{.ID_PORT}" -IMPORT="usb_id --export %p" +IMPORT{builtin}="usb_id" ENV{ID_SERIAL}=="", GOTO="persistent_serial_end" SUBSYSTEMS=="usb", ENV{ID_USB_INTERFACE_NUM}="$attr{bInterfaceNumber}" ENV{ID_USB_INTERFACE_NUM}=="", GOTO="persistent_serial_end" diff --git a/rules/rules.d/60-persistent-storage-tape.rules b/rules/rules.d/60-persistent-storage-tape.rules index 6ccd6e51e2..b1a64cee12 100644 --- a/rules/rules.d/60-persistent-storage-tape.rules +++ b/rules/rules.d/60-persistent-storage-tape.rules @@ -5,19 +5,20 @@ ACTION=="remove", GOTO="persistent_storage_tape_end" # type 8 devices are "Medium Changers" -SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --whitelisted -d $tempnode", SYMLINK+="tape/by-id/scsi-$env{ID_SERIAL}" +SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --whitelisted -d $tempnode", \ + SYMLINK+="tape/by-id/scsi-$env{ID_SERIAL}" SUBSYSTEM!="scsi_tape", GOTO="persistent_storage_tape_end" KERNEL=="st*[0-9]|nst*[0-9]", ATTRS{ieee1394_id}=="?*", ENV{ID_SERIAL}="$attr{ieee1394_id}", ENV{ID_BUS}="ieee1394" -KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{program}="usb_id --export %p" +KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id" KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", KERNELS=="[0-9]*:*[0-9]", ENV{.BSG_DEV}="$root/bsg/$id" -KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", IMPORT="scsi_id --whitelisted --export --device=$env{.BSG_DEV}", ENV{ID_BUS}="scsi" +KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --whitelisted --export --device=$env{.BSG_DEV}", ENV{ID_BUS}="scsi" KERNEL=="st*[0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SERIAL}" KERNEL=="nst*[0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SERIAL}-nst" # by-path (parent device path) -KERNEL=="st*[0-9]|nst*[0-9]", IMPORT{program}="path_id %p" +KERNEL=="st*[0-9]|nst*[0-9]", IMPORT{builtin}="path_id" KERNEL=="st*[0-9]", ENV{ID_PATH}=="?*", SYMLINK+="tape/by-path/$env{ID_PATH}" KERNEL=="nst*[0-9]", ENV{ID_PATH}=="?*", SYMLINK+="tape/by-path/$env{ID_PATH}-nst" diff --git a/rules/rules.d/60-persistent-storage.rules b/rules/rules.d/60-persistent-storage.rules index 5c2689b885..894b50ba8e 100644 --- a/rules/rules.d/60-persistent-storage.rules +++ b/rules/rules.d/60-persistent-storage.rules @@ -36,7 +36,7 @@ KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{type}== # Run ata_id on non-removable USB Mass Storage (SATA/PATA disks in enclosures) KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", ATTR{removable}=="0", SUBSYSTEMS=="usb", IMPORT{program}="ata_id --export $tempnode" # Otherwise fall back to using usb_id for USB devices -KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{program}="usb_id --export %p" +KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id" # scsi devices KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $tempnode", ENV{ID_BUS}="scsi" @@ -58,7 +58,7 @@ KERNEL=="mspblk[0-9]", SUBSYSTEMS=="memstick", ATTRS{name}=="?*", ATTRS{serial}= KERNEL=="mspblk[0-9]p[0-9]", ENV{ID_NAME}=="?*", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/memstick-$env{ID_NAME}_$env{ID_SERIAL}-part%n" # by-path (parent device path) -ENV{DEVTYPE}=="disk", ENV{ID_PATH}=="", DEVPATH!="*/virtual/*", IMPORT{program}="path_id %p" +ENV{DEVTYPE}=="disk", DEVPATH!="*/virtual/*", IMPORT{builtin}="path_id" ENV{DEVTYPE}=="disk", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}" ENV{DEVTYPE}=="partition", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}-part%n" diff --git a/rules/rules.d/75-net-description.rules b/rules/rules.d/75-net-description.rules index 490fcced1e..0ffce2c348 100644 --- a/rules/rules.d/75-net-description.rules +++ b/rules/rules.d/75-net-description.rules @@ -3,7 +3,7 @@ ACTION=="remove", GOTO="net_end" SUBSYSTEM!="net", GOTO="net_end" -SUBSYSTEMS=="usb", ENV{ID_MODEL}=="", IMPORT{program}="usb_id --export %p" +SUBSYSTEMS=="usb", ENV{ID_MODEL}=="", IMPORT{builtin}="usb_id" SUBSYSTEMS=="usb", ENV{ID_MODEL_FROM_DATABASE}=="", IMPORT{program}="usb-db %p" SUBSYSTEMS=="usb", ATTRS{idVendor}!="", ATTRS{idProduct}!="", ENV{ID_VENDOR_ID}="$attr{idVendor}", ENV{ID_MODEL_ID}="$attr{idProduct}" SUBSYSTEMS=="usb", GOTO="net_end" diff --git a/rules/rules.d/75-tty-description.rules b/rules/rules.d/75-tty-description.rules index c2980cfb69..b67c857dfd 100644 --- a/rules/rules.d/75-tty-description.rules +++ b/rules/rules.d/75-tty-description.rules @@ -3,7 +3,7 @@ ACTION=="remove", GOTO="tty_end" SUBSYSTEM!="tty", GOTO="tty_end" -SUBSYSTEMS=="usb", ENV{ID_MODEL}=="", IMPORT{program}="usb_id --export %p" +SUBSYSTEMS=="usb", ENV{ID_MODEL}=="", IMPORT{builtin}="usb_id" SUBSYSTEMS=="usb", ENV{ID_MODEL_FROM_DATABASE}=="", IMPORT{program}="usb-db %p" SUBSYSTEMS=="usb", ATTRS{idVendor}!="", ATTRS{idProduct}!="", ENV{ID_VENDOR_ID}="$attr{idVendor}", ENV{ID_MODEL_ID}="$attr{idProduct}" SUBSYSTEMS=="usb", GOTO="tty_end" diff --git a/rules/rules.d/78-sound-card.rules b/rules/rules.d/78-sound-card.rules index 1c36aeb432..e3a13b598d 100644 --- a/rules/rules.d/78-sound-card.rules +++ b/rules/rules.d/78-sound-card.rules @@ -37,7 +37,7 @@ KERNEL!="card*", GOTO="sound_end" ENV{SOUND_INITIALIZED}="1" -SUBSYSTEMS=="usb", ENV{ID_MODEL}=="", IMPORT{program}="usb_id --export %p" +SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id" SUBSYSTEMS=="usb", ENV{ID_VENDOR_FROM_DATABASE}=="", IMPORT{program}="usb-db %p" SUBSYSTEMS=="usb", GOTO="skip_pci" @@ -49,7 +49,7 @@ LABEL="skip_pci" ENV{ID_SERIAL}=="?*", ENV{ID_USB_INTERFACE_NUM}=="?*", ENV{ID_ID}="$env{ID_BUS}-$env{ID_SERIAL}-$env{ID_USB_INTERFACE_NUM}-$attr{id}" ENV{ID_SERIAL}=="?*", ENV{ID_USB_INTERFACE_NUM}=="", ENV{ID_ID}="$env{ID_BUS}-$env{ID_SERIAL}-$attr{id}" -ENV{ID_PATH}=="", IMPORT{program}="path_id %p/controlC%n" +IMPORT{builtin}="path_id" # The values used here for $SOUND_FORM_FACTOR and $SOUND_CLASS should be kept # in sync with those defined for PulseAudio's src/pulse/proplist.h diff --git a/test/udev-test.pl b/test/udev-test.pl index 4322da5ecd..b7592efcbd 100755 --- a/test/udev-test.pl +++ b/test/udev-test.pl @@ -292,7 +292,7 @@ EOF devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", exp_name => "node12345678", rules => <<EOF -SUBSYSTEMS=="scsi", IMPORT="/bin/echo -e \' TEST_KEY=12345678\\n TEST_key2=98765\'", SYMLINK+="node\$env{TEST_KEY}" +SUBSYSTEMS=="scsi", IMPORT{program}="/bin/echo -e \' TEST_KEY=12345678\\n TEST_key2=98765\'", SYMLINK+="node\$env{TEST_KEY}" KERNEL=="ttyACM0", SYMLINK+="modem" EOF }, @@ -1330,7 +1330,7 @@ EOF exp_name => "parent", option => "keep", rules => <<EOF -KERNEL=="sda", IMPORT="/bin/echo -e \'PARENT_KEY=parent_right\\nWRONG_PARENT_KEY=parent_wrong'" +KERNEL=="sda", IMPORT{program}="/bin/echo -e \'PARENT_KEY=parent_right\\nWRONG_PARENT_KEY=parent_wrong'" KERNEL=="sda", SYMLINK+="parent" EOF }, @@ -1506,6 +1506,16 @@ KERNEL=="sda", ENV{TESTENV}="change-envp" KERNEL=="sda", SYMLINK+="%k-%s{[dmi/id]product_name}-end", RUN+="socket:@/org/kernel/udev/monitor" EOF }, + { + desc => "builtin path_id", + subsys => "block", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0", + rules => <<EOF +KERNEL=="sda", IMPORT{builtin}="path_id" +KERNEL=="sda", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/\$env{ID_PATH}" +EOF + }, ); # set env 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> |