chiark / gitweb /
62d26d8c4d5b759b88548050a568e35e7ee69240
[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 *old;
54         char *pre;
55         int err;
56
57         old = *path;
58
59         va_start(va, fmt);
60         err = vasprintf(&pre, fmt, va);
61         va_end(va);
62         if (err < 0)
63                 return err;
64
65         if (old != NULL) {
66                 err = asprintf(path, "%s-%s", pre, old);
67                 if (err < 0)
68                         return err;
69                 free(pre);
70         } else {
71                 *path = pre;
72         }
73
74         free(old);
75         return 0;
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 **suffix)
341 {
342         const char *name;
343
344         name = udev_device_get_sysname(dev);
345         if (strncmp(name, "nst", 3) == 0 && strchr("lma", name[3]) != NULL)
346                 asprintf(suffix, "nst%c", name[3]);
347         else if (strncmp(name, "st", 2) == 0 && strchr("lma", name[2]) != NULL)
348                 asprintf(suffix, "st%c", name[2]);
349 }
350
351 static struct udev_device *handle_usb(struct udev_device *parent, char **path)
352 {
353         const char *devtype;
354         const char *str;
355         const char *port;
356
357         devtype = udev_device_get_devtype(parent);
358         if (devtype == NULL || strcmp(devtype, "usb_interface") != 0)
359                 return parent;
360
361         str = udev_device_get_sysname(parent);
362         port = strchr(str, '-');
363         if (port == NULL)
364                 return parent;
365         port++;
366
367         parent = skip_subsystem(parent, "usb");
368         path_prepend(path, "usb-0:%s", port);
369         return parent;
370 }
371
372 static struct udev_device *handle_cciss(struct udev_device *parent, char **path)
373 {
374         return NULL;
375 }
376
377 static struct udev_device *handle_ccw(struct udev_device *parent, struct udev_device *dev, char **path)
378 {
379         struct udev_device *scsi_dev;
380
381         scsi_dev = udev_device_get_parent_with_subsystem_devtype(dev, "scsi", "scsi_device");
382         if (scsi_dev != NULL) {
383                 const char *wwpn;
384                 const char *lun;
385                 const char *hba_id;
386
387                 hba_id = udev_device_get_sysattr_value(scsi_dev, "hba_id");
388                 wwpn = udev_device_get_sysattr_value(scsi_dev, "wwpn");
389                 lun = udev_device_get_sysattr_value(scsi_dev, "fcp_lun");
390                 if (hba_id != NULL && lun != NULL && wwpn != NULL) {
391                         path_prepend(path, "ccw-%s-zfcp-%s:%s", hba_id, wwpn, lun);
392                         goto out;
393                 }
394         }
395
396         path_prepend(path, "ccw-%s", udev_device_get_sysname(parent));
397 out:
398         parent = skip_subsystem(parent, "ccw");
399         return parent;
400 }
401
402 int main(int argc, char **argv)
403 {
404         static const struct option options[] = {
405                 { "debug", no_argument, NULL, 'd' },
406                 { "help", no_argument, NULL, 'h' },
407                 {}
408         };
409         struct udev *udev;
410         struct udev_device *dev;
411         struct udev_device *parent;
412         char syspath[UTIL_PATH_SIZE];
413         const char *devpath;
414         char *path;
415         char *path_suffix;
416         int rc = 1;
417
418         udev = udev_new();
419         if (udev == NULL)
420                 goto exit;
421
422         udev_log_init("path_id");
423         udev_set_log_fn(udev, log_fn);
424
425         while (1) {
426                 int option;
427
428                 option = getopt_long(argc, argv, "dh", options, NULL);
429                 if (option == -1)
430                         break;
431
432                 switch (option) {
433                 case 'd':
434                         debug = 1;
435                         if (udev_get_log_priority(udev) < LOG_INFO)
436                                 udev_set_log_priority(udev, LOG_INFO);
437                         break;
438                 case 'h':
439                         printf("Usage: path_id [--debug] [--help] <devpath>\n"
440                                "  --debug    print debug information\n"
441                                "  --help      print this help text\n\n");
442                         goto exit;
443                 }
444         }
445
446         devpath = argv[optind];
447         if (devpath == NULL) {
448                 fprintf(stderr, "No device specified\n");
449                 rc = 2;
450                 goto exit;
451         }
452
453         util_strscpyl(syspath, sizeof(syspath), udev_get_sys_path(udev), devpath, NULL);
454         dev = udev_device_new_from_syspath(udev, syspath);
455         if (dev == NULL) {
456                 fprintf(stderr, "unable to access '%s'\n", devpath);
457                 rc = 3;
458                 goto exit;
459         }
460
461         path = NULL;
462         path_suffix = NULL;
463
464         /* S390 ccw bus */
465         parent = udev_device_get_parent_with_subsystem_devtype(dev, "ccw", NULL);
466         if (parent != NULL) {
467                 handle_ccw(parent, dev, &path);
468                 goto out;
469         }
470
471         /* walk up the chain of devices and compose path */
472         parent = dev;
473         while (parent != NULL) {
474                 const char *subsys;
475
476                 subsys = udev_device_get_subsystem(parent);
477
478                 if (subsys == NULL) {
479                         ;
480                 } else if (strcmp(subsys, "scsi_tape") == 0) {
481                         handle_scsi_tape(parent, &path_suffix);
482                 } else if (strcmp(subsys, "scsi") == 0) {
483                         parent = handle_scsi(parent, &path);
484                 } else if (strcmp(subsys, "cciss") == 0) {
485                         handle_cciss(parent, &path);
486                 } else if (strcmp(subsys, "usb") == 0) {
487                         parent = handle_usb(parent, &path);
488                 } else if (strcmp(subsys, "serio") == 0) {
489                         path_prepend(&path, "serio-%s", udev_device_get_sysnum(parent));
490                         parent = skip_subsystem(parent, "serio");
491                 } else if (strcmp(subsys, "pci") == 0) {
492                         path_prepend(&path, "pci-%s", udev_device_get_sysname(parent));
493                         parent = skip_subsystem(parent, "pci");
494                 } else if (strcmp(subsys, "platform") == 0) {
495                         path_prepend(&path, "platform-%s", udev_device_get_sysname(parent));
496                         parent = skip_subsystem(parent, "platform");
497                 } else if (strcmp(subsys, "xen") == 0) {
498                         path_prepend(&path, "xen-%s", udev_device_get_sysname(parent));
499                         parent = skip_subsystem(parent, "xen");
500                 } else if (strcmp(subsys, "virtio") == 0) {
501                         path_prepend(&path, "virtio-pci-%s", udev_device_get_sysname(parent));
502                         parent = skip_subsystem(parent, "virtio");
503                 }
504
505                 parent = udev_device_get_parent(parent);
506         }
507 out:
508         if (path != NULL) {
509                 if (path_suffix != NULL) {
510                         printf("ID_PATH=%s%s\n", path, path_suffix);
511                         free(path_suffix);
512                 } else {
513                         printf("ID_PATH=%s\n", path);
514                 }
515                 free(path);
516                 rc = 0;
517         }
518
519         udev_device_unref(dev);
520 exit:
521         udev_unref(udev);
522         udev_log_close();
523         return rc;
524 }