1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 /*
3  * compose persistent device path
4  *
5  * Logic based on Hannes Reinecke's shell script.
6  */
7 
8 #include <ctype.h>
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <getopt.h>
12 #include <stdarg.h>
13 #include <stdio.h>
14 #include <unistd.h>
15 
16 #include "alloc-util.h"
17 #include "dirent-util.h"
18 #include "fd-util.h"
19 #include "parse-util.h"
20 #include "string-util.h"
21 #include "strv.h"
22 #include "sysexits.h"
23 #include "udev-builtin.h"
24 #include "udev-util.h"
25 
26 _printf_(2,3)
path_prepend(char ** path,const char * fmt,...)27 static void path_prepend(char **path, const char *fmt, ...) {
28         va_list va;
29         _cleanup_free_ char *pre = NULL;
30         int r;
31 
32         va_start(va, fmt);
33         r = vasprintf(&pre, fmt, va);
34         va_end(va);
35         if (r < 0) {
36                 log_oom();
37                 exit(EX_OSERR);
38         }
39 
40         if (*path) {
41                 char *new;
42 
43                 new = strjoin(pre, "-", *path);
44                 if (!new) {
45                         log_oom();
46                         exit(EX_OSERR);
47                 }
48 
49                 free_and_replace(*path, new);
50         } else
51                 *path = TAKE_PTR(pre);
52 }
53 
54 /*
55 ** Linux only supports 32 bit luns.
56 ** See drivers/scsi/scsi_scan.c::scsilun_to_int() for more details.
57 */
format_lun_number(sd_device * dev,char ** path)58 static int format_lun_number(sd_device *dev, char **path) {
59         const char *sysnum;
60         unsigned long lun;
61         int r;
62 
63         r = sd_device_get_sysnum(dev, &sysnum);
64         if (r < 0)
65                 return r;
66         if (!sysnum)
67                 return -ENOENT;
68 
69         r = safe_atolu_full(sysnum, 10, &lun);
70         if (r < 0)
71                 return r;
72         if (lun < 256)
73                 /* address method 0, peripheral device addressing with bus id of zero */
74                 path_prepend(path, "lun-%lu", lun);
75         else
76                 /* handle all other lun addressing methods by using a variant of the original lun format */
77                 path_prepend(path, "lun-0x%04lx%04lx00000000", lun & 0xffff, (lun >> 16) & 0xffff);
78 
79         return 0;
80 }
81 
skip_subsystem(sd_device * dev,const char * subsys)82 static sd_device *skip_subsystem(sd_device *dev, const char *subsys) {
83         sd_device *parent;
84 
85         assert(dev);
86         assert(subsys);
87 
88         /* Unlike the function name, this drops multiple parent devices EXCEPT FOR THE LAST ONE.
89          * The last one will be dropped at the end of the loop in builtin_path_id().
90          * E.g.
91          * Input:  /sys/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.0
92          * Output: /sys/devices/pci0000:00/0000:00:14.0/usb1
93          */
94 
95         for (parent = dev; ; ) {
96                 const char *subsystem;
97 
98                 if (sd_device_get_subsystem(parent, &subsystem) < 0)
99                         break;
100 
101                 if (!streq(subsystem, subsys))
102                         break;
103 
104                 dev = parent;
105                 if (sd_device_get_parent(dev, &parent) < 0)
106                         break;
107         }
108 
109         return dev;
110 }
111 
handle_scsi_fibre_channel(sd_device * parent,char ** path)112 static sd_device *handle_scsi_fibre_channel(sd_device *parent, char **path) {
113         sd_device *targetdev;
114         _cleanup_(sd_device_unrefp) sd_device *fcdev = NULL;
115         const char *port, *sysname;
116         _cleanup_free_ char *lun = NULL;
117 
118         assert(parent);
119         assert(path);
120 
121         if (sd_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target", &targetdev) < 0)
122                 return NULL;
123         if (sd_device_get_sysname(targetdev, &sysname) < 0)
124                 return NULL;
125         if (sd_device_new_from_subsystem_sysname(&fcdev, "fc_transport", sysname) < 0)
126                 return NULL;
127         if (sd_device_get_sysattr_value(fcdev, "port_name", &port) < 0)
128                 return NULL;
129 
130         format_lun_number(parent, &lun);
131         path_prepend(path, "fc-%s-%s", port, lun);
132         return parent;
133 }
134 
handle_scsi_sas_wide_port(sd_device * parent,char ** path)135 static sd_device *handle_scsi_sas_wide_port(sd_device *parent, char **path) {
136         sd_device *targetdev, *target_parent;
137         _cleanup_(sd_device_unrefp) sd_device *sasdev = NULL;
138         const char *sas_address, *sysname;
139         _cleanup_free_ char *lun = NULL;
140 
141         assert(parent);
142         assert(path);
143 
144         if (sd_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target", &targetdev) < 0)
145                 return NULL;
146         if (sd_device_get_parent(targetdev, &target_parent) < 0)
147                 return NULL;
148         if (sd_device_get_sysname(target_parent, &sysname) < 0)
149                 return NULL;
150         if (sd_device_new_from_subsystem_sysname(&sasdev, "sas_device", sysname) < 0)
151                 return NULL;
152         if (sd_device_get_sysattr_value(sasdev, "sas_address", &sas_address) < 0)
153                 return NULL;
154 
155         format_lun_number(parent, &lun);
156         path_prepend(path, "sas-%s-%s", sas_address, lun);
157         return parent;
158 }
159 
handle_scsi_sas(sd_device * parent,char ** path)160 static sd_device *handle_scsi_sas(sd_device *parent, char **path) {
161         sd_device *targetdev, *target_parent, *port, *expander;
162         _cleanup_(sd_device_unrefp) sd_device *target_sasdev = NULL, *expander_sasdev = NULL, *port_sasdev = NULL;
163         const char *sas_address = NULL;
164         const char *phy_id;
165         const char *phy_count, *sysname;
166         _cleanup_free_ char *lun = NULL;
167 
168         assert(parent);
169         assert(path);
170 
171         if (sd_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target", &targetdev) < 0)
172                 return NULL;
173         if (sd_device_get_parent(targetdev, &target_parent) < 0)
174                 return NULL;
175         if (sd_device_get_sysname(target_parent, &sysname) < 0)
176                 return NULL;
177         /* Get sas device */
178         if (sd_device_new_from_subsystem_sysname(&target_sasdev, "sas_device", sysname) < 0)
179                 return NULL;
180         /* The next parent is sas port */
181         if (sd_device_get_parent(target_parent, &port) < 0)
182                 return NULL;
183         if (sd_device_get_sysname(port, &sysname) < 0)
184                 return NULL;
185         /* Get port device */
186         if (sd_device_new_from_subsystem_sysname(&port_sasdev, "sas_port", sysname) < 0)
187                 return NULL;
188         if (sd_device_get_sysattr_value(port_sasdev, "num_phys", &phy_count) < 0)
189                 return NULL;
190 
191         /* Check if we are simple disk */
192         if (strncmp(phy_count, "1", 2) != 0)
193                 return handle_scsi_sas_wide_port(parent, path);
194 
195         /* Get connected phy */
196         if (sd_device_get_sysattr_value(target_sasdev, "phy_identifier", &phy_id) < 0)
197                 return NULL;
198 
199         /* The port's parent is either hba or expander */
200         if (sd_device_get_parent(port, &expander) < 0)
201                 return NULL;
202 
203         if (sd_device_get_sysname(expander, &sysname) < 0)
204                 return NULL;
205         /* Get expander device */
206         if (sd_device_new_from_subsystem_sysname(&expander_sasdev, "sas_device", sysname) >= 0) {
207                 /* Get expander's address */
208                 if (sd_device_get_sysattr_value(expander_sasdev, "sas_address", &sas_address) < 0)
209                         return NULL;
210         }
211 
212         format_lun_number(parent, &lun);
213         if (sas_address)
214                  path_prepend(path, "sas-exp%s-phy%s-%s", sas_address, phy_id, lun);
215         else
216                  path_prepend(path, "sas-phy%s-%s", phy_id, lun);
217 
218         return parent;
219 }
220 
handle_scsi_iscsi(sd_device * parent,char ** path)221 static sd_device *handle_scsi_iscsi(sd_device *parent, char **path) {
222         sd_device *transportdev;
223         _cleanup_(sd_device_unrefp) sd_device *sessiondev = NULL, *conndev = NULL;
224         const char *target, *connname, *addr, *port;
225         _cleanup_free_ char *lun = NULL;
226         const char *sysname, *sysnum;
227 
228         assert(parent);
229         assert(path);
230 
231         /* find iscsi session */
232         for (transportdev = parent; ; ) {
233 
234                 if (sd_device_get_parent(transportdev, &transportdev) < 0)
235                         return NULL;
236                 if (sd_device_get_sysname(transportdev, &sysname) < 0)
237                         return NULL;
238                 if (startswith(sysname, "session"))
239                         break;
240         }
241 
242         /* find iscsi session device */
243         if (sd_device_new_from_subsystem_sysname(&sessiondev, "iscsi_session", sysname) < 0)
244                 return NULL;
245 
246         if (sd_device_get_sysattr_value(sessiondev, "targetname", &target) < 0)
247                 return NULL;
248 
249         if (sd_device_get_sysnum(transportdev, &sysnum) < 0 || !sysnum)
250                 return NULL;
251         connname = strjoina("connection", sysnum, ":0");
252         if (sd_device_new_from_subsystem_sysname(&conndev, "iscsi_connection", connname) < 0)
253                 return NULL;
254 
255         if (sd_device_get_sysattr_value(conndev, "persistent_address", &addr) < 0)
256                 return NULL;
257         if (sd_device_get_sysattr_value(conndev, "persistent_port", &port) < 0)
258                 return NULL;
259 
260         format_lun_number(parent, &lun);
261         path_prepend(path, "ip-%s:%s-iscsi-%s-%s", addr, port, target, lun);
262         return parent;
263 }
264 
handle_scsi_ata(sd_device * parent,char ** path,char ** compat_path)265 static sd_device *handle_scsi_ata(sd_device *parent, char **path, char **compat_path) {
266         sd_device *targetdev, *target_parent;
267         _cleanup_(sd_device_unrefp) sd_device *atadev = NULL;
268         const char *port_no, *sysname, *name;
269         unsigned host, bus, target, lun;
270 
271         assert(parent);
272         assert(path);
273 
274         if (sd_device_get_sysname(parent, &name) < 0)
275                 return NULL;
276         if (sscanf(name, "%u:%u:%u:%u", &host, &bus, &target, &lun) != 4)
277                 return NULL;
278 
279         if (sd_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host", &targetdev) < 0)
280                 return NULL;
281 
282         if (sd_device_get_parent(targetdev, &target_parent) < 0)
283                 return NULL;
284 
285         if (sd_device_get_sysname(target_parent, &sysname) < 0)
286                 return NULL;
287         if (sd_device_new_from_subsystem_sysname(&atadev, "ata_port", sysname) < 0)
288                 return NULL;
289 
290         if (sd_device_get_sysattr_value(atadev, "port_no", &port_no) < 0)
291                 return NULL;
292 
293         if (bus != 0)
294                 /* Devices behind port multiplier have a bus != 0 */
295                 path_prepend(path, "ata-%s.%u.0", port_no, bus);
296         else
297                 /* Master/slave are distinguished by target id */
298                 path_prepend(path, "ata-%s.%u", port_no, target);
299 
300         /* old compatible persistent link for ATA devices */
301         if (compat_path)
302                 path_prepend(compat_path, "ata-%s", port_no);
303 
304         return parent;
305 }
306 
handle_scsi_default(sd_device * parent,char ** path)307 static sd_device *handle_scsi_default(sd_device *parent, char **path) {
308         sd_device *hostdev;
309         int host, bus, target, lun;
310         const char *name, *base, *pos;
311         _cleanup_closedir_ DIR *dir = NULL;
312         int basenum = -1;
313 
314         assert(parent);
315         assert(path);
316 
317         if (sd_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host", &hostdev) < 0)
318                 return NULL;
319 
320         if (sd_device_get_sysname(parent, &name) < 0)
321                 return NULL;
322         if (sscanf(name, "%d:%d:%d:%d", &host, &bus, &target, &lun) != 4)
323                 return NULL;
324 
325         /*
326          * Rebase host offset to get the local relative number
327          *
328          * Note: This is by definition racy, unreliable and too simple.
329          * Please do not copy this model anywhere. It's just a left-over
330          * from the time we had no idea how things should look like in
331          * the end.
332          *
333          * Making assumptions about a global in-kernel counter and use
334          * that to calculate a local offset is a very broken concept. It
335          * can only work as long as things are in strict order.
336          *
337          * The kernel needs to export the instance/port number of a
338          * controller directly, without the need for rebase magic like
339          * this. Manual driver unbind/bind, parallel hotplug/unplug will
340          * get into the way of this "I hope it works" logic.
341          */
342 
343         if (sd_device_get_syspath(hostdev, &base) < 0)
344                 return NULL;
345         pos = strrchr(base, '/');
346         if (!pos)
347                 return NULL;
348 
349         base = strndupa_safe(base, pos - base);
350         dir = opendir(base);
351         if (!dir)
352                 return NULL;
353 
354         FOREACH_DIRENT_ALL(de, dir, break) {
355                 unsigned i;
356 
357                 if (de->d_name[0] == '.')
358                         continue;
359                 if (!IN_SET(de->d_type, DT_DIR, DT_LNK))
360                         continue;
361                 if (!startswith(de->d_name, "host"))
362                         continue;
363                 if (safe_atou_full(&de->d_name[4], 10, &i) < 0)
364                         continue;
365                 /*
366                  * find the smallest number; the host really needs to export its
367                  * own instance number per parent device; relying on the global host
368                  * enumeration and plainly rebasing the numbers sounds unreliable
369                  */
370                 if (basenum == -1 || (int) i < basenum)
371                         basenum = i;
372         }
373         if (basenum == -1)
374                 return hostdev;
375         host -= basenum;
376 
377         path_prepend(path, "scsi-%u:%u:%u:%u", host, bus, target, lun);
378         return hostdev;
379 }
380 
handle_scsi_hyperv(sd_device * parent,char ** path,size_t guid_str_len)381 static sd_device *handle_scsi_hyperv(sd_device *parent, char **path, size_t guid_str_len) {
382         sd_device *hostdev;
383         sd_device *vmbusdev;
384         const char *guid_str;
385         _cleanup_free_ char *lun = NULL;
386         char guid[39];
387 
388         assert(parent);
389         assert(path);
390         assert(guid_str_len < sizeof(guid));
391 
392         if (sd_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host", &hostdev) < 0)
393                 return NULL;
394 
395         if (sd_device_get_parent(hostdev, &vmbusdev) < 0)
396                 return NULL;
397 
398         if (sd_device_get_sysattr_value(vmbusdev, "device_id", &guid_str) < 0)
399                 return NULL;
400 
401         if (strlen(guid_str) < guid_str_len || guid_str[0] != '{' || guid_str[guid_str_len-1] != '}')
402                 return NULL;
403 
404         size_t k = 0;
405         for (size_t i = 1; i < guid_str_len-1; i++) {
406                 if (guid_str[i] == '-')
407                         continue;
408                 guid[k++] = guid_str[i];
409         }
410         guid[k] = '\0';
411 
412         format_lun_number(parent, &lun);
413         path_prepend(path, "vmbus-%s-%s", guid, lun);
414         return parent;
415 }
416 
handle_scsi(sd_device * parent,char ** path,char ** compat_path,bool * supported_parent)417 static sd_device *handle_scsi(sd_device *parent, char **path, char **compat_path, bool *supported_parent) {
418         const char *devtype, *id, *name;
419 
420         if (sd_device_get_devtype(parent, &devtype) < 0 ||
421             !streq(devtype, "scsi_device"))
422                 return parent;
423 
424         /* firewire */
425         if (sd_device_get_sysattr_value(parent, "ieee1394_id", &id) >= 0) {
426                 path_prepend(path, "ieee1394-0x%s", id);
427                 *supported_parent = true;
428                 return skip_subsystem(parent, "scsi");
429         }
430 
431         /* scsi sysfs does not have a "subsystem" for the transport */
432         if (sd_device_get_syspath(parent, &name) < 0)
433                 return NULL;
434 
435         if (strstr(name, "/rport-")) {
436                 *supported_parent = true;
437                 return handle_scsi_fibre_channel(parent, path);
438         }
439 
440         if (strstr(name, "/end_device-")) {
441                 *supported_parent = true;
442                 return handle_scsi_sas(parent, path);
443         }
444 
445         if (strstr(name, "/session")) {
446                 *supported_parent = true;
447                 return handle_scsi_iscsi(parent, path);
448         }
449 
450         if (strstr(name, "/ata"))
451                 return handle_scsi_ata(parent, path, compat_path);
452 
453         if (strstr(name, "/vmbus_"))
454                 return handle_scsi_hyperv(parent, path, 37);
455         else if (strstr(name, "/VMBUS"))
456                 return handle_scsi_hyperv(parent, path, 38);
457 
458         return handle_scsi_default(parent, path);
459 }
460 
handle_cciss(sd_device * parent,char ** path)461 static sd_device *handle_cciss(sd_device *parent, char **path) {
462         const char *str;
463         unsigned controller, disk;
464 
465         if (sd_device_get_sysname(parent, &str) < 0)
466                 return NULL;
467         if (sscanf(str, "c%ud%u%*s", &controller, &disk) != 2)
468                 return NULL;
469 
470         path_prepend(path, "cciss-disk%u", disk);
471         return skip_subsystem(parent, "cciss");
472 }
473 
handle_scsi_tape(sd_device * dev,char ** path)474 static void handle_scsi_tape(sd_device *dev, char **path) {
475         const char *name;
476 
477         /* must be the last device in the syspath */
478         if (*path)
479                 return;
480 
481         if (sd_device_get_sysname(dev, &name) < 0)
482                 return;
483 
484         if (startswith(name, "nst") && strchr("lma", name[3]))
485                 path_prepend(path, "nst%c", name[3]);
486         else if (startswith(name, "st") && strchr("lma", name[2]))
487                 path_prepend(path, "st%c", name[2]);
488 }
489 
handle_usb(sd_device * parent,char ** path)490 static sd_device *handle_usb(sd_device *parent, char **path) {
491         const char *devtype, *str, *port;
492 
493         if (sd_device_get_devtype(parent, &devtype) < 0)
494                 return parent;
495         if (!STR_IN_SET(devtype, "usb_interface", "usb_device"))
496                 return parent;
497 
498         if (sd_device_get_sysname(parent, &str) < 0)
499                 return parent;
500         port = strchr(str, '-');
501         if (!port)
502                 return parent;
503         port++;
504 
505         /* USB host number may change across reboots (and probably even without reboot). The part after
506          * USB host number is determined by device topology and so does not change. Hence, drop the
507          * host number and always use '0' instead. */
508 
509         path_prepend(path, "usb-0:%s", port);
510         return skip_subsystem(parent, "usb");
511 }
512 
handle_bcma(sd_device * parent,char ** path)513 static sd_device *handle_bcma(sd_device *parent, char **path) {
514         const char *sysname;
515         unsigned core;
516 
517         if (sd_device_get_sysname(parent, &sysname) < 0)
518                 return NULL;
519         if (sscanf(sysname, "bcma%*u:%u", &core) != 1)
520                 return NULL;
521 
522         path_prepend(path, "bcma-%u", core);
523         return parent;
524 }
525 
526 /* Handle devices of AP bus in System z platform. */
handle_ap(sd_device * parent,char ** path)527 static sd_device *handle_ap(sd_device *parent, char **path) {
528         const char *type, *func;
529 
530         assert(parent);
531         assert(path);
532 
533         if (sd_device_get_sysattr_value(parent, "type", &type) >= 0 &&
534             sd_device_get_sysattr_value(parent, "ap_functions", &func) >= 0)
535                 path_prepend(path, "ap-%s-%s", type, func);
536         else {
537                 const char *sysname;
538 
539                 if (sd_device_get_sysname(parent, &sysname) >= 0)
540                         path_prepend(path, "ap-%s", sysname);
541         }
542 
543         return skip_subsystem(parent, "ap");
544 }
545 
builtin_path_id(sd_device * dev,sd_netlink ** rtnl,int argc,char * argv[],bool test)546 static int builtin_path_id(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) {
547         sd_device *parent;
548         _cleanup_free_ char *path = NULL;
549         _cleanup_free_ char *compat_path = NULL;
550         bool supported_transport = false;
551         bool supported_parent = false;
552         const char *subsystem;
553 
554         assert(dev);
555 
556         /* walk up the chain of devices and compose path */
557         parent = dev;
558         while (parent) {
559                 const char *subsys, *sysname;
560 
561                 if (sd_device_get_subsystem(parent, &subsys) < 0 ||
562                     sd_device_get_sysname(parent, &sysname) < 0) {
563                         ;
564                 } else if (streq(subsys, "scsi_tape")) {
565                         handle_scsi_tape(parent, &path);
566                 } else if (streq(subsys, "scsi")) {
567                         parent = handle_scsi(parent, &path, &compat_path, &supported_parent);
568                         supported_transport = true;
569                 } else if (streq(subsys, "cciss")) {
570                         parent = handle_cciss(parent, &path);
571                         supported_transport = true;
572                 } else if (streq(subsys, "usb")) {
573                         parent = handle_usb(parent, &path);
574                         supported_transport = true;
575                 } else if (streq(subsys, "bcma")) {
576                         parent = handle_bcma(parent, &path);
577                         supported_transport = true;
578                 } else if (streq(subsys, "serio")) {
579                         const char *sysnum;
580 
581                         if (sd_device_get_sysnum(parent, &sysnum) >= 0 && sysnum) {
582                                 path_prepend(&path, "serio-%s", sysnum);
583                                 parent = skip_subsystem(parent, "serio");
584                         }
585                 } else if (streq(subsys, "pci")) {
586                         path_prepend(&path, "pci-%s", sysname);
587                         if (compat_path)
588                                 path_prepend(&compat_path, "pci-%s", sysname);
589                         parent = skip_subsystem(parent, "pci");
590                         supported_parent = true;
591                 } else if (streq(subsys, "platform")) {
592                         path_prepend(&path, "platform-%s", sysname);
593                         if (compat_path)
594                                 path_prepend(&compat_path, "platform-%s", sysname);
595                         parent = skip_subsystem(parent, "platform");
596                         supported_transport = true;
597                         supported_parent = true;
598                 } else if (streq(subsys, "acpi")) {
599                         path_prepend(&path, "acpi-%s", sysname);
600                         if (compat_path)
601                                 path_prepend(&compat_path, "acpi-%s", sysname);
602                         parent = skip_subsystem(parent, "acpi");
603                         supported_parent = true;
604                 } else if (streq(subsys, "xen")) {
605                         path_prepend(&path, "xen-%s", sysname);
606                         if (compat_path)
607                                 path_prepend(&compat_path, "xen-%s", sysname);
608                         parent = skip_subsystem(parent, "xen");
609                         supported_parent = true;
610                 } else if (streq(subsys, "virtio")) {
611                         parent = skip_subsystem(parent, "virtio");
612                         supported_transport = true;
613                 } else if (streq(subsys, "scm")) {
614                         path_prepend(&path, "scm-%s", sysname);
615                         if (compat_path)
616                                 path_prepend(&compat_path, "scm-%s", sysname);
617                         parent = skip_subsystem(parent, "scm");
618                         supported_transport = true;
619                         supported_parent = true;
620                 } else if (streq(subsys, "ccw")) {
621                         path_prepend(&path, "ccw-%s", sysname);
622                         if (compat_path)
623                                 path_prepend(&compat_path, "ccw-%s", sysname);
624                         parent = skip_subsystem(parent, "ccw");
625                         supported_transport = true;
626                         supported_parent = true;
627                 } else if (streq(subsys, "ccwgroup")) {
628                         path_prepend(&path, "ccwgroup-%s", sysname);
629                         if (compat_path)
630                                 path_prepend(&compat_path, "ccwgroup-%s", sysname);
631                         parent = skip_subsystem(parent, "ccwgroup");
632                         supported_transport = true;
633                         supported_parent = true;
634                 } else if (streq(subsys, "ap")) {
635                         parent = handle_ap(parent, &path);
636                         supported_transport = true;
637                         supported_parent = true;
638                 } else if (streq(subsys, "iucv")) {
639                         path_prepend(&path, "iucv-%s", sysname);
640                         if (compat_path)
641                                 path_prepend(&compat_path, "iucv-%s", sysname);
642                         parent = skip_subsystem(parent, "iucv");
643                         supported_transport = true;
644                         supported_parent = true;
645                 } else if (streq(subsys, "nvme")) {
646                         const char *nsid;
647 
648                         if (sd_device_get_sysattr_value(dev, "nsid", &nsid) >= 0) {
649                                 path_prepend(&path, "nvme-%s", nsid);
650                                 if (compat_path)
651                                         path_prepend(&compat_path, "nvme-%s", nsid);
652                                 parent = skip_subsystem(parent, "nvme");
653                                 supported_parent = true;
654                                 supported_transport = true;
655                         }
656                 } else if (streq(subsys, "spi")) {
657                         const char *sysnum;
658 
659                         if (sd_device_get_sysnum(parent, &sysnum) >= 0 && sysnum) {
660                                 path_prepend(&path, "cs-%s", sysnum);
661                                 parent = skip_subsystem(parent, "spi");
662                         }
663                 }
664 
665                 if (!parent)
666                         break;
667                 if (sd_device_get_parent(parent, &parent) < 0)
668                         break;
669         }
670 
671         if (!path)
672                 return -ENOENT;
673 
674         /*
675          * Do not return devices with an unknown parent device type. They
676          * might produce conflicting IDs if the parent does not provide a
677          * unique and predictable name.
678          */
679         if (!supported_parent)
680                 return -ENOENT;
681 
682         /*
683          * Do not return block devices without a well-known transport. Some
684          * devices do not expose their buses and do not provide a unique
685          * and predictable name that way.
686          */
687         if (sd_device_get_subsystem(dev, &subsystem) >= 0 &&
688             streq(subsystem, "block") &&
689             !supported_transport)
690                 return -ENOENT;
691 
692         {
693                 char tag[UDEV_NAME_SIZE];
694                 size_t i = 0;
695 
696                 /* compose valid udev tag name */
697                 for (const char *p = path; *p; p++) {
698                         if ((*p >= '0' && *p <= '9') ||
699                             (*p >= 'A' && *p <= 'Z') ||
700                             (*p >= 'a' && *p <= 'z') ||
701                             *p == '-') {
702                                 tag[i++] = *p;
703                                 continue;
704                         }
705 
706                         /* skip all leading '_' */
707                         if (i == 0)
708                                 continue;
709 
710                         /* avoid second '_' */
711                         if (tag[i-1] == '_')
712                                 continue;
713 
714                         tag[i++] = '_';
715                 }
716                 /* strip trailing '_' */
717                 while (i > 0 && tag[i-1] == '_')
718                         i--;
719                 tag[i] = '\0';
720 
721                 udev_builtin_add_property(dev, test, "ID_PATH", path);
722                 udev_builtin_add_property(dev, test, "ID_PATH_TAG", tag);
723         }
724 
725         /*
726          * Compatible link generation for ATA devices
727          * we assign compat_link to the env variable
728          * ID_PATH_ATA_COMPAT
729          */
730         if (compat_path)
731                 udev_builtin_add_property(dev, test, "ID_PATH_ATA_COMPAT", compat_path);
732 
733         return 0;
734 }
735 
736 const UdevBuiltin udev_builtin_path_id = {
737         .name = "path_id",
738         .cmd = builtin_path_id,
739         .help = "Compose persistent device path",
740         .run_once = true,
741 };
742