From 8d843cc9cc0e989d3929f204f77223cd08730c7a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 24 Dec 2023 08:51:48 +0100 Subject: Merging upstream version 1.7.1. Signed-off-by: Daniel Baumann --- test/ioctl/discovery.c | 428 +++++++++++++ test/ioctl/features.c | 1604 ++++++++++++++++++++++++++++++++++++++++++++++++ test/ioctl/identify.c | 572 +++++++++++++++++ test/ioctl/meson.build | 42 ++ test/ioctl/mock.c | 174 ++++++ test/ioctl/mock.h | 104 ++++ test/ioctl/util.c | 65 ++ test/ioctl/util.h | 19 + test/meson.build | 18 +- test/mi-mctp.c | 118 +++- test/mi.c | 17 +- test/mock-ifaddrs.c | 123 ++++ test/test-util.c | 1 - test/test.c | 18 +- test/tree.c | 1115 ++++++++++++++++++++++++++++++--- 15 files changed, 4274 insertions(+), 144 deletions(-) create mode 100644 test/ioctl/discovery.c create mode 100644 test/ioctl/features.c create mode 100644 test/ioctl/identify.c create mode 100644 test/ioctl/meson.build create mode 100644 test/ioctl/mock.c create mode 100644 test/ioctl/mock.h create mode 100644 test/ioctl/util.c create mode 100644 test/ioctl/util.h create mode 100644 test/mock-ifaddrs.c (limited to 'test') diff --git a/test/ioctl/discovery.c b/test/ioctl/discovery.c new file mode 100644 index 0000000..f5f6f51 --- /dev/null +++ b/test/ioctl/discovery.c @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include + +#include +#include +#include +#include +#include + +#include "../../src/nvme/private.h" +#include "mock.h" +#include "util.h" + +#define TEST_FD 0xFD +#define HEADER_LEN 20 + +static void arbitrary_ascii_string(size_t max_len, char *str, char *log_str) +{ + size_t len; + size_t i; + + len = arbitrary_range(max_len + 1); + for (i = 0; i < len; i++) { + /* + * ASCII strings shall contain only code values 20h through 7Eh. + * Exclude 20h (space) because it ends the string. + */ + str[i] = log_str[i] = arbitrary_range(0x7E - 0x20) + 0x20 + 1; + } + for (i = len; i < max_len; i++) { + str[i] = '\0'; + log_str[i] = ' '; + } +} + +static void arbitrary_entry(struct nvmf_disc_log_entry *entry, + struct nvmf_disc_log_entry *log_entry) +{ + arbitrary(entry, sizeof(*entry)); + memcpy(log_entry, entry, sizeof(*entry)); + arbitrary_ascii_string( + sizeof(entry->trsvcid), entry->trsvcid, log_entry->trsvcid); + arbitrary_ascii_string( + sizeof(entry->traddr), entry->traddr, log_entry->traddr); +} + +static void arbitrary_entries(size_t len, + struct nvmf_disc_log_entry *entries, + struct nvmf_disc_log_entry *log_entries) +{ + size_t i; + + for (i = 0; i < len; i++) + arbitrary_entry(&entries[i], &log_entries[i]); +} + +static void test_no_entries(nvme_ctrl_t c) +{ + struct nvmf_discovery_log header = {}; + /* No entries to fetch after fetching the header */ + struct mock_cmd mock_admin_cmds[] = { + { + .opcode = nvme_admin_get_log_page, + .data_len = HEADER_LEN, + .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .out_data = &header, + }, + }; + struct nvmf_discovery_log *log = NULL; + + set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds)); + check(nvmf_get_discovery_log(c, &log, 1) == 0, "discovery failed: %m"); + end_mock_cmds(); + cmp(log, &header, sizeof(header), "incorrect header"); + free(log); +} + +static void test_four_entries(nvme_ctrl_t c) +{ + size_t num_entries = 4; + struct nvmf_disc_log_entry entries[num_entries]; + struct nvmf_disc_log_entry log_entries[num_entries]; + struct nvmf_discovery_log header = {.numrec = cpu_to_le64(num_entries)}; + /* + * All 4 entries should be fetched at once + * followed by the header again (to ensure genctr hasn't changed) + */ + struct mock_cmd mock_admin_cmds[] = { + { + .opcode = nvme_admin_get_log_page, + .data_len = HEADER_LEN, + .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .out_data = &header, + }, + { + .opcode = nvme_admin_get_log_page, + .data_len = sizeof(entries), + .cdw10 = (sizeof(entries) / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .cdw12 = sizeof(header), /* LPOL */ + .out_data = log_entries, + }, + { + .opcode = nvme_admin_get_log_page, + .data_len = HEADER_LEN, + .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .out_data = &header, + }, + }; + struct nvmf_discovery_log *log = NULL; + + arbitrary_entries(num_entries, entries, log_entries); + set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds)); + check(nvmf_get_discovery_log(c, &log, 1) == 0, "discovery failed: %m"); + end_mock_cmds(); + cmp(log, &header, sizeof(header), "incorrect header"); + cmp(log->entries, entries, sizeof(entries), "incorrect entries"); + free(log); +} + +static void test_five_entries(nvme_ctrl_t c) +{ + size_t num_entries = 5; + struct nvmf_disc_log_entry entries[num_entries]; + struct nvmf_disc_log_entry log_entries[num_entries]; + size_t first_entries = 4; + size_t first_data_len = first_entries * sizeof(*entries); + size_t second_entries = num_entries - first_entries; + size_t second_data_len = second_entries * sizeof(*entries); + struct nvmf_discovery_log header = {.numrec = cpu_to_le64(num_entries)}; + /* + * The first 4 entries (4 KB) are fetched together, + * followed by last entry separately. + * Finally, the header is fetched again to check genctr. + */ + struct mock_cmd mock_admin_cmds[] = { + { + .opcode = nvme_admin_get_log_page, + .data_len = HEADER_LEN, + .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .out_data = &header, + }, + { + .opcode = nvme_admin_get_log_page, + .data_len = first_data_len, + .cdw10 = (first_data_len / 4 - 1) << 16 /* NUMDL */ + | 1 << 15 /* RAE */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .cdw12 = sizeof(header), /* LPOL */ + .out_data = log_entries, + }, + { + .opcode = nvme_admin_get_log_page, + .data_len = second_data_len, + .cdw10 = (second_data_len / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .cdw12 = sizeof(header) + first_data_len, /* LPOL */ + .out_data = log_entries + first_entries, + }, + { + .opcode = nvme_admin_get_log_page, + .data_len = HEADER_LEN, + .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .out_data = &header, + }, + }; + struct nvmf_discovery_log *log = NULL; + + arbitrary_entries(num_entries, entries, log_entries); + set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds)); + check(nvmf_get_discovery_log(c, &log, 1) == 0, "discovery failed: %m"); + end_mock_cmds(); + cmp(log, &header, sizeof(header), "incorrect header"); + cmp(log->entries, entries, sizeof(entries), "incorrect entries"); + free(log); +} + +static void test_genctr_change(nvme_ctrl_t c) +{ + struct nvmf_disc_log_entry entries1[1]; + struct nvmf_discovery_log header1 = { + .numrec = cpu_to_le64(ARRAY_SIZE(entries1)), + }; + size_t num_entries2 = 2; + struct nvmf_disc_log_entry entries2[num_entries2]; + struct nvmf_disc_log_entry log_entries2[num_entries2]; + struct nvmf_discovery_log header2 = { + .genctr = cpu_to_le64(1), + .numrec = cpu_to_le64(num_entries2), + }; + /* + * genctr changes after the entries are fetched the first time, + * so the log page entries are refetched + */ + struct mock_cmd mock_admin_cmds[] = { + { + .opcode = nvme_admin_get_log_page, + .data_len = HEADER_LEN, + .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .out_data = &header1, + }, + { + .opcode = nvme_admin_get_log_page, + .data_len = sizeof(entries1), + .cdw10 = (sizeof(entries1) / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* NUMDL */ + .cdw12 = sizeof(header1), /* LPOL */ + .out_data = entries1, + }, + { + .opcode = nvme_admin_get_log_page, + .data_len = HEADER_LEN, + .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .out_data = &header2, + }, + { + .opcode = nvme_admin_get_log_page, + .data_len = sizeof(entries2), + .cdw10 = (sizeof(entries2) / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .cdw12 = sizeof(header2), /* LPOL */ + .out_data = log_entries2, + }, + { + .opcode = nvme_admin_get_log_page, + .data_len = HEADER_LEN, + .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .out_data = &header2, + }, + }; + struct nvmf_discovery_log *log = NULL; + + arbitrary(entries1, sizeof(entries1)); + arbitrary_entries(num_entries2, entries2, log_entries2); + set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds)); + check(nvmf_get_discovery_log(c, &log, 2) == 0, "discovery failed: %m"); + end_mock_cmds(); + cmp(log, &header2, sizeof(header2), "incorrect header"); + cmp(log->entries, entries2, sizeof(entries2), "incorrect entries"); + free(log); +} + +static void test_max_retries(nvme_ctrl_t c) +{ + struct nvmf_disc_log_entry entry; + struct nvmf_discovery_log header1 = {.numrec = cpu_to_le64(1)}; + struct nvmf_discovery_log header2 = { + .genctr = cpu_to_le64(1), + .numrec = cpu_to_le64(1), + }; + struct nvmf_discovery_log header3 = { + .genctr = cpu_to_le64(2), + .numrec = cpu_to_le64(1), + }; + /* genctr changes in both attempts, hitting the max retries (2) */ + struct mock_cmd mock_admin_cmds[] = { + { + .opcode = nvme_admin_get_log_page, + .data_len = HEADER_LEN, + .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .out_data = &header1, + }, + { + .opcode = nvme_admin_get_log_page, + .data_len = sizeof(entry), + .cdw10 = (sizeof(entry) / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .cdw12 = sizeof(header1), /* LPOL */ + .out_data = &entry, + }, + { + .opcode = nvme_admin_get_log_page, + .data_len = HEADER_LEN, + .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .out_data = &header2, + }, + { + .opcode = nvme_admin_get_log_page, + .data_len = sizeof(entry), + .cdw10 = (sizeof(entry) / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .cdw12 = sizeof(header2), /* LPOL */ + .out_data = &entry, + }, + { + .opcode = nvme_admin_get_log_page, + .data_len = HEADER_LEN, + .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .out_data = &header3, + }, + }; + struct nvmf_discovery_log *log = NULL; + + arbitrary(&entry, sizeof(entry)); + set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds)); + check(nvmf_get_discovery_log(c, &log, 2) == -1, "discovery succeeded"); + end_mock_cmds(); + check(errno == EAGAIN, "discovery failed: %m"); + check(!log, "unexpected log page returned"); +} + +static void test_header_error(nvme_ctrl_t c) +{ + /* Stop after an error in fetching the header the first time */ + struct mock_cmd mock_admin_cmds[] = { + { + .opcode = nvme_admin_get_log_page, + .data_len = HEADER_LEN, + .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .err = NVME_SC_INVALID_OPCODE, + }, + }; + struct nvmf_discovery_log *log = NULL; + + set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds)); + check(nvmf_get_discovery_log(c, &log, 1) == -1, "discovery succeeded"); + end_mock_cmds(); + check(!log, "unexpected log page returned"); +} + +static void test_entries_error(nvme_ctrl_t c) +{ + struct nvmf_discovery_log header = {.numrec = cpu_to_le64(1)}; + size_t entry_size = sizeof(struct nvmf_disc_log_entry); + /* Stop after an error in fetching the entries */ + struct mock_cmd mock_admin_cmds[] = { + { + .opcode = nvme_admin_get_log_page, + .data_len = HEADER_LEN, + .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .out_data = &header, + }, + { + .opcode = nvme_admin_get_log_page, + .data_len = entry_size, + .cdw10 = (entry_size / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .cdw12 = sizeof(header), /* LPOL */ + .err = -EIO, + }, + }; + struct nvmf_discovery_log *log = NULL; + + set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds)); + check(nvmf_get_discovery_log(c, &log, 1) == -1, "discovery succeeded"); + end_mock_cmds(); + check(errno == EIO, "discovery failed: %m"); + check(!log, "unexpected log page returned"); +} + +static void test_genctr_error(nvme_ctrl_t c) +{ + struct nvmf_disc_log_entry entry; + struct nvmf_discovery_log header = {.numrec = cpu_to_le64(1)}; + /* Stop after an error in refetching the header */ + struct mock_cmd mock_admin_cmds[] = { + { + .opcode = nvme_admin_get_log_page, + .data_len = HEADER_LEN, + .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .out_data = &header, + }, + { + .opcode = nvme_admin_get_log_page, + .data_len = sizeof(entry), + .cdw10 = (sizeof(entry) / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .cdw12 = sizeof(header), /* LPOL */ + .out_data = &entry, + }, + { + .opcode = nvme_admin_get_log_page, + .data_len = HEADER_LEN, + .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */ + | NVME_LOG_LID_DISCOVER, /* LID */ + .err = NVME_SC_INTERNAL, + }, + }; + struct nvmf_discovery_log *log = NULL; + + arbitrary(&entry, sizeof(entry)); + set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds)); + check(nvmf_get_discovery_log(c, &log, 1) == -1, "discovery succeeded"); + end_mock_cmds(); + check(!log, "unexpected log page returned"); +} + +static void run_test(const char *test_name, void (*test_fn)(nvme_ctrl_t)) +{ + struct nvme_ctrl c = {.fd = TEST_FD}; + + printf("Running test %s...", test_name); + fflush(stdout); + check(asprintf(&c.name, "%s_ctrl", test_name) >= 0, "asprintf() failed"); + test_fn(&c); + free(c.name); + puts(" OK"); +} + +#define RUN_TEST(name) run_test(#name, test_ ## name) + +int main(void) +{ + set_mock_fd(TEST_FD); + RUN_TEST(no_entries); + RUN_TEST(four_entries); + RUN_TEST(five_entries); + RUN_TEST(genctr_change); + RUN_TEST(max_retries); + RUN_TEST(header_error); + RUN_TEST(entries_error); + RUN_TEST(genctr_error); +} diff --git a/test/ioctl/features.c b/test/ioctl/features.c new file mode 100644 index 0000000..7386497 --- /dev/null +++ b/test/ioctl/features.c @@ -0,0 +1,1604 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include + +#include +#include + +#include "mock.h" +#include "util.h" + +#define TEST_FD 0xFD +#define TEST_TIMEOUT 1234 +#define TEST_NSID 0x89ABCDEF +#define TEST_CDW11 0x11111111 +#define TEST_CDW12 0x12121212 +#define TEST_CDW13 0x13131313 +#define TEST_CDW15 0x15151515 +#define TEST_UUIDX 0b1001110 +#define TEST_FID 0xFE +#define TEST_RESULT 0x12345678 +#define TEST_SEL NVME_GET_FEATURES_SEL_SAVED +#define TEST_SC NVME_SC_INVALID_FIELD + +static void test_set_features(void) +{ + uint32_t result = 0; + uint8_t data[256]; + struct nvme_set_features_args args = { + .result = &result, + .data = data, + .args_size = sizeof(args), + .fd = TEST_FD, + .timeout = TEST_TIMEOUT, + .nsid = TEST_NSID, + .cdw11 = TEST_CDW11, + .cdw12 = TEST_CDW12, + .cdw13 = TEST_CDW13, + .cdw15 = TEST_CDW15, + .data_len = sizeof(data), + .save = true, + .uuidx = TEST_UUIDX, + .fid = TEST_FID, + }; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .nsid = TEST_NSID, + .in_data = data, + .data_len = sizeof(data), + .cdw10 = (uint32_t)1 << 31 /* SAVE */ + | TEST_FID, + .cdw11 = TEST_CDW11, + .cdw12 = TEST_CDW12, + .cdw13 = TEST_CDW13, + .cdw14 = TEST_UUIDX, + .cdw15 = TEST_CDW15, + .timeout_ms = TEST_TIMEOUT, + .result = TEST_RESULT, + }; + int err; + + arbitrary(data, sizeof(data)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features(&args); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_features(void) +{ + uint32_t result = 0; + uint8_t data[256], get_data[sizeof(data)] = {}; + struct nvme_get_features_args args = { + .result = &result, + .data = get_data, + .args_size = sizeof(args), + .fd = TEST_FD, + .timeout = TEST_TIMEOUT, + .nsid = TEST_NSID, + .sel = TEST_SEL, + .cdw11 = TEST_CDW11, + .data_len = sizeof(data), + .fid = TEST_FID, + .uuidx = TEST_UUIDX, + }; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .nsid = TEST_NSID, + .data_len = sizeof(data), + .cdw10 = TEST_SEL << 8 | TEST_FID, + .cdw11 = TEST_CDW11, + .cdw14 = TEST_UUIDX, + .timeout_ms = TEST_TIMEOUT, + .out_data = data, + .result = TEST_RESULT, + }; + int err; + + arbitrary(data, sizeof(data)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features(&args); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); + cmp(get_data, data, sizeof(data), "incorrect data"); +} + +static void test_set_features_data(void) +{ + uint8_t data[128]; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .nsid = TEST_NSID, + .in_data = data, + .data_len = sizeof(data), + .cdw10 = TEST_FID, + .cdw11 = TEST_CDW11, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + arbitrary(data, sizeof(data)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_data( + TEST_FD, TEST_FID, TEST_NSID, TEST_CDW11, false, + sizeof(data), data, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_features_data(void) +{ + uint8_t data[128], get_data[sizeof(data)] = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .nsid = TEST_NSID, + .data_len = sizeof(data), + .cdw10 = NVME_GET_FEATURES_SEL_CURRENT << 8 | TEST_FID, + .out_data = data, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + arbitrary(data, sizeof(data)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_data( + TEST_FD, TEST_FID, TEST_NSID, sizeof(data), get_data, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); + cmp(get_data, data, sizeof(data), "incorrect data"); +} + +static void test_set_features_simple(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .nsid = TEST_NSID, + .cdw10 = (uint32_t)1 << 31 /* SAVE */ + | TEST_FID, + .cdw11 = TEST_CDW11, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_simple( + TEST_FD, TEST_FID, TEST_NSID, TEST_CDW11, true, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_features_simple(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .nsid = TEST_NSID, + .cdw10 = NVME_GET_FEATURES_SEL_CURRENT << 8 | TEST_FID, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_simple(TEST_FD, TEST_FID, TEST_NSID, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_arbitration(void) +{ + uint8_t HPW = 0xAA, MPW = 0xBB, LPW = 0xCC, AB = 0b111; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .cdw10 = NVME_FEAT_FID_ARBITRATION, + .cdw11 = (uint32_t)HPW << 24 | MPW << 16 | LPW << 8 | AB, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_arbitration( + TEST_FD, AB, LPW, MPW, HPW, false, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_arbitration(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_ARBITRATION, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_arbitration(TEST_FD, TEST_SEL, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_power_mgmt(void) +{ + uint8_t PS = 0b10101, WH = 0b101; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .cdw10 = (uint32_t)1 << 31 /* SAVE */ + | NVME_FEAT_FID_POWER_MGMT, + .cdw11 = WH << 5 | PS, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_power_mgmt(TEST_FD, PS, WH, true, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_power_mgmt(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_POWER_MGMT, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_power_mgmt(TEST_FD, TEST_SEL, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_lba_range(void) +{ + uint8_t NUM = 64; + struct nvme_lba_range_type range_types; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .nsid = TEST_NSID, + .in_data = &range_types, + .data_len = sizeof(range_types), + .cdw10 = NVME_FEAT_FID_LBA_RANGE, + .cdw11 = NUM - 1, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + arbitrary(&range_types, sizeof(range_types)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_lba_range( + TEST_FD, TEST_NSID, NUM, false, &range_types, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_lba_range(void) +{ + struct nvme_lba_range_type range_types, get_range_types = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .nsid = TEST_NSID, + .data_len = sizeof(range_types), + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_LBA_RANGE, + .out_data = &range_types, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + arbitrary(&range_types, sizeof(range_types)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_lba_range2( + TEST_FD, TEST_SEL, TEST_NSID, &get_range_types, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); + cmp(&get_range_types, &range_types, sizeof(range_types), + "incorrect LBA range types"); +} + +static void test_set_temp_thresh(void) +{ + uint16_t TMPTH = 0xFEDC; + uint8_t TMPSEL = 0x8; + enum nvme_feat_tmpthresh_thsel THSEL = + NVME_FEATURE_TEMPTHRESH_THSEL_UNDER; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .cdw10 = (uint32_t)1 << 31 /* SAVE */ + | NVME_FEAT_FID_TEMP_THRESH, + .cdw11 = THSEL << 20 | TMPSEL << 16 | TMPTH, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_temp_thresh( + TEST_FD, TMPTH, TMPSEL, THSEL, true, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_temp_thresh(void) +{ + /* + * nvme_get_features_temp_thresh() doesn't support + * specifying TMPSEL and THSEL + */ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_TEMP_THRESH, + .cdw11 = NVME_FEATURE_TEMPTHRESH_THSEL_OVER << 20, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_temp_thresh(TEST_FD, TEST_SEL, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_err_recovery(void) +{ + uint16_t TLER = 0xCDEF; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .nsid = TEST_NSID, + .cdw10 = NVME_FEAT_FID_ERR_RECOVERY, + .cdw11 = 1 << 16 /* DULBE */ + | TLER, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_err_recovery( + TEST_FD, TEST_NSID, TLER, true, false, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_err_recovery(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .nsid = TEST_NSID, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_ERR_RECOVERY, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_err_recovery2( + TEST_FD, TEST_SEL, TEST_NSID, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_volatile_wc(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .cdw10 = (uint32_t)1 << 31 /* SAVE */ + | NVME_FEAT_FID_VOLATILE_WC, + .cdw11 = 1 << 0, /* WCE */ + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_volatile_wc(TEST_FD, true, true, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_volatile_wc(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 + | NVME_FEAT_FID_VOLATILE_WC, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_volatile_wc(TEST_FD, TEST_SEL, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_num_queues(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_NUM_QUEUES, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_num_queues(TEST_FD, TEST_SEL, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_irq_coalesce(void) +{ + uint8_t THR = 0xAB, TIME = 0xCD; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .cdw10 = NVME_FEAT_FID_IRQ_COALESCE, + .cdw11 = TIME << 8 | THR, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_irq_coalesce( + TEST_FD, THR, TIME, false, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_irq_coalesce(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_IRQ_COALESCE, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_irq_coalesce(TEST_FD, TEST_SEL, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_irq_config(void) +{ + uint16_t IV = 0x1234; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .cdw10 = (uint32_t)1 << 31 /* SAVE */ + | NVME_FEAT_FID_IRQ_CONFIG, + .cdw11 = 1 << 16 /* CD */ + | IV, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_irq_config(TEST_FD, IV, true, true, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_irq_config(void) +{ + uint16_t IV = 0x5678; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_IRQ_CONFIG, + .cdw11 = IV, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_irq_config(TEST_FD, TEST_SEL, IV, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_write_atomic(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .cdw10 = NVME_FEAT_FID_WRITE_ATOMIC, + .cdw11 = 1 << 0, /* DN */ + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_write_atomic(TEST_FD, true, false, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_write_atomic(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_WRITE_ATOMIC, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_write_atomic(TEST_FD, TEST_SEL, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_async_event(void) +{ + uint32_t EVENTS = 0x87654321; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .cdw10 = (uint32_t)1 << 31 /* SAVE */ + | NVME_FEAT_FID_ASYNC_EVENT, + .cdw11 = EVENTS, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_async_event(TEST_FD, EVENTS, true, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_async_event(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_ASYNC_EVENT, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_async_event(TEST_FD, TEST_SEL, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_auto_pst(void) +{ + struct nvme_feat_auto_pst apst; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .in_data = &apst, + .data_len = sizeof(apst), + .cdw10 = NVME_FEAT_FID_AUTO_PST, + .cdw11 = 1 << 0, /* APSTE */ + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + arbitrary(&apst, sizeof(apst)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_auto_pst(TEST_FD, true, false, &apst, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_auto_pst(void) +{ + struct nvme_feat_auto_pst apst, get_apst = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .data_len = sizeof(apst), + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_AUTO_PST, + .out_data = &apst, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + arbitrary(&apst, sizeof(apst)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_auto_pst(TEST_FD, TEST_SEL, &get_apst, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); + cmp(&get_apst, &apst, sizeof(apst), "incorrect apst"); +} + +static void test_get_host_mem_buf(void) +{ + struct nvme_host_mem_buf_attrs attrs, get_attrs = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .data_len = sizeof(attrs), + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_HOST_MEM_BUF, + .out_data = &attrs, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + arbitrary(&attrs, sizeof(attrs)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_host_mem_buf2( + TEST_FD, TEST_SEL, &get_attrs, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); + cmp(&get_attrs, &attrs, sizeof(attrs), "incorrect attrs"); +} + +static void test_set_timestamp(void) +{ + struct nvme_timestamp ts = {.timestamp = {1, 2, 3, 4, 5, 6}}; + uint64_t timestamp = ts.timestamp[0] + | (uint64_t) ts.timestamp[1] << 8 + | (uint64_t) ts.timestamp[2] << 16 + | (uint64_t) ts.timestamp[3] << 24 + | (uint64_t) ts.timestamp[4] << 32 + | (uint64_t) ts.timestamp[5] << 40; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .in_data = &ts, + .data_len = sizeof(ts), + .cdw10 = (uint32_t)1 << 31 /* SAVE */ + | NVME_FEAT_FID_TIMESTAMP, + }; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_timestamp(TEST_FD, true, timestamp); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); +} + +static void test_get_timestamp(void) +{ + struct nvme_timestamp ts, get_ts = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .data_len = sizeof(ts), + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_TIMESTAMP, + .out_data = &ts, + }; + int err; + + arbitrary(&ts, sizeof(ts)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_timestamp(TEST_FD, TEST_SEL, &get_ts); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + cmp(&get_ts, &ts, sizeof(ts), "incorrect timestamp"); +} + +static void test_get_kato(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_KATO, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_kato(TEST_FD, TEST_SEL, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_hctm(void) +{ + uint16_t TMT2 = 0x4321, TMT1 = 0x8765; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .cdw10 = NVME_FEAT_FID_HCTM, + .cdw11 = (uint32_t)TMT1 << 16 | TMT2, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_hctm(TEST_FD, TMT2, TMT1, false, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_hctm(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_HCTM, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_hctm(TEST_FD, TEST_SEL, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_nopsc(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .cdw10 = (uint32_t)1 << 31 /* SAVE */ + | NVME_FEAT_FID_NOPSC, + .cdw11 = 1 << 0 /* NOPPME */, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_nopsc(TEST_FD, true, true, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_nopsc(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_NOPSC, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_nopsc(TEST_FD, TEST_SEL, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_rrl(void) +{ + uint8_t RRL = 0xA; + uint16_t NVMSETID = 0x1234; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .cdw10 = NVME_FEAT_FID_RRL, + .cdw11 = NVMSETID, + .cdw12 = RRL, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_rrl(TEST_FD, RRL, NVMSETID, false, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_rrl(void) +{ + /* nvme_get_features_rrl() doesn't support specifying the NVMSETID */ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_RRL, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_rrl(TEST_FD, TEST_SEL, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_plm_config(void) +{ + uint16_t NVMSETID = 0xFEDC; + struct nvme_plm_config config; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .in_data = &config, + .data_len = sizeof(config), + .cdw10 = (uint32_t)1 << 31 /* SAVE */ + | NVME_FEAT_FID_PLM_CONFIG, + .cdw11 = NVMSETID, + .cdw12 = 1 << 0 /* Predictable Latency Enable */, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + arbitrary(&config, sizeof(config)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_plm_config( + TEST_FD, true, NVMSETID, true, &config, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_plm_config(void) +{ + uint16_t NVMSETID = 0xABCD; + struct nvme_plm_config config, get_config = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .data_len = sizeof(config), + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_PLM_CONFIG, + .cdw11 = NVMSETID, + .out_data = &config, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + arbitrary(&config, sizeof(config)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_plm_config( + TEST_FD, TEST_SEL, NVMSETID, &get_config, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); + cmp(&get_config, &config, sizeof(config), "incorrect PLM config"); +} + +static void test_set_plm_window(void) +{ + enum nvme_feat_plm_window_select SEL = NVME_FEATURE_PLM_NDWIN; + uint16_t NVMSETID = 0x4321; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .cdw10 = NVME_FEAT_FID_PLM_WINDOW, + .cdw11 = NVMSETID, + .cdw12 = SEL, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_plm_window( + TEST_FD, SEL, NVMSETID, false, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_plm_window(void) +{ + uint16_t NVMSETID = 0x8765; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_PLM_WINDOW, + .cdw11 = NVMSETID, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_plm_window( + TEST_FD, TEST_SEL, NVMSETID, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_lba_sts_interval(void) +{ + uint16_t LSIRI = 0x1234, LSIPI = 0x5678; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .cdw10 = (uint32_t)1 << 31 /* SAVE */ + | NVME_FEAT_FID_LBA_STS_INTERVAL, + .cdw11 = LSIPI << 16 | LSIRI, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_lba_sts_interval( + TEST_FD, LSIRI, LSIPI, true, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_lba_sts_interval(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_LBA_STS_INTERVAL, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_lba_sts_interval(TEST_FD, TEST_SEL, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_host_behavior(void) +{ + /* nvme_set_features_host_behavior() ignores SAVE */ + struct nvme_feat_host_behavior behavior; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .in_data = &behavior, + .data_len = sizeof(behavior), + .cdw10 = NVME_FEAT_FID_HOST_BEHAVIOR, + }; + int err; + + arbitrary(&behavior, sizeof(behavior)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_host_behavior(TEST_FD, true, &behavior); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); +} + +static void test_get_host_behavior(void) +{ + struct nvme_feat_host_behavior behavior, get_behavior = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .data_len = sizeof(behavior), + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_HOST_BEHAVIOR, + .out_data = &behavior, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + arbitrary(&behavior, sizeof(behavior)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_host_behavior( + TEST_FD, TEST_SEL, &get_behavior, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); + cmp(&get_behavior, &behavior, sizeof(behavior), "incorrect behavior"); +} + +static void test_set_sanitize(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .cdw10 = NVME_FEAT_FID_SANITIZE, + .cdw11 = 1 << 0, /* NODRM */ + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_sanitize(TEST_FD, true, false, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_sanitize(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_SANITIZE, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_sanitize(TEST_FD, TEST_SEL, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_endurance_evt_cfg(void) +{ + uint16_t ENDGID = 0x9876; + uint8_t EGWARN = 0xCD; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .cdw10 = (uint32_t)1 << 31 /* SAVE */ + | NVME_FEAT_FID_ENDURANCE_EVT_CFG, + .cdw11 = EGWARN << 16 | ENDGID, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_endurance_evt_cfg( + TEST_FD, ENDGID, EGWARN, true, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_endurance_event_cfg(void) +{ + uint16_t ENDGID = 0x6789; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_ENDURANCE_EVT_CFG, + .cdw11 = ENDGID, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_endurance_event_cfg( + TEST_FD, TEST_SEL, ENDGID, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_iocs_profile(void) +{ + uint16_t IOCSI = 0b101100111; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .cdw10 = NVME_FEAT_FID_IOCS_PROFILE, + .cdw11 = IOCSI, + }; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_iocs_profile(TEST_FD, IOCSI, false); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); +} + +static void test_get_iocs_profile(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_IOCS_PROFILE, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_iocs_profile(TEST_FD, TEST_SEL, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_sw_progress(void) +{ + uint8_t PBSLC = 0xBA; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .cdw10 = (uint32_t)1 << 31 /* SAVE */ + | NVME_FEAT_FID_SW_PROGRESS, + .cdw11 = PBSLC, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_sw_progress(TEST_FD, PBSLC, true, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_sw_progress(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_SW_PROGRESS, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_sw_progress(TEST_FD, TEST_SEL, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_host_id(void) +{ + uint8_t hostid[8]; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .in_data = hostid, + .data_len = sizeof(hostid), + .cdw10 = (uint32_t)1 << 31 /* SAVE */ + | NVME_FEAT_FID_HOST_ID, + .result = TEST_RESULT, + }; + int err; + + arbitrary(hostid, sizeof(hostid)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_host_id(TEST_FD, false, true, hostid); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); +} + +static void test_set_host_id_extended(void) +{ + uint8_t hostid[16]; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .in_data = hostid, + .data_len = sizeof(hostid), + .cdw10 = NVME_FEAT_FID_HOST_ID, + .cdw11 = 1 << 0, /* EXHID */ + .result = TEST_RESULT, + }; + int err; + + arbitrary(hostid, sizeof(hostid)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_host_id(TEST_FD, true, false, hostid); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); +} + +static void test_get_host_id(void) +{ + uint8_t hostid[8], get_hostid[sizeof(hostid)] = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .data_len = sizeof(hostid), + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_HOST_ID, + .out_data = hostid, + .result = TEST_RESULT, + }; + int err; + + arbitrary(hostid, sizeof(hostid)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_host_id( + TEST_FD, TEST_SEL, false, sizeof(hostid), get_hostid); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + cmp(get_hostid, hostid, sizeof(hostid), "incorrect host identifier"); +} + +static void test_get_host_id_extended(void) +{ + uint8_t hostid[16], get_hostid[sizeof(hostid)] = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .data_len = sizeof(hostid), + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_HOST_ID, + .cdw11 = 1 << 0, /* EXHID */ + .out_data = hostid, + .result = TEST_RESULT, + }; + int err; + + arbitrary(hostid, sizeof(hostid)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_host_id( + TEST_FD, TEST_SEL, true, sizeof(hostid), get_hostid); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + cmp(get_hostid, hostid, sizeof(hostid), "incorrect host identifier"); +} + +static void test_set_resv_mask(void) +{ + uint32_t MASK = 0x23456789; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .nsid = TEST_NSID, + .cdw10 = (uint32_t)1 << 31 /* SAVE */ + | NVME_FEAT_FID_RESV_MASK, + .cdw11 = MASK, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_resv_mask2( + TEST_FD, TEST_NSID, MASK, true, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_resv_mask(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .nsid = TEST_NSID, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_RESV_MASK, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_resv_mask2( + TEST_FD, TEST_SEL, TEST_NSID, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_resv_persist(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .nsid = TEST_NSID, + .cdw10 = NVME_FEAT_FID_RESV_PERSIST, + .cdw11 = 1 << 0, /* PTPL */ + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_resv_persist2( + TEST_FD, TEST_NSID, true, false, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_resv_persist(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .nsid = TEST_NSID, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_RESV_PERSIST, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_resv_persist2( + TEST_FD, TEST_SEL, TEST_NSID, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_write_protect(void) +{ + /* nvme_set_features_write_protect() ignores SAVE */ + enum nvme_feat_nswpcfg_state STATE = + NVME_FEAT_NS_WRITE_PROTECT_PERMANENT; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .nsid = TEST_NSID, + .cdw10 = NVME_FEAT_FID_WRITE_PROTECT, + .cdw11 = STATE, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_write_protect2( + TEST_FD, TEST_NSID, STATE, true, &result); + end_mock_cmds(); + check(err == 0, "set features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_write_protect(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .nsid = TEST_NSID, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_WRITE_PROTECT, + .result = TEST_RESULT, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_write_protect( + TEST_FD, TEST_NSID, TEST_SEL, &result); + end_mock_cmds(); + check(err == 0, "get features returned error %d, errno %m", err); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +/* + * All set_features functions tail-call nvme_set_features(), + * so testing errors in any of them will do + */ + +static void test_set_status_code_error(void) +{ + uint32_t EVENTS = 0x12345678; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .cdw10 = NVME_FEAT_FID_ASYNC_EVENT, + .cdw11 = EVENTS, + .result = TEST_RESULT, + .err = TEST_SC, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_async_event(TEST_FD, EVENTS, false, &result); + end_mock_cmds(); + check(err == TEST_SC, "got error %d, expected %d", err, TEST_SC); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_set_kernel_error(void) +{ + uint32_t MASK = 0x87654321; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_set_features, + .nsid = TEST_NSID, + .cdw10 = NVME_FEAT_FID_RESV_MASK, + .cdw11 = MASK, + .result = TEST_RESULT, + .err = -EIO, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_set_features_resv_mask2( + TEST_FD, TEST_NSID, MASK, false, &result); + end_mock_cmds(); + check(err == -1, "got error %d, expected -1", err); + check(errno == EIO, "unexpected error %m"); + check(!result, "result unexpectedly set to %" PRIu32, result); +} + +/* + * All get_features functions tail-call nvme_get_features(), + * so testing errors in any of them will do + */ + +static void test_get_status_code_error(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_KATO, + .result = TEST_RESULT, + .err = TEST_SC, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_kato(TEST_FD, TEST_SEL, &result); + end_mock_cmds(); + check(err == TEST_SC, "got error %d, expected %d", err, TEST_SC); + check(result == TEST_RESULT, + "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT); +} + +static void test_get_kernel_error(void) +{ + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_get_features, + .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_NUM_QUEUES, + .result = TEST_RESULT, + .err = -EBUSY, + }; + uint32_t result = 0; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_get_features_num_queues(TEST_FD, TEST_SEL, &result); + end_mock_cmds(); + check(err == -1, "got error %d, expected -1", err); + check(errno == EBUSY, "unexpected error %m"); + check(!result, "result unexpectedly set to %" PRIu32, result); +} + +static void run_test(const char *test_name, void (*test_fn)(void)) +{ + printf("Running test %s...", test_name); + fflush(stdout); + test_fn(); + puts(" OK"); +} + +#define RUN_TEST(name) run_test(#name, test_ ## name) + +int main(void) +{ + set_mock_fd(TEST_FD); + RUN_TEST(set_features); + RUN_TEST(get_features); + RUN_TEST(set_features_data); + RUN_TEST(get_features_data); + RUN_TEST(set_features_simple); + RUN_TEST(get_features_simple); + RUN_TEST(set_arbitration); + RUN_TEST(get_arbitration); + RUN_TEST(set_power_mgmt); + RUN_TEST(get_power_mgmt); + RUN_TEST(set_lba_range); + RUN_TEST(get_lba_range); + RUN_TEST(set_temp_thresh); + RUN_TEST(get_temp_thresh); + RUN_TEST(set_err_recovery); + RUN_TEST(get_err_recovery); + RUN_TEST(set_volatile_wc); + RUN_TEST(get_volatile_wc); + RUN_TEST(get_num_queues); + RUN_TEST(set_irq_coalesce); + RUN_TEST(get_irq_coalesce); + RUN_TEST(set_irq_config); + RUN_TEST(get_irq_config); + RUN_TEST(set_write_atomic); + RUN_TEST(get_write_atomic); + RUN_TEST(set_async_event); + RUN_TEST(get_async_event); + RUN_TEST(set_auto_pst); + RUN_TEST(get_auto_pst); + RUN_TEST(get_host_mem_buf); + RUN_TEST(set_timestamp); + RUN_TEST(get_timestamp); + RUN_TEST(get_kato); + RUN_TEST(set_hctm); + RUN_TEST(get_hctm); + RUN_TEST(set_nopsc); + RUN_TEST(get_nopsc); + RUN_TEST(set_rrl); + RUN_TEST(get_rrl); + RUN_TEST(set_plm_config); + RUN_TEST(get_plm_config); + RUN_TEST(set_plm_window); + RUN_TEST(get_plm_window); + RUN_TEST(set_lba_sts_interval); + RUN_TEST(get_lba_sts_interval); + RUN_TEST(set_host_behavior); + RUN_TEST(get_host_behavior); + RUN_TEST(set_sanitize); + RUN_TEST(get_sanitize); + RUN_TEST(set_endurance_evt_cfg); + RUN_TEST(get_endurance_event_cfg); + RUN_TEST(set_iocs_profile); + RUN_TEST(get_iocs_profile); + RUN_TEST(set_sw_progress); + RUN_TEST(get_sw_progress); + RUN_TEST(set_host_id); + RUN_TEST(set_host_id_extended); + RUN_TEST(get_host_id); + RUN_TEST(get_host_id_extended); + RUN_TEST(set_resv_mask); + RUN_TEST(get_resv_mask); + RUN_TEST(set_resv_persist); + RUN_TEST(get_resv_persist); + RUN_TEST(set_write_protect); + RUN_TEST(get_write_protect); + RUN_TEST(set_status_code_error); + RUN_TEST(set_kernel_error); + RUN_TEST(get_status_code_error); + RUN_TEST(get_kernel_error); +} diff --git a/test/ioctl/identify.c b/test/ioctl/identify.c new file mode 100644 index 0000000..ccde02b --- /dev/null +++ b/test/ioctl/identify.c @@ -0,0 +1,572 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include + +#include +#include + +#include "mock.h" +#include "util.h" + +#define TEST_FD 0xFD +#define TEST_NSID 0x12345678 +#define TEST_NVMSETID 0xABCD +#define TEST_UUID 123 +#define TEST_CSI NVME_CSI_KV +#define TEST_CNTID 0x4321 +#define TEST_DOMID 0xFEDC +#define TEST_ENDGID 0x0123 +#define TEST_SC NVME_SC_INVALID_FIELD + +static void test_ns(void) +{ + struct nvme_id_ns expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .nsid = TEST_NSID, + .data_len = sizeof(expected_id), + .cdw10 = NVME_IDENTIFY_CNS_NS, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_ns(TEST_FD, TEST_NSID, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_ctrl(void) +{ + struct nvme_id_ctrl expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .data_len = sizeof(expected_id), + .cdw10 = NVME_IDENTIFY_CNS_CTRL, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_ctrl(TEST_FD, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_active_ns_list(void) +{ + struct nvme_ns_list expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .nsid = TEST_NSID, + .data_len = sizeof(expected_id), + .cdw10 = NVME_IDENTIFY_CNS_NS_ACTIVE_LIST, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_active_ns_list(TEST_FD, TEST_NSID, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_ns_descs(void) +{ + uint8_t expected_id[NVME_IDENTIFY_DATA_SIZE]; + struct nvme_ns_id_desc *id; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .nsid = TEST_NSID, + .data_len = sizeof(expected_id), + .cdw10 = NVME_IDENTIFY_CNS_NS_DESC_LIST, + .out_data = &expected_id, + }; + int err; + + arbitrary(expected_id, sizeof(expected_id)); + id = calloc(1, NVME_IDENTIFY_DATA_SIZE); + check(id, "memory allocation failed"); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_ns_descs(TEST_FD, TEST_NSID, id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(id, expected_id, sizeof(expected_id), "incorrect identify data"); + free(id); +} + +static void test_nvmset_list(void) +{ + struct nvme_id_nvmset_list expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .data_len = sizeof(expected_id), + .cdw10 = NVME_IDENTIFY_CNS_NVMSET_LIST, + .cdw11 = TEST_NVMSETID, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_nvmset_list(TEST_FD, TEST_NVMSETID, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_ns_csi(void) +{ + uint8_t expected_id[NVME_IDENTIFY_DATA_SIZE]; + uint8_t id[NVME_IDENTIFY_DATA_SIZE] = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .nsid = TEST_NSID, + .data_len = sizeof(expected_id), + .cdw10 = NVME_IDENTIFY_CNS_CSI_NS, + .cdw11 = TEST_CSI << 24, + .cdw14 = TEST_UUID, + .out_data = expected_id, + }; + int err; + + arbitrary(expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_ns_csi(TEST_FD, TEST_NSID, TEST_UUID, TEST_CSI, id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(id, expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_zns_identify_ns(void) +{ + struct nvme_zns_id_ns expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .nsid = TEST_NSID, + .data_len = sizeof(expected_id), + .cdw10 = NVME_IDENTIFY_CNS_CSI_NS, + .cdw11 = NVME_CSI_ZNS << 24, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_zns_identify_ns(TEST_FD, TEST_NSID, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_nvm_identify_ctrl(void) +{ + struct nvme_id_ctrl_nvm expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .data_len = sizeof(expected_id), + .cdw10 = NVME_IDENTIFY_CNS_CSI_CTRL, + .cdw11 = NVME_CSI_NVM << 24, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_nvm_identify_ctrl(TEST_FD, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_zns_identify_ctrl(void) +{ + struct nvme_zns_id_ctrl expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .data_len = sizeof(expected_id), + .cdw10 = NVME_IDENTIFY_CNS_CSI_CTRL, + .cdw11 = NVME_CSI_ZNS << 24, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_zns_identify_ctrl(TEST_FD, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_active_ns_list_csi(void) +{ + struct nvme_ns_list expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .nsid = TEST_NSID, + .data_len = sizeof(expected_id), + .cdw10 = NVME_IDENTIFY_CNS_CSI_NS_ACTIVE_LIST, + .cdw11 = TEST_CSI << 24, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_active_ns_list_csi( + TEST_FD, TEST_NSID, TEST_CSI, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_independent_identify_ns(void) +{ + struct nvme_id_independent_id_ns expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .nsid = TEST_NSID, + .data_len = sizeof(expected_id), + .cdw10 = NVME_IDENTIFY_CNS_CSI_INDEPENDENT_ID_NS, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + /* That's a mouthful! */ + err = nvme_identify_independent_identify_ns(TEST_FD, TEST_NSID, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_allocated_ns_list(void) +{ + struct nvme_ns_list expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .nsid = TEST_NSID, + .data_len = sizeof(expected_id), + .cdw10 = NVME_IDENTIFY_CNS_ALLOCATED_NS_LIST, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_allocated_ns_list(TEST_FD, TEST_NSID, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_allocated_ns(void) +{ + struct nvme_id_ns expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .nsid = TEST_NSID, + .data_len = sizeof(expected_id), + .cdw10 = NVME_IDENTIFY_CNS_ALLOCATED_NS, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_allocated_ns(TEST_FD, TEST_NSID, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_nsid_ctrl_list(void) +{ + struct nvme_ctrl_list expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .nsid = TEST_NSID, + .data_len = sizeof(expected_id), + .cdw10 = TEST_CNTID << 16 + | NVME_IDENTIFY_CNS_NS_CTRL_LIST, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_nsid_ctrl_list(TEST_FD, TEST_NSID, TEST_CNTID, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_ctrl_list(void) +{ + struct nvme_ctrl_list expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .data_len = sizeof(expected_id), + .cdw10 = TEST_CNTID << 16 + | NVME_IDENTIFY_CNS_CTRL_LIST, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_ctrl_list(TEST_FD, TEST_CNTID, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_primary_ctrl(void) +{ + struct nvme_primary_ctrl_cap expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .data_len = sizeof(expected_id), + .cdw10 = TEST_CNTID << 16 + | NVME_IDENTIFY_CNS_PRIMARY_CTRL_CAP, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_primary_ctrl(TEST_FD, TEST_CNTID, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_secondary_ctrl_list(void) +{ + struct nvme_secondary_ctrl_list expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .data_len = sizeof(expected_id), + .cdw10 = TEST_CNTID << 16 + | NVME_IDENTIFY_CNS_SECONDARY_CTRL_LIST, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_secondary_ctrl_list(TEST_FD, TEST_CNTID, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_ns_granularity(void) +{ + struct nvme_id_ns_granularity_list expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .data_len = sizeof(expected_id), + .cdw10 = NVME_IDENTIFY_CNS_NS_GRANULARITY, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_ns_granularity(TEST_FD, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_uuid(void) +{ + struct nvme_id_uuid_list expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .data_len = sizeof(expected_id), + .cdw10 = NVME_IDENTIFY_CNS_UUID_LIST, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_uuid(TEST_FD, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_domain_list(void) +{ + struct nvme_id_domain_list expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .data_len = sizeof(expected_id), + .cdw10 = NVME_IDENTIFY_CNS_DOMAIN_LIST, + .cdw11 = TEST_DOMID, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_domain_list(TEST_FD, TEST_DOMID, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_endurance_group_list(void) +{ + struct nvme_id_endurance_group_list expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .data_len = sizeof(expected_id), + .cdw10 = NVME_IDENTIFY_CNS_ENDURANCE_GROUP_ID, + .cdw11 = TEST_ENDGID, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_endurance_group_list(TEST_FD, TEST_ENDGID, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_allocated_ns_list_csi(void) +{ + struct nvme_ns_list expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .nsid = TEST_NSID, + .data_len = sizeof(expected_id), + .cdw10 = NVME_IDENTIFY_CNS_CSI_ALLOCATED_NS_LIST, + .cdw11 = TEST_CSI << 24, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_allocated_ns_list_csi( + TEST_FD, TEST_NSID, TEST_CSI, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +static void test_iocs(void) +{ + struct nvme_id_iocs expected_id, id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .data_len = sizeof(expected_id), + .cdw10 = TEST_CNTID << 16 + | NVME_IDENTIFY_CNS_COMMAND_SET_STRUCTURE, + .out_data = &expected_id, + }; + int err; + + arbitrary(&expected_id, sizeof(expected_id)); + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_iocs(TEST_FD, TEST_CNTID, &id); + end_mock_cmds(); + check(err == 0, "identify returned error %d, errno %m", err); + cmp(&id, &expected_id, sizeof(id), "incorrect identify data"); +} + +/* + * All identify functions tail-call nvme_identify(), + * so testing errors in any of them will do + */ + +static void test_status_code_error(void) +{ + struct nvme_id_nvmset_list id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .data_len = sizeof(id), + .cdw10 = NVME_IDENTIFY_CNS_NVMSET_LIST, + .cdw11 = TEST_NVMSETID, + .err = TEST_SC, + }; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_nvmset_list(TEST_FD, TEST_NVMSETID, &id); + end_mock_cmds(); + check(err == TEST_SC, "got error %d, expected %d", err, TEST_SC); +} + +static void test_kernel_error(void) +{ + struct nvme_id_ns id = {}; + struct mock_cmd mock_admin_cmd = { + .opcode = nvme_admin_identify, + .nsid = TEST_NSID, + .data_len = sizeof(id), + .cdw10 = NVME_IDENTIFY_CNS_NS, + .err = -EIO, + }; + int err; + + set_mock_admin_cmds(&mock_admin_cmd, 1); + err = nvme_identify_ns(TEST_FD, TEST_NSID, &id); + end_mock_cmds(); + check(err == -1, "got error %d, expected -1", err); + check(errno == EIO, "unexpected error %m"); +} + +static void run_test(const char *test_name, void (*test_fn)(void)) +{ + printf("Running test %s...", test_name); + fflush(stdout); + test_fn(); + puts(" OK"); +} + +#define RUN_TEST(name) run_test(#name, test_ ## name) + +int main(void) +{ + set_mock_fd(TEST_FD); + RUN_TEST(ns); + RUN_TEST(ctrl); + RUN_TEST(active_ns_list); + RUN_TEST(ns_descs); + RUN_TEST(nvmset_list); + RUN_TEST(ns_csi); + RUN_TEST(zns_identify_ns); + RUN_TEST(nvm_identify_ctrl); + RUN_TEST(zns_identify_ctrl); + RUN_TEST(active_ns_list_csi); + RUN_TEST(independent_identify_ns); + RUN_TEST(allocated_ns_list); + RUN_TEST(allocated_ns); + RUN_TEST(nsid_ctrl_list); + RUN_TEST(ctrl_list); + RUN_TEST(primary_ctrl); + RUN_TEST(secondary_ctrl_list); + RUN_TEST(ns_granularity); + RUN_TEST(uuid); + RUN_TEST(domain_list); + RUN_TEST(endurance_group_list); + RUN_TEST(allocated_ns_list_csi); + RUN_TEST(iocs); + RUN_TEST(status_code_error); + RUN_TEST(kernel_error); +} diff --git a/test/ioctl/meson.build b/test/ioctl/meson.build new file mode 100644 index 0000000..b329d27 --- /dev/null +++ b/test/ioctl/meson.build @@ -0,0 +1,42 @@ +mock_ioctl = library( + 'mock-ioctl', + ['mock.c', 'util.c'], +) + +# Add mock-ioctl to the LD_PRELOAD path so it overrides libc. +# Append to LD_PRELOAD so existing libraries, e.g. libasan, are kept. +# If libasan isn't specified in the LD_PRELOAD path, ASAN warns about mock-ioctl +# being loaded first because its memory allocations might not get intercepted. +# But it appears this isn't a problem; ASAN errors in mock-ioctl are reported. +# This is likely because the executable still links with libasan before libc. +mock_ioctl_env = environment() +mock_ioctl_env.append('LD_PRELOAD', mock_ioctl.full_path()) +mock_ioctl_env.set('ASAN_OPTIONS', 'verify_asan_link_order=0') + +discovery = executable( + 'test-discovery', + 'discovery.c', + dependencies: libnvme_dep, + include_directories: [incdir, internal_incdir], + link_with: mock_ioctl, +) + +test('discovery', discovery, env: mock_ioctl_env) + +features = executable( + 'test-features', + 'features.c', + dependencies: libnvme_dep, + link_with: mock_ioctl, +) + +test('features', features, env: mock_ioctl_env) + +identify = executable( + 'test-identify', + 'identify.c', + dependencies: libnvme_dep, + link_with: mock_ioctl, +) + +test('identify', identify, env: mock_ioctl_env) diff --git a/test/ioctl/mock.c b/test/ioctl/mock.c new file mode 100644 index 0000000..a97a357 --- /dev/null +++ b/test/ioctl/mock.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "mock.h" + +#include +#include +#include +#include +#include + +#include "../../src/nvme/ioctl.h" +#include "util.h" + +struct mock_cmds { + const char *name; + const struct mock_cmd *cmds; + size_t remaining_cmds; +}; + +static int mock_fd = -1; +static struct mock_cmds mock_admin_cmds = {.name = "admin"}; +static struct mock_cmds mock_io_cmds = {.name = "IO"}; + +static void set_mock_cmds( + struct mock_cmds *mock_cmds, const struct mock_cmd *cmds, size_t len) +{ + mock_cmds->cmds = cmds; + mock_cmds->remaining_cmds = len; +} + +static void mock_cmds_done(const struct mock_cmds *mock_cmds) +{ + check(!mock_cmds->remaining_cmds, + "%zu %s commands not executed", + mock_cmds->remaining_cmds, mock_cmds->name); +} + +void set_mock_fd(int fd) +{ + mock_fd = fd; +} + +void set_mock_admin_cmds(const struct mock_cmd *cmds, size_t len) +{ + set_mock_cmds(&mock_admin_cmds, cmds, len); +} + +void set_mock_io_cmds(const struct mock_cmd *cmds, size_t len) +{ + set_mock_cmds(&mock_io_cmds, cmds, len); +} + +void end_mock_cmds(void) +{ + mock_cmds_done(&mock_admin_cmds); + mock_cmds_done(&mock_io_cmds); +} + +#define execute_ioctl(cmd, mock_cmd) ({ \ + check((cmd)->opcode == (mock_cmd)->opcode, \ + "got opcode %" PRIu8 ", expected %" PRIu8, \ + (cmd)->opcode, (mock_cmd)->opcode); \ + check((cmd)->flags == (mock_cmd)->flags, \ + "got flags %" PRIu8 ", expected %" PRIu8, \ + (cmd)->flags, (mock_cmd)->flags); \ + check((cmd)->nsid == (mock_cmd)->nsid, \ + "got nsid %" PRIu32 ", expected %" PRIu32, \ + (cmd)->nsid, (mock_cmd)->nsid); \ + check((cmd)->cdw2 == (mock_cmd)->cdw2, \ + "got cdw2 %" PRIu32 ", expected %" PRIu32, \ + (cmd)->cdw2, (mock_cmd)->cdw2); \ + check((cmd)->cdw3 == (mock_cmd)->cdw3, \ + "got cdw3 %" PRIu32 ", expected %" PRIu32, \ + (cmd)->cdw3, (mock_cmd)->cdw3); \ + check((cmd)->metadata_len == (mock_cmd)->metadata_len, \ + "got metadata_len %" PRIu32 ", expected %" PRIu32, \ + (cmd)->metadata_len, (mock_cmd)->metadata_len); \ + if ((cmd)->metadata_len) { \ + cmp((void const *)(uintptr_t)(cmd)->metadata, \ + (mock_cmd)->metadata, \ + (cmd)->metadata_len, \ + "incorrect metadata"); \ + } \ + __u32 data_len = (cmd)->data_len; \ + check(data_len == (mock_cmd)->data_len, \ + "got data_len %" PRIu32 ", expected %" PRIu32, \ + data_len, (mock_cmd)->data_len); \ + void *data = (void *)(uintptr_t)(cmd)->addr; \ + if ((mock_cmd)->in_data) { \ + cmp(data, (mock_cmd)->in_data, data_len, "incorrect data"); \ + } \ + check((cmd)->cdw10 == (mock_cmd)->cdw10, \ + "got cdw10 %" PRIu32 ", expected %" PRIu32, \ + (cmd)->cdw10, (mock_cmd)->cdw10); \ + check((cmd)->cdw11 == (mock_cmd)->cdw11, \ + "got cdw11 %" PRIu32 ", expected %" PRIu32, \ + (cmd)->cdw11, (mock_cmd)->cdw11); \ + check((cmd)->cdw12 == (mock_cmd)->cdw12, \ + "got cdw12 %" PRIu32 ", expected %" PRIu32, \ + (cmd)->cdw12, (mock_cmd)->cdw12); \ + check((cmd)->cdw13 == (mock_cmd)->cdw13, \ + "got cdw13 %" PRIu32 ", expected %" PRIu32, \ + (cmd)->cdw13, (mock_cmd)->cdw13); \ + check((cmd)->cdw14 == (mock_cmd)->cdw14, \ + "got cdw14 %" PRIu32 ", expected %" PRIu32, \ + (cmd)->cdw14, (mock_cmd)->cdw14); \ + check((cmd)->cdw15 == (mock_cmd)->cdw15, \ + "got cdw15 %" PRIu32 ", expected %" PRIu32, \ + (cmd)->cdw15, (mock_cmd)->cdw15); \ + check((cmd)->timeout_ms == (mock_cmd)->timeout_ms, \ + "got timeout_ms %" PRIu32 ", expected %" PRIu32, \ + (cmd)->timeout_ms, (mock_cmd)->timeout_ms); \ + (cmd)->result = (mock_cmd)->result; \ + if ((mock_cmd)->out_data) { \ + memcpy(data, (mock_cmd)->out_data, data_len); \ + } \ +}) + +#ifdef HAVE_GLIBC_IOCTL +int ioctl(int fd, unsigned long request, ...) +#else +int ioctl(int fd, int request, ...) +#endif +{ + struct mock_cmds *mock_cmds; + bool result64; + const struct mock_cmd *mock_cmd; + va_list args; + void *cmd; + + check(fd == mock_fd, "got fd %d, expected %d", fd, mock_fd); + switch (request) { + case NVME_IOCTL_ADMIN_CMD: + mock_cmds = &mock_admin_cmds; + result64 = false; + break; + case NVME_IOCTL_ADMIN64_CMD: + mock_cmds = &mock_admin_cmds; + result64 = true; + break; + case NVME_IOCTL_IO_CMD: + mock_cmds = &mock_io_cmds; + result64 = false; + break; + case NVME_IOCTL_IO64_CMD: + mock_cmds = &mock_io_cmds; + result64 = true; + break; + default: + fail("unexpected %s %lu", __func__, (unsigned long) request); + } + check(mock_cmds->remaining_cmds, + "unexpected %s command", mock_cmds->name); + mock_cmd = mock_cmds->cmds++; + mock_cmds->remaining_cmds--; + + va_start(args, request); + cmd = va_arg(args, void *); + va_end(args); + if (result64) { + execute_ioctl((struct nvme_passthru_cmd64 *)cmd, mock_cmd); + } else { + check((uint32_t)mock_cmd->result == mock_cmd->result, + "expected 64-bit %s for result %" PRIu64, + __func__, mock_cmd->result); + execute_ioctl((struct nvme_passthru_cmd *)cmd, mock_cmd); + } + if (mock_cmd->err < 0) { + errno = -mock_cmd->err; + return -1; + } + + return mock_cmd->err; +} diff --git a/test/ioctl/mock.h b/test/ioctl/mock.h new file mode 100644 index 0000000..192eba8 --- /dev/null +++ b/test/ioctl/mock.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef _LIBNVME_TEST_IOCTL_MOCK_H +#define _LIBNVME_TEST_IOCTL_MOCK_H + +#include +#include + +/** + * struct mock_cmd - a mock NVMe passthru ioctl() invocation + * @opcode: the expected `opcode` passed to ioctl() + * @flags: the expected `flags` passed to ioctl() + * @nsid: the expected `nsid` passed to ioctl() + * @cdw2: the expected `cdw2` passed to ioctl() + * @cdw3: the expected `cdw3` passed to ioctl() + * @metadata: the expected `metadata` of length `metadata_len` passed to ioctl() + * @in_data: the expected `addr` of length `data_len` passed to ioctl(). + * Set this to NULL to skip checking the data, + * for example if the command is in the read direction. + * @metadata_len: the expected `metadata_len` passed to ioctl() + * @data_len: the expected `data_len` passed to ioctl() + * @cdw10: the expected `cdw10` passed to ioctl() + * @cdw11: the expected `cdw11` passed to ioctl() + * @cdw12: the expected `cdw12` passed to ioctl() + * @cdw13: the expected `cdw13` passed to ioctl() + * @cdw14: the expected `cdw14` passed to ioctl() + * @cdw15: the expected `cdw15` passed to ioctl() + * @timeout_ms: the expected `timeout_ms` passed to ioctl() + * @out_data: if not NULL, `data_len` bytes to copy to the caller's `addr` + * @result: copied to the caller's `result`. + * If `result` doesn't fit in a u32, the ioctl() must be the 64-bit one. + * @err: If negative, ioctl() returns -1 and sets `errno` to `-err`. + * Otherwise, ioctl() returns `err`, representing a NVMe status code. + */ +struct mock_cmd { + uint8_t opcode; + uint8_t flags; + uint32_t nsid; + uint32_t cdw2; + uint32_t cdw3; + const void *metadata; + const void *in_data; + uint32_t metadata_len; + uint32_t data_len; + uint32_t cdw10; + uint32_t cdw11; + uint32_t cdw12; + uint32_t cdw13; + uint32_t cdw14; + uint32_t cdw15; + uint32_t timeout_ms; + const void *out_data; + uint64_t result; + int err; +}; + +/** + * set_mock_fd() - sets the expected file descriptor for NVMe passthru ioctls() + * @fd: file descriptor expected to be passed to ioctl() + */ +void set_mock_fd(int fd); + +/** + * set_mock_admin_cmds() - mocks NVMe admin passthru ioctl() invocations + * @cmds: pointer to start of the mock_cmd slice + * @len: length of the mock_cmd slice (number of ioctl() invocations) + * + * Provides a sequence of mocks for NVMe admin passthru ioctl() invocations. + * Each ioctl() consumes the next mock from the sequence. + * Its arguments are checked against the mock's expected arguments, + * aborting the process if unexpected arguments are passed. + * The mock results (return value, NVMe result and data) + * are returned from the ioctl(). + * + * Analogous to set_mock_io_cmds(), but for admin commands. + * Both admin and IO mocks can be active at the same time. + */ +void set_mock_admin_cmds(const struct mock_cmd *cmds, size_t len); + +/** + * set_mock_io_cmds() - mocks NVMe IO passthru ioctl() invocations + * @cmds: pointer to start of the mock_cmd slice + * @len: length of the mock_cmd slice (number of ioctl() invocations) + * + * Provides a sequence of mocks for NVMe IO passthru ioctl() invocations. + * Each ioctl() consumes the next mock from the sequence. + * Its arguments are checked against the mock's expected arguments, + * aborting the process if unexpected arguments are passed. + * The mock results (return value, NVMe result and data) + * are returned from the ioctl(). + * + * Analogous to set_mock_admin_cmds(), but for IO commands. + * Both admin and IO mocks can be active at the same time. + */ +void set_mock_io_cmds(const struct mock_cmd *cmds, size_t len); + +/** + * end_mock_cmds() - finishes mocking NVMe passthru ioctl() invocations + * + * Checks that all mock ioctl() invocations were performed. + */ +void end_mock_cmds(void); + +#endif /* #ifndef _LIBNVME_TEST_IOCTL_MOCK_H */ diff --git a/test/ioctl/util.c b/test/ioctl/util.c new file mode 100644 index 0000000..09c6e7f --- /dev/null +++ b/test/ioctl/util.c @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "util.h" + +#include +#include +#include +#include +#include + +static void hexdump(const uint8_t *buf, size_t len) +{ + size_t i = 0; + + if (!len) + return; + + for (;;) { + fprintf(stderr, "%02X", buf[i++]); + if (i >= len) + break; + + fputc(i % 16 > 0 ? ' ' : '\n', stderr); + } + fputc('\n', stderr); +} + +void fail(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fputc('\n', stderr); + abort(); +} + +void cmp(const void *actual, const void *expected, size_t len, const char *msg) +{ + if (memcmp(actual, expected, len) == 0) + return; + + fputs(msg, stderr); + fputs("\nactual:\n", stderr); + hexdump(actual, len); + fputs("expected:\n", stderr); + hexdump(expected, len); + abort(); +} + +void arbitrary(void *buf_, size_t len) +{ + uint8_t *buf = buf_; + + while (len--) + *(buf++) = rand(); +} + +size_t arbitrary_range(size_t max) +{ + size_t value; + arbitrary(&value, sizeof(value)); + return value % max; +} diff --git a/test/ioctl/util.h b/test/ioctl/util.h new file mode 100644 index 0000000..aa86422 --- /dev/null +++ b/test/ioctl/util.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef _LIBNVME_TEST_IOCTL_UTIL_H +#define _LIBNVME_TEST_IOCTL_UTIL_H + +#include +#include + +noreturn void fail(const char *fmt, ...) __attribute__((format(printf, 1, 2))); + +#define check(condition, fmt...) ((condition) || (fail(fmt), 0)) + +void cmp(const void *actual, const void *expected, size_t len, const char *msg); + +void arbitrary(void *buf, size_t len); + +size_t arbitrary_range(size_t max); + +#endif /* #ifndef _LIBNVME_TEST_IOCTL_UTIL_H */ diff --git a/test/meson.build b/test/meson.build index 49cd1ac..2b4c6d8 100644 --- a/test/meson.build +++ b/test/meson.build @@ -67,21 +67,33 @@ uuid = executable( test('uuid', uuid) if conf.get('HAVE_NETDB') + mock_ifaddrs = library( + 'mock-ifaddrs', + ['mock-ifaddrs.c', ], + ) + + # See comment in test/ioctl/meson.build explaining how LD_PRELOAD is used + mock_ifaddrs_env = environment() + mock_ifaddrs_env.append('LD_PRELOAD', mock_ifaddrs.full_path()) + mock_ifaddrs_env.set('ASAN_OPTIONS', 'verify_asan_link_order=0') + tree = executable( 'tree', ['tree.c'], dependencies: libnvme_dep, - include_directories: [incdir, internal_incdir] + include_directories: [incdir, internal_incdir], + link_with: mock_ifaddrs, ) - test('tree', tree) + test('tree', tree, env: mock_ifaddrs_env) test_util = executable( 'test-util', ['test-util.c'], include_directories: [incdir, internal_incdir] ) - test('Test util.c', test_util) + test('util', test_util) endif +subdir('ioctl') subdir('nbft') diff --git a/test/mi-mctp.c b/test/mi-mctp.c index 6d83d42..5711c03 100644 --- a/test/mi-mctp.c +++ b/test/mi-mctp.c @@ -83,12 +83,14 @@ static void test_set_tx_mic(struct test_peer *peer) { extern __u32 nvme_mi_crc32_update(__u32 crc, void *data, size_t len); __u32 crc = 0xffffffff; + __le32 crc_le; - assert(peer->tx_buf_len + sizeof(crc) <= MAX_BUFSIZ); + assert(peer->tx_buf_len + sizeof(crc_le) <= MAX_BUFSIZ); crc = nvme_mi_crc32_update(crc, peer->tx_buf, peer->tx_buf_len); - *(uint32_t *)(peer->tx_buf + peer->tx_buf_len) = cpu_to_le32(~crc); - peer->tx_buf_len += sizeof(crc); + crc_le = cpu_to_le32(~crc); + memcpy(peer->tx_buf + peer->tx_buf_len, &crc_le, sizeof(crc_le)); + peer->tx_buf_len += sizeof(crc_le); } int __wrap_socket(int family, int type, int protocol) @@ -293,6 +295,89 @@ static void test_mi_resp_err(nvme_mi_ep_t ep, struct test_peer *peer) assert(rc == 0x2); } +static void setup_unaligned_ctrl_list_resp(struct test_peer *peer) +{ + /* even number of controllers */ + peer->tx_buf[8] = 0x02; + peer->tx_buf[9] = 0x00; + + /* controller ID 1 */ + peer->tx_buf[10] = 0x01; + peer->tx_buf[11] = 0x00; + + /* controller ID 2 */ + peer->tx_buf[12] = 0x02; + peer->tx_buf[13] = 0x00; + + peer->tx_buf_len = 14; +} + +/* Will call through the xfer/submit API expecting a full-sized list (so + * resp->data_len is set to sizeof(list)), but the endpoint will return an + * unaligned short list. + */ +static void test_mi_resp_unaligned(nvme_mi_ep_t ep, struct test_peer *peer) +{ + struct nvme_ctrl_list list; + int rc; + + setup_unaligned_ctrl_list_resp(peer); + + memset(&list, 0, sizeof(list)); + + rc = nvme_mi_mi_read_mi_data_ctrl_list(ep, 0, &list); + assert(rc == 0); + + assert(le16_to_cpu(list.num) == 2); + assert(le16_to_cpu(list.identifier[0]) == 1); + assert(le16_to_cpu(list.identifier[1]) == 2); +} + +/* Will call through the xfer/submit API expecting an unaligned list, + * and get a response of exactly that size. + */ +static void test_mi_resp_unaligned_expected(nvme_mi_ep_t ep, + struct test_peer *peer) +{ + /* direct access to the raw submit() API */ + extern int nvme_mi_submit(nvme_mi_ep_t ep, struct nvme_mi_req *req, + struct nvme_mi_resp *resp); + struct nvme_mi_mi_resp_hdr resp_hdr; + struct nvme_mi_mi_req_hdr req_hdr; + struct nvme_ctrl_list list; + struct nvme_mi_resp resp; + struct nvme_mi_req req; + int rc; + + setup_unaligned_ctrl_list_resp(peer); + + memset(&list, 0, sizeof(list)); + + memset(&req_hdr, 0, sizeof(req_hdr)); + req_hdr.hdr.type = NVME_MI_MSGTYPE_NVME; + req_hdr.hdr.nmp = (NVME_MI_ROR_REQ << 7) | (NVME_MI_MT_MI << 3); + req_hdr.opcode = nvme_mi_mi_opcode_mi_data_read; + req_hdr.cdw0 = cpu_to_le32(nvme_mi_dtyp_ctrl_list << 24); + + memset(&req, 0, sizeof(req)); + req.hdr = &req_hdr.hdr; + req.hdr_len = sizeof(req_hdr); + + memset(&resp, 0, sizeof(resp)); + resp.hdr = &resp_hdr.hdr; + resp.hdr_len = sizeof(resp_hdr); + resp.data = &list; + resp.data_len = peer->tx_buf_len; + + rc = nvme_mi_submit(ep, &req, &resp); + assert(rc == 0); + assert(resp.data_len == 6); /* 2-byte length, 2*2 byte controller IDs */ + + assert(le16_to_cpu(list.num) == 2); + assert(le16_to_cpu(list.identifier[0]) == 1); + assert(le16_to_cpu(list.identifier[1]) == 2); +} + static void test_admin_resp_err(nvme_mi_ep_t ep, struct test_peer *peer) { struct nvme_id_ctrl id; @@ -340,30 +425,6 @@ static void test_admin_resp_sizes(nvme_mi_ep_t ep, struct test_peer *peer) nvme_mi_close_ctrl(ctrl); } -/* test: unaligned response sizes - should always report a transport error */ -static void test_admin_resp_sizes_unaligned(nvme_mi_ep_t ep, struct test_peer *peer) -{ - struct nvme_id_ctrl id; - nvme_mi_ctrl_t ctrl; - unsigned int i; - int rc; - - ctrl = nvme_mi_init_ctrl(ep, 1); - assert(ctrl); - - peer->tx_buf[4] = 0x02; /* internal error */ - - for (i = 8; i <= 4096 + 8; i++) { - peer->tx_buf_len = i; - if (!(i & 0x3)) - continue; - rc = nvme_mi_admin_identify_ctrl(ctrl, &id); - assert(rc < 0); - } - - nvme_mi_close_ctrl(ctrl); -} - /* test: timeout value passed to poll */ static int poll_fn_timeout_value(struct test_peer *peer, struct pollfd *fds, nfds_t nfds, int timeout) @@ -664,9 +725,10 @@ struct test { DEFINE_TEST(read_mi_data), DEFINE_TEST(poll_err), DEFINE_TEST(mi_resp_err), + DEFINE_TEST(mi_resp_unaligned), + DEFINE_TEST(mi_resp_unaligned_expected), DEFINE_TEST(admin_resp_err), DEFINE_TEST(admin_resp_sizes), - DEFINE_TEST(admin_resp_sizes_unaligned), DEFINE_TEST(poll_timeout_value), DEFINE_TEST(poll_timeout), DEFINE_TEST(mpr_mi), diff --git a/test/mi.c b/test/mi.c index 7f8e005..a09c108 100644 --- a/test/mi.c +++ b/test/mi.c @@ -44,7 +44,8 @@ static int test_transport_submit(struct nvme_mi_ep *ep, /* start from a minimal response: zeroed data, nmp to match request */ memset(resp->hdr, 0, resp->hdr_len); - memset(resp->data, 0, resp->data_len); + if (resp->data_len) + memset(resp->data, 0, resp->data_len); resp->hdr->type = NVME_MI_MSGTYPE_NVME; resp->hdr->nmp = req->hdr->nmp | (NVME_MI_ROR_RSP << 7); @@ -1245,7 +1246,6 @@ static int test_admin_id_secondary_ctrl_list_cb(struct nvme_mi_ep *ep, void *data) { __u16 cns, ctrlid; - __u32 nsid; __u8 *hdr; hdr = (__u8 *)req->hdr; @@ -1256,9 +1256,6 @@ static int test_admin_id_secondary_ctrl_list_cb(struct nvme_mi_ep *ep, cns = hdr[45] << 8 | hdr[44]; assert(cns == NVME_IDENTIFY_CNS_SECONDARY_CTRL_LIST); - nsid = hdr[11] << 24 | hdr[10] << 16 | hdr[9] << 8 | hdr[8]; - assert(nsid == 0x01020304); - ctrlid = hdr[47] << 8 | hdr[46]; assert(ctrlid == 5); @@ -1280,8 +1277,7 @@ static void test_admin_id_secondary_ctrl_list(struct nvme_mi_ep *ep) ctrl = nvme_mi_init_ctrl(ep, 5); assert(ctrl); - rc = nvme_mi_admin_identify_secondary_ctrl_list(ctrl, 0x01020304, - 5, &list); + rc = nvme_mi_admin_identify_secondary_ctrl_list(ctrl, 5, &list); assert(!rc); } @@ -1651,7 +1647,10 @@ static int test_admin_format_nvm_cb(struct nvme_mi_ep *ep, assert(rq_hdr[4] == nvme_admin_format_nvm); - nsid = rq_hdr[11] << 24 | rq_hdr[10] << 16 | rq_hdr[9] << 8 | rq_hdr[8]; + nsid = (__u32)rq_hdr[11] << 24 + | rq_hdr[10] << 16 + | rq_hdr[9] << 8 + | rq_hdr[8]; assert(nsid == args->nsid); assert(((rq_hdr[44] >> 0) & 0xf) == args->lbaf); @@ -1726,7 +1725,7 @@ static int test_admin_sanitize_nvm_cb(struct nvme_mi_ep *ep, assert(((rq_hdr[45] >> 0) & 0x1) == args->oipbp); assert(((rq_hdr[45] >> 1) & 0x1) == args->nodas); - ovrpat = rq_hdr[51] << 24 | rq_hdr[50] << 16 | + ovrpat = (__u32)rq_hdr[51] << 24 | rq_hdr[50] << 16 | rq_hdr[49] << 8 | rq_hdr[48]; assert(ovrpat == args->ovrpat); diff --git a/test/mock-ifaddrs.c b/test/mock-ifaddrs.c new file mode 100644 index 0000000..87a2e50 --- /dev/null +++ b/test/mock-ifaddrs.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/** + * This file is part of libnvme. + * Copyright (c) 2023 Martin Belanger, Dell Technologies Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct ifaddrs_storage { + struct ifaddrs ifa; + union { + /* Reserve space for the biggest of the sockaddr types */ + struct sockaddr_in s4; + struct sockaddr_in6 s6; + } addr, netmask, broadaddr; + char name[IF_NAMESIZE + 1]; +}; + +static void init_entry(struct ifaddrs_storage *storage, + const char *ifname, + int family, + uint32_t addr1, + uint32_t addr2, + uint32_t addr3, + uint32_t addr4, + bool last) +{ + struct ifaddrs *p; + + p = &storage->ifa; + p->ifa_next = last ? NULL : &storage[1].ifa; + p->ifa_name = storage->name; + strcpy(p->ifa_name, ifname); + p->ifa_flags = 0; + + if (family == AF_INET) { + struct sockaddr_in *ipv4; + + ipv4 = &storage->addr.s4; + ipv4->sin_family = family; + ipv4->sin_port = 0; + ipv4->sin_addr.s_addr = htonl(addr1); + p->ifa_addr = (struct sockaddr *)ipv4; + + ipv4 = &storage->netmask.s4; + ipv4->sin_family = family; + ipv4->sin_port = 0; + ipv4->sin_addr.s_addr = 0xffffff00; + p->ifa_netmask = (struct sockaddr *)ipv4; + + ipv4 = &storage->broadaddr.s4; + ipv4->sin_family = family; + ipv4->sin_port = 0; + ipv4->sin_addr.s_addr = 0; + p->ifa_broadaddr = (struct sockaddr *)ipv4;; + } else { + struct sockaddr_in6 *ipv6; + + ipv6 = &storage->addr.s6; + ipv6->sin6_family = family; + ipv6->sin6_port = 0; + ipv6->sin6_flowinfo = 0; + ipv6->sin6_addr.s6_addr32[0] = htonl(addr1); + ipv6->sin6_addr.s6_addr32[1] = htonl(addr2); + ipv6->sin6_addr.s6_addr32[2] = htonl(addr3); + ipv6->sin6_addr.s6_addr32[3] = htonl(addr4); + ipv6->sin6_scope_id = 0; + p->ifa_addr = (struct sockaddr *)ipv6; + + ipv6 = &storage->netmask.s6; + ipv6->sin6_family = family; + ipv6->sin6_port = 0; + ipv6->sin6_flowinfo = 0; + ipv6->sin6_addr.s6_addr32[0] = 0xffffffff; + ipv6->sin6_addr.s6_addr32[1] = 0xffffffff; + ipv6->sin6_addr.s6_addr32[2] = 0xffffffff; + ipv6->sin6_addr.s6_addr32[3] = 0; + ipv6->sin6_scope_id = 0; + p->ifa_netmask = (struct sockaddr *)ipv6; + + ipv6 = &storage->broadaddr.s6; + ipv6->sin6_family = family; + ipv6->sin6_port = 0; + ipv6->sin6_flowinfo = 0; + ipv6->sin6_addr.s6_addr32[0] = 0; + ipv6->sin6_addr.s6_addr32[1] = 0; + ipv6->sin6_addr.s6_addr32[2] = 0; + ipv6->sin6_addr.s6_addr32[3] = 0; + ipv6->sin6_scope_id = 0; + p->ifa_broadaddr = (struct sockaddr *)ipv6; + } + + p->ifa_data = NULL; +} + +int getifaddrs(struct ifaddrs **ifap) { + struct ifaddrs_storage *storage; + + /* Allocate memory for 4 interfaces */ + storage = (struct ifaddrs_storage *)calloc(4, sizeof(struct ifaddrs_storage)); + *ifap = &storage[0].ifa; + + init_entry(&storage[0], "eth0", AF_INET, 0xc0a80114, 0, 0, 0, false); /* 192.168.1.20 */ + init_entry(&storage[1], "eth0", AF_INET6, 0xfe800000, 0, 0, 0xdeadbeef, false); /* fe80::dead:beef */ + + /* Loopback interface */ + init_entry(&storage[2], "lo", AF_INET, 0x7f000001, 0, 0, 0, false); /* 127.0.0.1 */ + init_entry(&storage[3], "lo", AF_INET6, 0, 0, 0, 1, true); /* ::1 */ + + return 0; +} + +void freeifaddrs(struct ifaddrs *ifa) { + free(ifa); +} + diff --git a/test/test-util.c b/test/test-util.c index e32f030..88a3f42 100644 --- a/test/test-util.c +++ b/test/test-util.c @@ -20,7 +20,6 @@ #include #include -#include "nvme/cleanup.c" /* to resolve cleanup_charp() */ #include "nvme/log.c" /* to resolve __nvme_msg() */ #include "nvme/util.c" diff --git a/test/test.c b/test/test.c index 2f24e1e..23036bb 100644 --- a/test/test.c +++ b/test/test.c @@ -16,6 +16,7 @@ * somewhere. */ #include +#include #include #include #include @@ -112,7 +113,7 @@ static int test_ctrl(nvme_ctrl_t c) printf(" PASSED: Identify Primary\n"); else printf(" ERROR: Identify Primary:%x\n", ret); - ret = nvme_identify_secondary_ctrl_list(fd, 1, 0, &sec); + ret = nvme_identify_secondary_ctrl_list(fd, 0, &sec); if (!ret) printf(" PASSED: Identify Secondary\n"); else @@ -197,7 +198,7 @@ static int test_ctrl(nvme_ctrl_t c) printf(" Temperature Threshold:%x\n", result); else if (ret > 0) printf(" ERROR: Temperature Threshold:%x\n", ret); - ret = nvme_get_features_err_recovery(fd, sel, &result); + ret = nvme_get_features_err_recovery2(fd, sel, 0, &result); if (!ret) printf(" Error Recovery:%x\n", result); else if (ret > 0) @@ -257,12 +258,12 @@ static int test_ctrl(nvme_ctrl_t c) printf(" SW Progress Marker:%x\n", result); else if (ret > 0) printf(" ERROR: Sanitize:%x\n", ret); - ret = nvme_get_features_resv_mask(fd, sel, &result); + ret = nvme_get_features_resv_mask2(fd, sel, 0, &result); if (!ret) printf(" Reservation Mask:%x\n", result); else if (ret > 0) printf(" ERROR: Reservation Mask:%x\n", ret); - ret = nvme_get_features_resv_persist(fd, sel, &result); + ret = nvme_get_features_resv_persist2(fd, sel, 0, &result); if (!ret) printf(" Reservation Persistence:%x\n", result); else if (ret > 0) @@ -274,7 +275,7 @@ static int test_namespace(nvme_ns_t n) { int ret, nsid = nvme_ns_get_nsid(n), fd = nvme_ns_get_fd(n); struct nvme_id_ns ns = { 0 }, allocated = { 0 }; - struct nvme_ns_id_desc descs = { 0 }; + struct nvme_ns_id_desc *descs; __u32 result = 0; __u8 flbas; @@ -292,11 +293,16 @@ static int test_namespace(nvme_ns_t n) printf(" Identify allocated ns\n"); else printf(" ERROR: Identify allocated ns:%x\n", ret); - ret = nvme_identify_ns_descs(fd, nsid, &descs); + descs = malloc(NVME_IDENTIFY_DATA_SIZE); + if (!descs) + return -1; + + ret = nvme_identify_ns_descs(fd, nsid, descs); if (!ret) printf(" Identify NS Descriptors\n"); else printf(" ERROR: Identify NS Descriptors:%x\n", ret); + free(descs); ret = nvme_get_features_write_protect(fd, nsid, NVME_GET_FEATURES_SEL_CURRENT, &result); if (!ret) diff --git a/test/tree.c b/test/tree.c index b3baac8..c9370f9 100644 --- a/test/tree.c +++ b/test/tree.c @@ -6,10 +6,14 @@ #include #include +#include +#include +#include #include #include +#include struct test_data { /* input data */ @@ -29,6 +33,8 @@ struct test_data { #define DEFAULT_SUBSYSNAME "subsysname" #define DEFAULT_SUBSYSNQN "subsysnqn" +#define SRC_ADDR4 "192.168.56.100" +#define SRC_ADDR6 "1234:5678:abcd:EF01:1234:5678:abcd:EF01" struct test_data test_data[] = { { DEFAULT_SUBSYSNAME, DEFAULT_SUBSYSNQN, "tcp", "192.168.1.1", "192.168.1.20", NULL, "4420" }, @@ -65,8 +71,10 @@ static void show_ctrl(nvme_ctrl_t c) if (d) printf("ctrl%d ", d->ctrl_id); + else + printf(" "); - printf("0x%p: %s %s %s %s %s\n", + printf("0x%p: %s %s %s %s %s ", c, nvme_ctrl_get_transport(c), nvme_ctrl_get_traddr(c), @@ -75,6 +83,41 @@ static void show_ctrl(nvme_ctrl_t c) nvme_ctrl_get_trsvcid(c)); } +static bool match_ctrl(struct test_data *d, nvme_ctrl_t c) +{ + bool pass = true; + const char *trsvid, *host_traddr, *host_iface; + + if (d->c != c) + pass = false; + + if (strcmp(d->transport, nvme_ctrl_get_transport(d->c))) + pass = false; + + if (strcmp(d->traddr, nvme_ctrl_get_traddr(d->c))) + pass = false; + + + host_traddr = nvme_ctrl_get_host_traddr(c); + if (d->host_traddr && + (!host_traddr || strcmp(d->host_traddr, host_traddr))) + pass = false; + + host_iface = nvme_ctrl_get_host_iface(c); + if (d->host_iface && + (!host_iface || strcmp(d->host_iface, host_iface))) + pass = false; + + trsvid = nvme_ctrl_get_trsvcid(c); + if (d->trsvcid && + (!trsvid || strcmp(d->trsvcid, trsvid))) + pass = false; + + printf("[%s]", pass? "PASS" : "FAILED"); + + return pass; +} + static nvme_root_t create_tree() { nvme_root_t r; @@ -97,14 +140,10 @@ static nvme_root_t create_tree() assert(d->c); d->ctrl_id = i; - assert(!strcmp(d->transport, nvme_ctrl_get_transport(d->c))); - assert(!strcmp(d->traddr, nvme_ctrl_get_traddr(d->c))); - assert(!d->host_traddr || !strcmp(d->host_traddr, nvme_ctrl_get_host_traddr(d->c))); - assert(!d->host_iface || !strcmp(d->host_iface, nvme_ctrl_get_host_iface(d->c))); - assert(!d->trsvcid || !strcmp(d->trsvcid, nvme_ctrl_get_trsvcid(d->c))); - printf(" "); show_ctrl(d->c); + match_ctrl(d, d->c); + printf("\n"); } printf("\n"); @@ -118,146 +157,1028 @@ static unsigned int count_entries(nvme_root_t r) nvme_ctrl_t c; unsigned int i = 0; - nvme_for_each_host(r, h) { - nvme_for_each_subsystem(h, s) { - nvme_subsystem_for_each_ctrl(s, c) { + nvme_for_each_host(r, h) + nvme_for_each_subsystem(h, s) + nvme_subsystem_for_each_ctrl(s, c) i++; - } - } - } return i; } -static void ctrl_lookups(nvme_root_t r) +static bool tcp_ctrl_lookup(nvme_subsystem_t s, struct test_data *d) { - nvme_host_t h; - nvme_subsystem_t s; nvme_ctrl_t c; + bool pass = true; - h = nvme_first_host(r); - s = nvme_lookup_subsystem(h, DEFAULT_SUBSYSNAME, DEFAULT_SUBSYSNQN); + c = nvme_lookup_ctrl(s, d->transport, d->traddr, NULL, + NULL, d->trsvcid, NULL); + printf("%10s %12s %10s -> ", d->trsvcid, "", ""); + show_ctrl(c); + pass &= match_ctrl(d, c); + printf("\n"); - printf(" lookup controller:\n"); - for (int i = 0; i < ARRAY_SIZE(test_data); i++) { - struct test_data *d = &test_data[i]; + if (d->host_traddr) { + c = nvme_lookup_ctrl(s, d->transport, d->traddr, d->host_traddr, + NULL, d->trsvcid, NULL); + printf("%10s %12s %10s -> ", d->trsvcid, d->host_traddr, ""); + show_ctrl(c); + pass &= match_ctrl(d, c); + printf("\n"); + } - printf("%10s %10s ", "", ""); - show_ctrl(d->c); + if (d->host_iface) { + c = nvme_lookup_ctrl(s, d->transport, d->traddr, NULL, + d->host_iface, d->trsvcid, NULL); + printf("%10s %12s %10s -> ", d->trsvcid, "", d->host_iface); + show_ctrl(c); + pass &= match_ctrl(d, c); + printf("\n"); + } + + if (d->host_iface && d->traddr) { c = nvme_lookup_ctrl(s, d->transport, d->traddr, d->host_traddr, - NULL, NULL, NULL); - printf("%10s %10s -> ", "-", "-"); + d->host_iface, d->trsvcid, NULL); + printf("%10s %12s %10s -> ", d->trsvcid, d->host_traddr, d->host_iface); show_ctrl(c); + pass &= match_ctrl(d, c); + printf("\n"); + } - assert(!strcmp(d->transport, nvme_ctrl_get_transport(c))); - assert(!strcmp(d->traddr, nvme_ctrl_get_traddr(c))); - assert(!strcmp(d->host_traddr, nvme_ctrl_get_host_traddr(c))); + return pass; +} - if (d->host_iface) { - c = nvme_lookup_ctrl(s, d->transport, d->traddr, d->host_traddr, - d->host_iface, NULL, NULL); - printf("%10s %10s -> ", d->host_iface, "-"); - show_ctrl(c); +static bool default_ctrl_lookup(nvme_subsystem_t s, struct test_data *d) +{ + nvme_ctrl_t c; + bool pass = true; - assert(!strcmp(d->transport, nvme_ctrl_get_transport(c))); - assert(!strcmp(d->traddr, nvme_ctrl_get_traddr(c))); - assert(!strcmp(d->host_traddr, nvme_ctrl_get_host_traddr(c))); - assert(!strcmp(d->host_iface, nvme_ctrl_get_host_iface(c))); - } + c = nvme_lookup_ctrl(s, d->transport, d->traddr, d->host_traddr, + NULL, NULL, NULL); + printf("%10s %12s %10s -> ", "", "", ""); + show_ctrl(c); + pass &= match_ctrl(d, c); + printf("\n"); - if (d->trsvcid) { - c = nvme_lookup_ctrl(s, d->transport, d->traddr, d->host_traddr, - NULL, d->trsvcid, NULL); - printf("%10s %10s -> ", "-", d->trsvcid); - show_ctrl(c); + return pass; +} - assert(!strcmp(d->transport, nvme_ctrl_get_transport(c))); - assert(!strcmp(d->traddr, nvme_ctrl_get_traddr(c))); - assert(!strcmp(d->host_traddr, nvme_ctrl_get_host_traddr(c))); - assert(!strcmp(d->trsvcid, nvme_ctrl_get_trsvcid(c))); - } +static bool ctrl_lookups(nvme_root_t r) +{ + nvme_host_t h; + nvme_subsystem_t s; + bool pass = true; - if (d->host_iface && d->trsvcid) { - c = nvme_lookup_ctrl(s, d->transport, d->traddr, d->host_traddr, - d->host_iface, d->trsvcid, NULL); - printf("%10s %10s -> ", d->host_iface, d->trsvcid); - show_ctrl(c); - - assert(!strcmp(d->transport, nvme_ctrl_get_transport(c))); - assert(!strcmp(d->traddr, nvme_ctrl_get_traddr(c))); - assert(!strcmp(d->host_traddr, nvme_ctrl_get_host_traddr(c))); - assert(!strcmp(d->trsvcid, nvme_ctrl_get_trsvcid(c))); - assert(!strcmp(d->host_iface, nvme_ctrl_get_host_iface(c))); - } + h = nvme_first_host(r); + s = nvme_lookup_subsystem(h, DEFAULT_SUBSYSNAME, DEFAULT_SUBSYSNQN); + + printf(" lookup controller:\n"); + for (int i = 0; i < ARRAY_SIZE(test_data); i++) { + struct test_data *d = &test_data[i]; + + printf("%10s %12s %10s ", "", "", ""); + show_ctrl(d->c); + printf("\n"); + + if (!strcmp("tcp", d->transport)) + pass &= tcp_ctrl_lookup(s, d); + else + pass &= default_ctrl_lookup(s, d); printf("\n"); } + + return pass; } -static void test_lookup_1(void) +static bool test_lookup(void) { nvme_root_t r; + bool pass; - printf("test_lookup_1:\n"); + printf("test_lookup:\n"); r = create_tree(); - assert(count_entries(r) == ARRAY_SIZE(test_data)); - ctrl_lookups(r); + pass = count_entries(r) == ARRAY_SIZE(test_data); + pass &= ctrl_lookups(r); nvme_free_tree(r); + + return pass; } -static void test_lookup_2(void) +static bool test_src_addr() { + bool pass = true; nvme_root_t r; - nvme_subsystem_t s; nvme_host_t h; - nvme_ctrl_t c1, c2, c3, c4; + nvme_ctrl_t c; + nvme_subsystem_t s; + char *src_addr, buffer[100]; /* big enough for IPv6 max length */ - printf("test_lookup_2:\n"); + printf("\n" + "test_src_addr:\n"); r = nvme_create_root(stdout, LOG_DEBUG); assert(r); + h = nvme_default_host(r); assert(h); s = nvme_lookup_subsystem(h, DEFAULT_SUBSYSNAME, DEFAULT_SUBSYSNQN); assert(s); - assert(nvme_lookup_ctrl(s, "tcp", "192.168.2.1", "192.168.2.20", - "eth0", "4420", NULL)); - - c1 = nvme_lookup_ctrl(s, "tcp", "192.168.1.1", "192.168.1.20", - NULL, NULL, NULL); - assert(c1); - printf("%10s %10s ", "", ""); - show_ctrl(c1); - - c2 = nvme_lookup_ctrl(s, "tcp", "192.168.1.1", "192.168.1.20", - "eth0", NULL, NULL); - assert(c1 == c2); - printf("%10s %10s ", "eth0", "-"); - show_ctrl(c2); - - c3 = nvme_lookup_ctrl(s, "tcp", "192.168.1.1", "192.168.1.20", - NULL, "4420", NULL); - assert(c1 == c3); - printf("%10s %10s ", "-", "4420"); - show_ctrl(c3); - - c4 = nvme_lookup_ctrl(s, "tcp", "192.168.1.1", "192.168.1.20", - "eth0", "4420", NULL); - assert(c1 == c4); - printf("%10s %10s ", "eth0", "4420"); - show_ctrl(c4); + c = nvme_lookup_ctrl(s, "tcp", "192.168.56.1", NULL, NULL, "8009", NULL); + assert(c); + + c->address = NULL; + printf(" - Test c->address = NULL : src_addr = NULL "); + src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer)); + if (src_addr != NULL) { + printf("[FAIL]\n"); + fprintf(stderr, + "nvme_ctrl_get_src_addr() c->address=NULL should return src_addr=NULL\n"); + pass = false; + } else { + printf("[PASS]\n"); + } + + c->address = ""; + printf(" - Test c->address = \"\" : src_addr = NULL "); + src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer)); + if (src_addr != NULL) { + printf("[FAIL]\n"); + fprintf(stderr, + "nvme_ctrl_get_src_addr() c->address="" should return src_addr=NULL\n"); + pass = false; + } else { + printf("[PASS]\n"); + } + + c->address = "traddr=192.168.56.1,trsvcid=8009"; + printf(" - Test c->address = \"%s\" : src_addr = NULL ", c->address); + src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer)); + if (src_addr != NULL) { + printf("[FAIL]\n"); + fprintf(stderr, + "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=NULL\n", + c->address); + pass = false; + } else { + printf("[PASS]\n"); + } + + c->address = "traddr=192.168.56.1,trsvcid=8009,src_addr=" SRC_ADDR4; + printf(" - Test c->address = \"%s\" : src_addr = \"" SRC_ADDR4 "\" ", c->address); + src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer)); + if (!src_addr || strcmp(src_addr, SRC_ADDR4)) { + printf("[FAIL]\n"); + fprintf(stderr, + "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=" SRC_ADDR4 "\n", + c->address); + pass = false; + } else { + printf("[PASS]\n"); + } + + c->address = "traddr=192.168.56.1,src_addr=" SRC_ADDR4 ",trsvcid=8009"; + printf(" - Test c->address = \"%s\" : src_addr = \"" SRC_ADDR4 "\" ", c->address); + src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer)); + if (!src_addr || strcmp(src_addr, SRC_ADDR4)) { + printf("[FAIL]\n"); + fprintf(stderr, + "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=" SRC_ADDR4 "\n", + c->address); + pass = false; + } else { + printf("[PASS]\n"); + } + + c->address = "traddr=1234::abcd,trsvcid=8009,src_addr=" SRC_ADDR6; + printf(" - Test c->address = \"%s\" : src_addr = \"" SRC_ADDR6 "\" ", c->address); + src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer)); + if (!src_addr || strcmp(src_addr, SRC_ADDR6)) { + printf("[FAIL]\n"); + fprintf(stderr, + "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=" SRC_ADDR6 "\n", + c->address); + pass = false; + } else { + printf("[PASS]\n"); + } + + c->address = "traddr=1234::abcd,src_addr=" SRC_ADDR6 ",trsvcid=8009"; + printf(" - Test c->address = \"%s\" : src_addr = \"" SRC_ADDR6 "\" ", c->address); + src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer)); + if (!src_addr || strcmp(src_addr, SRC_ADDR6)) { + printf("[FAIL]\n"); + fprintf(stderr, + "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=" SRC_ADDR6 "\n", + c->address); + pass = false; + } else { + printf("[PASS]\n"); + } + + c->address = "traddr=1234::abcd,trsvcid=8009,src_addr=" SRC_ADDR6 "%scope"; + printf(" - Test c->address = \"%s\" : src_addr = \"" SRC_ADDR6 "\" ", c->address); + src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer)); + if (!src_addr || strcmp(src_addr, SRC_ADDR6)) { + printf("[FAIL]\n"); + fprintf(stderr, + "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=" SRC_ADDR6 "\n", + c->address); + pass = false; + } else { + printf("[PASS]\n"); + } + + c->address = "traddr=1234::abcd,src_addr=" SRC_ADDR6 "%scope,trsvcid=8009"; + printf(" - Test c->address = \"%s\" : src_addr = \"" SRC_ADDR6 "\" ", c->address); + src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer)); + if (!src_addr || strcmp(src_addr, SRC_ADDR6)) { + printf("[FAIL]\n"); + fprintf(stderr, + "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=" SRC_ADDR6 "\n", + c->address); + pass = false; + } else { + printf("[PASS]\n"); + } + + c->address = NULL; /* Needed to avoid freeing non-malloced memory (see above) */ + + nvme_free_tree(r); + + return pass; +} + +struct ctrl_args { + const char *transport; + const char *traddr; + const char *trsvcid; + const char *host_traddr; + const char *host_iface; + const char *address; + const char *subsysnqn; +}; + +static void set_ctrl_args(struct ctrl_args *args, + const char *transport, + const char *traddr, + const char *trsvcid, + const char *host_traddr, + const char *host_iface, + const char *address, + const char *subsysnqn) +{ + args->transport = transport; + args->traddr = traddr; + args->trsvcid = trsvcid; + args->host_traddr = host_traddr; + args->host_iface = host_iface; + args->address = address; + args->subsysnqn = subsysnqn; +} + +static bool ctrl_match(const char *tag, + int reference_id, + int candidate_id, + struct ctrl_args *reference, + struct ctrl_args *candidate, + bool should_match) +{ + nvme_root_t r; + nvme_host_t h; + nvme_ctrl_t reference_ctrl; /* Existing controller (from sysfs) */ + nvme_ctrl_t candidate_ctrl; + nvme_ctrl_t found_ctrl; + nvme_subsystem_t s; + + r = nvme_create_root(stdout, LOG_INFO); + assert(r); + + h = nvme_default_host(r); + assert(h); + + s = nvme_lookup_subsystem(h, DEFAULT_SUBSYSNAME, reference->subsysnqn ? reference->subsysnqn : DEFAULT_SUBSYSNQN); + assert(s); + + reference_ctrl = nvme_lookup_ctrl(s, reference->transport, reference->traddr, + reference->host_traddr, reference->host_iface, + reference->trsvcid, NULL); + assert(reference_ctrl); + reference_ctrl->name = "nvme1"; /* fake the device name */ + if (reference->address) { + reference_ctrl->address = (char *)reference->address; + } + + /* nvme_ctrl_find() MUST BE RUN BEFORE nvme_lookup_ctrl() */ + found_ctrl = nvme_ctrl_find(s, candidate->transport, candidate->traddr, + candidate->trsvcid, candidate->subsysnqn, + candidate->host_traddr, + candidate->host_iface); + + candidate_ctrl = nvme_lookup_ctrl(s, candidate->transport, candidate->traddr, + candidate->host_traddr, candidate->host_iface, + candidate->trsvcid, NULL); + + if (should_match) { + if (candidate_ctrl != reference_ctrl) { + printf("%s-%d-%d: Candidate (%s, %s, %s, %s, %s, %s) failed to match (%s, %s, %s, %s, %s, %s, %s)\n", + tag, reference_id, candidate_id, + candidate->transport, candidate->traddr, candidate->trsvcid, + candidate->subsysnqn, candidate->host_traddr, candidate->host_iface, + reference->transport, reference->traddr, reference->trsvcid, reference->subsysnqn, + reference->host_traddr, reference->host_iface, reference->address); + return false; + } + + if (!found_ctrl) { + printf("%s-%d-%d: Candidate (%s, %s, %s, %s, %s, %s) failed to find controller\n", + tag, reference_id, candidate_id, + candidate->transport, candidate->traddr, candidate->trsvcid, + candidate->subsysnqn, candidate->host_traddr, candidate->host_iface); + return false; + } + } else { + if (candidate_ctrl == reference_ctrl) { + printf("%s-%d-%d: Candidate (%s, %s, %s, %s, %s, %s) should not match (%s, %s, %s, %s, %s, %s, %s)\n", + tag, reference_id, candidate_id, + candidate->transport, candidate->traddr, candidate->trsvcid, + candidate->subsysnqn, candidate->host_traddr, candidate->host_iface, + reference->transport, reference->traddr, reference->trsvcid, reference->subsysnqn, + reference->host_traddr, reference->host_iface, reference->address); + return false; + } + + if (found_ctrl) { + printf("%s-%d-%d: Candidate (%s, %s, %s, %s, %s, %s) should not have found controller. found_ctrl=%p reference=%p\n", + tag, reference_id, candidate_id, + candidate->transport, candidate->traddr, candidate->trsvcid, candidate->subsysnqn, + candidate->host_traddr, candidate->host_iface, found_ctrl, reference_ctrl); + return false; + } + } + + /* Set the faked data back to NULL before freeing the tree */ + reference_ctrl->name = NULL; + reference_ctrl->address = NULL; nvme_free_tree(r); + + return true; } +/** + * test_ctrl_match_fc - Test that we can look up FC controllers + * + * @return true when all tests have passed. false otherwise. + */ +static bool test_ctrl_match_fc(void) +{ + bool pass = true; + struct ctrl_args reference = {0}; + struct ctrl_args candidate = {0}; + + printf("test_ctrl_match_fc:\n"); + + /*******************************************************************/ + /* Reference ID 1 */ + set_ctrl_args(&reference, "fc", "21:00:00:e0:8b:05:05:01", "4420", "21:00:00:e0:8b:05:05:20", NULL, NULL, NULL); + + set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", "21:00:00:e0:8b:05:05:20", NULL, NULL, NULL); + pass &= ctrl_match("FC", 1, 0, &reference, &candidate, true); + + set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, NULL, NULL, NULL); + pass &= ctrl_match("FC", 1, 1, &reference, &candidate, true); + + set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("FC", 1, 2, &reference, &candidate, true); + + set_ctrl_args(&candidate, "fc", "192.168.2.2", "4420", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("FC", 1, 3, &reference, &candidate, false); + + set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("FC", 1, 4, &reference, &candidate, false); + + set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", "21:00:00:e0:8b:05:05:21", NULL, NULL, NULL); + pass &= ctrl_match("FC", 1, 5, &reference, &candidate, false); + + set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "8009", NULL, "", NULL, NULL); + pass &= ctrl_match("FC", 1, 6, &reference, &candidate, false); + + + /*******************************************************************/ + /* Reference ID 2 */ + set_ctrl_args(&reference, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, NULL, NULL, NULL); + + set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", "21:00:00:e0:8b:05:05:20", NULL, NULL, NULL); + pass &= ctrl_match("FC", 2, 0, &reference, &candidate, true); + + set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, NULL, NULL, NULL); + pass &= ctrl_match("FC", 2, 1, &reference, &candidate, true); + + set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("FC", 2, 2, &reference, &candidate, true); + + set_ctrl_args(&candidate, "fc", "192.168.2.2", "4420", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("FC", 2, 3, &reference, &candidate, false); + + set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("FC", 2, 4, &reference, &candidate, false); + + set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "8009", NULL, "", NULL, NULL); + pass &= ctrl_match("FC", 2, 5, &reference, &candidate, false); + + + /*******************************************************************/ + /* Reference ID 3 */ + set_ctrl_args(&reference, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, "eth0", NULL, NULL); + + set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", "21:00:00:e0:8b:05:05:20", NULL, NULL, NULL); + pass &= ctrl_match("FC", 3, 0, &reference, &candidate, true); + + set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, NULL, NULL, NULL); + pass &= ctrl_match("FC", 3, 1, &reference, &candidate, true); + + set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("FC", 3, 2, &reference, &candidate, true); + + set_ctrl_args(&candidate, "fc", "192.168.2.2", "4420", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("FC", 3, 3, &reference, &candidate, false); + + set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("FC", 3, 4, &reference, &candidate, false); + + set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "8009", NULL, "", NULL, NULL); + pass &= ctrl_match("FC", 3, 5, &reference, &candidate, false); + + return pass; +} + +/** + * test_ctrl_match_rdma - Test that we can look up RDMA controllers + * + * @return true when all tests have passed. false otherwise. + */ +static bool test_ctrl_match_rdma(void) +{ + bool pass = true; + struct ctrl_args reference = {0}; + struct ctrl_args candidate = {0}; + + printf("test_ctrl_match_rdma:\n"); + + /*******************************************************************/ + /* Reference ID 1 */ + set_ctrl_args(&reference, "rdma", "192.168.2.1", "4420", "192.168.1.20", NULL, NULL, NULL); + + set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", "192.168.1.20", NULL, NULL, NULL); + pass &= ctrl_match("RDMA", 1, 0, &reference, &candidate, true); + + set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", NULL, NULL, NULL, NULL); + pass &= ctrl_match("RDMA", 1, 1, &reference, &candidate, true); + + set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("RDMA", 1, 2, &reference, &candidate, true); + + set_ctrl_args(&candidate, "rdma", "192.168.2.2", "4420", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("RDMA", 1, 3, &reference, &candidate, false); + + set_ctrl_args(&candidate, "rdma", "192.168.2.1", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("RDMA", 1, 4, &reference, &candidate, false); + + set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", "192.168.1.21", NULL, NULL, NULL); + pass &= ctrl_match("RDMA", 1, 5, &reference, &candidate, false); + + set_ctrl_args(&candidate, "rdma", "192.168.2.1", "8009", NULL, "", NULL, NULL); + pass &= ctrl_match("RDMA", 1, 6, &reference, &candidate, false); + + + /*******************************************************************/ + /* Reference ID 2 */ + set_ctrl_args(&reference, "rdma", "192.168.2.1", "4420", NULL, NULL, NULL, NULL); + + set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", "192.168.1.20", NULL, NULL, NULL); + pass &= ctrl_match("RDMA", 2, 0, &reference, &candidate, true); + + set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", NULL, NULL, NULL, NULL); + pass &= ctrl_match("RDMA", 2, 1, &reference, &candidate, true); + + set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("RDMA", 2, 2, &reference, &candidate, true); + + set_ctrl_args(&candidate, "rdma", "192.168.2.2", "4420", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("RDMA", 2, 3, &reference, &candidate, false); + + set_ctrl_args(&candidate, "rdma", "192.168.2.1", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("RDMA", 2, 4, &reference, &candidate, false); + + set_ctrl_args(&candidate, "rdma", "192.168.2.1", "8009", NULL, "", NULL, NULL); + pass &= ctrl_match("RDMA", 2, 5, &reference, &candidate, false); + + + /*******************************************************************/ + /* Reference ID 3 */ + set_ctrl_args(&reference, "rdma", "192.168.2.1", "4420", NULL, "eth0", NULL, NULL); + + set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", "192.168.1.20", NULL, NULL, NULL); + pass &= ctrl_match("RDMA", 3, 0, &reference, &candidate, true); + + set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", NULL, NULL, NULL, NULL); + pass &= ctrl_match("RDMA", 3, 1, &reference, &candidate, true); + + set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("RDMA", 3, 2, &reference, &candidate, true); + + set_ctrl_args(&candidate, "rdma", "192.168.2.2", "4420", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("RDMA", 3, 3, &reference, &candidate, false); + + set_ctrl_args(&candidate, "rdma", "192.168.2.1", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("RDMA", 3, 4, &reference, &candidate, false); + + set_ctrl_args(&candidate, "rdma", "192.168.2.1", "8009", NULL, "", NULL, NULL); + pass &= ctrl_match("RDMA", 3, 5, &reference, &candidate, false); + + return pass; +} + +/** + * test_ctrl_match_tcp - Test that we can look up TCP controllers + * + * @note: The mocked getifaddrs() returns 2 interface entries with the + * following addresses. Therefore the tests must use IP addresses + * that match these. + * + * eth0 + * \_ 192.168.1.20 + * \_ fe80::dead:beef + * + * lo + * \_ 127.0.0.1 + * \_ ::1 + * + * @return true when all tests have passed. false otherwise. + */ +static bool test_ctrl_match_tcp() +{ + bool pass = true; + struct ctrl_args reference = {0}; + struct ctrl_args candidate = {0}; + + printf("\n" + "test_ctrl_match_tcp:\n"); + + /*******************************************************************/ + /* IPv4: Reference ID 1 */ + set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL); + + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 1, 0, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 1, 1, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 1, 2, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 1, 3, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 1, 4, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 1, 5, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 1, 6, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 1, 7, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 1, 8, &reference, &candidate, true); + + /*******************************************************************/ + /* IPv4: Reference ID 2 */ + set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL); + + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 2, 0, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 2, 1, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 2, 2, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 2, 3, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 2, 4, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 2, 5, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 2, 6, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 2, 7, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 2, 8, &reference, &candidate, false); + + /*******************************************************************/ + /* IPv4: Reference ID 3 */ + set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL); + + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 3, 0, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 3, 1, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 3, 2, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 3, 3, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 3, 4, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 3, 5, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 3, 6, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 3, 7, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 3, 8, &reference, &candidate, false); + + /*******************************************************************/ + /* IPv4: Reference ID 4 */ + set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL); + + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 4, 0, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 4, 1, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 4, 2, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 4, 3, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 4, 4, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 4, 5, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 4, 6, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 4, 7, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 4, 8, &reference, &candidate, false); + + /*******************************************************************/ + /* IPv4: Reference ID 5 */ + set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL); + + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 5, 0, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 5, 1, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 5, 2, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 5, 3, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 5, 4, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 5, 5, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 5, 6, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 5, 7, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 5, 8, &reference, &candidate, false); + + /*******************************************************************/ + /* IPv4: Reference ID 6 */ + set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", NULL, NULL, "traddr=123.123.123.123,trsvcid=8009,src_addr=192.168.1.20", NULL); + + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 6, 0, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 6, 1, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 6, 2, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 6, 3, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 6, 4, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 6, 5, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 6, 6, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 6, 7, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 6, 8, &reference, &candidate, false); + + /*******************************************************************/ + /* IPv4: Reference ID 7 */ + set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", NULL, NULL, "traddr=123.123.123.123,trsvcid=8009,src_addr=127.0.0.1", NULL); + + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 7, 0, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 7, 1, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 7, 2, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 7, 3, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL); + pass &= ctrl_match("IPv4", 7, 4, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL); + pass &= ctrl_match("IPv4", 7, 5, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 7, 6, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 7, 7, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL); + pass &= ctrl_match("IPv4", 7, 8, &reference, &candidate, false); + + /*******************************************************************/ + /* IPv6: Reference ID 1 */ + set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL); + + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 1, 0, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 1, 1, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 1, 2, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 1, 3, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 1, 4, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 1, 5, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 1, 6, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 1, 7, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 1, 8, &reference, &candidate, true); + + /*******************************************************************/ + /* IPv6: Reference ID 2 */ + set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL); + + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 2, 0, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 2, 1, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 2, 2, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 2, 3, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 2, 4, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 2, 5, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 2, 6, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 2, 7, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 2, 8, &reference, &candidate, false); + + /*******************************************************************/ + /* IPv6: Reference ID 3 */ + set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL); + + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 3, 0, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 3, 1, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 3, 2, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 3, 3, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 3, 4, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 3, 5, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 3, 6, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 3, 7, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 3, 8, &reference, &candidate, false); + + /*******************************************************************/ + /* IPv6: Reference ID 4 */ + set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL); + + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 4, 0, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 4, 1, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 4, 2, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 4, 3, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 4, 4, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 4, 5, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 4, 6, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 4, 7, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 4, 8, &reference, &candidate, false); + + /*******************************************************************/ + /* IPv6: Reference ID 5 */ + set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL); + + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 5, 0, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 5, 1, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 5, 2, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 5, 3, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 5, 4, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 5, 5, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 5, 6, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 5, 7, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 5, 8, &reference, &candidate, false); + + /*******************************************************************/ + /* IPv6: Reference ID 6 */ + set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", NULL, NULL, "traddr=aaaa::bbbb,trsvcid=8009,src_addr=fe80::dead:beef", NULL); + + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 6, 0, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 6, 1, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 6, 2, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 6, 3, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 6, 4, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 6, 5, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 6, 6, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 6, 7, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 6, 8, &reference, &candidate, false); + + /*******************************************************************/ + /* IPv6: Reference ID 7 */ + set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", NULL, NULL, "traddr=aaaa::bbbb,trsvcid=8009,src_addr=::1", NULL); + + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 7, 0, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 7, 1, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 7, 2, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 7, 3, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL); + pass &= ctrl_match("IPv6", 7, 4, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL); + pass &= ctrl_match("IPv6", 7, 5, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 7, 6, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 7, 7, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL); + pass &= ctrl_match("IPv6", 7, 8, &reference, &candidate, false); + + return pass; +} + +static bool ctrl_config_match(const char *tag, + int reference_id, + int candidate_id, + struct ctrl_args *reference, + struct ctrl_args *candidate, + bool should_match) +{ + bool match; + nvme_root_t r; + nvme_host_t h; + nvme_ctrl_t reference_ctrl; /* Existing controller (from sysfs) */ + nvme_subsystem_t s; + + r = nvme_create_root(stdout, LOG_INFO); + assert(r); + + h = nvme_default_host(r); + assert(h); + + s = nvme_lookup_subsystem(h, DEFAULT_SUBSYSNAME, reference->subsysnqn ? reference->subsysnqn : DEFAULT_SUBSYSNQN); + assert(s); + + reference_ctrl = nvme_lookup_ctrl(s, reference->transport, reference->traddr, + reference->host_traddr, reference->host_iface, + reference->trsvcid, NULL); + assert(reference_ctrl); + reference_ctrl->name = "nvme1"; /* fake the device name */ + if (reference->address) { + reference_ctrl->address = (char *)reference->address; + } + + match = nvme_ctrl_config_match(reference_ctrl, candidate->transport, candidate->traddr, + candidate->trsvcid, candidate->subsysnqn, + candidate->host_traddr, candidate->host_iface); + + if (should_match) { + if (!match) { + printf("%s-%d-%d: Failed to match config for Candidate (%s, %s, %s, %s, %s, %s)\n", + tag, reference_id, candidate_id, + candidate->transport, candidate->traddr, candidate->trsvcid, + candidate->subsysnqn, candidate->host_traddr, candidate->host_iface); + return false; + } + } else { + if (match) { + printf("%s-%d-%d: Config should not have matched for Candidate (%s, %s, %s, %s, %s, %s)\n", + tag, reference_id, candidate_id, + candidate->transport, candidate->traddr, candidate->trsvcid, + candidate->subsysnqn, candidate->host_traddr, candidate->host_iface); + return false; + } + } + + /* Set the faked data back to NULL before freeing the tree */ + reference_ctrl->name = NULL; + reference_ctrl->address = NULL; + + nvme_free_tree(r); + + return true; +} + +static bool test_ctrl_config_match() +{ + bool pass = true; + struct ctrl_args reference = {0}; + struct ctrl_args candidate = {0}; + + printf("\n" + "test_ctrl_config_match:\n"); + + set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL); + + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL); + pass &= ctrl_config_match("IPv4", 1, 0, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL); + pass &= ctrl_config_match("IPv4", 1, 1, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL); + pass &= ctrl_config_match("IPv4", 1, 2, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL); + pass &= ctrl_config_match("IPv4", 1, 3, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL); + pass &= ctrl_config_match("IPv4", 1, 4, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL); + pass &= ctrl_config_match("IPv4", 1, 5, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL); + pass &= ctrl_config_match("IPv4", 1, 6, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL); + pass &= ctrl_config_match("IPv4", 1, 7, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL); + pass &= ctrl_config_match("IPv4", 1, 8, &reference, &candidate, true); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, "hello"); + pass &= ctrl_config_match("IPv4", 1, 9, &reference, &candidate, false); + set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, DEFAULT_SUBSYSNQN); + pass &= ctrl_config_match("IPv4", 1, 9, &reference, &candidate, true); + + return pass; +} + + +/** + * This test module uses a mocked ifaddrs library (mock-ifaddrs.c) + * such that there are 2 fake interfaces (eth0 and lo) with the + * following IP addresses: + * + * - eth0 + * \_ IPv4: 192.168.1.20 + * \_ IPv6: fe80::dead:beef + * + * - lo + * \_ IPv4: 127.0.0.1 + * \_ IPv6: ::1 + */ int main(int argc, char *argv[]) { - test_lookup_1(); - test_lookup_2(); + bool pass = true; + + pass &= test_lookup(); + pass &= test_src_addr(); + pass &= test_ctrl_match_fc(); + pass &= test_ctrl_match_rdma(); + pass &= test_ctrl_match_tcp(); + pass &= test_ctrl_config_match(); + + fflush(stdout); - return 0; + exit(pass ? EXIT_SUCCESS : EXIT_FAILURE); } -- cgit v1.2.3