diff options
Diffstat (limited to 'src/udev')
54 files changed, 16716 insertions, 0 deletions
diff --git a/src/udev/.gitignore b/src/udev/.gitignore new file mode 100644 index 0000000000..3e375a7726 --- /dev/null +++ b/src/udev/.gitignore @@ -0,0 +1 @@ +/udev.pc diff --git a/src/udev/.vimrc b/src/udev/.vimrc new file mode 100644 index 0000000000..366fbdca4b --- /dev/null +++ b/src/udev/.vimrc @@ -0,0 +1,4 @@ +" 'set exrc' in ~/.vimrc will read .vimrc from the current directory +set tabstop=8 +set shiftwidth=8 +set expandtab diff --git a/src/udev/Makefile b/src/udev/Makefile new file mode 120000 index 0000000000..d0b0e8e008 --- /dev/null +++ b/src/udev/Makefile @@ -0,0 +1 @@ +../Makefile
\ No newline at end of file diff --git a/src/udev/accelerometer/accelerometer.c b/src/udev/accelerometer/accelerometer.c new file mode 100644 index 0000000000..67fed27c5e --- /dev/null +++ b/src/udev/accelerometer/accelerometer.c @@ -0,0 +1,358 @@ +/* + * accelerometer - exports device orientation through property + * + * When an "change" event is received on an accelerometer, + * open its device node, and from the value, as well as the previous + * value of the property, calculate the device's new orientation, + * and export it as ID_INPUT_ACCELEROMETER_ORIENTATION. + * + * Possible values are: + * undefined + * * normal + * * bottom-up + * * left-up + * * right-up + * + * The property will be persistent across sessions, and the new + * orientations can be deducted from the previous one (it allows + * for a threshold for switching between opposite ends of the + * orientation). + * + * Copyright (C) 2011 Red Hat, Inc. + * Author: + * Bastien Nocera <hadess@hadess.net> + * + * orientation_calc() from the sensorfw package + * Copyright (C) 2009-2010 Nokia Corporation + * Authors: + * Üstün Ergenoglu <ext-ustun.ergenoglu@nokia.com> + * Timo Rongas <ext-timo.2.rongas@nokia.com> + * Lihan Guo <lihan.guo@digia.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 keymap; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include <stdio.h> +#include <string.h> +#include <math.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <limits.h> +#include <linux/limits.h> +#include <linux/input.h> + +#include "libudev.h" +#include "libudev-private.h" + +/* we must use this kernel-compatible implementation */ +#define BITS_PER_LONG (sizeof(unsigned long) * 8) +#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1) +#define OFF(x) ((x)%BITS_PER_LONG) +#define BIT(x) (1UL<<OFF(x)) +#define LONG(x) ((x)/BITS_PER_LONG) +#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1) + +static int debug = 0; + +static void log_fn(struct udev *udev, int priority, + const char *file, int line, const char *fn, + const char *format, va_list args) +{ + if (debug) { + fprintf(stderr, "%s: ", fn); + vfprintf(stderr, format, args); + } else { + vsyslog(priority, format, args); + } +} + +typedef enum { + ORIENTATION_UNDEFINED, + ORIENTATION_NORMAL, + ORIENTATION_BOTTOM_UP, + ORIENTATION_LEFT_UP, + ORIENTATION_RIGHT_UP +} OrientationUp; + +static const char *orientations[] = { + "undefined", + "normal", + "bottom-up", + "left-up", + "right-up", + NULL +}; + +#define ORIENTATION_UP_UP ORIENTATION_NORMAL + +#define DEFAULT_THRESHOLD 250 +#define RADIANS_TO_DEGREES 180.0/M_PI +#define SAME_AXIS_LIMIT 5 + +#define THRESHOLD_LANDSCAPE 25 +#define THRESHOLD_PORTRAIT 20 + +static const char * +orientation_to_string (OrientationUp o) +{ + return orientations[o]; +} + +static OrientationUp +string_to_orientation (const char *orientation) +{ + int i; + + if (orientation == NULL) + return ORIENTATION_UNDEFINED; + for (i = 0; orientations[i] != NULL; i++) { + if (strcmp (orientation, orientations[i]) == 0) + return i; + } + return ORIENTATION_UNDEFINED; +} + +static OrientationUp +orientation_calc (OrientationUp prev, + int x, int y, int z) +{ + int rotation; + OrientationUp ret = prev; + + /* Portrait check */ + rotation = round(atan((double) x / sqrt(y * y + z * z)) * RADIANS_TO_DEGREES); + + if (abs(rotation) > THRESHOLD_PORTRAIT) { + ret = (rotation < 0) ? ORIENTATION_LEFT_UP : ORIENTATION_RIGHT_UP; + + /* Some threshold to switching between portrait modes */ + if (prev == ORIENTATION_LEFT_UP || prev == ORIENTATION_RIGHT_UP) { + if (abs(rotation) < SAME_AXIS_LIMIT) { + ret = prev; + } + } + + } else { + /* Landscape check */ + rotation = round(atan((double) y / sqrt(x * x + z * z)) * RADIANS_TO_DEGREES); + + if (abs(rotation) > THRESHOLD_LANDSCAPE) { + ret = (rotation < 0) ? ORIENTATION_BOTTOM_UP : ORIENTATION_NORMAL; + + /* Some threshold to switching between landscape modes */ + if (prev == ORIENTATION_BOTTOM_UP || prev == ORIENTATION_NORMAL) { + if (abs(rotation) < SAME_AXIS_LIMIT) { + ret = prev; + } + } + } + } + + return ret; +} + +static OrientationUp +get_prev_orientation(struct udev_device *dev) +{ + const char *value; + + value = udev_device_get_property_value(dev, "ID_INPUT_ACCELEROMETER_ORIENTATION"); + if (value == NULL) + return ORIENTATION_UNDEFINED; + return string_to_orientation(value); +} + +#define SET_AXIS(axis, code_) if (ev[i].code == code_) { if (got_##axis == 0) { axis = ev[i].value; got_##axis = 1; } } + +/* accelerometers */ +static void test_orientation(struct udev *udev, + struct udev_device *dev, + const char *devpath) +{ + OrientationUp old, new; + int fd, r; + struct input_event ev[64]; + int got_syn = 0; + int got_x, got_y, got_z; + int x = 0, y = 0, z = 0; + char text[64]; + + old = get_prev_orientation(dev); + + if ((fd = open(devpath, O_RDONLY)) < 0) + return; + + got_x = got_y = got_z = 0; + + while (1) { + int i; + + r = read(fd, ev, sizeof(struct input_event) * 64); + + if (r < (int) sizeof(struct input_event)) { + close(fd); + return; + } + + for (i = 0; i < r / (int) sizeof(struct input_event); i++) { + if (got_syn == 1) { + if (ev[i].type == EV_ABS) { + SET_AXIS(x, ABS_X); + SET_AXIS(y, ABS_Y); + SET_AXIS(z, ABS_Z); + } + } + if (ev[i].type == EV_SYN && ev[i].code == SYN_REPORT) { + got_syn = 1; + } + if (got_x && got_y && got_z) + goto read_dev; + } + } + +read_dev: + close(fd); + + if (!got_x || !got_y || !got_z) + return; + + new = orientation_calc(old, x, y, z); + snprintf(text, sizeof(text), "ID_INPUT_ACCELEROMETER_ORIENTATION=%s", orientation_to_string(new)); + puts(text); +} + +static void help(void) +{ + printf("Usage: accelerometer [options] <device path>\n" + " --debug debug to stderr\n" + " --help print this help text\n\n"); +} + +int main (int argc, char** argv) +{ + struct udev *udev; + struct udev_device *dev; + + static const struct option options[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + + char devpath[PATH_MAX]; + char *devnode; + const char *id_path; + struct udev_enumerate *enumerate; + struct udev_list_entry *list_entry; + + udev = udev_new(); + if (udev == NULL) + return 1; + + log_open(); + udev_set_log_fn(udev, log_fn); + + /* CLI argument parsing */ + while (1) { + int option; + + option = getopt_long(argc, argv, "dxh", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'd': + debug = 1; + log_set_max_level(LOG_DEBUG); + udev_set_log_priority(udev, LOG_DEBUG); + break; + case 'h': + help(); + exit(0); + default: + exit(1); + } + } + + if (argv[optind] == NULL) { + help(); + exit(1); + } + + /* get the device */ + snprintf(devpath, sizeof(devpath), "/sys/%s", argv[optind]); + dev = udev_device_new_from_syspath(udev, devpath); + if (dev == NULL) { + fprintf(stderr, "unable to access '%s'\n", devpath); + return 1; + } + + id_path = udev_device_get_property_value(dev, "ID_PATH"); + if (id_path == NULL) { + fprintf (stderr, "unable to get property ID_PATH for '%s'", devpath); + return 0; + } + + /* Get the children devices and find the devnode */ + /* FIXME: use udev_enumerate_add_match_parent() instead */ + devnode = NULL; + enumerate = udev_enumerate_new(udev); + udev_enumerate_add_match_property(enumerate, "ID_PATH", id_path); + udev_enumerate_add_match_subsystem(enumerate, "input"); + udev_enumerate_scan_devices(enumerate); + udev_list_entry_foreach(list_entry, udev_enumerate_get_list_entry(enumerate)) { + struct udev_device *device; + const char *node; + + device = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerate), + udev_list_entry_get_name(list_entry)); + if (device == NULL) + continue; + /* Already found it */ + if (devnode != NULL) { + udev_device_unref(device); + continue; + } + + node = udev_device_get_devnode(device); + if (node == NULL) { + udev_device_unref(device); + continue; + } + /* Use the event sub-device */ + if (strstr(node, "/event") == NULL) { + udev_device_unref(device); + continue; + } + + devnode = strdup(node); + udev_device_unref(device); + } + + if (devnode == NULL) { + fprintf(stderr, "unable to get device node for '%s'\n", devpath); + return 0; + } + + log_debug("opening accelerometer device %s\n", devnode); + test_orientation(udev, dev, devnode); + free(devnode); + log_close(); + return 0; +} diff --git a/src/udev/ata_id/ata_id.c b/src/udev/ata_id/ata_id.c new file mode 100644 index 0000000000..488fed4ac4 --- /dev/null +++ b/src/udev/ata_id/ata_id.c @@ -0,0 +1,714 @@ +/* + * 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 <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> +#include <scsi/scsi.h> +#include <scsi/sg.h> +#include <scsi/scsi_ioctl.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <linux/types.h> +#include <linux/hdreg.h> +#include <linux/fs.h> +#include <linux/cdrom.h> +#include <linux/bsg.h> +#include <arpa/inet.h> + +#include "libudev.h" +#include "libudev-private.h" +#include "log.h" + +#define COMMAND_TIMEOUT_MSEC (30 * 1000) + +static int disk_scsi_inquiry_command(int fd, + void *buf, + size_t buf_len) +{ + struct sg_io_v4 io_v4; + uint8_t cdb[6]; + uint8_t sense[32]; + int ret; + + /* + * INQUIRY, see SPC-4 section 6.4 + */ + memset(cdb, 0, sizeof(cdb)); + cdb[0] = 0x12; /* OPERATION CODE: INQUIRY */ + cdb[3] = (buf_len >> 8); /* ALLOCATION LENGTH */ + cdb[4] = (buf_len & 0xff); + + memset(sense, 0, sizeof(sense)); + + memset(&io_v4, 0, 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 (cdb); + io_v4.request = (uintptr_t) cdb; + io_v4.max_response_len = sizeof (sense); + io_v4.response = (uintptr_t) sense; + io_v4.din_xfer_len = buf_len; + io_v4.din_xferp = (uintptr_t) buf; + io_v4.timeout = COMMAND_TIMEOUT_MSEC; + + 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; + + memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); + io_hdr.interface_id = 'S'; + io_hdr.cmdp = (unsigned char*) cdb; + io_hdr.cmd_len = sizeof (cdb); + io_hdr.dxferp = buf; + io_hdr.dxfer_len = buf_len; + io_hdr.sbp = sense; + io_hdr.mx_sb_len = sizeof (sense); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.timeout = COMMAND_TIMEOUT_MSEC; + + ret = ioctl(fd, SG_IO, &io_hdr); + if (ret != 0) + goto out; + + /* 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; + ret = -1; + goto out; + } + } else { + goto out; + } + } + + /* 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; + ret = -1; + goto out; + } + + out: + return ret; +} + +static int disk_identify_command(int fd, + void *buf, + size_t buf_len) +{ + struct sg_io_v4 io_v4; + uint8_t cdb[12]; + uint8_t sense[32]; + uint8_t *desc = sense+8; + int ret; + + /* + * 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 + */ + memset(cdb, 0, sizeof(cdb)); + cdb[0] = 0xa1; /* OPERATION CODE: 12 byte pass through */ + cdb[1] = 4 << 1; /* PROTOCOL: PIO Data-in */ + cdb[2] = 0x2e; /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */ + cdb[3] = 0; /* FEATURES */ + cdb[4] = 1; /* SECTORS */ + cdb[5] = 0; /* LBA LOW */ + cdb[6] = 0; /* LBA MID */ + cdb[7] = 0; /* LBA HIGH */ + cdb[8] = 0 & 0x4F; /* SELECT */ + cdb[9] = 0xEC; /* Command: ATA IDENTIFY DEVICE */; + memset(sense, 0, sizeof(sense)); + + memset(&io_v4, 0, 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 (cdb); + io_v4.request = (uintptr_t) cdb; + io_v4.max_response_len = sizeof (sense); + io_v4.response = (uintptr_t) sense; + io_v4.din_xfer_len = buf_len; + io_v4.din_xferp = (uintptr_t) buf; + io_v4.timeout = COMMAND_TIMEOUT_MSEC; + + 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; + + memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); + io_hdr.interface_id = 'S'; + io_hdr.cmdp = (unsigned char*) cdb; + io_hdr.cmd_len = sizeof (cdb); + io_hdr.dxferp = buf; + io_hdr.dxfer_len = buf_len; + io_hdr.sbp = sense; + io_hdr.mx_sb_len = sizeof (sense); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.timeout = COMMAND_TIMEOUT_MSEC; + + ret = ioctl(fd, SG_IO, &io_hdr); + if (ret != 0) + goto out; + } else { + goto out; + } + } + + if (!(sense[0] == 0x72 && desc[0] == 0x9 && desc[1] == 0x0c)) { + errno = EIO; + ret = -1; + goto out; + } + + out: + return ret; +} + +static int disk_identify_packet_device_command(int fd, + void *buf, + size_t buf_len) +{ + struct sg_io_v4 io_v4; + uint8_t cdb[16]; + uint8_t sense[32]; + uint8_t *desc = sense+8; + int ret; + + /* + * 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 + */ + memset(cdb, 0, sizeof(cdb)); + cdb[0] = 0x85; /* OPERATION CODE: 16 byte pass through */ + cdb[1] = 4 << 1; /* PROTOCOL: PIO Data-in */ + cdb[2] = 0x2e; /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */ + cdb[3] = 0; /* FEATURES */ + cdb[4] = 0; /* FEATURES */ + cdb[5] = 0; /* SECTORS */ + cdb[6] = 1; /* SECTORS */ + cdb[7] = 0; /* LBA LOW */ + cdb[8] = 0; /* LBA LOW */ + cdb[9] = 0; /* LBA MID */ + cdb[10] = 0; /* LBA MID */ + cdb[11] = 0; /* LBA HIGH */ + cdb[12] = 0; /* LBA HIGH */ + cdb[13] = 0; /* DEVICE */ + cdb[14] = 0xA1; /* Command: ATA IDENTIFY PACKET DEVICE */; + cdb[15] = 0; /* CONTROL */ + memset(sense, 0, sizeof(sense)); + + memset(&io_v4, 0, 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 (cdb); + io_v4.request = (uintptr_t) cdb; + io_v4.max_response_len = sizeof (sense); + io_v4.response = (uintptr_t) sense; + io_v4.din_xfer_len = buf_len; + io_v4.din_xferp = (uintptr_t) buf; + io_v4.timeout = COMMAND_TIMEOUT_MSEC; + + 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; + + memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); + io_hdr.interface_id = 'S'; + io_hdr.cmdp = (unsigned char*) cdb; + io_hdr.cmd_len = sizeof (cdb); + io_hdr.dxferp = buf; + io_hdr.dxfer_len = buf_len; + io_hdr.sbp = sense; + io_hdr.mx_sb_len = sizeof (sense); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.timeout = COMMAND_TIMEOUT_MSEC; + + ret = ioctl(fd, SG_IO, &io_hdr); + if (ret != 0) + goto out; + } else { + goto out; + } + } + + if (!(sense[0] == 0x72 && desc[0] == 0x9 && desc[1] == 0x0c)) { + errno = EIO; + ret = -1; + goto out; + } + + out: + return ret; +} + +/** + * 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; + + /* init results */ + memset(out_identify, '\0', 512); + is_packet_device = 0; + + /* 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; +} + +static void log_fn(struct udev *udev, int priority, + const char *file, int line, const char *fn, + const char *format, va_list args) +{ + vsyslog(priority, format, args); +} + +int main(int argc, char *argv[]) +{ + struct udev *udev; + struct hd_driveid id; + uint8_t identify[512]; + uint16_t *identify_words; + char model[41]; + char model_enc[256]; + char serial[21]; + char revision[9]; + const char *node = NULL; + int export = 0; + int fd; + uint16_t word; + int rc = 0; + int is_packet_device = 0; + static const struct option options[] = { + { "export", no_argument, NULL, 'x' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + + udev = udev_new(); + if (udev == NULL) + goto exit; + + log_open(); + udev_set_log_fn(udev, log_fn); + + while (1) { + 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" + " --export print values as environment keys\n" + " --help print this help text\n\n"); + goto exit; + } + } + + node = argv[optind]; + if (node == NULL) { + log_error("no node specified\n"); + rc = 1; + goto exit; + } + + fd = open(node, O_RDONLY|O_NONBLOCK); + if (fd < 0) { + log_error("unable to open '%s'\n", node); + rc = 1; + goto exit; + } + + if (disk_identify(udev, fd, identify, &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, 10, 20); /* serial */ + disk_identify_fixup_string(identify, 23, 8); /* fwrev */ + disk_identify_fixup_string(identify, 27, 40); /* model */ + disk_identify_fixup_uint16(identify, 0); /* configuration */ + disk_identify_fixup_uint16(identify, 75); /* queue depth */ + disk_identify_fixup_uint16(identify, 75); /* SATA capabilities */ + disk_identify_fixup_uint16(identify, 82); /* command set supported */ + disk_identify_fixup_uint16(identify, 83); /* command set supported */ + disk_identify_fixup_uint16(identify, 84); /* command set supported */ + disk_identify_fixup_uint16(identify, 85); /* command set supported */ + disk_identify_fixup_uint16(identify, 86); /* command set supported */ + disk_identify_fixup_uint16(identify, 87); /* command set supported */ + disk_identify_fixup_uint16(identify, 89); /* time required for SECURITY ERASE UNIT */ + disk_identify_fixup_uint16(identify, 90); /* time required for enhanced SECURITY ERASE UNIT */ + disk_identify_fixup_uint16(identify, 91); /* current APM values */ + disk_identify_fixup_uint16(identify, 94); /* current AAM value */ + disk_identify_fixup_uint16(identify, 128); /* device lock function */ + disk_identify_fixup_uint16(identify, 217); /* nominal media rotation rate */ + memcpy(&id, identify, sizeof id); + } else { + /* If this fails, then try HDIO_GET_IDENTITY */ + if (ioctl(fd, HDIO_GET_IDENTITY, &id) != 0) { + log_info("HDIO_GET_IDENTITY failed for '%s': %m\n", node); + rc = 2; + goto close; + } + } + identify_words = (uint16_t *) identify; + + 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 = *((uint16_t *) identify + 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 = *((uint16_t *) identify + 217); + if (word != 0x0000) { + 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 = *((uint16_t *) identify + 108); + if ((word & 0xf000) == 0x5000) { + uint64_t wwwn; + + wwwn = *((uint16_t *) identify + 108); + wwwn <<= 16; + wwwn |= *((uint16_t *) identify + 109); + wwwn <<= 16; + wwwn |= *((uint16_t *) identify + 110); + wwwn <<= 16; + wwwn |= *((uint16_t *) identify + 111); + printf("ID_WWN=0x%llx\n", (unsigned long long int) wwwn); + /* ATA devices have no vendor extension */ + printf("ID_WWN_WITH_EXTENSION=0x%llx\n", (unsigned long long int) wwwn); + } + + /* from Linux's include/linux/ata.h */ + if (identify_words[0] == 0x848a || identify_words[0] == 0x844a) { + printf("ID_ATA_CFA=1\n"); + } else { + if ((identify_words[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); + } +close: + close(fd); +exit: + udev_unref(udev); + log_close(); + return rc; +} diff --git a/src/udev/cdrom_id/cdrom_id.c b/src/udev/cdrom_id/cdrom_id.c new file mode 100644 index 0000000000..1056536b7d --- /dev/null +++ b/src/udev/cdrom_id/cdrom_id.c @@ -0,0 +1,1099 @@ +/* + * 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/>. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <limits.h> +#include <fcntl.h> +#include <errno.h> +#include <getopt.h> +#include <time.h> +#include <scsi/sg.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <linux/cdrom.h> + +#include "libudev.h" +#include "libudev-private.h" + +static bool debug; + +static void log_fn(struct udev *udev, int priority, + const char *file, int line, const char *fn, + const char *format, va_list args) +{ + if (debug) { + fprintf(stderr, "%s: ", fn); + vfprintf(stderr, format, args); + } else { + vsyslog(priority, format, args); + } +} + +/* 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 -ENODEV; + + fp = fopen("/proc/self/mountinfo", "re"); + if (fp == NULL) + return -ENOSYS; + 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\n", cmd); + return; + } + log_debug("%s failed with SK=%Xh/ASC=%02Xh/ACQ=%02Xh\n", 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) +{ + memset(cmd, 0x00, 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\n"); + + err = ioctl(fd, CDROM_LOCKDOOR, lock ? 1 : 0); + if (err < 0) + log_debug("CDROM_LOCKDOOR failed\n"); + + 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\n"); + 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\n"); + 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\n"); + return -1; + } + + log_debug("INQUIRY: [%.8s][%.16s][%.4s]\n", 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 \n", cur_profile); + cd_media = 1; + cd_media_mo = 1; + break; + case 0x08: + log_debug("profile 0x%02x media_cd_rom\n", cur_profile); + cd_media = 1; + cd_media_cd_rom = 1; + break; + case 0x09: + log_debug("profile 0x%02x media_cd_r\n", cur_profile); + cd_media = 1; + cd_media_cd_r = 1; + break; + case 0x0a: + log_debug("profile 0x%02x media_cd_rw\n", cur_profile); + cd_media = 1; + cd_media_cd_rw = 1; + break; + case 0x10: + log_debug("profile 0x%02x media_dvd_ro\n", cur_profile); + cd_media = 1; + cd_media_dvd_rom = 1; + break; + case 0x11: + log_debug("profile 0x%02x media_dvd_r\n", cur_profile); + cd_media = 1; + cd_media_dvd_r = 1; + break; + case 0x12: + log_debug("profile 0x%02x media_dvd_ram\n", cur_profile); + cd_media = 1; + cd_media_dvd_ram = 1; + break; + case 0x13: + log_debug("profile 0x%02x media_dvd_rw_ro\n", 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\n", 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\n", cur_profile); + cd_media = 1; + cd_media_dvd_plus_r = 1; + break; + case 0x1A: + log_debug("profile 0x%02x media_dvd_plus_rw\n", cur_profile); + cd_media = 1; + cd_media_dvd_plus_rw = 1; + break; + case 0x2A: + log_debug("profile 0x%02x media_dvd_plus_rw_dl\n", 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\n", cur_profile); + cd_media = 1; + cd_media_dvd_plus_r_dl = 1; + break; + case 0x40: + log_debug("profile 0x%02x media_bd\n", cur_profile); + cd_media = 1; + cd_media_bd = 1; + break; + case 0x41: + case 0x42: + log_debug("profile 0x%02x media_bd_r\n", cur_profile); + cd_media = 1; + cd_media_bd_r = 1; + break; + case 0x43: + log_debug("profile 0x%02x media_bd_re\n", cur_profile); + cd_media = 1; + cd_media_bd_re = 1; + break; + case 0x50: + log_debug("profile 0x%02x media_hddvd\n", cur_profile); + cd_media = 1; + cd_media_hddvd = 1; + break; + case 0x51: + log_debug("profile 0x%02x media_hddvd_r\n", cur_profile); + cd_media = 1; + cd_media_hddvd_r = 1; + break; + case 0x52: + log_debug("profile 0x%02x media_hddvd_rw\n", cur_profile); + cd_media = 1; + cd_media_hddvd_rw = 1; + break; + default: + log_debug("profile 0x%02x <ignored>\n", 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\n", profile); + cd_mo = 1; + break; + case 0x08: + log_debug("profile 0x%02x cd_rom\n", profile); + cd_cd_rom = 1; + break; + case 0x09: + log_debug("profile 0x%02x cd_r\n", profile); + cd_cd_r = 1; + break; + case 0x0A: + log_debug("profile 0x%02x cd_rw\n", profile); + cd_cd_rw = 1; + break; + case 0x10: + log_debug("profile 0x%02x dvd_rom\n", profile); + cd_dvd_rom = 1; + break; + case 0x12: + log_debug("profile 0x%02x dvd_ram\n", profile); + cd_dvd_ram = 1; + break; + case 0x13: + case 0x14: + log_debug("profile 0x%02x dvd_rw\n", profile); + cd_dvd_rw = 1; + break; + case 0x1B: + log_debug("profile 0x%02x dvd_plus_r\n", profile); + cd_dvd_plus_r = 1; + break; + case 0x1A: + log_debug("profile 0x%02x dvd_plus_rw\n", profile); + cd_dvd_plus_rw = 1; + break; + case 0x2A: + log_debug("profile 0x%02x dvd_plus_rw_dl\n", profile); + cd_dvd_plus_rw_dl = 1; + break; + case 0x2B: + log_debug("profile 0x%02x dvd_plus_r_dl\n", profile); + cd_dvd_plus_r_dl = 1; + break; + case 0x40: + cd_bd = 1; + log_debug("profile 0x%02x bd\n", profile); + break; + case 0x41: + case 0x42: + cd_bd_r = 1; + log_debug("profile 0x%02x bd_r\n", profile); + break; + case 0x43: + cd_bd_re = 1; + log_debug("profile 0x%02x bd_re\n", profile); + break; + case 0x50: + cd_hddvd = 1; + log_debug("profile 0x%02x hddvd\n", profile); + break; + case 0x51: + cd_hddvd_r = 1; + log_debug("profile 0x%02x hddvd_r\n", profile); + break; + case 0x52: + cd_hddvd_rw = 1; + log_debug("profile 0x%02x hddvd_rw\n", profile); + break; + default: + log_debug("profile 0x%02x <ignored>\n", 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\n"); + cd_media_cd_rom = 1; + return 0; + } else { + log_debug("no current profile, assuming no media\n"); + return -1; + } + }; + + cd_media = 1; + + if (header[2] & 16) { + cd_media_cd_rw = 1; + log_debug("profile 0x0a media_cd_rw\n"); + } else if ((header[2] & 3) < 2 && cd_cd_r) { + cd_media_cd_r = 1; + log_debug("profile 0x09 media_cd_r\n"); + } else { + cd_media_cd_rom = 1; + log_debug("profile 0x08 media_cd_rom\n"); + } + 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) { + log_debug("drive is pre-MMC2 and does not support 46h get configuration command\n"); + log_debug("trying to work around the problem\n"); + 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\n", cur_profile); + feature_profile_media (udev, cur_profile); + ret = 0; /* we have media */ + } else { + log_debug("no current profile, assuming no media\n"); + } + + len = features[0] << 24 | features[1] << 16 | features[2] << 8 | features[3]; + log_debug("GET CONFIGURATION: size of features buffer 0x%04x\n", len); + + if (len > sizeof(features)) { + log_debug("can not get features in a single query, truncating\n"); + 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\n", len); + + if (len > sizeof(features)) { + log_debug("can not get features in a single query, truncating\n"); + 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\n", features[i+3] / 4); + feature_profiles(udev, &features[i]+4, features[i+3]); + break; + default: + log_debug("GET CONFIGURATION: feature 0x%04x <ignored>, with 0x%02x bytes\n", 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\n", header[8]); + log_debug("hardware reported media status: %s\n", 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 result, len; + int block, 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\n"); + 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\n"); + return -1; + } + + switch(format[8] & 3) { + case 1: + log_debug("unformatted DVD-RAM media inserted\n"); + /* 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\n"); + break; + + case 3: + cd_media = 0; //return no media + log_debug("format capacities returned no media\n"); + 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 */ + result = 0; + + for (block = 32768; block >= 0 && !result; block -= 32768) { + offset = block; + while (offset < (block + 2048) && !result) { + result = buffer [offset]; + offset++; + } + } + + if (!result) { + cd_media_state = media_status[0]; + log_debug("no data in blocks 0 or 16, assuming blank\n"); + } else { + log_debug("data in blocks 0 or 16, assuming complete\n"); + } + } + +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\n", 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\n", + 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\n", 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; + + udev = udev_new(); + if (udev == NULL) + goto exit; + + log_open(); + udev_set_log_fn(udev, log_fn); + + while (1) { + 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': + debug = true; + log_set_max_level(LOG_DEBUG); + udev_set_log_priority(udev, LOG_DEBUG); + break; + case 'h': + printf("Usage: cdrom_id [options] <device>\n" + " --lock-media lock the media (to enable eject request events)\n" + " --unlock-media unlock the media\n" + " --eject-media eject the media\n" + " --debug debug to stderr\n" + " --help print this help text\n\n"); + goto exit; + default: + rc = 1; + goto exit; + } + } + + node = argv[optind]; + if (!node) { + log_error("no device\n"); + fprintf(stderr, "no device\n"); + rc = 1; + goto exit; + } + + srand((unsigned int)getpid()); + for (cnt = 20; cnt > 0; cnt--) { + struct timespec duration; + + fd = open(node, O_RDONLY|O_NONBLOCK|(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'\n", node); + fprintf(stderr, "unable to open '%s'\n", node); + rc = 1; + goto exit; + } + log_debug("probing: '%s'\n", 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)\n"); + media_lock(udev, fd, true); + } + + if (unlock && cd_media) { + log_debug("PREVENT_ALLOW_MEDIUM_REMOVAL (unlock)\n"); + media_lock(udev, fd, false); + } + + if (eject) { + log_debug("PREVENT_ALLOW_MEDIUM_REMOVAL (unlock)\n"); + media_lock(udev, fd, false); + log_debug("START_STOP_UNIT (eject)\n"); + 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=%d\n", cd_media_session_next); + if (cd_media_session_count > 0) + printf("ID_CDROM_MEDIA_SESSION_COUNT=%d\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=%d\n", cd_media_track_count); + if (cd_media_track_count_audio > 0) + printf("ID_CDROM_MEDIA_TRACK_COUNT_AUDIO=%d\n", cd_media_track_count_audio); + if (cd_media_track_count_data > 0) + printf("ID_CDROM_MEDIA_TRACK_COUNT_DATA=%d\n", cd_media_track_count_data); +exit: + if (fd >= 0) + close(fd); + udev_unref(udev); + log_close(); + return rc; +} diff --git a/src/udev/collect/collect.c b/src/udev/collect/collect.c new file mode 100644 index 0000000000..3c46e40de1 --- /dev/null +++ b/src/udev/collect/collect.c @@ -0,0 +1,492 @@ +/* + * Collect variables across events. + * + * usage: collect [--add|--remove] <checkpoint> <id> <idlist> + * + * Adds ID <id> to the list governed by <checkpoint>. + * <id> must be part of the ID list <idlist>. + * If all IDs given by <idlist> are listed (ie collect has been + * invoked for each ID in <idlist>) collect returns 0, the + * number of missing IDs otherwise. + * A negative number is returned on error. + * + * Copyright(C) 2007, Hannes Reinecke <hare@suse.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <getopt.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "libudev.h" +#include "libudev-private.h" +#include "macro.h" + +#define BUFSIZE 16 +#define UDEV_ALARM_TIMEOUT 180 + +enum collect_state { + STATE_NONE, + STATE_OLD, + STATE_CONFIRMED, +}; + +struct _mate { + struct udev_list_node node; + char *name; + enum collect_state state; +}; + +static struct udev_list_node bunch; +static int debug; + +/* This can increase dynamically */ +static size_t bufsize = BUFSIZE; + +static inline struct _mate *node_to_mate(struct udev_list_node *node) +{ + return container_of(node, struct _mate, node); +} + +_noreturn_ static void sig_alrm(int signo) +{ + exit(4); +} + +static void usage(void) +{ + printf("usage: collect [--add|--remove] [--debug] <checkpoint> <id> <idlist>\n" + "\n" + " Adds ID <id> to the list governed by <checkpoint>.\n" + " <id> must be part of the list <idlist>.\n" + " If all IDs given by <idlist> are listed (ie collect has been\n" + " invoked for each ID in <idlist>) collect returns 0, the\n" + " number of missing IDs otherwise.\n" + " On error a negative number is returned.\n" + "\n"); +} + +/* + * prepare + * + * Prepares the database file + */ +static int prepare(char *dir, char *filename) +{ + struct stat statbuf; + char buf[512]; + int fd; + + if (stat(dir, &statbuf) < 0) + mkdir(dir, 0700); + + snprintf(buf, sizeof(buf), "%s/%s", dir, filename); + + fd = open(buf,O_RDWR|O_CREAT, S_IRUSR|S_IWUSR); + if (fd < 0) + fprintf(stderr, "Cannot open %s: %s\n", buf, strerror(errno)); + + if (lockf(fd,F_TLOCK,0) < 0) { + if (debug) + fprintf(stderr, "Lock taken, wait for %d seconds\n", UDEV_ALARM_TIMEOUT); + if (errno == EAGAIN || errno == EACCES) { + alarm(UDEV_ALARM_TIMEOUT); + lockf(fd, F_LOCK, 0); + if (debug) + fprintf(stderr, "Acquired lock on %s\n", buf); + } else { + if (debug) + fprintf(stderr, "Could not get lock on %s: %s\n", buf, strerror(errno)); + } + } + + return fd; +} + +/* + * Read checkpoint file + * + * Tricky reading this. We allocate a buffer twice as large + * as we're going to read. Then we read into the upper half + * of that buffer and start parsing. + * Once we do _not_ find end-of-work terminator (whitespace + * character) we move the upper half to the lower half, + * adjust the read pointer and read the next bit. + * Quite clever methinks :-) + * I should become a programmer ... + * + * Yes, one could have used fgets() for this. But then we'd + * have to use freopen etc which I found quite tedious. + */ +static int checkout(int fd) +{ + int len; + char *buf, *ptr, *word = NULL; + struct _mate *him; + + restart: + len = bufsize >> 1; + buf = calloc(1,bufsize + 1); + if (!buf) { + fprintf(stderr, "Out of memory.\n"); + return log_oom(); + } + memset(buf, ' ', bufsize); + ptr = buf + len; + while ((read(fd, buf + len, len)) > 0) { + while (ptr && *ptr) { + word = ptr; + ptr = strpbrk(word," \n\t\r"); + if (!ptr && word < (buf + len)) { + bufsize = bufsize << 1; + if (debug) + fprintf(stderr, "ID overflow, restarting with size %zi\n", bufsize); + free(buf); + lseek(fd, 0, SEEK_SET); + goto restart; + } + if (ptr) { + *ptr = '\0'; + ptr++; + if (!strlen(word)) + continue; + + if (debug) + fprintf(stderr, "Found word %s\n", word); + him = malloc(sizeof (struct _mate)); + if (!him) { + free(buf); + return log_oom(); + } + him->name = strdup(word); + if (!him->name) { + free(buf); + free(him); + return log_oom(); + } + him->state = STATE_OLD; + udev_list_node_append(&him->node, &bunch); + word = NULL; + } + } + memcpy(buf, buf + len, len); + memset(buf + len, ' ', len); + + if (!ptr) + ptr = word; + if (!ptr) + break; + ptr -= len; + } + + free(buf); + return 0; +} + +/* + * invite + * + * Adds a new ID 'us' to the internal list, + * marks it as confirmed. + */ +static void invite(char *us) +{ + struct udev_list_node *him_node; + struct _mate *who = NULL; + + if (debug) + fprintf(stderr, "Adding ID '%s'\n", us); + + udev_list_node_foreach(him_node, &bunch) { + struct _mate *him = node_to_mate(him_node); + + if (!strcmp(him->name, us)) { + him->state = STATE_CONFIRMED; + who = him; + } + } + if (debug && !who) + fprintf(stderr, "ID '%s' not in database\n", us); + +} + +/* + * reject + * + * Marks the ID 'us' as invalid, + * causing it to be removed when the + * list is written out. + */ +static void reject(char *us) +{ + struct udev_list_node *him_node; + struct _mate *who = NULL; + + if (debug) + fprintf(stderr, "Removing ID '%s'\n", us); + + udev_list_node_foreach(him_node, &bunch) { + struct _mate *him = node_to_mate(him_node); + + if (!strcmp(him->name, us)) { + him->state = STATE_NONE; + who = him; + } + } + if (debug && !who) + fprintf(stderr, "ID '%s' not in database\n", us); +} + +/* + * kickout + * + * Remove all IDs in the internal list which are not part + * of the list passed via the commandline. + */ +static void kickout(void) +{ + struct udev_list_node *him_node; + struct udev_list_node *tmp; + + udev_list_node_foreach_safe(him_node, tmp, &bunch) { + struct _mate *him = node_to_mate(him_node); + + if (him->state == STATE_OLD) { + udev_list_node_remove(&him->node); + free(him->name); + free(him); + } + } +} + +/* + * missing + * + * Counts all missing IDs in the internal list. + */ +static int missing(int fd) +{ + char *buf; + int ret = 0; + struct udev_list_node *him_node; + + buf = malloc(bufsize); + if (!buf) + return log_oom(); + + udev_list_node_foreach(him_node, &bunch) { + struct _mate *him = node_to_mate(him_node); + + if (him->state == STATE_NONE) { + ret++; + } else { + while (strlen(him->name)+1 >= bufsize) { + char *tmpbuf; + + bufsize = bufsize << 1; + tmpbuf = realloc(buf, bufsize); + if (!tmpbuf) { + free(buf); + return log_oom(); + } + buf = tmpbuf; + } + snprintf(buf, strlen(him->name)+2, "%s ", him->name); + if (write(fd, buf, strlen(buf)) < 0) { + free(buf); + return -1; + } + } + } + + free(buf); + return ret; +} + +/* + * everybody + * + * Prints out the status of the internal list. + */ +static void everybody(void) +{ + struct udev_list_node *him_node; + const char *state = ""; + + udev_list_node_foreach(him_node, &bunch) { + struct _mate *him = node_to_mate(him_node); + + switch (him->state) { + case STATE_NONE: + state = "none"; + break; + case STATE_OLD: + state = "old"; + break; + case STATE_CONFIRMED: + state = "confirmed"; + break; + } + fprintf(stderr, "ID: %s=%s\n", him->name, state); + } +} + +int main(int argc, char **argv) +{ + struct udev *udev; + static const struct option options[] = { + { "add", no_argument, NULL, 'a' }, + { "remove", no_argument, NULL, 'r' }, + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + int argi; + char *checkpoint, *us; + int fd; + int i; + int ret = EXIT_SUCCESS; + int prune = 0; + char tmpdir[UTIL_PATH_SIZE]; + + udev = udev_new(); + if (udev == NULL) { + ret = EXIT_FAILURE; + goto exit; + } + + while (1) { + int option; + + option = getopt_long(argc, argv, "ardh", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'a': + prune = 0; + break; + case 'r': + prune = 1; + break; + case 'd': + debug = 1; + break; + case 'h': + usage(); + goto exit; + default: + ret = 1; + goto exit; + } + } + + argi = optind; + if (argi + 2 > argc) { + printf("Missing parameter(s)\n"); + ret = 1; + goto exit; + } + checkpoint = argv[argi++]; + us = argv[argi++]; + + if (signal(SIGALRM, sig_alrm) == SIG_ERR) { + fprintf(stderr, "Cannot set SIGALRM: %s\n", strerror(errno)); + ret = 2; + goto exit; + } + + udev_list_node_init(&bunch); + + if (debug) + fprintf(stderr, "Using checkpoint '%s'\n", checkpoint); + + util_strscpyl(tmpdir, sizeof(tmpdir), "/run/udev/collect", NULL); + fd = prepare(tmpdir, checkpoint); + if (fd < 0) { + ret = 3; + goto out; + } + + if (checkout(fd) < 0) { + ret = 2; + goto out; + } + + for (i = argi; i < argc; i++) { + struct udev_list_node *him_node; + struct _mate *who; + + who = NULL; + udev_list_node_foreach(him_node, &bunch) { + struct _mate *him = node_to_mate(him_node); + + if (!strcmp(him->name, argv[i])) + who = him; + } + if (!who) { + struct _mate *him; + + if (debug) + fprintf(stderr, "ID %s: not in database\n", argv[i]); + him = malloc(sizeof (struct _mate)); + if (!him) { + ret = ENOMEM; + goto out; + } + + him->name = malloc(strlen(argv[i]) + 1); + if (!him->name) { + ret = ENOMEM; + goto out; + } + + strcpy(him->name, argv[i]); + him->state = STATE_NONE; + udev_list_node_append(&him->node, &bunch); + } else { + if (debug) + fprintf(stderr, "ID %s: found in database\n", argv[i]); + who->state = STATE_CONFIRMED; + } + } + + if (prune) + reject(us); + else + invite(us); + + if (debug) { + everybody(); + fprintf(stderr, "Prune lists\n"); + } + kickout(); + + lseek(fd, 0, SEEK_SET); + ftruncate(fd, 0); + ret = missing(fd); + + lockf(fd, F_ULOCK, 0); + close(fd); +out: + if (debug) + everybody(); + if (ret >= 0) + printf("COLLECT_%s=%d\n", checkpoint, ret); +exit: + udev_unref(udev); + return ret; +} diff --git a/src/udev/keymap/.gitignore b/src/udev/keymap/.gitignore new file mode 100644 index 0000000000..4567584f4e --- /dev/null +++ b/src/udev/keymap/.gitignore @@ -0,0 +1,5 @@ +keyboard-force-release.sh +keys-from-name.gperf +keys-from-name.h +keys-to-name.h +keys.txt diff --git a/src/udev/keymap/95-keyboard-force-release.rules b/src/udev/keymap/95-keyboard-force-release.rules new file mode 100644 index 0000000000..39f01a4d3e --- /dev/null +++ b/src/udev/keymap/95-keyboard-force-release.rules @@ -0,0 +1,57 @@ +# Set model specific atkbd force_release quirk +# +# Several laptops have hotkeys which don't generate release events, +# which can cause problems with software key repeat. +# The atkbd driver has a quirk handler for generating synthetic +# release events, which can be configured via sysfs since 2.6.32. +# Simply add a file with a list of scancodes for your laptop model +# in /usr/lib/udev/keymaps, and add a rule here. +# If the hotkeys also need a keymap assignment you can copy the +# scancodes from the keymap file, otherwise you can run +# /usr/lib/udev/keymap -i /dev/input/eventX +# on a Linux vt to find out. + +ACTION=="remove", GOTO="force_release_end" +SUBSYSTEM!="serio", GOTO="force_release_end" +KERNEL!="serio*", GOTO="force_release_end" +DRIVER!="atkbd", GOTO="force_release_end" + +ENV{DMI_VENDOR}="$attr{[dmi/id]sys_vendor}" + +ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", RUN+="keyboard-force-release.sh $devpath samsung-other" +ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", ATTR{[dmi/id]product_name}=="*90X3A*", RUN+="keyboard-force-release.sh $devpath samsung-90x3a" + +ENV{DMI_VENDOR}=="Dell Inc.", ATTR{[dmi/id]product_name}=="Studio 1557|Studio 1558", RUN+="keyboard-force-release.sh $devpath common-volume-keys" +ENV{DMI_VENDOR}=="Dell Inc.", ATTR{[dmi/id]product_name}=="Latitude E*|Latitude *U|Precision M*", RUN+="keyboard-force-release.sh $devpath dell-touchpad" +ENV{DMI_VENDOR}=="Dell Inc.", ATTR{[dmi/id]product_name}=="XPS*", RUN+="keyboard-force-release.sh $devpath dell-xps" + +ENV{DMI_VENDOR}=="FUJITSU SIEMENS", ATTR{[dmi/id]product_name}=="AMILO*", RUN+="keyboard-force-release.sh $devpath common-volume-keys" + +ENV{DMI_VENDOR}=="FOXCONN", ATTR{[dmi/id]product_name}=="QBOOK", RUN+="keyboard-force-release.sh $devpath common-volume-keys" + +ENV{DMI_VENDOR}=="MTC", ATTR{[dmi/id]product_version}=="A0", RUN+="keyboard-force-release.sh $devpath common-volume-keys" + +ENV{DMI_VENDOR}=="Mio Technology", ATTR{[dmi/id]product_name}=="N890", RUN+="keyboard-force-release.sh $devpath common-volume-keys" + +ENV{DMI_VENDOR}=="PEGATRON CORP.", ATTR{[dmi/id]product_name}=="Spring Peak", RUN+="keyboard-force-release.sh $devpath common-volume-keys" + +ENV{DMI_VENDOR}=="TOSHIBA", ATTR{[dmi/id]product_name}=="Satellite [uU]300*|Satellite Pro [uU]300*|Satellite [uU]305*|SATELLITE [uU]500*", RUN+="keyboard-force-release.sh $devpath common-volume-keys" + +ENV{DMI_VENDOR}=="Viooo Corporation", ATTR{[dmi/id]product_name}=="PT17", RUN+="keyboard-force-release.sh $devpath common-volume-keys" + +# These are all the HP laptops that setup a touchpad toggle key +ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*[pP][aA][vV][iI][lL][iI][oO][nN]*", RUN+="keyboard-force-release.sh $devpath hp-other" +ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*[tT][xX]2*", RUN+="keyboard-force-release.sh $devpath hp-other" +ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*2510p*|*2530p*|HP G60 Notebook PC", RUN+="keyboard-force-release.sh $devpath hp-other" + +ENV{DMI_VENDOR}=="Zepto", ATTR{[dmi/id]product_name}=="Znote 6615WD", RUN+="keyboard-force-release.sh $devpath common-volume-keys" + +ENV{DMI_VENDOR}=="Zepto", ATTR{[dmi/id]product_name}=="Znote", ATTR{[dmi/id]product_version}=="6625WD", RUN+="keyboard-force-release.sh $devpath common-volume-keys" + +ENV{DMI_VENDOR}=="HANNspree", ATTR{[dmi/id]product_name}=="SN10E100", RUN+="keyboard-force-release.sh $devpath common-volume-keys" + +ENV{DMI_VENDOR}=="GIGABYTE", ATTR{[dmi/id]product_name}=="i1520M", RUN+="keyboard-force-release.sh $devpath common-volume-keys" + +ENV{DMI_VENDOR}=="BenQ", ATTR{[dmi/id]product_name}=="*nScreen*", RUN+="keyboard-force-release.sh $devpath common-volume-keys" + +LABEL="force_release_end" diff --git a/src/udev/keymap/95-keymap.rules b/src/udev/keymap/95-keymap.rules new file mode 100644 index 0000000000..506310ef70 --- /dev/null +++ b/src/udev/keymap/95-keymap.rules @@ -0,0 +1,175 @@ +# Set model specific hotkey keycodes. +# +# Key map overrides can be specified by either giving scancode/keyname pairs +# directly as keymap arguments (if there are just one or two to change), or as +# a file name (in /usr/lib/udev/keymaps), which has to contain scancode/keyname +# pairs. + +ACTION=="remove", GOTO="keyboard_end" +KERNEL!="event*", GOTO="keyboard_end" +ENV{ID_INPUT_KEY}=="", GOTO="keyboard_end" +SUBSYSTEMS=="bluetooth", GOTO="keyboard_end" + +SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id" +SUBSYSTEMS=="usb", GOTO="keyboard_usbcheck" +GOTO="keyboard_modulecheck" + +# +# The following are external USB keyboards +# + +LABEL="keyboard_usbcheck" + +ENV{ID_VENDOR}=="Genius", ENV{ID_MODEL_ID}=="0708", ENV{ID_USB_INTERFACE_NUM}=="01", RUN+="keymap $name genius-slimstar-320" +ENV{ID_VENDOR}=="Logitech*", ATTRS{name}=="Logitech USB Multimedia Keyboard", RUN+="keymap $name logitech-wave" +ENV{ID_VENDOR}=="Logitech*", ATTRS{name}=="Logitech USB Receiver", RUN+="keymap $name logitech-wave-cordless" +# Logitech Cordless Wave Pro looks slightly weird; some hotkeys are coming through the mouse interface +ENV{ID_VENDOR_ID}=="046d", ENV{ID_MODEL_ID}=="c52[9b]", ATTRS{name}=="Logitech USB Receiver", RUN+="keymap $name logitech-wave-pro-cordless" + +ENV{ID_VENDOR}=="Lite-On_Technology_Corp*", ATTRS{name}=="Lite-On Technology Corp. ThinkPad USB Keyboard with TrackPoint", RUN+="keymap $name lenovo-thinkpad-usb-keyboard-trackpoint" +ENV{ID_VENDOR_ID}=="04b3", ENV{ID_MODEL_ID}=="301[89]", RUN+="keymap $name ibm-thinkpad-usb-keyboard-trackpoint" + +ENV{ID_VENDOR}=="Microsoft", ENV{ID_MODEL_ID}=="00db", RUN+="keymap $name 0xc022d zoomin 0xc022e zoomout" + +GOTO="keyboard_end" + +# +# The following are exposed as separate input devices with low key codes, thus +# we need to check their input device product name +# + +LABEL="keyboard_modulecheck" + +ENV{DMI_VENDOR}="$attr{[dmi/id]sys_vendor}" +ENV{DMI_VENDOR}=="", GOTO="keyboard_end" + +ENV{DMI_VENDOR}=="LENOVO*", KERNELS=="input*", ATTRS{name}=="ThinkPad Extra Buttons", RUN+="keymap $name module-lenovo" +ENV{DMI_VENDOR}=="LENOVO*", KERNELS=="input*", ATTRS{name}=="Lenovo ThinkPad SL Series extra buttons", RUN+="keymap $name 0x0E bluetooth" +ENV{DMI_VENDOR}=="LENOVO*", KERNELS=="input*", ATTRS{name}=="Ideapad extra buttons", RUN+="keymap $name 0x42 f23 0x43 f22" + +ENV{DMI_VENDOR}=="ASUS*", KERNELS=="input*", ATTRS{name}=="Asus Extra Buttons", ATTR{[dmi/id]product_name}=="W3J", RUN+="keymap $name module-asus-w3j" +ENV{DMI_VENDOR}=="ASUS*", KERNELS=="input*", ATTRS{name}=="Eee PC WMI hotkeys|Asus Laptop Support|Asus*WMI*", RUN+="keymap $name 0x6B f21" +ENV{DMI_VENDOR}=="ASUS*", KERNELS=="input*", ATTRS{name}=="Eee PC Hotkey Driver", RUN+="keymap $name 0x37 f21" + +ENV{DMI_VENDOR}=="IBM*", KERNELS=="input*", ATTRS{name}=="ThinkPad Extra Buttons", RUN+="keymap $name module-ibm" +ENV{DMI_VENDOR}=="Sony*", KERNELS=="input*", ATTRS{name}=="Sony Vaio Keys", RUN+="keymap $name module-sony" +ENV{DMI_VENDOR}=="Acer*", KERNELS=="input*", ATTRS{name}=="Acer WMI hotkeys", RUN+="keymap $name 0x82 f21" +ENV{DMI_VENDOR}=="MICRO-STAR*|Micro-Star*", KERNELS=="input*", ATTRS{name}=="MSI Laptop hotkeys", RUN+="keymap $name 0x213 f22 0x214 f23" + +# Older Vaios have some different keys +ENV{DMI_VENDOR}=="Sony*", ATTR{[dmi/id]product_name}=="*PCG-C1*|*PCG-K25*|*PCG-F1*|*PCG-F2*|*PCG-F3*|*PCG-F4*|*PCG-F5*|*PCG-F6*|*PCG-FX*|*PCG-FRV*|*PCG-GR*|*PCG-TR*|*PCG-NV*|*PCG-Z*|*VGN-S360*", ATTRS{name}=="Sony Vaio Keys", RUN+="keymap $name module-sony-old" + +# Some Sony VGN/VPC models have yet another one +ENV{DMI_VENDOR}=="Sony*", ATTR{[dmi/id]product_name}=="VGN-AR71*|VGN-FW*|VGN-Z21*", ATTRS{name}=="Sony Vaio Keys", RUN+="keymap $name module-sony-vgn" +ENV{DMI_VENDOR}=="Sony*", ATTR{[dmi/id]product_name}=="VPC*", ATTRS{name}=="Sony Vaio Keys", RUN+="keymap $name module-sony-vpc" + + +# +# The following rules belong to standard i8042 AT keyboard with high key codes. +# + +DRIVERS=="atkbd", GOTO="keyboard_vendorcheck" +GOTO="keyboard_end" + +LABEL="keyboard_vendorcheck" + +ENV{DMI_VENDOR}=="Dell*", RUN+="keymap $name dell" +ENV{DMI_VENDOR}=="Dell*", ATTR{[dmi/id]product_name}=="Inspiron 910|Inspiron 1010|Inspiron 1011|Inspiron 1012|Inspiron 1110|Inspiron 1210", RUN+="keymap $name 0x84 wlan" +ENV{DMI_VENDOR}=="Dell*", ATTR{[dmi/id]product_name}=="Latitude XT2", RUN+="keymap $name dell-latitude-xt2" + +ENV{DMI_VENDOR}=="Compaq*", ATTR{[dmi/id]product_name}=="*E500*|*Evo N*", RUN+="keymap $name compaq-e_evo" + +ENV{DMI_VENDOR}=="LENOVO*", ATTR{[dmi/id]product_version}=="*3000*", RUN+="keymap $name lenovo-3000" +ENV{DMI_VENDOR}=="LENOVO*", ATTR{[dmi/id]product_version}=="ThinkPad X6*", ATTR{[dmi/id]product_version}=="* Tablet", RUN+="keymap $name lenovo-thinkpad_x6_tablet" +ENV{DMI_VENDOR}=="LENOVO*", ATTR{[dmi/id]product_version}=="ThinkPad X2* Tablet*", ATTR{[dmi/id]product_version}=="* Tablet", RUN+="keymap $name lenovo-thinkpad_x200_tablet" +ENV{DMI_VENDOR}=="LENOVO*", ATTR{[dmi/id]product_version}=="*IdeaPad*", RUN+="keymap $name lenovo-ideapad" +ENV{DMI_VENDOR}=="LENOVO*", ATTR{[dmi/id]product_name}=="S10-*", RUN+="keymap $name lenovo-ideapad" +ENV{DMI_VENDOR}=="LENOVO", ATTR{[dmi/id]product_version}=="*IdeaPad Y550*", RUN+="keymap $name 0x95 media 0xA3 play" +ENV{DMI_VENDOR}=="LENOVO", ATTR{[dmi/id]product_version}=="*Lenovo V480*", RUN+="keymap $name 0xf1 f21" + +ENV{DMI_VENDOR}=="Hewlett-Packard*", RUN+="keymap $name hewlett-packard" +ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*[tT][aA][bB][lL][eE][tT]*", RUN+="keymap $name hewlett-packard-tablet" +ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*[pP][aA][vV][iI][lL][iI][oO][nN]*", RUN+="keymap $name hewlett-packard-pavilion" +ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*Compaq*|*EliteBook*|*2230s*", RUN+="keymap $name hewlett-packard-compaq_elitebook" +ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*Presario*CQ*", RUN+="keymap $name 0xD8 f21 0xD9 f21" +ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*2510p*|*2530p*|HP G60 Notebook PC", RUN+="keymap $name hewlett-packard-2510p_2530p" +ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*[tT][xX]2*", RUN+="keymap $name hewlett-packard-tx2" +ENV{DMI_VENDOR}=="Hewlett-Packard", ATTR{[dmi/id]product_name}=="Presario 2100*", RUN+="keymap $name hewlett-packard-presario-2100" +ENV{DMI_VENDOR}=="Hewlett-Packard", ATTR{[dmi/id]product_name}=="HP G62 Notebook PC", RUN+="keymap $name 0xB2 www" +ENV{DMI_VENDOR}=="Hewlett-Packard", ATTR{[dmi/id]product_name}=="HP ProBook*", RUN+="keymap $name 0xF8 rfkill" +ENV{DMI_VENDOR}=="Hewlett-Packard", ATTR{[dmi/id]product_name}=="HP EliteBook 8440p", RUN+="keymap $name hewlett-packard_elitebook-8440p" +# HP Pavillion dv6315ea has empty DMI_VENDOR +ATTR{[dmi/id]board_vendor}=="Quanta", ATTR{[dmi/id]board_name}=="30B7", ATTR{[dmi/id]board_version}=="65.2B", RUN+="keymap $name 0x88 media" # "quick play + +# Gateway clone of Acer Aspire One AOA110/AOA150 +ENV{DMI_VENDOR}=="Gateway*", ATTR{[dmi/id]product_name}=="*AOA1*", RUN+="keymap $name acer" + +ENV{DMI_VENDOR}=="Acer*", RUN+="keymap $name acer" +ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_name}=="Extensa*", ATTR{[dmi/id]product_name}=="*5210*|*5220*|*5610*|*5620*|*5720*", RUN+="keymap $name 0xEE screenlock" +ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_name}=="TravelMate*C3[01]0*", RUN+="keymap $name acer-travelmate_c300" +ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_name}=="TravelMate*6292*|TravelMate*8471*|TravelMate*4720*|TravelMate*7720*|Aspire 1810T*|AO751h|AO531h", RUN+="keymap $name 0xD9 bluetooth" +ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_name}=="TravelMate*4720*", RUN+="keymap $name 0xB2 www 0xEE screenlock" +ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_name}=="TravelMate 6593|Aspire 1640", RUN+="keymap $name 0xB2 www 0xEE screenlock" +ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_name}=="Aspire 6920", RUN+="keymap $name acer-aspire_6920" +ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_name}=="Aspire 5920G", RUN+="keymap $name acer-aspire_5920g" +ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_name}=="Aspire 5720*", RUN+="keymap $name acer-aspire_5720" +ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_name}=="Aspire 8930", RUN+="keymap $name acer-aspire_8930" +ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_serial}=="ZG8*", RUN+="keymap $name acer-aspire_5720" + +ENV{DMI_VENDOR}=="*BenQ*", ATTR{[dmi/id]product_name}=="*Joybook R22*", RUN+="keymap $name 0x6E wlan" + +ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="*AMILO Pro V3205*", RUN+="keymap $name fujitsu-amilo_pro_v3205" +ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="*AMILO Pa 2548*", RUN+="keymap $name fujitsu-amilo_pa_2548" +ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="*ESPRIMO Mobile V5*", RUN+="keymap $name fujitsu-esprimo_mobile_v5" +ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="*ESPRIMO Mobile V6*", RUN+="keymap $name fujitsu-esprimo_mobile_v6" +ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="*AMILO Pro Edition V3505*", RUN+="keymap $name fujitsu-amilo_pro_edition_v3505" +ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="*Amilo Si 1520*", RUN+="keymap $name fujitsu-amilo_si_1520" +ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="AMILO*M*", RUN+="keymap $name 0x97 prog2 0x9F prog1" +ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="Amilo Li 1718", RUN+="keymap $name 0xD6 wlan" +ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="AMILO Li 2732", RUN+="keymap $name fujitsu-amilo_li_2732" + +ENV{DMI_VENDOR}=="LG*", ATTR{[dmi/id]product_name}=="*X110*", RUN+="keymap $name lg-x110" + +ENV{DMI_VENDOR}=="MEDION*", ATTR{[dmi/id]product_name}=="*FID2060*", RUN+="keymap $name medion-fid2060" +ENV{DMI_VENDOR}=="MEDIONNB", ATTR{[dmi/id]product_name}=="A555*", RUN+="keymap $name medionnb-a555" + +ENV{DMI_VENDOR}=="MICRO-STAR*|Micro-Star*", RUN+="keymap $name micro-star" + +# some MSI models generate ACPI/input events on the LNXVIDEO input devices, +# plus some extra synthesized ones on atkbd as an echo of actually changing the +# brightness; so ignore those atkbd ones, to avoid loops +ENV{DMI_VENDOR}=="MICRO-STAR*", ATTR{[dmi/id]product_name}=="*U-100*|*U100*|*N033", RUN+="keymap $name 0xF7 reserved 0xF8 reserved" + +ENV{DMI_VENDOR}=="INVENTEC", ATTR{[dmi/id]product_name}=="SYMPHONY 6.0/7.0", RUN+="keymap $name inventec-symphony_6.0_7.0" + +ENV{DMI_VENDOR}=="MAXDATA", ATTR{[dmi/id]product_name}=="Pro 7000*", RUN+="keymap $name maxdata-pro_7000" + +ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", RUN+="keymap $name samsung-other" +ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", ATTR{[dmi/id]product_name}=="*SX20S*", RUN+="keymap $name samsung-sx20s" +ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", ATTR{[dmi/id]product_name}=="SQ1US", RUN+="keymap $name samsung-sq1us" +ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", ATTR{[dmi/id]product_name}=="*700Z*", RUN+="keymap $name 0xBA ejectcd 0x96 keyboardbrightnessup 0x97 keyboardbrightnessdown" +ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", ATTR{[dmi/id]product_name}=="*90X3A*", RUN+="keymap $name samsung-90x3a" + +ENV{DMI_VENDOR}=="TOSHIBA", ATTR{[dmi/id]product_name}=="SATELLITE A100", RUN+="keymap $name toshiba-satellite_a100" +ENV{DMI_VENDOR}=="TOSHIBA", ATTR{[dmi/id]product_name}=="Satellite A110", RUN+="keymap $name toshiba-satellite_a110" +ENV{DMI_VENDOR}=="TOSHIBA", ATTR{[dmi/id]product_name}=="Satellite M30X", RUN+="keymap $name toshiba-satellite_m30x" + +ENV{DMI_VENDOR}=="OQO Inc.*", ATTR{[dmi/id]product_name}=="OQO Model 2*", RUN+="keymap $name oqo-model2" + +ENV{DMI_VENDOR}=="ONKYO CORPORATION", ATTR{[dmi/id]product_name}=="ONKYOPC", RUN+="keymap $name onkyo" + +ENV{DMI_VENDOR}=="ASUS", RUN+="keymap $name asus" + +ENV{DMI_VENDOR}=="VIA", ATTR{[dmi/id]product_name}=="K8N800", ATTR{[dmi/id]product_version}=="VT8204B", RUN+="keymap $name 0x81 prog1" + +ENV{DMI_VENDOR}=="Zepto", ATTR{[dmi/id]product_name}=="Znote", ATTR{[dmi/id]product_version}=="62*|63*", RUN+="keymap $name zepto-znote" + +ENV{DMI_VENDOR}=="Everex", ATTR{[dmi/id]product_name}=="XT5000*", RUN+="keymap $name everex-xt5000" + +ENV{DMI_VENDOR}=="COMPAL", ATTR{[dmi/id]product_name}=="HEL80I", RUN+="keymap $name 0x84 wlan" + +ENV{DMI_VENDOR}=="OLPC", ATTR{[dmi/id]product_name}=="XO", RUN+="keymap $name olpc-xo" + +ENV{DMI_VENDOR}=="Alienware*", ATTR{[dmi/id]product_name}=="M14xR1", RUN+="keymap $name 0x8A ejectcd" + +LABEL="keyboard_end" diff --git a/src/udev/keymap/README.keymap.txt b/src/udev/keymap/README.keymap.txt new file mode 100644 index 0000000000..2cf2a4e88c --- /dev/null +++ b/src/udev/keymap/README.keymap.txt @@ -0,0 +1,97 @@ += The udev keymap tool = + +== Introduction == + +This udev extension configures computer model specific key mappings. This is +particularly necessary for the non-standard extra keys found on many laptops, +such as "brightness up", "next song", "www browser", or "suspend". Often these +are accessed with the Fn key. + +Every key produces a "scan code", which is highly vendor/model specific for the +nonstandard keys. This tool maintains mappings for these scan codes to standard +"key codes", which denote the "meaning" of the key. The key codes are defined +in /usr/include/linux/input.h. + +If some of your keys on your keyboard are not working at all, or produce the +wrong effect, then a very likely cause of this is that the scan code -> key +code mapping is incorrect on your computer. + +== Structure == + +udev-keymap consists of the following parts: + + keymaps/*:: mappings of scan codes to key code names + + 95-keymap.rules:: udev rules for mapping system vendor/product names and + input module names to one of the keymaps above + + keymap:: manipulate an evdev input device: + * write a key map file into a device (used by udev rules) + * dump current scan → key code mapping + * interactively display scan and key codes of pressed keys + + findkeyboards:: display evdev input devices which belong to actual keyboards, + i. e. those suitable for the keymap program + +== Fixing broken keys == + +In order to make a broken key work on your system and send it back to upstream +for inclusion you need to do the following steps: + + 1. Find the keyboard device. + + Run /usr/lib/udev/findkeyboards. This should always give you an "AT + keyboard" and possibly a "module". Some laptops (notably Thinkpads, Sonys, and + Acers) have multimedia/function keys on a separate input device instead of the + primary keyboard. The keyboard device should have a name like "input/event3". + In the following commands, the name will be written as "input/eventX" (replace + X with the appropriate number). + + 2. Find broken scan codes: + + sudo /usr/lib/udev/keymap -i input/eventX + + Press all multimedia/function keys and check if the key name that gets printed + out is plausible. If it is unknown or wrong, write down the scan code (looks + like "0x1E") and the intended functionality of this key. Look in + /usr/include/linux/input.h for an available KEY_XXXXX constant which most + closely approximates this functionality and write it down as the new key code. + + For example, you might press a key labeled "web browser" which currently + produces "unknown". Note down this: + + 0x1E www # Fn+F2 web browser + + Repeat that for all other keys. Write the resulting list into a file. Look at + /usr/lib/udev/keymaps/ for existing key map files and make sure that you use the + same structure. + + If the key only ever works once and then your keyboard (or the entire desktop) + gets stuck for a long time, then it is likely that the BIOS fails to send a + corresponding "key release" event after the key press event. Please note down + this case as well, as it can be worked around in + /usr/lib/udev/keymaps/95-keyboard-force-release.rules . + + 3. Find out your system vendor and product: + + cat /sys/class/dmi/id/sys_vendor + cat /sys/class/dmi/id/product_name + + 4. Generate a device dump with "udevadm info --export-db > /tmp/udev-db.txt". + + 6. Send the system vendor/product names, the key mapping from step 2, + and /tmp/udev-db.txt from step 4 to the linux-hotplug@vger.kernel.org mailing + list, so that they can be included in the next release. + +For local testing, copy your map file to /usr/lib/udev/keymaps/ with an appropriate +name, and add an appropriate udev rule to /usr/lib/udev/rules.d/95-keymap.rules: + + * If you selected an "AT keyboard", add the rule to the section after + 'LABEL="keyboard_vendorcheck"'. + + * If you selected a "module", add the rule to the top section where the + "ThinkPad Extra Buttons" are. + +== Author == + +keymap is written and maintained by Martin Pitt <martin.pitt@ubuntu.com>. diff --git a/src/udev/keymap/check-keymaps.sh b/src/udev/keymap/check-keymaps.sh new file mode 100755 index 0000000000..c4572745e0 --- /dev/null +++ b/src/udev/keymap/check-keymaps.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# check that all key names in keymaps/* are known in <linux/input.h> +# and that all key maps listed in the rules are valid and present in +# Makefile.am +SRCDIR=${1:-.} +KEYLIST=${2:-src/udev/keymap/keys.txt} +KEYMAPS_DIR=$SRCDIR/keymaps +RULES=$SRCDIR/src/udev/keymap/95-keymap.rules + +[ -e "$KEYLIST" ] || { + echo "need $KEYLIST please build first" >&2 + exit 1 +} + +missing=$(join -v 2 <(awk '{print tolower(substr($1,5))}' $KEYLIST | sort -u) \ + <(grep -hv '^#' ${KEYMAPS_DIR}/*| awk '{print $2}' | sort -u)) +[ -z "$missing" ] || { + echo "ERROR: unknown key names in keymaps/*:" >&2 + echo "$missing" >&2 + exit 1 +} + +# check that all maps referred to in $RULES exist +maps=$(sed -rn '/keymap \$name/ { s/^.*\$name ([^"[:space:]]+).*$/\1/; p }' $RULES) +for m in $maps; do + # ignore inline mappings + [ "$m" = "${m#0x}" ] || continue + + [ -e ${KEYMAPS_DIR}/$m ] || { + echo "ERROR: unknown map name in $RULES: $m" >&2 + exit 1 + } + grep -q "keymaps/$m\>" $SRCDIR/Makefile.am || { + echo "ERROR: map file $m is not added to Makefile.am" >&2 + exit 1 + } +done diff --git a/src/udev/keymap/findkeyboards b/src/udev/keymap/findkeyboards new file mode 100755 index 0000000000..9ce27429b2 --- /dev/null +++ b/src/udev/keymap/findkeyboards @@ -0,0 +1,68 @@ +#!/bin/sh -e +# Find "real" keyboard devices and print their device path. +# Author: Martin Pitt <martin.pitt@ubuntu.com> +# +# Copyright (C) 2009, Canonical Ltd. +# +# 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. + +# returns OK if $1 contains $2 +strstr() { + [ "${1#*$2*}" != "$1" ] +} + +# returns OK if $1 contains $2 at the beginning +str_starts() { + [ "${1#$2*}" != "$1" ] +} + +str_line_starts() { + while read a; do str_starts "$a" "$1" && return 0;done + return 1; +} + +# print a list of input devices which are keyboard-like +keyboard_devices() { + # standard AT keyboard + for dev in `udevadm trigger --dry-run --verbose --property-match=ID_INPUT_KEYBOARD=1`; do + walk=`udevadm info --attribute-walk --path=$dev` + env=`udevadm info --query=env --path=$dev` + # filter out non-event devices, such as the parent input devices which have no devnode + if ! echo "$env" | str_line_starts 'DEVNAME='; then + continue + fi + if strstr "$walk" 'DRIVERS=="atkbd"'; then + echo -n 'AT keyboard: ' + elif echo "$env" | str_line_starts 'ID_USB_DRIVER=usbhid'; then + echo -n 'USB keyboard: ' + else + echo -n 'Unknown type: ' + fi + udevadm info --query=name --path=$dev + done + + # modules + module=$(udevadm trigger --verbose --dry-run --subsystem-match=input --attr-match=name='*Extra Buttons') + module="$module + $(udevadm trigger --verbose --dry-run --subsystem-match=input --attr-match=name='*extra buttons')" + module="$module + $(udevadm trigger --verbose --dry-run --subsystem-match=input --attr-match=name='Sony Vaio Keys')" + for m in $module; do + for evdev in $m/event*/dev; do + if [ -e "$evdev" ]; then + echo -n 'module: ' + udevadm info --query=name --path=${evdev%%/dev} + fi + done + done +} + +keyboard_devices diff --git a/src/udev/keymap/keyboard-force-release.sh.in b/src/udev/keymap/keyboard-force-release.sh.in new file mode 100755 index 0000000000..b82674840f --- /dev/null +++ b/src/udev/keymap/keyboard-force-release.sh.in @@ -0,0 +1,22 @@ +#!/bin/sh -e +# read list of scancodes, convert hex to decimal and +# append to the atkbd force_release sysfs attribute +# $1 sysfs devpath for serioX +# $2 file with scancode list (hex or dec) + +case "$2" in + /*) scf="$2" ;; + *) scf="@udevlibexecdir@/keymaps/force-release/$2" ;; +esac + +read attr <"/sys/$1/force_release" +while read scancode dummy; do + case "$scancode" in + \#*) ;; + *) + scancode=$(($scancode)) + attr="$attr${attr:+,}$scancode" + ;; + esac +done <"$scf" +echo "$attr" >"/sys/$1/force_release" diff --git a/src/udev/keymap/keymap.c b/src/udev/keymap/keymap.c new file mode 100644 index 0000000000..939407fd0b --- /dev/null +++ b/src/udev/keymap/keymap.c @@ -0,0 +1,453 @@ +/* + * keymap - dump keymap of an evdev device or set a new keymap from a file + * + * Based on keyfuzz by Lennart Poettering <mzqrovna@0pointer.net> + * Adapted for udev-extras by Martin Pitt <martin.pitt@ubuntu.com> + * + * Copyright (C) 2006, Lennart Poettering + * Copyright (C) 2009, Canonical Ltd. + * + * keymap 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. + * + * keymap is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with keymap; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <ctype.h> +#include <unistd.h> +#include <errno.h> +#include <limits.h> +#include <fcntl.h> +#include <getopt.h> +#include <sys/ioctl.h> +#include <linux/limits.h> +#include <linux/input.h> + +const struct key* lookup_key (const char *str, unsigned int len); + +#include "keys-from-name.h" +#include "keys-to-name.h" +#include "macro.h" +#include "util.h" + +#define MAX_SCANCODES 1024 + +static int evdev_open(const char *dev) +{ + int fd; + char fn[PATH_MAX]; + + if (!startswith(dev, "/dev")) { + snprintf(fn, sizeof(fn), "/dev/%s", dev); + dev = fn; + } + + if ((fd = open(dev, O_RDWR)) < 0) { + fprintf(stderr, "error open('%s'): %m\n", dev); + return -1; + } + return fd; +} + +static int evdev_get_keycode(int fd, unsigned scancode, int e) +{ + unsigned codes[2]; + + codes[0] = scancode; + if (ioctl(fd, EVIOCGKEYCODE, codes) < 0) { + if (e && errno == EINVAL) { + return -2; + } else { + fprintf(stderr, "EVIOCGKEYCODE for scan code 0x%x: %m\n", scancode); + return -1; + } + } + return codes[1]; +} + +static int evdev_set_keycode(int fd, unsigned scancode, int keycode) +{ + unsigned codes[2]; + + codes[0] = scancode; + codes[1] = (unsigned) keycode; + + if (ioctl(fd, EVIOCSKEYCODE, codes) < 0) { + fprintf(stderr, "EVIOCSKEYCODE: %m\n"); + return -1; + } + return 0; +} + +static int evdev_driver_version(int fd, char *v, size_t l) +{ + int version; + + if (ioctl(fd, EVIOCGVERSION, &version)) { + fprintf(stderr, "EVIOCGVERSION: %m\n"); + return -1; + } + + snprintf(v, l, "%i.%i.%i.", version >> 16, (version >> 8) & 0xff, version & 0xff); + return 0; +} + +static int evdev_device_name(int fd, char *n, size_t l) +{ + if (ioctl(fd, EVIOCGNAME(l), n) < 0) { + fprintf(stderr, "EVIOCGNAME: %m\n"); + return -1; + } + return 0; +} + +/* Return a lower-case string with KEY_ prefix removed */ +static const char* format_keyname(const char* key) { + static char result[101]; + const char* s; + int len; + + for (s = key+4, len = 0; *s && len < 100; ++len, ++s) + result[len] = tolower(*s); + result[len] = '\0'; + return result; +} + +static int dump_table(int fd) { + char version[256], name[256]; + unsigned scancode; + int r = -1; + + if (evdev_driver_version(fd, version, sizeof(version)) < 0) + goto fail; + + if (evdev_device_name(fd, name, sizeof(name)) < 0) + goto fail; + + printf("### evdev %s, driver '%s'\n", version, name); + + r = 0; + for (scancode = 0; scancode < MAX_SCANCODES; scancode++) { + int keycode; + + if ((keycode = evdev_get_keycode(fd, scancode, 1)) < 0) { + if (keycode == -2) + continue; + r = -1; + break; + } + + if (keycode < KEY_MAX && key_names[keycode]) + printf("0x%03x %s\n", scancode, format_keyname(key_names[keycode])); + else + printf("0x%03x 0x%03x\n", scancode, keycode); + } +fail: + return r; +} + +static void set_key(int fd, const char* scancode_str, const char* keyname) +{ + unsigned scancode; + char *endptr; + char t[105] = "KEY_UNKNOWN"; + const struct key *k; + + scancode = (unsigned) strtol(scancode_str, &endptr, 0); + if (*endptr != '\0') { + fprintf(stderr, "ERROR: Invalid scancode\n"); + exit(1); + } + + snprintf(t, sizeof(t), "KEY_%s", keyname); + + if (!(k = lookup_key(t, strlen(t)))) { + fprintf(stderr, "ERROR: Unknown key name '%s'\n", keyname); + exit(1); + } + + if (evdev_set_keycode(fd, scancode, k->id) < 0) + fprintf(stderr, "setting scancode 0x%2X to key code %i failed\n", + scancode, k->id); + else + printf("setting scancode 0x%2X to key code %i\n", + scancode, k->id); +} + +static int merge_table(int fd, FILE *f) { + int r = 0; + int line = 0; + + while (!feof(f)) { + char s[256], *p; + unsigned scancode; + int new_keycode, old_keycode; + + if (!fgets(s, sizeof(s), f)) + break; + + line++; + p = s+strspn(s, "\t "); + if (*p == '#' || *p == '\n') + continue; + + if (sscanf(p, "%i %i", &scancode, &new_keycode) != 2) { + char t[105] = "KEY_UNKNOWN"; + const struct key *k; + + if (sscanf(p, "%i %100s", &scancode, t+4) != 2) { + fprintf(stderr, "WARNING: Parse failure at line %i, ignoring.\n", line); + r = -1; + continue; + } + + if (!(k = lookup_key(t, strlen(t)))) { + fprintf(stderr, "WARNING: Unknown key '%s' at line %i, ignoring.\n", t, line); + r = -1; + continue; + } + + new_keycode = k->id; + } + + + if ((old_keycode = evdev_get_keycode(fd, scancode, 0)) < 0) { + r = -1; + continue; + } + + if (evdev_set_keycode(fd, scancode, new_keycode) < 0) { + r = -1; + continue; + } + + if (new_keycode != old_keycode) + fprintf(stderr, "Remapped scancode 0x%02x to 0x%02x (prior: 0x%02x)\n", + scancode, new_keycode, old_keycode); + } + + fclose(f); + return r; +} + + +/* read one event; return 1 if valid */ +static int read_event(int fd, struct input_event* ev) +{ + int ret; + ret = read(fd, ev, sizeof(struct input_event)); + + if (ret < 0) { + perror("read"); + return 0; + } + if (ret != sizeof(struct input_event)) { + fprintf(stderr, "did not get enough data for event struct, aborting\n"); + return 0; + } + + return 1; +} + +static void print_key(unsigned scancode, uint16_t keycode, int has_scan, int has_key) +{ + const char *keyname; + + /* ignore key release events */ + if (has_key == 1) + return; + + if (has_key == 0 && has_scan != 0) { + fprintf(stderr, "got scan code event 0x%02X without a key code event\n", + scancode); + return; + } + + if (has_scan != 0) + printf("scan code: 0x%02X ", scancode); + else + printf("(no scan code received) "); + + keyname = key_names[keycode]; + if (keyname != NULL) + printf("key code: %s\n", format_keyname(keyname)); + else + printf("key code: %03X\n", keycode); +} + +static void interactive(int fd) +{ + struct input_event ev; + unsigned last_scan = 0; + uint16_t last_key = 0; + int has_scan; /* boolean */ + int has_key; /* 0: none, 1: release, 2: press */ + + /* grab input device */ + ioctl(fd, EVIOCGRAB, 1); + puts("Press ESC to finish, or Control-C if this device is not your primary keyboard"); + + has_scan = has_key = 0; + while (read_event(fd, &ev)) { + /* Drivers usually send the scan code first, then the key code, + * then a SYN. Some drivers (like thinkpad_acpi) send the key + * code first, and some drivers might not send SYN events, so + * keep a robust state machine which can deal with any of those + */ + + if (ev.type == EV_MSC && ev.code == MSC_SCAN) { + if (has_scan) { + fputs("driver did not send SYN event in between key events; previous event:\n", + stderr); + print_key(last_scan, last_key, has_scan, has_key); + has_key = 0; + } + + last_scan = ev.value; + has_scan = 1; + /*printf("--- got scan %u; has scan %i key %i\n", last_scan, has_scan, has_key); */ + } + else if (ev.type == EV_KEY) { + if (has_key) { + fputs("driver did not send SYN event in between key events; previous event:\n", + stderr); + print_key(last_scan, last_key, has_scan, has_key); + has_scan = 0; + } + + last_key = ev.code; + has_key = 1 + ev.value; + /*printf("--- got key %hu; has scan %i key %i\n", last_key, has_scan, has_key);*/ + + /* Stop on ESC */ + if (ev.code == KEY_ESC && ev.value == 0) + break; + } + else if (ev.type == EV_SYN) { + /*printf("--- got SYN; has scan %i key %i\n", has_scan, has_key);*/ + print_key(last_scan, last_key, has_scan, has_key); + + has_scan = has_key = 0; + } + + } + + /* release input device */ + ioctl(fd, EVIOCGRAB, 0); +} + +_noreturn_ static void help(int error) +{ + const char* h = "Usage: keymap <event device> [<map file>]\n" + " keymap <event device> scancode keyname [...]\n" + " keymap -i <event device>\n"; + if (error) { + fputs(h, stderr); + exit(2); + } else { + fputs(h, stdout); + exit(0); + } +} + +int main(int argc, char **argv) +{ + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "interactive", no_argument, NULL, 'i' }, + {} + }; + int fd = -1; + int opt_interactive = 0; + int i; + + while (1) { + int option; + + option = getopt_long(argc, argv, "hi", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'h': + help(0); + + case 'i': + opt_interactive = 1; + break; + default: + return 1; + } + } + + if (argc < optind+1) + help (1); + + if ((fd = evdev_open(argv[optind])) < 0) + return 3; + + /* one argument (device): dump or interactive */ + if (argc == optind+1) { + if (opt_interactive) + interactive(fd); + else + dump_table(fd); + return 0; + } + + /* two arguments (device, mapfile): set map file */ + if (argc == optind+2) { + const char *filearg = argv[optind+1]; + if (strchr(filearg, '/')) { + /* Keymap file argument is a path */ + FILE *f = fopen(filearg, "re"); + if (f) + merge_table(fd, f); + else + perror(filearg); + } else { + /* Keymap file argument is a filename */ + /* Open override file if present, otherwise default file */ + char keymap_path[PATH_MAX]; + FILE *f; + + snprintf(keymap_path, sizeof(keymap_path), "/etc/udev/keymaps/%s", filearg); + f = fopen(keymap_path, "re"); + if (f) { + merge_table(fd, f); + } else { + snprintf(keymap_path, sizeof(keymap_path), UDEVLIBEXECDIR "/keymaps/%s", filearg); + f = fopen(keymap_path, "re"); + if (f) + merge_table(fd, f); + else + perror(keymap_path); + } + } + return 0; + } + + /* more arguments (device, scancode/keyname pairs): set keys directly */ + if ((argc - optind - 1) % 2 == 0) { + for (i = optind+1; i < argc; i += 2) + set_key(fd, argv[i], argv[i+1]); + return 0; + } + + /* invalid number of arguments */ + help(1); + return 1; /* not reached */ +} diff --git a/src/udev/mtd_probe/mtd_probe.c b/src/udev/mtd_probe/mtd_probe.c new file mode 100644 index 0000000000..70c04db40b --- /dev/null +++ b/src/udev/mtd_probe/mtd_probe.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2010 - Maxim Levitsky + * + * mtd_probe 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. + * + * mtd_probe 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 mtd_probe; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +#include "mtd_probe.h" +#include <stdio.h> +#include <sys/ioctl.h> +#include <mtd/mtd-user.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> + +int main(int argc, char** argv) +{ + int mtd_fd; + int error; + mtd_info_t mtd_info; + + if (argc != 2) { + printf("usage: mtd_probe /dev/mtd[n]\n"); + return 1; + } + + mtd_fd = open(argv[1], O_RDONLY); + if (mtd_fd == -1) { + perror("open"); + exit(-1); + } + + error = ioctl(mtd_fd, MEMGETINFO, &mtd_info); + if (error == -1) { + perror("ioctl"); + exit(-1); + } + + probe_smart_media(mtd_fd, &mtd_info); + return -1; +} diff --git a/src/udev/mtd_probe/mtd_probe.h b/src/udev/mtd_probe/mtd_probe.h new file mode 100644 index 0000000000..2a37ede578 --- /dev/null +++ b/src/udev/mtd_probe/mtd_probe.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010 - Maxim Levitsky + * + * mtd_probe 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. + * + * mtd_probe 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 mtd_probe; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include <mtd/mtd-user.h> + +/* Full oob structure as written on the flash */ +struct sm_oob { + uint32_t reserved; + uint8_t data_status; + uint8_t block_status; + uint8_t lba_copy1[2]; + uint8_t ecc2[3]; + uint8_t lba_copy2[2]; + uint8_t ecc1[3]; +} __attribute__((packed)); + + +/* one sector is always 512 bytes, but it can consist of two nand pages */ +#define SM_SECTOR_SIZE 512 + +/* oob area is also 16 bytes, but might be from two pages */ +#define SM_OOB_SIZE 16 + +/* This is maximum zone size, and all devices that have more that one zone + have this size */ +#define SM_MAX_ZONE_SIZE 1024 + +/* support for small page nand */ +#define SM_SMALL_PAGE 256 +#define SM_SMALL_OOB_SIZE 8 + + +void probe_smart_media(int mtd_fd, mtd_info_t *info); diff --git a/src/udev/mtd_probe/probe_smartmedia.c b/src/udev/mtd_probe/probe_smartmedia.c new file mode 100644 index 0000000000..feadb5076c --- /dev/null +++ b/src/udev/mtd_probe/probe_smartmedia.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2010 - Maxim Levitsky + * + * mtd_probe 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. + * + * mtd_probe 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 mtd_probe; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <mtd/mtd-user.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdint.h> +#include <stdlib.h> +#include "mtd_probe.h" + +static const uint8_t cis_signature[] = { + 0x01, 0x03, 0xD9, 0x01, 0xFF, 0x18, 0x02, 0xDF, 0x01, 0x20 +}; + + +void probe_smart_media(int mtd_fd, mtd_info_t* info) +{ + int sector_size; + int block_size; + int size_in_megs; + int spare_count; + char* cis_buffer = malloc(SM_SECTOR_SIZE); + int offset; + int cis_found = 0; + + if (!cis_buffer) + return; + + if (info->type != MTD_NANDFLASH) + goto exit; + + sector_size = info->writesize; + block_size = info->erasesize; + size_in_megs = info->size / (1024 * 1024); + + if (sector_size != SM_SECTOR_SIZE && sector_size != SM_SMALL_PAGE) + goto exit; + + switch(size_in_megs) { + case 1: + case 2: + spare_count = 6; + break; + case 4: + spare_count = 12; + break; + default: + spare_count = 24; + break; + } + + for (offset = 0 ; offset < block_size * spare_count ; + offset += sector_size) { + lseek(mtd_fd, SEEK_SET, offset); + if (read(mtd_fd, cis_buffer, SM_SECTOR_SIZE) == SM_SECTOR_SIZE){ + cis_found = 1; + break; + } + } + + if (!cis_found) + goto exit; + + if (memcmp(cis_buffer, cis_signature, sizeof(cis_signature)) != 0 && + (memcmp(cis_buffer + SM_SMALL_PAGE, cis_signature, + sizeof(cis_signature)) != 0)) + goto exit; + + printf("MTD_FTL=smartmedia\n"); + free(cis_buffer); + exit(0); +exit: + free(cis_buffer); + return; +} diff --git a/src/udev/scsi_id/.gitignore b/src/udev/scsi_id/.gitignore new file mode 100644 index 0000000000..6aebddd809 --- /dev/null +++ b/src/udev/scsi_id/.gitignore @@ -0,0 +1 @@ +scsi_id_version.h diff --git a/src/udev/scsi_id/README b/src/udev/scsi_id/README new file mode 100644 index 0000000000..9cfe73991c --- /dev/null +++ b/src/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/udev/scsi_id/scsi.h b/src/udev/scsi_id/scsi.h new file mode 100644 index 0000000000..c423cac574 --- /dev/null +++ b/src/udev/scsi_id/scsi.h @@ -0,0 +1,97 @@ +/* + * 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 5 +#define SCSI_ID_NAA_IEEE_REG_EXTENDED 6 + +/* + * 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 0x2 +#define SCSI_CONDITION_MET 0x4 +#define SCSI_BUSY 0x8 +#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/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c new file mode 100644 index 0000000000..c90b6aa581 --- /dev/null +++ b/src/udev/scsi_id/scsi_id.c @@ -0,0 +1,634 @@ +/* + * 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 <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <syslog.h> +#include <stdarg.h> +#include <ctype.h> +#include <getopt.h> +#include <sys/stat.h> + +#include "libudev.h" +#include "libudev-private.h" +#include "scsi_id.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' }, + { "export", no_argument, NULL, 'x' }, + { "help", no_argument, NULL, 'h' }, + {} +}; + +static const char short_options[] = "d:f:ghip:uvVx"; +static const char dev_short_options[] = "bgp:"; + +static int all_good; +static int dev_specified; +static char config_file[MAX_PATH_LEN] = "/etc/scsi_id.config"; +static enum page_code default_page_code; +static int sg_version = 4; +static int use_stderr; +static int debug; +static int reformat_serial; +static int export; +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 log_fn(struct udev *udev, int priority, + const char *file, int line, const char *fn, + const char *format, va_list args) +{ + vsyslog(priority, format, args); +} + +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; + } + } + util_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; + FILE *fd; + char *buf; + char *str1; + char *vendor_in, *model_in, *options_in; /* read in from file */ + int lineno; + int c; + int retval = 0; + + fd = fopen(config_file, "re"); + if (fd == NULL) { + if (errno == ENOENT) { + return 1; + } else { + log_error("can't open %s: %s\n", config_file, strerror(errno)); + 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) { + fclose(fd); + return log_oom(); + } + + *newargv = NULL; + lineno = 0; + while (1) { + vendor_in = model_in = options_in = NULL; + + buf = fgets(buffer, MAX_BUFFER_LEN, fd); + if (buf == NULL) + break; + lineno++; + if (buf[strlen(buffer) - 1] != '\n') { + log_error("Config file line %d too long\n", 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 && strcasecmp(str1, "VENDOR") == 0) { + str1 = get_value(&buf); + if (!str1) { + retval = log_oom(); + break; + } + vendor_in = str1; + + str1 = strsep(&buf, "="); + if (str1 && strcasecmp(str1, "MODEL") == 0) { + str1 = get_value(&buf); + if (!str1) { + retval = log_oom(); + break; + } + model_in = str1; + str1 = strsep(&buf, "="); + } + } + + if (str1 && strcasecmp(str1, "OPTIONS") == 0) { + 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'\n", lineno, buffer); + retval = -1; + break; + } + if (vendor == NULL) { + if (vendor_in == NULL) + break; + } else if ((vendor_in && strncmp(vendor, vendor_in, + strlen(vendor_in)) == 0) && + (!model_in || (strncmp(model, model_in, + strlen(model_in)) == 0))) { + /* + * 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); + fclose(fd); + return retval; +} + +static int set_options(struct udev *udev, + int argc, char **argv, const char *short_opts, + 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 (1) { + option = getopt_long(argc, argv, short_opts, options, NULL); + if (option == -1) + break; + + switch (option) { + case 'b': + all_good = 0; + break; + + case 'd': + dev_specified = 1; + util_strscpy(maj_min_dev, MAX_PATH_LEN, optarg); + break; + + case 'e': + use_stderr = 1; + break; + + case 'f': + util_strscpy(config_file, MAX_PATH_LEN, optarg); + break; + + case 'g': + all_good = 1; + break; + + case 'h': + printf("Usage: scsi_id OPTIONS <device>\n" + " --device= device node for SG_IO commands\n" + " --config= location of config file\n" + " --page=0x80|0x83|pre-spc3-83 SCSI page (0x80, 0x83, pre-spc3-83)\n" + " --sg-version=3|4 use SGv3 or SGv4\n" + " --blacklisted threat device as blacklisted\n" + " --whitelisted threat device as whitelisted\n" + " --replace-whitespace replace all whitespaces by underscores\n" + " --verbose verbose logging\n" + " --version print version\n" + " --export print values as environment keys\n" + " --help print this help text\n\n"); + exit(0); + + case 'p': + if (strcmp(optarg, "0x80") == 0) { + default_page_code = PAGE_80; + } else if (strcmp(optarg, "0x83") == 0) { + default_page_code = PAGE_83; + } else if (strcmp(optarg, "pre-spc3-83") == 0) { + default_page_code = PAGE_83_PRE_SPC3; + } else { + log_error("Unknown page code '%s'\n", optarg); + return -1; + } + break; + + case 's': + sg_version = atoi(optarg); + if (sg_version < 3 || sg_version > 4) { + log_error("Unknown SG version '%s'\n", optarg); + return -1; + } + break; + + case 'u': + reformat_serial = 1; + break; + + case 'x': + export = 1; + break; + + case 'v': + debug++; + break; + + case 'V': + printf("%s\n", VERSION); + exit(0); + break; + + default: + exit(1); + } + } + if (optind < argc && !dev_specified) { + dev_specified = 1; + util_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, dev_short_options, options, NULL); + if (option == -1) + break; + + switch (option) { + case 'b': + *good_bad = 0; + break; + + case 'g': + *good_bad = 1; + break; + + case 'p': + if (strcmp(optarg, "0x80") == 0) { + *page_code = PAGE_80; + } else if (strcmp(optarg, "0x83") == 0) { + *page_code = PAGE_83; + } else if (strcmp(optarg, "pre-spc3-83") == 0) { + *page_code = PAGE_83_PRE_SPC3; + } else { + log_error("Unknown page code '%s'\n", optarg); + retval = -1; + } + break; + + default: + log_error("Unknown or bad option '%c' (0x%x)\n", 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; + + memset(&dev_scsi, 0x00, sizeof(struct scsi_id_device)); + + 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) +{ + struct udev *udev; + int retval = 0; + char maj_min_dev[MAX_PATH_LEN]; + int newargc; + char **newargv; + + udev = udev_new(); + if (udev == NULL) + goto exit; + + log_open(); + udev_set_log_fn(udev, log_fn); + + /* + * Get config file options. + */ + newargv = NULL; + retval = get_file_options(udev, NULL, NULL, &newargc, &newargv); + if (retval < 0) { + retval = 1; + goto exit; + } + if (newargv && (retval == 0)) { + if (set_options(udev, newargc, newargv, short_options, maj_min_dev) < 0) { + retval = 2; + goto exit; + } + free(newargv); + } + + /* + * Get command line options (overriding any config file settings). + */ + if (set_options(udev, argc, argv, short_options, maj_min_dev) < 0) + exit(1); + + if (!dev_specified) { + log_error("no device specified\n"); + retval = 1; + goto exit; + } + + retval = scsi_id(udev, maj_min_dev); + +exit: + udev_unref(udev); + log_close(); + return retval; +} diff --git a/src/udev/scsi_id/scsi_id.h b/src/udev/scsi_id/scsi_id.h new file mode 100644 index 0000000000..828a98305f --- /dev/null +++ b/src/udev/scsi_id/scsi_id.h @@ -0,0 +1,73 @@ +/* + * 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]; +}; + +extern int scsi_std_inquiry(struct udev *udev, struct scsi_id_device *dev_scsi, const char *devname); +extern 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/udev/scsi_id/scsi_serial.c b/src/udev/scsi_id/scsi_serial.c new file mode 100644 index 0000000000..3c52dee62d --- /dev/null +++ b/src/udev/scsi_id/scsi_serial.c @@ -0,0 +1,968 @@ +/* + * 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 <sys/types.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <syslog.h> +#include <time.h> +#include <inttypes.h> +#include <scsi/scsi.h> +#include <scsi/sg.h> +#include <linux/types.h> +#include <linux/bsg.h> + +#include "libudev.h" +#include "libudev-private.h" +#include "scsi.h" +#include "scsi_id.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\n", 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\n", + 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\n", + 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\n", + dev_scsi->kernel, code); + return -1; + } + log_debug("%s: sense key 0x%x ASC 0x%x ASCQ 0x%x\n", + 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\n", + dev_scsi->kernel, sb_len, 4 - sb_len); + return -1; + } + + if (sense_buffer[0] < 15) + log_debug("%s: old sense key: 0x%x\n", dev_scsi->kernel, sense_buffer[0] & 0x0f); + else + log_debug("%s: sense = %2x %2x\n", + dev_scsi->kernel, sense_buffer[0], sense_buffer[2]); + log_debug("%s: non-extended sense class %d code 0x%0x\n", + 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:\n", dev_scsi->kernel); + log_debug("%s: %s\n", 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\n", __FUNCTION__); + return -1; + } + + log_debug("%s: sg_io failed status 0x%x 0x%x 0x%x 0x%x\n", + 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\n", __FUNCTION__); + return -1; + } + + log_debug("%s: sg_io failed status 0x%x 0x%x 0x%x\n", + 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\n", buflen); + return -1; + } + +resend: + if (dev_scsi->use_sg == 4) { + memset(&io_v4, 0, 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 { + memset(&io_hdr, 0, 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("%s: ioctl failed: %s\n", dev_scsi->kernel, strerror(errno)); + 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.\n", + 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; + + memset(buffer, 0, 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.\n", dev_scsi->kernel); + return 1; + } + if (buffer[3] > len) { + log_debug("%s: page 0 buffer too long %d\n", 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 (!strncmp((char *)&buffer[VENDOR_LENGTH], dev_scsi->vendor, VENDOR_LENGTH)) { + log_debug("%s: invalid page0 data\n", 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\n", + 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\n", + 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); + + memset(page_83, 0, 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\n", 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]; + + memset(page_83, 0, 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\n", 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]; + + memset(buf, 0, 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\n", 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\n", + 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); + if (fd < 0) { + log_debug("scsi_id: cannot open %s: %s\n", + devname, strerror(errno)); + return 1; + } + + if (fstat(fd, &statbuf) < 0) { + log_debug("scsi_id: cannot stat %s: %s\n", + devname, strerror(errno)); + err = 2; + goto out; + } + sprintf(dev_scsi->kernel,"%d:%d", major(statbuf.st_rdev), + minor(statbuf.st_rdev)); + + memset(buf, 0, 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; + + memset(dev_scsi->serial, 0, len); + srand((unsigned int)getpid()); + for (cnt = 20; cnt > 0; cnt--) { + struct timespec duration; + + fd = open(devname, O_RDONLY | O_NONBLOCK); + 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\n", 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/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c new file mode 100644 index 0000000000..4293103046 --- /dev/null +++ b/src/udev/udev-builtin-blkid.c @@ -0,0 +1,204 @@ +/* + * probe disks for filesystems and partitions + * + * Copyright (C) 2011 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2011 Karel Zak <kzak@redhat.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <sys/stat.h> +#include <blkid/blkid.h> + +#include "udev.h" + +static void print_property(struct udev_device *dev, bool test, const char *name, const char *value) +{ + char s[265]; + + s[0] = '\0'; + + if (streq(name, "TYPE")) { + udev_builtin_add_property(dev, test, "ID_FS_TYPE", value); + + } else if (streq(name, "USAGE")) { + udev_builtin_add_property(dev, test, "ID_FS_USAGE", value); + + } else if (streq(name, "VERSION")) { + udev_builtin_add_property(dev, test, "ID_FS_VERSION", value); + + } else if (streq(name, "UUID")) { + blkid_safe_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_FS_UUID", s); + blkid_encode_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_FS_UUID_ENC", s); + + } else if (streq(name, "UUID_SUB")) { + blkid_safe_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_FS_UUID_SUB", s); + blkid_encode_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_FS_UUID_SUB_ENC", s); + + } else if (streq(name, "LABEL")) { + blkid_safe_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_FS_LABEL", s); + blkid_encode_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_FS_LABEL_ENC", s); + + } else if (streq(name, "PTTYPE")) { + udev_builtin_add_property(dev, test, "ID_PART_TABLE_TYPE", value); + + } else if (streq(name, "PART_ENTRY_NAME")) { + blkid_encode_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_PART_ENTRY_NAME", s); + + } else if (streq(name, "PART_ENTRY_TYPE")) { + blkid_encode_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_PART_ENTRY_TYPE", s); + + } else if (startswith(name, "PART_ENTRY_")) { + util_strscpyl(s, sizeof(s), "ID_", name, NULL); + udev_builtin_add_property(dev, test, s, value); + } +} + +static int probe_superblocks(blkid_probe pr) +{ + struct stat st; + int rc; + + if (fstat(blkid_probe_get_fd(pr), &st)) + return -1; + + blkid_probe_enable_partitions(pr, 1); + + if (!S_ISCHR(st.st_mode) && blkid_probe_get_size(pr) <= 1024 * 1440 && + blkid_probe_is_wholedisk(pr)) { + /* + * check if the small disk is partitioned, if yes then + * don't probe for filesystems. + */ + blkid_probe_enable_superblocks(pr, 0); + + rc = blkid_do_fullprobe(pr); + if (rc < 0) + return rc; /* -1 = error, 1 = nothing, 0 = succes */ + + if (blkid_probe_lookup_value(pr, "PTTYPE", NULL, NULL) == 0) + return 0; /* partition table detected */ + } + + blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS); + blkid_probe_enable_superblocks(pr, 1); + + return blkid_do_safeprobe(pr); +} + +static int builtin_blkid(struct udev_device *dev, int argc, char *argv[], bool test) +{ + int64_t offset = 0; + bool noraid = false; + int fd = -1; + blkid_probe pr; + const char *data; + const char *name; + int nvals; + int i; + size_t len; + int err = 0; + + static const struct option options[] = { + { "offset", optional_argument, NULL, 'o' }, + { "noraid", no_argument, NULL, 'R' }, + {} + }; + + for (;;) { + int option; + + option = getopt_long(argc, argv, "oR", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'o': + offset = strtoull(optarg, NULL, 0); + break; + case 'R': + noraid = true; + break; + } + } + + pr = blkid_new_probe(); + if (!pr) + return EXIT_FAILURE; + + blkid_probe_set_superblocks_flags(pr, + BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID | + BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE | + BLKID_SUBLKS_USAGE | BLKID_SUBLKS_VERSION); + + if (noraid) + blkid_probe_filter_superblocks_usage(pr, BLKID_FLTR_NOTIN, BLKID_USAGE_RAID); + + fd = open(udev_device_get_devnode(dev), O_RDONLY|O_CLOEXEC); + if (fd < 0) { + fprintf(stderr, "error: %s: %m\n", udev_device_get_devnode(dev)); + goto out; + } + + err = blkid_probe_set_device(pr, fd, offset, 0); + if (err < 0) + goto out; + + log_debug("probe %s %sraid offset=%llu\n", + udev_device_get_devnode(dev), + noraid ? "no" : "", (unsigned long long) offset); + + err = probe_superblocks(pr); + if (err < 0) + goto out; + + nvals = blkid_probe_numof_values(pr); + for (i = 0; i < nvals; i++) { + if (blkid_probe_get_value(pr, i, &name, &data, &len)) + continue; + len = strnlen((char *) data, len); + print_property(dev, test, name, (char *) data); + } + + blkid_free_probe(pr); +out: + if (fd > 0) + close(fd); + if (err < 0) + return EXIT_FAILURE; + return EXIT_SUCCESS; +} + +const struct udev_builtin udev_builtin_blkid = { + .name = "blkid", + .cmd = builtin_blkid, + .help = "filesystem and partition probing", + .run_once = true, +}; diff --git a/src/udev/udev-builtin-btrfs.c b/src/udev/udev-builtin-btrfs.c new file mode 100644 index 0000000000..dfb05525ad --- /dev/null +++ b/src/udev/udev-builtin-btrfs.c @@ -0,0 +1,65 @@ +/*** + 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 <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/ioctl.h> + +#include "udev.h" + +#define BTRFS_PATH_NAME_MAX 4087 +struct btrfs_ioctl_vol_args { + int64_t fd; + char name[BTRFS_PATH_NAME_MAX + 1]; +}; +#define BTRFS_IOCTL_MAGIC 0x94 +#define BTRFS_IOC_DEVICES_READY _IOR(BTRFS_IOCTL_MAGIC, 39, struct btrfs_ioctl_vol_args) + +static int builtin_btrfs(struct udev_device *dev, int argc, char *argv[], bool test) +{ + struct btrfs_ioctl_vol_args args; + int fd; + int err; + + if (argc != 3 || !streq(argv[1], "ready")) + return EXIT_FAILURE; + + fd = open("/dev/btrfs-control", O_RDWR); + if (fd < 0) + return EXIT_FAILURE; + + util_strscpy(args.name, sizeof(args.name), argv[2]); + err = ioctl(fd, BTRFS_IOC_DEVICES_READY, &args); + close(fd); + if (err < 0) + return EXIT_FAILURE; + + udev_builtin_add_property(dev, test, "ID_BTRFS_READY", err == 0 ? "1" : "0"); + return EXIT_SUCCESS; +} + +const struct udev_builtin udev_builtin_btrfs = { + .name = "btrfs", + .cmd = builtin_btrfs, + .help = "btrfs volume management", +}; diff --git a/src/udev/udev-builtin-firmware.c b/src/udev/udev-builtin-firmware.c new file mode 100644 index 0000000000..2fb75a7335 --- /dev/null +++ b/src/udev/udev-builtin-firmware.c @@ -0,0 +1,172 @@ +/* + * firmware - Kernel firmware loader + * + * Copyright (C) 2009 Piter Punk <piterpunk@slackware.com> + * Copyright (C) 2009-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 <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <getopt.h> +#include <errno.h> +#include <stdbool.h> +#include <sys/utsname.h> +#include <sys/stat.h> + +#include "udev.h" + +static bool set_loading(struct udev *udev, char *loadpath, const char *state) +{ + FILE *ldfile; + + ldfile = fopen(loadpath, "we"); + if (ldfile == NULL) { + log_error("error: can not open '%s'\n", loadpath); + return false; + }; + fprintf(ldfile, "%s\n", state); + fclose(ldfile); + return true; +} + +static bool copy_firmware(struct udev *udev, const char *source, const char *target, size_t size) +{ + char *buf; + FILE *fsource = NULL, *ftarget = NULL; + bool ret = false; + + buf = malloc(size); + if (buf == NULL) { + log_error("No memory available to load firmware file"); + return false; + } + + log_debug("writing '%s' (%zi) to '%s'\n", source, size, target); + + fsource = fopen(source, "re"); + if (fsource == NULL) + goto exit; + ftarget = fopen(target, "we"); + if (ftarget == NULL) + goto exit; + if (fread(buf, size, 1, fsource) != 1) + goto exit; + if (fwrite(buf, size, 1, ftarget) == 1) + ret = true; +exit: + if (ftarget != NULL) + fclose(ftarget); + if (fsource != NULL) + fclose(fsource); + free(buf); + return ret; +} + +static int builtin_firmware(struct udev_device *dev, int argc, char *argv[], bool test) +{ + struct udev *udev = udev_device_get_udev(dev); + static const char *searchpath[] = { FIRMWARE_PATH }; + char fwencpath[UTIL_PATH_SIZE]; + char misspath[UTIL_PATH_SIZE]; + char loadpath[UTIL_PATH_SIZE]; + char datapath[UTIL_PATH_SIZE]; + char fwpath[UTIL_PATH_SIZE]; + const char *firmware; + FILE *fwfile = NULL; + struct utsname kernel; + struct stat statbuf; + unsigned int i; + int rc = EXIT_SUCCESS; + + firmware = udev_device_get_property_value(dev, "FIRMWARE"); + if (firmware == NULL) { + log_error("firmware parameter missing\n\n"); + rc = EXIT_FAILURE; + goto exit; + } + + /* lookup firmware file */ + uname(&kernel); + for (i = 0; i < ELEMENTSOF(searchpath); i++) { + util_strscpyl(fwpath, sizeof(fwpath), searchpath[i], kernel.release, "/", firmware, NULL); + fwfile = fopen(fwpath, "re"); + if (fwfile != NULL) + break; + + util_strscpyl(fwpath, sizeof(fwpath), searchpath[i], firmware, NULL); + fwfile = fopen(fwpath, "re"); + if (fwfile != NULL) + break; + } + + util_path_encode(firmware, fwencpath, sizeof(fwencpath)); + util_strscpyl(misspath, sizeof(misspath), "/run/udev/firmware-missing/", fwencpath, NULL); + util_strscpyl(loadpath, sizeof(loadpath), udev_device_get_syspath(dev), "/loading", NULL); + + if (fwfile == NULL) { + int err; + + /* This link indicates the missing firmware file and the associated device */ + log_debug("did not find firmware file '%s'\n", firmware); + do { + err = mkdir_parents(misspath, 0755); + if (err != 0 && err != -ENOENT) + break; + err = symlink(udev_device_get_devpath(dev), misspath); + if (err != 0) + err = -errno; + } while (err == -ENOENT); + rc = EXIT_FAILURE; + /* + * Do not cancel the request in the initrd, the real root might have + * the firmware file and the 'coldplug' run in the real root will find + * this pending request and fulfill or cancel it. + * */ + if (!in_initrd()) + set_loading(udev, loadpath, "-1"); + goto exit; + } + + if (stat(fwpath, &statbuf) < 0 || statbuf.st_size == 0) { + rc = EXIT_FAILURE; + goto exit; + } + if (unlink(misspath) == 0) + util_delete_path(udev, misspath); + + if (!set_loading(udev, loadpath, "1")) + goto exit; + + util_strscpyl(datapath, sizeof(datapath), udev_device_get_syspath(dev), "/data", NULL); + if (!copy_firmware(udev, fwpath, datapath, statbuf.st_size)) { + log_error("error sending firmware '%s' to device\n", firmware); + set_loading(udev, loadpath, "-1"); + rc = EXIT_FAILURE; + goto exit; + }; + + set_loading(udev, loadpath, "0"); +exit: + if (fwfile) + fclose(fwfile); + return rc; +} + +const struct udev_builtin udev_builtin_firmware = { + .name = "firmware", + .cmd = builtin_firmware, + .help = "kernel firmware loader", + .run_once = true, +}; diff --git a/src/udev/udev-builtin-hwdb.c b/src/udev/udev-builtin-hwdb.c new file mode 100644 index 0000000000..234448cdf1 --- /dev/null +++ b/src/udev/udev-builtin-hwdb.c @@ -0,0 +1,159 @@ +/*** + 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 <stdio.h> +#include <errno.h> +#include <string.h> +#include <inttypes.h> +#include <ctype.h> +#include <stdlib.h> +#include <getopt.h> + +#include "udev.h" + +static struct udev_hwdb *hwdb; + +int udev_builtin_hwdb_lookup(struct udev_device *dev, const char *modalias, bool test) { + struct udev_list_entry *entry; + int n = 0; + + udev_list_entry_foreach(entry, udev_hwdb_get_properties_list_entry(hwdb, modalias, 0)) { + if (udev_builtin_add_property(dev, test, + udev_list_entry_get_name(entry), + udev_list_entry_get_value(entry)) < 0) + return -ENOMEM; + n++; + } + return n; +} + +static const char *modalias_usb(struct udev_device *dev, char *s, size_t size) { + const char *v, *p; + int vn, pn; + + v = udev_device_get_sysattr_value(dev, "idVendor"); + if (!v) + return NULL; + p = udev_device_get_sysattr_value(dev, "idProduct"); + if (!p) + return NULL; + vn = strtol(v, NULL, 16); + if (vn <= 0) + return NULL; + pn = strtol(p, NULL, 16); + if (pn <= 0) + return NULL; + snprintf(s, size, "usb:v%04Xp%04X*", vn, pn); + return s; +} + +static int udev_builtin_hwdb_search(struct udev_device *dev, const char *subsystem, bool test) { + struct udev_device *d; + char s[16]; + int n = 0; + + for (d = dev; d; d = udev_device_get_parent(d)) { + const char *dsubsys; + const char *modalias = NULL; + + dsubsys = udev_device_get_subsystem(d); + if (!dsubsys) + continue; + + /* look only at devices of a specific subsystem */ + if (subsystem && !streq(dsubsys, subsystem)) + continue; + + /* the usb_device does not have a modalias, compose one */ + if (streq(dsubsys, "usb")) + modalias = modalias_usb(dev, s, sizeof(s)); + + if (!modalias) + modalias = udev_device_get_property_value(d, "MODALIAS"); + + if (!modalias) + continue; + n = udev_builtin_hwdb_lookup(dev, modalias, test); + if (n > 0) + break; + } + + return n; +} + +static int builtin_hwdb(struct udev_device *dev, int argc, char *argv[], bool test) { + static const struct option options[] = { + { "subsystem", required_argument, NULL, 's' }, + {} + }; + const char *subsystem = NULL; + + if (!hwdb) + return EXIT_FAILURE; + + for (;;) { + int option; + + option = getopt_long(argc, argv, "s", options, NULL); + if (option == -1) + break; + + switch (option) { + case 's': + subsystem = optarg; + break; + } + } + + if (udev_builtin_hwdb_search(dev, subsystem, test) < 0) + return EXIT_FAILURE; + return EXIT_SUCCESS; +} + +/* called at udev startup and reload */ +static int builtin_hwdb_init(struct udev *udev) +{ + if (hwdb) + return 0; + hwdb = udev_hwdb_new(udev); + if (!hwdb) + return -ENOMEM; + return 0; +} + +/* called on udev shutdown and reload request */ +static void builtin_hwdb_exit(struct udev *udev) +{ + hwdb = udev_hwdb_unref(hwdb); +} + +/* called every couple of seconds during event activity; 'true' if config has changed */ +static bool builtin_hwdb_validate(struct udev *udev) +{ + return udev_hwdb_validate(hwdb); +} + +const struct udev_builtin udev_builtin_hwdb = { + .name = "hwdb", + .cmd = builtin_hwdb, + .init = builtin_hwdb_init, + .exit = builtin_hwdb_exit, + .validate = builtin_hwdb_validate, + .help = "hardware database", +}; diff --git a/src/udev/udev-builtin-input_id.c b/src/udev/udev-builtin-input_id.c new file mode 100644 index 0000000000..445b602f9c --- /dev/null +++ b/src/udev/udev-builtin-input_id.c @@ -0,0 +1,219 @@ +/* + * compose persistent device path + * + * Copyright (C) 2009 Martin Pitt <martin.pitt@ubuntu.com> + * Portions Copyright (C) 2004 David Zeuthen, <david@fubar.dk> + * Copyright (C) 2011 Kay Sievers <kay@vrfy.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <linux/limits.h> +#include <linux/input.h> + +#include "udev.h" + +/* we must use this kernel-compatible implementation */ +#define BITS_PER_LONG (sizeof(unsigned long) * 8) +#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1) +#define OFF(x) ((x)%BITS_PER_LONG) +#define BIT(x) (1UL<<OFF(x)) +#define LONG(x) ((x)/BITS_PER_LONG) +#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1) + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +/* + * Read a capability attribute and return bitmask. + * @param dev udev_device + * @param attr sysfs attribute name (e. g. "capabilities/key") + * @param bitmask: Output array which has a sizeof of bitmask_size + */ +static void get_cap_mask(struct udev_device *dev, + struct udev_device *pdev, const char* attr, + unsigned long *bitmask, size_t bitmask_size, + bool test) +{ + char text[4096]; + unsigned i; + char* word; + unsigned long val; + + snprintf(text, sizeof(text), "%s", udev_device_get_sysattr_value(pdev, attr)); + log_debug("%s raw kernel attribute: %s\n", attr, text); + + memset (bitmask, 0, bitmask_size); + i = 0; + while ((word = strrchr(text, ' ')) != NULL) { + val = strtoul (word+1, NULL, 16); + if (i < bitmask_size/sizeof(unsigned long)) + bitmask[i] = val; + else + log_debug("ignoring %s block %lX which is larger than maximum size\n", attr, val); + *word = '\0'; + ++i; + } + val = strtoul (text, NULL, 16); + if (i < bitmask_size / sizeof(unsigned long)) + bitmask[i] = val; + else + log_debug("ignoring %s block %lX which is larger than maximum size\n", attr, val); + + if (test) { + /* printf pattern with the right unsigned long number of hex chars */ + snprintf(text, sizeof(text), " bit %%4u: %%0%zilX\n", 2 * sizeof(unsigned long)); + log_debug("%s decoded bit map:\n", attr); + val = bitmask_size / sizeof (unsigned long); + /* skip over leading zeros */ + while (bitmask[val-1] == 0 && val > 0) + --val; + for (i = 0; i < val; ++i) + log_debug(text, i * BITS_PER_LONG, bitmask[i]); + } +} +#pragma GCC diagnostic pop + +/* pointer devices */ +static void test_pointers (struct udev_device *dev, + const unsigned long* bitmask_ev, + const unsigned long* bitmask_abs, + const unsigned long* bitmask_key, + const unsigned long* bitmask_rel, + bool test) +{ + int is_mouse = 0; + int is_touchpad = 0; + + if (!test_bit (EV_KEY, bitmask_ev)) { + if (test_bit (EV_ABS, bitmask_ev) && + test_bit (ABS_X, bitmask_abs) && + test_bit (ABS_Y, bitmask_abs) && + test_bit (ABS_Z, bitmask_abs)) + udev_builtin_add_property(dev, test, "ID_INPUT_ACCELEROMETER", "1"); + return; + } + + if (test_bit (EV_ABS, bitmask_ev) && + test_bit (ABS_X, bitmask_abs) && test_bit (ABS_Y, bitmask_abs)) { + if (test_bit (BTN_STYLUS, bitmask_key) || test_bit (BTN_TOOL_PEN, bitmask_key)) + udev_builtin_add_property(dev, test, "ID_INPUT_TABLET", "1"); + else if (test_bit (BTN_TOOL_FINGER, bitmask_key) && !test_bit (BTN_TOOL_PEN, bitmask_key)) + is_touchpad = 1; + else if (test_bit (BTN_TRIGGER, bitmask_key) || + test_bit (BTN_A, bitmask_key) || + test_bit (BTN_1, bitmask_key)) + udev_builtin_add_property(dev, test, "ID_INPUT_JOYSTICK", "1"); + else if (test_bit (BTN_MOUSE, bitmask_key)) + /* This path is taken by VMware's USB mouse, which has + * absolute axes, but no touch/pressure button. */ + is_mouse = 1; + else if (test_bit (BTN_TOUCH, bitmask_key)) + udev_builtin_add_property(dev, test, "ID_INPUT_TOUCHSCREEN", "1"); + } + + if (test_bit (EV_REL, bitmask_ev) && + test_bit (REL_X, bitmask_rel) && test_bit (REL_Y, bitmask_rel) && + test_bit (BTN_MOUSE, bitmask_key)) + is_mouse = 1; + + if (is_mouse) + udev_builtin_add_property(dev, test, "ID_INPUT_MOUSE", "1"); + if (is_touchpad) + udev_builtin_add_property(dev, test, "ID_INPUT_TOUCHPAD", "1"); +} + +/* key like devices */ +static void test_key (struct udev_device *dev, + const unsigned long* bitmask_ev, + const unsigned long* bitmask_key, + bool test) +{ + unsigned i; + unsigned long found; + unsigned long mask; + + /* do we have any KEY_* capability? */ + if (!test_bit (EV_KEY, bitmask_ev)) { + log_debug("test_key: no EV_KEY capability\n"); + return; + } + + /* only consider KEY_* here, not BTN_* */ + found = 0; + for (i = 0; i < BTN_MISC/BITS_PER_LONG; ++i) { + found |= bitmask_key[i]; + log_debug("test_key: checking bit block %lu for any keys; found=%i\n", (unsigned long)i*BITS_PER_LONG, found > 0); + } + /* If there are no keys in the lower block, check the higher block */ + if (!found) { + for (i = KEY_OK; i < BTN_TRIGGER_HAPPY; ++i) { + if (test_bit (i, bitmask_key)) { + log_debug("test_key: Found key %x in high block\n", i); + found = 1; + break; + } + } + } + + if (found > 0) + udev_builtin_add_property(dev, test, "ID_INPUT_KEY", "1"); + + /* the first 32 bits are ESC, numbers, and Q to D; if we have all of + * those, consider it a full keyboard; do not test KEY_RESERVED, though */ + mask = 0xFFFFFFFE; + if ((bitmask_key[0] & mask) == mask) + udev_builtin_add_property(dev, test, "ID_INPUT_KEYBOARD", "1"); +} + +static int builtin_input_id(struct udev_device *dev, int argc, char *argv[], bool test) +{ + struct udev_device *pdev; + unsigned long bitmask_ev[NBITS(EV_MAX)]; + unsigned long bitmask_abs[NBITS(ABS_MAX)]; + unsigned long bitmask_key[NBITS(KEY_MAX)]; + unsigned long bitmask_rel[NBITS(REL_MAX)]; + + /* walk up the parental chain until we find the real input device; the + * argument is very likely a subdevice of this, like eventN */ + pdev = dev; + while (pdev != NULL && udev_device_get_sysattr_value(pdev, "capabilities/ev") == NULL) + pdev = udev_device_get_parent_with_subsystem_devtype(pdev, "input", NULL); + + /* not an "input" class device */ + if (pdev == NULL) + return EXIT_SUCCESS; + + /* Use this as a flag that input devices were detected, so that this + * program doesn't need to be called more than once per device */ + udev_builtin_add_property(dev, test, "ID_INPUT", "1"); + get_cap_mask(dev, pdev, "capabilities/ev", bitmask_ev, sizeof(bitmask_ev), test); + get_cap_mask(dev, pdev, "capabilities/abs", bitmask_abs, sizeof(bitmask_abs), test); + get_cap_mask(dev, pdev, "capabilities/rel", bitmask_rel, sizeof(bitmask_rel), test); + get_cap_mask(dev, pdev, "capabilities/key", bitmask_key, sizeof(bitmask_key), test); + test_pointers(dev, bitmask_ev, bitmask_abs, bitmask_key, bitmask_rel, test); + test_key(dev, bitmask_ev, bitmask_key, test); + return EXIT_SUCCESS; +} + +const struct udev_builtin udev_builtin_input_id = { + .name = "input_id", + .cmd = builtin_input_id, + .help = "input device properties", +}; diff --git a/src/udev/udev-builtin-kmod.c b/src/udev/udev-builtin-kmod.c new file mode 100644 index 0000000000..17aca2944d --- /dev/null +++ b/src/udev/udev-builtin-kmod.c @@ -0,0 +1,134 @@ +/* + * load kernel modules + * + * Copyright (C) 2011-2012 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2011 ProFUSION embedded systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <libkmod.h> + +#include "udev.h" + +static struct kmod_ctx *ctx; + +static int load_module(struct udev *udev, const char *alias) +{ + struct kmod_list *list = NULL; + struct kmod_list *l; + int err; + + err = kmod_module_new_from_lookup(ctx, alias, &list); + if (err < 0) + return err; + + if (list == NULL) + log_debug("no module matches '%s'\n", alias); + + kmod_list_foreach(l, list) { + struct kmod_module *mod = kmod_module_get_module(l); + + err = kmod_module_probe_insert_module(mod, KMOD_PROBE_APPLY_BLACKLIST, NULL, NULL, NULL, NULL); + if (err == KMOD_PROBE_APPLY_BLACKLIST) + log_debug("module '%s' is blacklisted\n", kmod_module_get_name(mod)); + else if (err == 0) + log_debug("inserted '%s'\n", kmod_module_get_name(mod)); + else + log_debug("failed to insert '%s'\n", kmod_module_get_name(mod)); + + kmod_module_unref(mod); + } + + kmod_module_unref_list(list); + return err; +} + +static void udev_kmod_log(void *data, int priority, const char *file, int line, + const char *fn, const char *format, va_list args) +{ + udev_main_log(data, priority, file, line, fn, format, args); +} + +static int builtin_kmod(struct udev_device *dev, int argc, char *argv[], bool test) +{ + struct udev *udev = udev_device_get_udev(dev); + int i; + + if (!ctx) + return 0; + + if (argc < 3 || strcmp(argv[1], "load")) { + log_error("expect: %s load <module>\n", argv[0]); + return EXIT_FAILURE; + } + + for (i = 2; argv[i]; i++) { + log_debug("execute '%s' '%s'\n", argv[1], argv[i]); + load_module(udev, argv[i]); + } + + return EXIT_SUCCESS; +} + +/* called at udev startup and reload */ +static int builtin_kmod_init(struct udev *udev) +{ + if (ctx) + return 0; + + ctx = kmod_new(NULL, NULL); + if (!ctx) + return -ENOMEM; + + log_debug("load module index\n"); + kmod_set_log_fn(ctx, udev_kmod_log, udev); + kmod_load_resources(ctx); + return 0; +} + +/* called on udev shutdown and reload request */ +static void builtin_kmod_exit(struct udev *udev) +{ + log_debug("unload module index\n"); + ctx = kmod_unref(ctx); +} + +/* called every couple of seconds during event activity; 'true' if config has changed */ +static bool builtin_kmod_validate(struct udev *udev) +{ + log_debug("validate module index\n"); + if (!ctx) + return false; + return (kmod_validate_resources(ctx) != KMOD_RESOURCES_OK); +} + +const struct udev_builtin udev_builtin_kmod = { + .name = "kmod", + .cmd = builtin_kmod, + .init = builtin_kmod_init, + .exit = builtin_kmod_exit, + .validate = builtin_kmod_validate, + .help = "kernel module loader", + .run_once = false, +}; diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c new file mode 100644 index 0000000000..eff4552778 --- /dev/null +++ b/src/udev/udev-builtin-net_id.c @@ -0,0 +1,55 @@ +/*** + 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 <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include "udev.h" + +/* IEEE Organizationally Unique Identifier */ +static int lookup_OUI(struct udev_device *dev, bool test) { + const char *addr; + unsigned int a1, a2, a3; + char oui[16]; + + addr = udev_device_get_sysattr_value(dev, "address"); + if (!addr) + return -ENOENT; + + if (sscanf(addr, "%x:%x:%x:", &a1, &a2, &a3) != 3) + return -EINVAL; + + snprintf(oui, sizeof(oui), "OUI:%X%X%X", a1, a2, a3); + return udev_builtin_hwdb_lookup(dev, oui, test); +} + +static int builtin_net_id(struct udev_device *dev, int argc, char *argv[], bool test) { + lookup_OUI(dev, test); + return EXIT_SUCCESS; +} + +const struct udev_builtin udev_builtin_net_id = { + .name = "net_id", + .cmd = builtin_net_id, + .help = "network device properties", +}; diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c new file mode 100644 index 0000000000..bfdedc0989 --- /dev/null +++ b/src/udev/udev-builtin-path_id.c @@ -0,0 +1,549 @@ +/* + * compose persistent device path + * + * Copyright (C) 2009-2011 Kay Sievers <kay@vrfy.org> + * + * Logic based on Hannes Reinecke's shell script. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <fcntl.h> +#include <errno.h> +#include <dirent.h> +#include <getopt.h> + +#include "udev.h" + +static int path_prepend(char **path, const char *fmt, ...) +{ + va_list va; + char *pre; + int err = 0; + + va_start(va, fmt); + err = vasprintf(&pre, fmt, va); + va_end(va); + if (err < 0) + goto out; + + if (*path != NULL) { + char *new; + + err = asprintf(&new, "%s-%s", pre, *path); + free(pre); + if (err < 0) + goto out; + free(*path); + *path = new; + } else { + *path = pre; + } +out: + return err; +} + +/* +** Linux only supports 32 bit luns. +** See drivers/scsi/scsi_scan.c::scsilun_to_int() for more details. +*/ +static int format_lun_number(struct udev_device *dev, char **path) +{ + unsigned long lun = strtoul(udev_device_get_sysnum(dev), NULL, 10); + + /* address method 0, peripheral device addressing with bus id of zero */ + if (lun < 256) + return path_prepend(path, "lun-%d", lun); + /* handle all other lun addressing methods by using a variant of the original lun format */ + return path_prepend(path, "lun-0x%04x%04x00000000", (lun & 0xffff), (lun >> 16) & 0xffff); +} + +static struct udev_device *skip_subsystem(struct udev_device *dev, const char *subsys) +{ + struct udev_device *parent = dev; + + while (parent != NULL) { + const char *subsystem; + + subsystem = udev_device_get_subsystem(parent); + if (subsystem == NULL || strcmp(subsystem, subsys) != 0) + break; + dev = parent; + parent = udev_device_get_parent(parent); + } + return dev; +} + +static struct udev_device *handle_scsi_fibre_channel(struct udev_device *parent, char **path) +{ + struct udev *udev = udev_device_get_udev(parent); + struct udev_device *targetdev; + struct udev_device *fcdev = NULL; + const char *port; + char *lun = NULL; + + targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target"); + if (targetdev == NULL) + return NULL; + + fcdev = udev_device_new_from_subsystem_sysname(udev, "fc_transport", udev_device_get_sysname(targetdev)); + if (fcdev == NULL) + return NULL; + port = udev_device_get_sysattr_value(fcdev, "port_name"); + if (port == NULL) { + parent = NULL; + goto out; + } + + format_lun_number(parent, &lun); + path_prepend(path, "fc-%s-%s", port, lun); + if (lun) + free(lun); +out: + udev_device_unref(fcdev); + return parent; +} + +static struct udev_device *handle_scsi_sas(struct udev_device *parent, char **path) +{ + struct udev *udev = udev_device_get_udev(parent); + struct udev_device *targetdev; + struct udev_device *target_parent; + struct udev_device *sasdev; + const char *sas_address; + char *lun = NULL; + + targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target"); + if (targetdev == NULL) + return NULL; + + target_parent = udev_device_get_parent(targetdev); + if (target_parent == NULL) + return NULL; + + sasdev = udev_device_new_from_subsystem_sysname(udev, "sas_device", + udev_device_get_sysname(target_parent)); + if (sasdev == NULL) + return NULL; + + sas_address = udev_device_get_sysattr_value(sasdev, "sas_address"); + if (sas_address == NULL) { + parent = NULL; + goto out; + } + + format_lun_number(parent, &lun); + path_prepend(path, "sas-%s-%s", sas_address, lun); + if (lun) + free(lun); +out: + udev_device_unref(sasdev); + return parent; +} + +static struct udev_device *handle_scsi_iscsi(struct udev_device *parent, char **path) +{ + struct udev *udev = udev_device_get_udev(parent); + struct udev_device *transportdev; + struct udev_device *sessiondev = NULL; + const char *target; + char *connname; + struct udev_device *conndev = NULL; + const char *addr; + const char *port; + char *lun = NULL; + + /* find iscsi session */ + transportdev = parent; + for (;;) { + transportdev = udev_device_get_parent(transportdev); + if (transportdev == NULL) + return NULL; + if (startswith(udev_device_get_sysname(transportdev), "session")) + break; + } + + /* find iscsi session device */ + sessiondev = udev_device_new_from_subsystem_sysname(udev, "iscsi_session", udev_device_get_sysname(transportdev)); + if (sessiondev == NULL) + return NULL; + target = udev_device_get_sysattr_value(sessiondev, "targetname"); + if (target == NULL) { + parent = NULL; + goto out; + } + + if (asprintf(&connname, "connection%s:0", udev_device_get_sysnum(transportdev)) < 0) { + parent = NULL; + goto out; + } + conndev = udev_device_new_from_subsystem_sysname(udev, "iscsi_connection", connname); + free(connname); + if (conndev == NULL) { + parent = NULL; + goto out; + } + addr = udev_device_get_sysattr_value(conndev, "persistent_address"); + port = udev_device_get_sysattr_value(conndev, "persistent_port"); + if (addr == NULL || port == NULL) { + parent = NULL; + goto out; + } + + format_lun_number(parent, &lun); + path_prepend(path, "ip-%s:%s-iscsi-%s-%s", addr, port, target, lun); + if (lun) + free(lun); +out: + udev_device_unref(sessiondev); + udev_device_unref(conndev); + return parent; +} + +static struct udev_device *handle_scsi_default(struct udev_device *parent, char **path) +{ + struct udev_device *hostdev; + int host, bus, target, lun; + const char *name; + char *base; + char *pos; + DIR *dir; + struct dirent *dent; + int basenum; + + hostdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host"); + if (hostdev == NULL) + return NULL; + + name = udev_device_get_sysname(parent); + if (sscanf(name, "%d:%d:%d:%d", &host, &bus, &target, &lun) != 4) + return NULL; + + /* + * Rebase host offset to get the local relative number + * + * Note: This is by definition racy, unreliable and too simple. + * Please do not copy this model anywhere. It's just a left-over + * from the time we had no idea how things should look like in + * the end. + * + * Making assumptions about a global in-kernel counter and use + * that to calculate a local offset is a very broken concept. It + * can only work as long as things are in strict order. + * + * The kernel needs to export the instance/port number of a + * controller directly, without the need for rebase magic like + * this. Manual driver unbind/bind, parallel hotplug/unplug will + * get into the way of this "I hope it works" logic. + */ + basenum = -1; + base = strdup(udev_device_get_syspath(hostdev)); + if (base == NULL) + return NULL; + pos = strrchr(base, '/'); + if (pos == NULL) { + parent = NULL; + goto out; + } + pos[0] = '\0'; + dir = opendir(base); + if (dir == NULL) { + parent = NULL; + goto out; + } + for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { + char *rest; + int i; + + if (dent->d_name[0] == '.') + continue; + if (dent->d_type != DT_DIR && dent->d_type != DT_LNK) + continue; + if (!startswith(dent->d_name, "host")) + continue; + i = strtoul(&dent->d_name[4], &rest, 10); + if (rest[0] != '\0') + continue; + /* + * find the smallest number; the host really needs to export its + * own instance number per parent device; relying on the global host + * enumeration and plainly rebasing the numbers sounds unreliable + */ + if (basenum == -1 || i < basenum) + basenum = i; + } + closedir(dir); + if (basenum == -1) { + parent = NULL; + goto out; + } + host -= basenum; + + path_prepend(path, "scsi-%u:%u:%u:%u", host, bus, target, lun); +out: + free(base); + return hostdev; +} + +static struct udev_device *handle_scsi(struct udev_device *parent, char **path) +{ + const char *devtype; + const char *name; + const char *id; + + devtype = udev_device_get_devtype(parent); + if (devtype == NULL || strcmp(devtype, "scsi_device") != 0) + return parent; + + /* firewire */ + id = udev_device_get_sysattr_value(parent, "ieee1394_id"); + if (id != NULL) { + parent = skip_subsystem(parent, "scsi"); + path_prepend(path, "ieee1394-0x%s", id); + goto out; + } + + /* lousy scsi sysfs does not have a "subsystem" for the transport */ + name = udev_device_get_syspath(parent); + + if (strstr(name, "/rport-") != NULL) { + parent = handle_scsi_fibre_channel(parent, path); + goto out; + } + + if (strstr(name, "/end_device-") != NULL) { + parent = handle_scsi_sas(parent, path); + goto out; + } + + if (strstr(name, "/session") != NULL) { + parent = handle_scsi_iscsi(parent, path); + goto out; + } + + /* + * We do not support the ATA transport class, it creates duplicated link + * names as the fake SCSI host adapters are all separated, they are all + * re-based as host == 0. ATA should just stop faking two duplicated + * hierarchies for a single topology and leave the SCSI stuff alone; + * until that happens, there are no by-path/ links for ATA devices behind + * an ATA transport class. + */ + if (strstr(name, "/ata") != NULL) { + parent = NULL; + goto out; + } + + parent = handle_scsi_default(parent, path); +out: + return parent; +} + +static struct udev_device *handle_cciss(struct udev_device *parent, char **path) +{ + const char *str; + unsigned int controller, disk; + + str = udev_device_get_sysname(parent); + if (sscanf(str, "c%ud%u%*s", &controller, &disk) != 2) + return NULL; + + path_prepend(path, "cciss-disk%u", disk); + parent = skip_subsystem(parent, "cciss"); + return parent; +} + +static void handle_scsi_tape(struct udev_device *dev, char **path) +{ + const char *name; + + /* must be the last device in the syspath */ + if (*path != NULL) + return; + + name = udev_device_get_sysname(dev); + if (startswith(name, "nst") && strchr("lma", name[3]) != NULL) + path_prepend(path, "nst%c", name[3]); + else if (startswith(name, "st") && strchr("lma", name[2]) != NULL) + path_prepend(path, "st%c", name[2]); +} + +static struct udev_device *handle_usb(struct udev_device *parent, char **path) +{ + const char *devtype; + const char *str; + const char *port; + + devtype = udev_device_get_devtype(parent); + if (devtype == NULL) + return parent; + if (strcmp(devtype, "usb_interface") != 0 && strcmp(devtype, "usb_device") != 0) + return parent; + + str = udev_device_get_sysname(parent); + port = strchr(str, '-'); + if (port == NULL) + return parent; + port++; + + parent = skip_subsystem(parent, "usb"); + path_prepend(path, "usb-0:%s", port); + return parent; +} + +static struct udev_device *handle_ccw(struct udev_device *parent, struct udev_device *dev, char **path) +{ + struct udev_device *scsi_dev; + + scsi_dev = udev_device_get_parent_with_subsystem_devtype(dev, "scsi", "scsi_device"); + if (scsi_dev != NULL) { + const char *wwpn; + const char *lun; + const char *hba_id; + + hba_id = udev_device_get_sysattr_value(scsi_dev, "hba_id"); + wwpn = udev_device_get_sysattr_value(scsi_dev, "wwpn"); + lun = udev_device_get_sysattr_value(scsi_dev, "fcp_lun"); + if (hba_id != NULL && lun != NULL && wwpn != NULL) { + path_prepend(path, "ccw-%s-zfcp-%s:%s", hba_id, wwpn, lun); + goto out; + } + } + + path_prepend(path, "ccw-%s", udev_device_get_sysname(parent)); +out: + parent = skip_subsystem(parent, "ccw"); + return parent; +} + +static int builtin_path_id(struct udev_device *dev, int argc, char *argv[], bool test) +{ + struct udev_device *parent; + char *path = NULL; + bool some_transport = false; + + /* S390 ccw bus */ + parent = udev_device_get_parent_with_subsystem_devtype(dev, "ccw", NULL); + if (parent != NULL) { + handle_ccw(parent, dev, &path); + goto out; + } + + /* walk up the chain of devices and compose path */ + parent = dev; + while (parent != NULL) { + const char *subsys; + + subsys = udev_device_get_subsystem(parent); + if (subsys == NULL) { + ; + } else if (strcmp(subsys, "scsi_tape") == 0) { + handle_scsi_tape(parent, &path); + } else if (strcmp(subsys, "scsi") == 0) { + parent = handle_scsi(parent, &path); + some_transport = true; + } else if (strcmp(subsys, "cciss") == 0) { + parent = handle_cciss(parent, &path); + some_transport = true; + } else if (strcmp(subsys, "usb") == 0) { + parent = handle_usb(parent, &path); + some_transport = true; + } else if (strcmp(subsys, "serio") == 0) { + path_prepend(&path, "serio-%s", udev_device_get_sysnum(parent)); + parent = skip_subsystem(parent, "serio"); + } else if (strcmp(subsys, "pci") == 0) { + path_prepend(&path, "pci-%s", udev_device_get_sysname(parent)); + parent = skip_subsystem(parent, "pci"); + } else if (strcmp(subsys, "platform") == 0) { + path_prepend(&path, "platform-%s", udev_device_get_sysname(parent)); + parent = skip_subsystem(parent, "platform"); + some_transport = true; + } else if (strcmp(subsys, "acpi") == 0) { + path_prepend(&path, "acpi-%s", udev_device_get_sysname(parent)); + parent = skip_subsystem(parent, "acpi"); + } else if (strcmp(subsys, "xen") == 0) { + path_prepend(&path, "xen-%s", udev_device_get_sysname(parent)); + parent = skip_subsystem(parent, "xen"); + } else if (strcmp(subsys, "virtio") == 0) { + path_prepend(&path, "virtio-pci-%s", udev_device_get_sysname(parent)); + parent = skip_subsystem(parent, "virtio"); + } else if (strcmp(subsys, "scm") == 0) { + path_prepend(&path, "scm-%s", udev_device_get_sysname(parent)); + parent = skip_subsystem(parent, "scm"); + } + + parent = udev_device_get_parent(parent); + } + + /* + * Do not return a single-parent-device-only for block + * devices, they might have entire buses behind it which + * do not get unique IDs only by using the parent device. + */ + if (!some_transport && streq(udev_device_get_subsystem(dev), "block")) { + free(path); + path = NULL; + } + +out: + if (path != NULL) { + char tag[UTIL_NAME_SIZE]; + size_t i; + const char *p; + + /* compose valid udev tag name */ + for (p = path, i = 0; *p; p++) { + if ((*p >= '0' && *p <= '9') || + (*p >= 'A' && *p <= 'Z') || + (*p >= 'a' && *p <= 'z') || + *p == '-') { + tag[i++] = *p; + continue; + } + + /* skip all leading '_' */ + if (i == 0) + continue; + + /* avoid second '_' */ + if (tag[i-1] == '_') + continue; + + tag[i++] = '_'; + } + /* strip trailing '_' */ + while (i > 0 && tag[i-1] == '_') + i--; + tag[i] = '\0'; + + udev_builtin_add_property(dev, test, "ID_PATH", path); + udev_builtin_add_property(dev, test, "ID_PATH_TAG", tag); + free(path); + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +const struct udev_builtin udev_builtin_path_id = { + .name = "path_id", + .cmd = builtin_path_id, + .help = "compose persistent device path", + .run_once = true, +}; diff --git a/src/udev/udev-builtin-uaccess.c b/src/udev/udev-builtin-uaccess.c new file mode 100644 index 0000000000..662bac9e0b --- /dev/null +++ b/src/udev/udev-builtin-uaccess.c @@ -0,0 +1,99 @@ +/* + * manage device node user ACL + * + * Copyright 2010-2012 Kay Sievers <kay@vrfy.org> + * Copyright 2010 Lennart Poettering + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <fcntl.h> +#include <errno.h> +#include <dirent.h> +#include <getopt.h> + +#include <systemd/sd-daemon.h> +#include <systemd/sd-login.h> +#include "logind-acl.h" +#include "udev.h" +#include "util.h" + +static int builtin_uaccess(struct udev_device *dev, int argc, char *argv[], bool test) +{ + int r; + const char *path = NULL, *seat; + bool changed_acl = false; + uid_t uid; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + /* don't muck around with ACLs when the system is not running systemd */ + if (!sd_booted()) + return 0; + + path = udev_device_get_devnode(dev); + seat = udev_device_get_property_value(dev, "ID_SEAT"); + if (!seat) + seat = "seat0"; + + r = sd_seat_get_active(seat, NULL, &uid); + if (r == -ENOENT) { + /* No active session on this seat */ + r = 0; + goto finish; + } else if (r < 0) { + log_error("Failed to determine active user on seat %s.", seat); + goto finish; + } + + r = devnode_acl(path, true, false, 0, true, uid); + if (r < 0) { + log_error("Failed to apply ACL on %s: %s", path, strerror(-r)); + goto finish; + } + + changed_acl = true; + r = 0; + +finish: + if (path && !changed_acl) { + int k; + + /* Better be safe than sorry and reset ACL */ + k = devnode_acl(path, true, false, 0, false, 0); + if (k < 0) { + log_error("Failed to apply ACL on %s: %s", path, strerror(-k)); + if (r >= 0) + r = k; + } + } + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + +const struct udev_builtin udev_builtin_uaccess = { + .name = "uaccess", + .cmd = builtin_uaccess, + .help = "manage device node user ACL", +}; diff --git a/src/udev/udev-builtin-usb_id.c b/src/udev/udev-builtin-usb_id.c new file mode 100644 index 0000000000..13d1226393 --- /dev/null +++ b/src/udev/udev-builtin-usb_id.c @@ -0,0 +1,477 @@ +/* + * USB device properties and persistent device path + * + * Copyright (c) 2005 SUSE Linux Products GmbH, Germany + * Author: Hannes Reinecke <hare@suse.de> + * + * Copyright (C) 2005-2011 Kay Sievers <kay@vrfy.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <fcntl.h> +#include <errno.h> + +#include "udev.h" + +static void set_usb_iftype(char *to, int if_class_num, size_t len) +{ + const char *type = "generic"; + + switch (if_class_num) { + case 1: + type = "audio"; + break; + case 2: /* CDC-Control */ + break; + case 3: + type = "hid"; + break; + case 5: /* Physical */ + break; + case 6: + type = "media"; + break; + case 7: + type = "printer"; + break; + case 8: + type = "storage"; + break; + case 9: + type = "hub"; + break; + case 0x0a: /* CDC-Data */ + break; + case 0x0b: /* Chip/Smart Card */ + break; + case 0x0d: /* Content Security */ + break; + case 0x0e: + type = "video"; + break; + case 0xdc: /* Diagnostic Device */ + break; + case 0xe0: /* Wireless Controller */ + break; + case 0xfe: /* Application-specific */ + break; + case 0xff: /* Vendor-specific */ + break; + default: + break; + } + strncpy(to, type, len); + to[len-1] = '\0'; +} + +static int set_usb_mass_storage_ifsubtype(char *to, const char *from, size_t len) +{ + int type_num = 0; + char *eptr; + const char *type = "generic"; + + type_num = strtoul(from, &eptr, 0); + if (eptr != from) { + switch (type_num) { + case 2: + type = "atapi"; + break; + case 3: + type = "tape"; + break; + case 4: /* UFI */ + case 5: /* SFF-8070i */ + type = "floppy"; + break; + case 1: /* RBC devices */ + type = "rbc"; + break; + case 6: /* Transparent SPC-2 devices */ + type = "scsi"; + break; + default: + break; + } + } + util_strscpy(to, len, type); + return type_num; +} + +static void set_scsi_type(char *to, const char *from, size_t len) +{ + int type_num; + char *eptr; + const char *type = "generic"; + + type_num = strtoul(from, &eptr, 0); + if (eptr != from) { + switch (type_num) { + case 0: + case 0xe: + type = "disk"; + break; + case 1: + type = "tape"; + break; + case 4: + case 7: + case 0xf: + type = "optical"; + break; + case 5: + type = "cd"; + break; + default: + break; + } + } + util_strscpy(to, len, type); +} + +#define USB_DT_DEVICE 0x01 +#define USB_DT_INTERFACE 0x04 + +static int dev_if_packed_info(struct udev_device *dev, char *ifs_str, size_t len) +{ + char *filename = NULL; + int fd; + ssize_t size; + unsigned char buf[18 + 65535]; + unsigned int pos, strpos; + struct usb_interface_descriptor { + u_int8_t bLength; + u_int8_t bDescriptorType; + u_int8_t bInterfaceNumber; + u_int8_t bAlternateSetting; + u_int8_t bNumEndpoints; + u_int8_t bInterfaceClass; + u_int8_t bInterfaceSubClass; + u_int8_t bInterfaceProtocol; + u_int8_t iInterface; + } __attribute__((packed)); + int err = 0; + + if (asprintf(&filename, "%s/descriptors", udev_device_get_syspath(dev)) < 0) { + err = -1; + goto out; + } + fd = open(filename, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + fprintf(stderr, "error opening USB device 'descriptors' file\n"); + err = -1; + goto out; + } + size = read(fd, buf, sizeof(buf)); + close(fd); + if (size < 18 || size == sizeof(buf)) { + err = -1; + goto out; + } + + pos = 0; + strpos = 0; + ifs_str[0] = '\0'; + while (pos < sizeof(buf) && strpos+7 < len-2) { + struct usb_interface_descriptor *desc; + char if_str[8]; + + desc = (struct usb_interface_descriptor *) &buf[pos]; + if (desc->bLength < 3) + break; + pos += desc->bLength; + + if (desc->bDescriptorType != USB_DT_INTERFACE) + continue; + + if (snprintf(if_str, 8, ":%02x%02x%02x", + desc->bInterfaceClass, + desc->bInterfaceSubClass, + desc->bInterfaceProtocol) != 7) + continue; + + if (strstr(ifs_str, if_str) != NULL) + continue; + + memcpy(&ifs_str[strpos], if_str, 8), + strpos += 7; + } + if (strpos > 0) { + ifs_str[strpos++] = ':'; + ifs_str[strpos++] = '\0'; + } +out: + free(filename); + return err; +} + +/* + * A unique USB identification is generated like this: + * + * 1.) Get the USB device type from InterfaceClass and InterfaceSubClass + * 2.) If the device type is 'Mass-Storage/SPC-2' or 'Mass-Storage/RBC' + * use the SCSI vendor and model as USB-Vendor and USB-model. + * 3.) Otherwise use the USB manufacturer and product as + * USB-Vendor and USB-model. Any non-printable characters + * in those strings will be skipped; a slash '/' will be converted + * into a full stop '.'. + * 4.) If that fails, too, we will use idVendor and idProduct + * as USB-Vendor and USB-model. + * 5.) The USB identification is the USB-vendor and USB-model + * string concatenated with an underscore '_'. + * 6.) If the device supplies a serial number, this number + * is concatenated with the identification with an underscore '_'. + */ +static int builtin_usb_id(struct udev_device *dev, int argc, char *argv[], bool test) +{ + char vendor_str[64]; + char vendor_str_enc[256]; + const char *vendor_id; + char model_str[64]; + char model_str_enc[256]; + const char *product_id; + char serial_str[UTIL_NAME_SIZE]; + char packed_if_str[UTIL_NAME_SIZE]; + char revision_str[64]; + char type_str[64]; + char instance_str[64]; + const char *ifnum = NULL; + const char *driver = NULL; + char serial[256]; + + struct udev_device *dev_interface = NULL; + struct udev_device *dev_usb = NULL; + const char *if_class, *if_subclass; + int if_class_num; + int protocol = 0; + size_t l; + char *s; + + vendor_str[0] = '\0'; + model_str[0] = '\0'; + serial_str[0] = '\0'; + packed_if_str[0] = '\0'; + revision_str[0] = '\0'; + type_str[0] = '\0'; + instance_str[0] = '\0'; + + /* shortcut, if we are called directly for a "usb_device" type */ + if (udev_device_get_devtype(dev) != NULL && strcmp(udev_device_get_devtype(dev), "usb_device") == 0) { + dev_if_packed_info(dev, packed_if_str, sizeof(packed_if_str)); + dev_usb = dev; + goto fallback; + } + + /* usb interface directory */ + dev_interface = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_interface"); + if (dev_interface == NULL) { + log_debug("unable to access usb_interface device of '%s'\n", + udev_device_get_syspath(dev)); + return EXIT_FAILURE; + } + + ifnum = udev_device_get_sysattr_value(dev_interface, "bInterfaceNumber"); + driver = udev_device_get_sysattr_value(dev_interface, "driver"); + + if_class = udev_device_get_sysattr_value(dev_interface, "bInterfaceClass"); + if (!if_class) { + log_debug("%s: cannot get bInterfaceClass attribute\n", + udev_device_get_sysname(dev)); + return EXIT_FAILURE; + } + + if_class_num = strtoul(if_class, NULL, 16); + if (if_class_num == 8) { + /* mass storage */ + if_subclass = udev_device_get_sysattr_value(dev_interface, "bInterfaceSubClass"); + if (if_subclass != NULL) + protocol = set_usb_mass_storage_ifsubtype(type_str, if_subclass, sizeof(type_str)-1); + } else { + set_usb_iftype(type_str, if_class_num, sizeof(type_str)-1); + } + + log_debug("%s: if_class %d protocol %d\n", + udev_device_get_syspath(dev_interface), if_class_num, protocol); + + /* usb device directory */ + dev_usb = udev_device_get_parent_with_subsystem_devtype(dev_interface, "usb", "usb_device"); + if (!dev_usb) { + log_debug("unable to find parent 'usb' device of '%s'\n", + udev_device_get_syspath(dev)); + return EXIT_FAILURE; + } + + /* all interfaces of the device in a single string */ + dev_if_packed_info(dev_usb, packed_if_str, sizeof(packed_if_str)); + + /* mass storage : SCSI or ATAPI */ + if ((protocol == 6 || protocol == 2)) { + struct udev_device *dev_scsi; + const char *scsi_model, *scsi_vendor, *scsi_type, *scsi_rev; + int host, bus, target, lun; + + /* get scsi device */ + dev_scsi = udev_device_get_parent_with_subsystem_devtype(dev, "scsi", "scsi_device"); + if (dev_scsi == NULL) { + log_debug("unable to find parent 'scsi' device of '%s'\n", + udev_device_get_syspath(dev)); + goto fallback; + } + if (sscanf(udev_device_get_sysname(dev_scsi), "%d:%d:%d:%d", &host, &bus, &target, &lun) != 4) { + log_debug("invalid scsi device '%s'\n", udev_device_get_sysname(dev_scsi)); + goto fallback; + } + + /* Generic SPC-2 device */ + scsi_vendor = udev_device_get_sysattr_value(dev_scsi, "vendor"); + if (!scsi_vendor) { + log_debug("%s: cannot get SCSI vendor attribute\n", + udev_device_get_sysname(dev_scsi)); + goto fallback; + } + udev_util_encode_string(scsi_vendor, vendor_str_enc, sizeof(vendor_str_enc)); + util_replace_whitespace(scsi_vendor, vendor_str, sizeof(vendor_str)-1); + util_replace_chars(vendor_str, NULL); + + scsi_model = udev_device_get_sysattr_value(dev_scsi, "model"); + if (!scsi_model) { + log_debug("%s: cannot get SCSI model attribute\n", + udev_device_get_sysname(dev_scsi)); + goto fallback; + } + udev_util_encode_string(scsi_model, model_str_enc, sizeof(model_str_enc)); + util_replace_whitespace(scsi_model, model_str, sizeof(model_str)-1); + util_replace_chars(model_str, NULL); + + scsi_type = udev_device_get_sysattr_value(dev_scsi, "type"); + if (!scsi_type) { + log_debug("%s: cannot get SCSI type attribute\n", + udev_device_get_sysname(dev_scsi)); + goto fallback; + } + set_scsi_type(type_str, scsi_type, sizeof(type_str)-1); + + scsi_rev = udev_device_get_sysattr_value(dev_scsi, "rev"); + if (!scsi_rev) { + log_debug("%s: cannot get SCSI revision attribute\n", + udev_device_get_sysname(dev_scsi)); + goto fallback; + } + util_replace_whitespace(scsi_rev, revision_str, sizeof(revision_str)-1); + util_replace_chars(revision_str, NULL); + + /* + * some broken devices have the same identifiers + * for all luns, export the target:lun number + */ + sprintf(instance_str, "%d:%d", target, lun); + } + +fallback: + vendor_id = udev_device_get_sysattr_value(dev_usb, "idVendor"); + product_id = udev_device_get_sysattr_value(dev_usb, "idProduct"); + + /* fallback to USB vendor & device */ + if (vendor_str[0] == '\0') { + const char *usb_vendor = NULL; + + usb_vendor = udev_device_get_sysattr_value(dev_usb, "manufacturer"); + if (!usb_vendor) + usb_vendor = vendor_id; + if (!usb_vendor) { + log_debug("No USB vendor information available\n"); + return EXIT_FAILURE; + } + udev_util_encode_string(usb_vendor, vendor_str_enc, sizeof(vendor_str_enc)); + util_replace_whitespace(usb_vendor, vendor_str, sizeof(vendor_str)-1); + util_replace_chars(vendor_str, NULL); + } + + if (model_str[0] == '\0') { + const char *usb_model = NULL; + + usb_model = udev_device_get_sysattr_value(dev_usb, "product"); + if (!usb_model) + usb_model = product_id; + if (!usb_model) + return EXIT_FAILURE; + udev_util_encode_string(usb_model, model_str_enc, sizeof(model_str_enc)); + util_replace_whitespace(usb_model, model_str, sizeof(model_str)-1); + util_replace_chars(model_str, NULL); + } + + if (revision_str[0] == '\0') { + const char *usb_rev; + + usb_rev = udev_device_get_sysattr_value(dev_usb, "bcdDevice"); + if (usb_rev) { + util_replace_whitespace(usb_rev, revision_str, sizeof(revision_str)-1); + util_replace_chars(revision_str, NULL); + } + } + + if (serial_str[0] == '\0') { + const char *usb_serial; + + usb_serial = udev_device_get_sysattr_value(dev_usb, "serial"); + if (usb_serial) { + util_replace_whitespace(usb_serial, serial_str, sizeof(serial_str)-1); + util_replace_chars(serial_str, NULL); + } + } + + s = serial; + l = util_strpcpyl(&s, sizeof(serial), vendor_str, "_", model_str, NULL); + if (serial_str[0] != '\0') + l = util_strpcpyl(&s, l, "_", serial_str, NULL); + + if (instance_str[0] != '\0') + util_strpcpyl(&s, l, "-", instance_str, NULL); + + udev_builtin_add_property(dev, test, "ID_VENDOR", vendor_str); + udev_builtin_add_property(dev, test, "ID_VENDOR_ENC", vendor_str_enc); + udev_builtin_add_property(dev, test, "ID_VENDOR_ID", vendor_id); + udev_builtin_add_property(dev, test, "ID_MODEL", model_str); + udev_builtin_add_property(dev, test, "ID_MODEL_ENC", model_str_enc); + udev_builtin_add_property(dev, test, "ID_MODEL_ID", product_id); + udev_builtin_add_property(dev, test, "ID_REVISION", revision_str); + udev_builtin_add_property(dev, test, "ID_SERIAL", serial); + if (serial_str[0] != '\0') + udev_builtin_add_property(dev, test, "ID_SERIAL_SHORT", serial_str); + if (type_str[0] != '\0') + udev_builtin_add_property(dev, test, "ID_TYPE", type_str); + if (instance_str[0] != '\0') + udev_builtin_add_property(dev, test, "ID_INSTANCE", instance_str); + udev_builtin_add_property(dev, test, "ID_BUS", "usb"); + if (packed_if_str[0] != '\0') + udev_builtin_add_property(dev, test, "ID_USB_INTERFACES", packed_if_str); + if (ifnum != NULL) + udev_builtin_add_property(dev, test, "ID_USB_INTERFACE_NUM", ifnum); + if (driver != NULL) + udev_builtin_add_property(dev, test, "ID_USB_DRIVER", driver); + return EXIT_SUCCESS; +} + +const struct udev_builtin udev_builtin_usb_id = { + .name = "usb_id", + .cmd = builtin_usb_id, + .help = "usb device properties", + .run_once = true, +}; diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c new file mode 100644 index 0000000000..ee81b2d713 --- /dev/null +++ b/src/udev/udev-builtin.c @@ -0,0 +1,144 @@ +/*** + This file is part of systemd. + + Copyright 2007-2012 Kay Sievers <kay@vrfy.org> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> + +#include "udev.h" + +static bool initialized; + +static const struct udev_builtin *builtins[] = { + [UDEV_BUILTIN_BLKID] = &udev_builtin_blkid, + [UDEV_BUILTIN_BTRFS] = &udev_builtin_btrfs, + [UDEV_BUILTIN_FIRMWARE] = &udev_builtin_firmware, + [UDEV_BUILTIN_HWDB] = &udev_builtin_hwdb, + [UDEV_BUILTIN_INPUT_ID] = &udev_builtin_input_id, + [UDEV_BUILTIN_KMOD] = &udev_builtin_kmod, + [UDEV_BUILTIN_NET_ID] = &udev_builtin_net_id, + [UDEV_BUILTIN_PATH_ID] = &udev_builtin_path_id, + [UDEV_BUILTIN_USB_ID] = &udev_builtin_usb_id, +#ifdef HAVE_ACL + [UDEV_BUILTIN_UACCESS] = &udev_builtin_uaccess, +#endif +}; + +void udev_builtin_init(struct udev *udev) +{ + unsigned int i; + + if (initialized) + return; + + for (i = 0; i < ELEMENTSOF(builtins); i++) + if (builtins[i]->init) + builtins[i]->init(udev); + + initialized = true; +} + +void udev_builtin_exit(struct udev *udev) +{ + unsigned int i; + + if (!initialized) + return; + + for (i = 0; i < ELEMENTSOF(builtins); i++) + if (builtins[i]->exit) + builtins[i]->exit(udev); + + initialized = false; +} + +bool udev_builtin_validate(struct udev *udev) +{ + unsigned int i; + + for (i = 0; i < ELEMENTSOF(builtins); i++) + if (builtins[i]->validate && builtins[i]->validate(udev)) + return true; + return false; +} + +void udev_builtin_list(struct udev *udev) +{ + unsigned int i; + + for (i = 0; i < ELEMENTSOF(builtins); i++) + fprintf(stderr, " %-12s %s\n", builtins[i]->name, builtins[i]->help); +} + +const char *udev_builtin_name(enum udev_builtin_cmd cmd) +{ + return builtins[cmd]->name; +} + +bool udev_builtin_run_once(enum udev_builtin_cmd cmd) +{ + return builtins[cmd]->run_once; +} + +enum udev_builtin_cmd udev_builtin_lookup(const char *command) +{ + char name[UTIL_PATH_SIZE]; + enum udev_builtin_cmd i; + char *pos; + + util_strscpy(name, sizeof(name), command); + pos = strchr(name, ' '); + if (pos) + pos[0] = '\0'; + for (i = 0; i < ELEMENTSOF(builtins); i++) + if (strcmp(builtins[i]->name, name) == 0) + return i; + return UDEV_BUILTIN_MAX; +} + +int udev_builtin_run(struct udev_device *dev, enum udev_builtin_cmd cmd, const char *command, bool test) +{ + char arg[UTIL_PATH_SIZE]; + int argc; + char *argv[128]; + + /* we need '0' here to reset the internal state */ + optind = 0; + util_strscpy(arg, sizeof(arg), command); + udev_build_argv(udev_device_get_udev(dev), arg, &argc, argv); + return builtins[cmd]->cmd(dev, argc, argv, test); +} + +int udev_builtin_add_property(struct udev_device *dev, bool test, const char *key, const char *val) +{ + struct udev_list_entry *entry; + + entry = udev_device_add_property(dev, key, val); + /* store in db, skip private keys */ + if (key[0] != '.') + udev_list_entry_set_num(entry, true); + + if (test) + printf("%s=%s\n", key, val); + return 0; +} diff --git a/src/udev/udev-ctrl.c b/src/udev/udev-ctrl.c new file mode 100644 index 0000000000..a235912ffb --- /dev/null +++ b/src/udev/udev-ctrl.c @@ -0,0 +1,490 @@ +/* + * libudev - interface to udev device information + * + * Copyright (C) 2008 Kay Sievers <kay@vrfy.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/poll.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include "udev.h" + +/* wire protocol magic must match */ +#define UDEV_CTRL_MAGIC 0xdead1dea + +enum udev_ctrl_msg_type { + UDEV_CTRL_UNKNOWN, + UDEV_CTRL_SET_LOG_LEVEL, + UDEV_CTRL_STOP_EXEC_QUEUE, + UDEV_CTRL_START_EXEC_QUEUE, + UDEV_CTRL_RELOAD, + UDEV_CTRL_SET_ENV, + UDEV_CTRL_SET_CHILDREN_MAX, + UDEV_CTRL_PING, + UDEV_CTRL_EXIT, +}; + +struct udev_ctrl_msg_wire { + char version[16]; + unsigned int magic; + enum udev_ctrl_msg_type type; + union { + int intval; + char buf[256]; + }; +}; + +struct udev_ctrl_msg { + int refcount; + struct udev_ctrl_connection *conn; + struct udev_ctrl_msg_wire ctrl_msg_wire; +}; + +struct udev_ctrl { + int refcount; + struct udev *udev; + int sock; + struct sockaddr_un saddr; + socklen_t addrlen; + bool bound; + bool cleanup_socket; + bool connected; +}; + +struct udev_ctrl_connection { + int refcount; + struct udev_ctrl *uctrl; + int sock; +}; + +struct udev_ctrl *udev_ctrl_new_from_fd(struct udev *udev, int fd) +{ + struct udev_ctrl *uctrl; + + uctrl = calloc(1, sizeof(struct udev_ctrl)); + if (uctrl == NULL) + return NULL; + uctrl->refcount = 1; + uctrl->udev = udev; + + if (fd < 0) { + uctrl->sock = socket(AF_LOCAL, SOCK_SEQPACKET|SOCK_NONBLOCK|SOCK_CLOEXEC, 0); + if (uctrl->sock < 0) { + log_error("error getting socket: %m\n"); + udev_ctrl_unref(uctrl); + return NULL; + } + } else { + uctrl->bound = true; + uctrl->sock = fd; + } + + uctrl->saddr.sun_family = AF_LOCAL; + util_strscpy(uctrl->saddr.sun_path, sizeof(uctrl->saddr.sun_path), "/run/udev/control"); + uctrl->addrlen = offsetof(struct sockaddr_un, sun_path) + strlen(uctrl->saddr.sun_path); + return uctrl; +} + +struct udev_ctrl *udev_ctrl_new(struct udev *udev) +{ + return udev_ctrl_new_from_fd(udev, -1); +} + +int udev_ctrl_enable_receiving(struct udev_ctrl *uctrl) +{ + int err; + + if (!uctrl->bound) { + err = bind(uctrl->sock, (struct sockaddr *)&uctrl->saddr, uctrl->addrlen); + if (err < 0 && errno == EADDRINUSE) { + unlink(uctrl->saddr.sun_path); + err = bind(uctrl->sock, (struct sockaddr *)&uctrl->saddr, uctrl->addrlen); + } + + if (err < 0) { + err = -errno; + log_error("bind failed: %m\n"); + return err; + } + + err = listen(uctrl->sock, 0); + if (err < 0) { + err = -errno; + log_error("listen failed: %m\n"); + return err; + } + + uctrl->bound = true; + uctrl->cleanup_socket = true; + } + return 0; +} + +struct udev *udev_ctrl_get_udev(struct udev_ctrl *uctrl) +{ + return uctrl->udev; +} + +struct udev_ctrl *udev_ctrl_ref(struct udev_ctrl *uctrl) +{ + if (uctrl == NULL) + return NULL; + uctrl->refcount++; + return uctrl; +} + +struct udev_ctrl *udev_ctrl_unref(struct udev_ctrl *uctrl) +{ + if (uctrl == NULL) + return NULL; + uctrl->refcount--; + if (uctrl->refcount > 0) + return uctrl; + if (uctrl->sock >= 0) + close(uctrl->sock); + free(uctrl); + return NULL; +} + +int udev_ctrl_cleanup(struct udev_ctrl *uctrl) +{ + if (uctrl == NULL) + return 0; + if (uctrl->cleanup_socket) + unlink(uctrl->saddr.sun_path); + return 0; +} + +int udev_ctrl_get_fd(struct udev_ctrl *uctrl) +{ + if (uctrl == NULL) + return -EINVAL; + return uctrl->sock; +} + +struct udev_ctrl_connection *udev_ctrl_get_connection(struct udev_ctrl *uctrl) +{ + struct udev_ctrl_connection *conn; + struct ucred ucred; + socklen_t slen; + const int on = 1; + + conn = calloc(1, sizeof(struct udev_ctrl_connection)); + if (conn == NULL) + return NULL; + conn->refcount = 1; + conn->uctrl = uctrl; + + conn->sock = accept4(uctrl->sock, NULL, NULL, SOCK_CLOEXEC|SOCK_NONBLOCK); + if (conn->sock < 0) { + if (errno != EINTR) + log_error("unable to receive ctrl connection: %m\n"); + goto err; + } + + /* check peer credential of connection */ + slen = sizeof(ucred); + if (getsockopt(conn->sock, SOL_SOCKET, SO_PEERCRED, &ucred, &slen) < 0) { + log_error("unable to receive credentials of ctrl connection: %m\n"); + goto err; + } + if (ucred.uid > 0) { + log_error("sender uid=%i, message ignored\n", ucred.uid); + goto err; + } + + /* enable receiving of the sender credentials in the messages */ + setsockopt(conn->sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); + udev_ctrl_ref(uctrl); + return conn; +err: + if (conn->sock >= 0) + close(conn->sock); + free(conn); + return NULL; +} + +struct udev_ctrl_connection *udev_ctrl_connection_ref(struct udev_ctrl_connection *conn) +{ + if (conn == NULL) + return NULL; + conn->refcount++; + return conn; +} + +struct udev_ctrl_connection *udev_ctrl_connection_unref(struct udev_ctrl_connection *conn) +{ + if (conn == NULL) + return NULL; + conn->refcount--; + if (conn->refcount > 0) + return conn; + if (conn->sock >= 0) + close(conn->sock); + udev_ctrl_unref(conn->uctrl); + free(conn); + return NULL; +} + +static int ctrl_send(struct udev_ctrl *uctrl, enum udev_ctrl_msg_type type, int intval, const char *buf, int timeout) +{ + struct udev_ctrl_msg_wire ctrl_msg_wire; + int err = 0; + + memset(&ctrl_msg_wire, 0x00, sizeof(struct udev_ctrl_msg_wire)); + strcpy(ctrl_msg_wire.version, "udev-" VERSION); + ctrl_msg_wire.magic = UDEV_CTRL_MAGIC; + ctrl_msg_wire.type = type; + + if (buf != NULL) + util_strscpy(ctrl_msg_wire.buf, sizeof(ctrl_msg_wire.buf), buf); + else + ctrl_msg_wire.intval = intval; + + if (!uctrl->connected) { + if (connect(uctrl->sock, (struct sockaddr *)&uctrl->saddr, uctrl->addrlen) < 0) { + err = -errno; + goto out; + } + uctrl->connected = true; + } + if (send(uctrl->sock, &ctrl_msg_wire, sizeof(ctrl_msg_wire), 0) < 0) { + err = -errno; + goto out; + } + + /* wait for peer message handling or disconnect */ + for (;;) { + struct pollfd pfd[1]; + int r; + + pfd[0].fd = uctrl->sock; + pfd[0].events = POLLIN; + r = poll(pfd, 1, timeout * 1000); + if (r < 0) { + if (errno == EINTR) + continue; + err = -errno; + break; + } + + if (r > 0 && pfd[0].revents & POLLERR) { + err = -EIO; + break; + } + + if (r == 0) + err = -ETIMEDOUT; + break; + } +out: + return err; +} + +int udev_ctrl_send_set_log_level(struct udev_ctrl *uctrl, int priority, int timeout) +{ + return ctrl_send(uctrl, UDEV_CTRL_SET_LOG_LEVEL, priority, NULL, timeout); +} + +int udev_ctrl_send_stop_exec_queue(struct udev_ctrl *uctrl, int timeout) +{ + return ctrl_send(uctrl, UDEV_CTRL_STOP_EXEC_QUEUE, 0, NULL, timeout); +} + +int udev_ctrl_send_start_exec_queue(struct udev_ctrl *uctrl, int timeout) +{ + return ctrl_send(uctrl, UDEV_CTRL_START_EXEC_QUEUE, 0, NULL, timeout); +} + +int udev_ctrl_send_reload(struct udev_ctrl *uctrl, int timeout) +{ + return ctrl_send(uctrl, UDEV_CTRL_RELOAD, 0, NULL, timeout); +} + +int udev_ctrl_send_set_env(struct udev_ctrl *uctrl, const char *key, int timeout) +{ + return ctrl_send(uctrl, UDEV_CTRL_SET_ENV, 0, key, timeout); +} + +int udev_ctrl_send_set_children_max(struct udev_ctrl *uctrl, int count, int timeout) +{ + return ctrl_send(uctrl, UDEV_CTRL_SET_CHILDREN_MAX, count, NULL, timeout); +} + +int udev_ctrl_send_ping(struct udev_ctrl *uctrl, int timeout) +{ + return ctrl_send(uctrl, UDEV_CTRL_PING, 0, NULL, timeout); +} + +int udev_ctrl_send_exit(struct udev_ctrl *uctrl, int timeout) +{ + return ctrl_send(uctrl, UDEV_CTRL_EXIT, 0, NULL, timeout); +} + +struct udev_ctrl_msg *udev_ctrl_receive_msg(struct udev_ctrl_connection *conn) +{ + struct udev_ctrl_msg *uctrl_msg; + ssize_t size; + struct msghdr smsg; + struct cmsghdr *cmsg; + struct iovec iov; + struct ucred *cred; + char cred_msg[CMSG_SPACE(sizeof(struct ucred))]; + + uctrl_msg = calloc(1, sizeof(struct udev_ctrl_msg)); + if (uctrl_msg == NULL) + return NULL; + uctrl_msg->refcount = 1; + uctrl_msg->conn = conn; + udev_ctrl_connection_ref(conn); + + /* wait for the incoming message */ + for(;;) { + struct pollfd pfd[1]; + int r; + + pfd[0].fd = conn->sock; + pfd[0].events = POLLIN; + + r = poll(pfd, 1, 10000); + if (r < 0) { + if (errno == EINTR) + continue; + goto err; + } else if (r == 0) { + log_error("timeout waiting for ctrl message\n"); + goto err; + } else { + if (!(pfd[0].revents & POLLIN)) { + log_error("ctrl connection error: %m\n"); + goto err; + } + } + + break; + } + + iov.iov_base = &uctrl_msg->ctrl_msg_wire; + iov.iov_len = sizeof(struct udev_ctrl_msg_wire); + memset(&smsg, 0x00, sizeof(struct msghdr)); + smsg.msg_iov = &iov; + smsg.msg_iovlen = 1; + smsg.msg_control = cred_msg; + smsg.msg_controllen = sizeof(cred_msg); + size = recvmsg(conn->sock, &smsg, 0); + if (size < 0) { + log_error("unable to receive ctrl message: %m\n"); + goto err; + } + cmsg = CMSG_FIRSTHDR(&smsg); + cred = (struct ucred *) CMSG_DATA(cmsg); + + if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) { + log_error("no sender credentials received, message ignored\n"); + goto err; + } + + if (cred->uid != 0) { + log_error("sender uid=%i, message ignored\n", cred->uid); + goto err; + } + + if (uctrl_msg->ctrl_msg_wire.magic != UDEV_CTRL_MAGIC) { + log_error("message magic 0x%08x doesn't match, ignore it\n", uctrl_msg->ctrl_msg_wire.magic); + goto err; + } + + return uctrl_msg; +err: + udev_ctrl_msg_unref(uctrl_msg); + return NULL; +} + +struct udev_ctrl_msg *udev_ctrl_msg_ref(struct udev_ctrl_msg *ctrl_msg) +{ + if (ctrl_msg == NULL) + return NULL; + ctrl_msg->refcount++; + return ctrl_msg; +} + +struct udev_ctrl_msg *udev_ctrl_msg_unref(struct udev_ctrl_msg *ctrl_msg) +{ + if (ctrl_msg == NULL) + return NULL; + ctrl_msg->refcount--; + if (ctrl_msg->refcount > 0) + return ctrl_msg; + udev_ctrl_connection_unref(ctrl_msg->conn); + free(ctrl_msg); + return NULL; +} + +int udev_ctrl_get_set_log_level(struct udev_ctrl_msg *ctrl_msg) +{ + if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_SET_LOG_LEVEL) + return ctrl_msg->ctrl_msg_wire.intval; + return -1; +} + +int udev_ctrl_get_stop_exec_queue(struct udev_ctrl_msg *ctrl_msg) +{ + if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_STOP_EXEC_QUEUE) + return 1; + return -1; +} + +int udev_ctrl_get_start_exec_queue(struct udev_ctrl_msg *ctrl_msg) +{ + if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_START_EXEC_QUEUE) + return 1; + return -1; +} + +int udev_ctrl_get_reload(struct udev_ctrl_msg *ctrl_msg) +{ + if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_RELOAD) + return 1; + return -1; +} + +const char *udev_ctrl_get_set_env(struct udev_ctrl_msg *ctrl_msg) +{ + if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_SET_ENV) + return ctrl_msg->ctrl_msg_wire.buf; + return NULL; +} + +int udev_ctrl_get_set_children_max(struct udev_ctrl_msg *ctrl_msg) +{ + if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_SET_CHILDREN_MAX) + return ctrl_msg->ctrl_msg_wire.intval; + return -1; +} + +int udev_ctrl_get_ping(struct udev_ctrl_msg *ctrl_msg) +{ + if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_PING) + return 1; + return -1; +} + +int udev_ctrl_get_exit(struct udev_ctrl_msg *ctrl_msg) +{ + if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_EXIT) + return 1; + return -1; +} diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c new file mode 100644 index 0000000000..33ed477637 --- /dev/null +++ b/src/udev/udev-event.c @@ -0,0 +1,905 @@ +/* + * Copyright (C) 2003-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 <stdlib.h> +#include <stdio.h> +#include <stddef.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <ctype.h> +#include <string.h> +#include <time.h> +#include <net/if.h> +#include <sys/ioctl.h> +#include <sys/prctl.h> +#include <sys/poll.h> +#include <sys/epoll.h> +#include <sys/wait.h> +#include <sys/socket.h> +#include <sys/signalfd.h> +#include <linux/sockios.h> + +#include "udev.h" + +struct udev_event *udev_event_new(struct udev_device *dev) +{ + struct udev *udev = udev_device_get_udev(dev); + struct udev_event *event; + + event = calloc(1, sizeof(struct udev_event)); + if (event == NULL) + return NULL; + event->dev = dev; + event->udev = udev; + udev_list_init(udev, &event->run_list, false); + event->fd_signal = -1; + event->birth_usec = now(CLOCK_MONOTONIC); + event->timeout_usec = 30 * 1000 * 1000; + return event; +} + +void udev_event_unref(struct udev_event *event) +{ + if (event == NULL) + return; + udev_list_cleanup(&event->run_list); + free(event->program_result); + free(event->name); + free(event); +} + +size_t udev_event_apply_format(struct udev_event *event, const char *src, char *dest, size_t size) +{ + struct udev_device *dev = event->dev; + enum subst_type { + SUBST_UNKNOWN, + SUBST_DEVNODE, + SUBST_ATTR, + SUBST_ENV, + SUBST_KERNEL, + SUBST_KERNEL_NUMBER, + SUBST_DRIVER, + SUBST_DEVPATH, + SUBST_ID, + SUBST_MAJOR, + SUBST_MINOR, + SUBST_RESULT, + SUBST_PARENT, + SUBST_NAME, + SUBST_LINKS, + SUBST_ROOT, + SUBST_SYS, + }; + static const struct subst_map { + const char *name; + const char fmt; + enum subst_type type; + } map[] = { + { .name = "devnode", .fmt = 'N', .type = SUBST_DEVNODE }, + { .name = "tempnode", .fmt = 'N', .type = SUBST_DEVNODE }, + { .name = "attr", .fmt = 's', .type = SUBST_ATTR }, + { .name = "sysfs", .fmt = 's', .type = SUBST_ATTR }, + { .name = "env", .fmt = 'E', .type = SUBST_ENV }, + { .name = "kernel", .fmt = 'k', .type = SUBST_KERNEL }, + { .name = "number", .fmt = 'n', .type = SUBST_KERNEL_NUMBER }, + { .name = "driver", .fmt = 'd', .type = SUBST_DRIVER }, + { .name = "devpath", .fmt = 'p', .type = SUBST_DEVPATH }, + { .name = "id", .fmt = 'b', .type = SUBST_ID }, + { .name = "major", .fmt = 'M', .type = SUBST_MAJOR }, + { .name = "minor", .fmt = 'm', .type = SUBST_MINOR }, + { .name = "result", .fmt = 'c', .type = SUBST_RESULT }, + { .name = "parent", .fmt = 'P', .type = SUBST_PARENT }, + { .name = "name", .fmt = 'D', .type = SUBST_NAME }, + { .name = "links", .fmt = 'L', .type = SUBST_LINKS }, + { .name = "root", .fmt = 'r', .type = SUBST_ROOT }, + { .name = "sys", .fmt = 'S', .type = SUBST_SYS }, + }; + const char *from; + char *s; + size_t l; + + from = src; + s = dest; + l = size; + + for (;;) { + enum subst_type type = SUBST_UNKNOWN; + char attrbuf[UTIL_PATH_SIZE]; + char *attr = NULL; + + while (from[0] != '\0') { + if (from[0] == '$') { + /* substitute named variable */ + unsigned int i; + + if (from[1] == '$') { + from++; + goto copy; + } + + for (i = 0; i < ELEMENTSOF(map); i++) { + if (startswith(&from[1], map[i].name)) { + type = map[i].type; + from += strlen(map[i].name)+1; + goto subst; + } + } + } else if (from[0] == '%') { + /* substitute format char */ + unsigned int i; + + if (from[1] == '%') { + from++; + goto copy; + } + + for (i = 0; i < ELEMENTSOF(map); i++) { + if (from[1] == map[i].fmt) { + type = map[i].type; + from += 2; + goto subst; + } + } + } +copy: + /* copy char */ + if (l == 0) + goto out; + s[0] = from[0]; + from++; + s++; + l--; + } + + goto out; +subst: + /* extract possible $format{attr} */ + if (from[0] == '{') { + unsigned int i; + + from++; + for (i = 0; from[i] != '}'; i++) { + if (from[i] == '\0') { + log_error("missing closing brace for format '%s'\n", src); + goto out; + } + } + if (i >= sizeof(attrbuf)) + goto out; + memcpy(attrbuf, from, i); + attrbuf[i] = '\0'; + from += i+1; + attr = attrbuf; + } else { + attr = NULL; + } + + switch (type) { + case SUBST_DEVPATH: + l = util_strpcpy(&s, l, udev_device_get_devpath(dev)); + break; + case SUBST_KERNEL: + l = util_strpcpy(&s, l, udev_device_get_sysname(dev)); + break; + case SUBST_KERNEL_NUMBER: + if (udev_device_get_sysnum(dev) == NULL) + break; + l = util_strpcpy(&s, l, udev_device_get_sysnum(dev)); + break; + case SUBST_ID: + if (event->dev_parent == NULL) + break; + l = util_strpcpy(&s, l, udev_device_get_sysname(event->dev_parent)); + break; + case SUBST_DRIVER: { + const char *driver; + + if (event->dev_parent == NULL) + break; + + driver = udev_device_get_driver(event->dev_parent); + if (driver == NULL) + break; + l = util_strpcpy(&s, l, driver); + break; + } + case SUBST_MAJOR: { + char num[UTIL_PATH_SIZE]; + + sprintf(num, "%d", major(udev_device_get_devnum(dev))); + l = util_strpcpy(&s, l, num); + break; + } + case SUBST_MINOR: { + char num[UTIL_PATH_SIZE]; + + sprintf(num, "%d", minor(udev_device_get_devnum(dev))); + l = util_strpcpy(&s, l, num); + break; + } + case SUBST_RESULT: { + char *rest; + int i; + + if (event->program_result == NULL) + break; + /* get part part of the result string */ + i = 0; + if (attr != NULL) + i = strtoul(attr, &rest, 10); + if (i > 0) { + char result[UTIL_PATH_SIZE]; + char tmp[UTIL_PATH_SIZE]; + char *cpos; + + util_strscpy(result, sizeof(result), event->program_result); + cpos = result; + while (--i) { + while (cpos[0] != '\0' && !isspace(cpos[0])) + cpos++; + while (isspace(cpos[0])) + cpos++; + } + if (i > 0) { + log_error("requested part of result string not found\n"); + break; + } + util_strscpy(tmp, sizeof(tmp), cpos); + /* %{2+}c copies the whole string from the second part on */ + if (rest[0] != '+') { + cpos = strchr(tmp, ' '); + if (cpos) + cpos[0] = '\0'; + } + l = util_strpcpy(&s, l, tmp); + } else { + l = util_strpcpy(&s, l, event->program_result); + } + break; + } + case SUBST_ATTR: { + const char *value = NULL; + char vbuf[UTIL_NAME_SIZE]; + size_t len; + int count; + + if (attr == NULL) { + log_error("missing file parameter for attr\n"); + break; + } + + /* try to read the value specified by "[dmi/id]product_name" */ + if (util_resolve_subsys_kernel(event->udev, attr, vbuf, sizeof(vbuf), 1) == 0) + value = vbuf; + + /* try to read the attribute the device */ + if (value == NULL) + value = udev_device_get_sysattr_value(event->dev, attr); + + /* try to read the attribute of the parent device, other matches have selected */ + if (value == NULL && event->dev_parent != NULL && event->dev_parent != event->dev) + value = udev_device_get_sysattr_value(event->dev_parent, attr); + + if (value == NULL) + break; + + /* strip trailing whitespace, and replace unwanted characters */ + if (value != vbuf) + util_strscpy(vbuf, sizeof(vbuf), value); + len = strlen(vbuf); + while (len > 0 && isspace(vbuf[--len])) + vbuf[len] = '\0'; + count = util_replace_chars(vbuf, UDEV_ALLOWED_CHARS_INPUT); + if (count > 0) + log_debug("%i character(s) replaced\n" , count); + l = util_strpcpy(&s, l, vbuf); + break; + } + case SUBST_PARENT: { + struct udev_device *dev_parent; + const char *devnode; + + dev_parent = udev_device_get_parent(event->dev); + if (dev_parent == NULL) + break; + devnode = udev_device_get_devnode(dev_parent); + if (devnode != NULL) + l = util_strpcpy(&s, l, devnode + strlen("/dev/")); + break; + } + case SUBST_DEVNODE: + if (udev_device_get_devnode(dev) != NULL) + l = util_strpcpy(&s, l, udev_device_get_devnode(dev)); + break; + case SUBST_NAME: + if (event->name != NULL) + l = util_strpcpy(&s, l, event->name); + else if (udev_device_get_devnode(dev) != NULL) + l = util_strpcpy(&s, l, udev_device_get_devnode(dev) + strlen("/dev/")); + else + l = util_strpcpy(&s, l, udev_device_get_sysname(dev)); + break; + case SUBST_LINKS: { + struct udev_list_entry *list_entry; + + list_entry = udev_device_get_devlinks_list_entry(dev); + if (list_entry == NULL) + break; + l = util_strpcpy(&s, l, udev_list_entry_get_name(list_entry) + strlen("/dev/")); + udev_list_entry_foreach(list_entry, udev_list_entry_get_next(list_entry)) + l = util_strpcpyl(&s, l, " ", udev_list_entry_get_name(list_entry) + strlen("/dev/"), NULL); + break; + } + case SUBST_ROOT: + l = util_strpcpy(&s, l, "/dev"); + break; + case SUBST_SYS: + l = util_strpcpy(&s, l, "/sys"); + break; + case SUBST_ENV: + if (attr == NULL) { + break; + } else { + const char *value; + + value = udev_device_get_property_value(event->dev, attr); + if (value == NULL) + break; + l = util_strpcpy(&s, l, value); + break; + } + default: + log_error("unknown substitution type=%i\n", type); + break; + } + } + +out: + s[0] = '\0'; + return l; +} + +static int spawn_exec(struct udev_event *event, + const char *cmd, char *const argv[], char **envp, const sigset_t *sigmask, + int fd_stdout, int fd_stderr) +{ + int err; + int fd; + + /* discard child output or connect to pipe */ + fd = open("/dev/null", O_RDWR); + if (fd >= 0) { + dup2(fd, STDIN_FILENO); + if (fd_stdout < 0) + dup2(fd, STDOUT_FILENO); + if (fd_stderr < 0) + dup2(fd, STDERR_FILENO); + close(fd); + } else { + log_error("open /dev/null failed: %m\n"); + } + + /* connect pipes to std{out,err} */ + if (fd_stdout >= 0) { + dup2(fd_stdout, STDOUT_FILENO); + close(fd_stdout); + } + if (fd_stderr >= 0) { + dup2(fd_stderr, STDERR_FILENO); + close(fd_stderr); + } + + /* terminate child in case parent goes away */ + prctl(PR_SET_PDEATHSIG, SIGTERM); + + /* restore original udev sigmask before exec */ + if (sigmask) + sigprocmask(SIG_SETMASK, sigmask, NULL); + + execve(argv[0], argv, envp); + + /* exec failed */ + err = -errno; + log_error("failed to execute '%s' '%s': %m\n", argv[0], cmd); + return err; +} + +static void spawn_read(struct udev_event *event, + const char *cmd, + int fd_stdout, int fd_stderr, + char *result, size_t ressize) +{ + size_t respos = 0; + int fd_ep = -1; + struct epoll_event ep_outpipe, ep_errpipe; + + /* read from child if requested */ + if (fd_stdout < 0 && fd_stderr < 0) + return; + + fd_ep = epoll_create1(EPOLL_CLOEXEC); + if (fd_ep < 0) { + log_error("error creating epoll fd: %m\n"); + goto out; + } + + if (fd_stdout >= 0) { + memset(&ep_outpipe, 0, sizeof(struct epoll_event)); + ep_outpipe.events = EPOLLIN; + ep_outpipe.data.ptr = &fd_stdout; + if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_stdout, &ep_outpipe) < 0) { + log_error("fail to add fd to epoll: %m\n"); + goto out; + } + } + + if (fd_stderr >= 0) { + memset(&ep_errpipe, 0, sizeof(struct epoll_event)); + ep_errpipe.events = EPOLLIN; + ep_errpipe.data.ptr = &fd_stderr; + if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_stderr, &ep_errpipe) < 0) { + log_error("fail to add fd to epoll: %m\n"); + goto out; + } + } + + /* read child output */ + while (fd_stdout >= 0 || fd_stderr >= 0) { + int timeout; + int fdcount; + struct epoll_event ev[4]; + int i; + + if (event->timeout_usec > 0) { + usec_t age_usec; + + age_usec = now(CLOCK_MONOTONIC) - event->birth_usec; + if (age_usec >= event->timeout_usec) { + log_error("timeout '%s'\n", cmd); + goto out; + } + timeout = ((event->timeout_usec - age_usec) / 1000) + 1000; + } else { + timeout = -1; + } + + fdcount = epoll_wait(fd_ep, ev, ELEMENTSOF(ev), timeout); + if (fdcount < 0) { + if (errno == EINTR) + continue; + log_error("failed to poll: %m\n"); + goto out; + } + if (fdcount == 0) { + log_error("timeout '%s'\n", cmd); + goto out; + } + + for (i = 0; i < fdcount; i++) { + int *fd = (int *)ev[i].data.ptr; + + if (ev[i].events & EPOLLIN) { + ssize_t count; + char buf[4096]; + + count = read(*fd, buf, sizeof(buf)-1); + if (count <= 0) + continue; + buf[count] = '\0'; + + /* store stdout result */ + if (result != NULL && *fd == fd_stdout) { + if (respos + count < ressize) { + memcpy(&result[respos], buf, count); + respos += count; + } else { + log_error("'%s' ressize %zd too short\n", cmd, ressize); + } + } + + /* log debug output only if we watch stderr */ + if (fd_stderr >= 0) { + char *pos; + char *line; + + pos = buf; + while ((line = strsep(&pos, "\n"))) { + if (pos != NULL || line[0] != '\0') + log_debug("'%s'(%s) '%s'\n", cmd, *fd == fd_stdout ? "out" : "err" , line); + } + } + } else if (ev[i].events & EPOLLHUP) { + if (epoll_ctl(fd_ep, EPOLL_CTL_DEL, *fd, NULL) < 0) { + log_error("failed to remove fd from epoll: %m\n"); + goto out; + } + *fd = -1; + } + } + } + + /* return the child's stdout string */ + if (result != NULL) + result[respos] = '\0'; +out: + if (fd_ep >= 0) + close(fd_ep); +} + +static int spawn_wait(struct udev_event *event, const char *cmd, pid_t pid) +{ + struct pollfd pfd[1]; + int err = 0; + + pfd[0].events = POLLIN; + pfd[0].fd = event->fd_signal; + + while (pid > 0) { + int timeout; + int fdcount; + + if (event->timeout_usec > 0) { + usec_t age_usec; + + age_usec = now(CLOCK_MONOTONIC) - event->birth_usec; + if (age_usec >= event->timeout_usec) + timeout = 1000; + else + timeout = ((event->timeout_usec - age_usec) / 1000) + 1000; + } else { + timeout = -1; + } + + fdcount = poll(pfd, 1, timeout); + if (fdcount < 0) { + if (errno == EINTR) + continue; + err = -errno; + log_error("failed to poll: %m\n"); + goto out; + } + if (fdcount == 0) { + log_error("timeout: killing '%s' [%u]\n", cmd, pid); + kill(pid, SIGKILL); + } + + if (pfd[0].revents & POLLIN) { + struct signalfd_siginfo fdsi; + int status; + ssize_t size; + + size = read(event->fd_signal, &fdsi, sizeof(struct signalfd_siginfo)); + if (size != sizeof(struct signalfd_siginfo)) + continue; + + switch (fdsi.ssi_signo) { + case SIGTERM: + event->sigterm = true; + break; + case SIGCHLD: + if (waitpid(pid, &status, WNOHANG) < 0) + break; + if (WIFEXITED(status)) { + log_debug("'%s' [%u] exit with return code %i\n", cmd, pid, WEXITSTATUS(status)); + if (WEXITSTATUS(status) != 0) + err = -1; + } else if (WIFSIGNALED(status)) { + log_error("'%s' [%u] terminated by signal %i (%s)\n", cmd, pid, WTERMSIG(status), strsignal(WTERMSIG(status))); + err = -1; + } else if (WIFSTOPPED(status)) { + log_error("'%s' [%u] stopped\n", cmd, pid); + err = -1; + } else if (WIFCONTINUED(status)) { + log_error("'%s' [%u] continued\n", cmd, pid); + err = -1; + } else { + log_error("'%s' [%u] exit with status 0x%04x\n", cmd, pid, status); + err = -1; + } + pid = 0; + break; + } + } + } +out: + return err; +} + +int udev_build_argv(struct udev *udev, char *cmd, int *argc, char *argv[]) +{ + int i = 0; + char *pos; + + if (strchr(cmd, ' ') == NULL) { + argv[i++] = cmd; + goto out; + } + + pos = cmd; + while (pos != NULL && pos[0] != '\0') { + if (pos[0] == '\'') { + /* do not separate quotes */ + pos++; + argv[i] = strsep(&pos, "\'"); + if (pos != NULL) + while (pos[0] == ' ') + pos++; + } else { + argv[i] = strsep(&pos, " "); + if (pos != NULL) + while (pos[0] == ' ') + pos++; + } + i++; + } +out: + argv[i] = NULL; + if (argc) + *argc = i; + return 0; +} + +int udev_event_spawn(struct udev_event *event, + const char *cmd, char **envp, const sigset_t *sigmask, + char *result, size_t ressize) +{ + struct udev *udev = event->udev; + int outpipe[2] = {-1, -1}; + int errpipe[2] = {-1, -1}; + pid_t pid; + char arg[UTIL_PATH_SIZE]; + char *argv[128]; + char program[UTIL_PATH_SIZE]; + int err = 0; + + util_strscpy(arg, sizeof(arg), cmd); + udev_build_argv(event->udev, arg, NULL, argv); + + /* pipes from child to parent */ + if (result != NULL || udev_get_log_priority(udev) >= LOG_INFO) { + if (pipe2(outpipe, O_NONBLOCK) != 0) { + err = -errno; + log_error("pipe failed: %m\n"); + goto out; + } + } + if (udev_get_log_priority(udev) >= LOG_INFO) { + if (pipe2(errpipe, O_NONBLOCK) != 0) { + err = -errno; + log_error("pipe failed: %m\n"); + goto out; + } + } + + /* allow programs in /usr/lib/udev/ to be called without the path */ + if (argv[0][0] != '/') { + util_strscpyl(program, sizeof(program), UDEVLIBEXECDIR "/", argv[0], NULL); + argv[0] = program; + } + + pid = fork(); + switch(pid) { + case 0: + /* child closes parent's ends of pipes */ + if (outpipe[READ_END] >= 0) { + close(outpipe[READ_END]); + outpipe[READ_END] = -1; + } + if (errpipe[READ_END] >= 0) { + close(errpipe[READ_END]); + errpipe[READ_END] = -1; + } + + log_debug("starting '%s'\n", cmd); + + spawn_exec(event, cmd, argv, envp, sigmask, + outpipe[WRITE_END], errpipe[WRITE_END]); + + _exit(2 ); + case -1: + log_error("fork of '%s' failed: %m\n", cmd); + err = -1; + goto out; + default: + /* parent closed child's ends of pipes */ + if (outpipe[WRITE_END] >= 0) { + close(outpipe[WRITE_END]); + outpipe[WRITE_END] = -1; + } + if (errpipe[WRITE_END] >= 0) { + close(errpipe[WRITE_END]); + errpipe[WRITE_END] = -1; + } + + spawn_read(event, cmd, + outpipe[READ_END], errpipe[READ_END], + result, ressize); + + err = spawn_wait(event, cmd, pid); + } + +out: + if (outpipe[READ_END] >= 0) + close(outpipe[READ_END]); + if (outpipe[WRITE_END] >= 0) + close(outpipe[WRITE_END]); + if (errpipe[READ_END] >= 0) + close(errpipe[READ_END]); + if (errpipe[WRITE_END] >= 0) + close(errpipe[WRITE_END]); + return err; +} + +static int rename_netif(struct udev_event *event) +{ + struct udev_device *dev = event->dev; + int sk; + struct ifreq ifr; + int err; + + log_debug("changing net interface name from '%s' to '%s'\n", + udev_device_get_sysname(dev), event->name); + + sk = socket(PF_INET, SOCK_DGRAM, 0); + if (sk < 0) { + err = -errno; + log_error("error opening socket: %m\n"); + return err; + } + + memset(&ifr, 0x00, sizeof(struct ifreq)); + util_strscpy(ifr.ifr_name, IFNAMSIZ, udev_device_get_sysname(dev)); + util_strscpy(ifr.ifr_newname, IFNAMSIZ, event->name); + err = ioctl(sk, SIOCSIFNAME, &ifr); + if (err >= 0) { + print_kmsg("renamed network interface %s to %s\n", ifr.ifr_name, ifr.ifr_newname); + } else { + err = -errno; + log_error("error changing net interface name %s to %s: %m\n", ifr.ifr_name, ifr.ifr_newname); + } + close(sk); + return err; +} + +int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules, const sigset_t *sigmask) +{ + struct udev_device *dev = event->dev; + int err = 0; + + if (udev_device_get_subsystem(dev) == NULL) + return -1; + + if (strcmp(udev_device_get_action(dev), "remove") == 0) { + udev_device_read_db(dev, NULL); + udev_device_delete_db(dev); + udev_device_tag_index(dev, NULL, false); + + if (major(udev_device_get_devnum(dev)) != 0) + udev_watch_end(event->udev, dev); + + udev_rules_apply_to_event(rules, event, sigmask); + + if (major(udev_device_get_devnum(dev)) != 0) + udev_node_remove(dev); + } else { + event->dev_db = udev_device_new(event->udev); + if (event->dev_db != NULL) { + udev_device_set_syspath(event->dev_db, udev_device_get_syspath(dev)); + udev_device_set_subsystem(event->dev_db, udev_device_get_subsystem(dev)); + udev_device_read_db(event->dev_db, NULL); + udev_device_set_info_loaded(event->dev_db); + + /* disable watch during event processing */ + if (major(udev_device_get_devnum(dev)) != 0) + udev_watch_end(event->udev, event->dev_db); + } + + udev_rules_apply_to_event(rules, event, sigmask); + + /* rename a new network interface, if needed */ + if (udev_device_get_ifindex(dev) > 0 && strcmp(udev_device_get_action(dev), "add") == 0 && + event->name != NULL && strcmp(event->name, udev_device_get_sysname(dev)) != 0) { + char syspath[UTIL_PATH_SIZE]; + char *pos; + + err = rename_netif(event); + if (err == 0) { + log_debug("renamed netif to '%s'\n", event->name); + + /* remember old name */ + udev_device_add_property(dev, "INTERFACE_OLD", udev_device_get_sysname(dev)); + + /* now change the devpath, because the kernel device name has changed */ + util_strscpy(syspath, sizeof(syspath), udev_device_get_syspath(dev)); + pos = strrchr(syspath, '/'); + if (pos != NULL) { + pos++; + util_strscpy(pos, sizeof(syspath) - (pos - syspath), event->name); + udev_device_set_syspath(event->dev, syspath); + udev_device_add_property(dev, "INTERFACE", udev_device_get_sysname(dev)); + log_debug("changed devpath to '%s'\n", udev_device_get_devpath(dev)); + } + } + } + + if (major(udev_device_get_devnum(dev)) > 0) { + /* remove/update possible left-over symlinks from old database entry */ + if (event->dev_db != NULL) + udev_node_update_old_links(dev, event->dev_db); + + if (!event->mode_set) { + if (udev_device_get_devnode_mode(dev) > 0) { + /* kernel supplied value */ + event->mode = udev_device_get_devnode_mode(dev); + } else if (event->gid > 0) { + /* default 0660 if a group is assigned */ + event->mode = 0660; + } else { + /* default 0600 */ + event->mode = 0600; + } + } + + udev_node_add(dev, event->mode, event->uid, event->gid); + } + + /* preserve old, or get new initialization timestamp */ + if (event->dev_db != NULL && udev_device_get_usec_initialized(event->dev_db) > 0) + udev_device_set_usec_initialized(event->dev, udev_device_get_usec_initialized(event->dev_db)); + else if (udev_device_get_usec_initialized(event->dev) == 0) + udev_device_set_usec_initialized(event->dev, now(CLOCK_MONOTONIC)); + + /* (re)write database file */ + udev_device_update_db(dev); + udev_device_tag_index(dev, event->dev_db, true); + udev_device_set_is_initialized(dev); + + udev_device_unref(event->dev_db); + event->dev_db = NULL; + } + return err; +} + +void udev_event_execute_run(struct udev_event *event, const sigset_t *sigmask) +{ + struct udev_list_entry *list_entry; + + udev_list_entry_foreach(list_entry, udev_list_get_entry(&event->run_list)) { + const char *cmd = udev_list_entry_get_name(list_entry); + enum udev_builtin_cmd builtin_cmd = udev_list_entry_get_num(list_entry); + + if (builtin_cmd < UDEV_BUILTIN_MAX) { + char command[UTIL_PATH_SIZE]; + + udev_event_apply_format(event, cmd, command, sizeof(command)); + udev_builtin_run(event->dev, builtin_cmd, command, false); + } else { + char program[UTIL_PATH_SIZE]; + char **envp; + + if (event->exec_delay > 0) { + log_debug("delay execution of '%s'\n", program); + sleep(event->exec_delay); + } + + udev_event_apply_format(event, cmd, program, sizeof(program)); + envp = udev_device_get_properties_envp(event->dev); + udev_event_spawn(event, program, envp, sigmask, NULL, 0); + } + } +} diff --git a/src/udev/udev-node.c b/src/udev/udev-node.c new file mode 100644 index 0000000000..7774303704 --- /dev/null +++ b/src/udev/udev-node.c @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2003-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 <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <stddef.h> +#include <stdbool.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <grp.h> +#include <dirent.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "udev.h" + +#define TMP_FILE_EXT ".udev-tmp" + +static int node_symlink(struct udev *udev, const char *node, const char *slink) +{ + struct stat stats; + char target[UTIL_PATH_SIZE]; + char *s; + size_t l; + char slink_tmp[UTIL_PATH_SIZE + sizeof(TMP_FILE_EXT)]; + int i = 0; + int tail = 0; + int err = 0; + + /* use relative link */ + target[0] = '\0'; + while (node[i] && (node[i] == slink[i])) { + if (node[i] == '/') + tail = i+1; + i++; + } + s = target; + l = sizeof(target); + while (slink[i] != '\0') { + if (slink[i] == '/') + l = util_strpcpy(&s, l, "../"); + i++; + } + l = util_strscpy(s, l, &node[tail]); + if (l == 0) { + err = -EINVAL; + goto exit; + } + + /* preserve link with correct target, do not replace node of other device */ + if (lstat(slink, &stats) == 0) { + if (S_ISBLK(stats.st_mode) || S_ISCHR(stats.st_mode)) { + log_error("conflicting device node '%s' found, link to '%s' will not be created\n", slink, node); + goto exit; + } else if (S_ISLNK(stats.st_mode)) { + char buf[UTIL_PATH_SIZE]; + int len; + + len = readlink(slink, buf, sizeof(buf)); + if (len > 0 && len < (int)sizeof(buf)) { + buf[len] = '\0'; + if (strcmp(target, buf) == 0) { + log_debug("preserve already existing symlink '%s' to '%s'\n", slink, target); + label_fix(slink, true, false); + utimensat(AT_FDCWD, slink, NULL, AT_SYMLINK_NOFOLLOW); + goto exit; + } + } + } + } else { + log_debug("creating symlink '%s' to '%s'\n", slink, target); + do { + err = mkdir_parents_label(slink, 0755); + if (err != 0 && err != -ENOENT) + break; + label_context_set(slink, S_IFLNK); + err = symlink(target, slink); + if (err != 0) + err = -errno; + label_context_clear(); + } while (err == -ENOENT); + if (err == 0) + goto exit; + } + + log_debug("atomically replace '%s'\n", slink); + util_strscpyl(slink_tmp, sizeof(slink_tmp), slink, TMP_FILE_EXT, NULL); + unlink(slink_tmp); + do { + err = mkdir_parents_label(slink_tmp, 0755); + if (err != 0 && err != -ENOENT) + break; + label_context_set(slink_tmp, S_IFLNK); + err = symlink(target, slink_tmp); + if (err != 0) + err = -errno; + label_context_clear(); + } while (err == -ENOENT); + if (err != 0) { + log_error("symlink '%s' '%s' failed: %m\n", target, slink_tmp); + goto exit; + } + err = rename(slink_tmp, slink); + if (err != 0) { + log_error("rename '%s' '%s' failed: %m\n", slink_tmp, slink); + unlink(slink_tmp); + } +exit: + return err; +} + +/* find device node of device with highest priority */ +static const char *link_find_prioritized(struct udev_device *dev, bool add, const char *stackdir, char *buf, size_t bufsize) +{ + struct udev *udev = udev_device_get_udev(dev); + DIR *dir; + int priority = 0; + const char *target = NULL; + + if (add) { + priority = udev_device_get_devlink_priority(dev); + util_strscpy(buf, bufsize, udev_device_get_devnode(dev)); + target = buf; + } + + dir = opendir(stackdir); + if (dir == NULL) + return target; + for (;;) { + struct udev_device *dev_db; + struct dirent *dent; + + dent = readdir(dir); + if (dent == NULL || dent->d_name[0] == '\0') + break; + if (dent->d_name[0] == '.') + continue; + + log_debug("found '%s' claiming '%s'\n", dent->d_name, stackdir); + + /* did we find ourself? */ + if (strcmp(dent->d_name, udev_device_get_id_filename(dev)) == 0) + continue; + + dev_db = udev_device_new_from_device_id(udev, dent->d_name); + if (dev_db != NULL) { + const char *devnode; + + devnode = udev_device_get_devnode(dev_db); + if (devnode != NULL) { + if (target == NULL || udev_device_get_devlink_priority(dev_db) > priority) { + log_debug("'%s' claims priority %i for '%s'\n", + udev_device_get_syspath(dev_db), udev_device_get_devlink_priority(dev_db), stackdir); + priority = udev_device_get_devlink_priority(dev_db); + util_strscpy(buf, bufsize, devnode); + target = buf; + } + } + udev_device_unref(dev_db); + } + } + closedir(dir); + return target; +} + +/* manage "stack of names" with possibly specified device priorities */ +static void link_update(struct udev_device *dev, const char *slink, bool add) +{ + struct udev *udev = udev_device_get_udev(dev); + char name_enc[UTIL_PATH_SIZE]; + char filename[UTIL_PATH_SIZE * 2]; + char dirname[UTIL_PATH_SIZE]; + const char *target; + char buf[UTIL_PATH_SIZE]; + + util_path_encode(slink + strlen("/dev"), name_enc, sizeof(name_enc)); + util_strscpyl(dirname, sizeof(dirname), "/run/udev/links/", name_enc, NULL); + util_strscpyl(filename, sizeof(filename), dirname, "/", udev_device_get_id_filename(dev), NULL); + + if (!add && unlink(filename) == 0) + rmdir(dirname); + + target = link_find_prioritized(dev, add, dirname, buf, sizeof(buf)); + if (target == NULL) { + log_debug("no reference left, remove '%s'\n", slink); + if (unlink(slink) == 0) + util_delete_path(udev, slink); + } else { + log_debug("creating link '%s' to '%s'\n", slink, target); + node_symlink(udev, target, slink); + } + + if (add) { + int err; + + do { + int fd; + + err = mkdir_parents(filename, 0755); + if (err != 0 && err != -ENOENT) + break; + fd = open(filename, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC|O_NOFOLLOW, 0444); + if (fd >= 0) + close(fd); + else + err = -errno; + } while (err == -ENOENT); + } +} + +void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev_old) +{ + struct udev_list_entry *list_entry; + + /* update possible left-over symlinks */ + udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev_old)) { + const char *name = udev_list_entry_get_name(list_entry); + struct udev_list_entry *list_entry_current; + int found; + + /* check if old link name still belongs to this device */ + found = 0; + udev_list_entry_foreach(list_entry_current, udev_device_get_devlinks_list_entry(dev)) { + const char *name_current = udev_list_entry_get_name(list_entry_current); + + if (strcmp(name, name_current) == 0) { + found = 1; + break; + } + } + if (found) + continue; + + log_debug("update old name, '%s' no longer belonging to '%s'\n", + name, udev_device_get_devpath(dev)); + link_update(dev, name, false); + } +} + +static int node_fixup(struct udev_device *dev, mode_t mode, uid_t uid, gid_t gid) +{ + const char *devnode = udev_device_get_devnode(dev); + dev_t devnum = udev_device_get_devnum(dev); + struct stat stats; + int err = 0; + + if (strcmp(udev_device_get_subsystem(dev), "block") == 0) + mode |= S_IFBLK; + else + mode |= S_IFCHR; + + if (lstat(devnode, &stats) != 0) { + err = -errno; + log_debug("can not stat() node '%s' (%m)\n", devnode); + goto out; + } + + if (((stats.st_mode & S_IFMT) != (mode & S_IFMT)) || (stats.st_rdev != devnum)) { + err = -EEXIST; + log_debug("found node '%s' with non-matching devnum %s, skip handling\n", + udev_device_get_devnode(dev), udev_device_get_id_filename(dev)); + goto out; + } + + if ((stats.st_mode & 0777) != (mode & 0777) || stats.st_uid != uid || stats.st_gid != gid) { + log_debug("set permissions %s, %#o, uid=%u, gid=%u\n", devnode, mode, uid, gid); + chmod(devnode, mode); + chown(devnode, uid, gid); + } else { + log_debug("preserve permissions %s, %#o, uid=%u, gid=%u\n", devnode, mode, uid, gid); + } + + /* + * Set initial selinux file context only on add events. + * We set the proper context on bootup (triger) or for newly + * added devices, but we don't change it later, in case + * something else has set a custom context in the meantime. + */ + if (strcmp(udev_device_get_action(dev), "add") == 0) + label_fix(devnode, true, false); + + /* always update timestamp when we re-use the node, like on media change events */ + utimensat(AT_FDCWD, devnode, NULL, 0); +out: + return err; +} + +void udev_node_add(struct udev_device *dev, mode_t mode, uid_t uid, gid_t gid) +{ + struct udev *udev = udev_device_get_udev(dev); + char filename[UTIL_PATH_SIZE]; + struct udev_list_entry *list_entry; + + log_debug("handling device node '%s', devnum=%s, mode=%#o, uid=%d, gid=%d\n", + udev_device_get_devnode(dev), udev_device_get_id_filename(dev), mode, uid, gid); + + if (node_fixup(dev, mode, uid, gid) < 0) + return; + + /* always add /dev/{block,char}/$major:$minor */ + snprintf(filename, sizeof(filename), "/dev/%s/%u:%u", + strcmp(udev_device_get_subsystem(dev), "block") == 0 ? "block" : "char", + major(udev_device_get_devnum(dev)), minor(udev_device_get_devnum(dev))); + node_symlink(udev, udev_device_get_devnode(dev), filename); + + /* create/update symlinks, add symlinks to name index */ + udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev)) + link_update(dev, udev_list_entry_get_name(list_entry), true); +} + +void udev_node_remove(struct udev_device *dev) +{ + struct udev_list_entry *list_entry; + char filename[UTIL_PATH_SIZE]; + + /* remove/update symlinks, remove symlinks from name index */ + udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev)) + link_update(dev, udev_list_entry_get_name(list_entry), false); + + /* remove /dev/{block,char}/$major:$minor */ + snprintf(filename, sizeof(filename), "/dev/%s/%u:%u", + strcmp(udev_device_get_subsystem(dev), "block") == 0 ? "block" : "char", + major(udev_device_get_devnum(dev)), minor(udev_device_get_devnum(dev))); + unlink(filename); +} diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c new file mode 100644 index 0000000000..e6f0f5da7e --- /dev/null +++ b/src/udev/udev-rules.c @@ -0,0 +1,2573 @@ +/* + * Copyright (C) 2003-2012 Kay Sievers <kay@vrfy.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stddef.h> +#include <limits.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <stdio.h> +#include <fcntl.h> +#include <ctype.h> +#include <unistd.h> +#include <errno.h> +#include <dirent.h> +#include <fnmatch.h> +#include <time.h> + +#include "udev.h" +#include "path-util.h" +#include "conf-files.h" +#include "strbuf.h" + +#define PREALLOC_TOKEN 2048 + +struct uid_gid { + unsigned int name_off; + union { + uid_t uid; + gid_t gid; + }; +}; + +struct udev_rules { + struct udev *udev; + char **dirs; + usec_t *dirs_ts_usec; + int resolve_names; + + /* every key in the rules file becomes a token */ + struct token *tokens; + unsigned int token_cur; + unsigned int token_max; + + /* all key strings are copied and de-duplicated in a single continous string buffer */ + struct strbuf *strbuf; + + /* during rule parsing, uid/gid lookup results are cached */ + struct uid_gid *uids; + unsigned int uids_cur; + unsigned int uids_max; + struct uid_gid *gids; + unsigned int gids_cur; + unsigned int gids_max; +}; + +static char *rules_str(struct udev_rules *rules, unsigned int off) { + return rules->strbuf->buf + off; +} + +static unsigned int rules_add_string(struct udev_rules *rules, const char *s) { + return strbuf_add_string(rules->strbuf, s, strlen(s)); +} + +/* KEY=="", KEY!="", KEY+="", KEY="", KEY:="" */ +enum operation_type { + OP_UNSET, + + OP_MATCH, + OP_NOMATCH, + OP_MATCH_MAX, + + OP_ADD, + OP_ASSIGN, + OP_ASSIGN_FINAL, +}; + +enum string_glob_type { + GL_UNSET, + GL_PLAIN, /* no special chars */ + GL_GLOB, /* shell globs ?,*,[] */ + GL_SPLIT, /* multi-value A|B */ + GL_SPLIT_GLOB, /* multi-value with glob A*|B* */ + GL_SOMETHING, /* commonly used "?*" */ +}; + +enum string_subst_type { + SB_UNSET, + SB_NONE, + SB_FORMAT, + SB_SUBSYS, +}; + +/* tokens of a rule are sorted/handled in this order */ +enum token_type { + TK_UNSET, + TK_RULE, + + TK_M_ACTION, /* val */ + TK_M_DEVPATH, /* val */ + TK_M_KERNEL, /* val */ + TK_M_DEVLINK, /* val */ + TK_M_NAME, /* val */ + TK_M_ENV, /* val, attr */ + TK_M_TAG, /* val */ + TK_M_SUBSYSTEM, /* val */ + TK_M_DRIVER, /* val */ + TK_M_WAITFOR, /* val */ + TK_M_ATTR, /* val, attr */ + + TK_M_PARENTS_MIN, + TK_M_KERNELS, /* val */ + TK_M_SUBSYSTEMS, /* val */ + TK_M_DRIVERS, /* val */ + TK_M_ATTRS, /* val, attr */ + TK_M_TAGS, /* val */ + TK_M_PARENTS_MAX, + + TK_M_TEST, /* val, mode_t */ + TK_M_EVENT_TIMEOUT, /* int */ + TK_M_PROGRAM, /* val */ + TK_M_IMPORT_FILE, /* val */ + TK_M_IMPORT_PROG, /* val */ + TK_M_IMPORT_BUILTIN, /* val */ + TK_M_IMPORT_DB, /* val */ + TK_M_IMPORT_CMDLINE, /* val */ + TK_M_IMPORT_PARENT, /* val */ + TK_M_RESULT, /* val */ + TK_M_MAX, + + TK_A_STRING_ESCAPE_NONE, + TK_A_STRING_ESCAPE_REPLACE, + TK_A_DB_PERSIST, + TK_A_INOTIFY_WATCH, /* int */ + TK_A_DEVLINK_PRIO, /* int */ + TK_A_OWNER, /* val */ + TK_A_GROUP, /* val */ + TK_A_MODE, /* val */ + TK_A_OWNER_ID, /* uid_t */ + TK_A_GROUP_ID, /* gid_t */ + TK_A_MODE_ID, /* mode_t */ + TK_A_STATIC_NODE, /* val */ + TK_A_ENV, /* val, attr */ + TK_A_TAG, /* val */ + TK_A_NAME, /* val */ + TK_A_DEVLINK, /* val */ + TK_A_ATTR, /* val, attr */ + TK_A_RUN_BUILTIN, /* val, bool */ + TK_A_RUN_PROGRAM, /* val, bool */ + TK_A_GOTO, /* size_t */ + + TK_END, +}; + +/* we try to pack stuff in a way that we take only 12 bytes per token */ +struct token { + union { + unsigned char type; /* same in rule and key */ + struct { + enum token_type type:8; + bool can_set_name:1; + bool has_static_node:1; + unsigned int unused:6; + unsigned short token_count; + unsigned int label_off; + unsigned short filename_off; + unsigned short filename_line; + } rule; + struct { + enum token_type type:8; + enum operation_type op:8; + enum string_glob_type glob:8; + enum string_subst_type subst:4; + enum string_subst_type attrsubst:4; + unsigned int value_off; + union { + unsigned int attr_off; + unsigned int rule_goto; + mode_t mode; + uid_t uid; + gid_t gid; + int devlink_prio; + int event_timeout; + int watch; + enum udev_builtin_cmd builtin_cmd; + }; + } key; + }; +}; + +#define MAX_TK 64 +struct rule_tmp { + struct udev_rules *rules; + struct token rule; + struct token token[MAX_TK]; + unsigned int token_cur; +}; + +#ifdef DEBUG +static const char *operation_str(enum operation_type type) +{ + static const char *operation_strs[] = { + [OP_UNSET] = "UNSET", + [OP_MATCH] = "match", + [OP_NOMATCH] = "nomatch", + [OP_MATCH_MAX] = "MATCH_MAX", + + [OP_ADD] = "add", + [OP_ASSIGN] = "assign", + [OP_ASSIGN_FINAL] = "assign-final", +} ; + + return operation_strs[type]; +} + +static const char *string_glob_str(enum string_glob_type type) +{ + static const char *string_glob_strs[] = { + [GL_UNSET] = "UNSET", + [GL_PLAIN] = "plain", + [GL_GLOB] = "glob", + [GL_SPLIT] = "split", + [GL_SPLIT_GLOB] = "split-glob", + [GL_SOMETHING] = "split-glob", + }; + + return string_glob_strs[type]; +} + +static const char *token_str(enum token_type type) +{ + static const char *token_strs[] = { + [TK_UNSET] = "UNSET", + [TK_RULE] = "RULE", + + [TK_M_ACTION] = "M ACTION", + [TK_M_DEVPATH] = "M DEVPATH", + [TK_M_KERNEL] = "M KERNEL", + [TK_M_DEVLINK] = "M DEVLINK", + [TK_M_NAME] = "M NAME", + [TK_M_ENV] = "M ENV", + [TK_M_TAG] = "M TAG", + [TK_M_SUBSYSTEM] = "M SUBSYSTEM", + [TK_M_DRIVER] = "M DRIVER", + [TK_M_WAITFOR] = "M WAITFOR", + [TK_M_ATTR] = "M ATTR", + + [TK_M_PARENTS_MIN] = "M PARENTS_MIN", + [TK_M_KERNELS] = "M KERNELS", + [TK_M_SUBSYSTEMS] = "M SUBSYSTEMS", + [TK_M_DRIVERS] = "M DRIVERS", + [TK_M_ATTRS] = "M ATTRS", + [TK_M_TAGS] = "M TAGS", + [TK_M_PARENTS_MAX] = "M PARENTS_MAX", + + [TK_M_TEST] = "M TEST", + [TK_M_EVENT_TIMEOUT] = "M EVENT_TIMEOUT", + [TK_M_PROGRAM] = "M PROGRAM", + [TK_M_IMPORT_FILE] = "M IMPORT_FILE", + [TK_M_IMPORT_PROG] = "M IMPORT_PROG", + [TK_M_IMPORT_BUILTIN] = "M IMPORT_BUILTIN", + [TK_M_IMPORT_DB] = "M IMPORT_DB", + [TK_M_IMPORT_CMDLINE] = "M IMPORT_CMDLINE", + [TK_M_IMPORT_PARENT] = "M IMPORT_PARENT", + [TK_M_RESULT] = "M RESULT", + [TK_M_MAX] = "M MAX", + + [TK_A_STRING_ESCAPE_NONE] = "A STRING_ESCAPE_NONE", + [TK_A_STRING_ESCAPE_REPLACE] = "A STRING_ESCAPE_REPLACE", + [TK_A_DB_PERSIST] = "A DB_PERSIST", + [TK_A_INOTIFY_WATCH] = "A INOTIFY_WATCH", + [TK_A_DEVLINK_PRIO] = "A DEVLINK_PRIO", + [TK_A_OWNER] = "A OWNER", + [TK_A_GROUP] = "A GROUP", + [TK_A_MODE] = "A MODE", + [TK_A_OWNER_ID] = "A OWNER_ID", + [TK_A_GROUP_ID] = "A GROUP_ID", + [TK_A_STATIC_NODE] = "A STATIC_NODE", + [TK_A_MODE_ID] = "A MODE_ID", + [TK_A_ENV] = "A ENV", + [TK_A_TAG] = "A ENV", + [TK_A_NAME] = "A NAME", + [TK_A_DEVLINK] = "A DEVLINK", + [TK_A_ATTR] = "A ATTR", + [TK_A_RUN_BUILTIN] = "A RUN_BUILTIN", + [TK_A_RUN_PROGRAM] = "A RUN_PROGRAM", + [TK_A_GOTO] = "A GOTO", + + [TK_END] = "END", + }; + + return token_strs[type]; +} + +static void dump_token(struct udev_rules *rules, struct token *token) +{ + enum token_type type = token->type; + enum operation_type op = token->key.op; + enum string_glob_type glob = token->key.glob; + const char *value = str(rules, token->key.value_off); + const char *attr = &rules->buf[token->key.attr_off]; + + switch (type) { + case TK_RULE: + { + const char *tks_ptr = (char *)rules->tokens; + const char *tk_ptr = (char *)token; + unsigned int idx = (tk_ptr - tks_ptr) / sizeof(struct token); + + log_debug("* RULE %s:%u, token: %u, count: %u, label: '%s'\n", + &rules->buf[token->rule.filename_off], token->rule.filename_line, + idx, token->rule.token_count, + &rules->buf[token->rule.label_off]); + break; + } + case TK_M_ACTION: + case TK_M_DEVPATH: + case TK_M_KERNEL: + case TK_M_SUBSYSTEM: + case TK_M_DRIVER: + case TK_M_WAITFOR: + case TK_M_DEVLINK: + case TK_M_NAME: + case TK_M_KERNELS: + case TK_M_SUBSYSTEMS: + case TK_M_DRIVERS: + case TK_M_TAGS: + case TK_M_PROGRAM: + case TK_M_IMPORT_FILE: + case TK_M_IMPORT_PROG: + case TK_M_IMPORT_DB: + case TK_M_IMPORT_CMDLINE: + case TK_M_IMPORT_PARENT: + case TK_M_RESULT: + case TK_A_NAME: + case TK_A_DEVLINK: + case TK_A_OWNER: + case TK_A_GROUP: + case TK_A_MODE: + case TK_A_RUN_BUILTIN: + case TK_A_RUN_PROGRAM: + log_debug("%s %s '%s'(%s)\n", + token_str(type), operation_str(op), value, string_glob_str(glob)); + break; + case TK_M_IMPORT_BUILTIN: + log_debug("%s %i '%s'\n", token_str(type), token->key.builtin_cmd, value); + break; + case TK_M_ATTR: + case TK_M_ATTRS: + case TK_M_ENV: + case TK_A_ATTR: + case TK_A_ENV: + log_debug("%s %s '%s' '%s'(%s)\n", + token_str(type), operation_str(op), attr, value, string_glob_str(glob)); + break; + case TK_M_TAG: + case TK_A_TAG: + log_debug("%s %s '%s'\n", token_str(type), operation_str(op), value); + break; + case TK_A_STRING_ESCAPE_NONE: + case TK_A_STRING_ESCAPE_REPLACE: + case TK_A_DB_PERSIST: + log_debug("%s\n", token_str(type)); + break; + case TK_M_TEST: + log_debug("%s %s '%s'(%s) %#o\n", + token_str(type), operation_str(op), value, string_glob_str(glob), token->key.mode); + break; + case TK_A_INOTIFY_WATCH: + log_debug("%s %u\n", token_str(type), token->key.watch); + break; + case TK_A_DEVLINK_PRIO: + log_debug("%s %u\n", token_str(type), token->key.devlink_prio); + break; + case TK_A_OWNER_ID: + log_debug("%s %s %u\n", token_str(type), operation_str(op), token->key.uid); + break; + case TK_A_GROUP_ID: + log_debug("%s %s %u\n", token_str(type), operation_str(op), token->key.gid); + break; + case TK_A_MODE_ID: + log_debug("%s %s %#o\n", token_str(type), operation_str(op), token->key.mode); + break; + case TK_A_STATIC_NODE: + log_debug("%s '%s'\n", token_str(type), value); + break; + case TK_M_EVENT_TIMEOUT: + log_debug("%s %u\n", token_str(type), token->key.event_timeout); + break; + case TK_A_GOTO: + log_debug("%s '%s' %u\n", token_str(type), value, token->key.rule_goto); + break; + case TK_END: + log_debug("* %s\n", token_str(type)); + break; + case TK_M_PARENTS_MIN: + case TK_M_PARENTS_MAX: + case TK_M_MAX: + case TK_UNSET: + log_debug("unknown type %u\n", type); + break; + } +} + +static void dump_rules(struct udev_rules *rules) +{ + unsigned int i; + + log_debug("dumping %u (%zu bytes) tokens, %u (%zu bytes) strings\n", + rules->token_cur, + rules->token_cur * sizeof(struct token), + rules->buf_count, + rules->buf_cur); + for(i = 0; i < rules->token_cur; i++) + dump_token(rules, &rules->tokens[i]); +} +#else +static inline const char *operation_str(enum operation_type type) { return NULL; } +static inline const char *token_str(enum token_type type) { return NULL; } +static inline void dump_token(struct udev_rules *rules, struct token *token) {} +static inline void dump_rules(struct udev_rules *rules) {} +#endif /* DEBUG */ + +static int add_token(struct udev_rules *rules, struct token *token) +{ + /* grow buffer if needed */ + if (rules->token_cur+1 >= rules->token_max) { + struct token *tokens; + unsigned int add; + + /* double the buffer size */ + add = rules->token_max; + if (add < 8) + add = 8; + + tokens = realloc(rules->tokens, (rules->token_max + add ) * sizeof(struct token)); + if (tokens == NULL) + return -1; + rules->tokens = tokens; + rules->token_max += add; + } + memcpy(&rules->tokens[rules->token_cur], token, sizeof(struct token)); + rules->token_cur++; + return 0; +} + +static uid_t add_uid(struct udev_rules *rules, const char *owner) +{ + unsigned int i; + uid_t uid; + unsigned int off; + + /* lookup, if we know it already */ + for (i = 0; i < rules->uids_cur; i++) { + off = rules->uids[i].name_off; + if (streq(rules_str(rules, off), owner)) { + uid = rules->uids[i].uid; + return uid; + } + } + uid = util_lookup_user(rules->udev, owner); + + /* grow buffer if needed */ + if (rules->uids_cur+1 >= rules->uids_max) { + struct uid_gid *uids; + unsigned int add; + + /* double the buffer size */ + add = rules->uids_max; + if (add < 1) + add = 8; + + uids = realloc(rules->uids, (rules->uids_max + add ) * sizeof(struct uid_gid)); + if (uids == NULL) + return uid; + rules->uids = uids; + rules->uids_max += add; + } + rules->uids[rules->uids_cur].uid = uid; + off = rules_add_string(rules, owner); + if (off <= 0) + return uid; + rules->uids[rules->uids_cur].name_off = off; + rules->uids_cur++; + return uid; +} + +static gid_t add_gid(struct udev_rules *rules, const char *group) +{ + unsigned int i; + gid_t gid; + unsigned int off; + + /* lookup, if we know it already */ + for (i = 0; i < rules->gids_cur; i++) { + off = rules->gids[i].name_off; + if (streq(rules_str(rules, off), group)) { + gid = rules->gids[i].gid; + return gid; + } + } + gid = util_lookup_group(rules->udev, group); + + /* grow buffer if needed */ + if (rules->gids_cur+1 >= rules->gids_max) { + struct uid_gid *gids; + unsigned int add; + + /* double the buffer size */ + add = rules->gids_max; + if (add < 1) + add = 8; + + gids = realloc(rules->gids, (rules->gids_max + add ) * sizeof(struct uid_gid)); + if (gids == NULL) + return gid; + rules->gids = gids; + rules->gids_max += add; + } + rules->gids[rules->gids_cur].gid = gid; + off = rules_add_string(rules, group); + if (off <= 0) + return gid; + rules->gids[rules->gids_cur].name_off = off; + rules->gids_cur++; + return gid; +} + +static int import_property_from_string(struct udev_device *dev, char *line) +{ + char *key; + char *val; + size_t len; + + /* find key */ + key = line; + while (isspace(key[0])) + key++; + + /* comment or empty line */ + if (key[0] == '#' || key[0] == '\0') + return -1; + + /* split key/value */ + val = strchr(key, '='); + if (val == NULL) + return -1; + val[0] = '\0'; + val++; + + /* find value */ + while (isspace(val[0])) + val++; + + /* terminate key */ + len = strlen(key); + if (len == 0) + return -1; + while (isspace(key[len-1])) + len--; + key[len] = '\0'; + + /* terminate value */ + len = strlen(val); + if (len == 0) + return -1; + while (isspace(val[len-1])) + len--; + val[len] = '\0'; + + if (len == 0) + return -1; + + /* unquote */ + if (val[0] == '"' || val[0] == '\'') { + if (val[len-1] != val[0]) { + log_debug("inconsistent quoting: '%s', skip\n", line); + return -1; + } + val[len-1] = '\0'; + val++; + } + + /* handle device, renamed by external tool, returning new path */ + if (streq(key, "DEVPATH")) { + char syspath[UTIL_PATH_SIZE]; + + log_debug("updating devpath from '%s' to '%s'\n", + udev_device_get_devpath(dev), val); + util_strscpyl(syspath, sizeof(syspath), "/sys", val, NULL); + udev_device_set_syspath(dev, syspath); + } else { + struct udev_list_entry *entry; + + entry = udev_device_add_property(dev, key, val); + /* store in db, skip private keys */ + if (key[0] != '.') + udev_list_entry_set_num(entry, true); + } + return 0; +} + +static int import_file_into_properties(struct udev_device *dev, const char *filename) +{ + FILE *f; + char line[UTIL_LINE_SIZE]; + + f = fopen(filename, "re"); + if (f == NULL) + return -1; + while (fgets(line, sizeof(line), f) != NULL) + import_property_from_string(dev, line); + fclose(f); + return 0; +} + +static int import_program_into_properties(struct udev_event *event, const char *program, const sigset_t *sigmask) +{ + struct udev_device *dev = event->dev; + char **envp; + char result[UTIL_LINE_SIZE]; + char *line; + int err; + + envp = udev_device_get_properties_envp(dev); + err = udev_event_spawn(event, program, envp, sigmask, result, sizeof(result)); + if (err < 0) + return err; + + line = result; + while (line != NULL) { + char *pos; + + pos = strchr(line, '\n'); + if (pos != NULL) { + pos[0] = '\0'; + pos = &pos[1]; + } + import_property_from_string(dev, line); + line = pos; + } + return 0; +} + +static int import_parent_into_properties(struct udev_device *dev, const char *filter) +{ + struct udev_device *dev_parent; + struct udev_list_entry *list_entry; + + dev_parent = udev_device_get_parent(dev); + if (dev_parent == NULL) + return -1; + + udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(dev_parent)) { + const char *key = udev_list_entry_get_name(list_entry); + const char *val = udev_list_entry_get_value(list_entry); + + if (fnmatch(filter, key, 0) == 0) { + struct udev_list_entry *entry; + + entry = udev_device_add_property(dev, key, val); + /* store in db, skip private keys */ + if (key[0] != '.') + udev_list_entry_set_num(entry, true); + } + } + return 0; +} + +#define WAIT_LOOP_PER_SECOND 50 +static int wait_for_file(struct udev_device *dev, const char *file, int timeout) +{ + char filepath[UTIL_PATH_SIZE]; + char devicepath[UTIL_PATH_SIZE]; + struct stat stats; + int loop = timeout * WAIT_LOOP_PER_SECOND; + + /* a relative path is a device attribute */ + devicepath[0] = '\0'; + if (file[0] != '/') { + util_strscpyl(devicepath, sizeof(devicepath), udev_device_get_syspath(dev), NULL); + util_strscpyl(filepath, sizeof(filepath), devicepath, "/", file, NULL); + file = filepath; + } + + while (--loop) { + const struct timespec duration = { 0, 1000 * 1000 * 1000 / WAIT_LOOP_PER_SECOND }; + + /* lookup file */ + if (stat(file, &stats) == 0) { + log_debug("file '%s' appeared after %i loops\n", file, (timeout * WAIT_LOOP_PER_SECOND) - loop-1); + return 0; + } + /* make sure, the device did not disappear in the meantime */ + if (devicepath[0] != '\0' && stat(devicepath, &stats) != 0) { + log_debug("device disappeared while waiting for '%s'\n", file); + return -2; + } + log_debug("wait for '%s' for %i mseconds\n", file, 1000 / WAIT_LOOP_PER_SECOND); + nanosleep(&duration, NULL); + } + log_debug("waiting for '%s' failed\n", file); + return -1; +} + +static int attr_subst_subdir(char *attr, size_t len) +{ + bool found = false; + + if (strstr(attr, "/*/")) { + char *pos; + char dirname[UTIL_PATH_SIZE]; + const char *tail; + DIR *dir; + + util_strscpy(dirname, sizeof(dirname), attr); + pos = strstr(dirname, "/*/"); + if (pos == NULL) + return -1; + pos[0] = '\0'; + tail = &pos[2]; + dir = opendir(dirname); + if (dir != NULL) { + struct dirent *dent; + + for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { + struct stat stats; + + if (dent->d_name[0] == '.') + continue; + util_strscpyl(attr, len, dirname, "/", dent->d_name, tail, NULL); + if (stat(attr, &stats) == 0) { + found = true; + break; + } + } + closedir(dir); + } + } + + return found; +} + +static int get_key(struct udev *udev, char **line, char **key, enum operation_type *op, char **value) +{ + char *linepos; + char *temp; + + linepos = *line; + if (linepos == NULL || linepos[0] == '\0') + return -1; + + /* skip whitespace */ + while (isspace(linepos[0]) || linepos[0] == ',') + linepos++; + + /* get the key */ + if (linepos[0] == '\0') + return -1; + *key = linepos; + + for (;;) { + linepos++; + if (linepos[0] == '\0') + return -1; + if (isspace(linepos[0])) + break; + if (linepos[0] == '=') + break; + if ((linepos[0] == '+') || (linepos[0] == '!') || (linepos[0] == ':')) + if (linepos[1] == '=') + break; + } + + /* remember end of key */ + temp = linepos; + + /* skip whitespace after key */ + while (isspace(linepos[0])) + linepos++; + if (linepos[0] == '\0') + return -1; + + /* get operation type */ + if (linepos[0] == '=' && linepos[1] == '=') { + *op = OP_MATCH; + linepos += 2; + } else if (linepos[0] == '!' && linepos[1] == '=') { + *op = OP_NOMATCH; + linepos += 2; + } else if (linepos[0] == '+' && linepos[1] == '=') { + *op = OP_ADD; + linepos += 2; + } else if (linepos[0] == '=') { + *op = OP_ASSIGN; + linepos++; + } else if (linepos[0] == ':' && linepos[1] == '=') { + *op = OP_ASSIGN_FINAL; + linepos += 2; + } else + return -1; + + /* terminate key */ + temp[0] = '\0'; + + /* skip whitespace after operator */ + while (isspace(linepos[0])) + linepos++; + if (linepos[0] == '\0') + return -1; + + /* get the value */ + if (linepos[0] == '"') + linepos++; + else + return -1; + *value = linepos; + + /* terminate */ + temp = strchr(linepos, '"'); + if (!temp) + return -1; + temp[0] = '\0'; + temp++; + + /* move line to next key */ + *line = temp; + return 0; +} + +/* extract possible KEY{attr} */ +static const char *get_key_attribute(struct udev *udev, char *str) +{ + char *pos; + char *attr; + + attr = strchr(str, '{'); + if (attr != NULL) { + attr++; + pos = strchr(attr, '}'); + if (pos == NULL) { + log_error("missing closing brace for format\n"); + return NULL; + } + pos[0] = '\0'; + return attr; + } + return NULL; +} + +static int rule_add_key(struct rule_tmp *rule_tmp, enum token_type type, + enum operation_type op, + const char *value, const void *data) +{ + struct token *token = &rule_tmp->token[rule_tmp->token_cur]; + const char *attr = NULL; + + memset(token, 0x00, sizeof(struct token)); + + switch (type) { + case TK_M_ACTION: + case TK_M_DEVPATH: + case TK_M_KERNEL: + case TK_M_SUBSYSTEM: + case TK_M_DRIVER: + case TK_M_WAITFOR: + case TK_M_DEVLINK: + case TK_M_NAME: + case TK_M_KERNELS: + case TK_M_SUBSYSTEMS: + case TK_M_DRIVERS: + case TK_M_TAGS: + case TK_M_PROGRAM: + case TK_M_IMPORT_FILE: + case TK_M_IMPORT_PROG: + case TK_M_IMPORT_DB: + case TK_M_IMPORT_CMDLINE: + case TK_M_IMPORT_PARENT: + case TK_M_RESULT: + case TK_A_OWNER: + case TK_A_GROUP: + case TK_A_MODE: + case TK_A_DEVLINK: + case TK_A_NAME: + case TK_A_GOTO: + case TK_M_TAG: + case TK_A_TAG: + token->key.value_off = rules_add_string(rule_tmp->rules, value); + break; + case TK_M_IMPORT_BUILTIN: + token->key.value_off = rules_add_string(rule_tmp->rules, value); + token->key.builtin_cmd = *(enum udev_builtin_cmd *)data; + break; + case TK_M_ENV: + case TK_M_ATTR: + case TK_M_ATTRS: + case TK_A_ATTR: + case TK_A_ENV: + attr = data; + token->key.value_off = rules_add_string(rule_tmp->rules, value); + token->key.attr_off = rules_add_string(rule_tmp->rules, attr); + break; + case TK_M_TEST: + token->key.value_off = rules_add_string(rule_tmp->rules, value); + if (data != NULL) + token->key.mode = *(mode_t *)data; + break; + case TK_A_STRING_ESCAPE_NONE: + case TK_A_STRING_ESCAPE_REPLACE: + case TK_A_DB_PERSIST: + break; + case TK_A_RUN_BUILTIN: + case TK_A_RUN_PROGRAM: + token->key.builtin_cmd = *(enum udev_builtin_cmd *)data; + token->key.value_off = rules_add_string(rule_tmp->rules, value); + break; + case TK_A_INOTIFY_WATCH: + case TK_A_DEVLINK_PRIO: + token->key.devlink_prio = *(int *)data; + break; + case TK_A_OWNER_ID: + token->key.uid = *(uid_t *)data; + break; + case TK_A_GROUP_ID: + token->key.gid = *(gid_t *)data; + break; + case TK_A_MODE_ID: + token->key.mode = *(mode_t *)data; + break; + case TK_A_STATIC_NODE: + token->key.value_off = rules_add_string(rule_tmp->rules, value); + break; + case TK_M_EVENT_TIMEOUT: + token->key.event_timeout = *(int *)data; + break; + case TK_RULE: + case TK_M_PARENTS_MIN: + case TK_M_PARENTS_MAX: + case TK_M_MAX: + case TK_END: + case TK_UNSET: + log_error("wrong type %u\n", type); + return -1; + } + + if (value != NULL && type < TK_M_MAX) { + /* check if we need to split or call fnmatch() while matching rules */ + enum string_glob_type glob; + int has_split; + int has_glob; + + has_split = (strchr(value, '|') != NULL); + has_glob = (strchr(value, '*') != NULL || strchr(value, '?') != NULL || strchr(value, '[') != NULL); + if (has_split && has_glob) { + glob = GL_SPLIT_GLOB; + } else if (has_split) { + glob = GL_SPLIT; + } else if (has_glob) { + if (streq(value, "?*")) + glob = GL_SOMETHING; + else + glob = GL_GLOB; + } else { + glob = GL_PLAIN; + } + token->key.glob = glob; + } + + if (value != NULL && type > TK_M_MAX) { + /* check if assigned value has substitution chars */ + if (value[0] == '[') + token->key.subst = SB_SUBSYS; + else if (strchr(value, '%') != NULL || strchr(value, '$') != NULL) + token->key.subst = SB_FORMAT; + else + token->key.subst = SB_NONE; + } + + if (attr != NULL) { + /* check if property/attribut name has substitution chars */ + if (attr[0] == '[') + token->key.attrsubst = SB_SUBSYS; + else if (strchr(attr, '%') != NULL || strchr(attr, '$') != NULL) + token->key.attrsubst = SB_FORMAT; + else + token->key.attrsubst = SB_NONE; + } + + token->key.type = type; + token->key.op = op; + rule_tmp->token_cur++; + if (rule_tmp->token_cur >= ELEMENTSOF(rule_tmp->token)) { + log_error("temporary rule array too small\n"); + return -1; + } + return 0; +} + +static int sort_token(struct udev_rules *rules, struct rule_tmp *rule_tmp) +{ + unsigned int i; + unsigned int start = 0; + unsigned int end = rule_tmp->token_cur; + + for (i = 0; i < rule_tmp->token_cur; i++) { + enum token_type next_val = TK_UNSET; + unsigned int next_idx = 0; + unsigned int j; + + /* find smallest value */ + for (j = start; j < end; j++) { + if (rule_tmp->token[j].type == TK_UNSET) + continue; + if (next_val == TK_UNSET || rule_tmp->token[j].type < next_val) { + next_val = rule_tmp->token[j].type; + next_idx = j; + } + } + + /* add token and mark done */ + if (add_token(rules, &rule_tmp->token[next_idx]) != 0) + return -1; + rule_tmp->token[next_idx].type = TK_UNSET; + + /* shrink range */ + if (next_idx == start) + start++; + if (next_idx+1 == end) + end--; + } + return 0; +} + +static int add_rule(struct udev_rules *rules, char *line, + const char *filename, unsigned int filename_off, unsigned int lineno) +{ + char *linepos; + const char *attr; + struct rule_tmp rule_tmp; + + memset(&rule_tmp, 0x00, sizeof(struct rule_tmp)); + rule_tmp.rules = rules; + rule_tmp.rule.type = TK_RULE; + /* the offset in the rule is limited to unsigned short */ + if (filename_off < USHRT_MAX) + rule_tmp.rule.rule.filename_off = filename_off; + rule_tmp.rule.rule.filename_line = lineno; + + linepos = line; + for (;;) { + char *key; + char *value; + enum operation_type op; + + if (get_key(rules->udev, &linepos, &key, &op, &value) != 0) + break; + + if (streq(key, "ACTION")) { + if (op > OP_MATCH_MAX) { + log_error("invalid ACTION operation\n"); + goto invalid; + } + rule_add_key(&rule_tmp, TK_M_ACTION, op, value, NULL); + continue; + } + + if (streq(key, "DEVPATH")) { + if (op > OP_MATCH_MAX) { + log_error("invalid DEVPATH operation\n"); + goto invalid; + } + rule_add_key(&rule_tmp, TK_M_DEVPATH, op, value, NULL); + continue; + } + + if (streq(key, "KERNEL")) { + if (op > OP_MATCH_MAX) { + log_error("invalid KERNEL operation\n"); + goto invalid; + } + rule_add_key(&rule_tmp, TK_M_KERNEL, op, value, NULL); + continue; + } + + if (streq(key, "SUBSYSTEM")) { + if (op > OP_MATCH_MAX) { + log_error("invalid SUBSYSTEM operation\n"); + goto invalid; + } + /* bus, class, subsystem events should all be the same */ + if (streq(value, "subsystem") || + streq(value, "bus") || + streq(value, "class")) { + if (streq(value, "bus") || streq(value, "class")) + log_error("'%s' must be specified as 'subsystem' \n" + "please fix it in %s:%u", value, filename, lineno); + rule_add_key(&rule_tmp, TK_M_SUBSYSTEM, op, "subsystem|class|bus", NULL); + } else + rule_add_key(&rule_tmp, TK_M_SUBSYSTEM, op, value, NULL); + continue; + } + + if (streq(key, "DRIVER")) { + if (op > OP_MATCH_MAX) { + log_error("invalid DRIVER operation\n"); + goto invalid; + } + rule_add_key(&rule_tmp, TK_M_DRIVER, op, value, NULL); + continue; + } + + if (startswith(key, "ATTR{")) { + attr = get_key_attribute(rules->udev, key + sizeof("ATTR")-1); + if (attr == NULL) { + log_error("error parsing ATTR attribute\n"); + goto invalid; + } + if (op < OP_MATCH_MAX) { + rule_add_key(&rule_tmp, TK_M_ATTR, op, value, attr); + } else { + rule_add_key(&rule_tmp, TK_A_ATTR, op, value, attr); + } + continue; + } + + if (streq(key, "KERNELS")) { + if (op > OP_MATCH_MAX) { + log_error("invalid KERNELS operation\n"); + goto invalid; + } + rule_add_key(&rule_tmp, TK_M_KERNELS, op, value, NULL); + continue; + } + + if (streq(key, "SUBSYSTEMS")) { + if (op > OP_MATCH_MAX) { + log_error("invalid SUBSYSTEMS operation\n"); + goto invalid; + } + rule_add_key(&rule_tmp, TK_M_SUBSYSTEMS, op, value, NULL); + continue; + } + + if (streq(key, "DRIVERS")) { + if (op > OP_MATCH_MAX) { + log_error("invalid DRIVERS operation\n"); + goto invalid; + } + rule_add_key(&rule_tmp, TK_M_DRIVERS, op, value, NULL); + continue; + } + + if (startswith(key, "ATTRS{")) { + if (op > OP_MATCH_MAX) { + log_error("invalid ATTRS operation\n"); + goto invalid; + } + attr = get_key_attribute(rules->udev, key + sizeof("ATTRS")-1); + if (attr == NULL) { + log_error("error parsing ATTRS attribute\n"); + goto invalid; + } + if (startswith(attr, "device/")) + log_error("the 'device' link may not be available in a future kernel, " + "please fix it in %s:%u", filename, lineno); + else if (strstr(attr, "../") != NULL) + log_error("do not reference parent sysfs directories directly, " + "it may break with a future kernel, please fix it in %s:%u", filename, lineno); + rule_add_key(&rule_tmp, TK_M_ATTRS, op, value, attr); + continue; + } + + if (streq(key, "TAGS")) { + if (op > OP_MATCH_MAX) { + log_error("invalid TAGS operation\n"); + goto invalid; + } + rule_add_key(&rule_tmp, TK_M_TAGS, op, value, NULL); + continue; + } + + if (startswith(key, "ENV{")) { + attr = get_key_attribute(rules->udev, key + sizeof("ENV")-1); + if (attr == NULL) { + log_error("error parsing ENV attribute\n"); + goto invalid; + } + if (op < OP_MATCH_MAX) { + if (rule_add_key(&rule_tmp, TK_M_ENV, op, value, attr) != 0) + goto invalid; + } else { + static const char *blacklist[] = { + "ACTION", + "SUBSYSTEM", + "DEVTYPE", + "MAJOR", + "MINOR", + "DRIVER", + "IFINDEX", + "DEVNAME", + "DEVLINKS", + "DEVPATH", + "TAGS", + }; + unsigned int i; + + for (i = 0; i < ELEMENTSOF(blacklist); i++) { + if (!streq(attr, blacklist[i])) + continue; + log_error("invalid ENV attribute, '%s' can not be set %s:%u\n", attr, filename, lineno); + goto invalid; + } + if (rule_add_key(&rule_tmp, TK_A_ENV, op, value, attr) != 0) + goto invalid; + } + continue; + } + + if (streq(key, "TAG")) { + if (op < OP_MATCH_MAX) + rule_add_key(&rule_tmp, TK_M_TAG, op, value, NULL); + else + rule_add_key(&rule_tmp, TK_A_TAG, op, value, NULL); + continue; + } + + if (streq(key, "PROGRAM")) { + rule_add_key(&rule_tmp, TK_M_PROGRAM, op, value, NULL); + continue; + } + + if (streq(key, "RESULT")) { + if (op > OP_MATCH_MAX) { + log_error("invalid RESULT operation\n"); + goto invalid; + } + rule_add_key(&rule_tmp, TK_M_RESULT, op, value, NULL); + continue; + } + + if (startswith(key, "IMPORT")) { + attr = get_key_attribute(rules->udev, key + sizeof("IMPORT")-1); + if (attr == NULL) { + log_error("IMPORT{} type missing, ignoring IMPORT %s:%u\n", filename, lineno); + continue; + } + if (streq(attr, "program")) { + /* find known built-in command */ + if (value[0] != '/') { + enum udev_builtin_cmd cmd; + + cmd = udev_builtin_lookup(value); + if (cmd < UDEV_BUILTIN_MAX) { + log_debug("IMPORT found builtin '%s', replacing %s:%u\n", + value, filename, lineno); + rule_add_key(&rule_tmp, TK_M_IMPORT_BUILTIN, op, value, &cmd); + continue; + } + } + rule_add_key(&rule_tmp, TK_M_IMPORT_PROG, op, value, NULL); + } else if (streq(attr, "builtin")) { + enum udev_builtin_cmd cmd = udev_builtin_lookup(value); + + if (cmd < UDEV_BUILTIN_MAX) + rule_add_key(&rule_tmp, TK_M_IMPORT_BUILTIN, op, value, &cmd); + else + log_error("IMPORT{builtin}: '%s' unknown %s:%u\n", value, filename, lineno); + } else if (streq(attr, "file")) { + rule_add_key(&rule_tmp, TK_M_IMPORT_FILE, op, value, NULL); + } else if (streq(attr, "db")) { + rule_add_key(&rule_tmp, TK_M_IMPORT_DB, op, value, NULL); + } else if (streq(attr, "cmdline")) { + rule_add_key(&rule_tmp, TK_M_IMPORT_CMDLINE, op, value, NULL); + } else if (streq(attr, "parent")) { + rule_add_key(&rule_tmp, TK_M_IMPORT_PARENT, op, value, NULL); + } else + log_error("IMPORT{} unknown type, ignoring IMPORT %s:%u\n", filename, lineno); + continue; + } + + if (startswith(key, "TEST")) { + mode_t mode = 0; + + if (op > OP_MATCH_MAX) { + log_error("invalid TEST operation\n"); + goto invalid; + } + attr = get_key_attribute(rules->udev, key + sizeof("TEST")-1); + if (attr != NULL) { + mode = strtol(attr, NULL, 8); + rule_add_key(&rule_tmp, TK_M_TEST, op, value, &mode); + } else { + rule_add_key(&rule_tmp, TK_M_TEST, op, value, NULL); + } + continue; + } + + if (startswith(key, "RUN")) { + attr = get_key_attribute(rules->udev, key + sizeof("RUN")-1); + if (attr == NULL) + attr = "program"; + + if (streq(attr, "builtin")) { + enum udev_builtin_cmd cmd = udev_builtin_lookup(value); + + if (cmd < UDEV_BUILTIN_MAX) + rule_add_key(&rule_tmp, TK_A_RUN_BUILTIN, op, value, &cmd); + else + log_error("IMPORT{builtin}: '%s' unknown %s:%u\n", value, filename, lineno); + } else if (streq(attr, "program")) { + enum udev_builtin_cmd cmd = UDEV_BUILTIN_MAX; + + rule_add_key(&rule_tmp, TK_A_RUN_PROGRAM, op, value, &cmd); + } else { + log_error("RUN{} unknown type, ignoring RUN %s:%u\n", filename, lineno); + } + + continue; + } + + if (streq(key, "WAIT_FOR") || streq(key, "WAIT_FOR_SYSFS")) { + rule_add_key(&rule_tmp, TK_M_WAITFOR, 0, value, NULL); + continue; + } + + if (streq(key, "LABEL")) { + rule_tmp.rule.rule.label_off = rules_add_string(rules, value); + continue; + } + + if (streq(key, "GOTO")) { + rule_add_key(&rule_tmp, TK_A_GOTO, 0, value, NULL); + continue; + } + + if (startswith(key, "NAME")) { + if (op < OP_MATCH_MAX) { + rule_add_key(&rule_tmp, TK_M_NAME, op, value, NULL); + } else { + if (streq(value, "%k")) { + log_error("NAME=\"%%k\" is ignored, because it breaks kernel supplied names, " + "please remove it from %s:%u\n", filename, lineno); + continue; + } + if (value[0] == '\0') { + log_debug("NAME=\"\" is ignored, because udev will not delete any device nodes, " + "please remove it from %s:%u\n", filename, lineno); + continue; + } + rule_add_key(&rule_tmp, TK_A_NAME, op, value, NULL); + } + rule_tmp.rule.rule.can_set_name = true; + continue; + } + + if (streq(key, "SYMLINK")) { + if (op < OP_MATCH_MAX) + rule_add_key(&rule_tmp, TK_M_DEVLINK, op, value, NULL); + else + rule_add_key(&rule_tmp, TK_A_DEVLINK, op, value, NULL); + rule_tmp.rule.rule.can_set_name = true; + continue; + } + + if (streq(key, "OWNER")) { + uid_t uid; + char *endptr; + + uid = strtoul(value, &endptr, 10); + if (endptr[0] == '\0') { + rule_add_key(&rule_tmp, TK_A_OWNER_ID, op, NULL, &uid); + } else if ((rules->resolve_names > 0) && strchr("$%", value[0]) == NULL) { + uid = add_uid(rules, value); + rule_add_key(&rule_tmp, TK_A_OWNER_ID, op, NULL, &uid); + } else if (rules->resolve_names >= 0) { + rule_add_key(&rule_tmp, TK_A_OWNER, op, value, NULL); + } + rule_tmp.rule.rule.can_set_name = true; + continue; + } + + if (streq(key, "GROUP")) { + gid_t gid; + char *endptr; + + gid = strtoul(value, &endptr, 10); + if (endptr[0] == '\0') { + rule_add_key(&rule_tmp, TK_A_GROUP_ID, op, NULL, &gid); + } else if ((rules->resolve_names > 0) && strchr("$%", value[0]) == NULL) { + gid = add_gid(rules, value); + rule_add_key(&rule_tmp, TK_A_GROUP_ID, op, NULL, &gid); + } else if (rules->resolve_names >= 0) { + rule_add_key(&rule_tmp, TK_A_GROUP, op, value, NULL); + } + rule_tmp.rule.rule.can_set_name = true; + continue; + } + + if (streq(key, "MODE")) { + mode_t mode; + char *endptr; + + mode = strtol(value, &endptr, 8); + if (endptr[0] == '\0') + rule_add_key(&rule_tmp, TK_A_MODE_ID, op, NULL, &mode); + else + rule_add_key(&rule_tmp, TK_A_MODE, op, value, NULL); + rule_tmp.rule.rule.can_set_name = true; + continue; + } + + if (streq(key, "OPTIONS")) { + const char *pos; + + pos = strstr(value, "link_priority="); + if (pos != NULL) { + int prio = atoi(&pos[strlen("link_priority=")]); + + rule_add_key(&rule_tmp, TK_A_DEVLINK_PRIO, op, NULL, &prio); + } + + pos = strstr(value, "event_timeout="); + if (pos != NULL) { + int tout = atoi(&pos[strlen("event_timeout=")]); + + rule_add_key(&rule_tmp, TK_M_EVENT_TIMEOUT, op, NULL, &tout); + } + + pos = strstr(value, "string_escape="); + if (pos != NULL) { + pos = &pos[strlen("string_escape=")]; + if (startswith(pos, "none")) + rule_add_key(&rule_tmp, TK_A_STRING_ESCAPE_NONE, op, NULL, NULL); + else if (startswith(pos, "replace")) + rule_add_key(&rule_tmp, TK_A_STRING_ESCAPE_REPLACE, op, NULL, NULL); + } + + pos = strstr(value, "db_persist"); + if (pos != NULL) + rule_add_key(&rule_tmp, TK_A_DB_PERSIST, op, NULL, NULL); + + pos = strstr(value, "nowatch"); + if (pos != NULL) { + const int off = 0; + + rule_add_key(&rule_tmp, TK_A_INOTIFY_WATCH, op, NULL, &off); + } else { + pos = strstr(value, "watch"); + if (pos != NULL) { + const int on = 1; + + rule_add_key(&rule_tmp, TK_A_INOTIFY_WATCH, op, NULL, &on); + } + } + + pos = strstr(value, "static_node="); + if (pos != NULL) { + rule_add_key(&rule_tmp, TK_A_STATIC_NODE, op, &pos[strlen("static_node=")], NULL); + rule_tmp.rule.rule.has_static_node = true; + } + + continue; + } + + log_error("unknown key '%s' in %s:%u\n", key, filename, lineno); + goto invalid; + } + + /* add rule token */ + rule_tmp.rule.rule.token_count = 1 + rule_tmp.token_cur; + if (add_token(rules, &rule_tmp.rule) != 0) + goto invalid; + + /* add tokens to list, sorted by type */ + if (sort_token(rules, &rule_tmp) != 0) + goto invalid; + + return 0; +invalid: + log_error("invalid rule '%s:%u'\n", filename, lineno); + return -1; +} + +static int parse_file(struct udev_rules *rules, const char *filename) +{ + FILE *f; + unsigned int first_token; + unsigned int filename_off; + char line[UTIL_LINE_SIZE]; + int line_nr = 0; + unsigned int i; + + if (null_or_empty_path(filename)) { + log_debug("skip empty file: %s\n", filename); + return 0; + } + log_debug("read rules file: %s\n", filename); + + f = fopen(filename, "re"); + if (f == NULL) + return -1; + + first_token = rules->token_cur; + filename_off = rules_add_string(rules, filename); + + while (fgets(line, sizeof(line), f) != NULL) { + char *key; + size_t len; + + /* skip whitespace */ + line_nr++; + key = line; + while (isspace(key[0])) + key++; + + /* comment */ + if (key[0] == '#') + continue; + + len = strlen(line); + if (len < 3) + continue; + + /* continue reading if backslash+newline is found */ + while (line[len-2] == '\\') { + if (fgets(&line[len-2], (sizeof(line)-len)+2, f) == NULL) + break; + if (strlen(&line[len-2]) < 2) + break; + line_nr++; + len = strlen(line); + } + + if (len+1 >= sizeof(line)) { + log_error("line too long '%s':%u, ignored\n", filename, line_nr); + continue; + } + add_rule(rules, key, filename, filename_off, line_nr); + } + fclose(f); + + /* link GOTOs to LABEL rules in this file to be able to fast-forward */ + for (i = first_token+1; i < rules->token_cur; i++) { + if (rules->tokens[i].type == TK_A_GOTO) { + char *label = rules_str(rules, rules->tokens[i].key.value_off); + unsigned int j; + + for (j = i+1; j < rules->token_cur; j++) { + if (rules->tokens[j].type != TK_RULE) + continue; + if (rules->tokens[j].rule.label_off == 0) + continue; + if (!streq(label, rules_str(rules, rules->tokens[j].rule.label_off))) + continue; + rules->tokens[i].key.rule_goto = j; + break; + } + if (rules->tokens[i].key.rule_goto == 0) + log_error("GOTO '%s' has no matching label in: '%s'\n", label, filename); + } + } + return 0; +} + +struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names) +{ + struct udev_rules *rules; + struct udev_list file_list; + struct token end_token; + char **files, **f; + int r; + + rules = calloc(1, sizeof(struct udev_rules)); + if (rules == NULL) + return NULL; + rules->udev = udev; + rules->resolve_names = resolve_names; + udev_list_init(udev, &file_list, true); + + /* init token array and string buffer */ + rules->tokens = malloc(PREALLOC_TOKEN * sizeof(struct token)); + if (rules->tokens == NULL) + return udev_rules_unref(rules); + rules->token_max = PREALLOC_TOKEN; + + rules->strbuf = strbuf_new(); + if (!rules->strbuf) + return udev_rules_unref(rules); + + rules->dirs = strv_new("/etc/udev/rules.d", + "/run/udev/rules.d", + UDEVLIBEXECDIR "/rules.d", + NULL); + if (!rules->dirs) { + log_error("failed to build config directory array"); + return udev_rules_unref(rules); + } + if (!path_strv_canonicalize(rules->dirs)) { + log_error("failed to canonicalize config directories\n"); + return udev_rules_unref(rules); + } + strv_uniq(rules->dirs); + + rules->dirs_ts_usec = calloc(strv_length(rules->dirs), sizeof(long long)); + if(!rules->dirs_ts_usec) + return udev_rules_unref(rules); + udev_rules_check_timestamp(rules); + + r = conf_files_list_strv(&files, ".rules", (const char **)rules->dirs); + if (r < 0) { + log_error("failed to enumerate rules files: %s\n", strerror(-r)); + return udev_rules_unref(rules); + } + + /* + * The offset value in the rules strct is limited; add all + * rules file names to the beginning of the string buffer. + */ + STRV_FOREACH(f, files) + rules_add_string(rules, *f); + + STRV_FOREACH(f, files) + parse_file(rules, *f); + + strv_free(files); + + memset(&end_token, 0x00, sizeof(struct token)); + end_token.type = TK_END; + add_token(rules, &end_token); + log_debug("rules contain %zu bytes tokens (%u * %zu bytes), %zu bytes strings\n", + rules->token_max * sizeof(struct token), rules->token_max, sizeof(struct token), rules->strbuf->len); + + /* cleanup temporary strbuf data */ + log_debug("%zu strings (%zu bytes), %zu de-duplicated (%zu bytes), %zu trie nodes used\n", + rules->strbuf->in_count, rules->strbuf->in_len, + rules->strbuf->dedup_count, rules->strbuf->dedup_len, rules->strbuf->nodes_count); + strbuf_complete(rules->strbuf); + + /* cleanup uid/gid cache */ + free(rules->uids); + rules->uids = NULL; + rules->uids_cur = 0; + rules->uids_max = 0; + free(rules->gids); + rules->gids = NULL; + rules->gids_cur = 0; + rules->gids_max = 0; + + dump_rules(rules); + return rules; +} + +struct udev_rules *udev_rules_unref(struct udev_rules *rules) +{ + if (rules == NULL) + return NULL; + free(rules->tokens); + strbuf_cleanup(rules->strbuf); + free(rules->uids); + free(rules->gids); + strv_free(rules->dirs); + free(rules->dirs_ts_usec); + free(rules); + return NULL; +} + +bool udev_rules_check_timestamp(struct udev_rules *rules) +{ + unsigned int i; + bool changed = false; + + if (rules == NULL) + goto out; + + for (i = 0; rules->dirs[i]; i++) { + struct stat stats; + + if (stat(rules->dirs[i], &stats) < 0) + continue; + + if (rules->dirs_ts_usec[i] == timespec_load(&stats.st_mtim)) + continue; + + /* first check */ + if (rules->dirs_ts_usec[i] != 0) { + log_debug("reload - timestamp of '%s' changed\n", rules->dirs[i]); + changed = true; + } + + /* update timestamp */ + rules->dirs_ts_usec[i] = timespec_load(&stats.st_mtim); + } +out: + return changed; +} + +static int match_key(struct udev_rules *rules, struct token *token, const char *val) +{ + char *key_value = rules_str(rules, token->key.value_off); + char *pos; + bool match = false; + + if (val == NULL) + val = ""; + + switch (token->key.glob) { + case GL_PLAIN: + match = (streq(key_value, val)); + break; + case GL_GLOB: + match = (fnmatch(key_value, val, 0) == 0); + break; + case GL_SPLIT: + { + const char *s; + size_t len; + + s = rules_str(rules, token->key.value_off); + len = strlen(val); + for (;;) { + const char *next; + + next = strchr(s, '|'); + if (next != NULL) { + size_t matchlen = (size_t)(next - s); + + match = (matchlen == len && strncmp(s, val, matchlen) == 0); + if (match) + break; + } else { + match = (streq(s, val)); + break; + } + s = &next[1]; + } + break; + } + case GL_SPLIT_GLOB: + { + char value[UTIL_PATH_SIZE]; + + util_strscpy(value, sizeof(value), rules_str(rules, token->key.value_off)); + key_value = value; + while (key_value != NULL) { + pos = strchr(key_value, '|'); + if (pos != NULL) { + pos[0] = '\0'; + pos = &pos[1]; + } + match = (fnmatch(key_value, val, 0) == 0); + if (match) + break; + key_value = pos; + } + break; + } + case GL_SOMETHING: + match = (val[0] != '\0'); + break; + case GL_UNSET: + return -1; + } + + if (match && (token->key.op == OP_MATCH)) + return 0; + if (!match && (token->key.op == OP_NOMATCH)) + return 0; + return -1; +} + +static int match_attr(struct udev_rules *rules, struct udev_device *dev, struct udev_event *event, struct token *cur) +{ + const char *name; + char nbuf[UTIL_NAME_SIZE]; + const char *value; + char vbuf[UTIL_NAME_SIZE]; + size_t len; + + name = rules_str(rules, cur->key.attr_off); + switch (cur->key.attrsubst) { + case SB_FORMAT: + udev_event_apply_format(event, name, nbuf, sizeof(nbuf)); + name = nbuf; + /* fall through */ + case SB_NONE: + value = udev_device_get_sysattr_value(dev, name); + if (value == NULL) + return -1; + break; + case SB_SUBSYS: + if (util_resolve_subsys_kernel(event->udev, name, vbuf, sizeof(vbuf), 1) != 0) + return -1; + value = vbuf; + break; + default: + return -1; + } + + /* remove trailing whitespace, if not asked to match for it */ + len = strlen(value); + if (len > 0 && isspace(value[len-1])) { + const char *key_value; + size_t klen; + + key_value = rules_str(rules, cur->key.value_off); + klen = strlen(key_value); + if (klen > 0 && !isspace(key_value[klen-1])) { + if (value != vbuf) { + util_strscpy(vbuf, sizeof(vbuf), value); + value = vbuf; + } + while (len > 0 && isspace(vbuf[--len])) + vbuf[len] = '\0'; + } + } + + return match_key(rules, cur, value); +} + +enum escape_type { + ESCAPE_UNSET, + ESCAPE_NONE, + ESCAPE_REPLACE, +}; + +int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event, const sigset_t *sigmask) +{ + struct token *cur; + struct token *rule; + enum escape_type esc = ESCAPE_UNSET; + bool can_set_name; + + if (rules->tokens == NULL) + return -1; + + can_set_name = ((!streq(udev_device_get_action(event->dev), "remove")) && + (major(udev_device_get_devnum(event->dev)) > 0 || + udev_device_get_ifindex(event->dev) > 0)); + + /* loop through token list, match, run actions or forward to next rule */ + cur = &rules->tokens[0]; + rule = cur; + for (;;) { + dump_token(rules, cur); + switch (cur->type) { + case TK_RULE: + /* current rule */ + rule = cur; + /* possibly skip rules which want to set NAME, SYMLINK, OWNER, GROUP, MODE */ + if (!can_set_name && rule->rule.can_set_name) + goto nomatch; + esc = ESCAPE_UNSET; + break; + case TK_M_ACTION: + if (match_key(rules, cur, udev_device_get_action(event->dev)) != 0) + goto nomatch; + break; + case TK_M_DEVPATH: + if (match_key(rules, cur, udev_device_get_devpath(event->dev)) != 0) + goto nomatch; + break; + case TK_M_KERNEL: + if (match_key(rules, cur, udev_device_get_sysname(event->dev)) != 0) + goto nomatch; + break; + case TK_M_DEVLINK: { + struct udev_list_entry *list_entry; + bool match = false; + + udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(event->dev)) { + const char *devlink; + + devlink = udev_list_entry_get_name(list_entry) + strlen("/dev/"); + if (match_key(rules, cur, devlink) == 0) { + match = true; + break; + } + } + if (!match) + goto nomatch; + break; + } + case TK_M_NAME: + if (match_key(rules, cur, event->name) != 0) + goto nomatch; + break; + case TK_M_ENV: { + const char *key_name = rules_str(rules, cur->key.attr_off); + const char *value; + + value = udev_device_get_property_value(event->dev, key_name); + if (value == NULL) + value = ""; + if (match_key(rules, cur, value)) + goto nomatch; + break; + } + case TK_M_TAG: { + struct udev_list_entry *list_entry; + bool match = false; + + udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(event->dev)) { + if (streq(rules_str(rules, cur->key.value_off), udev_list_entry_get_name(list_entry))) { + match = true; + break; + } + } + if (!match && (cur->key.op != OP_NOMATCH)) + goto nomatch; + break; + } + case TK_M_SUBSYSTEM: + if (match_key(rules, cur, udev_device_get_subsystem(event->dev)) != 0) + goto nomatch; + break; + case TK_M_DRIVER: + if (match_key(rules, cur, udev_device_get_driver(event->dev)) != 0) + goto nomatch; + break; + case TK_M_WAITFOR: { + char filename[UTIL_PATH_SIZE]; + int found; + + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), filename, sizeof(filename)); + found = (wait_for_file(event->dev, filename, 10) == 0); + if (!found && (cur->key.op != OP_NOMATCH)) + goto nomatch; + break; + } + case TK_M_ATTR: + if (match_attr(rules, event->dev, event, cur) != 0) + goto nomatch; + break; + case TK_M_KERNELS: + case TK_M_SUBSYSTEMS: + case TK_M_DRIVERS: + case TK_M_ATTRS: + case TK_M_TAGS: { + struct token *next; + + /* get whole sequence of parent matches */ + next = cur; + while (next->type > TK_M_PARENTS_MIN && next->type < TK_M_PARENTS_MAX) + next++; + + /* loop over parents */ + event->dev_parent = event->dev; + for (;;) { + struct token *key; + + /* loop over sequence of parent match keys */ + for (key = cur; key < next; key++ ) { + dump_token(rules, key); + switch(key->type) { + case TK_M_KERNELS: + if (match_key(rules, key, udev_device_get_sysname(event->dev_parent)) != 0) + goto try_parent; + break; + case TK_M_SUBSYSTEMS: + if (match_key(rules, key, udev_device_get_subsystem(event->dev_parent)) != 0) + goto try_parent; + break; + case TK_M_DRIVERS: + if (match_key(rules, key, udev_device_get_driver(event->dev_parent)) != 0) + goto try_parent; + break; + case TK_M_ATTRS: + if (match_attr(rules, event->dev_parent, event, key) != 0) + goto try_parent; + break; + case TK_M_TAGS: { + bool match = udev_device_has_tag(event->dev_parent, rules_str(rules, cur->key.value_off)); + + if (match && key->key.op == OP_NOMATCH) + goto try_parent; + if (!match && key->key.op == OP_MATCH) + goto try_parent; + break; + } + default: + goto nomatch; + } + } + break; + + try_parent: + event->dev_parent = udev_device_get_parent(event->dev_parent); + if (event->dev_parent == NULL) + goto nomatch; + } + /* move behind our sequence of parent match keys */ + cur = next; + continue; + } + case TK_M_TEST: { + char filename[UTIL_PATH_SIZE]; + struct stat statbuf; + int match; + + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), filename, sizeof(filename)); + if (util_resolve_subsys_kernel(event->udev, filename, filename, sizeof(filename), 0) != 0) { + if (filename[0] != '/') { + char tmp[UTIL_PATH_SIZE]; + + util_strscpy(tmp, sizeof(tmp), filename); + util_strscpyl(filename, sizeof(filename), + udev_device_get_syspath(event->dev), "/", tmp, NULL); + } + } + attr_subst_subdir(filename, sizeof(filename)); + + match = (stat(filename, &statbuf) == 0); + if (match && cur->key.mode > 0) + match = ((statbuf.st_mode & cur->key.mode) > 0); + if (match && cur->key.op == OP_NOMATCH) + goto nomatch; + if (!match && cur->key.op == OP_MATCH) + goto nomatch; + break; + } + case TK_M_EVENT_TIMEOUT: + log_debug("OPTIONS event_timeout=%u\n", cur->key.event_timeout); + event->timeout_usec = cur->key.event_timeout * 1000 * 1000; + break; + case TK_M_PROGRAM: { + char program[UTIL_PATH_SIZE]; + char **envp; + char result[UTIL_PATH_SIZE]; + + free(event->program_result); + event->program_result = NULL; + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), program, sizeof(program)); + envp = udev_device_get_properties_envp(event->dev); + log_debug("PROGRAM '%s' %s:%u\n", + program, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + + if (udev_event_spawn(event, program, envp, sigmask, result, sizeof(result)) < 0) { + if (cur->key.op != OP_NOMATCH) + goto nomatch; + } else { + int count; + + util_remove_trailing_chars(result, '\n'); + if (esc == ESCAPE_UNSET || esc == ESCAPE_REPLACE) { + count = util_replace_chars(result, UDEV_ALLOWED_CHARS_INPUT); + if (count > 0) + log_debug("%i character(s) replaced\n" , count); + } + event->program_result = strdup(result); + if (cur->key.op == OP_NOMATCH) + goto nomatch; + } + break; + } + case TK_M_IMPORT_FILE: { + char import[UTIL_PATH_SIZE]; + + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), import, sizeof(import)); + if (import_file_into_properties(event->dev, import) != 0) + if (cur->key.op != OP_NOMATCH) + goto nomatch; + break; + } + case TK_M_IMPORT_PROG: { + char import[UTIL_PATH_SIZE]; + + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), import, sizeof(import)); + log_debug("IMPORT '%s' %s:%u\n", + import, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + + if (import_program_into_properties(event, import, sigmask) != 0) + if (cur->key.op != OP_NOMATCH) + goto nomatch; + break; + } + case TK_M_IMPORT_BUILTIN: { + char command[UTIL_PATH_SIZE]; + + if (udev_builtin_run_once(cur->key.builtin_cmd)) { + /* check if we ran already */ + if (event->builtin_run & (1 << cur->key.builtin_cmd)) { + log_debug("IMPORT builtin skip '%s' %s:%u\n", + udev_builtin_name(cur->key.builtin_cmd), + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + /* return the result from earlier run */ + if (event->builtin_ret & (1 << cur->key.builtin_cmd)) + if (cur->key.op != OP_NOMATCH) + goto nomatch; + break; + } + /* mark as ran */ + event->builtin_run |= (1 << cur->key.builtin_cmd); + } + + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), command, sizeof(command)); + log_debug("IMPORT builtin '%s' %s:%u\n", + udev_builtin_name(cur->key.builtin_cmd), + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + + if (udev_builtin_run(event->dev, cur->key.builtin_cmd, command, false) != 0) { + /* remember failure */ + log_debug("IMPORT builtin '%s' returned non-zero\n", + udev_builtin_name(cur->key.builtin_cmd)); + event->builtin_ret |= (1 << cur->key.builtin_cmd); + if (cur->key.op != OP_NOMATCH) + goto nomatch; + } + break; + } + case TK_M_IMPORT_DB: { + const char *key = rules_str(rules, cur->key.value_off); + const char *value; + + value = udev_device_get_property_value(event->dev_db, key); + if (value != NULL) { + struct udev_list_entry *entry; + + entry = udev_device_add_property(event->dev, key, value); + udev_list_entry_set_num(entry, true); + } else { + if (cur->key.op != OP_NOMATCH) + goto nomatch; + } + break; + } + case TK_M_IMPORT_CMDLINE: { + FILE *f; + bool imported = false; + + f = fopen("/proc/cmdline", "re"); + if (f != NULL) { + char cmdline[4096]; + + if (fgets(cmdline, sizeof(cmdline), f) != NULL) { + const char *key = rules_str(rules, cur->key.value_off); + char *pos; + + pos = strstr(cmdline, key); + if (pos != NULL) { + struct udev_list_entry *entry; + + pos += strlen(key); + if (pos[0] == '\0' || isspace(pos[0])) { + /* we import simple flags as 'FLAG=1' */ + entry = udev_device_add_property(event->dev, key, "1"); + udev_list_entry_set_num(entry, true); + imported = true; + } else if (pos[0] == '=') { + const char *value; + + pos++; + value = pos; + while (pos[0] != '\0' && !isspace(pos[0])) + pos++; + pos[0] = '\0'; + entry = udev_device_add_property(event->dev, key, value); + udev_list_entry_set_num(entry, true); + imported = true; + } + } + } + fclose(f); + } + if (!imported && cur->key.op != OP_NOMATCH) + goto nomatch; + break; + } + case TK_M_IMPORT_PARENT: { + char import[UTIL_PATH_SIZE]; + + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), import, sizeof(import)); + if (import_parent_into_properties(event->dev, import) != 0) + if (cur->key.op != OP_NOMATCH) + goto nomatch; + break; + } + case TK_M_RESULT: + if (match_key(rules, cur, event->program_result) != 0) + goto nomatch; + break; + case TK_A_STRING_ESCAPE_NONE: + esc = ESCAPE_NONE; + break; + case TK_A_STRING_ESCAPE_REPLACE: + esc = ESCAPE_REPLACE; + break; + case TK_A_DB_PERSIST: + udev_device_set_db_persist(event->dev); + break; + case TK_A_INOTIFY_WATCH: + if (event->inotify_watch_final) + break; + if (cur->key.op == OP_ASSIGN_FINAL) + event->inotify_watch_final = true; + event->inotify_watch = cur->key.watch; + break; + case TK_A_DEVLINK_PRIO: + udev_device_set_devlink_priority(event->dev, cur->key.devlink_prio); + break; + case TK_A_OWNER: { + char owner[UTIL_NAME_SIZE]; + + if (event->owner_final) + break; + if (cur->key.op == OP_ASSIGN_FINAL) + event->owner_final = true; + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), owner, sizeof(owner)); + event->uid = util_lookup_user(event->udev, owner); + log_debug("OWNER %u %s:%u\n", + event->uid, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + break; + } + case TK_A_GROUP: { + char group[UTIL_NAME_SIZE]; + + if (event->group_final) + break; + if (cur->key.op == OP_ASSIGN_FINAL) + event->group_final = true; + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), group, sizeof(group)); + event->gid = util_lookup_group(event->udev, group); + log_debug("GROUP %u %s:%u\n", + event->gid, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + break; + } + case TK_A_MODE: { + char mode_str[UTIL_NAME_SIZE]; + mode_t mode; + char *endptr; + + if (event->mode_final) + break; + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), mode_str, sizeof(mode_str)); + mode = strtol(mode_str, &endptr, 8); + if (endptr[0] != '\0') { + log_error("ignoring invalid mode '%s'\n", mode_str); + break; + } + if (cur->key.op == OP_ASSIGN_FINAL) + event->mode_final = true; + event->mode_set = true; + event->mode = mode; + log_debug("MODE %#o %s:%u\n", + event->mode, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + break; + } + case TK_A_OWNER_ID: + if (event->owner_final) + break; + if (cur->key.op == OP_ASSIGN_FINAL) + event->owner_final = true; + event->uid = cur->key.uid; + log_debug("OWNER %u %s:%u\n", + event->uid, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + break; + case TK_A_GROUP_ID: + if (event->group_final) + break; + if (cur->key.op == OP_ASSIGN_FINAL) + event->group_final = true; + event->gid = cur->key.gid; + log_debug("GROUP %u %s:%u\n", + event->gid, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + break; + case TK_A_MODE_ID: + if (event->mode_final) + break; + if (cur->key.op == OP_ASSIGN_FINAL) + event->mode_final = true; + event->mode_set = true; + event->mode = cur->key.mode; + log_debug("MODE %#o %s:%u\n", + event->mode, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + break; + case TK_A_ENV: { + const char *name = rules_str(rules, cur->key.attr_off); + char *value = rules_str(rules, cur->key.value_off); + char value_new[UTIL_NAME_SIZE]; + const char *value_old = NULL; + struct udev_list_entry *entry; + + if (value[0] == '\0') { + if (cur->key.op == OP_ADD) + break; + udev_device_add_property(event->dev, name, NULL); + break; + } + + if (cur->key.op == OP_ADD) + value_old = udev_device_get_property_value(event->dev, name); + if (value_old) { + char temp[UTIL_NAME_SIZE]; + + /* append value separated by space */ + udev_event_apply_format(event, value, temp, sizeof(temp)); + util_strscpyl(value_new, sizeof(value_new), value_old, " ", temp, NULL); + } else + udev_event_apply_format(event, value, value_new, sizeof(value_new)); + + entry = udev_device_add_property(event->dev, name, value_new); + /* store in db, skip private keys */ + if (name[0] != '.') + udev_list_entry_set_num(entry, true); + break; + } + case TK_A_TAG: { + char tag[UTIL_PATH_SIZE]; + const char *p; + + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), tag, sizeof(tag)); + if (cur->key.op == OP_ASSIGN || cur->key.op == OP_ASSIGN_FINAL) + udev_device_cleanup_tags_list(event->dev); + for (p = tag; *p != '\0'; p++) { + if ((*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z') || + (*p >= '0' && *p <= '9') || + *p == '-' || *p == '_') + continue; + log_error("ignoring invalid tag name '%s'\n", tag); + break; + } + udev_device_add_tag(event->dev, tag); + break; + } + case TK_A_NAME: { + const char *name = rules_str(rules, cur->key.value_off); + + char name_str[UTIL_PATH_SIZE]; + int count; + + if (event->name_final) + break; + if (cur->key.op == OP_ASSIGN_FINAL) + event->name_final = true; + udev_event_apply_format(event, name, name_str, sizeof(name_str)); + if (esc == ESCAPE_UNSET || esc == ESCAPE_REPLACE) { + count = util_replace_chars(name_str, "/"); + if (count > 0) + log_debug("%i character(s) replaced\n", count); + } + if (major(udev_device_get_devnum(event->dev)) && + (!streq(name_str, udev_device_get_devnode(event->dev) + strlen("/dev/")))) { + log_error("NAME=\"%s\" ignored, kernel device nodes " + "can not be renamed; please fix it in %s:%u\n", name, + rules_str(rules, rule->rule.filename_off), rule->rule.filename_line); + break; + } + free(event->name); + event->name = strdup(name_str); + log_debug("NAME '%s' %s:%u\n", + event->name, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + break; + } + case TK_A_DEVLINK: { + char temp[UTIL_PATH_SIZE]; + char filename[UTIL_PATH_SIZE]; + char *pos, *next; + int count = 0; + + if (event->devlink_final) + break; + if (major(udev_device_get_devnum(event->dev)) == 0) + break; + if (cur->key.op == OP_ASSIGN_FINAL) + event->devlink_final = true; + if (cur->key.op == OP_ASSIGN || cur->key.op == OP_ASSIGN_FINAL) + udev_device_cleanup_devlinks_list(event->dev); + + /* allow multiple symlinks separated by spaces */ + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), temp, sizeof(temp)); + if (esc == ESCAPE_UNSET) + count = util_replace_chars(temp, "/ "); + else if (esc == ESCAPE_REPLACE) + count = util_replace_chars(temp, "/"); + if (count > 0) + log_debug("%i character(s) replaced\n" , count); + pos = temp; + while (isspace(pos[0])) + pos++; + next = strchr(pos, ' '); + while (next != NULL) { + next[0] = '\0'; + log_debug("LINK '%s' %s:%u\n", pos, + rules_str(rules, rule->rule.filename_off), rule->rule.filename_line); + util_strscpyl(filename, sizeof(filename), "/dev/", pos, NULL); + udev_device_add_devlink(event->dev, filename); + while (isspace(next[1])) + next++; + pos = &next[1]; + next = strchr(pos, ' '); + } + if (pos[0] != '\0') { + log_debug("LINK '%s' %s:%u\n", pos, + rules_str(rules, rule->rule.filename_off), rule->rule.filename_line); + util_strscpyl(filename, sizeof(filename), "/dev/", pos, NULL); + udev_device_add_devlink(event->dev, filename); + } + break; + } + case TK_A_ATTR: { + const char *key_name = rules_str(rules, cur->key.attr_off); + char attr[UTIL_PATH_SIZE]; + char value[UTIL_NAME_SIZE]; + FILE *f; + + if (util_resolve_subsys_kernel(event->udev, key_name, attr, sizeof(attr), 0) != 0) + util_strscpyl(attr, sizeof(attr), udev_device_get_syspath(event->dev), "/", key_name, NULL); + attr_subst_subdir(attr, sizeof(attr)); + + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), value, sizeof(value)); + log_debug("ATTR '%s' writing '%s' %s:%u\n", attr, value, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + f = fopen(attr, "we"); + if (f != NULL) { + if (fprintf(f, "%s", value) <= 0) + log_error("error writing ATTR{%s}: %m\n", attr); + fclose(f); + } else { + log_error("error opening ATTR{%s} for writing: %m\n", attr); + } + break; + } + case TK_A_RUN_BUILTIN: + case TK_A_RUN_PROGRAM: { + struct udev_list_entry *entry; + + if (cur->key.op == OP_ASSIGN || cur->key.op == OP_ASSIGN_FINAL) + udev_list_cleanup(&event->run_list); + log_debug("RUN '%s' %s:%u\n", + rules_str(rules, cur->key.value_off), + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + entry = udev_list_entry_add(&event->run_list, rules_str(rules, cur->key.value_off), NULL); + udev_list_entry_set_num(entry, cur->key.builtin_cmd); + break; + } + case TK_A_GOTO: + if (cur->key.rule_goto == 0) + break; + cur = &rules->tokens[cur->key.rule_goto]; + continue; + case TK_END: + return 0; + + case TK_M_PARENTS_MIN: + case TK_M_PARENTS_MAX: + case TK_M_MAX: + case TK_UNSET: + log_error("wrong type %u\n", cur->type); + goto nomatch; + } + + cur++; + continue; + nomatch: + /* fast-forward to next rule */ + cur = rule + rule->rule.token_count; + } +} + +void udev_rules_apply_static_dev_perms(struct udev_rules *rules) +{ + struct token *cur; + struct token *rule; + uid_t uid = 0; + gid_t gid = 0; + mode_t mode = 0; + + if (rules->tokens == NULL) + return; + + cur = &rules->tokens[0]; + rule = cur; + for (;;) { + switch (cur->type) { + case TK_RULE: + /* current rule */ + rule = cur; + + /* skip rules without a static_node tag */ + if (!rule->rule.has_static_node) + goto next; + + uid = 0; + gid = 0; + mode = 0; + break; + case TK_A_OWNER_ID: + uid = cur->key.uid; + break; + case TK_A_GROUP_ID: + gid = cur->key.gid; + break; + case TK_A_MODE_ID: + mode = cur->key.mode; + break; + case TK_A_STATIC_NODE: { + char filename[UTIL_PATH_SIZE]; + struct stat stats; + + /* we assure, that the permissions tokens are sorted before the static token */ + if (mode == 0 && uid == 0 && gid == 0) + goto next; + util_strscpyl(filename, sizeof(filename), "/dev/", rules_str(rules, cur->key.value_off), NULL); + if (stat(filename, &stats) != 0) + goto next; + if (!S_ISBLK(stats.st_mode) && !S_ISCHR(stats.st_mode)) + goto next; + if (mode == 0) { + if (gid > 0) + mode = 0660; + else + mode = 0600; + } + if (mode != (stats.st_mode & 01777)) { + chmod(filename, mode); + log_debug("chmod '%s' %#o\n", filename, mode); + } + + if ((uid != 0 && uid != stats.st_uid) || (gid != 0 && gid != stats.st_gid)) { + chown(filename, uid, gid); + log_debug("chown '%s' %u %u\n", filename, uid, gid); + } + + utimensat(AT_FDCWD, filename, NULL, 0); + break; + } + case TK_END: + return; + } + + cur++; + continue; +next: + /* fast-forward to next rule */ + cur = rule + rule->rule.token_count; + continue; + } +} diff --git a/src/udev/udev-watch.c b/src/udev/udev-watch.c new file mode 100644 index 0000000000..311f5bdf23 --- /dev/null +++ b/src/udev/udev-watch.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2004-2012 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2009 Canonical Ltd. + * Copyright (C) 2009 Scott James Remnant <scott@netsplit.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <sys/types.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <dirent.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/inotify.h> + +#include "udev.h" + +static int inotify_fd = -1; + +/* inotify descriptor, will be shared with rules directory; + * set to cloexec since we need our children to be able to add + * watches for us + */ +int udev_watch_init(struct udev *udev) +{ + inotify_fd = inotify_init1(IN_CLOEXEC); + if (inotify_fd < 0) + log_error("inotify_init failed: %m\n"); + return inotify_fd; +} + +/* move any old watches directory out of the way, and then restore + * the watches + */ +void udev_watch_restore(struct udev *udev) +{ + if (inotify_fd < 0) + return; + + if (rename("/run/udev/watch", "/run/udev/watch.old") == 0) { + DIR *dir; + struct dirent *ent; + + dir = opendir("/run/udev/watch.old"); + if (dir == NULL) { + log_error("unable to open old watches dir /run/udev/watch.old; old watches will not be restored: %m"); + return; + } + + for (ent = readdir(dir); ent != NULL; ent = readdir(dir)) { + char device[UTIL_PATH_SIZE]; + ssize_t len; + struct udev_device *dev; + + if (ent->d_name[0] == '.') + continue; + + len = readlinkat(dirfd(dir), ent->d_name, device, sizeof(device)); + if (len <= 0 || len == (ssize_t)sizeof(device)) + goto unlink; + device[len] = '\0'; + + dev = udev_device_new_from_device_id(udev, device); + if (dev == NULL) + goto unlink; + + log_debug("restoring old watch on '%s'\n", udev_device_get_devnode(dev)); + udev_watch_begin(udev, dev); + udev_device_unref(dev); +unlink: + unlinkat(dirfd(dir), ent->d_name, 0); + } + + closedir(dir); + rmdir("/run/udev/watch.old"); + + } else if (errno != ENOENT) { + log_error("unable to move watches dir /run/udev/watch; old watches will not be restored: %m"); + } +} + +void udev_watch_begin(struct udev *udev, struct udev_device *dev) +{ + char filename[UTIL_PATH_SIZE]; + int wd; + int r; + + if (inotify_fd < 0) + return; + + log_debug("adding watch on '%s'\n", udev_device_get_devnode(dev)); + wd = inotify_add_watch(inotify_fd, udev_device_get_devnode(dev), IN_CLOSE_WRITE); + if (wd < 0) { + log_error("inotify_add_watch(%d, %s, %o) failed: %m\n", + inotify_fd, udev_device_get_devnode(dev), IN_CLOSE_WRITE); + return; + } + + snprintf(filename, sizeof(filename), "/run/udev/watch/%d", wd); + mkdir_parents(filename, 0755); + unlink(filename); + r = symlink(udev_device_get_id_filename(dev), filename); + if (r < 0) + log_error("Failed to create symlink: %m"); + + udev_device_set_watch_handle(dev, wd); +} + +void udev_watch_end(struct udev *udev, struct udev_device *dev) +{ + int wd; + char filename[UTIL_PATH_SIZE]; + + if (inotify_fd < 0) + return; + + wd = udev_device_get_watch_handle(dev); + if (wd < 0) + return; + + log_debug("removing watch on '%s'\n", udev_device_get_devnode(dev)); + inotify_rm_watch(inotify_fd, wd); + + snprintf(filename, sizeof(filename), "/run/udev/watch/%d", wd); + unlink(filename); + + udev_device_set_watch_handle(dev, -1); +} + +struct udev_device *udev_watch_lookup(struct udev *udev, int wd) +{ + char filename[UTIL_PATH_SIZE]; + char device[UTIL_NAME_SIZE]; + ssize_t len; + + if (inotify_fd < 0 || wd < 0) + return NULL; + + snprintf(filename, sizeof(filename), "/run/udev/watch/%d", wd); + len = readlink(filename, device, sizeof(device)); + if (len <= 0 || (size_t)len == sizeof(device)) + return NULL; + device[len] = '\0'; + + return udev_device_new_from_device_id(udev, device); +} diff --git a/src/udev/udev.conf b/src/udev/udev.conf new file mode 100644 index 0000000000..f39253eb67 --- /dev/null +++ b/src/udev/udev.conf @@ -0,0 +1,3 @@ +# see udev(7) for details + +#udev_log="info" diff --git a/src/udev/udev.h b/src/udev/udev.h new file mode 100644 index 0000000000..82cd2ad6d0 --- /dev/null +++ b/src/udev/udev.h @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com> + * Copyright (C) 2003-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/>. + */ + +#ifndef _UDEV_H_ +#define _UDEV_H_ + +#include <sys/types.h> +#include <sys/param.h> +#include <signal.h> + +#include "libudev.h" +#include "libudev-private.h" +#include "util.h" +#include "label.h" +#include "strv.h" + +struct udev_event { + struct udev *udev; + struct udev_device *dev; + struct udev_device *dev_parent; + struct udev_device *dev_db; + char *name; + char *program_result; + mode_t mode; + uid_t uid; + gid_t gid; + struct udev_list run_list; + int exec_delay; + usec_t birth_usec; + usec_t timeout_usec; + int fd_signal; + unsigned int builtin_run; + unsigned int builtin_ret; + bool sigterm; + bool inotify_watch; + bool inotify_watch_final; + bool group_final; + bool owner_final; + bool mode_set; + bool mode_final; + bool name_final; + bool devlink_final; + bool run_final; +}; + +struct udev_watch { + struct udev_list_node node; + int handle; + char *name; +}; + +/* udev-rules.c */ +struct udev_rules; +struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names); +struct udev_rules *udev_rules_unref(struct udev_rules *rules); +bool udev_rules_check_timestamp(struct udev_rules *rules); +int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event, const sigset_t *sigmask); +void udev_rules_apply_static_dev_perms(struct udev_rules *rules); + +/* udev-event.c */ +struct udev_event *udev_event_new(struct udev_device *dev); +void udev_event_unref(struct udev_event *event); +size_t udev_event_apply_format(struct udev_event *event, const char *src, char *dest, size_t size); +int udev_event_apply_subsys_kernel(struct udev_event *event, const char *string, + char *result, size_t maxsize, int read_value); +int udev_event_spawn(struct udev_event *event, + const char *cmd, char **envp, const sigset_t *sigmask, + char *result, size_t ressize); +int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules, const sigset_t *sigset); +void udev_event_execute_run(struct udev_event *event, const sigset_t *sigset); +int udev_build_argv(struct udev *udev, char *cmd, int *argc, char *argv[]); + +/* udev-watch.c */ +int udev_watch_init(struct udev *udev); +void udev_watch_restore(struct udev *udev); +void udev_watch_begin(struct udev *udev, struct udev_device *dev); +void udev_watch_end(struct udev *udev, struct udev_device *dev); +struct udev_device *udev_watch_lookup(struct udev *udev, int wd); + +/* udev-node.c */ +void udev_node_add(struct udev_device *dev, mode_t mode, uid_t uid, gid_t gid); +void udev_node_remove(struct udev_device *dev); +void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev_old); + +/* udev-ctrl.c */ +struct udev_ctrl; +struct udev_ctrl *udev_ctrl_new(struct udev *udev); +struct udev_ctrl *udev_ctrl_new_from_fd(struct udev *udev, int fd); +int udev_ctrl_enable_receiving(struct udev_ctrl *uctrl); +struct udev_ctrl *udev_ctrl_ref(struct udev_ctrl *uctrl); +struct udev_ctrl *udev_ctrl_unref(struct udev_ctrl *uctrl); +int udev_ctrl_cleanup(struct udev_ctrl *uctrl); +struct udev *udev_ctrl_get_udev(struct udev_ctrl *uctrl); +int udev_ctrl_get_fd(struct udev_ctrl *uctrl); +int udev_ctrl_send_set_log_level(struct udev_ctrl *uctrl, int priority, int timeout); +int udev_ctrl_send_stop_exec_queue(struct udev_ctrl *uctrl, int timeout); +int udev_ctrl_send_start_exec_queue(struct udev_ctrl *uctrl, int timeout); +int udev_ctrl_send_reload(struct udev_ctrl *uctrl, int timeout); +int udev_ctrl_send_ping(struct udev_ctrl *uctrl, int timeout); +int udev_ctrl_send_exit(struct udev_ctrl *uctrl, int timeout); +int udev_ctrl_send_set_env(struct udev_ctrl *uctrl, const char *key, int timeout); +int udev_ctrl_send_set_children_max(struct udev_ctrl *uctrl, int count, int timeout); +struct udev_ctrl_connection; +struct udev_ctrl_connection *udev_ctrl_get_connection(struct udev_ctrl *uctrl); +struct udev_ctrl_connection *udev_ctrl_connection_ref(struct udev_ctrl_connection *conn); +struct udev_ctrl_connection *udev_ctrl_connection_unref(struct udev_ctrl_connection *conn); +struct udev_ctrl_msg; +struct udev_ctrl_msg *udev_ctrl_receive_msg(struct udev_ctrl_connection *conn); +struct udev_ctrl_msg *udev_ctrl_msg_ref(struct udev_ctrl_msg *ctrl_msg); +struct udev_ctrl_msg *udev_ctrl_msg_unref(struct udev_ctrl_msg *ctrl_msg); +int udev_ctrl_get_set_log_level(struct udev_ctrl_msg *ctrl_msg); +int udev_ctrl_get_stop_exec_queue(struct udev_ctrl_msg *ctrl_msg); +int udev_ctrl_get_start_exec_queue(struct udev_ctrl_msg *ctrl_msg); +int udev_ctrl_get_reload(struct udev_ctrl_msg *ctrl_msg); +int udev_ctrl_get_ping(struct udev_ctrl_msg *ctrl_msg); +int udev_ctrl_get_exit(struct udev_ctrl_msg *ctrl_msg); +const char *udev_ctrl_get_set_env(struct udev_ctrl_msg *ctrl_msg); +int udev_ctrl_get_set_children_max(struct udev_ctrl_msg *ctrl_msg); + +/* built-in commands */ +enum udev_builtin_cmd { + UDEV_BUILTIN_BLKID, + UDEV_BUILTIN_BTRFS, + UDEV_BUILTIN_FIRMWARE, + UDEV_BUILTIN_HWDB, + UDEV_BUILTIN_INPUT_ID, + UDEV_BUILTIN_KMOD, + UDEV_BUILTIN_NET_ID, + UDEV_BUILTIN_PATH_ID, + UDEV_BUILTIN_USB_ID, +#ifdef HAVE_ACL + UDEV_BUILTIN_UACCESS, +#endif + UDEV_BUILTIN_MAX +}; +struct udev_builtin { + const char *name; + int (*cmd)(struct udev_device *dev, int argc, char *argv[], bool test); + const char *help; + int (*init)(struct udev *udev); + void (*exit)(struct udev *udev); + bool (*validate)(struct udev *udev); + bool run_once; +}; +extern const struct udev_builtin udev_builtin_blkid; +extern const struct udev_builtin udev_builtin_btrfs; +extern const struct udev_builtin udev_builtin_firmware; +extern const struct udev_builtin udev_builtin_hwdb; +extern const struct udev_builtin udev_builtin_input_id; +extern const struct udev_builtin udev_builtin_kmod; +extern const struct udev_builtin udev_builtin_net_id; +extern const struct udev_builtin udev_builtin_path_id; +extern const struct udev_builtin udev_builtin_usb_id; +extern const struct udev_builtin udev_builtin_uaccess; +void udev_builtin_init(struct udev *udev); +void udev_builtin_exit(struct udev *udev); +enum udev_builtin_cmd udev_builtin_lookup(const char *command); +const char *udev_builtin_name(enum udev_builtin_cmd cmd); +bool udev_builtin_run_once(enum udev_builtin_cmd cmd); +int udev_builtin_run(struct udev_device *dev, enum udev_builtin_cmd cmd, const char *command, bool test); +void udev_builtin_list(struct udev *udev); +bool udev_builtin_validate(struct udev *udev); +int udev_builtin_add_property(struct udev_device *dev, bool test, const char *key, const char *val); +int udev_builtin_hwdb_lookup(struct udev_device *dev, const char *modalias, bool test); + +/* udev logging */ +void udev_main_log(struct udev *udev, int priority, + const char *file, int line, const char *fn, + const char *format, va_list args); + +/* udevadm commands */ +struct udevadm_cmd { + const char *name; + int (*cmd)(struct udev *udev, int argc, char *argv[]); + const char *help; + int debug; +}; +extern const struct udevadm_cmd udevadm_info; +extern const struct udevadm_cmd udevadm_trigger; +extern const struct udevadm_cmd udevadm_settle; +extern const struct udevadm_cmd udevadm_control; +extern const struct udevadm_cmd udevadm_monitor; +extern const struct udevadm_cmd udevadm_hwdb; +extern const struct udevadm_cmd udevadm_test; +extern const struct udevadm_cmd udevadm_test_builtin; +#endif diff --git a/src/udev/udev.pc.in b/src/udev/udev.pc.in new file mode 100644 index 0000000000..a0c2e82d47 --- /dev/null +++ b/src/udev/udev.pc.in @@ -0,0 +1,5 @@ +Name: udev +Description: udev +Version: @VERSION@ + +udevdir=@udevlibexecdir@ diff --git a/src/udev/udevadm-control.c b/src/udev/udevadm-control.c new file mode 100644 index 0000000000..c5a189257d --- /dev/null +++ b/src/udev/udevadm-control.c @@ -0,0 +1,175 @@ +/* + * 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 <time.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <unistd.h> +#include <getopt.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/un.h> + +#include "udev.h" + +static void print_help(void) +{ + printf("Usage: udevadm control COMMAND\n" + " --exit instruct the daemon to cleanup and exit\n" + " --log-priority=<level> set the udev log level for the daemon\n" + " --stop-exec-queue do not execute events, queue only\n" + " --start-exec-queue execute events, flush queue\n" + " --reload reload rules and databases\n" + " --property=<KEY>=<value> set a global property for all events\n" + " --children-max=<N> maximum number of children\n" + " --timeout=<seconds> maximum time to block for a reply\n" + " --help print this help text\n\n"); +} + +static int adm_control(struct udev *udev, int argc, char *argv[]) +{ + struct udev_ctrl *uctrl = NULL; + int timeout = 60; + int rc = 1; + + 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' }, + { "property", required_argument, NULL, 'p' }, + { "env", required_argument, NULL, '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; + + for (;;) { + int option; + + option = getopt_long(argc, argv, "el:sSRp:m:h", options, NULL); + if (option == -1) + break; + + switch (option) { + 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); + goto out; + } + 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); + goto out; + } + 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); + goto out; + } + 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 (argv[optind] != NULL) + fprintf(stderr, "unknown option\n"); + else if (optind == 1) + fprintf(stderr, "missing option\n"); +out: + udev_ctrl_unref(uctrl); + return rc; +} + +const struct udevadm_cmd udevadm_control = { + .name = "control", + .cmd = adm_control, + .help = "control the udev daemon", +}; diff --git a/src/udev/udevadm-hwdb.c b/src/udev/udevadm-hwdb.c new file mode 100644 index 0000000000..02d8d01f8a --- /dev/null +++ b/src/udev/udevadm-hwdb.c @@ -0,0 +1,601 @@ +/*** + 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 <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <string.h> + +#include "util.h" +#include "strbuf.h" +#include "conf-files.h" + +#include "udev.h" +#include "libudev-hwdb-def.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; + int err = 0; + + /* extend array, add new entry, sort for bisection */ + child = realloc(node->children, (node->children_count + 1) * sizeof(struct trie_child_entry)); + if (!child) { + err = -ENOMEM; + goto out; + } + 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++; +out: + return err; +} + +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++) { + char *s; + ssize_t off; + + if (c == search[i + p]) + continue; + + /* split node */ + child = calloc(sizeof(struct trie_node), 1); + if (!child) { + err = -ENOMEM; + goto out; + } + + /* move values from parent to child */ + child->prefix_off = node->prefix_off + p+1; + child->children = node->children; + child->children_count = node->children_count; + child->values = node->values; + 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) { + err = -ENOMEM; + goto out; + } + off = strbuf_add_string(trie->strings, s, p); + free(s); + if (off < 0) { + err = off; + goto out; + } + node->prefix_off = off; + node->children = NULL; + node->children_count = 0; + node->values = NULL; + node->values_count = 0; + err = node_add_child(trie, node, child, c); + if (err) + goto out; + 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 = calloc(sizeof(struct trie_node), 1); + if (!child) { + err = -ENOMEM; + goto out; + } + off = strbuf_add_string(trie->strings, search + i+1, strlen(search + i+1)); + if (off < 0) { + err = off; + goto out; + } + child->prefix_off = off; + err = node_add_child(trie, node, child, c); + if (err) + goto out; + return trie_node_add_value(trie, child, key, value); + } + + node = child; + i++; + } +out: + return err; +} + +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) + 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, + }; + char *filename_tmp; + 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 */ + fseeko(t.f, sizeof(struct trie_header_f), SEEK_SET); + 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); + fseeko(t.f, 0, SEEK_SET); + 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(filename_tmp); + goto out; + } + + log_debug("=== trie on-disk ===\n"); + log_debug("size: %8llu bytes\n", (unsigned long long)size); + log_debug("header: %8zu bytes\n", sizeof(struct trie_header_f)); + log_debug("nodes: %8llu bytes (%8llu)\n", + (unsigned long long)t.nodes_count * sizeof(struct trie_node_f), (unsigned long long)t.nodes_count); + log_debug("child pointers: %8llu bytes (%8llu)\n", + (unsigned long long)t.children_count * sizeof(struct trie_child_entry_f), (unsigned long long)t.children_count); + log_debug("value pointers: %8llu bytes (%8llu)\n", + (unsigned long long)t.values_count * sizeof(struct trie_value_entry_f), (unsigned long long)t.values_count); + log_debug("string store: %8llu bytes\n", (unsigned long long)trie->strings->len); + log_debug("strings start: %8llu\n", (unsigned long long) t.strings_off); +out: + free(filename_tmp); + return err; +} + +static int import_file(struct trie *trie, const char *filename) { + FILE *f; + char line[LINE_MAX]; + char match[LINE_MAX]; + char cond[LINE_MAX]; + + f = fopen(filename, "re"); + if (f == NULL) + return -errno; + + match[0] = '\0'; + cond[0] = '\0'; + while (fgets(line, sizeof(line), f)) { + size_t len; + + if (line[0] == '#') + continue; + + /* new line, new record */ + if (line[0] == '\n') { + match[0] = '\0'; + cond[0] = '\0'; + continue; + } + + /* remove newline */ + len = strlen(line); + if (len < 2) + continue; + line[len-1] = '\0'; + + /* start of new record */ + if (match[0] == '\0') { + strcpy(match, line); + cond[0] = '\0'; + continue; + } + + if (line[0] == '+') { + strcpy(cond, line); + continue; + } + + /* TODO: support +; skip the entire record until we support it */ + if (cond[0] != '\0') + continue; + + /* value lines */ + if (line[0] == ' ') { + char *value; + + value = strchr(line, '='); + if (!value) + continue; + value[0] = '\0'; + value++; + trie_insert(trie, trie->root, match, line, value); + } + } + fclose(f); + return 0; +} + +static void help(void) { + printf("Usage: udevadm hwdb [--create] [--help]\n" + " --update update the hardware database\n" + " --test <modalias> query database and print result\n" + " --help\n\n"); +} + +static int adm_hwdb(struct udev *udev, int argc, char *argv[]) { + static const struct option options[] = { + { "update", no_argument, NULL, 'u' }, + { "test", required_argument, NULL, 't' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + const char *test = NULL; + bool update = false; + struct trie *trie = NULL; + int err; + int rc = EXIT_SUCCESS; + + for (;;) { + int option; + + option = getopt_long(argc, argv, "ut:h", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'u': + update = true; + break; + case 't': + test = optarg; + break; + case 'h': + help(); + return EXIT_SUCCESS; + } + } + + if (!update && !test) { + help(); + return EXIT_SUCCESS; + } + + if (update) { + char **files, **f; + + trie = calloc(sizeof(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 = calloc(sizeof(struct trie_node), 1); + if (!trie->root) { + rc = EXIT_FAILURE; + goto out; + } + trie->nodes_count++; + + err = conf_files_list_strv(&files, ".hwdb", (const char **)conf_file_dirs); + if (err < 0) { + log_error("failed to enumerate hwdb files: %s\n", strerror(-err)); + rc = EXIT_FAILURE; + goto out; + } + 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 ===\n"); + log_debug("nodes: %8zu bytes (%8zu)\n", + trie->nodes_count * sizeof(struct trie_node), trie->nodes_count); + log_debug("children arrays: %8zu bytes (%8zu)\n", + trie->children_count * sizeof(struct trie_child_entry), trie->children_count); + log_debug("values arrays: %8zu bytes (%8zu)\n", + trie->values_count * sizeof(struct trie_value_entry), trie->values_count); + log_debug("strings: %8zu bytes\n", + trie->strings->len); + log_debug("strings incoming: %8zu bytes (%8zu)\n", + trie->strings->in_len, trie->strings->in_count); + log_debug("strings dedup'ed: %8zu bytes (%8zu)\n", + trie->strings->dedup_len, trie->strings->dedup_count); + + mkdir_parents("/etc/udev/hwdb.bin", 0755); + err = trie_store(trie, "/etc/udev/hwdb.bin"); + if (err < 0) { + log_error("Failure writing hardware database '%s': %s", "/etc/udev/hwdb.bin", strerror(-err)); + rc = EXIT_FAILURE; + } + } + + if (test) { + struct udev_hwdb *hwdb = udev_hwdb_new(udev); + + if (hwdb) { + struct udev_list_entry *entry; + + udev_list_entry_foreach(entry, udev_hwdb_get_properties_list_entry(hwdb, test, 0)) + printf("%s=%s\n", udev_list_entry_get_name(entry), udev_list_entry_get_value(entry)); + hwdb = udev_hwdb_unref(hwdb); + } + } +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, + .help = "maintain the hardware database index", +}; diff --git a/src/udev/udevadm-info.c b/src/udev/udevadm-info.c new file mode 100644 index 0000000000..95f077ca95 --- /dev/null +++ b/src/udev/udevadm-info.c @@ -0,0 +1,550 @@ +/* + * 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 <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <stddef.h> +#include <ctype.h> +#include <stdarg.h> +#include <unistd.h> +#include <dirent.h> +#include <errno.h> +#include <getopt.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "udev.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 (strcmp(name, skip[i]) == 0) + 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=%d\n" + "%sMINOR=%d\n", + prefix, major(statbuf.st_dev), + prefix, minor(statbuf.st_dev)); + } else + printf("%d:%d\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)) { + 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); + closedir(dir2); + } + unlinkat(dirfd(dir), dent->d_name, AT_REMOVEDIR); + } else { + 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/watch"); + if (dir != NULL) { + cleanup_dir(dir, 0, 1); + closedir(dir); + } + + dir = opendir("/run/udev/firmware-missing"); + if (dir != NULL) { + cleanup_dir(dir, 0, 1); + closedir(dir); + } +} + +static struct udev_device *find_device(struct udev *udev, const char *id, const char *prefix) +{ + char name[UTIL_PATH_SIZE]; + + if (prefix && !startswith(id, prefix)) { + util_strscpyl(name, sizeof(name), prefix, id, NULL); + id = name; + } + + 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; +} + +static int uinfo(struct udev *udev, int argc, char *argv[]) +{ + 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 rc = 0; + + 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' }, + {} + }; + + static const char *usage = + "Usage: udevadm info OPTIONS\n" + " --query=<type> query device information:\n" + " name name of device node\n" + " symlink pointing to node\n" + " path sys device path\n" + " property the device properties\n" + " all all values\n" + " --path=<syspath> sys device path used for query or attribute walk\n" + " --name=<name> node or symlink name used for query or attribute walk\n" + " --root prepend dev directory to path names\n" + " --attribute-walk print all key matches while walking along the chain\n" + " of parent devices\n" + " --device-id-of-file=<file> print major:minor of device containing this file\n" + " --export export key/value pairs\n" + " --export-prefix export the key name with a prefix\n" + " --export-db export the content of the udev database\n" + " --cleanup-db cleanup the udev database\n" + " --help\n"; + + 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; + + for (;;) { + int option; + + option = getopt_long(argc, argv, "aced:n:p:q:rxP:RVh", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'n': { + if (device != NULL) { + fprintf(stderr, "device already specified\n"); + rc = 2; + goto exit; + } + + device = find_device(udev, optarg, "/dev/"); + if (device == NULL) { + fprintf(stderr, "device node not found\n"); + rc = 2; + goto exit; + } + break; + } + case 'p': + if (device != NULL) { + fprintf(stderr, "device already specified\n"); + rc = 2; + goto exit; + } + + device = find_device(udev, optarg, "/sys"); + if (device == NULL) { + fprintf(stderr, "syspath not found\n"); + rc = 2; + goto exit; + } + break; + case 'q': + action = ACTION_QUERY; + if (strcmp(optarg, "property") == 0 || strcmp(optarg, "env") == 0) { + query = QUERY_PROPERTY; + } else if (strcmp(optarg, "name") == 0) { + query = QUERY_NAME; + } else if (strcmp(optarg, "symlink") == 0) { + query = QUERY_SYMLINK; + } else if (strcmp(optarg, "path") == 0) { + query = QUERY_PATH; + } else if (strcmp(optarg, "all") == 0) { + query = QUERY_ALL; + } else { + fprintf(stderr, "unknown query type\n"); + rc = 3; + goto exit; + } + break; + case 'r': + root = true; + break; + case 'd': + action = ACTION_DEVICE_ID_FILE; + util_strscpy(name, sizeof(name), optarg); + break; + case 'a': + action = ACTION_ATTRIBUTE_WALK; + break; + case 'e': + export_devices(udev); + goto exit; + case 'c': + cleanup_db(udev); + goto exit; + case 'x': + export = true; + break; + case 'P': + export_prefix = optarg; + break; + case 'V': + printf("%s\n", VERSION); + goto exit; + case 'h': + printf("%s\n", usage); + goto exit; + default: + rc = 1; + goto exit; + } + } + + switch (action) { + case ACTION_QUERY: + if (!device) { + if (!argv[optind]) { + fprintf(stderr, "%s\n", usage); + rc = 2; + goto exit; + } + device = find_device(udev, argv[optind], NULL); + if (!device) { + fprintf(stderr, "Unknown device, --name=, --path=, or absolute path in /dev/ or /sys expected.\n"); + rc = 4; + goto exit; + } + } + + switch(query) { + case QUERY_NAME: { + const char *node = udev_device_get_devnode(device); + + if (node == NULL) { + fprintf(stderr, "no device node found\n"); + rc = 5; + goto exit; + } + + 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)); + goto exit; + 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: + fprintf(stderr, "unknown query type\n"); + break; + } + 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"); + rc = 4; + goto exit; + } + } + if (!device) { + fprintf(stderr, "Unknown device, --name=, --path=, or absolute path in /dev/ or /sys expected.\n"); + rc = 4; + goto exit; + } + print_device_chain(device); + break; + case ACTION_DEVICE_ID_FILE: + if (stat_device(name, export, export_prefix) != 0) + rc = 1; + break; + } + +exit: + udev_device_unref(device); + return rc; +} + +const struct udevadm_cmd udevadm_info = { + .name = "info", + .cmd = uinfo, + .help = "query sysfs or the udev database", +}; diff --git a/src/udev/udevadm-monitor.c b/src/udev/udevadm-monitor.c new file mode 100644 index 0000000000..ffa70d8300 --- /dev/null +++ b/src/udev/udevadm-monitor.c @@ -0,0 +1,297 @@ +/* + * 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 <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <signal.h> +#include <getopt.h> +#include <time.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/epoll.h> +#include <linux/types.h> +#include <linux/netlink.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; + + clock_gettime(CLOCK_MONOTONIC, &ts); + printf("%-6s[%llu.%06u] %-8s %s (%s)\n", + source, + (unsigned long long) ts.tv_sec, (unsigned int) 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 int adm_monitor(struct udev *udev, int argc, char *argv[]) +{ + struct sigaction act; + sigset_t mask; + int option; + bool prop = false; + bool print_kernel = false; + bool print_udev = false; + struct udev_list subsystem_match_list; + struct udev_list tag_match_list; + struct udev_monitor *udev_monitor = NULL; + struct udev_monitor *kernel_monitor = NULL; + int fd_ep = -1; + int fd_kernel = -1, fd_udev = -1; + struct epoll_event ep_kernel, ep_udev; + int rc = 0; + + static const struct option options[] = { + { "property", no_argument, NULL, 'p' }, + { "environment", no_argument, NULL, 'e' }, + { "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); + + for (;;) { + option = getopt_long(argc, argv, "pekus:t:h", options, NULL); + if (option == -1) + break; + + switch (option) { + 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; + + util_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': + printf("Usage: udevadm monitor [--property] [--kernel] [--udev] [--help]\n" + " --property print the event properties\n" + " --kernel print kernel uevents\n" + " --udev print udev events\n" + " --subsystem-match=<subsystem[/devtype]> filter events by subsystem\n" + " --tag-match=<tag> filter events by tag\n" + " --help\n\n"); + goto out; + default: + rc = 1; + goto out; + } + } + + if (!print_kernel && !print_udev) { + print_kernel = true; + print_udev = true; + } + + /* set signal handlers */ + memset(&act, 0x00, sizeof(struct sigaction)); + act.sa_handler = sig_handler; + sigemptyset(&act.sa_mask); + 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("error creating epoll fd: %m\n"); + goto out; + } + + 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"); + rc = 1; + goto out; + } + 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"); + rc = 2; + goto out; + } + + memset(&ep_udev, 0, 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("fail to add fd to epoll: %m\n"); + goto out; + } + + 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"); + rc = 3; + goto out; + } + 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"); + rc = 4; + goto out; + } + + memset(&ep_kernel, 0, 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("fail to add fd to epoll: %m\n"); + goto out; + } + + 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); + } + } + } +out: + if (fd_ep >= 0) + close(fd_ep); + udev_monitor_unref(udev_monitor); + udev_monitor_unref(kernel_monitor); + udev_list_cleanup(&subsystem_match_list); + udev_list_cleanup(&tag_match_list); + return rc; +} + +const struct udevadm_cmd udevadm_monitor = { + .name = "monitor", + .cmd = adm_monitor, + .help = "listen to kernel and udev events", +}; diff --git a/src/udev/udevadm-settle.c b/src/udev/udevadm-settle.c new file mode 100644 index 0000000000..c4fc4ee4e5 --- /dev/null +++ b/src/udev/udevadm-settle.c @@ -0,0 +1,232 @@ +/* + * 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 <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <errno.h> +#include <dirent.h> +#include <fcntl.h> +#include <syslog.h> +#include <getopt.h> +#include <signal.h> +#include <time.h> +#include <sys/inotify.h> +#include <sys/poll.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "udev.h" + +static int adm_settle(struct udev *udev, int argc, char *argv[]) +{ + static const struct option options[] = { + { "seq-start", required_argument, NULL, 's' }, + { "seq-end", required_argument, NULL, 'e' }, + { "timeout", required_argument, NULL, 't' }, + { "exit-if-exists", required_argument, NULL, 'E' }, + { "quiet", no_argument, NULL, 'q' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + usec_t start_usec = now(CLOCK_MONOTONIC); + usec_t start = 0; + usec_t end = 0; + int quiet = 0; + const char *exists = NULL; + unsigned int timeout = 120; + struct pollfd pfd[1] = { {.fd = -1}, }; + struct udev_queue *udev_queue = NULL; + int rc = EXIT_FAILURE; + + for (;;) { + int option; + int seconds; + + option = getopt_long(argc, argv, "s:e:t:E:qh", options, NULL); + if (option == -1) + break; + + switch (option) { + case 's': + start = strtoull(optarg, NULL, 0); + break; + case 'e': + end = strtoull(optarg, NULL, 0); + break; + case 't': + seconds = atoi(optarg); + if (seconds >= 0) + timeout = seconds; + else + fprintf(stderr, "invalid timeout value\n"); + break; + case 'q': + quiet = 1; + break; + case 'E': + exists = optarg; + break; + case 'h': + printf("Usage: udevadm settle OPTIONS\n" + " --timeout=<seconds> maximum time to wait for events\n" + " --seq-start=<seqnum> first seqnum to wait for\n" + " --seq-end=<seqnum> last seqnum to wait for\n" + " --exit-if-exists=<file> stop waiting if file exists\n" + " --quiet do not print list after timeout\n" + " --help\n\n"); + exit(EXIT_SUCCESS); + default: + exit(EXIT_FAILURE); + } + } + + udev_queue = udev_queue_new(udev); + if (udev_queue == NULL) + exit(2); + + if (start > 0) { + unsigned long long kernel_seq; + + kernel_seq = udev_queue_get_kernel_seqnum(udev_queue); + + /* unless specified, the last event is the current kernel seqnum */ + if (end == 0) + end = udev_queue_get_kernel_seqnum(udev_queue); + + if (start > end) { + log_error("seq-start larger than seq-end, ignoring\n"); + start = 0; + end = 0; + } + + if (start > kernel_seq || end > kernel_seq) { + log_error("seq-start or seq-end larger than current kernel value, ignoring\n"); + start = 0; + end = 0; + } + log_debug("start=%llu end=%llu current=%llu\n", (unsigned long long)start, (unsigned long long)end, kernel_seq); + } else { + if (end > 0) { + log_error("seq-end needs seq-start parameter, ignoring\n"); + end = 0; + } + } + + /* 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, timeout) < 0) { + log_debug("no connection to daemon\n"); + udev_ctrl_unref(uctrl); + rc = EXIT_SUCCESS; + goto out; + } + udev_ctrl_unref(uctrl); + } + } + + pfd[0].events = POLLIN; + pfd[0].fd = inotify_init1(IN_CLOEXEC); + if (pfd[0].fd < 0) { + log_error("inotify_init failed: %m\n"); + } else { + if (inotify_add_watch(pfd[0].fd, "/run/udev" , IN_MOVED_TO) < 0) { + log_error("watching /run/udev failed\n"); + close(pfd[0].fd); + pfd[0].fd = -1; + } + } + + for (;;) { + struct stat statbuf; + + if (exists != NULL && stat(exists, &statbuf) == 0) { + rc = EXIT_SUCCESS; + break; + } + + if (start > 0) { + /* if asked for, wait for a specific sequence of events */ + if (udev_queue_get_seqnum_sequence_is_finished(udev_queue, start, end) == 1) { + rc = EXIT_SUCCESS; + break; + } + } else { + /* exit if queue is empty */ + if (udev_queue_get_queue_is_empty(udev_queue)) { + rc = EXIT_SUCCESS; + break; + } + } + + if (pfd[0].fd >= 0) { + int delay; + + if (exists != NULL || start > 0) + delay = 100; + else + delay = 1000; + /* wake up after delay, or immediately after the queue is rebuilt */ + if (poll(pfd, 1, delay) > 0 && pfd[0].revents & POLLIN) { + char buf[sizeof(struct inotify_event) + PATH_MAX]; + + read(pfd[0].fd, buf, sizeof(buf)); + } + } else { + sleep(1); + } + + if (timeout > 0) { + usec_t age_usec; + + age_usec = now(CLOCK_MONOTONIC) - start_usec; + if (age_usec / (1000 * 1000) >= timeout) { + struct udev_list_entry *list_entry; + + if (!quiet && udev_queue_get_queued_list_entry(udev_queue) != NULL) { + log_debug("timeout waiting for udev queue\n"); + printf("\nudevadm settle - timeout of %i seconds reached, the event queue contains:\n", timeout); + udev_list_entry_foreach(list_entry, udev_queue_get_queued_list_entry(udev_queue)) + printf(" %s (%s)\n", + udev_list_entry_get_name(list_entry), + udev_list_entry_get_value(list_entry)); + } + + break; + } + } + } +out: + if (pfd[0].fd >= 0) + close(pfd[0].fd); + udev_queue_unref(udev_queue); + return rc; +} + +const struct udevadm_cmd udevadm_settle = { + .name = "settle", + .cmd = adm_settle, + .help = "wait for the event queue to finish", +}; diff --git a/src/udev/udevadm-test-builtin.c b/src/udev/udevadm-test-builtin.c new file mode 100644 index 0000000000..9853d83b49 --- /dev/null +++ b/src/udev/udevadm-test-builtin.c @@ -0,0 +1,127 @@ +/* + * 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 <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <errno.h> +#include <dirent.h> +#include <fcntl.h> +#include <syslog.h> +#include <getopt.h> +#include <signal.h> +#include <time.h> +#include <sys/inotify.h> +#include <sys/poll.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "udev.h" + +static void help(struct udev *udev) +{ + fprintf(stderr, "\n"); + fprintf(stderr, "Usage: udevadm builtin [--help] <command> <syspath>\n"); + udev_builtin_list(udev); + fprintf(stderr, "\n"); +} + +static int adm_builtin(struct udev *udev, int argc, char *argv[]) +{ + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + {} + }; + char *command = NULL; + char *syspath = NULL; + char filename[UTIL_PATH_SIZE]; + struct udev_device *dev = NULL; + enum udev_builtin_cmd cmd; + int rc = EXIT_SUCCESS; + + for (;;) { + int option; + + option = getopt_long(argc, argv, "h", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'h': + help(udev); + goto out; + } + } + + command = argv[optind++]; + if (command == NULL) { + fprintf(stderr, "command missing\n"); + help(udev); + rc = 2; + goto out; + } + + syspath = argv[optind++]; + if (syspath == NULL) { + fprintf(stderr, "syspath missing\n\n"); + rc = 3; + goto out; + } + + 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")) + util_strscpyl(filename, sizeof(filename), "/sys", syspath, NULL); + else + util_strscpy(filename, sizeof(filename), syspath); + util_remove_trailing_chars(filename, '/'); + + dev = udev_device_new_from_syspath(udev, filename); + if (dev == NULL) { + fprintf(stderr, "unable to open device '%s'\n\n", filename); + rc = 4; + goto out; + } + + 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/udev/udevadm-test.c b/src/udev/udevadm-test.c new file mode 100644 index 0000000000..2d8aa7913e --- /dev/null +++ b/src/udev/udevadm-test.c @@ -0,0 +1,172 @@ +/* + * 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 <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <stddef.h> +#include <unistd.h> +#include <errno.h> +#include <ctype.h> +#include <fcntl.h> +#include <signal.h> +#include <syslog.h> +#include <getopt.h> +#include <sys/signalfd.h> + +#include "udev.h" + +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_event *event = NULL; + struct udev_device *dev = NULL; + struct udev_rules *rules = NULL; + struct udev_list_entry *entry; + sigset_t mask, sigmask_orig; + int err; + int rc = 0; + + 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\n", VERSION); + + for (;;) { + int option; + + option = getopt_long(argc, argv, "a:s:N:fh", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'a': + action = optarg; + break; + case 'N': + if (strcmp (optarg, "early") == 0) { + resolve_names = 1; + } else if (strcmp (optarg, "late") == 0) { + resolve_names = 0; + } else if (strcmp (optarg, "never") == 0) { + 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\n"); + exit(EXIT_FAILURE); + } + break; + case 'h': + printf("Usage: udevadm test OPTIONS <syspath>\n" + " --action=<string> set action string\n" + " --help\n\n"); + exit(EXIT_SUCCESS); + default: + exit(EXIT_FAILURE); + } + } + 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")) + util_strscpyl(filename, sizeof(filename), "/sys", syspath, NULL); + else + util_strscpy(filename, sizeof(filename), syspath); + util_remove_trailing_chars(filename, '/'); + + dev = udev_device_new_from_syspath(udev, filename); + if (dev == NULL) { + fprintf(stderr, "unable to open device '%s'\n", filename); + rc = 4; + goto out; + } + + /* skip reading of db, but read kernel parameters */ + udev_device_set_info_loaded(dev); + udev_device_read_uevent_file(dev); + + udev_device_set_action(dev, action); + event = udev_event_new(dev); + + sigfillset(&mask); + sigprocmask(SIG_SETMASK, &mask, &sigmask_orig); + event->fd_signal = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); + if (event->fd_signal < 0) { + fprintf(stderr, "error creating signalfd\n"); + rc = 5; + goto out; + } + + err = udev_event_execute_rules(event, rules, &sigmask_orig); + + 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)); + + if (err == 0) { + 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: + if (event != NULL && event->fd_signal >= 0) + close(event->fd_signal); + udev_event_unref(event); + udev_device_unref(dev); + udev_rules_unref(rules); + 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/udev/udevadm-trigger.c b/src/udev/udevadm-trigger.c new file mode 100644 index 0000000000..d52ae461fe --- /dev/null +++ b/src/udev/udevadm-trigger.c @@ -0,0 +1,228 @@ +/* + * 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 <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <getopt.h> +#include <errno.h> +#include <dirent.h> +#include <fcntl.h> +#include <syslog.h> +#include <fnmatch.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include "udev.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; + util_strscpyl(filename, sizeof(filename), udev_list_entry_get_name(entry), "/uevent", NULL); + fd = open(filename, O_WRONLY); + if (fd < 0) + continue; + if (write(fd, action, strlen(action)) < 0) + log_debug("error writing '%s' to '%s': %m\n", action, filename); + close(fd); + } +} + +static const char *keyval(const char *str, const char **val, char *buf, size_t size) +{ + char *pos; + + util_strscpy(buf, size,str); + pos = strchr(buf, '='); + if (pos != NULL) { + pos[0] = 0; + pos++; + } + *val = pos; + return buf; +} + +static int adm_trigger(struct udev *udev, int argc, char *argv[]) +{ + 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' }, + { "parent-match", required_argument, NULL, 'b' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + enum { + TYPE_DEVICES, + TYPE_SUBSYSTEMS, + } device_type = TYPE_DEVICES; + const char *action = "change"; + struct udev_enumerate *udev_enumerate; + int rc = 0; + + udev_enumerate = udev_enumerate_new(udev); + if (udev_enumerate == NULL) { + rc = 1; + goto exit; + } + + for (;;) { + int option; + const char *key; + const char *val; + char buf[UTIL_PATH_SIZE]; + + option = getopt_long(argc, argv, "vng:o:t:hc:p:s:S:a:A:y:b:", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'v': + verbose = 1; + break; + case 'n': + dry_run = 1; + break; + case 't': + if (strcmp(optarg, "devices") == 0) { + device_type = TYPE_DEVICES; + } else if (strcmp(optarg, "subsystems") == 0) { + device_type = TYPE_SUBSYSTEMS; + } else { + log_error("unknown type --type=%s\n", optarg); + rc = 2; + goto exit; + } + break; + case 'c': + action = optarg; + break; + case 's': + udev_enumerate_add_match_subsystem(udev_enumerate, optarg); + break; + case 'S': + udev_enumerate_add_nomatch_subsystem(udev_enumerate, optarg); + break; + case 'a': + key = keyval(optarg, &val, buf, sizeof(buf)); + udev_enumerate_add_match_sysattr(udev_enumerate, key, val); + break; + case 'A': + key = keyval(optarg, &val, buf, sizeof(buf)); + udev_enumerate_add_nomatch_sysattr(udev_enumerate, key, val); + break; + case 'p': + key = keyval(optarg, &val, buf, sizeof(buf)); + udev_enumerate_add_match_property(udev_enumerate, key, val); + break; + case 'g': + udev_enumerate_add_match_tag(udev_enumerate, optarg); + break; + case 'y': + udev_enumerate_add_match_sysname(udev_enumerate, optarg); + break; + case 'b': { + char path[UTIL_PATH_SIZE]; + struct udev_device *dev; + + /* add sys dir if needed */ + if (!startswith(optarg, "/sys")) + util_strscpyl(path, sizeof(path), "/sys", optarg, NULL); + else + util_strscpy(path, sizeof(path), optarg); + util_remove_trailing_chars(path, '/'); + dev = udev_device_new_from_syspath(udev, path); + if (dev == NULL) { + log_error("unable to open the device '%s'\n", optarg); + rc = 2; + goto exit; + } + udev_enumerate_add_match_parent(udev_enumerate, dev); + /* drop reference immediately, enumerate pins the device as long as needed */ + udev_device_unref(dev); + break; + } + case 'h': + printf("Usage: udevadm trigger OPTIONS\n" + " --verbose print the list of devices while running\n" + " --dry-run do not actually trigger the events\n" + " --type= type of events to trigger\n" + " devices sys devices (default)\n" + " subsystems sys subsystems and drivers\n" + " --action=<action> event action value, default is \"change\"\n" + " --subsystem-match=<subsystem> trigger devices from a matching subsystem\n" + " --subsystem-nomatch=<subsystem> exclude devices from a matching subsystem\n" + " --attr-match=<file[=<value>]> trigger devices with a matching attribute\n" + " --attr-nomatch=<file[=<value>]> exclude devices with a matching attribute\n" + " --property-match=<key>=<value> trigger devices with a matching property\n" + " --tag-match=<key>=<value> trigger devices with a matching property\n" + " --sysname-match=<name> trigger devices with a matching name\n" + " --parent-match=<name> trigger devices with that parent device\n" + " --help\n\n"); + goto exit; + default: + rc = 1; + goto exit; + } + } + + switch (device_type) { + case TYPE_SUBSYSTEMS: + udev_enumerate_scan_subsystems(udev_enumerate); + exec_list(udev_enumerate, action); + goto exit; + case TYPE_DEVICES: + udev_enumerate_scan_devices(udev_enumerate); + exec_list(udev_enumerate, action); + goto exit; + default: + goto exit; + } +exit: + udev_enumerate_unref(udev_enumerate); + return rc; +} + +const struct udevadm_cmd udevadm_trigger = { + .name = "trigger", + .cmd = adm_trigger, + .help = "request events from the kernel", +}; diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c new file mode 100644 index 0000000000..53419ffa62 --- /dev/null +++ b/src/udev/udevadm.c @@ -0,0 +1,152 @@ +/* + * 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 <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> + +#include "udev.h" + +void udev_main_log(struct udev *udev, int priority, + const char *file, int line, const char *fn, + const char *format, va_list args) +{ + log_metav(priority, file, line, fn, format, args); +} + +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; + + fprintf(stderr, "Usage: udevadm [--help] [--version] [--debug] COMMAND [COMMAND OPTIONS]\n"); + 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); + fprintf(stderr, "\n"); + 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\n", 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; + + udev = udev_new(); + if (udev == NULL) + goto out; + + log_parse_environment(); + log_open(); + udev_set_log_fn(udev, udev_main_log); + label_init("/dev"); + + for (;;) { + int option; + + option = getopt_long(argc, argv, "+dhV", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'd': + log_set_max_level(LOG_DEBUG); + udev_set_log_priority(udev, 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 (strcmp(udevadm_cmds[i]->name, command) == 0) { + 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, "missing or unknown command\n\n"); + adm_help(udev, argc, argv); + rc = 2; +out: + label_finish(); + udev_unref(udev); + log_close(); + return rc; +} diff --git a/src/udev/udevd.c b/src/udev/udevd.c new file mode 100644 index 0000000000..ebd601e262 --- /dev/null +++ b/src/udev/udevd.c @@ -0,0 +1,1578 @@ +/* + * 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 <stddef.h> +#include <signal.h> +#include <unistd.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <ctype.h> +#include <fcntl.h> +#include <time.h> +#include <getopt.h> +#include <dirent.h> +#include <sys/time.h> +#include <sys/prctl.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/signalfd.h> +#include <sys/epoll.h> +#include <sys/poll.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/inotify.h> +#include <sys/utsname.h> + +#include "udev.h" +#include "sd-daemon.h" +#include "cgroup-util.h" +#include "dev-setup.h" + +static bool debug; + +void udev_main_log(struct udev *udev, int priority, + const char *file, int line, const char *fn, + const char *format, va_list args) +{ + log_metav(priority, file, line, fn, format, args); +} + +static struct udev_rules *rules; +static struct udev_queue_export *udev_queue_export; +static struct udev_ctrl *udev_ctrl; +static struct udev_monitor *monitor; +static int worker_watch[2] = { -1, -1 }; +static int fd_signal = -1; +static int fd_ep = -1; +static int fd_inotify = -1; +static bool stop_exec_queue; +static bool reload; +static int children; +static int children_max; +static int exec_delay; +static sigset_t sigmask_orig; +static UDEV_LIST(event_list); +static UDEV_LIST(worker_list); +char *udev_cgroup; +static bool udev_exit; + +enum event_state { + EVENT_UNDEF, + EVENT_QUEUED, + EVENT_RUNNING, +}; + +struct event { + struct udev_list_node node; + struct udev *udev; + struct udev_device *dev; + enum event_state state; + int exitcode; + 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; + bool nodelay; +}; + +static inline struct event *node_to_event(struct udev_list_node *node) +{ + return container_of(node, struct event, node); +} + +static void event_queue_cleanup(struct udev *udev, enum event_state type); + +enum worker_state { + WORKER_UNDEF, + WORKER_RUNNING, + WORKER_IDLE, + WORKER_KILLED, +}; + +struct worker { + struct udev_list_node node; + struct udev *udev; + int refcount; + pid_t pid; + struct udev_monitor *monitor; + enum worker_state state; + struct event *event; + usec_t event_start_usec; +}; + +/* passed from worker to main process */ +struct worker_message { + pid_t pid; + int exitcode; +}; + +static inline struct worker *node_to_worker(struct udev_list_node *node) +{ + return container_of(node, struct worker, node); +} + +static void event_queue_delete(struct event *event, bool export) +{ + udev_list_node_remove(&event->node); + + if (export) { + udev_queue_export_device_finished(udev_queue_export, event->dev); + log_debug("seq %llu done with %i\n", udev_device_get_seqnum(event->dev), event->exitcode); + } + udev_device_unref(event->dev); + free(event); +} + +static struct worker *worker_ref(struct worker *worker) +{ + worker->refcount++; + return worker; +} + +static void worker_cleanup(struct worker *worker) +{ + udev_list_node_remove(&worker->node); + udev_monitor_unref(worker->monitor); + children--; + free(worker); +} + +static void worker_unref(struct worker *worker) +{ + worker->refcount--; + if (worker->refcount > 0) + return; + log_debug("worker [%u] cleaned up\n", worker->pid); + worker_cleanup(worker); +} + +static void worker_list_cleanup(struct udev *udev) +{ + struct udev_list_node *loop, *tmp; + + udev_list_node_foreach_safe(loop, tmp, &worker_list) { + struct worker *worker = node_to_worker(loop); + + worker_cleanup(worker); + } +} + +static void worker_new(struct event *event) +{ + struct udev *udev = event->udev; + struct worker *worker; + struct udev_monitor *worker_monitor; + pid_t pid; + + /* 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, monitor); + udev_monitor_enable_receiving(worker_monitor); + + worker = calloc(1, sizeof(struct worker)); + if (worker == NULL) { + udev_monitor_unref(worker_monitor); + return; + } + /* worker + event reference */ + worker->refcount = 2; + worker->udev = udev; + + pid = fork(); + switch (pid) { + case 0: { + struct udev_device *dev = NULL; + int fd_monitor; + struct epoll_event ep_signal, ep_monitor; + sigset_t mask; + int rc = EXIT_SUCCESS; + + /* take initial device from queue */ + dev = event->dev; + event->dev = NULL; + + free(worker); + worker_list_cleanup(udev); + event_queue_cleanup(udev, EVENT_UNDEF); + udev_queue_export_unref(udev_queue_export); + udev_monitor_unref(monitor); + udev_ctrl_unref(udev_ctrl); + close(fd_signal); + close(fd_ep); + close(worker_watch[READ_END]); + + sigfillset(&mask); + fd_signal = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); + if (fd_signal < 0) { + log_error("error creating signalfd %m\n"); + rc = 2; + goto out; + } + + fd_ep = epoll_create1(EPOLL_CLOEXEC); + if (fd_ep < 0) { + log_error("error creating epoll fd: %m\n"); + rc = 3; + goto out; + } + + memset(&ep_signal, 0, sizeof(struct epoll_event)); + ep_signal.events = EPOLLIN; + ep_signal.data.fd = fd_signal; + + fd_monitor = udev_monitor_get_fd(worker_monitor); + memset(&ep_monitor, 0, sizeof(struct epoll_event)); + ep_monitor.events = EPOLLIN; + ep_monitor.data.fd = fd_monitor; + + 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) { + log_error("fail to add fds to epoll: %m\n"); + rc = 4; + goto out; + } + + /* request TERM signal if parent exits */ + prctl(PR_SET_PDEATHSIG, SIGTERM); + + /* reset OOM score, we only protect the main daemon */ + write_one_line_file("/proc/self/oom_score_adj", "0"); + + for (;;) { + struct udev_event *udev_event; + struct worker_message msg; + int err; + + log_debug("seq %llu running\n", udev_device_get_seqnum(dev)); + udev_event = udev_event_new(dev); + if (udev_event == NULL) { + rc = 5; + goto out; + } + + /* needed for SIGCHLD/SIGTERM in spawn() */ + udev_event->fd_signal = fd_signal; + + if (exec_delay > 0) + udev_event->exec_delay = exec_delay; + + /* apply rules, create node, symlinks */ + err = udev_event_execute_rules(udev_event, rules, &sigmask_orig); + + if (err == 0) + udev_event_execute_run(udev_event, &sigmask_orig); + + /* apply/restore inotify watch */ + if (err == 0 && udev_event->inotify_watch) { + udev_watch_begin(udev, dev); + udev_device_update_db(dev); + } + + /* send processed event back to libudev listeners */ + udev_monitor_send_device(worker_monitor, NULL, dev); + + /* send udevd the result of the event execution */ + memset(&msg, 0, sizeof(struct worker_message)); + if (err != 0) + msg.exitcode = err; + msg.pid = getpid(); + send(worker_watch[WRITE_END], &msg, sizeof(struct worker_message), 0); + + log_debug("seq %llu processed with %i\n", udev_device_get_seqnum(dev), err); + + udev_device_unref(dev); + dev = NULL; + + if (udev_event->sigterm) { + udev_event_unref(udev_event); + goto out; + } + + 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; + log_error("failed to poll: %m\n"); + 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); + if (fd_signal >= 0) + close(fd_signal); + if (fd_ep >= 0) + close(fd_ep); + close(fd_inotify); + close(worker_watch[WRITE_END]); + udev_rules_unref(rules); + udev_builtin_exit(udev); + udev_monitor_unref(worker_monitor); + udev_unref(udev); + log_close(); + exit(rc); + } + case -1: + udev_monitor_unref(worker_monitor); + event->state = EVENT_QUEUED; + free(worker); + log_error("fork of child failed: %m\n"); + break; + default: + /* close monitor, but keep address around */ + udev_monitor_disconnect(worker_monitor); + worker->monitor = worker_monitor; + worker->pid = pid; + worker->state = WORKER_RUNNING; + worker->event_start_usec = now(CLOCK_MONOTONIC); + worker->event = event; + event->state = EVENT_RUNNING; + udev_list_node_append(&worker->node, &worker_list); + children++; + log_debug("seq %llu forked new worker [%u]\n", udev_device_get_seqnum(event->dev), pid); + break; + } +} + +static void event_run(struct event *event) +{ + struct udev_list_node *loop; + + udev_list_node_foreach(loop, &worker_list) { + struct worker *worker = node_to_worker(loop); + ssize_t count; + + if (worker->state != WORKER_IDLE) + continue; + + count = udev_monitor_send_device(monitor, worker->monitor, event->dev); + if (count < 0) { + log_error("worker [%u] did not accept message %zi (%m), kill it\n", worker->pid, count); + kill(worker->pid, SIGKILL); + worker->state = WORKER_KILLED; + continue; + } + worker_ref(worker); + worker->event = event; + worker->state = WORKER_RUNNING; + worker->event_start_usec = now(CLOCK_MONOTONIC); + event->state = EVENT_RUNNING; + return; + } + + if (children >= children_max) { + if (children_max > 1) + log_debug("maximum number (%i) of children reached\n", children); + return; + } + + /* start new worker and pass initial device */ + worker_new(event); +} + +static int event_queue_insert(struct udev_device *dev) +{ + struct event *event; + + event = calloc(1, sizeof(struct event)); + if (event == NULL) + return -1; + + event->udev = udev_device_get_udev(dev); + event->dev = 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); + if (streq(udev_device_get_subsystem(dev), "firmware")) + event->nodelay = true; + + udev_queue_export_device_queued(udev_queue_export, dev); + log_debug("seq %llu queued, '%s' '%s'\n", udev_device_get_seqnum(dev), + udev_device_get_action(dev), udev_device_get_subsystem(dev)); + + event->state = EVENT_QUEUED; + udev_list_node_append(&event->node, &event_list); + return 0; +} + +static void worker_kill(struct udev *udev) +{ + struct udev_list_node *loop; + + udev_list_node_foreach(loop, &worker_list) { + struct worker *worker = node_to_worker(loop); + + 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(struct event *event) +{ + struct udev_list_node *loop; + size_t common; + + /* check if queue contains events we depend on */ + udev_list_node_foreach(loop, &event_list) { + 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 && strcmp(loop_event->devpath, event->devpath_old) == 0) { + 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; + } + + /* allow to bypass the dependency tracking */ + if (event->nodelay) + continue; + + /* 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 void event_queue_start(struct udev *udev) +{ + struct udev_list_node *loop; + + udev_list_node_foreach(loop, &event_list) { + 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(event)) + continue; + + event_run(event); + } +} + +static void event_queue_cleanup(struct udev *udev, enum event_state match_type) +{ + struct udev_list_node *loop, *tmp; + + udev_list_node_foreach_safe(loop, tmp, &event_list) { + struct event *event = node_to_event(loop); + + if (match_type != EVENT_UNDEF && match_type != event->state) + continue; + + event_queue_delete(event, false); + } +} + +static void worker_returned(int fd_worker) +{ + for (;;) { + struct worker_message msg; + ssize_t size; + struct udev_list_node *loop; + + size = recv(fd_worker, &msg, sizeof(struct worker_message), MSG_DONTWAIT); + if (size != sizeof(struct worker_message)) + break; + + /* lookup worker who sent the signal */ + udev_list_node_foreach(loop, &worker_list) { + struct worker *worker = node_to_worker(loop); + + if (worker->pid != msg.pid) + continue; + + /* worker returned */ + if (worker->event) { + worker->event->exitcode = msg.exitcode; + event_queue_delete(worker->event, true); + worker->event = NULL; + } + if (worker->state != WORKER_KILLED) + worker->state = WORKER_IDLE; + worker_unref(worker); + break; + } + } +} + +/* receive the udevd message from userspace */ +static struct udev_ctrl_connection *handle_ctrl_msg(struct udev_ctrl *uctrl) +{ + struct udev *udev = udev_ctrl_get_udev(uctrl); + struct udev_ctrl_connection *ctrl_conn; + struct udev_ctrl_msg *ctrl_msg = NULL; + const char *str; + int i; + + ctrl_conn = udev_ctrl_get_connection(uctrl); + if (ctrl_conn == NULL) + goto out; + + ctrl_msg = udev_ctrl_receive_msg(ctrl_conn); + if (ctrl_msg == NULL) + goto out; + + i = udev_ctrl_get_set_log_level(ctrl_msg); + if (i >= 0) { + log_debug("udevd message (SET_LOG_PRIORITY) received, log_priority=%i\n", i); + log_set_max_level(i); + udev_set_log_priority(udev, i); + worker_kill(udev); + } + + if (udev_ctrl_get_stop_exec_queue(ctrl_msg) > 0) { + log_debug("udevd message (STOP_EXEC_QUEUE) received\n"); + stop_exec_queue = true; + } + + if (udev_ctrl_get_start_exec_queue(ctrl_msg) > 0) { + log_debug("udevd message (START_EXEC_QUEUE) received\n"); + stop_exec_queue = false; + } + + if (udev_ctrl_get_reload(ctrl_msg) > 0) { + log_debug("udevd message (RELOAD) received\n"); + reload = true; + } + + str = udev_ctrl_get_set_env(ctrl_msg); + if (str != NULL) { + char *key; + + key = strdup(str); + if (key != NULL) { + 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'\n", key); + udev_add_property(udev, key, NULL); + } else { + log_debug("udevd message (ENV) received, set '%s=%s'\n", key, val); + udev_add_property(udev, key, val); + } + } else { + log_error("wrong key format '%s'\n", key); + } + free(key); + } + worker_kill(udev); + } + + i = udev_ctrl_get_set_children_max(ctrl_msg); + if (i >= 0) { + log_debug("udevd message (SET_MAX_CHILDREN) received, children_max=%i\n", i); + children_max = i; + } + + if (udev_ctrl_get_ping(ctrl_msg) > 0) + log_debug("udevd message (SYNC) received\n"); + + if (udev_ctrl_get_exit(ctrl_msg) > 0) { + log_debug("udevd message (EXIT) received\n"); + udev_exit = true; + /* keep reference to block the client until we exit */ + udev_ctrl_connection_ref(ctrl_conn); + } +out: + udev_ctrl_msg_unref(ctrl_msg); + return udev_ctrl_connection_unref(ctrl_conn); +} + +/* read inotify messages */ +static int handle_inotify(struct udev *udev) +{ + int nbytes, pos; + char *buf; + struct inotify_event *ev; + + if ((ioctl(fd_inotify, FIONREAD, &nbytes) < 0) || (nbytes <= 0)) + return 0; + + buf = malloc(nbytes); + if (buf == NULL) { + log_error("error getting buffer for inotify\n"); + return -1; + } + + nbytes = read(fd_inotify, buf, nbytes); + + for (pos = 0; pos < nbytes; pos += sizeof(struct inotify_event) + ev->len) { + struct udev_device *dev; + + ev = (struct inotify_event *)(buf + pos); + dev = udev_watch_lookup(udev, ev->wd); + if (dev != NULL) { + log_debug("inotify event: %x for %s\n", ev->mask, udev_device_get_devnode(dev)); + if (ev->mask & IN_CLOSE_WRITE) { + char filename[UTIL_PATH_SIZE]; + int fd; + + log_debug("device %s closed, synthesising 'change'\n", udev_device_get_devnode(dev)); + util_strscpyl(filename, sizeof(filename), udev_device_get_syspath(dev), "/uevent", NULL); + fd = open(filename, O_WRONLY); + if (fd >= 0) { + if (write(fd, "change", 6) < 0) + log_debug("error writing uevent: %m\n"); + close(fd); + } + } + if (ev->mask & IN_IGNORED) + udev_watch_end(udev, dev); + + udev_device_unref(dev); + } + + } + + free(buf); + return 0; +} + +static void handle_signal(struct udev *udev, int signo) +{ + switch (signo) { + case SIGINT: + case SIGTERM: + udev_exit = true; + break; + case SIGCHLD: + for (;;) { + pid_t pid; + int status; + struct udev_list_node *loop, *tmp; + + pid = waitpid(-1, &status, WNOHANG); + if (pid <= 0) + break; + + udev_list_node_foreach_safe(loop, tmp, &worker_list) { + struct worker *worker = node_to_worker(loop); + + if (worker->pid != pid) + continue; + log_debug("worker [%u] exit\n", pid); + + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) + log_error("worker [%u] exit with return code %i\n", pid, WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + log_error("worker [%u] terminated by signal %i (%s)\n", + pid, WTERMSIG(status), strsignal(WTERMSIG(status))); + } else if (WIFSTOPPED(status)) { + log_error("worker [%u] stopped\n", pid); + } else if (WIFCONTINUED(status)) { + log_error("worker [%u] continued\n", pid); + } else { + log_error("worker [%u] exit with status 0x%04x\n", pid, status); + } + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + if (worker->event) { + log_error("worker [%u] failed while handling '%s'\n", + pid, worker->event->devpath); + worker->event->exitcode = -32; + event_queue_delete(worker->event, true); + /* drop reference taken for state 'running' */ + worker_unref(worker); + } + } + worker_unref(worker); + break; + } + } + break; + case SIGHUP: + reload = true; + break; + } +} + +static void static_dev_create_from_modules(struct udev *udev) +{ + struct utsname kernel; + char modules[UTIL_PATH_SIZE]; + char buf[4096]; + FILE *f; + + uname(&kernel); + util_strscpyl(modules, sizeof(modules), ROOTPREFIX "/lib/modules/", kernel.release, "/modules.devname", NULL); + f = fopen(modules, "re"); + if (f == NULL) + return; + + while (fgets(buf, sizeof(buf), f) != NULL) { + char *s; + const char *modname; + const char *devname; + const char *devno; + int maj, min; + char type; + mode_t mode; + char filename[UTIL_PATH_SIZE]; + + if (buf[0] == '#') + continue; + + modname = buf; + s = strchr(modname, ' '); + if (s == NULL) + continue; + s[0] = '\0'; + + devname = &s[1]; + s = strchr(devname, ' '); + if (s == NULL) + continue; + s[0] = '\0'; + + devno = &s[1]; + s = strchr(devno, ' '); + if (s == NULL) + s = strchr(devno, '\n'); + if (s != NULL) + s[0] = '\0'; + if (sscanf(devno, "%c%u:%u", &type, &maj, &min) != 3) + continue; + + mode = 0600; + if (type == 'c') + mode |= S_IFCHR; + else if (type == 'b') + mode |= S_IFBLK; + else + continue; + + util_strscpyl(filename, sizeof(filename), "/dev/", devname, NULL); + mkdir_parents_label(filename, 0755); + label_context_set(filename, mode); + log_debug("mknod '%s' %c%u:%u\n", filename, type, maj, min); + if (mknod(filename, mode, makedev(maj, min)) < 0 && errno == EEXIST) + utimensat(AT_FDCWD, filename, NULL, 0); + label_context_clear(); + } + + fclose(f); +} + +static int mem_size_mb(void) +{ + FILE *f; + char buf[4096]; + long int memsize = -1; + + f = fopen("/proc/meminfo", "re"); + if (f == NULL) + return -1; + + while (fgets(buf, sizeof(buf), f) != NULL) { + long int value; + + if (sscanf(buf, "MemTotal: %ld kB", &value) == 1) { + memsize = value / 1024; + break; + } + } + + fclose(f); + return memsize; +} + +static int convert_db(struct udev *udev) +{ + char filename[UTIL_PATH_SIZE]; + struct udev_enumerate *udev_enumerate; + struct udev_list_entry *list_entry; + + /* current database */ + if (access("/run/udev/data", F_OK) >= 0) + return 0; + + /* make sure we do not get here again */ + mkdir_p("/run/udev/data", 0755); + + /* old database */ + util_strscpyl(filename, sizeof(filename), "/dev/.udev/db", NULL); + if (access(filename, F_OK) < 0) + return 0; + + print_kmsg("converting old udev database\n"); + + 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) + continue; + + /* try to find the old database for devices without a current one */ + if (udev_device_read_db(device, NULL) < 0) { + bool have_db; + const char *id; + struct stat stats; + char devpath[UTIL_PATH_SIZE]; + char from[UTIL_PATH_SIZE]; + + have_db = false; + + /* find database in old location */ + id = udev_device_get_id_filename(device); + util_strscpyl(from, sizeof(from), "/dev/.udev/db/", id, NULL); + if (lstat(from, &stats) == 0) { + if (!have_db) { + udev_device_read_db(device, from); + have_db = true; + } + unlink(from); + } + + /* find old database with $subsys:$sysname name */ + util_strscpyl(from, sizeof(from), "/dev/.udev/db/", + udev_device_get_subsystem(device), ":", udev_device_get_sysname(device), NULL); + if (lstat(from, &stats) == 0) { + if (!have_db) { + udev_device_read_db(device, from); + have_db = true; + } + unlink(from); + } + + /* find old database with the encoded devpath name */ + util_path_encode(udev_device_get_devpath(device), devpath, sizeof(devpath)); + util_strscpyl(from, sizeof(from), "/dev/.udev/db/", devpath, NULL); + if (lstat(from, &stats) == 0) { + if (!have_db) { + udev_device_read_db(device, from); + have_db = true; + } + unlink(from); + } + + /* write out new database */ + if (have_db) + udev_device_update_db(device); + } + udev_device_unref(device); + } + udev_enumerate_unref(udev_enumerate); + return 0; +} + +static int systemd_fds(struct udev *udev, int *rctrl, int *rnetlink) +{ + int ctrl = -1, netlink = -1; + int fd, n; + + n = sd_listen_fds(true); + if (n <= 0) + return -1; + + 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 >= 0) + return -1; + ctrl = fd; + continue; + } + + if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, -1)) { + if (netlink >= 0) + return -1; + netlink = fd; + continue; + } + + return -1; + } + + if (ctrl < 0 || netlink < 0) + return -1; + + log_debug("ctrl=%i netlink=%i\n", ctrl, netlink); + *rctrl = ctrl; + *rnetlink = netlink; + return 0; +} + +/* + * read the kernel commandline, 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 + */ +static void kernel_cmdline_options(struct udev *udev) +{ + char *line, *w, *state; + size_t l; + + if (read_one_line_file("/proc/cmdline", &line) < 0) + return; + + FOREACH_WORD_QUOTED(w, l, line, state) { + char *s, *opt; + + s = strndup(w, l); + if (!s) + break; + + /* accept the same options for the initrd, prefixed with "rd." */ + if (in_initrd() && startswith(s, "rd.")) + opt = s + 3; + else + opt = s; + + if (startswith(opt, "udev.log-priority=")) { + int prio; + + prio = util_log_priority(opt + 18); + log_set_max_level(prio); + udev_set_log_priority(udev, prio); + } else if (startswith(opt, "udev.children-max=")) { + children_max = strtoul(opt + 18, NULL, 0); + } else if (startswith(opt, "udev.exec-delay=")) { + exec_delay = strtoul(opt + 16, NULL, 0); + } + + free(s); + } + + free(line); +} + +int main(int argc, char *argv[]) +{ + struct udev *udev; + sigset_t mask; + int daemonize = false; + int resolve_names = 1; + 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' }, + { "resolve-names", required_argument, NULL, 'N' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + {} + }; + int fd_ctrl = -1; + int fd_netlink = -1; + int fd_worker = -1; + struct epoll_event ep_ctrl, ep_inotify, ep_signal, ep_netlink, ep_worker; + struct udev_ctrl_connection *ctrl_conn = NULL; + int rc = 1; + + udev = udev_new(); + if (udev == NULL) + goto exit; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + udev_set_log_fn(udev, udev_main_log); + log_debug("version %s\n", VERSION); + label_init("/dev"); + + for (;;) { + int option; + + option = getopt_long(argc, argv, "c:deDtN:hV", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'd': + daemonize = true; + break; + case 'c': + children_max = strtoul(optarg, NULL, 0); + break; + case 'e': + exec_delay = strtoul(optarg, NULL, 0); + break; + case 'D': + debug = true; + log_set_max_level(LOG_DEBUG); + udev_set_log_priority(udev, LOG_DEBUG); + break; + case 'N': + if (strcmp (optarg, "early") == 0) { + resolve_names = 1; + } else if (strcmp (optarg, "late") == 0) { + resolve_names = 0; + } else if (strcmp (optarg, "never") == 0) { + 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\n"); + goto exit; + } + break; + case 'h': + printf("Usage: udevd OPTIONS\n" + " --daemon\n" + " --debug\n" + " --children-max=<maximum number of workers>\n" + " --exec-delay=<seconds to wait before executing RUN=>\n" + " --resolve-names=early|late|never\n" + " --version\n" + " --help\n" + "\n"); + goto exit; + case 'V': + printf("%s\n", VERSION); + goto exit; + default: + goto exit; + } + } + + kernel_cmdline_options(udev); + + if (getuid() != 0) { + fprintf(stderr, "root privileges required\n"); + log_error("root privileges required\n"); + goto exit; + } + + /* set umask before creating any file/directory */ + chdir("/"); + umask(022); + + mkdir("/run/udev", 0755); + + dev_setup(NULL); + static_dev_create_from_modules(udev); + + /* before opening new files, make sure std{in,out,err} fds are in a sane state */ + if (daemonize) { + int fd; + + fd = open("/dev/null", O_RDWR); + if (fd >= 0) { + if (write(STDOUT_FILENO, 0, 0) < 0) + dup2(fd, STDOUT_FILENO); + if (write(STDERR_FILENO, 0, 0) < 0) + dup2(fd, STDERR_FILENO); + if (fd > STDERR_FILENO) + close(fd); + } else { + fprintf(stderr, "cannot open /dev/null\n"); + log_error("cannot open /dev/null\n"); + } + } + + if (systemd_fds(udev, &fd_ctrl, &fd_netlink) >= 0) { + /* get control and netlink socket from systemd */ + udev_ctrl = udev_ctrl_new_from_fd(udev, fd_ctrl); + if (udev_ctrl == NULL) { + log_error("error taking over udev control socket"); + rc = 1; + goto exit; + } + + monitor = udev_monitor_new_from_netlink_fd(udev, "kernel", fd_netlink); + if (monitor == NULL) { + log_error("error taking over netlink socket\n"); + rc = 3; + goto exit; + } + + /* get our own cgroup, we regularly kill everything udev has left behind */ + if (cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 0, &udev_cgroup) < 0) + udev_cgroup = NULL; + } else { + /* open control and netlink socket */ + udev_ctrl = udev_ctrl_new(udev); + if (udev_ctrl == NULL) { + fprintf(stderr, "error initializing udev control socket"); + log_error("error initializing udev control socket"); + rc = 1; + goto exit; + } + fd_ctrl = udev_ctrl_get_fd(udev_ctrl); + + monitor = udev_monitor_new_from_netlink(udev, "kernel"); + if (monitor == NULL) { + fprintf(stderr, "error initializing netlink socket\n"); + log_error("error initializing netlink socket\n"); + rc = 3; + goto exit; + } + fd_netlink = udev_monitor_get_fd(monitor); + } + + if (udev_monitor_enable_receiving(monitor) < 0) { + fprintf(stderr, "error binding netlink socket\n"); + log_error("error binding netlink socket\n"); + rc = 3; + goto exit; + } + + if (udev_ctrl_enable_receiving(udev_ctrl) < 0) { + fprintf(stderr, "error binding udev control socket\n"); + log_error("error binding udev control socket\n"); + rc = 1; + goto exit; + } + + udev_monitor_set_receive_buffer_size(monitor, 128*1024*1024); + + /* create queue file before signalling 'ready', to make sure we block 'settle' */ + udev_queue_export = udev_queue_export_new(udev); + if (udev_queue_export == NULL) { + log_error("error creating queue file\n"); + goto exit; + } + + if (daemonize) { + pid_t pid; + + pid = fork(); + switch (pid) { + case 0: + break; + case -1: + log_error("fork of daemon failed: %m\n"); + rc = 4; + goto exit; + default: + rc = EXIT_SUCCESS; + goto exit_daemonize; + } + + setsid(); + + write_one_line_file("/proc/self/oom_score_adj", "-1000"); + } else { + sd_notify(1, "READY=1"); + } + + print_kmsg("starting version " VERSION "\n"); + + if (!debug) { + int fd; + + fd = open("/dev/null", O_RDWR); + if (fd >= 0) { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); + } + } + + fd_inotify = udev_watch_init(udev); + if (fd_inotify < 0) { + fprintf(stderr, "error initializing inotify\n"); + log_error("error initializing inotify\n"); + rc = 4; + goto exit; + } + udev_watch_restore(udev); + + /* block and listen to all signals on signalfd */ + sigfillset(&mask); + sigprocmask(SIG_SETMASK, &mask, &sigmask_orig); + fd_signal = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); + if (fd_signal < 0) { + fprintf(stderr, "error creating signalfd\n"); + log_error("error creating signalfd\n"); + rc = 5; + goto exit; + } + + /* unnamed socket from workers to the main daemon */ + if (socketpair(AF_LOCAL, SOCK_DGRAM|SOCK_CLOEXEC, 0, worker_watch) < 0) { + fprintf(stderr, "error creating socketpair\n"); + log_error("error creating socketpair\n"); + rc = 6; + goto exit; + } + fd_worker = worker_watch[READ_END]; + + udev_builtin_init(udev); + + rules = udev_rules_new(udev, resolve_names); + if (rules == NULL) { + log_error("error reading rules\n"); + goto exit; + } + + memset(&ep_ctrl, 0, sizeof(struct epoll_event)); + ep_ctrl.events = EPOLLIN; + ep_ctrl.data.fd = fd_ctrl; + + memset(&ep_inotify, 0, sizeof(struct epoll_event)); + ep_inotify.events = EPOLLIN; + ep_inotify.data.fd = fd_inotify; + + memset(&ep_signal, 0, sizeof(struct epoll_event)); + ep_signal.events = EPOLLIN; + ep_signal.data.fd = fd_signal; + + memset(&ep_netlink, 0, sizeof(struct epoll_event)); + ep_netlink.events = EPOLLIN; + ep_netlink.data.fd = fd_netlink; + + memset(&ep_worker, 0, sizeof(struct epoll_event)); + ep_worker.events = EPOLLIN; + ep_worker.data.fd = fd_worker; + + fd_ep = epoll_create1(EPOLL_CLOEXEC); + if (fd_ep < 0) { + log_error("error creating epoll fd: %m\n"); + goto exit; + } + if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_ctrl, &ep_ctrl) < 0 || + epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_inotify, &ep_inotify) < 0 || + epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_signal, &ep_signal) < 0 || + epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_netlink, &ep_netlink) < 0 || + epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_worker, &ep_worker) < 0) { + log_error("fail to add fds to epoll: %m\n"); + goto exit; + } + + /* if needed, convert old database from earlier udev version */ + convert_db(udev); + + if (children_max <= 0) { + int memsize = mem_size_mb(); + + /* set value depending on the amount of RAM */ + if (memsize > 0) + children_max = 16 + (memsize / 8); + else + children_max = 16; + } + log_debug("set children_max to %u\n", children_max); + + udev_rules_apply_static_dev_perms(rules); + + udev_list_node_init(&event_list); + udev_list_node_init(&worker_list); + + for (;;) { + static usec_t last_usec; + struct epoll_event ev[8]; + int fdcount; + int timeout; + bool is_worker, is_signal, is_inotify, is_netlink, is_ctrl; + int i; + + if (udev_exit) { + /* close sources of new events and discard buffered events */ + if (fd_ctrl >= 0) { + epoll_ctl(fd_ep, EPOLL_CTL_DEL, fd_ctrl, NULL); + fd_ctrl = -1; + } + if (monitor != NULL) { + epoll_ctl(fd_ep, EPOLL_CTL_DEL, fd_netlink, NULL); + udev_monitor_unref(monitor); + monitor = NULL; + } + if (fd_inotify >= 0) { + epoll_ctl(fd_ep, EPOLL_CTL_DEL, fd_inotify, NULL); + close(fd_inotify); + fd_inotify = -1; + } + + /* discard queued events and kill workers */ + event_queue_cleanup(udev, EVENT_QUEUED); + worker_kill(udev); + + /* exit after all has cleaned up */ + if (udev_list_node_is_empty(&event_list) && udev_list_node_is_empty(&worker_list)) + break; + + /* timeout at exit for workers to finish */ + timeout = 30 * 1000; + } else if (udev_list_node_is_empty(&event_list) && !children) { + /* we are idle */ + timeout = -1; + + /* cleanup possible left-over processes in our cgroup */ + if (udev_cgroup) + cg_kill(SYSTEMD_CGROUP_CONTROLLER, udev_cgroup, SIGKILL, false, true, NULL); + } else { + /* kill idle or hanging workers */ + timeout = 3 * 1000; + } + fdcount = epoll_wait(fd_ep, ev, ELEMENTSOF(ev), timeout); + if (fdcount < 0) + continue; + + if (fdcount == 0) { + struct udev_list_node *loop; + + /* timeout */ + if (udev_exit) { + log_error("timeout, giving up waiting for workers to finish\n"); + break; + } + + /* kill idle workers */ + if (udev_list_node_is_empty(&event_list)) { + log_debug("cleanup idle workers\n"); + worker_kill(udev); + } + + /* check for hanging events */ + udev_list_node_foreach(loop, &worker_list) { + struct worker *worker = node_to_worker(loop); + + if (worker->state != WORKER_RUNNING) + continue; + + if ((now(CLOCK_MONOTONIC) - worker->event_start_usec) > 30 * 1000 * 1000) { + log_error("worker [%u] %s timeout; kill it\n", worker->pid, + worker->event ? worker->event->devpath : "<idle>"); + kill(worker->pid, SIGKILL); + worker->state = WORKER_KILLED; + /* drop reference taken for state 'running' */ + worker_unref(worker); + if (worker->event) { + log_error("seq %llu '%s' killed\n", + udev_device_get_seqnum(worker->event->dev), worker->event->devpath); + worker->event->exitcode = -64; + event_queue_delete(worker->event, true); + worker->event = NULL; + } + } + } + + } + + is_worker = is_signal = is_inotify = is_netlink = is_ctrl = false; + for (i = 0; i < fdcount; i++) { + if (ev[i].data.fd == fd_worker && ev[i].events & EPOLLIN) + is_worker = true; + else if (ev[i].data.fd == fd_netlink && ev[i].events & EPOLLIN) + is_netlink = true; + else if (ev[i].data.fd == fd_signal && ev[i].events & EPOLLIN) + is_signal = true; + else if (ev[i].data.fd == fd_inotify && ev[i].events & EPOLLIN) + is_inotify = true; + else if (ev[i].data.fd == fd_ctrl && ev[i].events & EPOLLIN) + is_ctrl = true; + } + + /* check for changed config, every 3 seconds at most */ + if ((now(CLOCK_MONOTONIC) - last_usec) > 3 * 1000 * 1000) { + if (udev_rules_check_timestamp(rules)) + reload = true; + if (udev_builtin_validate(udev)) + reload = true; + + last_usec = now(CLOCK_MONOTONIC); + } + + /* reload requested, HUP signal received, rules changed, builtin changed */ + if (reload) { + worker_kill(udev); + rules = udev_rules_unref(rules); + udev_builtin_exit(udev); + reload = false; + } + + /* event has finished */ + if (is_worker) + worker_returned(fd_worker); + + if (is_netlink) { + struct udev_device *dev; + + dev = udev_monitor_receive_device(monitor); + if (dev != NULL) { + udev_device_set_usec_initialized(dev, now(CLOCK_MONOTONIC)); + if (event_queue_insert(dev) < 0) + udev_device_unref(dev); + } + } + + /* start new events */ + if (!udev_list_node_is_empty(&event_list) && !udev_exit && !stop_exec_queue) { + udev_builtin_init(udev); + if (rules == NULL) + rules = udev_rules_new(udev, resolve_names); + if (rules != NULL) + event_queue_start(udev); + } + + if (is_signal) { + struct signalfd_siginfo fdsi; + ssize_t size; + + size = read(fd_signal, &fdsi, sizeof(struct signalfd_siginfo)); + if (size == sizeof(struct signalfd_siginfo)) + handle_signal(udev, fdsi.ssi_signo); + } + + /* we are shutting down, the events below are not handled anymore */ + if (udev_exit) + continue; + + /* device node watch */ + if (is_inotify) + handle_inotify(udev); + + /* + * This needs to be after the inotify handling, to make sure, + * that the ping is send back after the possibly generated + * "change" events by the inotify device node watch. + * + * A single time we may receive a client connection which we need to + * keep open to block the client. It will be closed right before we + * exit. + */ + if (is_ctrl) + ctrl_conn = handle_ctrl_msg(udev_ctrl); + } + + rc = EXIT_SUCCESS; +exit: + udev_queue_export_cleanup(udev_queue_export); + udev_ctrl_cleanup(udev_ctrl); +exit_daemonize: + if (fd_ep >= 0) + close(fd_ep); + worker_list_cleanup(udev); + event_queue_cleanup(udev, EVENT_UNDEF); + udev_rules_unref(rules); + udev_builtin_exit(udev); + if (fd_signal >= 0) + close(fd_signal); + if (worker_watch[READ_END] >= 0) + close(worker_watch[READ_END]); + if (worker_watch[WRITE_END] >= 0) + close(worker_watch[WRITE_END]); + udev_monitor_unref(monitor); + udev_queue_export_unref(udev_queue_export); + udev_ctrl_connection_unref(ctrl_conn); + udev_ctrl_unref(udev_ctrl); + label_finish(); + udev_unref(udev); + log_close(); + return rc; +} diff --git a/src/udev/v4l_id/v4l_id.c b/src/udev/v4l_id/v4l_id.c new file mode 100644 index 0000000000..8dcb645ed9 --- /dev/null +++ b/src/udev/v4l_id/v4l_id.c @@ -0,0 +1,87 @@ +/* + * 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 <stdio.h> +#include <errno.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <linux/videodev2.h> + +int main (int argc, char *argv[]) +{ + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + {} + }; + int fd; + char *device; + struct v4l2_capability v2cap; + + while (1) { + int option; + + option = getopt_long(argc, argv, "h", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'h': + printf("Usage: v4l_id [--help] <device file>\n\n"); + return 0; + default: + return 1; + } + } + 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"); + } + + close (fd); + return 0; +} |