/
githubmirror
/
nvme-cli
Обзор
Документация
Войти
/
githubmirror
/
nvme-cli
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/config-convert.c
631 строка
17 KB
Martin Belanger
nvme: redesign --persistent, remove --epcsd/--no-epcsd
10 авг 2026, 19:27
10 авг 2026, 19:27
745ada2
Код
Авторство
О чём код?
// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2026, Dell Technologies Inc. or its subsidiaries. * * Authors: Martin Belanger <Martin.Belanger@dell.com> */ #include <errno.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <libnvme.h> #include "common.h" #include "config-convert.h" #include "fabrics.h" #include "logging.h" #include "nvme-print.h" #include "argconfig.h" #include "cleanup.h" #ifdef CONFIG_JSONC #include "nvme-json.h" struct legacy_key { const char *json_key; const char *ini_key; }; /* Map config.json keys from underscore to hyphen notation. */ static const struct legacy_key int_keys[] = { { "nr_io_queues", "nr-io-queues" }, { "nr_write_queues", "nr-write-queues" }, { "nr_poll_queues", "nr-poll-queues" }, { "queue_size", "queue-size" }, { "keep_alive_tmo", "keep-alive-tmo" }, { "reconnect_delay", "reconnect-delay" }, { "ctrl_loss_tmo", "ctrl-loss-tmo" }, { "fast_io_fail_tmo", "fast-io-fail-tmo" }, { "tos", "tos" }, }; static const struct legacy_key bool_keys[] = { { "duplicate_connect", "duplicate-connect" }, { "disable_sqflow", "disable-sqflow" }, { "hdr_digest", "hdr-digest" }, { "data_digest", "data-digest" }, { "tls", "tls" }, { "concat", "concat" }, }; static const struct legacy_key string_keys[] = { { "tls_key", "tls-key" }, { "tls_key_identity", "tls-key-identity" }, { "keyring", "keyring" }, { "dhchap_key", "dhchap-secret" }, { "dhchap_ctrl_key", "dhchap-ctrl-secret" }, }; static const char *map_key(const struct legacy_key *table, size_t n, const char *json_key) { size_t i; for (i = 0; i < n; i++) if (!strcmp(table[i].json_key, json_key)) return table[i].ini_key; return NULL; } #define MAP_KEY(table, json_key) map_key(table, ARRAY_SIZE(table), json_key) static const char *json_get_string(struct json_object *obj, const char *key) { struct json_object *val = json_object_object_get(obj, key); return val ? json_object_get_string(val) : NULL; } static bool json_get_bool(struct json_object *obj, const char *key) { struct json_object *val = json_object_object_get(obj, key); return val && json_object_get_boolean(val); } /* Copy supported tunable and security parameters into @params. */ static void apply_port_params(struct libnvmf_params *params, struct json_object *port_obj) { json_object_object_foreach(port_obj, key_str, val_obj) { const char *ini_key; char buf[32]; ini_key = MAP_KEY(int_keys, key_str); if (ini_key) { snprintf(buf, sizeof(buf), "%d", json_object_get_int(val_obj)); libnvmf_params_set(params, ini_key, buf); continue; } ini_key = MAP_KEY(bool_keys, key_str); if (ini_key) { libnvmf_params_set(params, ini_key, json_object_get_boolean(val_obj) ? "true" : "false"); continue; } ini_key = MAP_KEY(string_keys, key_str); if (ini_key) libnvmf_params_set(params, ini_key, json_object_get_string(val_obj)); } } /* * The DH-HMAC-CHAP secret is defined per (hostnqn, subsysnqn), not per path * (NVMe Base Specification 2.3, section 8.3.5.5.7). If a port does not * specify a secret, inherit the host-level default. If both are present but * differ, keep the port-specific value and log the mismatch. */ static void apply_dhchap_default(struct libnvmf_params *params, struct json_object *port_obj, const char *host_default) { const char *port_value = json_get_string(port_obj, "dhchap_key"); if (!host_default) return; if (!port_value) { libnvmf_params_set(params, "dhchap-secret", host_default); } else if (strcmp(port_value, host_default)) { nvme_show_verbose_info( "config convert: dhchap_key differs between host default and one connection; keeping the connection's own value"); } } /* * The legacy config.json format predates EPCSD; its boolean "persistent" * meant unconditional persistence. Map true to "force", not the new * best-effort "auto" default, so migrating an existing config.json doesn't * silently change behavior for a connection that was persistent before. */ static void apply_dc_persistent(struct libnvmf_params *params, struct json_object *port_obj) { struct json_object *val = json_object_object_get(port_obj, "persistent"); if (val) libnvmf_params_set(params, "persistent", json_object_get_boolean(val) ? "force" : "no"); } static int convert_port(struct libnvmf_config_emitter *emitter, const char *hostnqn, const char *hostid, const char *hostsymname, const char *host_dhchap_key, const char *subsysnqn, struct json_object *port_obj) { struct libnvmf_params *params; bool is_dc = json_get_bool(port_obj, "discovery"); int ret; params = libnvmf_params_new(); if (!params) return -ENOMEM; apply_port_params(params, port_obj); apply_dhchap_default(params, port_obj, host_dhchap_key); if (is_dc) apply_dc_persistent(params, port_obj); ret = libnvmf_config_emit_add(emitter, is_dc, json_get_string(port_obj, "transport"), json_get_string(port_obj, "traddr"), json_get_string(port_obj, "trsvcid"), subsysnqn, json_get_string(port_obj, "host_traddr"), json_get_string(port_obj, "host_iface"), hostnqn, hostid, params, hostsymname); libnvmf_params_free(params); /* * A rejected entry (for example, invalid addressing or a conflicting * persona) affects only that entry. Log the error and continue * converting, matching json_parse_port()'s own tolerance. Stop only * if memory allocation fails. */ if (ret == -ENOMEM) return ret; if (ret) nvme_show_error( "config convert: skipping an entry that could not be added: %s", libnvme_strerror(-ret)); return 0; } static int convert_subsys(struct libnvmf_config_emitter *emitter, const char *hostnqn, const char *hostid, const char *hostsymname, const char *host_dhchap_key, struct json_object *subsys_obj) { struct json_object *port_array; const char *nqn = json_get_string(subsys_obj, "nqn"); int p, ret; /* The well-known discovery NQN is the emitter's default value. */ if (nqn && (!*nqn || !strcmp(nqn, NVME_DISC_SUBSYS_NAME))) nqn = NULL; port_array = json_object_object_get(subsys_obj, "ports"); if (!port_array) return 0; for (p = 0; p < json_object_array_length(port_array); p++) { struct json_object *port_obj = json_object_array_get_idx(port_array, p); if (!port_obj) continue; ret = convert_port(emitter, hostnqn, hostid, hostsymname, host_dhchap_key, nqn, port_obj); if (ret) return ret; } return 0; } static int convert_host(struct libnvmf_config_emitter *emitter, struct json_object *host_obj) { struct json_object *subsys_array; const char *hostnqn = json_get_string(host_obj, "hostnqn"); const char *hostid = json_get_string(host_obj, "hostid"); const char *hostsymname = json_get_string(host_obj, "hostsymname"); const char *host_dhchap_key = json_get_string(host_obj, "dhchap_key"); int s, ret; subsys_array = json_object_object_get(host_obj, "subsystems"); if (!subsys_array) return 0; for (s = 0; s < json_object_array_length(subsys_array); s++) { struct json_object *subsys_obj = json_object_array_get_idx(subsys_array, s); if (!subsys_obj) continue; ret = convert_subsys(emitter, hostnqn, hostid, hostsymname, host_dhchap_key, subsys_obj); if (ret) return ret; } return 0; } int nvme_config_convert_json(struct libnvmf_config_emitter *emitter, const char *json_file) { struct json_object *json_root, *host_array, *host_obj; int h, ret; json_root = json_object_from_file(json_file); if (!json_root) { nvme_show_error("failed to parse %s: %s", json_file, json_util_get_last_err()); return -EPROTO; } if (json_object_is_type(json_root, json_type_object)) { /* Current format: { "hosts": [ ... ] } */ host_array = json_object_object_get(json_root, "hosts"); if (!host_array || !json_object_is_type(host_array, json_type_array)) { nvme_show_error("%s: expected a 'hosts' array", json_file); json_object_put(json_root); return -EPROTO; } } else if (json_object_is_type(json_root, json_type_array)) { /* Legacy pre-3.0 format: a bare top-level array of hosts. */ host_array = json_root; } else { nvme_show_error("%s: expected a JSON object or array", json_file); json_object_put(json_root); return -EPROTO; } for (h = 0; h < json_object_array_length(host_array); h++) { host_obj = json_object_array_get_idx(host_array, h); if (!host_obj) continue; ret = convert_host(emitter, host_obj); if (ret) { json_object_put(json_root); return ret; } } json_object_put(json_root); return 0; } #else /* CONFIG_JSONC */ int nvme_config_convert_json(struct libnvmf_config_emitter *emitter, const char *json_file) { nvme_show_error( "built without json-c; config.json conversion unavailable"); return -ENOTSUP; } #endif /* CONFIG_JSONC */ int nvme_config_convert_discovery_args(struct libnvmf_config_emitter *emitter, const struct nvmf_args *fa, const char *persistent) { struct libnvmf_params *params; int ret; params = libnvmf_params_new(); if (!params) return -ENOMEM; nvmf_args_to_params(params, fa); if (persistent && libnvmf_params_set(params, "persistent", persistent)) { nvme_show_error( "discovery.conf: skipping a line with an invalid persistent value '%s'", persistent); libnvmf_params_free(params); return 0; } ret = libnvmf_config_emit_add(emitter, true, fa->transport, fa->traddr, fa->trsvcid, fa->subsysnqn, fa->host_traddr, fa->host_iface, fa->hostnqn, fa->hostid, params, NULL); libnvmf_params_free(params); /* * A rejected entry affects only that entry. Log the error and * continue converting. Stop only if memory allocation fails. */ if (ret == -ENOMEM) return ret; if (ret) nvme_show_error( "discovery.conf: skipping a line that could not be added: %s", libnvme_strerror(-ret)); return 0; } int nvme_config_convert_discovery(struct libnvmf_config_emitter *emitter, const char *disc_file) { __cleanup_file FILE *f = NULL; static char line[4096]; int ret; f = fopen(disc_file, "r"); if (!f) { nvme_show_error("failed to open %s: %s", disc_file, strerror(errno)); return -errno; } while (fgets(line, sizeof(line), f)) { ret = nvmf_convert_discovery_line(emitter, line); if (ret) return ret; } return 0; } /* * Best effort. The configuration has already been installed. @path is * left untouched so a rollback to a pre-INI version still finds it; the * symlink marks it as already converted. */ static void mark_converted(const char *path) { __cleanup_free char *dst = NULL; if (asprintf(&dst, "%s.converted", path) < 0) return; if (symlink(path, dst)) nvme_show_error( "converted %s but failed to mark it as converted (%s): %s", path, dst, strerror(errno)); } /* * True if the @path.converted symlink exists and resolves. A dangling * symlink (its target since removed) makes this return false, but that * alone does not mean @path needs converting -- callers must also check * whether @path itself exists. */ static bool already_converted(const char *path) { __cleanup_free char *converted = NULL; if (asprintf(&converted, "%s.converted", path) < 0) return false; return !access(converted, F_OK); } static int install_converted(struct libnvmf_config_emitter *emitter, const char *output_file, const char *json_path, const char *disc_path, bool converted_json, bool converted_disc, bool force) { int ret; ret = libnvmf_config_emit_install(emitter, output_file, force); if (ret == -EEXIST) { nvme_show_error("%s already exists; refusing to overwrite", output_file); return ret; } if (ret) { nvme_show_error("failed to write %s: %s", output_file, libnvme_strerror(-ret)); return ret; } if (converted_json) mark_converted(json_path); if (converted_disc) mark_converted(disc_path); return 0; } int nvme_config_convert_auto(struct libnvme_global_ctx *ctx, const char *config_file, char **ini_path) { struct libnvmf_config_emitter *emitter; const char *json_path = config_file; const char *ext; bool is_default; bool json_exists, json_done; bool disc_exists, disc_done; bool have_json, have_disc; bool converted_json = false, converted_disc = false; int ret; *ini_path = NULL; is_default = !strcmp(config_file, PATH_NVMF_INI) || !strcmp(config_file, PATH_NVMF_CONFIG); if (is_default) { json_path = PATH_NVMF_CONFIG; *ini_path = strdup(PATH_NVMF_INI); } else { ext = strrchr(config_file, '.'); if (!ext || strcmp(ext, ".json")) { *ini_path = strdup(config_file); return *ini_path ? 0 : -ENOMEM; } if (asprintf(ini_path, "%.*s.conf", (int)(ext - config_file), config_file) < 0) return -ENOMEM; } if (!*ini_path) return -ENOMEM; if (!access(*ini_path, F_OK)) return 0; json_exists = !access(json_path, F_OK); json_done = already_converted(json_path); have_json = json_exists && !json_done; disc_exists = is_default && !access(PATH_NVMF_DISC, F_OK); disc_done = is_default && already_converted(PATH_NVMF_DISC); have_disc = disc_exists && !disc_done; if (!have_json && !have_disc) { /* Default path: nothing to convert is fine, proceed empty. * Custom path: never existed and never converted is a * real error, not silent-empty. */ if (!is_default && !json_exists && !json_done) { nvme_show_error("%s: no such file", json_path); return -ENOENT; } return 0; } emitter = libnvmf_config_emit_new(ctx); if (!emitter) return -ENOMEM; if (have_json) { ret = nvme_config_convert_json(emitter, json_path); if (ret) goto out; converted_json = true; } if (have_disc) { ret = nvme_config_convert_discovery(emitter, PATH_NVMF_DISC); if (ret) goto out; converted_disc = true; } ret = install_converted(emitter, *ini_path, json_path, PATH_NVMF_DISC, converted_json, converted_disc, false); if (ret) goto out; nvme_show_error( "no %s found; converted legacy %s%s%s to it -- the original is preserved for rollback, marked converted by a *.converted symlink; use %s from now on", *ini_path, converted_json ? json_path : "", (converted_json && converted_disc) ? " and " : "", converted_disc ? PATH_NVMF_DISC : "", *ini_path); out: libnvmf_config_emit_free(emitter); return ret; } int nvme_config_convert(const char *desc, int argc, char **argv) { char *config_file = NULL; char *output_file = NULL; const char *target; const char *json_path; bool force = false; bool converted_json = false, converted_disc = false; bool json_already_done = false, disc_already_done = false; __cleanup_nvme_global_ctx struct libnvme_global_ctx *ctx = NULL; struct libnvmf_config_emitter *emitter = NULL; int ret; OPT_ARGS(opts) = { OPT_STRING("config", 'J', "FILE", &config_file, "convert this JSON file (default: config.json)"), OPT_STRING("output", 'o', "FILE", &output_file, "write result here (default: nvme-fabrics.conf)"), OPT_FLAG("force", 0, &force, "overwrite an existing target"), OPT_END() }; ret = parse_args(argc, argv, desc, opts); if (ret) return ret; ret = nvme_create_global_ctx(&ctx); if (ret) return ret; emitter = libnvmf_config_emit_new(ctx); if (!emitter) return -ENOMEM; json_path = config_file ? config_file : PATH_NVMF_CONFIG; if (already_converted(json_path)) { json_already_done = true; } else if (!access(json_path, F_OK)) { ret = nvme_config_convert_json(emitter, json_path); if (ret) goto out; converted_json = true; } else if (config_file) { /* * An explicit --config to a file that neither exists nor was * ever converted: let the JSON parser produce its own * "failed to parse" error instead of silently no-op'ing, * since this was an explicit ask. */ ret = nvme_config_convert_json(emitter, json_path); if (ret) goto out; converted_json = true; } if (already_converted(PATH_NVMF_DISC)) { disc_already_done = true; } else if (!access(PATH_NVMF_DISC, F_OK)) { ret = nvme_config_convert_discovery(emitter, PATH_NVMF_DISC); if (ret) goto out; converted_disc = true; } if (!converted_json && !converted_disc) { if (json_already_done || disc_already_done) { nvme_show_result("already converted; nothing to do"); ret = 0; goto out; } nvme_show_error("nothing to convert: neither %s nor %s exists", json_path, PATH_NVMF_DISC); ret = -ENOENT; goto out; } target = output_file ? output_file : PATH_NVMF_INI; ret = install_converted(emitter, target, json_path, PATH_NVMF_DISC, converted_json, converted_disc, force); if (!ret) { nvme_show_result( "converted legacy %s%s%s to %s -- the original is preserved for rollback, marked converted by a *.converted symlink", converted_json ? json_path : "", (converted_json && converted_disc) ? " and " : "", converted_disc ? PATH_NVMF_DISC : "", target); } out: libnvmf_config_emit_free(emitter); return ret; }