1
0
Fork 0

Merging upstream version 1.10.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-16 10:50:54 +01:00
parent 05578a6ab9
commit a02d194ad0
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
946 changed files with 4401 additions and 1290 deletions

View file

@ -0,0 +1,24 @@
#!/bin/bash -e
# SPDX-License-Identifier: LGPL-2.1-or-later
BUILD_DIR=$1
CONFIG_DUMP=$2
SYSDIR_INPUT=$3
CONFIG_JSON=$4
EXPECTED_OUTPUT=$5
ACTUAL_OUTPUT="${BUILD_DIR}"/$(basename "${EXPECTED_OUTPUT}")
TEST_NAME="$(basename -s .tar.xz $SYSDIR_INPUT)"
TEST_DIR="$BUILD_DIR/$TEST_NAME"
rm -rf "${TEST_DIR}"
mkdir "${TEST_DIR}"
tar -x -f "${SYSDIR_INPUT}" -C "${TEST_DIR}"
LIBNVME_SYSFS_PATH="$TEST_DIR" \
LIBNVME_HOSTNQN=nqn.2014-08.org.nvmexpress:uuid:ce4fee3e-c02c-11ee-8442-830d068a36c6 \
LIBNVME_HOSTID=ce4fee3e-c02c-11ee-8442-830d068a36c6 \
"${CONFIG_DUMP}" "${CONFIG_JSON}" > "${ACTUAL_OUTPUT}" || echo "test failed"
diff -u "${EXPECTED_OUTPUT}" "${ACTUAL_OUTPUT}"

53
test/config/config-dump.c Normal file
View file

@ -0,0 +1,53 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/**
* This file is part of libnvme.
* Copyright (c) 2024 Daniel Wagner, SUSE LLC
*/
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
#include <libnvme.h>
static bool config_dump(const char *file)
{
bool pass = false;
nvme_root_t r;
int err;
r = nvme_create_root(stderr, LOG_ERR);
if (!r)
return false;
err = nvme_scan_topology(r, NULL, NULL);
if (err) {
if (errno != ENOENT)
goto out;
}
err = nvme_read_config(r, file);
if (err)
goto out;
err = nvme_dump_config(r);
if (err)
goto out;
pass = true;
out:
nvme_free_tree(r);
return pass;
}
int main(int argc, char *argv[])
{
bool pass;
pass = config_dump(argv[1]);
fflush(stdout);
exit(pass ? EXIT_SUCCESS : EXIT_FAILURE);
}

View file

@ -0,0 +1,48 @@
[
{
"hostnqn":"nqn.2014-08.org.nvmexpress:uuid:2cd2c43b-a90a-45c1-a8cd-86b33ab273b5",
"hostid":"2cd2c43b-a90a-45c1-a8cd-86b33ab273b5",
"subsystems":[
{
"nqn":"nqn.io-1",
"ports":[
{
"transport":"tcp",
"traddr":"192.168.154.144",
"trsvcid":"4420",
"dhchap_key":"none"
},
{
"transport":"tcp",
"traddr":"192.168.154.144",
"trsvcid":"4421",
"dhchap_key":"none"
}
]
}
]
},
{
"hostnqn":"nqn.2014-08.org.nvmexpress:uuid:befdec4c-2234-11b2-a85c-ca77c773af36",
"hostid":"befdec4c-2234-11b2-a85c-ca77c773af36",
"subsystems":[
{
"nqn":"nqn.io-1",
"ports":[
{
"transport":"tcp",
"traddr":"192.168.154.144",
"trsvcid":"4420",
"dhchap_key":"none"
},
{
"transport":"tcp",
"traddr":"192.168.154.144",
"trsvcid":"4421",
"dhchap_key":"none"
}
]
}
]
}
]

View file

@ -0,0 +1,48 @@
[
{
"hostnqn":"nqn.2014-08.org.nvmexpress:uuid:2cd2c43b-a90a-45c1-a8cd-86b33ab273b5",
"hostid":"2cd2c43b-a90a-45c1-a8cd-86b33ab273b5",
"subsystems":[
{
"nqn":"nqn.io-1",
"ports":[
{
"transport":"tcp",
"traddr":"192.168.154.144",
"trsvcid":"4420",
"dhchap_key":"none"
},
{
"transport":"tcp",
"traddr":"192.168.154.144",
"trsvcid":"4421",
"dhchap_key":"none"
}
]
}
]
},
{
"hostnqn":"nqn.2014-08.org.nvmexpress:uuid:befdec4c-2234-11b2-a85c-ca77c773af36",
"hostid":"befdec4c-2234-11b2-a85c-ca77c773af36",
"subsystems":[
{
"nqn":"nqn.io-1",
"ports":[
{
"transport":"tcp",
"traddr":"192.168.154.144",
"trsvcid":"4420",
"dhchap_key":"none"
},
{
"transport":"tcp",
"traddr":"192.168.154.144",
"trsvcid":"4421",
"dhchap_key":"none"
}
]
}
]
}
]

