diff options
author | Luke Shumaker <lukeshu@sbcglobal.net> | 2016-06-13 13:01:46 -0400 |
---|---|---|
committer | Luke Shumaker <lukeshu@sbcglobal.net> | 2016-06-13 13:01:46 -0400 |
commit | ecc82f5eed7ae93d0a98ca50476b12fa695dee7c (patch) | |
tree | b898a80c9f7dec66307dcd84fdca3dfe181d337e /src/grp-udev | |
parent | 9a5fd42193c1a92178edb6e6c7c2970177eb499d (diff) |
./move.sh
Diffstat (limited to 'src/grp-udev')
62 files changed, 18095 insertions, 0 deletions
diff --git a/src/grp-udev/.gitignore b/src/grp-udev/.gitignore new file mode 100644 index 0000000000..f5d8be3dc1 --- /dev/null +++ b/src/grp-udev/.gitignore @@ -0,0 +1,4 @@ +/udev.pc +/keyboard-keys-from-name.gperf +/keyboard-keys-from-name.h +/keyboard-keys-list.txt diff --git a/src/grp-udev/.vimrc b/src/grp-udev/.vimrc new file mode 100644 index 0000000000..366fbdca4b --- /dev/null +++ b/src/grp-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/grp-udev/Makefile b/src/grp-udev/Makefile new file mode 100644 index 0000000000..cc85f0317f --- /dev/null +++ b/src/grp-udev/Makefile @@ -0,0 +1,72 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +INSTALL_DIRS += \ + $(sysconfdir)/udev/rules.d + +dist_udevrules_DATA += \ + rules/50-udev-default.rules \ + rules/60-block.rules \ + rules/60-drm.rules \ + rules/60-evdev.rules \ + rules/60-persistent-storage-tape.rules \ + rules/60-persistent-input.rules \ + rules/60-persistent-alsa.rules \ + rules/60-persistent-storage.rules \ + rules/60-serial.rules \ + rules/64-btrfs.rules \ + rules/70-mouse.rules \ + rules/75-net-description.rules \ + rules/78-sound-card.rules \ + rules/80-net-setup-link.rules + +nodist_udevrules_DATA += \ + rules/99-systemd.rules + +udevconfdir = $(sysconfdir)/udev +dist_udevconf_DATA = \ + src/udev/udev.conf + +pkgconfigdata_DATA += \ + src/udev/udev.pc + +EXTRA_DIST += \ + rules/99-systemd.rules.in \ + src/udev/udev.pc.in + +EXTRA_DIST += \ + units/systemd-udevd.service.in \ + units/systemd-udev-trigger.service.in \ + units/systemd-udev-settle.service.in + +SOCKETS_TARGET_WANTS += \ + systemd-udevd-control.socket \ + systemd-udevd-kernel.socket + +SYSINIT_TARGET_WANTS += \ + systemd-udevd.service \ + systemd-udev-trigger.service + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-udev/ata_id/Makefile b/src/grp-udev/ata_id/Makefile new file mode 100644 index 0000000000..00a8c37ac2 --- /dev/null +++ b/src/grp-udev/ata_id/Makefile @@ -0,0 +1,35 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ata_id_SOURCES = \ + src/udev/ata_id/ata_id.c + +ata_id_LDADD = \ + libshared.la + +udevlibexec_PROGRAMS += \ + ata_id + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-udev/ata_id/ata_id.c b/src/grp-udev/ata_id/ata_id.c new file mode 100644 index 0000000000..1e414664ce --- /dev/null +++ b/src/grp-udev/ata_id/ata_id.c @@ -0,0 +1,674 @@ +/* + * ata_id - reads product/serial number from ATA drives + * + * Copyright (C) 2005-2008 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2009 Lennart Poettering <lennart@poettering.net> + * Copyright (C) 2009-2010 David Zeuthen <zeuthen@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <linux/bsg.h> +#include <linux/hdreg.h> +#include <scsi/scsi.h> +#include <scsi/scsi_ioctl.h> +#include <scsi/sg.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "libudev.h" + +#include "fd-util.h" +#include "libudev-private.h" +#include "log.h" +#include "udev-util.h" + +#define COMMAND_TIMEOUT_MSEC (30 * 1000) + +static int disk_scsi_inquiry_command(int fd, + void *buf, + size_t buf_len) +{ + uint8_t cdb[6] = { + /* + * INQUIRY, see SPC-4 section 6.4 + */ + [0] = 0x12, /* OPERATION CODE: INQUIRY */ + [3] = (buf_len >> 8), /* ALLOCATION LENGTH */ + [4] = (buf_len & 0xff), + }; + uint8_t sense[32] = {}; + struct sg_io_v4 io_v4 = { + .guard = 'Q', + .protocol = BSG_PROTOCOL_SCSI, + .subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD, + .request_len = sizeof(cdb), + .request = (uintptr_t) cdb, + .max_response_len = sizeof(sense), + .response = (uintptr_t) sense, + .din_xfer_len = buf_len, + .din_xferp = (uintptr_t) buf, + .timeout = COMMAND_TIMEOUT_MSEC, + }; + int ret; + + ret = ioctl(fd, SG_IO, &io_v4); + if (ret != 0) { + /* could be that the driver doesn't do version 4, try version 3 */ + if (errno == EINVAL) { + struct sg_io_hdr io_hdr = { + .interface_id = 'S', + .cmdp = (unsigned char*) cdb, + .cmd_len = sizeof (cdb), + .dxferp = buf, + .dxfer_len = buf_len, + .sbp = sense, + .mx_sb_len = sizeof(sense), + .dxfer_direction = SG_DXFER_FROM_DEV, + .timeout = COMMAND_TIMEOUT_MSEC, + }; + + ret = ioctl(fd, SG_IO, &io_hdr); + if (ret != 0) + return ret; + + /* even if the ioctl succeeds, we need to check the return value */ + if (!(io_hdr.status == 0 && + io_hdr.host_status == 0 && + io_hdr.driver_status == 0)) { + errno = EIO; + return -1; + } + } else + return ret; + } + + /* even if the ioctl succeeds, we need to check the return value */ + if (!(io_v4.device_status == 0 && + io_v4.transport_status == 0 && + io_v4.driver_status == 0)) { + errno = EIO; + return -1; + } + + return 0; +} + +static int disk_identify_command(int fd, + void *buf, + size_t buf_len) +{ + uint8_t cdb[12] = { + /* + * ATA Pass-Through 12 byte command, as described in + * + * T10 04-262r8 ATA Command Pass-Through + * + * from http://www.t10.org/ftp/t10/document.04/04-262r8.pdf + */ + [0] = 0xa1, /* OPERATION CODE: 12 byte pass through */ + [1] = 4 << 1, /* PROTOCOL: PIO Data-in */ + [2] = 0x2e, /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */ + [3] = 0, /* FEATURES */ + [4] = 1, /* SECTORS */ + [5] = 0, /* LBA LOW */ + [6] = 0, /* LBA MID */ + [7] = 0, /* LBA HIGH */ + [8] = 0 & 0x4F, /* SELECT */ + [9] = 0xEC, /* Command: ATA IDENTIFY DEVICE */ + }; + uint8_t sense[32] = {}; + uint8_t *desc = sense + 8; + struct sg_io_v4 io_v4 = { + .guard = 'Q', + .protocol = BSG_PROTOCOL_SCSI, + .subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD, + .request_len = sizeof(cdb), + .request = (uintptr_t) cdb, + .max_response_len = sizeof(sense), + .response = (uintptr_t) sense, + .din_xfer_len = buf_len, + .din_xferp = (uintptr_t) buf, + .timeout = COMMAND_TIMEOUT_MSEC, + }; + int ret; + + ret = ioctl(fd, SG_IO, &io_v4); + if (ret != 0) { + /* could be that the driver doesn't do version 4, try version 3 */ + if (errno == EINVAL) { + struct sg_io_hdr io_hdr = { + .interface_id = 'S', + .cmdp = (unsigned char*) cdb, + .cmd_len = sizeof (cdb), + .dxferp = buf, + .dxfer_len = buf_len, + .sbp = sense, + .mx_sb_len = sizeof (sense), + .dxfer_direction = SG_DXFER_FROM_DEV, + .timeout = COMMAND_TIMEOUT_MSEC, + }; + + ret = ioctl(fd, SG_IO, &io_hdr); + if (ret != 0) + return ret; + } else + return ret; + } + + if (!(sense[0] == 0x72 && desc[0] == 0x9 && desc[1] == 0x0c)) { + errno = EIO; + return -1; + } + + return 0; +} + +static int disk_identify_packet_device_command(int fd, + void *buf, + size_t buf_len) +{ + uint8_t cdb[16] = { + /* + * ATA Pass-Through 16 byte command, as described in + * + * T10 04-262r8 ATA Command Pass-Through + * + * from http://www.t10.org/ftp/t10/document.04/04-262r8.pdf + */ + [0] = 0x85, /* OPERATION CODE: 16 byte pass through */ + [1] = 4 << 1, /* PROTOCOL: PIO Data-in */ + [2] = 0x2e, /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */ + [3] = 0, /* FEATURES */ + [4] = 0, /* FEATURES */ + [5] = 0, /* SECTORS */ + [6] = 1, /* SECTORS */ + [7] = 0, /* LBA LOW */ + [8] = 0, /* LBA LOW */ + [9] = 0, /* LBA MID */ + [10] = 0, /* LBA MID */ + [11] = 0, /* LBA HIGH */ + [12] = 0, /* LBA HIGH */ + [13] = 0, /* DEVICE */ + [14] = 0xA1, /* Command: ATA IDENTIFY PACKET DEVICE */ + [15] = 0, /* CONTROL */ + }; + uint8_t sense[32] = {}; + uint8_t *desc = sense + 8; + struct sg_io_v4 io_v4 = { + .guard = 'Q', + .protocol = BSG_PROTOCOL_SCSI, + .subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD, + .request_len = sizeof (cdb), + .request = (uintptr_t) cdb, + .max_response_len = sizeof (sense), + .response = (uintptr_t) sense, + .din_xfer_len = buf_len, + .din_xferp = (uintptr_t) buf, + .timeout = COMMAND_TIMEOUT_MSEC, + }; + int ret; + + ret = ioctl(fd, SG_IO, &io_v4); + if (ret != 0) { + /* could be that the driver doesn't do version 4, try version 3 */ + if (errno == EINVAL) { + struct sg_io_hdr io_hdr = { + .interface_id = 'S', + .cmdp = (unsigned char*) cdb, + .cmd_len = sizeof (cdb), + .dxferp = buf, + .dxfer_len = buf_len, + .sbp = sense, + .mx_sb_len = sizeof (sense), + .dxfer_direction = SG_DXFER_FROM_DEV, + .timeout = COMMAND_TIMEOUT_MSEC, + }; + + ret = ioctl(fd, SG_IO, &io_hdr); + if (ret != 0) + return ret; + } else + return ret; + } + + if (!(sense[0] == 0x72 && desc[0] == 0x9 && desc[1] == 0x0c)) { + errno = EIO; + return -1; + } + + return 0; +} + +/** + * disk_identify_get_string: + * @identify: A block of IDENTIFY data + * @offset_words: Offset of the string to get, in words. + * @dest: Destination buffer for the string. + * @dest_len: Length of destination buffer, in bytes. + * + * Copies the ATA string from @identify located at @offset_words into @dest. + */ +static void disk_identify_get_string(uint8_t identify[512], + unsigned int offset_words, + char *dest, + size_t dest_len) +{ + unsigned int c1; + unsigned int c2; + + while (dest_len > 0) { + c1 = identify[offset_words * 2 + 1]; + c2 = identify[offset_words * 2]; + *dest = c1; + dest++; + *dest = c2; + dest++; + offset_words++; + dest_len -= 2; + } +} + +static void disk_identify_fixup_string(uint8_t identify[512], + unsigned int offset_words, + size_t len) +{ + disk_identify_get_string(identify, offset_words, + (char *) identify + offset_words * 2, len); +} + +static void disk_identify_fixup_uint16 (uint8_t identify[512], unsigned int offset_words) +{ + uint16_t *p; + + p = (uint16_t *) identify; + p[offset_words] = le16toh (p[offset_words]); +} + +/** + * disk_identify: + * @udev: The libudev context. + * @fd: File descriptor for the block device. + * @out_identify: Return location for IDENTIFY data. + * @out_is_packet_device: Return location for whether returned data is from a IDENTIFY PACKET DEVICE. + * + * Sends the IDENTIFY DEVICE or IDENTIFY PACKET DEVICE command to the + * device represented by @fd. If successful, then the result will be + * copied into @out_identify and @out_is_packet_device. + * + * This routine is based on code from libatasmart, Copyright 2008 + * Lennart Poettering, LGPL v2.1. + * + * Returns: 0 if the data was successfully obtained, otherwise + * non-zero with errno set. + */ +static int disk_identify(struct udev *udev, + int fd, + uint8_t out_identify[512], + int *out_is_packet_device) +{ + int ret; + uint8_t inquiry_buf[36]; + int peripheral_device_type; + int all_nul_bytes; + int n; + int is_packet_device = 0; + + /* init results */ + memzero(out_identify, 512); + + /* If we were to use ATA PASS_THROUGH (12) on an ATAPI device + * we could accidentally blank media. This is because MMC's BLANK + * command has the same op-code (0x61). + * + * To prevent this from happening we bail out if the device + * isn't a Direct Access Block Device, e.g. SCSI type 0x00 + * (CD/DVD devices are type 0x05). So we send a SCSI INQUIRY + * command first... libata is handling this via its SCSI + * emulation layer. + * + * This also ensures that we're actually dealing with a device + * that understands SCSI commands. + * + * (Yes, it is a bit perverse that we're tunneling the ATA + * command through SCSI and relying on the ATA driver + * emulating SCSI well-enough...) + * + * (See commit 160b069c25690bfb0c785994c7c3710289179107 for + * the original bug-fix and see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=556635 + * for the original bug-report.) + */ + ret = disk_scsi_inquiry_command (fd, inquiry_buf, sizeof (inquiry_buf)); + if (ret != 0) + goto out; + + /* SPC-4, section 6.4.2: Standard INQUIRY data */ + peripheral_device_type = inquiry_buf[0] & 0x1f; + if (peripheral_device_type == 0x05) + { + is_packet_device = 1; + ret = disk_identify_packet_device_command(fd, out_identify, 512); + goto check_nul_bytes; + } + if (peripheral_device_type != 0x00) { + ret = -1; + errno = EIO; + goto out; + } + + /* OK, now issue the IDENTIFY DEVICE command */ + ret = disk_identify_command(fd, out_identify, 512); + if (ret != 0) + goto out; + + check_nul_bytes: + /* Check if IDENTIFY data is all NUL bytes - if so, bail */ + all_nul_bytes = 1; + for (n = 0; n < 512; n++) { + if (out_identify[n] != '\0') { + all_nul_bytes = 0; + break; + } + } + + if (all_nul_bytes) { + ret = -1; + errno = EIO; + goto out; + } + +out: + if (out_is_packet_device != NULL) + *out_is_packet_device = is_packet_device; + return ret; +} + +int main(int argc, char *argv[]) +{ + _cleanup_udev_unref_ struct udev *udev = NULL; + struct hd_driveid id; + union { + uint8_t byte[512]; + uint16_t wyde[256]; + } identify; + char model[41]; + char model_enc[256]; + char serial[21]; + char revision[9]; + const char *node = NULL; + int export = 0; + _cleanup_close_ int fd = -1; + uint16_t word; + int is_packet_device = 0; + static const struct option options[] = { + { "export", no_argument, NULL, 'x' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + + log_parse_environment(); + log_open(); + + udev = udev_new(); + if (udev == NULL) + return 0; + + for (;;) { + int option; + + option = getopt_long(argc, argv, "xh", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'x': + export = 1; + break; + case 'h': + printf("Usage: ata_id [--export] [--help] <device>\n" + " -x,--export print values as environment keys\n" + " -h,--help print this help text\n\n"); + return 0; + } + } + + node = argv[optind]; + if (node == NULL) { + log_error("no node specified"); + return 1; + } + + fd = open(node, O_RDONLY|O_NONBLOCK|O_CLOEXEC); + if (fd < 0) { + log_error("unable to open '%s'", node); + return 1; + } + + if (disk_identify(udev, fd, identify.byte, &is_packet_device) == 0) { + /* + * fix up only the fields from the IDENTIFY data that we are going to + * use and copy it into the hd_driveid struct for convenience + */ + disk_identify_fixup_string(identify.byte, 10, 20); /* serial */ + disk_identify_fixup_string(identify.byte, 23, 8); /* fwrev */ + disk_identify_fixup_string(identify.byte, 27, 40); /* model */ + disk_identify_fixup_uint16(identify.byte, 0); /* configuration */ + disk_identify_fixup_uint16(identify.byte, 75); /* queue depth */ + disk_identify_fixup_uint16(identify.byte, 76); /* SATA capabilities */ + disk_identify_fixup_uint16(identify.byte, 82); /* command set supported */ + disk_identify_fixup_uint16(identify.byte, 83); /* command set supported */ + disk_identify_fixup_uint16(identify.byte, 84); /* command set supported */ + disk_identify_fixup_uint16(identify.byte, 85); /* command set supported */ + disk_identify_fixup_uint16(identify.byte, 86); /* command set supported */ + disk_identify_fixup_uint16(identify.byte, 87); /* command set supported */ + disk_identify_fixup_uint16(identify.byte, 89); /* time required for SECURITY ERASE UNIT */ + disk_identify_fixup_uint16(identify.byte, 90); /* time required for enhanced SECURITY ERASE UNIT */ + disk_identify_fixup_uint16(identify.byte, 91); /* current APM values */ + disk_identify_fixup_uint16(identify.byte, 94); /* current AAM value */ + disk_identify_fixup_uint16(identify.byte, 108); /* WWN */ + disk_identify_fixup_uint16(identify.byte, 109); /* WWN */ + disk_identify_fixup_uint16(identify.byte, 110); /* WWN */ + disk_identify_fixup_uint16(identify.byte, 111); /* WWN */ + disk_identify_fixup_uint16(identify.byte, 128); /* device lock function */ + disk_identify_fixup_uint16(identify.byte, 217); /* nominal media rotation rate */ + memcpy(&id, identify.byte, sizeof id); + } else { + /* If this fails, then try HDIO_GET_IDENTITY */ + if (ioctl(fd, HDIO_GET_IDENTITY, &id) != 0) { + log_debug_errno(errno, "HDIO_GET_IDENTITY failed for '%s': %m", node); + return 2; + } + } + + memcpy(model, id.model, 40); + model[40] = '\0'; + udev_util_encode_string(model, model_enc, sizeof(model_enc)); + util_replace_whitespace((char *) id.model, model, 40); + util_replace_chars(model, NULL); + util_replace_whitespace((char *) id.serial_no, serial, 20); + util_replace_chars(serial, NULL); + util_replace_whitespace((char *) id.fw_rev, revision, 8); + util_replace_chars(revision, NULL); + + if (export) { + /* Set this to convey the disk speaks the ATA protocol */ + printf("ID_ATA=1\n"); + + if ((id.config >> 8) & 0x80) { + /* This is an ATAPI device */ + switch ((id.config >> 8) & 0x1f) { + case 0: + printf("ID_TYPE=cd\n"); + break; + case 1: + printf("ID_TYPE=tape\n"); + break; + case 5: + printf("ID_TYPE=cd\n"); + break; + case 7: + printf("ID_TYPE=optical\n"); + break; + default: + printf("ID_TYPE=generic\n"); + break; + } + } else { + printf("ID_TYPE=disk\n"); + } + printf("ID_BUS=ata\n"); + printf("ID_MODEL=%s\n", model); + printf("ID_MODEL_ENC=%s\n", model_enc); + printf("ID_REVISION=%s\n", revision); + if (serial[0] != '\0') { + printf("ID_SERIAL=%s_%s\n", model, serial); + printf("ID_SERIAL_SHORT=%s\n", serial); + } else { + printf("ID_SERIAL=%s\n", model); + } + + if (id.command_set_1 & (1<<5)) { + printf("ID_ATA_WRITE_CACHE=1\n"); + printf("ID_ATA_WRITE_CACHE_ENABLED=%d\n", (id.cfs_enable_1 & (1<<5)) ? 1 : 0); + } + if (id.command_set_1 & (1<<10)) { + printf("ID_ATA_FEATURE_SET_HPA=1\n"); + printf("ID_ATA_FEATURE_SET_HPA_ENABLED=%d\n", (id.cfs_enable_1 & (1<<10)) ? 1 : 0); + + /* + * TODO: use the READ NATIVE MAX ADDRESS command to get the native max address + * so it is easy to check whether the protected area is in use. + */ + } + if (id.command_set_1 & (1<<3)) { + printf("ID_ATA_FEATURE_SET_PM=1\n"); + printf("ID_ATA_FEATURE_SET_PM_ENABLED=%d\n", (id.cfs_enable_1 & (1<<3)) ? 1 : 0); + } + if (id.command_set_1 & (1<<1)) { + printf("ID_ATA_FEATURE_SET_SECURITY=1\n"); + printf("ID_ATA_FEATURE_SET_SECURITY_ENABLED=%d\n", (id.cfs_enable_1 & (1<<1)) ? 1 : 0); + printf("ID_ATA_FEATURE_SET_SECURITY_ERASE_UNIT_MIN=%d\n", id.trseuc * 2); + if ((id.cfs_enable_1 & (1<<1))) /* enabled */ { + if (id.dlf & (1<<8)) + printf("ID_ATA_FEATURE_SET_SECURITY_LEVEL=maximum\n"); + else + printf("ID_ATA_FEATURE_SET_SECURITY_LEVEL=high\n"); + } + if (id.dlf & (1<<5)) + printf("ID_ATA_FEATURE_SET_SECURITY_ENHANCED_ERASE_UNIT_MIN=%d\n", id.trsEuc * 2); + if (id.dlf & (1<<4)) + printf("ID_ATA_FEATURE_SET_SECURITY_EXPIRE=1\n"); + if (id.dlf & (1<<3)) + printf("ID_ATA_FEATURE_SET_SECURITY_FROZEN=1\n"); + if (id.dlf & (1<<2)) + printf("ID_ATA_FEATURE_SET_SECURITY_LOCKED=1\n"); + } + if (id.command_set_1 & (1<<0)) { + printf("ID_ATA_FEATURE_SET_SMART=1\n"); + printf("ID_ATA_FEATURE_SET_SMART_ENABLED=%d\n", (id.cfs_enable_1 & (1<<0)) ? 1 : 0); + } + if (id.command_set_2 & (1<<9)) { + printf("ID_ATA_FEATURE_SET_AAM=1\n"); + printf("ID_ATA_FEATURE_SET_AAM_ENABLED=%d\n", (id.cfs_enable_2 & (1<<9)) ? 1 : 0); + printf("ID_ATA_FEATURE_SET_AAM_VENDOR_RECOMMENDED_VALUE=%d\n", id.acoustic >> 8); + printf("ID_ATA_FEATURE_SET_AAM_CURRENT_VALUE=%d\n", id.acoustic & 0xff); + } + if (id.command_set_2 & (1<<5)) { + printf("ID_ATA_FEATURE_SET_PUIS=1\n"); + printf("ID_ATA_FEATURE_SET_PUIS_ENABLED=%d\n", (id.cfs_enable_2 & (1<<5)) ? 1 : 0); + } + if (id.command_set_2 & (1<<3)) { + printf("ID_ATA_FEATURE_SET_APM=1\n"); + printf("ID_ATA_FEATURE_SET_APM_ENABLED=%d\n", (id.cfs_enable_2 & (1<<3)) ? 1 : 0); + if ((id.cfs_enable_2 & (1<<3))) + printf("ID_ATA_FEATURE_SET_APM_CURRENT_VALUE=%d\n", id.CurAPMvalues & 0xff); + } + if (id.command_set_2 & (1<<0)) + printf("ID_ATA_DOWNLOAD_MICROCODE=1\n"); + + /* + * Word 76 indicates the capabilities of a SATA device. A PATA device shall set + * word 76 to 0000h or FFFFh. If word 76 is set to 0000h or FFFFh, then + * the device does not claim compliance with the Serial ATA specification and words + * 76 through 79 are not valid and shall be ignored. + */ + + word = identify.wyde[76]; + if (word != 0x0000 && word != 0xffff) { + printf("ID_ATA_SATA=1\n"); + /* + * If bit 2 of word 76 is set to one, then the device supports the Gen2 + * signaling rate of 3.0 Gb/s (see SATA 2.6). + * + * If bit 1 of word 76 is set to one, then the device supports the Gen1 + * signaling rate of 1.5 Gb/s (see SATA 2.6). + */ + if (word & (1<<2)) + printf("ID_ATA_SATA_SIGNAL_RATE_GEN2=1\n"); + if (word & (1<<1)) + printf("ID_ATA_SATA_SIGNAL_RATE_GEN1=1\n"); + } + + /* Word 217 indicates the nominal media rotation rate of the device */ + word = identify.wyde[217]; + if (word == 0x0001) + printf ("ID_ATA_ROTATION_RATE_RPM=0\n"); /* non-rotating e.g. SSD */ + else if (word >= 0x0401 && word <= 0xfffe) + printf ("ID_ATA_ROTATION_RATE_RPM=%d\n", word); + + /* + * Words 108-111 contain a mandatory World Wide Name (WWN) in the NAA IEEE Registered identifier + * format. Word 108 bits (15:12) shall contain 5h, indicating that the naming authority is IEEE. + * All other values are reserved. + */ + word = identify.wyde[108]; + if ((word & 0xf000) == 0x5000) { + uint64_t wwwn; + + wwwn = identify.wyde[108]; + wwwn <<= 16; + wwwn |= identify.wyde[109]; + wwwn <<= 16; + wwwn |= identify.wyde[110]; + wwwn <<= 16; + wwwn |= identify.wyde[111]; + printf("ID_WWN=0x%1$" PRIx64 "\n" + "ID_WWN_WITH_EXTENSION=0x%1$" PRIx64 "\n", + wwwn); + } + + /* from Linux's include/linux/ata.h */ + if (identify.wyde[0] == 0x848a || + identify.wyde[0] == 0x844a || + (identify.wyde[83] & 0xc004) == 0x4004) + printf("ID_ATA_CFA=1\n"); + } else { + if (serial[0] != '\0') + printf("%s_%s\n", model, serial); + else + printf("%s\n", model); + } + + return 0; +} diff --git a/src/grp-udev/cdrom_id/Makefile b/src/grp-udev/cdrom_id/Makefile new file mode 100644 index 0000000000..a9297413d3 --- /dev/null +++ b/src/grp-udev/cdrom_id/Makefile @@ -0,0 +1,38 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +cdrom_id_SOURCES = \ + src/udev/cdrom_id/cdrom_id.c + +cdrom_id_LDADD = \ + libshared.la + +udevlibexec_PROGRAMS += \ + cdrom_id + +dist_udevrules_DATA += \ + rules/60-cdrom_id.rules + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-udev/cdrom_id/cdrom_id.c b/src/grp-udev/cdrom_id/cdrom_id.c new file mode 100644 index 0000000000..72f284f710 --- /dev/null +++ b/src/grp-udev/cdrom_id/cdrom_id.c @@ -0,0 +1,1085 @@ +/* + * cdrom_id - optical drive and media information prober + * + * Copyright (C) 2008-2010 Kay Sievers <kay@vrfy.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <limits.h> +#include <linux/cdrom.h> +#include <scsi/sg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include "libudev.h" + +#include "libudev-private.h" +#include "random-util.h" + +/* device info */ +static unsigned int cd_cd_rom; +static unsigned int cd_cd_r; +static unsigned int cd_cd_rw; +static unsigned int cd_dvd_rom; +static unsigned int cd_dvd_r; +static unsigned int cd_dvd_rw; +static unsigned int cd_dvd_ram; +static unsigned int cd_dvd_plus_r; +static unsigned int cd_dvd_plus_rw; +static unsigned int cd_dvd_plus_r_dl; +static unsigned int cd_dvd_plus_rw_dl; +static unsigned int cd_bd; +static unsigned int cd_bd_r; +static unsigned int cd_bd_re; +static unsigned int cd_hddvd; +static unsigned int cd_hddvd_r; +static unsigned int cd_hddvd_rw; +static unsigned int cd_mo; +static unsigned int cd_mrw; +static unsigned int cd_mrw_w; + +/* media info */ +static unsigned int cd_media; +static unsigned int cd_media_cd_rom; +static unsigned int cd_media_cd_r; +static unsigned int cd_media_cd_rw; +static unsigned int cd_media_dvd_rom; +static unsigned int cd_media_dvd_r; +static unsigned int cd_media_dvd_rw; +static unsigned int cd_media_dvd_rw_ro; /* restricted overwrite mode */ +static unsigned int cd_media_dvd_rw_seq; /* sequential mode */ +static unsigned int cd_media_dvd_ram; +static unsigned int cd_media_dvd_plus_r; +static unsigned int cd_media_dvd_plus_rw; +static unsigned int cd_media_dvd_plus_r_dl; +static unsigned int cd_media_dvd_plus_rw_dl; +static unsigned int cd_media_bd; +static unsigned int cd_media_bd_r; +static unsigned int cd_media_bd_re; +static unsigned int cd_media_hddvd; +static unsigned int cd_media_hddvd_r; +static unsigned int cd_media_hddvd_rw; +static unsigned int cd_media_mo; +static unsigned int cd_media_mrw; +static unsigned int cd_media_mrw_w; + +static const char *cd_media_state = NULL; +static unsigned int cd_media_session_next; +static unsigned int cd_media_session_count; +static unsigned int cd_media_track_count; +static unsigned int cd_media_track_count_data; +static unsigned int cd_media_track_count_audio; +static unsigned long long int cd_media_session_last_offset; + +#define ERRCODE(s) ((((s)[2] & 0x0F) << 16) | ((s)[12] << 8) | ((s)[13])) +#define SK(errcode) (((errcode) >> 16) & 0xF) +#define ASC(errcode) (((errcode) >> 8) & 0xFF) +#define ASCQ(errcode) ((errcode) & 0xFF) + +static bool is_mounted(const char *device) +{ + struct stat statbuf; + FILE *fp; + int maj, min; + bool mounted = false; + + if (stat(device, &statbuf) < 0) + return false; + + fp = fopen("/proc/self/mountinfo", "re"); + if (fp == NULL) + return false; + while (fscanf(fp, "%*s %*s %i:%i %*[^\n]", &maj, &min) == 2) { + if (makedev(maj, min) == statbuf.st_rdev) { + mounted = true; + break; + } + } + fclose(fp); + return mounted; +} + +static void info_scsi_cmd_err(struct udev *udev, const char *cmd, int err) +{ + if (err == -1) { + log_debug("%s failed", cmd); + return; + } + log_debug("%s failed with SK=%Xh/ASC=%02Xh/ACQ=%02Xh", cmd, SK(err), ASC(err), ASCQ(err)); +} + +struct scsi_cmd { + struct cdrom_generic_command cgc; + union { + struct request_sense s; + unsigned char u[18]; + } _sense; + struct sg_io_hdr sg_io; +}; + +static void scsi_cmd_init(struct udev *udev, struct scsi_cmd *cmd) +{ + memzero(cmd, sizeof(struct scsi_cmd)); + cmd->cgc.quiet = 1; + cmd->cgc.sense = &cmd->_sense.s; + cmd->sg_io.interface_id = 'S'; + cmd->sg_io.mx_sb_len = sizeof(cmd->_sense); + cmd->sg_io.cmdp = cmd->cgc.cmd; + cmd->sg_io.sbp = cmd->_sense.u; + cmd->sg_io.flags = SG_FLAG_LUN_INHIBIT | SG_FLAG_DIRECT_IO; +} + +static void scsi_cmd_set(struct udev *udev, struct scsi_cmd *cmd, size_t i, unsigned char arg) +{ + cmd->sg_io.cmd_len = i + 1; + cmd->cgc.cmd[i] = arg; +} + +#define CHECK_CONDITION 0x01 + +static int scsi_cmd_run(struct udev *udev, struct scsi_cmd *cmd, int fd, unsigned char *buf, size_t bufsize) +{ + int ret = 0; + + if (bufsize > 0) { + cmd->sg_io.dxferp = buf; + cmd->sg_io.dxfer_len = bufsize; + cmd->sg_io.dxfer_direction = SG_DXFER_FROM_DEV; + } else { + cmd->sg_io.dxfer_direction = SG_DXFER_NONE; + } + if (ioctl(fd, SG_IO, &cmd->sg_io)) + return -1; + + if ((cmd->sg_io.info & SG_INFO_OK_MASK) != SG_INFO_OK) { + errno = EIO; + ret = -1; + if (cmd->sg_io.masked_status & CHECK_CONDITION) { + ret = ERRCODE(cmd->_sense.u); + if (ret == 0) + ret = -1; + } + } + return ret; +} + +static int media_lock(struct udev *udev, int fd, bool lock) +{ + int err; + + /* disable the kernel's lock logic */ + err = ioctl(fd, CDROM_CLEAR_OPTIONS, CDO_LOCK); + if (err < 0) + log_debug("CDROM_CLEAR_OPTIONS, CDO_LOCK failed"); + + err = ioctl(fd, CDROM_LOCKDOOR, lock ? 1 : 0); + if (err < 0) + log_debug("CDROM_LOCKDOOR failed"); + + return err; +} + +static int media_eject(struct udev *udev, int fd) +{ + struct scsi_cmd sc; + int err; + + scsi_cmd_init(udev, &sc); + scsi_cmd_set(udev, &sc, 0, 0x1b); + scsi_cmd_set(udev, &sc, 4, 0x02); + scsi_cmd_set(udev, &sc, 5, 0); + err = scsi_cmd_run(udev, &sc, fd, NULL, 0); + if ((err != 0)) { + info_scsi_cmd_err(udev, "START_STOP_UNIT", err); + return -1; + } + return 0; +} + +static int cd_capability_compat(struct udev *udev, int fd) +{ + int capability; + + capability = ioctl(fd, CDROM_GET_CAPABILITY, NULL); + if (capability < 0) { + log_debug("CDROM_GET_CAPABILITY failed"); + return -1; + } + + if (capability & CDC_CD_R) + cd_cd_r = 1; + if (capability & CDC_CD_RW) + cd_cd_rw = 1; + if (capability & CDC_DVD) + cd_dvd_rom = 1; + if (capability & CDC_DVD_R) + cd_dvd_r = 1; + if (capability & CDC_DVD_RAM) + cd_dvd_ram = 1; + if (capability & CDC_MRW) + cd_mrw = 1; + if (capability & CDC_MRW_W) + cd_mrw_w = 1; + return 0; +} + +static int cd_media_compat(struct udev *udev, int fd) +{ + if (ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT) != CDS_DISC_OK) { + log_debug("CDROM_DRIVE_STATUS != CDS_DISC_OK"); + return -1; + } + cd_media = 1; + return 0; +} + +static int cd_inquiry(struct udev *udev, int fd) +{ + struct scsi_cmd sc; + unsigned char inq[128]; + int err; + + scsi_cmd_init(udev, &sc); + scsi_cmd_set(udev, &sc, 0, 0x12); + scsi_cmd_set(udev, &sc, 4, 36); + scsi_cmd_set(udev, &sc, 5, 0); + err = scsi_cmd_run(udev, &sc, fd, inq, 36); + if ((err != 0)) { + info_scsi_cmd_err(udev, "INQUIRY", err); + return -1; + } + + if ((inq[0] & 0x1F) != 5) { + log_debug("not an MMC unit"); + return -1; + } + + log_debug("INQUIRY: [%.8s][%.16s][%.4s]", inq + 8, inq + 16, inq + 32); + return 0; +} + +static void feature_profile_media(struct udev *udev, int cur_profile) +{ + switch (cur_profile) { + case 0x03: + case 0x04: + case 0x05: + log_debug("profile 0x%02x ", cur_profile); + cd_media = 1; + cd_media_mo = 1; + break; + case 0x08: + log_debug("profile 0x%02x media_cd_rom", cur_profile); + cd_media = 1; + cd_media_cd_rom = 1; + break; + case 0x09: + log_debug("profile 0x%02x media_cd_r", cur_profile); + cd_media = 1; + cd_media_cd_r = 1; + break; + case 0x0a: + log_debug("profile 0x%02x media_cd_rw", cur_profile); + cd_media = 1; + cd_media_cd_rw = 1; + break; + case 0x10: + log_debug("profile 0x%02x media_dvd_ro", cur_profile); + cd_media = 1; + cd_media_dvd_rom = 1; + break; + case 0x11: + log_debug("profile 0x%02x media_dvd_r", cur_profile); + cd_media = 1; + cd_media_dvd_r = 1; + break; + case 0x12: + log_debug("profile 0x%02x media_dvd_ram", cur_profile); + cd_media = 1; + cd_media_dvd_ram = 1; + break; + case 0x13: + log_debug("profile 0x%02x media_dvd_rw_ro", cur_profile); + cd_media = 1; + cd_media_dvd_rw = 1; + cd_media_dvd_rw_ro = 1; + break; + case 0x14: + log_debug("profile 0x%02x media_dvd_rw_seq", cur_profile); + cd_media = 1; + cd_media_dvd_rw = 1; + cd_media_dvd_rw_seq = 1; + break; + case 0x1B: + log_debug("profile 0x%02x media_dvd_plus_r", cur_profile); + cd_media = 1; + cd_media_dvd_plus_r = 1; + break; + case 0x1A: + log_debug("profile 0x%02x media_dvd_plus_rw", cur_profile); + cd_media = 1; + cd_media_dvd_plus_rw = 1; + break; + case 0x2A: + log_debug("profile 0x%02x media_dvd_plus_rw_dl", cur_profile); + cd_media = 1; + cd_media_dvd_plus_rw_dl = 1; + break; + case 0x2B: + log_debug("profile 0x%02x media_dvd_plus_r_dl", cur_profile); + cd_media = 1; + cd_media_dvd_plus_r_dl = 1; + break; + case 0x40: + log_debug("profile 0x%02x media_bd", cur_profile); + cd_media = 1; + cd_media_bd = 1; + break; + case 0x41: + case 0x42: + log_debug("profile 0x%02x media_bd_r", cur_profile); + cd_media = 1; + cd_media_bd_r = 1; + break; + case 0x43: + log_debug("profile 0x%02x media_bd_re", cur_profile); + cd_media = 1; + cd_media_bd_re = 1; + break; + case 0x50: + log_debug("profile 0x%02x media_hddvd", cur_profile); + cd_media = 1; + cd_media_hddvd = 1; + break; + case 0x51: + log_debug("profile 0x%02x media_hddvd_r", cur_profile); + cd_media = 1; + cd_media_hddvd_r = 1; + break; + case 0x52: + log_debug("profile 0x%02x media_hddvd_rw", cur_profile); + cd_media = 1; + cd_media_hddvd_rw = 1; + break; + default: + log_debug("profile 0x%02x <ignored>", cur_profile); + break; + } +} + +static int feature_profiles(struct udev *udev, const unsigned char *profiles, size_t size) +{ + unsigned int i; + + for (i = 0; i+4 <= size; i += 4) { + int profile; + + profile = profiles[i] << 8 | profiles[i+1]; + switch (profile) { + case 0x03: + case 0x04: + case 0x05: + log_debug("profile 0x%02x mo", profile); + cd_mo = 1; + break; + case 0x08: + log_debug("profile 0x%02x cd_rom", profile); + cd_cd_rom = 1; + break; + case 0x09: + log_debug("profile 0x%02x cd_r", profile); + cd_cd_r = 1; + break; + case 0x0A: + log_debug("profile 0x%02x cd_rw", profile); + cd_cd_rw = 1; + break; + case 0x10: + log_debug("profile 0x%02x dvd_rom", profile); + cd_dvd_rom = 1; + break; + case 0x12: + log_debug("profile 0x%02x dvd_ram", profile); + cd_dvd_ram = 1; + break; + case 0x13: + case 0x14: + log_debug("profile 0x%02x dvd_rw", profile); + cd_dvd_rw = 1; + break; + case 0x1B: + log_debug("profile 0x%02x dvd_plus_r", profile); + cd_dvd_plus_r = 1; + break; + case 0x1A: + log_debug("profile 0x%02x dvd_plus_rw", profile); + cd_dvd_plus_rw = 1; + break; + case 0x2A: + log_debug("profile 0x%02x dvd_plus_rw_dl", profile); + cd_dvd_plus_rw_dl = 1; + break; + case 0x2B: + log_debug("profile 0x%02x dvd_plus_r_dl", profile); + cd_dvd_plus_r_dl = 1; + break; + case 0x40: + cd_bd = 1; + log_debug("profile 0x%02x bd", profile); + break; + case 0x41: + case 0x42: + cd_bd_r = 1; + log_debug("profile 0x%02x bd_r", profile); + break; + case 0x43: + cd_bd_re = 1; + log_debug("profile 0x%02x bd_re", profile); + break; + case 0x50: + cd_hddvd = 1; + log_debug("profile 0x%02x hddvd", profile); + break; + case 0x51: + cd_hddvd_r = 1; + log_debug("profile 0x%02x hddvd_r", profile); + break; + case 0x52: + cd_hddvd_rw = 1; + log_debug("profile 0x%02x hddvd_rw", profile); + break; + default: + log_debug("profile 0x%02x <ignored>", profile); + break; + } + } + return 0; +} + +/* returns 0 if media was detected */ +static int cd_profiles_old_mmc(struct udev *udev, int fd) +{ + struct scsi_cmd sc; + int err; + + unsigned char header[32]; + + scsi_cmd_init(udev, &sc); + scsi_cmd_set(udev, &sc, 0, 0x51); + scsi_cmd_set(udev, &sc, 8, sizeof(header)); + scsi_cmd_set(udev, &sc, 9, 0); + err = scsi_cmd_run(udev, &sc, fd, header, sizeof(header)); + if ((err != 0)) { + info_scsi_cmd_err(udev, "READ DISC INFORMATION", err); + if (cd_media == 1) { + log_debug("no current profile, but disc is present; assuming CD-ROM"); + cd_media_cd_rom = 1; + cd_media_track_count = 1; + cd_media_track_count_data = 1; + return 0; + } else { + log_debug("no current profile, assuming no media"); + return -1; + } + }; + + cd_media = 1; + + if (header[2] & 16) { + cd_media_cd_rw = 1; + log_debug("profile 0x0a media_cd_rw"); + } else if ((header[2] & 3) < 2 && cd_cd_r) { + cd_media_cd_r = 1; + log_debug("profile 0x09 media_cd_r"); + } else { + cd_media_cd_rom = 1; + log_debug("profile 0x08 media_cd_rom"); + } + return 0; +} + +/* returns 0 if media was detected */ +static int cd_profiles(struct udev *udev, int fd) +{ + struct scsi_cmd sc; + unsigned char features[65530]; + unsigned int cur_profile = 0; + unsigned int len; + unsigned int i; + int err; + int ret; + + ret = -1; + + /* First query the current profile */ + scsi_cmd_init(udev, &sc); + scsi_cmd_set(udev, &sc, 0, 0x46); + scsi_cmd_set(udev, &sc, 8, 8); + scsi_cmd_set(udev, &sc, 9, 0); + err = scsi_cmd_run(udev, &sc, fd, features, 8); + if ((err != 0)) { + info_scsi_cmd_err(udev, "GET CONFIGURATION", err); + /* handle pre-MMC2 drives which do not support GET CONFIGURATION */ + if (SK(err) == 0x5 && (ASC(err) == 0x20 || ASC(err) == 0x24)) { + log_debug("drive is pre-MMC2 and does not support 46h get configuration command"); + log_debug("trying to work around the problem"); + ret = cd_profiles_old_mmc(udev, fd); + } + goto out; + } + + cur_profile = features[6] << 8 | features[7]; + if (cur_profile > 0) { + log_debug("current profile 0x%02x", cur_profile); + feature_profile_media (udev, cur_profile); + ret = 0; /* we have media */ + } else { + log_debug("no current profile, assuming no media"); + } + + len = features[0] << 24 | features[1] << 16 | features[2] << 8 | features[3]; + log_debug("GET CONFIGURATION: size of features buffer 0x%04x", len); + + if (len > sizeof(features)) { + log_debug("can not get features in a single query, truncating"); + len = sizeof(features); + } else if (len <= 8) + len = sizeof(features); + + /* Now get the full feature buffer */ + scsi_cmd_init(udev, &sc); + scsi_cmd_set(udev, &sc, 0, 0x46); + scsi_cmd_set(udev, &sc, 7, ( len >> 8 ) & 0xff); + scsi_cmd_set(udev, &sc, 8, len & 0xff); + scsi_cmd_set(udev, &sc, 9, 0); + err = scsi_cmd_run(udev, &sc, fd, features, len); + if ((err != 0)) { + info_scsi_cmd_err(udev, "GET CONFIGURATION", err); + return -1; + } + + /* parse the length once more, in case the drive decided to have other features suddenly :) */ + len = features[0] << 24 | features[1] << 16 | features[2] << 8 | features[3]; + log_debug("GET CONFIGURATION: size of features buffer 0x%04x", len); + + if (len > sizeof(features)) { + log_debug("can not get features in a single query, truncating"); + len = sizeof(features); + } + + /* device features */ + for (i = 8; i+4 < len; i += (4 + features[i+3])) { + unsigned int feature; + + feature = features[i] << 8 | features[i+1]; + + switch (feature) { + case 0x00: + log_debug("GET CONFIGURATION: feature 'profiles', with %i entries", features[i+3] / 4); + feature_profiles(udev, &features[i]+4, MIN(features[i+3], len - i - 4)); + break; + default: + log_debug("GET CONFIGURATION: feature 0x%04x <ignored>, with 0x%02x bytes", feature, features[i+3]); + break; + } + } +out: + return ret; +} + +static int cd_media_info(struct udev *udev, int fd) +{ + struct scsi_cmd sc; + unsigned char header[32]; + static const char *media_status[] = { + "blank", + "appendable", + "complete", + "other" + }; + int err; + + scsi_cmd_init(udev, &sc); + scsi_cmd_set(udev, &sc, 0, 0x51); + scsi_cmd_set(udev, &sc, 8, sizeof(header) & 0xff); + scsi_cmd_set(udev, &sc, 9, 0); + err = scsi_cmd_run(udev, &sc, fd, header, sizeof(header)); + if ((err != 0)) { + info_scsi_cmd_err(udev, "READ DISC INFORMATION", err); + return -1; + }; + + cd_media = 1; + log_debug("disk type %02x", header[8]); + log_debug("hardware reported media status: %s", media_status[header[2] & 3]); + + /* exclude plain CDROM, some fake cdroms return 0 for "blank" media here */ + if (!cd_media_cd_rom) + cd_media_state = media_status[header[2] & 3]; + + /* fresh DVD-RW in restricted overwite mode reports itself as + * "appendable"; change it to "blank" to make it consistent with what + * gets reported after blanking, and what userspace expects */ + if (cd_media_dvd_rw_ro && (header[2] & 3) == 1) + cd_media_state = media_status[0]; + + /* DVD+RW discs (and DVD-RW in restricted mode) once formatted are + * always "complete", DVD-RAM are "other" or "complete" if the disc is + * write protected; we need to check the contents if it is blank */ + if ((cd_media_dvd_rw_ro || cd_media_dvd_plus_rw || cd_media_dvd_plus_rw_dl || cd_media_dvd_ram) && (header[2] & 3) > 1) { + unsigned char buffer[32 * 2048]; + unsigned char len; + int offset; + + if (cd_media_dvd_ram) { + /* a write protected dvd-ram may report "complete" status */ + + unsigned char dvdstruct[8]; + unsigned char format[12]; + + scsi_cmd_init(udev, &sc); + scsi_cmd_set(udev, &sc, 0, 0xAD); + scsi_cmd_set(udev, &sc, 7, 0xC0); + scsi_cmd_set(udev, &sc, 9, sizeof(dvdstruct)); + scsi_cmd_set(udev, &sc, 11, 0); + err = scsi_cmd_run(udev, &sc, fd, dvdstruct, sizeof(dvdstruct)); + if ((err != 0)) { + info_scsi_cmd_err(udev, "READ DVD STRUCTURE", err); + return -1; + } + if (dvdstruct[4] & 0x02) { + cd_media_state = media_status[2]; + log_debug("write-protected DVD-RAM media inserted"); + goto determined; + } + + /* let's make sure we don't try to read unformatted media */ + scsi_cmd_init(udev, &sc); + scsi_cmd_set(udev, &sc, 0, 0x23); + scsi_cmd_set(udev, &sc, 8, sizeof(format)); + scsi_cmd_set(udev, &sc, 9, 0); + err = scsi_cmd_run(udev, &sc, fd, format, sizeof(format)); + if ((err != 0)) { + info_scsi_cmd_err(udev, "READ DVD FORMAT CAPACITIES", err); + return -1; + } + + len = format[3]; + if (len & 7 || len < 16) { + log_debug("invalid format capacities length"); + return -1; + } + + switch(format[8] & 3) { + case 1: + log_debug("unformatted DVD-RAM media inserted"); + /* This means that last format was interrupted + * or failed, blank dvd-ram discs are factory + * formatted. Take no action here as it takes + * quite a while to reformat a dvd-ram and it's + * not automatically started */ + goto determined; + + case 2: + log_debug("formatted DVD-RAM media inserted"); + break; + + case 3: + cd_media = 0; //return no media + log_debug("format capacities returned no media"); + return -1; + } + } + + /* Take a closer look at formatted media (unformatted DVD+RW + * has "blank" status", DVD-RAM was examined earlier) and check + * for ISO and UDF PVDs or a fs superblock presence and do it + * in one ioctl (we need just sectors 0 and 16) */ + scsi_cmd_init(udev, &sc); + scsi_cmd_set(udev, &sc, 0, 0x28); + scsi_cmd_set(udev, &sc, 5, 0); + scsi_cmd_set(udev, &sc, 8, 32); + scsi_cmd_set(udev, &sc, 9, 0); + err = scsi_cmd_run(udev, &sc, fd, buffer, sizeof(buffer)); + if ((err != 0)) { + cd_media = 0; + info_scsi_cmd_err(udev, "READ FIRST 32 BLOCKS", err); + return -1; + } + + /* if any non-zero data is found in sector 16 (iso and udf) or + * eventually 0 (fat32 boot sector, ext2 superblock, etc), disc + * is assumed non-blank */ + + for (offset = 32768; offset < (32768 + 2048); offset++) { + if (buffer [offset]) { + log_debug("data in block 16, assuming complete"); + goto determined; + } + } + + for (offset = 0; offset < 2048; offset++) { + if (buffer [offset]) { + log_debug("data in block 0, assuming complete"); + goto determined; + } + } + + cd_media_state = media_status[0]; + log_debug("no data in blocks 0 or 16, assuming blank"); + } + +determined: + /* "other" is e. g. DVD-RAM, can't append sessions there; DVDs in + * restricted overwrite mode can never append, only in sequential mode */ + if ((header[2] & 3) < 2 && !cd_media_dvd_rw_ro) + cd_media_session_next = header[10] << 8 | header[5]; + cd_media_session_count = header[9] << 8 | header[4]; + cd_media_track_count = header[11] << 8 | header[6]; + + return 0; +} + +static int cd_media_toc(struct udev *udev, int fd) +{ + struct scsi_cmd sc; + unsigned char header[12]; + unsigned char toc[65536]; + unsigned int len, i, num_tracks; + unsigned char *p; + int err; + + scsi_cmd_init(udev, &sc); + scsi_cmd_set(udev, &sc, 0, 0x43); + scsi_cmd_set(udev, &sc, 6, 1); + scsi_cmd_set(udev, &sc, 8, sizeof(header) & 0xff); + scsi_cmd_set(udev, &sc, 9, 0); + err = scsi_cmd_run(udev, &sc, fd, header, sizeof(header)); + if ((err != 0)) { + info_scsi_cmd_err(udev, "READ TOC", err); + return -1; + } + + len = (header[0] << 8 | header[1]) + 2; + log_debug("READ TOC: len: %d, start track: %d, end track: %d", len, header[2], header[3]); + if (len > sizeof(toc)) + return -1; + if (len < 2) + return -1; + /* 2: first track, 3: last track */ + num_tracks = header[3] - header[2] + 1; + + /* empty media has no tracks */ + if (len < 8) + return 0; + + scsi_cmd_init(udev, &sc); + scsi_cmd_set(udev, &sc, 0, 0x43); + scsi_cmd_set(udev, &sc, 6, header[2]); /* First Track/Session Number */ + scsi_cmd_set(udev, &sc, 7, (len >> 8) & 0xff); + scsi_cmd_set(udev, &sc, 8, len & 0xff); + scsi_cmd_set(udev, &sc, 9, 0); + err = scsi_cmd_run(udev, &sc, fd, toc, len); + if ((err != 0)) { + info_scsi_cmd_err(udev, "READ TOC (tracks)", err); + return -1; + } + + /* Take care to not iterate beyond the last valid track as specified in + * the TOC, but also avoid going beyond the TOC length, just in case + * the last track number is invalidly large */ + for (p = toc+4, i = 4; i < len-8 && num_tracks > 0; i += 8, p += 8, --num_tracks) { + unsigned int block; + unsigned int is_data_track; + + is_data_track = (p[1] & 0x04) != 0; + + block = p[4] << 24 | p[5] << 16 | p[6] << 8 | p[7]; + log_debug("track=%u info=0x%x(%s) start_block=%u", + p[2], p[1] & 0x0f, is_data_track ? "data":"audio", block); + + if (is_data_track) + cd_media_track_count_data++; + else + cd_media_track_count_audio++; + } + + scsi_cmd_init(udev, &sc); + scsi_cmd_set(udev, &sc, 0, 0x43); + scsi_cmd_set(udev, &sc, 2, 1); /* Session Info */ + scsi_cmd_set(udev, &sc, 8, sizeof(header)); + scsi_cmd_set(udev, &sc, 9, 0); + err = scsi_cmd_run(udev, &sc, fd, header, sizeof(header)); + if ((err != 0)) { + info_scsi_cmd_err(udev, "READ TOC (multi session)", err); + return -1; + } + len = header[4+4] << 24 | header[4+5] << 16 | header[4+6] << 8 | header[4+7]; + log_debug("last track %u starts at block %u", header[4+2], len); + cd_media_session_last_offset = (unsigned long long int)len * 2048; + return 0; +} + +int main(int argc, char *argv[]) +{ + struct udev *udev; + static const struct option options[] = { + { "lock-media", no_argument, NULL, 'l' }, + { "unlock-media", no_argument, NULL, 'u' }, + { "eject-media", no_argument, NULL, 'e' }, + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + bool eject = false; + bool lock = false; + bool unlock = false; + const char *node = NULL; + int fd = -1; + int cnt; + int rc = 0; + + log_parse_environment(); + log_open(); + + udev = udev_new(); + if (udev == NULL) + goto exit; + + for (;;) { + int option; + + option = getopt_long(argc, argv, "deluh", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'l': + lock = true; + break; + case 'u': + unlock = true; + break; + case 'e': + eject = true; + break; + case 'd': + log_set_target(LOG_TARGET_CONSOLE); + log_set_max_level(LOG_DEBUG); + log_open(); + break; + case 'h': + printf("Usage: cdrom_id [options] <device>\n" + " -l,--lock-media lock the media (to enable eject request events)\n" + " -u,--unlock-media unlock the media\n" + " -e,--eject-media eject the media\n" + " -d,--debug debug to stderr\n" + " -h,--help print this help text\n\n"); + goto exit; + default: + rc = 1; + goto exit; + } + } + + node = argv[optind]; + if (!node) { + log_error("no device"); + fprintf(stderr, "no device\n"); + rc = 1; + goto exit; + } + + initialize_srand(); + for (cnt = 20; cnt > 0; cnt--) { + struct timespec duration; + + fd = open(node, O_RDONLY|O_NONBLOCK|O_CLOEXEC|(is_mounted(node) ? 0 : O_EXCL)); + if (fd >= 0 || errno != EBUSY) + break; + duration.tv_sec = 0; + duration.tv_nsec = (100 * 1000 * 1000) + (rand() % 100 * 1000 * 1000); + nanosleep(&duration, NULL); + } + if (fd < 0) { + log_debug("unable to open '%s'", node); + fprintf(stderr, "unable to open '%s'\n", node); + rc = 1; + goto exit; + } + log_debug("probing: '%s'", node); + + /* same data as original cdrom_id */ + if (cd_capability_compat(udev, fd) < 0) { + rc = 1; + goto exit; + } + + /* check for media - don't bail if there's no media as we still need to + * to read profiles */ + cd_media_compat(udev, fd); + + /* check if drive talks MMC */ + if (cd_inquiry(udev, fd) < 0) + goto work; + + /* read drive and possibly current profile */ + if (cd_profiles(udev, fd) != 0) + goto work; + + /* at this point we are guaranteed to have media in the drive - find out more about it */ + + /* get session/track info */ + cd_media_toc(udev, fd); + + /* get writable media state */ + cd_media_info(udev, fd); + +work: + /* lock the media, so we enable eject button events */ + if (lock && cd_media) { + log_debug("PREVENT_ALLOW_MEDIUM_REMOVAL (lock)"); + media_lock(udev, fd, true); + } + + if (unlock && cd_media) { + log_debug("PREVENT_ALLOW_MEDIUM_REMOVAL (unlock)"); + media_lock(udev, fd, false); + } + + if (eject) { + log_debug("PREVENT_ALLOW_MEDIUM_REMOVAL (unlock)"); + media_lock(udev, fd, false); + log_debug("START_STOP_UNIT (eject)"); + media_eject(udev, fd); + } + + printf("ID_CDROM=1\n"); + if (cd_cd_rom) + printf("ID_CDROM_CD=1\n"); + if (cd_cd_r) + printf("ID_CDROM_CD_R=1\n"); + if (cd_cd_rw) + printf("ID_CDROM_CD_RW=1\n"); + if (cd_dvd_rom) + printf("ID_CDROM_DVD=1\n"); + if (cd_dvd_r) + printf("ID_CDROM_DVD_R=1\n"); + if (cd_dvd_rw) + printf("ID_CDROM_DVD_RW=1\n"); + if (cd_dvd_ram) + printf("ID_CDROM_DVD_RAM=1\n"); + if (cd_dvd_plus_r) + printf("ID_CDROM_DVD_PLUS_R=1\n"); + if (cd_dvd_plus_rw) + printf("ID_CDROM_DVD_PLUS_RW=1\n"); + if (cd_dvd_plus_r_dl) + printf("ID_CDROM_DVD_PLUS_R_DL=1\n"); + if (cd_dvd_plus_rw_dl) + printf("ID_CDROM_DVD_PLUS_RW_DL=1\n"); + if (cd_bd) + printf("ID_CDROM_BD=1\n"); + if (cd_bd_r) + printf("ID_CDROM_BD_R=1\n"); + if (cd_bd_re) + printf("ID_CDROM_BD_RE=1\n"); + if (cd_hddvd) + printf("ID_CDROM_HDDVD=1\n"); + if (cd_hddvd_r) + printf("ID_CDROM_HDDVD_R=1\n"); + if (cd_hddvd_rw) + printf("ID_CDROM_HDDVD_RW=1\n"); + if (cd_mo) + printf("ID_CDROM_MO=1\n"); + if (cd_mrw) + printf("ID_CDROM_MRW=1\n"); + if (cd_mrw_w) + printf("ID_CDROM_MRW_W=1\n"); + + if (cd_media) + printf("ID_CDROM_MEDIA=1\n"); + if (cd_media_mo) + printf("ID_CDROM_MEDIA_MO=1\n"); + if (cd_media_mrw) + printf("ID_CDROM_MEDIA_MRW=1\n"); + if (cd_media_mrw_w) + printf("ID_CDROM_MEDIA_MRW_W=1\n"); + if (cd_media_cd_rom) + printf("ID_CDROM_MEDIA_CD=1\n"); + if (cd_media_cd_r) + printf("ID_CDROM_MEDIA_CD_R=1\n"); + if (cd_media_cd_rw) + printf("ID_CDROM_MEDIA_CD_RW=1\n"); + if (cd_media_dvd_rom) + printf("ID_CDROM_MEDIA_DVD=1\n"); + if (cd_media_dvd_r) + printf("ID_CDROM_MEDIA_DVD_R=1\n"); + if (cd_media_dvd_ram) + printf("ID_CDROM_MEDIA_DVD_RAM=1\n"); + if (cd_media_dvd_rw) + printf("ID_CDROM_MEDIA_DVD_RW=1\n"); + if (cd_media_dvd_plus_r) + printf("ID_CDROM_MEDIA_DVD_PLUS_R=1\n"); + if (cd_media_dvd_plus_rw) + printf("ID_CDROM_MEDIA_DVD_PLUS_RW=1\n"); + if (cd_media_dvd_plus_rw_dl) + printf("ID_CDROM_MEDIA_DVD_PLUS_RW_DL=1\n"); + if (cd_media_dvd_plus_r_dl) + printf("ID_CDROM_MEDIA_DVD_PLUS_R_DL=1\n"); + if (cd_media_bd) + printf("ID_CDROM_MEDIA_BD=1\n"); + if (cd_media_bd_r) + printf("ID_CDROM_MEDIA_BD_R=1\n"); + if (cd_media_bd_re) + printf("ID_CDROM_MEDIA_BD_RE=1\n"); + if (cd_media_hddvd) + printf("ID_CDROM_MEDIA_HDDVD=1\n"); + if (cd_media_hddvd_r) + printf("ID_CDROM_MEDIA_HDDVD_R=1\n"); + if (cd_media_hddvd_rw) + printf("ID_CDROM_MEDIA_HDDVD_RW=1\n"); + + if (cd_media_state != NULL) + printf("ID_CDROM_MEDIA_STATE=%s\n", cd_media_state); + if (cd_media_session_next > 0) + printf("ID_CDROM_MEDIA_SESSION_NEXT=%u\n", cd_media_session_next); + if (cd_media_session_count > 0) + printf("ID_CDROM_MEDIA_SESSION_COUNT=%u\n", cd_media_session_count); + if (cd_media_session_count > 1 && cd_media_session_last_offset > 0) + printf("ID_CDROM_MEDIA_SESSION_LAST_OFFSET=%llu\n", cd_media_session_last_offset); + if (cd_media_track_count > 0) + printf("ID_CDROM_MEDIA_TRACK_COUNT=%u\n", cd_media_track_count); + if (cd_media_track_count_audio > 0) + printf("ID_CDROM_MEDIA_TRACK_COUNT_AUDIO=%u\n", cd_media_track_count_audio); + if (cd_media_track_count_data > 0) + printf("ID_CDROM_MEDIA_TRACK_COUNT_DATA=%u\n", cd_media_track_count_data); +exit: + if (fd >= 0) + close(fd); + udev_unref(udev); + log_close(); + return rc; +} diff --git a/src/grp-udev/collect/Makefile b/src/grp-udev/collect/Makefile new file mode 100644 index 0000000000..60af3b7627 --- /dev/null +++ b/src/grp-udev/collect/Makefile @@ -0,0 +1,35 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +collect_SOURCES = \ + src/udev/collect/collect.c + +collect_LDADD = \ + libshared.la + +udevlibexec_PROGRAMS += \ + collect + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-udev/collect/collect.c b/src/grp-udev/collect/collect.c new file mode 100644 index 0000000000..349585b634 --- /dev/null +++ b/src/grp-udev/collect/collect.c @@ -0,0 +1,491 @@ +/* + * 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 <errno.h> +#include <getopt.h> +#include <stddef.h> +#include <stdio.h> + +#include "alloc-util.h" +#include "libudev-private.h" +#include "macro.h" +#include "stdio-util.h" +#include "string-util.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("%s [options] <checkpoint> <id> <idlist>\n\n" + "Collect variables across events.\n\n" + " -h --help Print this message\n" + " -a --add Add ID <id> to the list <idlist>\n" + " -r --remove Remove ID <id> from the list <idlist>\n" + " -d --debug Debug to stderr\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" + , program_invocation_short_name); +} + +/* + * prepare + * + * Prepares the database file + */ +static int prepare(char *dir, char *filename) +{ + char buf[512]; + int r, fd; + + r = mkdir(dir, 0700); + if (r < 0 && errno != EEXIST) + return -errno; + + xsprintf(buf, "%s/%s", dir, filename); + + fd = open(buf,O_RDWR|O_CREAT|O_CLOEXEC, S_IRUSR|S_IWUSR); + if (fd < 0) + fprintf(stderr, "Cannot open %s: %m\n", buf); + + 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: %m\n", buf); + } + } + + 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 = malloc(bufsize + 1); + if (!buf) + return log_oom(); + memset(buf, ' ', bufsize); + buf[bufsize] = '\0'; + + 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 %zu\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 (streq(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 (streq(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 command line. + */ +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; + } + + for (;;) { + 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: %m\n"); + ret = 2; + goto exit; + } + + udev_list_node_init(&bunch); + + if (debug) + fprintf(stderr, "Using checkpoint '%s'\n", checkpoint); + + 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 (streq(him->name, argv[i])) + who = him; + } + if (!who) { + struct _mate *him; + + if (debug) + fprintf(stderr, "ID %s: not in database\n", argv[i]); + him = new(struct _mate, 1); + if (!him) { + ret = ENOMEM; + goto out; + } + + him->name = strdup(argv[i]); + if (!him->name) { + free(him); + ret = ENOMEM; + goto out; + } + + 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/grp-udev/libudev-core/Makefile b/src/grp-udev/libudev-core/Makefile new file mode 100644 index 0000000000..e95d53120a --- /dev/null +++ b/src/grp-udev/libudev-core/Makefile @@ -0,0 +1,101 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +noinst_LTLIBRARIES += \ + libudev-core.la + +$(outdir)/keyboard-keys-list.txt: + $(AM_V_GEN)$(CPP) $(ALL_CPPFLAGS) -dM -include linux/input.h - < /dev/null | $(AWK) '/^#define[ \t]+KEY_[^ ]+[ \t]+[0-9K]/ { if ($$2 != "KEY_MAX") { print $$2 } }' > $@ + +$(outdir)/keyboard-keys-from-name.gperf: $(outdir)/keyboard-keys-list.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "struct key { const char* name; unsigned short id; };"; print "%null-strings"; print "%%";} { print tolower(substr($$1 ,5)) ", " $$1 }' < $< > $@ + +$(outdir)/keyboard-keys-from-name.h: $(outdir)/keyboard-keys-from-name.gperf + $(AM_V_GPERF)$(GPERF) -L ANSI-C -t -N keyboard_lookup_key -H hash_key_name -p -C < $< > $@ + +gperf_txt_sources += \ + src/udev/keyboard-keys-list.txt + +libudev_core_la_SOURCES = \ + src/udev/udev.h \ + src/udev/udev-event.c \ + src/udev/udev-watch.c \ + src/udev/udev-node.c \ + src/udev/udev-rules.c \ + src/udev/udev-ctrl.c \ + src/udev/udev-builtin.c \ + src/udev/udev-builtin-btrfs.c \ + src/udev/udev-builtin-hwdb.c \ + src/udev/udev-builtin-input_id.c \ + src/udev/udev-builtin-keyboard.c \ + src/udev/udev-builtin-net_id.c \ + src/udev/udev-builtin-net_setup_link.c \ + src/udev/udev-builtin-path_id.c \ + src/udev/udev-builtin-usb_id.c \ + src/udev/net/link-config.h \ + src/udev/net/link-config.c \ + src/udev/net/ethtool-util.h \ + src/udev/net/ethtool-util.c + +nodist_libudev_core_la_SOURCES = \ + src/udev/keyboard-keys-from-name.h \ + src/udev/net/link-config-gperf.c + +gperf_gperf_sources += \ + src/udev/net/link-config-gperf.gperf + +libudev_core_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(BLKID_CFLAGS) \ + $(KMOD_CFLAGS) + +libudev_core_la_LIBADD = \ + libsystemd-network.la \ + libshared.la \ + $(BLKID_LIBS) \ + $(KMOD_LIBS) + +ifneq ($(HAVE_KMOD),) +libudev_core_la_SOURCES += \ + src/udev/udev-builtin-kmod.c + +dist_udevrules_DATA += \ + rules/80-drivers.rules +endif # HAVE_KMOD + +ifneq ($(HAVE_BLKID),) +libudev_core_la_SOURCES += \ + src/udev/udev-builtin-blkid.c +endif # HAVE_BLKID + +ifneq ($(HAVE_ACL),) +libudev_core_la_SOURCES += \ + src/udev/udev-builtin-uaccess.c \ + src/login/logind-acl.c \ + src/libsystemd/sd-login/sd-login.c \ + src/systemd/sd-login.h +endif # HAVE_ACL + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-udev/libudev-core/net/.gitignore b/src/grp-udev/libudev-core/net/.gitignore new file mode 100644 index 0000000000..9ca85bacc9 --- /dev/null +++ b/src/grp-udev/libudev-core/net/.gitignore @@ -0,0 +1 @@ +/link-config-gperf.c diff --git a/src/grp-udev/libudev-core/net/ethtool-util.c b/src/grp-udev/libudev-core/net/ethtool-util.c new file mode 100644 index 0000000000..c00ff79123 --- /dev/null +++ b/src/grp-udev/libudev-core/net/ethtool-util.c @@ -0,0 +1,208 @@ +/*** + This file is part of systemd. + + Copyright (C) 2013 Tom Gundersen <teg@jklm.no> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <net/if.h> +#include <sys/ioctl.h> +#include <linux/ethtool.h> +#include <linux/sockios.h> + +#include "conf-parser.h" +#include "ethtool-util.h" +#include "log.h" +#include "string-table.h" +#include "strxcpyx.h" +#include "util.h" + +static const char* const duplex_table[_DUP_MAX] = { + [DUP_FULL] = "full", + [DUP_HALF] = "half" +}; + +DEFINE_STRING_TABLE_LOOKUP(duplex, Duplex); +DEFINE_CONFIG_PARSE_ENUM(config_parse_duplex, duplex, Duplex, "Failed to parse duplex setting"); + +static const char* const wol_table[_WOL_MAX] = { + [WOL_PHY] = "phy", + [WOL_MAGIC] = "magic", + [WOL_OFF] = "off" +}; + +DEFINE_STRING_TABLE_LOOKUP(wol, WakeOnLan); +DEFINE_CONFIG_PARSE_ENUM(config_parse_wol, wol, WakeOnLan, "Failed to parse WakeOnLan setting"); + +int ethtool_connect(int *ret) { + int fd; + + assert_return(ret, -EINVAL); + + fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd < 0) + return -errno; + + *ret = fd; + + return 0; +} + +int ethtool_get_driver(int *fd, const char *ifname, char **ret) { + struct ethtool_drvinfo ecmd = { + .cmd = ETHTOOL_GDRVINFO + }; + struct ifreq ifr = { + .ifr_data = (void*) &ecmd + }; + char *d; + int r; + + if (*fd < 0) { + r = ethtool_connect(fd); + if (r < 0) + return log_warning_errno(r, "link_config: could not connect to ethtool: %m"); + } + + strscpy(ifr.ifr_name, IFNAMSIZ, ifname); + + r = ioctl(*fd, SIOCETHTOOL, &ifr); + if (r < 0) + return -errno; + + d = strdup(ecmd.driver); + if (!d) + return -ENOMEM; + + *ret = d; + return 0; +} + +int ethtool_set_speed(int *fd, const char *ifname, unsigned int speed, Duplex duplex) { + struct ethtool_cmd ecmd = { + .cmd = ETHTOOL_GSET + }; + struct ifreq ifr = { + .ifr_data = (void*) &ecmd + }; + bool need_update = false; + int r; + + if (speed == 0 && duplex == _DUP_INVALID) + return 0; + + if (*fd < 0) { + r = ethtool_connect(fd); + if (r < 0) + return log_warning_errno(r, "link_config: could not connect to ethtool: %m"); + } + + strscpy(ifr.ifr_name, IFNAMSIZ, ifname); + + r = ioctl(*fd, SIOCETHTOOL, &ifr); + if (r < 0) + return -errno; + + if (ethtool_cmd_speed(&ecmd) != speed) { + ethtool_cmd_speed_set(&ecmd, speed); + need_update = true; + } + + switch (duplex) { + case DUP_HALF: + if (ecmd.duplex != DUPLEX_HALF) { + ecmd.duplex = DUPLEX_HALF; + need_update = true; + } + break; + case DUP_FULL: + if (ecmd.duplex != DUPLEX_FULL) { + ecmd.duplex = DUPLEX_FULL; + need_update = true; + } + break; + default: + break; + } + + if (need_update) { + ecmd.cmd = ETHTOOL_SSET; + + r = ioctl(*fd, SIOCETHTOOL, &ifr); + if (r < 0) + return -errno; + } + + return 0; +} + +int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol) { + struct ethtool_wolinfo ecmd = { + .cmd = ETHTOOL_GWOL + }; + struct ifreq ifr = { + .ifr_data = (void*) &ecmd + }; + bool need_update = false; + int r; + + if (wol == _WOL_INVALID) + return 0; + + if (*fd < 0) { + r = ethtool_connect(fd); + if (r < 0) + return log_warning_errno(r, "link_config: could not connect to ethtool: %m"); + } + + strscpy(ifr.ifr_name, IFNAMSIZ, ifname); + + r = ioctl(*fd, SIOCETHTOOL, &ifr); + if (r < 0) + return -errno; + + switch (wol) { + case WOL_PHY: + if (ecmd.wolopts != WAKE_PHY) { + ecmd.wolopts = WAKE_PHY; + need_update = true; + } + break; + case WOL_MAGIC: + if (ecmd.wolopts != WAKE_MAGIC) { + ecmd.wolopts = WAKE_MAGIC; + need_update = true; + } + break; + case WOL_OFF: + if (ecmd.wolopts != 0) { + ecmd.wolopts = 0; + need_update = true; + } + break; + default: + break; + } + + if (need_update) { + ecmd.cmd = ETHTOOL_SWOL; + + r = ioctl(*fd, SIOCETHTOOL, &ifr); + if (r < 0) + return -errno; + } + + return 0; +} diff --git a/src/grp-udev/libudev-core/net/ethtool-util.h b/src/grp-udev/libudev-core/net/ethtool-util.h new file mode 100644 index 0000000000..7716516e76 --- /dev/null +++ b/src/grp-udev/libudev-core/net/ethtool-util.h @@ -0,0 +1,54 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2013 Tom Gundersen <teg@jklm.no> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <macro.h> + +/* we can't use DUPLEX_ prefix, as it + * clashes with <linux/ethtool.h> */ +typedef enum Duplex { + DUP_FULL, + DUP_HALF, + _DUP_MAX, + _DUP_INVALID = -1 +} Duplex; + +typedef enum WakeOnLan { + WOL_PHY, + WOL_MAGIC, + WOL_OFF, + _WOL_MAX, + _WOL_INVALID = -1 +} WakeOnLan; + +int ethtool_connect(int *ret); + +int ethtool_get_driver(int *fd, const char *ifname, char **ret); +int ethtool_set_speed(int *fd, const char *ifname, unsigned int speed, Duplex duplex); +int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol); + +const char *duplex_to_string(Duplex d) _const_; +Duplex duplex_from_string(const char *d) _pure_; + +const char *wol_to_string(WakeOnLan wol) _const_; +WakeOnLan wol_from_string(const char *wol) _pure_; + +int config_parse_duplex(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_wol(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/grp-udev/libudev-core/net/link-config-gperf.gperf b/src/grp-udev/libudev-core/net/link-config-gperf.gperf new file mode 100644 index 0000000000..b25e4b3344 --- /dev/null +++ b/src/grp-udev/libudev-core/net/link-config-gperf.gperf @@ -0,0 +1,37 @@ +%{ +#include <stddef.h> +#include "conf-parser.h" +#include "network-internal.h" +#include "link-config.h" +#include "ethtool-util.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name link_config_gperf_hash +%define lookup-function-name link_config_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +Match.MACAddress, config_parse_hwaddr, 0, offsetof(link_config, match_mac) +Match.OriginalName, config_parse_ifnames, 0, offsetof(link_config, match_name) +Match.Path, config_parse_strv, 0, offsetof(link_config, match_path) +Match.Driver, config_parse_strv, 0, offsetof(link_config, match_driver) +Match.Type, config_parse_strv, 0, offsetof(link_config, match_type) +Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(link_config, match_host) +Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(link_config, match_virt) +Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(link_config, match_kernel) +Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(link_config, match_arch) +Link.Description, config_parse_string, 0, offsetof(link_config, description) +Link.MACAddressPolicy, config_parse_mac_policy, 0, offsetof(link_config, mac_policy) +Link.MACAddress, config_parse_hwaddr, 0, offsetof(link_config, mac) +Link.NamePolicy, config_parse_name_policy, 0, offsetof(link_config, name_policy) +Link.Name, config_parse_ifname, 0, offsetof(link_config, name) +Link.Alias, config_parse_ifalias, 0, offsetof(link_config, alias) +Link.MTUBytes, config_parse_iec_size, 0, offsetof(link_config, mtu) +Link.BitsPerSecond, config_parse_si_size, 0, offsetof(link_config, speed) +Link.Duplex, config_parse_duplex, 0, offsetof(link_config, duplex) +Link.WakeOnLan, config_parse_wol, 0, offsetof(link_config, wol) diff --git a/src/grp-udev/libudev-core/net/link-config.c b/src/grp-udev/libudev-core/net/link-config.c new file mode 100644 index 0000000000..350cd24e9c --- /dev/null +++ b/src/grp-udev/libudev-core/net/link-config.c @@ -0,0 +1,519 @@ +/*** + This file is part of systemd. + + Copyright (C) 2013 Tom Gundersen <teg@jklm.no> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <netinet/ether.h> + +#include <systemd/sd-netlink.h> + +#include "alloc-util.h" +#include "conf-files.h" +#include "conf-parser.h" +#include "ethtool-util.h" +#include "fd-util.h" +#include "libudev-private.h" +#include "link-config.h" +#include "log.h" +#include "missing.h" +#include "netlink-util.h" +#include "network-internal.h" +#include "parse-util.h" +#include "path-util.h" +#include "proc-cmdline.h" +#include "random-util.h" +#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +struct link_config_ctx { + LIST_HEAD(link_config, links); + + int ethtool_fd; + + bool enable_name_policy; + + sd_netlink *rtnl; + + usec_t link_dirs_ts_usec; +}; + +static const char* const link_dirs[] = { + "/etc/systemd/network", + "/run/systemd/network", + "/usr/lib/systemd/network", +#ifdef HAVE_SPLIT_USR + "/lib/systemd/network", +#endif + NULL}; + +static void link_config_free(link_config *link) { + if (!link) + return; + + free(link->filename); + + free(link->match_mac); + strv_free(link->match_path); + strv_free(link->match_driver); + strv_free(link->match_type); + free(link->match_name); + free(link->match_host); + free(link->match_virt); + free(link->match_kernel); + free(link->match_arch); + + free(link->description); + free(link->mac); + free(link->name_policy); + free(link->name); + free(link->alias); + + free(link); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(link_config*, link_config_free); + +static void link_configs_free(link_config_ctx *ctx) { + link_config *link, *link_next; + + if (!ctx) + return; + + LIST_FOREACH_SAFE(links, link, link_next, ctx->links) + link_config_free(link); +} + +void link_config_ctx_free(link_config_ctx *ctx) { + if (!ctx) + return; + + safe_close(ctx->ethtool_fd); + + sd_netlink_unref(ctx->rtnl); + + link_configs_free(ctx); + + free(ctx); + + return; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(link_config_ctx*, link_config_ctx_free); + +int link_config_ctx_new(link_config_ctx **ret) { + _cleanup_(link_config_ctx_freep) link_config_ctx *ctx = NULL; + + if (!ret) + return -EINVAL; + + ctx = new0(link_config_ctx, 1); + if (!ctx) + return -ENOMEM; + + LIST_HEAD_INIT(ctx->links); + + ctx->ethtool_fd = -1; + + ctx->enable_name_policy = true; + + *ret = ctx; + ctx = NULL; + + return 0; +} + +static int load_link(link_config_ctx *ctx, const char *filename) { + _cleanup_(link_config_freep) link_config *link = NULL; + _cleanup_fclose_ FILE *file = NULL; + int r; + + assert(ctx); + assert(filename); + + file = fopen(filename, "re"); + if (!file) { + if (errno == ENOENT) + return 0; + else + return -errno; + } + + if (null_or_empty_fd(fileno(file))) { + log_debug("Skipping empty file: %s", filename); + return 0; + } + + link = new0(link_config, 1); + if (!link) + return log_oom(); + + link->mac_policy = _MACPOLICY_INVALID; + link->wol = _WOL_INVALID; + link->duplex = _DUP_INVALID; + + r = config_parse(NULL, filename, file, + "Match\0Link\0Ethernet\0", + config_item_perf_lookup, link_config_gperf_lookup, + false, false, true, link); + if (r < 0) + return r; + else + log_debug("Parsed configuration file %s", filename); + + if (link->mtu > UINT_MAX || link->speed > UINT_MAX) + return -ERANGE; + + link->filename = strdup(filename); + + LIST_PREPEND(links, ctx->links, link); + link = NULL; + + return 0; +} + +static bool enable_name_policy(void) { + _cleanup_free_ char *line = NULL; + const char *word, *state; + int r; + size_t l; + + r = proc_cmdline(&line); + if (r < 0) { + log_warning_errno(r, "Failed to read /proc/cmdline, ignoring: %m"); + return true; + } + + FOREACH_WORD_QUOTED(word, l, line, state) + if (strneq(word, "net.ifnames=0", l)) + return false; + + return true; +} + +int link_config_load(link_config_ctx *ctx) { + int r; + _cleanup_strv_free_ char **files; + char **f; + + link_configs_free(ctx); + + if (!enable_name_policy()) { + ctx->enable_name_policy = false; + log_info("Network interface NamePolicy= disabled on kernel command line, ignoring."); + } + + /* update timestamp */ + paths_check_timestamp(link_dirs, &ctx->link_dirs_ts_usec, true); + + r = conf_files_list_strv(&files, ".link", NULL, link_dirs); + if (r < 0) + return log_error_errno(r, "failed to enumerate link files: %m"); + + STRV_FOREACH_BACKWARDS(f, files) { + r = load_link(ctx, *f); + if (r < 0) + return r; + } + + return 0; +} + +bool link_config_should_reload(link_config_ctx *ctx) { + return paths_check_timestamp(link_dirs, &ctx->link_dirs_ts_usec, false); +} + +int link_config_get(link_config_ctx *ctx, struct udev_device *device, + link_config **ret) { + link_config *link; + + assert(ctx); + assert(device); + assert(ret); + + LIST_FOREACH(links, link, ctx->links) { + const char* attr_value; + + attr_value = udev_device_get_sysattr_value(device, "address"); + + if (net_match_config(link->match_mac, link->match_path, link->match_driver, + link->match_type, link->match_name, link->match_host, + link->match_virt, link->match_kernel, link->match_arch, + attr_value ? ether_aton(attr_value) : NULL, + udev_device_get_property_value(device, "ID_PATH"), + udev_device_get_driver(udev_device_get_parent(device)), + udev_device_get_property_value(device, "ID_NET_DRIVER"), + udev_device_get_devtype(device), + udev_device_get_sysname(device))) { + if (link->match_name) { + unsigned char name_assign_type = NET_NAME_UNKNOWN; + + attr_value = udev_device_get_sysattr_value(device, "name_assign_type"); + if (attr_value) + (void) safe_atou8(attr_value, &name_assign_type); + + if (name_assign_type == NET_NAME_ENUM) { + log_warning("Config file %s applies to device based on potentially unpredictable interface name '%s'", + link->filename, udev_device_get_sysname(device)); + *ret = link; + + return 0; + } else if (name_assign_type == NET_NAME_RENAMED) { + log_warning("Config file %s matches device based on renamed interface name '%s', ignoring", + link->filename, udev_device_get_sysname(device)); + + continue; + } + } + + log_debug("Config file %s applies to device %s", + link->filename, udev_device_get_sysname(device)); + + *ret = link; + + return 0; + } + } + + *ret = NULL; + + return -ENOENT; +} + +static bool mac_is_random(struct udev_device *device) { + const char *s; + unsigned type; + int r; + + /* if we can't get the assign type, assume it is not random */ + s = udev_device_get_sysattr_value(device, "addr_assign_type"); + if (!s) + return false; + + r = safe_atou(s, &type); + if (r < 0) + return false; + + return type == NET_ADDR_RANDOM; +} + +static bool should_rename(struct udev_device *device, bool respect_predictable) { + const char *s; + unsigned type; + int r; + + /* if we can't get the assgin type, assume we should rename */ + s = udev_device_get_sysattr_value(device, "name_assign_type"); + if (!s) + return true; + + r = safe_atou(s, &type); + if (r < 0) + return true; + + switch (type) { + case NET_NAME_USER: + case NET_NAME_RENAMED: + /* these were already named by userspace, do not touch again */ + return false; + case NET_NAME_PREDICTABLE: + /* the kernel claims to have given a predictable name */ + if (respect_predictable) + return false; + /* fall through */ + case NET_NAME_ENUM: + default: + /* the name is known to be bad, or of an unknown type */ + return true; + } +} + +static int get_mac(struct udev_device *device, bool want_random, + struct ether_addr *mac) { + int r; + + if (want_random) + random_bytes(mac->ether_addr_octet, ETH_ALEN); + else { + uint64_t result; + + r = net_get_unique_predictable_data(device, &result); + if (r < 0) + return r; + + assert_cc(ETH_ALEN <= sizeof(result)); + memcpy(mac->ether_addr_octet, &result, ETH_ALEN); + } + + /* see eth_random_addr in the kernel */ + mac->ether_addr_octet[0] &= 0xfe; /* clear multicast bit */ + mac->ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */ + + return 0; +} + +int link_config_apply(link_config_ctx *ctx, link_config *config, + struct udev_device *device, const char **name) { + const char *old_name; + const char *new_name = NULL; + struct ether_addr generated_mac; + struct ether_addr *mac = NULL; + bool respect_predictable = false; + int r, ifindex; + + assert(ctx); + assert(config); + assert(device); + assert(name); + + old_name = udev_device_get_sysname(device); + if (!old_name) + return -EINVAL; + + r = ethtool_set_speed(&ctx->ethtool_fd, old_name, config->speed / 1024, config->duplex); + if (r < 0) + log_warning_errno(r, "Could not set speed or duplex of %s to %zu Mbps (%s): %m", + old_name, config->speed / 1024, + duplex_to_string(config->duplex)); + + r = ethtool_set_wol(&ctx->ethtool_fd, old_name, config->wol); + if (r < 0) + log_warning_errno(r, "Could not set WakeOnLan of %s to %s: %m", + old_name, wol_to_string(config->wol)); + + ifindex = udev_device_get_ifindex(device); + if (ifindex <= 0) { + log_warning("Could not find ifindex"); + return -ENODEV; + } + + if (ctx->enable_name_policy && config->name_policy) { + NamePolicy *policy; + + for (policy = config->name_policy; + !new_name && *policy != _NAMEPOLICY_INVALID; policy++) { + switch (*policy) { + case NAMEPOLICY_KERNEL: + respect_predictable = true; + break; + case NAMEPOLICY_DATABASE: + new_name = udev_device_get_property_value(device, "ID_NET_NAME_FROM_DATABASE"); + break; + case NAMEPOLICY_ONBOARD: + new_name = udev_device_get_property_value(device, "ID_NET_NAME_ONBOARD"); + break; + case NAMEPOLICY_SLOT: + new_name = udev_device_get_property_value(device, "ID_NET_NAME_SLOT"); + break; + case NAMEPOLICY_PATH: + new_name = udev_device_get_property_value(device, "ID_NET_NAME_PATH"); + break; + case NAMEPOLICY_MAC: + new_name = udev_device_get_property_value(device, "ID_NET_NAME_MAC"); + break; + default: + break; + } + } + } + + if (should_rename(device, respect_predictable)) { + /* if not set by policy, fall back manually set name */ + if (!new_name) + new_name = config->name; + } else + new_name = NULL; + + switch (config->mac_policy) { + case MACPOLICY_PERSISTENT: + if (mac_is_random(device)) { + r = get_mac(device, false, &generated_mac); + if (r == -ENOENT) { + log_warning_errno(r, "Could not generate persistent MAC address for %s: %m", old_name); + break; + } else if (r < 0) + return r; + mac = &generated_mac; + } + break; + case MACPOLICY_RANDOM: + if (!mac_is_random(device)) { + r = get_mac(device, true, &generated_mac); + if (r == -ENOENT) { + log_warning_errno(r, "Could not generate random MAC address for %s: %m", old_name); + break; + } else if (r < 0) + return r; + mac = &generated_mac; + } + break; + case MACPOLICY_NONE: + default: + mac = config->mac; + } + + r = rtnl_set_link_properties(&ctx->rtnl, ifindex, config->alias, mac, config->mtu); + if (r < 0) + return log_warning_errno(r, "Could not set Alias, MACAddress or MTU on %s: %m", old_name); + + *name = new_name; + + return 0; +} + +int link_get_driver(link_config_ctx *ctx, struct udev_device *device, char **ret) { + const char *name; + char *driver = NULL; + int r; + + name = udev_device_get_sysname(device); + if (!name) + return -EINVAL; + + r = ethtool_get_driver(&ctx->ethtool_fd, name, &driver); + if (r < 0) + return r; + + *ret = driver; + return 0; +} + +static const char* const mac_policy_table[_MACPOLICY_MAX] = { + [MACPOLICY_PERSISTENT] = "persistent", + [MACPOLICY_RANDOM] = "random", + [MACPOLICY_NONE] = "none" +}; + +DEFINE_STRING_TABLE_LOOKUP(mac_policy, MACPolicy); +DEFINE_CONFIG_PARSE_ENUM(config_parse_mac_policy, mac_policy, MACPolicy, + "Failed to parse MAC address policy"); + +static const char* const name_policy_table[_NAMEPOLICY_MAX] = { + [NAMEPOLICY_KERNEL] = "kernel", + [NAMEPOLICY_DATABASE] = "database", + [NAMEPOLICY_ONBOARD] = "onboard", + [NAMEPOLICY_SLOT] = "slot", + [NAMEPOLICY_PATH] = "path", + [NAMEPOLICY_MAC] = "mac" +}; + +DEFINE_STRING_TABLE_LOOKUP(name_policy, NamePolicy); +DEFINE_CONFIG_PARSE_ENUMV(config_parse_name_policy, name_policy, NamePolicy, + _NAMEPOLICY_INVALID, + "Failed to parse interface name policy"); diff --git a/src/grp-udev/libudev-core/net/link-config.h b/src/grp-udev/libudev-core/net/link-config.h new file mode 100644 index 0000000000..9df5529d05 --- /dev/null +++ b/src/grp-udev/libudev-core/net/link-config.h @@ -0,0 +1,98 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2013 Tom Gundersen <teg@jklm.no> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "libudev.h" + +#include "condition.h" +#include "ethtool-util.h" +#include "list.h" + +typedef struct link_config_ctx link_config_ctx; +typedef struct link_config link_config; + +typedef enum MACPolicy { + MACPOLICY_PERSISTENT, + MACPOLICY_RANDOM, + MACPOLICY_NONE, + _MACPOLICY_MAX, + _MACPOLICY_INVALID = -1 +} MACPolicy; + +typedef enum NamePolicy { + NAMEPOLICY_KERNEL, + NAMEPOLICY_DATABASE, + NAMEPOLICY_ONBOARD, + NAMEPOLICY_SLOT, + NAMEPOLICY_PATH, + NAMEPOLICY_MAC, + _NAMEPOLICY_MAX, + _NAMEPOLICY_INVALID = -1 +} NamePolicy; + +struct link_config { + char *filename; + + struct ether_addr *match_mac; + char **match_path; + char **match_driver; + char **match_type; + char **match_name; + Condition *match_host; + Condition *match_virt; + Condition *match_kernel; + Condition *match_arch; + + char *description; + struct ether_addr *mac; + MACPolicy mac_policy; + NamePolicy *name_policy; + char *name; + char *alias; + size_t mtu; + size_t speed; + Duplex duplex; + WakeOnLan wol; + + LIST_FIELDS(link_config, links); +}; + +int link_config_ctx_new(link_config_ctx **ret); +void link_config_ctx_free(link_config_ctx *ctx); + +int link_config_load(link_config_ctx *ctx); +bool link_config_should_reload(link_config_ctx *ctx); + +int link_config_get(link_config_ctx *ctx, struct udev_device *device, struct link_config **ret); +int link_config_apply(link_config_ctx *ctx, struct link_config *config, struct udev_device *device, const char **name); + +int link_get_driver(link_config_ctx *ctx, struct udev_device *device, char **ret); + +const char *name_policy_to_string(NamePolicy p) _const_; +NamePolicy name_policy_from_string(const char *p) _pure_; + +const char *mac_policy_to_string(MACPolicy p) _const_; +MACPolicy mac_policy_from_string(const char *p) _pure_; + +/* gperf lookup function */ +const struct ConfigPerfItem* link_config_gperf_lookup(const char *key, unsigned length); + +int config_parse_mac_policy(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_name_policy(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/grp-udev/libudev-core/udev-builtin-blkid.c b/src/grp-udev/libudev-core/udev-builtin-blkid.c new file mode 100644 index 0000000000..62cd93264b --- /dev/null +++ b/src/grp-udev/libudev-core/udev-builtin-blkid.c @@ -0,0 +1,337 @@ +/* + * probe disks for filesystems and partitions + * + * Copyright (C) 2011 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2011 Karel Zak <kzak@redhat.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <blkid/blkid.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include <systemd/sd-id128.h> + +#include "alloc-util.h" +#include "efivars.h" +#include "fd-util.h" +#include "gpt.h" +#include "string-util.h" +#include "udev.h" + +static void print_property(struct udev_device *dev, bool test, const char *name, const char *value) { + char s[256]; + + s[0] = '\0'; + + if (streq(name, "TYPE")) { + udev_builtin_add_property(dev, test, "ID_FS_TYPE", value); + + } else if (streq(name, "USAGE")) { + udev_builtin_add_property(dev, test, "ID_FS_USAGE", value); + + } else if (streq(name, "VERSION")) { + udev_builtin_add_property(dev, test, "ID_FS_VERSION", value); + + } else if (streq(name, "UUID")) { + blkid_safe_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_FS_UUID", s); + blkid_encode_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_FS_UUID_ENC", s); + + } else if (streq(name, "UUID_SUB")) { + blkid_safe_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_FS_UUID_SUB", s); + blkid_encode_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_FS_UUID_SUB_ENC", s); + + } else if (streq(name, "LABEL")) { + blkid_safe_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_FS_LABEL", s); + blkid_encode_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_FS_LABEL_ENC", s); + + } else if (streq(name, "PTTYPE")) { + udev_builtin_add_property(dev, test, "ID_PART_TABLE_TYPE", value); + + } else if (streq(name, "PTUUID")) { + udev_builtin_add_property(dev, test, "ID_PART_TABLE_UUID", value); + + } else if (streq(name, "PART_ENTRY_NAME")) { + blkid_encode_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_PART_ENTRY_NAME", s); + + } else if (streq(name, "PART_ENTRY_TYPE")) { + blkid_encode_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_PART_ENTRY_TYPE", s); + + } else if (startswith(name, "PART_ENTRY_")) { + strscpyl(s, sizeof(s), "ID_", name, NULL); + udev_builtin_add_property(dev, test, s, value); + + } else if (streq(name, "SYSTEM_ID")) { + blkid_encode_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_FS_SYSTEM_ID", s); + + } else if (streq(name, "PUBLISHER_ID")) { + blkid_encode_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_FS_PUBLISHER_ID", s); + + } else if (streq(name, "APPLICATION_ID")) { + blkid_encode_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_FS_APPLICATION_ID", s); + + } else if (streq(name, "BOOT_SYSTEM_ID")) { + blkid_encode_string(value, s, sizeof(s)); + udev_builtin_add_property(dev, test, "ID_FS_BOOT_SYSTEM_ID", s); + } +} + +static int find_gpt_root(struct udev_device *dev, blkid_probe pr, bool test) { + +#if defined(GPT_ROOT_NATIVE) && defined(ENABLE_EFI) + + _cleanup_free_ char *root_id = NULL; + bool found_esp = false; + blkid_partlist pl; + int i, nvals, r; + + assert(pr); + + /* Iterate through the partitions on this disk, and see if the + * EFI ESP we booted from is on it. If so, find the first root + * disk, and add a property indicating its partition UUID. */ + + errno = 0; + pl = blkid_probe_get_partitions(pr); + if (!pl) + return errno > 0 ? -errno : -ENOMEM; + + nvals = blkid_partlist_numof_partitions(pl); + for (i = 0; i < nvals; i++) { + blkid_partition pp; + const char *stype, *sid; + sd_id128_t type; + + pp = blkid_partlist_get_partition(pl, i); + if (!pp) + continue; + + sid = blkid_partition_get_uuid(pp); + if (!sid) + continue; + + stype = blkid_partition_get_type_string(pp); + if (!stype) + continue; + + if (sd_id128_from_string(stype, &type) < 0) + continue; + + if (sd_id128_equal(type, GPT_ESP)) { + sd_id128_t id, esp; + unsigned long long flags; + + flags = blkid_partition_get_flags(pp); + if (flags & GPT_FLAG_NO_AUTO) + continue; + + /* We found an ESP, let's see if it matches + * the ESP we booted from. */ + + if (sd_id128_from_string(sid, &id) < 0) + continue; + + r = efi_loader_get_device_part_uuid(&esp); + if (r < 0) + return r; + + if (sd_id128_equal(id, esp)) + found_esp = true; + + } else if (sd_id128_equal(type, GPT_ROOT_NATIVE)) { + + /* We found a suitable root partition, let's + * remember the first one. */ + + if (!root_id) { + root_id = strdup(sid); + if (!root_id) + return -ENOMEM; + } + } + } + + /* We found the ESP on this disk, and also found a root + * partition, nice! Let's export its UUID */ + if (found_esp && root_id) + udev_builtin_add_property(dev, test, "ID_PART_GPT_AUTO_ROOT_UUID", root_id); +#endif + + return 0; +} + +static int probe_superblocks(blkid_probe pr) { + struct stat st; + int rc; + + if (fstat(blkid_probe_get_fd(pr), &st)) + return -1; + + blkid_probe_enable_partitions(pr, 1); + + if (!S_ISCHR(st.st_mode) && + blkid_probe_get_size(pr) <= 1024 * 1440 && + blkid_probe_is_wholedisk(pr)) { + /* + * check if the small disk is partitioned, if yes then + * don't probe for filesystems. + */ + blkid_probe_enable_superblocks(pr, 0); + + rc = blkid_do_fullprobe(pr); + if (rc < 0) + return rc; /* -1 = error, 1 = nothing, 0 = success */ + + if (blkid_probe_lookup_value(pr, "PTTYPE", NULL, NULL) == 0) + return 0; /* partition table detected */ + } + + blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS); + blkid_probe_enable_superblocks(pr, 1); + + return blkid_do_safeprobe(pr); +} + +static int builtin_blkid(struct udev_device *dev, int argc, char *argv[], bool test) { + const char *root_partition; + int64_t offset = 0; + bool noraid = false; + _cleanup_close_ int fd = -1; + blkid_probe pr; + const char *data; + const char *name; + const char *prtype = NULL; + int nvals; + int i; + int err = 0; + bool is_gpt = false; + + static const struct option options[] = { + { "offset", optional_argument, NULL, 'o' }, + { "noraid", no_argument, NULL, 'R' }, + {} + }; + + for (;;) { + int option; + + option = getopt_long(argc, argv, "oR", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'o': + offset = strtoull(optarg, NULL, 0); + break; + case 'R': + noraid = true; + break; + } + } + + pr = blkid_new_probe(); + if (!pr) + return EXIT_FAILURE; + + blkid_probe_set_superblocks_flags(pr, + BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID | + BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE | + BLKID_SUBLKS_USAGE | BLKID_SUBLKS_VERSION | + BLKID_SUBLKS_BADCSUM); + + if (noraid) + blkid_probe_filter_superblocks_usage(pr, BLKID_FLTR_NOTIN, BLKID_USAGE_RAID); + + fd = open(udev_device_get_devnode(dev), O_RDONLY|O_CLOEXEC); + if (fd < 0) { + err = log_debug_errno(errno, "Failure opening block device %s: %m", udev_device_get_devnode(dev)); + goto out; + } + + err = blkid_probe_set_device(pr, fd, offset, 0); + if (err < 0) + goto out; + + log_debug("probe %s %sraid offset=%"PRIi64, + udev_device_get_devnode(dev), + noraid ? "no" : "", offset); + + err = probe_superblocks(pr); + if (err < 0) + goto out; + if (blkid_probe_has_value(pr, "SBBADCSUM")) { + if (!blkid_probe_lookup_value(pr, "TYPE", &prtype, NULL)) + log_warning("incorrect %s checksum on %s", + prtype, udev_device_get_devnode(dev)); + else + log_warning("incorrect checksum on %s", + udev_device_get_devnode(dev)); + goto out; + } + + /* If we are a partition then our parent passed on the root + * partition UUID to us */ + root_partition = udev_device_get_property_value(dev, "ID_PART_GPT_AUTO_ROOT_UUID"); + + nvals = blkid_probe_numof_values(pr); + for (i = 0; i < nvals; i++) { + if (blkid_probe_get_value(pr, i, &name, &data, NULL)) + continue; + + print_property(dev, test, name, data); + + /* Is this a disk with GPT partition table? */ + if (streq(name, "PTTYPE") && streq(data, "gpt")) + is_gpt = true; + + /* Is this a partition that matches the root partition + * property we inherited from our parent? */ + if (root_partition && streq(name, "PART_ENTRY_UUID") && streq(data, root_partition)) + udev_builtin_add_property(dev, test, "ID_PART_GPT_AUTO_ROOT", "1"); + } + + if (is_gpt) + find_gpt_root(dev, pr, test); + + blkid_free_probe(pr); +out: + if (err < 0) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} + +const struct udev_builtin udev_builtin_blkid = { + .name = "blkid", + .cmd = builtin_blkid, + .help = "Filesystem and partition probing", + .run_once = true, +}; diff --git a/src/grp-udev/libudev-core/udev-builtin-btrfs.c b/src/grp-udev/libudev-core/udev-builtin-btrfs.c new file mode 100644 index 0000000000..cfaa463804 --- /dev/null +++ b/src/grp-udev/libudev-core/udev-builtin-btrfs.c @@ -0,0 +1,58 @@ +/*** + This file is part of systemd. + + Copyright 2012 Kay Sievers <kay@vrfy.org> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <fcntl.h> +#include <stdlib.h> +#include <sys/ioctl.h> + +#ifdef HAVE_LINUX_BTRFS_H +#include <linux/btrfs.h> +#endif + +#include "fd-util.h" +#include "missing.h" +#include "string-util.h" +#include "udev.h" + +static int builtin_btrfs(struct udev_device *dev, int argc, char *argv[], bool test) { + struct btrfs_ioctl_vol_args args = {}; + _cleanup_close_ int fd = -1; + int err; + + if (argc != 3 || !streq(argv[1], "ready")) + return EXIT_FAILURE; + + fd = open("/dev/btrfs-control", O_RDWR|O_CLOEXEC); + if (fd < 0) + return EXIT_FAILURE; + + strscpy(args.name, sizeof(args.name), argv[2]); + err = ioctl(fd, BTRFS_IOC_DEVICES_READY, &args); + if (err < 0) + return EXIT_FAILURE; + + udev_builtin_add_property(dev, test, "ID_BTRFS_READY", one_zero(err == 0)); + return EXIT_SUCCESS; +} + +const struct udev_builtin udev_builtin_btrfs = { + .name = "btrfs", + .cmd = builtin_btrfs, + .help = "btrfs volume management", +}; diff --git a/src/grp-udev/libudev-core/udev-builtin-hwdb.c b/src/grp-udev/libudev-core/udev-builtin-hwdb.c new file mode 100644 index 0000000000..b96f39ba20 --- /dev/null +++ b/src/grp-udev/libudev-core/udev-builtin-hwdb.c @@ -0,0 +1,223 @@ +/*** + This file is part of systemd. + + Copyright 2012 Kay Sievers <kay@vrfy.org> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <fnmatch.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> + +#include <systemd/sd-hwdb.h> + +#include "alloc-util.h" +#include "hwdb-util.h" +#include "string-util.h" +#include "udev-util.h" +#include "udev.h" + +static sd_hwdb *hwdb; + +int udev_builtin_hwdb_lookup(struct udev_device *dev, + const char *prefix, const char *modalias, + const char *filter, bool test) { + _cleanup_free_ char *lookup = NULL; + const char *key, *value; + int n = 0; + + if (!hwdb) + return -ENOENT; + + if (prefix) { + lookup = strjoin(prefix, modalias, NULL); + if (!lookup) + return -ENOMEM; + modalias = lookup; + } + + SD_HWDB_FOREACH_PROPERTY(hwdb, modalias, key, value) { + if (filter && fnmatch(filter, key, FNM_NOESCAPE) != 0) + continue; + + if (udev_builtin_add_property(dev, test, key, value) < 0) + return -ENOMEM; + n++; + } + return n; +} + +static const char *modalias_usb(struct udev_device *dev, char *s, size_t size) { + const char *v, *p; + int vn, pn; + + v = udev_device_get_sysattr_value(dev, "idVendor"); + if (!v) + return NULL; + p = udev_device_get_sysattr_value(dev, "idProduct"); + if (!p) + return NULL; + vn = strtol(v, NULL, 16); + if (vn <= 0) + return NULL; + pn = strtol(p, NULL, 16); + if (pn <= 0) + return NULL; + snprintf(s, size, "usb:v%04Xp%04X*", vn, pn); + return s; +} + +static int udev_builtin_hwdb_search(struct udev_device *dev, struct udev_device *srcdev, + const char *subsystem, const char *prefix, + const char *filter, bool test) { + struct udev_device *d; + char s[16]; + bool last = false; + int r = 0; + + assert(dev); + + if (!srcdev) + srcdev = dev; + + for (d = srcdev; d && !last; d = udev_device_get_parent(d)) { + const char *dsubsys; + const char *modalias = NULL; + + dsubsys = udev_device_get_subsystem(d); + if (!dsubsys) + continue; + + /* look only at devices of a specific subsystem */ + if (subsystem && !streq(dsubsys, subsystem)) + continue; + + modalias = udev_device_get_property_value(d, "MODALIAS"); + + if (streq(dsubsys, "usb") && streq_ptr(udev_device_get_devtype(d), "usb_device")) { + /* if the usb_device does not have a modalias, compose one */ + if (!modalias) + modalias = modalias_usb(d, s, sizeof(s)); + + /* avoid looking at any parent device, they are usually just a USB hub */ + last = true; + } + + if (!modalias) + continue; + + r = udev_builtin_hwdb_lookup(dev, prefix, modalias, filter, test); + if (r > 0) + break; + } + + return r; +} + +static int builtin_hwdb(struct udev_device *dev, int argc, char *argv[], bool test) { + static const struct option options[] = { + { "filter", required_argument, NULL, 'f' }, + { "device", required_argument, NULL, 'd' }, + { "subsystem", required_argument, NULL, 's' }, + { "lookup-prefix", required_argument, NULL, 'p' }, + {} + }; + const char *filter = NULL; + const char *device = NULL; + const char *subsystem = NULL; + const char *prefix = NULL; + _cleanup_udev_device_unref_ struct udev_device *srcdev = NULL; + + if (!hwdb) + return EXIT_FAILURE; + + for (;;) { + int option; + + option = getopt_long(argc, argv, "f:d:s:p:", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'f': + filter = optarg; + break; + + case 'd': + device = optarg; + break; + + case 's': + subsystem = optarg; + break; + + case 'p': + prefix = optarg; + break; + } + } + + /* query a specific key given as argument */ + if (argv[optind]) { + if (udev_builtin_hwdb_lookup(dev, prefix, argv[optind], filter, test) > 0) + return EXIT_SUCCESS; + return EXIT_FAILURE; + } + + /* read data from another device than the device we will store the data */ + if (device) { + srcdev = udev_device_new_from_device_id(udev_device_get_udev(dev), device); + if (!srcdev) + return EXIT_FAILURE; + } + + if (udev_builtin_hwdb_search(dev, srcdev, subsystem, prefix, filter, test) > 0) + return EXIT_SUCCESS; + return EXIT_FAILURE; +} + +/* called at udev startup and reload */ +static int builtin_hwdb_init(struct udev *udev) { + int r; + + if (hwdb) + return 0; + + r = sd_hwdb_new(&hwdb); + if (r < 0) + return r; + + return 0; +} + +/* called on udev shutdown and reload request */ +static void builtin_hwdb_exit(struct udev *udev) { + hwdb = sd_hwdb_unref(hwdb); +} + +/* called every couple of seconds during event activity; 'true' if config has changed */ +static bool builtin_hwdb_validate(struct udev *udev) { + return hwdb_validate(hwdb); +} + +const struct udev_builtin udev_builtin_hwdb = { + .name = "hwdb", + .cmd = builtin_hwdb, + .init = builtin_hwdb_init, + .exit = builtin_hwdb_exit, + .validate = builtin_hwdb_validate, + .help = "Hardware database", +}; diff --git a/src/grp-udev/libudev-core/udev-builtin-input_id.c b/src/grp-udev/libudev-core/udev-builtin-input_id.c new file mode 100644 index 0000000000..51a55cdbc4 --- /dev/null +++ b/src/grp-udev/libudev-core/udev-builtin-input_id.c @@ -0,0 +1,334 @@ +/* + * expose input properties via udev + * + * Copyright (C) 2009 Martin Pitt <martin.pitt@ubuntu.com> + * Portions Copyright (C) 2004 David Zeuthen, <david@fubar.dk> + * Copyright (C) 2011 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2014 Carlos Garnacho <carlosg@gnome.org> + * Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <linux/limits.h> +#include <linux/input.h> + +#include "fd-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "udev.h" +#include "util.h" + +/* we must use this kernel-compatible implementation */ +#define BITS_PER_LONG (sizeof(unsigned long) * 8) +#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1) +#define OFF(x) ((x)%BITS_PER_LONG) +#define BIT(x) (1UL<<OFF(x)) +#define LONG(x) ((x)/BITS_PER_LONG) +#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1) + +static inline int abs_size_mm(const struct input_absinfo *absinfo) { + /* Resolution is defined to be in units/mm for ABS_X/Y */ + return (absinfo->maximum - absinfo->minimum) / absinfo->resolution; +} + +static void extract_info(struct udev_device *dev, const char *devpath, bool test) { + char width[DECIMAL_STR_MAX(int)], height[DECIMAL_STR_MAX(int)]; + struct input_absinfo xabsinfo = {}, yabsinfo = {}; + _cleanup_close_ int fd = -1; + + fd = open(devpath, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return; + + if (ioctl(fd, EVIOCGABS(ABS_X), &xabsinfo) < 0 || + ioctl(fd, EVIOCGABS(ABS_Y), &yabsinfo) < 0) + return; + + if (xabsinfo.resolution <= 0 || yabsinfo.resolution <= 0) + return; + + xsprintf(width, "%d", abs_size_mm(&xabsinfo)); + xsprintf(height, "%d", abs_size_mm(&yabsinfo)); + + udev_builtin_add_property(dev, test, "ID_INPUT_WIDTH_MM", width); + udev_builtin_add_property(dev, test, "ID_INPUT_HEIGHT_MM", height); +} + +/* + * Read a capability attribute and return bitmask. + * @param dev udev_device + * @param attr sysfs attribute name (e. g. "capabilities/key") + * @param bitmask: Output array which has a sizeof of bitmask_size + */ +static void get_cap_mask(struct udev_device *dev, + struct udev_device *pdev, const char* attr, + unsigned long *bitmask, size_t bitmask_size, + bool test) { + const char *v; + char text[4096]; + unsigned i; + char* word; + unsigned long val; + + v = udev_device_get_sysattr_value(pdev, attr); + if (!v) + v = ""; + + xsprintf(text, "%s", v); + log_debug("%s raw kernel attribute: %s", attr, text); + + memzero(bitmask, bitmask_size); + i = 0; + while ((word = strrchr(text, ' ')) != NULL) { + val = strtoul (word+1, NULL, 16); + if (i < bitmask_size/sizeof(unsigned long)) + bitmask[i] = val; + else + log_debug("ignoring %s block %lX which is larger than maximum size", attr, val); + *word = '\0'; + ++i; + } + val = strtoul (text, NULL, 16); + if (i < bitmask_size / sizeof(unsigned long)) + bitmask[i] = val; + else + log_debug("ignoring %s block %lX which is larger than maximum size", attr, val); + + if (test) { + /* printf pattern with the right unsigned long number of hex chars */ + xsprintf(text, " bit %%4u: %%0%zulX\n", + 2 * sizeof(unsigned long)); + log_debug("%s decoded bit map:", attr); + val = bitmask_size / sizeof (unsigned long); + /* skip over leading zeros */ + while (bitmask[val-1] == 0 && val > 0) + --val; + for (i = 0; i < val; ++i) { + DISABLE_WARNING_FORMAT_NONLITERAL; + log_debug(text, i * BITS_PER_LONG, bitmask[i]); + REENABLE_WARNING; + } + } +} + +/* pointer devices */ +static bool test_pointers(struct udev_device *dev, + const unsigned long* bitmask_ev, + const unsigned long* bitmask_abs, + const unsigned long* bitmask_key, + const unsigned long* bitmask_rel, + const unsigned long* bitmask_props, + bool test) { + bool has_abs_coordinates = false; + bool has_rel_coordinates = false; + bool has_mt_coordinates = false; + bool has_joystick_axes_or_buttons = false; + bool is_direct = false; + bool has_touch = false; + bool has_3d_coordinates = false; + bool has_keys = false; + bool stylus_or_pen = false; + bool finger_but_no_pen = false; + bool has_mouse_button = false; + bool is_mouse = false; + bool is_touchpad = false; + bool is_touchscreen = false; + bool is_tablet = false; + bool is_joystick = false; + bool is_accelerometer = false; + bool is_pointing_stick= false; + + has_keys = test_bit(EV_KEY, bitmask_ev); + has_abs_coordinates = test_bit(ABS_X, bitmask_abs) && test_bit(ABS_Y, bitmask_abs); + has_3d_coordinates = has_abs_coordinates && test_bit(ABS_Z, bitmask_abs); + is_accelerometer = test_bit(INPUT_PROP_ACCELEROMETER, bitmask_props); + + if (!has_keys && has_3d_coordinates) + is_accelerometer = true; + + if (is_accelerometer) { + udev_builtin_add_property(dev, test, "ID_INPUT_ACCELEROMETER", "1"); + return true; + } + + is_pointing_stick = test_bit(INPUT_PROP_POINTING_STICK, bitmask_props); + stylus_or_pen = test_bit(BTN_STYLUS, bitmask_key) || test_bit(BTN_TOOL_PEN, bitmask_key); + finger_but_no_pen = test_bit(BTN_TOOL_FINGER, bitmask_key) && !test_bit(BTN_TOOL_PEN, bitmask_key); + has_mouse_button = test_bit(BTN_LEFT, bitmask_key); + has_rel_coordinates = test_bit(EV_REL, bitmask_ev) && test_bit(REL_X, bitmask_rel) && test_bit(REL_Y, bitmask_rel); + has_mt_coordinates = test_bit(ABS_MT_POSITION_X, bitmask_abs) && test_bit(ABS_MT_POSITION_Y, bitmask_abs); + + /* unset has_mt_coordinates if devices claims to have all abs axis */ + if (has_mt_coordinates && test_bit(ABS_MT_SLOT, bitmask_abs) && test_bit(ABS_MT_SLOT - 1, bitmask_abs)) + has_mt_coordinates = false; + is_direct = test_bit(INPUT_PROP_DIRECT, bitmask_props); + has_touch = test_bit(BTN_TOUCH, bitmask_key); + /* joysticks don't necessarily have buttons; e. g. + * rudders/pedals are joystick-like, but buttonless; they have + * other fancy axes */ + has_joystick_axes_or_buttons = test_bit(BTN_TRIGGER, bitmask_key) || + test_bit(BTN_A, bitmask_key) || + test_bit(BTN_1, bitmask_key) || + test_bit(ABS_RX, bitmask_abs) || + test_bit(ABS_RY, bitmask_abs) || + test_bit(ABS_RZ, bitmask_abs) || + test_bit(ABS_THROTTLE, bitmask_abs) || + test_bit(ABS_RUDDER, bitmask_abs) || + test_bit(ABS_WHEEL, bitmask_abs) || + test_bit(ABS_GAS, bitmask_abs) || + test_bit(ABS_BRAKE, bitmask_abs); + + if (has_abs_coordinates) { + if (stylus_or_pen) + is_tablet = true; + else if (finger_but_no_pen && !is_direct) + is_touchpad = true; + else if (has_mouse_button) + /* This path is taken by VMware's USB mouse, which has + * absolute axes, but no touch/pressure button. */ + is_mouse = true; + else if (has_touch || is_direct) + is_touchscreen = true; + else if (has_joystick_axes_or_buttons) + is_joystick = true; + } + if (has_mt_coordinates && (is_direct || has_touch)) + is_touchscreen = true; + + if (has_rel_coordinates && has_mouse_button) + is_mouse = true; + + if (is_pointing_stick) + udev_builtin_add_property(dev, test, "ID_INPUT_POINTINGSTICK", "1"); + if (is_mouse) + udev_builtin_add_property(dev, test, "ID_INPUT_MOUSE", "1"); + if (is_touchpad) + udev_builtin_add_property(dev, test, "ID_INPUT_TOUCHPAD", "1"); + if (is_touchscreen) + udev_builtin_add_property(dev, test, "ID_INPUT_TOUCHSCREEN", "1"); + if (is_joystick) + udev_builtin_add_property(dev, test, "ID_INPUT_JOYSTICK", "1"); + if (is_tablet) + udev_builtin_add_property(dev, test, "ID_INPUT_TABLET", "1"); + + return is_tablet || is_mouse || is_touchpad || is_touchscreen || is_joystick || is_pointing_stick; +} + +/* key like devices */ +static bool test_key(struct udev_device *dev, + const unsigned long* bitmask_ev, + const unsigned long* bitmask_key, + bool test) { + unsigned i; + unsigned long found; + unsigned long mask; + bool ret = false; + + /* do we have any KEY_* capability? */ + if (!test_bit(EV_KEY, bitmask_ev)) { + log_debug("test_key: no EV_KEY capability"); + return false; + } + + /* only consider KEY_* here, not BTN_* */ + found = 0; + for (i = 0; i < BTN_MISC/BITS_PER_LONG; ++i) { + found |= bitmask_key[i]; + log_debug("test_key: checking bit block %lu for any keys; found=%i", (unsigned long)i*BITS_PER_LONG, found > 0); + } + /* If there are no keys in the lower block, check the higher block */ + if (!found) { + for (i = KEY_OK; i < BTN_TRIGGER_HAPPY; ++i) { + if (test_bit(i, bitmask_key)) { + log_debug("test_key: Found key %x in high block", i); + found = 1; + break; + } + } + } + + if (found > 0) { + udev_builtin_add_property(dev, test, "ID_INPUT_KEY", "1"); + ret = true; + } + + /* the first 32 bits are ESC, numbers, and Q to D; if we have all of + * those, consider it a full keyboard; do not test KEY_RESERVED, though */ + mask = 0xFFFFFFFE; + if ((bitmask_key[0] & mask) == mask) { + udev_builtin_add_property(dev, test, "ID_INPUT_KEYBOARD", "1"); + ret = true; + } + + return ret; +} + +static int builtin_input_id(struct udev_device *dev, int argc, char *argv[], bool test) { + struct udev_device *pdev; + unsigned long bitmask_ev[NBITS(EV_MAX)]; + unsigned long bitmask_abs[NBITS(ABS_MAX)]; + unsigned long bitmask_key[NBITS(KEY_MAX)]; + unsigned long bitmask_rel[NBITS(REL_MAX)]; + unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)]; + const char *sysname, *devnode; + bool is_pointer; + bool is_key; + + assert(dev); + + /* walk up the parental chain until we find the real input device; the + * argument is very likely a subdevice of this, like eventN */ + pdev = dev; + while (pdev != NULL && udev_device_get_sysattr_value(pdev, "capabilities/ev") == NULL) + pdev = udev_device_get_parent_with_subsystem_devtype(pdev, "input", NULL); + + if (pdev) { + /* Use this as a flag that input devices were detected, so that this + * program doesn't need to be called more than once per device */ + udev_builtin_add_property(dev, test, "ID_INPUT", "1"); + get_cap_mask(dev, pdev, "capabilities/ev", bitmask_ev, sizeof(bitmask_ev), test); + get_cap_mask(dev, pdev, "capabilities/abs", bitmask_abs, sizeof(bitmask_abs), test); + get_cap_mask(dev, pdev, "capabilities/rel", bitmask_rel, sizeof(bitmask_rel), test); + get_cap_mask(dev, pdev, "capabilities/key", bitmask_key, sizeof(bitmask_key), test); + get_cap_mask(dev, pdev, "properties", bitmask_props, sizeof(bitmask_props), test); + is_pointer = test_pointers(dev, bitmask_ev, bitmask_abs, + bitmask_key, bitmask_rel, + bitmask_props, test); + is_key = test_key(dev, bitmask_ev, bitmask_key, test); + /* Some evdev nodes have only a scrollwheel */ + if (!is_pointer && !is_key && test_bit(EV_REL, bitmask_ev) && + (test_bit(REL_WHEEL, bitmask_rel) || test_bit(REL_HWHEEL, bitmask_rel))) + udev_builtin_add_property(dev, test, "ID_INPUT_KEY", "1"); + } + + devnode = udev_device_get_devnode(dev); + sysname = udev_device_get_sysname(dev); + if (devnode && sysname && startswith(sysname, "event")) + extract_info(dev, devnode, test); + + return EXIT_SUCCESS; +} + +const struct udev_builtin udev_builtin_input_id = { + .name = "input_id", + .cmd = builtin_input_id, + .help = "Input device properties", +}; diff --git a/src/grp-udev/libudev-core/udev-builtin-keyboard.c b/src/grp-udev/libudev-core/udev-builtin-keyboard.c new file mode 100644 index 0000000000..aa10beafb0 --- /dev/null +++ b/src/grp-udev/libudev-core/udev-builtin-keyboard.c @@ -0,0 +1,277 @@ +/*** + This file is part of systemd. + + Copyright 2013 Kay Sievers <kay@vrfy.org> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <linux/input.h> + +#include "fd-util.h" +#include "parse-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "udev.h" + +static const struct key *keyboard_lookup_key(const char *str, unsigned len); +#include "keyboard-keys-from-name.h" + +static int install_force_release(struct udev_device *dev, const unsigned *release, unsigned release_count) { + struct udev_device *atkbd; + const char *cur; + char codes[4096]; + char *s; + size_t l; + unsigned i; + int ret; + + assert(dev); + assert(release); + + atkbd = udev_device_get_parent_with_subsystem_devtype(dev, "serio", NULL); + if (!atkbd) + return -ENODEV; + + cur = udev_device_get_sysattr_value(atkbd, "force_release"); + if (!cur) + return -ENODEV; + + s = codes; + l = sizeof(codes); + + /* copy current content */ + l = strpcpy(&s, l, cur); + + /* append new codes */ + for (i = 0; i < release_count; i++) + l = strpcpyf(&s, l, ",%u", release[i]); + + log_debug("keyboard: updating force-release list with '%s'", codes); + ret = udev_device_set_sysattr_value(atkbd, "force_release", codes); + if (ret < 0) + log_error_errno(ret, "Error writing force-release attribute: %m"); + return ret; +} + +static void map_keycode(int fd, const char *devnode, int scancode, const char *keycode) +{ + struct { + unsigned scan; + unsigned key; + } map; + char *endptr; + const struct key *k; + unsigned keycode_num; + + /* translate identifier to key code */ + k = keyboard_lookup_key(keycode, strlen(keycode)); + if (k) { + keycode_num = k->id; + } else { + /* check if it's a numeric code already */ + keycode_num = strtoul(keycode, &endptr, 0); + if (endptr[0] !='\0') { + log_error("Unknown key identifier '%s'", keycode); + return; + } + } + + map.scan = scancode; + map.key = keycode_num; + + log_debug("keyboard: mapping scan code %d (0x%x) to key code %d (0x%x)", + map.scan, map.scan, map.key, map.key); + + if (ioctl(fd, EVIOCSKEYCODE, &map) < 0) + log_error_errno(errno, "Error calling EVIOCSKEYCODE on device node '%s' (scan code 0x%x, key code %d): %m", devnode, map.scan, map.key); +} + +static inline char* parse_token(const char *current, int32_t *val_out) { + char *next; + int32_t val; + + if (!current) + return NULL; + + val = strtol(current, &next, 0); + if (*next && *next != ':') + return NULL; + + if (next != current) + *val_out = val; + + if (*next) + next++; + + return next; +} + +static void override_abs(int fd, const char *devnode, + unsigned evcode, const char *value) { + struct input_absinfo absinfo; + int rc; + char *next; + + rc = ioctl(fd, EVIOCGABS(evcode), &absinfo); + if (rc < 0) { + log_error_errno(errno, "Unable to EVIOCGABS device \"%s\"", devnode); + return; + } + + next = parse_token(value, &absinfo.minimum); + next = parse_token(next, &absinfo.maximum); + next = parse_token(next, &absinfo.resolution); + next = parse_token(next, &absinfo.fuzz); + next = parse_token(next, &absinfo.flat); + if (!next) { + log_error("Unable to parse EV_ABS override '%s' for '%s'", value, devnode); + return; + } + + log_debug("keyboard: %x overridden with %"PRIi32"/%"PRIi32"/%"PRIi32"/%"PRIi32"/%"PRIi32" for \"%s\"", + evcode, + absinfo.minimum, absinfo.maximum, absinfo.resolution, absinfo.fuzz, absinfo.flat, + devnode); + rc = ioctl(fd, EVIOCSABS(evcode), &absinfo); + if (rc < 0) + log_error_errno(errno, "Unable to EVIOCSABS device \"%s\"", devnode); +} + +static void set_trackpoint_sensitivity(struct udev_device *dev, const char *value) +{ + struct udev_device *pdev; + char val_s[DECIMAL_STR_MAX(int)]; + int r, val_i; + + assert(dev); + assert(value); + + /* The sensitivity sysfs attr belongs to the serio parent device */ + pdev = udev_device_get_parent_with_subsystem_devtype(dev, "serio", NULL); + if (!pdev) { + log_warning("Failed to get serio parent for '%s'", udev_device_get_devnode(dev)); + return; + } + + r = safe_atoi(value, &val_i); + if (r < 0) { + log_error("Unable to parse POINTINGSTICK_SENSITIVITY '%s' for '%s'", value, udev_device_get_devnode(dev)); + return; + } + + xsprintf(val_s, "%d", val_i); + + r = udev_device_set_sysattr_value(pdev, "sensitivity", val_s); + if (r < 0) + log_error_errno(r, "Failed to write 'sensitivity' attribute for '%s': %m", udev_device_get_devnode(pdev)); +} + +static int open_device(const char *devnode) { + int fd; + + fd = open(devnode, O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Error opening device \"%s\": %m", devnode); + + return fd; +} + +static int builtin_keyboard(struct udev_device *dev, int argc, char *argv[], bool test) { + struct udev_list_entry *entry; + unsigned release[1024]; + unsigned release_count = 0; + _cleanup_close_ int fd = -1; + const char *node; + + node = udev_device_get_devnode(dev); + if (!node) { + log_error("No device node for \"%s\"", udev_device_get_syspath(dev)); + return EXIT_FAILURE; + } + + udev_list_entry_foreach(entry, udev_device_get_properties_list_entry(dev)) { + const char *key; + char *endptr; + + key = udev_list_entry_get_name(entry); + if (startswith(key, "KEYBOARD_KEY_")) { + const char *keycode; + unsigned scancode; + + /* KEYBOARD_KEY_<hex scan code>=<key identifier string> */ + scancode = strtoul(key + 13, &endptr, 16); + if (endptr[0] != '\0') { + log_warning("Unable to parse scan code from \"%s\"", key); + continue; + } + + keycode = udev_list_entry_get_value(entry); + + /* a leading '!' needs a force-release entry */ + if (keycode[0] == '!') { + keycode++; + + release[release_count] = scancode; + if (release_count < ELEMENTSOF(release)-1) + release_count++; + + if (keycode[0] == '\0') + continue; + } + + if (fd == -1) { + fd = open_device(node); + if (fd < 0) + return EXIT_FAILURE; + } + + map_keycode(fd, node, scancode, keycode); + } else if (startswith(key, "EVDEV_ABS_")) { + unsigned evcode; + + /* EVDEV_ABS_<EV_ABS code>=<min>:<max>:<res>:<fuzz>:<flat> */ + evcode = strtoul(key + 10, &endptr, 16); + if (endptr[0] != '\0') { + log_warning("Unable to parse EV_ABS code from \"%s\"", key); + continue; + } + + if (fd == -1) { + fd = open_device(node); + if (fd < 0) + return EXIT_FAILURE; + } + + override_abs(fd, node, evcode, udev_list_entry_get_value(entry)); + } else if (streq(key, "POINTINGSTICK_SENSITIVITY")) + set_trackpoint_sensitivity(dev, udev_list_entry_get_value(entry)); + } + + /* install list of force-release codes */ + if (release_count > 0) + install_force_release(dev, release, release_count); + + return EXIT_SUCCESS; +} + +const struct udev_builtin udev_builtin_keyboard = { + .name = "keyboard", + .cmd = builtin_keyboard, + .help = "Keyboard scan code to key mapping", +}; diff --git a/src/grp-udev/libudev-core/udev-builtin-kmod.c b/src/grp-udev/libudev-core/udev-builtin-kmod.c new file mode 100644 index 0000000000..9665f678fd --- /dev/null +++ b/src/grp-udev/libudev-core/udev-builtin-kmod.c @@ -0,0 +1,123 @@ +/* + * load kernel modules + * + * Copyright (C) 2011-2012 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2011 ProFUSION embedded systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <libkmod.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> + +#include "string-util.h" +#include "udev.h" + +static struct kmod_ctx *ctx = NULL; + +static int load_module(struct udev *udev, const char *alias) { + struct kmod_list *list = NULL; + struct kmod_list *l; + int err; + + err = kmod_module_new_from_lookup(ctx, alias, &list); + if (err < 0) + return err; + + if (list == NULL) + log_debug("No module matches '%s'", alias); + + kmod_list_foreach(l, list) { + struct kmod_module *mod = kmod_module_get_module(l); + + err = kmod_module_probe_insert_module(mod, KMOD_PROBE_APPLY_BLACKLIST, NULL, NULL, NULL, NULL); + if (err == KMOD_PROBE_APPLY_BLACKLIST) + log_debug("Module '%s' is blacklisted", kmod_module_get_name(mod)); + else if (err == 0) + log_debug("Inserted '%s'", kmod_module_get_name(mod)); + else + log_debug("Failed to insert '%s'", kmod_module_get_name(mod)); + + kmod_module_unref(mod); + } + + kmod_module_unref_list(list); + return err; +} + +_printf_(6,0) static void udev_kmod_log(void *data, int priority, const char *file, int line, const char *fn, const char *format, va_list args) { + log_internalv(priority, 0, file, line, fn, format, args); +} + +static int builtin_kmod(struct udev_device *dev, int argc, char *argv[], bool test) { + struct udev *udev = udev_device_get_udev(dev); + int i; + + if (!ctx) + return 0; + + if (argc < 3 || !streq(argv[1], "load")) { + log_error("expect: %s load <module>", argv[0]); + return EXIT_FAILURE; + } + + for (i = 2; argv[i]; i++) { + log_debug("Execute '%s' '%s'", argv[1], argv[i]); + load_module(udev, argv[i]); + } + + return EXIT_SUCCESS; +} + +/* called at udev startup and reload */ +static int builtin_kmod_init(struct udev *udev) { + if (ctx) + return 0; + + ctx = kmod_new(NULL, NULL); + if (!ctx) + return -ENOMEM; + + log_debug("Load module index"); + kmod_set_log_fn(ctx, udev_kmod_log, udev); + kmod_load_resources(ctx); + return 0; +} + +/* called on udev shutdown and reload request */ +static void builtin_kmod_exit(struct udev *udev) { + log_debug("Unload module index"); + ctx = kmod_unref(ctx); +} + +/* called every couple of seconds during event activity; 'true' if config has changed */ +static bool builtin_kmod_validate(struct udev *udev) { + log_debug("Validate module index"); + if (!ctx) + return false; + return (kmod_validate_resources(ctx) != KMOD_RESOURCES_OK); +} + +const struct udev_builtin udev_builtin_kmod = { + .name = "kmod", + .cmd = builtin_kmod, + .init = builtin_kmod_init, + .exit = builtin_kmod_exit, + .validate = builtin_kmod_validate, + .help = "Kernel module loader", + .run_once = false, +}; diff --git a/src/grp-udev/libudev-core/udev-builtin-net_id.c b/src/grp-udev/libudev-core/udev-builtin-net_id.c new file mode 100644 index 0000000000..a7be2a4eed --- /dev/null +++ b/src/grp-udev/libudev-core/udev-builtin-net_id.c @@ -0,0 +1,623 @@ +/*** + This file is part of systemd. + + Copyright 2012 Kay Sievers <kay@vrfy.org> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +/* + * Predictable network interface device names based on: + * - firmware/bios-provided index numbers for on-board devices + * - firmware-provided pci-express hotplug slot index number + * - physical/geographical location of the hardware + * - the interface's MAC address + * + * http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames + * + * Two character prefixes based on the type of interface: + * en — Ethernet + * sl — serial line IP (slip) + * wl — wlan + * ww — wwan + * + * Type of names: + * b<number> — BCMA bus core number + * c<bus_id> — CCW bus group name, without leading zeros [s390] + * o<index>[d<dev_port>] — on-board device index number + * s<slot>[f<function>][d<dev_port>] — hotplug slot index number + * x<MAC> — MAC address + * [P<domain>]p<bus>s<slot>[f<function>][d<dev_port>] + * — PCI geographical location + * [P<domain>]p<bus>s<slot>[f<function>][u<port>][..][c<config>][i<interface>] + * — USB port number chain + * + * All multi-function PCI devices will carry the [f<function>] number in the + * device name, including the function 0 device. + * + * When using PCI geography, The PCI domain is only prepended when it is not 0. + * + * For USB devices the full chain of port numbers of hubs is composed. If the + * name gets longer than the maximum number of 15 characters, the name is not + * exported. + * The usual USB configuration == 1 and interface == 0 values are suppressed. + * + * PCI Ethernet card with firmware index "1": + * ID_NET_NAME_ONBOARD=eno1 + * ID_NET_NAME_ONBOARD_LABEL=Ethernet Port 1 + * + * PCI Ethernet card in hotplug slot with firmware index number: + * /sys/devices/pci0000:00/0000:00:1c.3/0000:05:00.0/net/ens1 + * ID_NET_NAME_MAC=enx000000000466 + * ID_NET_NAME_PATH=enp5s0 + * ID_NET_NAME_SLOT=ens1 + * + * PCI Ethernet multi-function card with 2 ports: + * /sys/devices/pci0000:00/0000:00:1c.0/0000:02:00.0/net/enp2s0f0 + * ID_NET_NAME_MAC=enx78e7d1ea46da + * ID_NET_NAME_PATH=enp2s0f0 + * /sys/devices/pci0000:00/0000:00:1c.0/0000:02:00.1/net/enp2s0f1 + * ID_NET_NAME_MAC=enx78e7d1ea46dc + * ID_NET_NAME_PATH=enp2s0f1 + * + * PCI wlan card: + * /sys/devices/pci0000:00/0000:00:1c.1/0000:03:00.0/net/wlp3s0 + * ID_NET_NAME_MAC=wlx0024d7e31130 + * ID_NET_NAME_PATH=wlp3s0 + * + * USB built-in 3G modem: + * /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.4/2-1.4:1.6/net/wwp0s29u1u4i6 + * ID_NET_NAME_MAC=wwx028037ec0200 + * ID_NET_NAME_PATH=wwp0s29u1u4i6 + * + * USB Android phone: + * /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/net/enp0s29u1u2 + * ID_NET_NAME_MAC=enxd626b3450fb5 + * ID_NET_NAME_PATH=enp0s29u1u2 + */ + +#include <errno.h> +#include <fcntl.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <linux/pci_regs.h> + +#include "fd-util.h" +#include "fileio.h" +#include "stdio-util.h" +#include "string-util.h" +#include "udev.h" + +#define ONBOARD_INDEX_MAX (16*1024-1) + +enum netname_type{ + NET_UNDEF, + NET_PCI, + NET_USB, + NET_BCMA, + NET_VIRTIO, + NET_CCWGROUP, +}; + +struct netnames { + enum netname_type type; + + uint8_t mac[6]; + bool mac_valid; + + struct udev_device *pcidev; + char pci_slot[IFNAMSIZ]; + char pci_path[IFNAMSIZ]; + char pci_onboard[IFNAMSIZ]; + const char *pci_onboard_label; + + char usb_ports[IFNAMSIZ]; + char bcma_core[IFNAMSIZ]; + char ccw_group[IFNAMSIZ]; +}; + +/* retrieve on-board index number and label from firmware */ +static int dev_pci_onboard(struct udev_device *dev, struct netnames *names) { + unsigned dev_port = 0; + size_t l; + char *s; + const char *attr; + int idx; + + /* ACPI _DSM — device specific method for naming a PCI or PCI Express device */ + attr = udev_device_get_sysattr_value(names->pcidev, "acpi_index"); + /* SMBIOS type 41 — Onboard Devices Extended Information */ + if (!attr) + attr = udev_device_get_sysattr_value(names->pcidev, "index"); + if (!attr) + return -ENOENT; + + idx = strtoul(attr, NULL, 0); + if (idx <= 0) + return -EINVAL; + + /* Some BIOSes report rubbish indexes that are excessively high (2^24-1 is an index VMware likes to report for + * example). Let's define a cut-off where we don't consider the index reliable anymore. We pick some arbitrary + * cut-off, which is somewhere beyond the realistic number of physical network interface a system might + * have. Ideally the kernel would already filter his crap for us, but it doesn't currently. */ + if (idx > ONBOARD_INDEX_MAX) + return -ENOENT; + + /* kernel provided port index for multiple ports on a single PCI function */ + attr = udev_device_get_sysattr_value(dev, "dev_port"); + if (attr) + dev_port = strtol(attr, NULL, 10); + + s = names->pci_onboard; + l = sizeof(names->pci_onboard); + l = strpcpyf(&s, l, "o%d", idx); + if (dev_port > 0) + l = strpcpyf(&s, l, "d%d", dev_port); + if (l == 0) + names->pci_onboard[0] = '\0'; + + names->pci_onboard_label = udev_device_get_sysattr_value(names->pcidev, "label"); + + return 0; +} + +/* read the 256 bytes PCI configuration space to check the multi-function bit */ +static bool is_pci_multifunction(struct udev_device *dev) { + _cleanup_close_ int fd = -1; + const char *filename; + uint8_t config[64]; + + filename = strjoina(udev_device_get_syspath(dev), "/config"); + fd = open(filename, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return false; + if (read(fd, &config, sizeof(config)) != sizeof(config)) + return false; + + /* bit 0-6 header type, bit 7 multi/single function device */ + if ((config[PCI_HEADER_TYPE] & 0x80) != 0) + return true; + + return false; +} + +static int dev_pci_slot(struct udev_device *dev, struct netnames *names) { + struct udev *udev = udev_device_get_udev(names->pcidev); + unsigned domain, bus, slot, func, dev_port = 0; + size_t l; + char *s; + const char *attr; + struct udev_device *pci = NULL; + char slots[256], str[256]; + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *dent; + int hotplug_slot = 0, err = 0; + + if (sscanf(udev_device_get_sysname(names->pcidev), "%x:%x:%x.%u", &domain, &bus, &slot, &func) != 4) + return -ENOENT; + + /* kernel provided port index for multiple ports on a single PCI function */ + attr = udev_device_get_sysattr_value(dev, "dev_port"); + if (attr) + dev_port = strtol(attr, NULL, 10); + + /* compose a name based on the raw kernel's PCI bus, slot numbers */ + s = names->pci_path; + l = sizeof(names->pci_path); + if (domain > 0) + l = strpcpyf(&s, l, "P%u", domain); + l = strpcpyf(&s, l, "p%us%u", bus, slot); + if (func > 0 || is_pci_multifunction(names->pcidev)) + l = strpcpyf(&s, l, "f%u", func); + if (dev_port > 0) + l = strpcpyf(&s, l, "d%u", dev_port); + if (l == 0) + names->pci_path[0] = '\0'; + + /* ACPI _SUN — slot user number */ + pci = udev_device_new_from_subsystem_sysname(udev, "subsystem", "pci"); + if (!pci) { + err = -ENOENT; + goto out; + } + xsprintf(slots, "%s/slots", udev_device_get_syspath(pci)); + dir = opendir(slots); + if (!dir) { + err = -errno; + goto out; + } + + for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { + int i; + char *rest; + char *address; + + if (dent->d_name[0] == '.') + continue; + i = strtol(dent->d_name, &rest, 10); + if (rest[0] != '\0') + continue; + if (i < 1) + continue; + xsprintf(str, "%s/%s/address", slots, dent->d_name); + if (read_one_line_file(str, &address) >= 0) { + /* match slot address with device by stripping the function */ + if (strneq(address, udev_device_get_sysname(names->pcidev), strlen(address))) + hotplug_slot = i; + free(address); + } + + if (hotplug_slot > 0) + break; + } + + if (hotplug_slot > 0) { + s = names->pci_slot; + l = sizeof(names->pci_slot); + if (domain > 0) + l = strpcpyf(&s, l, "P%d", domain); + l = strpcpyf(&s, l, "s%d", hotplug_slot); + if (func > 0 || is_pci_multifunction(names->pcidev)) + l = strpcpyf(&s, l, "f%d", func); + if (dev_port > 0) + l = strpcpyf(&s, l, "d%d", dev_port); + if (l == 0) + names->pci_slot[0] = '\0'; + } +out: + udev_device_unref(pci); + return err; +} + +static int names_pci(struct udev_device *dev, struct netnames *names) { + struct udev_device *parent; + + assert(dev); + assert(names); + + parent = udev_device_get_parent(dev); + + /* there can only ever be one virtio bus per parent device, so we can + safely ignore any virtio buses. see + <http://lists.linuxfoundation.org/pipermail/virtualization/2015-August/030331.html> */ + while (parent && streq_ptr("virtio", udev_device_get_subsystem(parent))) + parent = udev_device_get_parent(parent); + + if (!parent) + return -ENOENT; + + /* check if our direct parent is a PCI device with no other bus in-between */ + if (streq_ptr("pci", udev_device_get_subsystem(parent))) { + names->type = NET_PCI; + names->pcidev = parent; + } else { + names->pcidev = udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL); + if (!names->pcidev) + return -ENOENT; + } + dev_pci_onboard(dev, names); + dev_pci_slot(dev, names); + return 0; +} + +static int names_usb(struct udev_device *dev, struct netnames *names) { + struct udev_device *usbdev; + char name[256]; + char *ports; + char *config; + char *interf; + size_t l; + char *s; + + assert(dev); + assert(names); + + usbdev = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_interface"); + if (!usbdev) + return -ENOENT; + + /* get USB port number chain, configuration, interface */ + strscpy(name, sizeof(name), udev_device_get_sysname(usbdev)); + s = strchr(name, '-'); + if (!s) + return -EINVAL; + ports = s+1; + + s = strchr(ports, ':'); + if (!s) + return -EINVAL; + s[0] = '\0'; + config = s+1; + + s = strchr(config, '.'); + if (!s) + return -EINVAL; + s[0] = '\0'; + interf = s+1; + + /* prefix every port number in the chain with "u" */ + s = ports; + while ((s = strchr(s, '.'))) + s[0] = 'u'; + s = names->usb_ports; + l = strpcpyl(&s, sizeof(names->usb_ports), "u", ports, NULL); + + /* append USB config number, suppress the common config == 1 */ + if (!streq(config, "1")) + l = strpcpyl(&s, sizeof(names->usb_ports), "c", config, NULL); + + /* append USB interface number, suppress the interface == 0 */ + if (!streq(interf, "0")) + l = strpcpyl(&s, sizeof(names->usb_ports), "i", interf, NULL); + if (l == 0) + return -ENAMETOOLONG; + + names->type = NET_USB; + return 0; +} + +static int names_bcma(struct udev_device *dev, struct netnames *names) { + struct udev_device *bcmadev; + unsigned int core; + + assert(dev); + assert(names); + + bcmadev = udev_device_get_parent_with_subsystem_devtype(dev, "bcma", NULL); + if (!bcmadev) + return -ENOENT; + + /* bus num:core num */ + if (sscanf(udev_device_get_sysname(bcmadev), "bcma%*u:%u", &core) != 1) + return -EINVAL; + /* suppress the common core == 0 */ + if (core > 0) + xsprintf(names->bcma_core, "b%u", core); + + names->type = NET_BCMA; + return 0; +} + +static int names_ccw(struct udev_device *dev, struct netnames *names) { + struct udev_device *cdev; + const char *bus_id; + size_t bus_id_len; + int rc; + + assert(dev); + assert(names); + + /* Retrieve the associated CCW device */ + cdev = udev_device_get_parent(dev); + if (!cdev) + return -ENOENT; + + /* Network devices are always grouped CCW devices */ + if (!streq_ptr("ccwgroup", udev_device_get_subsystem(cdev))) + return -ENOENT; + + /* Retrieve bus-ID of the grouped CCW device. The bus-ID uniquely + * identifies the network device on the Linux on System z channel + * subsystem. Note that the bus-ID contains lowercase characters. + */ + bus_id = udev_device_get_sysname(cdev); + if (!bus_id) + return -ENOENT; + + /* Check the length of the bus-ID. Rely on that the kernel provides + * a correct bus-ID; alternatively, improve this check and parse and + * verify each bus-ID part... + */ + bus_id_len = strlen(bus_id); + if (!bus_id_len || bus_id_len < 8 || bus_id_len > 9) + return -EINVAL; + + /* Strip leading zeros from the bus id for aesthetic purposes. This + * keeps the ccw names stable, yet much shorter in general case of + * bus_id 0.0.0600 -> 600. This is similar to e.g. how PCI domain is + * not prepended when it is zero. + */ + bus_id += strspn(bus_id, ".0"); + + /* Store the CCW bus-ID for use as network device name */ + rc = snprintf(names->ccw_group, sizeof(names->ccw_group), "c%s", bus_id); + if (rc >= 0 && rc < (int)sizeof(names->ccw_group)) + names->type = NET_CCWGROUP; + return 0; +} + +static int names_mac(struct udev_device *dev, struct netnames *names) { + const char *s; + unsigned int i; + unsigned int a1, a2, a3, a4, a5, a6; + + /* check for NET_ADDR_PERM, skip random MAC addresses */ + s = udev_device_get_sysattr_value(dev, "addr_assign_type"); + if (!s) + return EXIT_FAILURE; + i = strtoul(s, NULL, 0); + if (i != 0) + return 0; + + s = udev_device_get_sysattr_value(dev, "address"); + if (!s) + return -ENOENT; + if (sscanf(s, "%x:%x:%x:%x:%x:%x", &a1, &a2, &a3, &a4, &a5, &a6) != 6) + return -EINVAL; + + /* skip empty MAC addresses */ + if (a1 + a2 + a3 + a4 + a5 + a6 == 0) + return -EINVAL; + + names->mac[0] = a1; + names->mac[1] = a2; + names->mac[2] = a3; + names->mac[3] = a4; + names->mac[4] = a5; + names->mac[5] = a6; + names->mac_valid = true; + return 0; +} + +/* IEEE Organizationally Unique Identifier vendor string */ +static int ieee_oui(struct udev_device *dev, struct netnames *names, bool test) { + char str[32]; + + if (!names->mac_valid) + return -ENOENT; + /* skip commonly misused 00:00:00 (Xerox) prefix */ + if (memcmp(names->mac, "\0\0\0", 3) == 0) + return -EINVAL; + xsprintf(str, "OUI:%02X%02X%02X%02X%02X%02X", names->mac[0], + names->mac[1], names->mac[2], names->mac[3], names->mac[4], + names->mac[5]); + udev_builtin_hwdb_lookup(dev, NULL, str, NULL, test); + return 0; +} + +static int builtin_net_id(struct udev_device *dev, int argc, char *argv[], bool test) { + const char *s; + const char *p; + unsigned int i; + const char *devtype; + const char *prefix = "en"; + struct netnames names = {}; + int err; + + /* handle only ARPHRD_ETHER and ARPHRD_SLIP devices */ + s = udev_device_get_sysattr_value(dev, "type"); + if (!s) + return EXIT_FAILURE; + i = strtoul(s, NULL, 0); + switch (i) { + case ARPHRD_ETHER: + prefix = "en"; + break; + case ARPHRD_SLIP: + prefix = "sl"; + break; + default: + return 0; + } + + /* skip stacked devices, like VLANs, ... */ + s = udev_device_get_sysattr_value(dev, "ifindex"); + if (!s) + return EXIT_FAILURE; + p = udev_device_get_sysattr_value(dev, "iflink"); + if (!p) + return EXIT_FAILURE; + if (!streq(s, p)) + return 0; + + devtype = udev_device_get_devtype(dev); + if (devtype) { + if (streq("wlan", devtype)) + prefix = "wl"; + else if (streq("wwan", devtype)) + prefix = "ww"; + } + + err = names_mac(dev, &names); + if (err >= 0 && names.mac_valid) { + char str[IFNAMSIZ]; + + xsprintf(str, "%sx%02x%02x%02x%02x%02x%02x", prefix, + names.mac[0], names.mac[1], names.mac[2], + names.mac[3], names.mac[4], names.mac[5]); + udev_builtin_add_property(dev, test, "ID_NET_NAME_MAC", str); + + ieee_oui(dev, &names, test); + } + + /* get path names for Linux on System z network devices */ + err = names_ccw(dev, &names); + if (err >= 0 && names.type == NET_CCWGROUP) { + char str[IFNAMSIZ]; + + if (snprintf(str, sizeof(str), "%s%s", prefix, names.ccw_group) < (int)sizeof(str)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str); + goto out; + } + + /* get PCI based path names, we compose only PCI based paths */ + err = names_pci(dev, &names); + if (err < 0) + goto out; + + /* plain PCI device */ + if (names.type == NET_PCI) { + char str[IFNAMSIZ]; + + if (names.pci_onboard[0]) + if (snprintf(str, sizeof(str), "%s%s", prefix, names.pci_onboard) < (int)sizeof(str)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_ONBOARD", str); + + if (names.pci_onboard_label) + if (snprintf(str, sizeof(str), "%s%s", prefix, names.pci_onboard_label) < (int)sizeof(str)) + udev_builtin_add_property(dev, test, "ID_NET_LABEL_ONBOARD", str); + + if (names.pci_path[0]) + if (snprintf(str, sizeof(str), "%s%s", prefix, names.pci_path) < (int)sizeof(str)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str); + + if (names.pci_slot[0]) + if (snprintf(str, sizeof(str), "%s%s", prefix, names.pci_slot) < (int)sizeof(str)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str); + goto out; + } + + /* USB device */ + err = names_usb(dev, &names); + if (err >= 0 && names.type == NET_USB) { + char str[IFNAMSIZ]; + + if (names.pci_path[0]) + if (snprintf(str, sizeof(str), "%s%s%s", prefix, names.pci_path, names.usb_ports) < (int)sizeof(str)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str); + + if (names.pci_slot[0]) + if (snprintf(str, sizeof(str), "%s%s%s", prefix, names.pci_slot, names.usb_ports) < (int)sizeof(str)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str); + goto out; + } + + /* Broadcom bus */ + err = names_bcma(dev, &names); + if (err >= 0 && names.type == NET_BCMA) { + char str[IFNAMSIZ]; + + if (names.pci_path[0]) + if (snprintf(str, sizeof(str), "%s%s%s", prefix, names.pci_path, names.bcma_core) < (int)sizeof(str)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str); + + if (names.pci_slot[0]) + if (snprintf(str, sizeof(str), "%s%s%s", prefix, names.pci_slot, names.bcma_core) < (int)sizeof(str)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str); + goto out; + } +out: + return EXIT_SUCCESS; +} + +const struct udev_builtin udev_builtin_net_id = { + .name = "net_id", + .cmd = builtin_net_id, + .help = "Network device properties", +}; diff --git a/src/grp-udev/libudev-core/udev-builtin-net_setup_link.c b/src/grp-udev/libudev-core/udev-builtin-net_setup_link.c new file mode 100644 index 0000000000..8e47775135 --- /dev/null +++ b/src/grp-udev/libudev-core/udev-builtin-net_setup_link.c @@ -0,0 +1,107 @@ +/*** + This file is part of systemd. + + Copyright 2013 Tom Gundersen + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "alloc-util.h" +#include "link-config.h" +#include "log.h" +#include "udev.h" + +static link_config_ctx *ctx = NULL; + +static int builtin_net_setup_link(struct udev_device *dev, int argc, char **argv, bool test) { + _cleanup_free_ char *driver = NULL; + const char *name = NULL; + link_config *link; + int r; + + if (argc > 1) { + log_error("This program takes no arguments."); + return EXIT_FAILURE; + } + + r = link_get_driver(ctx, dev, &driver); + if (r >= 0) + udev_builtin_add_property(dev, test, "ID_NET_DRIVER", driver); + + r = link_config_get(ctx, dev, &link); + if (r < 0) { + if (r == -ENOENT) { + log_debug("No matching link configuration found."); + return EXIT_SUCCESS; + } else { + log_error_errno(r, "Could not get link config: %m"); + return EXIT_FAILURE; + } + } + + r = link_config_apply(ctx, link, dev, &name); + if (r < 0) { + log_error_errno(r, "Could not apply link config to %s: %m", udev_device_get_sysname(dev)); + return EXIT_FAILURE; + } + + udev_builtin_add_property(dev, test, "ID_NET_LINK_FILE", link->filename); + + if (name) + udev_builtin_add_property(dev, test, "ID_NET_NAME", name); + + return EXIT_SUCCESS; +} + +static int builtin_net_setup_link_init(struct udev *udev) { + int r; + + if (ctx) + return 0; + + r = link_config_ctx_new(&ctx); + if (r < 0) + return r; + + r = link_config_load(ctx); + if (r < 0) + return r; + + log_debug("Created link configuration context."); + return 0; +} + +static void builtin_net_setup_link_exit(struct udev *udev) { + link_config_ctx_free(ctx); + ctx = NULL; + log_debug("Unloaded link configuration context."); +} + +static bool builtin_net_setup_link_validate(struct udev *udev) { + log_debug("Check if link configuration needs reloading."); + if (!ctx) + return false; + + return link_config_should_reload(ctx); +} + +const struct udev_builtin udev_builtin_net_setup_link = { + .name = "net_setup_link", + .cmd = builtin_net_setup_link, + .init = builtin_net_setup_link_init, + .exit = builtin_net_setup_link_exit, + .validate = builtin_net_setup_link_validate, + .help = "Configure network link", + .run_once = false, +}; diff --git a/src/grp-udev/libudev-core/udev-builtin-path_id.c b/src/grp-udev/libudev-core/udev-builtin-path_id.c new file mode 100644 index 0000000000..6e9adc6e96 --- /dev/null +++ b/src/grp-udev/libudev-core/udev-builtin-path_id.c @@ -0,0 +1,761 @@ +/* + * compose persistent device path + * + * Copyright (C) 2009-2011 Kay Sievers <kay@vrfy.org> + * + * Logic based on Hannes Reinecke's shell script. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "string-util.h" +#include "udev.h" + +_printf_(2,3) +static int path_prepend(char **path, const char *fmt, ...) { + va_list va; + char *pre; + int err = 0; + + va_start(va, fmt); + err = vasprintf(&pre, fmt, va); + va_end(va); + if (err < 0) + goto out; + + if (*path != NULL) { + char *new; + + err = asprintf(&new, "%s-%s", pre, *path); + free(pre); + if (err < 0) + goto out; + free(*path); + *path = new; + } else { + *path = pre; + } +out: + return err; +} + +/* +** Linux only supports 32 bit luns. +** See drivers/scsi/scsi_scan.c::scsilun_to_int() for more details. +*/ +static int format_lun_number(struct udev_device *dev, char **path) { + unsigned long lun = strtoul(udev_device_get_sysnum(dev), NULL, 10); + + /* address method 0, peripheral device addressing with bus id of zero */ + if (lun < 256) + return path_prepend(path, "lun-%lu", lun); + /* handle all other lun addressing methods by using a variant of the original lun format */ + return path_prepend(path, "lun-0x%04lx%04lx00000000", lun & 0xffff, (lun >> 16) & 0xffff); +} + +static struct udev_device *skip_subsystem(struct udev_device *dev, const char *subsys) { + struct udev_device *parent = dev; + + assert(dev); + assert(subsys); + + while (parent != NULL) { + const char *subsystem; + + subsystem = udev_device_get_subsystem(parent); + if (subsystem == NULL || !streq(subsystem, subsys)) + break; + dev = parent; + parent = udev_device_get_parent(parent); + } + return dev; +} + +static struct udev_device *handle_scsi_fibre_channel(struct udev_device *parent, char **path) { + struct udev *udev = udev_device_get_udev(parent); + struct udev_device *targetdev; + struct udev_device *fcdev = NULL; + const char *port; + char *lun = NULL; + + assert(parent); + assert(path); + + targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target"); + if (targetdev == NULL) + return NULL; + + fcdev = udev_device_new_from_subsystem_sysname(udev, "fc_transport", udev_device_get_sysname(targetdev)); + if (fcdev == NULL) + return NULL; + port = udev_device_get_sysattr_value(fcdev, "port_name"); + if (port == NULL) { + parent = NULL; + goto out; + } + + format_lun_number(parent, &lun); + path_prepend(path, "fc-%s-%s", port, lun); + free(lun); +out: + udev_device_unref(fcdev); + return parent; +} + +static struct udev_device *handle_scsi_sas_wide_port(struct udev_device *parent, char **path) { + struct udev *udev = udev_device_get_udev(parent); + struct udev_device *targetdev; + struct udev_device *target_parent; + struct udev_device *sasdev; + const char *sas_address; + char *lun = NULL; + + assert(parent); + assert(path); + + targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target"); + if (targetdev == NULL) + return NULL; + + target_parent = udev_device_get_parent(targetdev); + if (target_parent == NULL) + return NULL; + + sasdev = udev_device_new_from_subsystem_sysname(udev, "sas_device", + udev_device_get_sysname(target_parent)); + if (sasdev == NULL) + return NULL; + + sas_address = udev_device_get_sysattr_value(sasdev, "sas_address"); + if (sas_address == NULL) { + parent = NULL; + goto out; + } + + format_lun_number(parent, &lun); + path_prepend(path, "sas-%s-%s", sas_address, lun); + free(lun); +out: + udev_device_unref(sasdev); + return parent; +} + +static struct udev_device *handle_scsi_sas(struct udev_device *parent, char **path) +{ + struct udev *udev = udev_device_get_udev(parent); + struct udev_device *targetdev; + struct udev_device *target_parent; + struct udev_device *port; + struct udev_device *expander; + struct udev_device *target_sasdev = NULL; + struct udev_device *expander_sasdev = NULL; + struct udev_device *port_sasdev = NULL; + const char *sas_address = NULL; + const char *phy_id; + const char *phy_count; + char *lun = NULL; + + assert(parent); + assert(path); + + targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target"); + if (targetdev == NULL) + return NULL; + + target_parent = udev_device_get_parent(targetdev); + if (target_parent == NULL) + return NULL; + + /* Get sas device */ + target_sasdev = udev_device_new_from_subsystem_sysname(udev, + "sas_device", udev_device_get_sysname(target_parent)); + if (target_sasdev == NULL) + return NULL; + + /* The next parent is sas port */ + port = udev_device_get_parent(target_parent); + if (port == NULL) { + parent = NULL; + goto out; + } + + /* Get port device */ + port_sasdev = udev_device_new_from_subsystem_sysname(udev, + "sas_port", udev_device_get_sysname(port)); + + phy_count = udev_device_get_sysattr_value(port_sasdev, "num_phys"); + if (phy_count == NULL) { + parent = NULL; + goto out; + } + + /* Check if we are simple disk */ + if (strncmp(phy_count, "1", 2) != 0) { + parent = handle_scsi_sas_wide_port(parent, path); + goto out; + } + + /* Get connected phy */ + phy_id = udev_device_get_sysattr_value(target_sasdev, "phy_identifier"); + if (phy_id == NULL) { + parent = NULL; + goto out; + } + + /* The port's parent is either hba or expander */ + expander = udev_device_get_parent(port); + if (expander == NULL) { + parent = NULL; + goto out; + } + + /* Get expander device */ + expander_sasdev = udev_device_new_from_subsystem_sysname(udev, + "sas_device", udev_device_get_sysname(expander)); + if (expander_sasdev != NULL) { + /* Get expander's address */ + sas_address = udev_device_get_sysattr_value(expander_sasdev, + "sas_address"); + if (sas_address == NULL) { + parent = NULL; + goto out; + } + } + + format_lun_number(parent, &lun); + if (sas_address) + path_prepend(path, "sas-exp%s-phy%s-%s", sas_address, phy_id, lun); + else + path_prepend(path, "sas-phy%s-%s", phy_id, lun); + + free(lun); +out: + udev_device_unref(target_sasdev); + udev_device_unref(expander_sasdev); + udev_device_unref(port_sasdev); + return parent; +} + +static struct udev_device *handle_scsi_iscsi(struct udev_device *parent, char **path) { + struct udev *udev = udev_device_get_udev(parent); + struct udev_device *transportdev; + struct udev_device *sessiondev = NULL; + const char *target; + char *connname; + struct udev_device *conndev = NULL; + const char *addr; + const char *port; + char *lun = NULL; + + assert(parent); + assert(path); + + /* find iscsi session */ + transportdev = parent; + for (;;) { + transportdev = udev_device_get_parent(transportdev); + if (transportdev == NULL) + return NULL; + if (startswith(udev_device_get_sysname(transportdev), "session")) + break; + } + + /* find iscsi session device */ + sessiondev = udev_device_new_from_subsystem_sysname(udev, "iscsi_session", udev_device_get_sysname(transportdev)); + if (sessiondev == NULL) + return NULL; + target = udev_device_get_sysattr_value(sessiondev, "targetname"); + if (target == NULL) { + parent = NULL; + goto out; + } + + if (asprintf(&connname, "connection%s:0", udev_device_get_sysnum(transportdev)) < 0) { + parent = NULL; + goto out; + } + conndev = udev_device_new_from_subsystem_sysname(udev, "iscsi_connection", connname); + free(connname); + if (conndev == NULL) { + parent = NULL; + goto out; + } + addr = udev_device_get_sysattr_value(conndev, "persistent_address"); + port = udev_device_get_sysattr_value(conndev, "persistent_port"); + if (addr == NULL || port == NULL) { + parent = NULL; + goto out; + } + + format_lun_number(parent, &lun); + path_prepend(path, "ip-%s:%s-iscsi-%s-%s", addr, port, target, lun); + free(lun); +out: + udev_device_unref(sessiondev); + udev_device_unref(conndev); + return parent; +} + +static struct udev_device *handle_scsi_ata(struct udev_device *parent, char **path) { + struct udev *udev = udev_device_get_udev(parent); + struct udev_device *targetdev; + struct udev_device *target_parent; + struct udev_device *atadev; + const char *port_no; + + assert(parent); + assert(path); + + targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host"); + if (!targetdev) + return NULL; + + target_parent = udev_device_get_parent(targetdev); + if (!target_parent) + return NULL; + + atadev = udev_device_new_from_subsystem_sysname(udev, "ata_port", udev_device_get_sysname(target_parent)); + if (!atadev) + return NULL; + + port_no = udev_device_get_sysattr_value(atadev, "port_no"); + if (!port_no) { + parent = NULL; + goto out; + } + path_prepend(path, "ata-%s", port_no); +out: + udev_device_unref(atadev); + return parent; +} + +static struct udev_device *handle_scsi_default(struct udev_device *parent, char **path) { + struct udev_device *hostdev; + int host, bus, target, lun; + const char *name; + char *base; + char *pos; + DIR *dir; + struct dirent *dent; + int basenum; + + assert(parent); + assert(path); + + hostdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host"); + if (hostdev == NULL) + return NULL; + + name = udev_device_get_sysname(parent); + if (sscanf(name, "%d:%d:%d:%d", &host, &bus, &target, &lun) != 4) + return NULL; + + /* + * Rebase host offset to get the local relative number + * + * Note: This is by definition racy, unreliable and too simple. + * Please do not copy this model anywhere. It's just a left-over + * from the time we had no idea how things should look like in + * the end. + * + * Making assumptions about a global in-kernel counter and use + * that to calculate a local offset is a very broken concept. It + * can only work as long as things are in strict order. + * + * The kernel needs to export the instance/port number of a + * controller directly, without the need for rebase magic like + * this. Manual driver unbind/bind, parallel hotplug/unplug will + * get into the way of this "I hope it works" logic. + */ + basenum = -1; + base = strdup(udev_device_get_syspath(hostdev)); + if (base == NULL) + return NULL; + pos = strrchr(base, '/'); + if (pos == NULL) { + parent = NULL; + goto out; + } + pos[0] = '\0'; + dir = opendir(base); + if (dir == NULL) { + parent = NULL; + goto out; + } + for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { + char *rest; + int i; + + if (dent->d_name[0] == '.') + continue; + if (dent->d_type != DT_DIR && dent->d_type != DT_LNK) + continue; + if (!startswith(dent->d_name, "host")) + continue; + i = strtoul(&dent->d_name[4], &rest, 10); + if (rest[0] != '\0') + continue; + /* + * find the smallest number; the host really needs to export its + * own instance number per parent device; relying on the global host + * enumeration and plainly rebasing the numbers sounds unreliable + */ + if (basenum == -1 || i < basenum) + basenum = i; + } + closedir(dir); + if (basenum == -1) { + parent = NULL; + goto out; + } + host -= basenum; + + path_prepend(path, "scsi-%u:%u:%u:%u", host, bus, target, lun); +out: + free(base); + return hostdev; +} + +static struct udev_device *handle_scsi_hyperv(struct udev_device *parent, char **path) { + struct udev_device *hostdev; + struct udev_device *vmbusdev; + const char *guid_str; + char *lun = NULL; + char guid[38]; + size_t i, k; + + assert(parent); + assert(path); + + hostdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host"); + if (!hostdev) + return NULL; + + vmbusdev = udev_device_get_parent(hostdev); + if (!vmbusdev) + return NULL; + + guid_str = udev_device_get_sysattr_value(vmbusdev, "device_id"); + if (!guid_str) + return NULL; + + if (strlen(guid_str) < 37 || guid_str[0] != '{' || guid_str[36] != '}') + return NULL; + + for (i = 1, k = 0; i < 36; i++) { + if (guid_str[i] == '-') + continue; + guid[k++] = guid_str[i]; + } + guid[k] = '\0'; + + format_lun_number(parent, &lun); + path_prepend(path, "vmbus-%s-%s", guid, lun); + free(lun); + return parent; +} + +static struct udev_device *handle_scsi(struct udev_device *parent, char **path, bool *supported_parent) { + const char *devtype; + const char *name; + const char *id; + + devtype = udev_device_get_devtype(parent); + if (devtype == NULL || !streq(devtype, "scsi_device")) + return parent; + + /* firewire */ + id = udev_device_get_sysattr_value(parent, "ieee1394_id"); + if (id != NULL) { + parent = skip_subsystem(parent, "scsi"); + path_prepend(path, "ieee1394-0x%s", id); + *supported_parent = true; + goto out; + } + + /* scsi sysfs does not have a "subsystem" for the transport */ + name = udev_device_get_syspath(parent); + + if (strstr(name, "/rport-") != NULL) { + parent = handle_scsi_fibre_channel(parent, path); + *supported_parent = true; + goto out; + } + + if (strstr(name, "/end_device-") != NULL) { + parent = handle_scsi_sas(parent, path); + *supported_parent = true; + goto out; + } + + if (strstr(name, "/session") != NULL) { + parent = handle_scsi_iscsi(parent, path); + *supported_parent = true; + goto out; + } + + if (strstr(name, "/ata") != NULL) { + parent = handle_scsi_ata(parent, path); + goto out; + } + + if (strstr(name, "/vmbus_") != NULL) { + parent = handle_scsi_hyperv(parent, path); + goto out; + } + + parent = handle_scsi_default(parent, path); +out: + return parent; +} + +static struct udev_device *handle_cciss(struct udev_device *parent, char **path) { + const char *str; + unsigned int controller, disk; + + str = udev_device_get_sysname(parent); + if (sscanf(str, "c%ud%u%*s", &controller, &disk) != 2) + return NULL; + + path_prepend(path, "cciss-disk%u", disk); + parent = skip_subsystem(parent, "cciss"); + return parent; +} + +static void handle_scsi_tape(struct udev_device *dev, char **path) { + const char *name; + + /* must be the last device in the syspath */ + if (*path != NULL) + return; + + name = udev_device_get_sysname(dev); + if (startswith(name, "nst") && strchr("lma", name[3]) != NULL) + path_prepend(path, "nst%c", name[3]); + else if (startswith(name, "st") && strchr("lma", name[2]) != NULL) + path_prepend(path, "st%c", name[2]); +} + +static struct udev_device *handle_usb(struct udev_device *parent, char **path) { + const char *devtype; + const char *str; + const char *port; + + devtype = udev_device_get_devtype(parent); + if (devtype == NULL) + return parent; + if (!streq(devtype, "usb_interface") && !streq(devtype, "usb_device")) + return parent; + + str = udev_device_get_sysname(parent); + port = strchr(str, '-'); + if (port == NULL) + return parent; + port++; + + parent = skip_subsystem(parent, "usb"); + path_prepend(path, "usb-0:%s", port); + return parent; +} + +static struct udev_device *handle_bcma(struct udev_device *parent, char **path) { + const char *sysname; + unsigned int core; + + sysname = udev_device_get_sysname(parent); + if (sscanf(sysname, "bcma%*u:%u", &core) != 1) + return NULL; + + path_prepend(path, "bcma-%u", core); + return parent; +} + +/* Handle devices of AP bus in System z platform. */ +static struct udev_device *handle_ap(struct udev_device *parent, char **path) { + const char *type, *func; + + assert(parent); + assert(path); + + type = udev_device_get_sysattr_value(parent, "type"); + func = udev_device_get_sysattr_value(parent, "ap_functions"); + + if (type != NULL && func != NULL) { + path_prepend(path, "ap-%s-%s", type, func); + goto out; + } + path_prepend(path, "ap-%s", udev_device_get_sysname(parent)); +out: + parent = skip_subsystem(parent, "ap"); + return parent; +} + +static int builtin_path_id(struct udev_device *dev, int argc, char *argv[], bool test) { + struct udev_device *parent; + char *path = NULL; + bool supported_transport = false; + bool supported_parent = false; + + assert(dev); + + /* walk up the chain of devices and compose path */ + parent = dev; + while (parent != NULL) { + const char *subsys; + + subsys = udev_device_get_subsystem(parent); + if (subsys == NULL) { + ; + } else if (streq(subsys, "scsi_tape")) { + handle_scsi_tape(parent, &path); + } else if (streq(subsys, "scsi")) { + parent = handle_scsi(parent, &path, &supported_parent); + supported_transport = true; + } else if (streq(subsys, "cciss")) { + parent = handle_cciss(parent, &path); + supported_transport = true; + } else if (streq(subsys, "usb")) { + parent = handle_usb(parent, &path); + supported_transport = true; + } else if (streq(subsys, "bcma")) { + parent = handle_bcma(parent, &path); + supported_transport = true; + } else if (streq(subsys, "serio")) { + path_prepend(&path, "serio-%s", udev_device_get_sysnum(parent)); + parent = skip_subsystem(parent, "serio"); + } else if (streq(subsys, "pci")) { + path_prepend(&path, "pci-%s", udev_device_get_sysname(parent)); + parent = skip_subsystem(parent, "pci"); + supported_parent = true; + } else if (streq(subsys, "platform")) { + path_prepend(&path, "platform-%s", udev_device_get_sysname(parent)); + parent = skip_subsystem(parent, "platform"); + supported_transport = true; + supported_parent = true; + } else if (streq(subsys, "acpi")) { + path_prepend(&path, "acpi-%s", udev_device_get_sysname(parent)); + parent = skip_subsystem(parent, "acpi"); + supported_parent = true; + } else if (streq(subsys, "xen")) { + path_prepend(&path, "xen-%s", udev_device_get_sysname(parent)); + parent = skip_subsystem(parent, "xen"); + supported_parent = true; + } else if (streq(subsys, "virtio")) { + while (parent && streq_ptr("virtio", udev_device_get_subsystem(parent))) + parent = udev_device_get_parent(parent); + path_prepend(&path, "virtio-pci-%s", udev_device_get_sysname(parent)); + supported_transport = true; + supported_parent = true; + } else if (streq(subsys, "scm")) { + path_prepend(&path, "scm-%s", udev_device_get_sysname(parent)); + parent = skip_subsystem(parent, "scm"); + supported_transport = true; + supported_parent = true; + } else if (streq(subsys, "ccw")) { + path_prepend(&path, "ccw-%s", udev_device_get_sysname(parent)); + parent = skip_subsystem(parent, "ccw"); + supported_transport = true; + supported_parent = true; + } else if (streq(subsys, "ccwgroup")) { + path_prepend(&path, "ccwgroup-%s", udev_device_get_sysname(parent)); + parent = skip_subsystem(parent, "ccwgroup"); + supported_transport = true; + supported_parent = true; + } else if (streq(subsys, "ap")) { + parent = handle_ap(parent, &path); + supported_transport = true; + supported_parent = true; + } else if (streq(subsys, "iucv")) { + path_prepend(&path, "iucv-%s", udev_device_get_sysname(parent)); + parent = skip_subsystem(parent, "iucv"); + supported_transport = true; + supported_parent = true; + } + + if (parent) + parent = udev_device_get_parent(parent); + } + + /* + * Do not return devices with an unknown parent device type. They + * might produce conflicting IDs if the parent does not provide a + * unique and predictable name. + */ + if (!supported_parent) + path = mfree(path); + + /* + * Do not return block devices without a well-known transport. Some + * devices do not expose their buses and do not provide a unique + * and predictable name that way. + */ + if (streq_ptr(udev_device_get_subsystem(dev), "block") && !supported_transport) + path = mfree(path); + + if (path != NULL) { + char tag[UTIL_NAME_SIZE]; + size_t i; + const char *p; + + /* compose valid udev tag name */ + for (p = path, i = 0; *p; p++) { + if ((*p >= '0' && *p <= '9') || + (*p >= 'A' && *p <= 'Z') || + (*p >= 'a' && *p <= 'z') || + *p == '-') { + tag[i++] = *p; + continue; + } + + /* skip all leading '_' */ + if (i == 0) + continue; + + /* avoid second '_' */ + if (tag[i-1] == '_') + continue; + + tag[i++] = '_'; + } + /* strip trailing '_' */ + while (i > 0 && tag[i-1] == '_') + i--; + tag[i] = '\0'; + + udev_builtin_add_property(dev, test, "ID_PATH", path); + udev_builtin_add_property(dev, test, "ID_PATH_TAG", tag); + free(path); + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +const struct udev_builtin udev_builtin_path_id = { + .name = "path_id", + .cmd = builtin_path_id, + .help = "Compose persistent device path", + .run_once = true, +}; diff --git a/src/grp-udev/libudev-core/udev-builtin-uaccess.c b/src/grp-udev/libudev-core/udev-builtin-uaccess.c new file mode 100644 index 0000000000..2c27116ae9 --- /dev/null +++ b/src/grp-udev/libudev-core/udev-builtin-uaccess.c @@ -0,0 +1,88 @@ +/* + * manage device node user ACL + * + * Copyright 2010-2012 Kay Sievers <kay@vrfy.org> + * Copyright 2010 Lennart Poettering + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +#include <systemd/sd-login.h> + +#include "login-util.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; + + umask(0022); + + /* don't muck around with ACLs when the system is not running systemd */ + if (!logind_running()) + return 0; + + path = udev_device_get_devnode(dev); + seat = udev_device_get_property_value(dev, "ID_SEAT"); + if (!seat) + seat = "seat0"; + + r = sd_seat_get_active(seat, NULL, &uid); + if (r == -ENXIO || r == -ENODATA) { + /* No active session on this seat */ + r = 0; + goto finish; + } else if (r < 0) { + log_error("Failed to determine active user on seat %s.", seat); + goto finish; + } + + r = devnode_acl(path, true, false, 0, true, uid); + if (r < 0) { + log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_ERR, r, "Failed to apply ACL on %s: %m", path); + goto finish; + } + + changed_acl = true; + r = 0; + +finish: + if (path && !changed_acl) { + int k; + + /* Better be safe than sorry and reset ACL */ + k = devnode_acl(path, true, false, 0, false, 0); + if (k < 0) { + log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, k, "Failed to apply ACL on %s: %m", path); + if (r >= 0) + r = k; + } + } + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + +const struct udev_builtin udev_builtin_uaccess = { + .name = "uaccess", + .cmd = builtin_uaccess, + .help = "Manage device node user ACL", +}; diff --git a/src/grp-udev/libudev-core/udev-builtin-usb_id.c b/src/grp-udev/libudev-core/udev-builtin-usb_id.c new file mode 100644 index 0000000000..587649eff0 --- /dev/null +++ b/src/grp-udev/libudev-core/udev-builtin-usb_id.c @@ -0,0 +1,473 @@ +/* + * USB device properties and persistent device path + * + * Copyright (c) 2005 SUSE Linux Products GmbH, Germany + * Author: Hannes Reinecke <hare@suse.de> + * + * Copyright (C) 2005-2011 Kay Sievers <kay@vrfy.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "fd-util.h" +#include "string-util.h" +#include "udev.h" + +static void set_usb_iftype(char *to, int if_class_num, size_t len) { + const char *type = "generic"; + + switch (if_class_num) { + case 1: + type = "audio"; + break; + case 2: /* CDC-Control */ + break; + case 3: + type = "hid"; + break; + case 5: /* Physical */ + break; + case 6: + type = "media"; + break; + case 7: + type = "printer"; + break; + case 8: + type = "storage"; + break; + case 9: + type = "hub"; + break; + case 0x0a: /* CDC-Data */ + break; + case 0x0b: /* Chip/Smart Card */ + break; + case 0x0d: /* Content Security */ + break; + case 0x0e: + type = "video"; + break; + case 0xdc: /* Diagnostic Device */ + break; + case 0xe0: /* Wireless Controller */ + break; + case 0xfe: /* Application-specific */ + break; + case 0xff: /* Vendor-specific */ + break; + default: + break; + } + strncpy(to, type, len); + to[len-1] = '\0'; +} + +static int set_usb_mass_storage_ifsubtype(char *to, const char *from, size_t len) { + int type_num = 0; + char *eptr; + const char *type = "generic"; + + type_num = strtoul(from, &eptr, 0); + if (eptr != from) { + switch (type_num) { + case 1: /* RBC devices */ + type = "rbc"; + break; + case 2: + type = "atapi"; + break; + case 3: + type = "tape"; + break; + case 4: /* UFI */ + type = "floppy"; + break; + case 6: /* Transparent SPC-2 devices */ + type = "scsi"; + break; + default: + break; + } + } + strscpy(to, len, type); + return type_num; +} + +static void set_scsi_type(char *to, const char *from, size_t len) { + int type_num; + char *eptr; + const char *type = "generic"; + + type_num = strtoul(from, &eptr, 0); + if (eptr != from) { + switch (type_num) { + case 0: + case 0xe: + type = "disk"; + break; + case 1: + type = "tape"; + break; + case 4: + case 7: + case 0xf: + type = "optical"; + break; + case 5: + type = "cd"; + break; + default: + break; + } + } + strscpy(to, len, type); +} + +#define USB_DT_DEVICE 0x01 +#define USB_DT_INTERFACE 0x04 + +static int dev_if_packed_info(struct udev_device *dev, char *ifs_str, size_t len) { + _cleanup_free_ char *filename = NULL; + _cleanup_close_ int fd = -1; + ssize_t size; + unsigned char buf[18 + 65535]; + size_t pos = 0; + unsigned strpos = 0; + struct usb_interface_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; + } _packed_; + + if (asprintf(&filename, "%s/descriptors", udev_device_get_syspath(dev)) < 0) + return log_oom(); + + fd = open(filename, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_debug_errno(errno, "Error opening USB device 'descriptors' file: %m"); + + size = read(fd, buf, sizeof(buf)); + if (size < 18 || size == sizeof(buf)) + return -EIO; + + ifs_str[0] = '\0'; + while (pos + sizeof(struct usb_interface_descriptor) < (size_t) size && + strpos + 7 < len - 2) { + + struct usb_interface_descriptor *desc; + char if_str[8]; + + desc = (struct usb_interface_descriptor *) &buf[pos]; + if (desc->bLength < 3) + break; + pos += desc->bLength; + + if (desc->bDescriptorType != USB_DT_INTERFACE) + continue; + + if (snprintf(if_str, 8, ":%02x%02x%02x", + desc->bInterfaceClass, + desc->bInterfaceSubClass, + desc->bInterfaceProtocol) != 7) + continue; + + if (strstr(ifs_str, if_str) != NULL) + continue; + + memcpy(&ifs_str[strpos], if_str, 8), + strpos += 7; + } + + if (strpos > 0) { + ifs_str[strpos++] = ':'; + ifs_str[strpos++] = '\0'; + } + + return 0; +} + +/* + * A unique USB identification is generated like this: + * + * 1.) Get the USB device type from InterfaceClass and InterfaceSubClass + * 2.) If the device type is 'Mass-Storage/SPC-2' or 'Mass-Storage/RBC', + * use the SCSI vendor and model as USB-Vendor and USB-model. + * 3.) Otherwise, use the USB manufacturer and product as + * USB-Vendor and USB-model. Any non-printable characters + * in those strings will be skipped; a slash '/' will be converted + * into a full stop '.'. + * 4.) If that fails, too, we will use idVendor and idProduct + * as USB-Vendor and USB-model. + * 5.) The USB identification is the USB-vendor and USB-model + * string concatenated with an underscore '_'. + * 6.) If the device supplies a serial number, this number + * is concatenated with the identification with an underscore '_'. + */ +static int builtin_usb_id(struct udev_device *dev, int argc, char *argv[], bool test) { + char vendor_str[64] = ""; + char vendor_str_enc[256]; + const char *vendor_id; + char model_str[64] = ""; + char model_str_enc[256]; + const char *product_id; + char serial_str[UTIL_NAME_SIZE] = ""; + char packed_if_str[UTIL_NAME_SIZE] = ""; + char revision_str[64] = ""; + char type_str[64] = ""; + char instance_str[64] = ""; + const char *ifnum = NULL; + const char *driver = NULL; + char serial[256]; + + struct udev_device *dev_interface = NULL; + struct udev_device *dev_usb = NULL; + const char *if_class, *if_subclass; + int if_class_num; + int protocol = 0; + size_t l; + char *s; + + assert(dev); + + /* shortcut, if we are called directly for a "usb_device" type */ + if (udev_device_get_devtype(dev) != NULL && streq(udev_device_get_devtype(dev), "usb_device")) { + dev_if_packed_info(dev, packed_if_str, sizeof(packed_if_str)); + dev_usb = dev; + goto fallback; + } + + /* usb interface directory */ + dev_interface = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_interface"); + if (dev_interface == NULL) { + log_debug("unable to access usb_interface device of '%s'", + udev_device_get_syspath(dev)); + return EXIT_FAILURE; + } + + ifnum = udev_device_get_sysattr_value(dev_interface, "bInterfaceNumber"); + driver = udev_device_get_sysattr_value(dev_interface, "driver"); + + if_class = udev_device_get_sysattr_value(dev_interface, "bInterfaceClass"); + if (!if_class) { + log_debug("%s: cannot get bInterfaceClass attribute", + udev_device_get_sysname(dev)); + return EXIT_FAILURE; + } + + if_class_num = strtoul(if_class, NULL, 16); + if (if_class_num == 8) { + /* mass storage */ + if_subclass = udev_device_get_sysattr_value(dev_interface, "bInterfaceSubClass"); + if (if_subclass != NULL) + protocol = set_usb_mass_storage_ifsubtype(type_str, if_subclass, sizeof(type_str)-1); + } else { + set_usb_iftype(type_str, if_class_num, sizeof(type_str)-1); + } + + log_debug("%s: if_class %d protocol %d", + udev_device_get_syspath(dev_interface), if_class_num, protocol); + + /* usb device directory */ + dev_usb = udev_device_get_parent_with_subsystem_devtype(dev_interface, "usb", "usb_device"); + if (!dev_usb) { + log_debug("unable to find parent 'usb' device of '%s'", + udev_device_get_syspath(dev)); + return EXIT_FAILURE; + } + + /* all interfaces of the device in a single string */ + dev_if_packed_info(dev_usb, packed_if_str, sizeof(packed_if_str)); + + /* mass storage : SCSI or ATAPI */ + if (protocol == 6 || protocol == 2) { + struct udev_device *dev_scsi; + const char *scsi_model, *scsi_vendor, *scsi_type, *scsi_rev; + int host, bus, target, lun; + + /* get scsi device */ + dev_scsi = udev_device_get_parent_with_subsystem_devtype(dev, "scsi", "scsi_device"); + if (dev_scsi == NULL) { + log_debug("unable to find parent 'scsi' device of '%s'", + udev_device_get_syspath(dev)); + goto fallback; + } + if (sscanf(udev_device_get_sysname(dev_scsi), "%d:%d:%d:%d", &host, &bus, &target, &lun) != 4) { + log_debug("invalid scsi device '%s'", udev_device_get_sysname(dev_scsi)); + goto fallback; + } + + /* Generic SPC-2 device */ + scsi_vendor = udev_device_get_sysattr_value(dev_scsi, "vendor"); + if (!scsi_vendor) { + log_debug("%s: cannot get SCSI vendor attribute", + udev_device_get_sysname(dev_scsi)); + goto fallback; + } + udev_util_encode_string(scsi_vendor, vendor_str_enc, sizeof(vendor_str_enc)); + util_replace_whitespace(scsi_vendor, vendor_str, sizeof(vendor_str)-1); + util_replace_chars(vendor_str, NULL); + + scsi_model = udev_device_get_sysattr_value(dev_scsi, "model"); + if (!scsi_model) { + log_debug("%s: cannot get SCSI model attribute", + udev_device_get_sysname(dev_scsi)); + goto fallback; + } + udev_util_encode_string(scsi_model, model_str_enc, sizeof(model_str_enc)); + util_replace_whitespace(scsi_model, model_str, sizeof(model_str)-1); + util_replace_chars(model_str, NULL); + + scsi_type = udev_device_get_sysattr_value(dev_scsi, "type"); + if (!scsi_type) { + log_debug("%s: cannot get SCSI type attribute", + udev_device_get_sysname(dev_scsi)); + goto fallback; + } + set_scsi_type(type_str, scsi_type, sizeof(type_str)-1); + + scsi_rev = udev_device_get_sysattr_value(dev_scsi, "rev"); + if (!scsi_rev) { + log_debug("%s: cannot get SCSI revision attribute", + udev_device_get_sysname(dev_scsi)); + goto fallback; + } + util_replace_whitespace(scsi_rev, revision_str, sizeof(revision_str)-1); + util_replace_chars(revision_str, NULL); + + /* + * some broken devices have the same identifiers + * for all luns, export the target:lun number + */ + sprintf(instance_str, "%d:%d", target, lun); + } + +fallback: + vendor_id = udev_device_get_sysattr_value(dev_usb, "idVendor"); + product_id = udev_device_get_sysattr_value(dev_usb, "idProduct"); + + /* fallback to USB vendor & device */ + if (vendor_str[0] == '\0') { + const char *usb_vendor = NULL; + + usb_vendor = udev_device_get_sysattr_value(dev_usb, "manufacturer"); + if (!usb_vendor) + usb_vendor = vendor_id; + if (!usb_vendor) { + log_debug("No USB vendor information available"); + return EXIT_FAILURE; + } + udev_util_encode_string(usb_vendor, vendor_str_enc, sizeof(vendor_str_enc)); + util_replace_whitespace(usb_vendor, vendor_str, sizeof(vendor_str)-1); + util_replace_chars(vendor_str, NULL); + } + + if (model_str[0] == '\0') { + const char *usb_model = NULL; + + usb_model = udev_device_get_sysattr_value(dev_usb, "product"); + if (!usb_model) + usb_model = product_id; + if (!usb_model) + return EXIT_FAILURE; + udev_util_encode_string(usb_model, model_str_enc, sizeof(model_str_enc)); + util_replace_whitespace(usb_model, model_str, sizeof(model_str)-1); + util_replace_chars(model_str, NULL); + } + + if (revision_str[0] == '\0') { + const char *usb_rev; + + usb_rev = udev_device_get_sysattr_value(dev_usb, "bcdDevice"); + if (usb_rev) { + util_replace_whitespace(usb_rev, revision_str, sizeof(revision_str)-1); + util_replace_chars(revision_str, NULL); + } + } + + if (serial_str[0] == '\0') { + const char *usb_serial; + + usb_serial = udev_device_get_sysattr_value(dev_usb, "serial"); + if (usb_serial) { + const unsigned char *p; + + /* http://msdn.microsoft.com/en-us/library/windows/hardware/gg487321.aspx */ + for (p = (unsigned char *)usb_serial; *p != '\0'; p++) + if (*p < 0x20 || *p > 0x7f || *p == ',') { + usb_serial = NULL; + break; + } + } + + if (usb_serial) { + util_replace_whitespace(usb_serial, serial_str, sizeof(serial_str)-1); + util_replace_chars(serial_str, NULL); + } + } + + s = serial; + l = strpcpyl(&s, sizeof(serial), vendor_str, "_", model_str, NULL); + if (!isempty(serial_str)) + l = strpcpyl(&s, l, "_", serial_str, NULL); + + if (!isempty(instance_str)) + strpcpyl(&s, l, "-", instance_str, NULL); + + udev_builtin_add_property(dev, test, "ID_VENDOR", vendor_str); + udev_builtin_add_property(dev, test, "ID_VENDOR_ENC", vendor_str_enc); + udev_builtin_add_property(dev, test, "ID_VENDOR_ID", vendor_id); + udev_builtin_add_property(dev, test, "ID_MODEL", model_str); + udev_builtin_add_property(dev, test, "ID_MODEL_ENC", model_str_enc); + udev_builtin_add_property(dev, test, "ID_MODEL_ID", product_id); + udev_builtin_add_property(dev, test, "ID_REVISION", revision_str); + udev_builtin_add_property(dev, test, "ID_SERIAL", serial); + if (!isempty(serial_str)) + udev_builtin_add_property(dev, test, "ID_SERIAL_SHORT", serial_str); + if (!isempty(type_str)) + udev_builtin_add_property(dev, test, "ID_TYPE", type_str); + if (!isempty(instance_str)) + udev_builtin_add_property(dev, test, "ID_INSTANCE", instance_str); + udev_builtin_add_property(dev, test, "ID_BUS", "usb"); + if (!isempty(packed_if_str)) + udev_builtin_add_property(dev, test, "ID_USB_INTERFACES", packed_if_str); + if (ifnum != NULL) + udev_builtin_add_property(dev, test, "ID_USB_INTERFACE_NUM", ifnum); + if (driver != NULL) + udev_builtin_add_property(dev, test, "ID_USB_DRIVER", driver); + return EXIT_SUCCESS; +} + +const struct udev_builtin udev_builtin_usb_id = { + .name = "usb_id", + .cmd = builtin_usb_id, + .help = "USB device properties", + .run_once = true, +}; diff --git a/src/grp-udev/libudev-core/udev-builtin.c b/src/grp-udev/libudev-core/udev-builtin.c new file mode 100644 index 0000000000..e6b36f124f --- /dev/null +++ b/src/grp-udev/libudev-core/udev-builtin.c @@ -0,0 +1,142 @@ +/*** + This file is part of systemd. + + Copyright 2007-2012 Kay Sievers <kay@vrfy.org> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <getopt.h> +#include <stdio.h> +#include <string.h> + +#include "string-util.h" +#include "udev.h" + +static bool initialized; + +static const struct udev_builtin *builtins[] = { +#ifdef HAVE_BLKID + [UDEV_BUILTIN_BLKID] = &udev_builtin_blkid, +#endif + [UDEV_BUILTIN_BTRFS] = &udev_builtin_btrfs, + [UDEV_BUILTIN_HWDB] = &udev_builtin_hwdb, + [UDEV_BUILTIN_INPUT_ID] = &udev_builtin_input_id, + [UDEV_BUILTIN_KEYBOARD] = &udev_builtin_keyboard, +#ifdef HAVE_KMOD + [UDEV_BUILTIN_KMOD] = &udev_builtin_kmod, +#endif + [UDEV_BUILTIN_NET_ID] = &udev_builtin_net_id, + [UDEV_BUILTIN_NET_LINK] = &udev_builtin_net_setup_link, + [UDEV_BUILTIN_PATH_ID] = &udev_builtin_path_id, + [UDEV_BUILTIN_USB_ID] = &udev_builtin_usb_id, +#ifdef HAVE_ACL + [UDEV_BUILTIN_UACCESS] = &udev_builtin_uaccess, +#endif +}; + +void udev_builtin_init(struct udev *udev) { + unsigned int i; + + if (initialized) + return; + + for (i = 0; i < ELEMENTSOF(builtins); i++) + if (builtins[i] && builtins[i]->init) + builtins[i]->init(udev); + + initialized = true; +} + +void udev_builtin_exit(struct udev *udev) { + unsigned int i; + + if (!initialized) + return; + + for (i = 0; i < ELEMENTSOF(builtins); i++) + if (builtins[i] && builtins[i]->exit) + builtins[i]->exit(udev); + + initialized = false; +} + +bool udev_builtin_validate(struct udev *udev) { + unsigned int i; + + for (i = 0; i < ELEMENTSOF(builtins); i++) + if (builtins[i] && builtins[i]->validate && builtins[i]->validate(udev)) + return true; + return false; +} + +void udev_builtin_list(struct udev *udev) { + unsigned int i; + + for (i = 0; i < ELEMENTSOF(builtins); i++) + if (builtins[i]) + fprintf(stderr, " %-14s %s\n", builtins[i]->name, builtins[i]->help); +} + +const char *udev_builtin_name(enum udev_builtin_cmd cmd) { + if (!builtins[cmd]) + return NULL; + + return builtins[cmd]->name; +} + +bool udev_builtin_run_once(enum udev_builtin_cmd cmd) { + if (!builtins[cmd]) + return false; + + return builtins[cmd]->run_once; +} + +enum udev_builtin_cmd udev_builtin_lookup(const char *command) { + char name[UTIL_PATH_SIZE]; + enum udev_builtin_cmd i; + char *pos; + + strscpy(name, sizeof(name), command); + pos = strchr(name, ' '); + if (pos) + pos[0] = '\0'; + for (i = 0; i < ELEMENTSOF(builtins); i++) + if (builtins[i] && streq(builtins[i]->name, name)) + return i; + return UDEV_BUILTIN_MAX; +} + +int udev_builtin_run(struct udev_device *dev, enum udev_builtin_cmd cmd, const char *command, bool test) { + char arg[UTIL_PATH_SIZE]; + int argc; + char *argv[128]; + + if (!builtins[cmd]) + return -EOPNOTSUPP; + + /* we need '0' here to reset the internal state */ + optind = 0; + strscpy(arg, sizeof(arg), command); + udev_build_argv(udev_device_get_udev(dev), arg, &argc, argv); + return builtins[cmd]->cmd(dev, argc, argv, test); +} + +int udev_builtin_add_property(struct udev_device *dev, bool test, const char *key, const char *val) { + udev_device_add_property(dev, key, val); + + if (test) + printf("%s=%s\n", key, val); + return 0; +} diff --git a/src/grp-udev/libudev-core/udev-ctrl.c b/src/grp-udev/libudev-core/udev-ctrl.c new file mode 100644 index 0000000000..f68a09d7a8 --- /dev/null +++ b/src/grp-udev/libudev-core/udev-ctrl.c @@ -0,0 +1,462 @@ +/* + * libudev - interface to udev device information + * + * Copyright (C) 2008 Kay Sievers <kay@vrfy.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + */ + +#include <errno.h> +#include <poll.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "socket-util.h" +#include "udev.h" + +/* wire protocol magic must match */ +#define UDEV_CTRL_MAGIC 0xdead1dea + +enum udev_ctrl_msg_type { + UDEV_CTRL_UNKNOWN, + UDEV_CTRL_SET_LOG_LEVEL, + UDEV_CTRL_STOP_EXEC_QUEUE, + UDEV_CTRL_START_EXEC_QUEUE, + UDEV_CTRL_RELOAD, + UDEV_CTRL_SET_ENV, + UDEV_CTRL_SET_CHILDREN_MAX, + UDEV_CTRL_PING, + UDEV_CTRL_EXIT, +}; + +struct udev_ctrl_msg_wire { + char version[16]; + unsigned int magic; + enum udev_ctrl_msg_type type; + union { + int intval; + char buf[256]; + }; +}; + +struct udev_ctrl_msg { + int refcount; + struct udev_ctrl_connection *conn; + struct udev_ctrl_msg_wire ctrl_msg_wire; +}; + +struct udev_ctrl { + int refcount; + struct udev *udev; + int sock; + union sockaddr_union saddr; + socklen_t addrlen; + bool bound; + bool cleanup_socket; + bool connected; +}; + +struct udev_ctrl_connection { + int refcount; + struct udev_ctrl *uctrl; + int sock; +}; + +struct udev_ctrl *udev_ctrl_new_from_fd(struct udev *udev, int fd) { + struct udev_ctrl *uctrl; + const int on = 1; + int r; + + uctrl = new0(struct udev_ctrl, 1); + if (uctrl == NULL) + return NULL; + uctrl->refcount = 1; + uctrl->udev = udev; + + if (fd < 0) { + uctrl->sock = socket(AF_LOCAL, SOCK_SEQPACKET|SOCK_NONBLOCK|SOCK_CLOEXEC, 0); + if (uctrl->sock < 0) { + log_error_errno(errno, "error getting socket: %m"); + udev_ctrl_unref(uctrl); + return NULL; + } + } else { + uctrl->bound = true; + uctrl->sock = fd; + } + + /* + * FIXME: remove it as soon as we can depend on this: + * http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=90c6bd34f884cd9cee21f1d152baf6c18bcac949 + */ + r = setsockopt(uctrl->sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); + if (r < 0) + log_warning_errno(errno, "could not set SO_PASSCRED: %m"); + + uctrl->saddr.un.sun_family = AF_LOCAL; + strscpy(uctrl->saddr.un.sun_path, sizeof(uctrl->saddr.un.sun_path), "/run/udev/control"); + uctrl->addrlen = SOCKADDR_UN_LEN(uctrl->saddr.un); + return uctrl; +} + +struct udev_ctrl *udev_ctrl_new(struct udev *udev) { + return udev_ctrl_new_from_fd(udev, -1); +} + +int udev_ctrl_enable_receiving(struct udev_ctrl *uctrl) { + int err; + + if (!uctrl->bound) { + err = bind(uctrl->sock, &uctrl->saddr.sa, uctrl->addrlen); + if (err < 0 && errno == EADDRINUSE) { + unlink(uctrl->saddr.un.sun_path); + err = bind(uctrl->sock, &uctrl->saddr.sa, uctrl->addrlen); + } + + if (err < 0) + return log_error_errno(errno, "bind failed: %m"); + + err = listen(uctrl->sock, 0); + if (err < 0) + return log_error_errno(errno, "listen failed: %m"); + + uctrl->bound = true; + uctrl->cleanup_socket = true; + } + return 0; +} + +struct udev *udev_ctrl_get_udev(struct udev_ctrl *uctrl) { + return uctrl->udev; +} + +static struct udev_ctrl *udev_ctrl_ref(struct udev_ctrl *uctrl) { + if (uctrl) + uctrl->refcount++; + + return uctrl; +} + +struct udev_ctrl *udev_ctrl_unref(struct udev_ctrl *uctrl) { + if (uctrl && -- uctrl->refcount == 0) { + if (uctrl->sock >= 0) + close(uctrl->sock); + free(uctrl); + } + + return NULL; +} + +int udev_ctrl_cleanup(struct udev_ctrl *uctrl) { + if (uctrl == NULL) + return 0; + if (uctrl->cleanup_socket) + unlink(uctrl->saddr.un.sun_path); + return 0; +} + +int udev_ctrl_get_fd(struct udev_ctrl *uctrl) { + if (uctrl == NULL) + return -EINVAL; + return uctrl->sock; +} + +struct udev_ctrl_connection *udev_ctrl_get_connection(struct udev_ctrl *uctrl) { + struct udev_ctrl_connection *conn; + struct ucred ucred = {}; + const int on = 1; + int r; + + conn = new(struct udev_ctrl_connection, 1); + if (conn == NULL) + return NULL; + conn->refcount = 1; + conn->uctrl = uctrl; + + conn->sock = accept4(uctrl->sock, NULL, NULL, SOCK_CLOEXEC|SOCK_NONBLOCK); + if (conn->sock < 0) { + if (errno != EINTR) + log_error_errno(errno, "unable to receive ctrl connection: %m"); + goto err; + } + + /* check peer credential of connection */ + r = getpeercred(conn->sock, &ucred); + if (r < 0) { + log_error_errno(r, "unable to receive credentials of ctrl connection: %m"); + goto err; + } + if (ucred.uid > 0) { + log_error("sender uid="UID_FMT", message ignored", ucred.uid); + goto err; + } + + /* enable receiving of the sender credentials in the messages */ + r = setsockopt(conn->sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); + if (r < 0) + log_warning_errno(errno, "could not set SO_PASSCRED: %m"); + + udev_ctrl_ref(uctrl); + return conn; +err: + if (conn->sock >= 0) + close(conn->sock); + 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 && -- conn->refcount == 0) { + if (conn->sock >= 0) + close(conn->sock); + + udev_ctrl_unref(conn->uctrl); + + free(conn); + } + + return NULL; +} + +static int ctrl_send(struct udev_ctrl *uctrl, enum udev_ctrl_msg_type type, int intval, const char *buf, int timeout) { + struct udev_ctrl_msg_wire ctrl_msg_wire; + int err = 0; + + memzero(&ctrl_msg_wire, sizeof(struct udev_ctrl_msg_wire)); + strcpy(ctrl_msg_wire.version, "udev-" VERSION); + ctrl_msg_wire.magic = UDEV_CTRL_MAGIC; + ctrl_msg_wire.type = type; + + if (buf != NULL) + strscpy(ctrl_msg_wire.buf, sizeof(ctrl_msg_wire.buf), buf); + else + ctrl_msg_wire.intval = intval; + + if (!uctrl->connected) { + if (connect(uctrl->sock, &uctrl->saddr.sa, uctrl->addrlen) < 0) { + err = -errno; + goto out; + } + uctrl->connected = true; + } + if (send(uctrl->sock, &ctrl_msg_wire, sizeof(ctrl_msg_wire), 0) < 0) { + err = -errno; + goto out; + } + + /* wait for peer message handling or disconnect */ + for (;;) { + struct pollfd pfd[1]; + int r; + + pfd[0].fd = uctrl->sock; + pfd[0].events = POLLIN; + r = poll(pfd, 1, timeout * MSEC_PER_SEC); + if (r < 0) { + if (errno == EINTR) + continue; + err = -errno; + break; + } + + if (r > 0 && pfd[0].revents & POLLERR) { + err = -EIO; + break; + } + + if (r == 0) + err = -ETIMEDOUT; + break; + } +out: + return err; +} + +int udev_ctrl_send_set_log_level(struct udev_ctrl *uctrl, int priority, int timeout) { + return ctrl_send(uctrl, UDEV_CTRL_SET_LOG_LEVEL, priority, NULL, timeout); +} + +int udev_ctrl_send_stop_exec_queue(struct udev_ctrl *uctrl, int timeout) { + return ctrl_send(uctrl, UDEV_CTRL_STOP_EXEC_QUEUE, 0, NULL, timeout); +} + +int udev_ctrl_send_start_exec_queue(struct udev_ctrl *uctrl, int timeout) { + return ctrl_send(uctrl, UDEV_CTRL_START_EXEC_QUEUE, 0, NULL, timeout); +} + +int udev_ctrl_send_reload(struct udev_ctrl *uctrl, int timeout) { + return ctrl_send(uctrl, UDEV_CTRL_RELOAD, 0, NULL, timeout); +} + +int udev_ctrl_send_set_env(struct udev_ctrl *uctrl, const char *key, int timeout) { + return ctrl_send(uctrl, UDEV_CTRL_SET_ENV, 0, key, timeout); +} + +int udev_ctrl_send_set_children_max(struct udev_ctrl *uctrl, int count, int timeout) { + return ctrl_send(uctrl, UDEV_CTRL_SET_CHILDREN_MAX, count, NULL, timeout); +} + +int udev_ctrl_send_ping(struct udev_ctrl *uctrl, int timeout) { + return ctrl_send(uctrl, UDEV_CTRL_PING, 0, NULL, timeout); +} + +int udev_ctrl_send_exit(struct udev_ctrl *uctrl, int timeout) { + return ctrl_send(uctrl, UDEV_CTRL_EXIT, 0, NULL, timeout); +} + +struct udev_ctrl_msg *udev_ctrl_receive_msg(struct udev_ctrl_connection *conn) { + struct udev_ctrl_msg *uctrl_msg; + ssize_t size; + struct cmsghdr *cmsg; + struct iovec iov; + char cred_msg[CMSG_SPACE(sizeof(struct ucred))]; + struct msghdr smsg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cred_msg, + .msg_controllen = sizeof(cred_msg), + }; + struct ucred *cred; + + uctrl_msg = new0(struct udev_ctrl_msg, 1); + if (uctrl_msg == NULL) + return NULL; + uctrl_msg->refcount = 1; + uctrl_msg->conn = conn; + udev_ctrl_connection_ref(conn); + + /* wait for the incoming message */ + for (;;) { + struct pollfd pfd[1]; + int r; + + pfd[0].fd = conn->sock; + pfd[0].events = POLLIN; + + r = poll(pfd, 1, 10000); + if (r < 0) { + if (errno == EINTR) + continue; + goto err; + } else if (r == 0) { + log_error("timeout waiting for ctrl message"); + goto err; + } else { + if (!(pfd[0].revents & POLLIN)) { + log_error_errno(errno, "ctrl connection error: %m"); + goto err; + } + } + + break; + } + + iov.iov_base = &uctrl_msg->ctrl_msg_wire; + iov.iov_len = sizeof(struct udev_ctrl_msg_wire); + + size = recvmsg(conn->sock, &smsg, 0); + if (size < 0) { + log_error_errno(errno, "unable to receive ctrl message: %m"); + goto err; + } + + cmsg_close_all(&smsg); + + cmsg = CMSG_FIRSTHDR(&smsg); + + if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) { + log_error("no sender credentials received, message ignored"); + goto err; + } + + cred = (struct ucred *) CMSG_DATA(cmsg); + + if (cred->uid != 0) { + log_error("sender uid="UID_FMT", message ignored", cred->uid); + goto err; + } + + if (uctrl_msg->ctrl_msg_wire.magic != UDEV_CTRL_MAGIC) { + log_error("message magic 0x%08x doesn't match, ignore it", uctrl_msg->ctrl_msg_wire.magic); + goto err; + } + + return uctrl_msg; +err: + udev_ctrl_msg_unref(uctrl_msg); + return NULL; +} + +struct udev_ctrl_msg *udev_ctrl_msg_unref(struct udev_ctrl_msg *ctrl_msg) { + if (ctrl_msg && -- ctrl_msg->refcount == 0) { + udev_ctrl_connection_unref(ctrl_msg->conn); + free(ctrl_msg); + } + + return NULL; +} + +int udev_ctrl_get_set_log_level(struct udev_ctrl_msg *ctrl_msg) { + if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_SET_LOG_LEVEL) + return ctrl_msg->ctrl_msg_wire.intval; + return -1; +} + +int udev_ctrl_get_stop_exec_queue(struct udev_ctrl_msg *ctrl_msg) { + if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_STOP_EXEC_QUEUE) + return 1; + return -1; +} + +int udev_ctrl_get_start_exec_queue(struct udev_ctrl_msg *ctrl_msg) { + if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_START_EXEC_QUEUE) + return 1; + return -1; +} + +int udev_ctrl_get_reload(struct udev_ctrl_msg *ctrl_msg) { + if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_RELOAD) + return 1; + return -1; +} + +const char *udev_ctrl_get_set_env(struct udev_ctrl_msg *ctrl_msg) { + if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_SET_ENV) + return ctrl_msg->ctrl_msg_wire.buf; + return NULL; +} + +int udev_ctrl_get_set_children_max(struct udev_ctrl_msg *ctrl_msg) { + if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_SET_CHILDREN_MAX) + return ctrl_msg->ctrl_msg_wire.intval; + return -1; +} + +int udev_ctrl_get_ping(struct udev_ctrl_msg *ctrl_msg) { + if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_PING) + return 1; + return -1; +} + +int udev_ctrl_get_exit(struct udev_ctrl_msg *ctrl_msg) { + if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_EXIT) + return 1; + return -1; +} diff --git a/src/grp-udev/libudev-core/udev-event.c b/src/grp-udev/libudev-core/udev-event.c new file mode 100644 index 0000000000..8d601c9c2c --- /dev/null +++ b/src/grp-udev/libudev-core/udev-event.c @@ -0,0 +1,943 @@ +/* + * Copyright (C) 2003-2013 Kay Sievers <kay@vrfy.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <net/if.h> +#include <poll.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/epoll.h> +#include <sys/prctl.h> +#include <sys/signalfd.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "netlink-util.h" +#include "process-util.h" +#include "signal-util.h" +#include "string-util.h" +#include "udev.h" + +typedef struct Spawn { + const char *cmd; + pid_t pid; + usec_t timeout_warn; + usec_t timeout; + bool accept_failure; +} Spawn; + +struct udev_event *udev_event_new(struct udev_device *dev) { + struct udev *udev = udev_device_get_udev(dev); + struct udev_event *event; + + event = new0(struct udev_event, 1); + if (event == NULL) + return NULL; + event->dev = dev; + event->udev = udev; + udev_list_init(udev, &event->run_list, false); + udev_list_init(udev, &event->seclabel_list, false); + event->birth_usec = clock_boottime_or_monotonic(); + return event; +} + +void udev_event_unref(struct udev_event *event) { + if (event == NULL) + return; + sd_netlink_unref(event->rtnl); + udev_list_cleanup(&event->run_list); + udev_list_cleanup(&event->seclabel_list); + free(event->program_result); + free(event->name); + free(event); +} + +size_t udev_event_apply_format(struct udev_event *event, const char *src, char *dest, size_t size) { + struct udev_device *dev = event->dev; + enum subst_type { + SUBST_UNKNOWN, + SUBST_DEVNODE, + SUBST_ATTR, + SUBST_ENV, + SUBST_KERNEL, + SUBST_KERNEL_NUMBER, + SUBST_DRIVER, + SUBST_DEVPATH, + SUBST_ID, + SUBST_MAJOR, + SUBST_MINOR, + SUBST_RESULT, + SUBST_PARENT, + SUBST_NAME, + SUBST_LINKS, + SUBST_ROOT, + SUBST_SYS, + }; + static const struct subst_map { + const char *name; + const char fmt; + enum subst_type type; + } map[] = { + { .name = "devnode", .fmt = 'N', .type = SUBST_DEVNODE }, + { .name = "tempnode", .fmt = 'N', .type = SUBST_DEVNODE }, + { .name = "attr", .fmt = 's', .type = SUBST_ATTR }, + { .name = "sysfs", .fmt = 's', .type = SUBST_ATTR }, + { .name = "env", .fmt = 'E', .type = SUBST_ENV }, + { .name = "kernel", .fmt = 'k', .type = SUBST_KERNEL }, + { .name = "number", .fmt = 'n', .type = SUBST_KERNEL_NUMBER }, + { .name = "driver", .fmt = 'd', .type = SUBST_DRIVER }, + { .name = "devpath", .fmt = 'p', .type = SUBST_DEVPATH }, + { .name = "id", .fmt = 'b', .type = SUBST_ID }, + { .name = "major", .fmt = 'M', .type = SUBST_MAJOR }, + { .name = "minor", .fmt = 'm', .type = SUBST_MINOR }, + { .name = "result", .fmt = 'c', .type = SUBST_RESULT }, + { .name = "parent", .fmt = 'P', .type = SUBST_PARENT }, + { .name = "name", .fmt = 'D', .type = SUBST_NAME }, + { .name = "links", .fmt = 'L', .type = SUBST_LINKS }, + { .name = "root", .fmt = 'r', .type = SUBST_ROOT }, + { .name = "sys", .fmt = 'S', .type = SUBST_SYS }, + }; + const char *from; + char *s; + size_t l; + + assert(dev); + + from = src; + s = dest; + l = size; + + for (;;) { + enum subst_type type = SUBST_UNKNOWN; + char attrbuf[UTIL_PATH_SIZE]; + char *attr = NULL; + + while (from[0] != '\0') { + if (from[0] == '$') { + /* substitute named variable */ + unsigned int i; + + if (from[1] == '$') { + from++; + goto copy; + } + + for (i = 0; i < ELEMENTSOF(map); i++) { + if (startswith(&from[1], map[i].name)) { + type = map[i].type; + from += strlen(map[i].name)+1; + goto subst; + } + } + } else if (from[0] == '%') { + /* substitute format char */ + unsigned int i; + + if (from[1] == '%') { + from++; + goto copy; + } + + for (i = 0; i < ELEMENTSOF(map); i++) { + if (from[1] == map[i].fmt) { + type = map[i].type; + from += 2; + goto subst; + } + } + } +copy: + /* copy char */ + if (l == 0) + goto out; + s[0] = from[0]; + from++; + s++; + l--; + } + + goto out; +subst: + /* extract possible $format{attr} */ + if (from[0] == '{') { + unsigned int i; + + from++; + for (i = 0; from[i] != '}'; i++) { + if (from[i] == '\0') { + log_error("missing closing brace for format '%s'", src); + goto out; + } + } + if (i >= sizeof(attrbuf)) + goto out; + memcpy(attrbuf, from, i); + attrbuf[i] = '\0'; + from += i+1; + attr = attrbuf; + } else { + attr = NULL; + } + + switch (type) { + case SUBST_DEVPATH: + l = strpcpy(&s, l, udev_device_get_devpath(dev)); + break; + case SUBST_KERNEL: + l = strpcpy(&s, l, udev_device_get_sysname(dev)); + break; + case SUBST_KERNEL_NUMBER: + if (udev_device_get_sysnum(dev) == NULL) + break; + l = strpcpy(&s, l, udev_device_get_sysnum(dev)); + break; + case SUBST_ID: + if (event->dev_parent == NULL) + break; + l = strpcpy(&s, l, udev_device_get_sysname(event->dev_parent)); + break; + case SUBST_DRIVER: { + const char *driver; + + if (event->dev_parent == NULL) + break; + + driver = udev_device_get_driver(event->dev_parent); + if (driver == NULL) + break; + l = strpcpy(&s, l, driver); + break; + } + case SUBST_MAJOR: { + char num[UTIL_PATH_SIZE]; + + sprintf(num, "%u", major(udev_device_get_devnum(dev))); + l = strpcpy(&s, l, num); + break; + } + case SUBST_MINOR: { + char num[UTIL_PATH_SIZE]; + + sprintf(num, "%u", minor(udev_device_get_devnum(dev))); + l = strpcpy(&s, l, num); + break; + } + case SUBST_RESULT: { + char *rest; + int i; + + if (event->program_result == NULL) + break; + /* get part part of the result string */ + i = 0; + if (attr != NULL) + i = strtoul(attr, &rest, 10); + if (i > 0) { + char result[UTIL_PATH_SIZE]; + char tmp[UTIL_PATH_SIZE]; + char *cpos; + + strscpy(result, sizeof(result), event->program_result); + cpos = result; + while (--i) { + while (cpos[0] != '\0' && !isspace(cpos[0])) + cpos++; + while (isspace(cpos[0])) + cpos++; + if (cpos[0] == '\0') + break; + } + if (i > 0) { + log_error("requested part of result string not found"); + break; + } + strscpy(tmp, sizeof(tmp), cpos); + /* %{2+}c copies the whole string from the second part on */ + if (rest[0] != '+') { + cpos = strchr(tmp, ' '); + if (cpos) + cpos[0] = '\0'; + } + l = strpcpy(&s, l, tmp); + } else { + l = strpcpy(&s, l, event->program_result); + } + break; + } + case SUBST_ATTR: { + const char *value = NULL; + char vbuf[UTIL_NAME_SIZE]; + size_t len; + int count; + + if (attr == NULL) { + log_error("missing file parameter for attr"); + break; + } + + /* try to read the value specified by "[dmi/id]product_name" */ + if (util_resolve_subsys_kernel(event->udev, attr, vbuf, sizeof(vbuf), 1) == 0) + value = vbuf; + + /* try to read the attribute the device */ + if (value == NULL) + value = udev_device_get_sysattr_value(event->dev, attr); + + /* try to read the attribute of the parent device, other matches have selected */ + if (value == NULL && event->dev_parent != NULL && event->dev_parent != event->dev) + value = udev_device_get_sysattr_value(event->dev_parent, attr); + + if (value == NULL) + break; + + /* strip trailing whitespace, and replace unwanted characters */ + if (value != vbuf) + strscpy(vbuf, sizeof(vbuf), value); + len = strlen(vbuf); + while (len > 0 && isspace(vbuf[--len])) + vbuf[len] = '\0'; + count = util_replace_chars(vbuf, UDEV_ALLOWED_CHARS_INPUT); + if (count > 0) + log_debug("%i character(s) replaced" , count); + l = strpcpy(&s, l, vbuf); + break; + } + case SUBST_PARENT: { + struct udev_device *dev_parent; + const char *devnode; + + dev_parent = udev_device_get_parent(event->dev); + if (dev_parent == NULL) + break; + devnode = udev_device_get_devnode(dev_parent); + if (devnode != NULL) + l = strpcpy(&s, l, devnode + strlen("/dev/")); + break; + } + case SUBST_DEVNODE: + if (udev_device_get_devnode(dev) != NULL) + l = strpcpy(&s, l, udev_device_get_devnode(dev)); + break; + case SUBST_NAME: + if (event->name != NULL) + l = strpcpy(&s, l, event->name); + else if (udev_device_get_devnode(dev) != NULL) + l = strpcpy(&s, l, udev_device_get_devnode(dev) + strlen("/dev/")); + else + l = strpcpy(&s, l, udev_device_get_sysname(dev)); + break; + case SUBST_LINKS: { + struct udev_list_entry *list_entry; + + list_entry = udev_device_get_devlinks_list_entry(dev); + if (list_entry == NULL) + break; + l = strpcpy(&s, l, udev_list_entry_get_name(list_entry) + strlen("/dev/")); + udev_list_entry_foreach(list_entry, udev_list_entry_get_next(list_entry)) + l = strpcpyl(&s, l, " ", udev_list_entry_get_name(list_entry) + strlen("/dev/"), NULL); + break; + } + case SUBST_ROOT: + l = strpcpy(&s, l, "/dev"); + break; + case SUBST_SYS: + l = strpcpy(&s, l, "/sys"); + break; + case SUBST_ENV: + if (attr == NULL) { + break; + } else { + const char *value; + + value = udev_device_get_property_value(event->dev, attr); + if (value == NULL) + break; + l = strpcpy(&s, l, value); + break; + } + default: + log_error("unknown substitution type=%i", type); + break; + } + } + +out: + s[0] = '\0'; + return l; +} + +static int spawn_exec(struct udev_event *event, + const char *cmd, char *const argv[], char **envp, + int fd_stdout, int fd_stderr) { + _cleanup_close_ int fd = -1; + int r; + + /* discard child output or connect to pipe */ + fd = open("/dev/null", O_RDWR); + if (fd >= 0) { + r = dup2(fd, STDIN_FILENO); + if (r < 0) + log_warning_errno(errno, "redirecting stdin failed: %m"); + + if (fd_stdout < 0) { + r = dup2(fd, STDOUT_FILENO); + if (r < 0) + log_warning_errno(errno, "redirecting stdout failed: %m"); + } + + if (fd_stderr < 0) { + r = dup2(fd, STDERR_FILENO); + if (r < 0) + log_warning_errno(errno, "redirecting stderr failed: %m"); + } + } else + log_warning_errno(errno, "open /dev/null failed: %m"); + + /* connect pipes to std{out,err} */ + if (fd_stdout >= 0) { + r = dup2(fd_stdout, STDOUT_FILENO); + if (r < 0) + log_warning_errno(errno, "redirecting stdout failed: %m"); + + fd_stdout = safe_close(fd_stdout); + } + + if (fd_stderr >= 0) { + r = dup2(fd_stderr, STDERR_FILENO); + if (r < 0) + log_warning_errno(errno, "redirecting stdout failed: %m"); + + fd_stderr = safe_close(fd_stderr); + } + + /* terminate child in case parent goes away */ + prctl(PR_SET_PDEATHSIG, SIGTERM); + + /* restore sigmask before exec */ + (void) reset_signal_mask(); + + execve(argv[0], argv, envp); + + /* exec failed */ + return log_error_errno(errno, "failed to execute '%s' '%s': %m", argv[0], cmd); +} + +static void spawn_read(struct udev_event *event, + usec_t timeout_usec, + const char *cmd, + int fd_stdout, int fd_stderr, + char *result, size_t ressize) { + _cleanup_close_ int fd_ep = -1; + struct epoll_event ep_outpipe = { + .events = EPOLLIN, + .data.ptr = &fd_stdout, + }; + struct epoll_event ep_errpipe = { + .events = EPOLLIN, + .data.ptr = &fd_stderr, + }; + size_t respos = 0; + int r; + + /* read from child if requested */ + if (fd_stdout < 0 && fd_stderr < 0) + return; + + fd_ep = epoll_create1(EPOLL_CLOEXEC); + if (fd_ep < 0) { + log_error_errno(errno, "error creating epoll fd: %m"); + return; + } + + if (fd_stdout >= 0) { + r = epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_stdout, &ep_outpipe); + if (r < 0) { + log_error_errno(errno, "fail to add stdout fd to epoll: %m"); + return; + } + } + + if (fd_stderr >= 0) { + r = epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_stderr, &ep_errpipe); + if (r < 0) { + log_error_errno(errno, "fail to add stderr fd to epoll: %m"); + return; + } + } + + /* read child output */ + while (fd_stdout >= 0 || fd_stderr >= 0) { + int timeout; + int fdcount; + struct epoll_event ev[4]; + int i; + + if (timeout_usec > 0) { + usec_t age_usec; + + age_usec = clock_boottime_or_monotonic() - event->birth_usec; + if (age_usec >= timeout_usec) { + log_error("timeout '%s'", cmd); + return; + } + timeout = ((timeout_usec - age_usec) / USEC_PER_MSEC) + MSEC_PER_SEC; + } else { + timeout = -1; + } + + fdcount = epoll_wait(fd_ep, ev, ELEMENTSOF(ev), timeout); + if (fdcount < 0) { + if (errno == EINTR) + continue; + log_error_errno(errno, "failed to poll: %m"); + return; + } else if (fdcount == 0) { + log_error("timeout '%s'", cmd); + return; + } + + for (i = 0; i < fdcount; i++) { + int *fd = (int *)ev[i].data.ptr; + + if (*fd < 0) + continue; + + if (ev[i].events & EPOLLIN) { + ssize_t count; + char buf[4096]; + + count = read(*fd, buf, sizeof(buf)-1); + if (count <= 0) + continue; + buf[count] = '\0'; + + /* store stdout result */ + if (result != NULL && *fd == fd_stdout) { + if (respos + count < ressize) { + memcpy(&result[respos], buf, count); + respos += count; + } else { + log_error("'%s' ressize %zu too short", cmd, ressize); + } + } + + /* log debug output only if we watch stderr */ + if (fd_stderr >= 0) { + char *pos; + char *line; + + pos = buf; + while ((line = strsep(&pos, "\n"))) { + if (pos != NULL || line[0] != '\0') + log_debug("'%s'(%s) '%s'", cmd, *fd == fd_stdout ? "out" : "err" , line); + } + } + } else if (ev[i].events & EPOLLHUP) { + r = epoll_ctl(fd_ep, EPOLL_CTL_DEL, *fd, NULL); + if (r < 0) { + log_error_errno(errno, "failed to remove fd from epoll: %m"); + return; + } + *fd = -1; + } + } + } + + /* return the child's stdout string */ + if (result != NULL) + result[respos] = '\0'; +} + +static int on_spawn_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + Spawn *spawn = userdata; + char timeout[FORMAT_TIMESTAMP_RELATIVE_MAX]; + + assert(spawn); + + kill_and_sigcont(spawn->pid, SIGKILL); + + log_error("spawned process '%s' ["PID_FMT"] timed out after %s, killing", spawn->cmd, spawn->pid, + format_timestamp_relative(timeout, sizeof(timeout), spawn->timeout)); + + return 1; +} + +static int on_spawn_timeout_warning(sd_event_source *s, uint64_t usec, void *userdata) { + Spawn *spawn = userdata; + char timeout[FORMAT_TIMESTAMP_RELATIVE_MAX]; + + assert(spawn); + + log_warning("spawned process '%s' ["PID_FMT"] is taking longer than %s to complete", spawn->cmd, spawn->pid, + format_timestamp_relative(timeout, sizeof(timeout), spawn->timeout)); + + return 1; +} + +static int on_spawn_sigchld(sd_event_source *s, const siginfo_t *si, void *userdata) { + Spawn *spawn = userdata; + + assert(spawn); + + switch (si->si_code) { + case CLD_EXITED: + if (si->si_status == 0) { + log_debug("Process '%s' succeeded.", spawn->cmd); + sd_event_exit(sd_event_source_get_event(s), 0); + + return 1; + } else if (spawn->accept_failure) + log_debug("Process '%s' failed with exit code %i.", spawn->cmd, si->si_status); + else + log_warning("Process '%s' failed with exit code %i.", spawn->cmd, si->si_status); + + break; + case CLD_KILLED: + case CLD_DUMPED: + log_warning("Process '%s' terminated by signal %s.", spawn->cmd, signal_to_string(si->si_status)); + + break; + default: + log_error("Process '%s' failed due to unknown reason.", spawn->cmd); + } + + sd_event_exit(sd_event_source_get_event(s), -EIO); + + return 1; +} + +static int spawn_wait(struct udev_event *event, + usec_t timeout_usec, + usec_t timeout_warn_usec, + const char *cmd, pid_t pid, + bool accept_failure) { + Spawn spawn = { + .cmd = cmd, + .pid = pid, + .accept_failure = accept_failure, + }; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + int r, ret; + + r = sd_event_new(&e); + if (r < 0) + return r; + + if (timeout_usec > 0) { + usec_t usec, age_usec; + + usec = now(clock_boottime_or_monotonic()); + age_usec = usec - event->birth_usec; + if (age_usec < timeout_usec) { + if (timeout_warn_usec > 0 && timeout_warn_usec < timeout_usec && age_usec < timeout_warn_usec) { + spawn.timeout_warn = timeout_warn_usec - age_usec; + + r = sd_event_add_time(e, NULL, clock_boottime_or_monotonic(), + usec + spawn.timeout_warn, USEC_PER_SEC, + on_spawn_timeout_warning, &spawn); + if (r < 0) + return r; + } + + spawn.timeout = timeout_usec - age_usec; + + r = sd_event_add_time(e, NULL, clock_boottime_or_monotonic(), + usec + spawn.timeout, USEC_PER_SEC, on_spawn_timeout, &spawn); + if (r < 0) + return r; + } + } + + r = sd_event_add_child(e, NULL, pid, WEXITED, on_spawn_sigchld, &spawn); + if (r < 0) + return r; + + r = sd_event_loop(e); + if (r < 0) + return r; + + r = sd_event_get_exit_code(e, &ret); + if (r < 0) + return r; + + return ret; +} + +int udev_build_argv(struct udev *udev, char *cmd, int *argc, char *argv[]) { + int i = 0; + char *pos; + + if (strchr(cmd, ' ') == NULL) { + argv[i++] = cmd; + goto out; + } + + pos = cmd; + while (pos != NULL && pos[0] != '\0') { + if (pos[0] == '\'') { + /* do not separate quotes */ + pos++; + argv[i] = strsep(&pos, "\'"); + if (pos != NULL) + while (pos[0] == ' ') + pos++; + } else { + argv[i] = strsep(&pos, " "); + if (pos != NULL) + while (pos[0] == ' ') + pos++; + } + i++; + } +out: + argv[i] = NULL; + if (argc) + *argc = i; + return 0; +} + +int udev_event_spawn(struct udev_event *event, + usec_t timeout_usec, + usec_t timeout_warn_usec, + bool accept_failure, + const char *cmd, + char *result, size_t ressize) { + int outpipe[2] = {-1, -1}; + int errpipe[2] = {-1, -1}; + pid_t pid; + int err = 0; + + /* pipes from child to parent */ + if (result != NULL || log_get_max_level() >= LOG_INFO) { + if (pipe2(outpipe, O_NONBLOCK) != 0) { + err = log_error_errno(errno, "pipe failed: %m"); + goto out; + } + } + if (log_get_max_level() >= LOG_INFO) { + if (pipe2(errpipe, O_NONBLOCK) != 0) { + err = log_error_errno(errno, "pipe failed: %m"); + goto out; + } + } + + pid = fork(); + switch(pid) { + case 0: + { + char arg[UTIL_PATH_SIZE]; + char *argv[128]; + char program[UTIL_PATH_SIZE]; + + /* child closes parent's ends of pipes */ + outpipe[READ_END] = safe_close(outpipe[READ_END]); + errpipe[READ_END] = safe_close(errpipe[READ_END]); + + strscpy(arg, sizeof(arg), cmd); + udev_build_argv(event->udev, arg, NULL, argv); + + /* allow programs in /usr/lib/udev/ to be called without the path */ + if (argv[0][0] != '/') { + strscpyl(program, sizeof(program), UDEVLIBEXECDIR "/", argv[0], NULL); + argv[0] = program; + } + + log_debug("starting '%s'", cmd); + + spawn_exec(event, cmd, argv, udev_device_get_properties_envp(event->dev), + outpipe[WRITE_END], errpipe[WRITE_END]); + + _exit(2); + } + case -1: + log_error_errno(errno, "fork of '%s' failed: %m", cmd); + err = -1; + goto out; + default: + /* parent closed child's ends of pipes */ + outpipe[WRITE_END] = safe_close(outpipe[WRITE_END]); + errpipe[WRITE_END] = safe_close(errpipe[WRITE_END]); + + spawn_read(event, + timeout_usec, + cmd, + outpipe[READ_END], errpipe[READ_END], + result, ressize); + + err = spawn_wait(event, timeout_usec, timeout_warn_usec, cmd, pid, accept_failure); + } + +out: + if (outpipe[READ_END] >= 0) + close(outpipe[READ_END]); + if (outpipe[WRITE_END] >= 0) + close(outpipe[WRITE_END]); + if (errpipe[READ_END] >= 0) + close(errpipe[READ_END]); + if (errpipe[WRITE_END] >= 0) + close(errpipe[WRITE_END]); + return err; +} + +static int rename_netif(struct udev_event *event) { + struct udev_device *dev = event->dev; + char name[IFNAMSIZ]; + const char *oldname; + int r; + + oldname = udev_device_get_sysname(dev); + + strscpy(name, IFNAMSIZ, event->name); + + r = rtnl_set_link_name(&event->rtnl, udev_device_get_ifindex(dev), name); + if (r < 0) + return log_error_errno(r, "Error changing net interface name '%s' to '%s': %m", oldname, name); + + log_debug("renamed network interface '%s' to '%s'", oldname, name); + + return 0; +} + +void udev_event_execute_rules(struct udev_event *event, + usec_t timeout_usec, usec_t timeout_warn_usec, + struct udev_list *properties_list, + struct udev_rules *rules) { + struct udev_device *dev = event->dev; + + if (udev_device_get_subsystem(dev) == NULL) + return; + + if (streq(udev_device_get_action(dev), "remove")) { + udev_device_read_db(dev); + udev_device_tag_index(dev, NULL, false); + udev_device_delete_db(dev); + + if (major(udev_device_get_devnum(dev)) != 0) + udev_watch_end(event->udev, dev); + + udev_rules_apply_to_event(rules, event, + timeout_usec, timeout_warn_usec, + properties_list); + + if (major(udev_device_get_devnum(dev)) != 0) + udev_node_remove(dev); + } else { + event->dev_db = udev_device_clone_with_db(dev); + if (event->dev_db != NULL) { + /* disable watch during event processing */ + if (major(udev_device_get_devnum(dev)) != 0) + udev_watch_end(event->udev, event->dev_db); + + if (major(udev_device_get_devnum(dev)) == 0 && + streq(udev_device_get_action(dev), "move")) + udev_device_copy_properties(dev, event->dev_db); + } + + udev_rules_apply_to_event(rules, event, + timeout_usec, timeout_warn_usec, + properties_list); + + /* rename a new network interface, if needed */ + if (udev_device_get_ifindex(dev) > 0 && streq(udev_device_get_action(dev), "add") && + event->name != NULL && !streq(event->name, udev_device_get_sysname(dev))) { + int r; + + r = rename_netif(event); + if (r < 0) + log_warning_errno(r, "could not rename interface '%d' from '%s' to '%s': %m", udev_device_get_ifindex(dev), + udev_device_get_sysname(dev), event->name); + else { + r = udev_device_rename(dev, event->name); + if (r < 0) + log_warning_errno(r, "renamed interface '%d' from '%s' to '%s', but could not update udev_device: %m", + udev_device_get_ifindex(dev), udev_device_get_sysname(dev), event->name); + else + log_debug("changed devpath to '%s'", udev_device_get_devpath(dev)); + } + } + + if (major(udev_device_get_devnum(dev)) > 0) { + bool apply; + + /* remove/update possible left-over symlinks from old database entry */ + if (event->dev_db != NULL) + udev_node_update_old_links(dev, event->dev_db); + + if (!event->owner_set) + event->uid = udev_device_get_devnode_uid(dev); + + if (!event->group_set) + event->gid = udev_device_get_devnode_gid(dev); + + if (!event->mode_set) { + if (udev_device_get_devnode_mode(dev) > 0) { + /* kernel supplied value */ + event->mode = udev_device_get_devnode_mode(dev); + } else if (event->gid > 0) { + /* default 0660 if a group is assigned */ + event->mode = 0660; + } else { + /* default 0600 */ + event->mode = 0600; + } + } + + apply = streq(udev_device_get_action(dev), "add") || event->owner_set || event->group_set || event->mode_set; + udev_node_add(dev, apply, event->mode, event->uid, event->gid, &event->seclabel_list); + } + + /* preserve old, or get new initialization timestamp */ + udev_device_ensure_usec_initialized(event->dev, event->dev_db); + + /* (re)write database file */ + udev_device_tag_index(dev, event->dev_db, true); + udev_device_update_db(dev); + udev_device_set_is_initialized(dev); + + event->dev_db = udev_device_unref(event->dev_db); + } +} + +void udev_event_execute_run(struct udev_event *event, usec_t timeout_usec, usec_t timeout_warn_usec) { + struct udev_list_entry *list_entry; + + udev_list_entry_foreach(list_entry, udev_list_get_entry(&event->run_list)) { + char command[UTIL_PATH_SIZE]; + const char *cmd = udev_list_entry_get_name(list_entry); + enum udev_builtin_cmd builtin_cmd = udev_list_entry_get_num(list_entry); + + udev_event_apply_format(event, cmd, command, sizeof(command)); + + if (builtin_cmd < UDEV_BUILTIN_MAX) + udev_builtin_run(event->dev, builtin_cmd, command, false); + else { + if (event->exec_delay > 0) { + log_debug("delay execution of '%s'", command); + sleep(event->exec_delay); + } + + udev_event_spawn(event, timeout_usec, timeout_warn_usec, false, command, NULL, 0); + } + } +} diff --git a/src/grp-udev/libudev-core/udev-node.c b/src/grp-udev/libudev-core/udev-node.c new file mode 100644 index 0000000000..5d2997fd8f --- /dev/null +++ b/src/grp-udev/libudev-core/udev-node.c @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2003-2013 Kay Sievers <kay@vrfy.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "formats-util.h" +#include "fs-util.h" +#include "selinux-util.h" +#include "smack-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "udev.h" + +static int node_symlink(struct udev_device *dev, const char *node, const char *slink) { + struct stat stats; + char target[UTIL_PATH_SIZE]; + char *s; + size_t l; + char slink_tmp[UTIL_PATH_SIZE + 32]; + int i = 0; + int tail = 0; + int err = 0; + + /* use relative link */ + target[0] = '\0'; + while (node[i] && (node[i] == slink[i])) { + if (node[i] == '/') + tail = i+1; + i++; + } + s = target; + l = sizeof(target); + while (slink[i] != '\0') { + if (slink[i] == '/') + l = strpcpy(&s, l, "../"); + i++; + } + l = strscpy(s, l, &node[tail]); + if (l == 0) { + err = -EINVAL; + goto exit; + } + + /* preserve link with correct target, do not replace node of other device */ + if (lstat(slink, &stats) == 0) { + if (S_ISBLK(stats.st_mode) || S_ISCHR(stats.st_mode)) { + log_error("conflicting device node '%s' found, link to '%s' will not be created", slink, node); + goto exit; + } else if (S_ISLNK(stats.st_mode)) { + char buf[UTIL_PATH_SIZE]; + int len; + + len = readlink(slink, buf, sizeof(buf)); + if (len > 0 && len < (int)sizeof(buf)) { + buf[len] = '\0'; + if (streq(target, buf)) { + log_debug("preserve already existing symlink '%s' to '%s'", slink, target); + label_fix(slink, true, false); + utimensat(AT_FDCWD, slink, NULL, AT_SYMLINK_NOFOLLOW); + goto exit; + } + } + } + } else { + log_debug("creating symlink '%s' to '%s'", slink, target); + do { + err = mkdir_parents_label(slink, 0755); + if (err != 0 && err != -ENOENT) + break; + mac_selinux_create_file_prepare(slink, S_IFLNK); + err = symlink(target, slink); + if (err != 0) + err = -errno; + mac_selinux_create_file_clear(); + } while (err == -ENOENT); + if (err == 0) + goto exit; + } + + log_debug("atomically replace '%s'", slink); + strscpyl(slink_tmp, sizeof(slink_tmp), slink, ".tmp-", udev_device_get_id_filename(dev), NULL); + unlink(slink_tmp); + do { + err = mkdir_parents_label(slink_tmp, 0755); + if (err != 0 && err != -ENOENT) + break; + mac_selinux_create_file_prepare(slink_tmp, S_IFLNK); + err = symlink(target, slink_tmp); + if (err != 0) + err = -errno; + mac_selinux_create_file_clear(); + } while (err == -ENOENT); + if (err != 0) { + log_error_errno(errno, "symlink '%s' '%s' failed: %m", target, slink_tmp); + goto exit; + } + err = rename(slink_tmp, slink); + if (err != 0) { + log_error_errno(errno, "rename '%s' '%s' failed: %m", slink_tmp, slink); + unlink(slink_tmp); + } +exit: + return err; +} + +/* find device node of device with highest priority */ +static const char *link_find_prioritized(struct udev_device *dev, bool add, const char *stackdir, char *buf, size_t bufsize) { + struct udev *udev = udev_device_get_udev(dev); + DIR *dir; + int priority = 0; + const char *target = NULL; + + if (add) { + priority = udev_device_get_devlink_priority(dev); + strscpy(buf, bufsize, udev_device_get_devnode(dev)); + target = buf; + } + + dir = opendir(stackdir); + if (dir == NULL) + return target; + for (;;) { + struct udev_device *dev_db; + struct dirent *dent; + + dent = readdir(dir); + if (dent == NULL || dent->d_name[0] == '\0') + break; + if (dent->d_name[0] == '.') + continue; + + log_debug("found '%s' claiming '%s'", dent->d_name, stackdir); + + /* did we find ourself? */ + if (streq(dent->d_name, udev_device_get_id_filename(dev))) + continue; + + dev_db = udev_device_new_from_device_id(udev, dent->d_name); + if (dev_db != NULL) { + const char *devnode; + + devnode = udev_device_get_devnode(dev_db); + if (devnode != NULL) { + if (target == NULL || udev_device_get_devlink_priority(dev_db) > priority) { + log_debug("'%s' claims priority %i for '%s'", + udev_device_get_syspath(dev_db), udev_device_get_devlink_priority(dev_db), stackdir); + priority = udev_device_get_devlink_priority(dev_db); + strscpy(buf, bufsize, devnode); + target = buf; + } + } + udev_device_unref(dev_db); + } + } + closedir(dir); + return target; +} + +/* manage "stack of names" with possibly specified device priorities */ +static void link_update(struct udev_device *dev, const char *slink, bool add) { + char name_enc[UTIL_PATH_SIZE]; + char filename[UTIL_PATH_SIZE * 2]; + char dirname[UTIL_PATH_SIZE]; + const char *target; + char buf[UTIL_PATH_SIZE]; + + util_path_encode(slink + strlen("/dev"), name_enc, sizeof(name_enc)); + strscpyl(dirname, sizeof(dirname), "/run/udev/links/", name_enc, NULL); + strscpyl(filename, sizeof(filename), dirname, "/", udev_device_get_id_filename(dev), NULL); + + if (!add && unlink(filename) == 0) + rmdir(dirname); + + target = link_find_prioritized(dev, add, dirname, buf, sizeof(buf)); + if (target == NULL) { + log_debug("no reference left, remove '%s'", slink); + if (unlink(slink) == 0) + rmdir_parents(slink, "/"); + } else { + log_debug("creating link '%s' to '%s'", slink, target); + node_symlink(dev, target, slink); + } + + if (add) { + int err; + + do { + int fd; + + err = mkdir_parents(filename, 0755); + if (err != 0 && err != -ENOENT) + break; + fd = open(filename, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC|O_NOFOLLOW, 0444); + if (fd >= 0) + close(fd); + else + err = -errno; + } while (err == -ENOENT); + } +} + +void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev_old) { + struct udev_list_entry *list_entry; + + /* update possible left-over symlinks */ + udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev_old)) { + const char *name = udev_list_entry_get_name(list_entry); + struct udev_list_entry *list_entry_current; + int found; + + /* check if old link name still belongs to this device */ + found = 0; + udev_list_entry_foreach(list_entry_current, udev_device_get_devlinks_list_entry(dev)) { + const char *name_current = udev_list_entry_get_name(list_entry_current); + + if (streq(name, name_current)) { + found = 1; + break; + } + } + if (found) + continue; + + log_debug("update old name, '%s' no longer belonging to '%s'", + name, udev_device_get_devpath(dev)); + link_update(dev, name, false); + } +} + +static int node_permissions_apply(struct udev_device *dev, bool apply, + mode_t mode, uid_t uid, gid_t gid, + struct udev_list *seclabel_list) { + const char *devnode = udev_device_get_devnode(dev); + dev_t devnum = udev_device_get_devnum(dev); + struct stat stats; + struct udev_list_entry *entry; + int err = 0; + + if (streq(udev_device_get_subsystem(dev), "block")) + mode |= S_IFBLK; + else + mode |= S_IFCHR; + + if (lstat(devnode, &stats) != 0) { + err = log_debug_errno(errno, "can not stat() node '%s' (%m)", devnode); + goto out; + } + + if (((stats.st_mode & S_IFMT) != (mode & S_IFMT)) || (stats.st_rdev != devnum)) { + err = -EEXIST; + log_debug("found node '%s' with non-matching devnum %s, skip handling", + udev_device_get_devnode(dev), udev_device_get_id_filename(dev)); + goto out; + } + + if (apply) { + bool selinux = false; + bool smack = false; + + if ((stats.st_mode & 0777) != (mode & 0777) || stats.st_uid != uid || stats.st_gid != gid) { + log_debug("set permissions %s, %#o, uid=%u, gid=%u", devnode, mode, uid, gid); + err = chmod(devnode, mode); + if (err < 0) + log_warning_errno(errno, "setting mode of %s to %#o failed: %m", devnode, mode); + err = chown(devnode, uid, gid); + if (err < 0) + log_warning_errno(errno, "setting owner of %s to uid=%u, gid=%u failed: %m", devnode, uid, gid); + } else { + log_debug("preserve permissions %s, %#o, uid=%u, gid=%u", devnode, mode, uid, gid); + } + + /* apply SECLABEL{$module}=$label */ + udev_list_entry_foreach(entry, udev_list_get_entry(seclabel_list)) { + const char *name, *label; + int r; + + name = udev_list_entry_get_name(entry); + label = udev_list_entry_get_value(entry); + + if (streq(name, "selinux")) { + selinux = true; + + r = mac_selinux_apply(devnode, label); + if (r < 0) + log_error_errno(r, "SECLABEL: failed to set SELinux label '%s': %m", label); + else + log_debug("SECLABEL: set SELinux label '%s'", label); + + } else if (streq(name, "smack")) { + smack = true; + + r = mac_smack_apply(devnode, SMACK_ATTR_ACCESS, label); + if (r < 0) + log_error_errno(r, "SECLABEL: failed to set SMACK label '%s': %m", label); + else + log_debug("SECLABEL: set SMACK label '%s'", label); + + } else + log_error("SECLABEL: unknown subsystem, ignoring '%s'='%s'", name, label); + } + + /* set the defaults */ + if (!selinux) + mac_selinux_fix(devnode, true, false); + if (!smack) + mac_smack_apply(devnode, SMACK_ATTR_ACCESS, NULL); + } + + /* always update timestamp when we re-use the node, like on media change events */ + utimensat(AT_FDCWD, devnode, NULL, 0); +out: + return err; +} + +void udev_node_add(struct udev_device *dev, bool apply, + mode_t mode, uid_t uid, gid_t gid, + struct udev_list *seclabel_list) { + char filename[UTIL_PATH_SIZE]; + struct udev_list_entry *list_entry; + + log_debug("handling device node '%s', devnum=%s, mode=%#o, uid="UID_FMT", gid="GID_FMT, + udev_device_get_devnode(dev), udev_device_get_id_filename(dev), mode, uid, gid); + + if (node_permissions_apply(dev, apply, mode, uid, gid, seclabel_list) < 0) + return; + + /* always add /dev/{block,char}/$major:$minor */ + xsprintf(filename, "/dev/%s/%u:%u", + streq(udev_device_get_subsystem(dev), "block") ? "block" : "char", + major(udev_device_get_devnum(dev)), + minor(udev_device_get_devnum(dev))); + node_symlink(dev, udev_device_get_devnode(dev), filename); + + /* create/update symlinks, add symlinks to name index */ + udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev)) + link_update(dev, udev_list_entry_get_name(list_entry), true); +} + +void udev_node_remove(struct udev_device *dev) { + struct udev_list_entry *list_entry; + char filename[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 */ + xsprintf(filename, "/dev/%s/%u:%u", + streq(udev_device_get_subsystem(dev), "block") ? "block" : "char", + major(udev_device_get_devnum(dev)), + minor(udev_device_get_devnum(dev))); + unlink(filename); +} diff --git a/src/grp-udev/libudev-core/udev-rules.c b/src/grp-udev/libudev-core/udev-rules.c new file mode 100644 index 0000000000..26fa52cf6c --- /dev/null +++ b/src/grp-udev/libudev-core/udev-rules.c @@ -0,0 +1,2577 @@ +/* + * Copyright (C) 2003-2012 Kay Sievers <kay@vrfy.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <fnmatch.h> +#include <limits.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "conf-files.h" +#include "escape.h" +#include "fd-util.h" +#include "fs-util.h" +#include "glob-util.h" +#include "path-util.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "strbuf.h" +#include "string-util.h" +#include "strv.h" +#include "sysctl-util.h" +#include "udev.h" +#include "user-util.h" +#include "util.h" + +#define PREALLOC_TOKEN 2048 + +struct uid_gid { + unsigned int name_off; + union { + uid_t uid; + gid_t gid; + }; +}; + +static const char* const rules_dirs[] = { + "/etc/udev/rules.d", + "/run/udev/rules.d", + UDEVLIBEXECDIR "/rules.d", + NULL +}; + +struct udev_rules { + struct udev *udev; + usec_t dirs_ts_usec; + int resolve_names; + + /* every key in the rules file becomes a token */ + struct token *tokens; + unsigned int token_cur; + unsigned int token_max; + + /* all key strings are copied and de-duplicated in a single continuous string buffer */ + struct strbuf *strbuf; + + /* during rule parsing, uid/gid lookup results are cached */ + struct uid_gid *uids; + unsigned int uids_cur; + unsigned int uids_max; + struct uid_gid *gids; + unsigned int gids_cur; + unsigned int gids_max; +}; + +static char *rules_str(struct udev_rules *rules, unsigned int off) { + return rules->strbuf->buf + off; +} + +static unsigned int rules_add_string(struct udev_rules *rules, const char *s) { + return strbuf_add_string(rules->strbuf, s, strlen(s)); +} + +/* KEY=="", KEY!="", KEY+="", KEY-="", KEY="", KEY:="" */ +enum operation_type { + OP_UNSET, + + OP_MATCH, + OP_NOMATCH, + OP_MATCH_MAX, + + OP_ADD, + OP_REMOVE, + OP_ASSIGN, + OP_ASSIGN_FINAL, +}; + +enum string_glob_type { + GL_UNSET, + GL_PLAIN, /* no special chars */ + GL_GLOB, /* shell globs ?,*,[] */ + GL_SPLIT, /* multi-value A|B */ + GL_SPLIT_GLOB, /* multi-value with glob A*|B* */ + GL_SOMETHING, /* commonly used "?*" */ +}; + +enum string_subst_type { + SB_UNSET, + SB_NONE, + SB_FORMAT, + SB_SUBSYS, +}; + +/* tokens of a rule are sorted/handled in this order */ +enum token_type { + TK_UNSET, + TK_RULE, + + TK_M_ACTION, /* val */ + TK_M_DEVPATH, /* val */ + TK_M_KERNEL, /* val */ + TK_M_DEVLINK, /* val */ + TK_M_NAME, /* val */ + TK_M_ENV, /* val, attr */ + TK_M_TAG, /* val */ + TK_M_SUBSYSTEM, /* val */ + TK_M_DRIVER, /* val */ + TK_M_WAITFOR, /* val */ + TK_M_ATTR, /* val, attr */ + TK_M_SYSCTL, /* val, attr */ + + TK_M_PARENTS_MIN, + TK_M_KERNELS, /* val */ + TK_M_SUBSYSTEMS, /* val */ + TK_M_DRIVERS, /* val */ + TK_M_ATTRS, /* val, attr */ + TK_M_TAGS, /* val */ + TK_M_PARENTS_MAX, + + TK_M_TEST, /* val, mode_t */ + TK_M_PROGRAM, /* val */ + TK_M_IMPORT_FILE, /* val */ + TK_M_IMPORT_PROG, /* val */ + TK_M_IMPORT_BUILTIN, /* val */ + TK_M_IMPORT_DB, /* val */ + TK_M_IMPORT_CMDLINE, /* val */ + TK_M_IMPORT_PARENT, /* val */ + TK_M_RESULT, /* val */ + TK_M_MAX, + + TK_A_STRING_ESCAPE_NONE, + TK_A_STRING_ESCAPE_REPLACE, + TK_A_DB_PERSIST, + TK_A_INOTIFY_WATCH, /* int */ + TK_A_DEVLINK_PRIO, /* int */ + TK_A_OWNER, /* val */ + TK_A_GROUP, /* val */ + TK_A_MODE, /* val */ + TK_A_OWNER_ID, /* uid_t */ + TK_A_GROUP_ID, /* gid_t */ + TK_A_MODE_ID, /* mode_t */ + TK_A_TAG, /* val */ + TK_A_STATIC_NODE, /* val */ + TK_A_SECLABEL, /* val, attr */ + TK_A_ENV, /* val, attr */ + TK_A_NAME, /* val */ + TK_A_DEVLINK, /* val */ + TK_A_ATTR, /* val, attr */ + TK_A_SYSCTL, /* val, attr */ + TK_A_RUN_BUILTIN, /* val, bool */ + TK_A_RUN_PROGRAM, /* val, bool */ + TK_A_GOTO, /* size_t */ + + TK_END, +}; + +/* we try to pack stuff in a way that we take only 12 bytes per token */ +struct token { + union { + unsigned char type; /* same in rule and key */ + struct { + enum token_type type:8; + bool can_set_name:1; + bool has_static_node:1; + unsigned int unused:6; + unsigned short token_count; + unsigned int label_off; + unsigned short filename_off; + unsigned short filename_line; + } rule; + struct { + enum token_type type:8; + enum operation_type op:8; + enum string_glob_type glob:8; + enum string_subst_type subst:4; + enum string_subst_type attrsubst:4; + unsigned int value_off; + union { + unsigned int attr_off; + unsigned int rule_goto; + mode_t mode; + uid_t uid; + gid_t gid; + int devlink_prio; + int watch; + enum udev_builtin_cmd builtin_cmd; + }; + } key; + }; +}; + +#define MAX_TK 64 +struct rule_tmp { + struct udev_rules *rules; + struct token rule; + struct token token[MAX_TK]; + unsigned int token_cur; +}; + +#ifdef DEBUG +static const char *operation_str(enum operation_type type) { + static const char *operation_strs[] = { + [OP_UNSET] = "UNSET", + [OP_MATCH] = "match", + [OP_NOMATCH] = "nomatch", + [OP_MATCH_MAX] = "MATCH_MAX", + + [OP_ADD] = "add", + [OP_REMOVE] = "remove", + [OP_ASSIGN] = "assign", + [OP_ASSIGN_FINAL] = "assign-final", +} ; + + return operation_strs[type]; +} + +static const char *string_glob_str(enum string_glob_type type) { + static const char *string_glob_strs[] = { + [GL_UNSET] = "UNSET", + [GL_PLAIN] = "plain", + [GL_GLOB] = "glob", + [GL_SPLIT] = "split", + [GL_SPLIT_GLOB] = "split-glob", + [GL_SOMETHING] = "split-glob", + }; + + return string_glob_strs[type]; +} + +static const char *token_str(enum token_type type) { + static const char *token_strs[] = { + [TK_UNSET] = "UNSET", + [TK_RULE] = "RULE", + + [TK_M_ACTION] = "M ACTION", + [TK_M_DEVPATH] = "M DEVPATH", + [TK_M_KERNEL] = "M KERNEL", + [TK_M_DEVLINK] = "M DEVLINK", + [TK_M_NAME] = "M NAME", + [TK_M_ENV] = "M ENV", + [TK_M_TAG] = "M TAG", + [TK_M_SUBSYSTEM] = "M SUBSYSTEM", + [TK_M_DRIVER] = "M DRIVER", + [TK_M_WAITFOR] = "M WAITFOR", + [TK_M_ATTR] = "M ATTR", + [TK_M_SYSCTL] = "M SYSCTL", + + [TK_M_PARENTS_MIN] = "M PARENTS_MIN", + [TK_M_KERNELS] = "M KERNELS", + [TK_M_SUBSYSTEMS] = "M SUBSYSTEMS", + [TK_M_DRIVERS] = "M DRIVERS", + [TK_M_ATTRS] = "M ATTRS", + [TK_M_TAGS] = "M TAGS", + [TK_M_PARENTS_MAX] = "M PARENTS_MAX", + + [TK_M_TEST] = "M TEST", + [TK_M_PROGRAM] = "M PROGRAM", + [TK_M_IMPORT_FILE] = "M IMPORT_FILE", + [TK_M_IMPORT_PROG] = "M IMPORT_PROG", + [TK_M_IMPORT_BUILTIN] = "M IMPORT_BUILTIN", + [TK_M_IMPORT_DB] = "M IMPORT_DB", + [TK_M_IMPORT_CMDLINE] = "M IMPORT_CMDLINE", + [TK_M_IMPORT_PARENT] = "M IMPORT_PARENT", + [TK_M_RESULT] = "M RESULT", + [TK_M_MAX] = "M MAX", + + [TK_A_STRING_ESCAPE_NONE] = "A STRING_ESCAPE_NONE", + [TK_A_STRING_ESCAPE_REPLACE] = "A STRING_ESCAPE_REPLACE", + [TK_A_DB_PERSIST] = "A DB_PERSIST", + [TK_A_INOTIFY_WATCH] = "A INOTIFY_WATCH", + [TK_A_DEVLINK_PRIO] = "A DEVLINK_PRIO", + [TK_A_OWNER] = "A OWNER", + [TK_A_GROUP] = "A GROUP", + [TK_A_MODE] = "A MODE", + [TK_A_OWNER_ID] = "A OWNER_ID", + [TK_A_GROUP_ID] = "A GROUP_ID", + [TK_A_STATIC_NODE] = "A STATIC_NODE", + [TK_A_SECLABEL] = "A SECLABEL", + [TK_A_MODE_ID] = "A MODE_ID", + [TK_A_ENV] = "A ENV", + [TK_A_TAG] = "A ENV", + [TK_A_NAME] = "A NAME", + [TK_A_DEVLINK] = "A DEVLINK", + [TK_A_ATTR] = "A ATTR", + [TK_A_SYSCTL] = "A SYSCTL", + [TK_A_RUN_BUILTIN] = "A RUN_BUILTIN", + [TK_A_RUN_PROGRAM] = "A RUN_PROGRAM", + [TK_A_GOTO] = "A GOTO", + + [TK_END] = "END", + }; + + return token_strs[type]; +} + +static void dump_token(struct udev_rules *rules, struct token *token) { + enum token_type type = token->type; + enum operation_type op = token->key.op; + enum string_glob_type glob = token->key.glob; + const char *value = rules_str(rules, token->key.value_off); + const char *attr = &rules->strbuf->buf[token->key.attr_off]; + + switch (type) { + case TK_RULE: + { + const char *tks_ptr = (char *)rules->tokens; + const char *tk_ptr = (char *)token; + unsigned int idx = (tk_ptr - tks_ptr) / sizeof(struct token); + + log_debug("* RULE %s:%u, token: %u, count: %u, label: '%s'", + &rules->strbuf->buf[token->rule.filename_off], token->rule.filename_line, + idx, token->rule.token_count, + &rules->strbuf->buf[token->rule.label_off]); + break; + } + case TK_M_ACTION: + case TK_M_DEVPATH: + case TK_M_KERNEL: + case TK_M_SUBSYSTEM: + case TK_M_DRIVER: + case TK_M_WAITFOR: + case TK_M_DEVLINK: + case TK_M_NAME: + case TK_M_KERNELS: + case TK_M_SUBSYSTEMS: + case TK_M_DRIVERS: + case TK_M_TAGS: + case TK_M_PROGRAM: + case TK_M_IMPORT_FILE: + case TK_M_IMPORT_PROG: + case TK_M_IMPORT_DB: + case TK_M_IMPORT_CMDLINE: + case TK_M_IMPORT_PARENT: + case TK_M_RESULT: + case TK_A_NAME: + case TK_A_DEVLINK: + case TK_A_OWNER: + case TK_A_GROUP: + case TK_A_MODE: + case TK_A_RUN_BUILTIN: + case TK_A_RUN_PROGRAM: + log_debug("%s %s '%s'(%s)", + token_str(type), operation_str(op), value, string_glob_str(glob)); + break; + case TK_M_IMPORT_BUILTIN: + log_debug("%s %i '%s'", token_str(type), token->key.builtin_cmd, value); + break; + case TK_M_ATTR: + case TK_M_SYSCTL: + case TK_M_ATTRS: + case TK_M_ENV: + case TK_A_ATTR: + case TK_A_SYSCTL: + case TK_A_ENV: + log_debug("%s %s '%s' '%s'(%s)", + token_str(type), operation_str(op), attr, value, string_glob_str(glob)); + break; + case TK_M_TAG: + case TK_A_TAG: + log_debug("%s %s '%s'", token_str(type), operation_str(op), value); + break; + case TK_A_STRING_ESCAPE_NONE: + case TK_A_STRING_ESCAPE_REPLACE: + case TK_A_DB_PERSIST: + log_debug("%s", token_str(type)); + break; + case TK_M_TEST: + log_debug("%s %s '%s'(%s) %#o", + token_str(type), operation_str(op), value, string_glob_str(glob), token->key.mode); + break; + case TK_A_INOTIFY_WATCH: + log_debug("%s %u", token_str(type), token->key.watch); + break; + case TK_A_DEVLINK_PRIO: + log_debug("%s %u", token_str(type), token->key.devlink_prio); + break; + case TK_A_OWNER_ID: + log_debug("%s %s %u", token_str(type), operation_str(op), token->key.uid); + break; + case TK_A_GROUP_ID: + log_debug("%s %s %u", token_str(type), operation_str(op), token->key.gid); + break; + case TK_A_MODE_ID: + log_debug("%s %s %#o", token_str(type), operation_str(op), token->key.mode); + break; + case TK_A_STATIC_NODE: + log_debug("%s '%s'", token_str(type), value); + break; + case TK_A_SECLABEL: + log_debug("%s %s '%s' '%s'", token_str(type), operation_str(op), attr, value); + break; + case TK_A_GOTO: + log_debug("%s '%s' %u", token_str(type), value, token->key.rule_goto); + break; + case TK_END: + log_debug("* %s", token_str(type)); + break; + case TK_M_PARENTS_MIN: + case TK_M_PARENTS_MAX: + case TK_M_MAX: + case TK_UNSET: + log_debug("unknown type %u", type); + break; + } +} + +static void dump_rules(struct udev_rules *rules) { + unsigned int i; + + log_debug("dumping %u (%zu bytes) tokens, %zu (%zu bytes) strings", + rules->token_cur, + rules->token_cur * sizeof(struct token), + rules->strbuf->nodes_count, + rules->strbuf->len); + for (i = 0; i < rules->token_cur; i++) + dump_token(rules, &rules->tokens[i]); +} +#else +static inline void dump_token(struct udev_rules *rules, struct token *token) {} +static inline void dump_rules(struct udev_rules *rules) {} +#endif /* DEBUG */ + +static int add_token(struct udev_rules *rules, struct token *token) { + /* grow buffer if needed */ + if (rules->token_cur+1 >= rules->token_max) { + struct token *tokens; + unsigned int add; + + /* double the buffer size */ + add = rules->token_max; + if (add < 8) + add = 8; + + tokens = realloc(rules->tokens, (rules->token_max + add ) * sizeof(struct token)); + if (tokens == NULL) + return -1; + rules->tokens = tokens; + rules->token_max += add; + } + memcpy(&rules->tokens[rules->token_cur], token, sizeof(struct token)); + rules->token_cur++; + return 0; +} + +static uid_t add_uid(struct udev_rules *rules, const char *owner) { + unsigned int i; + uid_t uid = 0; + unsigned int off; + int r; + + /* lookup, if we know it already */ + for (i = 0; i < rules->uids_cur; i++) { + off = rules->uids[i].name_off; + if (streq(rules_str(rules, off), owner)) { + uid = rules->uids[i].uid; + return uid; + } + } + r = get_user_creds(&owner, &uid, NULL, NULL, NULL); + if (r < 0) { + if (r == -ENOENT || r == -ESRCH) + log_error("specified user '%s' unknown", owner); + else + log_error_errno(r, "error resolving user '%s': %m", owner); + } + + /* grow buffer if needed */ + if (rules->uids_cur+1 >= rules->uids_max) { + struct uid_gid *uids; + unsigned int add; + + /* double the buffer size */ + add = rules->uids_max; + if (add < 1) + add = 8; + + uids = realloc(rules->uids, (rules->uids_max + add ) * sizeof(struct uid_gid)); + if (uids == NULL) + return uid; + rules->uids = uids; + rules->uids_max += add; + } + rules->uids[rules->uids_cur].uid = uid; + off = rules_add_string(rules, owner); + if (off <= 0) + return uid; + rules->uids[rules->uids_cur].name_off = off; + rules->uids_cur++; + return uid; +} + +static gid_t add_gid(struct udev_rules *rules, const char *group) { + unsigned int i; + gid_t gid = 0; + unsigned int off; + int r; + + /* lookup, if we know it already */ + for (i = 0; i < rules->gids_cur; i++) { + off = rules->gids[i].name_off; + if (streq(rules_str(rules, off), group)) { + gid = rules->gids[i].gid; + return gid; + } + } + r = get_group_creds(&group, &gid); + if (r < 0) { + if (r == -ENOENT || r == -ESRCH) + log_error("specified group '%s' unknown", group); + else + log_error_errno(r, "error resolving group '%s': %m", group); + } + + /* grow buffer if needed */ + if (rules->gids_cur+1 >= rules->gids_max) { + struct uid_gid *gids; + unsigned int add; + + /* double the buffer size */ + add = rules->gids_max; + if (add < 1) + add = 8; + + gids = realloc(rules->gids, (rules->gids_max + add ) * sizeof(struct uid_gid)); + if (gids == NULL) + return gid; + rules->gids = gids; + rules->gids_max += add; + } + rules->gids[rules->gids_cur].gid = gid; + off = rules_add_string(rules, group); + if (off <= 0) + return gid; + rules->gids[rules->gids_cur].name_off = off; + rules->gids_cur++; + return gid; +} + +static int import_property_from_string(struct udev_device *dev, char *line) { + char *key; + char *val; + size_t len; + + /* find key */ + key = line; + while (isspace(key[0])) + key++; + + /* comment or empty line */ + if (key[0] == '#' || key[0] == '\0') + return -1; + + /* split key/value */ + val = strchr(key, '='); + if (val == NULL) + return -1; + val[0] = '\0'; + val++; + + /* find value */ + while (isspace(val[0])) + val++; + + /* terminate key */ + len = strlen(key); + if (len == 0) + return -1; + while (isspace(key[len-1])) + len--; + key[len] = '\0'; + + /* terminate value */ + len = strlen(val); + if (len == 0) + return -1; + while (isspace(val[len-1])) + len--; + val[len] = '\0'; + + if (len == 0) + return -1; + + /* unquote */ + if (val[0] == '"' || val[0] == '\'') { + if (val[len-1] != val[0]) { + log_debug("inconsistent quoting: '%s', skip", line); + return -1; + } + val[len-1] = '\0'; + val++; + } + + udev_device_add_property(dev, key, val); + + return 0; +} + +static int import_file_into_properties(struct udev_device *dev, const char *filename) { + FILE *f; + char line[UTIL_LINE_SIZE]; + + f = fopen(filename, "re"); + if (f == NULL) + return -1; + while (fgets(line, sizeof(line), f) != NULL) + import_property_from_string(dev, line); + fclose(f); + return 0; +} + +static int import_program_into_properties(struct udev_event *event, + usec_t timeout_usec, + usec_t timeout_warn_usec, + const char *program) { + char result[UTIL_LINE_SIZE]; + char *line; + int err; + + err = udev_event_spawn(event, timeout_usec, timeout_warn_usec, true, program, result, sizeof(result)); + if (err < 0) + return err; + + line = result; + while (line != NULL) { + char *pos; + + pos = strchr(line, '\n'); + if (pos != NULL) { + pos[0] = '\0'; + pos = &pos[1]; + } + import_property_from_string(event->dev, line); + line = pos; + } + return 0; +} + +static int import_parent_into_properties(struct udev_device *dev, const char *filter) { + struct udev_device *dev_parent; + struct udev_list_entry *list_entry; + + assert(dev); + assert(filter); + + dev_parent = udev_device_get_parent(dev); + if (dev_parent == NULL) + return -1; + + udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(dev_parent)) { + const char *key = udev_list_entry_get_name(list_entry); + const char *val = udev_list_entry_get_value(list_entry); + + if (fnmatch(filter, key, 0) == 0) + udev_device_add_property(dev, key, val); + } + return 0; +} + +static void attr_subst_subdir(char *attr, size_t len) { + const char *pos, *tail, *path; + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *dent; + + pos = strstr(attr, "/*/"); + if (!pos) + return; + + tail = pos + 2; + path = strndupa(attr, pos - attr + 1); /* include slash at end */ + dir = opendir(path); + if (dir == NULL) + return; + + for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) + if (dent->d_name[0] != '.') { + char n[strlen(dent->d_name) + strlen(tail) + 1]; + + strscpyl(n, sizeof n, dent->d_name, tail, NULL); + if (faccessat(dirfd(dir), n, F_OK, 0) == 0) { + strscpyl(attr, len, path, n, NULL); + break; + } + } +} + +static int get_key(struct udev *udev, char **line, char **key, enum operation_type *op, char **value) { + char *linepos; + char *temp; + + linepos = *line; + if (linepos == NULL || linepos[0] == '\0') + return -1; + + /* skip whitespace */ + while (isspace(linepos[0]) || linepos[0] == ',') + linepos++; + + /* get the key */ + if (linepos[0] == '\0') + return -1; + *key = linepos; + + for (;;) { + linepos++; + if (linepos[0] == '\0') + return -1; + if (isspace(linepos[0])) + break; + if (linepos[0] == '=') + break; + if ((linepos[0] == '+') || (linepos[0] == '-') || (linepos[0] == '!') || (linepos[0] == ':')) + if (linepos[1] == '=') + break; + } + + /* remember end of key */ + temp = linepos; + + /* skip whitespace after key */ + while (isspace(linepos[0])) + linepos++; + if (linepos[0] == '\0') + return -1; + + /* get operation type */ + if (linepos[0] == '=' && linepos[1] == '=') { + *op = OP_MATCH; + linepos += 2; + } else if (linepos[0] == '!' && linepos[1] == '=') { + *op = OP_NOMATCH; + linepos += 2; + } else if (linepos[0] == '+' && linepos[1] == '=') { + *op = OP_ADD; + linepos += 2; + } else if (linepos[0] == '-' && linepos[1] == '=') { + *op = OP_REMOVE; + linepos += 2; + } else if (linepos[0] == '=') { + *op = OP_ASSIGN; + linepos++; + } else if (linepos[0] == ':' && linepos[1] == '=') { + *op = OP_ASSIGN_FINAL; + linepos += 2; + } else + return -1; + + /* terminate key */ + temp[0] = '\0'; + + /* skip whitespace after operator */ + while (isspace(linepos[0])) + linepos++; + if (linepos[0] == '\0') + return -1; + + /* get the value */ + if (linepos[0] == '"') + linepos++; + else + return -1; + *value = linepos; + + /* terminate */ + temp = strchr(linepos, '"'); + if (!temp) + return -1; + temp[0] = '\0'; + temp++; + + /* move line to next key */ + *line = temp; + return 0; +} + +/* extract possible KEY{attr} */ +static const char *get_key_attribute(struct udev *udev, char *str) { + char *pos; + char *attr; + + attr = strchr(str, '{'); + if (attr != NULL) { + attr++; + pos = strchr(attr, '}'); + if (pos == NULL) { + log_error("missing closing brace for format"); + return NULL; + } + pos[0] = '\0'; + return attr; + } + return NULL; +} + +static void rule_add_key(struct rule_tmp *rule_tmp, enum token_type type, + enum operation_type op, + const char *value, const void *data) { + struct token *token = rule_tmp->token + rule_tmp->token_cur; + const char *attr = NULL; + + assert(rule_tmp->token_cur < ELEMENTSOF(rule_tmp->token)); + memzero(token, sizeof(struct token)); + + switch (type) { + case TK_M_ACTION: + case TK_M_DEVPATH: + case TK_M_KERNEL: + case TK_M_SUBSYSTEM: + case TK_M_DRIVER: + case TK_M_WAITFOR: + case TK_M_DEVLINK: + case TK_M_NAME: + case TK_M_KERNELS: + case TK_M_SUBSYSTEMS: + case TK_M_DRIVERS: + case TK_M_TAGS: + case TK_M_PROGRAM: + case TK_M_IMPORT_FILE: + case TK_M_IMPORT_PROG: + case TK_M_IMPORT_DB: + case TK_M_IMPORT_CMDLINE: + case TK_M_IMPORT_PARENT: + case TK_M_RESULT: + case TK_A_OWNER: + case TK_A_GROUP: + case TK_A_MODE: + case TK_A_DEVLINK: + case TK_A_NAME: + case TK_A_GOTO: + case TK_M_TAG: + case TK_A_TAG: + case TK_A_STATIC_NODE: + token->key.value_off = rules_add_string(rule_tmp->rules, value); + break; + case TK_M_IMPORT_BUILTIN: + token->key.value_off = rules_add_string(rule_tmp->rules, value); + token->key.builtin_cmd = *(enum udev_builtin_cmd *)data; + break; + case TK_M_ENV: + case TK_M_ATTR: + case TK_M_SYSCTL: + case TK_M_ATTRS: + case TK_A_ATTR: + case TK_A_SYSCTL: + case TK_A_ENV: + case TK_A_SECLABEL: + attr = data; + token->key.value_off = rules_add_string(rule_tmp->rules, value); + token->key.attr_off = rules_add_string(rule_tmp->rules, attr); + break; + case TK_M_TEST: + token->key.value_off = rules_add_string(rule_tmp->rules, value); + if (data != NULL) + token->key.mode = *(mode_t *)data; + break; + case TK_A_STRING_ESCAPE_NONE: + case TK_A_STRING_ESCAPE_REPLACE: + case TK_A_DB_PERSIST: + break; + case TK_A_RUN_BUILTIN: + case TK_A_RUN_PROGRAM: + token->key.builtin_cmd = *(enum udev_builtin_cmd *)data; + token->key.value_off = rules_add_string(rule_tmp->rules, value); + break; + case TK_A_INOTIFY_WATCH: + case TK_A_DEVLINK_PRIO: + token->key.devlink_prio = *(int *)data; + break; + case TK_A_OWNER_ID: + token->key.uid = *(uid_t *)data; + break; + case TK_A_GROUP_ID: + token->key.gid = *(gid_t *)data; + break; + case TK_A_MODE_ID: + token->key.mode = *(mode_t *)data; + break; + case TK_RULE: + case TK_M_PARENTS_MIN: + case TK_M_PARENTS_MAX: + case TK_M_MAX: + case TK_END: + case TK_UNSET: + assert_not_reached("wrong type"); + } + + if (value != NULL && type < TK_M_MAX) { + /* check if we need to split or call fnmatch() while matching rules */ + enum string_glob_type glob; + int has_split; + int has_glob; + + has_split = (strchr(value, '|') != NULL); + has_glob = string_is_glob(value); + if (has_split && has_glob) { + glob = GL_SPLIT_GLOB; + } else if (has_split) { + glob = GL_SPLIT; + } else if (has_glob) { + if (streq(value, "?*")) + glob = GL_SOMETHING; + else + glob = GL_GLOB; + } else { + glob = GL_PLAIN; + } + token->key.glob = glob; + } + + if (value != NULL && type > TK_M_MAX) { + /* check if assigned value has substitution chars */ + if (value[0] == '[') + token->key.subst = SB_SUBSYS; + else if (strchr(value, '%') != NULL || strchr(value, '$') != NULL) + token->key.subst = SB_FORMAT; + else + token->key.subst = SB_NONE; + } + + if (attr != NULL) { + /* check if property/attribute name has substitution chars */ + if (attr[0] == '[') + token->key.attrsubst = SB_SUBSYS; + else if (strchr(attr, '%') != NULL || strchr(attr, '$') != NULL) + token->key.attrsubst = SB_FORMAT; + else + token->key.attrsubst = SB_NONE; + } + + token->key.type = type; + token->key.op = op; + rule_tmp->token_cur++; +} + +static int sort_token(struct udev_rules *rules, struct rule_tmp *rule_tmp) { + unsigned int i; + unsigned int start = 0; + unsigned int end = rule_tmp->token_cur; + + for (i = 0; i < rule_tmp->token_cur; i++) { + enum token_type next_val = TK_UNSET; + unsigned int next_idx = 0; + unsigned int j; + + /* find smallest value */ + for (j = start; j < end; j++) { + if (rule_tmp->token[j].type == TK_UNSET) + continue; + if (next_val == TK_UNSET || rule_tmp->token[j].type < next_val) { + next_val = rule_tmp->token[j].type; + next_idx = j; + } + } + + /* add token and mark done */ + if (add_token(rules, &rule_tmp->token[next_idx]) != 0) + return -1; + rule_tmp->token[next_idx].type = TK_UNSET; + + /* shrink range */ + if (next_idx == start) + start++; + if (next_idx+1 == end) + end--; + } + return 0; +} + +#define LOG_RULE_ERROR(fmt, ...) log_error("Invalid rule %s:%u: " fmt, filename, lineno, ##__VA_ARGS__) +#define LOG_RULE_WARNING(fmt, ...) log_warning("%s:%u: " fmt, filename, lineno, ##__VA_ARGS__) +#define LOG_RULE_DEBUG(fmt, ...) log_debug("%s:%u: " fmt, filename, lineno, ##__VA_ARGS__) +#define LOG_AND_RETURN(fmt, ...) { LOG_RULE_ERROR(fmt, __VA_ARGS__); return; } + +static void add_rule(struct udev_rules *rules, char *line, + const char *filename, unsigned int filename_off, unsigned int lineno) { + char *linepos; + const char *attr; + struct rule_tmp rule_tmp = { + .rules = rules, + .rule.type = TK_RULE, + }; + + /* the offset in the rule is limited to unsigned short */ + if (filename_off < USHRT_MAX) + rule_tmp.rule.rule.filename_off = filename_off; + rule_tmp.rule.rule.filename_line = lineno; + + linepos = line; + for (;;) { + char *key; + char *value; + enum operation_type op; + + if (get_key(rules->udev, &linepos, &key, &op, &value) != 0) { + /* Avoid erroring on trailing whitespace. This is probably rare + * so save the work for the error case instead of always trying + * to strip the trailing whitespace with strstrip(). */ + while (isblank(*linepos)) + linepos++; + + /* If we aren't at the end of the line, this is a parsing error. + * Make a best effort to describe where the problem is. */ + if (!strchr(NEWLINE, *linepos)) { + char buf[2] = {*linepos}; + _cleanup_free_ char *tmp; + + tmp = cescape(buf); + log_error("invalid key/value pair in file %s on line %u, starting at character %tu ('%s')", + filename, lineno, linepos - line + 1, tmp); + if (*linepos == '#') + log_error("hint: comments can only start at beginning of line"); + } + break; + } + + if (rule_tmp.token_cur >= ELEMENTSOF(rule_tmp.token)) + LOG_AND_RETURN("temporary rule array too small, aborting event processing with %u items", rule_tmp.token_cur); + + if (streq(key, "ACTION")) { + if (op > OP_MATCH_MAX) + LOG_AND_RETURN("invalid %s operation", key); + + rule_add_key(&rule_tmp, TK_M_ACTION, op, value, NULL); + + } else if (streq(key, "DEVPATH")) { + if (op > OP_MATCH_MAX) + LOG_AND_RETURN("invalid %s operation", key); + + rule_add_key(&rule_tmp, TK_M_DEVPATH, op, value, NULL); + + } else if (streq(key, "KERNEL")) { + if (op > OP_MATCH_MAX) + LOG_AND_RETURN("invalid %s operation", key); + + rule_add_key(&rule_tmp, TK_M_KERNEL, op, value, NULL); + + } else if (streq(key, "SUBSYSTEM")) { + if (op > OP_MATCH_MAX) + LOG_AND_RETURN("invalid %s operation", key); + + /* bus, class, subsystem events should all be the same */ + if (STR_IN_SET(value, "subsystem", "bus", "class")) { + if (!streq(value, "subsystem")) + LOG_RULE_WARNING("'%s' must be specified as 'subsystem'; please fix", value); + + rule_add_key(&rule_tmp, TK_M_SUBSYSTEM, op, "subsystem|class|bus", NULL); + } else + rule_add_key(&rule_tmp, TK_M_SUBSYSTEM, op, value, NULL); + + } else if (streq(key, "DRIVER")) { + if (op > OP_MATCH_MAX) + LOG_AND_RETURN("invalid %s operation", key); + + rule_add_key(&rule_tmp, TK_M_DRIVER, op, value, NULL); + + } else if (startswith(key, "ATTR{")) { + attr = get_key_attribute(rules->udev, key + strlen("ATTR")); + if (attr == NULL) + LOG_AND_RETURN("error parsing %s attribute", "ATTR"); + + if (op == OP_REMOVE) + LOG_AND_RETURN("invalid %s operation", "ATTR"); + + if (op < OP_MATCH_MAX) + rule_add_key(&rule_tmp, TK_M_ATTR, op, value, attr); + else + rule_add_key(&rule_tmp, TK_A_ATTR, op, value, attr); + + } else if (startswith(key, "SYSCTL{")) { + attr = get_key_attribute(rules->udev, key + strlen("SYSCTL")); + if (attr == NULL) + LOG_AND_RETURN("error parsing %s attribute", "ATTR"); + + if (op == OP_REMOVE) + LOG_AND_RETURN("invalid %s operation", "ATTR"); + + if (op < OP_MATCH_MAX) + rule_add_key(&rule_tmp, TK_M_SYSCTL, op, value, attr); + else + rule_add_key(&rule_tmp, TK_A_SYSCTL, op, value, attr); + + } else if (startswith(key, "SECLABEL{")) { + attr = get_key_attribute(rules->udev, key + strlen("SECLABEL")); + if (attr == NULL) + LOG_AND_RETURN("error parsing %s attribute", "SECLABEL"); + + if (op == OP_REMOVE) + LOG_AND_RETURN("invalid %s operation", "SECLABEL"); + + rule_add_key(&rule_tmp, TK_A_SECLABEL, op, value, attr); + + } else if (streq(key, "KERNELS")) { + if (op > OP_MATCH_MAX) + LOG_AND_RETURN("invalid %s operation", key); + + rule_add_key(&rule_tmp, TK_M_KERNELS, op, value, NULL); + + } else if (streq(key, "SUBSYSTEMS")) { + if (op > OP_MATCH_MAX) + LOG_AND_RETURN("invalid %s operation", key); + + rule_add_key(&rule_tmp, TK_M_SUBSYSTEMS, op, value, NULL); + + } else if (streq(key, "DRIVERS")) { + if (op > OP_MATCH_MAX) + LOG_AND_RETURN("invalid %s operation", key); + + rule_add_key(&rule_tmp, TK_M_DRIVERS, op, value, NULL); + + } else if (startswith(key, "ATTRS{")) { + if (op > OP_MATCH_MAX) + LOG_AND_RETURN("invalid %s operation", "ATTRS"); + + attr = get_key_attribute(rules->udev, key + strlen("ATTRS")); + if (attr == NULL) + LOG_AND_RETURN("error parsing %s attribute", "ATTRS"); + + if (startswith(attr, "device/")) + LOG_RULE_WARNING("'device' link may not be available in future kernels; please fix"); + if (strstr(attr, "../") != NULL) + LOG_RULE_WARNING("direct reference to parent sysfs directory, may break in future kernels; please fix"); + rule_add_key(&rule_tmp, TK_M_ATTRS, op, value, attr); + + } else if (streq(key, "TAGS")) { + if (op > OP_MATCH_MAX) + LOG_AND_RETURN("invalid %s operation", key); + + rule_add_key(&rule_tmp, TK_M_TAGS, op, value, NULL); + + } else if (startswith(key, "ENV{")) { + attr = get_key_attribute(rules->udev, key + strlen("ENV")); + if (attr == NULL) + LOG_AND_RETURN("error parsing %s attribute", "ENV"); + + if (op == OP_REMOVE) + LOG_AND_RETURN("invalid %s operation", "ENV"); + + if (op < OP_MATCH_MAX) + rule_add_key(&rule_tmp, TK_M_ENV, op, value, attr); + else { + if (STR_IN_SET(attr, + "ACTION", + "SUBSYSTEM", + "DEVTYPE", + "MAJOR", + "MINOR", + "DRIVER", + "IFINDEX", + "DEVNAME", + "DEVLINKS", + "DEVPATH", + "TAGS")) + LOG_AND_RETURN("invalid ENV attribute, '%s' cannot be set", attr); + + rule_add_key(&rule_tmp, TK_A_ENV, op, value, attr); + } + + } else if (streq(key, "TAG")) { + if (op < OP_MATCH_MAX) + rule_add_key(&rule_tmp, TK_M_TAG, op, value, NULL); + else + rule_add_key(&rule_tmp, TK_A_TAG, op, value, NULL); + + } else if (streq(key, "PROGRAM")) { + if (op == OP_REMOVE) + LOG_AND_RETURN("invalid %s operation", key); + + rule_add_key(&rule_tmp, TK_M_PROGRAM, op, value, NULL); + + } else if (streq(key, "RESULT")) { + if (op > OP_MATCH_MAX) + LOG_AND_RETURN("invalid %s operation", key); + + rule_add_key(&rule_tmp, TK_M_RESULT, op, value, NULL); + + } else if (startswith(key, "IMPORT")) { + attr = get_key_attribute(rules->udev, key + strlen("IMPORT")); + if (attr == NULL) { + LOG_RULE_WARNING("ignoring IMPORT{} with missing type"); + continue; + } + if (op == OP_REMOVE) + LOG_AND_RETURN("invalid %s operation", "IMPORT"); + + if (streq(attr, "program")) { + /* find known built-in command */ + if (value[0] != '/') { + const enum udev_builtin_cmd cmd = udev_builtin_lookup(value); + + if (cmd < UDEV_BUILTIN_MAX) { + LOG_RULE_DEBUG("IMPORT found builtin '%s', replacing", value); + rule_add_key(&rule_tmp, TK_M_IMPORT_BUILTIN, op, value, &cmd); + continue; + } + } + rule_add_key(&rule_tmp, TK_M_IMPORT_PROG, op, value, NULL); + } else if (streq(attr, "builtin")) { + const enum udev_builtin_cmd cmd = udev_builtin_lookup(value); + + if (cmd >= UDEV_BUILTIN_MAX) + LOG_RULE_WARNING("IMPORT{builtin} '%s' unknown", value); + else + rule_add_key(&rule_tmp, TK_M_IMPORT_BUILTIN, op, value, &cmd); + } else if (streq(attr, "file")) + rule_add_key(&rule_tmp, TK_M_IMPORT_FILE, op, value, NULL); + else if (streq(attr, "db")) + rule_add_key(&rule_tmp, TK_M_IMPORT_DB, op, value, NULL); + else if (streq(attr, "cmdline")) + rule_add_key(&rule_tmp, TK_M_IMPORT_CMDLINE, op, value, NULL); + else if (streq(attr, "parent")) + rule_add_key(&rule_tmp, TK_M_IMPORT_PARENT, op, value, NULL); + else + LOG_RULE_ERROR("ignoring unknown %s{} type '%s'", "IMPORT", attr); + + } else if (startswith(key, "TEST")) { + mode_t mode = 0; + + if (op > OP_MATCH_MAX) + LOG_AND_RETURN("invalid %s operation", "TEST"); + + attr = get_key_attribute(rules->udev, key + strlen("TEST")); + if (attr != NULL) { + mode = strtol(attr, NULL, 8); + rule_add_key(&rule_tmp, TK_M_TEST, op, value, &mode); + } else + rule_add_key(&rule_tmp, TK_M_TEST, op, value, NULL); + + } else if (startswith(key, "RUN")) { + attr = get_key_attribute(rules->udev, key + strlen("RUN")); + if (attr == NULL) + attr = "program"; + if (op == OP_REMOVE) + LOG_AND_RETURN("invalid %s operation", "RUN"); + + if (streq(attr, "builtin")) { + const enum udev_builtin_cmd cmd = udev_builtin_lookup(value); + + if (cmd < UDEV_BUILTIN_MAX) + rule_add_key(&rule_tmp, TK_A_RUN_BUILTIN, op, value, &cmd); + else + LOG_RULE_ERROR("RUN{builtin}: '%s' unknown", value); + } else if (streq(attr, "program")) { + const enum udev_builtin_cmd cmd = UDEV_BUILTIN_MAX; + + rule_add_key(&rule_tmp, TK_A_RUN_PROGRAM, op, value, &cmd); + } else + LOG_RULE_ERROR("ignoring unknown %s{} type '%s'", "RUN", attr); + + } else if (streq(key, "LABEL")) { + if (op == OP_REMOVE) + LOG_AND_RETURN("invalid %s operation", key); + + rule_tmp.rule.rule.label_off = rules_add_string(rules, value); + + } else if (streq(key, "GOTO")) { + if (op == OP_REMOVE) + LOG_AND_RETURN("invalid %s operation", key); + + rule_add_key(&rule_tmp, TK_A_GOTO, 0, value, NULL); + + } else if (startswith(key, "NAME")) { + if (op == OP_REMOVE) + LOG_AND_RETURN("invalid %s operation", key); + + if (op < OP_MATCH_MAX) + rule_add_key(&rule_tmp, TK_M_NAME, op, value, NULL); + else { + if (streq(value, "%k")) { + LOG_RULE_WARNING("NAME=\"%%k\" is ignored, because it breaks kernel supplied names; please remove"); + continue; + } + if (isempty(value)) { + LOG_RULE_DEBUG("NAME=\"\" is ignored, because udev will not delete any device nodes; please remove"); + continue; + } + rule_add_key(&rule_tmp, TK_A_NAME, op, value, NULL); + } + rule_tmp.rule.rule.can_set_name = true; + + } else if (streq(key, "SYMLINK")) { + if (op == OP_REMOVE) + LOG_AND_RETURN("invalid %s operation", key); + + if (op < OP_MATCH_MAX) + rule_add_key(&rule_tmp, TK_M_DEVLINK, op, value, NULL); + else + rule_add_key(&rule_tmp, TK_A_DEVLINK, op, value, NULL); + rule_tmp.rule.rule.can_set_name = true; + + } else if (streq(key, "OWNER")) { + uid_t uid; + char *endptr; + + if (op == OP_REMOVE) + LOG_AND_RETURN("invalid %s operation", key); + + uid = strtoul(value, &endptr, 10); + if (endptr[0] == '\0') + rule_add_key(&rule_tmp, TK_A_OWNER_ID, op, NULL, &uid); + else if (rules->resolve_names > 0 && strchr("$%", value[0]) == NULL) { + uid = add_uid(rules, value); + rule_add_key(&rule_tmp, TK_A_OWNER_ID, op, NULL, &uid); + } else if (rules->resolve_names >= 0) + rule_add_key(&rule_tmp, TK_A_OWNER, op, value, NULL); + + rule_tmp.rule.rule.can_set_name = true; + + } else if (streq(key, "GROUP")) { + gid_t gid; + char *endptr; + + if (op == OP_REMOVE) + LOG_AND_RETURN("invalid %s operation", key); + + gid = strtoul(value, &endptr, 10); + if (endptr[0] == '\0') + rule_add_key(&rule_tmp, TK_A_GROUP_ID, op, NULL, &gid); + else if ((rules->resolve_names > 0) && strchr("$%", value[0]) == NULL) { + gid = add_gid(rules, value); + rule_add_key(&rule_tmp, TK_A_GROUP_ID, op, NULL, &gid); + } else if (rules->resolve_names >= 0) + rule_add_key(&rule_tmp, TK_A_GROUP, op, value, NULL); + + rule_tmp.rule.rule.can_set_name = true; + + } else if (streq(key, "MODE")) { + mode_t mode; + char *endptr; + + if (op == OP_REMOVE) + LOG_AND_RETURN("invalid %s operation", key); + + mode = strtol(value, &endptr, 8); + if (endptr[0] == '\0') + rule_add_key(&rule_tmp, TK_A_MODE_ID, op, NULL, &mode); + else + rule_add_key(&rule_tmp, TK_A_MODE, op, value, NULL); + rule_tmp.rule.rule.can_set_name = true; + + } else if (streq(key, "OPTIONS")) { + const char *pos; + + if (op == OP_REMOVE) + LOG_AND_RETURN("invalid %s operation", key); + + pos = strstr(value, "link_priority="); + if (pos != NULL) { + int prio = atoi(pos + strlen("link_priority=")); + + rule_add_key(&rule_tmp, TK_A_DEVLINK_PRIO, op, NULL, &prio); + } + + pos = strstr(value, "string_escape="); + if (pos != NULL) { + pos += strlen("string_escape="); + if (startswith(pos, "none")) + rule_add_key(&rule_tmp, TK_A_STRING_ESCAPE_NONE, op, NULL, NULL); + else if (startswith(pos, "replace")) + rule_add_key(&rule_tmp, TK_A_STRING_ESCAPE_REPLACE, op, NULL, NULL); + } + + pos = strstr(value, "db_persist"); + if (pos != NULL) + rule_add_key(&rule_tmp, TK_A_DB_PERSIST, op, NULL, NULL); + + pos = strstr(value, "nowatch"); + if (pos != NULL) { + const int off = 0; + + rule_add_key(&rule_tmp, TK_A_INOTIFY_WATCH, op, NULL, &off); + } else { + pos = strstr(value, "watch"); + if (pos != NULL) { + const int on = 1; + + rule_add_key(&rule_tmp, TK_A_INOTIFY_WATCH, op, NULL, &on); + } + } + + pos = strstr(value, "static_node="); + if (pos != NULL) { + pos += strlen("static_node="); + rule_add_key(&rule_tmp, TK_A_STATIC_NODE, op, pos, NULL); + rule_tmp.rule.rule.has_static_node = true; + } + + } else + LOG_AND_RETURN("unknown key '%s'", key); + } + + /* add rule token and sort tokens */ + rule_tmp.rule.rule.token_count = 1 + rule_tmp.token_cur; + if (add_token(rules, &rule_tmp.rule) != 0 || sort_token(rules, &rule_tmp) != 0) + LOG_RULE_ERROR("failed to add rule token"); +} + +static int parse_file(struct udev_rules *rules, const char *filename) { + _cleanup_fclose_ FILE *f = NULL; + unsigned int first_token; + unsigned int filename_off; + char line[UTIL_LINE_SIZE]; + int line_nr = 0; + unsigned int i; + + f = fopen(filename, "re"); + if (!f) { + if (errno == ENOENT) + return 0; + else + return -errno; + } + + if (null_or_empty_fd(fileno(f))) { + log_debug("Skipping empty file: %s", filename); + return 0; + } else + log_debug("Reading rules file: %s", filename); + + first_token = rules->token_cur; + filename_off = rules_add_string(rules, filename); + + while (fgets(line, sizeof(line), f) != NULL) { + char *key; + size_t len; + + /* skip whitespace */ + line_nr++; + key = line; + while (isspace(key[0])) + key++; + + /* comment */ + if (key[0] == '#') + continue; + + len = strlen(line); + if (len < 3) + continue; + + /* continue reading if backslash+newline is found */ + while (line[len-2] == '\\') { + if (fgets(&line[len-2], (sizeof(line)-len)+2, f) == NULL) + break; + if (strlen(&line[len-2]) < 2) + break; + line_nr++; + len = strlen(line); + } + + if (len+1 >= sizeof(line)) { + log_error("line too long '%s':%u, ignored", filename, line_nr); + continue; + } + add_rule(rules, key, filename, filename_off, line_nr); + } + + /* link GOTOs to LABEL rules in this file to be able to fast-forward */ + for (i = first_token+1; i < rules->token_cur; i++) { + if (rules->tokens[i].type == TK_A_GOTO) { + char *label = rules_str(rules, rules->tokens[i].key.value_off); + unsigned int j; + + for (j = i+1; j < rules->token_cur; j++) { + if (rules->tokens[j].type != TK_RULE) + continue; + if (rules->tokens[j].rule.label_off == 0) + continue; + if (!streq(label, rules_str(rules, rules->tokens[j].rule.label_off))) + continue; + rules->tokens[i].key.rule_goto = j; + break; + } + if (rules->tokens[i].key.rule_goto == 0) + log_error("GOTO '%s' has no matching label in: '%s'", label, filename); + } + } + return 0; +} + +struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names) { + struct udev_rules *rules; + struct udev_list file_list; + struct token end_token; + char **files, **f; + int r; + + rules = new0(struct udev_rules, 1); + if (rules == NULL) + return NULL; + rules->udev = udev; + rules->resolve_names = resolve_names; + udev_list_init(udev, &file_list, true); + + /* init token array and string buffer */ + rules->tokens = malloc(PREALLOC_TOKEN * sizeof(struct token)); + if (rules->tokens == NULL) + return udev_rules_unref(rules); + rules->token_max = PREALLOC_TOKEN; + + rules->strbuf = strbuf_new(); + if (!rules->strbuf) + return udev_rules_unref(rules); + + udev_rules_check_timestamp(rules); + + r = conf_files_list_strv(&files, ".rules", NULL, rules_dirs); + if (r < 0) { + log_error_errno(r, "failed to enumerate rules files: %m"); + return udev_rules_unref(rules); + } + + /* + * The offset value in the rules strct is limited; add all + * rules file names to the beginning of the string buffer. + */ + STRV_FOREACH(f, files) + rules_add_string(rules, *f); + + STRV_FOREACH(f, files) + parse_file(rules, *f); + + strv_free(files); + + memzero(&end_token, sizeof(struct token)); + end_token.type = TK_END; + add_token(rules, &end_token); + log_debug("rules contain %zu bytes tokens (%u * %zu bytes), %zu bytes strings", + rules->token_max * sizeof(struct token), rules->token_max, sizeof(struct token), rules->strbuf->len); + + /* cleanup temporary strbuf data */ + log_debug("%zu strings (%zu bytes), %zu de-duplicated (%zu bytes), %zu trie nodes used", + rules->strbuf->in_count, rules->strbuf->in_len, + rules->strbuf->dedup_count, rules->strbuf->dedup_len, rules->strbuf->nodes_count); + strbuf_complete(rules->strbuf); + + /* cleanup uid/gid cache */ + rules->uids = mfree(rules->uids); + rules->uids_cur = 0; + rules->uids_max = 0; + rules->gids = mfree(rules->gids); + rules->gids_cur = 0; + rules->gids_max = 0; + + dump_rules(rules); + return rules; +} + +struct udev_rules *udev_rules_unref(struct udev_rules *rules) { + if (rules == NULL) + return NULL; + free(rules->tokens); + strbuf_cleanup(rules->strbuf); + free(rules->uids); + free(rules->gids); + free(rules); + return NULL; +} + +bool udev_rules_check_timestamp(struct udev_rules *rules) { + if (!rules) + return false; + + return paths_check_timestamp(rules_dirs, &rules->dirs_ts_usec, true); +} + +static int match_key(struct udev_rules *rules, struct token *token, const char *val) { + char *key_value = rules_str(rules, token->key.value_off); + char *pos; + bool match = false; + + if (val == NULL) + val = ""; + + switch (token->key.glob) { + case GL_PLAIN: + match = (streq(key_value, val)); + break; + case GL_GLOB: + match = (fnmatch(key_value, val, 0) == 0); + break; + case GL_SPLIT: + { + const char *s; + size_t len; + + s = rules_str(rules, token->key.value_off); + len = strlen(val); + for (;;) { + const char *next; + + next = strchr(s, '|'); + if (next != NULL) { + size_t matchlen = (size_t)(next - s); + + match = (matchlen == len && strneq(s, val, matchlen)); + if (match) + break; + } else { + match = (streq(s, val)); + break; + } + s = &next[1]; + } + break; + } + case GL_SPLIT_GLOB: + { + char value[UTIL_PATH_SIZE]; + + strscpy(value, sizeof(value), rules_str(rules, token->key.value_off)); + key_value = value; + while (key_value != NULL) { + pos = strchr(key_value, '|'); + if (pos != NULL) { + pos[0] = '\0'; + pos = &pos[1]; + } + match = (fnmatch(key_value, val, 0) == 0); + if (match) + break; + key_value = pos; + } + break; + } + case GL_SOMETHING: + match = (val[0] != '\0'); + break; + case GL_UNSET: + return -1; + } + + if (match && (token->key.op == OP_MATCH)) + return 0; + if (!match && (token->key.op == OP_NOMATCH)) + return 0; + return -1; +} + +static int match_attr(struct udev_rules *rules, struct udev_device *dev, struct udev_event *event, struct token *cur) { + const char *name; + char nbuf[UTIL_NAME_SIZE]; + const char *value; + char vbuf[UTIL_NAME_SIZE]; + size_t len; + + name = rules_str(rules, cur->key.attr_off); + switch (cur->key.attrsubst) { + case SB_FORMAT: + udev_event_apply_format(event, name, nbuf, sizeof(nbuf)); + name = nbuf; + /* fall through */ + case SB_NONE: + value = udev_device_get_sysattr_value(dev, name); + if (value == NULL) + return -1; + break; + case SB_SUBSYS: + if (util_resolve_subsys_kernel(event->udev, name, vbuf, sizeof(vbuf), 1) != 0) + return -1; + value = vbuf; + break; + default: + return -1; + } + + /* remove trailing whitespace, if not asked to match for it */ + len = strlen(value); + if (len > 0 && isspace(value[len-1])) { + const char *key_value; + size_t klen; + + key_value = rules_str(rules, cur->key.value_off); + klen = strlen(key_value); + if (klen > 0 && !isspace(key_value[klen-1])) { + if (value != vbuf) { + strscpy(vbuf, sizeof(vbuf), value); + value = vbuf; + } + while (len > 0 && isspace(vbuf[--len])) + vbuf[len] = '\0'; + } + } + + return match_key(rules, cur, value); +} + +enum escape_type { + ESCAPE_UNSET, + ESCAPE_NONE, + ESCAPE_REPLACE, +}; + +void udev_rules_apply_to_event(struct udev_rules *rules, + struct udev_event *event, + usec_t timeout_usec, + usec_t timeout_warn_usec, + struct udev_list *properties_list) { + struct token *cur; + struct token *rule; + enum escape_type esc = ESCAPE_UNSET; + bool can_set_name; + + if (rules->tokens == NULL) + return; + + can_set_name = ((!streq(udev_device_get_action(event->dev), "remove")) && + (major(udev_device_get_devnum(event->dev)) > 0 || + udev_device_get_ifindex(event->dev) > 0)); + + /* loop through token list, match, run actions or forward to next rule */ + cur = &rules->tokens[0]; + rule = cur; + for (;;) { + dump_token(rules, cur); + switch (cur->type) { + case TK_RULE: + /* current rule */ + rule = cur; + /* possibly skip rules which want to set NAME, SYMLINK, OWNER, GROUP, MODE */ + if (!can_set_name && rule->rule.can_set_name) + goto nomatch; + esc = ESCAPE_UNSET; + break; + case TK_M_ACTION: + if (match_key(rules, cur, udev_device_get_action(event->dev)) != 0) + goto nomatch; + break; + case TK_M_DEVPATH: + if (match_key(rules, cur, udev_device_get_devpath(event->dev)) != 0) + goto nomatch; + break; + case TK_M_KERNEL: + if (match_key(rules, cur, udev_device_get_sysname(event->dev)) != 0) + goto nomatch; + break; + case TK_M_DEVLINK: { + struct udev_list_entry *list_entry; + bool match = false; + + udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(event->dev)) { + const char *devlink; + + devlink = udev_list_entry_get_name(list_entry) + strlen("/dev/"); + if (match_key(rules, cur, devlink) == 0) { + match = true; + break; + } + } + if (!match) + goto nomatch; + break; + } + case TK_M_NAME: + if (match_key(rules, cur, event->name) != 0) + goto nomatch; + break; + case TK_M_ENV: { + const char *key_name = rules_str(rules, cur->key.attr_off); + const char *value; + + value = udev_device_get_property_value(event->dev, key_name); + + /* check global properties */ + if (!value && properties_list) { + struct udev_list_entry *list_entry; + + list_entry = udev_list_get_entry(properties_list); + list_entry = udev_list_entry_get_by_name(list_entry, key_name); + if (list_entry != NULL) + value = udev_list_entry_get_value(list_entry); + } + + if (!value) + value = ""; + if (match_key(rules, cur, value)) + goto nomatch; + break; + } + case TK_M_TAG: { + struct udev_list_entry *list_entry; + bool match = false; + + udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(event->dev)) { + if (streq(rules_str(rules, cur->key.value_off), udev_list_entry_get_name(list_entry))) { + match = true; + break; + } + } + if ((!match && (cur->key.op != OP_NOMATCH)) || + (match && (cur->key.op == OP_NOMATCH))) + goto nomatch; + break; + } + case TK_M_SUBSYSTEM: + if (match_key(rules, cur, udev_device_get_subsystem(event->dev)) != 0) + goto nomatch; + break; + case TK_M_DRIVER: + if (match_key(rules, cur, udev_device_get_driver(event->dev)) != 0) + goto nomatch; + break; + case TK_M_ATTR: + if (match_attr(rules, event->dev, event, cur) != 0) + goto nomatch; + break; + case TK_M_SYSCTL: { + char filename[UTIL_PATH_SIZE]; + _cleanup_free_ char *value = NULL; + size_t len; + + udev_event_apply_format(event, rules_str(rules, cur->key.attr_off), filename, sizeof(filename)); + sysctl_normalize(filename); + if (sysctl_read(filename, &value) < 0) + goto nomatch; + + len = strlen(value); + while (len > 0 && isspace(value[--len])) + value[len] = '\0'; + if (match_key(rules, cur, value) != 0) + goto nomatch; + break; + } + case TK_M_KERNELS: + case TK_M_SUBSYSTEMS: + case TK_M_DRIVERS: + case TK_M_ATTRS: + case TK_M_TAGS: { + struct token *next; + + /* get whole sequence of parent matches */ + next = cur; + while (next->type > TK_M_PARENTS_MIN && next->type < TK_M_PARENTS_MAX) + next++; + + /* loop over parents */ + event->dev_parent = event->dev; + for (;;) { + struct token *key; + + /* loop over sequence of parent match keys */ + for (key = cur; key < next; key++ ) { + dump_token(rules, key); + switch(key->type) { + case TK_M_KERNELS: + if (match_key(rules, key, udev_device_get_sysname(event->dev_parent)) != 0) + goto try_parent; + break; + case TK_M_SUBSYSTEMS: + if (match_key(rules, key, udev_device_get_subsystem(event->dev_parent)) != 0) + goto try_parent; + break; + case TK_M_DRIVERS: + if (match_key(rules, key, udev_device_get_driver(event->dev_parent)) != 0) + goto try_parent; + break; + case TK_M_ATTRS: + if (match_attr(rules, event->dev_parent, event, key) != 0) + goto try_parent; + break; + case TK_M_TAGS: { + bool match = udev_device_has_tag(event->dev_parent, rules_str(rules, cur->key.value_off)); + + if (match && key->key.op == OP_NOMATCH) + goto try_parent; + if (!match && key->key.op == OP_MATCH) + goto try_parent; + break; + } + default: + goto nomatch; + } + } + break; + + try_parent: + event->dev_parent = udev_device_get_parent(event->dev_parent); + if (event->dev_parent == NULL) + goto nomatch; + } + /* move behind our sequence of parent match keys */ + cur = next; + continue; + } + case TK_M_TEST: { + char filename[UTIL_PATH_SIZE]; + struct stat statbuf; + int match; + + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), filename, sizeof(filename)); + if (util_resolve_subsys_kernel(event->udev, filename, filename, sizeof(filename), 0) != 0) { + if (filename[0] != '/') { + char tmp[UTIL_PATH_SIZE]; + + strscpy(tmp, sizeof(tmp), filename); + strscpyl(filename, sizeof(filename), + udev_device_get_syspath(event->dev), "/", tmp, NULL); + } + } + attr_subst_subdir(filename, sizeof(filename)); + + match = (stat(filename, &statbuf) == 0); + if (match && cur->key.mode > 0) + match = ((statbuf.st_mode & cur->key.mode) > 0); + if (match && cur->key.op == OP_NOMATCH) + goto nomatch; + if (!match && cur->key.op == OP_MATCH) + goto nomatch; + break; + } + case TK_M_PROGRAM: { + char program[UTIL_PATH_SIZE]; + char result[UTIL_LINE_SIZE]; + + event->program_result = mfree(event->program_result); + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), program, sizeof(program)); + log_debug("PROGRAM '%s' %s:%u", + program, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + + if (udev_event_spawn(event, timeout_usec, timeout_warn_usec, true, program, result, sizeof(result)) < 0) { + if (cur->key.op != OP_NOMATCH) + goto nomatch; + } else { + int count; + + util_remove_trailing_chars(result, '\n'); + if (esc == ESCAPE_UNSET || esc == ESCAPE_REPLACE) { + count = util_replace_chars(result, UDEV_ALLOWED_CHARS_INPUT); + if (count > 0) + log_debug("%i character(s) replaced" , count); + } + event->program_result = strdup(result); + if (cur->key.op == OP_NOMATCH) + goto nomatch; + } + break; + } + case TK_M_IMPORT_FILE: { + char import[UTIL_PATH_SIZE]; + + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), import, sizeof(import)); + if (import_file_into_properties(event->dev, import) != 0) + if (cur->key.op != OP_NOMATCH) + goto nomatch; + break; + } + case TK_M_IMPORT_PROG: { + char import[UTIL_PATH_SIZE]; + + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), import, sizeof(import)); + log_debug("IMPORT '%s' %s:%u", + import, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + + if (import_program_into_properties(event, timeout_usec, timeout_warn_usec, import) != 0) + if (cur->key.op != OP_NOMATCH) + goto nomatch; + break; + } + case TK_M_IMPORT_BUILTIN: { + char command[UTIL_PATH_SIZE]; + + if (udev_builtin_run_once(cur->key.builtin_cmd)) { + /* check if we ran already */ + if (event->builtin_run & (1 << cur->key.builtin_cmd)) { + log_debug("IMPORT builtin skip '%s' %s:%u", + udev_builtin_name(cur->key.builtin_cmd), + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + /* return the result from earlier run */ + if (event->builtin_ret & (1 << cur->key.builtin_cmd)) + if (cur->key.op != OP_NOMATCH) + goto nomatch; + break; + } + /* mark as ran */ + event->builtin_run |= (1 << cur->key.builtin_cmd); + } + + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), command, sizeof(command)); + log_debug("IMPORT builtin '%s' %s:%u", + udev_builtin_name(cur->key.builtin_cmd), + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + + if (udev_builtin_run(event->dev, cur->key.builtin_cmd, command, false) != 0) { + /* remember failure */ + log_debug("IMPORT builtin '%s' returned non-zero", + udev_builtin_name(cur->key.builtin_cmd)); + event->builtin_ret |= (1 << cur->key.builtin_cmd); + if (cur->key.op != OP_NOMATCH) + goto nomatch; + } + break; + } + case TK_M_IMPORT_DB: { + const char *key = rules_str(rules, cur->key.value_off); + const char *value; + + value = udev_device_get_property_value(event->dev_db, key); + if (value != NULL) + udev_device_add_property(event->dev, key, value); + else { + if (cur->key.op != OP_NOMATCH) + goto nomatch; + } + break; + } + case TK_M_IMPORT_CMDLINE: { + _cleanup_fclose_ FILE *f = NULL; + bool imported = false; + + f = fopen("/proc/cmdline", "re"); + if (f != NULL) { + char cmdline[4096]; + + if (fgets(cmdline, sizeof(cmdline), f) != NULL) { + const char *key = rules_str(rules, cur->key.value_off); + char *pos; + + pos = strstr(cmdline, key); + if (pos != NULL) { + imported = true; + pos += strlen(key); + if (pos[0] == '\0' || isspace(pos[0])) + /* we import simple flags as 'FLAG=1' */ + udev_device_add_property(event->dev, key, "1"); + else if (pos[0] == '=') { + const char *value; + + pos++; + value = pos; + while (pos[0] != '\0' && !isspace(pos[0])) + pos++; + pos[0] = '\0'; + udev_device_add_property(event->dev, key, value); + } + } + } + } + if (!imported && cur->key.op != OP_NOMATCH) + goto nomatch; + break; + } + case TK_M_IMPORT_PARENT: { + char import[UTIL_PATH_SIZE]; + + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), import, sizeof(import)); + if (import_parent_into_properties(event->dev, import) != 0) + if (cur->key.op != OP_NOMATCH) + goto nomatch; + break; + } + case TK_M_RESULT: + if (match_key(rules, cur, event->program_result) != 0) + goto nomatch; + break; + case TK_A_STRING_ESCAPE_NONE: + esc = ESCAPE_NONE; + break; + case TK_A_STRING_ESCAPE_REPLACE: + esc = ESCAPE_REPLACE; + break; + case TK_A_DB_PERSIST: + udev_device_set_db_persist(event->dev); + break; + case TK_A_INOTIFY_WATCH: + if (event->inotify_watch_final) + break; + if (cur->key.op == OP_ASSIGN_FINAL) + event->inotify_watch_final = true; + event->inotify_watch = cur->key.watch; + break; + case TK_A_DEVLINK_PRIO: + udev_device_set_devlink_priority(event->dev, cur->key.devlink_prio); + break; + case TK_A_OWNER: { + char owner[UTIL_NAME_SIZE]; + const char *ow = owner; + int r; + + if (event->owner_final) + break; + if (cur->key.op == OP_ASSIGN_FINAL) + event->owner_final = true; + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), owner, sizeof(owner)); + event->owner_set = true; + r = get_user_creds(&ow, &event->uid, NULL, NULL, NULL); + if (r < 0) { + if (r == -ENOENT || r == -ESRCH) + log_error("specified user '%s' unknown", owner); + else + log_error_errno(r, "error resolving user '%s': %m", owner); + + event->uid = 0; + } + log_debug("OWNER %u %s:%u", + event->uid, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + break; + } + case TK_A_GROUP: { + char group[UTIL_NAME_SIZE]; + const char *gr = group; + int r; + + if (event->group_final) + break; + if (cur->key.op == OP_ASSIGN_FINAL) + event->group_final = true; + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), group, sizeof(group)); + event->group_set = true; + r = get_group_creds(&gr, &event->gid); + if (r < 0) { + if (r == -ENOENT || r == -ESRCH) + log_error("specified group '%s' unknown", group); + else + log_error_errno(r, "error resolving group '%s': %m", group); + + event->gid = 0; + } + log_debug("GROUP %u %s:%u", + event->gid, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + break; + } + case TK_A_MODE: { + char mode_str[UTIL_NAME_SIZE]; + mode_t mode; + char *endptr; + + if (event->mode_final) + break; + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), mode_str, sizeof(mode_str)); + mode = strtol(mode_str, &endptr, 8); + if (endptr[0] != '\0') { + log_error("ignoring invalid mode '%s'", mode_str); + break; + } + if (cur->key.op == OP_ASSIGN_FINAL) + event->mode_final = true; + event->mode_set = true; + event->mode = mode; + log_debug("MODE %#o %s:%u", + event->mode, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + break; + } + case TK_A_OWNER_ID: + if (event->owner_final) + break; + if (cur->key.op == OP_ASSIGN_FINAL) + event->owner_final = true; + event->owner_set = true; + event->uid = cur->key.uid; + log_debug("OWNER %u %s:%u", + event->uid, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + break; + case TK_A_GROUP_ID: + if (event->group_final) + break; + if (cur->key.op == OP_ASSIGN_FINAL) + event->group_final = true; + event->group_set = true; + event->gid = cur->key.gid; + log_debug("GROUP %u %s:%u", + event->gid, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + break; + case TK_A_MODE_ID: + if (event->mode_final) + break; + if (cur->key.op == OP_ASSIGN_FINAL) + event->mode_final = true; + event->mode_set = true; + event->mode = cur->key.mode; + log_debug("MODE %#o %s:%u", + event->mode, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + break; + case TK_A_SECLABEL: { + const char *name, *label; + + name = rules_str(rules, cur->key.attr_off); + label = rules_str(rules, cur->key.value_off); + if (cur->key.op == OP_ASSIGN || cur->key.op == OP_ASSIGN_FINAL) + udev_list_cleanup(&event->seclabel_list); + udev_list_entry_add(&event->seclabel_list, name, label); + log_debug("SECLABEL{%s}='%s' %s:%u", + name, label, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + break; + } + case TK_A_ENV: { + const char *name = rules_str(rules, cur->key.attr_off); + char *value = rules_str(rules, cur->key.value_off); + char value_new[UTIL_NAME_SIZE]; + const char *value_old = NULL; + + if (value[0] == '\0') { + if (cur->key.op == OP_ADD) + break; + udev_device_add_property(event->dev, name, NULL); + break; + } + + if (cur->key.op == OP_ADD) + value_old = udev_device_get_property_value(event->dev, name); + if (value_old) { + char temp[UTIL_NAME_SIZE]; + + /* append value separated by space */ + udev_event_apply_format(event, value, temp, sizeof(temp)); + strscpyl(value_new, sizeof(value_new), value_old, " ", temp, NULL); + } else + udev_event_apply_format(event, value, value_new, sizeof(value_new)); + + udev_device_add_property(event->dev, name, value_new); + break; + } + case TK_A_TAG: { + char tag[UTIL_PATH_SIZE]; + const char *p; + + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), tag, sizeof(tag)); + if (cur->key.op == OP_ASSIGN || cur->key.op == OP_ASSIGN_FINAL) + udev_device_cleanup_tags_list(event->dev); + for (p = tag; *p != '\0'; p++) { + if ((*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z') || + (*p >= '0' && *p <= '9') || + *p == '-' || *p == '_') + continue; + log_error("ignoring invalid tag name '%s'", tag); + break; + } + if (cur->key.op == OP_REMOVE) + udev_device_remove_tag(event->dev, tag); + else + udev_device_add_tag(event->dev, tag); + break; + } + case TK_A_NAME: { + const char *name = rules_str(rules, cur->key.value_off); + + char name_str[UTIL_PATH_SIZE]; + int count; + + if (event->name_final) + break; + if (cur->key.op == OP_ASSIGN_FINAL) + event->name_final = true; + udev_event_apply_format(event, name, name_str, sizeof(name_str)); + if (esc == ESCAPE_UNSET || esc == ESCAPE_REPLACE) { + count = util_replace_chars(name_str, "/"); + if (count > 0) + log_debug("%i character(s) replaced", count); + } + if (major(udev_device_get_devnum(event->dev)) && + !streq(name_str, udev_device_get_devnode(event->dev) + strlen("/dev/"))) { + log_error("NAME=\"%s\" ignored, kernel device nodes cannot be renamed; please fix it in %s:%u\n", + name, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + break; + } + if (free_and_strdup(&event->name, name_str) < 0) { + log_oom(); + return; + } + log_debug("NAME '%s' %s:%u", + event->name, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + break; + } + case TK_A_DEVLINK: { + char temp[UTIL_PATH_SIZE]; + char filename[UTIL_PATH_SIZE]; + char *pos, *next; + int count = 0; + + if (event->devlink_final) + break; + if (major(udev_device_get_devnum(event->dev)) == 0) + break; + if (cur->key.op == OP_ASSIGN_FINAL) + event->devlink_final = true; + if (cur->key.op == OP_ASSIGN || cur->key.op == OP_ASSIGN_FINAL) + udev_device_cleanup_devlinks_list(event->dev); + + /* allow multiple symlinks separated by spaces */ + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), temp, sizeof(temp)); + if (esc == ESCAPE_UNSET) + count = util_replace_chars(temp, "/ "); + else if (esc == ESCAPE_REPLACE) + count = util_replace_chars(temp, "/"); + if (count > 0) + log_debug("%i character(s) replaced" , count); + pos = temp; + while (isspace(pos[0])) + pos++; + next = strchr(pos, ' '); + while (next != NULL) { + next[0] = '\0'; + log_debug("LINK '%s' %s:%u", pos, + rules_str(rules, rule->rule.filename_off), rule->rule.filename_line); + strscpyl(filename, sizeof(filename), "/dev/", pos, NULL); + udev_device_add_devlink(event->dev, filename); + while (isspace(next[1])) + next++; + pos = &next[1]; + next = strchr(pos, ' '); + } + if (pos[0] != '\0') { + log_debug("LINK '%s' %s:%u", pos, + rules_str(rules, rule->rule.filename_off), rule->rule.filename_line); + strscpyl(filename, sizeof(filename), "/dev/", pos, NULL); + udev_device_add_devlink(event->dev, filename); + } + break; + } + case TK_A_ATTR: { + const char *key_name = rules_str(rules, cur->key.attr_off); + char attr[UTIL_PATH_SIZE]; + char value[UTIL_NAME_SIZE]; + _cleanup_fclose_ FILE *f = NULL; + + if (util_resolve_subsys_kernel(event->udev, key_name, attr, sizeof(attr), 0) != 0) + strscpyl(attr, sizeof(attr), udev_device_get_syspath(event->dev), "/", key_name, NULL); + attr_subst_subdir(attr, sizeof(attr)); + + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), value, sizeof(value)); + log_debug("ATTR '%s' writing '%s' %s:%u", attr, value, + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + f = fopen(attr, "we"); + if (f == NULL) + log_error_errno(errno, "error opening ATTR{%s} for writing: %m", attr); + else if (fprintf(f, "%s", value) <= 0) + log_error_errno(errno, "error writing ATTR{%s}: %m", attr); + break; + } + case TK_A_SYSCTL: { + char filename[UTIL_PATH_SIZE]; + char value[UTIL_NAME_SIZE]; + int r; + + udev_event_apply_format(event, rules_str(rules, cur->key.attr_off), filename, sizeof(filename)); + sysctl_normalize(filename); + udev_event_apply_format(event, rules_str(rules, cur->key.value_off), value, sizeof(value)); + log_debug("SYSCTL '%s' writing '%s' %s:%u", filename, value, + rules_str(rules, rule->rule.filename_off), rule->rule.filename_line); + r = sysctl_write(filename, value); + if (r < 0) + log_error_errno(r, "error writing SYSCTL{%s}='%s': %m", filename, value); + break; + } + case TK_A_RUN_BUILTIN: + case TK_A_RUN_PROGRAM: { + struct udev_list_entry *entry; + + if (cur->key.op == OP_ASSIGN || cur->key.op == OP_ASSIGN_FINAL) + udev_list_cleanup(&event->run_list); + log_debug("RUN '%s' %s:%u", + rules_str(rules, cur->key.value_off), + rules_str(rules, rule->rule.filename_off), + rule->rule.filename_line); + entry = udev_list_entry_add(&event->run_list, rules_str(rules, cur->key.value_off), NULL); + udev_list_entry_set_num(entry, cur->key.builtin_cmd); + break; + } + case TK_A_GOTO: + if (cur->key.rule_goto == 0) + break; + cur = &rules->tokens[cur->key.rule_goto]; + continue; + case TK_END: + return; + + case TK_M_PARENTS_MIN: + case TK_M_PARENTS_MAX: + case TK_M_MAX: + case TK_UNSET: + log_error("wrong type %u", cur->type); + goto nomatch; + } + + cur++; + continue; + nomatch: + /* fast-forward to next rule */ + cur = rule + rule->rule.token_count; + } +} + +int udev_rules_apply_static_dev_perms(struct udev_rules *rules) { + struct token *cur; + struct token *rule; + uid_t uid = 0; + gid_t gid = 0; + mode_t mode = 0; + _cleanup_strv_free_ char **tags = NULL; + char **t; + FILE *f = NULL; + _cleanup_free_ char *path = NULL; + int r; + + if (rules->tokens == NULL) + return 0; + + cur = &rules->tokens[0]; + rule = cur; + for (;;) { + switch (cur->type) { + case TK_RULE: + /* current rule */ + rule = cur; + + /* skip rules without a static_node tag */ + if (!rule->rule.has_static_node) + goto next; + + uid = 0; + gid = 0; + mode = 0; + tags = strv_free(tags); + break; + case TK_A_OWNER_ID: + uid = cur->key.uid; + break; + case TK_A_GROUP_ID: + gid = cur->key.gid; + break; + case TK_A_MODE_ID: + mode = cur->key.mode; + break; + case TK_A_TAG: + r = strv_extend(&tags, rules_str(rules, cur->key.value_off)); + if (r < 0) + goto finish; + + break; + case TK_A_STATIC_NODE: { + char device_node[UTIL_PATH_SIZE]; + char tags_dir[UTIL_PATH_SIZE]; + char tag_symlink[UTIL_PATH_SIZE]; + struct stat stats; + + /* we assure, that the permissions tokens are sorted before the static token */ + + if (mode == 0 && uid == 0 && gid == 0 && tags == NULL) + goto next; + + strscpyl(device_node, sizeof(device_node), "/dev/", rules_str(rules, cur->key.value_off), NULL); + if (stat(device_node, &stats) != 0) + break; + if (!S_ISBLK(stats.st_mode) && !S_ISCHR(stats.st_mode)) + break; + + /* export the tags to a directory as symlinks, allowing otherwise dead nodes to be tagged */ + if (tags) { + STRV_FOREACH(t, tags) { + _cleanup_free_ char *unescaped_filename = NULL; + + strscpyl(tags_dir, sizeof(tags_dir), "/run/udev/static_node-tags/", *t, "/", NULL); + r = mkdir_p(tags_dir, 0755); + if (r < 0) + return log_error_errno(r, "failed to create %s: %m", tags_dir); + + unescaped_filename = xescape(rules_str(rules, cur->key.value_off), "/."); + + strscpyl(tag_symlink, sizeof(tag_symlink), tags_dir, unescaped_filename, NULL); + r = symlink(device_node, tag_symlink); + if (r < 0 && errno != EEXIST) + return log_error_errno(errno, "failed to create symlink %s -> %s: %m", + tag_symlink, device_node); + } + } + + /* don't touch the permissions if only the tags were set */ + if (mode == 0 && uid == 0 && gid == 0) + break; + + if (mode == 0) { + if (gid > 0) + mode = 0660; + else + mode = 0600; + } + if (mode != (stats.st_mode & 01777)) { + r = chmod(device_node, mode); + if (r < 0) { + log_error("failed to chmod '%s' %#o", device_node, mode); + return -errno; + } else + log_debug("chmod '%s' %#o", device_node, mode); + } + + if ((uid != 0 && uid != stats.st_uid) || (gid != 0 && gid != stats.st_gid)) { + r = chown(device_node, uid, gid); + if (r < 0) { + log_error("failed to chown '%s' %u %u ", device_node, uid, gid); + return -errno; + } else + log_debug("chown '%s' %u %u", device_node, uid, gid); + } + + utimensat(AT_FDCWD, device_node, NULL, 0); + break; + } + case TK_END: + goto finish; + } + + cur++; + continue; +next: + /* fast-forward to next rule */ + cur = rule + rule->rule.token_count; + continue; + } + +finish: + if (f) { + fflush(f); + fchmod(fileno(f), 0644); + if (ferror(f) || rename(path, "/run/udev/static_node-tags") < 0) { + unlink_noerrno("/run/udev/static_node-tags"); + unlink_noerrno(path); + return -errno; + } + } + + return 0; +} diff --git a/src/grp-udev/libudev-core/udev-watch.c b/src/grp-udev/libudev-core/udev-watch.c new file mode 100644 index 0000000000..9ce5e975de --- /dev/null +++ b/src/grp-udev/libudev-core/udev-watch.c @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2004-2012 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2009 Canonical Ltd. + * Copyright (C) 2009 Scott James Remnant <scott@netsplit.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <dirent.h> +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <sys/inotify.h> +#include <unistd.h> + +#include "stdio-util.h" +#include "udev.h" + +static int inotify_fd = -1; + +/* inotify descriptor, will be shared with rules directory; + * set to cloexec since we need our children to be able to add + * watches for us + */ +int udev_watch_init(struct udev *udev) { + inotify_fd = inotify_init1(IN_CLOEXEC); + if (inotify_fd < 0) + log_error_errno(errno, "inotify_init failed: %m"); + return inotify_fd; +} + +/* move any old watches directory out of the way, and then restore + * the watches + */ +void udev_watch_restore(struct udev *udev) { + if (inotify_fd < 0) + return; + + if (rename("/run/udev/watch", "/run/udev/watch.old") == 0) { + DIR *dir; + struct dirent *ent; + + dir = opendir("/run/udev/watch.old"); + if (dir == NULL) { + log_error_errno(errno, "unable to open old watches dir /run/udev/watch.old; old watches will not be restored: %m"); + return; + } + + for (ent = readdir(dir); ent != NULL; ent = readdir(dir)) { + char device[UTIL_PATH_SIZE]; + ssize_t len; + struct udev_device *dev; + + if (ent->d_name[0] == '.') + continue; + + len = readlinkat(dirfd(dir), ent->d_name, device, sizeof(device)); + if (len <= 0 || len == (ssize_t)sizeof(device)) + goto unlink; + device[len] = '\0'; + + dev = udev_device_new_from_device_id(udev, device); + if (dev == NULL) + goto unlink; + + log_debug("restoring old watch on '%s'", udev_device_get_devnode(dev)); + udev_watch_begin(udev, dev); + udev_device_unref(dev); +unlink: + unlinkat(dirfd(dir), ent->d_name, 0); + } + + closedir(dir); + rmdir("/run/udev/watch.old"); + + } else if (errno != ENOENT) + log_error_errno(errno, "unable to move watches dir /run/udev/watch; old watches will not be restored: %m"); +} + +void udev_watch_begin(struct udev *udev, struct udev_device *dev) { + char filename[UTIL_PATH_SIZE]; + int wd; + int r; + + if (inotify_fd < 0) + return; + + log_debug("adding watch on '%s'", udev_device_get_devnode(dev)); + wd = inotify_add_watch(inotify_fd, udev_device_get_devnode(dev), IN_CLOSE_WRITE); + if (wd < 0) { + log_error_errno(errno, "inotify_add_watch(%d, %s, %o) failed: %m", + inotify_fd, udev_device_get_devnode(dev), IN_CLOSE_WRITE); + return; + } + + xsprintf(filename, "/run/udev/watch/%d", wd); + mkdir_parents(filename, 0755); + unlink(filename); + r = symlink(udev_device_get_id_filename(dev), filename); + if (r < 0) + log_error_errno(errno, "Failed to create symlink %s: %m", filename); + + udev_device_set_watch_handle(dev, wd); +} + +void udev_watch_end(struct udev *udev, struct udev_device *dev) { + int wd; + char filename[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'", udev_device_get_devnode(dev)); + inotify_rm_watch(inotify_fd, wd); + + xsprintf(filename, "/run/udev/watch/%d", wd); + unlink(filename); + + udev_device_set_watch_handle(dev, -1); +} + +struct udev_device *udev_watch_lookup(struct udev *udev, int wd) { + char filename[UTIL_PATH_SIZE]; + char device[UTIL_NAME_SIZE]; + ssize_t len; + + if (inotify_fd < 0 || wd < 0) + return NULL; + + xsprintf(filename, "/run/udev/watch/%d", wd); + len = readlink(filename, device, sizeof(device)); + if (len <= 0 || (size_t)len == sizeof(device)) + return NULL; + device[len] = '\0'; + + return udev_device_new_from_device_id(udev, device); +} diff --git a/src/grp-udev/libudev-core/udev.conf b/src/grp-udev/libudev-core/udev.conf new file mode 100644 index 0000000000..47d1433002 --- /dev/null +++ b/src/grp-udev/libudev-core/udev.conf @@ -0,0 +1,3 @@ +# see udev.conf(5) for details + +#udev_log="info" diff --git a/src/grp-udev/libudev-core/udev.pc.in b/src/grp-udev/libudev-core/udev.pc.in new file mode 100644 index 0000000000..a0c2e82d47 --- /dev/null +++ b/src/grp-udev/libudev-core/udev.pc.in @@ -0,0 +1,5 @@ +Name: udev +Description: udev +Version: @VERSION@ + +udevdir=@udevlibexecdir@ diff --git a/src/grp-udev/mtd_probe/Makefile b/src/grp-udev/mtd_probe/Makefile new file mode 100644 index 0000000000..d7392a8a3b --- /dev/null +++ b/src/grp-udev/mtd_probe/Makefile @@ -0,0 +1,37 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +mtd_probe_SOURCES = \ + src/udev/mtd_probe/mtd_probe.c \ + src/udev/mtd_probe/mtd_probe.h \ + src/udev/mtd_probe/probe_smartmedia.c + +dist_udevrules_DATA += \ + rules/75-probe_mtd.rules + +udevlibexec_PROGRAMS += \ + mtd_probe + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-udev/mtd_probe/mtd_probe.c b/src/grp-udev/mtd_probe/mtd_probe.c new file mode 100644 index 0000000000..462fab7623 --- /dev/null +++ b/src/grp-udev/mtd_probe/mtd_probe.c @@ -0,0 +1,56 @@ +/* + * 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 <fcntl.h> +#include <mtd/mtd-user.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "mtd_probe.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|O_CLOEXEC); + 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/grp-udev/mtd_probe/mtd_probe.h b/src/grp-udev/mtd_probe/mtd_probe.h new file mode 100644 index 0000000000..68e4954537 --- /dev/null +++ b/src/grp-udev/mtd_probe/mtd_probe.h @@ -0,0 +1,51 @@ +#pragma once + +/* + * 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> + +#include "macro.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]; +} _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/grp-udev/mtd_probe/probe_smartmedia.c b/src/grp-udev/mtd_probe/probe_smartmedia.c new file mode 100644 index 0000000000..2a7ba17637 --- /dev/null +++ b/src/grp-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 <fcntl.h> +#include <mtd/mtd-user.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.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/grp-udev/scsi_id/.gitignore b/src/grp-udev/scsi_id/.gitignore new file mode 100644 index 0000000000..6aebddd809 --- /dev/null +++ b/src/grp-udev/scsi_id/.gitignore @@ -0,0 +1 @@ +scsi_id_version.h diff --git a/src/grp-udev/scsi_id/Makefile b/src/grp-udev/scsi_id/Makefile new file mode 100644 index 0000000000..7064a864f7 --- /dev/null +++ b/src/grp-udev/scsi_id/Makefile @@ -0,0 +1,41 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +scsi_id_SOURCES =\ + src/udev/scsi_id/scsi_id.c \ + src/udev/scsi_id/scsi_serial.c \ + src/udev/scsi_id/scsi.h \ + src/udev/scsi_id/scsi_id.h + +scsi_id_LDADD = \ + libshared.la + +udevlibexec_PROGRAMS += \ + scsi_id + +EXTRA_DIST += \ + src/udev/scsi_id/README + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-udev/scsi_id/README b/src/grp-udev/scsi_id/README new file mode 100644 index 0000000000..9cfe73991c --- /dev/null +++ b/src/grp-udev/scsi_id/README @@ -0,0 +1,4 @@ +scsi_id - generate a SCSI unique identifier for a given SCSI device + +Please send questions, comments or patches to <patmans@us.ibm.com> or +<linux-hotplug-devel@lists.sourceforge.net>. diff --git a/src/grp-udev/scsi_id/scsi.h b/src/grp-udev/scsi_id/scsi.h new file mode 100644 index 0000000000..a27a84a40a --- /dev/null +++ b/src/grp-udev/scsi_id/scsi.h @@ -0,0 +1,99 @@ +#pragma once + +/* + * scsi.h + * + * General scsi and linux scsi specific defines and structs. + * + * Copyright (C) IBM Corp. 2003 + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 of the License. + */ + +#include <scsi/scsi.h> + +struct scsi_ioctl_command { + unsigned int inlen; /* excluding scsi command length */ + unsigned int outlen; + unsigned char data[1]; + /* on input, scsi command starts here then opt. data */ +}; + +/* + * Default 5 second timeout + */ +#define DEF_TIMEOUT 5000 + +#define SENSE_BUFF_LEN 32 + +/* + * The request buffer size passed to the SCSI INQUIRY commands, use 254, + * as this is a nice value for some devices, especially some of the usb + * mass storage devices. + */ +#define SCSI_INQ_BUFF_LEN 254 + +/* + * SCSI INQUIRY vendor and model (really product) lengths. + */ +#define VENDOR_LENGTH 8 +#define MODEL_LENGTH 16 + +#define INQUIRY_CMD 0x12 +#define INQUIRY_CMDLEN 6 + +/* + * INQUIRY VPD page 0x83 identifier descriptor related values. Reference the + * SCSI Primary Commands specification for details. + */ + +/* + * id type values of id descriptors. These are assumed to fit in 4 bits. + */ +#define SCSI_ID_VENDOR_SPECIFIC 0 +#define SCSI_ID_T10_VENDOR 1 +#define SCSI_ID_EUI_64 2 +#define SCSI_ID_NAA 3 +#define SCSI_ID_RELPORT 4 +#define SCSI_ID_TGTGROUP 5 +#define SCSI_ID_LUNGROUP 6 +#define SCSI_ID_MD5 7 +#define SCSI_ID_NAME 8 + +/* + * Supported NAA values. These fit in 4 bits, so the "don't care" value + * cannot conflict with real values. + */ +#define SCSI_ID_NAA_DONT_CARE 0xff +#define SCSI_ID_NAA_IEEE_REG 0x05 +#define SCSI_ID_NAA_IEEE_REG_EXTENDED 0x06 + +/* + * Supported Code Set values. + */ +#define SCSI_ID_BINARY 1 +#define SCSI_ID_ASCII 2 + +struct scsi_id_search_values { + u_char id_type; + u_char naa_type; + u_char code_set; +}; + +/* + * Following are the "true" SCSI status codes. Linux has traditionally + * used a 1 bit right and masked version of these. So now CHECK_CONDITION + * and friends (in <scsi/scsi.h>) are deprecated. + */ +#define SCSI_CHECK_CONDITION 0x02 +#define SCSI_CONDITION_MET 0x04 +#define SCSI_BUSY 0x08 +#define SCSI_IMMEDIATE 0x10 +#define SCSI_IMMEDIATE_CONDITION_MET 0x14 +#define SCSI_RESERVATION_CONFLICT 0x18 +#define SCSI_COMMAND_TERMINATED 0x22 +#define SCSI_TASK_SET_FULL 0x28 +#define SCSI_ACA_ACTIVE 0x30 +#define SCSI_TASK_ABORTED 0x40 diff --git a/src/grp-udev/scsi_id/scsi_id.c b/src/grp-udev/scsi_id/scsi_id.c new file mode 100644 index 0000000000..4655691642 --- /dev/null +++ b/src/grp-udev/scsi_id/scsi_id.c @@ -0,0 +1,625 @@ +/* + * Copyright (C) IBM Corp. 2003 + * Copyright (C) SUSE Linux Products GmbH, 2006 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <signal.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "libudev.h" + +#include "fd-util.h" +#include "libudev-private.h" +#include "scsi_id.h" +#include "string-util.h" +#include "udev-util.h" + +static const struct option options[] = { + { "device", required_argument, NULL, 'd' }, + { "config", required_argument, NULL, 'f' }, + { "page", required_argument, NULL, 'p' }, + { "blacklisted", no_argument, NULL, 'b' }, + { "whitelisted", no_argument, NULL, 'g' }, + { "replace-whitespace", no_argument, NULL, 'u' }, + { "sg-version", required_argument, NULL, 's' }, + { "verbose", no_argument, NULL, 'v' }, + { "version", no_argument, NULL, 'V' }, /* don't advertise -V */ + { "export", no_argument, NULL, 'x' }, + { "help", no_argument, NULL, 'h' }, + {} +}; + +static bool all_good = false; +static bool dev_specified = false; +static char config_file[MAX_PATH_LEN] = "/etc/scsi_id.config"; +static enum page_code default_page_code = PAGE_UNSPECIFIED; +static int sg_version = 4; +static bool reformat_serial = false; +static bool export = false; +static char vendor_str[64]; +static char model_str[64]; +static char vendor_enc_str[256]; +static char model_enc_str[256]; +static char revision_str[16]; +static char type_str[16]; + +static void set_type(const char *from, char *to, size_t len) +{ + int type_num; + char *eptr; + const char *type = "generic"; + + type_num = strtoul(from, &eptr, 0); + if (eptr != from) { + switch (type_num) { + case 0: + type = "disk"; + break; + case 1: + type = "tape"; + break; + case 4: + type = "optical"; + break; + case 5: + type = "cd"; + break; + case 7: + type = "optical"; + break; + case 0xe: + type = "disk"; + break; + case 0xf: + type = "optical"; + break; + default: + break; + } + } + strscpy(to, len, type); +} + +/* + * get_value: + * + * buf points to an '=' followed by a quoted string ("foo") or a string ending + * with a space or ','. + * + * Return a pointer to the NUL terminated string, returns NULL if no + * matches. + */ +static char *get_value(char **buffer) +{ + static const char *quote_string = "\"\n"; + static const char *comma_string = ",\n"; + char *val; + const char *end; + + if (**buffer == '"') { + /* + * skip leading quote, terminate when quote seen + */ + (*buffer)++; + end = quote_string; + } else { + end = comma_string; + } + val = strsep(buffer, end); + if (val && end == quote_string) + /* + * skip trailing quote + */ + (*buffer)++; + + while (isspace(**buffer)) + (*buffer)++; + + return val; +} + +static int argc_count(char *opts) +{ + int i = 0; + while (*opts != '\0') + if (*opts++ == ' ') + i++; + return i; +} + +/* + * get_file_options: + * + * If vendor == NULL, find a line in the config file with only "OPTIONS="; + * if vendor and model are set find the first OPTIONS line in the config + * file that matches. Set argc and argv to match the OPTIONS string. + * + * vendor and model can end in '\n'. + */ +static int get_file_options(struct udev *udev, + const char *vendor, const char *model, + int *argc, char ***newargv) +{ + char *buffer; + _cleanup_fclose_ FILE *f; + char *buf; + char *str1; + char *vendor_in, *model_in, *options_in; /* read in from file */ + int lineno; + int c; + int retval = 0; + + f = fopen(config_file, "re"); + if (f == NULL) { + if (errno == ENOENT) + return 1; + else { + log_error_errno(errno, "can't open %s: %m", config_file); + return -1; + } + } + + /* + * Allocate a buffer rather than put it on the stack so we can + * keep it around to parse any options (any allocated newargv + * points into this buffer for its strings). + */ + buffer = malloc(MAX_BUFFER_LEN); + if (!buffer) + return log_oom(); + + *newargv = NULL; + lineno = 0; + for (;;) { + vendor_in = model_in = options_in = NULL; + + buf = fgets(buffer, MAX_BUFFER_LEN, f); + if (buf == NULL) + break; + lineno++; + if (buf[strlen(buffer) - 1] != '\n') { + log_error("Config file line %d too long", lineno); + break; + } + + while (isspace(*buf)) + buf++; + + /* blank or all whitespace line */ + if (*buf == '\0') + continue; + + /* comment line */ + if (*buf == '#') + continue; + + str1 = strsep(&buf, "="); + if (str1 && strcaseeq(str1, "VENDOR")) { + str1 = get_value(&buf); + if (!str1) { + retval = log_oom(); + break; + } + vendor_in = str1; + + str1 = strsep(&buf, "="); + if (str1 && strcaseeq(str1, "MODEL")) { + str1 = get_value(&buf); + if (!str1) { + retval = log_oom(); + break; + } + model_in = str1; + str1 = strsep(&buf, "="); + } + } + + if (str1 && strcaseeq(str1, "OPTIONS")) { + str1 = get_value(&buf); + if (!str1) { + retval = log_oom(); + break; + } + options_in = str1; + } + + /* + * Only allow: [vendor=foo[,model=bar]]options=stuff + */ + if (!options_in || (!vendor_in && model_in)) { + log_error("Error parsing config file line %d '%s'", lineno, buffer); + retval = -1; + break; + } + if (vendor == NULL) { + if (vendor_in == NULL) + break; + } else if (vendor_in && + strneq(vendor, vendor_in, strlen(vendor_in)) && + (!model_in || + (strneq(model, model_in, strlen(model_in))))) { + /* + * Matched vendor and optionally model. + * + * Note: a short vendor_in or model_in can + * give a partial match (that is FOO + * matches FOOBAR). + */ + break; + } + } + + if (retval == 0) { + if (vendor_in != NULL || model_in != NULL || + options_in != NULL) { + /* + * Something matched. Allocate newargv, and store + * values found in options_in. + */ + strcpy(buffer, options_in); + c = argc_count(buffer) + 2; + *newargv = calloc(c, sizeof(**newargv)); + if (!*newargv) + retval = log_oom(); + else { + *argc = c; + c = 0; + /* + * argv[0] at 0 is skipped by getopt, but + * store the buffer address there for + * later freeing + */ + (*newargv)[c] = buffer; + for (c = 1; c < *argc; c++) + (*newargv)[c] = strsep(&buffer, " \t"); + } + } else { + /* No matches */ + retval = 1; + } + } + if (retval != 0) + free(buffer); + return retval; +} + +static void help(void) { + printf("Usage: %s [OPTION...] DEVICE\n\n" + "SCSI device identification.\n\n" + " -h --help Print this message\n" + " --version Print version of the program\n\n" + " -d --device= Device node for SG_IO commands\n" + " -f --config= Location of config file\n" + " -p --page=0x80|0x83|pre-spc3-83 SCSI page (0x80, 0x83, pre-spc3-83)\n" + " -s --sg-version=3|4 Use SGv3 or SGv4\n" + " -b --blacklisted Treat device as blacklisted\n" + " -g --whitelisted Treat device as whitelisted\n" + " -u --replace-whitespace Replace all whitespace by underscores\n" + " -v --verbose Verbose logging\n" + " -x --export Print values as environment keys\n" + , program_invocation_short_name); + +} + +static int set_options(struct udev *udev, + int argc, char **argv, + char *maj_min_dev) +{ + int option; + + /* + * optind is a global extern used by getopt. Since we can call + * set_options twice (once for command line, and once for config + * file) we have to reset this back to 1. + */ + optind = 1; + while ((option = getopt_long(argc, argv, "d:f:gp:uvVxh", options, NULL)) >= 0) + switch (option) { + case 'b': + all_good = false; + break; + + case 'd': + dev_specified = true; + strscpy(maj_min_dev, MAX_PATH_LEN, optarg); + break; + + case 'f': + strscpy(config_file, MAX_PATH_LEN, optarg); + break; + + case 'g': + all_good = true; + break; + + case 'h': + help(); + exit(0); + + case 'p': + if (streq(optarg, "0x80")) + default_page_code = PAGE_80; + else if (streq(optarg, "0x83")) + default_page_code = PAGE_83; + else if (streq(optarg, "pre-spc3-83")) + default_page_code = PAGE_83_PRE_SPC3; + else { + log_error("Unknown page code '%s'", optarg); + return -1; + } + break; + + case 's': + sg_version = atoi(optarg); + if (sg_version < 3 || sg_version > 4) { + log_error("Unknown SG version '%s'", optarg); + return -1; + } + break; + + case 'u': + reformat_serial = true; + break; + + case 'v': + log_set_target(LOG_TARGET_CONSOLE); + log_set_max_level(LOG_DEBUG); + log_open(); + break; + + case 'V': + printf("%s\n", VERSION); + exit(0); + + case 'x': + export = true; + break; + + case '?': + return -1; + + default: + assert_not_reached("Unknown option"); + } + + if (optind < argc && !dev_specified) { + dev_specified = true; + strscpy(maj_min_dev, MAX_PATH_LEN, argv[optind]); + } + + return 0; +} + +static int per_dev_options(struct udev *udev, + struct scsi_id_device *dev_scsi, int *good_bad, int *page_code) +{ + int retval; + int newargc; + char **newargv = NULL; + int option; + + *good_bad = all_good; + *page_code = default_page_code; + + retval = get_file_options(udev, vendor_str, model_str, &newargc, &newargv); + + optind = 1; /* reset this global extern */ + while (retval == 0) { + option = getopt_long(newargc, newargv, "bgp:", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'b': + *good_bad = 0; + break; + + case 'g': + *good_bad = 1; + break; + + case 'p': + if (streq(optarg, "0x80")) { + *page_code = PAGE_80; + } else if (streq(optarg, "0x83")) { + *page_code = PAGE_83; + } else if (streq(optarg, "pre-spc3-83")) { + *page_code = PAGE_83_PRE_SPC3; + } else { + log_error("Unknown page code '%s'", optarg); + retval = -1; + } + break; + + default: + log_error("Unknown or bad option '%c' (0x%x)", option, option); + retval = -1; + break; + } + } + + if (newargv) { + free(newargv[0]); + free(newargv); + } + return retval; +} + +static int set_inq_values(struct udev *udev, struct scsi_id_device *dev_scsi, const char *path) +{ + int retval; + + dev_scsi->use_sg = sg_version; + + retval = scsi_std_inquiry(udev, dev_scsi, path); + if (retval) + return retval; + + udev_util_encode_string(dev_scsi->vendor, vendor_enc_str, sizeof(vendor_enc_str)); + udev_util_encode_string(dev_scsi->model, model_enc_str, sizeof(model_enc_str)); + + util_replace_whitespace(dev_scsi->vendor, vendor_str, sizeof(vendor_str)); + util_replace_chars(vendor_str, NULL); + util_replace_whitespace(dev_scsi->model, model_str, sizeof(model_str)); + util_replace_chars(model_str, NULL); + set_type(dev_scsi->type, type_str, sizeof(type_str)); + util_replace_whitespace(dev_scsi->revision, revision_str, sizeof(revision_str)); + util_replace_chars(revision_str, NULL); + return 0; +} + +/* + * scsi_id: try to get an id, if one is found, printf it to stdout. + * returns a value passed to exit() - 0 if printed an id, else 1. + */ +static int scsi_id(struct udev *udev, char *maj_min_dev) +{ + struct scsi_id_device dev_scsi = {}; + int good_dev; + int page_code; + int retval = 0; + + if (set_inq_values(udev, &dev_scsi, maj_min_dev) < 0) { + retval = 1; + goto out; + } + + /* get per device (vendor + model) options from the config file */ + per_dev_options(udev, &dev_scsi, &good_dev, &page_code); + if (!good_dev) { + retval = 1; + goto out; + } + + /* read serial number from mode pages (no values for optical drives) */ + scsi_get_serial(udev, &dev_scsi, maj_min_dev, page_code, MAX_SERIAL_LEN); + + if (export) { + char serial_str[MAX_SERIAL_LEN]; + + printf("ID_SCSI=1\n"); + printf("ID_VENDOR=%s\n", vendor_str); + printf("ID_VENDOR_ENC=%s\n", vendor_enc_str); + printf("ID_MODEL=%s\n", model_str); + printf("ID_MODEL_ENC=%s\n", model_enc_str); + printf("ID_REVISION=%s\n", revision_str); + printf("ID_TYPE=%s\n", type_str); + if (dev_scsi.serial[0] != '\0') { + util_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str)); + util_replace_chars(serial_str, NULL); + printf("ID_SERIAL=%s\n", serial_str); + util_replace_whitespace(dev_scsi.serial_short, serial_str, sizeof(serial_str)); + util_replace_chars(serial_str, NULL); + printf("ID_SERIAL_SHORT=%s\n", serial_str); + } + if (dev_scsi.wwn[0] != '\0') { + printf("ID_WWN=0x%s\n", dev_scsi.wwn); + if (dev_scsi.wwn_vendor_extension[0] != '\0') { + printf("ID_WWN_VENDOR_EXTENSION=0x%s\n", dev_scsi.wwn_vendor_extension); + printf("ID_WWN_WITH_EXTENSION=0x%s%s\n", dev_scsi.wwn, dev_scsi.wwn_vendor_extension); + } else + printf("ID_WWN_WITH_EXTENSION=0x%s\n", dev_scsi.wwn); + } + if (dev_scsi.tgpt_group[0] != '\0') + printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group); + if (dev_scsi.unit_serial_number[0] != '\0') + printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number); + goto out; + } + + if (dev_scsi.serial[0] == '\0') { + retval = 1; + goto out; + } + + if (reformat_serial) { + char serial_str[MAX_SERIAL_LEN]; + + util_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str)); + util_replace_chars(serial_str, NULL); + printf("%s\n", serial_str); + goto out; + } + + printf("%s\n", dev_scsi.serial); +out: + return retval; +} + +int main(int argc, char **argv) +{ + _cleanup_udev_unref_ struct udev *udev; + int retval = 0; + char maj_min_dev[MAX_PATH_LEN]; + int newargc; + char **newargv = NULL; + + log_parse_environment(); + log_open(); + + udev = udev_new(); + if (udev == NULL) + goto exit; + + /* + * Get config file options. + */ + retval = get_file_options(udev, NULL, NULL, &newargc, &newargv); + if (retval < 0) { + retval = 1; + goto exit; + } + if (retval == 0) { + assert(newargv); + + if (set_options(udev, newargc, newargv, maj_min_dev) < 0) { + retval = 2; + goto exit; + } + } + + /* + * Get command line options (overriding any config file settings). + */ + if (set_options(udev, argc, argv, maj_min_dev) < 0) + exit(1); + + if (!dev_specified) { + log_error("No device specified."); + retval = 1; + goto exit; + } + + retval = scsi_id(udev, maj_min_dev); + +exit: + if (newargv) { + free(newargv[0]); + free(newargv); + } + log_close(); + return retval; +} diff --git a/src/grp-udev/scsi_id/scsi_id.h b/src/grp-udev/scsi_id/scsi_id.h new file mode 100644 index 0000000000..5c2e1c28ee --- /dev/null +++ b/src/grp-udev/scsi_id/scsi_id.h @@ -0,0 +1,75 @@ +#pragma once + +/* + * Copyright (C) IBM Corp. 2003 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define MAX_PATH_LEN 512 + +/* + * MAX_ATTR_LEN: maximum length of the result of reading a sysfs + * attribute. + */ +#define MAX_ATTR_LEN 256 + +/* + * MAX_SERIAL_LEN: the maximum length of the serial number, including + * added prefixes such as vendor and product (model) strings. + */ +#define MAX_SERIAL_LEN 256 + +/* + * MAX_BUFFER_LEN: maximum buffer size and line length used while reading + * the config file. + */ +#define MAX_BUFFER_LEN 256 + +struct scsi_id_device { + char vendor[9]; + char model[17]; + char revision[5]; + char type[33]; + char kernel[64]; + char serial[MAX_SERIAL_LEN]; + char serial_short[MAX_SERIAL_LEN]; + int use_sg; + + /* Always from page 0x80 e.g. 'B3G1P8500RWT' - may not be unique */ + char unit_serial_number[MAX_SERIAL_LEN]; + + /* NULs if not set - otherwise hex encoding using lower-case e.g. '50014ee0016eb572' */ + char wwn[17]; + + /* NULs if not set - otherwise hex encoding using lower-case e.g. '0xe00000d80000' */ + char wwn_vendor_extension[17]; + + /* NULs if not set - otherwise decimal number */ + char tgpt_group[8]; +}; + +int scsi_std_inquiry(struct udev *udev, struct scsi_id_device *dev_scsi, const char *devname); +int scsi_get_serial(struct udev *udev, struct scsi_id_device *dev_scsi, const char *devname, + int page_code, int len); + +/* + * Page code values. + */ +enum page_code { + PAGE_83_PRE_SPC3 = -0x83, + PAGE_UNSPECIFIED = 0x00, + PAGE_80 = 0x80, + PAGE_83 = 0x83, +}; diff --git a/src/grp-udev/scsi_id/scsi_serial.c b/src/grp-udev/scsi_id/scsi_serial.c new file mode 100644 index 0000000000..e079e28698 --- /dev/null +++ b/src/grp-udev/scsi_id/scsi_serial.c @@ -0,0 +1,964 @@ +/* + * Copyright (C) IBM Corp. 2003 + * + * Author: Patrick Mansfield<patmans@us.ibm.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <linux/bsg.h> +#include <linux/types.h> +#include <scsi/scsi.h> +#include <scsi/sg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include "libudev.h" + +#include "libudev-private.h" +#include "random-util.h" +#include "scsi.h" +#include "scsi_id.h" +#include "string-util.h" + +/* + * A priority based list of id, naa, and binary/ascii for the identifier + * descriptor in VPD page 0x83. + * + * Brute force search for a match starting with the first value in the + * following id_search_list. This is not a performance issue, since there + * is normally one or some small number of descriptors. + */ +static const struct scsi_id_search_values id_search_list[] = { + { SCSI_ID_TGTGROUP, SCSI_ID_NAA_DONT_CARE, SCSI_ID_BINARY }, + { SCSI_ID_NAA, SCSI_ID_NAA_IEEE_REG_EXTENDED, SCSI_ID_BINARY }, + { SCSI_ID_NAA, SCSI_ID_NAA_IEEE_REG_EXTENDED, SCSI_ID_ASCII }, + { SCSI_ID_NAA, SCSI_ID_NAA_IEEE_REG, SCSI_ID_BINARY }, + { SCSI_ID_NAA, SCSI_ID_NAA_IEEE_REG, SCSI_ID_ASCII }, + /* + * Devices already exist using NAA values that are now marked + * reserved. These should not conflict with other values, or it is + * a bug in the device. As long as we find the IEEE extended one + * first, we really don't care what other ones are used. Using + * don't care here means that a device that returns multiple + * non-IEEE descriptors in a random order will get different + * names. + */ + { SCSI_ID_NAA, SCSI_ID_NAA_DONT_CARE, SCSI_ID_BINARY }, + { SCSI_ID_NAA, SCSI_ID_NAA_DONT_CARE, SCSI_ID_ASCII }, + { SCSI_ID_EUI_64, SCSI_ID_NAA_DONT_CARE, SCSI_ID_BINARY }, + { SCSI_ID_EUI_64, SCSI_ID_NAA_DONT_CARE, SCSI_ID_ASCII }, + { SCSI_ID_T10_VENDOR, SCSI_ID_NAA_DONT_CARE, SCSI_ID_BINARY }, + { SCSI_ID_T10_VENDOR, SCSI_ID_NAA_DONT_CARE, SCSI_ID_ASCII }, + { SCSI_ID_VENDOR_SPECIFIC, SCSI_ID_NAA_DONT_CARE, SCSI_ID_BINARY }, + { SCSI_ID_VENDOR_SPECIFIC, SCSI_ID_NAA_DONT_CARE, SCSI_ID_ASCII }, +}; + +static const char hex_str[]="0123456789abcdef"; + +/* + * Values returned in the result/status, only the ones used by the code + * are used here. + */ + +#define DID_NO_CONNECT 0x01 /* Unable to connect before timeout */ +#define DID_BUS_BUSY 0x02 /* Bus remain busy until timeout */ +#define DID_TIME_OUT 0x03 /* Timed out for some other reason */ +#define DRIVER_TIMEOUT 0x06 +#define DRIVER_SENSE 0x08 /* Sense_buffer has been set */ + +/* The following "category" function returns one of the following */ +#define SG_ERR_CAT_CLEAN 0 /* No errors or other information */ +#define SG_ERR_CAT_MEDIA_CHANGED 1 /* interpreted from sense buffer */ +#define SG_ERR_CAT_RESET 2 /* interpreted from sense buffer */ +#define SG_ERR_CAT_TIMEOUT 3 +#define SG_ERR_CAT_RECOVERED 4 /* Successful command after recovered err */ +#define SG_ERR_CAT_NOTSUPPORTED 5 /* Illegal / unsupported command */ +#define SG_ERR_CAT_SENSE 98 /* Something else in the sense buffer */ +#define SG_ERR_CAT_OTHER 99 /* Some other error/warning */ + +static int do_scsi_page80_inquiry(struct udev *udev, + struct scsi_id_device *dev_scsi, int fd, + char *serial, char *serial_short, int max_len); + +static int sg_err_category_new(struct udev *udev, + int scsi_status, int msg_status, int + host_status, int driver_status, const + unsigned char *sense_buffer, int sb_len) +{ + scsi_status &= 0x7e; + + /* + * XXX change to return only two values - failed or OK. + */ + + if (!scsi_status && !host_status && !driver_status) + return SG_ERR_CAT_CLEAN; + + if ((scsi_status == SCSI_CHECK_CONDITION) || + (scsi_status == SCSI_COMMAND_TERMINATED) || + ((driver_status & 0xf) == DRIVER_SENSE)) { + if (sense_buffer && (sb_len > 2)) { + int sense_key; + unsigned char asc; + + if (sense_buffer[0] & 0x2) { + sense_key = sense_buffer[1] & 0xf; + asc = sense_buffer[2]; + } else { + sense_key = sense_buffer[2] & 0xf; + asc = (sb_len > 12) ? sense_buffer[12] : 0; + } + + if (sense_key == RECOVERED_ERROR) + return SG_ERR_CAT_RECOVERED; + else if (sense_key == UNIT_ATTENTION) { + if (0x28 == asc) + return SG_ERR_CAT_MEDIA_CHANGED; + if (0x29 == asc) + return SG_ERR_CAT_RESET; + } else if (sense_key == ILLEGAL_REQUEST) + return SG_ERR_CAT_NOTSUPPORTED; + } + return SG_ERR_CAT_SENSE; + } + if (host_status) { + if ((host_status == DID_NO_CONNECT) || + (host_status == DID_BUS_BUSY) || + (host_status == DID_TIME_OUT)) + return SG_ERR_CAT_TIMEOUT; + } + if (driver_status) { + if (driver_status == DRIVER_TIMEOUT) + return SG_ERR_CAT_TIMEOUT; + } + return SG_ERR_CAT_OTHER; +} + +static int sg_err_category3(struct udev *udev, struct sg_io_hdr *hp) +{ + return sg_err_category_new(udev, + hp->status, hp->msg_status, + hp->host_status, hp->driver_status, + hp->sbp, hp->sb_len_wr); +} + +static int sg_err_category4(struct udev *udev, struct sg_io_v4 *hp) +{ + return sg_err_category_new(udev, hp->device_status, 0, + hp->transport_status, hp->driver_status, + (unsigned char *)(uintptr_t)hp->response, + hp->response_len); +} + +static int scsi_dump_sense(struct udev *udev, + struct scsi_id_device *dev_scsi, + unsigned char *sense_buffer, int sb_len) +{ + int s; + int code; + int sense_class; + int sense_key; + int asc, ascq; +#ifdef DUMP_SENSE + char out_buffer[256]; + int i, j; +#endif + + /* + * Figure out and print the sense key, asc and ascq. + * + * If you want to suppress these for a particular drive model, add + * a black list entry in the scsi_id config file. + * + * XXX We probably need to: lookup the sense/asc/ascq in a retry + * table, and if found return 1 (after dumping the sense, asc, and + * ascq). So, if/when we get something like a power on/reset, + * we'll retry the command. + */ + + if (sb_len < 1) { + log_debug("%s: sense buffer empty", dev_scsi->kernel); + return -1; + } + + sense_class = (sense_buffer[0] >> 4) & 0x07; + code = sense_buffer[0] & 0xf; + + if (sense_class == 7) { + /* + * extended sense data. + */ + s = sense_buffer[7] + 8; + if (sb_len < s) { + log_debug("%s: sense buffer too small %d bytes, %d bytes too short", + dev_scsi->kernel, sb_len, s - sb_len); + return -1; + } + if ((code == 0x0) || (code == 0x1)) { + sense_key = sense_buffer[2] & 0xf; + if (s < 14) { + /* + * Possible? + */ + log_debug("%s: sense result too" " small %d bytes", + dev_scsi->kernel, s); + return -1; + } + asc = sense_buffer[12]; + ascq = sense_buffer[13]; + } else if ((code == 0x2) || (code == 0x3)) { + sense_key = sense_buffer[1] & 0xf; + asc = sense_buffer[2]; + ascq = sense_buffer[3]; + } else { + log_debug("%s: invalid sense code 0x%x", + dev_scsi->kernel, code); + return -1; + } + log_debug("%s: sense key 0x%x ASC 0x%x ASCQ 0x%x", + dev_scsi->kernel, sense_key, asc, ascq); + } else { + if (sb_len < 4) { + log_debug("%s: sense buffer too small %d bytes, %d bytes too short", + dev_scsi->kernel, sb_len, 4 - sb_len); + return -1; + } + + if (sense_buffer[0] < 15) + log_debug("%s: old sense key: 0x%x", dev_scsi->kernel, sense_buffer[0] & 0x0f); + else + log_debug("%s: sense = %2x %2x", + dev_scsi->kernel, sense_buffer[0], sense_buffer[2]); + log_debug("%s: non-extended sense class %d code 0x%0x", + dev_scsi->kernel, sense_class, code); + + } + +#ifdef DUMP_SENSE + for (i = 0, j = 0; (i < s) && (j < 254); i++) { + out_buffer[j++] = hex_str[(sense_buffer[i] & 0xf0) >> 4]; + out_buffer[j++] = hex_str[sense_buffer[i] & 0x0f]; + out_buffer[j++] = ' '; + } + out_buffer[j] = '\0'; + log_debug("%s: sense dump:", dev_scsi->kernel); + log_debug("%s: %s", dev_scsi->kernel, out_buffer); + +#endif + return -1; +} + +static int scsi_dump(struct udev *udev, + struct scsi_id_device *dev_scsi, struct sg_io_hdr *io) +{ + if (!io->status && !io->host_status && !io->msg_status && + !io->driver_status) { + /* + * Impossible, should not be called. + */ + log_debug("%s: called with no error", __FUNCTION__); + return -1; + } + + log_debug("%s: sg_io failed status 0x%x 0x%x 0x%x 0x%x", + dev_scsi->kernel, io->driver_status, io->host_status, io->msg_status, io->status); + if (io->status == SCSI_CHECK_CONDITION) + return scsi_dump_sense(udev, dev_scsi, io->sbp, io->sb_len_wr); + else + return -1; +} + +static int scsi_dump_v4(struct udev *udev, + struct scsi_id_device *dev_scsi, struct sg_io_v4 *io) +{ + if (!io->device_status && !io->transport_status && + !io->driver_status) { + /* + * Impossible, should not be called. + */ + log_debug("%s: called with no error", __FUNCTION__); + return -1; + } + + log_debug("%s: sg_io failed status 0x%x 0x%x 0x%x", + dev_scsi->kernel, io->driver_status, io->transport_status, io->device_status); + if (io->device_status == SCSI_CHECK_CONDITION) + return scsi_dump_sense(udev, dev_scsi, (unsigned char *)(uintptr_t)io->response, + io->response_len); + else + return -1; +} + +static int scsi_inquiry(struct udev *udev, + struct scsi_id_device *dev_scsi, int fd, + unsigned char evpd, unsigned char page, + unsigned char *buf, unsigned int buflen) +{ + unsigned char inq_cmd[INQUIRY_CMDLEN] = + { INQUIRY_CMD, evpd, page, 0, buflen, 0 }; + unsigned char sense[SENSE_BUFF_LEN]; + void *io_buf; + struct sg_io_v4 io_v4; + struct sg_io_hdr io_hdr; + int retry = 3; /* rather random */ + int retval; + + if (buflen > SCSI_INQ_BUFF_LEN) { + log_debug("buflen %d too long", buflen); + return -1; + } + +resend: + if (dev_scsi->use_sg == 4) { + memzero(&io_v4, sizeof(struct sg_io_v4)); + io_v4.guard = 'Q'; + io_v4.protocol = BSG_PROTOCOL_SCSI; + io_v4.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD; + io_v4.request_len = sizeof(inq_cmd); + io_v4.request = (uintptr_t)inq_cmd; + io_v4.max_response_len = sizeof(sense); + io_v4.response = (uintptr_t)sense; + io_v4.din_xfer_len = buflen; + io_v4.din_xferp = (uintptr_t)buf; + io_buf = (void *)&io_v4; + } else { + memzero(&io_hdr, sizeof(struct sg_io_hdr)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(inq_cmd); + io_hdr.mx_sb_len = sizeof(sense); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = buflen; + io_hdr.dxferp = buf; + io_hdr.cmdp = inq_cmd; + io_hdr.sbp = sense; + io_hdr.timeout = DEF_TIMEOUT; + io_buf = (void *)&io_hdr; + } + + retval = ioctl(fd, SG_IO, io_buf); + if (retval < 0) { + if ((errno == EINVAL || errno == ENOSYS) && dev_scsi->use_sg == 4) { + dev_scsi->use_sg = 3; + goto resend; + } + log_debug_errno(errno, "%s: ioctl failed: %m", dev_scsi->kernel); + goto error; + } + + if (dev_scsi->use_sg == 4) + retval = sg_err_category4(udev, io_buf); + else + retval = sg_err_category3(udev, io_buf); + + switch (retval) { + case SG_ERR_CAT_NOTSUPPORTED: + buf[1] = 0; + /* Fallthrough */ + case SG_ERR_CAT_CLEAN: + case SG_ERR_CAT_RECOVERED: + retval = 0; + break; + + default: + if (dev_scsi->use_sg == 4) + retval = scsi_dump_v4(udev, dev_scsi, io_buf); + else + retval = scsi_dump(udev, dev_scsi, io_buf); + } + + if (!retval) { + retval = buflen; + } else if (retval > 0) { + if (--retry > 0) + goto resend; + retval = -1; + } + +error: + if (retval < 0) + log_debug("%s: Unable to get INQUIRY vpd %d page 0x%x.", + dev_scsi->kernel, evpd, page); + + return retval; +} + +/* Get list of supported EVPD pages */ +static int do_scsi_page0_inquiry(struct udev *udev, + struct scsi_id_device *dev_scsi, int fd, + unsigned char *buffer, unsigned int len) +{ + int retval; + + memzero(buffer, len); + retval = scsi_inquiry(udev, dev_scsi, fd, 1, 0x0, buffer, len); + if (retval < 0) + return 1; + + if (buffer[1] != 0) { + log_debug("%s: page 0 not available.", dev_scsi->kernel); + return 1; + } + if (buffer[3] > len) { + log_debug("%s: page 0 buffer too long %d", dev_scsi->kernel, buffer[3]); + return 1; + } + + /* + * Following check is based on code once included in the 2.5.x + * kernel. + * + * Some ill behaved devices return the standard inquiry here + * rather than the evpd data, snoop the data to verify. + */ + if (buffer[3] > MODEL_LENGTH) { + /* + * If the vendor id appears in the page assume the page is + * invalid. + */ + if (strneq((char *)&buffer[VENDOR_LENGTH], dev_scsi->vendor, VENDOR_LENGTH)) { + log_debug("%s: invalid page0 data", dev_scsi->kernel); + return 1; + } + } + return 0; +} + +/* + * The caller checks that serial is long enough to include the vendor + + * model. + */ +static int prepend_vendor_model(struct udev *udev, + struct scsi_id_device *dev_scsi, char *serial) +{ + int ind; + + strncpy(serial, dev_scsi->vendor, VENDOR_LENGTH); + strncat(serial, dev_scsi->model, MODEL_LENGTH); + ind = strlen(serial); + + /* + * This is not a complete check, since we are using strncat/cpy + * above, ind will never be too large. + */ + if (ind != (VENDOR_LENGTH + MODEL_LENGTH)) { + log_debug("%s: expected length %d, got length %d", + dev_scsi->kernel, (VENDOR_LENGTH + MODEL_LENGTH), ind); + return -1; + } + return ind; +} + +/* + * check_fill_0x83_id - check the page 0x83 id, if OK allocate and fill + * serial number. + */ +static int check_fill_0x83_id(struct udev *udev, + struct scsi_id_device *dev_scsi, + unsigned char *page_83, + const struct scsi_id_search_values + *id_search, char *serial, char *serial_short, + int max_len, char *wwn, + char *wwn_vendor_extension, char *tgpt_group) +{ + int i, j, s, len; + + /* + * ASSOCIATION must be with the device (value 0) + * or with the target port for SCSI_ID_TGTPORT + */ + if ((page_83[1] & 0x30) == 0x10) { + if (id_search->id_type != SCSI_ID_TGTGROUP) + return 1; + } else if ((page_83[1] & 0x30) != 0) + return 1; + + if ((page_83[1] & 0x0f) != id_search->id_type) + return 1; + + /* + * Possibly check NAA sub-type. + */ + if ((id_search->naa_type != SCSI_ID_NAA_DONT_CARE) && + (id_search->naa_type != (page_83[4] & 0xf0) >> 4)) + return 1; + + /* + * Check for matching code set - ASCII or BINARY. + */ + if ((page_83[0] & 0x0f) != id_search->code_set) + return 1; + + /* + * page_83[3]: identifier length + */ + len = page_83[3]; + if ((page_83[0] & 0x0f) != SCSI_ID_ASCII) + /* + * If not ASCII, use two bytes for each binary value. + */ + len *= 2; + + /* + * Add one byte for the NUL termination, and one for the id_type. + */ + len += 2; + if (id_search->id_type == SCSI_ID_VENDOR_SPECIFIC) + len += VENDOR_LENGTH + MODEL_LENGTH; + + if (max_len < len) { + log_debug("%s: length %d too short - need %d", + dev_scsi->kernel, max_len, len); + return 1; + } + + if (id_search->id_type == SCSI_ID_TGTGROUP && tgpt_group != NULL) { + unsigned int group; + + group = ((unsigned int)page_83[6] << 8) | page_83[7]; + sprintf(tgpt_group,"%x", group); + return 1; + } + + serial[0] = hex_str[id_search->id_type]; + + /* + * For SCSI_ID_VENDOR_SPECIFIC prepend the vendor and model before + * the id since it is not unique across all vendors and models, + * this differs from SCSI_ID_T10_VENDOR, where the vendor is + * included in the identifier. + */ + if (id_search->id_type == SCSI_ID_VENDOR_SPECIFIC) + if (prepend_vendor_model(udev, dev_scsi, &serial[1]) < 0) + return 1; + + i = 4; /* offset to the start of the identifier */ + s = j = strlen(serial); + if ((page_83[0] & 0x0f) == SCSI_ID_ASCII) { + /* + * ASCII descriptor. + */ + while (i < (4 + page_83[3])) + serial[j++] = page_83[i++]; + } else { + /* + * Binary descriptor, convert to ASCII, using two bytes of + * ASCII for each byte in the page_83. + */ + while (i < (4 + page_83[3])) { + serial[j++] = hex_str[(page_83[i] & 0xf0) >> 4]; + serial[j++] = hex_str[page_83[i] & 0x0f]; + i++; + } + } + + strcpy(serial_short, &serial[s]); + + if (id_search->id_type == SCSI_ID_NAA && wwn != NULL) { + strncpy(wwn, &serial[s], 16); + if (wwn_vendor_extension != NULL) + strncpy(wwn_vendor_extension, &serial[s + 16], 16); + } + + return 0; +} + +/* Extract the raw binary from VPD 0x83 pre-SPC devices */ +static int check_fill_0x83_prespc3(struct udev *udev, + struct scsi_id_device *dev_scsi, + unsigned char *page_83, + const struct scsi_id_search_values + *id_search, char *serial, char *serial_short, int max_len) +{ + int i, j; + + serial[0] = hex_str[id_search->id_type]; + /* serial has been memset to zero before */ + j = strlen(serial); /* j = 1; */ + + for (i = 0; (i < page_83[3]) && (j < max_len-3); ++i) { + serial[j++] = hex_str[(page_83[4+i] & 0xf0) >> 4]; + serial[j++] = hex_str[ page_83[4+i] & 0x0f]; + } + serial[max_len-1] = 0; + strncpy(serial_short, serial, max_len-1); + return 0; +} + + +/* Get device identification VPD page */ +static int do_scsi_page83_inquiry(struct udev *udev, + struct scsi_id_device *dev_scsi, int fd, + char *serial, char *serial_short, int len, + char *unit_serial_number, char *wwn, + char *wwn_vendor_extension, char *tgpt_group) +{ + int retval; + unsigned int id_ind, j; + unsigned char page_83[SCSI_INQ_BUFF_LEN]; + + /* also pick up the page 80 serial number */ + do_scsi_page80_inquiry(udev, dev_scsi, fd, NULL, unit_serial_number, MAX_SERIAL_LEN); + + memzero(page_83, SCSI_INQ_BUFF_LEN); + retval = scsi_inquiry(udev, dev_scsi, fd, 1, PAGE_83, page_83, + SCSI_INQ_BUFF_LEN); + if (retval < 0) + return 1; + + if (page_83[1] != PAGE_83) { + log_debug("%s: Invalid page 0x83", dev_scsi->kernel); + return 1; + } + + /* + * XXX Some devices (IBM 3542) return all spaces for an identifier if + * the LUN is not actually configured. This leads to identifiers of + * the form: "1 ". + */ + + /* + * Model 4, 5, and (some) model 6 EMC Symmetrix devices return + * a page 83 reply according to SCSI-2 format instead of SPC-2/3. + * + * The SCSI-2 page 83 format returns an IEEE WWN in binary + * encoded hexi-decimal in the 16 bytes following the initial + * 4-byte page 83 reply header. + * + * Both the SPC-2 and SPC-3 formats return an IEEE WWN as part + * of an Identification descriptor. The 3rd byte of the first + * Identification descriptor is a reserved (BSZ) byte field. + * + * Reference the 7th byte of the page 83 reply to determine + * whether the reply is compliant with SCSI-2 or SPC-2/3 + * specifications. A zero value in the 7th byte indicates + * an SPC-2/3 conformant reply, (i.e., the reserved field of the + * first Identification descriptor). This byte will be non-zero + * for a SCSI-2 conformant page 83 reply from these EMC + * Symmetrix models since the 7th byte of the reply corresponds + * to the 4th and 5th nibbles of the 6-byte OUI for EMC, that is, + * 0x006048. + */ + + if (page_83[6] != 0) + return check_fill_0x83_prespc3(udev, + dev_scsi, page_83, id_search_list, + serial, serial_short, len); + + /* + * Search for a match in the prioritized id_search_list - since WWN ids + * come first we can pick up the WWN in check_fill_0x83_id(). + */ + for (id_ind = 0; + id_ind < sizeof(id_search_list)/sizeof(id_search_list[0]); + id_ind++) { + /* + * Examine each descriptor returned. There is normally only + * one or a small number of descriptors. + */ + for (j = 4; j <= (unsigned int)page_83[3] + 3; j += page_83[j + 3] + 4) { + retval = check_fill_0x83_id(udev, + dev_scsi, &page_83[j], + &id_search_list[id_ind], + serial, serial_short, len, + wwn, wwn_vendor_extension, + tgpt_group); + if (!retval) + return retval; + else if (retval < 0) + return retval; + } + } + return 1; +} + +/* + * Get device identification VPD page for older SCSI-2 device which is not + * compliant with either SPC-2 or SPC-3 format. + * + * Return the hard coded error code value 2 if the page 83 reply is not + * conformant to the SCSI-2 format. + */ +static int do_scsi_page83_prespc3_inquiry(struct udev *udev, + struct scsi_id_device *dev_scsi, int fd, + char *serial, char *serial_short, int len) +{ + int retval; + int i, j; + unsigned char page_83[SCSI_INQ_BUFF_LEN]; + + memzero(page_83, SCSI_INQ_BUFF_LEN); + retval = scsi_inquiry(udev, dev_scsi, fd, 1, PAGE_83, page_83, SCSI_INQ_BUFF_LEN); + if (retval < 0) + return 1; + + if (page_83[1] != PAGE_83) { + log_debug("%s: Invalid page 0x83", dev_scsi->kernel); + return 1; + } + /* + * Model 4, 5, and (some) model 6 EMC Symmetrix devices return + * a page 83 reply according to SCSI-2 format instead of SPC-2/3. + * + * The SCSI-2 page 83 format returns an IEEE WWN in binary + * encoded hexi-decimal in the 16 bytes following the initial + * 4-byte page 83 reply header. + * + * Both the SPC-2 and SPC-3 formats return an IEEE WWN as part + * of an Identification descriptor. The 3rd byte of the first + * Identification descriptor is a reserved (BSZ) byte field. + * + * Reference the 7th byte of the page 83 reply to determine + * whether the reply is compliant with SCSI-2 or SPC-2/3 + * specifications. A zero value in the 7th byte indicates + * an SPC-2/3 conformant reply, (i.e., the reserved field of the + * first Identification descriptor). This byte will be non-zero + * for a SCSI-2 conformant page 83 reply from these EMC + * Symmetrix models since the 7th byte of the reply corresponds + * to the 4th and 5th nibbles of the 6-byte OUI for EMC, that is, + * 0x006048. + */ + if (page_83[6] == 0) + return 2; + + serial[0] = hex_str[id_search_list[0].id_type]; + /* + * The first four bytes contain data, not a descriptor. + */ + i = 4; + j = strlen(serial); + /* + * Binary descriptor, convert to ASCII, + * using two bytes of ASCII for each byte + * in the page_83. + */ + while (i < (page_83[3]+4)) { + serial[j++] = hex_str[(page_83[i] & 0xf0) >> 4]; + serial[j++] = hex_str[page_83[i] & 0x0f]; + i++; + } + return 0; +} + +/* Get unit serial number VPD page */ +static int do_scsi_page80_inquiry(struct udev *udev, + struct scsi_id_device *dev_scsi, int fd, + char *serial, char *serial_short, int max_len) +{ + int retval; + int ser_ind; + int i; + int len; + unsigned char buf[SCSI_INQ_BUFF_LEN]; + + memzero(buf, SCSI_INQ_BUFF_LEN); + retval = scsi_inquiry(udev, dev_scsi, fd, 1, PAGE_80, buf, SCSI_INQ_BUFF_LEN); + if (retval < 0) + return retval; + + if (buf[1] != PAGE_80) { + log_debug("%s: Invalid page 0x80", dev_scsi->kernel); + return 1; + } + + len = 1 + VENDOR_LENGTH + MODEL_LENGTH + buf[3]; + if (max_len < len) { + log_debug("%s: length %d too short - need %d", + dev_scsi->kernel, max_len, len); + return 1; + } + /* + * Prepend 'S' to avoid unlikely collision with page 0x83 vendor + * specific type where we prepend '0' + vendor + model. + */ + len = buf[3]; + if (serial != NULL) { + serial[0] = 'S'; + ser_ind = prepend_vendor_model(udev, dev_scsi, &serial[1]); + if (ser_ind < 0) + return 1; + ser_ind++; /* for the leading 'S' */ + for (i = 4; i < len + 4; i++, ser_ind++) + serial[ser_ind] = buf[i]; + } + if (serial_short != NULL) { + memcpy(serial_short, &buf[4], len); + serial_short[len] = '\0'; + } + return 0; +} + +int scsi_std_inquiry(struct udev *udev, + struct scsi_id_device *dev_scsi, const char *devname) +{ + int fd; + unsigned char buf[SCSI_INQ_BUFF_LEN]; + struct stat statbuf; + int err = 0; + + fd = open(devname, O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (fd < 0) { + log_debug_errno(errno, "scsi_id: cannot open %s: %m", devname); + return 1; + } + + if (fstat(fd, &statbuf) < 0) { + log_debug_errno(errno, "scsi_id: cannot stat %s: %m", devname); + err = 2; + goto out; + } + sprintf(dev_scsi->kernel,"%d:%d", major(statbuf.st_rdev), + minor(statbuf.st_rdev)); + + memzero(buf, SCSI_INQ_BUFF_LEN); + err = scsi_inquiry(udev, dev_scsi, fd, 0, 0, buf, SCSI_INQ_BUFF_LEN); + if (err < 0) + goto out; + + err = 0; + memcpy(dev_scsi->vendor, buf + 8, 8); + dev_scsi->vendor[8] = '\0'; + memcpy(dev_scsi->model, buf + 16, 16); + dev_scsi->model[16] = '\0'; + memcpy(dev_scsi->revision, buf + 32, 4); + dev_scsi->revision[4] = '\0'; + sprintf(dev_scsi->type,"%x", buf[0] & 0x1f); + +out: + close(fd); + return err; +} + +int scsi_get_serial(struct udev *udev, + struct scsi_id_device *dev_scsi, const char *devname, + int page_code, int len) +{ + unsigned char page0[SCSI_INQ_BUFF_LEN]; + int fd = -1; + int cnt; + int ind; + int retval; + + memzero(dev_scsi->serial, len); + initialize_srand(); + for (cnt = 20; cnt > 0; cnt--) { + struct timespec duration; + + fd = open(devname, O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (fd >= 0 || errno != EBUSY) + break; + duration.tv_sec = 0; + duration.tv_nsec = (200 * 1000 * 1000) + (rand() % 100 * 1000 * 1000); + nanosleep(&duration, NULL); + } + if (fd < 0) + return 1; + + if (page_code == PAGE_80) { + if (do_scsi_page80_inquiry(udev, dev_scsi, fd, dev_scsi->serial, dev_scsi->serial_short, len)) { + retval = 1; + goto completed; + } else { + retval = 0; + goto completed; + } + } else if (page_code == PAGE_83) { + if (do_scsi_page83_inquiry(udev, dev_scsi, fd, dev_scsi->serial, dev_scsi->serial_short, len, dev_scsi->unit_serial_number, dev_scsi->wwn, dev_scsi->wwn_vendor_extension, dev_scsi->tgpt_group)) { + retval = 1; + goto completed; + } else { + retval = 0; + goto completed; + } + } else if (page_code == PAGE_83_PRE_SPC3) { + retval = do_scsi_page83_prespc3_inquiry(udev, dev_scsi, fd, dev_scsi->serial, dev_scsi->serial_short, len); + if (retval) { + /* + * Fallback to servicing a SPC-2/3 compliant page 83 + * inquiry if the page 83 reply format does not + * conform to pre-SPC3 expectations. + */ + if (retval == 2) { + if (do_scsi_page83_inquiry(udev, dev_scsi, fd, dev_scsi->serial, dev_scsi->serial_short, len, dev_scsi->unit_serial_number, dev_scsi->wwn, dev_scsi->wwn_vendor_extension, dev_scsi->tgpt_group)) { + retval = 1; + goto completed; + } else { + retval = 0; + goto completed; + } + } + else { + retval = 1; + goto completed; + } + } else { + retval = 0; + goto completed; + } + } else if (page_code != 0x00) { + log_debug("%s: unsupported page code 0x%d", dev_scsi->kernel, page_code); + retval = 1; + goto completed; + } + + /* + * Get page 0, the page of the pages. By default, try from best to + * worst of supported pages: 0x83 then 0x80. + */ + if (do_scsi_page0_inquiry(udev, dev_scsi, fd, page0, SCSI_INQ_BUFF_LEN)) { + /* + * Don't try anything else. Black list if a specific page + * should be used for this vendor+model, or maybe have an + * optional fall-back to page 0x80 or page 0x83. + */ + retval = 1; + goto completed; + } + + for (ind = 4; ind <= page0[3] + 3; ind++) + if (page0[ind] == PAGE_83) + if (!do_scsi_page83_inquiry(udev, dev_scsi, fd, + dev_scsi->serial, dev_scsi->serial_short, len, dev_scsi->unit_serial_number, dev_scsi->wwn, dev_scsi->wwn_vendor_extension, dev_scsi->tgpt_group)) { + /* + * Success + */ + retval = 0; + goto completed; + } + + for (ind = 4; ind <= page0[3] + 3; ind++) + if (page0[ind] == PAGE_80) + if (!do_scsi_page80_inquiry(udev, dev_scsi, fd, + dev_scsi->serial, dev_scsi->serial_short, len)) { + /* + * Success + */ + retval = 0; + goto completed; + } + retval = 1; + +completed: + close(fd); + return retval; +} diff --git a/src/grp-udev/systemd-udevd/Makefile b/src/grp-udev/systemd-udevd/Makefile new file mode 100644 index 0000000000..5bbc548cfe --- /dev/null +++ b/src/grp-udev/systemd-udevd/Makefile @@ -0,0 +1,35 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +rootlibexec_PROGRAMS += \ + systemd-udevd + +systemd_udevd_SOURCES = \ + src/udev/udevd.c + +systemd_udevd_LDADD = \ + libudev-core.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-udev/systemd-udevd/udevd.c b/src/grp-udev/systemd-udevd/udevd.c new file mode 100644 index 0000000000..89006e6e3a --- /dev/null +++ b/src/grp-udev/systemd-udevd/udevd.c @@ -0,0 +1,1765 @@ +/* + * Copyright (C) 2004-2012 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2004 Chris Friesen <chris_friesen@sympatico.ca> + * Copyright (C) 2009 Canonical Ltd. + * Copyright (C) 2009 Scott James Remnant <scott@netsplit.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <signal.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/epoll.h> +#include <sys/file.h> +#include <sys/inotify.h> +#include <sys/ioctl.h> +#include <sys/mount.h> +#include <sys/prctl.h> +#include <sys/signalfd.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <systemd/sd-daemon.h> +#include <systemd/sd-event.h> + +#include "alloc-util.h" +#include "cgroup-util.h" +#include "cpu-set-util.h" +#include "dev-setup.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "hashmap.h" +#include "io-util.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "process-util.h" +#include "selinux-util.h" +#include "signal-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "terminal-util.h" +#include "udev-util.h" +#include "udev.h" +#include "user-util.h" + +static bool arg_debug = false; +static int arg_daemonize = false; +static int arg_resolve_names = 1; +static unsigned arg_children_max; +static int arg_exec_delay; +static usec_t arg_event_timeout_usec = 180 * USEC_PER_SEC; +static usec_t arg_event_timeout_warn_usec = 180 * USEC_PER_SEC / 3; + +typedef struct Manager { + struct udev *udev; + sd_event *event; + Hashmap *workers; + struct udev_list_node events; + const char *cgroup; + pid_t pid; /* the process that originally allocated the manager object */ + + struct udev_rules *rules; + struct udev_list properties; + + struct udev_monitor *monitor; + struct udev_ctrl *ctrl; + struct udev_ctrl_connection *ctrl_conn_blocking; + int fd_inotify; + int worker_watch[2]; + + sd_event_source *ctrl_event; + sd_event_source *uevent_event; + sd_event_source *inotify_event; + + usec_t last_usec; + + bool stop_exec_queue:1; + bool exit:1; +} Manager; + +enum event_state { + EVENT_UNDEF, + EVENT_QUEUED, + EVENT_RUNNING, +}; + +struct event { + struct udev_list_node node; + Manager *manager; + struct udev *udev; + struct udev_device *dev; + struct udev_device *dev_kernel; + struct worker *worker; + enum event_state state; + unsigned long long int delaying_seqnum; + unsigned long long int seqnum; + const char *devpath; + size_t devpath_len; + const char *devpath_old; + dev_t devnum; + int ifindex; + bool is_block; + sd_event_source *timeout_warning; + sd_event_source *timeout; +}; + +static inline struct event *node_to_event(struct udev_list_node *node) { + return container_of(node, struct event, node); +} + +static void event_queue_cleanup(Manager *manager, enum event_state type); + +enum worker_state { + WORKER_UNDEF, + WORKER_RUNNING, + WORKER_IDLE, + WORKER_KILLED, +}; + +struct worker { + Manager *manager; + struct udev_list_node node; + int refcount; + pid_t pid; + struct udev_monitor *monitor; + enum worker_state state; + struct event *event; +}; + +/* passed from worker to main process */ +struct worker_message { +}; + +static void event_free(struct event *event) { + int r; + + if (!event) + return; + + udev_list_node_remove(&event->node); + udev_device_unref(event->dev); + udev_device_unref(event->dev_kernel); + + sd_event_source_unref(event->timeout_warning); + sd_event_source_unref(event->timeout); + + if (event->worker) + event->worker->event = NULL; + + assert(event->manager); + + if (udev_list_node_is_empty(&event->manager->events)) { + /* only clean up the queue from the process that created it */ + if (event->manager->pid == getpid()) { + r = unlink("/run/udev/queue"); + if (r < 0) + log_warning_errno(errno, "could not unlink /run/udev/queue: %m"); + } + } + + free(event); +} + +static void worker_free(struct worker *worker) { + if (!worker) + return; + + assert(worker->manager); + + hashmap_remove(worker->manager->workers, PID_TO_PTR(worker->pid)); + udev_monitor_unref(worker->monitor); + event_free(worker->event); + + free(worker); +} + +static void manager_workers_free(Manager *manager) { + struct worker *worker; + Iterator i; + + assert(manager); + + HASHMAP_FOREACH(worker, manager->workers, i) + worker_free(worker); + + manager->workers = hashmap_free(manager->workers); +} + +static int worker_new(struct worker **ret, Manager *manager, struct udev_monitor *worker_monitor, pid_t pid) { + _cleanup_free_ struct worker *worker = NULL; + int r; + + assert(ret); + assert(manager); + assert(worker_monitor); + assert(pid > 1); + + worker = new0(struct worker, 1); + if (!worker) + return -ENOMEM; + + worker->refcount = 1; + worker->manager = manager; + /* close monitor, but keep address around */ + udev_monitor_disconnect(worker_monitor); + worker->monitor = udev_monitor_ref(worker_monitor); + worker->pid = pid; + + r = hashmap_ensure_allocated(&manager->workers, NULL); + if (r < 0) + return r; + + r = hashmap_put(manager->workers, PID_TO_PTR(pid), worker); + if (r < 0) + return r; + + *ret = worker; + worker = NULL; + + return 0; +} + +static int on_event_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + struct event *event = userdata; + + assert(event); + assert(event->worker); + + kill_and_sigcont(event->worker->pid, SIGKILL); + event->worker->state = WORKER_KILLED; + + log_error("seq %llu '%s' killed", udev_device_get_seqnum(event->dev), event->devpath); + + return 1; +} + +static int on_event_timeout_warning(sd_event_source *s, uint64_t usec, void *userdata) { + struct event *event = userdata; + + assert(event); + + log_warning("seq %llu '%s' is taking a long time", udev_device_get_seqnum(event->dev), event->devpath); + + return 1; +} + +static void worker_attach_event(struct worker *worker, struct event *event) { + sd_event *e; + uint64_t usec; + + assert(worker); + assert(worker->manager); + assert(event); + assert(!event->worker); + assert(!worker->event); + + worker->state = WORKER_RUNNING; + worker->event = event; + event->state = EVENT_RUNNING; + event->worker = worker; + + e = worker->manager->event; + + assert_se(sd_event_now(e, clock_boottime_or_monotonic(), &usec) >= 0); + + (void) sd_event_add_time(e, &event->timeout_warning, clock_boottime_or_monotonic(), + usec + arg_event_timeout_warn_usec, USEC_PER_SEC, on_event_timeout_warning, event); + + (void) sd_event_add_time(e, &event->timeout, clock_boottime_or_monotonic(), + usec + arg_event_timeout_usec, USEC_PER_SEC, on_event_timeout, event); +} + +static void manager_free(Manager *manager) { + if (!manager) + return; + + udev_builtin_exit(manager->udev); + + sd_event_source_unref(manager->ctrl_event); + sd_event_source_unref(manager->uevent_event); + sd_event_source_unref(manager->inotify_event); + + udev_unref(manager->udev); + sd_event_unref(manager->event); + manager_workers_free(manager); + event_queue_cleanup(manager, EVENT_UNDEF); + + udev_monitor_unref(manager->monitor); + udev_ctrl_unref(manager->ctrl); + udev_ctrl_connection_unref(manager->ctrl_conn_blocking); + + udev_list_cleanup(&manager->properties); + udev_rules_unref(manager->rules); + + safe_close(manager->fd_inotify); + safe_close_pair(manager->worker_watch); + + free(manager); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); + +static int worker_send_message(int fd) { + struct worker_message message = {}; + + return loop_write(fd, &message, sizeof(message), false); +} + +static void worker_spawn(Manager *manager, struct event *event) { + struct udev *udev = event->udev; + _cleanup_udev_monitor_unref_ struct udev_monitor *worker_monitor = NULL; + pid_t pid; + int r = 0; + + /* listen for new events */ + worker_monitor = udev_monitor_new_from_netlink(udev, NULL); + if (worker_monitor == NULL) + return; + /* allow the main daemon netlink address to send devices to the worker */ + udev_monitor_allow_unicast_sender(worker_monitor, manager->monitor); + r = udev_monitor_enable_receiving(worker_monitor); + if (r < 0) + log_error_errno(r, "worker: could not enable receiving of device: %m"); + + pid = fork(); + switch (pid) { + case 0: { + struct udev_device *dev = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + int fd_monitor; + _cleanup_close_ int fd_signal = -1, fd_ep = -1; + struct epoll_event ep_signal = { .events = EPOLLIN }; + struct epoll_event ep_monitor = { .events = EPOLLIN }; + sigset_t mask; + + /* take initial device from queue */ + dev = event->dev; + event->dev = NULL; + + unsetenv("NOTIFY_SOCKET"); + + manager_workers_free(manager); + event_queue_cleanup(manager, EVENT_UNDEF); + + manager->monitor = udev_monitor_unref(manager->monitor); + manager->ctrl_conn_blocking = udev_ctrl_connection_unref(manager->ctrl_conn_blocking); + manager->ctrl = udev_ctrl_unref(manager->ctrl); + manager->ctrl_conn_blocking = udev_ctrl_connection_unref(manager->ctrl_conn_blocking); + manager->worker_watch[READ_END] = safe_close(manager->worker_watch[READ_END]); + + manager->ctrl_event = sd_event_source_unref(manager->ctrl_event); + manager->uevent_event = sd_event_source_unref(manager->uevent_event); + manager->inotify_event = sd_event_source_unref(manager->inotify_event); + + manager->event = sd_event_unref(manager->event); + + sigfillset(&mask); + fd_signal = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); + if (fd_signal < 0) { + r = log_error_errno(errno, "error creating signalfd %m"); + goto out; + } + ep_signal.data.fd = fd_signal; + + fd_monitor = udev_monitor_get_fd(worker_monitor); + ep_monitor.data.fd = fd_monitor; + + fd_ep = epoll_create1(EPOLL_CLOEXEC); + if (fd_ep < 0) { + r = log_error_errno(errno, "error creating epoll fd: %m"); + goto out; + } + + if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_signal, &ep_signal) < 0 || + epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_monitor, &ep_monitor) < 0) { + r = log_error_errno(errno, "fail to add fds to epoll: %m"); + goto out; + } + + /* Request TERM signal if parent exits. + Ignore error, not much we can do in that case. */ + (void) prctl(PR_SET_PDEATHSIG, SIGTERM); + + /* Reset OOM score, we only protect the main daemon. */ + write_string_file("/proc/self/oom_score_adj", "0", 0); + + for (;;) { + struct udev_event *udev_event; + int fd_lock = -1; + + assert(dev); + + log_debug("seq %llu running", udev_device_get_seqnum(dev)); + udev_event = udev_event_new(dev); + if (udev_event == NULL) { + r = -ENOMEM; + goto out; + } + + if (arg_exec_delay > 0) + udev_event->exec_delay = arg_exec_delay; + + /* + * Take a shared lock on the device node; this establishes + * a concept of device "ownership" to serialize device + * access. External processes holding an exclusive lock will + * cause udev to skip the event handling; in the case udev + * acquired the lock, the external process can block until + * udev has finished its event handling. + */ + if (!streq_ptr(udev_device_get_action(dev), "remove") && + streq_ptr("block", udev_device_get_subsystem(dev)) && + !startswith(udev_device_get_sysname(dev), "dm-") && + !startswith(udev_device_get_sysname(dev), "md")) { + struct udev_device *d = dev; + + if (streq_ptr("partition", udev_device_get_devtype(d))) + d = udev_device_get_parent(d); + + if (d) { + fd_lock = open(udev_device_get_devnode(d), O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK); + if (fd_lock >= 0 && flock(fd_lock, LOCK_SH|LOCK_NB) < 0) { + log_debug_errno(errno, "Unable to flock(%s), skipping event handling: %m", udev_device_get_devnode(d)); + fd_lock = safe_close(fd_lock); + goto skip; + } + } + } + + /* needed for renaming netifs */ + udev_event->rtnl = rtnl; + + /* apply rules, create node, symlinks */ + udev_event_execute_rules(udev_event, + arg_event_timeout_usec, arg_event_timeout_warn_usec, + &manager->properties, + manager->rules); + + udev_event_execute_run(udev_event, + arg_event_timeout_usec, arg_event_timeout_warn_usec); + + if (udev_event->rtnl) + /* in case rtnl was initialized */ + rtnl = sd_netlink_ref(udev_event->rtnl); + + /* apply/restore inotify watch */ + if (udev_event->inotify_watch) { + udev_watch_begin(udev, dev); + udev_device_update_db(dev); + } + + safe_close(fd_lock); + + /* send processed event back to libudev listeners */ + udev_monitor_send_device(worker_monitor, NULL, dev); + +skip: + log_debug("seq %llu processed", udev_device_get_seqnum(dev)); + + /* send udevd the result of the event execution */ + r = worker_send_message(manager->worker_watch[WRITE_END]); + if (r < 0) + log_error_errno(r, "failed to send result of seq %llu to main daemon: %m", + udev_device_get_seqnum(dev)); + + udev_device_unref(dev); + dev = NULL; + + udev_event_unref(udev_event); + + /* wait for more device messages from main udevd, or term signal */ + while (dev == NULL) { + struct epoll_event ev[4]; + int fdcount; + int i; + + fdcount = epoll_wait(fd_ep, ev, ELEMENTSOF(ev), -1); + if (fdcount < 0) { + if (errno == EINTR) + continue; + r = log_error_errno(errno, "failed to poll: %m"); + goto out; + } + + for (i = 0; i < fdcount; i++) { + if (ev[i].data.fd == fd_monitor && ev[i].events & EPOLLIN) { + dev = udev_monitor_receive_device(worker_monitor); + break; + } else if (ev[i].data.fd == fd_signal && ev[i].events & EPOLLIN) { + struct signalfd_siginfo fdsi; + ssize_t size; + + size = read(fd_signal, &fdsi, sizeof(struct signalfd_siginfo)); + if (size != sizeof(struct signalfd_siginfo)) + continue; + switch (fdsi.ssi_signo) { + case SIGTERM: + goto out; + } + } + } + } + } +out: + udev_device_unref(dev); + manager_free(manager); + log_close(); + _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); + } + case -1: + event->state = EVENT_QUEUED; + log_error_errno(errno, "fork of child failed: %m"); + break; + default: + { + struct worker *worker; + + r = worker_new(&worker, manager, worker_monitor, pid); + if (r < 0) + return; + + worker_attach_event(worker, event); + + log_debug("seq %llu forked new worker ["PID_FMT"]", udev_device_get_seqnum(event->dev), pid); + break; + } + } +} + +static void event_run(Manager *manager, struct event *event) { + struct worker *worker; + Iterator i; + + assert(manager); + assert(event); + + HASHMAP_FOREACH(worker, manager->workers, i) { + ssize_t count; + + if (worker->state != WORKER_IDLE) + continue; + + count = udev_monitor_send_device(manager->monitor, worker->monitor, event->dev); + if (count < 0) { + log_error_errno(errno, "worker ["PID_FMT"] did not accept message %zi (%m), kill it", + worker->pid, count); + kill(worker->pid, SIGKILL); + worker->state = WORKER_KILLED; + continue; + } + worker_attach_event(worker, event); + return; + } + + if (hashmap_size(manager->workers) >= arg_children_max) { + if (arg_children_max > 1) + log_debug("maximum number (%i) of children reached", hashmap_size(manager->workers)); + return; + } + + /* start new worker and pass initial device */ + worker_spawn(manager, event); +} + +static int event_queue_insert(Manager *manager, struct udev_device *dev) { + struct event *event; + int r; + + assert(manager); + assert(dev); + + /* only one process can add events to the queue */ + if (manager->pid == 0) + manager->pid = getpid(); + + assert(manager->pid == getpid()); + + event = new0(struct event, 1); + if (!event) + return -ENOMEM; + + event->udev = udev_device_get_udev(dev); + event->manager = manager; + event->dev = dev; + event->dev_kernel = udev_device_shallow_clone(dev); + udev_device_copy_properties(event->dev_kernel, dev); + event->seqnum = udev_device_get_seqnum(dev); + event->devpath = udev_device_get_devpath(dev); + event->devpath_len = strlen(event->devpath); + event->devpath_old = udev_device_get_devpath_old(dev); + event->devnum = udev_device_get_devnum(dev); + event->is_block = streq("block", udev_device_get_subsystem(dev)); + event->ifindex = udev_device_get_ifindex(dev); + + log_debug("seq %llu queued, '%s' '%s'", udev_device_get_seqnum(dev), + udev_device_get_action(dev), udev_device_get_subsystem(dev)); + + event->state = EVENT_QUEUED; + + if (udev_list_node_is_empty(&manager->events)) { + r = touch("/run/udev/queue"); + if (r < 0) + log_warning_errno(r, "could not touch /run/udev/queue: %m"); + } + + udev_list_node_append(&event->node, &manager->events); + + return 0; +} + +static void manager_kill_workers(Manager *manager) { + struct worker *worker; + Iterator i; + + assert(manager); + + HASHMAP_FOREACH(worker, manager->workers, i) { + if (worker->state == WORKER_KILLED) + continue; + + worker->state = WORKER_KILLED; + kill(worker->pid, SIGTERM); + } +} + +/* lookup event for identical, parent, child device */ +static bool is_devpath_busy(Manager *manager, struct event *event) { + struct udev_list_node *loop; + size_t common; + + /* check if queue contains events we depend on */ + udev_list_node_foreach(loop, &manager->events) { + struct event *loop_event = node_to_event(loop); + + /* we already found a later event, earlier can not block us, no need to check again */ + if (loop_event->seqnum < event->delaying_seqnum) + continue; + + /* event we checked earlier still exists, no need to check again */ + if (loop_event->seqnum == event->delaying_seqnum) + return true; + + /* found ourself, no later event can block us */ + if (loop_event->seqnum >= event->seqnum) + break; + + /* check major/minor */ + if (major(event->devnum) != 0 && event->devnum == loop_event->devnum && event->is_block == loop_event->is_block) + return true; + + /* check network device ifindex */ + if (event->ifindex != 0 && event->ifindex == loop_event->ifindex) + return true; + + /* check our old name */ + if (event->devpath_old != NULL && streq(loop_event->devpath, event->devpath_old)) { + event->delaying_seqnum = loop_event->seqnum; + return true; + } + + /* compare devpath */ + common = MIN(loop_event->devpath_len, event->devpath_len); + + /* one devpath is contained in the other? */ + if (memcmp(loop_event->devpath, event->devpath, common) != 0) + continue; + + /* identical device event found */ + if (loop_event->devpath_len == event->devpath_len) { + /* devices names might have changed/swapped in the meantime */ + if (major(event->devnum) != 0 && (event->devnum != loop_event->devnum || event->is_block != loop_event->is_block)) + continue; + if (event->ifindex != 0 && event->ifindex != loop_event->ifindex) + continue; + event->delaying_seqnum = loop_event->seqnum; + return true; + } + + /* parent device event found */ + if (event->devpath[common] == '/') { + event->delaying_seqnum = loop_event->seqnum; + return true; + } + + /* child device event found */ + if (loop_event->devpath[common] == '/') { + event->delaying_seqnum = loop_event->seqnum; + return true; + } + + /* no matching device */ + continue; + } + + return false; +} + +static int on_exit_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + Manager *manager = userdata; + + assert(manager); + + log_error_errno(ETIMEDOUT, "giving up waiting for workers to finish"); + + sd_event_exit(manager->event, -ETIMEDOUT); + + return 1; +} + +static void manager_exit(Manager *manager) { + uint64_t usec; + int r; + + assert(manager); + + manager->exit = true; + + sd_notify(false, + "STOPPING=1\n" + "STATUS=Starting shutdown..."); + + /* close sources of new events and discard buffered events */ + manager->ctrl_event = sd_event_source_unref(manager->ctrl_event); + manager->ctrl = udev_ctrl_unref(manager->ctrl); + + manager->inotify_event = sd_event_source_unref(manager->inotify_event); + manager->fd_inotify = safe_close(manager->fd_inotify); + + manager->uevent_event = sd_event_source_unref(manager->uevent_event); + manager->monitor = udev_monitor_unref(manager->monitor); + + /* discard queued events and kill workers */ + event_queue_cleanup(manager, EVENT_QUEUED); + manager_kill_workers(manager); + + assert_se(sd_event_now(manager->event, clock_boottime_or_monotonic(), &usec) >= 0); + + r = sd_event_add_time(manager->event, NULL, clock_boottime_or_monotonic(), + usec + 30 * USEC_PER_SEC, USEC_PER_SEC, on_exit_timeout, manager); + if (r < 0) + return; +} + +/* reload requested, HUP signal received, rules changed, builtin changed */ +static void manager_reload(Manager *manager) { + + assert(manager); + + sd_notify(false, + "RELOADING=1\n" + "STATUS=Flushing configuration..."); + + manager_kill_workers(manager); + manager->rules = udev_rules_unref(manager->rules); + udev_builtin_exit(manager->udev); + + sd_notify(false, + "READY=1\n" + "STATUS=Processing..."); +} + +static void event_queue_start(Manager *manager) { + struct udev_list_node *loop; + usec_t usec; + + assert(manager); + + if (udev_list_node_is_empty(&manager->events) || + manager->exit || manager->stop_exec_queue) + return; + + assert_se(sd_event_now(manager->event, clock_boottime_or_monotonic(), &usec) >= 0); + /* check for changed config, every 3 seconds at most */ + if (manager->last_usec == 0 || + (usec - manager->last_usec) > 3 * USEC_PER_SEC) { + if (udev_rules_check_timestamp(manager->rules) || + udev_builtin_validate(manager->udev)) + manager_reload(manager); + + manager->last_usec = usec; + } + + udev_builtin_init(manager->udev); + + if (!manager->rules) { + manager->rules = udev_rules_new(manager->udev, arg_resolve_names); + if (!manager->rules) + return; + } + + udev_list_node_foreach(loop, &manager->events) { + struct event *event = node_to_event(loop); + + if (event->state != EVENT_QUEUED) + continue; + + /* do not start event if parent or child event is still running */ + if (is_devpath_busy(manager, event)) + continue; + + event_run(manager, event); + } +} + +static void event_queue_cleanup(Manager *manager, enum event_state match_type) { + struct udev_list_node *loop, *tmp; + + udev_list_node_foreach_safe(loop, tmp, &manager->events) { + struct event *event = node_to_event(loop); + + if (match_type != EVENT_UNDEF && match_type != event->state) + continue; + + event_free(event); + } +} + +static int on_worker(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Manager *manager = userdata; + + assert(manager); + + for (;;) { + struct worker_message msg; + struct iovec iovec = { + .iov_base = &msg, + .iov_len = sizeof(msg), + }; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; + } control = {}; + struct msghdr msghdr = { + .msg_iov = &iovec, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; + ssize_t size; + struct ucred *ucred = NULL; + struct worker *worker; + + size = recvmsg(fd, &msghdr, MSG_DONTWAIT); + if (size < 0) { + if (errno == EINTR) + continue; + else if (errno == EAGAIN) + /* nothing more to read */ + break; + + return log_error_errno(errno, "failed to receive message: %m"); + } else if (size != sizeof(struct worker_message)) { + log_warning_errno(EIO, "ignoring worker message with invalid size %zi bytes", size); + continue; + } + + CMSG_FOREACH(cmsg, &msghdr) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_CREDENTIALS && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) + ucred = (struct ucred*) CMSG_DATA(cmsg); + } + + if (!ucred || ucred->pid <= 0) { + log_warning_errno(EIO, "ignoring worker message without valid PID"); + continue; + } + + /* lookup worker who sent the signal */ + worker = hashmap_get(manager->workers, PID_TO_PTR(ucred->pid)); + if (!worker) { + log_debug("worker ["PID_FMT"] returned, but is no longer tracked", ucred->pid); + continue; + } + + if (worker->state != WORKER_KILLED) + worker->state = WORKER_IDLE; + + /* worker returned */ + event_free(worker->event); + } + + /* we have free workers, try to schedule events */ + event_queue_start(manager); + + return 1; +} + +static int on_uevent(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Manager *manager = userdata; + struct udev_device *dev; + int r; + + assert(manager); + + dev = udev_monitor_receive_device(manager->monitor); + if (dev) { + udev_device_ensure_usec_initialized(dev, NULL); + r = event_queue_insert(manager, dev); + if (r < 0) + udev_device_unref(dev); + else + /* we have fresh events, try to schedule them */ + event_queue_start(manager); + } + + return 1; +} + +/* receive the udevd message from userspace */ +static int on_ctrl_msg(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Manager *manager = userdata; + _cleanup_udev_ctrl_connection_unref_ struct udev_ctrl_connection *ctrl_conn = NULL; + _cleanup_udev_ctrl_msg_unref_ struct udev_ctrl_msg *ctrl_msg = NULL; + const char *str; + int i; + + assert(manager); + + ctrl_conn = udev_ctrl_get_connection(manager->ctrl); + if (!ctrl_conn) + return 1; + + ctrl_msg = udev_ctrl_receive_msg(ctrl_conn); + if (!ctrl_msg) + return 1; + + i = udev_ctrl_get_set_log_level(ctrl_msg); + if (i >= 0) { + log_debug("udevd message (SET_LOG_LEVEL) received, log_priority=%i", i); + log_set_max_level(i); + manager_kill_workers(manager); + } + + if (udev_ctrl_get_stop_exec_queue(ctrl_msg) > 0) { + log_debug("udevd message (STOP_EXEC_QUEUE) received"); + manager->stop_exec_queue = true; + } + + if (udev_ctrl_get_start_exec_queue(ctrl_msg) > 0) { + log_debug("udevd message (START_EXEC_QUEUE) received"); + manager->stop_exec_queue = false; + event_queue_start(manager); + } + + if (udev_ctrl_get_reload(ctrl_msg) > 0) { + log_debug("udevd message (RELOAD) received"); + manager_reload(manager); + } + + str = udev_ctrl_get_set_env(ctrl_msg); + if (str != NULL) { + _cleanup_free_ char *key = NULL; + + key = strdup(str); + if (key) { + char *val; + + val = strchr(key, '='); + if (val != NULL) { + val[0] = '\0'; + val = &val[1]; + if (val[0] == '\0') { + log_debug("udevd message (ENV) received, unset '%s'", key); + udev_list_entry_add(&manager->properties, key, NULL); + } else { + log_debug("udevd message (ENV) received, set '%s=%s'", key, val); + udev_list_entry_add(&manager->properties, key, val); + } + } else + log_error("wrong key format '%s'", key); + } + manager_kill_workers(manager); + } + + i = udev_ctrl_get_set_children_max(ctrl_msg); + if (i >= 0) { + log_debug("udevd message (SET_MAX_CHILDREN) received, children_max=%i", i); + arg_children_max = i; + } + + if (udev_ctrl_get_ping(ctrl_msg) > 0) + log_debug("udevd message (SYNC) received"); + + if (udev_ctrl_get_exit(ctrl_msg) > 0) { + log_debug("udevd message (EXIT) received"); + manager_exit(manager); + /* keep reference to block the client until we exit + TODO: deal with several blocking exit requests */ + manager->ctrl_conn_blocking = udev_ctrl_connection_ref(ctrl_conn); + } + + return 1; +} + +static int synthesize_change(struct udev_device *dev) { + char filename[UTIL_PATH_SIZE]; + int r; + + if (streq_ptr("block", udev_device_get_subsystem(dev)) && + streq_ptr("disk", udev_device_get_devtype(dev)) && + !startswith(udev_device_get_sysname(dev), "dm-")) { + bool part_table_read = false; + bool has_partitions = false; + int fd; + struct udev *udev = udev_device_get_udev(dev); + _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; + struct udev_list_entry *item; + + /* + * Try to re-read the partition table. This only succeeds if + * none of the devices is busy. The kernel returns 0 if no + * partition table is found, and we will not get an event for + * the disk. + */ + fd = open(udev_device_get_devnode(dev), O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK); + if (fd >= 0) { + r = flock(fd, LOCK_EX|LOCK_NB); + if (r >= 0) + r = ioctl(fd, BLKRRPART, 0); + + close(fd); + if (r >= 0) + part_table_read = true; + } + + /* search for partitions */ + e = udev_enumerate_new(udev); + if (!e) + return -ENOMEM; + + r = udev_enumerate_add_match_parent(e, dev); + if (r < 0) + return r; + + r = udev_enumerate_add_match_subsystem(e, "block"); + if (r < 0) + return r; + + r = udev_enumerate_scan_devices(e); + if (r < 0) + return r; + + udev_list_entry_foreach(item, udev_enumerate_get_list_entry(e)) { + _cleanup_udev_device_unref_ struct udev_device *d = NULL; + + d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); + if (!d) + continue; + + if (!streq_ptr("partition", udev_device_get_devtype(d))) + continue; + + has_partitions = true; + break; + } + + /* + * We have partitions and re-read the table, the kernel already sent + * out a "change" event for the disk, and "remove/add" for all + * partitions. + */ + if (part_table_read && has_partitions) + return 0; + + /* + * We have partitions but re-reading the partition table did not + * work, synthesize "change" for the disk and all partitions. + */ + log_debug("device %s closed, synthesising 'change'", udev_device_get_devnode(dev)); + strscpyl(filename, sizeof(filename), udev_device_get_syspath(dev), "/uevent", NULL); + write_string_file(filename, "change", WRITE_STRING_FILE_CREATE); + + udev_list_entry_foreach(item, udev_enumerate_get_list_entry(e)) { + _cleanup_udev_device_unref_ struct udev_device *d = NULL; + + d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); + if (!d) + continue; + + if (!streq_ptr("partition", udev_device_get_devtype(d))) + continue; + + log_debug("device %s closed, synthesising partition '%s' 'change'", + udev_device_get_devnode(dev), udev_device_get_devnode(d)); + strscpyl(filename, sizeof(filename), udev_device_get_syspath(d), "/uevent", NULL); + write_string_file(filename, "change", WRITE_STRING_FILE_CREATE); + } + + return 0; + } + + log_debug("device %s closed, synthesising 'change'", udev_device_get_devnode(dev)); + strscpyl(filename, sizeof(filename), udev_device_get_syspath(dev), "/uevent", NULL); + write_string_file(filename, "change", WRITE_STRING_FILE_CREATE); + + return 0; +} + +static int on_inotify(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Manager *manager = userdata; + union inotify_event_buffer buffer; + struct inotify_event *e; + ssize_t l; + + assert(manager); + + l = read(fd, &buffer, sizeof(buffer)); + if (l < 0) { + if (errno == EAGAIN || errno == EINTR) + return 1; + + return log_error_errno(errno, "Failed to read inotify fd: %m"); + } + + FOREACH_INOTIFY_EVENT(e, buffer, l) { + _cleanup_udev_device_unref_ struct udev_device *dev = NULL; + + dev = udev_watch_lookup(manager->udev, e->wd); + if (!dev) + continue; + + log_debug("inotify event: %x for %s", e->mask, udev_device_get_devnode(dev)); + if (e->mask & IN_CLOSE_WRITE) { + synthesize_change(dev); + + /* settle might be waiting on us to determine the queue + * state. If we just handled an inotify event, we might have + * generated a "change" event, but we won't have queued up + * the resultant uevent yet. Do that. + */ + on_uevent(NULL, -1, 0, manager); + } else if (e->mask & IN_IGNORED) + udev_watch_end(manager->udev, dev); + } + + return 1; +} + +static int on_sigterm(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + Manager *manager = userdata; + + assert(manager); + + manager_exit(manager); + + return 1; +} + +static int on_sighup(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + Manager *manager = userdata; + + assert(manager); + + manager_reload(manager); + + return 1; +} + +static int on_sigchld(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + Manager *manager = userdata; + + assert(manager); + + for (;;) { + pid_t pid; + int status; + struct worker *worker; + + pid = waitpid(-1, &status, WNOHANG); + if (pid <= 0) + break; + + worker = hashmap_get(manager->workers, PID_TO_PTR(pid)); + if (!worker) { + log_warning("worker ["PID_FMT"] is unknown, ignoring", pid); + continue; + } + + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) == 0) + log_debug("worker ["PID_FMT"] exited", pid); + else + log_warning("worker ["PID_FMT"] exited with return code %i", pid, WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + log_warning("worker ["PID_FMT"] terminated by signal %i (%s)", pid, WTERMSIG(status), strsignal(WTERMSIG(status))); + } else if (WIFSTOPPED(status)) { + log_info("worker ["PID_FMT"] stopped", pid); + continue; + } else if (WIFCONTINUED(status)) { + log_info("worker ["PID_FMT"] continued", pid); + continue; + } else + log_warning("worker ["PID_FMT"] exit with status 0x%04x", pid, status); + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + if (worker->event) { + log_error("worker ["PID_FMT"] failed while handling '%s'", pid, worker->event->devpath); + /* delete state from disk */ + udev_device_delete_db(worker->event->dev); + udev_device_tag_index(worker->event->dev, NULL, false); + /* forward kernel event without amending it */ + udev_monitor_send_device(manager->monitor, NULL, worker->event->dev_kernel); + } + } + + worker_free(worker); + } + + /* we can start new workers, try to schedule events */ + event_queue_start(manager); + + return 1; +} + +static int on_post(sd_event_source *s, void *userdata) { + Manager *manager = userdata; + int r; + + assert(manager); + + if (udev_list_node_is_empty(&manager->events)) { + /* no pending events */ + if (!hashmap_isempty(manager->workers)) { + /* there are idle workers */ + log_debug("cleanup idle workers"); + manager_kill_workers(manager); + } else { + /* we are idle */ + if (manager->exit) { + r = sd_event_exit(manager->event, 0); + if (r < 0) + return r; + } else if (manager->cgroup) + /* cleanup possible left-over processes in our cgroup */ + cg_kill(SYSTEMD_CGROUP_CONTROLLER, manager->cgroup, SIGKILL, false, true, NULL); + } + } + + return 1; +} + +static int listen_fds(int *rctrl, int *rnetlink) { + _cleanup_udev_unref_ struct udev *udev = NULL; + int ctrl_fd = -1, netlink_fd = -1; + int fd, n, r; + + assert(rctrl); + assert(rnetlink); + + n = sd_listen_fds(true); + if (n < 0) + return n; + + for (fd = SD_LISTEN_FDS_START; fd < n + SD_LISTEN_FDS_START; fd++) { + if (sd_is_socket(fd, AF_LOCAL, SOCK_SEQPACKET, -1)) { + if (ctrl_fd >= 0) + return -EINVAL; + ctrl_fd = fd; + continue; + } + + if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, -1)) { + if (netlink_fd >= 0) + return -EINVAL; + netlink_fd = fd; + continue; + } + + return -EINVAL; + } + + if (ctrl_fd < 0) { + _cleanup_udev_ctrl_unref_ struct udev_ctrl *ctrl = NULL; + + udev = udev_new(); + if (!udev) + return -ENOMEM; + + ctrl = udev_ctrl_new(udev); + if (!ctrl) + return log_error_errno(EINVAL, "error initializing udev control socket"); + + r = udev_ctrl_enable_receiving(ctrl); + if (r < 0) + return log_error_errno(EINVAL, "error binding udev control socket"); + + fd = udev_ctrl_get_fd(ctrl); + if (fd < 0) + return log_error_errno(EIO, "could not get ctrl fd"); + + ctrl_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (ctrl_fd < 0) + return log_error_errno(errno, "could not dup ctrl fd: %m"); + } + + if (netlink_fd < 0) { + _cleanup_udev_monitor_unref_ struct udev_monitor *monitor = NULL; + + if (!udev) { + udev = udev_new(); + if (!udev) + return -ENOMEM; + } + + monitor = udev_monitor_new_from_netlink(udev, "kernel"); + if (!monitor) + return log_error_errno(EINVAL, "error initializing netlink socket"); + + (void) udev_monitor_set_receive_buffer_size(monitor, 128 * 1024 * 1024); + + r = udev_monitor_enable_receiving(monitor); + if (r < 0) + return log_error_errno(EINVAL, "error binding netlink socket"); + + fd = udev_monitor_get_fd(monitor); + if (fd < 0) + return log_error_errno(netlink_fd, "could not get uevent fd: %m"); + + netlink_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (ctrl_fd < 0) + return log_error_errno(errno, "could not dup netlink fd: %m"); + } + + *rctrl = ctrl_fd; + *rnetlink = netlink_fd; + + return 0; +} + +/* + * read the kernel command line, in case we need to get into debug mode + * udev.log-priority=<level> syslog priority + * udev.children-max=<number of workers> events are fully serialized if set to 1 + * udev.exec-delay=<number of seconds> delay execution of every executed program + * udev.event-timeout=<number of seconds> seconds to wait before terminating an event + */ +static int parse_proc_cmdline_item(const char *key, const char *value) { + const char *full_key = key; + int r; + + assert(key); + + if (!value) + return 0; + + if (startswith(key, "rd.")) + key += strlen("rd."); + + if (startswith(key, "udev.")) + key += strlen("udev."); + else + return 0; + + if (streq(key, "log-priority")) { + int prio; + + prio = util_log_priority(value); + if (prio < 0) + goto invalid; + log_set_max_level(prio); + } else if (streq(key, "children-max")) { + r = safe_atou(value, &arg_children_max); + if (r < 0) + goto invalid; + } else if (streq(key, "exec-delay")) { + r = safe_atoi(value, &arg_exec_delay); + if (r < 0) + goto invalid; + } else if (streq(key, "event-timeout")) { + r = safe_atou64(value, &arg_event_timeout_usec); + if (r < 0) + goto invalid; + arg_event_timeout_usec *= USEC_PER_SEC; + arg_event_timeout_warn_usec = (arg_event_timeout_usec / 3) ? : 1; + } + + return 0; +invalid: + log_warning("invalid %s ignored: %s", full_key, value); + return 0; +} + +static void help(void) { + printf("%s [OPTIONS...]\n\n" + "Manages devices.\n\n" + " -h --help Print this message\n" + " --version Print version of the program\n" + " --daemon Detach and run in the background\n" + " --debug Enable debug output\n" + " --children-max=INT Set maximum number of workers\n" + " --exec-delay=SECONDS Seconds to wait before executing RUN=\n" + " --event-timeout=SECONDS Seconds to wait before terminating an event\n" + " --resolve-names=early|late|never\n" + " When to resolve users and groups\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + static const struct option options[] = { + { "daemon", no_argument, NULL, 'd' }, + { "debug", no_argument, NULL, 'D' }, + { "children-max", required_argument, NULL, 'c' }, + { "exec-delay", required_argument, NULL, 'e' }, + { "event-timeout", required_argument, NULL, 't' }, + { "resolve-names", required_argument, NULL, 'N' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "c:de:Dt:N:hV", options, NULL)) >= 0) { + int r; + + switch (c) { + + case 'd': + arg_daemonize = true; + break; + case 'c': + r = safe_atou(optarg, &arg_children_max); + if (r < 0) + log_warning("Invalid --children-max ignored: %s", optarg); + break; + case 'e': + r = safe_atoi(optarg, &arg_exec_delay); + if (r < 0) + log_warning("Invalid --exec-delay ignored: %s", optarg); + break; + case 't': + r = safe_atou64(optarg, &arg_event_timeout_usec); + if (r < 0) + log_warning("Invalid --event-timeout ignored: %s", optarg); + else { + arg_event_timeout_usec *= USEC_PER_SEC; + arg_event_timeout_warn_usec = (arg_event_timeout_usec / 3) ? : 1; + } + break; + case 'D': + arg_debug = true; + break; + case 'N': + if (streq(optarg, "early")) { + arg_resolve_names = 1; + } else if (streq(optarg, "late")) { + arg_resolve_names = 0; + } else if (streq(optarg, "never")) { + arg_resolve_names = -1; + } else { + log_error("resolve-names must be early, late or never"); + return 0; + } + break; + case 'h': + help(); + return 0; + case 'V': + printf("%s\n", VERSION); + return 0; + case '?': + return -EINVAL; + default: + assert_not_reached("Unhandled option"); + + } + } + + return 1; +} + +static int manager_new(Manager **ret, int fd_ctrl, int fd_uevent, const char *cgroup) { + _cleanup_(manager_freep) Manager *manager = NULL; + int r, fd_worker, one = 1; + + assert(ret); + assert(fd_ctrl >= 0); + assert(fd_uevent >= 0); + + manager = new0(Manager, 1); + if (!manager) + return log_oom(); + + manager->fd_inotify = -1; + manager->worker_watch[WRITE_END] = -1; + manager->worker_watch[READ_END] = -1; + + manager->udev = udev_new(); + if (!manager->udev) + return log_error_errno(errno, "could not allocate udev context: %m"); + + udev_builtin_init(manager->udev); + + manager->rules = udev_rules_new(manager->udev, arg_resolve_names); + if (!manager->rules) + return log_error_errno(ENOMEM, "error reading rules"); + + udev_list_node_init(&manager->events); + udev_list_init(manager->udev, &manager->properties, true); + + manager->cgroup = cgroup; + + manager->ctrl = udev_ctrl_new_from_fd(manager->udev, fd_ctrl); + if (!manager->ctrl) + return log_error_errno(EINVAL, "error taking over udev control socket"); + + manager->monitor = udev_monitor_new_from_netlink_fd(manager->udev, "kernel", fd_uevent); + if (!manager->monitor) + return log_error_errno(EINVAL, "error taking over netlink socket"); + + /* unnamed socket from workers to the main daemon */ + r = socketpair(AF_LOCAL, SOCK_DGRAM|SOCK_CLOEXEC, 0, manager->worker_watch); + if (r < 0) + return log_error_errno(errno, "error creating socketpair: %m"); + + fd_worker = manager->worker_watch[READ_END]; + + r = setsockopt(fd_worker, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); + if (r < 0) + return log_error_errno(errno, "could not enable SO_PASSCRED: %m"); + + manager->fd_inotify = udev_watch_init(manager->udev); + if (manager->fd_inotify < 0) + return log_error_errno(ENOMEM, "error initializing inotify"); + + udev_watch_restore(manager->udev); + + /* block and listen to all signals on signalfd */ + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGHUP, SIGCHLD, -1) >= 0); + + r = sd_event_default(&manager->event); + if (r < 0) + return log_error_errno(r, "could not allocate event loop: %m"); + + r = sd_event_add_signal(manager->event, NULL, SIGINT, on_sigterm, manager); + if (r < 0) + return log_error_errno(r, "error creating sigint event source: %m"); + + r = sd_event_add_signal(manager->event, NULL, SIGTERM, on_sigterm, manager); + if (r < 0) + return log_error_errno(r, "error creating sigterm event source: %m"); + + r = sd_event_add_signal(manager->event, NULL, SIGHUP, on_sighup, manager); + if (r < 0) + return log_error_errno(r, "error creating sighup event source: %m"); + + r = sd_event_add_signal(manager->event, NULL, SIGCHLD, on_sigchld, manager); + if (r < 0) + return log_error_errno(r, "error creating sigchld event source: %m"); + + r = sd_event_set_watchdog(manager->event, true); + if (r < 0) + return log_error_errno(r, "error creating watchdog event source: %m"); + + r = sd_event_add_io(manager->event, &manager->ctrl_event, fd_ctrl, EPOLLIN, on_ctrl_msg, manager); + if (r < 0) + return log_error_errno(r, "error creating ctrl event source: %m"); + + /* This needs to be after the inotify and uevent handling, to make sure + * that the ping is send back after fully processing the pending uevents + * (including the synthetic ones we may create due to inotify events). + */ + r = sd_event_source_set_priority(manager->ctrl_event, SD_EVENT_PRIORITY_IDLE); + if (r < 0) + return log_error_errno(r, "cold not set IDLE event priority for ctrl event source: %m"); + + r = sd_event_add_io(manager->event, &manager->inotify_event, manager->fd_inotify, EPOLLIN, on_inotify, manager); + if (r < 0) + return log_error_errno(r, "error creating inotify event source: %m"); + + r = sd_event_add_io(manager->event, &manager->uevent_event, fd_uevent, EPOLLIN, on_uevent, manager); + if (r < 0) + return log_error_errno(r, "error creating uevent event source: %m"); + + r = sd_event_add_io(manager->event, NULL, fd_worker, EPOLLIN, on_worker, manager); + if (r < 0) + return log_error_errno(r, "error creating worker event source: %m"); + + r = sd_event_add_post(manager->event, NULL, on_post, manager); + if (r < 0) + return log_error_errno(r, "error creating post event source: %m"); + + *ret = manager; + manager = NULL; + + return 0; +} + +static int run(int fd_ctrl, int fd_uevent, const char *cgroup) { + _cleanup_(manager_freep) Manager *manager = NULL; + int r; + + r = manager_new(&manager, fd_ctrl, fd_uevent, cgroup); + if (r < 0) { + r = log_error_errno(r, "failed to allocate manager object: %m"); + goto exit; + } + + r = udev_rules_apply_static_dev_perms(manager->rules); + if (r < 0) + log_error_errno(r, "failed to apply permissions on static device nodes: %m"); + + (void) sd_notify(false, + "READY=1\n" + "STATUS=Processing..."); + + r = sd_event_loop(manager->event); + if (r < 0) { + log_error_errno(r, "event loop failed: %m"); + goto exit; + } + + sd_event_get_exit_code(manager->event, &r); + +exit: + sd_notify(false, + "STOPPING=1\n" + "STATUS=Shutting down..."); + if (manager) + udev_ctrl_cleanup(manager->ctrl); + return r; +} + +int main(int argc, char *argv[]) { + _cleanup_free_ char *cgroup = NULL; + int fd_ctrl = -1, fd_uevent = -1; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto exit; + + r = parse_proc_cmdline(parse_proc_cmdline_item); + if (r < 0) + log_warning_errno(r, "failed to parse kernel command line, ignoring: %m"); + + if (arg_debug) { + log_set_target(LOG_TARGET_CONSOLE); + log_set_max_level(LOG_DEBUG); + } + + if (getuid() != 0) { + r = log_error_errno(EPERM, "root privileges required"); + goto exit; + } + + if (arg_children_max == 0) { + cpu_set_t cpu_set; + + arg_children_max = 8; + + if (sched_getaffinity(0, sizeof(cpu_set), &cpu_set) == 0) + arg_children_max += CPU_COUNT(&cpu_set) * 2; + + log_debug("set children_max to %u", arg_children_max); + } + + /* set umask before creating any file/directory */ + r = chdir("/"); + if (r < 0) { + r = log_error_errno(errno, "could not change dir to /: %m"); + goto exit; + } + + umask(022); + + r = mac_selinux_init(); + if (r < 0) { + log_error_errno(r, "could not initialize labelling: %m"); + goto exit; + } + + r = mkdir("/run/udev", 0755); + if (r < 0 && errno != EEXIST) { + r = log_error_errno(errno, "could not create /run/udev: %m"); + goto exit; + } + + dev_setup(NULL, UID_INVALID, GID_INVALID); + + if (getppid() == 1) { + /* get our own cgroup, we regularly kill everything udev has left behind + we only do this on systemd systems, and only if we are directly spawned + by PID1. otherwise we are not guaranteed to have a dedicated cgroup */ + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &cgroup); + if (r < 0) { + if (r == -ENOENT || r == -ENOMEDIUM) + log_debug_errno(r, "did not find dedicated cgroup: %m"); + else + log_warning_errno(r, "failed to get cgroup: %m"); + } + } + + r = listen_fds(&fd_ctrl, &fd_uevent); + if (r < 0) { + r = log_error_errno(r, "could not listen on fds: %m"); + goto exit; + } + + if (arg_daemonize) { + pid_t pid; + + log_info("starting version " VERSION); + + /* connect /dev/null to stdin, stdout, stderr */ + if (log_get_max_level() < LOG_DEBUG) + (void) make_null_stdio(); + + pid = fork(); + switch (pid) { + case 0: + break; + case -1: + r = log_error_errno(errno, "fork of daemon failed: %m"); + goto exit; + default: + mac_selinux_finish(); + log_close(); + _exit(EXIT_SUCCESS); + } + + setsid(); + + write_string_file("/proc/self/oom_score_adj", "-1000", 0); + } + + r = run(fd_ctrl, fd_uevent, cgroup); + +exit: + mac_selinux_finish(); + log_close(); + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-udev/udevadm/Makefile b/src/grp-udev/udevadm/Makefile new file mode 100644 index 0000000000..ba3b466935 --- /dev/null +++ b/src/grp-udev/udevadm/Makefile @@ -0,0 +1,45 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +rootbin_PROGRAMS += \ + udevadm + +udevadm_SOURCES = \ + src/udev/udevadm.c \ + src/udev/udevadm-info.c \ + src/udev/udevadm-control.c \ + src/udev/udevadm-monitor.c \ + src/udev/udevadm-hwdb.c \ + src/udev/udevadm-settle.c \ + src/udev/udevadm-trigger.c \ + src/udev/udevadm-test.c \ + src/udev/udevadm-test-builtin.c \ + src/udev/udevadm-util.c \ + src/udev/udevadm-util.h + +udevadm_LDADD = \ + libudev-core.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-udev/udevadm/udevadm-control.c b/src/grp-udev/udevadm/udevadm-control.c new file mode 100644 index 0000000000..989decbe95 --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-control.c @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2005-2011 Kay Sievers <kay@vrfy.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <errno.h> +#include <getopt.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "udev-util.h" +#include "udev.h" + +static void print_help(void) { + printf("%s control COMMAND\n\n" + "Control the udev daemon.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -e --exit Instruct the daemon to cleanup and exit\n" + " -l --log-priority=LEVEL Set the udev log level for the daemon\n" + " -s --stop-exec-queue Do not execute events, queue only\n" + " -S --start-exec-queue Execute events, flush queue\n" + " -R --reload Reload rules and databases\n" + " -p --property=KEY=VALUE Set a global property for all events\n" + " -m --children-max=N Maximum number of children\n" + " --timeout=SECONDS Maximum time to block for a reply\n" + , program_invocation_short_name); +} + +static int adm_control(struct udev *udev, int argc, char *argv[]) { + _cleanup_udev_ctrl_unref_ struct udev_ctrl *uctrl = NULL; + int timeout = 60; + int rc = 1, c; + + static const struct option options[] = { + { "exit", no_argument, NULL, 'e' }, + { "log-priority", required_argument, NULL, 'l' }, + { "stop-exec-queue", no_argument, NULL, 's' }, + { "start-exec-queue", no_argument, NULL, 'S' }, + { "reload", no_argument, NULL, 'R' }, + { "reload-rules", no_argument, NULL, 'R' }, /* alias for -R */ + { "property", required_argument, NULL, 'p' }, + { "env", required_argument, NULL, 'p' }, /* alias for -p */ + { "children-max", required_argument, NULL, 'm' }, + { "timeout", required_argument, NULL, 't' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + + if (getuid() != 0) { + fprintf(stderr, "root privileges required\n"); + return 1; + } + + uctrl = udev_ctrl_new(udev); + if (uctrl == NULL) + return 2; + + while ((c = getopt_long(argc, argv, "el:sSRp:m:h", options, NULL)) >= 0) + switch (c) { + case 'e': + if (udev_ctrl_send_exit(uctrl, timeout) < 0) + rc = 2; + else + rc = 0; + break; + case 'l': { + int i; + + i = util_log_priority(optarg); + if (i < 0) { + fprintf(stderr, "invalid number '%s'\n", optarg); + return rc; + } + if (udev_ctrl_send_set_log_level(uctrl, util_log_priority(optarg), timeout) < 0) + rc = 2; + else + rc = 0; + break; + } + case 's': + if (udev_ctrl_send_stop_exec_queue(uctrl, timeout) < 0) + rc = 2; + else + rc = 0; + break; + case 'S': + if (udev_ctrl_send_start_exec_queue(uctrl, timeout) < 0) + rc = 2; + else + rc = 0; + break; + case 'R': + if (udev_ctrl_send_reload(uctrl, timeout) < 0) + rc = 2; + else + rc = 0; + break; + case 'p': + if (strchr(optarg, '=') == NULL) { + fprintf(stderr, "expect <KEY>=<value> instead of '%s'\n", optarg); + return rc; + } + if (udev_ctrl_send_set_env(uctrl, optarg, timeout) < 0) + rc = 2; + else + rc = 0; + break; + case 'm': { + char *endp; + int i; + + i = strtoul(optarg, &endp, 0); + if (endp[0] != '\0' || i < 1) { + fprintf(stderr, "invalid number '%s'\n", optarg); + return rc; + } + if (udev_ctrl_send_set_children_max(uctrl, i, timeout) < 0) + rc = 2; + else + rc = 0; + break; + } + case 't': { + int seconds; + + seconds = atoi(optarg); + if (seconds >= 0) + timeout = seconds; + else + fprintf(stderr, "invalid timeout value\n"); + break; + } + case 'h': + print_help(); + rc = 0; + break; + } + + if (optind < argc) + fprintf(stderr, "Extraneous argument: %s\n", argv[optind]); + else if (optind == 1) + fprintf(stderr, "Option missing\n"); + return rc; +} + +const struct udevadm_cmd udevadm_control = { + .name = "control", + .cmd = adm_control, + .help = "Control the udev daemon", +}; diff --git a/src/grp-udev/udevadm/udevadm-hwdb.c b/src/grp-udev/udevadm/udevadm-hwdb.c new file mode 100644 index 0000000000..948ad0f5a5 --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-hwdb.c @@ -0,0 +1,692 @@ +/*** + This file is part of systemd. + + Copyright 2012 Kay Sievers <kay@vrfy.org> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <ctype.h> +#include <getopt.h> +#include <stdlib.h> +#include <string.h> + +#include "alloc-util.h" +#include "conf-files.h" +#include "fileio.h" +#include "fs-util.h" +#include "hwdb-internal.h" +#include "hwdb-util.h" +#include "strbuf.h" +#include "string-util.h" +#include "udev.h" +#include "util.h" + +/* + * Generic udev properties, key/value database based on modalias strings. + * Uses a Patricia/radix trie to index all matches for efficient lookup. + */ + +static const char * const conf_file_dirs[] = { + "/etc/udev/hwdb.d", + UDEVLIBEXECDIR "/hwdb.d", + NULL +}; + +/* in-memory trie objects */ +struct trie { + struct trie_node *root; + struct strbuf *strings; + + size_t nodes_count; + size_t children_count; + size_t values_count; +}; + +struct trie_node { + /* prefix, common part for all children of this node */ + size_t prefix_off; + + /* sorted array of pointers to children nodes */ + struct trie_child_entry *children; + uint8_t children_count; + + /* sorted array of key/value pairs */ + struct trie_value_entry *values; + size_t values_count; +}; + +/* children array item with char (0-255) index */ +struct trie_child_entry { + uint8_t c; + struct trie_node *child; +}; + +/* value array item with key/value pairs */ +struct trie_value_entry { + size_t key_off; + size_t value_off; +}; + +static int trie_children_cmp(const void *v1, const void *v2) { + const struct trie_child_entry *n1 = v1; + const struct trie_child_entry *n2 = v2; + + return n1->c - n2->c; +} + +static int node_add_child(struct trie *trie, struct trie_node *node, struct trie_node *node_child, uint8_t c) { + struct trie_child_entry *child; + + /* extend array, add new entry, sort for bisection */ + child = realloc(node->children, (node->children_count + 1) * sizeof(struct trie_child_entry)); + if (!child) + return -ENOMEM; + + node->children = child; + trie->children_count++; + node->children[node->children_count].c = c; + node->children[node->children_count].child = node_child; + node->children_count++; + qsort(node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp); + trie->nodes_count++; + + return 0; +} + +static struct trie_node *node_lookup(const struct trie_node *node, uint8_t c) { + struct trie_child_entry *child; + struct trie_child_entry search; + + search.c = c; + child = bsearch(&search, node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp); + if (child) + return child->child; + return NULL; +} + +static void trie_node_cleanup(struct trie_node *node) { + size_t i; + + for (i = 0; i < node->children_count; i++) + trie_node_cleanup(node->children[i].child); + free(node->children); + free(node->values); + free(node); +} + +static int trie_values_cmp(const void *v1, const void *v2, void *arg) { + const struct trie_value_entry *val1 = v1; + const struct trie_value_entry *val2 = v2; + struct trie *trie = arg; + + return strcmp(trie->strings->buf + val1->key_off, + trie->strings->buf + val2->key_off); +} + +static int trie_node_add_value(struct trie *trie, struct trie_node *node, + const char *key, const char *value) { + ssize_t k, v; + struct trie_value_entry *val; + + k = strbuf_add_string(trie->strings, key, strlen(key)); + if (k < 0) + return k; + v = strbuf_add_string(trie->strings, value, strlen(value)); + if (v < 0) + return v; + + if (node->values_count) { + struct trie_value_entry search = { + .key_off = k, + .value_off = v, + }; + + val = xbsearch_r(&search, node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie); + if (val) { + /* replace existing earlier key with new value */ + val->value_off = v; + return 0; + } + } + + /* extend array, add new entry, sort for bisection */ + val = realloc(node->values, (node->values_count + 1) * sizeof(struct trie_value_entry)); + if (!val) + return -ENOMEM; + trie->values_count++; + node->values = val; + node->values[node->values_count].key_off = k; + node->values[node->values_count].value_off = v; + node->values_count++; + qsort_r(node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie); + return 0; +} + +static int trie_insert(struct trie *trie, struct trie_node *node, const char *search, + const char *key, const char *value) { + size_t i = 0; + int err = 0; + + for (;;) { + size_t p; + uint8_t c; + struct trie_node *child; + + for (p = 0; (c = trie->strings->buf[node->prefix_off + p]); p++) { + _cleanup_free_ char *s = NULL; + ssize_t off; + _cleanup_free_ struct trie_node *new_child = NULL; + + if (c == search[i + p]) + continue; + + /* split node */ + new_child = new0(struct trie_node, 1); + if (!new_child) + return -ENOMEM; + + /* move values from parent to child */ + new_child->prefix_off = node->prefix_off + p+1; + new_child->children = node->children; + new_child->children_count = node->children_count; + new_child->values = node->values; + new_child->values_count = node->values_count; + + /* update parent; use strdup() because the source gets realloc()d */ + s = strndup(trie->strings->buf + node->prefix_off, p); + if (!s) + return -ENOMEM; + + off = strbuf_add_string(trie->strings, s, p); + if (off < 0) + return off; + + node->prefix_off = off; + node->children = NULL; + node->children_count = 0; + node->values = NULL; + node->values_count = 0; + err = node_add_child(trie, node, new_child, c); + if (err) + return err; + + new_child = NULL; /* avoid cleanup */ + break; + } + i += p; + + c = search[i]; + if (c == '\0') + return trie_node_add_value(trie, node, key, value); + + child = node_lookup(node, c); + if (!child) { + ssize_t off; + + /* new child */ + child = new0(struct trie_node, 1); + if (!child) + return -ENOMEM; + + off = strbuf_add_string(trie->strings, search + i+1, strlen(search + i+1)); + if (off < 0) { + free(child); + return off; + } + + child->prefix_off = off; + err = node_add_child(trie, node, child, c); + if (err) { + free(child); + return err; + } + + return trie_node_add_value(trie, child, key, value); + } + + node = child; + i++; + } +} + +struct trie_f { + FILE *f; + struct trie *trie; + uint64_t strings_off; + + uint64_t nodes_count; + uint64_t children_count; + uint64_t values_count; +}; + +/* calculate the storage space for the nodes, children arrays, value arrays */ +static void trie_store_nodes_size(struct trie_f *trie, struct trie_node *node) { + uint64_t i; + + for (i = 0; i < node->children_count; i++) + trie_store_nodes_size(trie, node->children[i].child); + + trie->strings_off += sizeof(struct trie_node_f); + for (i = 0; i < node->children_count; i++) + trie->strings_off += sizeof(struct trie_child_entry_f); + for (i = 0; i < node->values_count; i++) + trie->strings_off += sizeof(struct trie_value_entry_f); +} + +static int64_t trie_store_nodes(struct trie_f *trie, struct trie_node *node) { + uint64_t i; + struct trie_node_f n = { + .prefix_off = htole64(trie->strings_off + node->prefix_off), + .children_count = node->children_count, + .values_count = htole64(node->values_count), + }; + struct trie_child_entry_f *children = NULL; + int64_t node_off; + + if (node->children_count) { + children = new0(struct trie_child_entry_f, node->children_count); + if (!children) + return -ENOMEM; + } + + /* post-order recursion */ + for (i = 0; i < node->children_count; i++) { + int64_t child_off; + + child_off = trie_store_nodes(trie, node->children[i].child); + if (child_off < 0) { + free(children); + return child_off; + } + children[i].c = node->children[i].c; + children[i].child_off = htole64(child_off); + } + + /* write node */ + node_off = ftello(trie->f); + fwrite(&n, sizeof(struct trie_node_f), 1, trie->f); + trie->nodes_count++; + + /* append children array */ + if (node->children_count) { + fwrite(children, sizeof(struct trie_child_entry_f), node->children_count, trie->f); + trie->children_count += node->children_count; + free(children); + } + + /* append values array */ + for (i = 0; i < node->values_count; i++) { + struct trie_value_entry_f v = { + .key_off = htole64(trie->strings_off + node->values[i].key_off), + .value_off = htole64(trie->strings_off + node->values[i].value_off), + }; + + fwrite(&v, sizeof(struct trie_value_entry_f), 1, trie->f); + trie->values_count++; + } + + return node_off; +} + +static int trie_store(struct trie *trie, const char *filename) { + struct trie_f t = { + .trie = trie, + }; + _cleanup_free_ char *filename_tmp = NULL; + int64_t pos; + int64_t root_off; + int64_t size; + struct trie_header_f h = { + .signature = HWDB_SIG, + .tool_version = htole64(atoi(VERSION)), + .header_size = htole64(sizeof(struct trie_header_f)), + .node_size = htole64(sizeof(struct trie_node_f)), + .child_entry_size = htole64(sizeof(struct trie_child_entry_f)), + .value_entry_size = htole64(sizeof(struct trie_value_entry_f)), + }; + int err; + + /* calculate size of header, nodes, children entries, value entries */ + t.strings_off = sizeof(struct trie_header_f); + trie_store_nodes_size(&t, trie->root); + + err = fopen_temporary(filename , &t.f, &filename_tmp); + if (err < 0) + return err; + fchmod(fileno(t.f), 0444); + + /* write nodes */ + err = fseeko(t.f, sizeof(struct trie_header_f), SEEK_SET); + if (err < 0) { + fclose(t.f); + unlink_noerrno(filename_tmp); + return -errno; + } + root_off = trie_store_nodes(&t, trie->root); + h.nodes_root_off = htole64(root_off); + pos = ftello(t.f); + h.nodes_len = htole64(pos - sizeof(struct trie_header_f)); + + /* write string buffer */ + fwrite(trie->strings->buf, trie->strings->len, 1, t.f); + h.strings_len = htole64(trie->strings->len); + + /* write header */ + size = ftello(t.f); + h.file_size = htole64(size); + err = fseeko(t.f, 0, SEEK_SET); + if (err < 0) { + fclose(t.f); + unlink_noerrno(filename_tmp); + return -errno; + } + fwrite(&h, sizeof(struct trie_header_f), 1, t.f); + err = ferror(t.f); + if (err) + err = -errno; + fclose(t.f); + if (err < 0 || rename(filename_tmp, filename) < 0) { + unlink_noerrno(filename_tmp); + return err < 0 ? err : -errno; + } + + log_debug("=== trie on-disk ==="); + log_debug("size: %8"PRIi64" bytes", size); + log_debug("header: %8zu bytes", sizeof(struct trie_header_f)); + log_debug("nodes: %8"PRIu64" bytes (%8"PRIu64")", + t.nodes_count * sizeof(struct trie_node_f), t.nodes_count); + log_debug("child pointers: %8"PRIu64" bytes (%8"PRIu64")", + t.children_count * sizeof(struct trie_child_entry_f), t.children_count); + log_debug("value pointers: %8"PRIu64" bytes (%8"PRIu64")", + t.values_count * sizeof(struct trie_value_entry_f), t.values_count); + log_debug("string store: %8zu bytes", trie->strings->len); + log_debug("strings start: %8"PRIu64, t.strings_off); + + return 0; +} + +static int insert_data(struct trie *trie, struct udev_list *match_list, + char *line, const char *filename) { + char *value; + struct udev_list_entry *entry; + + value = strchr(line, '='); + if (!value) { + log_error("Error, key/value pair expected but got '%s' in '%s':", line, filename); + return -EINVAL; + } + + value[0] = '\0'; + value++; + + /* libudev requires properties to start with a space */ + while (isblank(line[0]) && isblank(line[1])) + line++; + + if (line[0] == '\0' || value[0] == '\0') { + log_error("Error, empty key or value '%s' in '%s':", line, filename); + return -EINVAL; + } + + udev_list_entry_foreach(entry, udev_list_get_entry(match_list)) + trie_insert(trie, trie->root, udev_list_entry_get_name(entry), line, value); + + return 0; +} + +static int import_file(struct udev *udev, struct trie *trie, const char *filename) { + enum { + HW_MATCH, + HW_DATA, + HW_NONE, + } state = HW_NONE; + FILE *f; + char line[LINE_MAX]; + struct udev_list match_list; + + udev_list_init(udev, &match_list, false); + + f = fopen(filename, "re"); + if (f == NULL) + return -errno; + + while (fgets(line, sizeof(line), f)) { + size_t len; + char *pos; + + /* comment line */ + if (line[0] == '#') + continue; + + /* strip trailing comment */ + pos = strchr(line, '#'); + if (pos) + pos[0] = '\0'; + + /* strip trailing whitespace */ + len = strlen(line); + while (len > 0 && isspace(line[len-1])) + len--; + line[len] = '\0'; + + switch (state) { + case HW_NONE: + if (len == 0) + break; + + if (line[0] == ' ') { + log_error("Error, MATCH expected but got '%s' in '%s':", line, filename); + break; + } + + /* start of record, first match */ + state = HW_MATCH; + udev_list_entry_add(&match_list, line, NULL); + break; + + case HW_MATCH: + if (len == 0) { + log_error("Error, DATA expected but got empty line in '%s':", filename); + state = HW_NONE; + udev_list_cleanup(&match_list); + break; + } + + /* another match */ + if (line[0] != ' ') { + udev_list_entry_add(&match_list, line, NULL); + break; + } + + /* first data */ + state = HW_DATA; + insert_data(trie, &match_list, line, filename); + break; + + case HW_DATA: + /* end of record */ + if (len == 0) { + state = HW_NONE; + udev_list_cleanup(&match_list); + break; + } + + if (line[0] != ' ') { + log_error("Error, DATA expected but got '%s' in '%s':", line, filename); + state = HW_NONE; + udev_list_cleanup(&match_list); + break; + } + + insert_data(trie, &match_list, line, filename); + break; + }; + } + + fclose(f); + udev_list_cleanup(&match_list); + return 0; +} + +static void help(void) { + printf("Usage: udevadm hwdb OPTIONS\n" + " -u,--update update the hardware database\n" + " --usr generate in " UDEVLIBEXECDIR " instead of /etc/udev\n" + " -t,--test=MODALIAS query database and print result\n" + " -r,--root=PATH alternative root path in the filesystem\n" + " -h,--help\n\n"); +} + +static int adm_hwdb(struct udev *udev, int argc, char *argv[]) { + enum { + ARG_USR = 0x100, + }; + + static const struct option options[] = { + { "update", no_argument, NULL, 'u' }, + { "usr", no_argument, NULL, ARG_USR }, + { "test", required_argument, NULL, 't' }, + { "root", required_argument, NULL, 'r' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + const char *test = NULL; + const char *root = ""; + const char *hwdb_bin_dir = "/etc/udev"; + bool update = false; + struct trie *trie = NULL; + int err, c; + int rc = EXIT_SUCCESS; + + while ((c = getopt_long(argc, argv, "ut:r:h", options, NULL)) >= 0) + switch(c) { + case 'u': + update = true; + break; + case ARG_USR: + hwdb_bin_dir = UDEVLIBEXECDIR; + break; + case 't': + test = optarg; + break; + case 'r': + root = optarg; + break; + case 'h': + help(); + return EXIT_SUCCESS; + case '?': + return EXIT_FAILURE; + default: + assert_not_reached("Unknown option"); + } + + if (!update && !test) { + log_error("Either --update or --test must be used"); + return EXIT_FAILURE; + } + + if (update) { + char **files, **f; + _cleanup_free_ char *hwdb_bin = NULL; + + trie = new0(struct trie, 1); + if (!trie) { + rc = EXIT_FAILURE; + goto out; + } + + /* string store */ + trie->strings = strbuf_new(); + if (!trie->strings) { + rc = EXIT_FAILURE; + goto out; + } + + /* index */ + trie->root = new0(struct trie_node, 1); + if (!trie->root) { + rc = EXIT_FAILURE; + goto out; + } + trie->nodes_count++; + + err = conf_files_list_strv(&files, ".hwdb", root, conf_file_dirs); + if (err < 0) { + log_error_errno(err, "failed to enumerate hwdb files: %m"); + rc = EXIT_FAILURE; + goto out; + } + STRV_FOREACH(f, files) { + log_debug("reading file '%s'", *f); + import_file(udev, trie, *f); + } + strv_free(files); + + strbuf_complete(trie->strings); + + log_debug("=== trie in-memory ==="); + log_debug("nodes: %8zu bytes (%8zu)", + trie->nodes_count * sizeof(struct trie_node), trie->nodes_count); + log_debug("children arrays: %8zu bytes (%8zu)", + trie->children_count * sizeof(struct trie_child_entry), trie->children_count); + log_debug("values arrays: %8zu bytes (%8zu)", + trie->values_count * sizeof(struct trie_value_entry), trie->values_count); + log_debug("strings: %8zu bytes", + trie->strings->len); + log_debug("strings incoming: %8zu bytes (%8zu)", + trie->strings->in_len, trie->strings->in_count); + log_debug("strings dedup'ed: %8zu bytes (%8zu)", + trie->strings->dedup_len, trie->strings->dedup_count); + + hwdb_bin = strjoin(root, "/", hwdb_bin_dir, "/hwdb.bin", NULL); + if (!hwdb_bin) { + rc = EXIT_FAILURE; + goto out; + } + mkdir_parents(hwdb_bin, 0755); + err = trie_store(trie, hwdb_bin); + if (err < 0) { + log_error_errno(err, "Failure writing database %s: %m", hwdb_bin); + rc = EXIT_FAILURE; + } + } + + if (test) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + int r; + + r = sd_hwdb_new(&hwdb); + if (r >= 0) { + const char *key, *value; + + SD_HWDB_FOREACH_PROPERTY(hwdb, test, key, value) + printf("%s=%s\n", key, value); + } + } +out: + if (trie) { + if (trie->root) + trie_node_cleanup(trie->root); + strbuf_cleanup(trie->strings); + free(trie); + } + return rc; +} + +const struct udevadm_cmd udevadm_hwdb = { + .name = "hwdb", + .cmd = adm_hwdb, +}; diff --git a/src/grp-udev/udevadm/udevadm-info.c b/src/grp-udev/udevadm/udevadm-info.c new file mode 100644 index 0000000000..7182668f23 --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-info.c @@ -0,0 +1,494 @@ +/* + * Copyright (C) 2004-2009 Kay Sievers <kay@vrfy.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "fd-util.h" +#include "string-util.h" +#include "udev-util.h" +#include "udev.h" +#include "udevadm-util.h" + +static bool skip_attribute(const char *name) { + static const char* const skip[] = { + "uevent", + "dev", + "modalias", + "resource", + "driver", + "subsystem", + "module", + }; + unsigned int i; + + for (i = 0; i < ELEMENTSOF(skip); i++) + if (streq(name, skip[i])) + return true; + return false; +} + +static void print_all_attributes(struct udev_device *device, const char *key) { + struct udev_list_entry *sysattr; + + udev_list_entry_foreach(sysattr, udev_device_get_sysattr_list_entry(device)) { + const char *name; + const char *value; + size_t len; + + name = udev_list_entry_get_name(sysattr); + if (skip_attribute(name)) + continue; + + value = udev_device_get_sysattr_value(device, name); + if (value == NULL) + continue; + + /* skip any values that look like a path */ + if (value[0] == '/') + continue; + + /* skip nonprintable attributes */ + len = strlen(value); + while (len > 0 && isprint(value[len-1])) + len--; + if (len > 0) + continue; + + printf(" %s{%s}==\"%s\"\n", key, name, value); + } + printf("\n"); +} + +static int print_device_chain(struct udev_device *device) { + struct udev_device *device_parent; + const char *str; + + printf("\n" + "Udevadm info starts with the device specified by the devpath and then\n" + "walks up the chain of parent devices. It prints for every device\n" + "found, all possible attributes in the udev rules key format.\n" + "A rule to match, can be composed by the attributes of the device\n" + "and the attributes from one single parent device.\n" + "\n"); + + printf(" looking at device '%s':\n", udev_device_get_devpath(device)); + printf(" KERNEL==\"%s\"\n", udev_device_get_sysname(device)); + str = udev_device_get_subsystem(device); + if (str == NULL) + str = ""; + printf(" SUBSYSTEM==\"%s\"\n", str); + str = udev_device_get_driver(device); + if (str == NULL) + str = ""; + printf(" DRIVER==\"%s\"\n", str); + print_all_attributes(device, "ATTR"); + + device_parent = device; + do { + device_parent = udev_device_get_parent(device_parent); + if (device_parent == NULL) + break; + printf(" looking at parent device '%s':\n", udev_device_get_devpath(device_parent)); + printf(" KERNELS==\"%s\"\n", udev_device_get_sysname(device_parent)); + str = udev_device_get_subsystem(device_parent); + if (str == NULL) + str = ""; + printf(" SUBSYSTEMS==\"%s\"\n", str); + str = udev_device_get_driver(device_parent); + if (str == NULL) + str = ""; + printf(" DRIVERS==\"%s\"\n", str); + print_all_attributes(device_parent, "ATTRS"); + } while (device_parent != NULL); + + return 0; +} + +static void print_record(struct udev_device *device) { + const char *str; + int i; + struct udev_list_entry *list_entry; + + printf("P: %s\n", udev_device_get_devpath(device)); + + str = udev_device_get_devnode(device); + if (str != NULL) + printf("N: %s\n", str + strlen("/dev/")); + + i = udev_device_get_devlink_priority(device); + if (i != 0) + printf("L: %i\n", i); + + udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(device)) + printf("S: %s\n", udev_list_entry_get_name(list_entry) + strlen("/dev/")); + + udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(device)) + printf("E: %s=%s\n", + udev_list_entry_get_name(list_entry), + udev_list_entry_get_value(list_entry)); + printf("\n"); +} + +static int stat_device(const char *name, bool export, const char *prefix) { + struct stat statbuf; + + if (stat(name, &statbuf) != 0) + return -1; + + if (export) { + if (prefix == NULL) + prefix = "INFO_"; + printf("%sMAJOR=%u\n" + "%sMINOR=%u\n", + prefix, major(statbuf.st_dev), + prefix, minor(statbuf.st_dev)); + } else + printf("%u:%u\n", major(statbuf.st_dev), minor(statbuf.st_dev)); + return 0; +} + +static int export_devices(struct udev *udev) { + struct udev_enumerate *udev_enumerate; + struct udev_list_entry *list_entry; + + udev_enumerate = udev_enumerate_new(udev); + if (udev_enumerate == NULL) + return -1; + udev_enumerate_scan_devices(udev_enumerate); + udev_list_entry_foreach(list_entry, udev_enumerate_get_list_entry(udev_enumerate)) { + struct udev_device *device; + + device = udev_device_new_from_syspath(udev, udev_list_entry_get_name(list_entry)); + if (device != NULL) { + print_record(device); + udev_device_unref(device); + } + } + udev_enumerate_unref(udev_enumerate); + return 0; +} + +static void cleanup_dir(DIR *dir, mode_t mask, int depth) { + struct dirent *dent; + + if (depth <= 0) + return; + + for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { + struct stat stats; + + if (dent->d_name[0] == '.') + continue; + if (fstatat(dirfd(dir), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) != 0) + continue; + if ((stats.st_mode & mask) != 0) + continue; + if (S_ISDIR(stats.st_mode)) { + _cleanup_closedir_ DIR *dir2; + + dir2 = fdopendir(openat(dirfd(dir), dent->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC)); + if (dir2 != NULL) + cleanup_dir(dir2, mask, depth-1); + + (void) unlinkat(dirfd(dir), dent->d_name, AT_REMOVEDIR); + } else + (void) unlinkat(dirfd(dir), dent->d_name, 0); + } +} + +static void cleanup_db(struct udev *udev) { + DIR *dir; + + unlink("/run/udev/queue.bin"); + + dir = opendir("/run/udev/data"); + if (dir != NULL) { + cleanup_dir(dir, S_ISVTX, 1); + closedir(dir); + } + + dir = opendir("/run/udev/links"); + if (dir != NULL) { + cleanup_dir(dir, 0, 2); + closedir(dir); + } + + dir = opendir("/run/udev/tags"); + if (dir != NULL) { + cleanup_dir(dir, 0, 2); + closedir(dir); + } + + dir = opendir("/run/udev/static_node-tags"); + if (dir != NULL) { + cleanup_dir(dir, 0, 2); + closedir(dir); + } + + dir = opendir("/run/udev/watch"); + if (dir != NULL) { + cleanup_dir(dir, 0, 1); + closedir(dir); + } +} + +static void help(void) { + + printf("%s info [OPTIONS] [DEVPATH|FILE]\n\n" + "Query sysfs or the udev database.\n\n" + " -h --help Print this message\n" + " --version Print version of the program\n" + " -q --query=TYPE Query device information:\n" + " name Name of device node\n" + " symlink Pointing to node\n" + " path sysfs device path\n" + " property The device properties\n" + " all All values\n" + " -p --path=SYSPATH sysfs device path used for query or attribute walk\n" + " -n --name=NAME Node or symlink name used for query or attribute walk\n" + " -r --root Prepend dev directory to path names\n" + " -a --attribute-walk Print all key matches walking along the chain\n" + " of parent devices\n" + " -d --device-id-of-file=FILE Print major:minor of device containing this file\n" + " -x --export Export key/value pairs\n" + " -P --export-prefix Export the key name with a prefix\n" + " -e --export-db Export the content of the udev database\n" + " -c --cleanup-db Clean up the udev database\n" + , program_invocation_short_name); +} + +static int uinfo(struct udev *udev, int argc, char *argv[]) { + _cleanup_udev_device_unref_ struct udev_device *device = NULL; + bool root = 0; + bool export = 0; + const char *export_prefix = NULL; + char name[UTIL_PATH_SIZE]; + struct udev_list_entry *list_entry; + int c; + + static const struct option options[] = { + { "name", required_argument, NULL, 'n' }, + { "path", required_argument, NULL, 'p' }, + { "query", required_argument, NULL, 'q' }, + { "attribute-walk", no_argument, NULL, 'a' }, + { "cleanup-db", no_argument, NULL, 'c' }, + { "export-db", no_argument, NULL, 'e' }, + { "root", no_argument, NULL, 'r' }, + { "device-id-of-file", required_argument, NULL, 'd' }, + { "export", no_argument, NULL, 'x' }, + { "export-prefix", required_argument, NULL, 'P' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + + enum action_type { + ACTION_QUERY, + ACTION_ATTRIBUTE_WALK, + ACTION_DEVICE_ID_FILE, + } action = ACTION_QUERY; + + enum query_type { + QUERY_NAME, + QUERY_PATH, + QUERY_SYMLINK, + QUERY_PROPERTY, + QUERY_ALL, + } query = QUERY_ALL; + + while ((c = getopt_long(argc, argv, "aced:n:p:q:rxP:RVh", options, NULL)) >= 0) + switch (c) { + case 'n': { + if (device != NULL) { + fprintf(stderr, "device already specified\n"); + return 2; + } + + device = find_device(udev, optarg, "/dev/"); + if (device == NULL) { + fprintf(stderr, "device node not found\n"); + return 2; + } + break; + } + case 'p': + if (device != NULL) { + fprintf(stderr, "device already specified\n"); + return 2; + } + + device = find_device(udev, optarg, "/sys"); + if (device == NULL) { + fprintf(stderr, "syspath not found\n"); + return 2; + } + break; + case 'q': + action = ACTION_QUERY; + if (streq(optarg, "property") || streq(optarg, "env")) + query = QUERY_PROPERTY; + else if (streq(optarg, "name")) + query = QUERY_NAME; + else if (streq(optarg, "symlink")) + query = QUERY_SYMLINK; + else if (streq(optarg, "path")) + query = QUERY_PATH; + else if (streq(optarg, "all")) + query = QUERY_ALL; + else { + fprintf(stderr, "unknown query type\n"); + return 3; + } + break; + case 'r': + root = true; + break; + case 'd': + action = ACTION_DEVICE_ID_FILE; + strscpy(name, sizeof(name), optarg); + break; + case 'a': + action = ACTION_ATTRIBUTE_WALK; + break; + case 'e': + export_devices(udev); + return 0; + case 'c': + cleanup_db(udev); + return 0; + case 'x': + export = true; + break; + case 'P': + export_prefix = optarg; + break; + case 'V': + printf("%s\n", VERSION); + return 0; + case 'h': + help(); + return 0; + default: + return 1; + } + + switch (action) { + case ACTION_QUERY: + if (!device) { + if (!argv[optind]) { + help(); + return 2; + } + device = find_device(udev, argv[optind], NULL); + if (!device) { + fprintf(stderr, "Unknown device, --name=, --path=, or absolute path in /dev/ or /sys expected.\n"); + return 4; + } + } + + switch(query) { + case QUERY_NAME: { + const char *node = udev_device_get_devnode(device); + + if (node == NULL) { + fprintf(stderr, "no device node found\n"); + return 5; + } + + if (root) + printf("%s\n", udev_device_get_devnode(device)); + else + printf("%s\n", udev_device_get_devnode(device) + strlen("/dev/")); + break; + } + case QUERY_SYMLINK: + list_entry = udev_device_get_devlinks_list_entry(device); + while (list_entry != NULL) { + if (root) + printf("%s", udev_list_entry_get_name(list_entry)); + else + printf("%s", udev_list_entry_get_name(list_entry) + strlen("/dev/")); + list_entry = udev_list_entry_get_next(list_entry); + if (list_entry != NULL) + printf(" "); + } + printf("\n"); + break; + case QUERY_PATH: + printf("%s\n", udev_device_get_devpath(device)); + return 0; + case QUERY_PROPERTY: + list_entry = udev_device_get_properties_list_entry(device); + while (list_entry != NULL) { + if (export) { + const char *prefix = export_prefix; + + if (prefix == NULL) + prefix = ""; + printf("%s%s='%s'\n", prefix, + udev_list_entry_get_name(list_entry), + udev_list_entry_get_value(list_entry)); + } else { + printf("%s=%s\n", udev_list_entry_get_name(list_entry), udev_list_entry_get_value(list_entry)); + } + list_entry = udev_list_entry_get_next(list_entry); + } + break; + case QUERY_ALL: + print_record(device); + break; + default: + assert_not_reached("unknown query type"); + } + break; + case ACTION_ATTRIBUTE_WALK: + if (!device && argv[optind]) { + device = find_device(udev, argv[optind], NULL); + if (!device) { + fprintf(stderr, "Unknown device, absolute path in /dev/ or /sys expected.\n"); + return 4; + } + } + if (!device) { + fprintf(stderr, "Unknown device, --name=, --path=, or absolute path in /dev/ or /sys expected.\n"); + return 4; + } + print_device_chain(device); + break; + case ACTION_DEVICE_ID_FILE: + if (stat_device(name, export, export_prefix) != 0) + return 1; + break; + } + + return 0; +} + +const struct udevadm_cmd udevadm_info = { + .name = "info", + .cmd = uinfo, + .help = "Query sysfs or the udev database", +}; diff --git a/src/grp-udev/udevadm/udevadm-monitor.c b/src/grp-udev/udevadm/udevadm-monitor.c new file mode 100644 index 0000000000..c0ef073476 --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-monitor.c @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2004-2010 Kay Sievers <kay@vrfy.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <getopt.h> +#include <signal.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <sys/epoll.h> +#include <sys/time.h> +#include <time.h> + +#include "fd-util.h" +#include "formats-util.h" +#include "udev-util.h" +#include "udev.h" + +static bool udev_exit; + +static void sig_handler(int signum) { + if (signum == SIGINT || signum == SIGTERM) + udev_exit = true; +} + +static void print_device(struct udev_device *device, const char *source, int prop) { + struct timespec ts; + + assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0); + printf("%-6s[%"PRI_TIME".%06ld] %-8s %s (%s)\n", + source, + ts.tv_sec, ts.tv_nsec/1000, + udev_device_get_action(device), + udev_device_get_devpath(device), + udev_device_get_subsystem(device)); + if (prop) { + struct udev_list_entry *list_entry; + + udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(device)) + printf("%s=%s\n", + udev_list_entry_get_name(list_entry), + udev_list_entry_get_value(list_entry)); + printf("\n"); + } +} + +static void help(void) { + printf("%s monitor [--property] [--kernel] [--udev] [--help]\n\n" + "Listen to kernel and udev events.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -p --property Print the event properties\n" + " -k --kernel Print kernel uevents\n" + " -u --udev Print udev events\n" + " -s --subsystem-match=SUBSYSTEM[/DEVTYPE] Filter events by subsystem\n" + " -t --tag-match=TAG Filter events by tag\n" + , program_invocation_short_name); +} + +static int adm_monitor(struct udev *udev, int argc, char *argv[]) { + struct sigaction act = {}; + sigset_t mask; + bool prop = false; + bool print_kernel = false; + bool print_udev = false; + _cleanup_udev_list_cleanup_ struct udev_list subsystem_match_list; + _cleanup_udev_list_cleanup_ struct udev_list tag_match_list; + _cleanup_udev_monitor_unref_ struct udev_monitor *udev_monitor = NULL; + _cleanup_udev_monitor_unref_ struct udev_monitor *kernel_monitor = NULL; + _cleanup_close_ int fd_ep = -1; + int fd_kernel = -1, fd_udev = -1; + struct epoll_event ep_kernel, ep_udev; + int c; + + static const struct option options[] = { + { "property", no_argument, NULL, 'p' }, + { "environment", no_argument, NULL, 'e' }, /* alias for -p */ + { "kernel", no_argument, NULL, 'k' }, + { "udev", no_argument, NULL, 'u' }, + { "subsystem-match", required_argument, NULL, 's' }, + { "tag-match", required_argument, NULL, 't' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + + udev_list_init(udev, &subsystem_match_list, true); + udev_list_init(udev, &tag_match_list, true); + + while ((c = getopt_long(argc, argv, "pekus:t:h", options, NULL)) >= 0) + switch (c) { + case 'p': + case 'e': + prop = true; + break; + case 'k': + print_kernel = true; + break; + case 'u': + print_udev = true; + break; + case 's': + { + char subsys[UTIL_NAME_SIZE]; + char *devtype; + + strscpy(subsys, sizeof(subsys), optarg); + devtype = strchr(subsys, '/'); + if (devtype != NULL) { + devtype[0] = '\0'; + devtype++; + } + udev_list_entry_add(&subsystem_match_list, subsys, devtype); + break; + } + case 't': + udev_list_entry_add(&tag_match_list, optarg, NULL); + break; + case 'h': + help(); + return 0; + default: + return 1; + } + + if (!print_kernel && !print_udev) { + print_kernel = true; + print_udev = true; + } + + /* set signal handlers */ + act.sa_handler = sig_handler; + act.sa_flags = SA_RESTART; + sigaction(SIGINT, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + sigprocmask(SIG_UNBLOCK, &mask, NULL); + + fd_ep = epoll_create1(EPOLL_CLOEXEC); + if (fd_ep < 0) { + log_error_errno(errno, "error creating epoll fd: %m"); + return 1; + } + + printf("monitor will print the received events for:\n"); + if (print_udev) { + struct udev_list_entry *entry; + + udev_monitor = udev_monitor_new_from_netlink(udev, "udev"); + if (udev_monitor == NULL) { + fprintf(stderr, "error: unable to create netlink socket\n"); + return 1; + } + udev_monitor_set_receive_buffer_size(udev_monitor, 128*1024*1024); + fd_udev = udev_monitor_get_fd(udev_monitor); + + udev_list_entry_foreach(entry, udev_list_get_entry(&subsystem_match_list)) { + const char *subsys = udev_list_entry_get_name(entry); + const char *devtype = udev_list_entry_get_value(entry); + + if (udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, subsys, devtype) < 0) + fprintf(stderr, "error: unable to apply subsystem filter '%s'\n", subsys); + } + + udev_list_entry_foreach(entry, udev_list_get_entry(&tag_match_list)) { + const char *tag = udev_list_entry_get_name(entry); + + if (udev_monitor_filter_add_match_tag(udev_monitor, tag) < 0) + fprintf(stderr, "error: unable to apply tag filter '%s'\n", tag); + } + + if (udev_monitor_enable_receiving(udev_monitor) < 0) { + fprintf(stderr, "error: unable to subscribe to udev events\n"); + return 2; + } + + memzero(&ep_udev, sizeof(struct epoll_event)); + ep_udev.events = EPOLLIN; + ep_udev.data.fd = fd_udev; + if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_udev, &ep_udev) < 0) { + log_error_errno(errno, "fail to add fd to epoll: %m"); + return 2; + } + + printf("UDEV - the event which udev sends out after rule processing\n"); + } + + if (print_kernel) { + struct udev_list_entry *entry; + + kernel_monitor = udev_monitor_new_from_netlink(udev, "kernel"); + if (kernel_monitor == NULL) { + fprintf(stderr, "error: unable to create netlink socket\n"); + return 3; + } + udev_monitor_set_receive_buffer_size(kernel_monitor, 128*1024*1024); + fd_kernel = udev_monitor_get_fd(kernel_monitor); + + udev_list_entry_foreach(entry, udev_list_get_entry(&subsystem_match_list)) { + const char *subsys = udev_list_entry_get_name(entry); + + if (udev_monitor_filter_add_match_subsystem_devtype(kernel_monitor, subsys, NULL) < 0) + fprintf(stderr, "error: unable to apply subsystem filter '%s'\n", subsys); + } + + if (udev_monitor_enable_receiving(kernel_monitor) < 0) { + fprintf(stderr, "error: unable to subscribe to kernel events\n"); + return 4; + } + + memzero(&ep_kernel, sizeof(struct epoll_event)); + ep_kernel.events = EPOLLIN; + ep_kernel.data.fd = fd_kernel; + if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_kernel, &ep_kernel) < 0) { + log_error_errno(errno, "fail to add fd to epoll: %m"); + return 5; + } + + printf("KERNEL - the kernel uevent\n"); + } + printf("\n"); + + while (!udev_exit) { + int fdcount; + struct epoll_event ev[4]; + int i; + + fdcount = epoll_wait(fd_ep, ev, ELEMENTSOF(ev), -1); + if (fdcount < 0) { + if (errno != EINTR) + fprintf(stderr, "error receiving uevent message: %m\n"); + continue; + } + + for (i = 0; i < fdcount; i++) { + if (ev[i].data.fd == fd_kernel && ev[i].events & EPOLLIN) { + struct udev_device *device; + + device = udev_monitor_receive_device(kernel_monitor); + if (device == NULL) + continue; + print_device(device, "KERNEL", prop); + udev_device_unref(device); + } else if (ev[i].data.fd == fd_udev && ev[i].events & EPOLLIN) { + struct udev_device *device; + + device = udev_monitor_receive_device(udev_monitor); + if (device == NULL) + continue; + print_device(device, "UDEV", prop); + udev_device_unref(device); + } + } + } + + return 0; +} + +const struct udevadm_cmd udevadm_monitor = { + .name = "monitor", + .cmd = adm_monitor, + .help = "Listen to kernel and udev events", +}; diff --git a/src/grp-udev/udevadm/udevadm-settle.c b/src/grp-udev/udevadm/udevadm-settle.c new file mode 100644 index 0000000000..6a5dc6e9e4 --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-settle.c @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2006-2009 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2009 Canonical Ltd. + * Copyright (C) 2009 Scott James Remnant <scott@netsplit.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <getopt.h> +#include <poll.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "parse-util.h" +#include "udev.h" +#include "util.h" + +static void help(void) { + printf("%s settle OPTIONS\n\n" + "Wait for pending udev events.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -t --timeout=SECONDS Maximum time to wait for events\n" + " -E --exit-if-exists=FILE Stop waiting if file exists\n" + , program_invocation_short_name); +} + +static int adm_settle(struct udev *udev, int argc, char *argv[]) { + static const struct option options[] = { + { "timeout", required_argument, NULL, 't' }, + { "exit-if-exists", required_argument, NULL, 'E' }, + { "help", no_argument, NULL, 'h' }, + { "seq-start", required_argument, NULL, 's' }, /* removed */ + { "seq-end", required_argument, NULL, 'e' }, /* removed */ + { "quiet", no_argument, NULL, 'q' }, /* removed */ + {} + }; + usec_t deadline; + const char *exists = NULL; + unsigned int timeout = 120; + struct pollfd pfd[1] = { {.fd = -1}, }; + int c; + struct udev_queue *queue; + int rc = EXIT_FAILURE; + + while ((c = getopt_long(argc, argv, "t:E:hs:e:q", options, NULL)) >= 0) { + switch (c) { + + case 't': { + int r; + + r = safe_atou(optarg, &timeout); + if (r < 0) { + log_error_errno(r, "Invalid timeout value '%s': %m", optarg); + return EXIT_FAILURE; + } + break; + } + + case 'E': + exists = optarg; + break; + + case 'h': + help(); + return EXIT_SUCCESS; + + case 's': + case 'e': + case 'q': + log_info("Option -%c no longer supported.", c); + return EXIT_FAILURE; + + case '?': + return EXIT_FAILURE; + + default: + assert_not_reached("Unknown argument"); + } + } + + if (optind < argc) { + fprintf(stderr, "Extraneous argument: '%s'\n", argv[optind]); + return EXIT_FAILURE; + } + + deadline = now(CLOCK_MONOTONIC) + timeout * USEC_PER_SEC; + + /* guarantee that the udev daemon isn't pre-processing */ + if (getuid() == 0) { + struct udev_ctrl *uctrl; + + uctrl = udev_ctrl_new(udev); + if (uctrl != NULL) { + if (udev_ctrl_send_ping(uctrl, MAX(5U, timeout)) < 0) { + log_debug("no connection to daemon"); + udev_ctrl_unref(uctrl); + return EXIT_SUCCESS; + } + udev_ctrl_unref(uctrl); + } + } + + queue = udev_queue_new(udev); + if (!queue) { + log_error("unable to get udev queue"); + return EXIT_FAILURE; + } + + pfd[0].events = POLLIN; + pfd[0].fd = udev_queue_get_fd(queue); + if (pfd[0].fd < 0) { + log_debug("queue is empty, nothing to watch"); + rc = EXIT_SUCCESS; + goto out; + } + + for (;;) { + if (exists && access(exists, F_OK) >= 0) { + rc = EXIT_SUCCESS; + break; + } + + /* exit if queue is empty */ + if (udev_queue_get_queue_is_empty(queue)) { + rc = EXIT_SUCCESS; + break; + } + + if (now(CLOCK_MONOTONIC) >= deadline) + break; + + /* wake up when queue is empty */ + if (poll(pfd, 1, MSEC_PER_SEC) > 0 && pfd[0].revents & POLLIN) + udev_queue_flush(queue); + } + +out: + udev_queue_unref(queue); + return rc; +} + +const struct udevadm_cmd udevadm_settle = { + .name = "settle", + .cmd = adm_settle, + .help = "Wait for pending udev events", +}; diff --git a/src/grp-udev/udevadm/udevadm-test-builtin.c b/src/grp-udev/udevadm/udevadm-test-builtin.c new file mode 100644 index 0000000000..0b180d03eb --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-test-builtin.c @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2011 Kay Sievers <kay@vrfy.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <getopt.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> + +#include "string-util.h" +#include "udev.h" + +static void help(struct udev *udev) { + printf("%s builtin [--help] COMMAND SYSPATH\n\n" + "Test a built-in command.\n\n" + " -h --help Print this message\n" + " --version Print version of the program\n\n" + "Commands:\n" + , program_invocation_short_name); + + udev_builtin_list(udev); +} + +static int adm_builtin(struct udev *udev, int argc, char *argv[]) { + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + {} + }; + char *command = NULL; + char *syspath = NULL; + char filename[UTIL_PATH_SIZE]; + struct udev_device *dev = NULL; + enum udev_builtin_cmd cmd; + int rc = EXIT_SUCCESS, c; + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + switch (c) { + case 'h': + help(udev); + goto out; + } + + command = argv[optind++]; + if (command == NULL) { + fprintf(stderr, "command missing\n"); + help(udev); + rc = 2; + goto out; + } + + syspath = argv[optind++]; + if (syspath == NULL) { + fprintf(stderr, "syspath missing\n"); + rc = 3; + goto out; + } + + udev_builtin_init(udev); + + cmd = udev_builtin_lookup(command); + if (cmd >= UDEV_BUILTIN_MAX) { + fprintf(stderr, "unknown command '%s'\n", command); + help(udev); + rc = 5; + goto out; + } + + /* add /sys if needed */ + if (!startswith(syspath, "/sys")) + strscpyl(filename, sizeof(filename), "/sys", syspath, NULL); + else + strscpy(filename, sizeof(filename), syspath); + util_remove_trailing_chars(filename, '/'); + + dev = udev_device_new_from_syspath(udev, filename); + if (dev == NULL) { + fprintf(stderr, "unable to open device '%s'\n\n", filename); + rc = 4; + goto out; + } + + rc = udev_builtin_run(dev, cmd, command, true); + if (rc < 0) { + fprintf(stderr, "error executing '%s', exit code %i\n\n", command, rc); + rc = 6; + } +out: + udev_device_unref(dev); + udev_builtin_exit(udev); + return rc; +} + +const struct udevadm_cmd udevadm_test_builtin = { + .name = "test-builtin", + .cmd = adm_builtin, + .help = "Test a built-in command", + .debug = true, +}; diff --git a/src/grp-udev/udevadm/udevadm-test.c b/src/grp-udev/udevadm/udevadm-test.c new file mode 100644 index 0000000000..702dbe5282 --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-test.c @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2003-2004 Greg Kroah-Hartman <greg@kroah.com> + * Copyright (C) 2004-2008 Kay Sievers <kay@vrfy.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <getopt.h> +#include <signal.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/signalfd.h> +#include <unistd.h> + +#include "string-util.h" +#include "udev-util.h" +#include "udev.h" + +static void help(void) { + + printf("%s test OPTIONS <syspath>\n\n" + "Test an event run.\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -a --action=ACTION Set action string\n" + " -N --resolve-names=early|late|never When to resolve names\n" + , program_invocation_short_name); +} + +static int adm_test(struct udev *udev, int argc, char *argv[]) { + int resolve_names = 1; + char filename[UTIL_PATH_SIZE]; + const char *action = "add"; + const char *syspath = NULL; + struct udev_list_entry *entry; + _cleanup_udev_rules_unref_ struct udev_rules *rules = NULL; + _cleanup_udev_device_unref_ struct udev_device *dev = NULL; + _cleanup_udev_event_unref_ struct udev_event *event = NULL; + sigset_t mask, sigmask_orig; + int rc = 0, c; + + static const struct option options[] = { + { "action", required_argument, NULL, 'a' }, + { "resolve-names", required_argument, NULL, 'N' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + + log_debug("version %s", VERSION); + + while ((c = getopt_long(argc, argv, "a:N:h", options, NULL)) >= 0) + switch (c) { + case 'a': + action = optarg; + break; + case 'N': + if (streq (optarg, "early")) { + resolve_names = 1; + } else if (streq (optarg, "late")) { + resolve_names = 0; + } else if (streq (optarg, "never")) { + resolve_names = -1; + } else { + fprintf(stderr, "resolve-names must be early, late or never\n"); + log_error("resolve-names must be early, late or never"); + exit(EXIT_FAILURE); + } + break; + case 'h': + help(); + exit(EXIT_SUCCESS); + case '?': + exit(EXIT_FAILURE); + default: + assert_not_reached("Unknown option"); + } + + syspath = argv[optind]; + if (syspath == NULL) { + fprintf(stderr, "syspath parameter missing\n"); + rc = 2; + goto out; + } + + printf("This program is for debugging only, it does not run any program\n" + "specified by a RUN key. It may show incorrect results, because\n" + "some values may be different, or not available at a simulation run.\n" + "\n"); + + sigprocmask(SIG_SETMASK, NULL, &sigmask_orig); + + udev_builtin_init(udev); + + rules = udev_rules_new(udev, resolve_names); + if (rules == NULL) { + fprintf(stderr, "error reading rules\n"); + rc = 3; + goto out; + } + + /* add /sys if needed */ + if (!startswith(syspath, "/sys")) + strscpyl(filename, sizeof(filename), "/sys", syspath, NULL); + else + strscpy(filename, sizeof(filename), syspath); + util_remove_trailing_chars(filename, '/'); + + dev = udev_device_new_from_synthetic_event(udev, filename, action); + if (dev == NULL) { + fprintf(stderr, "unable to open device '%s'\n", filename); + rc = 4; + goto out; + } + + /* don't read info from the db */ + udev_device_set_info_loaded(dev); + + event = udev_event_new(dev); + + sigfillset(&mask); + sigprocmask(SIG_SETMASK, &mask, &sigmask_orig); + + udev_event_execute_rules(event, + 60 * USEC_PER_SEC, 20 * USEC_PER_SEC, + NULL, + rules); + + udev_list_entry_foreach(entry, udev_device_get_properties_list_entry(dev)) + printf("%s=%s\n", udev_list_entry_get_name(entry), udev_list_entry_get_value(entry)); + + udev_list_entry_foreach(entry, udev_list_get_entry(&event->run_list)) { + char program[UTIL_PATH_SIZE]; + + udev_event_apply_format(event, udev_list_entry_get_name(entry), program, sizeof(program)); + printf("run: '%s'\n", program); + } +out: + udev_builtin_exit(udev); + return rc; +} + +const struct udevadm_cmd udevadm_test = { + .name = "test", + .cmd = adm_test, + .help = "Test an event run", + .debug = true, +}; diff --git a/src/grp-udev/udevadm/udevadm-trigger.c b/src/grp-udev/udevadm/udevadm-trigger.c new file mode 100644 index 0000000000..9d52345d92 --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-trigger.c @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2008-2009 Kay Sievers <kay@vrfy.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "string-util.h" +#include "udev-util.h" +#include "udev.h" +#include "udevadm-util.h" +#include "util.h" + +static int verbose; +static int dry_run; + +static void exec_list(struct udev_enumerate *udev_enumerate, const char *action) { + struct udev_list_entry *entry; + + udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(udev_enumerate)) { + char filename[UTIL_PATH_SIZE]; + int fd; + + if (verbose) + printf("%s\n", udev_list_entry_get_name(entry)); + if (dry_run) + continue; + strscpyl(filename, sizeof(filename), udev_list_entry_get_name(entry), "/uevent", NULL); + fd = open(filename, O_WRONLY|O_CLOEXEC); + if (fd < 0) + continue; + if (write(fd, action, strlen(action)) < 0) + log_debug_errno(errno, "error writing '%s' to '%s': %m", action, filename); + close(fd); + } +} + +static const char *keyval(const char *str, const char **val, char *buf, size_t size) { + char *pos; + + strscpy(buf, size,str); + pos = strchr(buf, '='); + if (pos != NULL) { + pos[0] = 0; + pos++; + } + *val = pos; + return buf; +} + +static void help(void) { + printf("%s trigger OPTIONS\n\n" + "Request events from the kernel.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -v --verbose Print the list of devices while running\n" + " -n --dry-run Do not actually trigger the events\n" + " -t --type= Type of events to trigger\n" + " devices sysfs devices (default)\n" + " subsystems sysfs subsystems and drivers\n" + " -c --action=ACTION Event action value, default is \"change\"\n" + " -s --subsystem-match=SUBSYSTEM Trigger devices from a matching subsystem\n" + " -S --subsystem-nomatch=SUBSYSTEM Exclude devices from a matching subsystem\n" + " -a --attr-match=FILE[=VALUE] Trigger devices with a matching attribute\n" + " -A --attr-nomatch=FILE[=VALUE] Exclude devices with a matching attribute\n" + " -p --property-match=KEY=VALUE Trigger devices with a matching property\n" + " -g --tag-match=KEY=VALUE Trigger devices with a matching property\n" + " -y --sysname-match=NAME Trigger devices with this /sys path\n" + " --name-match=NAME Trigger devices with this /dev name\n" + " -b --parent-match=NAME Trigger devices with that parent device\n" + , program_invocation_short_name); +} + +static int adm_trigger(struct udev *udev, int argc, char *argv[]) { + enum { + ARG_NAME = 0x100, + }; + + static const struct option options[] = { + { "verbose", no_argument, NULL, 'v' }, + { "dry-run", no_argument, NULL, 'n' }, + { "type", required_argument, NULL, 't' }, + { "action", required_argument, NULL, 'c' }, + { "subsystem-match", required_argument, NULL, 's' }, + { "subsystem-nomatch", required_argument, NULL, 'S' }, + { "attr-match", required_argument, NULL, 'a' }, + { "attr-nomatch", required_argument, NULL, 'A' }, + { "property-match", required_argument, NULL, 'p' }, + { "tag-match", required_argument, NULL, 'g' }, + { "sysname-match", required_argument, NULL, 'y' }, + { "name-match", required_argument, NULL, ARG_NAME }, + { "parent-match", required_argument, NULL, 'b' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + enum { + TYPE_DEVICES, + TYPE_SUBSYSTEMS, + } device_type = TYPE_DEVICES; + const char *action = "change"; + _cleanup_udev_enumerate_unref_ struct udev_enumerate *udev_enumerate = NULL; + int c, r; + + udev_enumerate = udev_enumerate_new(udev); + if (udev_enumerate == NULL) + return 1; + + while ((c = getopt_long(argc, argv, "vno:t:c:s:S:a:A:p:g:y:b:h", options, NULL)) >= 0) { + const char *key; + const char *val; + char buf[UTIL_PATH_SIZE]; + + switch (c) { + case 'v': + verbose = 1; + break; + case 'n': + dry_run = 1; + break; + case 't': + if (streq(optarg, "devices")) + device_type = TYPE_DEVICES; + else if (streq(optarg, "subsystems")) + device_type = TYPE_SUBSYSTEMS; + else { + log_error("unknown type --type=%s", optarg); + return 2; + } + break; + case 'c': + if (!nulstr_contains("add\0" "remove\0" "change\0", optarg)) { + log_error("unknown action '%s'", optarg); + return 2; + } else + action = optarg; + + break; + case 's': + r = udev_enumerate_add_match_subsystem(udev_enumerate, optarg); + if (r < 0) { + log_error_errno(r, "could not add subsystem match '%s': %m", optarg); + return 2; + } + break; + case 'S': + r = udev_enumerate_add_nomatch_subsystem(udev_enumerate, optarg); + if (r < 0) { + log_error_errno(r, "could not add negative subsystem match '%s': %m", optarg); + return 2; + } + break; + case 'a': + key = keyval(optarg, &val, buf, sizeof(buf)); + r = udev_enumerate_add_match_sysattr(udev_enumerate, key, val); + if (r < 0) { + log_error_errno(r, "could not add sysattr match '%s=%s': %m", key, val); + return 2; + } + break; + case 'A': + key = keyval(optarg, &val, buf, sizeof(buf)); + r = udev_enumerate_add_nomatch_sysattr(udev_enumerate, key, val); + if (r < 0) { + log_error_errno(r, "could not add negative sysattr match '%s=%s': %m", key, val); + return 2; + } + break; + case 'p': + key = keyval(optarg, &val, buf, sizeof(buf)); + r = udev_enumerate_add_match_property(udev_enumerate, key, val); + if (r < 0) { + log_error_errno(r, "could not add property match '%s=%s': %m", key, val); + return 2; + } + break; + case 'g': + r = udev_enumerate_add_match_tag(udev_enumerate, optarg); + if (r < 0) { + log_error_errno(r, "could not add tag match '%s': %m", optarg); + return 2; + } + break; + case 'y': + r = udev_enumerate_add_match_sysname(udev_enumerate, optarg); + if (r < 0) { + log_error_errno(r, "could not add sysname match '%s': %m", optarg); + return 2; + } + break; + case 'b': { + _cleanup_udev_device_unref_ struct udev_device *dev; + + dev = find_device(udev, optarg, "/sys"); + if (dev == NULL) { + log_error("unable to open the device '%s'", optarg); + return 2; + } + + r = udev_enumerate_add_match_parent(udev_enumerate, dev); + if (r < 0) { + log_error_errno(r, "could not add parent match '%s': %m", optarg); + return 2; + } + break; + } + + case ARG_NAME: { + _cleanup_udev_device_unref_ struct udev_device *dev; + + dev = find_device(udev, optarg, "/dev/"); + if (dev == NULL) { + log_error("unable to open the device '%s'", optarg); + return 2; + } + + r = udev_enumerate_add_match_parent(udev_enumerate, dev); + if (r < 0) { + log_error_errno(r, "could not add parent match '%s': %m", optarg); + return 2; + } + break; + } + + case 'h': + help(); + return 0; + case '?': + return 1; + default: + assert_not_reached("Unknown option"); + } + } + + for (; optind < argc; optind++) { + _cleanup_udev_device_unref_ struct udev_device *dev; + + dev = find_device(udev, argv[optind], NULL); + if (dev == NULL) { + log_error("unable to open the device '%s'", argv[optind]); + return 2; + } + + r = udev_enumerate_add_match_parent(udev_enumerate, dev); + if (r < 0) { + log_error_errno(r, "could not add tag match '%s': %m", optarg); + return 2; + } + } + + switch (device_type) { + case TYPE_SUBSYSTEMS: + udev_enumerate_scan_subsystems(udev_enumerate); + exec_list(udev_enumerate, action); + return 0; + case TYPE_DEVICES: + udev_enumerate_scan_devices(udev_enumerate); + exec_list(udev_enumerate, action); + return 0; + default: + assert_not_reached("device_type"); + } +} + +const struct udevadm_cmd udevadm_trigger = { + .name = "trigger", + .cmd = adm_trigger, + .help = "Request events from the kernel", +}; diff --git a/src/grp-udev/udevadm/udevadm-util.c b/src/grp-udev/udevadm/udevadm-util.c new file mode 100644 index 0000000000..3539c1d6ab --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-util.c @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2008-2009 Kay Sievers <kay@vrfy.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "string-util.h" +#include "udevadm-util.h" + +struct udev_device *find_device(struct udev *udev, + const char *id, + const char *prefix) { + + assert(udev); + assert(id); + + if (prefix && !startswith(id, prefix)) + id = strjoina(prefix, id); + + if (startswith(id, "/dev/")) { + struct stat statbuf; + char type; + + if (stat(id, &statbuf) < 0) + return NULL; + + if (S_ISBLK(statbuf.st_mode)) + type = 'b'; + else if (S_ISCHR(statbuf.st_mode)) + type = 'c'; + else + return NULL; + + return udev_device_new_from_devnum(udev, type, statbuf.st_rdev); + } else if (startswith(id, "/sys/")) + return udev_device_new_from_syspath(udev, id); + else + return NULL; +} diff --git a/src/grp-udev/udevadm/udevadm-util.h b/src/grp-udev/udevadm/udevadm-util.h new file mode 100644 index 0000000000..dc712b0d93 --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-util.h @@ -0,0 +1,24 @@ +#pragma once + +/* + * Copyright (C) 2014 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "udev.h" + +struct udev_device *find_device(struct udev *udev, + const char *id, + const char *prefix); diff --git a/src/grp-udev/udevadm/udevadm.c b/src/grp-udev/udevadm/udevadm.c new file mode 100644 index 0000000000..a6a873e5de --- /dev/null +++ b/src/grp-udev/udevadm/udevadm.c @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2007-2012 Kay Sievers <kay@vrfy.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <getopt.h> +#include <stddef.h> +#include <stdio.h> + +#include "selinux-util.h" +#include "string-util.h" +#include "udev.h" + +static int adm_version(struct udev *udev, int argc, char *argv[]) { + printf("%s\n", VERSION); + return 0; +} + +static const struct udevadm_cmd udevadm_version = { + .name = "version", + .cmd = adm_version, +}; + +static int adm_help(struct udev *udev, int argc, char *argv[]); + +static const struct udevadm_cmd udevadm_help = { + .name = "help", + .cmd = adm_help, +}; + +static const struct udevadm_cmd *udevadm_cmds[] = { + &udevadm_info, + &udevadm_trigger, + &udevadm_settle, + &udevadm_control, + &udevadm_monitor, + &udevadm_hwdb, + &udevadm_test, + &udevadm_test_builtin, + &udevadm_version, + &udevadm_help, +}; + +static int adm_help(struct udev *udev, int argc, char *argv[]) { + unsigned int i; + + printf("%s [--help] [--version] [--debug] COMMAND [COMMAND OPTIONS]\n\n" + "Send control commands or test the device manager.\n\n" + "Commands:\n" + , program_invocation_short_name); + + for (i = 0; i < ELEMENTSOF(udevadm_cmds); i++) + if (udevadm_cmds[i]->help != NULL) + printf(" %-12s %s\n", udevadm_cmds[i]->name, udevadm_cmds[i]->help); + return 0; +} + +static int run_command(struct udev *udev, const struct udevadm_cmd *cmd, int argc, char *argv[]) { + if (cmd->debug) + log_set_max_level(LOG_DEBUG); + log_debug("calling: %s", cmd->name); + return cmd->cmd(udev, argc, argv); +} + +int main(int argc, char *argv[]) { + struct udev *udev; + static const struct option options[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + {} + }; + const char *command; + unsigned int i; + int rc = 1, c; + + udev = udev_new(); + if (udev == NULL) + goto out; + + log_parse_environment(); + log_open(); + mac_selinux_init(); + + while ((c = getopt_long(argc, argv, "+dhV", options, NULL)) >= 0) + switch (c) { + + case 'd': + log_set_max_level(LOG_DEBUG); + break; + + case 'h': + rc = adm_help(udev, argc, argv); + goto out; + + case 'V': + rc = adm_version(udev, argc, argv); + goto out; + + default: + goto out; + } + + command = argv[optind]; + + if (command != NULL) + for (i = 0; i < ELEMENTSOF(udevadm_cmds); i++) + if (streq(udevadm_cmds[i]->name, command)) { + argc -= optind; + argv += optind; + /* we need '0' here to reset the internal state */ + optind = 0; + rc = run_command(udev, udevadm_cmds[i], argc, argv); + goto out; + } + + fprintf(stderr, "%s: missing or unknown command\n", program_invocation_short_name); + rc = 2; +out: + mac_selinux_finish(); + udev_unref(udev); + log_close(); + return rc; +} diff --git a/src/grp-udev/v4l_id/Makefile b/src/grp-udev/v4l_id/Makefile new file mode 100644 index 0000000000..0641af8065 --- /dev/null +++ b/src/grp-udev/v4l_id/Makefile @@ -0,0 +1,38 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +v4l_id_SOURCES = \ + src/udev/v4l_id/v4l_id.c + +v4l_id_LDADD = \ + libshared.la + +udevlibexec_PROGRAMS += \ + v4l_id + +dist_udevrules_DATA += \ + rules/60-persistent-v4l.rules + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-udev/v4l_id/v4l_id.c b/src/grp-udev/v4l_id/v4l_id.c new file mode 100644 index 0000000000..aec6676a33 --- /dev/null +++ b/src/grp-udev/v4l_id/v4l_id.c @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2009 Kay Sievers <kay@vrfy.org> + * Copyright (c) 2009 Filippo Argiolas <filippo.argiolas@gmail.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details: + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#include <linux/videodev2.h> + +#include "fd-util.h" +#include "util.h" + +int main(int argc, char *argv[]) { + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + {} + }; + _cleanup_close_ int fd = -1; + char *device; + struct v4l2_capability v2cap; + int c; + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + case 'h': + printf("%s [-h,--help] <device file>\n\n" + "Video4Linux device identification.\n\n" + " -h Print this message\n" + , program_invocation_short_name); + return 0; + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + device = argv[optind]; + if (device == NULL) + return 2; + + fd = open(device, O_RDONLY); + if (fd < 0) + return 3; + + if (ioctl(fd, VIDIOC_QUERYCAP, &v2cap) == 0) { + printf("ID_V4L_VERSION=2\n"); + printf("ID_V4L_PRODUCT=%s\n", v2cap.card); + printf("ID_V4L_CAPABILITIES=:"); + if ((v2cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) > 0) + printf("capture:"); + if ((v2cap.capabilities & V4L2_CAP_VIDEO_OUTPUT) > 0) + printf("video_output:"); + if ((v2cap.capabilities & V4L2_CAP_VIDEO_OVERLAY) > 0) + printf("video_overlay:"); + if ((v2cap.capabilities & V4L2_CAP_AUDIO) > 0) + printf("audio:"); + if ((v2cap.capabilities & V4L2_CAP_TUNER) > 0) + printf("tuner:"); + if ((v2cap.capabilities & V4L2_CAP_RADIO) > 0) + printf("radio:"); + printf("\n"); + } + + return 0; +} |