Bug Summary

File:.build-ci/../src/config-convert.c
Warning:line 385, column 9
Opened stream never closed. Potential resource leak

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-redhat-linux-gnu -O3 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name config-convert.c -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model static -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/__w/nvme-cli/nvme-cli/.build-ci -fcoverage-compilation-dir=/__w/nvme-cli/nvme-cli/.build-ci -resource-dir /usr/bin/../lib/clang/22 -include /__w/nvme-cli/nvme-cli/.build-ci/nvme-config.h -I nvme.p -I . -I .. -I src -I ../src -I ccan -I ../ccan -I libnvme/src -I ../libnvme/src -I /usr/include/json-c -D _FILE_OFFSET_BITS=64 -D _GNU_SOURCE -U NDEBUG -internal-isystem /usr/bin/../lib/clang/22/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/16/../../../../x86_64-redhat-linux/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -std=gnu11 -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcolor-diagnostics -vectorize-loops -vectorize-slp -analyzer-opt-analyze-headers -analyzer-output=html -faddrsig -fdwarf2-cfi-asm -o /__w/nvme-cli/nvme-cli/.build-ci/scan-results/2026-09-23-073103-589-1 -x c ../src/config-convert.c
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
28struct legacy_key {
29 const char *json_key;
30 const char *ini_key;
31};
32
33/* Map config.json keys from underscore to hyphen notation. */
34static 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
46static 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
55static 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
63static 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
77static 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
84static 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. */
92static 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 */
126static 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 */
148static 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
158static 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
203static 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
235static 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
264int 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
315int 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
325int 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
365int 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");
1
Stream opened here
2
Assuming that 'fopen' is successful
373 if (!f
2.1
'f' is non-null
) {
3
Taking false branch
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)) {
4
Loop condition is false. Execution jumps to the end of the function
380 ret = nvmf_convert_discovery_line(emitter, line);
381 if (ret)
382 return ret;
383 }
384
385 return 0;
5
Opened stream never closed. Potential resource leak
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 */
393static 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 */
412static 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
422static 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
449int 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
535out:
536 libnvmf_config_emit_free(emitter);
537
538 return ret;
539}
540
541int 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
629out:
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 */
648static 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
686int 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}