Binary file not shown.

View file

View file

View file

@ -0,0 +1,48 @@
[
{
"hostnqn":"nqn.2014-08.org.nvmexpress:uuid:2cd2c43b-a90a-45c1-a8cd-86b33ab273b5",
"hostid":"2cd2c43b-a90a-45c1-a8cd-86b33ab273b5",
"subsystems":[
{
"nqn":"nqn.io-1",
"ports":[
{
"transport":"tcp",
"traddr":"192.168.154.144",
"trsvcid":"4420",
"dhchap_key":"none"
},
{
"transport":"tcp",
"traddr":"192.168.154.144",
"trsvcid":"4421",
"dhchap_key":"none"
}
]
}
]
},
{
"hostnqn":"nqn.2014-08.org.nvmexpress:uuid:befdec4c-2234-11b2-a85c-ca77c773af36",
"hostid":"befdec4c-2234-11b2-a85c-ca77c773af36",
"subsystems":[
{
"nqn":"nqn.io-1",
"ports":[
{
"transport":"tcp",
"traddr":"192.168.154.144",
"trsvcid":"4420",
"dhchap_key":"none"
},
{
"transport":"tcp",
"traddr":"192.168.154.144",
"trsvcid":"4421",
"dhchap_key":"none"
}
]
}
]
}
]

View file

Binary file not shown.

164
test/config/hostnqn-order.c Normal file
View file

@ -0,0 +1,164 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/**
* This file is part of libnvme.
* Copyright (c) 2024 Daniel Wagner, SUSE LLC
*/
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
#include <libnvme.h>
static bool command_line(void)
{
bool pass = false;
nvme_root_t r;
int err;
char *hostnqn, *hostid, *hnqn, *hid;
r = nvme_create_root(stderr, LOG_ERR);
if (!r)
return false;
err = nvme_scan_topology(r, NULL, NULL);
if (err) {
if (errno != ENOENT)
goto out;
}
hostnqn = "nqn.2014-08.org.nvmexpress:uuid:ce4fee3e-c02c-11ee-8442-830d068a36c6";
hostid = "ce4fee3e-c02c-11ee-8442-830d068a36c6";
err = nvme_host_get_ids(r, hostnqn, hostid, &hnqn, &hid);
if (err)
goto out;
if (strcmp(hostnqn, hnqn)) {
printf("json config hostnqn '%s' does not match '%s'\n", hostnqn, hnqn);
goto out;
}
if (strcmp(hostid, hid)) {
printf("json config hostid '%s' does not match '%s'\n", hostid, hid);
goto out;
}
free(hnqn);
free(hid);
pass = true;
out:
nvme_free_tree(r);
return pass;
}
static bool json_config(char *file)
{
bool pass = false;
nvme_root_t r;
int err;
char *hostnqn, *hostid, *hnqn, *hid;
setenv("LIBNVME_HOSTNQN", "", 1);
setenv("LIBNVME_HOSTID", "", 1);
r = nvme_create_root(stderr, LOG_ERR);
if (!r)
return false;
/* We need to read the config in before we scan */
err = nvme_read_config(r, file);
if (err)
goto out;
err = nvme_scan_topology(r, NULL, NULL);
if (err) {
if (errno != ENOENT)
goto out;
}
hostnqn = "nqn.2014-08.org.nvmexpress:uuid:2cd2c43b-a90a-45c1-a8cd-86b33ab273b5";
hostid = "2cd2c43b-a90a-45c1-a8cd-86b33ab273b5";
err = nvme_host_get_ids(r, NULL, NULL, &hnqn, &hid);
if (err)
goto out;
if (strcmp(hostnqn, hnqn)) {
printf("json config hostnqn '%s' does not match '%s'\n", hostnqn, hnqn);
goto out;
}
if (strcmp(hostid, hid)) {
printf("json config hostid '%s' does not match '%s'\n", hostid, hid);
goto out;
}
free(hnqn);
free(hid);
pass = true;
out:
nvme_free_tree(r);
return pass;
}
static bool from_file(void)
{
bool pass = false;
nvme_root_t r;
int err;
char *hostnqn, *hostid, *hnqn, *hid;
hostnqn = "nqn.2014-08.org.nvmexpress:uuid:ce4fee3e-c02c-11ee-8442-830d068a36c6";
hostid = "ce4fee3e-c02c-11ee-8442-830d068a36c6";
setenv("LIBNVME_HOSTNQN", hostnqn, 1);
setenv("LIBNVME_HOSTID", hostid, 1);
r = nvme_create_root(stderr, LOG_ERR);
if (!r)
return false;
err = nvme_scan_topology(r, NULL, NULL);
if (err) {
if (errno != ENOENT)
goto out;
}
err = nvme_host_get_ids(r, NULL, NULL, &hnqn, &hid);
if (err)
goto out;
if (strcmp(hostnqn, hnqn)) {
printf("json config hostnqn '%s' does not match '%s'\n", hostnqn, hnqn);
goto out;
}
if (strcmp(hostid, hid)) {
printf("json config hostid '%s' does not match '%s'\n", hostid, hid);
goto out;
}
free(hnqn);
free(hid);
pass = true;
out:
nvme_free_tree(r);
return pass;
}
int main(int argc, char *argv[])
{
bool pass;
pass = command_line();
pass &= json_config(argv[1]);
pass &= from_file();
fflush(stdout);
exit(pass ? EXIT_SUCCESS : EXIT_FAILURE);
}

