1 /* SPDX-License-Identifier: GPL-2.0+ */
3 * compose persistent device path
5 * Logic based on Hannes Reinecke's shell script.
17 #include "alloc-util.h"
18 #include "dirent-util.h"
20 #include "libudev-util.h"
21 #include "string-util.h"
24 #include "udev-builtin.h"
27 static void path_prepend(char **path
, const char *fmt
, ...) {
29 _cleanup_free_
char *pre
= NULL
;
33 r
= vasprintf(&pre
, fmt
, va
);
43 new = strjoin(pre
, "-", *path
);
49 free_and_replace(*path
, new);
51 *path
= TAKE_PTR(pre
);
55 ** Linux only supports 32 bit luns.
56 ** See drivers/scsi/scsi_scan.c::scsilun_to_int() for more details.
58 static int format_lun_number(sd_device
*dev
, char **path
) {
63 r
= sd_device_get_sysnum(dev
, &sysnum
);
69 lun
= strtoul(sysnum
, NULL
, 10);
71 /* address method 0, peripheral device addressing with bus id of zero */
72 path_prepend(path
, "lun-%lu", lun
);
74 /* handle all other lun addressing methods by using a variant of the original lun format */
75 path_prepend(path
, "lun-0x%04lx%04lx00000000", lun
& 0xffff, (lun
>> 16) & 0xffff);
80 static sd_device
*skip_subsystem(sd_device
*dev
, const char *subsys
) {
86 for (parent
= dev
; ; ) {
87 const char *subsystem
;
89 if (sd_device_get_subsystem(parent
, &subsystem
) < 0)
92 if (!streq(subsystem
, subsys
))
96 if (sd_device_get_parent(dev
, &parent
) < 0)
103 static sd_device
*handle_scsi_fibre_channel(sd_device
*parent
, char **path
) {
104 sd_device
*targetdev
;
105 _cleanup_(sd_device_unrefp
) sd_device
*fcdev
= NULL
;
106 const char *port
, *sysname
;
107 _cleanup_free_
char *lun
= NULL
;
112 if (sd_device_get_parent_with_subsystem_devtype(parent
, "scsi", "scsi_target", &targetdev
) < 0)
114 if (sd_device_get_sysname(targetdev
, &sysname
) < 0)
116 if (sd_device_new_from_subsystem_sysname(&fcdev
, "fc_transport", sysname
) < 0)
118 if (sd_device_get_sysattr_value(fcdev
, "port_name", &port
) < 0)
121 format_lun_number(parent
, &lun
);
122 path_prepend(path
, "fc-%s-%s", port
, lun
);
126 static sd_device
*handle_scsi_sas_wide_port(sd_device
*parent
, char **path
) {
127 sd_device
*targetdev
, *target_parent
;
128 _cleanup_(sd_device_unrefp
) sd_device
*sasdev
= NULL
;
129 const char *sas_address
, *sysname
;
130 _cleanup_free_
char *lun
= NULL
;
135 if (sd_device_get_parent_with_subsystem_devtype(parent
, "scsi", "scsi_target", &targetdev
) < 0)
137 if (sd_device_get_parent(targetdev
, &target_parent
) < 0)
139 if (sd_device_get_sysname(target_parent
, &sysname
) < 0)
141 if (sd_device_new_from_subsystem_sysname(&sasdev
, "sas_device", sysname
) < 0)
143 if (sd_device_get_sysattr_value(sasdev
, "sas_address", &sas_address
) < 0)
146 format_lun_number(parent
, &lun
);
147 path_prepend(path
, "sas-%s-%s", sas_address
, lun
);
151 static sd_device
*handle_scsi_sas(sd_device
*parent
, char **path
) {
152 sd_device
*targetdev
, *target_parent
, *port
, *expander
;
153 _cleanup_(sd_device_unrefp
) sd_device
*target_sasdev
= NULL
, *expander_sasdev
= NULL
, *port_sasdev
= NULL
;
154 const char *sas_address
= NULL
;
156 const char *phy_count
, *sysname
;
157 _cleanup_free_
char *lun
= NULL
;
162 if (sd_device_get_parent_with_subsystem_devtype(parent
, "scsi", "scsi_target", &targetdev
) < 0)
164 if (sd_device_get_parent(targetdev
, &target_parent
) < 0)
166 if (sd_device_get_sysname(target_parent
, &sysname
) < 0)
169 if (sd_device_new_from_subsystem_sysname(&target_sasdev
, "sas_device", sysname
) < 0)
171 /* The next parent is sas port */
172 if (sd_device_get_parent(target_parent
, &port
) < 0)
174 if (sd_device_get_sysname(port
, &sysname
) < 0)
176 /* Get port device */
177 if (sd_device_new_from_subsystem_sysname(&port_sasdev
, "sas_port", sysname
) < 0)
179 if (sd_device_get_sysattr_value(port_sasdev
, "num_phys", &phy_count
) < 0)
182 /* Check if we are simple disk */
183 if (strncmp(phy_count
, "1", 2) != 0)
184 return handle_scsi_sas_wide_port(parent
, path
);
186 /* Get connected phy */
187 if (sd_device_get_sysattr_value(target_sasdev
, "phy_identifier", &phy_id
) < 0)
190 /* The port's parent is either hba or expander */
191 if (sd_device_get_parent(port
, &expander
) < 0)
194 if (sd_device_get_sysname(expander
, &sysname
) < 0)
196 /* Get expander device */
197 if (sd_device_new_from_subsystem_sysname(&expander_sasdev
, "sas_device", sysname
) >= 0) {
198 /* Get expander's address */
199 if (sd_device_get_sysattr_value(expander_sasdev
, "sas_address", &sas_address
) < 0)
203 format_lun_number(parent
, &lun
);
205 path_prepend(path
, "sas-exp%s-phy%s-%s", sas_address
, phy_id
, lun
);
207 path_prepend(path
, "sas-phy%s-%s", phy_id
, lun
);
212 static sd_device
*handle_scsi_iscsi(sd_device
*parent
, char **path
) {
213 sd_device
*transportdev
;
214 _cleanup_(sd_device_unrefp
) sd_device
*sessiondev
= NULL
, *conndev
= NULL
;
215 const char *target
, *connname
, *addr
, *port
;
216 _cleanup_free_
char *lun
= NULL
;
217 const char *sysname
, *sysnum
;
222 /* find iscsi session */
223 for (transportdev
= parent
; ; ) {
225 if (sd_device_get_parent(transportdev
, &transportdev
) < 0)
227 if (sd_device_get_sysname(transportdev
, &sysname
) < 0)
229 if (startswith(sysname
, "session"))
233 /* find iscsi session device */
234 if (sd_device_new_from_subsystem_sysname(&sessiondev
, "iscsi_session", sysname
) < 0)
237 if (sd_device_get_sysattr_value(sessiondev
, "targetname", &target
) < 0)
240 if (sd_device_get_sysnum(transportdev
, &sysnum
) < 0 || !sysnum
)
242 connname
= strjoina("connection", sysnum
, ":0");
243 if (sd_device_new_from_subsystem_sysname(&conndev
, "iscsi_connection", connname
) < 0)
246 if (sd_device_get_sysattr_value(conndev
, "persistent_address", &addr
) < 0)
248 if (sd_device_get_sysattr_value(conndev
, "persistent_port", &port
) < 0)
251 format_lun_number(parent
, &lun
);
252 path_prepend(path
, "ip-%s:%s-iscsi-%s-%s", addr
, port
, target
, lun
);
256 static sd_device
*handle_scsi_ata(sd_device
*parent
, char **path
) {
257 sd_device
*targetdev
, *target_parent
;
258 _cleanup_(sd_device_unrefp
) sd_device
*atadev
= NULL
;
259 const char *port_no
, *sysname
;
264 if (sd_device_get_parent_with_subsystem_devtype(parent
, "scsi", "scsi_host", &targetdev
) < 0)
267 if (sd_device_get_parent(targetdev
, &target_parent
) < 0)
270 if (sd_device_get_sysname(target_parent
, &sysname
) < 0)
272 if (sd_device_new_from_subsystem_sysname(&atadev
, "ata_port", sysname
) < 0)
275 if (sd_device_get_sysattr_value(atadev
, "port_no", &port_no
) < 0)
278 path_prepend(path
, "ata-%s", port_no
);
282 static sd_device
*handle_scsi_default(sd_device
*parent
, char **path
) {
284 int host
, bus
, target
, lun
;
285 const char *name
, *base
, *pos
;
286 _cleanup_closedir_
DIR *dir
= NULL
;
293 if (sd_device_get_parent_with_subsystem_devtype(parent
, "scsi", "scsi_host", &hostdev
) < 0)
296 if (sd_device_get_sysname(parent
, &name
) < 0)
298 if (sscanf(name
, "%d:%d:%d:%d", &host
, &bus
, &target
, &lun
) != 4)
302 * Rebase host offset to get the local relative number
304 * Note: This is by definition racy, unreliable and too simple.
305 * Please do not copy this model anywhere. It's just a left-over
306 * from the time we had no idea how things should look like in
309 * Making assumptions about a global in-kernel counter and use
310 * that to calculate a local offset is a very broken concept. It
311 * can only work as long as things are in strict order.
313 * The kernel needs to export the instance/port number of a
314 * controller directly, without the need for rebase magic like
315 * this. Manual driver unbind/bind, parallel hotplug/unplug will
316 * get into the way of this "I hope it works" logic.
319 if (sd_device_get_syspath(hostdev
, &base
) < 0)
321 pos
= strrchr(base
, '/');
325 base
= strndupa(base
, pos
- base
);
330 FOREACH_DIRENT_ALL(dent
, dir
, break) {
334 if (dent
->d_name
[0] == '.')
336 if (!IN_SET(dent
->d_type
, DT_DIR
, DT_LNK
))
338 if (!startswith(dent
->d_name
, "host"))
340 i
= strtoul(&dent
->d_name
[4], &rest
, 10);
344 * find the smallest number; the host really needs to export its
345 * own instance number per parent device; relying on the global host
346 * enumeration and plainly rebasing the numbers sounds unreliable
348 if (basenum
== -1 || i
< basenum
)
355 path_prepend(path
, "scsi-%u:%u:%u:%u", host
, bus
, target
, lun
);
359 static sd_device
*handle_scsi_hyperv(sd_device
*parent
, char **path
, size_t guid_str_len
) {
362 const char *guid_str
;
363 _cleanup_free_
char *lun
= NULL
;
369 assert(guid_str_len
< sizeof(guid
));
371 if (sd_device_get_parent_with_subsystem_devtype(parent
, "scsi", "scsi_host", &hostdev
) < 0)
374 if (sd_device_get_parent(hostdev
, &vmbusdev
) < 0)
377 if (sd_device_get_sysattr_value(vmbusdev
, "device_id", &guid_str
) < 0)
380 if (strlen(guid_str
) < guid_str_len
|| guid_str
[0] != '{' || guid_str
[guid_str_len
-1] != '}')
383 for (i
= 1, k
= 0; i
< guid_str_len
-1; i
++) {
384 if (guid_str
[i
] == '-')
386 guid
[k
++] = guid_str
[i
];
390 format_lun_number(parent
, &lun
);
391 path_prepend(path
, "vmbus-%s-%s", guid
, lun
);
395 static sd_device
*handle_scsi(sd_device
*parent
, char **path
, bool *supported_parent
) {
396 const char *devtype
, *id
, *name
;
398 if (sd_device_get_devtype(parent
, &devtype
) < 0 ||
399 !streq(devtype
, "scsi_device"))
403 if (sd_device_get_sysattr_value(parent
, "ieee1394_id", &id
) >= 0) {
404 path_prepend(path
, "ieee1394-0x%s", id
);
405 *supported_parent
= true;
406 return skip_subsystem(parent
, "scsi");
409 /* scsi sysfs does not have a "subsystem" for the transport */
410 if (sd_device_get_syspath(parent
, &name
) < 0)
413 if (strstr(name
, "/rport-")) {
414 *supported_parent
= true;
415 return handle_scsi_fibre_channel(parent
, path
);
418 if (strstr(name
, "/end_device-")) {
419 *supported_parent
= true;
420 return handle_scsi_sas(parent
, path
);
423 if (strstr(name
, "/session")) {
424 *supported_parent
= true;
425 return handle_scsi_iscsi(parent
, path
);
428 if (strstr(name
, "/ata"))
429 return handle_scsi_ata(parent
, path
);
431 if (strstr(name
, "/vmbus_"))
432 return handle_scsi_hyperv(parent
, path
, 37);
433 else if (strstr(name
, "/VMBUS"))
434 return handle_scsi_hyperv(parent
, path
, 38);
436 return handle_scsi_default(parent
, path
);
439 static sd_device
*handle_cciss(sd_device
*parent
, char **path
) {
441 unsigned controller
, disk
;
443 if (sd_device_get_sysname(parent
, &str
) < 0)
445 if (sscanf(str
, "c%ud%u%*s", &controller
, &disk
) != 2)
448 path_prepend(path
, "cciss-disk%u", disk
);
449 return skip_subsystem(parent
, "cciss");
452 static void handle_scsi_tape(sd_device
*dev
, char **path
) {
455 /* must be the last device in the syspath */
459 if (sd_device_get_sysname(dev
, &name
) < 0)
462 if (startswith(name
, "nst") && strchr("lma", name
[3]))
463 path_prepend(path
, "nst%c", name
[3]);
464 else if (startswith(name
, "st") && strchr("lma", name
[2]))
465 path_prepend(path
, "st%c", name
[2]);
468 static sd_device
*handle_usb(sd_device
*parent
, char **path
) {
469 const char *devtype
, *str
, *port
;
471 if (sd_device_get_devtype(parent
, &devtype
) < 0)
473 if (!STR_IN_SET(devtype
, "usb_interface", "usb_device"))
476 if (sd_device_get_sysname(parent
, &str
) < 0)
478 port
= strchr(str
, '-');
483 path_prepend(path
, "usb-0:%s", port
);
484 return skip_subsystem(parent
, "usb");
487 static sd_device
*handle_bcma(sd_device
*parent
, char **path
) {
491 if (sd_device_get_sysname(parent
, &sysname
) < 0)
493 if (sscanf(sysname
, "bcma%*u:%u", &core
) != 1)
496 path_prepend(path
, "bcma-%u", core
);
500 /* Handle devices of AP bus in System z platform. */
501 static sd_device
*handle_ap(sd_device
*parent
, char **path
) {
502 const char *type
, *func
;
507 if (sd_device_get_sysattr_value(parent
, "type", &type
) >= 0 &&
508 sd_device_get_sysattr_value(parent
, "ap_functions", &func
) >= 0)
509 path_prepend(path
, "ap-%s-%s", type
, func
);
513 if (sd_device_get_sysname(parent
, &sysname
) >= 0)
514 path_prepend(path
, "ap-%s", sysname
);
517 return skip_subsystem(parent
, "ap");
520 static int builtin_path_id(sd_device
*dev
, int argc
, char *argv
[], bool test
) {
522 _cleanup_free_
char *path
= NULL
;
523 bool supported_transport
= false;
524 bool supported_parent
= false;
525 const char *subsystem
;
529 /* walk up the chain of devices and compose path */
532 const char *subsys
, *sysname
;
534 if (sd_device_get_subsystem(parent
, &subsys
) < 0 ||
535 sd_device_get_sysname(parent
, &sysname
) < 0) {
537 } else if (streq(subsys
, "scsi_tape")) {
538 handle_scsi_tape(parent
, &path
);
539 } else if (streq(subsys
, "scsi")) {
540 parent
= handle_scsi(parent
, &path
, &supported_parent
);
541 supported_transport
= true;
542 } else if (streq(subsys
, "cciss")) {
543 parent
= handle_cciss(parent
, &path
);
544 supported_transport
= true;
545 } else if (streq(subsys
, "usb")) {
546 parent
= handle_usb(parent
, &path
);
547 supported_transport
= true;
548 } else if (streq(subsys
, "bcma")) {
549 parent
= handle_bcma(parent
, &path
);
550 supported_transport
= true;
551 } else if (streq(subsys
, "serio")) {
554 if (sd_device_get_sysnum(parent
, &sysnum
) >= 0 && sysnum
) {
555 path_prepend(&path
, "serio-%s", sysnum
);
556 parent
= skip_subsystem(parent
, "serio");
558 } else if (streq(subsys
, "pci")) {
559 path_prepend(&path
, "pci-%s", sysname
);
560 parent
= skip_subsystem(parent
, "pci");
561 supported_parent
= true;
562 } else if (streq(subsys
, "platform")) {
563 path_prepend(&path
, "platform-%s", sysname
);
564 parent
= skip_subsystem(parent
, "platform");
565 supported_transport
= true;
566 supported_parent
= true;
567 } else if (streq(subsys
, "acpi")) {
568 path_prepend(&path
, "acpi-%s", sysname
);
569 parent
= skip_subsystem(parent
, "acpi");
570 supported_parent
= true;
571 } else if (streq(subsys
, "xen")) {
572 path_prepend(&path
, "xen-%s", sysname
);
573 parent
= skip_subsystem(parent
, "xen");
574 supported_parent
= true;
575 } else if (streq(subsys
, "virtio")) {
576 parent
= skip_subsystem(parent
, "virtio");
577 supported_transport
= true;
578 } else if (streq(subsys
, "scm")) {
579 path_prepend(&path
, "scm-%s", sysname
);
580 parent
= skip_subsystem(parent
, "scm");
581 supported_transport
= true;
582 supported_parent
= true;
583 } else if (streq(subsys
, "ccw")) {
584 path_prepend(&path
, "ccw-%s", sysname
);
585 parent
= skip_subsystem(parent
, "ccw");
586 supported_transport
= true;
587 supported_parent
= true;
588 } else if (streq(subsys
, "ccwgroup")) {
589 path_prepend(&path
, "ccwgroup-%s", sysname
);
590 parent
= skip_subsystem(parent
, "ccwgroup");
591 supported_transport
= true;
592 supported_parent
= true;
593 } else if (streq(subsys
, "ap")) {
594 parent
= handle_ap(parent
, &path
);
595 supported_transport
= true;
596 supported_parent
= true;
597 } else if (streq(subsys
, "iucv")) {
598 path_prepend(&path
, "iucv-%s", sysname
);
599 parent
= skip_subsystem(parent
, "iucv");
600 supported_transport
= true;
601 supported_parent
= true;
602 } else if (streq(subsys
, "nvme")) {
605 if (sd_device_get_sysattr_value(dev
, "nsid", &nsid
) >= 0) {
606 path_prepend(&path
, "nvme-%s", nsid
);
607 parent
= skip_subsystem(parent
, "nvme");
608 supported_parent
= true;
609 supported_transport
= true;
615 if (sd_device_get_parent(parent
, &parent
) < 0)
623 * Do not return devices with an unknown parent device type. They
624 * might produce conflicting IDs if the parent does not provide a
625 * unique and predictable name.
627 if (!supported_parent
)
631 * Do not return block devices without a well-known transport. Some
632 * devices do not expose their buses and do not provide a unique
633 * and predictable name that way.
635 if (sd_device_get_subsystem(dev
, &subsystem
) >= 0 &&
636 streq(subsystem
, "block") &&
637 !supported_transport
)
641 char tag
[UTIL_NAME_SIZE
];
645 /* compose valid udev tag name */
646 for (p
= path
, i
= 0; *p
; p
++) {
647 if ((*p
>= '0' && *p
<= '9') ||
648 (*p
>= 'A' && *p
<= 'Z') ||
649 (*p
>= 'a' && *p
<= 'z') ||
655 /* skip all leading '_' */
659 /* avoid second '_' */
665 /* strip trailing '_' */
666 while (i
> 0 && tag
[i
-1] == '_')
670 udev_builtin_add_property(dev
, test
, "ID_PATH", path
);
671 udev_builtin_add_property(dev
, test
, "ID_PATH_TAG", tag
);
677 const UdevBuiltin udev_builtin_path_id
= {
679 .cmd
= builtin_path_id
,
680 .help
= "Compose persistent device path",