| 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' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 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 | ||||
| 31 | struct 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 | */ | |||
| 43 | static 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 | ||||
| 58 | static 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 | ||||
| 94 | static 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 | ||||
| 119 | static 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 | ||||
| 144 | static 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 | ||||
| 189 | static 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 | ||||
| 209 | static 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 | */ | |||
| 240 | static 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 | ||||
| 267 | static 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 | ||||
| 294 | static 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 */ | |||
| 315 | static 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); | |||
| 336 | n = fread(buf, 1, sizeof(buf) - 1, tmp); | |||
| ||||
| 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 */ | |||
| 350 | static 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 */ | |||
| 423 | static 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 | ||||
| 469 | int 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(); | |||
| ||||
| 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 | } |