59
test/config/meson.build Normal file
View file

@ -0,0 +1,59 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of libnvme.
# Copyright (c) 2024 SUSE LLC.
#
# Authors: Daniel Wagner <dwagner@suse.de>
diff = find_program('diff', required : false)
if diff.found()
config_dump = executable(
'test-config-dump',
['config-dump.c'],
dependencies: libnvme_dep,
include_directories: [incdir],
)
config_data = [
'config-pcie',
'config-pcie-with-tcp-config',
]
config_diff = find_program('config-diff.sh')
foreach t_file : config_data
test(
t_file,
config_diff,
args : [
meson.current_build_dir(),
config_dump.full_path(),
files('data'/t_file + '.tar.xz'),
files('data'/t_file + '.json'),
files('data'/t_file + '.out'),
],
depends : config_dump,
)
endforeach
test_hostnqn_order = executable(
'test-hostnqn-order',
['hostnqn-order.c'],
dependencies: libnvme_dep,
include_directories: [incdir],
)
test(
'hostnqn-order',
config_diff,
args : [
meson.current_build_dir(),
test_hostnqn_order.full_path(),
files('data/hostnqn-order.tar.xz'),
files('data/hostnqn-order.json'),
files('data/hostnqn-order.out'),
],
depends : test_hostnqn_order,
)
endif

643
test/ioctl/ana.c Normal file
View file

@ -0,0 +1,643 @@
// 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);
}

View file

