summaryrefslogtreecommitdiff
path: root/src/grp-udev
diff options
context:
space:
mode:
Diffstat (limited to 'src/grp-udev')
-rw-r--r--src/grp-udev/ata_id/ata_id.c674
-rw-r--r--src/grp-udev/cdrom_id/cdrom_id.c1085
-rw-r--r--src/grp-udev/rules/.gitignore1
-rw-r--r--src/grp-udev/rules/50-udev-default.rules77
-rw-r--r--src/grp-udev/rules/60-block.rules11
-rw-r--r--src/grp-udev/rules/60-cdrom_id.rules25
-rw-r--r--src/grp-udev/rules/60-drm.rules3
-rw-r--r--src/grp-udev/rules/60-evdev.rules19
-rw-r--r--src/grp-udev/rules/60-persistent-alsa.rules14
-rw-r--r--src/grp-udev/rules/60-persistent-input.rules38
-rw-r--r--src/grp-udev/rules/60-persistent-storage-tape.rules25
-rw-r--r--src/grp-udev/rules/60-persistent-storage.rules87
-rw-r--r--src/grp-udev/rules/60-persistent-v4l.rules20
-rw-r--r--src/grp-udev/rules/60-serial.rules26
-rw-r--r--src/grp-udev/rules/64-btrfs.rules13
-rw-r--r--src/grp-udev/rules/70-mouse.rules18
-rw-r--r--src/grp-udev/rules/75-net-description.rules14
-rw-r--r--src/grp-udev/rules/75-probe_mtd.rules7
-rw-r--r--src/grp-udev/rules/78-sound-card.rules89
-rw-r--r--src/grp-udev/rules/80-drivers.rules13
-rw-r--r--src/grp-udev/rules/80-net-setup-link.rules13
-rw-r--r--src/grp-udev/rules/99-systemd.rules.in67
-rw-r--r--src/grp-udev/scsi_id/.gitignore1
-rw-r--r--src/grp-udev/scsi_id/README4
-rw-r--r--src/grp-udev/scsi_id/scsi.h99
-rw-r--r--src/grp-udev/scsi_id/scsi_id.c625
-rw-r--r--src/grp-udev/scsi_id/scsi_id.h75
-rw-r--r--src/grp-udev/scsi_id/scsi_serial.c964
-rw-r--r--src/grp-udev/systemd-hwdb/hwdb.c739
-rw-r--r--src/grp-udev/systemd-hwdb/systemd-hwdb.xml93
-rw-r--r--src/grp-udev/systemd-udevd/systemd-udevd.service.in26
-rw-r--r--src/grp-udev/systemd-udevd/udevd.c1765
-rw-r--r--src/grp-udev/udevadm/udevadm-control.c163
-rw-r--r--src/grp-udev/udevadm/udevadm-hwdb.c692
-rw-r--r--src/grp-udev/udevadm/udevadm-info.c494
-rw-r--r--src/grp-udev/udevadm/udevadm-monitor.c278
-rw-r--r--src/grp-udev/udevadm/udevadm-settle.c162
-rw-r--r--src/grp-udev/udevadm/udevadm-test-builtin.c112
-rw-r--r--src/grp-udev/udevadm/udevadm-test.c160
-rw-r--r--src/grp-udev/udevadm/udevadm-trigger.c286
-rw-r--r--src/grp-udev/udevadm/udevadm-util.c50
-rw-r--r--src/grp-udev/udevadm/udevadm-util.h24
-rw-r--r--src/grp-udev/udevadm/udevadm.c137
-rw-r--r--src/grp-udev/udevadm/udevadm.completion.bash97
-rw-r--r--src/grp-udev/udevadm/udevadm.completion.zsh141
-rw-r--r--src/grp-udev/udevadm/udevadm.xml576
-rw-r--r--src/grp-udev/v4l_id/v4l_id.c86
47 files changed, 10188 insertions, 0 deletions
diff --git a/src/grp-udev/ata_id/ata_id.c b/src/grp-udev/ata_id/ata_id.c
new file mode 100644
index 0000000000..1e414664ce
--- /dev/null
+++ b/src/grp-udev/ata_id/ata_id.c
@@ -0,0 +1,674 @@
+/*
+ * ata_id - reads product/serial number from ATA drives
+ *
+ * Copyright (C) 2005-2008 Kay Sievers <kay@vrfy.org>
+ * Copyright (C) 2009 Lennart Poettering <lennart@poettering.net>
+ * Copyright (C) 2009-2010 David Zeuthen <zeuthen@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <linux/bsg.h>
+#include <linux/hdreg.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_ioctl.h>
+#include <scsi/sg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "libudev.h"
+
+#include "fd-util.h"
+#include "libudev-private.h"
+#include "log.h"
+#include "udev-util.h"
+
+#define COMMAND_TIMEOUT_MSEC (30 * 1000)
+
+static int disk_scsi_inquiry_command(int fd,
+ void *buf,
+ size_t buf_len)
+{
+ uint8_t cdb[6] = {
+ /*
+ * INQUIRY, see SPC-4 section 6.4
+ */
+ [0] = 0x12, /* OPERATION CODE: INQUIRY */
+ [3] = (buf_len >> 8), /* ALLOCATION LENGTH */
+ [4] = (buf_len & 0xff),
+ };
+ uint8_t sense[32] = {};
+ struct sg_io_v4 io_v4 = {
+ .guard = 'Q',
+ .protocol = BSG_PROTOCOL_SCSI,
+ .subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD,
+ .request_len = sizeof(cdb),
+ .request = (uintptr_t) cdb,
+ .max_response_len = sizeof(sense),
+ .response = (uintptr_t) sense,
+ .din_xfer_len = buf_len,
+ .din_xferp = (uintptr_t) buf,
+ .timeout = COMMAND_TIMEOUT_MSEC,
+ };
+ int ret;
+
+ ret = ioctl(fd, SG_IO, &io_v4);
+ if (ret != 0) {
+ /* could be that the driver doesn't do version 4, try version 3 */
+ if (errno == EINVAL) {
+ struct sg_io_hdr io_hdr = {
+ .interface_id = 'S',
+ .cmdp = (unsigned char*) cdb,
+ .cmd_len = sizeof (cdb),
+ .dxferp = buf,
+ .dxfer_len = buf_len,
+ .sbp = sense,
+ .mx_sb_len = sizeof(sense),
+ .dxfer_direction = SG_DXFER_FROM_DEV,
+ .timeout = COMMAND_TIMEOUT_MSEC,
+ };
+
+ ret = ioctl(fd, SG_IO, &io_hdr);
+ if (ret != 0)
+ return ret;
+
+ /* even if the ioctl succeeds, we need to check the return value */
+ if (!(io_hdr.status == 0 &&
+ io_hdr.host_status == 0 &&
+ io_hdr.driver_status == 0)) {
+ errno = EIO;
+ return -1;
+ }
+ } else
+ return ret;
+ }
+
+ /* even if the ioctl succeeds, we need to check the return value */
+ if (!(io_v4.device_status == 0 &&
+ io_v4.transport_status == 0 &&
+ io_v4.driver_status == 0)) {
+ errno = EIO;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int disk_identify_command(int fd,
+ void *buf,
+ size_t buf_len)
+{
+ uint8_t cdb[12] = {
+ /*
+ * ATA Pass-Through 12 byte command, as described in
+ *
+ * T10 04-262r8 ATA Command Pass-Through
+ *
+ * from http://www.t10.org/ftp/t10/document.04/04-262r8.pdf
+ */
+ [0] = 0xa1, /* OPERATION CODE: 12 byte pass through */
+ [1] = 4 << 1, /* PROTOCOL: PIO Data-in */
+ [2] = 0x2e, /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */
+ [3] = 0, /* FEATURES */
+ [4] = 1, /* SECTORS */
+ [5] = 0, /* LBA LOW */
+ [6] = 0, /* LBA MID */
+ [7] = 0, /* LBA HIGH */
+ [8] = 0 & 0x4F, /* SELECT */
+ [9] = 0xEC, /* Command: ATA IDENTIFY DEVICE */
+ };
+ uint8_t sense[32] = {};
+ uint8_t *desc = sense + 8;
+ struct sg_io_v4 io_v4 = {
+ .guard = 'Q',
+ .protocol = BSG_PROTOCOL_SCSI,
+ .subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD,
+ .request_len = sizeof(cdb),
+ .request = (uintptr_t) cdb,
+ .max_response_len = sizeof(sense),
+ .response = (uintptr_t) sense,
+ .din_xfer_len = buf_len,
+ .din_xferp = (uintptr_t) buf,
+ .timeout = COMMAND_TIMEOUT_MSEC,
+ };
+ int ret;
+
+ ret = ioctl(fd, SG_IO, &io_v4);
+ if (ret != 0) {
+ /* could be that the driver doesn't do version 4, try version 3 */
+ if (errno == EINVAL) {
+ struct sg_io_hdr io_hdr = {
+ .interface_id = 'S',
+ .cmdp = (unsigned char*) cdb,
+ .cmd_len = sizeof (cdb),
+ .dxferp = buf,
+ .dxfer_len = buf_len,
+ .sbp = sense,
+ .mx_sb_len = sizeof (sense),
+ .dxfer_direction = SG_DXFER_FROM_DEV,
+ .timeout = COMMAND_TIMEOUT_MSEC,
+ };
+
+ ret = ioctl(fd, SG_IO, &io_hdr);
+ if (ret != 0)
+ return ret;
+ } else
+ return ret;
+ }
+
+ if (!(sense[0] == 0x72 && desc[0] == 0x9 && desc[1] == 0x0c)) {
+ errno = EIO;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int disk_identify_packet_device_command(int fd,
+ void *buf,
+ size_t buf_len)
+{
+ uint8_t cdb[16] = {
+ /*
+ * ATA Pass-Through 16 byte command, as described in
+ *
+ * T10 04-262r8 ATA Command Pass-Through
+ *
+ * from http://www.t10.org/ftp/t10/document.04/04-262r8.pdf
+ */
+ [0] = 0x85, /* OPERATION CODE: 16 byte pass through */
+ [1] = 4 << 1, /* PROTOCOL: PIO Data-in */
+ [2] = 0x2e, /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */
+ [3] = 0, /* FEATURES */
+ [4] = 0, /* FEATURES */
+ [5] = 0, /* SECTORS */
+ [6] = 1, /* SECTORS */
+ [7] = 0, /* LBA LOW */
+ [8] = 0, /* LBA LOW */
+ [9] = 0, /* LBA MID */
+ [10] = 0, /* LBA MID */
+ [11] = 0, /* LBA HIGH */
+ [12] = 0, /* LBA HIGH */
+ [13] = 0, /* DEVICE */
+ [14] = 0xA1, /* Command: ATA IDENTIFY PACKET DEVICE */
+ [15] = 0, /* CONTROL */
+ };
+ uint8_t sense[32] = {};
+ uint8_t *desc = sense + 8;
+ struct sg_io_v4 io_v4 = {
+ .guard = 'Q',
+ .protocol = BSG_PROTOCOL_SCSI,
+ .subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD,
+ .request_len = sizeof (cdb),
+ .request = (uintptr_t) cdb,
+ .max_response_len = sizeof (sense),
+ .response = (uintptr_t) sense,
+ .din_xfer_len = buf_len,
+ .din_xferp = (uintptr_t) buf,
+ .timeout = COMMAND_TIMEOUT_MSEC,
+ };
+ int ret;
+
+ ret = ioctl(fd, SG_IO, &io_v4);
+ if (ret != 0) {
+ /* could be that the driver doesn't do version 4, try version 3 */
+ if (errno == EINVAL) {
+ struct sg_io_hdr io_hdr = {
+ .interface_id = 'S',
+ .cmdp = (unsigned char*) cdb,
+ .cmd_len = sizeof (cdb),
+ .dxferp = buf,
+ .dxfer_len = buf_len,
+ .sbp = sense,
+ .mx_sb_len = sizeof (sense),
+ .dxfer_direction = SG_DXFER_FROM_DEV,
+ .timeout = COMMAND_TIMEOUT_MSEC,
+ };
+
+ ret = ioctl(fd, SG_IO, &io_hdr);
+ if (ret != 0)
+ return ret;
+ } else
+ return ret;
+ }
+
+ if (!(sense[0] == 0x72 && desc[0] == 0x9 && desc[1] == 0x0c)) {
+ errno = EIO;
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * disk_identify_get_string:
+ * @identify: A block of IDENTIFY data
+ * @offset_words: Offset of the string to get, in words.
+ * @dest: Destination buffer for the string.
+ * @dest_len: Length of destination buffer, in bytes.
+ *
+ * Copies the ATA string from @identify located at @offset_words into @dest.
+ */
+static void disk_identify_get_string(uint8_t identify[512],
+ unsigned int offset_words,
+ char *dest,
+ size_t dest_len)
+{
+ unsigned int c1;
+ unsigned int c2;
+
+ while (dest_len > 0) {
+ c1 = identify[offset_words * 2 + 1];
+ c2 = identify[offset_words * 2];
+ *dest = c1;
+ dest++;
+ *dest = c2;
+ dest++;
+ offset_words++;
+ dest_len -= 2;
+ }
+}
+
+static void disk_identify_fixup_string(uint8_t identify[512],
+ unsigned int offset_words,
+ size_t len)
+{
+ disk_identify_get_string(identify, offset_words,
+ (char *) identify + offset_words * 2, len);
+}
+
+static void disk_identify_fixup_uint16 (uint8_t identify[512], unsigned int offset_words)
+{
+ uint16_t *p;
+
+ p = (uint16_t *) identify;
+ p[offset_words] = le16toh (p[offset_words]);
+}
+
+/**
+ * disk_identify:
+ * @udev: The libudev context.
+ * @fd: File descriptor for the block device.
+ * @out_identify: Return location for IDENTIFY data.
+ * @out_is_packet_device: Return location for whether returned data is from a IDENTIFY PACKET DEVICE.
+ *
+ * Sends the IDENTIFY DEVICE or IDENTIFY PACKET DEVICE command to the
+ * device represented by @fd. If successful, then the result will be
+ * copied into @out_identify and @out_is_packet_device.
+ *
+ * This routine is based on code from libatasmart, Copyright 2008
+ * Lennart Poettering, LGPL v2.1.
+ *
+ * Returns: 0 if the data was successfully obtained, otherwise
+ * non-zero with errno set.
+ */
+static int disk_identify(struct udev *udev,
+ int fd,
+ uint8_t out_identify[512],
+ int *out_is_packet_device)
+{
+ int ret;
+ uint8_t inquiry_buf[36];
+ int peripheral_device_type;
+ int all_nul_bytes;
+ int n;
+ int is_packet_device = 0;
+
+ /* init results */
+ memzero(out_identify, 512);
+
+ /* If we were to use ATA PASS_THROUGH (12) on an ATAPI device
+ * we could accidentally blank media. This is because MMC's BLANK
+ * command has the same op-code (0x61).
+ *
+ * To prevent this from happening we bail out if the device
+ * isn't a Direct Access Block Device, e.g. SCSI type 0x00
+ * (CD/DVD devices are type 0x05). So we send a SCSI INQUIRY
+ * command first... libata is handling this via its SCSI
+ * emulation layer.
+ *
+ * This also ensures that we're actually dealing with a device
+ * that understands SCSI commands.
+ *
+ * (Yes, it is a bit perverse that we're tunneling the ATA
+ * command through SCSI and relying on the ATA driver
+ * emulating SCSI well-enough...)
+ *
+ * (See commit 160b069c25690bfb0c785994c7c3710289179107 for
+ * the original bug-fix and see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=556635
+ * for the original bug-report.)
+ */
+ ret = disk_scsi_inquiry_command (fd, inquiry_buf, sizeof (inquiry_buf));
+ if (ret != 0)
+ goto out;
+
+ /* SPC-4, section 6.4.2: Standard INQUIRY data */
+ peripheral_device_type = inquiry_buf[0] & 0x1f;
+ if (peripheral_device_type == 0x05)
+ {
+ is_packet_device = 1;
+ ret = disk_identify_packet_device_command(fd, out_identify, 512);
+ goto check_nul_bytes;
+ }
+ if (peripheral_device_type != 0x00) {
+ ret = -1;
+ errno = EIO;
+ goto out;
+ }
+
+ /* OK, now issue the IDENTIFY DEVICE command */
+ ret = disk_identify_command(fd, out_identify, 512);
+ if (ret != 0)
+ goto out;
+
+ check_nul_bytes:
+ /* Check if IDENTIFY data is all NUL bytes - if so, bail */
+ all_nul_bytes = 1;
+ for (n = 0; n < 512; n++) {
+ if (out_identify[n] != '\0') {
+ all_nul_bytes = 0;
+ break;
+ }
+ }
+
+ if (all_nul_bytes) {
+ ret = -1;
+ errno = EIO;
+ goto out;
+ }
+
+out:
+ if (out_is_packet_device != NULL)
+ *out_is_packet_device = is_packet_device;
+ return ret;
+}
+
+int main(int argc, char *argv[])
+{
+ _cleanup_udev_unref_ struct udev *udev = NULL;
+ struct hd_driveid id;
+ union {
+ uint8_t byte[512];
+ uint16_t wyde[256];
+ } identify;
+ char model[41];
+ char model_enc[256];
+ char serial[21];
+ char revision[9];
+ const char *node = NULL;
+ int export = 0;
+ _cleanup_close_ int fd = -1;
+ uint16_t word;
+ int is_packet_device = 0;
+ static const struct option options[] = {
+ { "export", no_argument, NULL, 'x' },
+ { "help", no_argument, NULL, 'h' },
+ {}
+ };
+
+ log_parse_environment();
+ log_open();
+
+ udev = udev_new();
+ if (udev == NULL)
+ return 0;
+
+ for (;;) {
+ int option;
+
+ option = getopt_long(argc, argv, "xh", options, NULL);
+ if (option == -1)
+ break;
+
+ switch (option) {
+ case 'x':
+ export = 1;
+ break;
+ case 'h':
+ printf("Usage: ata_id [--export] [--help] <device>\n"
+ " -x,--export print values as environment keys\n"
+ " -h,--help print this help text\n\n");
+ return 0;
+ }
+ }
+
+ node = argv[optind];
+ if (node == NULL) {
+ log_error("no node specified");
+ return 1;
+ }
+
+ fd = open(node, O_RDONLY|O_NONBLOCK|O_CLOEXEC);
+ if (fd < 0) {
+ log_error("unable to open '%s'", node);
+ return 1;
+ }
+
+ if (disk_identify(udev, fd, identify.byte, &is_packet_device) == 0) {
+ /*
+ * fix up only the fields from the IDENTIFY data that we are going to
+ * use and copy it into the hd_driveid struct for convenience
+ */
+ disk_identify_fixup_string(identify.byte, 10, 20); /* serial */
+ disk_identify_fixup_string(identify.byte, 23, 8); /* fwrev */
+ disk_identify_fixup_string(identify.byte, 27, 40); /* model */
+ disk_identify_fixup_uint16(identify.byte, 0); /* configuration */
+ disk_identify_fixup_uint16(identify.byte, 75); /* queue depth */
+ disk_identify_fixup_uint16(identify.byte, 76); /* SATA capabilities */
+ disk_identify_fixup_uint16(identify.byte, 82); /* command set supported */
+ disk_identify_fixup_uint16(identify.byte, 83); /* command set supported */
+ disk_identify_fixup_uint16(identify.byte, 84); /* command set supported */
+ disk_identify_fixup_uint16(identify.byte, 85); /* command set supported */
+ disk_identify_fixup_uint16(identify.byte, 86); /* command set supported */
+ disk_identify_fixup_uint16(identify.byte, 87); /* command set supported */
+ disk_identify_fixup_uint16(identify.byte, 89); /* time required for SECURITY ERASE UNIT */
+ disk_identify_fixup_uint16(identify.byte, 90); /* time required for enhanced SECURITY ERASE UNIT */
+ disk_identify_fixup_uint16(identify.byte, 91); /* current APM values */
+ disk_identify_fixup_uint16(identify.byte, 94); /* current AAM value */
+ disk_identify_fixup_uint16(identify.byte, 108); /* WWN */
+ disk_identify_fixup_uint16(identify.byte, 109); /* WWN */
+ disk_identify_fixup_uint16(identify.byte, 110); /* WWN */
+ disk_identify_fixup_uint16(identify.byte, 111); /* WWN */
+ disk_identify_fixup_uint16(identify.byte, 128); /* device lock function */
+ disk_identify_fixup_uint16(identify.byte, 217); /* nominal media rotation rate */
+ memcpy(&id, identify.byte, sizeof id);
+ } else {
+ /* If this fails, then try HDIO_GET_IDENTITY */
+ if (ioctl(fd, HDIO_GET_IDENTITY, &id) != 0) {
+ log_debug_errno(errno, "HDIO_GET_IDENTITY failed for '%s': %m", node);
+ return 2;
+ }
+ }
+
+ memcpy(model, id.model, 40);
+ model[40] = '\0';
+ udev_util_encode_string(model, model_enc, sizeof(model_enc));
+ util_replace_whitespace((char *) id.model, model, 40);
+ util_replace_chars(model, NULL);
+ util_replace_whitespace((char *) id.serial_no, serial, 20);
+ util_replace_chars(serial, NULL);
+ util_replace_whitespace((char *) id.fw_rev, revision, 8);
+ util_replace_chars(revision, NULL);
+
+ if (export) {
+ /* Set this to convey the disk speaks the ATA protocol */
+ printf("ID_ATA=1\n");
+
+ if ((id.config >> 8) & 0x80) {
+ /* This is an ATAPI device */
+ switch ((id.config >> 8) & 0x1f) {
+ case 0:
+ printf("ID_TYPE=cd\n");
+ break;
+ case 1:
+ printf("ID_TYPE=tape\n");
+ break;
+ case 5:
+ printf("ID_TYPE=cd\n");
+ break;
+ case 7:
+ printf("ID_TYPE=optical\n");
+ break;
+ default:
+ printf("ID_TYPE=generic\n");
+ break;
+ }
+ } else {
+ printf("ID_TYPE=disk\n");
+ }
+ printf("ID_BUS=ata\n");
+ printf("ID_MODEL=%s\n", model);
+ printf("ID_MODEL_ENC=%s\n", model_enc);
+ printf("ID_REVISION=%s\n", revision);
+ if (serial[0] != '\0') {
+ printf("ID_SERIAL=%s_%s\n", model, serial);
+ printf("ID_SERIAL_SHORT=%s\n", serial);
+ } else {
+ printf("ID_SERIAL=%s\n", model);
+ }
+
+ if (id.command_set_1 & (1<<5)) {
+ printf("ID_ATA_WRITE_CACHE=1\n");
+ printf("ID_ATA_WRITE_CACHE_ENABLED=%d\n", (id.cfs_enable_1 & (1<<5)) ? 1 : 0);
+ }
+ if (id.command_set_1 & (1<<10)) {
+ printf("ID_ATA_FEATURE_SET_HPA=1\n");
+ printf("ID_ATA_FEATURE_SET_HPA_ENABLED=%d\n", (id.cfs_enable_1 & (1<<10)) ? 1 : 0);
+
+ /*
+ * TODO: use the READ NATIVE MAX ADDRESS command to get the native max address
+ * so it is easy to check whether the protected area is in use.
+ */
+ }
+ if (id.command_set_1 & (1<<3)) {
+ printf("ID_ATA_FEATURE_SET_PM=1\n");
+ printf("ID_ATA_FEATURE_SET_PM_ENABLED=%d\n", (id.cfs_enable_1 & (1<<3)) ? 1 : 0);
+ }
+ if (id.command_set_1 & (1<<1)) {
+ printf("ID_ATA_FEATURE_SET_SECURITY=1\n");
+ printf("ID_ATA_FEATURE_SET_SECURITY_ENABLED=%d\n", (id.cfs_enable_1 & (1<<1)) ? 1 : 0);
+ printf("ID_ATA_FEATURE_SET_SECURITY_ERASE_UNIT_MIN=%d\n", id.trseuc * 2);
+ if ((id.cfs_enable_1 & (1<<1))) /* enabled */ {
+ if (id.dlf & (1<<8))
+ printf("ID_ATA_FEATURE_SET_SECURITY_LEVEL=maximum\n");
+ else
+ printf("ID_ATA_FEATURE_SET_SECURITY_LEVEL=high\n");
+ }
+ if (id.dlf & (1<<5))
+ printf("ID_ATA_FEATURE_SET_SECURITY_ENHANCED_ERASE_UNIT_MIN=%d\n", id.trsEuc * 2);
+ if (id.dlf & (1<<4))
+ printf("ID_ATA_FEATURE_SET_SECURITY_EXPIRE=1\n");
+ if (id.dlf & (1<<3))
+ printf("ID_ATA_FEATURE_SET_SECURITY_FROZEN=1\n");
+ if (id.dlf & (1<<2))
+ printf("ID_ATA_FEATURE_SET_SECURITY_LOCKED=1\n");
+ }
+ if (id.command_set_1 & (1<<0)) {
+ printf("ID_ATA_FEATURE_SET_SMART=1\n");
+ printf("ID_ATA_FEATURE_SET_SMART_ENABLED=%d\n", (id.cfs_enable_1 & (1<<0)) ? 1 : 0);
+ }
+ if (id.command_set_2 & (1<<9)) {
+ printf("ID_ATA_FEATURE_SET_AAM=1\n");
+ printf("ID_ATA_FEATURE_SET_AAM_ENABLED=%d\n", (id.cfs_enable_2 & (1<<9)) ? 1 : 0);
+ printf("ID_ATA_FEATURE_SET_AAM_VENDOR_RECOMMENDED_VALUE=%d\n", id.acoustic >> 8);
+ printf("ID_ATA_FEATURE_SET_AAM_CURRENT_VALUE=%d\n", id.acoustic & 0xff);
+ }
+ if (id.command_set_2 & (1<<5)) {
+ printf("ID_ATA_FEATURE_SET_PUIS=1\n");
+ printf("ID_ATA_FEATURE_SET_PUIS_ENABLED=%d\n", (id.cfs_enable_2 & (1<<5)) ? 1 : 0);
+ }
+ if (id.command_set_2 & (1<<3)) {
+ printf("ID_ATA_FEATURE_SET_APM=1\n");
+ printf("ID_ATA_FEATURE_SET_APM_ENABLED=%d\n", (id.cfs_enable_2 & (1<<3)) ? 1 : 0);
+ if ((id.cfs_enable_2 & (1<<3)))
+ printf("ID_ATA_FEATURE_SET_APM_CURRENT_VALUE=%d\n", id.CurAPMvalues & 0xff);
+ }
+ if (id.command_set_2 & (1<<0))
+ printf("ID_ATA_DOWNLOAD_MICROCODE=1\n");
+
+ /*
+ * Word 76 indicates the capabilities of a SATA device. A PATA device shall set
+ * word 76 to 0000h or FFFFh. If word 76 is set to 0000h or FFFFh, then
+ * the device does not claim compliance with the Serial ATA specification and words
+ * 76 through 79 are not valid and shall be ignored.
+ */
+
+ word = identify.wyde[76];
+ if (word != 0x0000 && word != 0xffff) {
+ printf("ID_ATA_SATA=1\n");
+ /*
+ * If bit 2 of word 76 is set to one, then the device supports the Gen2
+ * signaling rate of 3.0 Gb/s (see SATA 2.6).
+ *
+ * If bit 1 of word 76 is set to one, then the device supports the Gen1
+ * signaling rate of 1.5 Gb/s (see SATA 2.6).
+ */
+ if (word & (1<<2))
+ printf("ID_ATA_SATA_SIGNAL_RATE_GEN2=1\n");
+ if (word & (1<<1))
+ printf("ID_ATA_SATA_SIGNAL_RATE_GEN1=1\n");
+ }
+
+ /* Word 217 indicates the nominal media rotation rate of the device */
+ word = identify.wyde[217];
+ if (word == 0x0001)
+ printf ("ID_ATA_ROTATION_RATE_RPM=0\n"); /* non-rotating e.g. SSD */
+ else if (word >= 0x0401 && word <= 0xfffe)
+ printf ("ID_ATA_ROTATION_RATE_RPM=%d\n", word);
+
+ /*
+ * Words 108-111 contain a mandatory World Wide Name (WWN) in the NAA IEEE Registered identifier
+ * format. Word 108 bits (15:12) shall contain 5h, indicating that the naming authority is IEEE.
+ * All other values are reserved.
+ */
+ word = identify.wyde[108];
+ if ((word & 0xf000) == 0x5000) {
+ uint64_t wwwn;
+
+ wwwn = identify.wyde[108];
+ wwwn <<= 16;
+ wwwn |= identify.wyde[109];
+ wwwn <<= 16;
+ wwwn |= identify.wyde[110];
+ wwwn <<= 16;
+ wwwn |= identify.wyde[111];
+ printf("ID_WWN=0x%1$" PRIx64 "\n"
+ "ID_WWN_WITH_EXTENSION=0x%1$" PRIx64 "\n",
+ wwwn);
+ }
+
+ /* from Linux's include/linux/ata.h */
+ if (identify.wyde[0] == 0x848a ||
+ identify.wyde[0] == 0x844a ||
+ (identify.wyde[83] & 0xc004) == 0x4004)
+ printf("ID_ATA_CFA=1\n");
+ } else {
+ if (serial[0] != '\0')
+ printf("%s_%s\n", model, serial);
+ else
+ printf("%s\n", model);
+ }
+
+ return 0;
+}
diff --git a/src/grp-udev/cdrom_id/cdrom_id.c b/src/grp-udev/cdrom_id/cdrom_id.c
new file mode 100644
index 0000000000..72f284f710
--- /dev/null
+++ b/src/grp-udev/cdrom_id/cdrom_id.c
@@ -0,0 +1,1085 @@
+/*
+ * cdrom_id - optical drive and media information prober
+ *
+ * Copyright (C) 2008-2010 Kay Sievers <kay@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <linux/cdrom.h>
+#include <scsi/sg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "libudev.h"
+
+#include "libudev-private.h"
+#include "random-util.h"
+
+/* device info */
+static unsigned int cd_cd_rom;
+static unsigned int cd_cd_r;
+static unsigned int cd_cd_rw;
+static unsigned int cd_dvd_rom;
+static unsigned int cd_dvd_r;
+static unsigned int cd_dvd_rw;
+static unsigned int cd_dvd_ram;
+static unsigned int cd_dvd_plus_r;
+static unsigned int cd_dvd_plus_rw;
+static unsigned int cd_dvd_plus_r_dl;
+static unsigned int cd_dvd_plus_rw_dl;
+static unsigned int cd_bd;
+static unsigned int cd_bd_r;
+static unsigned int cd_bd_re;
+static unsigned int cd_hddvd;
+static unsigned int cd_hddvd_r;
+static unsigned int cd_hddvd_rw;
+static unsigned int cd_mo;
+static unsigned int cd_mrw;
+static unsigned int cd_mrw_w;
+
+/* media info */
+static unsigned int cd_media;
+static unsigned int cd_media_cd_rom;
+static unsigned int cd_media_cd_r;
+static unsigned int cd_media_cd_rw;
+static unsigned int cd_media_dvd_rom;
+static unsigned int cd_media_dvd_r;
+static unsigned int cd_media_dvd_rw;
+static unsigned int cd_media_dvd_rw_ro; /* restricted overwrite mode */
+static unsigned int cd_media_dvd_rw_seq; /* sequential mode */
+static unsigned int cd_media_dvd_ram;
+static unsigned int cd_media_dvd_plus_r;
+static unsigned int cd_media_dvd_plus_rw;
+static unsigned int cd_media_dvd_plus_r_dl;
+static unsigned int cd_media_dvd_plus_rw_dl;
+static unsigned int cd_media_bd;
+static unsigned int cd_media_bd_r;
+static unsigned int cd_media_bd_re;
+static unsigned int cd_media_hddvd;
+static unsigned int cd_media_hddvd_r;
+static unsigned int cd_media_hddvd_rw;
+static unsigned int cd_media_mo;
+static unsigned int cd_media_mrw;
+static unsigned int cd_media_mrw_w;
+
+static const char *cd_media_state = NULL;
+static unsigned int cd_media_session_next;
+static unsigned int cd_media_session_count;
+static unsigned int cd_media_track_count;
+static unsigned int cd_media_track_count_data;
+static unsigned int cd_media_track_count_audio;
+static unsigned long long int cd_media_session_last_offset;
+
+#define ERRCODE(s) ((((s)[2] & 0x0F) << 16) | ((s)[12] << 8) | ((s)[13]))
+#define SK(errcode) (((errcode) >> 16) & 0xF)
+#define ASC(errcode) (((errcode) >> 8) & 0xFF)
+#define ASCQ(errcode) ((errcode) & 0xFF)
+
+static bool is_mounted(const char *device)
+{
+ struct stat statbuf;
+ FILE *fp;
+ int maj, min;
+ bool mounted = false;
+
+ if (stat(device, &statbuf) < 0)
+ return false;
+
+ fp = fopen("/proc/self/mountinfo", "re");
+ if (fp == NULL)
+ return false;
+ while (fscanf(fp, "%*s %*s %i:%i %*[^\n]", &maj, &min) == 2) {
+ if (makedev(maj, min) == statbuf.st_rdev) {
+ mounted = true;
+ break;
+ }
+ }
+ fclose(fp);
+ return mounted;
+}
+
+static void info_scsi_cmd_err(struct udev *udev, const char *cmd, int err)
+{
+ if (err == -1) {
+ log_debug("%s failed", cmd);
+ return;
+ }
+ log_debug("%s failed with SK=%Xh/ASC=%02Xh/ACQ=%02Xh", cmd, SK(err), ASC(err), ASCQ(err));
+}
+
+struct scsi_cmd {
+ struct cdrom_generic_command cgc;
+ union {
+ struct request_sense s;
+ unsigned char u[18];
+ } _sense;
+ struct sg_io_hdr sg_io;
+};
+
+static void scsi_cmd_init(struct udev *udev, struct scsi_cmd *cmd)
+{
+ memzero(cmd, sizeof(struct scsi_cmd));
+ cmd->cgc.quiet = 1;
+ cmd->cgc.sense = &cmd->_sense.s;
+ cmd->sg_io.interface_id = 'S';
+ cmd->sg_io.mx_sb_len = sizeof(cmd->_sense);
+ cmd->sg_io.cmdp = cmd->cgc.cmd;
+ cmd->sg_io.sbp = cmd->_sense.u;
+ cmd->sg_io.flags = SG_FLAG_LUN_INHIBIT | SG_FLAG_DIRECT_IO;
+}
+
+static void scsi_cmd_set(struct udev *udev, struct scsi_cmd *cmd, size_t i, unsigned char arg)
+{
+ cmd->sg_io.cmd_len = i + 1;
+ cmd->cgc.cmd[i] = arg;
+}
+
+#define CHECK_CONDITION 0x01
+
+static int scsi_cmd_run(struct udev *udev, struct scsi_cmd *cmd, int fd, unsigned char *buf, size_t bufsize)
+{
+ int ret = 0;
+
+ if (bufsize > 0) {
+ cmd->sg_io.dxferp = buf;
+ cmd->sg_io.dxfer_len = bufsize;
+ cmd->sg_io.dxfer_direction = SG_DXFER_FROM_DEV;
+ } else {
+ cmd->sg_io.dxfer_direction = SG_DXFER_NONE;
+ }
+ if (ioctl(fd, SG_IO, &cmd->sg_io))
+ return -1;
+
+ if ((cmd->sg_io.info & SG_INFO_OK_MASK) != SG_INFO_OK) {
+ errno = EIO;
+ ret = -1;
+ if (cmd->sg_io.masked_status & CHECK_CONDITION) {
+ ret = ERRCODE(cmd->_sense.u);
+ if (ret == 0)
+ ret = -1;
+ }
+ }
+ return ret;
+}
+
+static int media_lock(struct udev *udev, int fd, bool lock)
+{
+ int err;
+
+ /* disable the kernel's lock logic */
+ err = ioctl(fd, CDROM_CLEAR_OPTIONS, CDO_LOCK);
+ if (err < 0)
+ log_debug("CDROM_CLEAR_OPTIONS, CDO_LOCK failed");
+
+ err = ioctl(fd, CDROM_LOCKDOOR, lock ? 1 : 0);
+ if (err < 0)
+ log_debug("CDROM_LOCKDOOR failed");
+
+ return err;
+}
+
+static int media_eject(struct udev *udev, int fd)
+{
+ struct scsi_cmd sc;
+ int err;
+
+ scsi_cmd_init(udev, &sc);
+ scsi_cmd_set(udev, &sc, 0, 0x1b);
+ scsi_cmd_set(udev, &sc, 4, 0x02);
+ scsi_cmd_set(udev, &sc, 5, 0);
+ err = scsi_cmd_run(udev, &sc, fd, NULL, 0);
+ if ((err != 0)) {
+ info_scsi_cmd_err(udev, "START_STOP_UNIT", err);
+ return -1;
+ }
+ return 0;
+}
+
+static int cd_capability_compat(struct udev *udev, int fd)
+{
+ int capability;
+
+ capability = ioctl(fd, CDROM_GET_CAPABILITY, NULL);
+ if (capability < 0) {
+ log_debug("CDROM_GET_CAPABILITY failed");
+ return -1;
+ }
+
+ if (capability & CDC_CD_R)
+ cd_cd_r = 1;
+ if (capability & CDC_CD_RW)
+ cd_cd_rw = 1;
+ if (capability & CDC_DVD)
+ cd_dvd_rom = 1;
+ if (capability & CDC_DVD_R)
+ cd_dvd_r = 1;
+ if (capability & CDC_DVD_RAM)
+ cd_dvd_ram = 1;
+ if (capability & CDC_MRW)
+ cd_mrw = 1;
+ if (capability & CDC_MRW_W)
+ cd_mrw_w = 1;
+ return 0;
+}
+
+static int cd_media_compat(struct udev *udev, int fd)
+{
+ if (ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT) != CDS_DISC_OK) {
+ log_debug("CDROM_DRIVE_STATUS != CDS_DISC_OK");
+ return -1;
+ }
+ cd_media = 1;
+ return 0;
+}
+
+static int cd_inquiry(struct udev *udev, int fd)
+{
+ struct scsi_cmd sc;
+ unsigned char inq[128];
+ int err;
+
+ scsi_cmd_init(udev, &sc);
+ scsi_cmd_set(udev, &sc, 0, 0x12);
+ scsi_cmd_set(udev, &sc, 4, 36);
+ scsi_cmd_set(udev, &sc, 5, 0);
+ err = scsi_cmd_run(udev, &sc, fd, inq, 36);
+ if ((err != 0)) {
+ info_scsi_cmd_err(udev, "INQUIRY", err);
+ return -1;
+ }
+
+ if ((inq[0] & 0x1F) != 5) {
+ log_debug("not an MMC unit");
+ return -1;
+ }
+
+ log_debug("INQUIRY: [%.8s][%.16s][%.4s]", inq + 8, inq + 16, inq + 32);
+ return 0;
+}
+
+static void feature_profile_media(struct udev *udev, int cur_profile)
+{
+ switch (cur_profile) {
+ case 0x03:
+ case 0x04:
+ case 0x05:
+ log_debug("profile 0x%02x ", cur_profile);
+ cd_media = 1;
+ cd_media_mo = 1;
+ break;
+ case 0x08:
+ log_debug("profile 0x%02x media_cd_rom", cur_profile);
+ cd_media = 1;
+ cd_media_cd_rom = 1;
+ break;
+ case 0x09:
+ log_debug("profile 0x%02x media_cd_r", cur_profile);
+ cd_media = 1;
+ cd_media_cd_r = 1;
+ break;
+ case 0x0a:
+ log_debug("profile 0x%02x media_cd_rw", cur_profile);
+ cd_media = 1;
+ cd_media_cd_rw = 1;
+ break;
+ case 0x10:
+ log_debug("profile 0x%02x media_dvd_ro", cur_profile);
+ cd_media = 1;
+ cd_media_dvd_rom = 1;
+ break;
+ case 0x11:
+ log_debug("profile 0x%02x media_dvd_r", cur_profile);
+ cd_media = 1;
+ cd_media_dvd_r = 1;
+ break;
+ case 0x12:
+ log_debug("profile 0x%02x media_dvd_ram", cur_profile);
+ cd_media = 1;
+ cd_media_dvd_ram = 1;
+ break;
+ case 0x13:
+ log_debug("profile 0x%02x media_dvd_rw_ro", cur_profile);
+ cd_media = 1;
+ cd_media_dvd_rw = 1;
+ cd_media_dvd_rw_ro = 1;
+ break;
+ case 0x14:
+ log_debug("profile 0x%02x media_dvd_rw_seq", cur_profile);
+ cd_media = 1;
+ cd_media_dvd_rw = 1;
+ cd_media_dvd_rw_seq = 1;
+ break;
+ case 0x1B:
+ log_debug("profile 0x%02x media_dvd_plus_r", cur_profile);
+ cd_media = 1;
+ cd_media_dvd_plus_r = 1;
+ break;
+ case 0x1A:
+ log_debug("profile 0x%02x media_dvd_plus_rw", cur_profile);
+ cd_media = 1;
+ cd_media_dvd_plus_rw = 1;
+ break;
+ case 0x2A:
+ log_debug("profile 0x%02x media_dvd_plus_rw_dl", cur_profile);
+ cd_media = 1;
+ cd_media_dvd_plus_rw_dl = 1;
+ break;
+ case 0x2B:
+ log_debug("profile 0x%02x media_dvd_plus_r_dl", cur_profile);
+ cd_media = 1;
+ cd_media_dvd_plus_r_dl = 1;
+ break;
+ case 0x40:
+ log_debug("profile 0x%02x media_bd", cur_profile);
+ cd_media = 1;
+ cd_media_bd = 1;
+ break;
+ case 0x41:
+ case 0x42:
+ log_debug("profile 0x%02x media_bd_r", cur_profile);
+ cd_media = 1;
+ cd_media_bd_r = 1;
+ break;
+ case 0x43:
+ log_debug("profile 0x%02x media_bd_re", cur_profile);
+ cd_media = 1;
+ cd_media_bd_re = 1;
+ break;
+ case 0x50:
+ log_debug("profile 0x%02x media_hddvd", cur_profile);
+ cd_media = 1;
+ cd_media_hddvd = 1;
+ break;
+ case 0x51:
+ log_debug("profile 0x%02x media_hddvd_r", cur_profile);
+ cd_media = 1;
+ cd_media_hddvd_r = 1;
+ break;
+ case 0x52:
+ log_debug("profile 0x%02x media_hddvd_rw", cur_profile);
+ cd_media = 1;
+ cd_media_hddvd_rw = 1;
+ break;
+ default:
+ log_debug("profile 0x%02x <ignored>", cur_profile);
+ break;
+ }
+}
+
+static int feature_profiles(struct udev *udev, const unsigned char *profiles, size_t size)
+{
+ unsigned int i;
+
+ for (i = 0; i+4 <= size; i += 4) {
+ int profile;
+
+ profile = profiles[i] << 8 | profiles[i+1];
+ switch (profile) {
+ case 0x03:
+ case 0x04:
+ case 0x05:
+ log_debug("profile 0x%02x mo", profile);
+ cd_mo = 1;
+ break;
+ case 0x08:
+ log_debug("profile 0x%02x cd_rom", profile);
+ cd_cd_rom = 1;
+ break;
+ case 0x09:
+ log_debug("profile 0x%02x cd_r", profile);
+ cd_cd_r = 1;
+ break;
+ case 0x0A:
+ log_debug("profile 0x%02x cd_rw", profile);
+ cd_cd_rw = 1;
+ break;
+ case 0x10:
+ log_debug("profile 0x%02x dvd_rom", profile);
+ cd_dvd_rom = 1;
+ break;
+ case 0x12:
+ log_debug("profile 0x%02x dvd_ram", profile);
+ cd_dvd_ram = 1;
+ break;
+ case 0x13:
+ case 0x14:
+ log_debug("profile 0x%02x dvd_rw", profile);
+ cd_dvd_rw = 1;
+ break;
+ case 0x1B:
+ log_debug("profile 0x%02x dvd_plus_r", profile);
+ cd_dvd_plus_r = 1;
+ break;
+ case 0x1A:
+ log_debug("profile 0x%02x dvd_plus_rw", profile);
+ cd_dvd_plus_rw = 1;
+ break;
+ case 0x2A:
+ log_debug("profile 0x%02x dvd_plus_rw_dl", profile);
+ cd_dvd_plus_rw_dl = 1;
+ break;
+ case 0x2B:
+ log_debug("profile 0x%02x dvd_plus_r_dl", profile);
+ cd_dvd_plus_r_dl = 1;
+ break;
+ case 0x40:
+ cd_bd = 1;
+ log_debug("profile 0x%02x bd", profile);
+ break;
+ case 0x41:
+ case 0x42:
+ cd_bd_r = 1;
+ log_debug("profile 0x%02x bd_r", profile);
+ break;
+ case 0x43:
+ cd_bd_re = 1;
+ log_debug("profile 0x%02x bd_re", profile);
+ break;
+ case 0x50:
+ cd_hddvd = 1;
+ log_debug("profile 0x%02x hddvd", profile);
+ break;
+ case 0x51:
+ cd_hddvd_r = 1;
+ log_debug("profile 0x%02x hddvd_r", profile);
+ break;
+ case 0x52:
+ cd_hddvd_rw = 1;
+ log_debug("profile 0x%02x hddvd_rw", profile);
+ break;
+ default:
+ log_debug("profile 0x%02x <ignored>", profile);
+ break;
+ }
+ }
+ return 0;
+}
+
+/* returns 0 if media was detected */
+static int cd_profiles_old_mmc(struct udev *udev, int fd)
+{
+ struct scsi_cmd sc;
+ int err;
+
+ unsigned char header[32];
+
+ scsi_cmd_init(udev, &sc);
+ scsi_cmd_set(udev, &sc, 0, 0x51);
+ scsi_cmd_set(udev, &sc, 8, sizeof(header));
+ scsi_cmd_set(udev, &sc, 9, 0);
+ err = scsi_cmd_run(udev, &sc, fd, header, sizeof(header));
+ if ((err != 0)) {
+ info_scsi_cmd_err(udev, "READ DISC INFORMATION", err);
+ if (cd_media == 1) {
+ log_debug("no current profile, but disc is present; assuming CD-ROM");
+ cd_media_cd_rom = 1;
+ cd_media_track_count = 1;
+ cd_media_track_count_data = 1;
+ return 0;
+ } else {
+ log_debug("no current profile, assuming no media");
+ return -1;
+ }
+ };
+
+ cd_media = 1;
+
+ if (header[2] & 16) {
+ cd_media_cd_rw = 1;
+ log_debug("profile 0x0a media_cd_rw");
+ } else if ((header[2] & 3) < 2 && cd_cd_r) {
+ cd_media_cd_r = 1;
+ log_debug("profile 0x09 media_cd_r");
+ } else {
+ cd_media_cd_rom = 1;
+ log_debug("profile 0x08 media_cd_rom");
+ }
+ return 0;
+}
+
+/* returns 0 if media was detected */
+static int cd_profiles(struct udev *udev, int fd)
+{
+ struct scsi_cmd sc;
+ unsigned char features[65530];
+ unsigned int cur_profile = 0;
+ unsigned int len;
+ unsigned int i;
+ int err;
+ int ret;
+
+ ret = -1;
+
+ /* First query the current profile */
+ scsi_cmd_init(udev, &sc);
+ scsi_cmd_set(udev, &sc, 0, 0x46);
+ scsi_cmd_set(udev, &sc, 8, 8);
+ scsi_cmd_set(udev, &sc, 9, 0);
+ err = scsi_cmd_run(udev, &sc, fd, features, 8);
+ if ((err != 0)) {
+ info_scsi_cmd_err(udev, "GET CONFIGURATION", err);
+ /* handle pre-MMC2 drives which do not support GET CONFIGURATION */
+ if (SK(err) == 0x5 && (ASC(err) == 0x20 || ASC(err) == 0x24)) {
+ log_debug("drive is pre-MMC2 and does not support 46h get configuration command");
+ log_debug("trying to work around the problem");
+ ret = cd_profiles_old_mmc(udev, fd);
+ }
+ goto out;
+ }
+
+ cur_profile = features[6] << 8 | features[7];
+ if (cur_profile > 0) {
+ log_debug("current profile 0x%02x", cur_profile);
+ feature_profile_media (udev, cur_profile);
+ ret = 0; /* we have media */
+ } else {
+ log_debug("no current profile, assuming no media");
+ }
+
+ len = features[0] << 24 | features[1] << 16 | features[2] << 8 | features[3];
+ log_debug("GET CONFIGURATION: size of features buffer 0x%04x", len);
+
+ if (len > sizeof(features)) {
+ log_debug("can not get features in a single query, truncating");
+ len = sizeof(features);
+ } else if (len <= 8)
+ len = sizeof(features);
+
+ /* Now get the full feature buffer */
+ scsi_cmd_init(udev, &sc);
+ scsi_cmd_set(udev, &sc, 0, 0x46);
+ scsi_cmd_set(udev, &sc, 7, ( len >> 8 ) & 0xff);
+ scsi_cmd_set(udev, &sc, 8, len & 0xff);
+ scsi_cmd_set(udev, &sc, 9, 0);
+ err = scsi_cmd_run(udev, &sc, fd, features, len);
+ if ((err != 0)) {
+ info_scsi_cmd_err(udev, "GET CONFIGURATION", err);
+ return -1;
+ }
+
+ /* parse the length once more, in case the drive decided to have other features suddenly :) */
+ len = features[0] << 24 | features[1] << 16 | features[2] << 8 | features[3];
+ log_debug("GET CONFIGURATION: size of features buffer 0x%04x", len);
+
+ if (len > sizeof(features)) {
+ log_debug("can not get features in a single query, truncating");
+ len = sizeof(features);
+ }
+
+ /* device features */
+ for (i = 8; i+4 < len; i += (4 + features[i+3])) {
+ unsigned int feature;
+
+ feature = features[i] << 8 | features[i+1];
+
+ switch (feature) {
+ case 0x00:
+ log_debug("GET CONFIGURATION: feature 'profiles', with %i entries", features[i+3] / 4);
+ feature_profiles(udev, &features[i]+4, MIN(features[i+3], len - i - 4));
+ break;
+ default:
+ log_debug("GET CONFIGURATION: feature 0x%04x <ignored>, with 0x%02x bytes", feature, features[i+3]);
+ break;
+ }
+ }
+out:
+ return ret;
+}
+
+static int cd_media_info(struct udev *udev, int fd)
+{
+ struct scsi_cmd sc;
+ unsigned char header[32];
+ static const char *media_status[] = {
+ "blank",
+ "appendable",
+ "complete",
+ "other"
+ };
+ int err;
+
+ scsi_cmd_init(udev, &sc);
+ scsi_cmd_set(udev, &sc, 0, 0x51);
+ scsi_cmd_set(udev, &sc, 8, sizeof(header) & 0xff);
+ scsi_cmd_set(udev, &sc, 9, 0);
+ err = scsi_cmd_run(udev, &sc, fd, header, sizeof(header));
+ if ((err != 0)) {
+ info_scsi_cmd_err(udev, "READ DISC INFORMATION", err);
+ return -1;
+ };
+
+ cd_media = 1;
+ log_debug("disk type %02x", header[8]);
+ log_debug("hardware reported media status: %s", media_status[header[2] & 3]);
+
+ /* exclude plain CDROM, some fake cdroms return 0 for "blank" media here */
+ if (!cd_media_cd_rom)
+ cd_media_state = media_status[header[2] & 3];
+
+ /* fresh DVD-RW in restricted overwite mode reports itself as
+ * "appendable"; change it to "blank" to make it consistent with what
+ * gets reported after blanking, and what userspace expects */
+ if (cd_media_dvd_rw_ro && (header[2] & 3) == 1)
+ cd_media_state = media_status[0];
+
+ /* DVD+RW discs (and DVD-RW in restricted mode) once formatted are
+ * always "complete", DVD-RAM are "other" or "complete" if the disc is
+ * write protected; we need to check the contents if it is blank */
+ if ((cd_media_dvd_rw_ro || cd_media_dvd_plus_rw || cd_media_dvd_plus_rw_dl || cd_media_dvd_ram) && (header[2] & 3) > 1) {
+ unsigned char buffer[32 * 2048];
+ unsigned char len;
+ int offset;
+
+ if (cd_media_dvd_ram) {
+ /* a write protected dvd-ram may report "complete" status */
+
+ unsigned char dvdstruct[8];
+ unsigned char format[12];
+
+ scsi_cmd_init(udev, &sc);
+ scsi_cmd_set(udev, &sc, 0, 0xAD);
+ scsi_cmd_set(udev, &sc, 7, 0xC0);
+ scsi_cmd_set(udev, &sc, 9, sizeof(dvdstruct));
+ scsi_cmd_set(udev, &sc, 11, 0);
+ err = scsi_cmd_run(udev, &sc, fd, dvdstruct, sizeof(dvdstruct));
+ if ((err != 0)) {
+ info_scsi_cmd_err(udev, "READ DVD STRUCTURE", err);
+ return -1;
+ }
+ if (dvdstruct[4] & 0x02) {
+ cd_media_state = media_status[2];
+ log_debug("write-protected DVD-RAM media inserted");
+ goto determined;
+ }
+
+ /* let's make sure we don't try to read unformatted media */
+ scsi_cmd_init(udev, &sc);
+ scsi_cmd_set(udev, &sc, 0, 0x23);
+ scsi_cmd_set(udev, &sc, 8, sizeof(format));
+ scsi_cmd_set(udev, &sc, 9, 0);
+ err = scsi_cmd_run(udev, &sc, fd, format, sizeof(format));
+ if ((err != 0)) {
+ info_scsi_cmd_err(udev, "READ DVD FORMAT CAPACITIES", err);
+ return -1;
+ }
+
+ len = format[3];
+ if (len & 7 || len < 16) {
+ log_debug("invalid format capacities length");
+ return -1;
+ }
+
+ switch(format[8] & 3) {
+ case 1:
+ log_debug("unformatted DVD-RAM media inserted");
+ /* This means that last format was interrupted
+ * or failed, blank dvd-ram discs are factory
+ * formatted. Take no action here as it takes
+ * quite a while to reformat a dvd-ram and it's
+ * not automatically started */
+ goto determined;
+
+ case 2:
+ log_debug("formatted DVD-RAM media inserted");
+ break;
+
+ case 3:
+ cd_media = 0; //return no media
+ log_debug("format capacities returned no media");
+ return -1;
+ }
+ }
+
+ /* Take a closer look at formatted media (unformatted DVD+RW
+ * has "blank" status", DVD-RAM was examined earlier) and check
+ * for ISO and UDF PVDs or a fs superblock presence and do it
+ * in one ioctl (we need just sectors 0 and 16) */
+ scsi_cmd_init(udev, &sc);
+ scsi_cmd_set(udev, &sc, 0, 0x28);
+ scsi_cmd_set(udev, &sc, 5, 0);
+ scsi_cmd_set(udev, &sc, 8, 32);
+ scsi_cmd_set(udev, &sc, 9, 0);
+ err = scsi_cmd_run(udev, &sc, fd, buffer, sizeof(buffer));
+ if ((err != 0)) {
+ cd_media = 0;
+ info_scsi_cmd_err(udev, "READ FIRST 32 BLOCKS", err);
+ return -1;
+ }
+
+ /* if any non-zero data is found in sector 16 (iso and udf) or
+ * eventually 0 (fat32 boot sector, ext2 superblock, etc), disc
+ * is assumed non-blank */
+
+ for (offset = 32768; offset < (32768 + 2048); offset++) {
+ if (buffer [offset]) {
+ log_debug("data in block 16, assuming complete");
+ goto determined;
+ }
+ }
+
+ for (offset = 0; offset < 2048; offset++) {
+ if (buffer [offset]) {
+ log_debug("data in block 0, assuming complete");
+ goto determined;
+ }
+ }
+
+ cd_media_state = media_status[0];
+ log_debug("no data in blocks 0 or 16, assuming blank");
+ }
+
+determined:
+ /* "other" is e. g. DVD-RAM, can't append sessions there; DVDs in
+ * restricted overwrite mode can never append, only in sequential mode */
+ if ((header[2] & 3) < 2 && !cd_media_dvd_rw_ro)
+ cd_media_session_next = header[10] << 8 | header[5];
+ cd_media_session_count = header[9] << 8 | header[4];
+ cd_media_track_count = header[11] << 8 | header[6];
+
+ return 0;
+}
+
+static int cd_media_toc(struct udev *udev, int fd)
+{
+ struct scsi_cmd sc;
+ unsigned char header[12];
+ unsigned char toc[65536];
+ unsigned int len, i, num_tracks;
+ unsigned char *p;
+ int err;
+
+ scsi_cmd_init(udev, &sc);
+ scsi_cmd_set(udev, &sc, 0, 0x43);
+ scsi_cmd_set(udev, &sc, 6, 1);
+ scsi_cmd_set(udev, &sc, 8, sizeof(header) & 0xff);
+ scsi_cmd_set(udev, &sc, 9, 0);
+ err = scsi_cmd_run(udev, &sc, fd, header, sizeof(header));
+ if ((err != 0)) {
+ info_scsi_cmd_err(udev, "READ TOC", err);
+ return -1;
+ }
+
+ len = (header[0] << 8 | header[1]) + 2;
+ log_debug("READ TOC: len: %d, start track: %d, end track: %d", len, header[2], header[3]);
+ if (len > sizeof(toc))
+ return -1;
+ if (len < 2)
+ return -1;
+ /* 2: first track, 3: last track */
+ num_tracks = header[3] - header[2] + 1;
+
+ /* empty media has no tracks */
+ if (len < 8)
+ return 0;
+
+ scsi_cmd_init(udev, &sc);
+ scsi_cmd_set(udev, &sc, 0, 0x43);
+ scsi_cmd_set(udev, &sc, 6, header[2]); /* First Track/Session Number */
+ scsi_cmd_set(udev, &sc, 7, (len >> 8) & 0xff);
+ scsi_cmd_set(udev, &sc, 8, len & 0xff);
+ scsi_cmd_set(udev, &sc, 9, 0);
+ err = scsi_cmd_run(udev, &sc, fd, toc, len);
+ if ((err != 0)) {
+ info_scsi_cmd_err(udev, "READ TOC (tracks)", err);
+ return -1;
+ }
+
+ /* Take care to not iterate beyond the last valid track as specified in
+ * the TOC, but also avoid going beyond the TOC length, just in case
+ * the last track number is invalidly large */
+ for (p = toc+4, i = 4; i < len-8 && num_tracks > 0; i += 8, p += 8, --num_tracks) {
+ unsigned int block;
+ unsigned int is_data_track;
+
+ is_data_track = (p[1] & 0x04) != 0;
+
+ block = p[4] << 24 | p[5] << 16 | p[6] << 8 | p[7];
+ log_debug("track=%u info=0x%x(%s) start_block=%u",
+ p[2], p[1] & 0x0f, is_data_track ? "data":"audio", block);
+
+ if (is_data_track)
+ cd_media_track_count_data++;
+ else
+ cd_media_track_count_audio++;
+ }
+
+ scsi_cmd_init(udev, &sc);
+ scsi_cmd_set(udev, &sc, 0, 0x43);
+ scsi_cmd_set(udev, &sc, 2, 1); /* Session Info */
+ scsi_cmd_set(udev, &sc, 8, sizeof(header));
+ scsi_cmd_set(udev, &sc, 9, 0);
+ err = scsi_cmd_run(udev, &sc, fd, header, sizeof(header));
+ if ((err != 0)) {
+ info_scsi_cmd_err(udev, "READ TOC (multi session)", err);
+ return -1;
+ }
+ len = header[4+4] << 24 | header[4+5] << 16 | header[4+6] << 8 | header[4+7];
+ log_debug("last track %u starts at block %u", header[4+2], len);
+ cd_media_session_last_offset = (unsigned long long int)len * 2048;
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct udev *udev;
+ static const struct option options[] = {
+ { "lock-media", no_argument, NULL, 'l' },
+ { "unlock-media", no_argument, NULL, 'u' },
+ { "eject-media", no_argument, NULL, 'e' },
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ {}
+ };
+ bool eject = false;
+ bool lock = false;
+ bool unlock = false;
+ const char *node = NULL;
+ int fd = -1;
+ int cnt;
+ int rc = 0;
+
+ log_parse_environment();
+ log_open();
+
+ udev = udev_new();
+ if (udev == NULL)
+ goto exit;
+
+ for (;;) {
+ int option;
+
+ option = getopt_long(argc, argv, "deluh", options, NULL);
+ if (option == -1)
+ break;
+
+ switch (option) {
+ case 'l':
+ lock = true;
+ break;
+ case 'u':
+ unlock = true;
+ break;
+ case 'e':
+ eject = true;
+ break;
+ case 'd':
+ log_set_target(LOG_TARGET_CONSOLE);
+ log_set_max_level(LOG_DEBUG);
+ log_open();
+ break;
+ case 'h':
+ printf("Usage: cdrom_id [options] <device>\n"
+ " -l,--lock-media lock the media (to enable eject request events)\n"
+ " -u,--unlock-media unlock the media\n"
+ " -e,--eject-media eject the media\n"
+ " -d,--debug debug to stderr\n"
+ " -h,--help print this help text\n\n");
+ goto exit;
+ default:
+ rc = 1;
+ goto exit;
+ }
+ }
+
+ node = argv[optind];
+ if (!node) {
+ log_error("no device");
+ fprintf(stderr, "no device\n");
+ rc = 1;
+ goto exit;
+ }
+
+ initialize_srand();
+ for (cnt = 20; cnt > 0; cnt--) {
+ struct timespec duration;
+
+ fd = open(node, O_RDONLY|O_NONBLOCK|O_CLOEXEC|(is_mounted(node) ? 0 : O_EXCL));
+ if (fd >= 0 || errno != EBUSY)
+ break;
+ duration.tv_sec = 0;
+ duration.tv_nsec = (100 * 1000 * 1000) + (rand() % 100 * 1000 * 1000);
+ nanosleep(&duration, NULL);
+ }
+ if (fd < 0) {
+ log_debug("unable to open '%s'", node);
+ fprintf(stderr, "unable to open '%s'\n", node);
+ rc = 1;
+ goto exit;
+ }
+ log_debug("probing: '%s'", node);
+
+ /* same data as original cdrom_id */
+ if (cd_capability_compat(udev, fd) < 0) {
+ rc = 1;
+ goto exit;
+ }
+
+ /* check for media - don't bail if there's no media as we still need to
+ * to read profiles */
+ cd_media_compat(udev, fd);
+
+ /* check if drive talks MMC */
+ if (cd_inquiry(udev, fd) < 0)
+ goto work;
+
+ /* read drive and possibly current profile */
+ if (cd_profiles(udev, fd) != 0)
+ goto work;
+
+ /* at this point we are guaranteed to have media in the drive - find out more about it */
+
+ /* get session/track info */
+ cd_media_toc(udev, fd);
+
+ /* get writable media state */
+ cd_media_info(udev, fd);
+
+work:
+ /* lock the media, so we enable eject button events */
+ if (lock && cd_media) {
+ log_debug("PREVENT_ALLOW_MEDIUM_REMOVAL (lock)");
+ media_lock(udev, fd, true);
+ }
+
+ if (unlock && cd_media) {
+ log_debug("PREVENT_ALLOW_MEDIUM_REMOVAL (unlock)");
+ media_lock(udev, fd, false);
+ }
+
+ if (eject) {
+ log_debug("PREVENT_ALLOW_MEDIUM_REMOVAL (unlock)");
+ media_lock(udev, fd, false);
+ log_debug("START_STOP_UNIT (eject)");
+ media_eject(udev, fd);
+ }
+
+ printf("ID_CDROM=1\n");
+ if (cd_cd_rom)
+ printf("ID_CDROM_CD=1\n");
+ if (cd_cd_r)
+ printf("ID_CDROM_CD_R=1\n");
+ if (cd_cd_rw)
+ printf("ID_CDROM_CD_RW=1\n");
+ if (cd_dvd_rom)
+ printf("ID_CDROM_DVD=1\n");
+ if (cd_dvd_r)
+ printf("ID_CDROM_DVD_R=1\n");
+ if (cd_dvd_rw)
+ printf("ID_CDROM_DVD_RW=1\n");
+ if (cd_dvd_ram)
+ printf("ID_CDROM_DVD_RAM=1\n");
+ if (cd_dvd_plus_r)
+ printf("ID_CDROM_DVD_PLUS_R=1\n");
+ if (cd_dvd_plus_rw)
+ printf("ID_CDROM_DVD_PLUS_RW=1\n");
+ if (cd_dvd_plus_r_dl)
+ printf("ID_CDROM_DVD_PLUS_R_DL=1\n");
+ if (cd_dvd_plus_rw_dl)
+ printf("ID_CDROM_DVD_PLUS_RW_DL=1\n");
+ if (cd_bd)
+ printf("ID_CDROM_BD=1\n");
+ if (cd_bd_r)
+ printf("ID_CDROM_BD_R=1\n");
+ if (cd_bd_re)
+ printf("ID_CDROM_BD_RE=1\n");
+ if (cd_hddvd)
+ printf("ID_CDROM_HDDVD=1\n");
+ if (cd_hddvd_r)
+ printf("ID_CDROM_HDDVD_R=1\n");
+ if (cd_hddvd_rw)
+ printf("ID_CDROM_HDDVD_RW=1\n");
+ if (cd_mo)
+ printf("ID_CDROM_MO=1\n");
+ if (cd_mrw)
+ printf("ID_CDROM_MRW=1\n");
+ if (cd_mrw_w)
+ printf("ID_CDROM_MRW_W=1\n");
+
+ if (cd_media)
+ printf("ID_CDROM_MEDIA=1\n");
+ if (cd_media_mo)
+ printf("ID_CDROM_MEDIA_MO=1\n");
+ if (cd_media_mrw)
+ printf("ID_CDROM_MEDIA_MRW=1\n");
+ if (cd_media_mrw_w)
+ printf("ID_CDROM_MEDIA_MRW_W=1\n");
+ if (cd_media_cd_rom)
+ printf("ID_CDROM_MEDIA_CD=1\n");
+ if (cd_media_cd_r)
+ printf("ID_CDROM_MEDIA_CD_R=1\n");
+ if (cd_media_cd_rw)
+ printf("ID_CDROM_MEDIA_CD_RW=1\n");
+ if (cd_media_dvd_rom)
+ printf("ID_CDROM_MEDIA_DVD=1\n");
+ if (cd_media_dvd_r)
+ printf("ID_CDROM_MEDIA_DVD_R=1\n");
+ if (cd_media_dvd_ram)
+ printf("ID_CDROM_MEDIA_DVD_RAM=1\n");
+ if (cd_media_dvd_rw)
+ printf("ID_CDROM_MEDIA_DVD_RW=1\n");
+ if (cd_media_dvd_plus_r)
+ printf("ID_CDROM_MEDIA_DVD_PLUS_R=1\n");
+ if (cd_media_dvd_plus_rw)
+ printf("ID_CDROM_MEDIA_DVD_PLUS_RW=1\n");
+ if (cd_media_dvd_plus_rw_dl)
+ printf("ID_CDROM_MEDIA_DVD_PLUS_RW_DL=1\n");
+ if (cd_media_dvd_plus_r_dl)
+ printf("ID_CDROM_MEDIA_DVD_PLUS_R_DL=1\n");
+ if (cd_media_bd)
+ printf("ID_CDROM_MEDIA_BD=1\n");
+ if (cd_media_bd_r)
+ printf("ID_CDROM_MEDIA_BD_R=1\n");
+ if (cd_media_bd_re)
+ printf("ID_CDROM_MEDIA_BD_RE=1\n");
+ if (cd_media_hddvd)
+ printf("ID_CDROM_MEDIA_HDDVD=1\n");
+ if (cd_media_hddvd_r)
+ printf("ID_CDROM_MEDIA_HDDVD_R=1\n");
+ if (cd_media_hddvd_rw)
+ printf("ID_CDROM_MEDIA_HDDVD_RW=1\n");
+
+ if (cd_media_state != NULL)
+ printf("ID_CDROM_MEDIA_STATE=%s\n", cd_media_state);
+ if (cd_media_session_next > 0)
+ printf("ID_CDROM_MEDIA_SESSION_NEXT=%u\n", cd_media_session_next);
+ if (cd_media_session_count > 0)
+ printf("ID_CDROM_MEDIA_SESSION_COUNT=%u\n", cd_media_session_count);
+ if (cd_media_session_count > 1 && cd_media_session_last_offset > 0)
+ printf("ID_CDROM_MEDIA_SESSION_LAST_OFFSET=%llu\n", cd_media_session_last_offset);
+ if (cd_media_track_count > 0)
+ printf("ID_CDROM_MEDIA_TRACK_COUNT=%u\n", cd_media_track_count);
+ if (cd_media_track_count_audio > 0)
+ printf("ID_CDROM_MEDIA_TRACK_COUNT_AUDIO=%u\n", cd_media_track_count_audio);
+ if (cd_media_track_count_data > 0)
+ printf("ID_CDROM_MEDIA_TRACK_COUNT_DATA=%u\n", cd_media_track_count_data);
+exit:
+ if (fd >= 0)
+ close(fd);
+ udev_unref(udev);
+ log_close();
+ return rc;
+}
diff --git a/src/grp-udev/rules/.gitignore b/src/grp-udev/rules/.gitignore
new file mode 100644
index 0000000000..93a50ddd80
--- /dev/null
+++ b/src/grp-udev/rules/.gitignore
@@ -0,0 +1 @@
+/99-systemd.rules
diff --git a/src/grp-udev/rules/50-udev-default.rules b/src/grp-udev/rules/50-udev-default.rules
new file mode 100644
index 0000000000..e9eeb8518e
--- /dev/null
+++ b/src/grp-udev/rules/50-udev-default.rules
@@ -0,0 +1,77 @@
+# do not edit this file, it will be overwritten on update
+
+# run a command on remove events
+ACTION=="remove", ENV{REMOVE_CMD}!="", RUN+="$env{REMOVE_CMD}"
+ACTION=="remove", GOTO="default_end"
+
+SUBSYSTEM=="virtio-ports", KERNEL=="vport*", ATTR{name}=="?*", SYMLINK+="virtio-ports/$attr{name}"
+
+# select "system RTC" or just use the first one
+SUBSYSTEM=="rtc", ATTR{hctosys}=="1", SYMLINK+="rtc"
+SUBSYSTEM=="rtc", KERNEL=="rtc0", SYMLINK+="rtc", OPTIONS+="link_priority=-100"
+
+SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb"
+SUBSYSTEM=="input", ENV{ID_INPUT}=="", IMPORT{builtin}="input_id"
+ENV{MODALIAS}!="", IMPORT{builtin}="hwdb --subsystem=$env{SUBSYSTEM}"
+
+ACTION!="add", GOTO="default_end"
+
+SUBSYSTEM=="tty", KERNEL=="ptmx", GROUP="tty", MODE="0666"
+SUBSYSTEM=="tty", KERNEL=="tty", GROUP="tty", MODE="0666"
+SUBSYSTEM=="tty", KERNEL=="tty[0-9]*", GROUP="tty", MODE="0620"
+SUBSYSTEM=="tty", KERNEL=="sclp_line[0-9]*", GROUP="tty", MODE="0620"
+SUBSYSTEM=="tty", KERNEL=="ttysclp[0-9]*", GROUP="tty", MODE="0620"
+SUBSYSTEM=="tty", KERNEL=="3270/tty[0-9]*", GROUP="tty", MODE="0620"
+SUBSYSTEM=="vc", KERNEL=="vcs*|vcsa*", GROUP="tty"
+KERNEL=="tty[A-Z]*[0-9]|pppox[0-9]*|ircomm[0-9]*|noz[0-9]*|rfcomm[0-9]*", GROUP="dialout"
+
+SUBSYSTEM=="mem", KERNEL=="mem|kmem|port", GROUP="kmem", MODE="0640"
+
+SUBSYSTEM=="input", GROUP="input"
+SUBSYSTEM=="input", KERNEL=="js[0-9]*", MODE="0664"
+
+SUBSYSTEM=="video4linux", GROUP="video"
+SUBSYSTEM=="graphics", GROUP="video"
+SUBSYSTEM=="drm", GROUP="video"
+SUBSYSTEM=="dvb", GROUP="video"
+
+SUBSYSTEM=="sound", GROUP="audio", \
+ OPTIONS+="static_node=snd/seq", OPTIONS+="static_node=snd/timer"
+
+SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", MODE="0664"
+
+SUBSYSTEM=="firewire", ATTR{units}=="*0x00a02d:0x00010*", GROUP="video"
+SUBSYSTEM=="firewire", ATTR{units}=="*0x00b09d:0x00010*", GROUP="video"
+SUBSYSTEM=="firewire", ATTR{units}=="*0x00a02d:0x010001*", GROUP="video"
+SUBSYSTEM=="firewire", ATTR{units}=="*0x00a02d:0x014001*", GROUP="video"
+
+KERNEL=="parport[0-9]*", GROUP="lp"
+SUBSYSTEM=="printer", KERNEL=="lp*", GROUP="lp"
+SUBSYSTEM=="ppdev", GROUP="lp"
+KERNEL=="lp[0-9]*", GROUP="lp"
+KERNEL=="irlpt[0-9]*", GROUP="lp"
+SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{ID_USB_INTERFACES}=="*:0701??:*", GROUP="lp"
+
+SUBSYSTEM=="block", GROUP="disk"
+SUBSYSTEM=="block", KERNEL=="sr[0-9]*", GROUP="cdrom"
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="4|5", GROUP="cdrom"
+KERNEL=="sch[0-9]*", GROUP="cdrom"
+KERNEL=="pktcdvd[0-9]*", GROUP="cdrom"
+KERNEL=="pktcdvd", GROUP="cdrom"
+
+SUBSYSTEM=="scsi_generic|scsi_tape", SUBSYSTEMS=="scsi", ATTRS{type}=="1|8", GROUP="tape"
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="0", GROUP="disk"
+KERNEL=="qft[0-9]*|nqft[0-9]*|zqft[0-9]*|nzqft[0-9]*|rawqft[0-9]*|nrawqft[0-9]*", GROUP="disk"
+KERNEL=="loop-control", GROUP="disk", OPTIONS+="static_node=loop-control"
+KERNEL=="btrfs-control", GROUP="disk"
+KERNEL=="rawctl", GROUP="disk"
+SUBSYSTEM=="raw", KERNEL=="raw[0-9]*", GROUP="disk"
+SUBSYSTEM=="aoe", GROUP="disk", MODE="0220"
+SUBSYSTEM=="aoe", KERNEL=="err", MODE="0440"
+
+KERNEL=="rfkill", MODE="0664"
+KERNEL=="tun", MODE="0666", OPTIONS+="static_node=net/tun"
+
+KERNEL=="fuse", MODE="0666", OPTIONS+="static_node=fuse"
+
+LABEL="default_end"
diff --git a/src/grp-udev/rules/60-block.rules b/src/grp-udev/rules/60-block.rules
new file mode 100644
index 0000000000..c74caca49f
--- /dev/null
+++ b/src/grp-udev/rules/60-block.rules
@@ -0,0 +1,11 @@
+# do not edit this file, it will be overwritten on update
+
+# enable in-kernel media-presence polling
+ACTION=="add", SUBSYSTEM=="module", KERNEL=="block", ATTR{parameters/events_dfl_poll_msecs}=="0", \
+ ATTR{parameters/events_dfl_poll_msecs}="2000"
+
+# forward scsi device event to corresponding block device
+ACTION=="change", SUBSYSTEM=="scsi", ENV{DEVTYPE}=="scsi_device", TEST=="block", ATTR{block/*/uevent}="change"
+
+# watch metadata changes, caused by tools closing the device node which was opened for writing
+ACTION!="remove", SUBSYSTEM=="block", KERNEL=="loop*|nvme*|sd*|vd*|xvd*", OPTIONS+="watch"
diff --git a/src/grp-udev/rules/60-cdrom_id.rules b/src/grp-udev/rules/60-cdrom_id.rules
new file mode 100644
index 0000000000..5c3b52ebb9
--- /dev/null
+++ b/src/grp-udev/rules/60-cdrom_id.rules
@@ -0,0 +1,25 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="cdrom_end"
+SUBSYSTEM!="block", GOTO="cdrom_end"
+KERNEL!="sr[0-9]*|xvd*", GOTO="cdrom_end"
+ENV{DEVTYPE}!="disk", GOTO="cdrom_end"
+
+# unconditionally tag device as CDROM
+KERNEL=="sr[0-9]*", ENV{ID_CDROM}="1"
+
+# media eject button pressed
+ENV{DISK_EJECT_REQUEST}=="?*", RUN+="cdrom_id --eject-media $devnode", GOTO="cdrom_end"
+
+# import device and media properties and lock tray to
+# enable the receiving of media eject button events
+IMPORT{program}="cdrom_id --lock-media $devnode"
+
+# ejecting a CD does not remove the device node, so mark the systemd device
+# unit as inactive while there is no medium; this automatically cleans up of
+# stale mounts after ejecting
+ENV{DISK_MEDIA_CHANGE}=="?*", ENV{ID_CDROM_MEDIA}!="?*", ENV{SYSTEMD_READY}="0"
+
+KERNEL=="sr0", SYMLINK+="cdrom", OPTIONS+="link_priority=-100"
+
+LABEL="cdrom_end"
diff --git a/src/grp-udev/rules/60-drm.rules b/src/grp-udev/rules/60-drm.rules
new file mode 100644
index 0000000000..1ed3e445f2
--- /dev/null
+++ b/src/grp-udev/rules/60-drm.rules
@@ -0,0 +1,3 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="remove", SUBSYSTEM=="drm", SUBSYSTEMS=="pci|usb|platform", IMPORT{builtin}="path_id"
diff --git a/src/grp-udev/rules/60-evdev.rules b/src/grp-udev/rules/60-evdev.rules
new file mode 100644
index 0000000000..ade7e7f646
--- /dev/null
+++ b/src/grp-udev/rules/60-evdev.rules
@@ -0,0 +1,19 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="evdev_end"
+KERNEL!="event*", GOTO="evdev_end"
+
+# skip later rules when we find something for this input device
+IMPORT{builtin}="hwdb --subsystem=input --lookup-prefix=evdev:", \
+ RUN{builtin}+="keyboard", GOTO="evdev_end"
+
+# AT keyboard matching by the machine's DMI data
+ENV{ID_INPUT_KEY}=="?*", DRIVERS=="atkbd", \
+ IMPORT{builtin}="hwdb 'evdev:atkbd:$attr{[dmi/id]modalias}'", \
+ RUN{builtin}+="keyboard", GOTO="evdev_end"
+
+# device matching the input device name and the machine's DMI data
+KERNELS=="input*", IMPORT{builtin}="hwdb 'evdev:name:$attr{name}:$attr{[dmi/id]modalias}'", \
+ RUN{builtin}+="keyboard", GOTO="evdev_end"
+
+LABEL="evdev_end"
diff --git a/src/grp-udev/rules/60-persistent-alsa.rules b/src/grp-udev/rules/60-persistent-alsa.rules
new file mode 100644
index 0000000000..8154e2dbb5
--- /dev/null
+++ b/src/grp-udev/rules/60-persistent-alsa.rules
@@ -0,0 +1,14 @@
+# do not edit this file, it will be overwritten on update
+
+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{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}"
+
+IMPORT{builtin}="path_id"
+ENV{ID_PATH}=="?*", SYMLINK+="snd/by-path/$env{ID_PATH}"
+
+LABEL="persistent_alsa_end"
diff --git a/src/grp-udev/rules/60-persistent-input.rules b/src/grp-udev/rules/60-persistent-input.rules
new file mode 100644
index 0000000000..0e33e68384
--- /dev/null
+++ b/src/grp-udev/rules/60-persistent-input.rules
@@ -0,0 +1,38 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="persistent_input_end"
+SUBSYSTEM!="input", GOTO="persistent_input_end"
+SUBSYSTEMS=="bluetooth", GOTO="persistent_input_end"
+
+SUBSYSTEMS=="usb", ENV{ID_BUS}=="", IMPORT{builtin}="usb_id"
+
+# determine class name for persistent symlinks
+ENV{ID_INPUT_KEYBOARD}=="?*", ENV{.INPUT_CLASS}="kbd"
+ENV{ID_INPUT_MOUSE}=="?*", ENV{.INPUT_CLASS}="mouse"
+ENV{ID_INPUT_TOUCHPAD}=="?*", ENV{.INPUT_CLASS}="mouse"
+ENV{ID_INPUT_TABLET}=="?*", ENV{.INPUT_CLASS}="mouse"
+ENV{ID_INPUT_JOYSTICK}=="?*", ENV{.INPUT_CLASS}="joystick"
+DRIVERS=="pcspkr", ENV{.INPUT_CLASS}="spkr"
+ATTRS{name}=="*dvb*|*DVB*|* IR *", ENV{.INPUT_CLASS}="ir"
+
+# fill empty serial number
+ENV{.INPUT_CLASS}=="?*", ENV{ID_SERIAL}=="", ENV{ID_SERIAL}="noserial"
+
+# by-id links
+KERNEL=="mouse*|js*", ENV{ID_BUS}=="?*", ENV{.INPUT_CLASS}=="?*", ATTRS{bInterfaceNumber}=="|00", SYMLINK+="input/by-id/$env{ID_BUS}-$env{ID_SERIAL}-$env{.INPUT_CLASS}"
+KERNEL=="mouse*|js*", ENV{ID_BUS}=="?*", ENV{.INPUT_CLASS}=="?*", ATTRS{bInterfaceNumber}=="?*", ATTRS{bInterfaceNumber}!="00", SYMLINK+="input/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$attr{bInterfaceNumber}-$env{.INPUT_CLASS}"
+KERNEL=="event*", ENV{ID_BUS}=="?*", ENV{.INPUT_CLASS}=="?*", ATTRS{bInterfaceNumber}=="|00", SYMLINK+="input/by-id/$env{ID_BUS}-$env{ID_SERIAL}-event-$env{.INPUT_CLASS}"
+KERNEL=="event*", ENV{ID_BUS}=="?*", ENV{.INPUT_CLASS}=="?*", ATTRS{bInterfaceNumber}=="?*", ATTRS{bInterfaceNumber}!="00", SYMLINK+="input/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$attr{bInterfaceNumber}-event-$env{.INPUT_CLASS}"
+# allow empty class for USB devices, by appending the interface number
+SUBSYSTEMS=="usb", ENV{ID_BUS}=="?*", KERNEL=="event*", ENV{.INPUT_CLASS}=="", ATTRS{bInterfaceNumber}=="?*", \
+ SYMLINK+="input/by-id/$env{ID_BUS}-$env{ID_SERIAL}-event-if$attr{bInterfaceNumber}"
+
+# by-path
+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
+SUBSYSTEMS=="usb|platform", ENV{ID_PATH}=="?*", KERNEL=="event*", ENV{.INPUT_CLASS}=="", \
+ SYMLINK+="input/by-path/$env{ID_PATH}-event"
+
+LABEL="persistent_input_end"
diff --git a/src/grp-udev/rules/60-persistent-storage-tape.rules b/src/grp-udev/rules/60-persistent-storage-tape.rules
new file mode 100644
index 0000000000..f2eabd92a8
--- /dev/null
+++ b/src/grp-udev/rules/60-persistent-storage-tape.rules
@@ -0,0 +1,25 @@
+# do not edit this file, it will be overwritten on update
+
+# persistent storage links: /dev/tape/{by-id,by-path}
+
+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 $devnode", \
+ 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{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{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{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"
+
+LABEL="persistent_storage_tape_end"
diff --git a/src/grp-udev/rules/60-persistent-storage.rules b/src/grp-udev/rules/60-persistent-storage.rules
new file mode 100644
index 0000000000..408733915c
--- /dev/null
+++ b/src/grp-udev/rules/60-persistent-storage.rules
@@ -0,0 +1,87 @@
+# do not edit this file, it will be overwritten on update
+
+# persistent storage links: /dev/disk/{by-id,by-uuid,by-label,by-path}
+# scheme based on "Linux persistent device names", 2004, Hannes Reinecke <hare@suse.de>
+
+ACTION=="remove", GOTO="persistent_storage_end"
+
+SUBSYSTEM!="block", GOTO="persistent_storage_end"
+KERNEL!="loop*|mmcblk*[0-9]|msblk*[0-9]|mspblk*[0-9]|nvme*|sd*|sr*|vd*|xvd*|bcache*|cciss*|dasd*|ubd*", GOTO="persistent_storage_end"
+
+# ignore partitions that span the entire disk
+TEST=="whole_disk", GOTO="persistent_storage_end"
+
+# for partitions import parent information
+ENV{DEVTYPE}=="partition", IMPORT{parent}="ID_*"
+
+# NVMe
+KERNEL=="nvme*[0-9]n*[0-9]", ATTR{wwid}=="?*", SYMLINK+="disk/by-id/nvme-$attr{wwid}"
+KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{wwid}=="?*", SYMLINK+="disk/by-id/nvme-$attr{wwid}-part%n"
+
+# virtio-blk
+KERNEL=="vd*[!0-9]", ATTRS{serial}=="?*", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/virtio-$env{ID_SERIAL}"
+KERNEL=="vd*[0-9]", ATTRS{serial}=="?*", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/virtio-$env{ID_SERIAL}-part%n"
+
+# ATA
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", IMPORT{program}="ata_id --export $devnode"
+
+# ATAPI devices (SPC-3 or later)
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{type}=="5", ATTRS{scsi_level}=="[6-9]*", IMPORT{program}="ata_id --export $devnode"
+
+# 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 $devnode"
+
+# Fall back usb_id for USB devices
+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 $devnode", ENV{ID_BUS}="scsi"
+KERNEL=="cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="cciss"
+KERNEL=="sd*|sr*|cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
+KERNEL=="sd*|cciss*", ENV{DEVTYPE}=="partition", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}-part%n"
+
+# FireWire
+KERNEL=="sd*[!0-9]|sr*", ATTRS{ieee1394_id}=="?*", SYMLINK+="disk/by-id/ieee1394-$attr{ieee1394_id}"
+KERNEL=="sd*[0-9]", ATTRS{ieee1394_id}=="?*", SYMLINK+="disk/by-id/ieee1394-$attr{ieee1394_id}-part%n"
+
+# MMC
+KERNEL=="mmcblk[0-9]", SUBSYSTEMS=="mmc", ATTRS{name}=="?*", ATTRS{serial}=="?*", \
+ ENV{ID_NAME}="$attr{name}", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/mmc-$env{ID_NAME}_$env{ID_SERIAL}"
+KERNEL=="mmcblk[0-9]p[0-9]", ENV{ID_NAME}=="?*", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/mmc-$env{ID_NAME}_$env{ID_SERIAL}-part%n"
+
+# Memstick
+KERNEL=="msblk[0-9]|mspblk[0-9]", SUBSYSTEMS=="memstick", ATTRS{name}=="?*", ATTRS{serial}=="?*", \
+ ENV{ID_NAME}="$attr{name}", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/memstick-$env{ID_NAME}_$env{ID_SERIAL}"
+KERNEL=="msblk[0-9]p[0-9]|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
+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"
+
+# probe filesystem metadata of optical drives which have a media inserted
+KERNEL=="sr*", ENV{DISK_EJECT_REQUEST}!="?*", ENV{ID_CDROM_MEDIA_TRACK_COUNT_DATA}=="?*", ENV{ID_CDROM_MEDIA_SESSION_LAST_OFFSET}=="?*", \
+ IMPORT{builtin}="blkid --offset=$env{ID_CDROM_MEDIA_SESSION_LAST_OFFSET}"
+# single-session CDs do not have ID_CDROM_MEDIA_SESSION_LAST_OFFSET
+KERNEL=="sr*", ENV{DISK_EJECT_REQUEST}!="?*", ENV{ID_CDROM_MEDIA_TRACK_COUNT_DATA}=="?*", ENV{ID_CDROM_MEDIA_SESSION_LAST_OFFSET}=="", \
+ IMPORT{builtin}="blkid --noraid"
+
+# probe filesystem metadata of disks
+KERNEL!="sr*", IMPORT{builtin}="blkid"
+
+# by-label/by-uuid links (filesystem metadata)
+ENV{ID_FS_USAGE}=="filesystem|other|crypto", ENV{ID_FS_UUID_ENC}=="?*", SYMLINK+="disk/by-uuid/$env{ID_FS_UUID_ENC}"
+ENV{ID_FS_USAGE}=="filesystem|other", ENV{ID_FS_LABEL_ENC}=="?*", SYMLINK+="disk/by-label/$env{ID_FS_LABEL_ENC}"
+
+# by-id (World Wide Name)
+ENV{DEVTYPE}=="disk", ENV{ID_WWN_WITH_EXTENSION}=="?*", SYMLINK+="disk/by-id/wwn-$env{ID_WWN_WITH_EXTENSION}"
+ENV{DEVTYPE}=="partition", ENV{ID_WWN_WITH_EXTENSION}=="?*", SYMLINK+="disk/by-id/wwn-$env{ID_WWN_WITH_EXTENSION}-part%n"
+
+# by-partlabel/by-partuuid links (partition metadata)
+ENV{ID_PART_ENTRY_UUID}=="?*", SYMLINK+="disk/by-partuuid/$env{ID_PART_ENTRY_UUID}"
+ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_ENTRY_NAME}=="?*", SYMLINK+="disk/by-partlabel/$env{ID_PART_ENTRY_NAME}"
+
+# add symlink to GPT root disk
+ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_GPT_AUTO_ROOT}=="1", SYMLINK+="gpt-auto-root"
+
+LABEL="persistent_storage_end"
diff --git a/src/grp-udev/rules/60-persistent-v4l.rules b/src/grp-udev/rules/60-persistent-v4l.rules
new file mode 100644
index 0000000000..93c5ee8c27
--- /dev/null
+++ b/src/grp-udev/rules/60-persistent-v4l.rules
@@ -0,0 +1,20 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="persistent_v4l_end"
+SUBSYSTEM!="video4linux", GOTO="persistent_v4l_end"
+ENV{MAJOR}=="", GOTO="persistent_v4l_end"
+
+IMPORT{program}="v4l_id $devnode"
+
+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{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}"
+
+LABEL="persistent_v4l_end"
diff --git a/src/grp-udev/rules/60-serial.rules b/src/grp-udev/rules/60-serial.rules
new file mode 100644
index 0000000000..f303e27fd5
--- /dev/null
+++ b/src/grp-udev/rules/60-serial.rules
@@ -0,0 +1,26 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="serial_end"
+SUBSYSTEM!="tty", GOTO="serial_end"
+
+SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}"
+SUBSYSTEMS=="pci", IMPORT{builtin}="hwdb --subsystem=pci"
+SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb"
+
+# /dev/serial/by-path/, /dev/serial/by-id/ for USB devices
+KERNEL!="ttyUSB[0-9]*|ttyACM[0-9]*", GOTO="serial_end"
+
+SUBSYSTEMS=="usb-serial", ENV{.ID_PORT}="$attr{port_number}"
+
+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{builtin}="usb_id"
+ENV{ID_SERIAL}=="", GOTO="serial_end"
+SUBSYSTEMS=="usb", ENV{ID_USB_INTERFACE_NUM}="$attr{bInterfaceNumber}"
+ENV{ID_USB_INTERFACE_NUM}=="", GOTO="serial_end"
+ENV{.ID_PORT}=="", SYMLINK+="serial/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$env{ID_USB_INTERFACE_NUM}"
+ENV{.ID_PORT}=="?*", SYMLINK+="serial/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$env{ID_USB_INTERFACE_NUM}-port$env{.ID_PORT}"
+
+LABEL="serial_end"
diff --git a/src/grp-udev/rules/64-btrfs.rules b/src/grp-udev/rules/64-btrfs.rules
new file mode 100644
index 0000000000..fe0100131e
--- /dev/null
+++ b/src/grp-udev/rules/64-btrfs.rules
@@ -0,0 +1,13 @@
+# do not edit this file, it will be overwritten on update
+
+SUBSYSTEM!="block", GOTO="btrfs_end"
+ACTION=="remove", GOTO="btrfs_end"
+ENV{ID_FS_TYPE}!="btrfs", GOTO="btrfs_end"
+
+# let the kernel know about this btrfs filesystem, and check if it is complete
+IMPORT{builtin}="btrfs ready $devnode"
+
+# mark the device as not ready to be used by the system
+ENV{ID_BTRFS_READY}=="0", ENV{SYSTEMD_READY}="0"
+
+LABEL="btrfs_end"
diff --git a/src/grp-udev/rules/70-mouse.rules b/src/grp-udev/rules/70-mouse.rules
new file mode 100644
index 0000000000..3ea743aff9
--- /dev/null
+++ b/src/grp-udev/rules/70-mouse.rules
@@ -0,0 +1,18 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="mouse_end"
+KERNEL!="event*", GOTO="mouse_end"
+ENV{ID_INPUT_MOUSE}=="", GOTO="mouse_end"
+
+# mouse:<subsystem>:v<vid>p<pid>:name:<name>:*
+KERNELS=="input*", ENV{ID_BUS}=="usb", \
+ IMPORT{builtin}="hwdb 'mouse:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'", \
+ GOTO="mouse_end"
+KERNELS=="input*", ENV{ID_BUS}=="bluetooth", \
+ IMPORT{builtin}="hwdb 'mouse:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'", \
+ GOTO="mouse_end"
+DRIVERS=="psmouse", SUBSYSTEMS=="serio", \
+ IMPORT{builtin}="hwdb 'mouse:ps2::name:$attr{device/name}:'", \
+ GOTO="mouse_end"
+
+LABEL="mouse_end"
diff --git a/src/grp-udev/rules/75-net-description.rules b/src/grp-udev/rules/75-net-description.rules
new file mode 100644
index 0000000000..7e62f8b26b
--- /dev/null
+++ b/src/grp-udev/rules/75-net-description.rules
@@ -0,0 +1,14 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="net_end"
+SUBSYSTEM!="net", GOTO="net_end"
+
+IMPORT{builtin}="net_id"
+
+SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb"
+SUBSYSTEMS=="usb", GOTO="net_end"
+
+SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}"
+SUBSYSTEMS=="pci", IMPORT{builtin}="hwdb --subsystem=pci"
+
+LABEL="net_end"
diff --git a/src/grp-udev/rules/75-probe_mtd.rules b/src/grp-udev/rules/75-probe_mtd.rules
new file mode 100644
index 0000000000..8848aeeaed
--- /dev/null
+++ b/src/grp-udev/rules/75-probe_mtd.rules
@@ -0,0 +1,7 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add", GOTO="mtd_probe_end"
+
+KERNEL=="mtd*ro", IMPORT{program}="mtd_probe $devnode"
+
+LABEL="mtd_probe_end"
diff --git a/src/grp-udev/rules/78-sound-card.rules b/src/grp-udev/rules/78-sound-card.rules
new file mode 100644
index 0000000000..04740e8b97
--- /dev/null
+++ b/src/grp-udev/rules/78-sound-card.rules
@@ -0,0 +1,89 @@
+# do not edit this file, it will be overwritten on update
+
+SUBSYSTEM!="sound", GOTO="sound_end"
+
+ACTION=="add|change", KERNEL=="controlC*", ATTR{../uevent}="change"
+ACTION!="change", GOTO="sound_end"
+
+# Ok, we probably need a little explanation here for what the two lines above
+# are good for.
+#
+# The story goes like this: when ALSA registers a new sound card it emits a
+# series of 'add' events to userspace, for the main card device and for all the
+# child device nodes that belong to it. udev relays those to applications,
+# however only maintains the order between father and child, but not between
+# the siblings. The control device node creation can be used as synchronization
+# point. All other devices that belong to a card are created in the kernel
+# before it. However unfortunately due to the fact that siblings are forwarded
+# out of order by udev this fact is lost to applications.
+#
+# OTOH before an application can open a device it needs to make sure that all
+# its device nodes are completely created and set up.
+#
+# As a workaround for this issue we have added the udev rule above which will
+# generate a 'change' event on the main card device from the 'add' event of the
+# card's control device. Due to the ordering semantics of udev this event will
+# only be relayed after all child devices have finished processing properly.
+# When an application needs to listen for appearing devices it can hence look
+# for 'change' events only, and ignore the actual 'add' events.
+#
+# When the application is initialized at the same time as a device is plugged
+# in it may need to figure out if the 'change' event has already been triggered
+# or not for a card. To find that out we store the flag environment variable
+# SOUND_INITIALIZED on the device which simply tells us if the card 'change'
+# event has already been processed.
+
+KERNEL!="card*", GOTO="sound_end"
+
+ENV{SOUND_INITIALIZED}="1"
+
+IMPORT{builtin}="hwdb"
+SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
+SUBSYSTEMS=="usb", GOTO="skip_pci"
+
+SUBSYSTEMS=="firewire", ATTRS{guid}=="?*", \
+ ENV{ID_BUS}="firewire", ENV{ID_SERIAL}="$attr{guid}", ENV{ID_SERIAL_SHORT}="$attr{guid}", \
+ ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{model}", \
+ ENV{ID_VENDOR}="$attr{vendor_name}", ENV{ID_MODEL}="$attr{model_name}"
+SUBSYSTEMS=="firewire", GOTO="skip_pci"
+
+SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}"
+LABEL="skip_pci"
+
+# Define ID_ID if ID_BUS and ID_SERIAL are set. This will work for both
+# USB and firewire.
+ENV{ID_SERIAL}=="?*", ENV{ID_USB_INTERFACE_NUM}=="?*", ENV{ID_ID}="$env{ID_BUS}-$env{ID_SERIAL}-$env{ID_USB_INTERFACE_NUM}"
+ENV{ID_SERIAL}=="?*", ENV{ID_USB_INTERFACE_NUM}=="", ENV{ID_ID}="$env{ID_BUS}-$env{ID_SERIAL}"
+
+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
+# PA_PROP_DEVICE_FORM_FACTOR, PA_PROP_DEVICE_CLASS properties.
+
+# If the first PCM device of this card has the pcm class 'modem', then the card is a modem
+ATTR{pcmC%nD0p/pcm_class}=="modem", ENV{SOUND_CLASS}="modem", GOTO="sound_end"
+
+# Identify cards on the internal PCI bus as internal
+SUBSYSTEMS=="pci", DEVPATH=="*/0000:00:??.?/sound/*", ENV{SOUND_FORM_FACTOR}="internal", GOTO="sound_end"
+
+# Devices that also support Image/Video interfaces are most likely webcams
+SUBSYSTEMS=="usb", ENV{ID_USB_INTERFACES}=="*:0e????:*", ENV{SOUND_FORM_FACTOR}="webcam", GOTO="sound_end"
+
+# Matching on the model strings is a bit ugly, I admit
+ENV{ID_MODEL}=="*[Ss]peaker*", ENV{SOUND_FORM_FACTOR}="speaker", GOTO="sound_end"
+ENV{ID_MODEL_FROM_DATABASE}=="*[Ss]peaker*", ENV{SOUND_FORM_FACTOR}="speaker", GOTO="sound_end"
+
+ENV{ID_MODEL}=="*[Hh]eadphone*", ENV{SOUND_FORM_FACTOR}="headphone", GOTO="sound_end"
+ENV{ID_MODEL_FROM_DATABASE}=="*[Hh]eadphone*", ENV{SOUND_FORM_FACTOR}="headphone", GOTO="sound_end"
+
+ENV{ID_MODEL}=="*[Hh]eadset*", ENV{SOUND_FORM_FACTOR}="headset", GOTO="sound_end"
+ENV{ID_MODEL_FROM_DATABASE}=="*[Hh]eadset*", ENV{SOUND_FORM_FACTOR}="headset", GOTO="sound_end"
+
+ENV{ID_MODEL}=="*[Hh]andset*", ENV{SOUND_FORM_FACTOR}="handset", GOTO="sound_end"
+ENV{ID_MODEL_FROM_DATABASE}=="*[Hh]andset*", ENV{SOUND_FORM_FACTOR}="handset", GOTO="sound_end"
+
+ENV{ID_MODEL}=="*[Mm]icrophone*", ENV{SOUND_FORM_FACTOR}="microphone", GOTO="sound_end"
+ENV{ID_MODEL_FROM_DATABASE}=="*[Mm]icrophone*", ENV{SOUND_FORM_FACTOR}="microphone", GOTO="sound_end"
+
+LABEL="sound_end"
diff --git a/src/grp-udev/rules/80-drivers.rules b/src/grp-udev/rules/80-drivers.rules
new file mode 100644
index 0000000000..8551f47a4b
--- /dev/null
+++ b/src/grp-udev/rules/80-drivers.rules
@@ -0,0 +1,13 @@
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="drivers_end"
+
+ENV{MODALIAS}=="?*", RUN{builtin}+="kmod load $env{MODALIAS}"
+SUBSYSTEM=="tifm", ENV{TIFM_CARD_TYPE}=="SD", RUN{builtin}+="kmod load tifm_sd"
+SUBSYSTEM=="tifm", ENV{TIFM_CARD_TYPE}=="MS", RUN{builtin}+="kmod load tifm_ms"
+SUBSYSTEM=="memstick", RUN{builtin}+="kmod load ms_block mspro_block"
+SUBSYSTEM=="i2o", RUN{builtin}+="kmod load i2o_block"
+SUBSYSTEM=="module", KERNEL=="parport_pc", RUN{builtin}+="kmod load ppdev"
+KERNEL=="mtd*ro", ENV{MTD_FTL}=="smartmedia", RUN{builtin}+="kmod load sm_ftl"
+
+LABEL="drivers_end"
diff --git a/src/grp-udev/rules/80-net-setup-link.rules b/src/grp-udev/rules/80-net-setup-link.rules
new file mode 100644
index 0000000000..6e411a91f0
--- /dev/null
+++ b/src/grp-udev/rules/80-net-setup-link.rules
@@ -0,0 +1,13 @@
+# do not edit this file, it will be overwritten on update
+
+SUBSYSTEM!="net", GOTO="net_setup_link_end"
+
+IMPORT{builtin}="path_id"
+
+ACTION!="add", GOTO="net_setup_link_end"
+
+IMPORT{builtin}="net_setup_link"
+
+NAME=="", ENV{ID_NET_NAME}!="", NAME="$env{ID_NET_NAME}"
+
+LABEL="net_setup_link_end"
diff --git a/src/grp-udev/rules/99-systemd.rules.in b/src/grp-udev/rules/99-systemd.rules.in
new file mode 100644
index 0000000000..fb4517606d
--- /dev/null
+++ b/src/grp-udev/rules/99-systemd.rules.in
@@ -0,0 +1,67 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+ACTION=="remove", GOTO="systemd_end"
+
+SUBSYSTEM=="tty", KERNEL=="tty[a-zA-Z]*|hvc*|xvc*|hvsi*|ttysclp*|sclp_line*|3270/tty[0-9]*", TAG+="systemd"
+KERNEL=="vport*", TAG+="systemd"
+
+SUBSYSTEM=="block", TAG+="systemd"
+SUBSYSTEM=="block", ACTION=="add", ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}=="1", ENV{SYSTEMD_READY}="0"
+
+# Ignore encrypted devices with no identified superblock on it, since
+# we are probably still calling mke2fs or mkswap on it.
+SUBSYSTEM=="block", ENV{DM_UUID}=="CRYPT-*", ENV{ID_PART_TABLE_TYPE}=="", ENV{ID_FS_USAGE}=="", ENV{SYSTEMD_READY}="0"
+
+# Ignore raid devices that are not yet assembled and started
+SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", KERNEL=="md*", TEST!="md/array_state", ENV{SYSTEMD_READY}="0"
+SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", KERNEL=="md*", ATTR{md/array_state}=="|clear|inactive", ENV{SYSTEMD_READY}="0"
+
+# Ignore loop devices that don't have any file attached
+SUBSYSTEM=="block", KERNEL=="loop[0-9]*", ENV{DEVTYPE}=="disk", TEST!="loop/backing_file", ENV{SYSTEMD_READY}="0"
+
+# Ignore nbd devices until the PID file exists (which signals a connected device)
+SUBSYSTEM=="block", KERNEL=="nbd*", ENV{DEVTYPE}=="disk", TEST!="pid", ENV{SYSTEMD_READY}="0"
+
+# We need a hardware independent way to identify network devices. We
+# use the /sys/subsystem/ path for this. Kernel "bus" and "class" names
+# should be treated as one namespace, like udev handles it. This is mostly
+# just an identification string for systemd, so whether the path actually is
+# accessible or not does not matter as long as it is unique and in the
+# filesystem namespace.
+#
+# http://cgit.freedesktop.org/systemd/systemd/tree/src/libudev/libudev-enumerate.c#n955
+
+SUBSYSTEM=="net", KERNEL!="lo", TAG+="systemd", ENV{SYSTEMD_ALIAS}+="/sys/subsystem/net/devices/$name"
+SUBSYSTEM=="bluetooth", TAG+="systemd", ENV{SYSTEMD_ALIAS}+="/sys/subsystem/bluetooth/devices/%k"
+
+SUBSYSTEM=="bluetooth", TAG+="systemd", ENV{SYSTEMD_WANTS}+="bluetooth.target"
+ENV{ID_SMARTCARD_READER}=="?*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="smartcard.target"
+SUBSYSTEM=="sound", KERNEL=="card*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sound.target"
+
+SUBSYSTEM=="printer", TAG+="systemd", ENV{SYSTEMD_WANTS}+="printer.target"
+SUBSYSTEM=="usb", KERNEL=="lp*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="printer.target"
+SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{ID_USB_INTERFACES}=="*:0701??:*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="printer.target"
+
+# Apply sysctl variables to network devices (and only to those) as they appear.
+ACTION=="add", SUBSYSTEM=="net", KERNEL!="lo", RUN+="@rootlibexecdir@/systemd-sysctl --prefix=/net/ipv4/conf/$name --prefix=/net/ipv4/neigh/$name --prefix=/net/ipv6/conf/$name --prefix=/net/ipv6/neigh/$name"
+
+# Pull in backlight save/restore for all backlight devices and
+# keyboard backlights
+SUBSYSTEM=="backlight", TAG+="systemd", IMPORT{builtin}="path_id", ENV{SYSTEMD_WANTS}+="systemd-backlight@backlight:$name.service"
+SUBSYSTEM=="leds", KERNEL=="*kbd_backlight", TAG+="systemd", IMPORT{builtin}="path_id", ENV{SYSTEMD_WANTS}+="systemd-backlight@leds:$name.service"
+
+# Pull in rfkill save/restore for all rfkill devices
+
+SUBSYSTEM=="rfkill", IMPORT{builtin}="path_id"
+SUBSYSTEM=="misc", KERNEL=="rfkill", TAG+="systemd", ENV{SYSTEMD_WANTS}+="systemd-rfkill.socket"
+
+# Asynchronously mount file systems implemented by these modules as soon as they are loaded.
+SUBSYSTEM=="module", KERNEL=="fuse", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sys-fs-fuse-connections.mount"
+SUBSYSTEM=="module", KERNEL=="configfs", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sys-kernel-config.mount"
+
+LABEL="systemd_end"
diff --git a/src/grp-udev/scsi_id/.gitignore b/src/grp-udev/scsi_id/.gitignore
new file mode 100644
index 0000000000..6aebddd809
--- /dev/null
+++ b/src/grp-udev/scsi_id/.gitignore
@@ -0,0 +1 @@
+scsi_id_version.h
diff --git a/src/grp-udev/scsi_id/README b/src/grp-udev/scsi_id/README
new file mode 100644
index 0000000000..9cfe73991c
--- /dev/null
+++ b/src/grp-udev/scsi_id/README
@@ -0,0 +1,4 @@
+scsi_id - generate a SCSI unique identifier for a given SCSI device
+
+Please send questions, comments or patches to <patmans@us.ibm.com> or
+<linux-hotplug-devel@lists.sourceforge.net>.
diff --git a/src/grp-udev/scsi_id/scsi.h b/src/grp-udev/scsi_id/scsi.h
new file mode 100644
index 0000000000..a27a84a40a
--- /dev/null
+++ b/src/grp-udev/scsi_id/scsi.h
@@ -0,0 +1,99 @@
+#pragma once
+
+/*
+ * scsi.h
+ *
+ * General scsi and linux scsi specific defines and structs.
+ *
+ * Copyright (C) IBM Corp. 2003
+ *
+ * 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 version 2 of the License.
+ */
+
+#include <scsi/scsi.h>
+
+struct scsi_ioctl_command {
+ unsigned int inlen; /* excluding scsi command length */
+ unsigned int outlen;
+ unsigned char data[1];
+ /* on input, scsi command starts here then opt. data */
+};
+
+/*
+ * Default 5 second timeout
+ */
+#define DEF_TIMEOUT 5000
+
+#define SENSE_BUFF_LEN 32
+
+/*
+ * The request buffer size passed to the SCSI INQUIRY commands, use 254,
+ * as this is a nice value for some devices, especially some of the usb
+ * mass storage devices.
+ */
+#define SCSI_INQ_BUFF_LEN 254
+
+/*
+ * SCSI INQUIRY vendor and model (really product) lengths.
+ */
+#define VENDOR_LENGTH 8
+#define MODEL_LENGTH 16
+
+#define INQUIRY_CMD 0x12
+#define INQUIRY_CMDLEN 6
+
+/*
+ * INQUIRY VPD page 0x83 identifier descriptor related values. Reference the
+ * SCSI Primary Commands specification for details.
+ */
+
+/*
+ * id type values of id descriptors. These are assumed to fit in 4 bits.
+ */
+#define SCSI_ID_VENDOR_SPECIFIC 0
+#define SCSI_ID_T10_VENDOR 1
+#define SCSI_ID_EUI_64 2
+#define SCSI_ID_NAA 3
+#define SCSI_ID_RELPORT 4
+#define SCSI_ID_TGTGROUP 5
+#define SCSI_ID_LUNGROUP 6
+#define SCSI_ID_MD5 7
+#define SCSI_ID_NAME 8
+
+/*
+ * Supported NAA values. These fit in 4 bits, so the "don't care" value
+ * cannot conflict with real values.
+ */
+#define SCSI_ID_NAA_DONT_CARE 0xff
+#define SCSI_ID_NAA_IEEE_REG 0x05
+#define SCSI_ID_NAA_IEEE_REG_EXTENDED 0x06
+
+/*
+ * Supported Code Set values.
+ */
+#define SCSI_ID_BINARY 1
+#define SCSI_ID_ASCII 2
+
+struct scsi_id_search_values {
+ u_char id_type;
+ u_char naa_type;
+ u_char code_set;
+};
+
+/*
+ * Following are the "true" SCSI status codes. Linux has traditionally
+ * used a 1 bit right and masked version of these. So now CHECK_CONDITION
+ * and friends (in <scsi/scsi.h>) are deprecated.
+ */
+#define SCSI_CHECK_CONDITION 0x02
+#define SCSI_CONDITION_MET 0x04
+#define SCSI_BUSY 0x08
+#define SCSI_IMMEDIATE 0x10
+#define SCSI_IMMEDIATE_CONDITION_MET 0x14
+#define SCSI_RESERVATION_CONFLICT 0x18
+#define SCSI_COMMAND_TERMINATED 0x22
+#define SCSI_TASK_SET_FULL 0x28
+#define SCSI_ACA_ACTIVE 0x30
+#define SCSI_TASK_ABORTED 0x40
diff --git a/src/grp-udev/scsi_id/scsi_id.c b/src/grp-udev/scsi_id/scsi_id.c
new file mode 100644
index 0000000000..4655691642
--- /dev/null
+++ b/src/grp-udev/scsi_id/scsi_id.c
@@ -0,0 +1,625 @@
+/*
+ * Copyright (C) IBM Corp. 2003
+ * Copyright (C) SUSE Linux Products GmbH, 2006
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "libudev.h"
+
+#include "fd-util.h"
+#include "libudev-private.h"
+#include "scsi_id.h"
+#include "string-util.h"
+#include "udev-util.h"
+
+static const struct option options[] = {
+ { "device", required_argument, NULL, 'd' },
+ { "config", required_argument, NULL, 'f' },
+ { "page", required_argument, NULL, 'p' },
+ { "blacklisted", no_argument, NULL, 'b' },
+ { "whitelisted", no_argument, NULL, 'g' },
+ { "replace-whitespace", no_argument, NULL, 'u' },
+ { "sg-version", required_argument, NULL, 's' },
+ { "verbose", no_argument, NULL, 'v' },
+ { "version", no_argument, NULL, 'V' }, /* don't advertise -V */
+ { "export", no_argument, NULL, 'x' },
+ { "help", no_argument, NULL, 'h' },
+ {}
+};
+
+static bool all_good = false;
+static bool dev_specified = false;
+static char config_file[MAX_PATH_LEN] = "/etc/scsi_id.config";
+static enum page_code default_page_code = PAGE_UNSPECIFIED;
+static int sg_version = 4;
+static bool reformat_serial = false;
+static bool export = false;
+static char vendor_str[64];
+static char model_str[64];
+static char vendor_enc_str[256];
+static char model_enc_str[256];
+static char revision_str[16];
+static char type_str[16];
+
+static void set_type(const char *from, char *to, size_t len)
+{
+ int type_num;
+ char *eptr;
+ const char *type = "generic";
+
+ type_num = strtoul(from, &eptr, 0);
+ if (eptr != from) {
+ switch (type_num) {
+ case 0:
+ type = "disk";
+ break;
+ case 1:
+ type = "tape";
+ break;
+ case 4:
+ type = "optical";
+ break;
+ case 5:
+ type = "cd";
+ break;
+ case 7:
+ type = "optical";
+ break;
+ case 0xe:
+ type = "disk";
+ break;
+ case 0xf:
+ type = "optical";
+ break;
+ default:
+ break;
+ }
+ }
+ strscpy(to, len, type);
+}
+
+/*
+ * get_value:
+ *
+ * buf points to an '=' followed by a quoted string ("foo") or a string ending
+ * with a space or ','.
+ *
+ * Return a pointer to the NUL terminated string, returns NULL if no
+ * matches.
+ */
+static char *get_value(char **buffer)
+{
+ static const char *quote_string = "\"\n";
+ static const char *comma_string = ",\n";
+ char *val;
+ const char *end;
+
+ if (**buffer == '"') {
+ /*
+ * skip leading quote, terminate when quote seen
+ */
+ (*buffer)++;
+ end = quote_string;
+ } else {
+ end = comma_string;
+ }
+ val = strsep(buffer, end);
+ if (val && end == quote_string)
+ /*
+ * skip trailing quote
+ */
+ (*buffer)++;
+
+ while (isspace(**buffer))
+ (*buffer)++;
+
+ return val;
+}
+
+static int argc_count(char *opts)
+{
+ int i = 0;
+ while (*opts != '\0')
+ if (*opts++ == ' ')
+ i++;
+ return i;
+}
+
+/*
+ * get_file_options:
+ *
+ * If vendor == NULL, find a line in the config file with only "OPTIONS=";
+ * if vendor and model are set find the first OPTIONS line in the config
+ * file that matches. Set argc and argv to match the OPTIONS string.
+ *
+ * vendor and model can end in '\n'.
+ */
+static int get_file_options(struct udev *udev,
+ const char *vendor, const char *model,
+ int *argc, char ***newargv)
+{
+ char *buffer;
+ _cleanup_fclose_ FILE *f;
+ char *buf;
+ char *str1;
+ char *vendor_in, *model_in, *options_in; /* read in from file */
+ int lineno;
+ int c;
+ int retval = 0;
+
+ f = fopen(config_file, "re");
+ if (f == NULL) {
+ if (errno == ENOENT)
+ return 1;
+ else {
+ log_error_errno(errno, "can't open %s: %m", config_file);
+ return -1;
+ }
+ }
+
+ /*
+ * Allocate a buffer rather than put it on the stack so we can
+ * keep it around to parse any options (any allocated newargv
+ * points into this buffer for its strings).
+ */
+ buffer = malloc(MAX_BUFFER_LEN);
+ if (!buffer)
+ return log_oom();
+
+ *newargv = NULL;
+ lineno = 0;
+ for (;;) {
+ vendor_in = model_in = options_in = NULL;
+
+ buf = fgets(buffer, MAX_BUFFER_LEN, f);
+ if (buf == NULL)
+ break;
+ lineno++;
+ if (buf[strlen(buffer) - 1] != '\n') {
+ log_error("Config file line %d too long", lineno);
+ break;
+ }
+
+ while (isspace(*buf))
+ buf++;
+
+ /* blank or all whitespace line */
+ if (*buf == '\0')
+ continue;
+
+ /* comment line */
+ if (*buf == '#')
+ continue;
+
+ str1 = strsep(&buf, "=");
+ if (str1 && strcaseeq(str1, "VENDOR")) {
+ str1 = get_value(&buf);
+ if (!str1) {
+ retval = log_oom();
+ break;
+ }
+ vendor_in = str1;
+
+ str1 = strsep(&buf, "=");
+ if (str1 && strcaseeq(str1, "MODEL")) {
+ str1 = get_value(&buf);
+ if (!str1) {
+ retval = log_oom();
+ break;
+ }
+ model_in = str1;
+ str1 = strsep(&buf, "=");
+ }
+ }
+
+ if (str1 && strcaseeq(str1, "OPTIONS")) {
+ str1 = get_value(&buf);
+ if (!str1) {
+ retval = log_oom();
+ break;
+ }
+ options_in = str1;
+ }
+
+ /*
+ * Only allow: [vendor=foo[,model=bar]]options=stuff
+ */
+ if (!options_in || (!vendor_in && model_in)) {
+ log_error("Error parsing config file line %d '%s'", lineno, buffer);
+ retval = -1;
+ break;
+ }
+ if (vendor == NULL) {
+ if (vendor_in == NULL)
+ break;
+ } else if (vendor_in &&
+ strneq(vendor, vendor_in, strlen(vendor_in)) &&
+ (!model_in ||
+ (strneq(model, model_in, strlen(model_in))))) {
+ /*
+ * Matched vendor and optionally model.
+ *
+ * Note: a short vendor_in or model_in can
+ * give a partial match (that is FOO
+ * matches FOOBAR).
+ */
+ break;
+ }
+ }
+
+ if (retval == 0) {
+ if (vendor_in != NULL || model_in != NULL ||
+ options_in != NULL) {
+ /*
+ * Something matched. Allocate newargv, and store
+ * values found in options_in.
+ */
+ strcpy(buffer, options_in);
+ c = argc_count(buffer) + 2;
+ *newargv = calloc(c, sizeof(**newargv));
+ if (!*newargv)
+ retval = log_oom();
+ else {
+ *argc = c;
+ c = 0;
+ /*
+ * argv[0] at 0 is skipped by getopt, but
+ * store the buffer address there for
+ * later freeing
+ */
+ (*newargv)[c] = buffer;
+ for (c = 1; c < *argc; c++)
+ (*newargv)[c] = strsep(&buffer, " \t");
+ }
+ } else {
+ /* No matches */
+ retval = 1;
+ }
+ }
+ if (retval != 0)
+ free(buffer);
+ return retval;
+}
+
+static void help(void) {
+ printf("Usage: %s [OPTION...] DEVICE\n\n"
+ "SCSI device identification.\n\n"
+ " -h --help Print this message\n"
+ " --version Print version of the program\n\n"
+ " -d --device= Device node for SG_IO commands\n"
+ " -f --config= Location of config file\n"
+ " -p --page=0x80|0x83|pre-spc3-83 SCSI page (0x80, 0x83, pre-spc3-83)\n"
+ " -s --sg-version=3|4 Use SGv3 or SGv4\n"
+ " -b --blacklisted Treat device as blacklisted\n"
+ " -g --whitelisted Treat device as whitelisted\n"
+ " -u --replace-whitespace Replace all whitespace by underscores\n"
+ " -v --verbose Verbose logging\n"
+ " -x --export Print values as environment keys\n"
+ , program_invocation_short_name);
+
+}
+
+static int set_options(struct udev *udev,
+ int argc, char **argv,
+ char *maj_min_dev)
+{
+ int option;
+
+ /*
+ * optind is a global extern used by getopt. Since we can call
+ * set_options twice (once for command line, and once for config
+ * file) we have to reset this back to 1.
+ */
+ optind = 1;
+ while ((option = getopt_long(argc, argv, "d:f:gp:uvVxh", options, NULL)) >= 0)
+ switch (option) {
+ case 'b':
+ all_good = false;
+ break;
+
+ case 'd':
+ dev_specified = true;
+ strscpy(maj_min_dev, MAX_PATH_LEN, optarg);
+ break;
+
+ case 'f':
+ strscpy(config_file, MAX_PATH_LEN, optarg);
+ break;
+
+ case 'g':
+ all_good = true;
+ break;
+
+ case 'h':
+ help();
+ exit(0);
+
+ case 'p':
+ if (streq(optarg, "0x80"))
+ default_page_code = PAGE_80;
+ else if (streq(optarg, "0x83"))
+ default_page_code = PAGE_83;
+ else if (streq(optarg, "pre-spc3-83"))
+ default_page_code = PAGE_83_PRE_SPC3;
+ else {
+ log_error("Unknown page code '%s'", optarg);
+ return -1;
+ }
+ break;
+
+ case 's':
+ sg_version = atoi(optarg);
+ if (sg_version < 3 || sg_version > 4) {
+ log_error("Unknown SG version '%s'", optarg);
+ return -1;
+ }
+ break;
+
+ case 'u':
+ reformat_serial = true;
+ break;
+
+ case 'v':
+ log_set_target(LOG_TARGET_CONSOLE);
+ log_set_max_level(LOG_DEBUG);
+ log_open();
+ break;
+
+ case 'V':
+ printf("%s\n", VERSION);
+ exit(0);
+
+ case 'x':
+ export = true;
+ break;
+
+ case '?':
+ return -1;
+
+ default:
+ assert_not_reached("Unknown option");
+ }
+
+ if (optind < argc && !dev_specified) {
+ dev_specified = true;
+ strscpy(maj_min_dev, MAX_PATH_LEN, argv[optind]);
+ }
+
+ return 0;
+}
+
+static int per_dev_options(struct udev *udev,
+ struct scsi_id_device *dev_scsi, int *good_bad, int *page_code)
+{
+ int retval;
+ int newargc;
+ char **newargv = NULL;
+ int option;
+
+ *good_bad = all_good;
+ *page_code = default_page_code;
+
+ retval = get_file_options(udev, vendor_str, model_str, &newargc, &newargv);
+
+ optind = 1; /* reset this global extern */
+ while (retval == 0) {
+ option = getopt_long(newargc, newargv, "bgp:", options, NULL);
+ if (option == -1)
+ break;
+
+ switch (option) {
+ case 'b':
+ *good_bad = 0;
+ break;
+
+ case 'g':
+ *good_bad = 1;
+ break;
+
+ case 'p':
+ if (streq(optarg, "0x80")) {
+ *page_code = PAGE_80;
+ } else if (streq(optarg, "0x83")) {
+ *page_code = PAGE_83;
+ } else if (streq(optarg, "pre-spc3-83")) {
+ *page_code = PAGE_83_PRE_SPC3;
+ } else {
+ log_error("Unknown page code '%s'", optarg);
+ retval = -1;
+ }
+ break;
+
+ default:
+ log_error("Unknown or bad option '%c' (0x%x)", option, option);
+ retval = -1;
+ break;
+ }
+ }
+
+ if (newargv) {
+ free(newargv[0]);
+ free(newargv);
+ }
+ return retval;
+}
+
+static int set_inq_values(struct udev *udev, struct scsi_id_device *dev_scsi, const char *path)
+{
+ int retval;
+
+ dev_scsi->use_sg = sg_version;
+
+ retval = scsi_std_inquiry(udev, dev_scsi, path);
+ if (retval)
+ return retval;
+
+ udev_util_encode_string(dev_scsi->vendor, vendor_enc_str, sizeof(vendor_enc_str));
+ udev_util_encode_string(dev_scsi->model, model_enc_str, sizeof(model_enc_str));
+
+ util_replace_whitespace(dev_scsi->vendor, vendor_str, sizeof(vendor_str));
+ util_replace_chars(vendor_str, NULL);
+ util_replace_whitespace(dev_scsi->model, model_str, sizeof(model_str));
+ util_replace_chars(model_str, NULL);
+ set_type(dev_scsi->type, type_str, sizeof(type_str));
+ util_replace_whitespace(dev_scsi->revision, revision_str, sizeof(revision_str));
+ util_replace_chars(revision_str, NULL);
+ return 0;
+}
+
+/*
+ * scsi_id: try to get an id, if one is found, printf it to stdout.
+ * returns a value passed to exit() - 0 if printed an id, else 1.
+ */
+static int scsi_id(struct udev *udev, char *maj_min_dev)
+{
+ struct scsi_id_device dev_scsi = {};
+ int good_dev;
+ int page_code;
+ int retval = 0;
+
+ if (set_inq_values(udev, &dev_scsi, maj_min_dev) < 0) {
+ retval = 1;
+ goto out;
+ }
+
+ /* get per device (vendor + model) options from the config file */
+ per_dev_options(udev, &dev_scsi, &good_dev, &page_code);
+ if (!good_dev) {
+ retval = 1;
+ goto out;
+ }
+
+ /* read serial number from mode pages (no values for optical drives) */
+ scsi_get_serial(udev, &dev_scsi, maj_min_dev, page_code, MAX_SERIAL_LEN);
+
+ if (export) {
+ char serial_str[MAX_SERIAL_LEN];
+
+ printf("ID_SCSI=1\n");
+ printf("ID_VENDOR=%s\n", vendor_str);
+ printf("ID_VENDOR_ENC=%s\n", vendor_enc_str);
+ printf("ID_MODEL=%s\n", model_str);
+ printf("ID_MODEL_ENC=%s\n", model_enc_str);
+ printf("ID_REVISION=%s\n", revision_str);
+ printf("ID_TYPE=%s\n", type_str);
+ if (dev_scsi.serial[0] != '\0') {
+ util_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str));
+ util_replace_chars(serial_str, NULL);
+ printf("ID_SERIAL=%s\n", serial_str);
+ util_replace_whitespace(dev_scsi.serial_short, serial_str, sizeof(serial_str));
+ util_replace_chars(serial_str, NULL);
+ printf("ID_SERIAL_SHORT=%s\n", serial_str);
+ }
+ if (dev_scsi.wwn[0] != '\0') {
+ printf("ID_WWN=0x%s\n", dev_scsi.wwn);
+ if (dev_scsi.wwn_vendor_extension[0] != '\0') {
+ printf("ID_WWN_VENDOR_EXTENSION=0x%s\n", dev_scsi.wwn_vendor_extension);
+ printf("ID_WWN_WITH_EXTENSION=0x%s%s\n", dev_scsi.wwn, dev_scsi.wwn_vendor_extension);
+ } else
+ printf("ID_WWN_WITH_EXTENSION=0x%s\n", dev_scsi.wwn);
+ }
+ if (dev_scsi.tgpt_group[0] != '\0')
+ printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group);
+ if (dev_scsi.unit_serial_number[0] != '\0')
+ printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number);
+ goto out;
+ }
+
+ if (dev_scsi.serial[0] == '\0') {
+ retval = 1;
+ goto out;
+ }
+
+ if (reformat_serial) {
+ char serial_str[MAX_SERIAL_LEN];
+
+ util_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str));
+ util_replace_chars(serial_str, NULL);
+ printf("%s\n", serial_str);
+ goto out;
+ }
+
+ printf("%s\n", dev_scsi.serial);
+out:
+ return retval;
+}
+
+int main(int argc, char **argv)
+{
+ _cleanup_udev_unref_ struct udev *udev;
+ int retval = 0;
+ char maj_min_dev[MAX_PATH_LEN];
+ int newargc;
+ char **newargv = NULL;
+
+ log_parse_environment();
+ log_open();
+
+ udev = udev_new();
+ if (udev == NULL)
+ goto exit;
+
+ /*
+ * Get config file options.
+ */
+ retval = get_file_options(udev, NULL, NULL, &newargc, &newargv);
+ if (retval < 0) {
+ retval = 1;
+ goto exit;
+ }
+ if (retval == 0) {
+ assert(newargv);
+
+ if (set_options(udev, newargc, newargv, maj_min_dev) < 0) {
+ retval = 2;
+ goto exit;
+ }
+ }
+
+ /*
+ * Get command line options (overriding any config file settings).
+ */
+ if (set_options(udev, argc, argv, maj_min_dev) < 0)
+ exit(1);
+
+ if (!dev_specified) {
+ log_error("No device specified.");
+ retval = 1;
+ goto exit;
+ }
+
+ retval = scsi_id(udev, maj_min_dev);
+
+exit:
+ if (newargv) {
+ free(newargv[0]);
+ free(newargv);
+ }
+ log_close();
+ return retval;
+}
diff --git a/src/grp-udev/scsi_id/scsi_id.h b/src/grp-udev/scsi_id/scsi_id.h
new file mode 100644
index 0000000000..5c2e1c28ee
--- /dev/null
+++ b/src/grp-udev/scsi_id/scsi_id.h
@@ -0,0 +1,75 @@
+#pragma once
+
+/*
+ * Copyright (C) IBM Corp. 2003
+ *
+ * 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/>.
+ */
+
+#define MAX_PATH_LEN 512
+
+/*
+ * MAX_ATTR_LEN: maximum length of the result of reading a sysfs
+ * attribute.
+ */
+#define MAX_ATTR_LEN 256
+
+/*
+ * MAX_SERIAL_LEN: the maximum length of the serial number, including
+ * added prefixes such as vendor and product (model) strings.
+ */
+#define MAX_SERIAL_LEN 256
+
+/*
+ * MAX_BUFFER_LEN: maximum buffer size and line length used while reading
+ * the config file.
+ */
+#define MAX_BUFFER_LEN 256
+
+struct scsi_id_device {
+ char vendor[9];
+ char model[17];
+ char revision[5];
+ char type[33];
+ char kernel[64];
+ char serial[MAX_SERIAL_LEN];
+ char serial_short[MAX_SERIAL_LEN];
+ int use_sg;
+
+ /* Always from page 0x80 e.g. 'B3G1P8500RWT' - may not be unique */
+ char unit_serial_number[MAX_SERIAL_LEN];
+
+ /* NULs if not set - otherwise hex encoding using lower-case e.g. '50014ee0016eb572' */
+ char wwn[17];
+
+ /* NULs if not set - otherwise hex encoding using lower-case e.g. '0xe00000d80000' */
+ char wwn_vendor_extension[17];
+
+ /* NULs if not set - otherwise decimal number */
+ char tgpt_group[8];
+};
+
+int scsi_std_inquiry(struct udev *udev, struct scsi_id_device *dev_scsi, const char *devname);
+int scsi_get_serial(struct udev *udev, struct scsi_id_device *dev_scsi, const char *devname,
+ int page_code, int len);
+
+/*
+ * Page code values.
+ */
+enum page_code {
+ PAGE_83_PRE_SPC3 = -0x83,
+ PAGE_UNSPECIFIED = 0x00,
+ PAGE_80 = 0x80,
+ PAGE_83 = 0x83,
+};
diff --git a/src/grp-udev/scsi_id/scsi_serial.c b/src/grp-udev/scsi_id/scsi_serial.c
new file mode 100644
index 0000000000..e079e28698
--- /dev/null
+++ b/src/grp-udev/scsi_id/scsi_serial.c
@@ -0,0 +1,964 @@
+/*
+ * Copyright (C) IBM Corp. 2003
+ *
+ * Author: Patrick Mansfield<patmans@us.ibm.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <linux/bsg.h>
+#include <linux/types.h>
+#include <scsi/scsi.h>
+#include <scsi/sg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "libudev.h"
+
+#include "libudev-private.h"
+#include "random-util.h"
+#include "scsi.h"
+#include "scsi_id.h"
+#include "string-util.h"
+
+/*
+ * A priority based list of id, naa, and binary/ascii for the identifier
+ * descriptor in VPD page 0x83.
+ *
+ * Brute force search for a match starting with the first value in the
+ * following id_search_list. This is not a performance issue, since there
+ * is normally one or some small number of descriptors.
+ */
+static const struct scsi_id_search_values id_search_list[] = {
+ { SCSI_ID_TGTGROUP, SCSI_ID_NAA_DONT_CARE, SCSI_ID_BINARY },
+ { SCSI_ID_NAA, SCSI_ID_NAA_IEEE_REG_EXTENDED, SCSI_ID_BINARY },
+ { SCSI_ID_NAA, SCSI_ID_NAA_IEEE_REG_EXTENDED, SCSI_ID_ASCII },
+ { SCSI_ID_NAA, SCSI_ID_NAA_IEEE_REG, SCSI_ID_BINARY },
+ { SCSI_ID_NAA, SCSI_ID_NAA_IEEE_REG, SCSI_ID_ASCII },
+ /*
+ * Devices already exist using NAA values that are now marked
+ * reserved. These should not conflict with other values, or it is
+ * a bug in the device. As long as we find the IEEE extended one
+ * first, we really don't care what other ones are used. Using
+ * don't care here means that a device that returns multiple
+ * non-IEEE descriptors in a random order will get different
+ * names.
+ */
+ { SCSI_ID_NAA, SCSI_ID_NAA_DONT_CARE, SCSI_ID_BINARY },
+ { SCSI_ID_NAA, SCSI_ID_NAA_DONT_CARE, SCSI_ID_ASCII },
+ { SCSI_ID_EUI_64, SCSI_ID_NAA_DONT_CARE, SCSI_ID_BINARY },
+ { SCSI_ID_EUI_64, SCSI_ID_NAA_DONT_CARE, SCSI_ID_ASCII },
+ { SCSI_ID_T10_VENDOR, SCSI_ID_NAA_DONT_CARE, SCSI_ID_BINARY },
+ { SCSI_ID_T10_VENDOR, SCSI_ID_NAA_DONT_CARE, SCSI_ID_ASCII },
+ { SCSI_ID_VENDOR_SPECIFIC, SCSI_ID_NAA_DONT_CARE, SCSI_ID_BINARY },
+ { SCSI_ID_VENDOR_SPECIFIC, SCSI_ID_NAA_DONT_CARE, SCSI_ID_ASCII },
+};
+
+static const char hex_str[]="0123456789abcdef";
+
+/*
+ * Values returned in the result/status, only the ones used by the code
+ * are used here.
+ */
+
+#define DID_NO_CONNECT 0x01 /* Unable to connect before timeout */
+#define DID_BUS_BUSY 0x02 /* Bus remain busy until timeout */
+#define DID_TIME_OUT 0x03 /* Timed out for some other reason */
+#define DRIVER_TIMEOUT 0x06
+#define DRIVER_SENSE 0x08 /* Sense_buffer has been set */
+
+/* The following "category" function returns one of the following */
+#define SG_ERR_CAT_CLEAN 0 /* No errors or other information */
+#define SG_ERR_CAT_MEDIA_CHANGED 1 /* interpreted from sense buffer */
+#define SG_ERR_CAT_RESET 2 /* interpreted from sense buffer */
+#define SG_ERR_CAT_TIMEOUT 3
+#define SG_ERR_CAT_RECOVERED 4 /* Successful command after recovered err */
+#define SG_ERR_CAT_NOTSUPPORTED 5 /* Illegal / unsupported command */
+#define SG_ERR_CAT_SENSE 98 /* Something else in the sense buffer */
+#define SG_ERR_CAT_OTHER 99 /* Some other error/warning */
+
+static int do_scsi_page80_inquiry(struct udev *udev,
+ struct scsi_id_device *dev_scsi, int fd,
+ char *serial, char *serial_short, int max_len);
+
+static int sg_err_category_new(struct udev *udev,
+ int scsi_status, int msg_status, int
+ host_status, int driver_status, const
+ unsigned char *sense_buffer, int sb_len)
+{
+ scsi_status &= 0x7e;
+
+ /*
+ * XXX change to return only two values - failed or OK.
+ */
+
+ if (!scsi_status && !host_status && !driver_status)
+ return SG_ERR_CAT_CLEAN;
+
+ if ((scsi_status == SCSI_CHECK_CONDITION) ||
+ (scsi_status == SCSI_COMMAND_TERMINATED) ||
+ ((driver_status & 0xf) == DRIVER_SENSE)) {
+ if (sense_buffer && (sb_len > 2)) {
+ int sense_key;
+ unsigned char asc;
+
+ if (sense_buffer[0] & 0x2) {
+ sense_key = sense_buffer[1] & 0xf;
+ asc = sense_buffer[2];
+ } else {
+ sense_key = sense_buffer[2] & 0xf;
+ asc = (sb_len > 12) ? sense_buffer[12] : 0;
+ }
+
+ if (sense_key == RECOVERED_ERROR)
+ return SG_ERR_CAT_RECOVERED;
+ else if (sense_key == UNIT_ATTENTION) {
+ if (0x28 == asc)
+ return SG_ERR_CAT_MEDIA_CHANGED;
+ if (0x29 == asc)
+ return SG_ERR_CAT_RESET;
+ } else if (sense_key == ILLEGAL_REQUEST)
+ return SG_ERR_CAT_NOTSUPPORTED;
+ }
+ return SG_ERR_CAT_SENSE;
+ }
+ if (host_status) {
+ if ((host_status == DID_NO_CONNECT) ||
+ (host_status == DID_BUS_BUSY) ||
+ (host_status == DID_TIME_OUT))
+ return SG_ERR_CAT_TIMEOUT;
+ }
+ if (driver_status) {
+ if (driver_status == DRIVER_TIMEOUT)
+ return SG_ERR_CAT_TIMEOUT;
+ }
+ return SG_ERR_CAT_OTHER;
+}
+
+static int sg_err_category3(struct udev *udev, struct sg_io_hdr *hp)
+{
+ return sg_err_category_new(udev,
+ hp->status, hp->msg_status,
+ hp->host_status, hp->driver_status,
+ hp->sbp, hp->sb_len_wr);
+}
+
+static int sg_err_category4(struct udev *udev, struct sg_io_v4 *hp)
+{
+ return sg_err_category_new(udev, hp->device_status, 0,
+ hp->transport_status, hp->driver_status,
+ (unsigned char *)(uintptr_t)hp->response,
+ hp->response_len);
+}
+
+static int scsi_dump_sense(struct udev *udev,
+ struct scsi_id_device *dev_scsi,
+ unsigned char *sense_buffer, int sb_len)
+{
+ int s;
+ int code;
+ int sense_class;
+ int sense_key;
+ int asc, ascq;
+#ifdef DUMP_SENSE
+ char out_buffer[256];
+ int i, j;
+#endif
+
+ /*
+ * Figure out and print the sense key, asc and ascq.
+ *
+ * If you want to suppress these for a particular drive model, add
+ * a black list entry in the scsi_id config file.
+ *
+ * XXX We probably need to: lookup the sense/asc/ascq in a retry
+ * table, and if found return 1 (after dumping the sense, asc, and
+ * ascq). So, if/when we get something like a power on/reset,
+ * we'll retry the command.
+ */
+
+ if (sb_len < 1) {
+ log_debug("%s: sense buffer empty", dev_scsi->kernel);
+ return -1;
+ }
+
+ sense_class = (sense_buffer[0] >> 4) & 0x07;
+ code = sense_buffer[0] & 0xf;
+
+ if (sense_class == 7) {
+ /*
+ * extended sense data.
+ */
+ s = sense_buffer[7] + 8;
+ if (sb_len < s) {
+ log_debug("%s: sense buffer too small %d bytes, %d bytes too short",
+ dev_scsi->kernel, sb_len, s - sb_len);
+ return -1;
+ }
+ if ((code == 0x0) || (code == 0x1)) {
+ sense_key = sense_buffer[2] & 0xf;
+ if (s < 14) {
+ /*
+ * Possible?
+ */
+ log_debug("%s: sense result too" " small %d bytes",
+ dev_scsi->kernel, s);
+ return -1;
+ }
+ asc = sense_buffer[12];
+ ascq = sense_buffer[13];
+ } else if ((code == 0x2) || (code == 0x3)) {
+ sense_key = sense_buffer[1] & 0xf;
+ asc = sense_buffer[2];
+ ascq = sense_buffer[3];
+ } else {
+ log_debug("%s: invalid sense code 0x%x",
+ dev_scsi->kernel, code);
+ return -1;
+ }
+ log_debug("%s: sense key 0x%x ASC 0x%x ASCQ 0x%x",
+ dev_scsi->kernel, sense_key, asc, ascq);
+ } else {
+ if (sb_len < 4) {
+ log_debug("%s: sense buffer too small %d bytes, %d bytes too short",
+ dev_scsi->kernel, sb_len, 4 - sb_len);
+ return -1;
+ }
+
+ if (sense_buffer[0] < 15)
+ log_debug("%s: old sense key: 0x%x", dev_scsi->kernel, sense_buffer[0] & 0x0f);
+ else
+ log_debug("%s: sense = %2x %2x",
+ dev_scsi->kernel, sense_buffer[0], sense_buffer[2]);
+ log_debug("%s: non-extended sense class %d code 0x%0x",
+ dev_scsi->kernel, sense_class, code);
+
+ }
+
+#ifdef DUMP_SENSE
+ for (i = 0, j = 0; (i < s) && (j < 254); i++) {
+ out_buffer[j++] = hex_str[(sense_buffer[i] & 0xf0) >> 4];
+ out_buffer[j++] = hex_str[sense_buffer[i] & 0x0f];
+ out_buffer[j++] = ' ';
+ }
+ out_buffer[j] = '\0';
+ log_debug("%s: sense dump:", dev_scsi->kernel);
+ log_debug("%s: %s", dev_scsi->kernel, out_buffer);
+
+#endif
+ return -1;
+}
+
+static int scsi_dump(struct udev *udev,
+ struct scsi_id_device *dev_scsi, struct sg_io_hdr *io)
+{
+ if (!io->status && !io->host_status && !io->msg_status &&
+ !io->driver_status) {
+ /*
+ * Impossible, should not be called.
+ */
+ log_debug("%s: called with no error", __FUNCTION__);
+ return -1;
+ }
+
+ log_debug("%s: sg_io failed status 0x%x 0x%x 0x%x 0x%x",
+ dev_scsi->kernel, io->driver_status, io->host_status, io->msg_status, io->status);
+ if (io->status == SCSI_CHECK_CONDITION)
+ return scsi_dump_sense(udev, dev_scsi, io->sbp, io->sb_len_wr);
+ else
+ return -1;
+}
+
+static int scsi_dump_v4(struct udev *udev,
+ struct scsi_id_device *dev_scsi, struct sg_io_v4 *io)
+{
+ if (!io->device_status && !io->transport_status &&
+ !io->driver_status) {
+ /*
+ * Impossible, should not be called.
+ */
+ log_debug("%s: called with no error", __FUNCTION__);
+ return -1;
+ }
+
+ log_debug("%s: sg_io failed status 0x%x 0x%x 0x%x",
+ dev_scsi->kernel, io->driver_status, io->transport_status, io->device_status);
+ if (io->device_status == SCSI_CHECK_CONDITION)
+ return scsi_dump_sense(udev, dev_scsi, (unsigned char *)(uintptr_t)io->response,
+ io->response_len);
+ else
+ return -1;
+}
+
+static int scsi_inquiry(struct udev *udev,
+ struct scsi_id_device *dev_scsi, int fd,
+ unsigned char evpd, unsigned char page,
+ unsigned char *buf, unsigned int buflen)
+{
+ unsigned char inq_cmd[INQUIRY_CMDLEN] =
+ { INQUIRY_CMD, evpd, page, 0, buflen, 0 };
+ unsigned char sense[SENSE_BUFF_LEN];
+ void *io_buf;
+ struct sg_io_v4 io_v4;
+ struct sg_io_hdr io_hdr;
+ int retry = 3; /* rather random */
+ int retval;
+
+ if (buflen > SCSI_INQ_BUFF_LEN) {
+ log_debug("buflen %d too long", buflen);
+ return -1;
+ }
+
+resend:
+ if (dev_scsi->use_sg == 4) {
+ memzero(&io_v4, sizeof(struct sg_io_v4));
+ io_v4.guard = 'Q';
+ io_v4.protocol = BSG_PROTOCOL_SCSI;
+ io_v4.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD;
+ io_v4.request_len = sizeof(inq_cmd);
+ io_v4.request = (uintptr_t)inq_cmd;
+ io_v4.max_response_len = sizeof(sense);
+ io_v4.response = (uintptr_t)sense;
+ io_v4.din_xfer_len = buflen;
+ io_v4.din_xferp = (uintptr_t)buf;
+ io_buf = (void *)&io_v4;
+ } else {
+ memzero(&io_hdr, sizeof(struct sg_io_hdr));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(inq_cmd);
+ io_hdr.mx_sb_len = sizeof(sense);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = buflen;
+ io_hdr.dxferp = buf;
+ io_hdr.cmdp = inq_cmd;
+ io_hdr.sbp = sense;
+ io_hdr.timeout = DEF_TIMEOUT;
+ io_buf = (void *)&io_hdr;
+ }
+
+ retval = ioctl(fd, SG_IO, io_buf);
+ if (retval < 0) {
+ if ((errno == EINVAL || errno == ENOSYS) && dev_scsi->use_sg == 4) {
+ dev_scsi->use_sg = 3;
+ goto resend;
+ }
+ log_debug_errno(errno, "%s: ioctl failed: %m", dev_scsi->kernel);
+ goto error;
+ }
+
+ if (dev_scsi->use_sg == 4)
+ retval = sg_err_category4(udev, io_buf);
+ else
+ retval = sg_err_category3(udev, io_buf);
+
+ switch (retval) {
+ case SG_ERR_CAT_NOTSUPPORTED:
+ buf[1] = 0;
+ /* Fallthrough */
+ case SG_ERR_CAT_CLEAN:
+ case SG_ERR_CAT_RECOVERED:
+ retval = 0;
+ break;
+
+ default:
+ if (dev_scsi->use_sg == 4)
+ retval = scsi_dump_v4(udev, dev_scsi, io_buf);
+ else
+ retval = scsi_dump(udev, dev_scsi, io_buf);
+ }
+
+ if (!retval) {
+ retval = buflen;
+ } else if (retval > 0) {
+ if (--retry > 0)
+ goto resend;
+ retval = -1;
+ }
+
+error:
+ if (retval < 0)
+ log_debug("%s: Unable to get INQUIRY vpd %d page 0x%x.",
+ dev_scsi->kernel, evpd, page);
+
+ return retval;
+}
+
+/* Get list of supported EVPD pages */
+static int do_scsi_page0_inquiry(struct udev *udev,
+ struct scsi_id_device *dev_scsi, int fd,
+ unsigned char *buffer, unsigned int len)
+{
+ int retval;
+
+ memzero(buffer, len);
+ retval = scsi_inquiry(udev, dev_scsi, fd, 1, 0x0, buffer, len);
+ if (retval < 0)
+ return 1;
+
+ if (buffer[1] != 0) {
+ log_debug("%s: page 0 not available.", dev_scsi->kernel);
+ return 1;
+ }
+ if (buffer[3] > len) {
+ log_debug("%s: page 0 buffer too long %d", dev_scsi->kernel, buffer[3]);
+ return 1;
+ }
+
+ /*
+ * Following check is based on code once included in the 2.5.x
+ * kernel.
+ *
+ * Some ill behaved devices return the standard inquiry here
+ * rather than the evpd data, snoop the data to verify.
+ */
+ if (buffer[3] > MODEL_LENGTH) {
+ /*
+ * If the vendor id appears in the page assume the page is
+ * invalid.
+ */
+ if (strneq((char *)&buffer[VENDOR_LENGTH], dev_scsi->vendor, VENDOR_LENGTH)) {
+ log_debug("%s: invalid page0 data", dev_scsi->kernel);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * The caller checks that serial is long enough to include the vendor +
+ * model.
+ */
+static int prepend_vendor_model(struct udev *udev,
+ struct scsi_id_device *dev_scsi, char *serial)
+{
+ int ind;
+
+ strncpy(serial, dev_scsi->vendor, VENDOR_LENGTH);
+ strncat(serial, dev_scsi->model, MODEL_LENGTH);
+ ind = strlen(serial);
+
+ /*
+ * This is not a complete check, since we are using strncat/cpy
+ * above, ind will never be too large.
+ */
+ if (ind != (VENDOR_LENGTH + MODEL_LENGTH)) {
+ log_debug("%s: expected length %d, got length %d",
+ dev_scsi->kernel, (VENDOR_LENGTH + MODEL_LENGTH), ind);
+ return -1;
+ }
+ return ind;
+}
+
+/*
+ * check_fill_0x83_id - check the page 0x83 id, if OK allocate and fill
+ * serial number.
+ */
+static int check_fill_0x83_id(struct udev *udev,
+ struct scsi_id_device *dev_scsi,
+ unsigned char *page_83,
+ const struct scsi_id_search_values
+ *id_search, char *serial, char *serial_short,
+ int max_len, char *wwn,
+ char *wwn_vendor_extension, char *tgpt_group)
+{
+ int i, j, s, len;
+
+ /*
+ * ASSOCIATION must be with the device (value 0)
+ * or with the target port for SCSI_ID_TGTPORT
+ */
+ if ((page_83[1] & 0x30) == 0x10) {
+ if (id_search->id_type != SCSI_ID_TGTGROUP)
+ return 1;
+ } else if ((page_83[1] & 0x30) != 0)
+ return 1;
+
+ if ((page_83[1] & 0x0f) != id_search->id_type)
+ return 1;
+
+ /*
+ * Possibly check NAA sub-type.
+ */
+ if ((id_search->naa_type != SCSI_ID_NAA_DONT_CARE) &&
+ (id_search->naa_type != (page_83[4] & 0xf0) >> 4))
+ return 1;
+
+ /*
+ * Check for matching code set - ASCII or BINARY.
+ */
+ if ((page_83[0] & 0x0f) != id_search->code_set)
+ return 1;
+
+ /*
+ * page_83[3]: identifier length
+ */
+ len = page_83[3];
+ if ((page_83[0] & 0x0f) != SCSI_ID_ASCII)
+ /*
+ * If not ASCII, use two bytes for each binary value.
+ */
+ len *= 2;
+
+ /*
+ * Add one byte for the NUL termination, and one for the id_type.
+ */
+ len += 2;
+ if (id_search->id_type == SCSI_ID_VENDOR_SPECIFIC)
+ len += VENDOR_LENGTH + MODEL_LENGTH;
+
+ if (max_len < len) {
+ log_debug("%s: length %d too short - need %d",
+ dev_scsi->kernel, max_len, len);
+ return 1;
+ }
+
+ if (id_search->id_type == SCSI_ID_TGTGROUP && tgpt_group != NULL) {
+ unsigned int group;
+
+ group = ((unsigned int)page_83[6] << 8) | page_83[7];
+ sprintf(tgpt_group,"%x", group);
+ return 1;
+ }
+
+ serial[0] = hex_str[id_search->id_type];
+
+ /*
+ * For SCSI_ID_VENDOR_SPECIFIC prepend the vendor and model before
+ * the id since it is not unique across all vendors and models,
+ * this differs from SCSI_ID_T10_VENDOR, where the vendor is
+ * included in the identifier.
+ */
+ if (id_search->id_type == SCSI_ID_VENDOR_SPECIFIC)
+ if (prepend_vendor_model(udev, dev_scsi, &serial[1]) < 0)
+ return 1;
+
+ i = 4; /* offset to the start of the identifier */
+ s = j = strlen(serial);
+ if ((page_83[0] & 0x0f) == SCSI_ID_ASCII) {
+ /*
+ * ASCII descriptor.
+ */
+ while (i < (4 + page_83[3]))
+ serial[j++] = page_83[i++];
+ } else {
+ /*
+ * Binary descriptor, convert to ASCII, using two bytes of
+ * ASCII for each byte in the page_83.
+ */
+ while (i < (4 + page_83[3])) {
+ serial[j++] = hex_str[(page_83[i] & 0xf0) >> 4];
+ serial[j++] = hex_str[page_83[i] & 0x0f];
+ i++;
+ }
+ }
+
+ strcpy(serial_short, &serial[s]);
+
+ if (id_search->id_type == SCSI_ID_NAA && wwn != NULL) {
+ strncpy(wwn, &serial[s], 16);
+ if (wwn_vendor_extension != NULL)
+ strncpy(wwn_vendor_extension, &serial[s + 16], 16);
+ }
+
+ return 0;
+}
+
+/* Extract the raw binary from VPD 0x83 pre-SPC devices */
+static int check_fill_0x83_prespc3(struct udev *udev,
+ struct scsi_id_device *dev_scsi,
+ unsigned char *page_83,
+ const struct scsi_id_search_values
+ *id_search, char *serial, char *serial_short, int max_len)
+{
+ int i, j;
+
+ serial[0] = hex_str[id_search->id_type];
+ /* serial has been memset to zero before */
+ j = strlen(serial); /* j = 1; */
+
+ for (i = 0; (i < page_83[3]) && (j < max_len-3); ++i) {
+ serial[j++] = hex_str[(page_83[4+i] & 0xf0) >> 4];
+ serial[j++] = hex_str[ page_83[4+i] & 0x0f];
+ }
+ serial[max_len-1] = 0;
+ strncpy(serial_short, serial, max_len-1);
+ return 0;
+}
+
+
+/* Get device identification VPD page */
+static int do_scsi_page83_inquiry(struct udev *udev,
+ struct scsi_id_device *dev_scsi, int fd,
+ char *serial, char *serial_short, int len,
+ char *unit_serial_number, char *wwn,
+ char *wwn_vendor_extension, char *tgpt_group)
+{
+ int retval;
+ unsigned int id_ind, j;
+ unsigned char page_83[SCSI_INQ_BUFF_LEN];
+
+ /* also pick up the page 80 serial number */
+ do_scsi_page80_inquiry(udev, dev_scsi, fd, NULL, unit_serial_number, MAX_SERIAL_LEN);
+
+ memzero(page_83, SCSI_INQ_BUFF_LEN);
+ retval = scsi_inquiry(udev, dev_scsi, fd, 1, PAGE_83, page_83,
+ SCSI_INQ_BUFF_LEN);
+ if (retval < 0)
+ return 1;
+
+ if (page_83[1] != PAGE_83) {
+ log_debug("%s: Invalid page 0x83", dev_scsi->kernel);
+ return 1;
+ }
+
+ /*
+ * XXX Some devices (IBM 3542) return all spaces for an identifier if
+ * the LUN is not actually configured. This leads to identifiers of
+ * the form: "1 ".
+ */
+
+ /*
+ * Model 4, 5, and (some) model 6 EMC Symmetrix devices return
+ * a page 83 reply according to SCSI-2 format instead of SPC-2/3.
+ *
+ * The SCSI-2 page 83 format returns an IEEE WWN in binary
+ * encoded hexi-decimal in the 16 bytes following the initial
+ * 4-byte page 83 reply header.
+ *
+ * Both the SPC-2 and SPC-3 formats return an IEEE WWN as part
+ * of an Identification descriptor. The 3rd byte of the first
+ * Identification descriptor is a reserved (BSZ) byte field.
+ *
+ * Reference the 7th byte of the page 83 reply to determine
+ * whether the reply is compliant with SCSI-2 or SPC-2/3
+ * specifications. A zero value in the 7th byte indicates
+ * an SPC-2/3 conformant reply, (i.e., the reserved field of the
+ * first Identification descriptor). This byte will be non-zero
+ * for a SCSI-2 conformant page 83 reply from these EMC
+ * Symmetrix models since the 7th byte of the reply corresponds
+ * to the 4th and 5th nibbles of the 6-byte OUI for EMC, that is,
+ * 0x006048.
+ */
+
+ if (page_83[6] != 0)
+ return check_fill_0x83_prespc3(udev,
+ dev_scsi, page_83, id_search_list,
+ serial, serial_short, len);
+
+ /*
+ * Search for a match in the prioritized id_search_list - since WWN ids
+ * come first we can pick up the WWN in check_fill_0x83_id().
+ */
+ for (id_ind = 0;
+ id_ind < sizeof(id_search_list)/sizeof(id_search_list[0]);
+ id_ind++) {
+ /*
+ * Examine each descriptor returned. There is normally only
+ * one or a small number of descriptors.
+ */
+ for (j = 4; j <= (unsigned int)page_83[3] + 3; j += page_83[j + 3] + 4) {
+ retval = check_fill_0x83_id(udev,
+ dev_scsi, &page_83[j],
+ &id_search_list[id_ind],
+ serial, serial_short, len,
+ wwn, wwn_vendor_extension,
+ tgpt_group);
+ if (!retval)
+ return retval;
+ else if (retval < 0)
+ return retval;
+ }
+ }
+ return 1;
+}
+
+/*
+ * Get device identification VPD page for older SCSI-2 device which is not
+ * compliant with either SPC-2 or SPC-3 format.
+ *
+ * Return the hard coded error code value 2 if the page 83 reply is not
+ * conformant to the SCSI-2 format.
+ */
+static int do_scsi_page83_prespc3_inquiry(struct udev *udev,
+ struct scsi_id_device *dev_scsi, int fd,
+ char *serial, char *serial_short, int len)
+{
+ int retval;
+ int i, j;
+ unsigned char page_83[SCSI_INQ_BUFF_LEN];
+
+ memzero(page_83, SCSI_INQ_BUFF_LEN);
+ retval = scsi_inquiry(udev, dev_scsi, fd, 1, PAGE_83, page_83, SCSI_INQ_BUFF_LEN);
+ if (retval < 0)
+ return 1;
+
+ if (page_83[1] != PAGE_83) {
+ log_debug("%s: Invalid page 0x83", dev_scsi->kernel);
+ return 1;
+ }
+ /*
+ * Model 4, 5, and (some) model 6 EMC Symmetrix devices return
+ * a page 83 reply according to SCSI-2 format instead of SPC-2/3.
+ *
+ * The SCSI-2 page 83 format returns an IEEE WWN in binary
+ * encoded hexi-decimal in the 16 bytes following the initial
+ * 4-byte page 83 reply header.
+ *
+ * Both the SPC-2 and SPC-3 formats return an IEEE WWN as part
+ * of an Identification descriptor. The 3rd byte of the first
+ * Identification descriptor is a reserved (BSZ) byte field.
+ *
+ * Reference the 7th byte of the page 83 reply to determine
+ * whether the reply is compliant with SCSI-2 or SPC-2/3
+ * specifications. A zero value in the 7th byte indicates
+ * an SPC-2/3 conformant reply, (i.e., the reserved field of the
+ * first Identification descriptor). This byte will be non-zero
+ * for a SCSI-2 conformant page 83 reply from these EMC
+ * Symmetrix models since the 7th byte of the reply corresponds
+ * to the 4th and 5th nibbles of the 6-byte OUI for EMC, that is,
+ * 0x006048.
+ */
+ if (page_83[6] == 0)
+ return 2;
+
+ serial[0] = hex_str[id_search_list[0].id_type];
+ /*
+ * The first four bytes contain data, not a descriptor.
+ */
+ i = 4;
+ j = strlen(serial);
+ /*
+ * Binary descriptor, convert to ASCII,
+ * using two bytes of ASCII for each byte
+ * in the page_83.
+ */
+ while (i < (page_83[3]+4)) {
+ serial[j++] = hex_str[(page_83[i] & 0xf0) >> 4];
+ serial[j++] = hex_str[page_83[i] & 0x0f];
+ i++;
+ }
+ return 0;
+}
+
+/* Get unit serial number VPD page */
+static int do_scsi_page80_inquiry(struct udev *udev,
+ struct scsi_id_device *dev_scsi, int fd,
+ char *serial, char *serial_short, int max_len)
+{
+ int retval;
+ int ser_ind;
+ int i;
+ int len;
+ unsigned char buf[SCSI_INQ_BUFF_LEN];
+
+ memzero(buf, SCSI_INQ_BUFF_LEN);
+ retval = scsi_inquiry(udev, dev_scsi, fd, 1, PAGE_80, buf, SCSI_INQ_BUFF_LEN);
+ if (retval < 0)
+ return retval;
+
+ if (buf[1] != PAGE_80) {
+ log_debug("%s: Invalid page 0x80", dev_scsi->kernel);
+ return 1;
+ }
+
+ len = 1 + VENDOR_LENGTH + MODEL_LENGTH + buf[3];
+ if (max_len < len) {
+ log_debug("%s: length %d too short - need %d",
+ dev_scsi->kernel, max_len, len);
+ return 1;
+ }
+ /*
+ * Prepend 'S' to avoid unlikely collision with page 0x83 vendor
+ * specific type where we prepend '0' + vendor + model.
+ */
+ len = buf[3];
+ if (serial != NULL) {
+ serial[0] = 'S';
+ ser_ind = prepend_vendor_model(udev, dev_scsi, &serial[1]);
+ if (ser_ind < 0)
+ return 1;
+ ser_ind++; /* for the leading 'S' */
+ for (i = 4; i < len + 4; i++, ser_ind++)
+ serial[ser_ind] = buf[i];
+ }
+ if (serial_short != NULL) {
+ memcpy(serial_short, &buf[4], len);
+ serial_short[len] = '\0';
+ }
+ return 0;
+}
+
+int scsi_std_inquiry(struct udev *udev,
+ struct scsi_id_device *dev_scsi, const char *devname)
+{
+ int fd;
+ unsigned char buf[SCSI_INQ_BUFF_LEN];
+ struct stat statbuf;
+ int err = 0;
+
+ fd = open(devname, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
+ if (fd < 0) {
+ log_debug_errno(errno, "scsi_id: cannot open %s: %m", devname);
+ return 1;
+ }
+
+ if (fstat(fd, &statbuf) < 0) {
+ log_debug_errno(errno, "scsi_id: cannot stat %s: %m", devname);
+ err = 2;
+ goto out;
+ }
+ sprintf(dev_scsi->kernel,"%d:%d", major(statbuf.st_rdev),
+ minor(statbuf.st_rdev));
+
+ memzero(buf, SCSI_INQ_BUFF_LEN);
+ err = scsi_inquiry(udev, dev_scsi, fd, 0, 0, buf, SCSI_INQ_BUFF_LEN);
+ if (err < 0)
+ goto out;
+
+ err = 0;
+ memcpy(dev_scsi->vendor, buf + 8, 8);
+ dev_scsi->vendor[8] = '\0';
+ memcpy(dev_scsi->model, buf + 16, 16);
+ dev_scsi->model[16] = '\0';
+ memcpy(dev_scsi->revision, buf + 32, 4);
+ dev_scsi->revision[4] = '\0';
+ sprintf(dev_scsi->type,"%x", buf[0] & 0x1f);
+
+out:
+ close(fd);
+ return err;
+}
+
+int scsi_get_serial(struct udev *udev,
+ struct scsi_id_device *dev_scsi, const char *devname,
+ int page_code, int len)
+{
+ unsigned char page0[SCSI_INQ_BUFF_LEN];
+ int fd = -1;
+ int cnt;
+ int ind;
+ int retval;
+
+ memzero(dev_scsi->serial, len);
+ initialize_srand();
+ for (cnt = 20; cnt > 0; cnt--) {
+ struct timespec duration;
+
+ fd = open(devname, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
+ if (fd >= 0 || errno != EBUSY)
+ break;
+ duration.tv_sec = 0;
+ duration.tv_nsec = (200 * 1000 * 1000) + (rand() % 100 * 1000 * 1000);
+ nanosleep(&duration, NULL);
+ }
+ if (fd < 0)
+ return 1;
+
+ if (page_code == PAGE_80) {
+ if (do_scsi_page80_inquiry(udev, dev_scsi, fd, dev_scsi->serial, dev_scsi->serial_short, len)) {
+ retval = 1;
+ goto completed;
+ } else {
+ retval = 0;
+ goto completed;
+ }
+ } else if (page_code == PAGE_83) {
+ if (do_scsi_page83_inquiry(udev, dev_scsi, fd, dev_scsi->serial, dev_scsi->serial_short, len, dev_scsi->unit_serial_number, dev_scsi->wwn, dev_scsi->wwn_vendor_extension, dev_scsi->tgpt_group)) {
+ retval = 1;
+ goto completed;
+ } else {
+ retval = 0;
+ goto completed;
+ }
+ } else if (page_code == PAGE_83_PRE_SPC3) {
+ retval = do_scsi_page83_prespc3_inquiry(udev, dev_scsi, fd, dev_scsi->serial, dev_scsi->serial_short, len);
+ if (retval) {
+ /*
+ * Fallback to servicing a SPC-2/3 compliant page 83
+ * inquiry if the page 83 reply format does not
+ * conform to pre-SPC3 expectations.
+ */
+ if (retval == 2) {
+ if (do_scsi_page83_inquiry(udev, dev_scsi, fd, dev_scsi->serial, dev_scsi->serial_short, len, dev_scsi->unit_serial_number, dev_scsi->wwn, dev_scsi->wwn_vendor_extension, dev_scsi->tgpt_group)) {
+ retval = 1;
+ goto completed;
+ } else {
+ retval = 0;
+ goto completed;
+ }
+ }
+ else {
+ retval = 1;
+ goto completed;
+ }
+ } else {
+ retval = 0;
+ goto completed;
+ }
+ } else if (page_code != 0x00) {
+ log_debug("%s: unsupported page code 0x%d", dev_scsi->kernel, page_code);
+ retval = 1;
+ goto completed;
+ }
+
+ /*
+ * Get page 0, the page of the pages. By default, try from best to
+ * worst of supported pages: 0x83 then 0x80.
+ */
+ if (do_scsi_page0_inquiry(udev, dev_scsi, fd, page0, SCSI_INQ_BUFF_LEN)) {
+ /*
+ * Don't try anything else. Black list if a specific page
+ * should be used for this vendor+model, or maybe have an
+ * optional fall-back to page 0x80 or page 0x83.
+ */
+ retval = 1;
+ goto completed;
+ }
+
+ for (ind = 4; ind <= page0[3] + 3; ind++)
+ if (page0[ind] == PAGE_83)
+ if (!do_scsi_page83_inquiry(udev, dev_scsi, fd,
+ dev_scsi->serial, dev_scsi->serial_short, len, dev_scsi->unit_serial_number, dev_scsi->wwn, dev_scsi->wwn_vendor_extension, dev_scsi->tgpt_group)) {
+ /*
+ * Success
+ */
+ retval = 0;
+ goto completed;
+ }
+
+ for (ind = 4; ind <= page0[3] + 3; ind++)
+ if (page0[ind] == PAGE_80)
+ if (!do_scsi_page80_inquiry(udev, dev_scsi, fd,
+ dev_scsi->serial, dev_scsi->serial_short, len)) {
+ /*
+ * Success
+ */
+ retval = 0;
+ goto completed;
+ }
+ retval = 1;
+
+completed:
+ close(fd);
+ return retval;
+}
diff --git a/src/grp-udev/systemd-hwdb/hwdb.c b/src/grp-udev/systemd-hwdb/hwdb.c
new file mode 100644
index 0000000000..1160dacdf1
--- /dev/null
+++ b/src/grp-udev/systemd-hwdb/hwdb.c
@@ -0,0 +1,739 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Kay Sievers <kay@vrfy.org>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <ctype.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc-util.h"
+#include "conf-files.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "hwdb-internal.h"
+#include "hwdb-util.h"
+#include "mkdir.h"
+#include "strbuf.h"
+#include "string-util.h"
+#include "strv.h"
+#include "util.h"
+#include "verbs.h"
+
+/*
+ * Generic udev properties, key/value database based on modalias strings.
+ * Uses a Patricia/radix trie to index all matches for efficient lookup.
+ */
+
+static const char *arg_hwdb_bin_dir = "/etc/udev";
+static const char *arg_root = "";
+
+static const char * const conf_file_dirs[] = {
+ "/etc/udev/hwdb.d",
+ UDEVLIBEXECDIR "/hwdb.d",
+ NULL
+};
+
+/* in-memory trie objects */
+struct trie {
+ struct trie_node *root;
+ struct strbuf *strings;
+
+ size_t nodes_count;
+ size_t children_count;
+ size_t values_count;
+};
+
+struct trie_node {
+ /* prefix, common part for all children of this node */
+ size_t prefix_off;
+
+ /* sorted array of pointers to children nodes */
+ struct trie_child_entry *children;
+ uint8_t children_count;
+
+ /* sorted array of key/value pairs */
+ struct trie_value_entry *values;
+ size_t values_count;
+};
+
+/* children array item with char (0-255) index */
+struct trie_child_entry {
+ uint8_t c;
+ struct trie_node *child;
+};
+
+/* value array item with key/value pairs */
+struct trie_value_entry {
+ size_t key_off;
+ size_t value_off;
+};
+
+static int trie_children_cmp(const void *v1, const void *v2) {
+ const struct trie_child_entry *n1 = v1;
+ const struct trie_child_entry *n2 = v2;
+
+ return n1->c - n2->c;
+}
+
+static int node_add_child(struct trie *trie, struct trie_node *node, struct trie_node *node_child, uint8_t c) {
+ struct trie_child_entry *child;
+
+ /* extend array, add new entry, sort for bisection */
+ child = realloc(node->children, (node->children_count + 1) * sizeof(struct trie_child_entry));
+ if (!child)
+ return -ENOMEM;
+
+ node->children = child;
+ trie->children_count++;
+ node->children[node->children_count].c = c;
+ node->children[node->children_count].child = node_child;
+ node->children_count++;
+ qsort(node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp);
+ trie->nodes_count++;
+
+ return 0;
+}
+
+static struct trie_node *node_lookup(const struct trie_node *node, uint8_t c) {
+ struct trie_child_entry *child;
+ struct trie_child_entry search;
+
+ search.c = c;
+ child = bsearch(&search, node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp);
+ if (child)
+ return child->child;
+ return NULL;
+}
+
+static void trie_node_cleanup(struct trie_node *node) {
+ size_t i;
+
+ for (i = 0; i < node->children_count; i++)
+ trie_node_cleanup(node->children[i].child);
+ free(node->children);
+ free(node->values);
+ free(node);
+}
+
+static void trie_free(struct trie *trie) {
+ if (!trie)
+ return;
+
+ if (trie->root)
+ trie_node_cleanup(trie->root);
+
+ strbuf_cleanup(trie->strings);
+ free(trie);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct trie*, trie_free);
+
+static int trie_values_cmp(const void *v1, const void *v2, void *arg) {
+ const struct trie_value_entry *val1 = v1;
+ const struct trie_value_entry *val2 = v2;
+ struct trie *trie = arg;
+
+ return strcmp(trie->strings->buf + val1->key_off,
+ trie->strings->buf + val2->key_off);
+}
+
+static int trie_node_add_value(struct trie *trie, struct trie_node *node,
+ const char *key, const char *value) {
+ ssize_t k, v;
+ struct trie_value_entry *val;
+
+ k = strbuf_add_string(trie->strings, key, strlen(key));
+ if (k < 0)
+ return k;
+ v = strbuf_add_string(trie->strings, value, strlen(value));
+ if (v < 0)
+ return v;
+
+ if (node->values_count) {
+ struct trie_value_entry search = {
+ .key_off = k,
+ .value_off = v,
+ };
+
+ val = xbsearch_r(&search, node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie);
+ if (val) {
+ /* replace existing earlier key with new value */
+ val->value_off = v;
+ return 0;
+ }
+ }
+
+ /* extend array, add new entry, sort for bisection */
+ val = realloc(node->values, (node->values_count + 1) * sizeof(struct trie_value_entry));
+ if (!val)
+ return -ENOMEM;
+ trie->values_count++;
+ node->values = val;
+ node->values[node->values_count].key_off = k;
+ node->values[node->values_count].value_off = v;
+ node->values_count++;
+ qsort_r(node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie);
+ return 0;
+}
+
+static int trie_insert(struct trie *trie, struct trie_node *node, const char *search,
+ const char *key, const char *value) {
+ size_t i = 0;
+ int err = 0;
+
+ for (;;) {
+ size_t p;
+ uint8_t c;
+ struct trie_node *child;
+
+ for (p = 0; (c = trie->strings->buf[node->prefix_off + p]); p++) {
+ _cleanup_free_ char *s = NULL;
+ ssize_t off;
+ _cleanup_free_ struct trie_node *new_child = NULL;
+
+ if (c == search[i + p])
+ continue;
+
+ /* split node */
+ new_child = new0(struct trie_node, 1);
+ if (!new_child)
+ return -ENOMEM;
+
+ /* move values from parent to child */
+ new_child->prefix_off = node->prefix_off + p+1;
+ new_child->children = node->children;
+ new_child->children_count = node->children_count;
+ new_child->values = node->values;
+ new_child->values_count = node->values_count;
+
+ /* update parent; use strdup() because the source gets realloc()d */
+ s = strndup(trie->strings->buf + node->prefix_off, p);
+ if (!s)
+ return -ENOMEM;
+
+ off = strbuf_add_string(trie->strings, s, p);
+ if (off < 0)
+ return off;
+
+ node->prefix_off = off;
+ node->children = NULL;
+ node->children_count = 0;
+ node->values = NULL;
+ node->values_count = 0;
+ err = node_add_child(trie, node, new_child, c);
+ if (err < 0)
+ return err;
+
+ new_child = NULL; /* avoid cleanup */
+ break;
+ }
+ i += p;
+
+ c = search[i];
+ if (c == '\0')
+ return trie_node_add_value(trie, node, key, value);
+
+ child = node_lookup(node, c);
+ if (!child) {
+ ssize_t off;
+
+ /* new child */
+ child = new0(struct trie_node, 1);
+ if (!child)
+ return -ENOMEM;
+
+ off = strbuf_add_string(trie->strings, search + i+1, strlen(search + i+1));
+ if (off < 0) {
+ free(child);
+ return off;
+ }
+
+ child->prefix_off = off;
+ err = node_add_child(trie, node, child, c);
+ if (err < 0) {
+ free(child);
+ return err;
+ }
+
+ return trie_node_add_value(trie, child, key, value);
+ }
+
+ node = child;
+ i++;
+ }
+}
+
+struct trie_f {
+ FILE *f;
+ struct trie *trie;
+ uint64_t strings_off;
+
+ uint64_t nodes_count;
+ uint64_t children_count;
+ uint64_t values_count;
+};
+
+/* calculate the storage space for the nodes, children arrays, value arrays */
+static void trie_store_nodes_size(struct trie_f *trie, struct trie_node *node) {
+ uint64_t i;
+
+ for (i = 0; i < node->children_count; i++)
+ trie_store_nodes_size(trie, node->children[i].child);
+
+ trie->strings_off += sizeof(struct trie_node_f);
+ for (i = 0; i < node->children_count; i++)
+ trie->strings_off += sizeof(struct trie_child_entry_f);
+ for (i = 0; i < node->values_count; i++)
+ trie->strings_off += sizeof(struct trie_value_entry_f);
+}
+
+static int64_t trie_store_nodes(struct trie_f *trie, struct trie_node *node) {
+ uint64_t i;
+ struct trie_node_f n = {
+ .prefix_off = htole64(trie->strings_off + node->prefix_off),
+ .children_count = node->children_count,
+ .values_count = htole64(node->values_count),
+ };
+ struct trie_child_entry_f *children = NULL;
+ int64_t node_off;
+
+ if (node->children_count) {
+ children = new0(struct trie_child_entry_f, node->children_count);
+ if (!children)
+ return -ENOMEM;
+ }
+
+ /* post-order recursion */
+ for (i = 0; i < node->children_count; i++) {
+ int64_t child_off;
+
+ child_off = trie_store_nodes(trie, node->children[i].child);
+ if (child_off < 0) {
+ free(children);
+ return child_off;
+ }
+ children[i].c = node->children[i].c;
+ children[i].child_off = htole64(child_off);
+ }
+
+ /* write node */
+ node_off = ftello(trie->f);
+ fwrite(&n, sizeof(struct trie_node_f), 1, trie->f);
+ trie->nodes_count++;
+
+ /* append children array */
+ if (node->children_count) {
+ fwrite(children, sizeof(struct trie_child_entry_f), node->children_count, trie->f);
+ trie->children_count += node->children_count;
+ free(children);
+ }
+
+ /* append values array */
+ for (i = 0; i < node->values_count; i++) {
+ struct trie_value_entry_f v = {
+ .key_off = htole64(trie->strings_off + node->values[i].key_off),
+ .value_off = htole64(trie->strings_off + node->values[i].value_off),
+ };
+
+ fwrite(&v, sizeof(struct trie_value_entry_f), 1, trie->f);
+ trie->values_count++;
+ }
+
+ return node_off;
+}
+
+static int trie_store(struct trie *trie, const char *filename) {
+ struct trie_f t = {
+ .trie = trie,
+ };
+ _cleanup_free_ char *filename_tmp = NULL;
+ int64_t pos;
+ int64_t root_off;
+ int64_t size;
+ struct trie_header_f h = {
+ .signature = HWDB_SIG,
+ .tool_version = htole64(atoi(VERSION)),
+ .header_size = htole64(sizeof(struct trie_header_f)),
+ .node_size = htole64(sizeof(struct trie_node_f)),
+ .child_entry_size = htole64(sizeof(struct trie_child_entry_f)),
+ .value_entry_size = htole64(sizeof(struct trie_value_entry_f)),
+ };
+ int err;
+
+ /* calculate size of header, nodes, children entries, value entries */
+ t.strings_off = sizeof(struct trie_header_f);
+ trie_store_nodes_size(&t, trie->root);
+
+ err = fopen_temporary(filename , &t.f, &filename_tmp);
+ if (err < 0)
+ return err;
+ fchmod(fileno(t.f), 0444);
+
+ /* write nodes */
+ err = fseeko(t.f, sizeof(struct trie_header_f), SEEK_SET);
+ if (err < 0) {
+ fclose(t.f);
+ unlink_noerrno(filename_tmp);
+ return -errno;
+ }
+ root_off = trie_store_nodes(&t, trie->root);
+ h.nodes_root_off = htole64(root_off);
+ pos = ftello(t.f);
+ h.nodes_len = htole64(pos - sizeof(struct trie_header_f));
+
+ /* write string buffer */
+ fwrite(trie->strings->buf, trie->strings->len, 1, t.f);
+ h.strings_len = htole64(trie->strings->len);
+
+ /* write header */
+ size = ftello(t.f);
+ h.file_size = htole64(size);
+ err = fseeko(t.f, 0, SEEK_SET);
+ if (err < 0) {
+ fclose(t.f);
+ unlink_noerrno(filename_tmp);
+ return -errno;
+ }
+ fwrite(&h, sizeof(struct trie_header_f), 1, t.f);
+ err = ferror(t.f);
+ if (err)
+ err = -errno;
+ fclose(t.f);
+ if (err < 0 || rename(filename_tmp, filename) < 0) {
+ unlink_noerrno(filename_tmp);
+ return err < 0 ? err : -errno;
+ }
+
+ log_debug("=== trie on-disk ===");
+ log_debug("size: %8"PRIi64" bytes", size);
+ log_debug("header: %8zu bytes", sizeof(struct trie_header_f));
+ log_debug("nodes: %8"PRIu64" bytes (%8"PRIu64")",
+ t.nodes_count * sizeof(struct trie_node_f), t.nodes_count);
+ log_debug("child pointers: %8"PRIu64" bytes (%8"PRIu64")",
+ t.children_count * sizeof(struct trie_child_entry_f), t.children_count);
+ log_debug("value pointers: %8"PRIu64" bytes (%8"PRIu64")",
+ t.values_count * sizeof(struct trie_value_entry_f), t.values_count);
+ log_debug("string store: %8zu bytes", trie->strings->len);
+ log_debug("strings start: %8"PRIu64, t.strings_off);
+
+ return 0;
+}
+
+static int insert_data(struct trie *trie, char **match_list, char *line, const char *filename) {
+ char *value, **entry;
+
+ value = strchr(line, '=');
+ if (!value) {
+ log_error("Error, key/value pair expected but got '%s' in '%s':", line, filename);
+ return -EINVAL;
+ }
+
+ value[0] = '\0';
+ value++;
+
+ /* libudev requires properties to start with a space */
+ while (isblank(line[0]) && isblank(line[1]))
+ line++;
+
+ if (line[0] == '\0' || value[0] == '\0') {
+ log_error("Error, empty key or value '%s' in '%s':", line, filename);
+ return -EINVAL;
+ }
+
+ STRV_FOREACH(entry, match_list)
+ trie_insert(trie, trie->root, *entry, line, value);
+
+ return 0;
+}
+
+static int import_file(struct trie *trie, const char *filename) {
+ enum {
+ HW_NONE,
+ HW_MATCH,
+ HW_DATA,
+ } state = HW_NONE;
+ _cleanup_fclose_ FILE *f = NULL;
+ char line[LINE_MAX];
+ _cleanup_strv_free_ char **match_list = NULL;
+ char *match = NULL;
+ int r;
+
+ f = fopen(filename, "re");
+ if (!f)
+ return -errno;
+
+ while (fgets(line, sizeof(line), f)) {
+ size_t len;
+ char *pos;
+
+ /* comment line */
+ if (line[0] == '#')
+ continue;
+
+ /* strip trailing comment */
+ pos = strchr(line, '#');
+ if (pos)
+ pos[0] = '\0';
+
+ /* strip trailing whitespace */
+ len = strlen(line);
+ while (len > 0 && isspace(line[len-1]))
+ len--;
+ line[len] = '\0';
+
+ switch (state) {
+ case HW_NONE:
+ if (len == 0)
+ break;
+
+ if (line[0] == ' ') {
+ log_error("Error, MATCH expected but got '%s' in '%s':", line, filename);
+ break;
+ }
+
+ /* start of record, first match */
+ state = HW_MATCH;
+
+ match = strdup(line);
+ if (!match)
+ return -ENOMEM;
+
+ r = strv_consume(&match_list, match);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case HW_MATCH:
+ if (len == 0) {
+ log_error("Error, DATA expected but got empty line in '%s':", filename);
+ state = HW_NONE;
+ strv_clear(match_list);
+ break;
+ }
+
+ /* another match */
+ if (line[0] != ' ') {
+ match = strdup(line);
+ if (!match)
+ return -ENOMEM;
+
+ r = strv_consume(&match_list, match);
+ if (r < 0)
+ return r;
+
+ break;
+ }
+
+ /* first data */
+ state = HW_DATA;
+ insert_data(trie, match_list, line, filename);
+ break;
+
+ case HW_DATA:
+ /* end of record */
+ if (len == 0) {
+ state = HW_NONE;
+ strv_clear(match_list);
+ break;
+ }
+
+ if (line[0] != ' ') {
+ log_error("Error, DATA expected but got '%s' in '%s':", line, filename);
+ state = HW_NONE;
+ strv_clear(match_list);
+ break;
+ }
+
+ insert_data(trie, match_list, line, filename);
+ break;
+ };
+ }
+
+ return 0;
+}
+
+static int hwdb_query(int argc, char *argv[], void *userdata) {
+ _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
+ const char *key, *value;
+ const char *modalias;
+ int r;
+
+ assert(argc >= 2);
+ assert(argv);
+
+ modalias = argv[1];
+
+ r = sd_hwdb_new(&hwdb);
+ if (r < 0)
+ return r;
+
+ SD_HWDB_FOREACH_PROPERTY(hwdb, modalias, key, value)
+ printf("%s=%s\n", key, value);
+
+ return 0;
+}
+
+static int hwdb_update(int argc, char *argv[], void *userdata) {
+ _cleanup_free_ char *hwdb_bin = NULL;
+ _cleanup_(trie_freep) struct trie *trie = NULL;
+ char **files, **f;
+ int r;
+
+ trie = new0(struct trie, 1);
+ if (!trie)
+ return -ENOMEM;
+
+ /* string store */
+ trie->strings = strbuf_new();
+ if (!trie->strings)
+ return -ENOMEM;
+
+ /* index */
+ trie->root = new0(struct trie_node, 1);
+ if (!trie->root)
+ return -ENOMEM;
+
+ trie->nodes_count++;
+
+ r = conf_files_list_strv(&files, ".hwdb", arg_root, conf_file_dirs);
+ if (r < 0)
+ return log_error_errno(r, "failed to enumerate hwdb files: %m");
+
+ STRV_FOREACH(f, files) {
+ log_debug("reading file '%s'", *f);
+ import_file(trie, *f);
+ }
+ strv_free(files);
+
+ strbuf_complete(trie->strings);
+
+ log_debug("=== trie in-memory ===");
+ log_debug("nodes: %8zu bytes (%8zu)",
+ trie->nodes_count * sizeof(struct trie_node), trie->nodes_count);
+ log_debug("children arrays: %8zu bytes (%8zu)",
+ trie->children_count * sizeof(struct trie_child_entry), trie->children_count);
+ log_debug("values arrays: %8zu bytes (%8zu)",
+ trie->values_count * sizeof(struct trie_value_entry), trie->values_count);
+ log_debug("strings: %8zu bytes",
+ trie->strings->len);
+ log_debug("strings incoming: %8zu bytes (%8zu)",
+ trie->strings->in_len, trie->strings->in_count);
+ log_debug("strings dedup'ed: %8zu bytes (%8zu)",
+ trie->strings->dedup_len, trie->strings->dedup_count);
+
+ hwdb_bin = strjoin(arg_root, "/", arg_hwdb_bin_dir, "/hwdb.bin", NULL);
+ if (!hwdb_bin)
+ return -ENOMEM;
+
+ mkdir_parents(hwdb_bin, 0755);
+ r = trie_store(trie, hwdb_bin);
+ if (r < 0)
+ return log_error_errno(r, "Failure writing database %s: %m", hwdb_bin);
+
+ return 0;
+}
+
+static void help(void) {
+ printf("Usage: %s OPTIONS COMMAND\n\n"
+ "Update or query the hardware database.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --usr Generate in " UDEVLIBEXECDIR " instead of /etc/udev\n"
+ " -r --root=PATH Alternative root path in the filesystem\n\n"
+ "Commands:\n"
+ " update Update the hwdb database\n"
+ " query MODALIAS Query database and print result\n",
+ program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_USR,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "usr", no_argument, NULL, ARG_USR },
+ { "root", required_argument, NULL, 'r' },
+ {}
+ };
+
+ int c;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "ut:r:h", options, NULL)) >= 0) {
+ switch(c) {
+
+ case 'h':
+ help();
+ return 0;
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_USR:
+ arg_hwdb_bin_dir = UDEVLIBEXECDIR;
+ break;
+
+ case 'r':
+ arg_root = optarg;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unknown option");
+ }
+ }
+
+ return 1;
+}
+
+static int hwdb_main(int argc, char *argv[]) {
+ const Verb verbs[] = {
+ { "update", 1, 1, 0, hwdb_update },
+ { "query", 2, 2, 0, hwdb_query },
+ {},
+ };
+
+ return dispatch_verb(argc, argv, verbs, NULL);
+}
+
+int main (int argc, char *argv[]) {
+ int r;
+
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ goto finish;
+
+ r = hwdb_main(argc, argv);
+
+finish:
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-udev/systemd-hwdb/systemd-hwdb.xml b/src/grp-udev/systemd-hwdb/systemd-hwdb.xml
new file mode 100644
index 0000000000..2b363c77f2
--- /dev/null
+++ b/src/grp-udev/systemd-hwdb/systemd-hwdb.xml
@@ -0,0 +1,93 @@
+<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<refentry id="systemd-hwdb" conditional="ENABLE_HWDB">
+ <refentryinfo>
+ <title>systemd-hwdb</title>
+ <productname>systemd</productname>
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Kay</firstname>
+ <surname>Sievers</surname>
+ <email>kay@vrfy.org</email>
+ </author>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Tom</firstname>
+ <surname>Gundersen</surname>
+ <email>teg@jklm.no</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-hwdb</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-hwdb</refname><refpurpose>hardware database management tool</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>systemd-hwdb <optional>options</optional> update</command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>systemd-hwdb <optional>options</optional> query <replaceable>modalias</replaceable></command>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1><title>Description</title>
+ <para><command>systemd-hwdb</command> expects a command and command
+ specific arguments. It manages the binary hardware database.</para>
+ </refsect1>
+
+ <refsect1><title>Options</title>
+ <variablelist>
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--usr</option></term>
+ <listitem>
+ <para>Generate in /usr/lib/udev instead of /etc/udev.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-r</option></term>
+ <term><option>--root=<replaceable>PATH</replaceable></option></term>
+ <listitem>
+ <para>Alternate root path in the filesystem.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <refsect2><title>systemd-hwdb
+ <arg choice="opt"><replaceable>options</replaceable></arg>
+ update</title>
+ <para>Update the binary database.</para>
+ </refsect2>
+
+ <refsect2><title>systemd-hwdb
+ <arg choice="opt"><replaceable>options</replaceable></arg>
+ query
+ <arg><replaceable>MODALIAS</replaceable></arg>
+ </title>
+ <para>Query database and print result.</para>
+ </refsect2>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para><citerefentry>
+ <refentrytitle>hwdb</refentrytitle><manvolnum>7</manvolnum>
+ </citerefentry></para>
+ </refsect1>
+</refentry>
diff --git a/src/grp-udev/systemd-udevd/systemd-udevd.service.in b/src/grp-udev/systemd-udevd/systemd-udevd.service.in
new file mode 100644
index 0000000000..79f28c87c6
--- /dev/null
+++ b/src/grp-udev/systemd-udevd/systemd-udevd.service.in
@@ -0,0 +1,26 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=udev Kernel Device Manager
+Documentation=man:systemd-udevd.service(8) man:udev(7)
+DefaultDependencies=no
+Wants=systemd-udevd-control.socket systemd-udevd-kernel.socket
+After=systemd-udevd-control.socket systemd-udevd-kernel.socket systemd-sysusers.service
+Before=sysinit.target
+ConditionPathIsReadWrite=/sys
+
+[Service]
+Type=notify
+OOMScoreAdjust=-1000
+Sockets=systemd-udevd-control.socket systemd-udevd-kernel.socket
+Restart=always
+RestartSec=0
+ExecStart=@rootlibexecdir@/systemd-udevd
+MountFlags=slave
+KillMode=mixed
+WatchdogSec=3min
diff --git a/src/grp-udev/systemd-udevd/udevd.c b/src/grp-udev/systemd-udevd/udevd.c
new file mode 100644
index 0000000000..89006e6e3a
--- /dev/null
+++ b/src/grp-udev/systemd-udevd/udevd.c
@@ -0,0 +1,1765 @@
+/*
+ * Copyright (C) 2004-2012 Kay Sievers <kay@vrfy.org>
+ * Copyright (C) 2004 Chris Friesen <chris_friesen@sympatico.ca>
+ * Copyright (C) 2009 Canonical Ltd.
+ * Copyright (C) 2009 Scott James Remnant <scott@netsplit.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/file.h>
+#include <sys/inotify.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <sys/prctl.h>
+#include <sys/signalfd.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <systemd/sd-daemon.h>
+#include <systemd/sd-event.h>
+
+#include "alloc-util.h"
+#include "cgroup-util.h"
+#include "cpu-set-util.h"
+#include "dev-setup.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "formats-util.h"
+#include "fs-util.h"
+#include "hashmap.h"
+#include "io-util.h"
+#include "netlink-util.h"
+#include "parse-util.h"
+#include "proc-cmdline.h"
+#include "process-util.h"
+#include "selinux-util.h"
+#include "signal-util.h"
+#include "socket-util.h"
+#include "string-util.h"
+#include "terminal-util.h"
+#include "udev-util.h"
+#include "udev.h"
+#include "user-util.h"
+
+static bool arg_debug = false;
+static int arg_daemonize = false;
+static int arg_resolve_names = 1;
+static unsigned arg_children_max;
+static int arg_exec_delay;
+static usec_t arg_event_timeout_usec = 180 * USEC_PER_SEC;
+static usec_t arg_event_timeout_warn_usec = 180 * USEC_PER_SEC / 3;
+
+typedef struct Manager {
+ struct udev *udev;
+ sd_event *event;
+ Hashmap *workers;
+ struct udev_list_node events;
+ const char *cgroup;
+ pid_t pid; /* the process that originally allocated the manager object */
+
+ struct udev_rules *rules;
+ struct udev_list properties;
+
+ struct udev_monitor *monitor;
+ struct udev_ctrl *ctrl;
+ struct udev_ctrl_connection *ctrl_conn_blocking;
+ int fd_inotify;
+ int worker_watch[2];
+
+ sd_event_source *ctrl_event;
+ sd_event_source *uevent_event;
+ sd_event_source *inotify_event;
+
+ usec_t last_usec;
+
+ bool stop_exec_queue:1;
+ bool exit:1;
+} Manager;
+
+enum event_state {
+ EVENT_UNDEF,
+ EVENT_QUEUED,
+ EVENT_RUNNING,
+};
+
+struct event {
+ struct udev_list_node node;
+ Manager *manager;
+ struct udev *udev;
+ struct udev_device *dev;
+ struct udev_device *dev_kernel;
+ struct worker *worker;
+ enum event_state state;
+ unsigned long long int delaying_seqnum;
+ unsigned long long int seqnum;
+ const char *devpath;
+ size_t devpath_len;
+ const char *devpath_old;
+ dev_t devnum;
+ int ifindex;
+ bool is_block;
+ sd_event_source *timeout_warning;
+ sd_event_source *timeout;
+};
+
+static inline struct event *node_to_event(struct udev_list_node *node) {
+ return container_of(node, struct event, node);
+}
+
+static void event_queue_cleanup(Manager *manager, enum event_state type);
+
+enum worker_state {
+ WORKER_UNDEF,
+ WORKER_RUNNING,
+ WORKER_IDLE,
+ WORKER_KILLED,
+};
+
+struct worker {
+ Manager *manager;
+ struct udev_list_node node;
+ int refcount;
+ pid_t pid;
+ struct udev_monitor *monitor;
+ enum worker_state state;
+ struct event *event;
+};
+
+/* passed from worker to main process */
+struct worker_message {
+};
+
+static void event_free(struct event *event) {
+ int r;
+
+ if (!event)
+ return;
+
+ udev_list_node_remove(&event->node);
+ udev_device_unref(event->dev);
+ udev_device_unref(event->dev_kernel);
+
+ sd_event_source_unref(event->timeout_warning);
+ sd_event_source_unref(event->timeout);
+
+ if (event->worker)
+ event->worker->event = NULL;
+
+ assert(event->manager);
+
+ if (udev_list_node_is_empty(&event->manager->events)) {
+ /* only clean up the queue from the process that created it */
+ if (event->manager->pid == getpid()) {
+ r = unlink("/run/udev/queue");
+ if (r < 0)
+ log_warning_errno(errno, "could not unlink /run/udev/queue: %m");
+ }
+ }
+
+ free(event);
+}
+
+static void worker_free(struct worker *worker) {
+ if (!worker)
+ return;
+
+ assert(worker->manager);
+
+ hashmap_remove(worker->manager->workers, PID_TO_PTR(worker->pid));
+ udev_monitor_unref(worker->monitor);
+ event_free(worker->event);
+
+ free(worker);
+}
+
+static void manager_workers_free(Manager *manager) {
+ struct worker *worker;
+ Iterator i;
+
+ assert(manager);
+
+ HASHMAP_FOREACH(worker, manager->workers, i)
+ worker_free(worker);
+
+ manager->workers = hashmap_free(manager->workers);
+}
+
+static int worker_new(struct worker **ret, Manager *manager, struct udev_monitor *worker_monitor, pid_t pid) {
+ _cleanup_free_ struct worker *worker = NULL;
+ int r;
+
+ assert(ret);
+ assert(manager);
+ assert(worker_monitor);
+ assert(pid > 1);
+
+ worker = new0(struct worker, 1);
+ if (!worker)
+ return -ENOMEM;
+
+ worker->refcount = 1;
+ worker->manager = manager;
+ /* close monitor, but keep address around */
+ udev_monitor_disconnect(worker_monitor);
+ worker->monitor = udev_monitor_ref(worker_monitor);
+ worker->pid = pid;
+
+ r = hashmap_ensure_allocated(&manager->workers, NULL);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(manager->workers, PID_TO_PTR(pid), worker);
+ if (r < 0)
+ return r;
+
+ *ret = worker;
+ worker = NULL;
+
+ return 0;
+}
+
+static int on_event_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
+ struct event *event = userdata;
+
+ assert(event);
+ assert(event->worker);
+
+ kill_and_sigcont(event->worker->pid, SIGKILL);
+ event->worker->state = WORKER_KILLED;
+
+ log_error("seq %llu '%s' killed", udev_device_get_seqnum(event->dev), event->devpath);
+
+ return 1;
+}
+
+static int on_event_timeout_warning(sd_event_source *s, uint64_t usec, void *userdata) {
+ struct event *event = userdata;
+
+ assert(event);
+
+ log_warning("seq %llu '%s' is taking a long time", udev_device_get_seqnum(event->dev), event->devpath);
+
+ return 1;
+}
+
+static void worker_attach_event(struct worker *worker, struct event *event) {
+ sd_event *e;
+ uint64_t usec;
+
+ assert(worker);
+ assert(worker->manager);
+ assert(event);
+ assert(!event->worker);
+ assert(!worker->event);
+
+ worker->state = WORKER_RUNNING;
+ worker->event = event;
+ event->state = EVENT_RUNNING;
+ event->worker = worker;
+
+ e = worker->manager->event;
+
+ assert_se(sd_event_now(e, clock_boottime_or_monotonic(), &usec) >= 0);
+
+ (void) sd_event_add_time(e, &event->timeout_warning, clock_boottime_or_monotonic(),
+ usec + arg_event_timeout_warn_usec, USEC_PER_SEC, on_event_timeout_warning, event);
+
+ (void) sd_event_add_time(e, &event->timeout, clock_boottime_or_monotonic(),
+ usec + arg_event_timeout_usec, USEC_PER_SEC, on_event_timeout, event);
+}
+
+static void manager_free(Manager *manager) {
+ if (!manager)
+ return;
+
+ udev_builtin_exit(manager->udev);
+
+ sd_event_source_unref(manager->ctrl_event);
+ sd_event_source_unref(manager->uevent_event);
+ sd_event_source_unref(manager->inotify_event);
+
+ udev_unref(manager->udev);
+ sd_event_unref(manager->event);
+ manager_workers_free(manager);
+ event_queue_cleanup(manager, EVENT_UNDEF);
+
+ udev_monitor_unref(manager->monitor);
+ udev_ctrl_unref(manager->ctrl);
+ udev_ctrl_connection_unref(manager->ctrl_conn_blocking);
+
+ udev_list_cleanup(&manager->properties);
+ udev_rules_unref(manager->rules);
+
+ safe_close(manager->fd_inotify);
+ safe_close_pair(manager->worker_watch);
+
+ free(manager);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
+
+static int worker_send_message(int fd) {
+ struct worker_message message = {};
+
+ return loop_write(fd, &message, sizeof(message), false);
+}
+
+static void worker_spawn(Manager *manager, struct event *event) {
+ struct udev *udev = event->udev;
+ _cleanup_udev_monitor_unref_ struct udev_monitor *worker_monitor = NULL;
+ pid_t pid;
+ int r = 0;
+
+ /* listen for new events */
+ worker_monitor = udev_monitor_new_from_netlink(udev, NULL);
+ if (worker_monitor == NULL)
+ return;
+ /* allow the main daemon netlink address to send devices to the worker */
+ udev_monitor_allow_unicast_sender(worker_monitor, manager->monitor);
+ r = udev_monitor_enable_receiving(worker_monitor);
+ if (r < 0)
+ log_error_errno(r, "worker: could not enable receiving of device: %m");
+
+ pid = fork();
+ switch (pid) {
+ case 0: {
+ struct udev_device *dev = NULL;
+ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+ int fd_monitor;
+ _cleanup_close_ int fd_signal = -1, fd_ep = -1;
+ struct epoll_event ep_signal = { .events = EPOLLIN };
+ struct epoll_event ep_monitor = { .events = EPOLLIN };
+ sigset_t mask;
+
+ /* take initial device from queue */
+ dev = event->dev;
+ event->dev = NULL;
+
+ unsetenv("NOTIFY_SOCKET");
+
+ manager_workers_free(manager);
+ event_queue_cleanup(manager, EVENT_UNDEF);
+
+ manager->monitor = udev_monitor_unref(manager->monitor);
+ manager->ctrl_conn_blocking = udev_ctrl_connection_unref(manager->ctrl_conn_blocking);
+ manager->ctrl = udev_ctrl_unref(manager->ctrl);
+ manager->ctrl_conn_blocking = udev_ctrl_connection_unref(manager->ctrl_conn_blocking);
+ manager->worker_watch[READ_END] = safe_close(manager->worker_watch[READ_END]);
+
+ manager->ctrl_event = sd_event_source_unref(manager->ctrl_event);
+ manager->uevent_event = sd_event_source_unref(manager->uevent_event);
+ manager->inotify_event = sd_event_source_unref(manager->inotify_event);
+
+ manager->event = sd_event_unref(manager->event);
+
+ sigfillset(&mask);
+ fd_signal = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
+ if (fd_signal < 0) {
+ r = log_error_errno(errno, "error creating signalfd %m");
+ goto out;
+ }
+ ep_signal.data.fd = fd_signal;
+
+ fd_monitor = udev_monitor_get_fd(worker_monitor);
+ ep_monitor.data.fd = fd_monitor;
+
+ fd_ep = epoll_create1(EPOLL_CLOEXEC);
+ if (fd_ep < 0) {
+ r = log_error_errno(errno, "error creating epoll fd: %m");
+ goto out;
+ }
+
+ if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_signal, &ep_signal) < 0 ||
+ epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_monitor, &ep_monitor) < 0) {
+ r = log_error_errno(errno, "fail to add fds to epoll: %m");
+ goto out;
+ }
+
+ /* Request TERM signal if parent exits.
+ Ignore error, not much we can do in that case. */
+ (void) prctl(PR_SET_PDEATHSIG, SIGTERM);
+
+ /* Reset OOM score, we only protect the main daemon. */
+ write_string_file("/proc/self/oom_score_adj", "0", 0);
+
+ for (;;) {
+ struct udev_event *udev_event;
+ int fd_lock = -1;
+
+ assert(dev);
+
+ log_debug("seq %llu running", udev_device_get_seqnum(dev));
+ udev_event = udev_event_new(dev);
+ if (udev_event == NULL) {
+ r = -ENOMEM;
+ goto out;
+ }
+
+ if (arg_exec_delay > 0)
+ udev_event->exec_delay = arg_exec_delay;
+
+ /*
+ * Take a shared lock on the device node; this establishes
+ * a concept of device "ownership" to serialize device
+ * access. External processes holding an exclusive lock will
+ * cause udev to skip the event handling; in the case udev
+ * acquired the lock, the external process can block until
+ * udev has finished its event handling.
+ */
+ if (!streq_ptr(udev_device_get_action(dev), "remove") &&
+ streq_ptr("block", udev_device_get_subsystem(dev)) &&
+ !startswith(udev_device_get_sysname(dev), "dm-") &&
+ !startswith(udev_device_get_sysname(dev), "md")) {
+ struct udev_device *d = dev;
+
+ if (streq_ptr("partition", udev_device_get_devtype(d)))
+ d = udev_device_get_parent(d);
+
+ if (d) {
+ fd_lock = open(udev_device_get_devnode(d), O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
+ if (fd_lock >= 0 && flock(fd_lock, LOCK_SH|LOCK_NB) < 0) {
+ log_debug_errno(errno, "Unable to flock(%s), skipping event handling: %m", udev_device_get_devnode(d));
+ fd_lock = safe_close(fd_lock);
+ goto skip;
+ }
+ }
+ }
+
+ /* needed for renaming netifs */
+ udev_event->rtnl = rtnl;
+
+ /* apply rules, create node, symlinks */
+ udev_event_execute_rules(udev_event,
+ arg_event_timeout_usec, arg_event_timeout_warn_usec,
+ &manager->properties,
+ manager->rules);
+
+ udev_event_execute_run(udev_event,
+ arg_event_timeout_usec, arg_event_timeout_warn_usec);
+
+ if (udev_event->rtnl)
+ /* in case rtnl was initialized */
+ rtnl = sd_netlink_ref(udev_event->rtnl);
+
+ /* apply/restore inotify watch */
+ if (udev_event->inotify_watch) {
+ udev_watch_begin(udev, dev);
+ udev_device_update_db(dev);
+ }
+
+ safe_close(fd_lock);
+
+ /* send processed event back to libudev listeners */
+ udev_monitor_send_device(worker_monitor, NULL, dev);
+
+skip:
+ log_debug("seq %llu processed", udev_device_get_seqnum(dev));
+
+ /* send udevd the result of the event execution */
+ r = worker_send_message(manager->worker_watch[WRITE_END]);
+ if (r < 0)
+ log_error_errno(r, "failed to send result of seq %llu to main daemon: %m",
+ udev_device_get_seqnum(dev));
+
+ udev_device_unref(dev);
+ dev = NULL;
+
+ udev_event_unref(udev_event);
+
+ /* wait for more device messages from main udevd, or term signal */
+ while (dev == NULL) {
+ struct epoll_event ev[4];
+ int fdcount;
+ int i;
+
+ fdcount = epoll_wait(fd_ep, ev, ELEMENTSOF(ev), -1);
+ if (fdcount < 0) {
+ if (errno == EINTR)
+ continue;
+ r = log_error_errno(errno, "failed to poll: %m");
+ goto out;
+ }
+
+ for (i = 0; i < fdcount; i++) {
+ if (ev[i].data.fd == fd_monitor && ev[i].events & EPOLLIN) {
+ dev = udev_monitor_receive_device(worker_monitor);
+ break;
+ } else if (ev[i].data.fd == fd_signal && ev[i].events & EPOLLIN) {
+ struct signalfd_siginfo fdsi;
+ ssize_t size;
+
+ size = read(fd_signal, &fdsi, sizeof(struct signalfd_siginfo));
+ if (size != sizeof(struct signalfd_siginfo))
+ continue;
+ switch (fdsi.ssi_signo) {
+ case SIGTERM:
+ goto out;
+ }
+ }
+ }
+ }
+ }
+out:
+ udev_device_unref(dev);
+ manager_free(manager);
+ log_close();
+ _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
+ }
+ case -1:
+ event->state = EVENT_QUEUED;
+ log_error_errno(errno, "fork of child failed: %m");
+ break;
+ default:
+ {
+ struct worker *worker;
+
+ r = worker_new(&worker, manager, worker_monitor, pid);
+ if (r < 0)
+ return;
+
+ worker_attach_event(worker, event);
+
+ log_debug("seq %llu forked new worker ["PID_FMT"]", udev_device_get_seqnum(event->dev), pid);
+ break;
+ }
+ }
+}
+
+static void event_run(Manager *manager, struct event *event) {
+ struct worker *worker;
+ Iterator i;
+
+ assert(manager);
+ assert(event);
+
+ HASHMAP_FOREACH(worker, manager->workers, i) {
+ ssize_t count;
+
+ if (worker->state != WORKER_IDLE)
+ continue;
+
+ count = udev_monitor_send_device(manager->monitor, worker->monitor, event->dev);
+ if (count < 0) {
+ log_error_errno(errno, "worker ["PID_FMT"] did not accept message %zi (%m), kill it",
+ worker->pid, count);
+ kill(worker->pid, SIGKILL);
+ worker->state = WORKER_KILLED;
+ continue;
+ }
+ worker_attach_event(worker, event);
+ return;
+ }
+
+ if (hashmap_size(manager->workers) >= arg_children_max) {
+ if (arg_children_max > 1)
+ log_debug("maximum number (%i) of children reached", hashmap_size(manager->workers));
+ return;
+ }
+
+ /* start new worker and pass initial device */
+ worker_spawn(manager, event);
+}
+
+static int event_queue_insert(Manager *manager, struct udev_device *dev) {
+ struct event *event;
+ int r;
+
+ assert(manager);
+ assert(dev);
+
+ /* only one process can add events to the queue */
+ if (manager->pid == 0)
+ manager->pid = getpid();
+
+ assert(manager->pid == getpid());
+
+ event = new0(struct event, 1);
+ if (!event)
+ return -ENOMEM;
+
+ event->udev = udev_device_get_udev(dev);
+ event->manager = manager;
+ event->dev = dev;
+ event->dev_kernel = udev_device_shallow_clone(dev);
+ udev_device_copy_properties(event->dev_kernel, dev);
+ event->seqnum = udev_device_get_seqnum(dev);
+ event->devpath = udev_device_get_devpath(dev);
+ event->devpath_len = strlen(event->devpath);
+ event->devpath_old = udev_device_get_devpath_old(dev);
+ event->devnum = udev_device_get_devnum(dev);
+ event->is_block = streq("block", udev_device_get_subsystem(dev));
+ event->ifindex = udev_device_get_ifindex(dev);
+
+ log_debug("seq %llu queued, '%s' '%s'", udev_device_get_seqnum(dev),
+ udev_device_get_action(dev), udev_device_get_subsystem(dev));
+
+ event->state = EVENT_QUEUED;
+
+ if (udev_list_node_is_empty(&manager->events)) {
+ r = touch("/run/udev/queue");
+ if (r < 0)
+ log_warning_errno(r, "could not touch /run/udev/queue: %m");
+ }
+
+ udev_list_node_append(&event->node, &manager->events);
+
+ return 0;
+}
+
+static void manager_kill_workers(Manager *manager) {
+ struct worker *worker;
+ Iterator i;
+
+ assert(manager);
+
+ HASHMAP_FOREACH(worker, manager->workers, i) {
+ if (worker->state == WORKER_KILLED)
+ continue;
+
+ worker->state = WORKER_KILLED;
+ kill(worker->pid, SIGTERM);
+ }
+}
+
+/* lookup event for identical, parent, child device */
+static bool is_devpath_busy(Manager *manager, struct event *event) {
+ struct udev_list_node *loop;
+ size_t common;
+
+ /* check if queue contains events we depend on */
+ udev_list_node_foreach(loop, &manager->events) {
+ struct event *loop_event = node_to_event(loop);
+
+ /* we already found a later event, earlier can not block us, no need to check again */
+ if (loop_event->seqnum < event->delaying_seqnum)
+ continue;
+
+ /* event we checked earlier still exists, no need to check again */
+ if (loop_event->seqnum == event->delaying_seqnum)
+ return true;
+
+ /* found ourself, no later event can block us */
+ if (loop_event->seqnum >= event->seqnum)
+ break;
+
+ /* check major/minor */
+ if (major(event->devnum) != 0 && event->devnum == loop_event->devnum && event->is_block == loop_event->is_block)
+ return true;
+
+ /* check network device ifindex */
+ if (event->ifindex != 0 && event->ifindex == loop_event->ifindex)
+ return true;
+
+ /* check our old name */
+ if (event->devpath_old != NULL && streq(loop_event->devpath, event->devpath_old)) {
+ event->delaying_seqnum = loop_event->seqnum;
+ return true;
+ }
+
+ /* compare devpath */
+ common = MIN(loop_event->devpath_len, event->devpath_len);
+
+ /* one devpath is contained in the other? */
+ if (memcmp(loop_event->devpath, event->devpath, common) != 0)
+ continue;
+
+ /* identical device event found */
+ if (loop_event->devpath_len == event->devpath_len) {
+ /* devices names might have changed/swapped in the meantime */
+ if (major(event->devnum) != 0 && (event->devnum != loop_event->devnum || event->is_block != loop_event->is_block))
+ continue;
+ if (event->ifindex != 0 && event->ifindex != loop_event->ifindex)
+ continue;
+ event->delaying_seqnum = loop_event->seqnum;
+ return true;
+ }
+
+ /* parent device event found */
+ if (event->devpath[common] == '/') {
+ event->delaying_seqnum = loop_event->seqnum;
+ return true;
+ }
+
+ /* child device event found */
+ if (loop_event->devpath[common] == '/') {
+ event->delaying_seqnum = loop_event->seqnum;
+ return true;
+ }
+
+ /* no matching device */
+ continue;
+ }
+
+ return false;
+}
+
+static int on_exit_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
+ Manager *manager = userdata;
+
+ assert(manager);
+
+ log_error_errno(ETIMEDOUT, "giving up waiting for workers to finish");
+
+ sd_event_exit(manager->event, -ETIMEDOUT);
+
+ return 1;
+}
+
+static void manager_exit(Manager *manager) {
+ uint64_t usec;
+ int r;
+
+ assert(manager);
+
+ manager->exit = true;
+
+ sd_notify(false,
+ "STOPPING=1\n"
+ "STATUS=Starting shutdown...");
+
+ /* close sources of new events and discard buffered events */
+ manager->ctrl_event = sd_event_source_unref(manager->ctrl_event);
+ manager->ctrl = udev_ctrl_unref(manager->ctrl);
+
+ manager->inotify_event = sd_event_source_unref(manager->inotify_event);
+ manager->fd_inotify = safe_close(manager->fd_inotify);
+
+ manager->uevent_event = sd_event_source_unref(manager->uevent_event);
+ manager->monitor = udev_monitor_unref(manager->monitor);
+
+ /* discard queued events and kill workers */
+ event_queue_cleanup(manager, EVENT_QUEUED);
+ manager_kill_workers(manager);
+
+ assert_se(sd_event_now(manager->event, clock_boottime_or_monotonic(), &usec) >= 0);
+
+ r = sd_event_add_time(manager->event, NULL, clock_boottime_or_monotonic(),
+ usec + 30 * USEC_PER_SEC, USEC_PER_SEC, on_exit_timeout, manager);
+ if (r < 0)
+ return;
+}
+
+/* reload requested, HUP signal received, rules changed, builtin changed */
+static void manager_reload(Manager *manager) {
+
+ assert(manager);
+
+ sd_notify(false,
+ "RELOADING=1\n"
+ "STATUS=Flushing configuration...");
+
+ manager_kill_workers(manager);
+ manager->rules = udev_rules_unref(manager->rules);
+ udev_builtin_exit(manager->udev);
+
+ sd_notify(false,
+ "READY=1\n"
+ "STATUS=Processing...");
+}
+
+static void event_queue_start(Manager *manager) {
+ struct udev_list_node *loop;
+ usec_t usec;
+
+ assert(manager);
+
+ if (udev_list_node_is_empty(&manager->events) ||
+ manager->exit || manager->stop_exec_queue)
+ return;
+
+ assert_se(sd_event_now(manager->event, clock_boottime_or_monotonic(), &usec) >= 0);
+ /* check for changed config, every 3 seconds at most */
+ if (manager->last_usec == 0 ||
+ (usec - manager->last_usec) > 3 * USEC_PER_SEC) {
+ if (udev_rules_check_timestamp(manager->rules) ||
+ udev_builtin_validate(manager->udev))
+ manager_reload(manager);
+
+ manager->last_usec = usec;
+ }
+
+ udev_builtin_init(manager->udev);
+
+ if (!manager->rules) {
+ manager->rules = udev_rules_new(manager->udev, arg_resolve_names);
+ if (!manager->rules)
+ return;
+ }
+
+ udev_list_node_foreach(loop, &manager->events) {
+ struct event *event = node_to_event(loop);
+
+ if (event->state != EVENT_QUEUED)
+ continue;
+
+ /* do not start event if parent or child event is still running */
+ if (is_devpath_busy(manager, event))
+ continue;
+
+ event_run(manager, event);
+ }
+}
+
+static void event_queue_cleanup(Manager *manager, enum event_state match_type) {
+ struct udev_list_node *loop, *tmp;
+
+ udev_list_node_foreach_safe(loop, tmp, &manager->events) {
+ struct event *event = node_to_event(loop);
+
+ if (match_type != EVENT_UNDEF && match_type != event->state)
+ continue;
+
+ event_free(event);
+ }
+}
+
+static int on_worker(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ Manager *manager = userdata;
+
+ assert(manager);
+
+ for (;;) {
+ struct worker_message msg;
+ struct iovec iovec = {
+ .iov_base = &msg,
+ .iov_len = sizeof(msg),
+ };
+ union {
+ struct cmsghdr cmsghdr;
+ uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
+ } control = {};
+ struct msghdr msghdr = {
+ .msg_iov = &iovec,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ struct cmsghdr *cmsg;
+ ssize_t size;
+ struct ucred *ucred = NULL;
+ struct worker *worker;
+
+ size = recvmsg(fd, &msghdr, MSG_DONTWAIT);
+ if (size < 0) {
+ if (errno == EINTR)
+ continue;
+ else if (errno == EAGAIN)
+ /* nothing more to read */
+ break;
+
+ return log_error_errno(errno, "failed to receive message: %m");
+ } else if (size != sizeof(struct worker_message)) {
+ log_warning_errno(EIO, "ignoring worker message with invalid size %zi bytes", size);
+ continue;
+ }
+
+ CMSG_FOREACH(cmsg, &msghdr) {
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_CREDENTIALS &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred)))
+ ucred = (struct ucred*) CMSG_DATA(cmsg);
+ }
+
+ if (!ucred || ucred->pid <= 0) {
+ log_warning_errno(EIO, "ignoring worker message without valid PID");
+ continue;
+ }
+
+ /* lookup worker who sent the signal */
+ worker = hashmap_get(manager->workers, PID_TO_PTR(ucred->pid));
+ if (!worker) {
+ log_debug("worker ["PID_FMT"] returned, but is no longer tracked", ucred->pid);
+ continue;
+ }
+
+ if (worker->state != WORKER_KILLED)
+ worker->state = WORKER_IDLE;
+
+ /* worker returned */
+ event_free(worker->event);
+ }
+
+ /* we have free workers, try to schedule events */
+ event_queue_start(manager);
+
+ return 1;
+}
+
+static int on_uevent(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ Manager *manager = userdata;
+ struct udev_device *dev;
+ int r;
+
+ assert(manager);
+
+ dev = udev_monitor_receive_device(manager->monitor);
+ if (dev) {
+ udev_device_ensure_usec_initialized(dev, NULL);
+ r = event_queue_insert(manager, dev);
+ if (r < 0)
+ udev_device_unref(dev);
+ else
+ /* we have fresh events, try to schedule them */
+ event_queue_start(manager);
+ }
+
+ return 1;
+}
+
+/* receive the udevd message from userspace */
+static int on_ctrl_msg(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ Manager *manager = userdata;
+ _cleanup_udev_ctrl_connection_unref_ struct udev_ctrl_connection *ctrl_conn = NULL;
+ _cleanup_udev_ctrl_msg_unref_ struct udev_ctrl_msg *ctrl_msg = NULL;
+ const char *str;
+ int i;
+
+ assert(manager);
+
+ ctrl_conn = udev_ctrl_get_connection(manager->ctrl);
+ if (!ctrl_conn)
+ return 1;
+
+ ctrl_msg = udev_ctrl_receive_msg(ctrl_conn);
+ if (!ctrl_msg)
+ return 1;
+
+ i = udev_ctrl_get_set_log_level(ctrl_msg);
+ if (i >= 0) {
+ log_debug("udevd message (SET_LOG_LEVEL) received, log_priority=%i", i);
+ log_set_max_level(i);
+ manager_kill_workers(manager);
+ }
+
+ if (udev_ctrl_get_stop_exec_queue(ctrl_msg) > 0) {
+ log_debug("udevd message (STOP_EXEC_QUEUE) received");
+ manager->stop_exec_queue = true;
+ }
+
+ if (udev_ctrl_get_start_exec_queue(ctrl_msg) > 0) {
+ log_debug("udevd message (START_EXEC_QUEUE) received");
+ manager->stop_exec_queue = false;
+ event_queue_start(manager);
+ }
+
+ if (udev_ctrl_get_reload(ctrl_msg) > 0) {
+ log_debug("udevd message (RELOAD) received");
+ manager_reload(manager);
+ }
+
+ str = udev_ctrl_get_set_env(ctrl_msg);
+ if (str != NULL) {
+ _cleanup_free_ char *key = NULL;
+
+ key = strdup(str);
+ if (key) {
+ char *val;
+
+ val = strchr(key, '=');
+ if (val != NULL) {
+ val[0] = '\0';
+ val = &val[1];
+ if (val[0] == '\0') {
+ log_debug("udevd message (ENV) received, unset '%s'", key);
+ udev_list_entry_add(&manager->properties, key, NULL);
+ } else {
+ log_debug("udevd message (ENV) received, set '%s=%s'", key, val);
+ udev_list_entry_add(&manager->properties, key, val);
+ }
+ } else
+ log_error("wrong key format '%s'", key);
+ }
+ manager_kill_workers(manager);
+ }
+
+ i = udev_ctrl_get_set_children_max(ctrl_msg);
+ if (i >= 0) {
+ log_debug("udevd message (SET_MAX_CHILDREN) received, children_max=%i", i);
+ arg_children_max = i;
+ }
+
+ if (udev_ctrl_get_ping(ctrl_msg) > 0)
+ log_debug("udevd message (SYNC) received");
+
+ if (udev_ctrl_get_exit(ctrl_msg) > 0) {
+ log_debug("udevd message (EXIT) received");
+ manager_exit(manager);
+ /* keep reference to block the client until we exit
+ TODO: deal with several blocking exit requests */
+ manager->ctrl_conn_blocking = udev_ctrl_connection_ref(ctrl_conn);
+ }
+
+ return 1;
+}
+
+static int synthesize_change(struct udev_device *dev) {
+ char filename[UTIL_PATH_SIZE];
+ int r;
+
+ if (streq_ptr("block", udev_device_get_subsystem(dev)) &&
+ streq_ptr("disk", udev_device_get_devtype(dev)) &&
+ !startswith(udev_device_get_sysname(dev), "dm-")) {
+ bool part_table_read = false;
+ bool has_partitions = false;
+ int fd;
+ struct udev *udev = udev_device_get_udev(dev);
+ _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL;
+ struct udev_list_entry *item;
+
+ /*
+ * Try to re-read the partition table. This only succeeds if
+ * none of the devices is busy. The kernel returns 0 if no
+ * partition table is found, and we will not get an event for
+ * the disk.
+ */
+ fd = open(udev_device_get_devnode(dev), O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
+ if (fd >= 0) {
+ r = flock(fd, LOCK_EX|LOCK_NB);
+ if (r >= 0)
+ r = ioctl(fd, BLKRRPART, 0);
+
+ close(fd);
+ if (r >= 0)
+ part_table_read = true;
+ }
+
+ /* search for partitions */
+ e = udev_enumerate_new(udev);
+ if (!e)
+ return -ENOMEM;
+
+ r = udev_enumerate_add_match_parent(e, dev);
+ if (r < 0)
+ return r;
+
+ r = udev_enumerate_add_match_subsystem(e, "block");
+ if (r < 0)
+ return r;
+
+ r = udev_enumerate_scan_devices(e);
+ if (r < 0)
+ return r;
+
+ udev_list_entry_foreach(item, udev_enumerate_get_list_entry(e)) {
+ _cleanup_udev_device_unref_ struct udev_device *d = NULL;
+
+ d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));
+ if (!d)
+ continue;
+
+ if (!streq_ptr("partition", udev_device_get_devtype(d)))
+ continue;
+
+ has_partitions = true;
+ break;
+ }
+
+ /*
+ * We have partitions and re-read the table, the kernel already sent
+ * out a "change" event for the disk, and "remove/add" for all
+ * partitions.
+ */
+ if (part_table_read && has_partitions)
+ return 0;
+
+ /*
+ * We have partitions but re-reading the partition table did not
+ * work, synthesize "change" for the disk and all partitions.
+ */
+ log_debug("device %s closed, synthesising 'change'", udev_device_get_devnode(dev));
+ strscpyl(filename, sizeof(filename), udev_device_get_syspath(dev), "/uevent", NULL);
+ write_string_file(filename, "change", WRITE_STRING_FILE_CREATE);
+
+ udev_list_entry_foreach(item, udev_enumerate_get_list_entry(e)) {
+ _cleanup_udev_device_unref_ struct udev_device *d = NULL;
+
+ d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));
+ if (!d)
+ continue;
+
+ if (!streq_ptr("partition", udev_device_get_devtype(d)))
+ continue;
+
+ log_debug("device %s closed, synthesising partition '%s' 'change'",
+ udev_device_get_devnode(dev), udev_device_get_devnode(d));
+ strscpyl(filename, sizeof(filename), udev_device_get_syspath(d), "/uevent", NULL);
+ write_string_file(filename, "change", WRITE_STRING_FILE_CREATE);
+ }
+
+ return 0;
+ }
+
+ log_debug("device %s closed, synthesising 'change'", udev_device_get_devnode(dev));
+ strscpyl(filename, sizeof(filename), udev_device_get_syspath(dev), "/uevent", NULL);
+ write_string_file(filename, "change", WRITE_STRING_FILE_CREATE);
+
+ return 0;
+}
+
+static int on_inotify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ Manager *manager = userdata;
+ union inotify_event_buffer buffer;
+ struct inotify_event *e;
+ ssize_t l;
+
+ assert(manager);
+
+ l = read(fd, &buffer, sizeof(buffer));
+ if (l < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ return 1;
+
+ return log_error_errno(errno, "Failed to read inotify fd: %m");
+ }
+
+ FOREACH_INOTIFY_EVENT(e, buffer, l) {
+ _cleanup_udev_device_unref_ struct udev_device *dev = NULL;
+
+ dev = udev_watch_lookup(manager->udev, e->wd);
+ if (!dev)
+ continue;
+
+ log_debug("inotify event: %x for %s", e->mask, udev_device_get_devnode(dev));
+ if (e->mask & IN_CLOSE_WRITE) {
+ synthesize_change(dev);
+
+ /* settle might be waiting on us to determine the queue
+ * state. If we just handled an inotify event, we might have
+ * generated a "change" event, but we won't have queued up
+ * the resultant uevent yet. Do that.
+ */
+ on_uevent(NULL, -1, 0, manager);
+ } else if (e->mask & IN_IGNORED)
+ udev_watch_end(manager->udev, dev);
+ }
+
+ return 1;
+}
+
+static int on_sigterm(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
+ Manager *manager = userdata;
+
+ assert(manager);
+
+ manager_exit(manager);
+
+ return 1;
+}
+
+static int on_sighup(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
+ Manager *manager = userdata;
+
+ assert(manager);
+
+ manager_reload(manager);
+
+ return 1;
+}
+
+static int on_sigchld(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
+ Manager *manager = userdata;
+
+ assert(manager);
+
+ for (;;) {
+ pid_t pid;
+ int status;
+ struct worker *worker;
+
+ pid = waitpid(-1, &status, WNOHANG);
+ if (pid <= 0)
+ break;
+
+ worker = hashmap_get(manager->workers, PID_TO_PTR(pid));
+ if (!worker) {
+ log_warning("worker ["PID_FMT"] is unknown, ignoring", pid);
+ continue;
+ }
+
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) == 0)
+ log_debug("worker ["PID_FMT"] exited", pid);
+ else
+ log_warning("worker ["PID_FMT"] exited with return code %i", pid, WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status)) {
+ log_warning("worker ["PID_FMT"] terminated by signal %i (%s)", pid, WTERMSIG(status), strsignal(WTERMSIG(status)));
+ } else if (WIFSTOPPED(status)) {
+ log_info("worker ["PID_FMT"] stopped", pid);
+ continue;
+ } else if (WIFCONTINUED(status)) {
+ log_info("worker ["PID_FMT"] continued", pid);
+ continue;
+ } else
+ log_warning("worker ["PID_FMT"] exit with status 0x%04x", pid, status);
+
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ if (worker->event) {
+ log_error("worker ["PID_FMT"] failed while handling '%s'", pid, worker->event->devpath);
+ /* delete state from disk */
+ udev_device_delete_db(worker->event->dev);
+ udev_device_tag_index(worker->event->dev, NULL, false);
+ /* forward kernel event without amending it */
+ udev_monitor_send_device(manager->monitor, NULL, worker->event->dev_kernel);
+ }
+ }
+
+ worker_free(worker);
+ }
+
+ /* we can start new workers, try to schedule events */
+ event_queue_start(manager);
+
+ return 1;
+}
+
+static int on_post(sd_event_source *s, void *userdata) {
+ Manager *manager = userdata;
+ int r;
+
+ assert(manager);
+
+ if (udev_list_node_is_empty(&manager->events)) {
+ /* no pending events */
+ if (!hashmap_isempty(manager->workers)) {
+ /* there are idle workers */
+ log_debug("cleanup idle workers");
+ manager_kill_workers(manager);
+ } else {
+ /* we are idle */
+ if (manager->exit) {
+ r = sd_event_exit(manager->event, 0);
+ if (r < 0)
+ return r;
+ } else if (manager->cgroup)
+ /* cleanup possible left-over processes in our cgroup */
+ cg_kill(SYSTEMD_CGROUP_CONTROLLER, manager->cgroup, SIGKILL, false, true, NULL);
+ }
+ }
+
+ return 1;
+}
+
+static int listen_fds(int *rctrl, int *rnetlink) {
+ _cleanup_udev_unref_ struct udev *udev = NULL;
+ int ctrl_fd = -1, netlink_fd = -1;
+ int fd, n, r;
+
+ assert(rctrl);
+ assert(rnetlink);
+
+ n = sd_listen_fds(true);
+ if (n < 0)
+ return n;
+
+ for (fd = SD_LISTEN_FDS_START; fd < n + SD_LISTEN_FDS_START; fd++) {
+ if (sd_is_socket(fd, AF_LOCAL, SOCK_SEQPACKET, -1)) {
+ if (ctrl_fd >= 0)
+ return -EINVAL;
+ ctrl_fd = fd;
+ continue;
+ }
+
+ if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, -1)) {
+ if (netlink_fd >= 0)
+ return -EINVAL;
+ netlink_fd = fd;
+ continue;
+ }
+
+ return -EINVAL;
+ }
+
+ if (ctrl_fd < 0) {
+ _cleanup_udev_ctrl_unref_ struct udev_ctrl *ctrl = NULL;
+
+ udev = udev_new();
+ if (!udev)
+ return -ENOMEM;
+
+ ctrl = udev_ctrl_new(udev);
+ if (!ctrl)
+ return log_error_errno(EINVAL, "error initializing udev control socket");
+
+ r = udev_ctrl_enable_receiving(ctrl);
+ if (r < 0)
+ return log_error_errno(EINVAL, "error binding udev control socket");
+
+ fd = udev_ctrl_get_fd(ctrl);
+ if (fd < 0)
+ return log_error_errno(EIO, "could not get ctrl fd");
+
+ ctrl_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+ if (ctrl_fd < 0)
+ return log_error_errno(errno, "could not dup ctrl fd: %m");
+ }
+
+ if (netlink_fd < 0) {
+ _cleanup_udev_monitor_unref_ struct udev_monitor *monitor = NULL;
+
+ if (!udev) {
+ udev = udev_new();
+ if (!udev)
+ return -ENOMEM;
+ }
+
+ monitor = udev_monitor_new_from_netlink(udev, "kernel");
+ if (!monitor)
+ return log_error_errno(EINVAL, "error initializing netlink socket");
+
+ (void) udev_monitor_set_receive_buffer_size(monitor, 128 * 1024 * 1024);
+
+ r = udev_monitor_enable_receiving(monitor);
+ if (r < 0)
+ return log_error_errno(EINVAL, "error binding netlink socket");
+
+ fd = udev_monitor_get_fd(monitor);
+ if (fd < 0)
+ return log_error_errno(netlink_fd, "could not get uevent fd: %m");
+
+ netlink_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+ if (ctrl_fd < 0)
+ return log_error_errno(errno, "could not dup netlink fd: %m");
+ }
+
+ *rctrl = ctrl_fd;
+ *rnetlink = netlink_fd;
+
+ return 0;
+}
+
+/*
+ * read the kernel command line, in case we need to get into debug mode
+ * udev.log-priority=<level> syslog priority
+ * udev.children-max=<number of workers> events are fully serialized if set to 1
+ * udev.exec-delay=<number of seconds> delay execution of every executed program
+ * udev.event-timeout=<number of seconds> seconds to wait before terminating an event
+ */
+static int parse_proc_cmdline_item(const char *key, const char *value) {
+ const char *full_key = key;
+ int r;
+
+ assert(key);
+
+ if (!value)
+ return 0;
+
+ if (startswith(key, "rd."))
+ key += strlen("rd.");
+
+ if (startswith(key, "udev."))
+ key += strlen("udev.");
+ else
+ return 0;
+
+ if (streq(key, "log-priority")) {
+ int prio;
+
+ prio = util_log_priority(value);
+ if (prio < 0)
+ goto invalid;
+ log_set_max_level(prio);
+ } else if (streq(key, "children-max")) {
+ r = safe_atou(value, &arg_children_max);
+ if (r < 0)
+ goto invalid;
+ } else if (streq(key, "exec-delay")) {
+ r = safe_atoi(value, &arg_exec_delay);
+ if (r < 0)
+ goto invalid;
+ } else if (streq(key, "event-timeout")) {
+ r = safe_atou64(value, &arg_event_timeout_usec);
+ if (r < 0)
+ goto invalid;
+ arg_event_timeout_usec *= USEC_PER_SEC;
+ arg_event_timeout_warn_usec = (arg_event_timeout_usec / 3) ? : 1;
+ }
+
+ return 0;
+invalid:
+ log_warning("invalid %s ignored: %s", full_key, value);
+ return 0;
+}
+
+static void help(void) {
+ printf("%s [OPTIONS...]\n\n"
+ "Manages devices.\n\n"
+ " -h --help Print this message\n"
+ " --version Print version of the program\n"
+ " --daemon Detach and run in the background\n"
+ " --debug Enable debug output\n"
+ " --children-max=INT Set maximum number of workers\n"
+ " --exec-delay=SECONDS Seconds to wait before executing RUN=\n"
+ " --event-timeout=SECONDS Seconds to wait before terminating an event\n"
+ " --resolve-names=early|late|never\n"
+ " When to resolve users and groups\n"
+ , program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ static const struct option options[] = {
+ { "daemon", no_argument, NULL, 'd' },
+ { "debug", no_argument, NULL, 'D' },
+ { "children-max", required_argument, NULL, 'c' },
+ { "exec-delay", required_argument, NULL, 'e' },
+ { "event-timeout", required_argument, NULL, 't' },
+ { "resolve-names", required_argument, NULL, 'N' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ {}
+ };
+
+ int c;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "c:de:Dt:N:hV", options, NULL)) >= 0) {
+ int r;
+
+ switch (c) {
+
+ case 'd':
+ arg_daemonize = true;
+ break;
+ case 'c':
+ r = safe_atou(optarg, &arg_children_max);
+ if (r < 0)
+ log_warning("Invalid --children-max ignored: %s", optarg);
+ break;
+ case 'e':
+ r = safe_atoi(optarg, &arg_exec_delay);
+ if (r < 0)
+ log_warning("Invalid --exec-delay ignored: %s", optarg);
+ break;
+ case 't':
+ r = safe_atou64(optarg, &arg_event_timeout_usec);
+ if (r < 0)
+ log_warning("Invalid --event-timeout ignored: %s", optarg);
+ else {
+ arg_event_timeout_usec *= USEC_PER_SEC;
+ arg_event_timeout_warn_usec = (arg_event_timeout_usec / 3) ? : 1;
+ }
+ break;
+ case 'D':
+ arg_debug = true;
+ break;
+ case 'N':
+ if (streq(optarg, "early")) {
+ arg_resolve_names = 1;
+ } else if (streq(optarg, "late")) {
+ arg_resolve_names = 0;
+ } else if (streq(optarg, "never")) {
+ arg_resolve_names = -1;
+ } else {
+ log_error("resolve-names must be early, late or never");
+ return 0;
+ }
+ break;
+ case 'h':
+ help();
+ return 0;
+ case 'V':
+ printf("%s\n", VERSION);
+ return 0;
+ case '?':
+ return -EINVAL;
+ default:
+ assert_not_reached("Unhandled option");
+
+ }
+ }
+
+ return 1;
+}
+
+static int manager_new(Manager **ret, int fd_ctrl, int fd_uevent, const char *cgroup) {
+ _cleanup_(manager_freep) Manager *manager = NULL;
+ int r, fd_worker, one = 1;
+
+ assert(ret);
+ assert(fd_ctrl >= 0);
+ assert(fd_uevent >= 0);
+
+ manager = new0(Manager, 1);
+ if (!manager)
+ return log_oom();
+
+ manager->fd_inotify = -1;
+ manager->worker_watch[WRITE_END] = -1;
+ manager->worker_watch[READ_END] = -1;
+
+ manager->udev = udev_new();
+ if (!manager->udev)
+ return log_error_errno(errno, "could not allocate udev context: %m");
+
+ udev_builtin_init(manager->udev);
+
+ manager->rules = udev_rules_new(manager->udev, arg_resolve_names);
+ if (!manager->rules)
+ return log_error_errno(ENOMEM, "error reading rules");
+
+ udev_list_node_init(&manager->events);
+ udev_list_init(manager->udev, &manager->properties, true);
+
+ manager->cgroup = cgroup;
+
+ manager->ctrl = udev_ctrl_new_from_fd(manager->udev, fd_ctrl);
+ if (!manager->ctrl)
+ return log_error_errno(EINVAL, "error taking over udev control socket");
+
+ manager->monitor = udev_monitor_new_from_netlink_fd(manager->udev, "kernel", fd_uevent);
+ if (!manager->monitor)
+ return log_error_errno(EINVAL, "error taking over netlink socket");
+
+ /* unnamed socket from workers to the main daemon */
+ r = socketpair(AF_LOCAL, SOCK_DGRAM|SOCK_CLOEXEC, 0, manager->worker_watch);
+ if (r < 0)
+ return log_error_errno(errno, "error creating socketpair: %m");
+
+ fd_worker = manager->worker_watch[READ_END];
+
+ r = setsockopt(fd_worker, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
+ if (r < 0)
+ return log_error_errno(errno, "could not enable SO_PASSCRED: %m");
+
+ manager->fd_inotify = udev_watch_init(manager->udev);
+ if (manager->fd_inotify < 0)
+ return log_error_errno(ENOMEM, "error initializing inotify");
+
+ udev_watch_restore(manager->udev);
+
+ /* block and listen to all signals on signalfd */
+ assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGHUP, SIGCHLD, -1) >= 0);
+
+ r = sd_event_default(&manager->event);
+ if (r < 0)
+ return log_error_errno(r, "could not allocate event loop: %m");
+
+ r = sd_event_add_signal(manager->event, NULL, SIGINT, on_sigterm, manager);
+ if (r < 0)
+ return log_error_errno(r, "error creating sigint event source: %m");
+
+ r = sd_event_add_signal(manager->event, NULL, SIGTERM, on_sigterm, manager);
+ if (r < 0)
+ return log_error_errno(r, "error creating sigterm event source: %m");
+
+ r = sd_event_add_signal(manager->event, NULL, SIGHUP, on_sighup, manager);
+ if (r < 0)
+ return log_error_errno(r, "error creating sighup event source: %m");
+
+ r = sd_event_add_signal(manager->event, NULL, SIGCHLD, on_sigchld, manager);
+ if (r < 0)
+ return log_error_errno(r, "error creating sigchld event source: %m");
+
+ r = sd_event_set_watchdog(manager->event, true);
+ if (r < 0)
+ return log_error_errno(r, "error creating watchdog event source: %m");
+
+ r = sd_event_add_io(manager->event, &manager->ctrl_event, fd_ctrl, EPOLLIN, on_ctrl_msg, manager);
+ if (r < 0)
+ return log_error_errno(r, "error creating ctrl event source: %m");
+
+ /* This needs to be after the inotify and uevent handling, to make sure
+ * that the ping is send back after fully processing the pending uevents
+ * (including the synthetic ones we may create due to inotify events).
+ */
+ r = sd_event_source_set_priority(manager->ctrl_event, SD_EVENT_PRIORITY_IDLE);
+ if (r < 0)
+ return log_error_errno(r, "cold not set IDLE event priority for ctrl event source: %m");
+
+ r = sd_event_add_io(manager->event, &manager->inotify_event, manager->fd_inotify, EPOLLIN, on_inotify, manager);
+ if (r < 0)
+ return log_error_errno(r, "error creating inotify event source: %m");
+
+ r = sd_event_add_io(manager->event, &manager->uevent_event, fd_uevent, EPOLLIN, on_uevent, manager);
+ if (r < 0)
+ return log_error_errno(r, "error creating uevent event source: %m");
+
+ r = sd_event_add_io(manager->event, NULL, fd_worker, EPOLLIN, on_worker, manager);
+ if (r < 0)
+ return log_error_errno(r, "error creating worker event source: %m");
+
+ r = sd_event_add_post(manager->event, NULL, on_post, manager);
+ if (r < 0)
+ return log_error_errno(r, "error creating post event source: %m");
+
+ *ret = manager;
+ manager = NULL;
+
+ return 0;
+}
+
+static int run(int fd_ctrl, int fd_uevent, const char *cgroup) {
+ _cleanup_(manager_freep) Manager *manager = NULL;
+ int r;
+
+ r = manager_new(&manager, fd_ctrl, fd_uevent, cgroup);
+ if (r < 0) {
+ r = log_error_errno(r, "failed to allocate manager object: %m");
+ goto exit;
+ }
+
+ r = udev_rules_apply_static_dev_perms(manager->rules);
+ if (r < 0)
+ log_error_errno(r, "failed to apply permissions on static device nodes: %m");
+
+ (void) sd_notify(false,
+ "READY=1\n"
+ "STATUS=Processing...");
+
+ r = sd_event_loop(manager->event);
+ if (r < 0) {
+ log_error_errno(r, "event loop failed: %m");
+ goto exit;
+ }
+
+ sd_event_get_exit_code(manager->event, &r);
+
+exit:
+ sd_notify(false,
+ "STOPPING=1\n"
+ "STATUS=Shutting down...");
+ if (manager)
+ udev_ctrl_cleanup(manager->ctrl);
+ return r;
+}
+
+int main(int argc, char *argv[]) {
+ _cleanup_free_ char *cgroup = NULL;
+ int fd_ctrl = -1, fd_uevent = -1;
+ int r;
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ goto exit;
+
+ r = parse_proc_cmdline(parse_proc_cmdline_item);
+ if (r < 0)
+ log_warning_errno(r, "failed to parse kernel command line, ignoring: %m");
+
+ if (arg_debug) {
+ log_set_target(LOG_TARGET_CONSOLE);
+ log_set_max_level(LOG_DEBUG);
+ }
+
+ if (getuid() != 0) {
+ r = log_error_errno(EPERM, "root privileges required");
+ goto exit;
+ }
+
+ if (arg_children_max == 0) {
+ cpu_set_t cpu_set;
+
+ arg_children_max = 8;
+
+ if (sched_getaffinity(0, sizeof(cpu_set), &cpu_set) == 0)
+ arg_children_max += CPU_COUNT(&cpu_set) * 2;
+
+ log_debug("set children_max to %u", arg_children_max);
+ }
+
+ /* set umask before creating any file/directory */
+ r = chdir("/");
+ if (r < 0) {
+ r = log_error_errno(errno, "could not change dir to /: %m");
+ goto exit;
+ }
+
+ umask(022);
+
+ r = mac_selinux_init();
+ if (r < 0) {
+ log_error_errno(r, "could not initialize labelling: %m");
+ goto exit;
+ }
+
+ r = mkdir("/run/udev", 0755);
+ if (r < 0 && errno != EEXIST) {
+ r = log_error_errno(errno, "could not create /run/udev: %m");
+ goto exit;
+ }
+
+ dev_setup(NULL, UID_INVALID, GID_INVALID);
+
+ if (getppid() == 1) {
+ /* get our own cgroup, we regularly kill everything udev has left behind
+ we only do this on systemd systems, and only if we are directly spawned
+ by PID1. otherwise we are not guaranteed to have a dedicated cgroup */
+ r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &cgroup);
+ if (r < 0) {
+ if (r == -ENOENT || r == -ENOMEDIUM)
+ log_debug_errno(r, "did not find dedicated cgroup: %m");
+ else
+ log_warning_errno(r, "failed to get cgroup: %m");
+ }
+ }
+
+ r = listen_fds(&fd_ctrl, &fd_uevent);
+ if (r < 0) {
+ r = log_error_errno(r, "could not listen on fds: %m");
+ goto exit;
+ }
+
+ if (arg_daemonize) {
+ pid_t pid;
+
+ log_info("starting version " VERSION);
+
+ /* connect /dev/null to stdin, stdout, stderr */
+ if (log_get_max_level() < LOG_DEBUG)
+ (void) make_null_stdio();
+
+ pid = fork();
+ switch (pid) {
+ case 0:
+ break;
+ case -1:
+ r = log_error_errno(errno, "fork of daemon failed: %m");
+ goto exit;
+ default:
+ mac_selinux_finish();
+ log_close();
+ _exit(EXIT_SUCCESS);
+ }
+
+ setsid();
+
+ write_string_file("/proc/self/oom_score_adj", "-1000", 0);
+ }
+
+ r = run(fd_ctrl, fd_uevent, cgroup);
+
+exit:
+ mac_selinux_finish();
+ log_close();
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-udev/udevadm/udevadm-control.c b/src/grp-udev/udevadm/udevadm-control.c
new file mode 100644
index 0000000000..989decbe95
--- /dev/null
+++ b/src/grp-udev/udevadm/udevadm-control.c
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2005-2011 Kay Sievers <kay@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "udev-util.h"
+#include "udev.h"
+
+static void print_help(void) {
+ printf("%s control COMMAND\n\n"
+ "Control the udev daemon.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " -e --exit Instruct the daemon to cleanup and exit\n"
+ " -l --log-priority=LEVEL Set the udev log level for the daemon\n"
+ " -s --stop-exec-queue Do not execute events, queue only\n"
+ " -S --start-exec-queue Execute events, flush queue\n"
+ " -R --reload Reload rules and databases\n"
+ " -p --property=KEY=VALUE Set a global property for all events\n"
+ " -m --children-max=N Maximum number of children\n"
+ " --timeout=SECONDS Maximum time to block for a reply\n"
+ , program_invocation_short_name);
+}
+
+static int adm_control(struct udev *udev, int argc, char *argv[]) {
+ _cleanup_udev_ctrl_unref_ struct udev_ctrl *uctrl = NULL;
+ int timeout = 60;
+ int rc = 1, c;
+
+ static const struct option options[] = {
+ { "exit", no_argument, NULL, 'e' },
+ { "log-priority", required_argument, NULL, 'l' },
+ { "stop-exec-queue", no_argument, NULL, 's' },
+ { "start-exec-queue", no_argument, NULL, 'S' },
+ { "reload", no_argument, NULL, 'R' },
+ { "reload-rules", no_argument, NULL, 'R' }, /* alias for -R */
+ { "property", required_argument, NULL, 'p' },
+ { "env", required_argument, NULL, 'p' }, /* alias for -p */
+ { "children-max", required_argument, NULL, 'm' },
+ { "timeout", required_argument, NULL, 't' },
+ { "help", no_argument, NULL, 'h' },
+ {}
+ };
+
+ if (getuid() != 0) {
+ fprintf(stderr, "root privileges required\n");
+ return 1;
+ }
+
+ uctrl = udev_ctrl_new(udev);
+ if (uctrl == NULL)
+ return 2;
+
+ while ((c = getopt_long(argc, argv, "el:sSRp:m:h", options, NULL)) >= 0)
+ switch (c) {
+ case 'e':
+ if (udev_ctrl_send_exit(uctrl, timeout) < 0)
+ rc = 2;
+ else
+ rc = 0;
+ break;
+ case 'l': {
+ int i;
+
+ i = util_log_priority(optarg);
+ if (i < 0) {
+ fprintf(stderr, "invalid number '%s'\n", optarg);
+ return rc;
+ }
+ if (udev_ctrl_send_set_log_level(uctrl, util_log_priority(optarg), timeout) < 0)
+ rc = 2;
+ else
+ rc = 0;
+ break;
+ }
+ case 's':
+ if (udev_ctrl_send_stop_exec_queue(uctrl, timeout) < 0)
+ rc = 2;
+ else
+ rc = 0;
+ break;
+ case 'S':
+ if (udev_ctrl_send_start_exec_queue(uctrl, timeout) < 0)
+ rc = 2;
+ else
+ rc = 0;
+ break;
+ case 'R':
+ if (udev_ctrl_send_reload(uctrl, timeout) < 0)
+ rc = 2;
+ else
+ rc = 0;
+ break;
+ case 'p':
+ if (strchr(optarg, '=') == NULL) {
+ fprintf(stderr, "expect <KEY>=<value> instead of '%s'\n", optarg);
+ return rc;
+ }
+ if (udev_ctrl_send_set_env(uctrl, optarg, timeout) < 0)
+ rc = 2;
+ else
+ rc = 0;
+ break;
+ case 'm': {
+ char *endp;
+ int i;
+
+ i = strtoul(optarg, &endp, 0);
+ if (endp[0] != '\0' || i < 1) {
+ fprintf(stderr, "invalid number '%s'\n", optarg);
+ return rc;
+ }
+ if (udev_ctrl_send_set_children_max(uctrl, i, timeout) < 0)
+ rc = 2;
+ else
+ rc = 0;
+ break;
+ }
+ case 't': {
+ int seconds;
+
+ seconds = atoi(optarg);
+ if (seconds >= 0)
+ timeout = seconds;
+ else
+ fprintf(stderr, "invalid timeout value\n");
+ break;
+ }
+ case 'h':
+ print_help();
+ rc = 0;
+ break;
+ }
+
+ if (optind < argc)
+ fprintf(stderr, "Extraneous argument: %s\n", argv[optind]);
+ else if (optind == 1)
+ fprintf(stderr, "Option missing\n");
+ return rc;
+}
+
+const struct udevadm_cmd udevadm_control = {
+ .name = "control",
+ .cmd = adm_control,
+ .help = "Control the udev daemon",
+};
diff --git a/src/grp-udev/udevadm/udevadm-hwdb.c b/src/grp-udev/udevadm/udevadm-hwdb.c
new file mode 100644
index 0000000000..948ad0f5a5
--- /dev/null
+++ b/src/grp-udev/udevadm/udevadm-hwdb.c
@@ -0,0 +1,692 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Kay Sievers <kay@vrfy.org>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <ctype.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc-util.h"
+#include "conf-files.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "hwdb-internal.h"
+#include "hwdb-util.h"
+#include "strbuf.h"
+#include "string-util.h"
+#include "udev.h"
+#include "util.h"
+
+/*
+ * Generic udev properties, key/value database based on modalias strings.
+ * Uses a Patricia/radix trie to index all matches for efficient lookup.
+ */
+
+static const char * const conf_file_dirs[] = {
+ "/etc/udev/hwdb.d",
+ UDEVLIBEXECDIR "/hwdb.d",
+ NULL
+};
+
+/* in-memory trie objects */
+struct trie {
+ struct trie_node *root;
+ struct strbuf *strings;
+
+ size_t nodes_count;
+ size_t children_count;
+ size_t values_count;
+};
+
+struct trie_node {
+ /* prefix, common part for all children of this node */
+ size_t prefix_off;
+
+ /* sorted array of pointers to children nodes */
+ struct trie_child_entry *children;
+ uint8_t children_count;
+
+ /* sorted array of key/value pairs */
+ struct trie_value_entry *values;
+ size_t values_count;
+};
+
+/* children array item with char (0-255) index */
+struct trie_child_entry {
+ uint8_t c;
+ struct trie_node *child;
+};
+
+/* value array item with key/value pairs */
+struct trie_value_entry {
+ size_t key_off;
+ size_t value_off;
+};
+
+static int trie_children_cmp(const void *v1, const void *v2) {
+ const struct trie_child_entry *n1 = v1;
+ const struct trie_child_entry *n2 = v2;
+
+ return n1->c - n2->c;
+}
+
+static int node_add_child(struct trie *trie, struct trie_node *node, struct trie_node *node_child, uint8_t c) {
+ struct trie_child_entry *child;
+
+ /* extend array, add new entry, sort for bisection */
+ child = realloc(node->children, (node->children_count + 1) * sizeof(struct trie_child_entry));
+ if (!child)
+ return -ENOMEM;
+
+ node->children = child;
+ trie->children_count++;
+ node->children[node->children_count].c = c;
+ node->children[node->children_count].child = node_child;
+ node->children_count++;
+ qsort(node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp);
+ trie->nodes_count++;
+
+ return 0;
+}
+
+static struct trie_node *node_lookup(const struct trie_node *node, uint8_t c) {
+ struct trie_child_entry *child;
+ struct trie_child_entry search;
+
+ search.c = c;
+ child = bsearch(&search, node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp);
+ if (child)
+ return child->child;
+ return NULL;
+}
+
+static void trie_node_cleanup(struct trie_node *node) {
+ size_t i;
+
+ for (i = 0; i < node->children_count; i++)
+ trie_node_cleanup(node->children[i].child);
+ free(node->children);
+ free(node->values);
+ free(node);
+}
+
+static int trie_values_cmp(const void *v1, const void *v2, void *arg) {
+ const struct trie_value_entry *val1 = v1;
+ const struct trie_value_entry *val2 = v2;
+ struct trie *trie = arg;
+
+ return strcmp(trie->strings->buf + val1->key_off,
+ trie->strings->buf + val2->key_off);
+}
+
+static int trie_node_add_value(struct trie *trie, struct trie_node *node,
+ const char *key, const char *value) {
+ ssize_t k, v;
+ struct trie_value_entry *val;
+
+ k = strbuf_add_string(trie->strings, key, strlen(key));
+ if (k < 0)
+ return k;
+ v = strbuf_add_string(trie->strings, value, strlen(value));
+ if (v < 0)
+ return v;
+
+ if (node->values_count) {
+ struct trie_value_entry search = {
+ .key_off = k,
+ .value_off = v,
+ };
+
+ val = xbsearch_r(&search, node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie);
+ if (val) {
+ /* replace existing earlier key with new value */
+ val->value_off = v;
+ return 0;
+ }
+ }
+
+ /* extend array, add new entry, sort for bisection */
+ val = realloc(node->values, (node->values_count + 1) * sizeof(struct trie_value_entry));
+ if (!val)
+ return -ENOMEM;
+ trie->values_count++;
+ node->values = val;
+ node->values[node->values_count].key_off = k;
+ node->values[node->values_count].value_off = v;
+ node->values_count++;
+ qsort_r(node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie);
+ return 0;
+}
+
+static int trie_insert(struct trie *trie, struct trie_node *node, const char *search,
+ const char *key, const char *value) {
+ size_t i = 0;
+ int err = 0;
+
+ for (;;) {
+ size_t p;
+ uint8_t c;
+ struct trie_node *child;
+
+ for (p = 0; (c = trie->strings->buf[node->prefix_off + p]); p++) {
+ _cleanup_free_ char *s = NULL;
+ ssize_t off;
+ _cleanup_free_ struct trie_node *new_child = NULL;
+
+ if (c == search[i + p])
+ continue;
+
+ /* split node */
+ new_child = new0(struct trie_node, 1);
+ if (!new_child)
+ return -ENOMEM;
+
+ /* move values from parent to child */
+ new_child->prefix_off = node->prefix_off + p+1;
+ new_child->children = node->children;
+ new_child->children_count = node->children_count;
+ new_child->values = node->values;
+ new_child->values_count = node->values_count;
+
+ /* update parent; use strdup() because the source gets realloc()d */
+ s = strndup(trie->strings->buf + node->prefix_off, p);
+ if (!s)
+ return -ENOMEM;
+
+ off = strbuf_add_string(trie->strings, s, p);
+ if (off < 0)
+ return off;
+
+ node->prefix_off = off;
+ node->children = NULL;
+ node->children_count = 0;
+ node->values = NULL;
+ node->values_count = 0;
+ err = node_add_child(trie, node, new_child, c);
+ if (err)
+ return err;
+
+ new_child = NULL; /* avoid cleanup */
+ break;
+ }
+ i += p;
+
+ c = search[i];
+ if (c == '\0')
+ return trie_node_add_value(trie, node, key, value);
+
+ child = node_lookup(node, c);
+ if (!child) {
+ ssize_t off;
+
+ /* new child */
+ child = new0(struct trie_node, 1);
+ if (!child)
+ return -ENOMEM;
+
+ off = strbuf_add_string(trie->strings, search + i+1, strlen(search + i+1));
+ if (off < 0) {
+ free(child);
+ return off;
+ }
+
+ child->prefix_off = off;
+ err = node_add_child(trie, node, child, c);
+ if (err) {
+ free(child);
+ return err;
+ }
+
+ return trie_node_add_value(trie, child, key, value);
+ }
+
+ node = child;
+ i++;
+ }
+}
+
+struct trie_f {
+ FILE *f;
+ struct trie *trie;
+ uint64_t strings_off;
+
+ uint64_t nodes_count;
+ uint64_t children_count;
+ uint64_t values_count;
+};
+
+/* calculate the storage space for the nodes, children arrays, value arrays */
+static void trie_store_nodes_size(struct trie_f *trie, struct trie_node *node) {
+ uint64_t i;
+
+ for (i = 0; i < node->children_count; i++)
+ trie_store_nodes_size(trie, node->children[i].child);
+
+ trie->strings_off += sizeof(struct trie_node_f);
+ for (i = 0; i < node->children_count; i++)
+ trie->strings_off += sizeof(struct trie_child_entry_f);
+ for (i = 0; i < node->values_count; i++)
+ trie->strings_off += sizeof(struct trie_value_entry_f);
+}
+
+static int64_t trie_store_nodes(struct trie_f *trie, struct trie_node *node) {
+ uint64_t i;
+ struct trie_node_f n = {
+ .prefix_off = htole64(trie->strings_off + node->prefix_off),
+ .children_count = node->children_count,
+ .values_count = htole64(node->values_count),
+ };
+ struct trie_child_entry_f *children = NULL;
+ int64_t node_off;
+
+ if (node->children_count) {
+ children = new0(struct trie_child_entry_f, node->children_count);
+ if (!children)
+ return -ENOMEM;
+ }
+
+ /* post-order recursion */
+ for (i = 0; i < node->children_count; i++) {
+ int64_t child_off;
+
+ child_off = trie_store_nodes(trie, node->children[i].child);
+ if (child_off < 0) {
+ free(children);
+ return child_off;
+ }
+ children[i].c = node->children[i].c;
+ children[i].child_off = htole64(child_off);
+ }
+
+ /* write node */
+ node_off = ftello(trie->f);
+ fwrite(&n, sizeof(struct trie_node_f), 1, trie->f);
+ trie->nodes_count++;
+
+ /* append children array */
+ if (node->children_count) {
+ fwrite(children, sizeof(struct trie_child_entry_f), node->children_count, trie->f);
+ trie->children_count += node->children_count;
+ free(children);
+ }
+
+ /* append values array */
+ for (i = 0; i < node->values_count; i++) {
+ struct trie_value_entry_f v = {
+ .key_off = htole64(trie->strings_off + node->values[i].key_off),
+ .value_off = htole64(trie->strings_off + node->values[i].value_off),
+ };
+
+ fwrite(&v, sizeof(struct trie_value_entry_f), 1, trie->f);
+ trie->values_count++;
+ }
+
+ return node_off;
+}
+
+static int trie_store(struct trie *trie, const char *filename) {
+ struct trie_f t = {
+ .trie = trie,
+ };
+ _cleanup_free_ char *filename_tmp = NULL;
+ int64_t pos;
+ int64_t root_off;
+ int64_t size;
+ struct trie_header_f h = {
+ .signature = HWDB_SIG,
+ .tool_version = htole64(atoi(VERSION)),
+ .header_size = htole64(sizeof(struct trie_header_f)),
+ .node_size = htole64(sizeof(struct trie_node_f)),
+ .child_entry_size = htole64(sizeof(struct trie_child_entry_f)),
+ .value_entry_size = htole64(sizeof(struct trie_value_entry_f)),
+ };
+ int err;
+
+ /* calculate size of header, nodes, children entries, value entries */
+ t.strings_off = sizeof(struct trie_header_f);
+ trie_store_nodes_size(&t, trie->root);
+
+ err = fopen_temporary(filename , &t.f, &filename_tmp);
+ if (err < 0)
+ return err;
+ fchmod(fileno(t.f), 0444);
+
+ /* write nodes */
+ err = fseeko(t.f, sizeof(struct trie_header_f), SEEK_SET);
+ if (err < 0) {
+ fclose(t.f);
+ unlink_noerrno(filename_tmp);
+ return -errno;
+ }
+ root_off = trie_store_nodes(&t, trie->root);
+ h.nodes_root_off = htole64(root_off);
+ pos = ftello(t.f);
+ h.nodes_len = htole64(pos - sizeof(struct trie_header_f));
+
+ /* write string buffer */
+ fwrite(trie->strings->buf, trie->strings->len, 1, t.f);
+ h.strings_len = htole64(trie->strings->len);
+
+ /* write header */
+ size = ftello(t.f);
+ h.file_size = htole64(size);
+ err = fseeko(t.f, 0, SEEK_SET);
+ if (err < 0) {
+ fclose(t.f);
+ unlink_noerrno(filename_tmp);
+ return -errno;
+ }
+ fwrite(&h, sizeof(struct trie_header_f), 1, t.f);
+ err = ferror(t.f);
+ if (err)
+ err = -errno;
+ fclose(t.f);
+ if (err < 0 || rename(filename_tmp, filename) < 0) {
+ unlink_noerrno(filename_tmp);
+ return err < 0 ? err : -errno;
+ }
+
+ log_debug("=== trie on-disk ===");
+ log_debug("size: %8"PRIi64" bytes", size);
+ log_debug("header: %8zu bytes", sizeof(struct trie_header_f));
+ log_debug("nodes: %8"PRIu64" bytes (%8"PRIu64")",
+ t.nodes_count * sizeof(struct trie_node_f), t.nodes_count);
+ log_debug("child pointers: %8"PRIu64" bytes (%8"PRIu64")",
+ t.children_count * sizeof(struct trie_child_entry_f), t.children_count);
+ log_debug("value pointers: %8"PRIu64" bytes (%8"PRIu64")",
+ t.values_count * sizeof(struct trie_value_entry_f), t.values_count);
+ log_debug("string store: %8zu bytes", trie->strings->len);
+ log_debug("strings start: %8"PRIu64, t.strings_off);
+
+ return 0;
+}
+
+static int insert_data(struct trie *trie, struct udev_list *match_list,
+ char *line, const char *filename) {
+ char *value;
+ struct udev_list_entry *entry;
+
+ value = strchr(line, '=');
+ if (!value) {
+ log_error("Error, key/value pair expected but got '%s' in '%s':", line, filename);
+ return -EINVAL;
+ }
+
+ value[0] = '\0';
+ value++;
+
+ /* libudev requires properties to start with a space */
+ while (isblank(line[0]) && isblank(line[1]))
+ line++;
+
+ if (line[0] == '\0' || value[0] == '\0') {
+ log_error("Error, empty key or value '%s' in '%s':", line, filename);
+ return -EINVAL;
+ }
+
+ udev_list_entry_foreach(entry, udev_list_get_entry(match_list))
+ trie_insert(trie, trie->root, udev_list_entry_get_name(entry), line, value);
+
+ return 0;
+}
+
+static int import_file(struct udev *udev, struct trie *trie, const char *filename) {
+ enum {
+ HW_MATCH,
+ HW_DATA,
+ HW_NONE,
+ } state = HW_NONE;
+ FILE *f;
+ char line[LINE_MAX];
+ struct udev_list match_list;
+
+ udev_list_init(udev, &match_list, false);
+
+ f = fopen(filename, "re");
+ if (f == NULL)
+ return -errno;
+
+ while (fgets(line, sizeof(line), f)) {
+ size_t len;
+ char *pos;
+
+ /* comment line */
+ if (line[0] == '#')
+ continue;
+
+ /* strip trailing comment */
+ pos = strchr(line, '#');
+ if (pos)
+ pos[0] = '\0';
+
+ /* strip trailing whitespace */
+ len = strlen(line);
+ while (len > 0 && isspace(line[len-1]))
+ len--;
+ line[len] = '\0';
+
+ switch (state) {
+ case HW_NONE:
+ if (len == 0)
+ break;
+
+ if (line[0] == ' ') {
+ log_error("Error, MATCH expected but got '%s' in '%s':", line, filename);
+ break;
+ }
+
+ /* start of record, first match */
+ state = HW_MATCH;
+ udev_list_entry_add(&match_list, line, NULL);
+ break;
+
+ case HW_MATCH:
+ if (len == 0) {
+ log_error("Error, DATA expected but got empty line in '%s':", filename);
+ state = HW_NONE;
+ udev_list_cleanup(&match_list);
+ break;
+ }
+
+ /* another match */
+ if (line[0] != ' ') {
+ udev_list_entry_add(&match_list, line, NULL);
+ break;
+ }
+
+ /* first data */
+ state = HW_DATA;
+ insert_data(trie, &match_list, line, filename);
+ break;
+
+ case HW_DATA:
+ /* end of record */
+ if (len == 0) {
+ state = HW_NONE;
+ udev_list_cleanup(&match_list);
+ break;
+ }
+
+ if (line[0] != ' ') {
+ log_error("Error, DATA expected but got '%s' in '%s':", line, filename);
+ state = HW_NONE;
+ udev_list_cleanup(&match_list);
+ break;
+ }
+
+ insert_data(trie, &match_list, line, filename);
+ break;
+ };
+ }
+
+ fclose(f);
+ udev_list_cleanup(&match_list);
+ return 0;
+}
+
+static void help(void) {
+ printf("Usage: udevadm hwdb OPTIONS\n"
+ " -u,--update update the hardware database\n"
+ " --usr generate in " UDEVLIBEXECDIR " instead of /etc/udev\n"
+ " -t,--test=MODALIAS query database and print result\n"
+ " -r,--root=PATH alternative root path in the filesystem\n"
+ " -h,--help\n\n");
+}
+
+static int adm_hwdb(struct udev *udev, int argc, char *argv[]) {
+ enum {
+ ARG_USR = 0x100,
+ };
+
+ static const struct option options[] = {
+ { "update", no_argument, NULL, 'u' },
+ { "usr", no_argument, NULL, ARG_USR },
+ { "test", required_argument, NULL, 't' },
+ { "root", required_argument, NULL, 'r' },
+ { "help", no_argument, NULL, 'h' },
+ {}
+ };
+ const char *test = NULL;
+ const char *root = "";
+ const char *hwdb_bin_dir = "/etc/udev";
+ bool update = false;
+ struct trie *trie = NULL;
+ int err, c;
+ int rc = EXIT_SUCCESS;
+
+ while ((c = getopt_long(argc, argv, "ut:r:h", options, NULL)) >= 0)
+ switch(c) {
+ case 'u':
+ update = true;
+ break;
+ case ARG_USR:
+ hwdb_bin_dir = UDEVLIBEXECDIR;
+ break;
+ case 't':
+ test = optarg;
+ break;
+ case 'r':
+ root = optarg;
+ break;
+ case 'h':
+ help();
+ return EXIT_SUCCESS;
+ case '?':
+ return EXIT_FAILURE;
+ default:
+ assert_not_reached("Unknown option");
+ }
+
+ if (!update && !test) {
+ log_error("Either --update or --test must be used");
+ return EXIT_FAILURE;
+ }
+
+ if (update) {
+ char **files, **f;
+ _cleanup_free_ char *hwdb_bin = NULL;
+
+ trie = new0(struct trie, 1);
+ if (!trie) {
+ rc = EXIT_FAILURE;
+ goto out;
+ }
+
+ /* string store */
+ trie->strings = strbuf_new();
+ if (!trie->strings) {
+ rc = EXIT_FAILURE;
+ goto out;
+ }
+
+ /* index */
+ trie->root = new0(struct trie_node, 1);
+ if (!trie->root) {
+ rc = EXIT_FAILURE;
+ goto out;
+ }
+ trie->nodes_count++;
+
+ err = conf_files_list_strv(&files, ".hwdb", root, conf_file_dirs);
+ if (err < 0) {
+ log_error_errno(err, "failed to enumerate hwdb files: %m");
+ rc = EXIT_FAILURE;
+ goto out;
+ }
+ STRV_FOREACH(f, files) {
+ log_debug("reading file '%s'", *f);
+ import_file(udev, trie, *f);
+ }
+ strv_free(files);
+
+ strbuf_complete(trie->strings);
+
+ log_debug("=== trie in-memory ===");
+ log_debug("nodes: %8zu bytes (%8zu)",
+ trie->nodes_count * sizeof(struct trie_node), trie->nodes_count);
+ log_debug("children arrays: %8zu bytes (%8zu)",
+ trie->children_count * sizeof(struct trie_child_entry), trie->children_count);
+ log_debug("values arrays: %8zu bytes (%8zu)",
+ trie->values_count * sizeof(struct trie_value_entry), trie->values_count);
+ log_debug("strings: %8zu bytes",
+ trie->strings->len);
+ log_debug("strings incoming: %8zu bytes (%8zu)",
+ trie->strings->in_len, trie->strings->in_count);
+ log_debug("strings dedup'ed: %8zu bytes (%8zu)",
+ trie->strings->dedup_len, trie->strings->dedup_count);
+
+ hwdb_bin = strjoin(root, "/", hwdb_bin_dir, "/hwdb.bin", NULL);
+ if (!hwdb_bin) {
+ rc = EXIT_FAILURE;
+ goto out;
+ }
+ mkdir_parents(hwdb_bin, 0755);
+ err = trie_store(trie, hwdb_bin);
+ if (err < 0) {
+ log_error_errno(err, "Failure writing database %s: %m", hwdb_bin);
+ rc = EXIT_FAILURE;
+ }
+ }
+
+ if (test) {
+ _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
+ int r;
+
+ r = sd_hwdb_new(&hwdb);
+ if (r >= 0) {
+ const char *key, *value;
+
+ SD_HWDB_FOREACH_PROPERTY(hwdb, test, key, value)
+ printf("%s=%s\n", key, value);
+ }
+ }
+out:
+ if (trie) {
+ if (trie->root)
+ trie_node_cleanup(trie->root);
+ strbuf_cleanup(trie->strings);
+ free(trie);
+ }
+ return rc;
+}
+
+const struct udevadm_cmd udevadm_hwdb = {
+ .name = "hwdb",
+ .cmd = adm_hwdb,
+};
diff --git a/src/grp-udev/udevadm/udevadm-info.c b/src/grp-udev/udevadm/udevadm-info.c
new file mode 100644
index 0000000000..7182668f23
--- /dev/null
+++ b/src/grp-udev/udevadm/udevadm-info.c
@@ -0,0 +1,494 @@
+/*
+ * Copyright (C) 2004-2009 Kay Sievers <kay@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "fd-util.h"
+#include "string-util.h"
+#include "udev-util.h"
+#include "udev.h"
+#include "udevadm-util.h"
+
+static bool skip_attribute(const char *name) {
+ static const char* const skip[] = {
+ "uevent",
+ "dev",
+ "modalias",
+ "resource",
+ "driver",
+ "subsystem",
+ "module",
+ };
+ unsigned int i;
+
+ for (i = 0; i < ELEMENTSOF(skip); i++)
+ if (streq(name, skip[i]))
+ return true;
+ return false;
+}
+
+static void print_all_attributes(struct udev_device *device, const char *key) {
+ struct udev_list_entry *sysattr;
+
+ udev_list_entry_foreach(sysattr, udev_device_get_sysattr_list_entry(device)) {
+ const char *name;
+ const char *value;
+ size_t len;
+
+ name = udev_list_entry_get_name(sysattr);
+ if (skip_attribute(name))
+ continue;
+
+ value = udev_device_get_sysattr_value(device, name);
+ if (value == NULL)
+ continue;
+
+ /* skip any values that look like a path */
+ if (value[0] == '/')
+ continue;
+
+ /* skip nonprintable attributes */
+ len = strlen(value);
+ while (len > 0 && isprint(value[len-1]))
+ len--;
+ if (len > 0)
+ continue;
+
+ printf(" %s{%s}==\"%s\"\n", key, name, value);
+ }
+ printf("\n");
+}
+
+static int print_device_chain(struct udev_device *device) {
+ struct udev_device *device_parent;
+ const char *str;
+
+ printf("\n"
+ "Udevadm info starts with the device specified by the devpath and then\n"
+ "walks up the chain of parent devices. It prints for every device\n"
+ "found, all possible attributes in the udev rules key format.\n"
+ "A rule to match, can be composed by the attributes of the device\n"
+ "and the attributes from one single parent device.\n"
+ "\n");
+
+ printf(" looking at device '%s':\n", udev_device_get_devpath(device));
+ printf(" KERNEL==\"%s\"\n", udev_device_get_sysname(device));
+ str = udev_device_get_subsystem(device);
+ if (str == NULL)
+ str = "";
+ printf(" SUBSYSTEM==\"%s\"\n", str);
+ str = udev_device_get_driver(device);
+ if (str == NULL)
+ str = "";
+ printf(" DRIVER==\"%s\"\n", str);
+ print_all_attributes(device, "ATTR");
+
+ device_parent = device;
+ do {
+ device_parent = udev_device_get_parent(device_parent);
+ if (device_parent == NULL)
+ break;
+ printf(" looking at parent device '%s':\n", udev_device_get_devpath(device_parent));
+ printf(" KERNELS==\"%s\"\n", udev_device_get_sysname(device_parent));
+ str = udev_device_get_subsystem(device_parent);
+ if (str == NULL)
+ str = "";
+ printf(" SUBSYSTEMS==\"%s\"\n", str);
+ str = udev_device_get_driver(device_parent);
+ if (str == NULL)
+ str = "";
+ printf(" DRIVERS==\"%s\"\n", str);
+ print_all_attributes(device_parent, "ATTRS");
+ } while (device_parent != NULL);
+
+ return 0;
+}
+
+static void print_record(struct udev_device *device) {
+ const char *str;
+ int i;
+ struct udev_list_entry *list_entry;
+
+ printf("P: %s\n", udev_device_get_devpath(device));
+
+ str = udev_device_get_devnode(device);
+ if (str != NULL)
+ printf("N: %s\n", str + strlen("/dev/"));
+
+ i = udev_device_get_devlink_priority(device);
+ if (i != 0)
+ printf("L: %i\n", i);
+
+ udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(device))
+ printf("S: %s\n", udev_list_entry_get_name(list_entry) + strlen("/dev/"));
+
+ udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(device))
+ printf("E: %s=%s\n",
+ udev_list_entry_get_name(list_entry),
+ udev_list_entry_get_value(list_entry));
+ printf("\n");
+}
+
+static int stat_device(const char *name, bool export, const char *prefix) {
+ struct stat statbuf;
+
+ if (stat(name, &statbuf) != 0)
+ return -1;
+
+ if (export) {
+ if (prefix == NULL)
+ prefix = "INFO_";
+ printf("%sMAJOR=%u\n"
+ "%sMINOR=%u\n",
+ prefix, major(statbuf.st_dev),
+ prefix, minor(statbuf.st_dev));
+ } else
+ printf("%u:%u\n", major(statbuf.st_dev), minor(statbuf.st_dev));
+ return 0;
+}
+
+static int export_devices(struct udev *udev) {
+ struct udev_enumerate *udev_enumerate;
+ struct udev_list_entry *list_entry;
+
+ udev_enumerate = udev_enumerate_new(udev);
+ if (udev_enumerate == NULL)
+ return -1;
+ udev_enumerate_scan_devices(udev_enumerate);
+ udev_list_entry_foreach(list_entry, udev_enumerate_get_list_entry(udev_enumerate)) {
+ struct udev_device *device;
+
+ device = udev_device_new_from_syspath(udev, udev_list_entry_get_name(list_entry));
+ if (device != NULL) {
+ print_record(device);
+ udev_device_unref(device);
+ }
+ }
+ udev_enumerate_unref(udev_enumerate);
+ return 0;
+}
+
+static void cleanup_dir(DIR *dir, mode_t mask, int depth) {
+ struct dirent *dent;
+
+ if (depth <= 0)
+ return;
+
+ for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+ struct stat stats;
+
+ if (dent->d_name[0] == '.')
+ continue;
+ if (fstatat(dirfd(dir), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) != 0)
+ continue;
+ if ((stats.st_mode & mask) != 0)
+ continue;
+ if (S_ISDIR(stats.st_mode)) {
+ _cleanup_closedir_ DIR *dir2;
+
+ dir2 = fdopendir(openat(dirfd(dir), dent->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC));
+ if (dir2 != NULL)
+ cleanup_dir(dir2, mask, depth-1);
+
+ (void) unlinkat(dirfd(dir), dent->d_name, AT_REMOVEDIR);
+ } else
+ (void) unlinkat(dirfd(dir), dent->d_name, 0);
+ }
+}
+
+static void cleanup_db(struct udev *udev) {
+ DIR *dir;
+
+ unlink("/run/udev/queue.bin");
+
+ dir = opendir("/run/udev/data");
+ if (dir != NULL) {
+ cleanup_dir(dir, S_ISVTX, 1);
+ closedir(dir);
+ }
+
+ dir = opendir("/run/udev/links");
+ if (dir != NULL) {
+ cleanup_dir(dir, 0, 2);
+ closedir(dir);
+ }
+
+ dir = opendir("/run/udev/tags");
+ if (dir != NULL) {
+ cleanup_dir(dir, 0, 2);
+ closedir(dir);
+ }
+
+ dir = opendir("/run/udev/static_node-tags");
+ if (dir != NULL) {
+ cleanup_dir(dir, 0, 2);
+ closedir(dir);
+ }
+
+ dir = opendir("/run/udev/watch");
+ if (dir != NULL) {
+ cleanup_dir(dir, 0, 1);
+ closedir(dir);
+ }
+}
+
+static void help(void) {
+
+ printf("%s info [OPTIONS] [DEVPATH|FILE]\n\n"
+ "Query sysfs or the udev database.\n\n"
+ " -h --help Print this message\n"
+ " --version Print version of the program\n"
+ " -q --query=TYPE Query device information:\n"
+ " name Name of device node\n"
+ " symlink Pointing to node\n"
+ " path sysfs device path\n"
+ " property The device properties\n"
+ " all All values\n"
+ " -p --path=SYSPATH sysfs device path used for query or attribute walk\n"
+ " -n --name=NAME Node or symlink name used for query or attribute walk\n"
+ " -r --root Prepend dev directory to path names\n"
+ " -a --attribute-walk Print all key matches walking along the chain\n"
+ " of parent devices\n"
+ " -d --device-id-of-file=FILE Print major:minor of device containing this file\n"
+ " -x --export Export key/value pairs\n"
+ " -P --export-prefix Export the key name with a prefix\n"
+ " -e --export-db Export the content of the udev database\n"
+ " -c --cleanup-db Clean up the udev database\n"
+ , program_invocation_short_name);
+}
+
+static int uinfo(struct udev *udev, int argc, char *argv[]) {
+ _cleanup_udev_device_unref_ struct udev_device *device = NULL;
+ bool root = 0;
+ bool export = 0;
+ const char *export_prefix = NULL;
+ char name[UTIL_PATH_SIZE];
+ struct udev_list_entry *list_entry;
+ int c;
+
+ static const struct option options[] = {
+ { "name", required_argument, NULL, 'n' },
+ { "path", required_argument, NULL, 'p' },
+ { "query", required_argument, NULL, 'q' },
+ { "attribute-walk", no_argument, NULL, 'a' },
+ { "cleanup-db", no_argument, NULL, 'c' },
+ { "export-db", no_argument, NULL, 'e' },
+ { "root", no_argument, NULL, 'r' },
+ { "device-id-of-file", required_argument, NULL, 'd' },
+ { "export", no_argument, NULL, 'x' },
+ { "export-prefix", required_argument, NULL, 'P' },
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
+ {}
+ };
+
+ enum action_type {
+ ACTION_QUERY,
+ ACTION_ATTRIBUTE_WALK,
+ ACTION_DEVICE_ID_FILE,
+ } action = ACTION_QUERY;
+
+ enum query_type {
+ QUERY_NAME,
+ QUERY_PATH,
+ QUERY_SYMLINK,
+ QUERY_PROPERTY,
+ QUERY_ALL,
+ } query = QUERY_ALL;
+
+ while ((c = getopt_long(argc, argv, "aced:n:p:q:rxP:RVh", options, NULL)) >= 0)
+ switch (c) {
+ case 'n': {
+ if (device != NULL) {
+ fprintf(stderr, "device already specified\n");
+ return 2;
+ }
+
+ device = find_device(udev, optarg, "/dev/");
+ if (device == NULL) {
+ fprintf(stderr, "device node not found\n");
+ return 2;
+ }
+ break;
+ }
+ case 'p':
+ if (device != NULL) {
+ fprintf(stderr, "device already specified\n");
+ return 2;
+ }
+
+ device = find_device(udev, optarg, "/sys");
+ if (device == NULL) {
+ fprintf(stderr, "syspath not found\n");
+ return 2;
+ }
+ break;
+ case 'q':
+ action = ACTION_QUERY;
+ if (streq(optarg, "property") || streq(optarg, "env"))
+ query = QUERY_PROPERTY;
+ else if (streq(optarg, "name"))
+ query = QUERY_NAME;
+ else if (streq(optarg, "symlink"))
+ query = QUERY_SYMLINK;
+ else if (streq(optarg, "path"))
+ query = QUERY_PATH;
+ else if (streq(optarg, "all"))
+ query = QUERY_ALL;
+ else {
+ fprintf(stderr, "unknown query type\n");
+ return 3;
+ }
+ break;
+ case 'r':
+ root = true;
+ break;
+ case 'd':
+ action = ACTION_DEVICE_ID_FILE;
+ strscpy(name, sizeof(name), optarg);
+ break;
+ case 'a':
+ action = ACTION_ATTRIBUTE_WALK;
+ break;
+ case 'e':
+ export_devices(udev);
+ return 0;
+ case 'c':
+ cleanup_db(udev);
+ return 0;
+ case 'x':
+ export = true;
+ break;
+ case 'P':
+ export_prefix = optarg;
+ break;
+ case 'V':
+ printf("%s\n", VERSION);
+ return 0;
+ case 'h':
+ help();
+ return 0;
+ default:
+ return 1;
+ }
+
+ switch (action) {
+ case ACTION_QUERY:
+ if (!device) {
+ if (!argv[optind]) {
+ help();
+ return 2;
+ }
+ device = find_device(udev, argv[optind], NULL);
+ if (!device) {
+ fprintf(stderr, "Unknown device, --name=, --path=, or absolute path in /dev/ or /sys expected.\n");
+ return 4;
+ }
+ }
+
+ switch(query) {
+ case QUERY_NAME: {
+ const char *node = udev_device_get_devnode(device);
+
+ if (node == NULL) {
+ fprintf(stderr, "no device node found\n");
+ return 5;
+ }
+
+ if (root)
+ printf("%s\n", udev_device_get_devnode(device));
+ else
+ printf("%s\n", udev_device_get_devnode(device) + strlen("/dev/"));
+ break;
+ }
+ case QUERY_SYMLINK:
+ list_entry = udev_device_get_devlinks_list_entry(device);
+ while (list_entry != NULL) {
+ if (root)
+ printf("%s", udev_list_entry_get_name(list_entry));
+ else
+ printf("%s", udev_list_entry_get_name(list_entry) + strlen("/dev/"));
+ list_entry = udev_list_entry_get_next(list_entry);
+ if (list_entry != NULL)
+ printf(" ");
+ }
+ printf("\n");
+ break;
+ case QUERY_PATH:
+ printf("%s\n", udev_device_get_devpath(device));
+ return 0;
+ case QUERY_PROPERTY:
+ list_entry = udev_device_get_properties_list_entry(device);
+ while (list_entry != NULL) {
+ if (export) {
+ const char *prefix = export_prefix;
+
+ if (prefix == NULL)
+ prefix = "";
+ printf("%s%s='%s'\n", prefix,
+ udev_list_entry_get_name(list_entry),
+ udev_list_entry_get_value(list_entry));
+ } else {
+ printf("%s=%s\n", udev_list_entry_get_name(list_entry), udev_list_entry_get_value(list_entry));
+ }
+ list_entry = udev_list_entry_get_next(list_entry);
+ }
+ break;
+ case QUERY_ALL:
+ print_record(device);
+ break;
+ default:
+ assert_not_reached("unknown query type");
+ }
+ break;
+ case ACTION_ATTRIBUTE_WALK:
+ if (!device && argv[optind]) {
+ device = find_device(udev, argv[optind], NULL);
+ if (!device) {
+ fprintf(stderr, "Unknown device, absolute path in /dev/ or /sys expected.\n");
+ return 4;
+ }
+ }
+ if (!device) {
+ fprintf(stderr, "Unknown device, --name=, --path=, or absolute path in /dev/ or /sys expected.\n");
+ return 4;
+ }
+ print_device_chain(device);
+ break;
+ case ACTION_DEVICE_ID_FILE:
+ if (stat_device(name, export, export_prefix) != 0)
+ return 1;
+ break;
+ }
+
+ return 0;
+}
+
+const struct udevadm_cmd udevadm_info = {
+ .name = "info",
+ .cmd = uinfo,
+ .help = "Query sysfs or the udev database",
+};
diff --git a/src/grp-udev/udevadm/udevadm-monitor.c b/src/grp-udev/udevadm/udevadm-monitor.c
new file mode 100644
index 0000000000..c0ef073476
--- /dev/null
+++ b/src/grp-udev/udevadm/udevadm-monitor.c
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2004-2010 Kay Sievers <kay@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include "fd-util.h"
+#include "formats-util.h"
+#include "udev-util.h"
+#include "udev.h"
+
+static bool udev_exit;
+
+static void sig_handler(int signum) {
+ if (signum == SIGINT || signum == SIGTERM)
+ udev_exit = true;
+}
+
+static void print_device(struct udev_device *device, const char *source, int prop) {
+ struct timespec ts;
+
+ assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
+ printf("%-6s[%"PRI_TIME".%06ld] %-8s %s (%s)\n",
+ source,
+ ts.tv_sec, ts.tv_nsec/1000,
+ udev_device_get_action(device),
+ udev_device_get_devpath(device),
+ udev_device_get_subsystem(device));
+ if (prop) {
+ struct udev_list_entry *list_entry;
+
+ udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(device))
+ printf("%s=%s\n",
+ udev_list_entry_get_name(list_entry),
+ udev_list_entry_get_value(list_entry));
+ printf("\n");
+ }
+}
+
+static void help(void) {
+ printf("%s monitor [--property] [--kernel] [--udev] [--help]\n\n"
+ "Listen to kernel and udev events.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " -p --property Print the event properties\n"
+ " -k --kernel Print kernel uevents\n"
+ " -u --udev Print udev events\n"
+ " -s --subsystem-match=SUBSYSTEM[/DEVTYPE] Filter events by subsystem\n"
+ " -t --tag-match=TAG Filter events by tag\n"
+ , program_invocation_short_name);
+}
+
+static int adm_monitor(struct udev *udev, int argc, char *argv[]) {
+ struct sigaction act = {};
+ sigset_t mask;
+ bool prop = false;
+ bool print_kernel = false;
+ bool print_udev = false;
+ _cleanup_udev_list_cleanup_ struct udev_list subsystem_match_list;
+ _cleanup_udev_list_cleanup_ struct udev_list tag_match_list;
+ _cleanup_udev_monitor_unref_ struct udev_monitor *udev_monitor = NULL;
+ _cleanup_udev_monitor_unref_ struct udev_monitor *kernel_monitor = NULL;
+ _cleanup_close_ int fd_ep = -1;
+ int fd_kernel = -1, fd_udev = -1;
+ struct epoll_event ep_kernel, ep_udev;
+ int c;
+
+ static const struct option options[] = {
+ { "property", no_argument, NULL, 'p' },
+ { "environment", no_argument, NULL, 'e' }, /* alias for -p */
+ { "kernel", no_argument, NULL, 'k' },
+ { "udev", no_argument, NULL, 'u' },
+ { "subsystem-match", required_argument, NULL, 's' },
+ { "tag-match", required_argument, NULL, 't' },
+ { "help", no_argument, NULL, 'h' },
+ {}
+ };
+
+ udev_list_init(udev, &subsystem_match_list, true);
+ udev_list_init(udev, &tag_match_list, true);
+
+ while ((c = getopt_long(argc, argv, "pekus:t:h", options, NULL)) >= 0)
+ switch (c) {
+ case 'p':
+ case 'e':
+ prop = true;
+ break;
+ case 'k':
+ print_kernel = true;
+ break;
+ case 'u':
+ print_udev = true;
+ break;
+ case 's':
+ {
+ char subsys[UTIL_NAME_SIZE];
+ char *devtype;
+
+ strscpy(subsys, sizeof(subsys), optarg);
+ devtype = strchr(subsys, '/');
+ if (devtype != NULL) {
+ devtype[0] = '\0';
+ devtype++;
+ }
+ udev_list_entry_add(&subsystem_match_list, subsys, devtype);
+ break;
+ }
+ case 't':
+ udev_list_entry_add(&tag_match_list, optarg, NULL);
+ break;
+ case 'h':
+ help();
+ return 0;
+ default:
+ return 1;
+ }
+
+ if (!print_kernel && !print_udev) {
+ print_kernel = true;
+ print_udev = true;
+ }
+
+ /* set signal handlers */
+ act.sa_handler = sig_handler;
+ act.sa_flags = SA_RESTART;
+ sigaction(SIGINT, &act, NULL);
+ sigaction(SIGTERM, &act, NULL);
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGINT);
+ sigaddset(&mask, SIGTERM);
+ sigprocmask(SIG_UNBLOCK, &mask, NULL);
+
+ fd_ep = epoll_create1(EPOLL_CLOEXEC);
+ if (fd_ep < 0) {
+ log_error_errno(errno, "error creating epoll fd: %m");
+ return 1;
+ }
+
+ printf("monitor will print the received events for:\n");
+ if (print_udev) {
+ struct udev_list_entry *entry;
+
+ udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
+ if (udev_monitor == NULL) {
+ fprintf(stderr, "error: unable to create netlink socket\n");
+ return 1;
+ }
+ udev_monitor_set_receive_buffer_size(udev_monitor, 128*1024*1024);
+ fd_udev = udev_monitor_get_fd(udev_monitor);
+
+ udev_list_entry_foreach(entry, udev_list_get_entry(&subsystem_match_list)) {
+ const char *subsys = udev_list_entry_get_name(entry);
+ const char *devtype = udev_list_entry_get_value(entry);
+
+ if (udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, subsys, devtype) < 0)
+ fprintf(stderr, "error: unable to apply subsystem filter '%s'\n", subsys);
+ }
+
+ udev_list_entry_foreach(entry, udev_list_get_entry(&tag_match_list)) {
+ const char *tag = udev_list_entry_get_name(entry);
+
+ if (udev_monitor_filter_add_match_tag(udev_monitor, tag) < 0)
+ fprintf(stderr, "error: unable to apply tag filter '%s'\n", tag);
+ }
+
+ if (udev_monitor_enable_receiving(udev_monitor) < 0) {
+ fprintf(stderr, "error: unable to subscribe to udev events\n");
+ return 2;
+ }
+
+ memzero(&ep_udev, sizeof(struct epoll_event));
+ ep_udev.events = EPOLLIN;
+ ep_udev.data.fd = fd_udev;
+ if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_udev, &ep_udev) < 0) {
+ log_error_errno(errno, "fail to add fd to epoll: %m");
+ return 2;
+ }
+
+ printf("UDEV - the event which udev sends out after rule processing\n");
+ }
+
+ if (print_kernel) {
+ struct udev_list_entry *entry;
+
+ kernel_monitor = udev_monitor_new_from_netlink(udev, "kernel");
+ if (kernel_monitor == NULL) {
+ fprintf(stderr, "error: unable to create netlink socket\n");
+ return 3;
+ }
+ udev_monitor_set_receive_buffer_size(kernel_monitor, 128*1024*1024);
+ fd_kernel = udev_monitor_get_fd(kernel_monitor);
+
+ udev_list_entry_foreach(entry, udev_list_get_entry(&subsystem_match_list)) {
+ const char *subsys = udev_list_entry_get_name(entry);
+
+ if (udev_monitor_filter_add_match_subsystem_devtype(kernel_monitor, subsys, NULL) < 0)
+ fprintf(stderr, "error: unable to apply subsystem filter '%s'\n", subsys);
+ }
+
+ if (udev_monitor_enable_receiving(kernel_monitor) < 0) {
+ fprintf(stderr, "error: unable to subscribe to kernel events\n");
+ return 4;
+ }
+
+ memzero(&ep_kernel, sizeof(struct epoll_event));
+ ep_kernel.events = EPOLLIN;
+ ep_kernel.data.fd = fd_kernel;
+ if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_kernel, &ep_kernel) < 0) {
+ log_error_errno(errno, "fail to add fd to epoll: %m");
+ return 5;
+ }
+
+ printf("KERNEL - the kernel uevent\n");
+ }
+ printf("\n");
+
+ while (!udev_exit) {
+ int fdcount;
+ struct epoll_event ev[4];
+ int i;
+
+ fdcount = epoll_wait(fd_ep, ev, ELEMENTSOF(ev), -1);
+ if (fdcount < 0) {
+ if (errno != EINTR)
+ fprintf(stderr, "error receiving uevent message: %m\n");
+ continue;
+ }
+
+ for (i = 0; i < fdcount; i++) {
+ if (ev[i].data.fd == fd_kernel && ev[i].events & EPOLLIN) {
+ struct udev_device *device;
+
+ device = udev_monitor_receive_device(kernel_monitor);
+ if (device == NULL)
+ continue;
+ print_device(device, "KERNEL", prop);
+ udev_device_unref(device);
+ } else if (ev[i].data.fd == fd_udev && ev[i].events & EPOLLIN) {
+ struct udev_device *device;
+
+ device = udev_monitor_receive_device(udev_monitor);
+ if (device == NULL)
+ continue;
+ print_device(device, "UDEV", prop);
+ udev_device_unref(device);
+ }
+ }
+ }
+
+ return 0;
+}
+
+const struct udevadm_cmd udevadm_monitor = {
+ .name = "monitor",
+ .cmd = adm_monitor,
+ .help = "Listen to kernel and udev events",
+};
diff --git a/src/grp-udev/udevadm/udevadm-settle.c b/src/grp-udev/udevadm/udevadm-settle.c
new file mode 100644
index 0000000000..6a5dc6e9e4
--- /dev/null
+++ b/src/grp-udev/udevadm/udevadm-settle.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2006-2009 Kay Sievers <kay@vrfy.org>
+ * Copyright (C) 2009 Canonical Ltd.
+ * Copyright (C) 2009 Scott James Remnant <scott@netsplit.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <poll.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "parse-util.h"
+#include "udev.h"
+#include "util.h"
+
+static void help(void) {
+ printf("%s settle OPTIONS\n\n"
+ "Wait for pending udev events.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " -t --timeout=SECONDS Maximum time to wait for events\n"
+ " -E --exit-if-exists=FILE Stop waiting if file exists\n"
+ , program_invocation_short_name);
+}
+
+static int adm_settle(struct udev *udev, int argc, char *argv[]) {
+ static const struct option options[] = {
+ { "timeout", required_argument, NULL, 't' },
+ { "exit-if-exists", required_argument, NULL, 'E' },
+ { "help", no_argument, NULL, 'h' },
+ { "seq-start", required_argument, NULL, 's' }, /* removed */
+ { "seq-end", required_argument, NULL, 'e' }, /* removed */
+ { "quiet", no_argument, NULL, 'q' }, /* removed */
+ {}
+ };
+ usec_t deadline;
+ const char *exists = NULL;
+ unsigned int timeout = 120;
+ struct pollfd pfd[1] = { {.fd = -1}, };
+ int c;
+ struct udev_queue *queue;
+ int rc = EXIT_FAILURE;
+
+ while ((c = getopt_long(argc, argv, "t:E:hs:e:q", options, NULL)) >= 0) {
+ switch (c) {
+
+ case 't': {
+ int r;
+
+ r = safe_atou(optarg, &timeout);
+ if (r < 0) {
+ log_error_errno(r, "Invalid timeout value '%s': %m", optarg);
+ return EXIT_FAILURE;
+ }
+ break;
+ }
+
+ case 'E':
+ exists = optarg;
+ break;
+
+ case 'h':
+ help();
+ return EXIT_SUCCESS;
+
+ case 's':
+ case 'e':
+ case 'q':
+ log_info("Option -%c no longer supported.", c);
+ return EXIT_FAILURE;
+
+ case '?':
+ return EXIT_FAILURE;
+
+ default:
+ assert_not_reached("Unknown argument");
+ }
+ }
+
+ if (optind < argc) {
+ fprintf(stderr, "Extraneous argument: '%s'\n", argv[optind]);
+ return EXIT_FAILURE;
+ }
+
+ deadline = now(CLOCK_MONOTONIC) + timeout * USEC_PER_SEC;
+
+ /* guarantee that the udev daemon isn't pre-processing */
+ if (getuid() == 0) {
+ struct udev_ctrl *uctrl;
+
+ uctrl = udev_ctrl_new(udev);
+ if (uctrl != NULL) {
+ if (udev_ctrl_send_ping(uctrl, MAX(5U, timeout)) < 0) {
+ log_debug("no connection to daemon");
+ udev_ctrl_unref(uctrl);
+ return EXIT_SUCCESS;
+ }
+ udev_ctrl_unref(uctrl);
+ }
+ }
+
+ queue = udev_queue_new(udev);
+ if (!queue) {
+ log_error("unable to get udev queue");
+ return EXIT_FAILURE;
+ }
+
+ pfd[0].events = POLLIN;
+ pfd[0].fd = udev_queue_get_fd(queue);
+ if (pfd[0].fd < 0) {
+ log_debug("queue is empty, nothing to watch");
+ rc = EXIT_SUCCESS;
+ goto out;
+ }
+
+ for (;;) {
+ if (exists && access(exists, F_OK) >= 0) {
+ rc = EXIT_SUCCESS;
+ break;
+ }
+
+ /* exit if queue is empty */
+ if (udev_queue_get_queue_is_empty(queue)) {
+ rc = EXIT_SUCCESS;
+ break;
+ }
+
+ if (now(CLOCK_MONOTONIC) >= deadline)
+ break;
+
+ /* wake up when queue is empty */
+ if (poll(pfd, 1, MSEC_PER_SEC) > 0 && pfd[0].revents & POLLIN)
+ udev_queue_flush(queue);
+ }
+
+out:
+ udev_queue_unref(queue);
+ return rc;
+}
+
+const struct udevadm_cmd udevadm_settle = {
+ .name = "settle",
+ .cmd = adm_settle,
+ .help = "Wait for pending udev events",
+};
diff --git a/src/grp-udev/udevadm/udevadm-test-builtin.c b/src/grp-udev/udevadm/udevadm-test-builtin.c
new file mode 100644
index 0000000000..0b180d03eb
--- /dev/null
+++ b/src/grp-udev/udevadm/udevadm-test-builtin.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2011 Kay Sievers <kay@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "string-util.h"
+#include "udev.h"
+
+static void help(struct udev *udev) {
+ printf("%s builtin [--help] COMMAND SYSPATH\n\n"
+ "Test a built-in command.\n\n"
+ " -h --help Print this message\n"
+ " --version Print version of the program\n\n"
+ "Commands:\n"
+ , program_invocation_short_name);
+
+ udev_builtin_list(udev);
+}
+
+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, c;
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+ switch (c) {
+ 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");
+ rc = 3;
+ goto out;
+ }
+
+ udev_builtin_init(udev);
+
+ cmd = udev_builtin_lookup(command);
+ if (cmd >= UDEV_BUILTIN_MAX) {
+ fprintf(stderr, "unknown command '%s'\n", command);
+ help(udev);
+ rc = 5;
+ goto out;
+ }
+
+ /* add /sys if needed */
+ if (!startswith(syspath, "/sys"))
+ strscpyl(filename, sizeof(filename), "/sys", syspath, NULL);
+ else
+ 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;
+ }
+
+ rc = udev_builtin_run(dev, cmd, command, true);
+ if (rc < 0) {
+ fprintf(stderr, "error executing '%s', exit code %i\n\n", command, rc);
+ rc = 6;
+ }
+out:
+ udev_device_unref(dev);
+ udev_builtin_exit(udev);
+ return rc;
+}
+
+const struct udevadm_cmd udevadm_test_builtin = {
+ .name = "test-builtin",
+ .cmd = adm_builtin,
+ .help = "Test a built-in command",
+ .debug = true,
+};
diff --git a/src/grp-udev/udevadm/udevadm-test.c b/src/grp-udev/udevadm/udevadm-test.c
new file mode 100644
index 0000000000..702dbe5282
--- /dev/null
+++ b/src/grp-udev/udevadm/udevadm-test.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2003-2004 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2004-2008 Kay Sievers <kay@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/signalfd.h>
+#include <unistd.h>
+
+#include "string-util.h"
+#include "udev-util.h"
+#include "udev.h"
+
+static void help(void) {
+
+ printf("%s test OPTIONS <syspath>\n\n"
+ "Test an event run.\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " -a --action=ACTION Set action string\n"
+ " -N --resolve-names=early|late|never When to resolve names\n"
+ , program_invocation_short_name);
+}
+
+static int adm_test(struct udev *udev, int argc, char *argv[]) {
+ int resolve_names = 1;
+ char filename[UTIL_PATH_SIZE];
+ const char *action = "add";
+ const char *syspath = NULL;
+ struct udev_list_entry *entry;
+ _cleanup_udev_rules_unref_ struct udev_rules *rules = NULL;
+ _cleanup_udev_device_unref_ struct udev_device *dev = NULL;
+ _cleanup_udev_event_unref_ struct udev_event *event = NULL;
+ sigset_t mask, sigmask_orig;
+ int rc = 0, c;
+
+ static const struct option options[] = {
+ { "action", required_argument, NULL, 'a' },
+ { "resolve-names", required_argument, NULL, 'N' },
+ { "help", no_argument, NULL, 'h' },
+ {}
+ };
+
+ log_debug("version %s", VERSION);
+
+ while ((c = getopt_long(argc, argv, "a:N:h", options, NULL)) >= 0)
+ switch (c) {
+ case 'a':
+ action = optarg;
+ break;
+ case 'N':
+ if (streq (optarg, "early")) {
+ resolve_names = 1;
+ } else if (streq (optarg, "late")) {
+ resolve_names = 0;
+ } else if (streq (optarg, "never")) {
+ resolve_names = -1;
+ } else {
+ fprintf(stderr, "resolve-names must be early, late or never\n");
+ log_error("resolve-names must be early, late or never");
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'h':
+ help();
+ exit(EXIT_SUCCESS);
+ case '?':
+ exit(EXIT_FAILURE);
+ default:
+ assert_not_reached("Unknown option");
+ }
+
+ syspath = argv[optind];
+ if (syspath == NULL) {
+ fprintf(stderr, "syspath parameter missing\n");
+ rc = 2;
+ goto out;
+ }
+
+ printf("This program is for debugging only, it does not run any program\n"
+ "specified by a RUN key. It may show incorrect results, because\n"
+ "some values may be different, or not available at a simulation run.\n"
+ "\n");
+
+ sigprocmask(SIG_SETMASK, NULL, &sigmask_orig);
+
+ udev_builtin_init(udev);
+
+ rules = udev_rules_new(udev, resolve_names);
+ if (rules == NULL) {
+ fprintf(stderr, "error reading rules\n");
+ rc = 3;
+ goto out;
+ }
+
+ /* add /sys if needed */
+ if (!startswith(syspath, "/sys"))
+ strscpyl(filename, sizeof(filename), "/sys", syspath, NULL);
+ else
+ strscpy(filename, sizeof(filename), syspath);
+ util_remove_trailing_chars(filename, '/');
+
+ dev = udev_device_new_from_synthetic_event(udev, filename, action);
+ if (dev == NULL) {
+ fprintf(stderr, "unable to open device '%s'\n", filename);
+ rc = 4;
+ goto out;
+ }
+
+ /* don't read info from the db */
+ udev_device_set_info_loaded(dev);
+
+ event = udev_event_new(dev);
+
+ sigfillset(&mask);
+ sigprocmask(SIG_SETMASK, &mask, &sigmask_orig);
+
+ udev_event_execute_rules(event,
+ 60 * USEC_PER_SEC, 20 * USEC_PER_SEC,
+ NULL,
+ rules);
+
+ udev_list_entry_foreach(entry, udev_device_get_properties_list_entry(dev))
+ printf("%s=%s\n", udev_list_entry_get_name(entry), udev_list_entry_get_value(entry));
+
+ udev_list_entry_foreach(entry, udev_list_get_entry(&event->run_list)) {
+ char program[UTIL_PATH_SIZE];
+
+ udev_event_apply_format(event, udev_list_entry_get_name(entry), program, sizeof(program));
+ printf("run: '%s'\n", program);
+ }
+out:
+ udev_builtin_exit(udev);
+ return rc;
+}
+
+const struct udevadm_cmd udevadm_test = {
+ .name = "test",
+ .cmd = adm_test,
+ .help = "Test an event run",
+ .debug = true,
+};
diff --git a/src/grp-udev/udevadm/udevadm-trigger.c b/src/grp-udev/udevadm/udevadm-trigger.c
new file mode 100644
index 0000000000..9d52345d92
--- /dev/null
+++ b/src/grp-udev/udevadm/udevadm-trigger.c
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2008-2009 Kay Sievers <kay@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "string-util.h"
+#include "udev-util.h"
+#include "udev.h"
+#include "udevadm-util.h"
+#include "util.h"
+
+static int verbose;
+static int dry_run;
+
+static void exec_list(struct udev_enumerate *udev_enumerate, const char *action) {
+ struct udev_list_entry *entry;
+
+ udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(udev_enumerate)) {
+ char filename[UTIL_PATH_SIZE];
+ int fd;
+
+ if (verbose)
+ printf("%s\n", udev_list_entry_get_name(entry));
+ if (dry_run)
+ continue;
+ strscpyl(filename, sizeof(filename), udev_list_entry_get_name(entry), "/uevent", NULL);
+ fd = open(filename, O_WRONLY|O_CLOEXEC);
+ if (fd < 0)
+ continue;
+ if (write(fd, action, strlen(action)) < 0)
+ log_debug_errno(errno, "error writing '%s' to '%s': %m", action, filename);
+ close(fd);
+ }
+}
+
+static const char *keyval(const char *str, const char **val, char *buf, size_t size) {
+ char *pos;
+
+ strscpy(buf, size,str);
+ pos = strchr(buf, '=');
+ if (pos != NULL) {
+ pos[0] = 0;
+ pos++;
+ }
+ *val = pos;
+ return buf;
+}
+
+static void help(void) {
+ printf("%s trigger OPTIONS\n\n"
+ "Request events from the kernel.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " -v --verbose Print the list of devices while running\n"
+ " -n --dry-run Do not actually trigger the events\n"
+ " -t --type= Type of events to trigger\n"
+ " devices sysfs devices (default)\n"
+ " subsystems sysfs subsystems and drivers\n"
+ " -c --action=ACTION Event action value, default is \"change\"\n"
+ " -s --subsystem-match=SUBSYSTEM Trigger devices from a matching subsystem\n"
+ " -S --subsystem-nomatch=SUBSYSTEM Exclude devices from a matching subsystem\n"
+ " -a --attr-match=FILE[=VALUE] Trigger devices with a matching attribute\n"
+ " -A --attr-nomatch=FILE[=VALUE] Exclude devices with a matching attribute\n"
+ " -p --property-match=KEY=VALUE Trigger devices with a matching property\n"
+ " -g --tag-match=KEY=VALUE Trigger devices with a matching property\n"
+ " -y --sysname-match=NAME Trigger devices with this /sys path\n"
+ " --name-match=NAME Trigger devices with this /dev name\n"
+ " -b --parent-match=NAME Trigger devices with that parent device\n"
+ , program_invocation_short_name);
+}
+
+static int adm_trigger(struct udev *udev, int argc, char *argv[]) {
+ enum {
+ ARG_NAME = 0x100,
+ };
+
+ static const struct option options[] = {
+ { "verbose", no_argument, NULL, 'v' },
+ { "dry-run", no_argument, NULL, 'n' },
+ { "type", required_argument, NULL, 't' },
+ { "action", required_argument, NULL, 'c' },
+ { "subsystem-match", required_argument, NULL, 's' },
+ { "subsystem-nomatch", required_argument, NULL, 'S' },
+ { "attr-match", required_argument, NULL, 'a' },
+ { "attr-nomatch", required_argument, NULL, 'A' },
+ { "property-match", required_argument, NULL, 'p' },
+ { "tag-match", required_argument, NULL, 'g' },
+ { "sysname-match", required_argument, NULL, 'y' },
+ { "name-match", required_argument, NULL, ARG_NAME },
+ { "parent-match", required_argument, NULL, 'b' },
+ { "help", no_argument, NULL, 'h' },
+ {}
+ };
+ enum {
+ TYPE_DEVICES,
+ TYPE_SUBSYSTEMS,
+ } device_type = TYPE_DEVICES;
+ const char *action = "change";
+ _cleanup_udev_enumerate_unref_ struct udev_enumerate *udev_enumerate = NULL;
+ int c, r;
+
+ udev_enumerate = udev_enumerate_new(udev);
+ if (udev_enumerate == NULL)
+ return 1;
+
+ while ((c = getopt_long(argc, argv, "vno:t:c:s:S:a:A:p:g:y:b:h", options, NULL)) >= 0) {
+ const char *key;
+ const char *val;
+ char buf[UTIL_PATH_SIZE];
+
+ switch (c) {
+ case 'v':
+ verbose = 1;
+ break;
+ case 'n':
+ dry_run = 1;
+ break;
+ case 't':
+ if (streq(optarg, "devices"))
+ device_type = TYPE_DEVICES;
+ else if (streq(optarg, "subsystems"))
+ device_type = TYPE_SUBSYSTEMS;
+ else {
+ log_error("unknown type --type=%s", optarg);
+ return 2;
+ }
+ break;
+ case 'c':
+ if (!nulstr_contains("add\0" "remove\0" "change\0", optarg)) {
+ log_error("unknown action '%s'", optarg);
+ return 2;
+ } else
+ action = optarg;
+
+ break;
+ case 's':
+ r = udev_enumerate_add_match_subsystem(udev_enumerate, optarg);
+ if (r < 0) {
+ log_error_errno(r, "could not add subsystem match '%s': %m", optarg);
+ return 2;
+ }
+ break;
+ case 'S':
+ r = udev_enumerate_add_nomatch_subsystem(udev_enumerate, optarg);
+ if (r < 0) {
+ log_error_errno(r, "could not add negative subsystem match '%s': %m", optarg);
+ return 2;
+ }
+ break;
+ case 'a':
+ key = keyval(optarg, &val, buf, sizeof(buf));
+ r = udev_enumerate_add_match_sysattr(udev_enumerate, key, val);
+ if (r < 0) {
+ log_error_errno(r, "could not add sysattr match '%s=%s': %m", key, val);
+ return 2;
+ }
+ break;
+ case 'A':
+ key = keyval(optarg, &val, buf, sizeof(buf));
+ r = udev_enumerate_add_nomatch_sysattr(udev_enumerate, key, val);
+ if (r < 0) {
+ log_error_errno(r, "could not add negative sysattr match '%s=%s': %m", key, val);
+ return 2;
+ }
+ break;
+ case 'p':
+ key = keyval(optarg, &val, buf, sizeof(buf));
+ r = udev_enumerate_add_match_property(udev_enumerate, key, val);
+ if (r < 0) {
+ log_error_errno(r, "could not add property match '%s=%s': %m", key, val);
+ return 2;
+ }
+ break;
+ case 'g':
+ r = udev_enumerate_add_match_tag(udev_enumerate, optarg);
+ if (r < 0) {
+ log_error_errno(r, "could not add tag match '%s': %m", optarg);
+ return 2;
+ }
+ break;
+ case 'y':
+ r = udev_enumerate_add_match_sysname(udev_enumerate, optarg);
+ if (r < 0) {
+ log_error_errno(r, "could not add sysname match '%s': %m", optarg);
+ return 2;
+ }
+ break;
+ case 'b': {
+ _cleanup_udev_device_unref_ struct udev_device *dev;
+
+ dev = find_device(udev, optarg, "/sys");
+ if (dev == NULL) {
+ log_error("unable to open the device '%s'", optarg);
+ return 2;
+ }
+
+ r = udev_enumerate_add_match_parent(udev_enumerate, dev);
+ if (r < 0) {
+ log_error_errno(r, "could not add parent match '%s': %m", optarg);
+ return 2;
+ }
+ break;
+ }
+
+ case ARG_NAME: {
+ _cleanup_udev_device_unref_ struct udev_device *dev;
+
+ dev = find_device(udev, optarg, "/dev/");
+ if (dev == NULL) {
+ log_error("unable to open the device '%s'", optarg);
+ return 2;
+ }
+
+ r = udev_enumerate_add_match_parent(udev_enumerate, dev);
+ if (r < 0) {
+ log_error_errno(r, "could not add parent match '%s': %m", optarg);
+ return 2;
+ }
+ break;
+ }
+
+ case 'h':
+ help();
+ return 0;
+ case '?':
+ return 1;
+ default:
+ assert_not_reached("Unknown option");
+ }
+ }
+
+ for (; optind < argc; optind++) {
+ _cleanup_udev_device_unref_ struct udev_device *dev;
+
+ dev = find_device(udev, argv[optind], NULL);
+ if (dev == NULL) {
+ log_error("unable to open the device '%s'", argv[optind]);
+ return 2;
+ }
+
+ r = udev_enumerate_add_match_parent(udev_enumerate, dev);
+ if (r < 0) {
+ log_error_errno(r, "could not add tag match '%s': %m", optarg);
+ return 2;
+ }
+ }
+
+ switch (device_type) {
+ case TYPE_SUBSYSTEMS:
+ udev_enumerate_scan_subsystems(udev_enumerate);
+ exec_list(udev_enumerate, action);
+ return 0;
+ case TYPE_DEVICES:
+ udev_enumerate_scan_devices(udev_enumerate);
+ exec_list(udev_enumerate, action);
+ return 0;
+ default:
+ assert_not_reached("device_type");
+ }
+}
+
+const struct udevadm_cmd udevadm_trigger = {
+ .name = "trigger",
+ .cmd = adm_trigger,
+ .help = "Request events from the kernel",
+};
diff --git a/src/grp-udev/udevadm/udevadm-util.c b/src/grp-udev/udevadm/udevadm-util.c
new file mode 100644
index 0000000000..3539c1d6ab
--- /dev/null
+++ b/src/grp-udev/udevadm/udevadm-util.c
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2008-2009 Kay Sievers <kay@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "string-util.h"
+#include "udevadm-util.h"
+
+struct udev_device *find_device(struct udev *udev,
+ const char *id,
+ const char *prefix) {
+
+ assert(udev);
+ assert(id);
+
+ if (prefix && !startswith(id, prefix))
+ id = strjoina(prefix, id);
+
+ if (startswith(id, "/dev/")) {
+ struct stat statbuf;
+ char type;
+
+ if (stat(id, &statbuf) < 0)
+ return NULL;
+
+ if (S_ISBLK(statbuf.st_mode))
+ type = 'b';
+ else if (S_ISCHR(statbuf.st_mode))
+ type = 'c';
+ else
+ return NULL;
+
+ return udev_device_new_from_devnum(udev, type, statbuf.st_rdev);
+ } else if (startswith(id, "/sys/"))
+ return udev_device_new_from_syspath(udev, id);
+ else
+ return NULL;
+}
diff --git a/src/grp-udev/udevadm/udevadm-util.h b/src/grp-udev/udevadm/udevadm-util.h
new file mode 100644
index 0000000000..dc712b0d93
--- /dev/null
+++ b/src/grp-udev/udevadm/udevadm-util.h
@@ -0,0 +1,24 @@
+#pragma once
+
+/*
+ * Copyright (C) 2014 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
+ *
+ * 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 "udev.h"
+
+struct udev_device *find_device(struct udev *udev,
+ const char *id,
+ const char *prefix);
diff --git a/src/grp-udev/udevadm/udevadm.c b/src/grp-udev/udevadm/udevadm.c
new file mode 100644
index 0000000000..a6a873e5de
--- /dev/null
+++ b/src/grp-udev/udevadm/udevadm.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2007-2012 Kay Sievers <kay@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#include "selinux-util.h"
+#include "string-util.h"
+#include "udev.h"
+
+static int adm_version(struct udev *udev, int argc, char *argv[]) {
+ printf("%s\n", VERSION);
+ return 0;
+}
+
+static const struct udevadm_cmd udevadm_version = {
+ .name = "version",
+ .cmd = adm_version,
+};
+
+static int adm_help(struct udev *udev, int argc, char *argv[]);
+
+static const struct udevadm_cmd udevadm_help = {
+ .name = "help",
+ .cmd = adm_help,
+};
+
+static const struct udevadm_cmd *udevadm_cmds[] = {
+ &udevadm_info,
+ &udevadm_trigger,
+ &udevadm_settle,
+ &udevadm_control,
+ &udevadm_monitor,
+ &udevadm_hwdb,
+ &udevadm_test,
+ &udevadm_test_builtin,
+ &udevadm_version,
+ &udevadm_help,
+};
+
+static int adm_help(struct udev *udev, int argc, char *argv[]) {
+ unsigned int i;
+
+ printf("%s [--help] [--version] [--debug] COMMAND [COMMAND OPTIONS]\n\n"
+ "Send control commands or test the device manager.\n\n"
+ "Commands:\n"
+ , program_invocation_short_name);
+
+ for (i = 0; i < ELEMENTSOF(udevadm_cmds); i++)
+ if (udevadm_cmds[i]->help != NULL)
+ printf(" %-12s %s\n", udevadm_cmds[i]->name, udevadm_cmds[i]->help);
+ return 0;
+}
+
+static int run_command(struct udev *udev, const struct udevadm_cmd *cmd, int argc, char *argv[]) {
+ if (cmd->debug)
+ log_set_max_level(LOG_DEBUG);
+ log_debug("calling: %s", cmd->name);
+ return cmd->cmd(udev, argc, argv);
+}
+
+int main(int argc, char *argv[]) {
+ struct udev *udev;
+ static const struct option options[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ {}
+ };
+ const char *command;
+ unsigned int i;
+ int rc = 1, c;
+
+ udev = udev_new();
+ if (udev == NULL)
+ goto out;
+
+ log_parse_environment();
+ log_open();
+ mac_selinux_init();
+
+ while ((c = getopt_long(argc, argv, "+dhV", options, NULL)) >= 0)
+ switch (c) {
+
+ case 'd':
+ log_set_max_level(LOG_DEBUG);
+ break;
+
+ case 'h':
+ rc = adm_help(udev, argc, argv);
+ goto out;
+
+ case 'V':
+ rc = adm_version(udev, argc, argv);
+ goto out;
+
+ default:
+ goto out;
+ }
+
+ command = argv[optind];
+
+ if (command != NULL)
+ for (i = 0; i < ELEMENTSOF(udevadm_cmds); i++)
+ if (streq(udevadm_cmds[i]->name, command)) {
+ argc -= optind;
+ argv += optind;
+ /* we need '0' here to reset the internal state */
+ optind = 0;
+ rc = run_command(udev, udevadm_cmds[i], argc, argv);
+ goto out;
+ }
+
+ fprintf(stderr, "%s: missing or unknown command\n", program_invocation_short_name);
+ rc = 2;
+out:
+ mac_selinux_finish();
+ udev_unref(udev);
+ log_close();
+ return rc;
+}
diff --git a/src/grp-udev/udevadm/udevadm.completion.bash b/src/grp-udev/udevadm/udevadm.completion.bash
new file mode 100644
index 0000000000..b828b8dd7c
--- /dev/null
+++ b/src/grp-udev/udevadm/udevadm.completion.bash
@@ -0,0 +1,97 @@
+# udevadm(8) completion -*- shell-script -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010 Ran Benita
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <http://www.gnu.org/licenses/>.
+
+__contains_word () {
+ local w word=$1; shift
+ for w in "$@"; do
+ [[ $w = "$word" ]] && return
+ done
+}
+
+__get_all_sysdevs() {
+ local -a devs=(/sys/bus/*/devices/*/ /sys/class/*/*/)
+ printf '%s\n' "${devs[@]%/}"
+}
+
+_udevadm() {
+ local i verb comps
+ local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]}
+ local OPTS='-h --help --version --debug'
+
+ local verbs=(info trigger settle control monitor hwdb test-builtin test)
+
+ for ((i=0; i < COMP_CWORD; i++)); do
+ if __contains_word "${COMP_WORDS[i]}" "${verbs[@]}" &&
+ ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG]}; then
+ verb=${COMP_WORDS[i]}
+ break
+ fi
+ done
+
+ if [[ -z $verb ]]; then
+ COMPREPLY=( $(compgen -W '${OPTS[*]} ${verbs[*]}' -- "$cur") )
+ return 0
+ fi
+
+ case $verb in
+ 'info')
+ if [[ $cur = -* ]]; then
+ comps='--help --query= --path= --name= --root --attribute-walk --export-db --cleanup-db'
+ else
+ comps=$( __get_all_sysdevs )
+ fi
+ ;;
+ 'trigger')
+ comps='--help --verbose --dry-run --type= --action= --subsystem-match=
+ --subsystem-nomatch= --attr-match= --attr-nomatch= --property-match=
+ --tag-match= --sysname-match= --parent-match='
+ ;;
+ 'settle')
+ comps='--help --timeout= --seq-start= --seq-end= --exit-if-exists= --quiet'
+ ;;
+ 'control')
+ comps='--help --exit --log-priority= --stop-exec-queue --start-exec-queue
+ --reload --property= --children-max= --timeout='
+ ;;
+ 'monitor')
+ comps='--help --kernel --udev --property --subsystem-match= --tag-match='
+ ;;
+ 'hwdb')
+ comps='--help --update --test='
+ ;;
+ 'test')
+ if [[ $cur = -* ]]; then
+ comps='--help --action='
+ else
+ comps=$( __get_all_sysdevs )
+ fi
+ ;;
+ 'test-builtin')
+ comps='blkid btrfs hwdb input_id keyboard kmod net_id net_setup_link path_id usb_id uaccess'
+ ;;
+ *)
+ comps=${VERBS[*]}
+ ;;
+ esac
+
+ COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
+ return 0
+}
+
+complete -F _udevadm udevadm
diff --git a/src/grp-udev/udevadm/udevadm.completion.zsh b/src/grp-udev/udevadm/udevadm.completion.zsh
new file mode 100644
index 0000000000..bb23e64d24
--- /dev/null
+++ b/src/grp-udev/udevadm/udevadm.completion.zsh
@@ -0,0 +1,141 @@
+#compdef udevadm
+
+_udevadm_info(){
+ _arguments \
+ '--query=[Query the database for specified type of device data. It needs the --path or --name to identify the specified device.]:type:(name symlink path property all)' \
+ '--path=[The devpath of the device to query.]:sys files:_files -P /sys/ -W /sys' \
+ '--name=[The name of the device node or a symlink to query]:device files:_files -P /dev/ -W /dev' \
+ '--root[Print absolute paths in name or symlink query.]' \
+ '--attribute-walk[Print all sysfs properties of the specified device that can be used in udev rules to match the specified device]' \
+ '--export[Print output as key/value pairs.]' \
+ '--export-prefix=[Add a prefix to the key name of exported values.]:prefix' \
+ '--device-id-of-file=[Print major/minor numbers of the underlying device, where the file lives on.]:files:_udevadm_mounts' \
+ '--export-db[Export the content of the udev database.]' \
+ '--cleanup-db[Cleanup the udev database.]'
+}
+
+_udevadm_trigger(){
+ _arguments \
+ '--verbose[Print the list of devices which will be triggered.]' \
+ '--dry-run[Do not actually trigger the event.]' \
+ '--type=[Trigger a specific type of devices.]:types:(devices subsystems failed)' \
+ '--action=[Type of event to be triggered.]:actions:(add change remove)' \
+ '--subsystem-match=[Trigger events for devices which belong to a matching subsystem.]' \
+ '--subsystem-nomatch=[Do not trigger events for devices which belong to a matching subsystem.]' \
+ '--attr-match=attribute=[Trigger events for devices with a matching sysfs attribute.]' \
+ '--attr-nomatch=attribute=[Do not trigger events for devices with a matching sysfs attribute.]' \
+ '--property-match=[Trigger events for devices with a matching property value.]' \
+ '--tag-match=property[Trigger events for devices with a matching tag.]' \
+ '--sysname-match=[Trigger events for devices with a matching sys device name.]' \
+ '--parent-match=[Trigger events for all children of a given device.]'
+}
+
+_udevadm_settle(){
+ _arguments \
+ '--timeout=[Maximum number of seconds to wait for the event queue to become empty.]' \
+ '--seq-start=[Wait only for events after the given sequence number.]' \
+ '--seq-end=[Wait only for events before the given sequence number.]' \
+ '--exit-if-exists=[Stop waiting if file exists.]:files:_files' \
+ '--quiet[Do not print any output, like the remaining queue entries when reaching the timeout.]' \
+ '--help[Print help text.]'
+}
+
+_udevadm_control(){
+ _arguments \
+ '--exit[Signal and wait for systemd-udevd to exit.]' \
+ '--log-priority=[Set the internal log level of systemd-udevd.]:priorities:(err info debug)' \
+ '--stop-exec-queue[Signal systemd-udevd to stop executing new events. Incoming events will be queued.]' \
+ '--start-exec-queue[Signal systemd-udevd to enable the execution of events.]' \
+ '--reload[Signal systemd-udevd to reload the rules files and other databases like the kernel module index.]' \
+ '--property=[Set a global property for all events.]' \
+ '--children-max=[Set the maximum number of events.]' \
+ '--timeout=[The maximum number of seconds to wait for a reply from systemd-udevd.]' \
+ '--help[Print help text.]'
+}
+
+_udevadm_monitor(){
+ _arguments \
+ '--kernel[Print the kernel uevents.]' \
+ '--udev[Print the udev event after the rule processing.]' \
+ '--property[Also print the properties of the event.]' \
+ '--subsystem-match=[Filter events by subsystem/\[devtype\].]' \
+ '--tag-match=[Filter events by property.]' \
+ '--help[Print help text.]'
+}
+
+_udevadm_test(){
+ _arguments \
+ '--action=[The action string.]:actions:(add change remove)' \
+ '--subsystem=[The subsystem string.]' \
+ '--help[Print help text.]' \
+ '*::devpath:_files -P /sys/ -W /sys'
+}
+
+_udevadm_test-builtin(){
+ if (( CURRENT == 2 )); then
+ _arguments \
+ '--help[Print help text]' \
+ '*::builtins:(blkid btrfs hwdb input_id net_id net_setup_link kmod path_id usb_id uaccess)'
+ elif (( CURRENT == 3 )); then
+ _arguments \
+ '--help[Print help text]' \
+ '*::syspath:_files -P /sys -W /sys'
+ else
+ _arguments \
+ '--help[Print help text]'
+ fi
+}
+
+_udevadm_mounts(){
+ local dev_tmp dpath_tmp mp_tmp mline
+
+ tmp=( "${(@f)$(< /proc/self/mounts)}" )
+ dev_tmp=( "${(@)${(@)tmp%% *}:#none}" )
+ mp_tmp=( "${(@)${(@)tmp#* }%% *}" )
+
+ local MATCH
+ mp_tmp=("${(@q)mp_tmp//(#m)\\[0-7](#c3)/${(#)$(( 8#${MATCH[2,-1]} ))}}")
+ dpath_tmp=( "${(@Mq)dev_tmp:#/*}" )
+ dev_tmp=( "${(@q)dev_tmp:#/*}" )
+
+ _alternative \
+ 'device-paths: device path:compadd -a dpath_tmp' \
+ 'directories:mount point:compadd -a mp_tmp'
+}
+
+
+_udevadm_command(){
+ local -a _udevadm_cmds
+ _udevadm_cmds=(
+ 'info:query sysfs or the udev database'
+ 'trigger:request events from the kernel'
+ 'settle:wait for the event queue to finish'
+ 'control:control the udev daemon'
+ 'monitor:listen to kernel and udev events'
+ 'test:test an event run'
+ 'test-builtin:test a built-in command'
+ )
+
+ if ((CURRENT == 1)); then
+ _describe -t commands 'udevadm commands' _udevadm_cmds
+ else
+ local curcontext="$curcontext"
+ cmd="${${_udevadm_cmds[(r)$words[1]:*]%%:*}}"
+ if (($#cmd)); then
+ if (( $+functions[_udevadm_$cmd] )); then
+ _udevadm_$cmd
+ else
+ _message "no options for $cmd"
+ fi
+ else
+ _message "no more options"
+ fi
+ fi
+}
+
+
+_arguments \
+ '--debug[Print debug messages to stderr]' \
+ '--version[Print version number]' \
+ '--help[Print help text]' \
+ '*::udevadm commands:_udevadm_command'
diff --git a/src/grp-udev/udevadm/udevadm.xml b/src/grp-udev/udevadm/udevadm.xml
new file mode 100644
index 0000000000..8c1abd2770
--- /dev/null
+++ b/src/grp-udev/udevadm/udevadm.xml
@@ -0,0 +1,576 @@
+<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<refentry id="udevadm">
+ <refentryinfo>
+ <title>udevadm</title>
+ <productname>systemd</productname>
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Kay</firstname>
+ <surname>Sievers</surname>
+ <email>kay@vrfy.org</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>udevadm</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>udevadm</refname><refpurpose>udev management tool</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>udevadm</command>
+ <arg><option>--debug</option></arg>
+ <arg><option>--version</option></arg>
+ <arg><option>--help</option></arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>udevadm info <replaceable>options</replaceable></command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>udevadm trigger <optional>options</optional></command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>udevadm settle <optional>options</optional></command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>udevadm control <replaceable>command</replaceable></command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>udevadm monitor <optional>options</optional></command>
+ </cmdsynopsis>
+ <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>
+ <para><command>udevadm</command> expects a command and command
+ specific options. It controls the runtime behavior of
+ <command>systemd-udevd</command>, requests kernel events, manages
+ the event queue, and provides simple debugging mechanisms.</para>
+ </refsect1>
+
+ <refsect1><title>Options</title>
+ <variablelist>
+ <varlistentry>
+ <term><option>--debug</option></term>
+ <listitem>
+ <para>Print debug messages to standard error.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--version</option></term>
+ <listitem>
+ <para>Print version number.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <refsect2><title>udevadm info
+ <arg choice="opt"><replaceable>options</replaceable></arg>
+ <arg choice="opt"><replaceable>devpath</replaceable>|<replaceable>file</replaceable></arg>
+ </title>
+
+ <para>Queries the udev database for device information
+ stored in the udev database. It can also query the properties
+ of a device from its sysfs representation to help creating udev
+ rules that match this device.</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>-q</option></term>
+ <term><option>--query=<replaceable>TYPE</replaceable></option></term>
+ <listitem>
+ <para>Query the database for the specified type of device
+ data. It needs the <option>--path</option> or
+ <option>--name</option> to identify the specified device.
+ Valid <replaceable>TYPE</replaceable>s are:
+ <constant>name</constant>, <constant>symlink</constant>,
+ <constant>path</constant>, <constant>property</constant>,
+ <constant>all</constant>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-p</option></term>
+ <term><option>--path=<replaceable>DEVPATH</replaceable></option></term>
+ <listitem>
+ <para>The <filename>/sys</filename> path of the device to
+ query, e.g.
+ <filename><optional>/sys</optional>/class/block/sda</filename>.
+ Note that this option usually is not very useful, since
+ <command>udev</command> can guess the type of the
+ argument, so <command>udevadm
+ --devpath=/class/block/sda</command> is equivalent to
+ <command>udevadm /sys/class/block/sda</command>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-n</option></term>
+ <term><option>--name=<replaceable>FILE</replaceable></option></term>
+ <listitem>
+ <para>The name of the device node or a symlink to query,
+ e.g. <filename><optional>/dev</optional>/sda</filename>.
+ Note that this option usually is not very useful, since
+ <command>udev</command> can guess the type of the
+ argument, so <command>udevadm --name=sda</command> is
+ equivalent to <command>udevadm /dev/sda</command>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-r</option></term>
+ <term><option>--root</option></term>
+ <listitem>
+ <para>Print absolute paths in <command>name</command> or <command>symlink</command>
+ query.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-a</option></term>
+ <term><option>--attribute-walk</option></term>
+ <listitem>
+ <para>Print all sysfs properties of the specified device that can be used
+ in udev rules to match the specified device. It prints all devices
+ along the chain, up to the root of sysfs that can be used in udev rules.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-x</option></term>
+ <term><option>--export</option></term>
+ <listitem>
+ <para>Print output as key/value pairs. Values are enclosed in single quotes.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-P</option></term>
+ <term><option>--export-prefix=<replaceable>NAME</replaceable></option></term>
+ <listitem>
+ <para>Add a prefix to the key name of exported values.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-d</option></term>
+ <term><option>--device-id-of-file=<replaceable>FILE</replaceable></option></term>
+ <listitem>
+ <para>Print major/minor numbers of the underlying device, where the file
+ lives on.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-e</option></term>
+ <term><option>--export-db</option></term>
+ <listitem>
+ <para>Export the content of the udev database.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-c</option></term>
+ <term><option>--cleanup-db</option></term>
+ <listitem>
+ <para>Cleanup the udev database.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--version</option></term>
+ <listitem>
+ <para>Print version.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>In addition, an optional positional argument can be used
+ to specify a device name or a sys path. It must start with
+ <filename>/dev</filename> or <filename>/sys</filename>
+ respectively.</para>
+ </refsect2>
+
+ <refsect2><title>udevadm trigger
+ <arg choice="opt"><replaceable>options</replaceable></arg>
+ <arg choice="opt" rep="repeat"><replaceable>devpath</replaceable>|<replaceable>file</replaceable></arg></title>
+ <para>Request device events from the kernel. Primarily used to replay events at system coldplug time.</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>-v</option></term>
+ <term><option>--verbose</option></term>
+ <listitem>
+ <para>Print the list of devices which will be triggered.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-n</option></term>
+ <term><option>--dry-run</option></term>
+ <listitem>
+ <para>Do not actually trigger the event.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-t</option></term>
+ <term><option>--type=<replaceable>TYPE</replaceable></option></term>
+ <listitem>
+ <para>Trigger a specific type of devices. Valid types are:
+ <command>devices</command>, <command>subsystems</command>.
+ The default value is <command>devices</command>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-c</option></term>
+ <term><option>--action=<replaceable>ACTION</replaceable></option></term>
+ <listitem>
+ <para>Type of event to be triggered. The default value is
+ <command>change</command>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-s</option></term>
+ <term><option>--subsystem-match=<replaceable>SUBSYSTEM</replaceable></option></term>
+ <listitem>
+ <para>Trigger events for devices which belong to a
+ matching subsystem. This option can be specified multiple
+ times and supports shell style pattern matching.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-S</option></term>
+ <term><option>--subsystem-nomatch=<replaceable>SUBSYSTEM</replaceable></option></term>
+ <listitem>
+ <para>Do not trigger events for devices which belong to a matching subsystem. This option
+ can be specified multiple times and supports shell style pattern matching.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-a</option></term>
+ <term><option>--attr-match=<replaceable>ATTRIBUTE</replaceable>=<replaceable>VALUE</replaceable></option></term>
+ <listitem>
+ <para>Trigger events for devices with a matching sysfs
+ attribute. If a value is specified along with the
+ attribute name, the content of the attribute is matched
+ against the given value using shell style pattern
+ matching. If no value is specified, the existence of the
+ sysfs attribute is checked. This option can be specified
+ multiple times.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-A</option></term>
+ <term><option>--attr-nomatch=<replaceable>ATTRIBUTE</replaceable>=<replaceable>VALUE</replaceable></option></term>
+ <listitem>
+ <para>Do not trigger events for devices with a matching
+ sysfs attribute. If a value is specified along with the
+ attribute name, the content of the attribute is matched
+ against the given value using shell style pattern
+ matching. If no value is specified, the existence of the
+ sysfs attribute is checked. This option can be specified
+ multiple times.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-p</option></term>
+ <term><option>--property-match=<replaceable>PROPERTY</replaceable>=<replaceable>VALUE</replaceable></option></term>
+ <listitem>
+ <para>Trigger events for devices with a matching property
+ value. This option can be specified multiple times and
+ supports shell style pattern matching.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-g</option></term>
+ <term><option>--tag-match=<replaceable>PROPERTY</replaceable></option></term>
+ <listitem>
+ <para>Trigger events for devices with a matching tag. This
+ option can be specified multiple times.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-y</option></term>
+ <term><option>--sysname-match=<replaceable>PATH</replaceable></option></term>
+ <listitem>
+ <para>Trigger events for devices with a matching sys
+ device path. This option can be specified multiple times
+ and supports shell style pattern matching.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--name-match=<replaceable>NAME</replaceable></option></term>
+ <listitem>
+ <para>Trigger events for devices with a matching
+ device path. This option can be specified multiple
+ times.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-b</option></term>
+ <term><option>--parent-match=<replaceable>SYSPATH</replaceable></option></term>
+ <listitem>
+ <para>Trigger events for all children of a given
+ device.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>In addition, optional positional arguments can be used
+ to specify device names or sys paths. They must start with
+ <filename>/dev</filename> or <filename>/sys</filename>
+ respectively.</para>
+ </refsect2>
+
+ <refsect2><title>udevadm settle
+ <arg choice="opt"><replaceable>options</replaceable></arg>
+ </title>
+ <para>Watches the udev event queue, and exits if all current events are handled.</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>-t</option></term>
+ <term><option>--timeout=<replaceable>SECONDS</replaceable></option></term>
+ <listitem>
+ <para>Maximum number of seconds to wait for the event
+ queue to become empty. The default value is 120 seconds. A
+ value of 0 will check if the queue is empty and always
+ return immediately.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-E</option></term>
+ <term><option>--exit-if-exists=<replaceable>FILE</replaceable></option></term>
+ <listitem>
+ <para>Stop waiting if file exists.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
+ <refsect2><title>udevadm control <replaceable>command</replaceable></title>
+ <para>Modify the internal state of the running udev daemon.</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>-x</option></term>
+ <term><option>--exit</option></term>
+ <listitem>
+ <para>Signal and wait for systemd-udevd to exit.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-l</option></term>
+ <term><option>--log-priority=<replaceable>value</replaceable></option></term>
+ <listitem>
+ <para>Set the internal log level of
+ <filename>systemd-udevd</filename>. Valid values are the
+ numerical syslog priorities or their textual
+ representations: <option>emerg</option>,
+ <option>alert</option>, <option>crit</option>,
+ <option>err</option>, <option>warning</option>,
+ <option>notice</option>, <option>info</option>, and
+ <option>debug</option>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-s</option></term>
+ <term><option>--stop-exec-queue</option></term>
+ <listitem>
+ <para>Signal systemd-udevd to stop executing new events. Incoming events
+ will be queued.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-S</option></term>
+ <term><option>--start-exec-queue</option></term>
+ <listitem>
+ <para>Signal systemd-udevd to enable the execution of events.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-R</option></term>
+ <term><option>--reload</option></term>
+ <listitem>
+ <para>Signal systemd-udevd to reload the rules files and other databases like the kernel
+ module index. Reloading rules and databases does not apply any changes to already
+ existing devices; the new configuration will only be applied to new events.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-p</option></term>
+ <term><option>--property=<replaceable>KEY</replaceable>=<replaceable>value</replaceable></option></term>
+ <listitem>
+ <para>Set a global property for all events.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-m</option></term>
+ <term><option>--children-max=</option><replaceable>value</replaceable></term>
+ <listitem>
+ <para>Set the maximum number of events, systemd-udevd will handle at the
+ same time.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--timeout=</option><replaceable>seconds</replaceable></term>
+ <listitem>
+ <para>The maximum number of seconds to wait for a reply from systemd-udevd.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
+ <refsect2><title>udevadm monitor
+ <arg choice="opt"><replaceable>options</replaceable></arg>
+ </title>
+ <para>Listens to the kernel uevents and events sent out by a udev rule
+ and prints the devpath of the event to the console. It can be used to analyze the
+ event timing, by comparing the timestamps of the kernel uevent and the udev event.
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><option>-k</option></term>
+ <term><option>--kernel</option></term>
+ <listitem>
+ <para>Print the kernel uevents.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-u</option></term>
+ <term><option>--udev</option></term>
+ <listitem>
+ <para>Print the udev event after the rule processing.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-p</option></term>
+ <term><option>--property</option></term>
+ <listitem>
+ <para>Also print the properties of the event.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-s</option></term>
+ <term><option>--subsystem-match=<replaceable>string[/string]</replaceable></option></term>
+ <listitem>
+ <para>Filter events by subsystem[/devtype]. Only udev events with a matching subsystem value will pass.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-t</option></term>
+ <term><option>--tag-match=<replaceable>string</replaceable></option></term>
+ <listitem>
+ <para>Filter events by property. Only udev events with a given tag attached will pass.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
+ <refsect2><title>udevadm test
+ <arg choice="opt"><replaceable>options</replaceable></arg>
+ <arg><replaceable>devpath</replaceable></arg>
+ </title>
+ <para>Simulate a udev event run for the given device, and print debug output.</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>-a</option></term>
+ <term><option>--action=<replaceable>string</replaceable></option></term>
+ <listitem>
+ <para>The action string.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-N</option></term>
+ <term><option>--resolve-names=<constant>early</constant>|<constant>late</constant>|<constant>never</constant></option></term>
+ <listitem>
+ <para>Specify when udevadm should resolve names of users
+ and groups. When set to <constant>early</constant> (the
+ default), names will be resolved when the rules are
+ parsed. When set to <constant>late</constant>, names will
+ be resolved for every event. When set to
+ <constant>never</constant>, names will never be resolved
+ and all devices will be owned by root.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
+ <refsect2><title>udevadm test-builtin
+ <arg choice="opt"><replaceable>options</replaceable></arg>
+ <arg><replaceable>command</replaceable></arg>
+ <arg><replaceable>devpath</replaceable></arg>
+ </title>
+ <para>Run a built-in command <replaceable>COMMAND</replaceable>
+ for device <replaceable>DEVPATH</replaceable>, and print debug
+ output.</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para><citerefentry>
+ <refentrytitle>udev</refentrytitle><manvolnum>7</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>systemd-udevd.service</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry></para>
+ </refsect1>
+</refentry>
diff --git a/src/grp-udev/v4l_id/v4l_id.c b/src/grp-udev/v4l_id/v4l_id.c
new file mode 100644
index 0000000000..aec6676a33
--- /dev/null
+++ b/src/grp-udev/v4l_id/v4l_id.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2009 Kay Sievers <kay@vrfy.org>
+ * Copyright (c) 2009 Filippo Argiolas <filippo.argiolas@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details:
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <linux/videodev2.h>
+
+#include "fd-util.h"
+#include "util.h"
+
+int main(int argc, char *argv[]) {
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ {}
+ };
+ _cleanup_close_ int fd = -1;
+ char *device;
+ struct v4l2_capability v2cap;
+ int c;
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+
+ switch (c) {
+ case 'h':
+ printf("%s [-h,--help] <device file>\n\n"
+ "Video4Linux device identification.\n\n"
+ " -h Print this message\n"
+ , program_invocation_short_name);
+ return 0;
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option");
+ }
+
+ device = argv[optind];
+ if (device == NULL)
+ return 2;
+
+ fd = open(device, O_RDONLY);
+ if (fd < 0)
+ return 3;
+
+ if (ioctl(fd, VIDIOC_QUERYCAP, &v2cap) == 0) {
+ printf("ID_V4L_VERSION=2\n");
+ printf("ID_V4L_PRODUCT=%s\n", v2cap.card);
+ printf("ID_V4L_CAPABILITIES=:");
+ if ((v2cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) > 0)
+ printf("capture:");
+ if ((v2cap.capabilities & V4L2_CAP_VIDEO_OUTPUT) > 0)
+ printf("video_output:");
+ if ((v2cap.capabilities & V4L2_CAP_VIDEO_OVERLAY) > 0)
+ printf("video_overlay:");
+ if ((v2cap.capabilities & V4L2_CAP_AUDIO) > 0)
+ printf("audio:");
+ if ((v2cap.capabilities & V4L2_CAP_TUNER) > 0)
+ printf("tuner:");
+ if ((v2cap.capabilities & V4L2_CAP_RADIO) > 0)
+ printf("radio:");
+ printf("\n");
+ }
+
+ return 0;
+}