chiark / gitweb /
path_id: add ID_PATH_TAG= to be used in udev tags
[elogind.git] / extras / path_id / path_id.c
1 /*
2  * compose persistent device path
3  *
4  * Copyright (C) 2009 Kay Sievers <kay.sievers@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 "libudev.h"
34 #include "libudev-private.h"
35
36 static int debug;
37
38 static void log_fn(struct udev *udev, int priority,
39                    const char *file, int line, const char *fn,
40                    const char *format, va_list args)
41 {
42         if (debug) {
43                 fprintf(stderr, "%s: ", fn != NULL ? fn : file);
44                 vfprintf(stderr, format, args);
45         } else {
46                 vsyslog(priority, format, args);
47         }
48 }
49
50 static int path_prepend(char **path, const char *fmt, ...)
51 {
52         va_list va;
53         char *pre;
54         int err = 0;
55
56         va_start(va, fmt);
57         err = vasprintf(&pre, fmt, va);
58         va_end(va);
59         if (err < 0)
60                 goto out;
61
62         if (*path != NULL) {
63                 char *new;
64
65                 err = asprintf(&new, "%s-%s", pre, *path);
66                 free(pre);
67                 if (err < 0)
68                         goto out;
69                 free(*path);
70                 *path = new;
71         } else {
72                 *path = pre;
73         }
74 out:
75         return err;
76 }
77
78 /*
79 ** Linux only supports 32 bit luns.
80 ** See drivers/scsi/scsi_scan.c::scsilun_to_int() for more details.
81 */
82 static int format_lun_number(struct udev_device *dev, char **path)
83 {
84         unsigned long lun = strtoul(udev_device_get_sysnum(dev), NULL, 10);
85
86         /* address method 0, peripheral device addressing with bus id of zero */
87         if (lun < 256)
88                 return path_prepend(path, "lun-%d", lun);
89         /* handle all other lun addressing methods by using a variant of the original lun format */
90         return path_prepend(path, "lun-0x%04x%04x00000000", (lun & 0xffff), (lun >> 16) & 0xffff);
91 }
92
93 static struct udev_device *skip_subsystem(struct udev_device *dev, const char *subsys)
94 {
95         struct udev_device *parent = dev;
96
97         while (parent != NULL) {
98                 const char *subsystem;
99
100                 subsystem = udev_device_get_subsystem(parent);
101                 if (subsystem == NULL || strcmp(subsystem, subsys) != 0)
102                         break;
103                 dev = parent;
104                 parent = udev_device_get_parent(parent);
105         }
106         return dev;
107 }
108
109 static struct udev_device *handle_scsi_fibre_channel(struct udev_device *parent, char **path)
110 {
111         struct udev *udev  = udev_device_get_udev(parent);
112         struct udev_device *targetdev;
113         struct udev_device *fcdev = NULL;
114         const char *port;
115         char *lun = NULL;;
116
117         targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target");
118         if (targetdev == NULL)
119                 return NULL;
120
121         fcdev = udev_device_new_from_subsystem_sysname(udev, "fc_transport", udev_device_get_sysname(targetdev));
122         if (fcdev == NULL)
123                 return NULL;
124         port = udev_device_get_sysattr_value(fcdev, "port_name");
125         if (port == NULL) {
126                 parent = NULL;
127                 goto out;
128         }
129
130         format_lun_number(parent, &lun);
131         path_prepend(path, "fc-%s-%s", port, lun);
132         if (lun)
133                 free(lun);
134 out:
135         udev_device_unref(fcdev);
136         return parent;
137 }
138
139 static struct udev_device *handle_scsi_sas(struct udev_device *parent, char **path)
140 {
141         struct udev *udev  = udev_device_get_udev(parent);
142         struct udev_device *targetdev;
143         struct udev_device *target_parent;
144         struct udev_device *sasdev;
145         const char *sas_address;
146         char *lun = NULL;
147
148         targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target");
149         if (targetdev == NULL)
150                 return NULL;
151
152         target_parent = udev_device_get_parent(targetdev);
153         if (target_parent == NULL)
154                 return NULL;
155
156         sasdev = udev_device_new_from_subsystem_sysname(udev, "sas_device", 
157                                 udev_device_get_sysname(target_parent));
158         if (sasdev == NULL)
159                 return NULL;
160
161         sas_address = udev_device_get_sysattr_value(sasdev, "sas_address");
162         if (sas_address == NULL) {
163                 parent = NULL;
164                 goto out;
165         }
166
167         format_lun_number(parent, &lun);
168         path_prepend(path, "sas-%s-%s", sas_address, lun);
169         if (lun)
170                 free(lun);
171 out:
172         udev_device_unref(sasdev);
173         return parent;
174 }
175
176 static struct udev_device *handle_scsi_iscsi(struct udev_device *parent, char **path)
177 {
178         struct udev *udev  = udev_device_get_udev(parent);
179         struct udev_device *transportdev;
180         struct udev_device *sessiondev = NULL;
181         const char *target;
182         char *connname;
183         struct udev_device *conndev = NULL;
184         const char *addr;
185         const char *port;
186         char *lun = NULL;
187
188         /* find iscsi session */
189         transportdev = parent;
190         for (;;) {
191                 transportdev = udev_device_get_parent(transportdev);
192                 if (transportdev == NULL)
193                         return NULL;
194                 if (strncmp(udev_device_get_sysname(transportdev), "session", 7) == 0)
195                         break;
196         }
197
198         /* find iscsi session device */
199         sessiondev = udev_device_new_from_subsystem_sysname(udev, "iscsi_session", udev_device_get_sysname(transportdev));
200         if (sessiondev == NULL)
201                 return NULL;
202         target = udev_device_get_sysattr_value(sessiondev, "targetname");
203         if (target == NULL) {
204                 parent = NULL;
205                 goto out;
206         }
207
208         if (asprintf(&connname, "connection%s:0", udev_device_get_sysnum(transportdev)) < 0) {
209                 parent = NULL;
210                 goto out;
211         }
212         conndev = udev_device_new_from_subsystem_sysname(udev, "iscsi_connection", connname);
213         free(connname);
214         if (conndev == NULL) {
215                 parent = NULL;
216                 goto out;
217         }
218         addr = udev_device_get_sysattr_value(conndev, "persistent_address");
219         port = udev_device_get_sysattr_value(conndev, "persistent_port");
220         if (addr == NULL || port == NULL) {
221                 parent = NULL;
222                 goto out;
223         }
224
225         format_lun_number(parent, &lun);
226         path_prepend(path, "ip-%s:%s-iscsi-%s-%s", addr, port, target, lun);
227         if (lun)
228                 free(lun);
229 out:
230         udev_device_unref(sessiondev);
231         udev_device_unref(conndev);
232         return parent;
233 }
234
235 static struct udev_device *handle_scsi_default(struct udev_device *parent, char **path)
236 {
237         struct udev_device *hostdev;
238         int host, bus, target, lun;
239         const char *name;
240         char *base;
241         char *pos;
242         DIR *dir;
243         struct dirent *dent;
244         int basenum;
245
246         hostdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_host");
247         if (hostdev == NULL)
248                 return NULL;
249
250         name = udev_device_get_sysname(parent);
251         if (sscanf(name, "%d:%d:%d:%d", &host, &bus, &target, &lun) != 4)
252                 return NULL;
253
254         /* rebase host offset to get the local relative number */
255         basenum = -1;
256         base = strdup(udev_device_get_syspath(hostdev));
257         if (base == NULL)
258                 return NULL;
259         pos = strrchr(base, '/');
260         if (pos == NULL) {
261                 parent = NULL;
262                 goto out;
263         }
264         pos[0] = '\0';
265         dir = opendir(base);
266         if (dir == NULL) {
267                 parent = NULL;
268                 goto out;
269         }
270         for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
271                 char *rest;
272                 int i;
273
274                 if (dent->d_name[0] == '.')
275                         continue;
276                 if (dent->d_type != DT_DIR && dent->d_type != DT_LNK)
277                         continue;
278                 if (strncmp(dent->d_name, "host", 4) != 0)
279                         continue;
280                 i = strtoul(&dent->d_name[4], &rest, 10);
281                 if (rest[0] != '\0')
282                         continue;
283                 if (basenum == -1 || i < basenum)
284                         basenum = i;
285         }
286         closedir(dir);
287         if (basenum == -1) {
288                 parent = NULL;
289                 goto out;
290         }
291         host -= basenum;
292
293         path_prepend(path, "scsi-%u:%u:%u:%u", host, bus, target, lun);
294 out:
295         free(base);
296         return hostdev;
297 }
298
299 static struct udev_device *handle_scsi(struct udev_device *parent, char **path)
300 {
301         const char *devtype;
302         const char *name;
303         const char *id;
304
305         devtype = udev_device_get_devtype(parent);
306         if (devtype == NULL || strcmp(devtype, "scsi_device") != 0)
307                 return parent;
308
309         /* firewire */
310         id = udev_device_get_sysattr_value(parent, "ieee1394_id");
311         if (id != NULL) {
312                 parent = skip_subsystem(parent, "scsi");
313                 path_prepend(path, "ieee1394-0x%s", id);
314                 goto out;
315         }
316
317         /* lousy scsi sysfs does not have a "subsystem" for the transport */
318         name = udev_device_get_syspath(parent);
319
320         if (strstr(name, "/rport-") != NULL) {
321                 parent = handle_scsi_fibre_channel(parent, path);
322                 goto out;
323         }
324
325         if (strstr(name, "/end_device-") != NULL) {
326                 parent = handle_scsi_sas(parent, path);
327                 goto out;
328         }
329
330         if (strstr(name, "/session") != NULL) {
331                 parent = handle_scsi_iscsi(parent, path);
332                 goto out;
333         }
334
335         parent = handle_scsi_default(parent, path);
336 out:
337         return parent;
338 }
339
340 static void handle_scsi_tape(struct udev_device *dev, char **path)
341 {
342         const char *name;
343
344         /* must be the last device in the syspath */
345         if (*path != NULL)
346                 return;
347
348         name = udev_device_get_sysname(dev);
349         if (strncmp(name, "nst", 3) == 0 && strchr("lma", name[3]) != NULL)
350                 path_prepend(path, "nst%c", name[3]);
351         else if (strncmp(name, "st", 2) == 0 && strchr("lma", name[2]) != NULL)
352                 path_prepend(path, "st%c", name[2]);
353 }
354
355 static struct udev_device *handle_usb(struct udev_device *parent, char **path)
356 {
357         const char *devtype;
358         const char *str;
359         const char *port;
360
361         devtype = udev_device_get_devtype(parent);
362         if (devtype == NULL)
363                 return parent;
364         if (strcmp(devtype, "usb_interface") != 0 && strcmp(devtype, "usb_device") != 0)
365                 return parent;
366
367         str = udev_device_get_sysname(parent);
368         port = strchr(str, '-');
369         if (port == NULL)
370                 return parent;
371         port++;
372
373         parent = skip_subsystem(parent, "usb");
374         path_prepend(path, "usb-0:%s", port);
375         return parent;
376 }
377
378 static struct udev_device *handle_cciss(struct udev_device *parent, char **path)
379 {
380         return NULL;
381 }
382
383 static struct udev_device *handle_ccw(struct udev_device *parent, struct udev_device *dev, char **path)
384 {
385         struct udev_device *scsi_dev;
386
387         scsi_dev = udev_device_get_parent_with_subsystem_devtype(dev, "scsi", "scsi_device");
388         if (scsi_dev != NULL) {
389                 const char *wwpn;
390                 const char *lun;
391                 const char *hba_id;
392
393                 hba_id = udev_device_get_sysattr_value(scsi_dev, "hba_id");
394                 wwpn = udev_device_get_sysattr_value(scsi_dev, "wwpn");
395                 lun = udev_device_get_sysattr_value(scsi_dev, "fcp_lun");
396                 if (hba_id != NULL && lun != NULL && wwpn != NULL) {
397                         path_prepend(path, "ccw-%s-zfcp-%s:%s", hba_id, wwpn, lun);
398                         goto out;
399                 }
400         }
401
402         path_prepend(path, "ccw-%s", udev_device_get_sysname(parent));
403 out:
404         parent = skip_subsystem(parent, "ccw");
405         return parent;
406 }
407
408 int main(int argc, char **argv)
409 {
410         static const struct option options[] = {
411                 { "debug", no_argument, NULL, 'd' },
412                 { "help", no_argument, NULL, 'h' },
413                 {}
414         };
415         struct udev *udev;
416         struct udev_device *dev;
417         struct udev_device *parent;
418         char syspath[UTIL_PATH_SIZE];
419         const char *devpath;
420         char *path = NULL;
421         int rc = EXIT_FAILURE;
422
423         udev = udev_new();
424         if (udev == NULL)
425                 goto exit;
426
427         udev_log_init("path_id");
428         udev_set_log_fn(udev, log_fn);
429
430         while (1) {
431                 int option;
432
433                 option = getopt_long(argc, argv, "dh", options, NULL);
434                 if (option == -1)
435                         break;
436
437                 switch (option) {
438                 case 'd':
439                         debug = 1;
440                         if (udev_get_log_priority(udev) < LOG_INFO)
441                                 udev_set_log_priority(udev, LOG_INFO);
442                         break;
443                 case 'h':
444                         printf("Usage: path_id [--debug] [--help] <devpath>\n"
445                                "  --debug    print debug information\n"
446                                "  --help      print this help text\n\n");
447                         goto exit;
448                 }
449         }
450
451         devpath = argv[optind];
452         if (devpath == NULL) {
453                 fprintf(stderr, "No device specified\n");
454                 rc = 2;
455                 goto exit;
456         }
457
458         util_strscpyl(syspath, sizeof(syspath), udev_get_sys_path(udev), devpath, NULL);
459         dev = udev_device_new_from_syspath(udev, syspath);
460         if (dev == NULL) {
461                 fprintf(stderr, "unable to access '%s'\n", devpath);
462                 rc = 3;
463                 goto exit;
464         }
465
466         /* S390 ccw bus */
467         parent = udev_device_get_parent_with_subsystem_devtype(dev, "ccw", NULL);
468         if (parent != NULL) {
469                 handle_ccw(parent, dev, &path);
470                 goto out;
471         }
472
473         /* walk up the chain of devices and compose path */
474         parent = dev;
475         while (parent != NULL) {
476                 const char *subsys;
477
478                 subsys = udev_device_get_subsystem(parent);
479
480                 if (subsys == NULL) {
481                         ;
482                 } else if (strcmp(subsys, "scsi_tape") == 0) {
483                         handle_scsi_tape(parent, &path);
484                 } else if (strcmp(subsys, "scsi") == 0) {
485                         parent = handle_scsi(parent, &path);
486                 } else if (strcmp(subsys, "cciss") == 0) {
487                         handle_cciss(parent, &path);
488                 } else if (strcmp(subsys, "usb") == 0) {
489                         parent = handle_usb(parent, &path);
490                 } else if (strcmp(subsys, "serio") == 0) {
491                         path_prepend(&path, "serio-%s", udev_device_get_sysnum(parent));
492                         parent = skip_subsystem(parent, "serio");
493                 } else if (strcmp(subsys, "pci") == 0) {
494                         path_prepend(&path, "pci-%s", udev_device_get_sysname(parent));
495                         parent = skip_subsystem(parent, "pci");
496                 } else if (strcmp(subsys, "platform") == 0) {
497                         path_prepend(&path, "platform-%s", udev_device_get_sysname(parent));
498                         parent = skip_subsystem(parent, "platform");
499                 } else if (strcmp(subsys, "xen") == 0) {
500                         path_prepend(&path, "xen-%s", udev_device_get_sysname(parent));
501                         parent = skip_subsystem(parent, "xen");
502                 } else if (strcmp(subsys, "virtio") == 0) {
503                         path_prepend(&path, "virtio-pci-%s", udev_device_get_sysname(parent));
504                         parent = skip_subsystem(parent, "virtio");
505                 }
506
507                 parent = udev_device_get_parent(parent);
508         }
509 out:
510         if (path != NULL) {
511                 char tag[UTIL_NAME_SIZE];
512                 size_t i;
513                 const char *p;
514
515                 /* compose valid udev tag name */
516                 for (p = path, i = 0; *p; p++) {
517                         if ((*p >= '0' && *p <= '9') ||
518                             (*p >= 'A' && *p <= 'Z') ||
519                             (*p >= 'a' && *p <= 'z') ||
520                             *p == '-') {
521                                 tag[i++] = *p;
522                                 continue;
523                         }
524
525                         /* skip all leading '_' */
526                         if (i == 0)
527                                 continue;
528
529                         /* avoid second '_' */
530                         if (tag[i-1] == '_')
531                                 continue;
532
533                         tag[i++] = '_';
534                 }
535                 /* strip trailing '_' */
536                 while (i > 0 && tag[i-1] == '_')
537                         i--;
538                 tag[i] = '\0';
539
540                 printf("ID_PATH=%s\n", path);
541                 printf("ID_PATH_TAG=%s\n", tag);
542                 free(path);
543                 rc = EXIT_SUCCESS;
544         }
545
546         udev_device_unref(dev);
547 exit:
548         udev_unref(udev);
549         udev_log_close();
550         return rc;
551 }