@ -13,6 +13,16 @@ 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')
ana = executable(
'test-ana',
'ana.c',
dependencies: libnvme_dep,
include_directories: [incdir, internal_incdir],
link_with: mock_ioctl,
)
test('ana', ana, env: mock_ioctl_env)
discovery = executable(
'test-discovery',
'discovery.c',

View file

@ -58,19 +58,19 @@ void end_mock_cmds(void)
#define execute_ioctl(cmd, mock_cmd) ({ \
check((cmd)->opcode == (mock_cmd)->opcode, \
"got opcode %" PRIu8 ", expected %" PRIu8, \
"got opcode 0x%" PRIx8 ", expected 0x%" PRIx8, \
(cmd)->opcode, (mock_cmd)->opcode); \
check((cmd)->flags == (mock_cmd)->flags, \
"got flags %" PRIu8 ", expected %" PRIu8, \
"got flags 0x%" PRIx8 ", expected 0x%" PRIx8, \
(cmd)->flags, (mock_cmd)->flags); \
check((cmd)->nsid == (mock_cmd)->nsid, \
"got nsid %" PRIu32 ", expected %" PRIu32, \
"got nsid 0x%" PRIx32 ", expected 0x%" PRIx32, \
(cmd)->nsid, (mock_cmd)->nsid); \
check((cmd)->cdw2 == (mock_cmd)->cdw2, \
"got cdw2 %" PRIu32 ", expected %" PRIu32, \
"got cdw2 0x%" PRIx32 ", expected 0x%" PRIx32, \
(cmd)->cdw2, (mock_cmd)->cdw2); \
check((cmd)->cdw3 == (mock_cmd)->cdw3, \
"got cdw3 %" PRIu32 ", expected %" PRIu32, \
"got cdw3 0x%" PRIx32 ", expected 0x%" PRIx32, \
(cmd)->cdw3, (mock_cmd)->cdw3); \
check((cmd)->metadata_len == (mock_cmd)->metadata_len, \
"got metadata_len %" PRIu32 ", expected %" PRIu32, \
@ -90,29 +90,30 @@ void end_mock_cmds(void)
cmp(data, (mock_cmd)->in_data, data_len, "incorrect data"); \
} \
check((cmd)->cdw10 == (mock_cmd)->cdw10, \
"got cdw10 %" PRIu32 ", expected %" PRIu32, \
"got cdw10 0x%" PRIx32 ", expected 0x%" PRIx32, \
(cmd)->cdw10, (mock_cmd)->cdw10); \
check((cmd)->cdw11 == (mock_cmd)->cdw11, \
"got cdw11 %" PRIu32 ", expected %" PRIu32, \
"got cdw11 0x%" PRIx32 ", expected 0x%" PRIx32, \
(cmd)->cdw11, (mock_cmd)->cdw11); \
check((cmd)->cdw12 == (mock_cmd)->cdw12, \
"got cdw12 %" PRIu32 ", expected %" PRIu32, \
"got cdw12 0x%" PRIx32 ", expected 0x%" PRIx32, \
(cmd)->cdw12, (mock_cmd)->cdw12); \
check((cmd)->cdw13 == (mock_cmd)->cdw13, \
"got cdw13 %" PRIu32 ", expected %" PRIu32, \
"got cdw13 0x%" PRIx32 ", expected 0x%" PRIx32, \
(cmd)->cdw13, (mock_cmd)->cdw13); \
check((cmd)->cdw14 == (mock_cmd)->cdw14, \
"got cdw14 %" PRIu32 ", expected %" PRIu32, \
"got cdw14 0x%" PRIx32 ", expected 0x%" PRIx32, \
(cmd)->cdw14, (mock_cmd)->cdw14); \
check((cmd)->cdw15 == (mock_cmd)->cdw15, \
"got cdw15 %" PRIu32 ", expected %" PRIu32, \
"got cdw15 0x%" PRIx32 ", expected 0x%" PRIx32, \
(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); \
const void *out_data = (mock_cmd)->out_data; \
if (out_data) { \
memcpy(data, out_data, (mock_cmd)->out_data_len ?: data_len); \
} \
})

View file

@ -26,7 +26,9 @@
* @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`
* @out_data: if not NULL, bytes to copy to the caller's `addr`
* @out_data_len: length of `out_data` buffer to return.
* If 0, `data_len` is used instead.
* @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`.
@ -50,6 +52,7 @@ struct mock_cmd {
uint32_t cdw15;
uint32_t timeout_ms;
const void *out_data;
uint32_t out_data_len;
uint64_t result;
int err;
};

View file

