Bug Summary

File:.build-ci/../tests/unit/c/test-global-config.c
Warning:line 336, column 6
Value of 'errno' was not checked and may be overwritten by function 'fread'

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 test-global-config.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 tests/unit/c/test-global-config.p -I tests/unit/c -I ../tests/unit/c -I . -I .. -I ccan -I ../ccan -I libnvme/src -I ../libnvme/src -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-10-24-125112-639-1 -x c ../tests/unit/c/test-global-config.c
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * This file is part of libnvme.
4 * Copyright (c) 2026 SUSE Software Solutions
5 *
6 * Authors: Daniel Wagner <dwagner@suse.de>
7 */
8
9/*
10 * Unit tests for global-config.c: mapping /etc/nvme/nvme-cli.conf's
11 * [Global] keys onto struct nvme_args, and its fail-safe handling of
12 * unknown keys, malformed values, and a missing file.
13 */
14
15#include <errno(*__errno_location ()).h>
16#include <inttypes.h>
17#include <stdbool.h>
18#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
21#include <unistd.h>
22
23#include <ccan/array_size/array_size.h>
24#include <shared/assert-util.h>
25#include <shared/fs-util.h>
26
27#include "../src/argconfig.h"
28#include "../src/args.h"
29#include "../src/global-config.h"
30
31struct nvme_args nvme_args = {
32 .output_format = "normal",
33 .output_format_ver = 2,
34 .timeout = 5000,
35 .supported_output_formats = NORMAL,
36};
37
38/*
39 * The throwaway .conf files land in the current directory, which meson
40 * sets to the build directory. A relative template keeps this off "/tmp",
41 * which isn't a usable temp directory on Windows.
42 */
43static char *write_temp(const char *content)
44{
45 char *path = strdup("nvme-cli-conf-test-XXXXXX");
46 int fd;
47
48 shr_assert(path)((void)((path) || (shr_assert_fail("../tests/unit/c/test-global-config.c"
, 48, "path"), 0)))
;
49
50 fd = shr_mkstemp(path);
51 shr_assert(fd >= 0)((void)((fd >= 0) || (shr_assert_fail("../tests/unit/c/test-global-config.c"
, 51, "fd >= 0"), 0)))
;
52 shr_assert(write(fd, content, strlen(content)) == (ssize_t)strlen(content))((void)((write(fd, content, strlen(content)) == (ssize_t)strlen
(content)) || (shr_assert_fail("../tests/unit/c/test-global-config.c"
, 52, "write(fd, content, strlen(content)) == (ssize_t)strlen(content)"
), 0)))
;
53 close(fd);
54
55 return path;
56}
57
58static bool_Bool test_valid_keys(void)
59{
60 char *path = write_temp(
61 "[Global]\n"
62 "verbose = 2\n"
63 "quiet = true\n"
64 "output-format = json\n"
65 "timeout = 12345\n"
66 "dry-run = yes\n"
67 "no-retries = on\n"
68 "no-ioctl-probing = 1\n"
69 "output-format-version = 1\n"
70 "set-options = foo=bar\n");
71 bool_Bool pass = true1;
72
73 printf("test_valid_keys:\n");
74
75 nvme_load_global_config_from(path);
76 unlink(path);
77 free(path);
78
79 if (nvme_args.verbose != 2 || !nvme_args.quiet ||
80 strcmp(nvme_args.output_format, "json") ||
81 nvme_args.timeout != 12345 || !nvme_args.dry_run ||
82 !nvme_args.no_retries || !nvme_args.no_ioctl_probing ||
83 nvme_args.output_format_ver != 1 ||
84 strcmp(nvme_args.set_options, "foo=bar")) {
85 printf(" - values applied to nvme_args [FAIL]\n");
86 pass = false0;
87 } else {
88 printf(" - values applied to nvme_args [PASS]\n");
89 }
90
91 return pass;
92}
93
94static bool_Bool test_unknown_key_ignored(void)
95{
96 char *path = write_temp(
97 "[Global]\n"
98 "timeout = 999\n"
99 "bogus-key = whatever\n");
100 bool_Bool pass = true1;
101
102 printf("test_unknown_key_ignored:\n");
103
104 nvme_load_global_config_from(path);
105 unlink(path);
106 free(path);
107
108 /* An unknown key must not abort parsing of the rest of the file. */
109 if (nvme_args.timeout != 999) {
110 printf(" - sibling keys still applied [FAIL]\n");
111 pass = false0;
112 } else {
113 printf(" - sibling keys still applied [PASS]\n");
114 }
115
116 return pass;
117}
118
119static bool_Bool test_value_names(void)
120{
121 char *path = write_temp(
122 "[Global]\n"
123 "timeout = infinite\n");
124 bool_Bool pass = true1;
125
126 printf("test_value_names:\n");
127
128 nvme_args.timeout = 5000;
129 nvme_load_global_config_from(path);
130 unlink(path);
131 free(path);
132
133 /* The names the command line takes work in the file too */
134 if (nvme_args.timeout != UINT32_MAX(4294967295U)) {
135 printf(" - timeout = infinite [FAIL]\n");
136 pass = false0;
137 } else {
138 printf(" - timeout = infinite [PASS]\n");
139 }
140
141 return pass;
142}
143
144static bool_Bool test_strict_numbers_and_buf(void)
145{
146 bool_Bool pass = true1;
147
148 printf("test_strict_numbers_and_buf:\n");
149
150 nvme_args.timeout = 42;
151 nvme_load_global_config_buf("[Global]\ntimeout = +5\n", "buf");
152 if (nvme_args.timeout != 42) {
153 printf(" - '+5' rejected [FAIL]\n");
154 pass = false0;
155 } else {
156 printf(" - '+5' rejected [PASS]\n");
157 }
158
159 nvme_load_global_config_buf("[Global]\ntimeout = 0x10\n", "buf");
160 if (nvme_args.timeout != 16) {
161 printf(" - '0x10' taken from a buffer [FAIL]\n");
162 pass = false0;
163 } else {
164 printf(" - '0x10' taken from a buffer [PASS]\n");
165 }
166
167 nvme_args.timeout = 42;
168 nvme_load_global_config_buf("[Global]\ntimeout = 010\n", "buf");
169 if (nvme_args.timeout != 42) {
170 printf(" - '010' rejected [FAIL]\n");
171 pass = false0;
172 } else {
173 printf(" - '010' rejected [PASS]\n");
174 }
175
176 nvme_args.output_format_ver = 2;
177 nvme_load_global_config_buf("[Global]\noutput-format-version = 3\n",
178 "buf");
179 if (nvme_args.output_format_ver != 2) {
180 printf(" - '3' out of range rejected [FAIL]\n");
181 pass = false0;
182 } else {
183 printf(" - '3' out of range rejected [PASS]\n");
184 }
185
186 return pass;
187}
188
189static bool_Bool test_repeated_string(void)
190{
191 bool_Bool pass = true1;
192
193 printf("test_repeated_string:\n");
194
195 nvme_load_global_config_buf("[Global]\noutput-format = json\n"
196 "output-format = binary\n", "buf");
197 if (strcmp(nvme_args.output_format, "binary")) {
198 printf(" - the last value counts [FAIL]\n");
199 pass = false0;
200 } else {
201 printf(" - the last value counts [PASS]\n");
202 }
203 nvme_global_config_free();
204 nvme_args.output_format = "normal";
205
206 return pass;
207}
208
209static bool_Bool test_malformed_value_skipped(void)
210{
211 char *path;
212 bool_Bool pass = true1;
213
214 nvme_args.timeout = 42;
215 path = write_temp(
216 "[Global]\n"
217 "timeout = not-a-number\n"
218 "verbose = also-not-a-number\n");
219
220 printf("test_malformed_value_skipped:\n");
221
222 nvme_load_global_config_from(path);
223 unlink(path);
224 free(path);
225
226 if (nvme_args.timeout != 42) {
227 printf(" - bad uint value left field untouched [FAIL]\n");
228 pass = false0;
229 } else {
230 printf(" - bad uint value left field untouched [PASS]\n");
231 }
232
233 return pass;
234}
235
236/*
237 * The keys are the global options the help shows; the hidden
238 * human-readable is none. A verbose count beyond INT_MAX is rejected.
239 */
240static bool_Bool test_hidden_and_out_of_range(void)
241{
242 char *path;
243 bool_Bool pass = true1;
244
245 nvme_args.verbose = 1;
246 path = write_temp(
247 "[Global]\n"
248 "human-readable = 5\n"
249 "verbose = 4294967295\n");
250
251 printf("test_hidden_and_out_of_range:\n");
252
253 nvme_load_global_config_from(path);
254 unlink(path);
255 free(path);
256
257 if (nvme_args.verbose != 1) {
258 printf(" - hidden key and huge verbose ignored [FAIL]\n");
259 pass = false0;
260 } else {
261 printf(" - hidden key and huge verbose ignored [PASS]\n");
262 }
263
264 return pass;
265}
266
267static bool_Bool test_keys_outside_global_ignored(void)
268{
269 char *path;
270 bool_Bool pass = true1;
271
272 nvme_args.timeout = 7;
273 path = write_temp(
274 "timeout = 111\n"
275 "[Other]\n"
276 "timeout = 222\n");
277
278 printf("test_keys_outside_global_ignored:\n");
279
280 nvme_load_global_config_from(path);
281 unlink(path);
282 free(path);
283
284 if (nvme_args.timeout != 7) {
285 printf(" - keys outside [Global] ignored [FAIL]\n");
286 pass = false0;
287 } else {
288 printf(" - keys outside [Global] ignored [PASS]\n");
289 }
290
291 return pass;
292}
293
294static bool_Bool test_missing_file_is_noop(void)
295{
296 bool_Bool pass = true1;
297
298 nvme_args.timeout = 55;
299
300 printf("test_missing_file_is_noop:\n");
301
302 nvme_load_global_config_from("/nonexistent/nvme-cli.conf");
303
304 if (nvme_args.timeout != 55) {
305 printf(" - missing file leaves nvme_args untouched [FAIL]\n");
306 pass = false0;
307 } else {
308 printf(" - missing file leaves nvme_args untouched [PASS]\n");
309 }
310
311 return pass;
312}
313
314/* A file which can't be read is reported with its name */
315static bool_Bool test_unreadable_file_reported(void)
316{
317 char buf[256] = {};
318 int saved, err;
319 FILE *tmp;
320 size_t n;
321
322 printf("test_unreadable_file_reported:\n");
323
324 /* a directory can't be read even by root */
325 tmp = tmpfile();
326 shr_assert(tmp)((void)((tmp) || (shr_assert_fail("../tests/unit/c/test-global-config.c"
, 326, "tmp"), 0)))
;
327 fflush(stderrstderr);
328 saved = dup(STDERR_FILENO2);
329 shr_assert(saved >= 0)((void)((saved >= 0) || (shr_assert_fail("../tests/unit/c/test-global-config.c"
, 329, "saved >= 0"), 0)))
;
330 dup2(fileno(tmp), STDERR_FILENO2);
331 err = nvme_load_global_config_from(".");
332 fflush(stderrstderr);
333 dup2(saved, STDERR_FILENO2);
334 close(saved);
335 rewind(tmp);
2
After calling 'rewind' reading 'errno' is required to find out if the call has failed
336 n = fread(buf, 1, sizeof(buf) - 1, tmp);
3
Value of 'errno' was not checked and may be overwritten by function 'fread'
337 buf[n] = '\0';
338 fclose(tmp);
339
340 if (err != -EISDIR21 || strncmp(buf, ".: ", 3)) {
341 printf(" - error and file name reported [FAIL]\n");
342 return false0;
343 }
344 printf(" - error and file name reported [PASS]\n");
345
346 return true1;
347}
348
349/* The global options in front of the command win over the file */
350static bool_Bool test_command_line_wins(void)
351{
352 static const char * const args[] = {
353 "nvme", "-o", "binary", "-v", "--timeout=7", "list",
354 };
355 char *argv[ARRAY_SIZE(args)(sizeof(args) / sizeof((args)[0]) + 0) + 1] = {};
356 bool_Bool pass = true1;
357 int argind;
358 size_t i;
359
360 for (i = 0; i < ARRAY_SIZE(args)(sizeof(args) / sizeof((args)[0]) + 0); i++)
361 argv[i] = (char *)args[i];
362
363 printf("test_command_line_wins:\n");
364
365 nvme_args_register();
366 nvme_args.verbose = 0;
367 nvme_args.timeout = 42;
368 if (argconfig_parse_global(6, argv, NULL((void*)0), &argind) || argind != 5) {
369 printf(" - global options parsed [FAIL]\n");
370 return false0;
371 }
372
373 nvme_load_global_config_buf("[Global]\n"
374 "output-format = json\n"
375 "verbose = 2\n"
376 "timeout = not-a-number\n"
377 "timeout = 9\n"
378 "quiet = true\n", "buf");
379 if (strcmp(nvme_args.output_format, "binary")) {
380 printf(" - the given string stays [FAIL]\n");
381 pass = false0;
382 } else {
383 printf(" - the given string stays [PASS]\n");
384 }
385 if (nvme_args.timeout != 7) {
386 printf(" - the given number stays [FAIL]\n");
387 pass = false0;
388 } else {
389 printf(" - the given number stays [PASS]\n");
390 }
391 if (nvme_args.verbose != 3) {
392 printf(" - the count adds up [FAIL]\n");
393 pass = false0;
394 } else {
395 printf(" - the count adds up [PASS]\n");
396 }
397 if (!nvme_args.quiet) {
398 printf(" - an option not given is set [FAIL]\n");
399 pass = false0;
400 } else {
401 printf(" - an option not given is set [PASS]\n");
402 }
403
404 /* registering the options again forgets what was given */
405 nvme_args_register();
406 nvme_load_global_config_buf("[Global]\ntimeout = 9\n", "buf");
407 if (nvme_args.timeout != 9) {
408 printf(" - a new register starts over [FAIL]\n");
409 pass = false0;
410 } else {
411 printf(" - a new register starts over [PASS]\n");
412 }
413
414 nvme_global_config_free();
415 nvme_args.output_format = "normal";
416 nvme_args.verbose = 0;
417 nvme_args.quiet = false0;
418
419 return pass;
420}
421
422/* The last valid value of a count adds to the one given, not each value */
423static bool_Bool test_command_line_count_repeated(void)
424{
425 static const char * const args[] = { "nvme", "-v", "list" };
426 char *argv[ARRAY_SIZE(args)(sizeof(args) / sizeof((args)[0]) + 0) + 1] = {};
427 bool_Bool pass = true1;
428 int argind;
429 size_t i;
430
431 for (i = 0; i < ARRAY_SIZE(args)(sizeof(args) / sizeof((args)[0]) + 0); i++)
432 argv[i] = (char *)args[i];
433
434 printf("test_command_line_count_repeated:\n");
435
436 nvme_args_register();
437 nvme_args.verbose = 0;
438 if (argconfig_parse_global(3, argv, NULL((void*)0), &argind) || argind != 2) {
439 printf(" - global options parsed [FAIL]\n");
440 return false0;
441 }
442
443 nvme_load_global_config_buf("[Global]\n"
444 "verbose = 2\n"
445 "verbose = 0\n"
446 "verbose = bogus\n", "buf");
447 if (nvme_args.verbose != 1) {
448 printf(" - the last valid count adds up [FAIL]\n");
449 pass = false0;
450 } else {
451 printf(" - the last valid count adds up [PASS]\n");
452 }
453
454 nvme_load_global_config_buf("[Global]\nverbose = 3\n", "buf");
455 if (nvme_args.verbose != 4) {
456 printf(" - a later file replaces the count [FAIL]\n");
457 pass = false0;
458 } else {
459 printf(" - a later file replaces the count [PASS]\n");
460 }
461
462 nvme_global_config_free();
463 nvme_args_register();
464 nvme_args.verbose = 0;
465
466 return pass;
467}
468
469int main(void)
470{
471 bool_Bool pass = true1;
472
473 nvme_args_register();
474
475 pass &= test_valid_keys();
476 pass &= test_unknown_key_ignored();
477 pass &= test_value_names();
478 pass &= test_strict_numbers_and_buf();
479 pass &= test_repeated_string();
480 pass &= test_malformed_value_skipped();
481 pass &= test_hidden_and_out_of_range();
482 pass &= test_keys_outside_global_ignored();
483 pass &= test_missing_file_is_noop();
484 pass &= test_unreadable_file_reported();
1
Calling 'test_unreadable_file_reported'
485 pass &= test_command_line_wins();
486 pass &= test_command_line_count_repeated();
487
488 fflush(stdoutstdout);
489 exit(pass ? EXIT_SUCCESS0 : EXIT_FAILURE1);
490}