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