| File: | .build-ci/../src/config-convert.c |
| Warning: | line 385, column 9 Opened stream never closed. Potential resource leak |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | // SPDX-License-Identifier: GPL-2.0-or-later | |||
| 2 | /* | |||
| 3 | * Copyright (c) 2026, Dell Technologies Inc. or its subsidiaries. | |||
| 4 | * | |||
| 5 | * Authors: Martin Belanger <Martin.Belanger@dell.com> | |||
| 6 | */ | |||
| 7 | ||||
| 8 | #include <errno(*__errno_location ()).h> | |||
| 9 | #include <stdio.h> | |||
| 10 | #include <string.h> | |||
| 11 | #include <sys/stat.h> | |||
| 12 | #include <unistd.h> | |||
| 13 | ||||
| 14 | #include <libnvme.h> | |||
| 15 | ||||
| 16 | #include <ccan/array_size/array_size.h> | |||
| 17 | ||||
| 18 | #include "argconfig.h" | |||
| 19 | #include "cleanup.h" | |||
| 20 | #include "config-convert.h" | |||
| 21 | #include "fabrics.h" | |||
| 22 | #include "global-ctx.h" | |||
| 23 | #include "nvme-print.h" | |||
| 24 | ||||
| 25 | #ifdef CONFIG_JSONC | |||
| 26 | #include "nvme-json.h" | |||
| 27 | ||||
| 28 | struct legacy_key { | |||
| 29 | const char *json_key; | |||
| 30 | const char *ini_key; | |||
| 31 | }; | |||
| 32 | ||||
| 33 | /* Map config.json keys from underscore to hyphen notation. */ | |||
| 34 | static const struct legacy_key int_keys[] = { | |||
| 35 | { "nr_io_queues", "nr-io-queues" }, | |||
| 36 | { "nr_write_queues", "nr-write-queues" }, | |||
| 37 | { "nr_poll_queues", "nr-poll-queues" }, | |||
| 38 | { "queue_size", "queue-size" }, | |||
| 39 | { "keep_alive_tmo", "keep-alive-tmo" }, | |||
| 40 | { "reconnect_delay", "reconnect-delay" }, | |||
| 41 | { "ctrl_loss_tmo", "ctrl-loss-tmo" }, | |||
| 42 | { "fast_io_fail_tmo", "fast-io-fail-tmo" }, | |||
| 43 | { "tos", "tos" }, | |||
| 44 | }; | |||
| 45 | ||||
| 46 | static const struct legacy_key bool_keys[] = { | |||
| 47 | { "duplicate_connect", "duplicate-connect" }, | |||
| 48 | { "disable_sqflow", "disable-sqflow" }, | |||
| 49 | { "hdr_digest", "hdr-digest" }, | |||
| 50 | { "data_digest", "data-digest" }, | |||
| 51 | { "tls", "tls" }, | |||
| 52 | { "concat", "concat" }, | |||
| 53 | }; | |||
| 54 | ||||
| 55 | static const struct legacy_key string_keys[] = { | |||
| 56 | { "tls_key", "tls-key" }, | |||
| 57 | { "tls_key_identity", "tls-key-identity" }, | |||
| 58 | { "keyring", "keyring" }, | |||
| 59 | { "dhchap_key", "kxchap-secret" }, | |||
| 60 | { "dhchap_ctrl_key", "kxchap-ctrl-secret" }, | |||
| 61 | }; | |||
| 62 | ||||
| 63 | static const char *map_key(const struct legacy_key *table, size_t n, | |||
| 64 | const char *json_key) | |||
| 65 | { | |||
| 66 | size_t i; | |||
| 67 | ||||
| 68 | for (i = 0; i < n; i++) | |||
| 69 | if (!strcmp(table[i].json_key, json_key)) | |||
| 70 | return table[i].ini_key; | |||
| 71 | ||||
| 72 | return NULL((void*)0); | |||
| 73 | } | |||
| 74 | ||||
| 75 | #define MAP_KEY(table, json_key)map_key(table, (sizeof(table) / sizeof((table)[0]) + 0), json_key ) map_key(table, ARRAY_SIZE(table)(sizeof(table) / sizeof((table)[0]) + 0), json_key) | |||
| 76 | ||||
| 77 | static const char *json_get_string(struct json_object *obj, const char *key) | |||
| 78 | { | |||
| 79 | struct json_object *val = json_object_object_get(obj, key); | |||
| 80 | ||||
| 81 | return val ? json_object_get_string(val) : NULL((void*)0); | |||
| 82 | } | |||
| 83 | ||||
| 84 | static bool_Bool json_get_bool(struct json_object *obj, const char *key) | |||
| 85 | { | |||
| 86 | struct json_object *val = json_object_object_get(obj, key); | |||
| 87 | ||||
| 88 | return val && json_object_get_boolean(val); | |||
| 89 | } | |||
| 90 | ||||
| 91 | /* Copy supported tunable and security parameters into @params. */ | |||
| 92 | static void apply_port_params(struct libnvmf_params *params, | |||
| 93 | struct json_object *port_obj) | |||
| 94 | { | |||
| 95 | json_object_object_foreach(port_obj, key_str, val_obj)char *key_str = ((void*)0); struct json_object *val_obj __attribute__ ((__unused__)) = ((void*)0); for (struct lh_entry *entrykey_str = lh_table_head(json_object_get_object(port_obj)), *entry_nextkey_str = ((void*)0); ({ if (entrykey_str) { key_str = (char *)lh_entry_k (entrykey_str); val_obj = (struct json_object *)lh_entry_v(entrykey_str ); entry_nextkey_str = lh_entry_next(entrykey_str); }; entrykey_str ; }); entrykey_str = entry_nextkey_str) { | |||
| 96 | const char *ini_key; | |||
| 97 | char buf[32]; | |||
| 98 | ||||
| 99 | ini_key = MAP_KEY(int_keys, key_str)map_key(int_keys, (sizeof(int_keys) / sizeof((int_keys)[0]) + 0), key_str); | |||
| 100 | if (ini_key) { | |||
| 101 | snprintf(buf, sizeof(buf), "%d", | |||
| 102 | json_object_get_int(val_obj)); | |||
| 103 | libnvmf_params_set(params, ini_key, buf); | |||
| 104 | continue; | |||
| 105 | } | |||
| 106 | ini_key = MAP_KEY(bool_keys, key_str)map_key(bool_keys, (sizeof(bool_keys) / sizeof((bool_keys)[0] ) + 0), key_str); | |||
| 107 | if (ini_key) { | |||
| 108 | libnvmf_params_set(params, ini_key, | |||
| 109 | json_object_get_boolean(val_obj) ? | |||
| 110 | "true" : "false"); | |||
| 111 | continue; | |||
| 112 | } | |||
| 113 | ini_key = MAP_KEY(string_keys, key_str)map_key(string_keys, (sizeof(string_keys) / sizeof((string_keys )[0]) + 0), key_str); | |||
| 114 | if (ini_key) | |||
| 115 | libnvmf_params_set(params, ini_key, | |||
| 116 | json_object_get_string(val_obj)); | |||
| 117 | } | |||
| 118 | } | |||
| 119 | ||||
| 120 | /* | |||
| 121 | * The KX-HMAC-CHAP secret is defined per (hostnqn, subsysnqn), not per path | |||
| 122 | * (NVMe Base Specification 2.4, section 8.3.4.5.7). If a port does not | |||
| 123 | * specify a secret, inherit the host-level default. If both are present but | |||
| 124 | * differ, keep the port-specific value and log the mismatch. | |||
| 125 | */ | |||
| 126 | static void apply_dhchap_default(struct libnvmf_params *params, | |||
| 127 | struct json_object *port_obj, const char *host_default) | |||
| 128 | { | |||
| 129 | const char *port_value = json_get_string(port_obj, "dhchap_key"); | |||
| 130 | ||||
| 131 | if (!host_default) | |||
| 132 | return; | |||
| 133 | ||||
| 134 | if (!port_value) { | |||
| 135 | libnvmf_params_set(params, "kxchap-secret", host_default); | |||
| 136 | } else if (strcmp(port_value, host_default)) { | |||
| 137 | nvme_show_verbose_info(nvme_show_verbose_message("config convert: dhchap_key differs between host default and one connection; keeping the connection's own value" ) | |||
| 138 | "config convert: dhchap_key differs between host default and one connection; keeping the connection's own value")nvme_show_verbose_message("config convert: dhchap_key differs between host default and one connection; keeping the connection's own value" ); | |||
| 139 | } | |||
| 140 | } | |||
| 141 | ||||
| 142 | /* | |||
| 143 | * The legacy config.json format predates EPCSD; its boolean "persistent" | |||
| 144 | * meant unconditional persistence. Map true to "force", not the new | |||
| 145 | * best-effort "auto" default, so migrating an existing config.json doesn't | |||
| 146 | * silently change behavior for a connection that was persistent before. | |||
| 147 | */ | |||
| 148 | static void apply_dc_persistent(struct libnvmf_params *params, | |||
| 149 | struct json_object *port_obj) | |||
| 150 | { | |||
| 151 | struct json_object *val = json_object_object_get(port_obj, "persistent"); | |||
| 152 | ||||
| 153 | if (val) | |||
| 154 | libnvmf_params_set(params, "persistent", | |||
| 155 | json_object_get_boolean(val) ? "force" : "no"); | |||
| 156 | } | |||
| 157 | ||||
| 158 | static int convert_port(struct libnvmf_config_emitter *emitter, | |||
| 159 | const char *hostnqn, const char *hostid, | |||
| 160 | const char *hostsymname, const char *host_dhchap_key, | |||
| 161 | const char *subsysnqn, struct json_object *port_obj) | |||
| 162 | { | |||
| 163 | struct libnvmf_params *params; | |||
| 164 | bool_Bool is_dc = json_get_bool(port_obj, "discovery"); | |||
| 165 | int ret; | |||
| 166 | ||||
| 167 | params = libnvmf_params_new(); | |||
| 168 | if (!params) | |||
| 169 | return -ENOMEM12; | |||
| 170 | ||||
| 171 | apply_port_params(params, port_obj); | |||
| 172 | apply_dhchap_default(params, port_obj, host_dhchap_key); | |||
| 173 | if (is_dc) | |||
| 174 | apply_dc_persistent(params, port_obj); | |||
| 175 | ||||
| 176 | ret = libnvmf_config_emit_add(emitter, is_dc, | |||
| 177 | json_get_string(port_obj, "transport"), | |||
| 178 | json_get_string(port_obj, "traddr"), | |||
| 179 | json_get_string(port_obj, "trsvcid"), | |||
| 180 | subsysnqn, | |||
| 181 | json_get_string(port_obj, "host_traddr"), | |||
| 182 | json_get_string(port_obj, "host_iface"), | |||
| 183 | hostnqn, hostid, params, hostsymname); | |||
| 184 | ||||
| 185 | libnvmf_params_free(params); | |||
| 186 | ||||
| 187 | /* | |||
| 188 | * A rejected entry (for example, invalid addressing or a conflicting | |||
| 189 | * persona) affects only that entry. Log the error and continue | |||
| 190 | * converting, matching json_parse_port()'s own tolerance. Stop only | |||
| 191 | * if memory allocation fails. | |||
| 192 | */ | |||
| 193 | if (ret == -ENOMEM12) | |||
| 194 | return ret; | |||
| 195 | if (ret) | |||
| 196 | nvme_show_error(nvme_show_message(1, "config convert: skipping an entry that could not be added: %s" , libnvme_strerror(-ret)) | |||
| 197 | "config convert: skipping an entry that could not be added: %s",nvme_show_message(1, "config convert: skipping an entry that could not be added: %s" , libnvme_strerror(-ret)) | |||
| 198 | libnvme_strerror(-ret))nvme_show_message(1, "config convert: skipping an entry that could not be added: %s" , libnvme_strerror(-ret)); | |||
| 199 | ||||
| 200 | return 0; | |||
| 201 | } | |||
| 202 | ||||
| 203 | static int convert_subsys(struct libnvmf_config_emitter *emitter, | |||
| 204 | const char *hostnqn, const char *hostid, | |||
| 205 | const char *hostsymname, const char *host_dhchap_key, | |||
| 206 | struct json_object *subsys_obj) | |||
| 207 | { | |||
| 208 | struct json_object *port_array; | |||
| 209 | const char *nqn = json_get_string(subsys_obj, "nqn"); | |||
| 210 | int p, ret; | |||
| 211 | ||||
| 212 | /* The well-known discovery NQN is the emitter's default value. */ | |||
| 213 | if (nqn && (!*nqn || !strcmp(nqn, NVME_DISC_SUBSYS_NAME"nqn.2014-08.org.nvmexpress.discovery"))) | |||
| 214 | nqn = NULL((void*)0); | |||
| 215 | ||||
| 216 | port_array = json_object_object_get(subsys_obj, "ports"); | |||
| 217 | if (!port_array) | |||
| 218 | return 0; | |||
| 219 | ||||
| 220 | for (p = 0; p < json_object_array_length(port_array); p++) { | |||
| 221 | struct json_object *port_obj = | |||
| 222 | json_object_array_get_idx(port_array, p); | |||
| 223 | ||||
| 224 | if (!port_obj) | |||
| 225 | continue; | |||
| 226 | ret = convert_port(emitter, hostnqn, hostid, hostsymname, | |||
| 227 | host_dhchap_key, nqn, port_obj); | |||
| 228 | if (ret) | |||
| 229 | return ret; | |||
| 230 | } | |||
| 231 | ||||
| 232 | return 0; | |||
| 233 | } | |||
| 234 | ||||
| 235 | static int convert_host(struct libnvmf_config_emitter *emitter, | |||
| 236 | struct json_object *host_obj) | |||
| 237 | { | |||
| 238 | struct json_object *subsys_array; | |||
| 239 | const char *hostnqn = json_get_string(host_obj, "hostnqn"); | |||
| 240 | const char *hostid = json_get_string(host_obj, "hostid"); | |||
| 241 | const char *hostsymname = json_get_string(host_obj, "hostsymname"); | |||
| 242 | const char *host_dhchap_key = json_get_string(host_obj, "dhchap_key"); | |||
| 243 | int s, ret; | |||
| 244 | ||||
| 245 | subsys_array = json_object_object_get(host_obj, "subsystems"); | |||
| 246 | if (!subsys_array) | |||
| 247 | return 0; | |||
| 248 | ||||
| 249 | for (s = 0; s < json_object_array_length(subsys_array); s++) { | |||
| 250 | struct json_object *subsys_obj = | |||
| 251 | json_object_array_get_idx(subsys_array, s); | |||
| 252 | ||||
| 253 | if (!subsys_obj) | |||
| 254 | continue; | |||
| 255 | ret = convert_subsys(emitter, hostnqn, hostid, hostsymname, | |||
| 256 | host_dhchap_key, subsys_obj); | |||
| 257 | if (ret) | |||
| 258 | return ret; | |||
| 259 | } | |||
| 260 | ||||
| 261 | return 0; | |||
| 262 | } | |||
| 263 | ||||
| 264 | int nvme_config_convert_json(struct libnvmf_config_emitter *emitter, | |||
| 265 | const char *json_file) | |||
| 266 | { | |||
| 267 | struct json_object *json_root, *host_array, *host_obj; | |||
| 268 | int h, ret; | |||
| 269 | ||||
| 270 | json_root = json_object_from_file(json_file); | |||
| 271 | if (!json_root) { | |||
| 272 | nvme_show_error("failed to parse %s: %s", json_file,nvme_show_message(1, "failed to parse %s: %s", json_file, json_util_get_last_err ()) | |||
| 273 | json_util_get_last_err())nvme_show_message(1, "failed to parse %s: %s", json_file, json_util_get_last_err ()); | |||
| 274 | return -EPROTO71; | |||
| 275 | } | |||
| 276 | ||||
| 277 | if (json_object_is_type(json_root, json_type_object)) { | |||
| 278 | /* Current format: { "hosts": [ ... ] } */ | |||
| 279 | host_array = json_object_object_get(json_root, "hosts"); | |||
| 280 | if (!host_array || | |||
| 281 | !json_object_is_type(host_array, json_type_array)) { | |||
| 282 | nvme_show_error("%s: expected a 'hosts' array",nvme_show_message(1, "%s: expected a 'hosts' array", json_file ) | |||
| 283 | json_file)nvme_show_message(1, "%s: expected a 'hosts' array", json_file ); | |||
| 284 | json_object_put(json_root); | |||
| 285 | return -EPROTO71; | |||
| 286 | } | |||
| 287 | } else if (json_object_is_type(json_root, json_type_array)) { | |||
| 288 | /* Legacy pre-3.0 format: a bare top-level array of hosts. */ | |||
| 289 | host_array = json_root; | |||
| 290 | } else { | |||
| 291 | nvme_show_error("%s: expected a JSON object or array",nvme_show_message(1, "%s: expected a JSON object or array", json_file ) | |||
| 292 | json_file)nvme_show_message(1, "%s: expected a JSON object or array", json_file ); | |||
| 293 | json_object_put(json_root); | |||
| 294 | return -EPROTO71; | |||
| 295 | } | |||
| 296 | ||||
| 297 | for (h = 0; h < json_object_array_length(host_array); h++) { | |||
| 298 | host_obj = json_object_array_get_idx(host_array, h); | |||
| 299 | if (!host_obj) | |||
| 300 | continue; | |||
| 301 | ret = convert_host(emitter, host_obj); | |||
| 302 | if (ret) { | |||
| 303 | json_object_put(json_root); | |||
| 304 | return ret; | |||
| 305 | } | |||
| 306 | } | |||
| 307 | ||||
| 308 | json_object_put(json_root); | |||
| 309 | ||||
| 310 | return 0; | |||
| 311 | } | |||
| 312 | ||||
| 313 | #else /* CONFIG_JSONC */ | |||
| 314 | ||||
| 315 | int nvme_config_convert_json(struct libnvmf_config_emitter *emitter, | |||
| 316 | const char *json_file) | |||
| 317 | { | |||
| 318 | nvme_show_error(nvme_show_message(1, "built without json-c; config.json conversion unavailable" ) | |||
| 319 | "built without json-c; config.json conversion unavailable")nvme_show_message(1, "built without json-c; config.json conversion unavailable" ); | |||
| 320 | return -ENOTSUP95; | |||
| 321 | } | |||
| 322 | ||||
| 323 | #endif /* CONFIG_JSONC */ | |||
| 324 | ||||
| 325 | int nvme_config_convert_discovery_args(struct libnvmf_config_emitter *emitter, | |||
| 326 | const struct nvmf_args *fa, const char *persistent) | |||
| 327 | { | |||
| 328 | struct libnvmf_params *params; | |||
| 329 | int ret; | |||
| 330 | ||||
| 331 | params = libnvmf_params_new(); | |||
| 332 | if (!params) | |||
| 333 | return -ENOMEM12; | |||
| 334 | ||||
| 335 | nvmf_args_to_params(params, fa); | |||
| 336 | if (persistent && | |||
| 337 | libnvmf_params_set(params, "persistent", persistent)) { | |||
| 338 | nvme_show_error(nvme_show_message(1, "discovery.conf: skipping a line with an invalid persistent value '%s'" , persistent) | |||
| 339 | "discovery.conf: skipping a line with an invalid persistent value '%s'",nvme_show_message(1, "discovery.conf: skipping a line with an invalid persistent value '%s'" , persistent) | |||
| 340 | persistent)nvme_show_message(1, "discovery.conf: skipping a line with an invalid persistent value '%s'" , persistent); | |||
| 341 | libnvmf_params_free(params); | |||
| 342 | return 0; | |||
| 343 | } | |||
| 344 | ||||
| 345 | ret = libnvmf_config_emit_add(emitter, true1, fa->transport, fa->traddr, | |||
| 346 | fa->trsvcid, fa->subsysnqn, fa->host_traddr, | |||
| 347 | fa->host_iface, fa->hostnqn, fa->hostid, params, NULL((void*)0)); | |||
| 348 | ||||
| 349 | libnvmf_params_free(params); | |||
| 350 | ||||
| 351 | /* | |||
| 352 | * A rejected entry affects only that entry. Log the error and | |||
| 353 | * continue converting. Stop only if memory allocation fails. | |||
| 354 | */ | |||
| 355 | if (ret == -ENOMEM12) | |||
| 356 | return ret; | |||
| 357 | if (ret) | |||
| 358 | nvme_show_error(nvme_show_message(1, "discovery.conf: skipping a line that could not be added: %s" , libnvme_strerror(-ret)) | |||
| 359 | "discovery.conf: skipping a line that could not be added: %s",nvme_show_message(1, "discovery.conf: skipping a line that could not be added: %s" , libnvme_strerror(-ret)) | |||
| 360 | libnvme_strerror(-ret))nvme_show_message(1, "discovery.conf: skipping a line that could not be added: %s" , libnvme_strerror(-ret)); | |||
| 361 | ||||
| 362 | return 0; | |||
| 363 | } | |||
| 364 | ||||
| 365 | int nvme_config_convert_discovery(struct libnvmf_config_emitter *emitter, | |||
| 366 | const char *disc_file) | |||
| 367 | { | |||
| 368 | __cleanup_file__attribute__((cleanup(shr_cleanup_file))) FILE *f = NULL((void*)0); | |||
| 369 | static char line[4096]; | |||
| 370 | int ret; | |||
| 371 | ||||
| 372 | f = fopen(disc_file, "r"); | |||
| ||||
| 373 | if (!f
| |||
| 374 | nvme_show_error("failed to open %s: %s", disc_file,nvme_show_message(1, "failed to open %s: %s", disc_file, strerror ((*__errno_location ()))) | |||
| 375 | strerror(errno))nvme_show_message(1, "failed to open %s: %s", disc_file, strerror ((*__errno_location ()))); | |||
| 376 | return -errno(*__errno_location ()); | |||
| 377 | } | |||
| 378 | ||||
| 379 | while (fgets(line, sizeof(line), f)) { | |||
| 380 | ret = nvmf_convert_discovery_line(emitter, line); | |||
| 381 | if (ret) | |||
| 382 | return ret; | |||
| 383 | } | |||
| 384 | ||||
| 385 | return 0; | |||
| ||||
| 386 | } | |||
| 387 | ||||
| 388 | /* | |||
| 389 | * Best effort. The configuration has already been installed. @path is | |||
| 390 | * left untouched so a rollback to a pre-INI version still finds it; the | |||
| 391 | * symlink marks it as already converted. | |||
| 392 | */ | |||
| 393 | static void mark_converted(const char *path) | |||
| 394 | { | |||
| 395 | __cleanup_free__attribute__((cleanup(shr_freep))) char *dst = NULL((void*)0); | |||
| 396 | ||||
| 397 | if (asprintf(&dst, "%s.converted", path) < 0) | |||
| 398 | return; | |||
| 399 | ||||
| 400 | if (symlink(path, dst)) | |||
| 401 | nvme_show_error(nvme_show_message(1, "converted %s but failed to mark it as converted (%s): %s" , path, dst, strerror((*__errno_location ()))) | |||
| 402 | "converted %s but failed to mark it as converted (%s): %s",nvme_show_message(1, "converted %s but failed to mark it as converted (%s): %s" , path, dst, strerror((*__errno_location ()))) | |||
| 403 | path, dst, strerror(errno))nvme_show_message(1, "converted %s but failed to mark it as converted (%s): %s" , path, dst, strerror((*__errno_location ()))); | |||
| 404 | } | |||
| 405 | ||||
| 406 | /* | |||
| 407 | * True if the @path.converted symlink exists and resolves. A dangling | |||
| 408 | * symlink (its target since removed) makes this return false, but that | |||
| 409 | * alone does not mean @path needs converting -- callers must also check | |||
| 410 | * whether @path itself exists. | |||
| 411 | */ | |||
| 412 | static bool_Bool already_converted(const char *path) | |||
| 413 | { | |||
| 414 | __cleanup_free__attribute__((cleanup(shr_freep))) char *converted = NULL((void*)0); | |||
| 415 | ||||
| 416 | if (asprintf(&converted, "%s.converted", path) < 0) | |||
| 417 | return false0; | |||
| 418 | ||||
| 419 | return !access(converted, F_OK0); | |||
| 420 | } | |||
| 421 | ||||
| 422 | static int install_converted(struct libnvmf_config_emitter *emitter, | |||
| 423 | const char *output_file, const char *json_path, | |||
| 424 | const char *disc_path, bool_Bool converted_json, | |||
| 425 | bool_Bool converted_disc, bool_Bool force) | |||
| 426 | { | |||
| 427 | int ret; | |||
| 428 | ||||
| 429 | ret = libnvmf_config_emit_install(emitter, output_file, force); | |||
| 430 | if (ret == -EEXIST17) { | |||
| 431 | nvme_show_error("%s already exists; refusing to overwrite",nvme_show_message(1, "%s already exists; refusing to overwrite" , output_file) | |||
| 432 | output_file)nvme_show_message(1, "%s already exists; refusing to overwrite" , output_file); | |||
| 433 | return ret; | |||
| 434 | } | |||
| 435 | if (ret) { | |||
| 436 | nvme_show_error("failed to write %s: %s", output_file,nvme_show_message(1, "failed to write %s: %s", output_file, libnvme_strerror (-ret)) | |||
| 437 | libnvme_strerror(-ret))nvme_show_message(1, "failed to write %s: %s", output_file, libnvme_strerror (-ret)); | |||
| 438 | return ret; | |||
| 439 | } | |||
| 440 | ||||
| 441 | if (converted_json) | |||
| 442 | mark_converted(json_path); | |||
| 443 | if (converted_disc) | |||
| 444 | mark_converted(disc_path); | |||
| 445 | ||||
| 446 | return 0; | |||
| 447 | } | |||
| 448 | ||||
| 449 | int nvme_config_convert_auto(struct libnvme_global_ctx *ctx, | |||
| 450 | const char *config_file, char **ini_path) | |||
| 451 | { | |||
| 452 | struct libnvmf_config_emitter *emitter; | |||
| 453 | const char *json_path = config_file; | |||
| 454 | const char *ext; | |||
| 455 | bool_Bool is_default; | |||
| 456 | bool_Bool json_exists, json_done; | |||
| 457 | bool_Bool disc_exists, disc_done; | |||
| 458 | bool_Bool have_json, have_disc; | |||
| 459 | bool_Bool converted_json = false0, converted_disc = false0; | |||
| 460 | int ret; | |||
| 461 | ||||
| 462 | *ini_path = NULL((void*)0); | |||
| 463 | ||||
| 464 | is_default = !strcmp(config_file, PATH_NVMF_INI"/usr/local/etc" "/nvme/nvme-fabrics.conf") || | |||
| 465 | !strcmp(config_file, PATH_NVMF_CONFIG"/usr/local/etc" "/nvme/config.json"); | |||
| 466 | if (is_default) { | |||
| 467 | json_path = PATH_NVMF_CONFIG"/usr/local/etc" "/nvme/config.json"; | |||
| 468 | *ini_path = strdup(PATH_NVMF_INI"/usr/local/etc" "/nvme/nvme-fabrics.conf"); | |||
| 469 | } else { | |||
| 470 | ext = strrchr(config_file, '.')_Generic (0 ? (config_file) : (void *) 1, const void *: (const char *) (strrchr (config_file, '.')), default: strrchr (config_file , '.')); | |||
| 471 | if (!ext || strcmp(ext, ".json")) { | |||
| 472 | *ini_path = strdup(config_file); | |||
| 473 | return *ini_path ? 0 : -ENOMEM12; | |||
| 474 | } | |||
| 475 | ||||
| 476 | if (asprintf(ini_path, "%.*s.conf", | |||
| 477 | (int)(ext - config_file), config_file) < 0) | |||
| 478 | return -ENOMEM12; | |||
| 479 | } | |||
| 480 | if (!*ini_path) | |||
| 481 | return -ENOMEM12; | |||
| 482 | ||||
| 483 | if (!access(*ini_path, F_OK0)) | |||
| 484 | return 0; | |||
| 485 | ||||
| 486 | json_exists = !access(json_path, F_OK0); | |||
| 487 | json_done = already_converted(json_path); | |||
| 488 | have_json = json_exists && !json_done; | |||
| 489 | ||||
| 490 | disc_exists = is_default && !access(PATH_NVMF_DISC"/usr/local/etc" "/nvme/discovery.conf", F_OK0); | |||
| 491 | disc_done = is_default && already_converted(PATH_NVMF_DISC"/usr/local/etc" "/nvme/discovery.conf"); | |||
| 492 | have_disc = disc_exists && !disc_done; | |||
| 493 | ||||
| 494 | if (!have_json && !have_disc) { | |||
| 495 | /* Default path: nothing to convert is fine, proceed empty. | |||
| 496 | * Custom path: never existed and never converted is a | |||
| 497 | * real error, not silent-empty. | |||
| 498 | */ | |||
| 499 | if (!is_default && !json_exists && !json_done) { | |||
| 500 | nvme_show_error("%s: no such file", json_path)nvme_show_message(1, "%s: no such file", json_path); | |||
| 501 | return -ENOENT2; | |||
| 502 | } | |||
| 503 | return 0; | |||
| 504 | } | |||
| 505 | ||||
| 506 | emitter = libnvmf_config_emit_new(ctx); | |||
| 507 | if (!emitter) | |||
| 508 | return -ENOMEM12; | |||
| 509 | ||||
| 510 | if (have_json) { | |||
| 511 | ret = nvme_config_convert_json(emitter, json_path); | |||
| 512 | if (ret) | |||
| 513 | goto out; | |||
| 514 | converted_json = true1; | |||
| 515 | } | |||
| 516 | ||||
| 517 | if (have_disc) { | |||
| 518 | ret = nvme_config_convert_discovery(emitter, PATH_NVMF_DISC"/usr/local/etc" "/nvme/discovery.conf"); | |||
| 519 | if (ret) | |||
| 520 | goto out; | |||
| 521 | converted_disc = true1; | |||
| 522 | } | |||
| 523 | ||||
| 524 | ret = install_converted(emitter, *ini_path, json_path, PATH_NVMF_DISC"/usr/local/etc" "/nvme/discovery.conf", | |||
| 525 | converted_json, converted_disc, false0); | |||
| 526 | if (ret) | |||
| 527 | goto out; | |||
| 528 | ||||
| 529 | nvme_show_error(nvme_show_message(1, "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 ? "/usr/local/etc" "/nvme/discovery.conf" : "", *ini_path) | |||
| 530 | "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",nvme_show_message(1, "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 ? "/usr/local/etc" "/nvme/discovery.conf" : "", *ini_path) | |||
| 531 | *ini_path, converted_json ? json_path : "",nvme_show_message(1, "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 ? "/usr/local/etc" "/nvme/discovery.conf" : "", *ini_path) | |||
| 532 | (converted_json && converted_disc) ? " and " : "",nvme_show_message(1, "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 ? "/usr/local/etc" "/nvme/discovery.conf" : "", *ini_path) | |||
| 533 | converted_disc ? PATH_NVMF_DISC : "", *ini_path)nvme_show_message(1, "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 ? "/usr/local/etc" "/nvme/discovery.conf" : "", *ini_path); | |||
| 534 | ||||
| 535 | out: | |||
| 536 | libnvmf_config_emit_free(emitter); | |||
| 537 | ||||
| 538 | return ret; | |||
| 539 | } | |||
| 540 | ||||
| 541 | int nvme_config_convert(const char *desc, int argc, char **argv) | |||
| 542 | { | |||
| 543 | char *config_file = NULL((void*)0); | |||
| 544 | char *output_file = NULL((void*)0); | |||
| 545 | const char *target; | |||
| 546 | const char *json_path; | |||
| 547 | bool_Bool force = false0; | |||
| 548 | bool_Bool converted_json = false0, converted_disc = false0; | |||
| 549 | bool_Bool json_already_done = false0, disc_already_done = false0; | |||
| 550 | __cleanup_nvme_global_ctx__attribute__((cleanup(cleanup_nvme_global_ctx))) struct libnvme_global_ctx *ctx = NULL((void*)0); | |||
| 551 | struct libnvmf_config_emitter *emitter = NULL((void*)0); | |||
| 552 | int ret; | |||
| 553 | ||||
| 554 | OPT_ARGS(opts)struct argconfig_commandline_options opts[] = { | |||
| 555 | OPT_STRING("config", 'J', "FILE", &config_file,{"config", 'J', "FILE", CFG_STRING, &config_file, 1, "convert this JSON file (default: config.json)" , 0, } | |||
| 556 | "convert this JSON file (default: config.json)"){"config", 'J', "FILE", CFG_STRING, &config_file, 1, "convert this JSON file (default: config.json)" , 0, }, | |||
| 557 | OPT_STRING("output", 'o', "FILE", &output_file,{"output", 'o', "FILE", CFG_STRING, &output_file, 1, "write result here (default: nvme-fabrics.conf)" , 0, } | |||
| 558 | "write result here (default: nvme-fabrics.conf)"){"output", 'o', "FILE", CFG_STRING, &output_file, 1, "write result here (default: nvme-fabrics.conf)" , 0, }, | |||
| 559 | OPT_FLAG("force", 0, &force,{"force", 0, ((void*)0), CFG_FLAG, &force, 0, "overwrite an existing target" , 0, } | |||
| 560 | "overwrite an existing target"){"force", 0, ((void*)0), CFG_FLAG, &force, 0, "overwrite an existing target" , 0, }, | |||
| 561 | OPT_END(){ ((void*)0) } | |||
| 562 | }; | |||
| 563 | ||||
| 564 | ret = parse_args(argc, argv, desc, opts); | |||
| 565 | if (ret) | |||
| 566 | return ret; | |||
| 567 | ||||
| 568 | ret = nvme_create_global_ctx(&ctx); | |||
| 569 | if (ret) | |||
| 570 | return ret; | |||
| 571 | ||||
| 572 | emitter = libnvmf_config_emit_new(ctx); | |||
| 573 | if (!emitter) | |||
| 574 | return -ENOMEM12; | |||
| 575 | ||||
| 576 | json_path = config_file ? config_file : PATH_NVMF_CONFIG"/usr/local/etc" "/nvme/config.json"; | |||
| 577 | if (already_converted(json_path)) { | |||
| 578 | json_already_done = true1; | |||
| 579 | } else if (!access(json_path, F_OK0)) { | |||
| 580 | ret = nvme_config_convert_json(emitter, json_path); | |||
| 581 | if (ret) | |||
| 582 | goto out; | |||
| 583 | converted_json = true1; | |||
| 584 | } else if (config_file) { | |||
| 585 | /* | |||
| 586 | * An explicit --config to a file that neither exists nor was | |||
| 587 | * ever converted: let the JSON parser produce its own | |||
| 588 | * "failed to parse" error instead of silently no-op'ing, | |||
| 589 | * since this was an explicit ask. | |||
| 590 | */ | |||
| 591 | ret = nvme_config_convert_json(emitter, json_path); | |||
| 592 | if (ret) | |||
| 593 | goto out; | |||
| 594 | converted_json = true1; | |||
| 595 | } | |||
| 596 | ||||
| 597 | if (already_converted(PATH_NVMF_DISC"/usr/local/etc" "/nvme/discovery.conf")) { | |||
| 598 | disc_already_done = true1; | |||
| 599 | } else if (!access(PATH_NVMF_DISC"/usr/local/etc" "/nvme/discovery.conf", F_OK0)) { | |||
| 600 | ret = nvme_config_convert_discovery(emitter, PATH_NVMF_DISC"/usr/local/etc" "/nvme/discovery.conf"); | |||
| 601 | if (ret) | |||
| 602 | goto out; | |||
| 603 | converted_disc = true1; | |||
| 604 | } | |||
| 605 | ||||
| 606 | if (!converted_json && !converted_disc) { | |||
| 607 | if (json_already_done || disc_already_done) { | |||
| 608 | nvme_show_result("already converted; nothing to do")nvme_show_message(0, "already converted; nothing to do"); | |||
| 609 | ret = 0; | |||
| 610 | goto out; | |||
| 611 | } | |||
| 612 | nvme_show_error("nothing to convert: neither %s nor %s exists",nvme_show_message(1, "nothing to convert: neither %s nor %s exists" , json_path, "/usr/local/etc" "/nvme/discovery.conf") | |||
| 613 | json_path, PATH_NVMF_DISC)nvme_show_message(1, "nothing to convert: neither %s nor %s exists" , json_path, "/usr/local/etc" "/nvme/discovery.conf"); | |||
| 614 | ret = -ENOENT2; | |||
| 615 | goto out; | |||
| 616 | } | |||
| 617 | ||||
| 618 | target = output_file ? output_file : PATH_NVMF_INI"/usr/local/etc" "/nvme/nvme-fabrics.conf"; | |||
| 619 | ret = install_converted(emitter, target, json_path, PATH_NVMF_DISC"/usr/local/etc" "/nvme/discovery.conf", | |||
| 620 | converted_json, converted_disc, force); | |||
| 621 | if (!ret) { | |||
| 622 | nvme_show_result(nvme_show_message(0, "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 ? "/usr/local/etc" "/nvme/discovery.conf" : "", target) | |||
| 623 | "converted legacy %s%s%s to %s -- the original is preserved for rollback, marked converted by a *.converted symlink",nvme_show_message(0, "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 ? "/usr/local/etc" "/nvme/discovery.conf" : "", target) | |||
| 624 | converted_json ? json_path : "",nvme_show_message(0, "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 ? "/usr/local/etc" "/nvme/discovery.conf" : "", target) | |||
| 625 | (converted_json && converted_disc) ? " and " : "",nvme_show_message(0, "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 ? "/usr/local/etc" "/nvme/discovery.conf" : "", target) | |||
| 626 | converted_disc ? PATH_NVMF_DISC : "", target)nvme_show_message(0, "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 ? "/usr/local/etc" "/nvme/discovery.conf" : "", target); | |||
| 627 | } | |||
| 628 | ||||
| 629 | out: | |||
| 630 | libnvmf_config_emit_free(emitter); | |||
| 631 | ||||
| 632 | return ret; | |||
| 633 | } | |||
| 634 | ||||
| 635 | /* | |||
| 636 | * Report @path's legacy-conversion state in one line, if there is | |||
| 637 | * anything to report. Returns false, printing nothing, when neither | |||
| 638 | * @path nor its marker exists. | |||
| 639 | * | |||
| 640 | * The "*.converted" marker (see mark_converted()) is a symlink whose | |||
| 641 | * target is @path itself, so whether it resolves is not independent | |||
| 642 | * information from whether @path exists -- it is the same fact observed | |||
| 643 | * two ways. That leaves four real states: no file and no marker; a file | |||
| 644 | * not yet converted; a converted file (marker resolves, @path exists by | |||
| 645 | * construction); and a dangling marker left over after @path was removed | |||
| 646 | * post-conversion (@path absent by construction). | |||
| 647 | */ | |||
| 648 | static bool_Bool report_legacy_status(const char *path) | |||
| 649 | { | |||
| 650 | __cleanup_free__attribute__((cleanup(shr_freep))) char *marker = NULL((void*)0); | |||
| 651 | struct stat sb; | |||
| 652 | bool_Bool file_exists; | |||
| 653 | bool_Bool marker_present; | |||
| 654 | ||||
| 655 | if (asprintf(&marker, "%s.converted", path) < 0) | |||
| 656 | return false0; | |||
| 657 | ||||
| 658 | file_exists = !access(path, F_OK0); | |||
| 659 | marker_present = !lstat(marker, &sb); | |||
| 660 | ||||
| 661 | if (!marker_present) { | |||
| 662 | if (!file_exists) | |||
| 663 | return false0; | |||
| 664 | ||||
| 665 | nvme_show_result("%s: present, not yet converted", path)nvme_show_message(0, "%s: present, not yet converted", path); | |||
| 666 | return true1; | |||
| 667 | } | |||
| 668 | ||||
| 669 | if (!file_exists) { | |||
| 670 | nvme_show_result(nvme_show_message(0, "%s: not present (a stale %s marker exists -- safe to delete)" , path, marker) | |||
| 671 | "%s: not present (a stale %s marker exists -- safe to delete)",nvme_show_message(0, "%s: not present (a stale %s marker exists -- safe to delete)" , path, marker) | |||
| 672 | path, marker)nvme_show_message(0, "%s: not present (a stale %s marker exists -- safe to delete)" , path, marker); | |||
| 673 | return true1; | |||
| 674 | } | |||
| 675 | ||||
| 676 | nvme_show_result(nvme_show_message(0, "%s: present, already converted (%s exists) -- delete both once rollback is no longer a concern" , path, marker) | |||
| 677 | "%s: present, already converted (%s exists) -- delete both once rollback is no longer a concern",nvme_show_message(0, "%s: present, already converted (%s exists) -- delete both once rollback is no longer a concern" , path, marker) | |||
| 678 | path, marker)nvme_show_message(0, "%s: present, already converted (%s exists) -- delete both once rollback is no longer a concern" , path, marker); | |||
| 679 | nvme_show_result(nvme_show_message(0, "note: if %s was replaced after conversion (for example, a package rollback followed by reinstall), this marker may be stale; verify its contents against the INI configuration before deleting either file" , path) | |||
| 680 | "note: if %s was replaced after conversion (for example, a package rollback followed by reinstall), this marker may be stale; verify its contents against the INI configuration before deleting either file",nvme_show_message(0, "note: if %s was replaced after conversion (for example, a package rollback followed by reinstall), this marker may be stale; verify its contents against the INI configuration before deleting either file" , path) | |||
| 681 | path)nvme_show_message(0, "note: if %s was replaced after conversion (for example, a package rollback followed by reinstall), this marker may be stale; verify its contents against the INI configuration before deleting either file" , path); | |||
| 682 | ||||
| 683 | return true1; | |||
| 684 | } | |||
| 685 | ||||
| 686 | int nvme_config_status(const char *desc, int argc, char **argv) | |||
| 687 | { | |||
| 688 | OPT_ARGS(opts)struct argconfig_commandline_options opts[] = { | |||
| 689 | OPT_END(){ ((void*)0) } | |||
| 690 | }; | |||
| 691 | bool_Bool json_found, disc_found; | |||
| 692 | int ret; | |||
| 693 | ||||
| 694 | ret = parse_args(argc, argv, desc, opts); | |||
| 695 | if (ret) | |||
| 696 | return ret; | |||
| 697 | ||||
| 698 | json_found = report_legacy_status(PATH_NVMF_CONFIG"/usr/local/etc" "/nvme/config.json"); | |||
| 699 | disc_found = report_legacy_status(PATH_NVMF_DISC"/usr/local/etc" "/nvme/discovery.conf"); | |||
| 700 | ||||
| 701 | if (!json_found && !disc_found) { | |||
| 702 | nvme_show_result(nvme_show_message(0, "no legacy configuration found; nothing to convert" ) | |||
| 703 | "no legacy configuration found; nothing to convert")nvme_show_message(0, "no legacy configuration found; nothing to convert" ); | |||
| 704 | return 0; | |||
| 705 | } | |||
| 706 | ||||
| 707 | return 1; | |||
| 708 | } |