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