1
0
Fork 0
libnvme/test/ioctl/ana.c
Daniel Baumann a02d194ad0
Merging upstream version 1.10.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-02-16 10:50:54 +01:00

643 lines
21 KiB
C

// SPDX-License-Identifier: LGPL-2.1-or-later
#include <libnvme.h>
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <ccan/array_size/array_size.h>
#include <ccan/endian/endian.h>
#include "mock.h"
#include "util.h"
#define TEST_FD 0xFD
#define PDU_SIZE NVME_LOG_PAGE_PDU_SIZE
static void test_no_retries(void)
{
struct nvme_ana_log log;
__u32 len = sizeof(log);
/* max_retries = 0 is nonsensical */
check(nvme_get_ana_log_atomic(TEST_FD, false, false, 0, &log, &len),
"get log page succeeded");
check(errno == EINVAL, "unexpected error: %m");
}
static void test_len_too_short(void)
{
struct nvme_ana_log log;
__u32 len = sizeof(log) - 1;
/* Provided buffer doesn't have enough space to read the header */
check(nvme_get_ana_log_atomic(TEST_FD, false, false, 1, &log, &len),
"get log page succeeded");
check(errno == ENOSPC, "unexpected error: %m");
}
static void test_no_groups(void)
{
struct nvme_ana_log header;
/* The header reports no ANA groups. No additional commands needed. */
struct mock_cmd mock_admin_cmd = {
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header),
.cdw10 = (sizeof(header) / 4 - 1) << 16 /* NUMDL */
| NVME_LOG_LID_ANA, /* LID */
.out_data = &header,
};
struct nvme_ana_log log;
__u32 len = sizeof(log);
arbitrary(&log, sizeof(log));
arbitrary(&header, sizeof(header));
header.ngrps = cpu_to_le16(0);
set_mock_admin_cmds(&mock_admin_cmd, 1);
check(!nvme_get_ana_log_atomic(TEST_FD, false, false, 1, &log, &len),
"get log page failed: %m");
end_mock_cmds();
cmp(&log, &header, sizeof(header), "incorrect header");
check(len == sizeof(header),
"got len %" PRIu32 ", expected %zu", len, sizeof(header));
}
static void test_one_group_rgo(void)
{
struct nvme_ana_log header;
struct nvme_ana_group_desc group;
__u8 log_page[sizeof(header) + sizeof(group)];
__u32 len = 123;
size_t len_dwords = len / 4;
/*
* Header and group fetched in a single Get Log Page command.
* Since only one command was issued, chgcnt doesn't need to be checked.
*/
struct mock_cmd mock_admin_cmd = {
.opcode = nvme_admin_get_log_page,
.data_len = len_dwords * 4,
.cdw10 = (len_dwords - 1) << 16 /* NUMDL */
| NVME_LOG_ANA_LSP_RGO_GROUPS_ONLY << 8 /* LSP */
| NVME_LOG_LID_ANA, /* LID */
.out_data = log_page,
.out_data_len = sizeof(log_page),
};
struct nvme_ana_log *log = malloc(len);
arbitrary(log, len);
arbitrary(&header, sizeof(header));
header.ngrps = cpu_to_le16(1);
arbitrary(&group, sizeof(group));
group.nnsids = cpu_to_le32(0);
memcpy(log_page, &header, sizeof(header));
memcpy(log_page + sizeof(header), &group, sizeof(group));
set_mock_admin_cmds(&mock_admin_cmd, 1);
check(!nvme_get_ana_log_atomic(TEST_FD, true, false, 1, log, &len),
"get log page failed: %m");
end_mock_cmds();
cmp(log, log_page, sizeof(log_page), "incorrect log page");
check(len == sizeof(log_page),
"got len %" PRIu32 ", expected %zu", len, sizeof(log_page));
free(log);
}
static void test_one_group_nsids(void)
{
struct nvme_ana_log header;
struct nvme_ana_group_desc group;
__le32 nsids[3];
__u8 log_page[sizeof(header) + sizeof(group) + sizeof(nsids)];
__u32 len = 124;
size_t len_dwords = len / 4;
/*
* Header, group, and NSIDs fetched in a single Get Log Page command.
* Since only one command was issued, chgcnt doesn't need to be checked.
*/
struct mock_cmd mock_admin_cmd = {
.opcode = nvme_admin_get_log_page,
.data_len = len_dwords * 4,
.cdw10 = (len_dwords - 1) << 16 /* NUMDL */
| NVME_LOG_LID_ANA, /* LID */
.out_data = log_page,
.out_data_len = sizeof(log_page),
};
struct nvme_ana_log *log = malloc(len);
arbitrary(log, len);
arbitrary(&header, sizeof(header));
header.ngrps = cpu_to_le16(1);
arbitrary(&group, sizeof(group));
group.nnsids = cpu_to_le32(ARRAY_SIZE(nsids));
arbitrary(nsids, sizeof(nsids));
memcpy(log_page, &header, sizeof(header));
memcpy(log_page + sizeof(header), &group, sizeof(group));
memcpy(log_page + sizeof(header) + sizeof(group), nsids, sizeof(nsids));
set_mock_admin_cmds(&mock_admin_cmd, 1);
check(!nvme_get_ana_log_atomic(TEST_FD, false, false, 1, log, &len),
"get log page failed: %m");
end_mock_cmds();
cmp(log, log_page, sizeof(log_page), "incorrect log page");
check(len == sizeof(log_page),
"got len %" PRIu32 ", expected %zu", len, sizeof(log_page));
free(log);
}
static void test_multiple_groups_rgo(void)
{
struct nvme_ana_log header;
struct nvme_ana_group_desc groups[3];
__u8 log_page[sizeof(header) + sizeof(groups)];
__u32 len = 125;
size_t len_dwords = len / 4;
/*
* Header and groups fetched in a single Get Log Page command.
* Since only one command was issued, chgcnt doesn't need to be checked.
*/
struct mock_cmd mock_admin_cmd = {
.opcode = nvme_admin_get_log_page,
.data_len = len_dwords * 4,
.cdw10 = (len_dwords - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| NVME_LOG_ANA_LSP_RGO_GROUPS_ONLY << 8 /* LSP */
| NVME_LOG_LID_ANA, /* LID */
.out_data = log_page,
.out_data_len = sizeof(log_page),
};
struct nvme_ana_log *log = malloc(len);
arbitrary(log, len);
arbitrary(&header, sizeof(header));
header.ngrps = cpu_to_le16(ARRAY_SIZE(groups));
arbitrary(groups, sizeof(groups));
for (size_t i = 0; i < ARRAY_SIZE(groups); i++)
groups[i].nnsids = cpu_to_le32(0);
memcpy(log_page, &header, sizeof(header));
memcpy(log_page + sizeof(header), groups, sizeof(groups));
set_mock_admin_cmds(&mock_admin_cmd, 1);
check(!nvme_get_ana_log_atomic(TEST_FD, true, true, 1, log, &len),
"get log page failed: %m");
end_mock_cmds();
cmp(log, log_page, sizeof(log_page), "incorrect log page");
check(len == sizeof(log_page),
"got len %" PRIu32 ", expected %zu", len, sizeof(log_page));
free(log);
}
static void test_multiple_groups_nsids(void)
{
struct nvme_ana_log header;
struct nvme_ana_group_desc group1;
__le32 nsids1[3];
struct nvme_ana_group_desc group2;
__le32 nsids2[2];
struct nvme_ana_group_desc group3;
__le32 nsids3[1];
__u8 log_page[sizeof(header) +
sizeof(group1) + sizeof(nsids1) +
sizeof(group2) + sizeof(nsids2) +
sizeof(group3) + sizeof(nsids3)];
__u32 len = 456;
size_t len_dwords = len / 4;
/*
* Header, group, and NSIDs fetched in a single Get Log Page command.
* Since only one command was issued, chgcnt doesn't need to be checked.
*/
struct mock_cmd mock_admin_cmd = {
.opcode = nvme_admin_get_log_page,
.data_len = len_dwords * 4,
.cdw10 = (len_dwords - 1) << 16 /* NUMDL */
| NVME_LOG_LID_ANA, /* LID */
.out_data = log_page,
.out_data_len = sizeof(log_page),
};
struct nvme_ana_log *log = malloc(len);
arbitrary(log, len);
arbitrary(&header, sizeof(header));
header.ngrps = cpu_to_le16(3);
arbitrary(&group1, sizeof(group1));
group1.nnsids = cpu_to_le32(ARRAY_SIZE(nsids1));
arbitrary(nsids1, sizeof(nsids1));
arbitrary(&group2, sizeof(group2));
group2.nnsids = cpu_to_le32(ARRAY_SIZE(nsids2));
arbitrary(nsids2, sizeof(nsids2));
arbitrary(&group3, sizeof(group3));
group3.nnsids = cpu_to_le32(ARRAY_SIZE(nsids3));
arbitrary(nsids3, sizeof(nsids3));
memcpy(log_page, &header, sizeof(header));
memcpy(log_page + sizeof(header), &group1, sizeof(group1));
memcpy(log_page + sizeof(header) + sizeof(group1),
nsids1, sizeof(nsids1));
memcpy(log_page + sizeof(header) + sizeof(group1) + sizeof(nsids1),
&group2, sizeof(group2));
memcpy(log_page + sizeof(header) + sizeof(group1) + sizeof(nsids1) +
sizeof(group2),
nsids2, sizeof(nsids2));
memcpy(log_page + sizeof(header) + sizeof(group1) + sizeof(nsids1) +
sizeof(group2) + sizeof(nsids2),
&group3, sizeof(group3));
memcpy(log_page + sizeof(header) + sizeof(group1) + sizeof(nsids1) +
sizeof(group2) + sizeof(nsids2) + sizeof(group3),
nsids3, sizeof(nsids3));
set_mock_admin_cmds(&mock_admin_cmd, 1);
check(!nvme_get_ana_log_atomic(TEST_FD, false, false, 1, log, &len),
"get log page failed: %m");
end_mock_cmds();
cmp(log, log_page, sizeof(log_page), "incorrect log page");
check(len == sizeof(log_page),
"got len %" PRIu32 ", expected %zu", len, sizeof(log_page));
free(log);
}
static void test_long_log(void)
{
struct nvme_ana_log header;
struct nvme_ana_group_desc group;
__le32 nsids[PDU_SIZE * 2 / sizeof(*group.nsids)];
__u8 log_page[sizeof(header) + sizeof(group) + sizeof(nsids)];
__u32 len = PDU_SIZE * 4;
/*
* Get Log Page is issued for 4 KB, returning the header (with 1 group),
* the group (with 2048 NSIDs) and the start of its NSIDs.
* Another Get Log page command is issued for the next 1024 NSIDs.
* Another Get Log page command is issued for the last NSIDs.
* Header is fetched again to verify chgcnt hasn't changed.
*/
struct mock_cmd mock_admin_cmds[] = {
{
.opcode = nvme_admin_get_log_page,
.data_len = PDU_SIZE,
.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| NVME_LOG_LID_ANA, /* LID */
.out_data = log_page,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = PDU_SIZE,
.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| NVME_LOG_LID_ANA, /* LID */
.cdw12 = PDU_SIZE, /* LPOL */
.out_data = log_page + PDU_SIZE,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = PDU_SIZE,
.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| NVME_LOG_LID_ANA, /* LID */
.cdw12 = PDU_SIZE * 2, /* LPOL */
.out_data = log_page + PDU_SIZE * 2,
.out_data_len = sizeof(log_page) - PDU_SIZE * 2,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = PDU_SIZE,
.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| NVME_LOG_LID_ANA, /* LID */
.out_data = log_page,
},
};
struct nvme_ana_log *log = malloc(len);
arbitrary(log, len);
arbitrary(&header, sizeof(header));
header.ngrps = cpu_to_le16(1);
arbitrary(&group, sizeof(group));
group.nnsids = cpu_to_le32(ARRAY_SIZE(nsids));
arbitrary(nsids, sizeof(nsids));
memcpy(log_page, &header, sizeof(header));
memcpy(log_page + sizeof(header), &group, sizeof(group));
memcpy(log_page + sizeof(header) + sizeof(group), nsids, sizeof(nsids));
set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
check(!nvme_get_ana_log_atomic(TEST_FD, false, true, 1, log, &len),
"get log page failed: %m");
end_mock_cmds();
cmp(log, log_page, sizeof(log_page), "incorrect log page");
check(len == sizeof(log_page),
"got len %" PRIu32 ", expected %zu", len, sizeof(log_page));
free(log);
}
static void test_chgcnt_change(void)
{
struct nvme_ana_log header1;
struct nvme_ana_group_desc groups1[PDU_SIZE / sizeof(*header1.descs)];
__u8 log_page1[sizeof(header1) + sizeof(groups1)];
struct nvme_ana_log header2;
struct nvme_ana_group_desc group2;
__u8 log_page2[sizeof(header2) + sizeof(group2)];
__u32 len = PDU_SIZE + 126;
size_t remainder_len_dwords = (len - PDU_SIZE) / 4;
/*
* Get Log Page is issued for 4 KB,
* returning the header (with 128 groups), and the start of the groups.
* Get Log Page is issued for the rest of the groups.
* Get Log Page is issued for the first 4 KB again to check chgcnt.
* chgcnt has changed, but there is only 1 group now,
* which was already fetched with the header.
*/
struct mock_cmd mock_admin_cmds[] = {
{
.opcode = nvme_admin_get_log_page,
.data_len = PDU_SIZE,
.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| NVME_LOG_ANA_LSP_RGO_GROUPS_ONLY << 8 /* LSP */
| NVME_LOG_LID_ANA, /* LID */
.out_data = log_page1,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = remainder_len_dwords * 4,
.cdw10 = (remainder_len_dwords - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| NVME_LOG_ANA_LSP_RGO_GROUPS_ONLY << 8 /* LSP */
| NVME_LOG_LID_ANA, /* LID */
.cdw12 = PDU_SIZE, /* LPOL */
.out_data = log_page1 + PDU_SIZE,
.out_data_len = sizeof(log_page1) - PDU_SIZE,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = PDU_SIZE,
.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| NVME_LOG_ANA_LSP_RGO_GROUPS_ONLY << 8 /* LSP */
| NVME_LOG_LID_ANA, /* LID */
.out_data = log_page2,
.out_data_len = sizeof(log_page2),
},
};
struct nvme_ana_log *log = malloc(len);
arbitrary(log, len);
arbitrary(&header1, sizeof(header1));
header1.ngrps = cpu_to_le16(ARRAY_SIZE(groups1));
arbitrary(&groups1, sizeof(groups1));
for (size_t i = 0; i < ARRAY_SIZE(groups1); i++)
groups1[i].nnsids = cpu_to_le32(0);
memcpy(log_page1, &header1, sizeof(header1));
memcpy(log_page1 + sizeof(header1), groups1, sizeof(groups1));
arbitrary(&header2, sizeof(header2));
header2.ngrps = cpu_to_le16(1);
arbitrary(&group2, sizeof(group2));
group2.nnsids = cpu_to_le32(0);
memcpy(log_page2, &header2, sizeof(header2));
memcpy(log_page2 + sizeof(header2), &group2, sizeof(group2));
set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
check(!nvme_get_ana_log_atomic(TEST_FD, true, true, 2, log, &len),
"get log page failed: %m");
end_mock_cmds();
cmp(log, log_page2, sizeof(log_page2), "incorrect log page");
check(len == sizeof(log_page2),
"got len %" PRIu32 ", expected %zu", len, sizeof(log_page2));
free(log);
}
static void test_buffer_too_short_chgcnt_change(void)
{
struct nvme_ana_log header1;
struct nvme_ana_group_desc group1_1;
__le32 nsids1[PDU_SIZE / sizeof(*group1_1.nsids)];
struct nvme_ana_group_desc group1_2;
__u8 log_page1[sizeof(header1) +
sizeof(group1_1) + sizeof(nsids1) + sizeof(group1_2)];
struct nvme_ana_log header2;
struct nvme_ana_group_desc group2;
__le32 nsid2;
uint8_t log_page2[sizeof(header2) + sizeof(group2) + sizeof(nsid2)];
__u32 len = PDU_SIZE + 123;
size_t remainder_len_dwords = (len - PDU_SIZE) / 4;
/*
* Get Log Page issued for 4 KB, returning the header (with 2 groups),
* the first group (with 1024 NSIDs), and the start of the NSIDs.
* Get Log Page is issued for the rest of the NSIDs and the second group.
* The second group contains garbage, making the log exceed the buffer.
* The first 4 KB is fetched again, returning a header with a new chgcnt
* and a group with one NSID.
*/
struct mock_cmd mock_admin_cmds[] = {
{
.opcode = nvme_admin_get_log_page,
.data_len = PDU_SIZE,
.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
| NVME_LOG_LID_ANA, /* LID */
.out_data = log_page1,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = remainder_len_dwords * 4,
.cdw10 = (remainder_len_dwords - 1) << 16 /* NUMDL */
| NVME_LOG_LID_ANA, /* LID */
.cdw12 = PDU_SIZE, /* LPOL */
.out_data = log_page1 + PDU_SIZE,
.out_data_len = sizeof(log_page1) - PDU_SIZE,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = PDU_SIZE,
.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
| NVME_LOG_LID_ANA, /* LID */
.out_data = log_page2,
.out_data_len = sizeof(log_page2),
},
};
struct nvme_ana_log *log = malloc(len);
arbitrary(log, len);
arbitrary(&header1, sizeof(header1));
header1.ngrps = cpu_to_le16(2);
arbitrary(&group1_1, sizeof(group1_1));
group1_1.nnsids = cpu_to_le32(ARRAY_SIZE(nsids1));
arbitrary(nsids1, sizeof(nsids1));
memset(&group1_2, -1, sizeof(group1_2));
memcpy(log_page1, &header1, sizeof(header1));
memcpy(log_page1 + sizeof(header1), &group1_1, sizeof(group1_1));
memcpy(log_page1 + sizeof(header1) + sizeof(group1_1),
nsids1, sizeof(nsids1));
memcpy(log_page1 + sizeof(header1) + sizeof(group1_1) + sizeof(nsids1),
&group1_2, sizeof(group1_2));
arbitrary(&header2, sizeof(header2));
header2.ngrps = cpu_to_le16(1);
arbitrary(&group2, sizeof(group2));
group2.nnsids = cpu_to_le32(1);
arbitrary(&nsid2, sizeof(nsid2));
memcpy(log_page2, &header2, sizeof(header2));
memcpy(log_page2 + sizeof(header2), &group2, sizeof(group2));
memcpy(log_page2 + sizeof(header2) + sizeof(group2),
&nsid2, sizeof(nsid2));
set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
check(!nvme_get_ana_log_atomic(TEST_FD, false, false, 2, log, &len),
"get log page failed: %m");
end_mock_cmds();
cmp(log, log_page2, sizeof(log_page2), "incorrect log page");
check(len == sizeof(log_page2),
"got len %" PRIu32 ", expected %zu", len, sizeof(log_page2));
free(log);
}
static void test_chgcnt_max_retries(void)
{
struct nvme_ana_log header1, header2, header3;
struct nvme_ana_group_desc group;
__le32 nsids[PDU_SIZE / sizeof(*group.nsids)];
__u8 log_page1[sizeof(header1) + sizeof(group) + sizeof(nsids)],
log_page2[sizeof(header2) + sizeof(group) + sizeof(nsids)];
__u32 len = PDU_SIZE * 2;
/*
* Get Log Page is issued for 4 KB, returning the header (with 1 group),
* the group (with 1024 NSIDs), and the start of the NSIDs.
* Get Log Page is issued for the rest of the NSIDs.
* Get Log Page is issued for the first 4 KB again to check chgcnt.
* chgcnt has changed and there is still 1 group with 1024 NSIDs.
* Get Log Page is issued for the rest of the NSIDs.
* Get Log Page is issued for the first 4 KB again to check chgcnt.
* chgcnt has changed again.
* This exceeds max_retries = 2 so nvme_get_ana_log() exits with EAGAIN.
*/
struct mock_cmd mock_admin_cmds[] = {
{
.opcode = nvme_admin_get_log_page,
.data_len = PDU_SIZE,
.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| NVME_LOG_LID_ANA, /* LID */
.out_data = log_page1,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = PDU_SIZE,
.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| NVME_LOG_LID_ANA, /* LID */
.cdw12 = PDU_SIZE, /* LPOL */
.out_data = log_page1 + PDU_SIZE,
.out_data_len = sizeof(log_page1) - PDU_SIZE,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = PDU_SIZE,
.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| NVME_LOG_LID_ANA, /* LID */
.out_data = log_page2,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = PDU_SIZE,
.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| NVME_LOG_LID_ANA, /* LID */
.cdw12 = PDU_SIZE, /* LPOL */
.out_data = log_page2 + PDU_SIZE,
.out_data_len = sizeof(log_page2) - PDU_SIZE,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = PDU_SIZE,
.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| NVME_LOG_LID_ANA, /* LID */
.out_data = &header3,
.out_data_len = sizeof(header3),
},
};
struct nvme_ana_log *log = malloc(len);
arbitrary(log, len);
arbitrary(&header1, sizeof(header1));
header1.ngrps = cpu_to_le16(1);
arbitrary(&header2, sizeof(header2));
header2.ngrps = cpu_to_le16(1);
arbitrary(&header3, sizeof(header3));
header3.ngrps = cpu_to_le16(0);
arbitrary(&group, sizeof(group));
group.nnsids = cpu_to_le32(ARRAY_SIZE(nsids));
arbitrary(nsids, sizeof(nsids));
memcpy(log_page1, &header1, sizeof(header1));
memcpy(log_page1 + sizeof(header1), &group, sizeof(group));
memcpy(log_page1 + sizeof(header1) + sizeof(group),
nsids, sizeof(nsids));
memcpy(log_page2, &header2, sizeof(header2));
memcpy(log_page2 + sizeof(header2), &group, sizeof(group));
memcpy(log_page2 + sizeof(header2) + sizeof(group),
nsids, sizeof(nsids));
set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
check(nvme_get_ana_log_atomic(TEST_FD, false, true, 2, log, &len) == -1,
"get log page succeeded");
end_mock_cmds();
check(errno == EAGAIN, "unexpected error: %m");
free(log);
}
static void test_buffer_too_short(void)
{
struct nvme_ana_log header;
struct nvme_ana_group_desc group;
__le32 nsids[20];
__u8 log_page[sizeof(header) + sizeof(group) + sizeof(nsids)];
__u32 len = 123;
__u32 len_dwords = len / 4;
/*
* Header, group, and NSIDs fetched in a single Get Log Page command.
* This length exceeds the provided buffer.
* Only one command was issued, so the log page couldn't have changed.
* nvme_get_ana_log() returns ENOSPC because the buffer is too small.
*/
struct mock_cmd mock_admin_cmd = {
.opcode = nvme_admin_get_log_page,
.data_len = len_dwords * 4,
.cdw10 = (len_dwords - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| NVME_LOG_LID_ANA, /* LID */
.out_data = log_page,
};
struct nvme_ana_log *log = malloc(len);
arbitrary(log, len);
arbitrary(&header, sizeof(header));
header.ngrps = cpu_to_le16(1);
arbitrary(&group, sizeof(group));
group.nnsids = cpu_to_le32(ARRAY_SIZE(nsids));
arbitrary(nsids, sizeof(nsids));
memcpy(log_page, &header, sizeof(header));
memcpy(log_page + sizeof(header), &group, sizeof(group));
memcpy(log_page + sizeof(header) + sizeof(group), nsids, sizeof(nsids));
set_mock_admin_cmds(&mock_admin_cmd, 1);
check(nvme_get_ana_log_atomic(TEST_FD, false, true, 2, log, &len) == -1,
"get log page succeeded");
end_mock_cmds();
check(errno == ENOSPC, "unexpected error: %m");
free(log);
}
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(no_retries);
RUN_TEST(len_too_short);
RUN_TEST(no_groups);
RUN_TEST(one_group_rgo);
RUN_TEST(one_group_nsids);
RUN_TEST(multiple_groups_rgo);
RUN_TEST(multiple_groups_nsids);
RUN_TEST(long_log);
RUN_TEST(chgcnt_change);
RUN_TEST(buffer_too_short_chgcnt_change);
RUN_TEST(chgcnt_max_retries);
RUN_TEST(buffer_too_short);
}