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