1
0
Fork 0

Merging upstream version 1.6.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-16 10:12:19 +01:00
parent adbb3a10cc
commit 6add9877e4
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
871 changed files with 8481 additions and 1502 deletions

460
test/ioctl/discovery.c Normal file
View file

@ -0,0 +1,460 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include <libnvme.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <ccan/array_size/array_size.h>
#include <ccan/endian/endian.h>
#include "../../src/nvme/private.h"
#include "mock.h"
#include "util.h"
#define TEST_FD 0xFD
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 = sizeof(header),
.cdw10 = (sizeof(header) / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 = sizeof(header),
.cdw10 = (sizeof(header) / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 */
| 1 << 15 /* RAE */
| NVME_LOG_LID_DISCOVER, /* LID */
.cdw12 = sizeof(header), /* LPOL */
.out_data = log_entries,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header),
.cdw10 = (sizeof(header) / 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 = sizeof(header),
.cdw10 = (sizeof(header) / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 */
| 1 << 15 /* RAE */
| 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 = sizeof(header),
.cdw10 = (sizeof(header) / 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 fetch is retried
*/
struct mock_cmd mock_admin_cmds[] = {
{
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header1),
.cdw10 = (sizeof(header1) / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 */
| 1 << 15 /* RAE */
| NVME_LOG_LID_DISCOVER, /* NUMDL */
.cdw12 = sizeof(header1), /* LPOL */
.out_data = entries1,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header2),
.cdw10 = (sizeof(header2) / 4 - 1) << 16 /* NUMDL */
| NVME_LOG_LID_DISCOVER, /* LID */
.out_data = &header2,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header2),
.cdw10 = (sizeof(header2) / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 */
| 1 << 15 /* RAE */
| NVME_LOG_LID_DISCOVER, /* LID */
.cdw12 = sizeof(header2), /* LPOL */
.out_data = log_entries2,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header2),
.cdw10 = (sizeof(header2) / 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 = sizeof(header1),
.cdw10 = (sizeof(header1) / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 */
| 1 << 15 /* RAE */
| NVME_LOG_LID_DISCOVER, /* LID */
.cdw12 = sizeof(header1), /* LPOL */
.out_data = &entry,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header2),
.cdw10 = (sizeof(header2) / 4 - 1) << 16 /* NUMDL */
| NVME_LOG_LID_DISCOVER, /* LID */
.out_data = &header2,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header2),
.cdw10 = (sizeof(header2) / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 */
| 1 << 15 /* RAE */
| NVME_LOG_LID_DISCOVER, /* LID */
.cdw12 = sizeof(header2), /* LPOL */
.out_data = &entry,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header3),
.cdw10 = (sizeof(header3) / 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)
{
size_t header_size = sizeof(struct nvmf_discovery_log);
/* 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_size,
.cdw10 = (header_size / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 = sizeof(header),
.cdw10 = (sizeof(header) / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 */
| 1 << 15 /* RAE */
| 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 = sizeof(header),
.cdw10 = (sizeof(header) / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 */
| 1 << 15 /* RAE */
| NVME_LOG_LID_DISCOVER, /* LID */
.cdw12 = sizeof(header), /* LPOL */
.out_data = &entry,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header),
.cdw10 = (sizeof(header) / 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);
}

1604
test/ioctl/features.c Normal file

File diff suppressed because it is too large Load diff

572
test/ioctl/identify.c Normal file
View file

@ -0,0 +1,572 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include <libnvme.h>
#include <errno.h>
#include <stdlib.h>
#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);
}

32
test/ioctl/meson.build Normal file
View file

@ -0,0 +1,32 @@
mock_ioctl = library(
'mock-ioctl',
['mock.c', 'util.c'],
)
discovery = executable(
'test-discovery',
'discovery.c',
dependencies: libnvme_dep,
include_directories: [incdir, internal_incdir],
link_with: mock_ioctl,
)
test('discovery', discovery, env: ['LD_PRELOAD=' + mock_ioctl.full_path()])
features = executable(
'test-features',
'features.c',
dependencies: libnvme_dep,
link_with: mock_ioctl,
)
test('features', features, env: ['LD_PRELOAD=' + mock_ioctl.full_path()])
identify = executable(
'test-identify',
'identify.c',
dependencies: libnvme_dep,
link_with: mock_ioctl,
)
test('identify', identify, env: ['LD_PRELOAD=' + mock_ioctl.full_path()])

168
test/ioctl/mock.c Normal file
View file

@ -0,0 +1,168 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "mock.h"
#include <errno.h>
#include <inttypes.h>
#include <stdarg.h>
#include <string.h>
#include <sys/ioctl.h>
#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); \
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); \
} \
})
int ioctl(int fd, unsigned long request, ...)
{
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__, 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;
}

104
test/ioctl/mock.h Normal file
View file

@ -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 <stddef.h>
#include <stdint.h>
/**
* 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 */

65
test/ioctl/util.c Normal file
View file

@ -0,0 +1,65 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "util.h"
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}

19
test/ioctl/util.h Normal file
View file

@ -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 <stddef.h>
#include <stdnoreturn.h>
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 */

View file

@ -67,14 +67,20 @@ uuid = executable(
test('uuid', uuid)
if conf.get('HAVE_NETDB')
mock_ifaddrs = library(
'mock-ifaddrs',
['mock-ifaddrs.c', ],
)
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: ['LD_PRELOAD=' + mock_ifaddrs.full_path()])
test_util = executable(
'test-util',
@ -84,4 +90,5 @@ if conf.get('HAVE_NETDB')
test('Test util.c', test_util)
endif
subdir('ioctl')
subdir('nbft')

View file

@ -293,6 +293,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 +423,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 +723,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),

View file

@ -1245,7 +1245,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 +1255,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 +1276,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);
}

123
test/mock-ifaddrs.c Normal file
View file

@ -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 <sys/types.h>
#include <ifaddrs.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
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);
}

View file

@ -16,6 +16,7 @@
* somewhere.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <inttypes.h>
@ -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)

File diff suppressed because it is too large Load diff