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