@ -66,6 +66,15 @@ uuid = executable(
test('uuid', uuid)
uriparser = executable(
'test-uriparser',
['uriparser.c'],
dependencies: libnvme_dep,
include_directories: [incdir, internal_incdir]
)
test('uriparser', uriparser)
if conf.get('HAVE_NETDB')
mock_ifaddrs = library(
'mock-ifaddrs',
@ -99,5 +108,6 @@ subdir('ioctl')
subdir('nbft')
if json_c_dep.found()
subdir('sysfs')
subdir('sysfs')
subdir('config')
endif

View file

@ -115,6 +115,9 @@ nvme_mi_ep_t nvme_mi_open_test(nvme_root_t root)
ep = nvme_mi_init_ep(root);
assert(ep);
/* preempt the quirk probe to avoid clutter */
ep->quirks_probed = true;
tpd = malloc(sizeof(*tpd));
assert(tpd);
@ -1856,6 +1859,55 @@ static void test_admin_get_log_split(struct nvme_mi_ep *ep)
assert(ldata.n == 3);
}
static int test_endpoint_quirk_probe_cb_stage2(struct nvme_mi_ep *ep,
struct nvme_mi_req *req,
struct nvme_mi_resp *resp,
void *data)
{
return test_read_mi_data_cb(ep, req, resp, data);
}
static int test_endpoint_quirk_probe_cb_stage1(struct nvme_mi_ep *ep,
struct nvme_mi_req *req,
struct nvme_mi_resp *resp,
void *data)
{
struct nvme_mi_admin_req_hdr *admin_req;
__u8 ror, mt;
assert(req->hdr->type == NVME_MI_MSGTYPE_NVME);
ror = req->hdr->nmp >> 7;
mt = req->hdr->nmp >> 3 & 0x7;
assert(ror == NVME_MI_ROR_REQ);
assert(mt == NVME_MI_MT_ADMIN);
assert(req->hdr_len == sizeof(struct nvme_mi_admin_req_hdr));
admin_req = (struct nvme_mi_admin_req_hdr *)req->hdr;
assert(admin_req->opcode == nvme_admin_identify);
assert(le32_to_cpu(admin_req->doff) == 0);
assert(le32_to_cpu(admin_req->dlen) == offsetof(struct nvme_id_ctrl, rab));
test_set_transport_callback(ep, test_endpoint_quirk_probe_cb_stage2, data);
return 0;
}
static void test_endpoint_quirk_probe(struct nvme_mi_ep *ep)
{
struct nvme_mi_read_nvm_ss_info ss_info;
int rc;
/* force the probe to occur */
ep->quirks_probed = false;
test_set_transport_callback(ep, test_endpoint_quirk_probe_cb_stage1, NULL);
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
assert(rc == 0);
}
#define DEFINE_TEST(name) { #name, test_ ## name }
struct test {
const char *name;
@ -1897,6 +1949,7 @@ struct test {
DEFINE_TEST(admin_format_nvm),
DEFINE_TEST(admin_sanitize_nvm),
DEFINE_TEST(admin_get_log_split),
DEFINE_TEST(endpoint_quirk_probe),
};
static void run_test(struct test *test, FILE *logfd, nvme_mi_ep_t ep)

View file

@ -4,17 +4,6 @@
"hostnqn":"nqn.2014-08.org.nvmexpress:uuid:ce4fee3e-c02c-11ee-8442-830d068a36c6",
"hostid":"ce4fee3e-c02c-11ee-8442-830d068a36c6",
"subsystems":[
{
"name":"nvme-subsys1",
"nqn":"nqn.2019-08.org.qemu:nvme-0",
"controllers":[
{
"name":"nvme1",
"transport":"pcie",
"traddr":"0000:00:05.0"
}
]
},
{
"name":"nvme-subsys0",
"nqn":"nqn.2019-08.org.qemu:subsys1",
@ -25,6 +14,17 @@
"traddr":"0000:0f:00.0"
}
]
},
{
"name":"nvme-subsys1",
"nqn":"nqn.2019-08.org.qemu:nvme-0",
"controllers":[
{
"name":"nvme1",
"transport":"pcie",
"traddr":"0000:00:05.0"
}
]
}
]
}

Binary file not shown.

View file

