libmultipath: foreign/nvme: implement path display
[multipath-tools/.git] / libmultipath / foreign / nvme.c
1 /*
2   Copyright (c) 2018 Martin Wilck, SUSE Linux GmbH
3
4   This program is free software; you can redistribute it and/or
5   modify it under the terms of the GNU General Public License
6   as published by the Free Software Foundation; either version 2
7   of the License, or (at your option) any later version.
8
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13
14   You should have received a copy of the GNU General Public License
15   along with this program; if not, write to the Free Software
16   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
17   USA.
18 */
19
20 #include <sys/sysmacros.h>
21 #include <libudev.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <stdbool.h>
26 #include <libudev.h>
27 #include <pthread.h>
28 #include <limits.h>
29 #include <dirent.h>
30 #include <errno.h>
31 #include "vector.h"
32 #include "generic.h"
33 #include "foreign.h"
34 #include "debug.h"
35 #include "structs.h"
36 #include "sysfs.h"
37
38 static const char nvme_vendor[] = "NVMe";
39 static const char N_A[] = "n/a";
40 const char *THIS;
41
42 struct nvme_map;
43 struct nvme_path {
44         struct gen_path gen;
45         struct udev_device *udev;
46         struct udev_device *ctl;
47         struct nvme_map *map;
48         bool seen;
49 };
50
51 struct nvme_pathgroup {
52         struct gen_pathgroup gen;
53         vector pathvec;
54 };
55
56 struct nvme_map {
57         struct gen_multipath gen;
58         struct udev_device *udev;
59         struct udev_device *subsys;
60         dev_t devt;
61         /* Just one static pathgroup for NVMe for now */
62         struct nvme_pathgroup pg;
63         struct gen_pathgroup *gpg;
64         struct _vector pgvec;
65         vector pathvec;
66         int nr_live;
67 };
68
69 #define NAME_LEN 64 /* buffer length for temp attributes */
70 #define const_gen_mp_to_nvme(g) ((const struct nvme_map*)(g))
71 #define gen_mp_to_nvme(g) ((struct nvme_map*)(g))
72 #define nvme_mp_to_gen(n) &((n)->gen)
73 #define const_gen_pg_to_nvme(g) ((const struct nvme_pathgroup*)(g))
74 #define gen_pg_to_nvme(g) ((struct nvme_pathgroup*)(g))
75 #define nvme_pg_to_gen(n) &((n)->gen)
76 #define const_gen_path_to_nvme(g) ((const struct nvme_path*)(g))
77 #define gen_path_to_nvme(g) ((struct nvme_path*)(g))
78 #define nvme_path_to_gen(n) &((n)->gen)
79
80 static void cleanup_nvme_path(struct nvme_path *path)
81 {
82         condlog(5, "%s: %p %p", __func__, path, path->udev);
83         if (path->udev)
84                 udev_device_unref(path->udev);
85         /* ctl is implicitly referenced by udev, no need to unref */
86         free(path);
87 }
88
89 static void cleanup_nvme_map(struct nvme_map *map)
90 {
91         if (map->pathvec) {
92                 struct nvme_path *path;
93                 int i;
94
95                 vector_foreach_slot_backwards(map->pathvec, path, i) {
96                         condlog(5, "%s: %d %p", __func__, i, path);
97                         cleanup_nvme_path(path);
98                         vector_del_slot(map->pathvec, i);
99                 }
100         }
101         vector_free(map->pathvec);
102         if (map->udev)
103                 udev_device_unref(map->udev);
104         /* subsys is implicitly referenced by udev, no need to unref */
105         free(map);
106 }
107
108 static const struct _vector*
109 nvme_mp_get_pgs(const struct gen_multipath *gmp) {
110         const struct nvme_map *nvme = const_gen_mp_to_nvme(gmp);
111
112         /* This is all used under the lock, no need to copy */
113         return &nvme->pgvec;
114 }
115
116 static void
117 nvme_mp_rel_pgs(const struct gen_multipath *gmp, const struct _vector *v)
118 {
119         /* empty */
120 }
121
122 static void rstrip(char *str)
123 {
124         int n;
125
126         for (n = strlen(str) - 1; n >= 0 && str[n] == ' '; n--);
127         str[n+1] = '\0';
128 }
129
130 static int snprint_nvme_map(const struct gen_multipath *gmp,
131                             char *buff, int len, char wildcard)
132 {
133         const struct nvme_map *nvm = const_gen_mp_to_nvme(gmp);
134         char fld[NAME_LEN];
135         const char *val;
136
137         switch (wildcard) {
138         case 'd':
139                 return snprintf(buff, len, "%s",
140                                 udev_device_get_sysname(nvm->udev));
141         case 'n':
142                 return snprintf(buff, len, "%s:NQN:%s",
143                                 udev_device_get_sysname(nvm->subsys),
144                                 udev_device_get_sysattr_value(nvm->subsys,
145                                                               "subsysnqn"));
146         case 'w':
147                 return snprintf(buff, len, "%s",
148                                 udev_device_get_sysattr_value(nvm->udev,
149                                                               "wwid"));
150         case 'N':
151                 return snprintf(buff, len, "%u", nvm->nr_live);
152         case 'S':
153                 return snprintf(buff, len, "%s",
154                                 udev_device_get_sysattr_value(nvm->udev,
155                                                               "size"));
156         case 'v':
157                 return snprintf(buff, len, "%s", nvme_vendor);
158         case 's':
159         case 'p':
160                 snprintf(fld, sizeof(fld), "%s",
161                          udev_device_get_sysattr_value(nvm->subsys,
162                                                       "model"));
163                 rstrip(fld);
164                 if (wildcard == 'p')
165                         return snprintf(buff, len, "%s", fld);
166                 return snprintf(buff, len, "%s,%s,%s", nvme_vendor, fld,
167                                 udev_device_get_sysattr_value(nvm->subsys,
168                                                               "firmware_rev"));
169         case 'e':
170                 return snprintf(buff, len, "%s",
171                                 udev_device_get_sysattr_value(nvm->subsys,
172                                                               "firmware_rev"));
173         case 'r':
174                 val = udev_device_get_sysattr_value(nvm->udev, "ro");
175                 if (val[0] == 1)
176                         return snprintf(buff, len, "%s", "ro");
177                 else
178                         return snprintf(buff, len, "%s", "rw");
179         case 'G':
180                 return snprintf(buff, len, "%s", THIS);
181         default:
182                 return snprintf(buff, len, N_A);
183                 break;
184         }
185         return 0;
186 }
187
188 static const struct _vector*
189 nvme_pg_get_paths(const struct gen_pathgroup *gpg) {
190         const struct nvme_pathgroup *gp = const_gen_pg_to_nvme(gpg);
191
192         /* This is all used under the lock, no need to copy */
193         return gp->pathvec;
194 }
195
196 static void
197 nvme_pg_rel_paths(const struct gen_pathgroup *gpg, const struct _vector *v)
198 {
199         /* empty */
200 }
201
202 static int snprint_nvme_pg(const struct gen_pathgroup *gmp,
203                            char *buff, int len, char wildcard)
204 {
205         return snprintf(buff, len, N_A);
206 }
207
208 static int snprint_hcil(const struct nvme_path *np, char *buf, int len)
209 {
210         unsigned int nvmeid, ctlid, nsid;
211         int rc;
212         const char *sysname = udev_device_get_sysname(np->udev);
213
214         rc = sscanf(sysname, "nvme%uc%un%u", &nvmeid, &ctlid, &nsid);
215         if (rc != 3) {
216                 condlog(1, "%s: failed to scan %s", __func__, sysname);
217                 rc = snprintf(buf, len, "(ERR:%s)", sysname);
218         } else
219                 rc = snprintf(buf, len, "%u:%u:%u", nvmeid, ctlid, nsid);
220         return (rc < len ? rc : len);
221 }
222
223 static int snprint_nvme_path(const struct gen_path *gp,
224                              char *buff, int len, char wildcard)
225 {
226         const struct nvme_path *np = const_gen_path_to_nvme(gp);
227         dev_t devt;
228         char fld[NAME_LEN];
229         struct udev_device *pci;
230
231         switch (wildcard) {
232         case 'w':
233                 return snprintf(buff, len, "%s",
234                                 udev_device_get_sysattr_value(np->udev,
235                                                               "wwid"));
236         case 'd':
237                 return snprintf(buff, len, "%s",
238                                 udev_device_get_sysname(np->udev));
239         case 'i':
240                 return snprint_hcil(np, buff, len);
241         case 'D':
242                 devt = udev_device_get_devnum(np->udev);
243                 return snprintf(buff, len, "%u:%u", major(devt), minor(devt));
244         case 'o':
245                 sysfs_attr_get_value(np->ctl, "state", fld, sizeof(fld));
246                 return snprintf(buff, len, "%s", fld);
247         case 's':
248                 snprintf(fld, sizeof(fld), "%s",
249                          udev_device_get_sysattr_value(np->ctl,
250                                                       "model"));
251                 rstrip(fld);
252                 return snprintf(buff, len, "%s,%s,%s", nvme_vendor, fld,
253                                 udev_device_get_sysattr_value(np->ctl,
254                                                               "firmware_rev"));
255         case 'S':
256                 return snprintf(buff, len, "%s",
257                         udev_device_get_sysattr_value(np->udev,
258                                                       "size"));
259         case 'z':
260                 return snprintf(buff, len, "%s",
261                                 udev_device_get_sysattr_value(np->ctl,
262                                                               "serial"));
263         case 'm':
264                 return snprintf(buff, len, "%s",
265                                 udev_device_get_sysname(np->map->udev));
266         case 'N':
267         case 'R':
268                 return snprintf(buff, len, "%s:%s",
269                         udev_device_get_sysattr_value(np->ctl,
270                                                       "transport"),
271                         udev_device_get_sysattr_value(np->ctl,
272                                                       "address"));
273         case 'G':
274                 return snprintf(buff, len, "[%s]", THIS);
275         case 'a':
276                 pci = udev_device_get_parent_with_subsystem_devtype(np->ctl,
277                                                                     "pci",
278                                                                     NULL);
279                 if (pci != NULL)
280                         return snprintf(buff, len, "PCI:%s",
281                                         udev_device_get_sysname(pci));
282                 /* fall through */
283         default:
284                 return snprintf(buff, len, "%s", N_A);
285                 break;
286         }
287         return 0;
288 }
289
290 static const struct gen_multipath_ops nvme_map_ops = {
291         .get_pathgroups = nvme_mp_get_pgs,
292         .rel_pathgroups = nvme_mp_rel_pgs,
293         .style = generic_style,
294         .snprint = snprint_nvme_map,
295 };
296
297 static const struct gen_pathgroup_ops nvme_pg_ops __attribute__((unused)) = {
298         .get_paths = nvme_pg_get_paths,
299         .rel_paths = nvme_pg_rel_paths,
300         .snprint = snprint_nvme_pg,
301 };
302
303 static const struct gen_path_ops nvme_path_ops __attribute__((unused)) = {
304         .snprint = snprint_nvme_path,
305 };
306
307 struct context {
308         pthread_mutex_t mutex;
309         vector mpvec;
310         struct udev *udev;
311 };
312
313 void lock(struct context *ctx)
314 {
315         pthread_mutex_lock(&ctx->mutex);
316 }
317
318 void unlock(void *arg)
319 {
320         struct context *ctx = arg;
321
322         pthread_mutex_unlock(&ctx->mutex);
323 }
324
325 static int _delete_all(struct context *ctx)
326 {
327         struct nvme_map *nm;
328         int n = VECTOR_SIZE(ctx->mpvec), i;
329
330         if (n == 0)
331                 return FOREIGN_IGNORED;
332
333         vector_foreach_slot_backwards(ctx->mpvec, nm, i) {
334                 vector_del_slot(ctx->mpvec, i);
335                 cleanup_nvme_map(nm);
336         }
337         return FOREIGN_OK;
338 }
339
340 int delete_all(struct context *ctx)
341 {
342         int rc;
343
344         condlog(5, "%s called for \"%s\"", __func__, THIS);
345
346         lock(ctx);
347         pthread_cleanup_push(unlock, ctx);
348         rc = _delete_all(ctx);
349         pthread_cleanup_pop(1);
350
351         return rc;
352 }
353
354 void cleanup(struct context *ctx)
355 {
356         (void)delete_all(ctx);
357
358         lock(ctx);
359         /*
360          * Locking is not strictly necessary here, locking in foreign.c
361          * makes sure that no other code is called with this ctx any more.
362          * But this should make static checkers feel better.
363          */
364         pthread_cleanup_push(unlock, ctx);
365         if (ctx->udev)
366                 udev_unref(ctx->udev);
367         if (ctx->mpvec)
368                 vector_free(ctx->mpvec);
369         ctx->mpvec = NULL;
370         ctx->udev = NULL;
371         pthread_cleanup_pop(1);
372         pthread_mutex_destroy(&ctx->mutex);
373
374         free(ctx);
375 }
376
377 struct context *init(unsigned int api, const char *name)
378 {
379         struct context *ctx;
380
381         if (api > LIBMP_FOREIGN_API) {
382                 condlog(0, "%s: api version mismatch: %08x > %08x\n",
383                         __func__, api, LIBMP_FOREIGN_API);
384                 return NULL;
385         }
386
387         if ((ctx = calloc(1, sizeof(*ctx)))== NULL)
388                 return NULL;
389
390         pthread_mutex_init(&ctx->mutex, NULL);
391
392         ctx->udev = udev_new();
393         if (ctx->udev == NULL)
394                 goto err;
395
396         ctx->mpvec = vector_alloc();
397         if (ctx->mpvec == NULL)
398                 goto err;
399
400         THIS = name;
401         return ctx;
402 err:
403         cleanup(ctx);
404         return NULL;
405 }
406
407 static struct nvme_map *_find_nvme_map_by_devt(const struct context *ctx,
408                                               dev_t devt)
409 {
410         struct nvme_map *nm;
411         int i;
412
413         if (ctx->mpvec == NULL)
414                 return NULL;
415
416         vector_foreach_slot(ctx->mpvec, nm, i) {
417                 if (nm->devt == devt)
418                         return nm;
419         }
420
421         return NULL;
422 }
423
424 static struct nvme_path *
425 _find_path_by_syspath(struct nvme_map *map, const char *syspath)
426 {
427         struct nvme_path *path;
428         char real[PATH_MAX];
429         const char *ppath;
430         int i;
431
432         ppath = realpath(syspath, real);
433         if (ppath == NULL) {
434                 condlog(1, "%s: %s: error in realpath", __func__, THIS);
435                 ppath = syspath;
436         }
437
438         vector_foreach_slot(map->pathvec, path, i) {
439                 if (!strcmp(ppath,
440                             udev_device_get_syspath(path->udev)))
441                         return path;
442         }
443         condlog(4, "%s: %s: %s not found", __func__, THIS, ppath);
444         return NULL;
445 }
446
447 static int no_dotfiles(const struct dirent *di)
448 {
449         return di->d_name[0] != '.';
450 }
451
452 static void _find_slaves(struct context *ctx, struct nvme_map *map)
453 {
454         char pathbuf[PATH_MAX];
455         struct dirent **di = NULL;
456         struct nvme_path *path;
457         int r, i;
458
459         if (map == NULL || map->udev == NULL)
460                 return;
461
462         vector_foreach_slot(map->pathvec, path, i)
463                 path->seen = false;
464
465         snprintf(pathbuf, sizeof(pathbuf),
466                 "%s/slaves",
467                 udev_device_get_syspath(map->udev));
468
469         r = scandir(pathbuf, &di, no_dotfiles, alphasort);
470
471         if (r == 0) {
472                 condlog(3, "%s: %s: no paths for %s", __func__, THIS,
473                         udev_device_get_sysname(map->udev));
474                 return;
475         } else if (r < 0) {
476                 condlog(1, "%s: %s: error %d scanning paths of %s", __func__,
477                         THIS, errno, udev_device_get_sysname(map->udev));
478                 return;
479         }
480
481         pthread_cleanup_push(free, di);
482         for (i = 0; i < r; i++) {
483                 char *fn = di[i]->d_name;
484                 struct udev_device *udev;
485
486                 if (snprintf(pathbuf, sizeof(pathbuf), "%s/slaves/%s",
487                              udev_device_get_syspath(map->udev), fn)
488                     >= sizeof(pathbuf))
489                         continue;
490
491                 path = _find_path_by_syspath(map, pathbuf);
492                 if (path != NULL) {
493                         path->seen = true;
494                         condlog(4, "%s: %s already known",
495                                 __func__, fn);
496                         continue;
497                 }
498
499                 udev = udev_device_new_from_syspath(ctx->udev, pathbuf);
500                 if (udev == NULL) {
501                         condlog(1, "%s: %s: failed to get udev device for %s",
502                                 __func__, THIS, fn);
503                         continue;
504                 }
505
506                 path = calloc(1, sizeof(*path));
507                 if (path == NULL)
508                         continue;
509
510                 path->gen.ops = &nvme_path_ops;
511                 path->udev = udev;
512                 path->seen = true;
513                 path->map = map;
514                 path->ctl = udev_device_get_parent_with_subsystem_devtype
515                         (udev, "nvme", NULL);
516                 if (path->ctl == NULL) {
517                         condlog(1, "%s: %s: failed to get controller for %s",
518                                 __func__, THIS, fn);
519                         cleanup_nvme_path(path);
520                         continue;
521                 }
522
523                 if (vector_alloc_slot(map->pathvec) == NULL) {
524                         cleanup_nvme_path(path);
525                         continue;
526                 }
527                 condlog(3, "%s: %s: new path %s added to %s",
528                         __func__, THIS, udev_device_get_sysname(udev),
529                         udev_device_get_sysname(map->udev));
530                 vector_set_slot(map->pathvec, path);
531         }
532         pthread_cleanup_pop(1);
533
534         map->nr_live = 0;
535         vector_foreach_slot_backwards(map->pathvec, path, i) {
536                 if (!path->seen) {
537                         condlog(1, "path %d not found in %s any more",
538                                 i, udev_device_get_sysname(map->udev));
539                         vector_del_slot(map->pathvec, i);
540                         cleanup_nvme_path(path);
541                 } else {
542                         static const char live_state[] = "live";
543                         char state[16];
544
545                         if ((sysfs_attr_get_value(path->ctl, "state", state,
546                                                   sizeof(state)) > 0) &&
547                             !strncmp(state, live_state, sizeof(live_state) - 1))
548                                 map->nr_live++;
549                 }
550         }
551         condlog(3, "%s: %s: map %s has %d/%d live paths", __func__, THIS,
552                 udev_device_get_sysname(map->udev), map->nr_live,
553                 VECTOR_SIZE(map->pathvec));
554 }
555
556 static int _add_map(struct context *ctx, struct udev_device *ud,
557                     struct udev_device *subsys)
558 {
559         dev_t devt = udev_device_get_devnum(ud);
560         struct nvme_map *map;
561
562         if (_find_nvme_map_by_devt(ctx, devt) != NULL)
563                 return FOREIGN_OK;
564
565         map = calloc(1, sizeof(*map));
566         if (map == NULL)
567                 return FOREIGN_ERR;
568
569         map->devt = devt;
570         map->udev = udev_device_ref(ud);
571         /*
572          * subsys is implicitly referenced by map->udev,
573          * no need to take a reference here.
574          */
575         map->subsys = subsys;
576         map->gen.ops = &nvme_map_ops;
577
578         map->pathvec = vector_alloc();
579         if (map->pathvec == NULL) {
580                 cleanup_nvme_map(map);
581                 return FOREIGN_ERR;
582         }
583
584         map->pg.gen.ops = &nvme_pg_ops;
585         map->pg.pathvec = map->pathvec;
586         map->gpg = nvme_pg_to_gen(&map->pg);
587
588         map->pgvec.allocated = 1;
589         map->pgvec.slot = (void**)&map->gpg;
590
591         if (vector_alloc_slot(ctx->mpvec) == NULL) {
592                 cleanup_nvme_map(map);
593                 return FOREIGN_ERR;
594         }
595         vector_set_slot(ctx->mpvec, map);
596         _find_slaves(ctx, map);
597
598         return FOREIGN_CLAIMED;
599 }
600
601 int add(struct context *ctx, struct udev_device *ud)
602 {
603         struct udev_device *subsys;
604         int rc;
605
606         condlog(5, "%s called for \"%s\"", __func__, THIS);
607
608         if (ud == NULL)
609                 return FOREIGN_ERR;
610         if (strcmp("disk", udev_device_get_devtype(ud)))
611                 return FOREIGN_IGNORED;
612
613         subsys = udev_device_get_parent_with_subsystem_devtype(ud,
614                                                                "nvme-subsystem",
615                                                                NULL);
616         if (subsys == NULL)
617                 return FOREIGN_IGNORED;
618
619         lock(ctx);
620         pthread_cleanup_push(unlock, ctx);
621         rc = _add_map(ctx, ud, subsys);
622         pthread_cleanup_pop(1);
623
624         if (rc == FOREIGN_CLAIMED)
625                 condlog(3, "%s: %s: added map %s", __func__, THIS,
626                         udev_device_get_sysname(ud));
627         else if (rc != FOREIGN_OK)
628                 condlog(1, "%s: %s: retcode %d adding %s",
629                         __func__, THIS, rc, udev_device_get_sysname(ud));
630
631         return rc;
632 }
633
634 int change(struct context *ctx, struct udev_device *ud)
635 {
636         condlog(5, "%s called for \"%s\"", __func__, THIS);
637         return FOREIGN_IGNORED;
638 }
639
640 static int _delete_map(struct context *ctx, struct udev_device *ud)
641 {
642         int k;
643         struct nvme_map *map;
644         dev_t devt = udev_device_get_devnum(ud);
645
646         map = _find_nvme_map_by_devt(ctx, devt);
647         if (map ==NULL)
648                 return FOREIGN_IGNORED;
649
650         k = find_slot(ctx->mpvec, map);
651         if (k == -1)
652                 return FOREIGN_ERR;
653         else
654                 vector_del_slot(ctx->mpvec, k);
655
656         cleanup_nvme_map(map);
657
658         return FOREIGN_OK;
659 }
660
661 int delete(struct context *ctx, struct udev_device *ud)
662 {
663         int rc;
664
665         condlog(5, "%s called for \"%s\"", __func__, THIS);
666
667         if (ud == NULL)
668                 return FOREIGN_ERR;
669
670         lock(ctx);
671         pthread_cleanup_push(unlock, ctx);
672         rc = _delete_map(ctx, ud);
673         pthread_cleanup_pop(1);
674
675         if (rc == FOREIGN_OK)
676                 condlog(3, "%s: %s: map %s deleted", __func__, THIS,
677                         udev_device_get_sysname(ud));
678         else if (rc != FOREIGN_IGNORED)
679                 condlog(1, "%s: %s: retcode %d deleting map %s", __func__,
680                         THIS, rc, udev_device_get_sysname(ud));
681
682         return rc;
683 }
684
685 void _check(struct context *ctx)
686 {
687         struct gen_multipath *gm;
688         int i;
689
690         vector_foreach_slot(ctx->mpvec, gm, i) {
691                 struct nvme_map *map = gen_mp_to_nvme(gm);
692
693                 _find_slaves(ctx, map);
694         }
695 }
696
697 void check(struct context *ctx)
698 {
699         condlog(4, "%s called for \"%s\"", __func__, THIS);
700         lock(ctx);
701         pthread_cleanup_push(unlock, ctx);
702         _check(ctx);
703         pthread_cleanup_pop(1);
704         return;
705 }
706
707 /*
708  * It's safe to pass our internal pointer, this is only used under the lock.
709  */
710 const struct _vector *get_multipaths(const struct context *ctx)
711 {
712         condlog(5, "%s called for \"%s\"", __func__, THIS);
713         return ctx->mpvec;
714 }
715
716 void release_multipaths(const struct context *ctx, const struct _vector *mpvec)
717 {
718         condlog(5, "%s called for \"%s\"", __func__, THIS);
719         /* NOP */
720 }
721
722 /*
723  * It's safe to pass our internal pointer, this is only used under the lock.
724  */
725 const struct _vector * get_paths(const struct context *ctx)
726 {
727         vector paths = NULL;
728         const struct gen_multipath *gm;
729         int i;
730
731         condlog(5, "%s called for \"%s\"", __func__, THIS);
732         vector_foreach_slot(ctx->mpvec, gm, i) {
733                 const struct nvme_map *nm = const_gen_mp_to_nvme(gm);
734                 paths = vector_convert(paths, nm->pathvec,
735                                        struct gen_path, identity);
736         }
737         return paths;
738 }
739
740 void release_paths(const struct context *ctx, const struct _vector *mpvec)
741 {
742         condlog(5, "%s called for \"%s\"", __func__, THIS);
743         vector_free_const(mpvec);
744 }
745
746 /* compile-time check whether all methods are present and correctly typed */
747 #define _METHOD_INIT(x) .x = x
748 static struct foreign __methods __attribute__((unused)) = {
749         _METHOD_INIT(init),
750         _METHOD_INIT(cleanup),
751         _METHOD_INIT(change),
752         _METHOD_INIT(delete),
753         _METHOD_INIT(delete_all),
754         _METHOD_INIT(check),
755         _METHOD_INIT(lock),
756         _METHOD_INIT(unlock),
757         _METHOD_INIT(get_multipaths),
758         _METHOD_INIT(release_multipaths),
759         _METHOD_INIT(get_paths),
760         _METHOD_INIT(release_paths),
761 };