]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/udev/udev-builtin-path_id.c
coccinelle: fix IN_SET/!IN_SET scripts, and apply some changes it found
[thirdparty/systemd.git] / src / udev / udev-builtin-path_id.c
CommitLineData
e7145211 1/* SPDX-License-Identifier: GPL-2.0+ */
d7867b31
KS
2/*
3 * compose persistent device path
4 *
1298001e 5 * Copyright (C) 2009-2011 Kay Sievers <kay@vrfy.org>
d7867b31
KS
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
d7867b31 23#include <ctype.h>
07630cea
LP
24#include <errno.h>
25#include <fcntl.h>
d7867b31 26#include <getopt.h>
07630cea
LP
27#include <stdarg.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <unistd.h>
d7867b31 32
b5efdb8a 33#include "alloc-util.h"
8fb3f009 34#include "dirent-util.h"
d7d7daec 35#include "fd-util.h"
07630cea 36#include "string-util.h"
a6856129 37#include "sysexits.h"
d7867b31 38#include "udev.h"
d7d7daec 39#include "udev-util.h"
d7867b31 40
9091e686 41_printf_(2,3)
a6856129 42static void path_prepend(char **path, const char *fmt, ...) {
912541b0 43 va_list va;
d7d7daec
ZJS
44 _cleanup_free_ char *pre = NULL;
45 int r;
912541b0
KS
46
47 va_start(va, fmt);
d7d7daec 48 r = vasprintf(&pre, fmt, va);
912541b0 49 va_end(va);
a6856129
ZJS
50 if (r < 0) {
51 log_oom();
52 exit(EX_OSERR);
53 }
912541b0 54
d7d7daec 55 if (*path) {
912541b0
KS
56 char *new;
57
d7d7daec 58 new = strjoin(pre, "-", *path);
a6856129
ZJS
59 if (!new) {
60 log_oom();
61 exit(EX_OSERR);
62 }
63
d7d7daec 64 free_and_replace(*path, new);
912541b0
KS
65 } else {
66 *path = pre;
d7d7daec 67 pre = NULL;
912541b0 68 }
d7867b31
KS
69}
70
71/*
72** Linux only supports 32 bit luns.
73** See drivers/scsi/scsi_scan.c::scsilun_to_int() for more details.
74*/
a6856129 75static void format_lun_number(struct udev_device *dev, char **path) {
912541b0 76 unsigned long lun = strtoul(udev_device_get_sysnum(dev), NULL, 10);
d7867b31 77
912541b0 78 if (lun < 256)
a6856129
ZJS
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);
d7867b31
KS
84}
85
9ec6e95b 86static struct udev_device *skip_subsystem(struct udev_device *dev, const char *subsys) {
912541b0
KS
87 struct udev_device *parent = dev;
88
3b64e4d4
TG
89 assert(dev);
90 assert(subsys);
91
d7d7daec 92 while (parent) {
912541b0
KS
93 const char *subsystem;
94
95 subsystem = udev_device_get_subsystem(parent);
d7d7daec 96 if (!streq_ptr(subsystem, subsys))
912541b0 97 break;
d7d7daec 98
912541b0
KS
99 dev = parent;
100 parent = udev_device_get_parent(parent);
101 }
d7d7daec 102
912541b0 103 return dev;
d7867b31
KS
104}
105
9ec6e95b 106static struct udev_device *handle_scsi_fibre_channel(struct udev_device *parent, char **path) {
d7d7daec 107 struct udev *udev;
912541b0 108 struct udev_device *targetdev;
d7d7daec 109 _cleanup_udev_device_unref_ struct udev_device *fcdev = NULL;
912541b0 110 const char *port;
d7d7daec 111 _cleanup_free_ char *lun = NULL;
912541b0 112
3b64e4d4
TG
113 assert(parent);
114 assert(path);
115
d7d7daec
ZJS
116 udev = udev_device_get_udev(parent);
117
912541b0 118 targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target");
d7d7daec 119 if (!targetdev)
912541b0
KS
120 return NULL;
121
122 fcdev = udev_device_new_from_subsystem_sysname(udev, "fc_transport", udev_device_get_sysname(targetdev));
d7d7daec 123 if (!fcdev)
912541b0 124 return NULL;
d7d7daec 125
912541b0 126 port = udev_device_get_sysattr_value(fcdev, "port_name");
d7d7daec
ZJS
127 if (!port)
128 return NULL;
912541b0
KS
129
130 format_lun_number(parent, &lun);
131 path_prepend(path, "fc-%s-%s", port, lun);
912541b0 132 return parent;
d7867b31
KS
133}
134
66bba0e7 135static struct udev_device *handle_scsi_sas_wide_port(struct udev_device *parent, char **path) {
d7d7daec
ZJS
136 struct udev *udev;
137 struct udev_device *targetdev, *target_parent;
138 _cleanup_udev_device_unref_ struct udev_device *sasdev = NULL;
912541b0 139 const char *sas_address;
d7d7daec 140 _cleanup_free_ char *lun = NULL;
912541b0 141
3b64e4d4
TG
142 assert(parent);
143 assert(path);
144
d7d7daec
ZJS
145 udev = udev_device_get_udev(parent);
146
912541b0 147 targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target");
d7d7daec 148 if (!targetdev)
912541b0
KS
149 return NULL;
150
151 target_parent = udev_device_get_parent(targetdev);
d7d7daec 152 if (!target_parent)
912541b0
KS
153 return NULL;
154
155 sasdev = udev_device_new_from_subsystem_sysname(udev, "sas_device",
d7d7daec
ZJS
156 udev_device_get_sysname(target_parent));
157 if (!sasdev)
912541b0
KS
158 return NULL;
159
160 sas_address = udev_device_get_sysattr_value(sasdev, "sas_address");
d7d7daec
ZJS
161 if (!sas_address)
162 return NULL;
912541b0
KS
163
164 format_lun_number(parent, &lun);
165 path_prepend(path, "sas-%s-%s", sas_address, lun);
912541b0 166 return parent;
d7867b31
KS
167}
168
66bba0e7
ML
169static struct udev_device *handle_scsi_sas(struct udev_device *parent, char **path)
170{
d7d7daec
ZJS
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;
66bba0e7
ML
175 const char *sas_address = NULL;
176 const char *phy_id;
177 const char *phy_count;
d7d7daec 178 _cleanup_free_ char *lun = NULL;
66bba0e7 179
3b64e4d4
TG
180 assert(parent);
181 assert(path);
182
d7d7daec
ZJS
183 udev = udev_device_get_udev(parent);
184
66bba0e7 185 targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target");
d7d7daec 186 if (!targetdev)
66bba0e7
ML
187 return NULL;
188
189 target_parent = udev_device_get_parent(targetdev);
d7d7daec 190 if (!target_parent)
66bba0e7
ML
191 return NULL;
192
193 /* Get sas device */
d7d7daec
ZJS
194 target_sasdev = udev_device_new_from_subsystem_sysname(
195 udev, "sas_device", udev_device_get_sysname(target_parent));
196 if (!target_sasdev)
66bba0e7
ML
197 return NULL;
198
199 /* The next parent is sas port */
200 port = udev_device_get_parent(target_parent);
d7d7daec
ZJS
201 if (!port)
202 return NULL;
66bba0e7
ML
203
204 /* Get port device */
d7d7daec
ZJS
205 port_sasdev = udev_device_new_from_subsystem_sysname(
206 udev, "sas_port", udev_device_get_sysname(port));
66bba0e7
ML
207
208 phy_count = udev_device_get_sysattr_value(port_sasdev, "num_phys");
d7d7daec
ZJS
209 if (!phy_count)
210 return NULL;
66bba0e7
ML
211
212 /* Check if we are simple disk */
d7d7daec
ZJS
213 if (strncmp(phy_count, "1", 2) != 0)
214 return handle_scsi_sas_wide_port(parent, path);
66bba0e7
ML
215
216 /* Get connected phy */
217 phy_id = udev_device_get_sysattr_value(target_sasdev, "phy_identifier");
d7d7daec
ZJS
218 if (!phy_id)
219 return NULL;
66bba0e7
ML
220
221 /* The port's parent is either hba or expander */
222 expander = udev_device_get_parent(port);
d7d7daec
ZJS
223 if (!expander)
224 return NULL;
66bba0e7
ML
225
226 /* Get expander device */
d7d7daec
ZJS
227 expander_sasdev = udev_device_new_from_subsystem_sysname(
228 udev, "sas_device", udev_device_get_sysname(expander));
229 if (expander_sasdev) {
66bba0e7
ML
230 /* Get expander's address */
231 sas_address = udev_device_get_sysattr_value(expander_sasdev,
232 "sas_address");
d7d7daec
ZJS
233 if (!sas_address)
234 return NULL;
66bba0e7
ML
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
66bba0e7
ML
243 return parent;
244}
245
9ec6e95b 246static struct udev_device *handle_scsi_iscsi(struct udev_device *parent, char **path) {
d7d7daec 247 struct udev *udev;
912541b0 248 struct udev_device *transportdev;
d7d7daec
ZJS
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;
912541b0 253
3b64e4d4
TG
254 assert(parent);
255 assert(path);
256
d7d7daec
ZJS
257 udev = udev_device_get_udev(parent);
258
912541b0
KS
259 /* find iscsi session */
260 transportdev = parent;
261 for (;;) {
262 transportdev = udev_device_get_parent(transportdev);
d7d7daec 263 if (!transportdev)
912541b0 264 return NULL;
33502ffe 265 if (startswith(udev_device_get_sysname(transportdev), "session"))
912541b0
KS
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));
d7d7daec 271 if (!sessiondev)
912541b0 272 return NULL;
d7d7daec 273
912541b0 274 target = udev_device_get_sysattr_value(sessiondev, "targetname");
d7d7daec
ZJS
275 if (!target)
276 return NULL;
912541b0 277
d7d7daec 278 connname = strjoina("connection", udev_device_get_sysnum(transportdev), ":0");
912541b0 279 conndev = udev_device_new_from_subsystem_sysname(udev, "iscsi_connection", connname);
d7d7daec
ZJS
280 if (!conndev)
281 return NULL;
282
912541b0
KS
283 addr = udev_device_get_sysattr_value(conndev, "persistent_address");
284 port = udev_device_get_sysattr_value(conndev, "persistent_port");
d7d7daec
ZJS
285 if (!addr || !port)
286 return NULL;
912541b0
KS
287
288 format_lun_number(parent, &lun);
289 path_prepend(path, "ip-%s:%s-iscsi-%s-%s", addr, port, target, lun);
912541b0 290 return parent;
d7867b31
KS
291}
292
ba86822d 293static struct udev_device *handle_scsi_ata(struct udev_device *parent, char **path) {
d7d7daec
ZJS
294 struct udev *udev;
295 struct udev_device *targetdev, *target_parent;
296 _cleanup_udev_device_unref_ struct udev_device *atadev = NULL;
ba86822d
DM
297 const char *port_no;
298
299 assert(parent);
300 assert(path);
301
d7d7daec
ZJS
302 udev = udev_device_get_udev(parent);
303
ba86822d
DM
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");
d7d7daec
ZJS
317 if (!port_no)
318 return NULL;
319
ba86822d 320 path_prepend(path, "ata-%s", port_no);
ba86822d
DM
321 return parent;
322}
323
9ec6e95b 324static struct udev_device *handle_scsi_default(struct udev_device *parent, char **path) {
912541b0
KS
325 struct udev_device *hostdev;
326 int host, bus, target, lun;
d7d7daec
ZJS
327 const char *name, *base, *pos;
328 _cleanup_closedir_ DIR *dir = NULL;
912541b0 329 struct dirent *dent;
d7d7daec 330 int basenum = -1;
912541b0 331
3b64e4d4
TG
332 assert(parent);
333 assert(path);
334
912541b0 335 hostdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host");
d7d7daec 336 if (!hostdev)
912541b0
KS
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
1c7dfbf2
KS
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 */
d7d7daec
ZJS
360
361 base = udev_device_get_syspath(hostdev);
912541b0 362 pos = strrchr(base, '/');
d7d7daec
ZJS
363 if (!pos)
364 return NULL;
365
366 base = strndupa(base, pos - base);
912541b0 367 dir = opendir(base);
d7d7daec
ZJS
368 if (!dir)
369 return NULL;
370
8fb3f009 371 FOREACH_DIRENT_ALL(dent, dir, break) {
912541b0
KS
372 char *rest;
373 int i;
374
375 if (dent->d_name[0] == '.')
376 continue;
ec2ce0c5 377 if (!IN_SET(dent->d_type, DT_DIR, DT_LNK))
912541b0 378 continue;
33502ffe 379 if (!startswith(dent->d_name, "host"))
912541b0
KS
380 continue;
381 i = strtoul(&dent->d_name[4], &rest, 10);
382 if (rest[0] != '\0')
383 continue;
746b5152
KS
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 */
912541b0
KS
389 if (basenum == -1 || i < basenum)
390 basenum = i;
391 }
d7d7daec
ZJS
392 if (basenum == -1)
393 return hostdev;
912541b0
KS
394 host -= basenum;
395
396 path_prepend(path, "scsi-%u:%u:%u:%u", host, bus, target, lun);
912541b0 397 return hostdev;
d7867b31
KS
398}
399
a24d03b8
HR
400static 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;
d7d7daec 404 _cleanup_free_ char *lun = NULL;
a24d03b8
HR
405 char guid[38];
406 size_t i, k;
407
3b64e4d4
TG
408 assert(parent);
409 assert(path);
410
a24d03b8
HR
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);
a24d03b8
HR
435 return parent;
436}
437
cc821d02 438static struct udev_device *handle_scsi(struct udev_device *parent, char **path, bool *supported_parent) {
d7d7daec 439 const char *devtype, *id, *name;
912541b0
KS
440
441 devtype = udev_device_get_devtype(parent);
d7d7daec 442 if (!streq_ptr(devtype, "scsi_device"))
912541b0
KS
443 return parent;
444
445 /* firewire */
446 id = udev_device_get_sysattr_value(parent, "ieee1394_id");
d7d7daec 447 if (id) {
912541b0 448 path_prepend(path, "ieee1394-0x%s", id);
cc821d02 449 *supported_parent = true;
d7d7daec 450 return skip_subsystem(parent, "scsi");
912541b0
KS
451 }
452
59d86149 453 /* scsi sysfs does not have a "subsystem" for the transport */
912541b0
KS
454 name = udev_device_get_syspath(parent);
455
d7d7daec 456 if (strstr(name, "/rport-")) {
cc821d02 457 *supported_parent = true;
d7d7daec 458 return handle_scsi_fibre_channel(parent, path);
912541b0
KS
459 }
460
d7d7daec 461 if (strstr(name, "/end_device-")) {
cc821d02 462 *supported_parent = true;
d7d7daec 463 return handle_scsi_sas(parent, path);
912541b0
KS
464 }
465
d7d7daec 466 if (strstr(name, "/session")) {
cc821d02 467 *supported_parent = true;
d7d7daec 468 return handle_scsi_iscsi(parent, path);
912541b0
KS
469 }
470
d7d7daec
ZJS
471 if (strstr(name, "/ata"))
472 return handle_scsi_ata(parent, path);
481dcf7c 473
d7d7daec
ZJS
474 if (strstr(name, "/vmbus_"))
475 return handle_scsi_hyperv(parent, path);
a24d03b8 476
d7d7daec 477 return handle_scsi_default(parent, path);
d7867b31
KS
478}
479
9ec6e95b 480static struct udev_device *handle_cciss(struct udev_device *parent, char **path) {
68acb21d
HR
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);
d7d7daec 489 return skip_subsystem(parent, "cciss");
68acb21d
HR
490}
491
9ec6e95b 492static void handle_scsi_tape(struct udev_device *dev, char **path) {
912541b0 493 const char *name;
d7867b31 494
912541b0 495 /* must be the last device in the syspath */
d7d7daec 496 if (*path)
912541b0 497 return;
d7867b31 498
912541b0 499 name = udev_device_get_sysname(dev);
d7d7daec 500 if (startswith(name, "nst") && strchr("lma", name[3]))
912541b0 501 path_prepend(path, "nst%c", name[3]);
d7d7daec 502 else if (startswith(name, "st") && strchr("lma", name[2]))
912541b0 503 path_prepend(path, "st%c", name[2]);
d7867b31
KS
504}
505
9ec6e95b 506static struct udev_device *handle_usb(struct udev_device *parent, char **path) {
d7d7daec 507 const char *devtype, *str, *port;
912541b0
KS
508
509 devtype = udev_device_get_devtype(parent);
d7d7daec 510 if (!devtype)
912541b0 511 return parent;
d7d7daec 512 if (!STR_IN_SET(devtype, "usb_interface", "usb_device"))
912541b0
KS
513 return parent;
514
515 str = udev_device_get_sysname(parent);
516 port = strchr(str, '-');
d7d7daec 517 if (!port)
912541b0
KS
518 return parent;
519 port++;
520
912541b0 521 path_prepend(path, "usb-0:%s", port);
d7d7daec 522 return skip_subsystem(parent, "usb");
d7867b31
KS
523}
524
9ec6e95b 525static struct udev_device *handle_bcma(struct udev_device *parent, char **path) {
89f17d4f
TG
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
e7eb5a8d
LYY
537/* Handle devices of AP bus in System z platform. */
538static struct udev_device *handle_ap(struct udev_device *parent, char **path) {
539 const char *type, *func;
912541b0 540
3b64e4d4 541 assert(parent);
3b64e4d4
TG
542 assert(path);
543
e7eb5a8d
LYY
544 type = udev_device_get_sysattr_value(parent, "type");
545 func = udev_device_get_sysattr_value(parent, "ap_functions");
912541b0 546
d7d7daec 547 if (type && func)
e7eb5a8d 548 path_prepend(path, "ap-%s-%s", type, func);
d7d7daec
ZJS
549 else
550 path_prepend(path, "ap-%s", udev_device_get_sysname(parent));
551
552 return skip_subsystem(parent, "ap");
d7867b31
KS
553}
554
9ec6e95b 555static int builtin_path_id(struct udev_device *dev, int argc, char *argv[], bool test) {
912541b0 556 struct udev_device *parent;
d7d7daec 557 _cleanup_free_ char *path = NULL;
e98bbfd2
KS
558 bool supported_transport = false;
559 bool supported_parent = false;
912541b0 560
3b64e4d4
TG
561 assert(dev);
562
912541b0
KS
563 /* walk up the chain of devices and compose path */
564 parent = dev;
d7d7daec 565 while (parent) {
912541b0
KS
566 const char *subsys;
567
568 subsys = udev_device_get_subsystem(parent);
d7d7daec 569 if (!subsys) {
912541b0 570 ;
090be865 571 } else if (streq(subsys, "scsi_tape")) {
912541b0 572 handle_scsi_tape(parent, &path);
090be865 573 } else if (streq(subsys, "scsi")) {
cc821d02 574 parent = handle_scsi(parent, &path, &supported_parent);
e98bbfd2 575 supported_transport = true;
090be865 576 } else if (streq(subsys, "cciss")) {
68acb21d 577 parent = handle_cciss(parent, &path);
e98bbfd2 578 supported_transport = true;
090be865 579 } else if (streq(subsys, "usb")) {
912541b0 580 parent = handle_usb(parent, &path);
e98bbfd2 581 supported_transport = true;
89f17d4f
TG
582 } else if (streq(subsys, "bcma")) {
583 parent = handle_bcma(parent, &path);
e98bbfd2 584 supported_transport = true;
090be865 585 } else if (streq(subsys, "serio")) {
912541b0
KS
586 path_prepend(&path, "serio-%s", udev_device_get_sysnum(parent));
587 parent = skip_subsystem(parent, "serio");
090be865 588 } else if (streq(subsys, "pci")) {
912541b0
KS
589 path_prepend(&path, "pci-%s", udev_device_get_sysname(parent));
590 parent = skip_subsystem(parent, "pci");
e98bbfd2 591 supported_parent = true;
090be865 592 } else if (streq(subsys, "platform")) {
912541b0
KS
593 path_prepend(&path, "platform-%s", udev_device_get_sysname(parent));
594 parent = skip_subsystem(parent, "platform");
e98bbfd2
KS
595 supported_transport = true;
596 supported_parent = true;
090be865 597 } else if (streq(subsys, "acpi")) {
912541b0
KS
598 path_prepend(&path, "acpi-%s", udev_device_get_sysname(parent));
599 parent = skip_subsystem(parent, "acpi");
e98bbfd2 600 supported_parent = true;
090be865 601 } else if (streq(subsys, "xen")) {
912541b0
KS
602 path_prepend(&path, "xen-%s", udev_device_get_sysname(parent));
603 parent = skip_subsystem(parent, "xen");
e98bbfd2 604 supported_parent = true;
f073b1b3 605 } else if (streq(subsys, "virtio")) {
fb92fbb1 606 parent = skip_subsystem(parent, "virtio");
f073b1b3 607 supported_transport = true;
090be865 608 } else if (streq(subsys, "scm")) {
4ecc1318
SO
609 path_prepend(&path, "scm-%s", udev_device_get_sysname(parent));
610 parent = skip_subsystem(parent, "scm");
e98bbfd2
KS
611 supported_transport = true;
612 supported_parent = true;
e7eb5a8d
LYY
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;
b4c6f71b
KB
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 }
912541b0
KS
641 }
642
28541a3d
TG
643 if (parent)
644 parent = udev_device_get_parent(parent);
912541b0 645 }
7fdd367e 646
d7d7daec
ZJS
647 if (!path)
648 return EXIT_FAILURE;
649
7fdd367e 650 /*
a42cdff1
KS
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.
7fdd367e 654 */
97b11eed 655 if (!supported_parent)
d7d7daec 656 return EXIT_FAILURE;
e98bbfd2
KS
657
658 /*
a42cdff1
KS
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.
e98bbfd2 662 */
5181ab91 663 if (streq_ptr(udev_device_get_subsystem(dev), "block") && !supported_transport)
d7d7daec 664 return EXIT_FAILURE;
7fdd367e 665
d7d7daec 666 {
912541b0
KS
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);
912541b0 698 }
d7d7daec
ZJS
699
700 return EXIT_SUCCESS;
d7867b31
KS
701}
702
703const struct udev_builtin udev_builtin_path_id = {
912541b0
KS
704 .name = "path_id",
705 .cmd = builtin_path_id,
5ac0162c 706 .help = "Compose persistent device path",
912541b0 707 .run_once = true,
d7867b31 708};