[PATCH 22/35] conn-db: add simple connection registry
From: <hidden>
Date: 2021-01-26 20:36:19
Subsystem:
kernel build + files below scripts/ (unless maintained elsewhere), the rest · Maintainers:
Nathan Chancellor, Nicolas Schier, Linus Torvalds
From: Martin Wilck <redacted>
The monitor works best if it maintains a discovery controller connection
to every transport address that provides a discovery subsystem.
While controllers are easily tracked in sysfs, addresses ("connections"),
i.e. (transport, traddr, trsvid, host_traddr) tuples, are not. Create
a simple registry that tracks the state of "connections" and their
associated discovery controllers.
A detailed description of the API is provided in the header file conn-db.h.
Signed-off-by: Martin Wilck <redacted>
---
Makefile | 2 +-
conn-db.c | 340 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
conn-db.h | 140 ++++++++++++++++++++++
3 files changed, 481 insertions(+), 1 deletion(-)
create mode 100644 conn-db.c
create mode 100644 conn-db.h
diff --git a/Makefile b/Makefile
index 21c9a23..6ac5030 100644
--- a/Makefile
+++ b/Makefile@@ -69,7 +69,7 @@ OBJS := nvme-print.o nvme-ioctl.o nvme-rpmb.o \ nvme-status.o nvme-filters.o nvme-topology.o ifeq ($(HAVE_LIBUDEV),0) - OBJS += monitor.o + OBJS += monitor.o conn-db.o endif UTIL_OBJS := util/argconfig.o util/suffix.o util/json.o util/parser.o
diff --git a/conn-db.c b/conn-db.c
new file mode 100644
index 0000000..99d88da
--- /dev/null
+++ b/conn-db.c@@ -0,0 +1,340 @@ +/* + * Copyright (C) 2021 SUSE LLC + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This file implements a simple registry for NVMe connections, i.e. + * (transport type, host_traddr, traddr, trsvcid) tuples. + */ + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <syslog.h> +#include <time.h> + +#include "common.h" +#include "list.h" +#include "nvme.h" +#include "fabrics.h" +#include "conn-db.h" + +#define LOG_FUNCNAME 1 +#include "log.h" + +struct conn_int { + struct nvme_connection c; + struct list_head lst; +}; + +#define conn2internal(co) container_of(co, struct conn_int, c) + +static LIST_HEAD(connections); + +CLEANUP_FUNC(char) + +static const char * const _status_str[] = { + [CS_NEW] = "new", + [CS_DISC_RUNNING] = "discovery-running", + [CS_ONLINE] = "online", + [CS_FAILED] = "failed", +}; + +const char *conn_status_str(int status) +{ + return arg_str(_status_str, ARRAY_SIZE(_status_str), status); +} + +#define _log_conn(lvl, msg, transport, traddr, trsvcid, host_traddr) \ + do { \ + const char *__trs = trsvcid; \ + \ + log(lvl, "%s <%s>: %s ==> %s(%s)\n", \ + msg, transport, host_traddr, traddr, \ + __trs && *__trs ? __trs : "none"); \ + } while (0) + +#define log_conn(lvl, msg, conn) \ + _log_conn(lvl, msg, (conn)->c.transport, \ + (conn)->c.traddr, (conn)->c.trsvcid, \ + (conn)->c.host_traddr) + +static void conn_free(struct conn_int *ci) +{ + if (!ci) + return; + if (ci->c.traddr) + free(ci->c.traddr); + if (ci->c.trsvcid) + free(ci->c.trsvcid); + if (ci->c.host_traddr) + free(ci->c.host_traddr); + free(ci); +} + +static void conn_free_p(struct conn_int **ci) +{ + if (*ci) { + conn_free(*ci); + *ci = NULL; + } +} + +static int conn_del(struct conn_int *ci) +{ + if (!ci) + return -ENOENT; + if (list_empty(&ci->lst)) + return -EINVAL; + log_conn(LOG_INFO, "forgetting connection", ci); + list_del(&ci->lst); + conn_free(ci); + return 0; +} + +bool conndb_matches(const char *transport, const char *traddr, + const char *trsvcid, const char *host_traddr, + const struct nvme_connection *co) +{ + if (!co) + return false; + if (!transport || strcmp(transport, co->transport)) + return false; + if (!traddr || strncmp(traddr, co->traddr, NVMF_TRADDR_SIZE)) + return false; + if ((!trsvcid && co->trsvcid) || + (trsvcid && *trsvcid && (!co->trsvcid || + strncmp(trsvcid, co->trsvcid, NVMF_TRSVCID_SIZE)))) + return false; + if (!host_traddr || (strncmp(host_traddr, co->host_traddr, + NVMF_TRADDR_SIZE))) + return false; + return true; +} + +static struct conn_int *conn_find(const char *transport, const char *traddr, + const char *trsvcid, const char *host_traddr) +{ + struct conn_int *ci; + + if (!transport || !traddr || !host_traddr) + return NULL; + list_for_each_entry(ci, &connections, lst) { + if (conndb_matches(transport, traddr, trsvcid, host_traddr, &ci->c)) + return ci; + } + return NULL; +} + +static bool is_supported_transport(const char *transport) +{ + + return !strcmp(transport, "fc") || !strcmp(transport, "rdma") || + !strcmp(transport, "tcp") || !strcmp(transport, "loop"); +} + +static int _conn_add(const char *transport, const char *traddr, + const char *trsvcid, const char *host_traddr, + struct conn_int **new_ci) +{ + struct conn_int *ci __attribute__((cleanup(conn_free_p))) = NULL; + + if (!transport || !is_supported_transport(transport) || !traddr) + return -EINVAL; + + if (!(ci = calloc(1, sizeof(*ci))) || + !(ci->c.traddr = strndup(traddr, NVMF_TRADDR_SIZE)) || + !(ci->c.host_traddr = strndup(host_traddr, NVMF_TRADDR_SIZE)) || + (trsvcid && *trsvcid && + !(ci->c.trsvcid = strndup(trsvcid, NVMF_TRSVCID_SIZE)))) + return -ENOMEM; + memccpy(ci->c.transport, transport, '\0', sizeof(ci->c.transport)); + ci->c.status = CS_NEW; + ci->c.discovery_instance = -1; + list_add(&ci->lst, &connections); + *new_ci = ci; + ci = NULL; + return 0; +} + +static int conn_add(const char *transport, const char *traddr, + const char *trsvcid, const char *host_traddr, + struct conn_int **new_ci) +{ + struct conn_int *ci = conn_find(transport, traddr, trsvcid, host_traddr); + int rc; + + if (ci) { + *new_ci = ci; + return -EEXIST; + } + rc = _conn_add(transport, traddr, trsvcid, host_traddr, new_ci); + if (!rc) + log_conn(LOG_DEBUG, "added connection", *new_ci); + else + _log_conn(LOG_ERR, "failed to add", transport, traddr, + trsvcid, host_traddr); + return rc; +} + +int conndb_add(const char *transport, const char *traddr, + const char *trsvcid, const char *host_traddr, + struct nvme_connection **new_conn) +{ + struct conn_int *ci = NULL; + int rc = conn_add(transport, traddr, trsvcid, host_traddr, &ci); + + if (rc != 0 && rc != -EEXIST) + return rc; + if (new_conn) + *new_conn = &ci->c; + return rc; +} + +struct nvme_connection *conndb_find(const char *transport, const char *traddr, + const char *trsvcid, const char *host_traddr) +{ + struct conn_int *ci; + + ci = conn_find(transport, traddr, trsvcid, host_traddr); + if (ci) + return &ci->c; + else + return NULL; +} + +struct nvme_connection *conndb_find_by_pid(pid_t pid) +{ + struct conn_int *ci; + + list_for_each_entry(ci, &connections, lst) { + if (ci->c.status == CS_DISC_RUNNING && + ci->c.discovery_task == pid) + return &ci->c; + } + return NULL; +} + +struct nvme_connection *conndb_find_by_ctrl(const char *devname) +{ + struct conn_int *ci; + int instance; + + instance = ctrl_instance(devname); + if (!instance) + return NULL; + + list_for_each_entry(ci, &connections, lst) { + if (ci->c.discovery_instance == instance) + return &ci->c; + } + return NULL; +} + +int conndb_delete(struct nvme_connection *co) +{ + if (!co) + return -ENOENT; + return conn_del(conn2internal(co)); +} + +void conndb_free(void) +{ + struct conn_int *ci, *next; + + list_for_each_entry_safe(ci, next, &connections, lst) + conn_del(ci); +} + +int conndb_init_from_sysfs(void) +{ + struct dirent **devices; + int i, n, ret = 0; + char syspath[PATH_MAX]; + + n = scandir(SYS_NVME, &devices, scan_ctrls_filter, alphasort); + if (n <= 0) + return n; + + for (i = 0; i < n; i++) { + int len, rc; + struct conn_int *ci; + CLEANUP(char, transport) = NULL; + CLEANUP(char, address) = NULL; + CLEANUP(char, traddr) = NULL; + CLEANUP(char, trsvcid) = NULL; + CLEANUP(char, host_traddr) = NULL; + CLEANUP(char, subsysnqn) = NULL; + + len = snprintf(syspath, sizeof(syspath), SYS_NVME "/%s", + devices[i]->d_name); + if (len < 0 || len >= sizeof(syspath)) + continue; + + transport = nvme_get_ctrl_attr(syspath, "transport"); + address = nvme_get_ctrl_attr(syspath, "address"); + if (!transport || !address) + continue; + traddr = parse_conn_arg(address, ' ', "traddr"); + trsvcid = parse_conn_arg(address, ' ', "trsvcid"); + host_traddr = parse_conn_arg(address, ' ', "host_traddr"); + + rc = conn_add(transport, traddr, trsvcid, host_traddr, &ci); + if (rc != 0 && rc != -EEXIST) + continue; + + if (rc == 0) + ret ++; + subsysnqn = nvme_get_ctrl_attr(syspath, "subsysnqn"); + if (subsysnqn && !strcmp(subsysnqn, NVME_DISC_SUBSYS_NAME)) { + int instance = ctrl_instance(devices[i]->d_name); + + if (instance >= 0) { + ci->c.discovery_instance = instance; + log(LOG_DEBUG, "found discovery controller %s\n", + devices[i]->d_name); + } + } + free(devices[i]); + } + free(devices); + return ret; +} + +int conndb_for_each(int (*callback)(struct nvme_connection *co, void *arg), + void *arg) +{ + struct conn_int *ci, *next; + int ret = 0; + + list_for_each_entry_safe(ci, next, &connections, lst) { + int rc = callback(&ci->c, arg); + + if (rc & ~(CD_CB_ERR|CD_CB_DEL|CD_CB_BREAK)) { + log(LOG_ERR, + "invalid return value 0x%x from callback\n", rc); + ret = -EINVAL; + continue; + } + if (rc & CD_CB_ERR) { + log(LOG_WARNING, "callback returned error\n"); + if (!ret) + ret = errno ? -errno : -EIO; + } + if (rc & CD_CB_DEL) + conn_del(ci); + if (rc & CD_CB_BREAK) + break; + } + return ret; +}
diff --git a/conn-db.h b/conn-db.h
new file mode 100644
index 0000000..c599c15
--- /dev/null
+++ b/conn-db.h@@ -0,0 +1,140 @@ +#ifndef _CONN_DB_H +#define _CONN_DB_H + +struct nvme_connection { + char transport[5]; + char *traddr; + char *trsvcid; + char *host_traddr; + + int status; + int discovery_pending:1; + int did_discovery:1; + int successful_discovery:1; + union { + pid_t discovery_task; + int discovery_result; + }; + int discovery_instance; +}; + +/* connection status */ +enum { + CS_NEW = 0, + CS_DISC_RUNNING, + CS_ONLINE, + CS_FAILED, + __CS_LAST, +}; + +/** + * conn_status_str() - return string representation of connection status + */ +const char *conn_status_str(int status); + +/** + * conndb_add() - add a connection with given parameters + * + * @new_conn: if non-NULL and the function succeeds, will receive a pointer + * to the either existing or newly created connection object. + * + * Looks up the given connection parameters in the db and adds a new connection + * unless found. All input parameters except trsvcid must be non-NULL. + * + * Return: 0 if controller was added, -EEXIST if controller existed in the db + * (this is considered success), or other negative error code in + * the error case. + * + */ +int conndb_add(const char *transport, const char *traddr, + const char *trsvcid, const char *host_traddr, + struct nvme_connection **new_conn); + +/** + * conndb_find() - lookup a connection with given parameters + * + * Return: NULL if not found, valid connection object otherwise. + */ +struct nvme_connection *conndb_find(const char *transport, const char *traddr, + const char *trsvcid, const char *host_traddr); + + +/** + * conndb_find_by_pid() - lookup connection by discovery task pid + * + * Return: valid connetion object if successful, NULL otherwise. + */ +struct nvme_connection *conndb_find_by_pid(pid_t pid); + + +/** + * conndb_find_by_pid() - lookup connection from controller instance + * + * Return: valid connetion object if a connection was found that has + * the given device as discovery controller. NULL otherwise. + */ +struct nvme_connection *conndb_find_by_ctrl(const char *devname); + +enum { + CD_CB_OK = 0, + CD_CB_ERR = (1 << 0), + CD_CB_DEL = (1 << 1), + CD_CB_BREAK = (1 << 2), +}; + +/** + * conndb_for_each() - run a callback for each connection + * + * @callback: function to be called + * @arg: user argument passed to callback + * + * The callback must return a bitmask created from the CD_CB_* enum + * values above. CD_CB_ERR signals an error condition in the callback. + * CD_CB_DEL causes the connection to be deleted after the callback + * returns. CD_CB_BREAK stops the iteration. Returning a value that + * is not an OR-ed from these values is an error. + * + * Return: 0 if all callbacks completed successfully. + * A negative error code if some callback failed. + */ +int conndb_for_each(int (*callback)(struct nvme_connection *co, void *arg), + void *arg); + +/** + * conndb_matches - check if connection matches given parameters + * + * The arguments @transport and @traddr must be non-null and non-empty. + * @trscvid and @host_traddr may be NULL, in which case they match + * connections that don't have these attributes set, either. + * + * Return: true iff the given connection matches the given attributes. + */ +bool conndb_matches(const char *transport, const char *traddr, + const char *trsvcid, const char *host_traddr, + const struct nvme_connection *co); + +/** + * conndb_delete() - remove a given nvme connection object + * + * Removes the object from the data base and frees it. + * + * Return: 0 if successful, negative error code otherwise + */ +int conndb_delete(struct nvme_connection *co); + +/** + * conndb-free() - free internal data structures + */ +void conndb_free(void); + +/** + * conndb_init_from_sysfs() - check existing NVMe connections + * + * Populates the connection db from existing contoller devices in sysfs. + * + * Return: (positive or zero) number of found connections on success. + * Negative error code on failure. + */ +int conndb_init_from_sysfs(void); + +#endif
--
2.29.2
_______________________________________________
Linux-nvme mailing list
Linux-nvme@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-nvme