]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/udev/udev-builtin-path_id.c
Merge pull request #8313 from alexgartrell/compression-threshold
[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, size_t guid_str_len) {
401 struct udev_device *hostdev;
402 struct udev_device *vmbusdev;
403 const char *guid_str;
404 _cleanup_free_ char *lun = NULL;
405 char guid[39];
406 size_t i, k;
407
408 assert(parent);
409 assert(path);
410 assert(guid_str_len < sizeof(guid));
411
412 hostdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host");
413 if (!hostdev)
414 return NULL;
415
416 vmbusdev = udev_device_get_parent(hostdev);
417 if (!vmbusdev)
418 return NULL;
419
420 guid_str = udev_device_get_sysattr_value(vmbusdev, "device_id");
421 if (!guid_str)
422 return NULL;
423
424 if (strlen(guid_str) < guid_str_len || guid_str[0] != '{' || guid_str[guid_str_len-1] != '}')
425 return NULL;
426
427 for (i = 1, k = 0; i < guid_str_len-1; i++) {
428 if (guid_str[i] == '-')
429 continue;
430 guid[k++] = guid_str[i];
431 }
432 guid[k] = '\0';
433
434 format_lun_number(parent, &lun);
435 path_prepend(path, "vmbus-%s-%s", guid, lun);
436 return parent;
437 }
438
439 static struct udev_device *handle_scsi(struct udev_device *parent, char **path, bool *supported_parent) {
440 const char *devtype, *id, *name;
441
442 devtype = udev_device_get_devtype(parent);
443 if (!streq_ptr(devtype, "scsi_device"))
444 return parent;
445
446 /* firewire */
447 id = udev_device_get_sysattr_value(parent, "ieee1394_id");
448 if (id) {
449 path_prepend(path, "ieee1394-0x%s", id);
450 *supported_parent = true;
451 return skip_subsystem(parent, "scsi");
452 }
453
454 /* scsi sysfs does not have a "subsystem" for the transport */
455 name = udev_device_get_syspath(parent);
456
457 if (strstr(name, "/rport-")) {
458 *supported_parent = true;
459 return handle_scsi_fibre_channel(parent, path);
460 }
461
462 if (strstr(name, "/end_device-")) {
463 *supported_parent = true;
464 return handle_scsi_sas(parent, path);
465 }
466
467 if (strstr(name, "/session")) {
468 *supported_parent = true;
469 return handle_scsi_iscsi(parent, path);
470 }
471
472 if (strstr(name, "/ata"))
473 return handle_scsi_ata(parent, path);
474
475 if (strstr(name, "/vmbus_"))
476 return handle_scsi_hyperv(parent, path, 37);
477 else if (strstr(name, "/VMBUS"))
478 return handle_scsi_hyperv(parent, path, 38);
479
480 return handle_scsi_default(parent, path);
481 }
482
483 static struct udev_device *handle_cciss(struct udev_device *parent, char **path) {
484 const char *str;
485 unsigned int controller, disk;
486
487 str = udev_device_get_sysname(parent);
488 if (sscanf(str, "c%ud%u%*s", &controller, &disk) != 2)
489 return NULL;
490
491 path_prepend(path, "cciss-disk%u", disk);
492 return skip_subsystem(parent, "cciss");
493 }
494
495 static void handle_scsi_tape(struct udev_device *dev, char **path) {
496 const char *name;
497
498 /* must be the last device in the syspath */
499 if (*path)
500 return;
501
502 name = udev_device_get_sysname(dev);
503 if (startswith(name, "nst") && strchr("lma", name[3]))
504 path_prepend(path, "nst%c", name[3]);
505 else if (startswith(name, "st") && strchr("lma", name[2]))
506 path_prepend(path, "st%c", name[2]);
507 }
508
509 static struct udev_device *handle_usb(struct udev_device *parent, char **path) {
510 const char *devtype, *str, *port;
511
512 devtype = udev_device_get_devtype(parent);
513 if (!devtype)
514 return parent;
515 if (!STR_IN_SET(devtype, "usb_interface", "usb_device"))
516 return parent;
517
518 str = udev_device_get_sysname(parent);
519 port = strchr(str, '-');
520 if (!port)
521 return parent;
522 port++;
523
524 path_prepend(path, "usb-0:%s", port);
525 return skip_subsystem(parent, "usb");
526 }
527
528 static struct udev_device *handle_bcma(struct udev_device *parent, char **path) {
529 const char *sysname;
530 unsigned int core;
531
532 sysname = udev_device_get_sysname(parent);
533 if (sscanf(sysname, "bcma%*u:%u", &core) != 1)
534 return NULL;
535
536 path_prepend(path, "bcma-%u", core);
537 return parent;
538 }
539
540 /* Handle devices of AP bus in System z platform. */
541 static struct udev_device *handle_ap(struct udev_device *parent, char **path) {
542 const char *type, *func;
543
544 assert(parent);
545 assert(path);
546
547 type = udev_device_get_sysattr_value(parent, "type");
548 func = udev_device_get_sysattr_value(parent, "ap_functions");
549
550 if (type && func)
551 path_prepend(path, "ap-%s-%s", type, func);
552 else
553 path_prepend(path, "ap-%s", udev_device_get_sysname(parent));
554
555 return skip_subsystem(parent, "ap");
556 }
557
558 static int builtin_path_id(struct udev_device *dev, int argc, char *argv[], bool test) {
559 struct udev_device *parent;
560 _cleanup_free_ char *path = NULL;
561 bool supported_transport = false;
562 bool supported_parent = false;
563
564 assert(dev);
565
566 /* walk up the chain of devices and compose path */
567 parent = dev;
568 while (parent) {
569 const char *subsys;
570
571 subsys = udev_device_get_subsystem(parent);
572 if (!subsys) {
573 ;
574 } else if (streq(subsys, "scsi_tape")) {
575 handle_scsi_tape(parent, &path);
576 } else if (streq(subsys, "scsi")) {
577 parent = handle_scsi(parent, &path, &supported_parent);
578 supported_transport = true;
579 } else if (streq(subsys, "cciss")) {
580 parent = handle_cciss(parent, &path);
581 supported_transport = true;
582 } else if (streq(subsys, "usb")) {
583 parent = handle_usb(parent, &path);
584 supported_transport = true;
585 } else if (streq(subsys, "bcma")) {
586 parent = handle_bcma(parent, &path);
587 supported_transport = true;
588 } else if (streq(subsys, "serio")) {
589 path_prepend(&path, "serio-%s", udev_device_get_sysnum(parent));
590 parent = skip_subsystem(parent, "serio");
591 } else if (streq(subsys, "pci")) {
592 path_prepend(&path, "pci-%s", udev_device_get_sysname(parent));
593 parent = skip_subsystem(parent, "pci");
594 supported_parent = true;
595 } else if (streq(subsys, "platform")) {
596 path_prepend(&path, "platform-%s", udev_device_get_sysname(parent));
597 parent = skip_subsystem(parent, "platform");
598 supported_transport = true;
599 supported_parent = true;
600 } else if (streq(subsys, "acpi")) {
601 path_prepend(&path, "acpi-%s", udev_device_get_sysname(parent));
602 parent = skip_subsystem(parent, "acpi");
603 supported_parent = true;
604 } else if (streq(subsys, "xen")) {
605 path_prepend(&path, "xen-%s", udev_device_get_sysname(parent));
606 parent = skip_subsystem(parent, "xen");
607 supported_parent = true;
608 } else if (streq(subsys, "virtio")) {
609 parent = skip_subsystem(parent, "virtio");
610 supported_transport = true;
611 } else if (streq(subsys, "scm")) {
612 path_prepend(&path, "scm-%s", udev_device_get_sysname(parent));
613 parent = skip_subsystem(parent, "scm");
614 supported_transport = true;
615 supported_parent = true;
616 } else if (streq(subsys, "ccw")) {
617 path_prepend(&path, "ccw-%s", udev_device_get_sysname(parent));
618 parent = skip_subsystem(parent, "ccw");
619 supported_transport = true;
620 supported_parent = true;
621 } else if (streq(subsys, "ccwgroup")) {
622 path_prepend(&path, "ccwgroup-%s", udev_device_get_sysname(parent));
623 parent = skip_subsystem(parent, "ccwgroup");
624 supported_transport = true;
625 supported_parent = true;
626 } else if (streq(subsys, "ap")) {
627 parent = handle_ap(parent, &path);
628 supported_transport = true;
629 supported_parent = true;
630 } else if (streq(subsys, "iucv")) {
631 path_prepend(&path, "iucv-%s", udev_device_get_sysname(parent));
632 parent = skip_subsystem(parent, "iucv");
633 supported_transport = true;
634 supported_parent = true;
635 } else if (streq(subsys, "nvme")) {
636 const char *nsid = udev_device_get_sysattr_value(dev, "nsid");
637
638 if (nsid) {
639 path_prepend(&path, "nvme-%s", nsid);
640 parent = skip_subsystem(parent, "nvme");
641 supported_parent = true;
642 supported_transport = true;
643 }
644 }
645
646 if (parent)
647 parent = udev_device_get_parent(parent);
648 }
649
650 if (!path)
651 return EXIT_FAILURE;
652
653 /*
654 * Do not return devices with an unknown parent device type. They
655 * might produce conflicting IDs if the parent does not provide a
656 * unique and predictable name.
657 */
658 if (!supported_parent)
659 return EXIT_FAILURE;
660
661 /*
662 * Do not return block devices without a well-known transport. Some
663 * devices do not expose their buses and do not provide a unique
664 * and predictable name that way.
665 */
666 if (streq_ptr(udev_device_get_subsystem(dev), "block") && !supported_transport)
667 return EXIT_FAILURE;
668
669 {
670 char tag[UTIL_NAME_SIZE];
671 size_t i;
672 const char *p;
673
674 /* compose valid udev tag name */
675 for (p = path, i = 0; *p; p++) {
676 if ((*p >= '0' && *p <= '9') ||
677 (*p >= 'A' && *p <= 'Z') ||
678 (*p >= 'a' && *p <= 'z') ||
679 *p == '-') {
680 tag[i++] = *p;
681 continue;
682 }
683
684 /* skip all leading '_' */
685 if (i == 0)
686 continue;
687
688 /* avoid second '_' */
689 if (tag[i-1] == '_')
690 continue;
691
692 tag[i++] = '_';
693 }
694 /* strip trailing '_' */
695 while (i > 0 && tag[i-1] == '_')
696 i--;
697 tag[i] = '\0';
698
699 udev_builtin_add_property(dev, test, "ID_PATH", path);
700 udev_builtin_add_property(dev, test, "ID_PATH_TAG", tag);
701 }
702
703 return EXIT_SUCCESS;
704 }
705
706 const struct udev_builtin udev_builtin_path_id = {
707 .name = "path_id",
708 .cmd = builtin_path_id,
709 .help = "Compose persistent device path",
710 .run_once = true,
711 };