diff options
Diffstat (limited to 'src/udev/udev-builtin-path_id.c')
-rw-r--r-- | src/udev/udev-builtin-path_id.c | 732 |
1 files changed, 732 insertions, 0 deletions
diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c new file mode 100644 index 0000000..0da59e2 --- /dev/null +++ b/src/udev/udev-builtin-path_id.c @@ -0,0 +1,732 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * compose persistent device path + * + * Logic based on Hannes Reinecke's shell script. + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "libudev-util.h" +#include "string-util.h" +#include "strv.h" +#include "sysexits.h" +#include "udev-builtin.h" + +_printf_(2,3) +static void path_prepend(char **path, const char *fmt, ...) { + va_list va; + _cleanup_free_ char *pre = NULL; + int r; + + va_start(va, fmt); + r = vasprintf(&pre, fmt, va); + va_end(va); + if (r < 0) { + log_oom(); + exit(EX_OSERR); + } + + if (*path) { + char *new; + + new = strjoin(pre, "-", *path); + if (!new) { + log_oom(); + exit(EX_OSERR); + } + + free_and_replace(*path, new); + } else + *path = TAKE_PTR(pre); +} + +/* +** Linux only supports 32 bit luns. +** See drivers/scsi/scsi_scan.c::scsilun_to_int() for more details. +*/ +static int format_lun_number(sd_device *dev, char **path) { + const char *sysnum; + unsigned long lun; + int r; + + r = sd_device_get_sysnum(dev, &sysnum); + if (r < 0) + return r; + if (!sysnum) + return -ENOENT; + + lun = strtoul(sysnum, NULL, 10); + if (lun < 256) + /* address method 0, peripheral device addressing with bus id of zero */ + path_prepend(path, "lun-%lu", lun); + else + /* handle all other lun addressing methods by using a variant of the original lun format */ + path_prepend(path, "lun-0x%04lx%04lx00000000", lun & 0xffff, (lun >> 16) & 0xffff); + + return 0; +} + +static sd_device *skip_subsystem(sd_device *dev, const char *subsys) { + sd_device *parent; + + assert(dev); + assert(subsys); + + for (parent = dev; ; ) { + const char *subsystem; + + if (sd_device_get_subsystem(parent, &subsystem) < 0) + break; + + if (!streq(subsystem, subsys)) + break; + + dev = parent; + if (sd_device_get_parent(dev, &parent) < 0) + break; + } + + return dev; +} + +static sd_device *handle_scsi_fibre_channel(sd_device *parent, char **path) { + sd_device *targetdev; + _cleanup_(sd_device_unrefp) sd_device *fcdev = NULL; + const char *port, *sysname; + _cleanup_free_ char *lun = NULL; + + assert(parent); + assert(path); + + if (sd_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target", &targetdev) < 0) + return NULL; + if (sd_device_get_sysname(targetdev, &sysname) < 0) + return NULL; + if (sd_device_new_from_subsystem_sysname(&fcdev, "fc_transport", sysname) < 0) + return NULL; + if (sd_device_get_sysattr_value(fcdev, "port_name", &port) < 0) + return NULL; + + format_lun_number(parent, &lun); + path_prepend(path, "fc-%s-%s", port, lun); + return parent; +} + +static sd_device *handle_scsi_sas_wide_port(sd_device *parent, char **path) { + sd_device *targetdev, *target_parent; + _cleanup_(sd_device_unrefp) sd_device *sasdev = NULL; + const char *sas_address, *sysname; + _cleanup_free_ char *lun = NULL; + + assert(parent); + assert(path); + + if (sd_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target", &targetdev) < 0) + return NULL; + if (sd_device_get_parent(targetdev, &target_parent) < 0) + return NULL; + if (sd_device_get_sysname(target_parent, &sysname) < 0) + return NULL; + if (sd_device_new_from_subsystem_sysname(&sasdev, "sas_device", sysname) < 0) + return NULL; + if (sd_device_get_sysattr_value(sasdev, "sas_address", &sas_address) < 0) + return NULL; + + format_lun_number(parent, &lun); + path_prepend(path, "sas-%s-%s", sas_address, lun); + return parent; +} + +static sd_device *handle_scsi_sas(sd_device *parent, char **path) { + sd_device *targetdev, *target_parent, *port, *expander; + _cleanup_(sd_device_unrefp) sd_device *target_sasdev = NULL, *expander_sasdev = NULL, *port_sasdev = NULL; + const char *sas_address = NULL; + const char *phy_id; + const char *phy_count, *sysname; + _cleanup_free_ char *lun = NULL; + + assert(parent); + assert(path); + + if (sd_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target", &targetdev) < 0) + return NULL; + if (sd_device_get_parent(targetdev, &target_parent) < 0) + return NULL; + if (sd_device_get_sysname(target_parent, &sysname) < 0) + return NULL; + /* Get sas device */ + if (sd_device_new_from_subsystem_sysname(&target_sasdev, "sas_device", sysname) < 0) + return NULL; + /* The next parent is sas port */ + if (sd_device_get_parent(target_parent, &port) < 0) + return NULL; + if (sd_device_get_sysname(port, &sysname) < 0) + return NULL; + /* Get port device */ + if (sd_device_new_from_subsystem_sysname(&port_sasdev, "sas_port", sysname) < 0) + return NULL; + if (sd_device_get_sysattr_value(port_sasdev, "num_phys", &phy_count) < 0) + return NULL; + + /* Check if we are simple disk */ + if (strncmp(phy_count, "1", 2) != 0) + return handle_scsi_sas_wide_port(parent, path); + + /* Get connected phy */ + if (sd_device_get_sysattr_value(target_sasdev, "phy_identifier", &phy_id) < 0) + return NULL; + + /* The port's parent is either hba or expander */ + if (sd_device_get_parent(port, &expander) < 0) + return NULL; + + if (sd_device_get_sysname(expander, &sysname) < 0) + return NULL; + /* Get expander device */ + if (sd_device_new_from_subsystem_sysname(&expander_sasdev, "sas_device", sysname) >= 0) { + /* Get expander's address */ + if (sd_device_get_sysattr_value(expander_sasdev, "sas_address", &sas_address) < 0) + return NULL; + } + + 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); + + return parent; +} + +static sd_device *handle_scsi_iscsi(sd_device *parent, char **path) { + sd_device *transportdev; + _cleanup_(sd_device_unrefp) sd_device *sessiondev = NULL, *conndev = NULL; + const char *target, *connname, *addr, *port; + _cleanup_free_ char *lun = NULL; + const char *sysname, *sysnum; + + assert(parent); + assert(path); + + /* find iscsi session */ + for (transportdev = parent; ; ) { + + if (sd_device_get_parent(transportdev, &transportdev) < 0) + return NULL; + if (sd_device_get_sysname(transportdev, &sysname) < 0) + return NULL; + if (startswith(sysname, "session")) + break; + } + + /* find iscsi session device */ + if (sd_device_new_from_subsystem_sysname(&sessiondev, "iscsi_session", sysname) < 0) + return NULL; + + if (sd_device_get_sysattr_value(sessiondev, "targetname", &target) < 0) + return NULL; + + if (sd_device_get_sysnum(transportdev, &sysnum) < 0 || !sysnum) + return NULL; + connname = strjoina("connection", sysnum, ":0"); + if (sd_device_new_from_subsystem_sysname(&conndev, "iscsi_connection", connname) < 0) + return NULL; + + if (sd_device_get_sysattr_value(conndev, "persistent_address", &addr) < 0) + return NULL; + if (sd_device_get_sysattr_value(conndev, "persistent_port", &port) < 0) + return NULL; + + format_lun_number(parent, &lun); + path_prepend(path, "ip-%s:%s-iscsi-%s-%s", addr, port, target, lun); + return parent; +} + +static sd_device *handle_scsi_ata(sd_device *parent, char **path, char **compat_path) { + sd_device *targetdev, *target_parent; + _cleanup_(sd_device_unrefp) sd_device *atadev = NULL; + const char *port_no, *sysname, *name; + unsigned host, bus, target, lun; + + assert(parent); + assert(path); + + if (sd_device_get_sysname(parent, &name) < 0) + return NULL; + if (sscanf(name, "%u:%u:%u:%u", &host, &bus, &target, &lun) != 4) + return NULL; + + if (sd_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host", &targetdev) < 0) + return NULL; + + if (sd_device_get_parent(targetdev, &target_parent) < 0) + return NULL; + + if (sd_device_get_sysname(target_parent, &sysname) < 0) + return NULL; + if (sd_device_new_from_subsystem_sysname(&atadev, "ata_port", sysname) < 0) + return NULL; + + if (sd_device_get_sysattr_value(atadev, "port_no", &port_no) < 0) + return NULL; + + if (bus != 0) + /* Devices behind port multiplier have a bus != 0*/ + path_prepend(path, "ata-%s.%u.0", port_no, bus); + else + /* Master/slave are distinguished by target id */ + path_prepend(path, "ata-%s.%u", port_no, target); + + /* old compatible persistent link for ATA devices */ + if (compat_path) + path_prepend(compat_path, "ata-%s", port_no); + + return parent; +} + +static sd_device *handle_scsi_default(sd_device *parent, char **path) { + sd_device *hostdev; + int host, bus, target, lun; + const char *name, *base, *pos; + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *dent; + int basenum = -1; + + assert(parent); + assert(path); + + if (sd_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host", &hostdev) < 0) + return NULL; + + if (sd_device_get_sysname(parent, &name) < 0) + return NULL; + 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. + */ + + if (sd_device_get_syspath(hostdev, &base) < 0) + return NULL; + pos = strrchr(base, '/'); + if (!pos) + return NULL; + + base = strndupa(base, pos - base); + dir = opendir(base); + if (!dir) + return NULL; + + FOREACH_DIRENT_ALL(dent, dir, break) { + char *rest; + int i; + + if (dent->d_name[0] == '.') + continue; + if (!IN_SET(dent->d_type, DT_DIR, 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; + } + if (basenum == -1) + return hostdev; + host -= basenum; + + path_prepend(path, "scsi-%u:%u:%u:%u", host, bus, target, lun); + return hostdev; +} + +static sd_device *handle_scsi_hyperv(sd_device *parent, char **path, size_t guid_str_len) { + sd_device *hostdev; + sd_device *vmbusdev; + const char *guid_str; + _cleanup_free_ char *lun = NULL; + char guid[39]; + size_t i, k; + + assert(parent); + assert(path); + assert(guid_str_len < sizeof(guid)); + + if (sd_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host", &hostdev) < 0) + return NULL; + + if (sd_device_get_parent(hostdev, &vmbusdev) < 0) + return NULL; + + if (sd_device_get_sysattr_value(vmbusdev, "device_id", &guid_str) < 0) + return NULL; + + if (strlen(guid_str) < guid_str_len || guid_str[0] != '{' || guid_str[guid_str_len-1] != '}') + return NULL; + + for (i = 1, k = 0; i < guid_str_len-1; 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); + return parent; +} + +static sd_device *handle_scsi(sd_device *parent, char **path, char **compat_path, bool *supported_parent) { + const char *devtype, *id, *name; + + if (sd_device_get_devtype(parent, &devtype) < 0 || + !streq(devtype, "scsi_device")) + return parent; + + /* firewire */ + if (sd_device_get_sysattr_value(parent, "ieee1394_id", &id) >= 0) { + path_prepend(path, "ieee1394-0x%s", id); + *supported_parent = true; + return skip_subsystem(parent, "scsi"); + } + + /* scsi sysfs does not have a "subsystem" for the transport */ + if (sd_device_get_syspath(parent, &name) < 0) + return NULL; + + if (strstr(name, "/rport-")) { + *supported_parent = true; + return handle_scsi_fibre_channel(parent, path); + } + + if (strstr(name, "/end_device-")) { + *supported_parent = true; + return handle_scsi_sas(parent, path); + } + + if (strstr(name, "/session")) { + *supported_parent = true; + return handle_scsi_iscsi(parent, path); + } + + if (strstr(name, "/ata")) + return handle_scsi_ata(parent, path, compat_path); + + if (strstr(name, "/vmbus_")) + return handle_scsi_hyperv(parent, path, 37); + else if (strstr(name, "/VMBUS")) + return handle_scsi_hyperv(parent, path, 38); + + return handle_scsi_default(parent, path); +} + +static sd_device *handle_cciss(sd_device *parent, char **path) { + const char *str; + unsigned controller, disk; + + if (sd_device_get_sysname(parent, &str) < 0) + return NULL; + if (sscanf(str, "c%ud%u%*s", &controller, &disk) != 2) + return NULL; + + path_prepend(path, "cciss-disk%u", disk); + return skip_subsystem(parent, "cciss"); +} + +static void handle_scsi_tape(sd_device *dev, char **path) { + const char *name; + + /* must be the last device in the syspath */ + if (*path) + return; + + if (sd_device_get_sysname(dev, &name) < 0) + return; + + if (startswith(name, "nst") && strchr("lma", name[3])) + path_prepend(path, "nst%c", name[3]); + else if (startswith(name, "st") && strchr("lma", name[2])) + path_prepend(path, "st%c", name[2]); +} + +static sd_device *handle_usb(sd_device *parent, char **path) { + const char *devtype, *str, *port; + + if (sd_device_get_devtype(parent, &devtype) < 0) + return parent; + if (!STR_IN_SET(devtype, "usb_interface", "usb_device")) + return parent; + + if (sd_device_get_sysname(parent, &str) < 0) + return parent; + port = strchr(str, '-'); + if (!port) + return parent; + port++; + + path_prepend(path, "usb-0:%s", port); + return skip_subsystem(parent, "usb"); +} + +static sd_device *handle_bcma(sd_device *parent, char **path) { + const char *sysname; + unsigned core; + + if (sd_device_get_sysname(parent, &sysname) < 0) + return NULL; + 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 sd_device *handle_ap(sd_device *parent, char **path) { + const char *type, *func; + + assert(parent); + assert(path); + + if (sd_device_get_sysattr_value(parent, "type", &type) >= 0 && + sd_device_get_sysattr_value(parent, "ap_functions", &func) >= 0) + path_prepend(path, "ap-%s-%s", type, func); + else { + const char *sysname; + + if (sd_device_get_sysname(parent, &sysname) >= 0) + path_prepend(path, "ap-%s", sysname); + } + + return skip_subsystem(parent, "ap"); +} + +static int builtin_path_id(sd_device *dev, int argc, char *argv[], bool test) { + sd_device *parent; + _cleanup_free_ char *path = NULL; + _cleanup_free_ char *compat_path = NULL; + bool supported_transport = false; + bool supported_parent = false; + const char *subsystem; + + assert(dev); + + /* walk up the chain of devices and compose path */ + parent = dev; + while (parent) { + const char *subsys, *sysname; + + if (sd_device_get_subsystem(parent, &subsys) < 0 || + sd_device_get_sysname(parent, &sysname) < 0) { + ; + } else if (streq(subsys, "scsi_tape")) { + handle_scsi_tape(parent, &path); + } else if (streq(subsys, "scsi")) { + parent = handle_scsi(parent, &path, &compat_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")) { + const char *sysnum; + + if (sd_device_get_sysnum(parent, &sysnum) >= 0 && sysnum) { + path_prepend(&path, "serio-%s", sysnum); + parent = skip_subsystem(parent, "serio"); + } + } else if (streq(subsys, "pci")) { + path_prepend(&path, "pci-%s", sysname); + if (compat_path) + path_prepend(&compat_path, "pci-%s", sysname); + parent = skip_subsystem(parent, "pci"); + supported_parent = true; + } else if (streq(subsys, "platform")) { + path_prepend(&path, "platform-%s", sysname); + if (compat_path) + path_prepend(&compat_path, "platform-%s", sysname); + parent = skip_subsystem(parent, "platform"); + supported_transport = true; + supported_parent = true; + } else if (streq(subsys, "acpi")) { + path_prepend(&path, "acpi-%s", sysname); + if (compat_path) + path_prepend(&compat_path, "acpi-%s", sysname); + parent = skip_subsystem(parent, "acpi"); + supported_parent = true; + } else if (streq(subsys, "xen")) { + path_prepend(&path, "xen-%s", sysname); + if (compat_path) + path_prepend(&compat_path, "xen-%s", sysname); + parent = skip_subsystem(parent, "xen"); + supported_parent = true; + } else if (streq(subsys, "virtio")) { + parent = skip_subsystem(parent, "virtio"); + supported_transport = true; + } else if (streq(subsys, "scm")) { + path_prepend(&path, "scm-%s", sysname); + if (compat_path) + path_prepend(&compat_path, "scm-%s", sysname); + parent = skip_subsystem(parent, "scm"); + supported_transport = true; + supported_parent = true; + } else if (streq(subsys, "ccw")) { + path_prepend(&path, "ccw-%s", sysname); + if (compat_path) + path_prepend(&compat_path, "ccw-%s", sysname); + parent = skip_subsystem(parent, "ccw"); + supported_transport = true; + supported_parent = true; + } else if (streq(subsys, "ccwgroup")) { + path_prepend(&path, "ccwgroup-%s", sysname); + if (compat_path) + path_prepend(&compat_path, "ccwgroup-%s", sysname); + 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", sysname); + if (compat_path) + path_prepend(&compat_path, "iucv-%s", sysname); + parent = skip_subsystem(parent, "iucv"); + supported_transport = true; + supported_parent = true; + } else if (streq(subsys, "nvme")) { + const char *nsid; + + if (sd_device_get_sysattr_value(dev, "nsid", &nsid) >= 0) { + path_prepend(&path, "nvme-%s", nsid); + if (compat_path) + path_prepend(&compat_path, "nvme-%s", nsid); + parent = skip_subsystem(parent, "nvme"); + supported_parent = true; + supported_transport = true; + } + } else if (streq(subsys, "spi")) { + const char *sysnum; + + if (sd_device_get_sysnum(parent, &sysnum) >= 0 && sysnum) { + path_prepend(&path, "cs-%s", sysnum); + parent = skip_subsystem(parent, "spi"); + } + } + + if (!parent) + break; + if (sd_device_get_parent(parent, &parent) < 0) + break; + } + + if (!path) + return -ENOENT; + + /* + * 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) + return -ENOENT; + + /* + * 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 (sd_device_get_subsystem(dev, &subsystem) >= 0 && + streq(subsystem, "block") && + !supported_transport) + return -ENOENT; + + { + 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); + } + + /* + * Compatible link generation for ATA devices + * we assign compat_link to the env variable + * ID_PATH_ATA_COMPAT + */ + if (compat_path) + udev_builtin_add_property(dev, test, "ID_PATH_ATA_COMPAT", compat_path); + + return 0; +} + +const UdevBuiltin udev_builtin_path_id = { + .name = "path_id", + .cmd = builtin_path_id, + .help = "Compose persistent device path", + .run_once = true, +}; |