@ -7,30 +7,30 @@
diff = find_program('diff', required : false)
if diff.found()
sysfs_tree_print = executable(
'sysfs-tree-print',
['sysfs.c'],
tree_dump = executable(
'test-tree-dump',
['tree-dump.c'],
dependencies: libnvme_dep,
include_directories: [incdir],
)
sysfs_files= [
'nvme-sysfs-tw-carbon-6.8.0-rc1+'
tree_data = [
'tree-pcie',
]
sysfs_tree_diff = find_program('sysfs-tree-diff.sh')
tree_diff = find_program('tree-diff.sh')
foreach t_file : sysfs_files
foreach t_file : tree_data
test(
'sysfs',
sysfs_tree_diff,
t_file,
tree_diff,
args : [
meson.current_build_dir(),
sysfs_tree_print.full_path(),
tree_dump.full_path(),
files('data'/t_file + '.tar.xz'),
files('data'/t_file + '.out'),
],
depends : sysfs_tree_print,
depends : tree_dump,
)
endforeach
endif

View file

@ -1,22 +0,0 @@
#!/bin/bash -e
# SPDX-License-Identifier: LGPL-2.1-or-later
BUILD_DIR=$1
SYSFS_TREE_PRINT=$2
INPUT=$3
EXPECTED_OUTPUT=$4
TEST_NAME="$(basename -s .tar.xz $INPUT)"
TEST_DIR="$BUILD_DIR/$TEST_NAME"
ACTUAL_OUTPUT="$TEST_DIR.out"
rm -rf "$TEST_DIR"
mkdir "$TEST_DIR"
tar -x -f "$INPUT" -C "$TEST_DIR"
LIBNVME_SYSFS_PATH="$TEST_DIR" \
LIBNVME_HOSTNQN=nqn.2014-08.org.nvmexpress:uuid:ce4fee3e-c02c-11ee-8442-830d068a36c6 \
LIBNVME_HOSTID=ce4fee3e-c02c-11ee-8442-830d068a36c6 \
"$SYSFS_TREE_PRINT" > "$ACTUAL_OUTPUT"
diff -u "$EXPECTED_OUTPUT" "$ACTUAL_OUTPUT"

View file

@ -1,24 +0,0 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/**
* This file is part of libnvme.
* Copyright (c) 2024 Daniel Wagner, SUSE LLC
*/
#include <assert.h>
#include <libnvme.h>
int main(int argc, char *argv[])
{
nvme_root_t r;
r = nvme_create_root(stdout, LOG_ERR);
assert(r);
assert(nvme_scan_topology(r, NULL, NULL) == 0);
assert(nvme_dump_tree(r) == 0);
printf("\n");
nvme_free_tree(r);
}

22
test/sysfs/tree-diff.sh Normal file
View file

@ -0,0 +1,22 @@
#!/bin/bash -e
# SPDX-License-Identifier: LGPL-2.1-or-later
BUILD_DIR=$1
TREE_DUMP=$2
SYSFS_INPUT=$3
EXPECTED_OUTPUT=$4
TEST_NAME="$(basename -s .tar.xz ${SYSFS_INPUT})"
TEST_DIR="${BUILD_DIR}/${TEST_NAME}"
ACTUAL_OUTPUT="${TEST_DIR}.out"
rm -rf "${TEST_DIR}"
mkdir "${TEST_DIR}"
tar -x -f "${SYSFS_INPUT}" -C "${TEST_DIR}"
LIBNVME_SYSFS_PATH="${TEST_DIR}" \
LIBNVME_HOSTNQN=nqn.2014-08.org.nvmexpress:uuid:ce4fee3e-c02c-11ee-8442-830d068a36c6 \
LIBNVME_HOSTID=ce4fee3e-c02c-11ee-8442-830d068a36c6 \
"${TREE_DUMP}" > "${ACTUAL_OUTPUT}" || echo "test failed"
diff -u "${EXPECTED_OUTPUT}" "${ACTUAL_OUTPUT}"

49
test/sysfs/tree-dump.c Normal file
View file

@ -0,0 +1,49 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/**
* This file is part of libnvme.
* Copyright (c) 2024 Daniel Wagner, SUSE LLC
*/
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
#include <libnvme.h>
static bool tree_dump(void)
{
bool pass = false;
nvme_root_t r;
int err;
r = nvme_create_root(stdout, LOG_ERR);
if (!r)
return false;
err = nvme_scan_topology(r, NULL, NULL);
if (err) {
if (errno != ENOENT)
goto out;
}
if (nvme_dump_tree(r))
goto out;
printf("\n");
pass = true;
out:
nvme_free_tree(r);
return pass;
}
int main(int argc, char *argv[])
{
bool pass = true;
pass = tree_dump();
fflush(stdout);
exit(pass ? EXIT_SUCCESS : EXIT_FAILURE);
}

View file

@ -48,7 +48,7 @@ static int test_ctrl(nvme_ctrl_t c)
struct nvme_self_test_log st = { 0 };
struct nvme_telemetry_log *telem = (void *)buf;
struct nvme_endurance_group_log eglog = { 0 };
struct nvme_ana_group_desc *analog = (void *)buf;
struct nvme_ana_log *analog = (void *)buf;
struct nvme_resv_notification_log resvnotify = { 0 };
struct nvme_sanitize_log_page sanlog = { 0 };
struct nvme_id_uuid_list uuid = { 0 };

221
test/uriparser.c Normal file
View file

@ -0,0 +1,221 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/**
* This file is part of libnvme.
* Copyright (c) 2024 Tomas Bzatek <tbzatek@redhat.com>
*/
#include <assert.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <ccan/array_size/array_size.h>
#include <libnvme.h>
#include <nvme/private.h>
struct test_data {
const char *uri;
/* parsed data */
const char *scheme;
const char *host;
const char *user;
const char *proto;
int port;
const char *path[7];
const char *query;
const char *frag;
};
static struct test_data test_data[] = {
{ "nvme://192.168.1.1", "nvme", "192.168.1.1" },
{ "nvme://192.168.1.1/", "nvme", "192.168.1.1" },
{ "nvme://192.168.1.1:1234", "nvme", "192.168.1.1", .port = 1234 },
{ "nvme://192.168.1.1:1234/", "nvme", "192.168.1.1", .port = 1234 },
{ "nvme+tcp://192.168.1.1", "nvme", "192.168.1.1", .proto = "tcp" },
{ "nvme+rdma://192.168.1.1/", "nvme", "192.168.1.1", .proto = "rdma" },
{ "nvme+tcp://192.168.1.1:1234",
"nvme", "192.168.1.1", .proto = "tcp", .port = 1234 },
{ "nvme+tcp://192.168.1.1:1234/",
"nvme", "192.168.1.1", .proto = "tcp", .port = 1234 },
{ "nvme+tcp://192.168.1.1:4420/path",
"nvme", "192.168.1.1", .proto = "tcp", .port = 4420,
.path = { "path", NULL }},
{ "nvme+tcp://192.168.1.1/path/",
"nvme", "192.168.1.1", .proto = "tcp", .path = { "path", NULL }},
{ "nvme+tcp://192.168.1.1:4420/p1/p2/p3",
"nvme", "192.168.1.1", .proto = "tcp", .port = 4420,
.path = { "p1", "p2", "p3", NULL }},
{ "nvme+tcp://192.168.1.1:4420/p1/p2/p3/",
"nvme", "192.168.1.1", .proto = "tcp", .port = 4420,
.path = { "p1", "p2", "p3", NULL }},
{ "nvme+tcp://192.168.1.1:4420//p1//p2/////p3",
"nvme", "192.168.1.1", .proto = "tcp", .port = 4420,
.path = { "p1", "p2", "p3", NULL }},
{ "nvme+tcp://192.168.1.1:4420//p1//p2/////p3/",
"nvme", "192.168.1.1", .proto = "tcp", .port = 4420,
.path = { "p1", "p2", "p3", NULL }},
{ "nvme://[fe80::1010]", "nvme", "fe80::1010" },
{ "nvme://[fe80::1010]/", "nvme", "fe80::1010" },
{ "nvme://[fe80::1010]:1234", "nvme", "fe80::1010", .port = 1234 },
{ "nvme://[fe80::1010]:1234/", "nvme", "fe80::1010", .port = 1234 },
{ "nvme+tcp://[fe80::1010]", "nvme", "fe80::1010", .proto = "tcp" },
{ "nvme+rdma://[fe80::1010]/", "nvme", "fe80::1010", .proto = "rdma" },
{ "nvme+tcp://[fe80::1010]:1234",
"nvme", "fe80::1010", .proto = "tcp", .port = 1234 },
{ "nvme+tcp://[fe80::1010]:1234/",
"nvme", "fe80::1010", .proto = "tcp", .port = 1234 },
{ "nvme+tcp://[fe80::1010]:4420/path",
"nvme", "fe80::1010", .proto = "tcp", .port = 4420,
.path = { "path", NULL }},
{ "nvme+tcp://[fe80::1010]/path/",
"nvme", "fe80::1010", .proto = "tcp", .path = { "path", NULL }},
{ "nvme+tcp://[fe80::1010]:4420/p1/p2/p3",
"nvme", "fe80::1010", .proto = "tcp", .port = 4420,
.path = { "p1", "p2", "p3", NULL }},
{ "nvme+tcp://[fe80::fc7d:8cff:fe5b:962e]:666/p1/p2/p3/",
"nvme", "fe80::fc7d:8cff:fe5b:962e", .proto = "tcp", .port = 666,
.path = { "p1", "p2", "p3", NULL }},
{ "nvme://h?query", "nvme", "h", .query = "query" },
{ "nvme://h/?query", "nvme", "h", .query = "query" },
{ "nvme://h/x?query",
"nvme", "h", .path = { "x" }, .query = "query" },
{ "nvme://h/p1/?query",
"nvme", "h", .path = { "p1" }, .query = "query" },
{ "nvme://h/p1/x?query",
"nvme", "h", .path = { "p1", "x" }, .query = "query" },
{ "nvme://h#fragment", "nvme", "h", .frag = "fragment" },
{ "nvme://h/#fragment", "nvme", "h", .frag = "fragment" },
{ "nvme://h/x#fragment",
"nvme", "h", .path = { "x" }, .frag = "fragment" },
{ "nvme://h/p1/#fragment",
"nvme", "h", .path = { "p1" }, .frag = "fragment" },
{ "nvme://h/p1/x#fragment",
"nvme", "h", .path = { "p1", "x" }, .frag = "fragment" },
{ "nvme://h/?query#fragment",
"nvme", "h", .query = "query", .frag = "fragment" },
{ "nvme://h/x?query#fragment",
"nvme", "h", .path = { "x" }, .query = "query", .frag = "fragment" },
{ "nvme://h/p1/?query#fragment",
"nvme", "h", .path = { "p1" }, .query = "query", .frag = "fragment" },
{ "nvme://h/p1/x?query#fragment",
"nvme", "h", .path = { "p1", "x" }, .query = "query",
.frag = "fragment" },
{ "nvme://h/#fragment?query",
"nvme", "h", .frag = "fragment?query" },
{ "nvme://h/x#fragment?query",
"nvme", "h", .path = { "x" }, .frag = "fragment?query" },
{ "nvme://h/p1/#fragment?query",
"nvme", "h", .path = { "p1" }, .frag = "fragment?query" },
{ "nvme://h/p1/x#fragment?query",
"nvme", "h", .path = { "p1", "x" }, .frag = "fragment?query" },
{ "nvme://user@h", "nvme", "h", .user = "user" },
{ "nvme://user@h/", "nvme", "h", .user = "user" },
{ "nvme://user:pass@h/", "nvme", "h", .user = "user:pass" },
{ "nvme://[fe80::1010]@h/", "nvme", "h", .user = "[fe80::1010]" },
{ "nvme://u[fe80::1010]@h/", "nvme", "h", .user = "u[fe80::1010]" },
{ "nvme://u[aa:bb::cc]@h/", "nvme", "h", .user = "u[aa:bb::cc]" },
{ "nvme+rdma://u[aa:bb::cc]@[aa:bb::cc]:12345/p1/x?q=val#fr",
"nvme", "aa:bb::cc", .proto = "rdma", .port = 12345,
.user = "u[aa:bb::cc]", .path = { "p1", "x" },
.query = "q=val", .frag = "fr" },
{ "nvme://ex%5Cmp%3Ae", "nvme", "ex\\mp:e" },
{ "nvme://ex%5Cmp%3Ae.com/", "nvme", "ex\\mp:e.com" },
{ "nvme://u%24er@ex%5Cmp%3Ae.com/", "nvme", "ex\\mp:e.com",
.user = "u$er" },
{ "nvme+tcp://ex%5Cmp%3Ae.com:1234",
"nvme", "ex\\mp:e.com", .proto = "tcp", .port = 1234 },
{ "nvme+tcp://ex%5Cmp%3Ae.com:1234/p1/ex%3Camp%3Ele/p3",
"nvme", "ex\\mp:e.com", .proto = "tcp", .port = 1234,
.path = { "p1", "ex<amp>le", "p3", NULL } },
{ "nvme+tcp://ex%5Cmp%3Ae.com:1234/p1/%3C%3E/p3?q%5E%24ry#fr%26gm%23nt",
"nvme", "ex\\mp:e.com", .proto = "tcp", .port = 1234,
.path = { "p1", "<>", "p3", NULL }, .query = "q^$ry",
.frag = "fr&gm#nt" },
};
const char *test_data_bad[] = {
"",
" ",
"nonsense",
"vnme:",
"vnme:/",
"vnme://",
"vnme:///",
"vnme+foo://",
"nvme:hostname/",
"nvme:/hostname/",
"nvme:///hostname/",
"nvme+foo:///hostname/",
};
static void test_uriparser(void)
{
printf("Testing URI parser:\n");
for (int i = 0; i < ARRAY_SIZE(test_data); i++) {
const struct test_data *d = &test_data[i];
struct nvme_fabrics_uri *parsed_data;
char **s;
int i;
printf(" '%s'...", d->uri);
parsed_data = nvme_parse_uri(d->uri);
assert(parsed_data);
assert(strcmp(d->scheme, parsed_data->scheme) == 0);
if (d->proto) {
assert(parsed_data->protocol != NULL);
assert(strcmp(d->proto, parsed_data->protocol) == 0);
} else
assert(d->proto == parsed_data->protocol);
assert(strcmp(d->host, parsed_data->host) == 0);
assert(d->port == parsed_data->port);
if (!parsed_data->path_segments)
assert(d->path[0] == NULL);
else {
for (i = 0, s = parsed_data->path_segments;
s && *s; s++, i++) {
assert(d->path[i] != NULL);
assert(strcmp(d->path[i], *s) == 0);
}
/* trailing NULL element */
assert(d->path[i] == parsed_data->path_segments[i]);
}
if (d->query) {
assert(parsed_data->query != NULL);
assert(strcmp(d->query, parsed_data->query) == 0);
} else
assert(d->query == parsed_data->query);
if (d->frag) {
assert(parsed_data->fragment != NULL);
assert(strcmp(d->frag, parsed_data->fragment) == 0);
} else
assert(d->frag == parsed_data->fragment);
nvme_free_uri(parsed_data);
printf(" OK\n");
}
}
static void test_uriparser_bad(void)
{
printf("Testing malformed URI strings:\n");
for (int i = 0; i < ARRAY_SIZE(test_data_bad); i++) {
struct nvme_fabrics_uri *parsed_data;
printf(" '%s'...", test_data_bad[i]);
parsed_data = nvme_parse_uri(test_data_bad[i]);
assert(parsed_data == NULL);
printf(" OK\n");
}
}
int main(int argc, char *argv[])
{
test_uriparser();
test_uriparser_bad();
fflush(stdout);
return 0;
}