Systemd/src/udev/udev-builtin-path_id.c
Lidong Zhong 9b3278d907 udev: rename the persistent link for ATA devices
ATA devices should use the ATA ids like port number and (possibly)
master/slave or multiplier id, not the generic SCSI ID.
Currently only port number is included in the link. With this patch
the link would be able to support more cases, which are a) when the
device is behind a port multiplexer b) the distinction between master
and slave (distinguished by target id).

I tried to verify scenario a) with this patch, but I failed to find a
machine with PMP SATA devices attached. But the link below
https://github.com/systemd/systemd/issues/3943
could show what's the difference. Here is my test for scenario b)

Current version:
linux-ql21:~ # ll /sys/class/block/sd[ab]
lrwxrwxrwx 1 root root 0 May  8 20:46 /sys/class/block/sda ->
../../devices/pci0000:00/0000:00:01.1/ata4/host3/target3:0:0/3:0:0:0/block/sda
lrwxrwxrwx 1 root root 0 May  8 20:46 /sys/class/block/sdb ->
../../devices/pci0000:00/0000:00:01.1/ata4/host3/target3:0:1/3:0:1:0/block/sdb
linux-ql21:~ # ll /dev/disk/by-path/pci-0000\:00\:01.1-ata-1
lrwxrwxrwx 1 root root 9 May  8 20:44
/dev/disk/by-path/pci-0000:00:01.1-ata-1 -> ../../sdb
linux-ql21:~ # udevadm info /sys/class/block/sda |grep by-path
S: disk/by-path/pci-0000:00:01.1-ata-1
E: DEVLINKS=/dev/disk/by-id/ata-VBOX_HARDDISK_VB3649e885-3e0cdd64
/dev/disk/by-id/scsi-0ATA_VBOX_HARDDISK_VB3649e885-3e0cdd64
/dev/disk/by-id/scsi-1ATA_VBOX_HARDDISK_VB3649e885-3e0cdd64
/dev/disk/by-path/pci-0000:00:01.1-ata-1
/dev/disk/by-id/scsi-SATA_VBOX_HARDDISK_VB3649e885-3e0cdd64
linux-ql21:~ # udevadm info /sys/class/block/sdb |grep by-path
S: disk/by-path/pci-0000:00:01.1-ata-1
E: DEVLINKS=/dev/disk/by-id/ata-VBOX_HARDDISK_VBc53b2498-d84ae8de
/dev/disk/by-id/scsi-SATA_VBOX_HARDDISK_VBc53b2498-d84ae8de
/dev/disk/by-id/scsi-1ATA_VBOX_HARDDISK_VBc53b2498-d84ae8de
/dev/disk/by-id/scsi-0ATA_VBOX_HARDDISK_VBc53b2498-d84ae8de
/dev/disk/by-path/pci-0000:00:01.1-ata-1

After patch applied:
linux-ql21:~ # ll /sys/class/block/sd[ab]
lrwxrwxrwx 1 root root 0 May  8 21:07 /sys/class/block/sda ->
../../devices/pci0000:00/0000:00:01.1/ata4/host3/target3:0:0/3:0:0:0/block/sda
lrwxrwxrwx 1 root root 0 May  8 21:07 /sys/class/block/sdb ->
../../devices/pci0000:00/0000:00:01.1/ata4/host3/target3:0:1/3:0:1:0/block/sdb
linux-ql21:~ # ll /dev/disk/by-path/pci-0000\:00\:01.1-ata-*
lrwxrwxrwx 1 root root  9 May  8 21:07
/dev/disk/by-path/pci-0000:00:01.1-ata-1.0 -> ../../sda
lrwxrwxrwx 1 root root  9 May  8 21:07
/dev/disk/by-path/pci-0000:00:01.1-ata-1.1 -> ../../sdb
linux-ql21:~ # udevadm info /sys/class/block/sda |grep by-path
S: disk/by-path/pci-0000:00:01.1-ata-1.0
E: DEVLINKS=/dev/disk/by-id/scsi-1ATA_VBOX_HARDDISK_VB3649e885-3e0cdd64
/dev/disk/by-id/scsi-0ATA_VBOX_HARDDISK_VB3649e885-3e0cdd64
/dev/disk/by-id/ata-VBOX_HARDDISK_VB3649e885-3e0cdd64
/dev/disk/by-path/pci-0000:00:01.1-ata-1.0
/dev/disk/by-id/scsi-SATA_VBOX_HARDDISK_VB3649e885-3e0cdd64
linux-ql21:~ # udevadm info /sys/class/block/sdb |grep by-path
S: disk/by-path/pci-0000:00:01.1-ata-1.1
E: DEVLINKS=/dev/disk/by-id/scsi-0ATA_VBOX_HARDDISK_VBc53b2498-d84ae8de
/dev/disk/by-id/ata-VBOX_HARDDISK_VBc53b2498-d84ae8de
/dev/disk/by-id/scsi-1ATA_VBOX_HARDDISK_VBc53b2498-d84ae8de
/dev/disk/by-id/scsi-SATA_VBOX_HARDDISK_VBc53b2498-d84ae8de
/dev/disk/by-path/pci-0000:00:01.1-ata-1.1

Changelog:
v5: add another parameter compat_link in handle_scsi()
v4: comment for ID_PATH_ATA_COMPAT
    get string length with pointer difference
    (suggested by Franck Bui<fbui@suse.com>)
v3: creating compatible link from env
    variables type change
v2: remove udev rules modification for compatible link
    setup a test scenario of master/slave ATA devices
v1: initial patch
2020-05-21 09:58:06 +02:00

726 lines
26 KiB
C

/* SPDX-License-Identifier: GPL-2.0+ */
/*
* 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;
}
}
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,
};