On Tue, Mar 06, 2018 at 12:15:05AM +0100, Martin Wilck wrote: > implement display of path information for NVMe foreign paths and maps. > With this patch, I get output like this for Linux NVMe soft targets: > > nvme-submultipathd show topology > sys0:NQN:subsysname (uuid.96926ba3-b207-437c-902c-4a4df6538c3f) [nvme] nvme0n1 NVMe,Linux,4.15.0-r > size=2097152 features='n/a' hwhandler='n/a' wp=rw > `-+- policy='n/a' prio=n/a status=n/a > |- 0:1:1 nvme0c1n1 0:0 n/a n/a live > |- 0:2:1 nvme0c2n1 0:0 n/a n/a live > |- 0:3:1 nvme0c3n1 0:0 n/a n/a live > `- 0:4:1 nvme0c4n1 0:0 n/a n/a live > > multipathd show paths format '%G %d %i %o %z %m %N' > foreign dev hcil dev_st serial multipath host WWNN > [nvme] nvme0c1n1 0:1:1 live 1c2c86659503a02f nvme0n1 rdma:traddr=192.168.201.101,trsvcid=4420 > [nvme] nvme0c2n1 0:2:1 live 1c2c86659503a02f nvme0n1 rdma:traddr=192.168.202.101,trsvcid=4420 > [nvme] nvme0c3n1 0:3:1 live 1c2c86659503a02f nvme0n1 rdma:traddr=192.168.203.101,trsvcid=4420 > [nvme] nvme0c4n1 0:4:1 live 1c2c86659503a02f nvme0n1 rdma:traddr=192.168.204.101,trsvcid=4420 > > (admittedly, I abused the 'WWNN' wildcard here a bit to display information > which is helpful for NVMe over RDMA). > Reviewed-by: Benjamin Marzinski <bmarzins@xxxxxxxxxx> > Signed-off-by: Martin Wilck <mwilck@xxxxxxxx> > --- > libmultipath/foreign/nvme.c | 334 ++++++++++++++++++++++++++++++++++++++++++-- > 1 file changed, 320 insertions(+), 14 deletions(-) > > diff --git a/libmultipath/foreign/nvme.c b/libmultipath/foreign/nvme.c > index 32bd5c96c44a..235f75dd2add 100644 > --- a/libmultipath/foreign/nvme.c > +++ b/libmultipath/foreign/nvme.c > @@ -25,42 +25,98 @@ > #include <stdbool.h> > #include <libudev.h> > #include <pthread.h> > +#include <limits.h> > +#include <dirent.h> > +#include <errno.h> > #include "vector.h" > #include "generic.h" > #include "foreign.h" > #include "debug.h" > +#include "structs.h" > +#include "sysfs.h" > > +static const char nvme_vendor[] = "NVMe"; > +static const char N_A[] = "n/a"; > const char *THIS; > > +struct nvme_map; > +struct nvme_path { > + struct gen_path gen; > + struct udev_device *udev; > + struct udev_device *ctl; > + struct nvme_map *map; > + bool seen; > +}; > + > +struct nvme_pathgroup { > + struct gen_pathgroup gen; > + vector pathvec; > +}; > + > struct nvme_map { > struct gen_multipath gen; > struct udev_device *udev; > struct udev_device *subsys; > dev_t devt; > + /* Just one static pathgroup for NVMe for now */ > + struct nvme_pathgroup pg; > + struct gen_pathgroup *gpg; > + struct _vector pgvec; > + vector pathvec; > + int nr_live; > }; > > -#define NAME_LEN 64 /* buffer length temp model name */ > +#define NAME_LEN 64 /* buffer length for temp attributes */ > #define const_gen_mp_to_nvme(g) ((const struct nvme_map*)(g)) > #define gen_mp_to_nvme(g) ((struct nvme_map*)(g)) > #define nvme_mp_to_gen(n) &((n)->gen) > +#define const_gen_pg_to_nvme(g) ((const struct nvme_pathgroup*)(g)) > +#define gen_pg_to_nvme(g) ((struct nvme_pathgroup*)(g)) > +#define nvme_pg_to_gen(n) &((n)->gen) > +#define const_gen_path_to_nvme(g) ((const struct nvme_path*)(g)) > +#define gen_path_to_nvme(g) ((struct nvme_path*)(g)) > +#define nvme_path_to_gen(n) &((n)->gen) > + > +static void cleanup_nvme_path(struct nvme_path *path) > +{ > + condlog(5, "%s: %p %p", __func__, path, path->udev); > + if (path->udev) > + udev_device_unref(path->udev); > + /* ctl is implicitly referenced by udev, no need to unref */ > + free(path); > +} > > static void cleanup_nvme_map(struct nvme_map *map) > { > + if (map->pathvec) { > + struct nvme_path *path; > + int i; > + > + vector_foreach_slot_backwards(map->pathvec, path, i) { > + condlog(5, "%s: %d %p", __func__, i, path); > + cleanup_nvme_path(path); > + vector_del_slot(map->pathvec, i); > + } > + } > + vector_free(map->pathvec); > if (map->udev) > udev_device_unref(map->udev); > - if (map->subsys) > - udev_device_unref(map->subsys); > + /* subsys is implicitly referenced by udev, no need to unref */ > free(map); > } > > static const struct _vector* > nvme_mp_get_pgs(const struct gen_multipath *gmp) { > - return NULL; > + const struct nvme_map *nvme = const_gen_mp_to_nvme(gmp); > + > + /* This is all used under the lock, no need to copy */ > + return &nvme->pgvec; > } > > static void > nvme_mp_rel_pgs(const struct gen_multipath *gmp, const struct _vector *v) > { > + /* empty */ > } > > static void rstrip(char *str) > @@ -75,7 +131,6 @@ static int snprint_nvme_map(const struct gen_multipath *gmp, > char *buff, int len, char wildcard) > { > const struct nvme_map *nvm = const_gen_mp_to_nvme(gmp); > - static const char nvme_vendor[] = "NVMe"; > char fld[NAME_LEN]; > const char *val; > > @@ -92,6 +147,8 @@ static int snprint_nvme_map(const struct gen_multipath *gmp, > return snprintf(buff, len, "%s", > udev_device_get_sysattr_value(nvm->udev, > "wwid")); > + case 'N': > + return snprintf(buff, len, "%u", nvm->nr_live); > case 'S': > return snprintf(buff, len, "%s", > udev_device_get_sysattr_value(nvm->udev, > @@ -122,7 +179,7 @@ static int snprint_nvme_map(const struct gen_multipath *gmp, > case 'G': > return snprintf(buff, len, "%s", THIS); > default: > - return snprintf(buff, len, "N/A"); > + return snprintf(buff, len, N_A); > break; > } > return 0; > @@ -130,27 +187,101 @@ static int snprint_nvme_map(const struct gen_multipath *gmp, > > static const struct _vector* > nvme_pg_get_paths(const struct gen_pathgroup *gpg) { > - return NULL; > + const struct nvme_pathgroup *gp = const_gen_pg_to_nvme(gpg); > + > + /* This is all used under the lock, no need to copy */ > + return gp->pathvec; > } > > static void > nvme_pg_rel_paths(const struct gen_pathgroup *gpg, const struct _vector *v) > { > + /* empty */ > } > > static int snprint_nvme_pg(const struct gen_pathgroup *gmp, > char *buff, int len, char wildcard) > { > - return 0; > + return snprintf(buff, len, N_A); > } > > -static int snprint_nvme_path(const struct gen_path *gmp, > +static int snprint_hcil(const struct nvme_path *np, char *buf, int len) > +{ > + unsigned int nvmeid, ctlid, nsid; > + int rc; > + const char *sysname = udev_device_get_sysname(np->udev); > + > + rc = sscanf(sysname, "nvme%uc%un%u", &nvmeid, &ctlid, &nsid); > + if (rc != 3) { > + condlog(1, "%s: failed to scan %s", __func__, sysname); > + rc = snprintf(buf, len, "(ERR:%s)", sysname); > + } else > + rc = snprintf(buf, len, "%u:%u:%u", nvmeid, ctlid, nsid); > + return (rc < len ? rc : len); > +} > + > +static int snprint_nvme_path(const struct gen_path *gp, > char *buff, int len, char wildcard) > { > + const struct nvme_path *np = const_gen_path_to_nvme(gp); > + dev_t devt; > + char fld[NAME_LEN]; > + struct udev_device *pci; > + > switch (wildcard) { > + case 'w': > + return snprintf(buff, len, "%s", > + udev_device_get_sysattr_value(np->udev, > + "wwid")); > + case 'd': > + return snprintf(buff, len, "%s", > + udev_device_get_sysname(np->udev)); > + case 'i': > + return snprint_hcil(np, buff, len); > + case 'D': > + devt = udev_device_get_devnum(np->udev); > + return snprintf(buff, len, "%u:%u", major(devt), minor(devt)); > + case 'o': > + sysfs_attr_get_value(np->ctl, "state", fld, sizeof(fld)); > + return snprintf(buff, len, "%s", fld); > + case 's': > + snprintf(fld, sizeof(fld), "%s", > + udev_device_get_sysattr_value(np->ctl, > + "model")); > + rstrip(fld); > + return snprintf(buff, len, "%s,%s,%s", nvme_vendor, fld, > + udev_device_get_sysattr_value(np->ctl, > + "firmware_rev")); > + case 'S': > + return snprintf(buff, len, "%s", > + udev_device_get_sysattr_value(np->udev, > + "size")); > + case 'z': > + return snprintf(buff, len, "%s", > + udev_device_get_sysattr_value(np->ctl, > + "serial")); > + case 'm': > + return snprintf(buff, len, "%s", > + udev_device_get_sysname(np->map->udev)); > + case 'N': > case 'R': > - return snprintf(buff, len, "[foreign: %s]", THIS); > + return snprintf(buff, len, "%s:%s", > + udev_device_get_sysattr_value(np->ctl, > + "transport"), > + udev_device_get_sysattr_value(np->ctl, > + "address")); > + case 'G': > + return snprintf(buff, len, "[%s]", THIS); > + case 'a': > + pci = udev_device_get_parent_with_subsystem_devtype(np->ctl, > + "pci", > + NULL); > + if (pci != NULL) > + return snprintf(buff, len, "PCI:%s", > + udev_device_get_sysname(pci)); > + /* fall through */ > default: > + return snprintf(buff, len, "%s", N_A); > break; > } > return 0; > @@ -176,6 +307,7 @@ static const struct gen_path_ops nvme_path_ops __attribute__((unused)) = { > struct context { > pthread_mutex_t mutex; > vector mpvec; > + struct udev *udev; > }; > > void lock(struct context *ctx) > @@ -257,6 +389,10 @@ struct context *init(unsigned int api, const char *name) > > pthread_mutex_init(&ctx->mutex, NULL); > > + ctx->udev = udev_new(); > + if (ctx->udev == NULL) > + goto err; > + > ctx->mpvec = vector_alloc(); > if (ctx->mpvec == NULL) > goto err; > @@ -285,6 +421,138 @@ static struct nvme_map *_find_nvme_map_by_devt(const struct context *ctx, > return NULL; > } > > +static struct nvme_path * > +_find_path_by_syspath(struct nvme_map *map, const char *syspath) > +{ > + struct nvme_path *path; > + char real[PATH_MAX]; > + const char *ppath; > + int i; > + > + ppath = realpath(syspath, real); > + if (ppath == NULL) { > + condlog(1, "%s: %s: error in realpath", __func__, THIS); > + ppath = syspath; > + } > + > + vector_foreach_slot(map->pathvec, path, i) { > + if (!strcmp(ppath, > + udev_device_get_syspath(path->udev))) > + return path; > + } > + condlog(4, "%s: %s: %s not found", __func__, THIS, ppath); > + return NULL; > +} > + > +static int no_dotfiles(const struct dirent *di) > +{ > + return di->d_name[0] != '.'; > +} > + > +static void _find_slaves(struct context *ctx, struct nvme_map *map) > +{ > + char pathbuf[PATH_MAX]; > + struct dirent **di = NULL; > + struct nvme_path *path; > + int r, i; > + > + if (map == NULL || map->udev == NULL) > + return; > + > + vector_foreach_slot(map->pathvec, path, i) > + path->seen = false; > + > + snprintf(pathbuf, sizeof(pathbuf), > + "%s/slaves", > + udev_device_get_syspath(map->udev)); > + > + r = scandir(pathbuf, &di, no_dotfiles, alphasort); > + > + if (r == 0) { > + condlog(3, "%s: %s: no paths for %s", __func__, THIS, > + udev_device_get_sysname(map->udev)); > + return; > + } else if (r < 0) { > + condlog(1, "%s: %s: error %d scanning paths of %s", __func__, > + THIS, errno, udev_device_get_sysname(map->udev)); > + return; > + } > + > + pthread_cleanup_push(free, di); > + for (i = 0; i < r; i++) { > + char *fn = di[i]->d_name; > + struct udev_device *udev; > + > + if (snprintf(pathbuf, sizeof(pathbuf), "%s/slaves/%s", > + udev_device_get_syspath(map->udev), fn) > + >= sizeof(pathbuf)) > + continue; > + > + path = _find_path_by_syspath(map, pathbuf); > + if (path != NULL) { > + path->seen = true; > + condlog(4, "%s: %s already known", > + __func__, fn); > + continue; > + } > + > + udev = udev_device_new_from_syspath(ctx->udev, pathbuf); > + if (udev == NULL) { > + condlog(1, "%s: %s: failed to get udev device for %s", > + __func__, THIS, fn); > + continue; > + } > + > + path = calloc(1, sizeof(*path)); > + if (path == NULL) > + continue; > + > + path->gen.ops = &nvme_path_ops; > + path->udev = udev; > + path->seen = true; > + path->map = map; > + path->ctl = udev_device_get_parent_with_subsystem_devtype > + (udev, "nvme", NULL); > + if (path->ctl == NULL) { > + condlog(1, "%s: %s: failed to get controller for %s", > + __func__, THIS, fn); > + cleanup_nvme_path(path); > + continue; > + } > + > + if (vector_alloc_slot(map->pathvec) == NULL) { > + cleanup_nvme_path(path); > + continue; > + } > + condlog(3, "%s: %s: new path %s added to %s", > + __func__, THIS, udev_device_get_sysname(udev), > + udev_device_get_sysname(map->udev)); > + vector_set_slot(map->pathvec, path); > + } > + pthread_cleanup_pop(1); > + > + map->nr_live = 0; > + vector_foreach_slot_backwards(map->pathvec, path, i) { > + if (!path->seen) { > + condlog(1, "path %d not found in %s any more", > + i, udev_device_get_sysname(map->udev)); > + vector_del_slot(map->pathvec, i); > + cleanup_nvme_path(path); > + } else { > + static const char live_state[] = "live"; > + char state[16]; > + > + if ((sysfs_attr_get_value(path->ctl, "state", state, > + sizeof(state)) > 0) && > + !strncmp(state, live_state, sizeof(live_state) - 1)) > + map->nr_live++; > + } > + } > + condlog(3, "%s: %s: map %s has %d/%d live paths", __func__, THIS, > + udev_device_get_sysname(map->udev), map->nr_live, > + VECTOR_SIZE(map->pathvec)); > +} > + > static int _add_map(struct context *ctx, struct udev_device *ud, > struct udev_device *subsys) > { > @@ -307,12 +575,25 @@ static int _add_map(struct context *ctx, struct udev_device *ud, > map->subsys = subsys; > map->gen.ops = &nvme_map_ops; > > - if (vector_alloc_slot(ctx->mpvec) == NULL) { > + map->pathvec = vector_alloc(); > + if (map->pathvec == NULL) { > cleanup_nvme_map(map); > return FOREIGN_ERR; > } > > + map->pg.gen.ops = &nvme_pg_ops; > + map->pg.pathvec = map->pathvec; > + map->gpg = nvme_pg_to_gen(&map->pg); > + > + map->pgvec.allocated = 1; > + map->pgvec.slot = (void**)&map->gpg; > + > + if (vector_alloc_slot(ctx->mpvec) == NULL) { > + cleanup_nvme_map(map); > + return FOREIGN_ERR; > + } > vector_set_slot(ctx->mpvec, map); > + _find_slaves(ctx, map); > > return FOREIGN_CLAIMED; > } > @@ -401,9 +682,25 @@ int delete(struct context *ctx, struct udev_device *ud) > return rc; > } > > +void _check(struct context *ctx) > +{ > + struct gen_multipath *gm; > + int i; > + > + vector_foreach_slot(ctx->mpvec, gm, i) { > + struct nvme_map *map = gen_mp_to_nvme(gm); > + > + _find_slaves(ctx, map); > + } > +} > + > void check(struct context *ctx) > { > - condlog(5, "%s called for \"%s\"", __func__, THIS); > + condlog(4, "%s called for \"%s\"", __func__, THIS); > + lock(ctx); > + pthread_cleanup_push(unlock, ctx); > + _check(ctx); > + pthread_cleanup_pop(1); > return; > } > > @@ -427,14 +724,23 @@ void release_multipaths(const struct context *ctx, const struct _vector *mpvec) > */ > const struct _vector * get_paths(const struct context *ctx) > { > + vector paths = NULL; > + const struct gen_multipath *gm; > + int i; > + > condlog(5, "%s called for \"%s\"", __func__, THIS); > - return NULL; > + vector_foreach_slot(ctx->mpvec, gm, i) { > + const struct nvme_map *nm = const_gen_mp_to_nvme(gm); > + paths = vector_convert(paths, nm->pathvec, > + struct gen_path, identity); > + } > + return paths; > } > > void release_paths(const struct context *ctx, const struct _vector *mpvec) > { > condlog(5, "%s called for \"%s\"", __func__, THIS); > - /* NOP */ > + vector_free_const(mpvec); > } > > /* compile-time check whether all methods are present and correctly typed */ > -- > 2.16.1 -- dm-devel mailing list dm-devel@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/dm-devel