This still contains stubs for path handling and checking, but it's functional for printing already. Signed-off-by: Martin Wilck <mwilck@xxxxxxxx> --- Makefile | 1 + libmultipath/foreign/Makefile | 30 +++ libmultipath/foreign/nvme.c | 455 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 486 insertions(+) create mode 100644 libmultipath/foreign/Makefile create mode 100644 libmultipath/foreign/nvme.c diff --git a/Makefile b/Makefile index 11c46eb4dbc9..4b145c593605 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ BUILDDIRS = \ libmultipath \ libmultipath/prioritizers \ libmultipath/checkers \ + libmultipath/foreign \ libmpathpersist \ multipath \ multipathd \ diff --git a/libmultipath/foreign/Makefile b/libmultipath/foreign/Makefile new file mode 100644 index 000000000000..dfba11e86d76 --- /dev/null +++ b/libmultipath/foreign/Makefile @@ -0,0 +1,30 @@ +# +# Copyright (C) 2003 Christophe Varoqui, <christophe.varoqui@xxxxxxxxxxx> +# +include ../../Makefile.inc + +CFLAGS += $(LIB_CFLAGS) -I.. + +# If you add or remove a checker also update multipath/multipath.conf.5 +LIBS= \ + libforeign-nvme.so + +all: $(LIBS) + +libforeign-%.so: %.o + $(CC) $(LDFLAGS) $(SHARED_FLAGS) -o $@ $^ + +install: + $(INSTALL_PROGRAM) -m 755 $(LIBS) $(DESTDIR)$(libdir) + +uninstall: + for file in $(LIBS); do $(RM) $(DESTDIR)$(libdir)/$$file; done + +clean: dep_clean + $(RM) core *.a *.o *.gz *.so + +OBJS := $(LIBS:libforeign-%.so=%.o) +include $(wildcard $(OBJS:.o=.d)) + +dep_clean: + $(RM) $(OBJS:.o=.d) diff --git a/libmultipath/foreign/nvme.c b/libmultipath/foreign/nvme.c new file mode 100644 index 000000000000..32bd5c96c44a --- /dev/null +++ b/libmultipath/foreign/nvme.c @@ -0,0 +1,455 @@ +/* + Copyright (c) 2018 Martin Wilck, SUSE Linux GmbH + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + USA. +*/ + +#include <sys/sysmacros.h> +#include <libudev.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <libudev.h> +#include <pthread.h> +#include "vector.h" +#include "generic.h" +#include "foreign.h" +#include "debug.h" + +const char *THIS; + +struct nvme_map { + struct gen_multipath gen; + struct udev_device *udev; + struct udev_device *subsys; + dev_t devt; +}; + +#define NAME_LEN 64 /* buffer length temp model name */ +#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) + +static void cleanup_nvme_map(struct nvme_map *map) +{ + if (map->udev) + udev_device_unref(map->udev); + if (map->subsys) + udev_device_unref(map->subsys); + free(map); +} + +static const struct _vector* +nvme_mp_get_pgs(const struct gen_multipath *gmp) { + return NULL; +} + +static void +nvme_mp_rel_pgs(const struct gen_multipath *gmp, const struct _vector *v) +{ +} + +static void rstrip(char *str) +{ + int n; + + for (n = strlen(str) - 1; n >= 0 && str[n] == ' '; n--); + str[n+1] = '\0'; +} + +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; + + switch (wildcard) { + case 'd': + return snprintf(buff, len, "%s", + udev_device_get_sysname(nvm->udev)); + case 'n': + return snprintf(buff, len, "%s:NQN:%s", + udev_device_get_sysname(nvm->subsys), + udev_device_get_sysattr_value(nvm->subsys, + "subsysnqn")); + case 'w': + return snprintf(buff, len, "%s", + udev_device_get_sysattr_value(nvm->udev, + "wwid")); + case 'S': + return snprintf(buff, len, "%s", + udev_device_get_sysattr_value(nvm->udev, + "size")); + case 'v': + return snprintf(buff, len, "%s", nvme_vendor); + case 's': + case 'p': + snprintf(fld, sizeof(fld), "%s", + udev_device_get_sysattr_value(nvm->subsys, + "model")); + rstrip(fld); + if (wildcard == 'p') + return snprintf(buff, len, "%s", fld); + return snprintf(buff, len, "%s,%s,%s", nvme_vendor, fld, + udev_device_get_sysattr_value(nvm->subsys, + "firmware_rev")); + case 'e': + return snprintf(buff, len, "%s", + udev_device_get_sysattr_value(nvm->subsys, + "firmware_rev")); + case 'r': + val = udev_device_get_sysattr_value(nvm->udev, "ro"); + if (val[0] == 1) + return snprintf(buff, len, "%s", "ro"); + else + return snprintf(buff, len, "%s", "rw"); + case 'G': + return snprintf(buff, len, "%s", THIS); + default: + return snprintf(buff, len, "N/A"); + break; + } + return 0; +} + +static const struct _vector* +nvme_pg_get_paths(const struct gen_pathgroup *gpg) { + return NULL; +} + +static void +nvme_pg_rel_paths(const struct gen_pathgroup *gpg, const struct _vector *v) +{ +} + +static int snprint_nvme_pg(const struct gen_pathgroup *gmp, + char *buff, int len, char wildcard) +{ + return 0; +} + +static int snprint_nvme_path(const struct gen_path *gmp, + char *buff, int len, char wildcard) +{ + switch (wildcard) { + case 'R': + return snprintf(buff, len, "[foreign: %s]", THIS); + default: + break; + } + return 0; +} + +static const struct gen_multipath_ops nvme_map_ops = { + .get_pathgroups = nvme_mp_get_pgs, + .rel_pathgroups = nvme_mp_rel_pgs, + .style = generic_style, + .snprint = snprint_nvme_map, +}; + +static const struct gen_pathgroup_ops nvme_pg_ops __attribute__((unused)) = { + .get_paths = nvme_pg_get_paths, + .rel_paths = nvme_pg_rel_paths, + .snprint = snprint_nvme_pg, +}; + +static const struct gen_path_ops nvme_path_ops __attribute__((unused)) = { + .snprint = snprint_nvme_path, +}; + +struct context { + pthread_mutex_t mutex; + vector mpvec; +}; + +void lock(struct context *ctx) +{ + pthread_mutex_lock(&ctx->mutex); +} + +void unlock(void *arg) +{ + struct context *ctx = arg; + + pthread_mutex_unlock(&ctx->mutex); +} + +static int _delete_all(struct context *ctx) +{ + struct nvme_map *nm; + int n = VECTOR_SIZE(ctx->mpvec), i; + + if (n == 0) + return FOREIGN_IGNORED; + + vector_foreach_slot_backwards(ctx->mpvec, nm, i) { + vector_del_slot(ctx->mpvec, i); + cleanup_nvme_map(nm); + } + return FOREIGN_OK; +} + +int delete_all(struct context *ctx) +{ + int rc; + + condlog(5, "%s called for \"%s\"", __func__, THIS); + + lock(ctx); + pthread_cleanup_push(unlock, ctx); + rc = _delete_all(ctx); + pthread_cleanup_pop(1); + + return rc; +} + +void cleanup(struct context *ctx) +{ + (void)delete_all(ctx); + + lock(ctx); + /* + * Locking is not strictly necessary here, locking in foreign.c + * makes sure that no other code is called with this ctx any more. + * But this should make static checkers feel better. + */ + pthread_cleanup_push(unlock, ctx); + if (ctx->udev) + udev_unref(ctx->udev); + if (ctx->mpvec) + vector_free(ctx->mpvec); + ctx->mpvec = NULL; + ctx->udev = NULL; + pthread_cleanup_pop(1); + pthread_mutex_destroy(&ctx->mutex); + + free(ctx); +} + +struct context *init(unsigned int api, const char *name) +{ + struct context *ctx; + + if (api > LIBMP_FOREIGN_API) { + condlog(0, "%s: api version mismatch: %08x > %08x\n", + __func__, api, LIBMP_FOREIGN_API); + return NULL; + } + + if ((ctx = calloc(1, sizeof(*ctx)))== NULL) + return NULL; + + pthread_mutex_init(&ctx->mutex, NULL); + + ctx->mpvec = vector_alloc(); + if (ctx->mpvec == NULL) + goto err; + + THIS = name; + return ctx; +err: + cleanup(ctx); + return NULL; +} + +static struct nvme_map *_find_nvme_map_by_devt(const struct context *ctx, + dev_t devt) +{ + struct nvme_map *nm; + int i; + + if (ctx->mpvec == NULL) + return NULL; + + vector_foreach_slot(ctx->mpvec, nm, i) { + if (nm->devt == devt) + return nm; + } + + return NULL; +} + +static int _add_map(struct context *ctx, struct udev_device *ud, + struct udev_device *subsys) +{ + dev_t devt = udev_device_get_devnum(ud); + struct nvme_map *map; + + if (_find_nvme_map_by_devt(ctx, devt) != NULL) + return FOREIGN_OK; + + map = calloc(1, sizeof(*map)); + if (map == NULL) + return FOREIGN_ERR; + + map->devt = devt; + map->udev = udev_device_ref(ud); + /* + * subsys is implicitly referenced by map->udev, + * no need to take a reference here. + */ + map->subsys = subsys; + map->gen.ops = &nvme_map_ops; + + if (vector_alloc_slot(ctx->mpvec) == NULL) { + cleanup_nvme_map(map); + return FOREIGN_ERR; + } + + vector_set_slot(ctx->mpvec, map); + + return FOREIGN_CLAIMED; +} + +int add(struct context *ctx, struct udev_device *ud) +{ + struct udev_device *subsys; + int rc; + + condlog(5, "%s called for \"%s\"", __func__, THIS); + + if (ud == NULL) + return FOREIGN_ERR; + if (strcmp("disk", udev_device_get_devtype(ud))) + return FOREIGN_IGNORED; + + subsys = udev_device_get_parent_with_subsystem_devtype(ud, + "nvme-subsystem", + NULL); + if (subsys == NULL) + return FOREIGN_IGNORED; + + lock(ctx); + pthread_cleanup_push(unlock, ctx); + rc = _add_map(ctx, ud, subsys); + pthread_cleanup_pop(1); + + if (rc == FOREIGN_CLAIMED) + condlog(3, "%s: %s: added map %s", __func__, THIS, + udev_device_get_sysname(ud)); + else if (rc != FOREIGN_OK) + condlog(1, "%s: %s: retcode %d adding %s", + __func__, THIS, rc, udev_device_get_sysname(ud)); + + return rc; +} + +int change(struct context *ctx, struct udev_device *ud) +{ + condlog(5, "%s called for \"%s\"", __func__, THIS); + return FOREIGN_IGNORED; +} + +static int _delete_map(struct context *ctx, struct udev_device *ud) +{ + int k; + struct nvme_map *map; + dev_t devt = udev_device_get_devnum(ud); + + map = _find_nvme_map_by_devt(ctx, devt); + if (map ==NULL) + return FOREIGN_IGNORED; + + k = find_slot(ctx->mpvec, map); + if (k == -1) + return FOREIGN_ERR; + else + vector_del_slot(ctx->mpvec, k); + + cleanup_nvme_map(map); + + return FOREIGN_OK; +} + +int delete(struct context *ctx, struct udev_device *ud) +{ + int rc; + + condlog(5, "%s called for \"%s\"", __func__, THIS); + + if (ud == NULL) + return FOREIGN_ERR; + + lock(ctx); + pthread_cleanup_push(unlock, ctx); + rc = _delete_map(ctx, ud); + pthread_cleanup_pop(1); + + if (rc == FOREIGN_OK) + condlog(3, "%s: %s: map %s deleted", __func__, THIS, + udev_device_get_sysname(ud)); + else if (rc != FOREIGN_IGNORED) + condlog(1, "%s: %s: retcode %d deleting map %s", __func__, + THIS, rc, udev_device_get_sysname(ud)); + + return rc; +} + +void check(struct context *ctx) +{ + condlog(5, "%s called for \"%s\"", __func__, THIS); + return; +} + +/* + * It's safe to pass our internal pointer, this is only used under the lock. + */ +const struct _vector *get_multipaths(const struct context *ctx) +{ + condlog(5, "%s called for \"%s\"", __func__, THIS); + return ctx->mpvec; +} + +void release_multipaths(const struct context *ctx, const struct _vector *mpvec) +{ + condlog(5, "%s called for \"%s\"", __func__, THIS); + /* NOP */ +} + +/* + * It's safe to pass our internal pointer, this is only used under the lock. + */ +const struct _vector * get_paths(const struct context *ctx) +{ + condlog(5, "%s called for \"%s\"", __func__, THIS); + return NULL; +} + +void release_paths(const struct context *ctx, const struct _vector *mpvec) +{ + condlog(5, "%s called for \"%s\"", __func__, THIS); + /* NOP */ +} + +/* compile-time check whether all methods are present and correctly typed */ +#define _METHOD_INIT(x) .x = x +static struct foreign __methods __attribute__((unused)) = { + _METHOD_INIT(init), + _METHOD_INIT(cleanup), + _METHOD_INIT(change), + _METHOD_INIT(delete), + _METHOD_INIT(delete_all), + _METHOD_INIT(check), + _METHOD_INIT(lock), + _METHOD_INIT(unlock), + _METHOD_INIT(get_multipaths), + _METHOD_INIT(release_multipaths), + _METHOD_INIT(get_paths), + _METHOD_INIT(release_paths), +}; -- 2.16.1 -- dm-devel mailing list dm-devel@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/dm-devel