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-private.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
;
114 if (sd_device_get_parent_with_subsystem_devtype(parent
, "scsi", "scsi_target", &targetdev
) < 0)
116 if (sd_device_get_sysname(targetdev
, &sysname
) < 0)
118 if (sd_device_new_from_subsystem_sysname(&fcdev
, "fc_transport", sysname
) < 0)
120 if (sd_device_get_sysattr_value(fcdev
, "port_name", &port
) < 0)
123 format_lun_number(parent
, &lun
);
124 path_prepend(path
, "fc-%s-%s", port
, lun
);
128 static sd_device
*handle_scsi_sas_wide_port(sd_device
*parent
, char **path
) {
129 sd_device
*targetdev
, *target_parent
;
130 _cleanup_(sd_device_unrefp
) sd_device
*sasdev
= NULL
;
131 const char *sas_address
, *sysname
;
132 _cleanup_free_
char *lun
= NULL
;
137 if (sd_device_get_parent_with_subsystem_devtype(parent
, "scsi", "scsi_target", &targetdev
) < 0)
139 if (sd_device_get_parent(targetdev
, &target_parent
) < 0)
141 if (sd_device_get_sysname(target_parent
, &sysname
) < 0)
143 if (sd_device_new_from_subsystem_sysname(&sasdev
, "sas_device", sysname
) < 0)
145 if (sd_device_get_sysattr_value(sasdev
, "sas_address", &sas_address
) < 0)
148 format_lun_number(parent
, &lun
);
149 path_prepend(path
, "sas-%s-%s", sas_address
, lun
);
153 static sd_device
*handle_scsi_sas(sd_device
*parent
, char **path
) {
154 sd_device
*targetdev
, *target_parent
, *port
, *expander
;
155 _cleanup_(sd_device_unrefp
) sd_device
*target_sasdev
= NULL
, *expander_sasdev
= NULL
, *port_sasdev
= NULL
;
156 const char *sas_address
= NULL
;
158 const char *phy_count
, *sysname
;
159 _cleanup_free_
char *lun
= NULL
;
164 if (sd_device_get_parent_with_subsystem_devtype(parent
, "scsi", "scsi_target", &targetdev
) < 0)
166 if (sd_device_get_parent(targetdev
, &target_parent
) < 0)
168 if (sd_device_get_sysname(target_parent
, &sysname
) < 0)
171 if (sd_device_new_from_subsystem_sysname(&target_sasdev
, "sas_device", sysname
) < 0)
173 /* The next parent is sas port */
174 if (sd_device_get_parent(target_parent
, &port
) < 0)
176 if (sd_device_get_sysname(port
, &sysname
) < 0)
178 /* Get port device */
179 if (sd_device_new_from_subsystem_sysname(&port_sasdev
, "sas_port", sysname
) < 0)
181 if (sd_device_get_sysattr_value(port_sasdev
, "num_phys", &phy_count
) < 0)
184 /* Check if we are simple disk */
185 if (strncmp(phy_count
, "1", 2) != 0)
186 return handle_scsi_sas_wide_port(parent
, path
);
188 /* Get connected phy */
189 if (sd_device_get_sysattr_value(target_sasdev
, "phy_identifier", &phy_id
) < 0)
192 /* The port's parent is either hba or expander */
193 if (sd_device_get_parent(port
, &expander
) < 0)
196 if (sd_device_get_sysname(expander
, &sysname
) < 0)
198 /* Get expander device */
199 if (sd_device_new_from_subsystem_sysname(&expander_sasdev
, "sas_device", sysname
) >= 0) {
200 /* Get expander's address */
201 if (sd_device_get_sysattr_value(expander_sasdev
, "sas_address", &sas_address
) < 0)
205 format_lun_number(parent
, &lun
);
207 path_prepend(path
, "sas-exp%s-phy%s-%s", sas_address
, phy_id
, lun
);
209 path_prepend(path
, "sas-phy%s-%s", phy_id
, lun
);
214 static sd_device
*handle_scsi_iscsi(sd_device
*parent
, char **path
) {
215 sd_device
*transportdev
;
216 _cleanup_(sd_device_unrefp
) sd_device
*sessiondev
= NULL
, *conndev
= NULL
;
217 const char *target
, *connname
, *addr
, *port
;
218 _cleanup_free_
char *lun
= NULL
;
219 const char *sysname
, *sysnum
;
224 /* find iscsi session */
225 for (transportdev
= parent
; ; ) {
227 if (sd_device_get_parent(transportdev
, &transportdev
) < 0)
229 if (sd_device_get_sysname(transportdev
, &sysname
) < 0)
231 if (startswith(sysname
, "session"))
235 /* find iscsi session device */
236 if (sd_device_new_from_subsystem_sysname(&sessiondev
, "iscsi_session", sysname
) < 0)
239 if (sd_device_get_sysattr_value(sessiondev
, "targetname", &target
) < 0)
242 if (sd_device_get_sysnum(transportdev
, &sysnum
) < 0 || !sysnum
)
244 connname
= strjoina("connection", sysnum
, ":0");
245 if (sd_device_new_from_subsystem_sysname(&conndev
, "iscsi_connection", connname
) < 0)
248 if (sd_device_get_sysattr_value(conndev
, "persistent_address", &addr
) < 0)
250 if (sd_device_get_sysattr_value(conndev
, "persistent_port", &port
) < 0)
253 format_lun_number(parent
, &lun
);
254 path_prepend(path
, "ip-%s:%s-iscsi-%s-%s", addr
, port
, target
, lun
);
258 static sd_device
*handle_scsi_ata(sd_device
*parent
, char **path
) {
259 sd_device
*targetdev
, *target_parent
;
260 _cleanup_(sd_device_unrefp
) sd_device
*atadev
= NULL
;
261 const char *port_no
, *sysname
;
266 if (sd_device_get_parent_with_subsystem_devtype(parent
, "scsi", "scsi_host", &targetdev
) < 0)
269 if (sd_device_get_parent(targetdev
, &target_parent
) < 0)
272 if (sd_device_get_sysname(target_parent
, &sysname
) < 0)
274 if (sd_device_new_from_subsystem_sysname(&atadev
, "ata_port", sysname
) < 0)
277 if (sd_device_get_sysattr_value(atadev
, "port_no", &port_no
) < 0)
280 path_prepend(path
, "ata-%s", port_no
);
284 static sd_device
*handle_scsi_default(sd_device
*parent
, char **path
) {
286 int host
, bus
, target
, lun
;
287 const char *name
, *base
, *pos
;
288 _cleanup_closedir_
DIR *dir
= NULL
;
295 if (sd_device_get_parent_with_subsystem_devtype(parent
, "scsi", "scsi_host", &hostdev
) < 0)
298 if (sd_device_get_sysname(parent
, &name
) < 0)
300 if (sscanf(name
, "%d:%d:%d:%d", &host
, &bus
, &target
, &lun
) != 4)
304 * Rebase host offset to get the local relative number
306 * Note: This is by definition racy, unreliable and too simple.
307 * Please do not copy this model anywhere. It's just a left-over
308 * from the time we had no idea how things should look like in
311 * Making assumptions about a global in-kernel counter and use
312 * that to calculate a local offset is a very broken concept. It
313 * can only work as long as things are in strict order.
315 * The kernel needs to export the instance/port number of a
316 * controller directly, without the need for rebase magic like
317 * this. Manual driver unbind/bind, parallel hotplug/unplug will
318 * get into the way of this "I hope it works" logic.
321 if (sd_device_get_syspath(hostdev
, &base
) < 0)
323 pos
= strrchr(base
, '/');
327 base
= strndupa(base
, pos
- base
);
332 FOREACH_DIRENT_ALL(dent
, dir
, break) {
336 if (dent
->d_name
[0] == '.')
338 if (!IN_SET(dent
->d_type
, DT_DIR
, DT_LNK
))
340 if (!startswith(dent
->d_name
, "host"))
342 i
= strtoul(&dent
->d_name
[4], &rest
, 10);
346 * find the smallest number; the host really needs to export its
347 * own instance number per parent device; relying on the global host
348 * enumeration and plainly rebasing the numbers sounds unreliable
350 if (basenum
== -1 || i
< basenum
)
357 path_prepend(path
, "scsi-%u:%u:%u:%u", host
, bus
, target
, lun
);
361 static sd_device
*handle_scsi_hyperv(sd_device
*parent
, char **path
, size_t guid_str_len
) {
364 const char *guid_str
;
365 _cleanup_free_
char *lun
= NULL
;
371 assert(guid_str_len
< sizeof(guid
));
373 if (sd_device_get_parent_with_subsystem_devtype(parent
, "scsi", "scsi_host", &hostdev
) < 0)
376 if (sd_device_get_parent(hostdev
, &vmbusdev
) < 0)
379 if (sd_device_get_sysattr_value(vmbusdev
, "device_id", &guid_str
) < 0)
382 if (strlen(guid_str
) < guid_str_len
|| guid_str
[0] != '{' || guid_str
[guid_str_len
-1] != '}')
385 for (i
= 1, k
= 0; i
< guid_str_len
-1; i
++) {
386 if (guid_str
[i
] == '-')
388 guid
[k
++] = guid_str
[i
];
392 format_lun_number(parent
, &lun
);
393 path_prepend(path
, "vmbus-%s-%s", guid
, lun
);
397 static sd_device
*handle_scsi(sd_device
*parent
, char **path
, bool *supported_parent
) {
398 const char *devtype
, *id
, *name
;
400 if (sd_device_get_devtype(parent
, &devtype
) < 0 ||
401 !streq(devtype
, "scsi_device"))
405 if (sd_device_get_sysattr_value(parent
, "ieee1394_id", &id
) >= 0) {
406 path_prepend(path
, "ieee1394-0x%s", id
);
407 *supported_parent
= true;
408 return skip_subsystem(parent
, "scsi");
411 /* scsi sysfs does not have a "subsystem" for the transport */
412 if (sd_device_get_syspath(parent
, &name
) < 0)
415 if (strstr(name
, "/rport-")) {
416 *supported_parent
= true;
417 return handle_scsi_fibre_channel(parent
, path
);
420 if (strstr(name
, "/end_device-")) {
421 *supported_parent
= true;
422 return handle_scsi_sas(parent
, path
);
425 if (strstr(name
, "/session")) {
426 *supported_parent
= true;
427 return handle_scsi_iscsi(parent
, path
);
430 if (strstr(name
, "/ata"))
431 return handle_scsi_ata(parent
, path
);
433 if (strstr(name
, "/vmbus_"))
434 return handle_scsi_hyperv(parent
, path
, 37);
435 else if (strstr(name
, "/VMBUS"))
436 return handle_scsi_hyperv(parent
, path
, 38);
438 return handle_scsi_default(parent
, path
);
441 static sd_device
*handle_cciss(sd_device
*parent
, char **path
) {
443 unsigned controller
, disk
;
445 if (sd_device_get_sysname(parent
, &str
) < 0)
447 if (sscanf(str
, "c%ud%u%*s", &controller
, &disk
) != 2)
450 path_prepend(path
, "cciss-disk%u", disk
);
451 return skip_subsystem(parent
, "cciss");
454 static void handle_scsi_tape(sd_device
*dev
, char **path
) {
457 /* must be the last device in the syspath */
461 if (sd_device_get_sysname(dev
, &name
) < 0)
464 if (startswith(name
, "nst") && strchr("lma", name
[3]))
465 path_prepend(path
, "nst%c", name
[3]);
466 else if (startswith(name
, "st") && strchr("lma", name
[2]))
467 path_prepend(path
, "st%c", name
[2]);
470 static sd_device
*handle_usb(sd_device
*parent
, char **path
) {
471 const char *devtype
, *str
, *port
;
473 if (sd_device_get_devtype(parent
, &devtype
) < 0)
475 if (!STR_IN_SET(devtype
, "usb_interface", "usb_device"))
478 if (sd_device_get_sysname(parent
, &str
) < 0)
480 port
= strchr(str
, '-');
485 path_prepend(path
, "usb-0:%s", port
);
486 return skip_subsystem(parent
, "usb");
489 static sd_device
*handle_bcma(sd_device
*parent
, char **path
) {
493 if (sd_device_get_sysname(parent
, &sysname
) < 0)
495 if (sscanf(sysname
, "bcma%*u:%u", &core
) != 1)
498 path_prepend(path
, "bcma-%u", core
);
502 /* Handle devices of AP bus in System z platform. */
503 static sd_device
*handle_ap(sd_device
*parent
, char **path
) {
504 const char *type
, *func
;
509 if (sd_device_get_sysattr_value(parent
, "type", &type
) >= 0 &&
510 sd_device_get_sysattr_value(parent
, "ap_functions", &func
) >= 0)
511 path_prepend(path
, "ap-%s-%s", type
, func
);
515 if (sd_device_get_sysname(parent
, &sysname
) >= 0)
516 path_prepend(path
, "ap-%s", sysname
);
519 return skip_subsystem(parent
, "ap");
522 static int builtin_path_id(sd_device
*dev
, int argc
, char *argv
[], bool test
) {
524 _cleanup_free_
char *path
= NULL
;
525 bool supported_transport
= false;
526 bool supported_parent
= false;
527 const char *subsystem
;
531 /* walk up the chain of devices and compose path */
534 const char *subsys
, *sysname
;
536 if (sd_device_get_subsystem(parent
, &subsys
) < 0 ||
537 sd_device_get_sysname(parent
, &sysname
) < 0) {
539 } else if (streq(subsys
, "scsi_tape")) {
540 handle_scsi_tape(parent
, &path
);
541 } else if (streq(subsys
, "scsi")) {
542 parent
= handle_scsi(parent
, &path
, &supported_parent
);
543 supported_transport
= true;
544 } else if (streq(subsys
, "cciss")) {
545 parent
= handle_cciss(parent
, &path
);
546 supported_transport
= true;
547 } else if (streq(subsys
, "usb")) {
548 parent
= handle_usb(parent
, &path
);
549 supported_transport
= true;
550 } else if (streq(subsys
, "bcma")) {
551 parent
= handle_bcma(parent
, &path
);
552 supported_transport
= true;
553 } else if (streq(subsys
, "serio")) {
556 if (sd_device_get_sysnum(parent
, &sysnum
) >= 0 && sysnum
) {
557 path_prepend(&path
, "serio-%s", sysnum
);
558 parent
= skip_subsystem(parent
, "serio");
560 } else if (streq(subsys
, "pci")) {
561 path_prepend(&path
, "pci-%s", sysname
);
562 parent
= skip_subsystem(parent
, "pci");
563 supported_parent
= true;
564 } else if (streq(subsys
, "platform")) {
565 path_prepend(&path
, "platform-%s", sysname
);
566 parent
= skip_subsystem(parent
, "platform");
567 supported_transport
= true;
568 supported_parent
= true;
569 } else if (streq(subsys
, "acpi")) {
570 path_prepend(&path
, "acpi-%s", sysname
);
571 parent
= skip_subsystem(parent
, "acpi");
572 supported_parent
= true;
573 } else if (streq(subsys
, "xen")) {
574 path_prepend(&path
, "xen-%s", sysname
);
575 parent
= skip_subsystem(parent
, "xen");
576 supported_parent
= true;
577 } else if (streq(subsys
, "virtio")) {
578 parent
= skip_subsystem(parent
, "virtio");
579 supported_transport
= true;
580 } else if (streq(subsys
, "scm")) {
581 path_prepend(&path
, "scm-%s", sysname
);
582 parent
= skip_subsystem(parent
, "scm");
583 supported_transport
= true;
584 supported_parent
= true;
585 } else if (streq(subsys
, "ccw")) {
586 path_prepend(&path
, "ccw-%s", sysname
);
587 parent
= skip_subsystem(parent
, "ccw");
588 supported_transport
= true;
589 supported_parent
= true;
590 } else if (streq(subsys
, "ccwgroup")) {
591 path_prepend(&path
, "ccwgroup-%s", sysname
);
592 parent
= skip_subsystem(parent
, "ccwgroup");
593 supported_transport
= true;
594 supported_parent
= true;
595 } else if (streq(subsys
, "ap")) {
596 parent
= handle_ap(parent
, &path
);
597 supported_transport
= true;
598 supported_parent
= true;
599 } else if (streq(subsys
, "iucv")) {
600 path_prepend(&path
, "iucv-%s", sysname
);
601 parent
= skip_subsystem(parent
, "iucv");
602 supported_transport
= true;
603 supported_parent
= true;
604 } else if (streq(subsys
, "nvme")) {
607 if (sd_device_get_sysattr_value(dev
, "nsid", &nsid
) >= 0) {
608 path_prepend(&path
, "nvme-%s", nsid
);
609 parent
= skip_subsystem(parent
, "nvme");
610 supported_parent
= true;
611 supported_transport
= true;
617 if (sd_device_get_parent(parent
, &parent
) < 0)
625 * Do not return devices with an unknown parent device type. They
626 * might produce conflicting IDs if the parent does not provide a
627 * unique and predictable name.
629 if (!supported_parent
)
633 * Do not return block devices without a well-known transport. Some
634 * devices do not expose their buses and do not provide a unique
635 * and predictable name that way.
637 if (sd_device_get_subsystem(dev
, &subsystem
) >= 0 &&
638 streq(subsystem
, "block") &&
639 !supported_transport
)
643 char tag
[UTIL_NAME_SIZE
];
647 /* compose valid udev tag name */
648 for (p
= path
, i
= 0; *p
; p
++) {
649 if ((*p
>= '0' && *p
<= '9') ||
650 (*p
>= 'A' && *p
<= 'Z') ||
651 (*p
>= 'a' && *p
<= 'z') ||
657 /* skip all leading '_' */
661 /* avoid second '_' */
667 /* strip trailing '_' */
668 while (i
> 0 && tag
[i
-1] == '_')
672 udev_builtin_add_property(dev
, test
, "ID_PATH", path
);
673 udev_builtin_add_property(dev
, test
, "ID_PATH_TAG", tag
);
679 const struct udev_builtin udev_builtin_path_id
= {
681 .cmd
= builtin_path_id
,
682 .help
= "Compose persistent device path",