1 /* SPDX-License-Identifier: GPL-2.0+ */
3 * compose persistent device path
5 * Logic based on Hannes Reinecke's shell script.
18 #include "alloc-util.h"
19 #include "dirent-util.h"
21 #include "libudev-util.h"
22 #include "string-util.h"
25 #include "udev-builtin.h"
28 static void path_prepend(char **path
, const char *fmt
, ...) {
30 _cleanup_free_
char *pre
= NULL
;
34 r
= vasprintf(&pre
, fmt
, va
);
44 new = strjoin(pre
, "-", *path
);
50 free_and_replace(*path
, new);
52 *path
= TAKE_PTR(pre
);
56 ** Linux only supports 32 bit luns.
57 ** See drivers/scsi/scsi_scan.c::scsilun_to_int() for more details.
59 static int format_lun_number(sd_device
*dev
, char **path
) {
64 r
= sd_device_get_sysnum(dev
, &sysnum
);
70 lun
= strtoul(sysnum
, NULL
, 10);
72 /* address method 0, peripheral device addressing with bus id of zero */
73 path_prepend(path
, "lun-%lu", lun
);
75 /* handle all other lun addressing methods by using a variant of the original lun format */
76 path_prepend(path
, "lun-0x%04lx%04lx00000000", lun
& 0xffff, (lun
>> 16) & 0xffff);
81 static sd_device
*skip_subsystem(sd_device
*dev
, const char *subsys
) {
87 for (parent
= dev
; ; ) {
88 const char *subsystem
;
90 if (sd_device_get_subsystem(parent
, &subsystem
) < 0)
93 if (!streq(subsystem
, subsys
))
97 if (sd_device_get_parent(dev
, &parent
) < 0)
104 static sd_device
*handle_scsi_fibre_channel(sd_device
*parent
, char **path
) {
105 sd_device
*targetdev
;
106 _cleanup_(sd_device_unrefp
) sd_device
*fcdev
= NULL
;
107 const char *port
, *sysname
;
108 _cleanup_free_
char *lun
= NULL
;
113 if (sd_device_get_parent_with_subsystem_devtype(parent
, "scsi", "scsi_target", &targetdev
) < 0)
115 if (sd_device_get_sysname(targetdev
, &sysname
) < 0)
117 if (sd_device_new_from_subsystem_sysname(&fcdev
, "fc_transport", sysname
) < 0)
119 if (sd_device_get_sysattr_value(fcdev
, "port_name", &port
) < 0)
122 format_lun_number(parent
, &lun
);
123 path_prepend(path
, "fc-%s-%s", port
, lun
);
127 static sd_device
*handle_scsi_sas_wide_port(sd_device
*parent
, char **path
) {
128 sd_device
*targetdev
, *target_parent
;
129 _cleanup_(sd_device_unrefp
) sd_device
*sasdev
= NULL
;
130 const char *sas_address
, *sysname
;
131 _cleanup_free_
char *lun
= NULL
;
136 if (sd_device_get_parent_with_subsystem_devtype(parent
, "scsi", "scsi_target", &targetdev
) < 0)
138 if (sd_device_get_parent(targetdev
, &target_parent
) < 0)
140 if (sd_device_get_sysname(target_parent
, &sysname
) < 0)
142 if (sd_device_new_from_subsystem_sysname(&sasdev
, "sas_device", sysname
) < 0)
144 if (sd_device_get_sysattr_value(sasdev
, "sas_address", &sas_address
) < 0)
147 format_lun_number(parent
, &lun
);
148 path_prepend(path
, "sas-%s-%s", sas_address
, lun
);
152 static sd_device
*handle_scsi_sas(sd_device
*parent
, char **path
) {
153 sd_device
*targetdev
, *target_parent
, *port
, *expander
;
154 _cleanup_(sd_device_unrefp
) sd_device
*target_sasdev
= NULL
, *expander_sasdev
= NULL
, *port_sasdev
= NULL
;
155 const char *sas_address
= NULL
;
157 const char *phy_count
, *sysname
;
158 _cleanup_free_
char *lun
= NULL
;
163 if (sd_device_get_parent_with_subsystem_devtype(parent
, "scsi", "scsi_target", &targetdev
) < 0)
165 if (sd_device_get_parent(targetdev
, &target_parent
) < 0)
167 if (sd_device_get_sysname(target_parent
, &sysname
) < 0)
170 if (sd_device_new_from_subsystem_sysname(&target_sasdev
, "sas_device", sysname
) < 0)
172 /* The next parent is sas port */
173 if (sd_device_get_parent(target_parent
, &port
) < 0)
175 if (sd_device_get_sysname(port
, &sysname
) < 0)
177 /* Get port device */
178 if (sd_device_new_from_subsystem_sysname(&port_sasdev
, "sas_port", sysname
) < 0)
180 if (sd_device_get_sysattr_value(port_sasdev
, "num_phys", &phy_count
) < 0)
183 /* Check if we are simple disk */
184 if (strncmp(phy_count
, "1", 2) != 0)
185 return handle_scsi_sas_wide_port(parent
, path
);
187 /* Get connected phy */
188 if (sd_device_get_sysattr_value(target_sasdev
, "phy_identifier", &phy_id
) < 0)
191 /* The port's parent is either hba or expander */
192 if (sd_device_get_parent(port
, &expander
) < 0)
195 if (sd_device_get_sysname(expander
, &sysname
) < 0)
197 /* Get expander device */
198 if (sd_device_new_from_subsystem_sysname(&expander_sasdev
, "sas_device", sysname
) >= 0) {
199 /* Get expander's address */
200 if (sd_device_get_sysattr_value(expander_sasdev
, "sas_address", &sas_address
) < 0)
204 format_lun_number(parent
, &lun
);
206 path_prepend(path
, "sas-exp%s-phy%s-%s", sas_address
, phy_id
, lun
);
208 path_prepend(path
, "sas-phy%s-%s", phy_id
, lun
);
213 static sd_device
*handle_scsi_iscsi(sd_device
*parent
, char **path
) {
214 sd_device
*transportdev
;
215 _cleanup_(sd_device_unrefp
) sd_device
*sessiondev
= NULL
, *conndev
= NULL
;
216 const char *target
, *connname
, *addr
, *port
;
217 _cleanup_free_
char *lun
= NULL
;
218 const char *sysname
, *sysnum
;
223 /* find iscsi session */
224 for (transportdev
= parent
; ; ) {
226 if (sd_device_get_parent(transportdev
, &transportdev
) < 0)
228 if (sd_device_get_sysname(transportdev
, &sysname
) < 0)
230 if (startswith(sysname
, "session"))
234 /* find iscsi session device */
235 if (sd_device_new_from_subsystem_sysname(&sessiondev
, "iscsi_session", sysname
) < 0)
238 if (sd_device_get_sysattr_value(sessiondev
, "targetname", &target
) < 0)
241 if (sd_device_get_sysnum(transportdev
, &sysnum
) < 0 || !sysnum
)
243 connname
= strjoina("connection", sysnum
, ":0");
244 if (sd_device_new_from_subsystem_sysname(&conndev
, "iscsi_connection", connname
) < 0)
247 if (sd_device_get_sysattr_value(conndev
, "persistent_address", &addr
) < 0)
249 if (sd_device_get_sysattr_value(conndev
, "persistent_port", &port
) < 0)
252 format_lun_number(parent
, &lun
);
253 path_prepend(path
, "ip-%s:%s-iscsi-%s-%s", addr
, port
, target
, lun
);
257 static sd_device
*handle_scsi_ata(sd_device
*parent
, char **path
) {
258 sd_device
*targetdev
, *target_parent
;
259 _cleanup_(sd_device_unrefp
) sd_device
*atadev
= NULL
;
260 const char *port_no
, *sysname
;
265 if (sd_device_get_parent_with_subsystem_devtype(parent
, "scsi", "scsi_host", &targetdev
) < 0)
268 if (sd_device_get_parent(targetdev
, &target_parent
) < 0)
271 if (sd_device_get_sysname(target_parent
, &sysname
) < 0)
273 if (sd_device_new_from_subsystem_sysname(&atadev
, "ata_port", sysname
) < 0)
276 if (sd_device_get_sysattr_value(atadev
, "port_no", &port_no
) < 0)
279 path_prepend(path
, "ata-%s", port_no
);
283 static sd_device
*handle_scsi_default(sd_device
*parent
, char **path
) {
285 int host
, bus
, target
, lun
;
286 const char *name
, *base
, *pos
;
287 _cleanup_closedir_
DIR *dir
= NULL
;
294 if (sd_device_get_parent_with_subsystem_devtype(parent
, "scsi", "scsi_host", &hostdev
) < 0)
297 if (sd_device_get_sysname(parent
, &name
) < 0)
299 if (sscanf(name
, "%d:%d:%d:%d", &host
, &bus
, &target
, &lun
) != 4)
303 * Rebase host offset to get the local relative number
305 * Note: This is by definition racy, unreliable and too simple.
306 * Please do not copy this model anywhere. It's just a left-over
307 * from the time we had no idea how things should look like in
310 * Making assumptions about a global in-kernel counter and use
311 * that to calculate a local offset is a very broken concept. It
312 * can only work as long as things are in strict order.
314 * The kernel needs to export the instance/port number of a
315 * controller directly, without the need for rebase magic like
316 * this. Manual driver unbind/bind, parallel hotplug/unplug will
317 * get into the way of this "I hope it works" logic.
320 if (sd_device_get_syspath(hostdev
, &base
) < 0)
322 pos
= strrchr(base
, '/');
326 base
= strndupa(base
, pos
- base
);
331 FOREACH_DIRENT_ALL(dent
, dir
, break) {
335 if (dent
->d_name
[0] == '.')
337 if (!IN_SET(dent
->d_type
, DT_DIR
, DT_LNK
))
339 if (!startswith(dent
->d_name
, "host"))
341 i
= strtoul(&dent
->d_name
[4], &rest
, 10);
345 * find the smallest number; the host really needs to export its
346 * own instance number per parent device; relying on the global host
347 * enumeration and plainly rebasing the numbers sounds unreliable
349 if (basenum
== -1 || i
< basenum
)
356 path_prepend(path
, "scsi-%u:%u:%u:%u", host
, bus
, target
, lun
);
360 static sd_device
*handle_scsi_hyperv(sd_device
*parent
, char **path
, size_t guid_str_len
) {
363 const char *guid_str
;
364 _cleanup_free_
char *lun
= NULL
;
370 assert(guid_str_len
< sizeof(guid
));
372 if (sd_device_get_parent_with_subsystem_devtype(parent
, "scsi", "scsi_host", &hostdev
) < 0)
375 if (sd_device_get_parent(hostdev
, &vmbusdev
) < 0)
378 if (sd_device_get_sysattr_value(vmbusdev
, "device_id", &guid_str
) < 0)
381 if (strlen(guid_str
) < guid_str_len
|| guid_str
[0] != '{' || guid_str
[guid_str_len
-1] != '}')
384 for (i
= 1, k
= 0; i
< guid_str_len
-1; i
++) {
385 if (guid_str
[i
] == '-')
387 guid
[k
++] = guid_str
[i
];
391 format_lun_number(parent
, &lun
);
392 path_prepend(path
, "vmbus-%s-%s", guid
, lun
);
396 static sd_device
*handle_scsi(sd_device
*parent
, char **path
, bool *supported_parent
) {
397 const char *devtype
, *id
, *name
;
399 if (sd_device_get_devtype(parent
, &devtype
) < 0 ||
400 !streq(devtype
, "scsi_device"))
404 if (sd_device_get_sysattr_value(parent
, "ieee1394_id", &id
) >= 0) {
405 path_prepend(path
, "ieee1394-0x%s", id
);
406 *supported_parent
= true;
407 return skip_subsystem(parent
, "scsi");
410 /* scsi sysfs does not have a "subsystem" for the transport */
411 if (sd_device_get_syspath(parent
, &name
) < 0)
414 if (strstr(name
, "/rport-")) {
415 *supported_parent
= true;
416 return handle_scsi_fibre_channel(parent
, path
);
419 if (strstr(name
, "/end_device-")) {
420 *supported_parent
= true;
421 return handle_scsi_sas(parent
, path
);
424 if (strstr(name
, "/session")) {
425 *supported_parent
= true;
426 return handle_scsi_iscsi(parent
, path
);
429 if (strstr(name
, "/ata"))
430 return handle_scsi_ata(parent
, path
);
432 if (strstr(name
, "/vmbus_"))
433 return handle_scsi_hyperv(parent
, path
, 37);
434 else if (strstr(name
, "/VMBUS"))
435 return handle_scsi_hyperv(parent
, path
, 38);
437 return handle_scsi_default(parent
, path
);
440 static sd_device
*handle_cciss(sd_device
*parent
, char **path
) {
442 unsigned controller
, disk
;
444 if (sd_device_get_sysname(parent
, &str
) < 0)
446 if (sscanf(str
, "c%ud%u%*s", &controller
, &disk
) != 2)
449 path_prepend(path
, "cciss-disk%u", disk
);
450 return skip_subsystem(parent
, "cciss");
453 static void handle_scsi_tape(sd_device
*dev
, char **path
) {
456 /* must be the last device in the syspath */
460 if (sd_device_get_sysname(dev
, &name
) < 0)
463 if (startswith(name
, "nst") && strchr("lma", name
[3]))
464 path_prepend(path
, "nst%c", name
[3]);
465 else if (startswith(name
, "st") && strchr("lma", name
[2]))
466 path_prepend(path
, "st%c", name
[2]);
469 static sd_device
*handle_usb(sd_device
*parent
, char **path
) {
470 const char *devtype
, *str
, *port
;
472 if (sd_device_get_devtype(parent
, &devtype
) < 0)
474 if (!STR_IN_SET(devtype
, "usb_interface", "usb_device"))
477 if (sd_device_get_sysname(parent
, &str
) < 0)
479 port
= strchr(str
, '-');
484 path_prepend(path
, "usb-0:%s", port
);
485 return skip_subsystem(parent
, "usb");
488 static sd_device
*handle_bcma(sd_device
*parent
, char **path
) {
492 if (sd_device_get_sysname(parent
, &sysname
) < 0)
494 if (sscanf(sysname
, "bcma%*u:%u", &core
) != 1)
497 path_prepend(path
, "bcma-%u", core
);
501 /* Handle devices of AP bus in System z platform. */
502 static sd_device
*handle_ap(sd_device
*parent
, char **path
) {
503 const char *type
, *func
;
508 if (sd_device_get_sysattr_value(parent
, "type", &type
) >= 0 &&
509 sd_device_get_sysattr_value(parent
, "ap_functions", &func
) >= 0)
510 path_prepend(path
, "ap-%s-%s", type
, func
);
514 if (sd_device_get_sysname(parent
, &sysname
) >= 0)
515 path_prepend(path
, "ap-%s", sysname
);
518 return skip_subsystem(parent
, "ap");
521 static int builtin_path_id(sd_device
*dev
, int argc
, char *argv
[], bool test
) {
523 _cleanup_free_
char *path
= NULL
;
524 bool supported_transport
= false;
525 bool supported_parent
= false;
526 const char *subsystem
;
530 /* walk up the chain of devices and compose path */
533 const char *subsys
, *sysname
;
535 if (sd_device_get_subsystem(parent
, &subsys
) < 0 ||
536 sd_device_get_sysname(parent
, &sysname
) < 0) {
538 } else if (streq(subsys
, "scsi_tape")) {
539 handle_scsi_tape(parent
, &path
);
540 } else if (streq(subsys
, "scsi")) {
541 parent
= handle_scsi(parent
, &path
, &supported_parent
);
542 supported_transport
= true;
543 } else if (streq(subsys
, "cciss")) {
544 parent
= handle_cciss(parent
, &path
);
545 supported_transport
= true;
546 } else if (streq(subsys
, "usb")) {
547 parent
= handle_usb(parent
, &path
);
548 supported_transport
= true;
549 } else if (streq(subsys
, "bcma")) {
550 parent
= handle_bcma(parent
, &path
);
551 supported_transport
= true;
552 } else if (streq(subsys
, "serio")) {
555 if (sd_device_get_sysnum(parent
, &sysnum
) >= 0 && sysnum
) {
556 path_prepend(&path
, "serio-%s", sysnum
);
557 parent
= skip_subsystem(parent
, "serio");
559 } else if (streq(subsys
, "pci")) {
560 path_prepend(&path
, "pci-%s", sysname
);
561 parent
= skip_subsystem(parent
, "pci");
562 supported_parent
= true;
563 } else if (streq(subsys
, "platform")) {
564 path_prepend(&path
, "platform-%s", sysname
);
565 parent
= skip_subsystem(parent
, "platform");
566 supported_transport
= true;
567 supported_parent
= true;
568 } else if (streq(subsys
, "acpi")) {
569 path_prepend(&path
, "acpi-%s", sysname
);
570 parent
= skip_subsystem(parent
, "acpi");
571 supported_parent
= true;
572 } else if (streq(subsys
, "xen")) {
573 path_prepend(&path
, "xen-%s", sysname
);
574 parent
= skip_subsystem(parent
, "xen");
575 supported_parent
= true;
576 } else if (streq(subsys
, "virtio")) {
577 parent
= skip_subsystem(parent
, "virtio");
578 supported_transport
= true;
579 } else if (streq(subsys
, "scm")) {
580 path_prepend(&path
, "scm-%s", sysname
);
581 parent
= skip_subsystem(parent
, "scm");
582 supported_transport
= true;
583 supported_parent
= true;
584 } else if (streq(subsys
, "ccw")) {
585 path_prepend(&path
, "ccw-%s", sysname
);
586 parent
= skip_subsystem(parent
, "ccw");
587 supported_transport
= true;
588 supported_parent
= true;
589 } else if (streq(subsys
, "ccwgroup")) {
590 path_prepend(&path
, "ccwgroup-%s", sysname
);
591 parent
= skip_subsystem(parent
, "ccwgroup");
592 supported_transport
= true;
593 supported_parent
= true;
594 } else if (streq(subsys
, "ap")) {
595 parent
= handle_ap(parent
, &path
);
596 supported_transport
= true;
597 supported_parent
= true;
598 } else if (streq(subsys
, "iucv")) {
599 path_prepend(&path
, "iucv-%s", sysname
);
600 parent
= skip_subsystem(parent
, "iucv");
601 supported_transport
= true;
602 supported_parent
= true;
603 } else if (streq(subsys
, "nvme")) {
606 if (sd_device_get_sysattr_value(dev
, "nsid", &nsid
) >= 0) {
607 path_prepend(&path
, "nvme-%s", nsid
);
608 parent
= skip_subsystem(parent
, "nvme");
609 supported_parent
= true;
610 supported_transport
= true;
616 if (sd_device_get_parent(parent
, &parent
) < 0)
624 * Do not return devices with an unknown parent device type. They
625 * might produce conflicting IDs if the parent does not provide a
626 * unique and predictable name.
628 if (!supported_parent
)
632 * Do not return block devices without a well-known transport. Some
633 * devices do not expose their buses and do not provide a unique
634 * and predictable name that way.
636 if (sd_device_get_subsystem(dev
, &subsystem
) >= 0 &&
637 streq(subsystem
, "block") &&
638 !supported_transport
)
642 char tag
[UTIL_NAME_SIZE
];
646 /* compose valid udev tag name */
647 for (p
= path
, i
= 0; *p
; p
++) {
648 if ((*p
>= '0' && *p
<= '9') ||
649 (*p
>= 'A' && *p
<= 'Z') ||
650 (*p
>= 'a' && *p
<= 'z') ||
656 /* skip all leading '_' */
660 /* avoid second '_' */
666 /* strip trailing '_' */
667 while (i
> 0 && tag
[i
-1] == '_')
671 udev_builtin_add_property(dev
, test
, "ID_PATH", path
);
672 udev_builtin_add_property(dev
, test
, "ID_PATH_TAG", tag
);
678 const struct udev_builtin udev_builtin_path_id
= {
680 .cmd
= builtin_path_id
,
681 .help
= "Compose persistent device path",