diff options
Diffstat (limited to '')
28 files changed, 1491 insertions, 81 deletions
diff --git a/test/config/config-diff.sh b/test/config/config-diff.sh new file mode 100644 index 0000000..c15b42c --- /dev/null +++ b/test/config/config-diff.sh @@ -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}" diff --git a/test/config/config-dump.c b/test/config/config-dump.c new file mode 100644 index 0000000..c0c8e73 --- /dev/null +++ b/test/config/config-dump.c @@ -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); +} diff --git a/test/config/data/config-pcie-with-tcp-config.json b/test/config/data/config-pcie-with-tcp-config.json new file mode 100644 index 0000000..6810f75 --- /dev/null +++ b/test/config/data/config-pcie-with-tcp-config.json @@ -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" + } + ] + } + ] + } +] diff --git a/test/config/data/config-pcie-with-tcp-config.out b/test/config/data/config-pcie-with-tcp-config.out new file mode 100644 index 0000000..6810f75 --- /dev/null +++ b/test/config/data/config-pcie-with-tcp-config.out @@ -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" + } + ] + } + ] + } +] diff --git a/test/config/data/config-pcie-with-tcp-config.tar.xz b/test/config/data/config-pcie-with-tcp-config.tar.xz Binary files differnew file mode 100644 index 0000000..73c9f62 --- /dev/null +++ b/test/config/data/config-pcie-with-tcp-config.tar.xz diff --git a/test/config/data/config-pcie.json b/test/config/data/config-pcie.json new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/config/data/config-pcie.json diff --git a/test/config/data/config-pcie.out b/test/config/data/config-pcie.out new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/config/data/config-pcie.out diff --git a/test/sysfs/data/nvme-sysfs-tw-carbon-6.8.0-rc1+.tar.xz b/test/config/data/config-pcie.tar.xz Binary files differindex ee11fde..ee11fde 100644 --- a/test/sysfs/data/nvme-sysfs-tw-carbon-6.8.0-rc1+.tar.xz +++ b/test/config/data/config-pcie.tar.xz diff --git a/test/config/data/hostnqn-order.json b/test/config/data/hostnqn-order.json new file mode 100644 index 0000000..6810f75 --- /dev/null +++ b/test/config/data/hostnqn-order.json @@ -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" + } + ] + } + ] + } +] diff --git a/test/config/data/hostnqn-order.out b/test/config/data/hostnqn-order.out new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/config/data/hostnqn-order.out diff --git a/test/config/data/hostnqn-order.tar.xz b/test/config/data/hostnqn-order.tar.xz Binary files differnew file mode 100644 index 0000000..73c9f62 --- /dev/null +++ b/test/config/data/hostnqn-order.tar.xz diff --git a/test/config/hostnqn-order.c b/test/config/hostnqn-order.c new file mode 100644 index 0000000..22fc227 --- /dev/null +++ b/test/config/hostnqn-order.c @@ -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); +} diff --git a/test/config/meson.build b/test/config/meson.build new file mode 100644 index 0000000..c1ee7ca --- /dev/null +++ b/test/config/meson.build @@ -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 diff --git a/test/ioctl/ana.c b/test/ioctl/ana.c new file mode 100644 index 0000000..ba85498 --- /dev/null +++ b/test/ioctl/ana.c @@ -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); +} diff --git a/test/ioctl/meson.build b/test/ioctl/meson.build index b329d27..bbee047 100644 --- a/test/ioctl/meson.build +++ b/test/ioctl/meson.build @@ -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', diff --git a/test/ioctl/mock.c b/test/ioctl/mock.c index a97a357..1fb3ec1 100644 --- a/test/ioctl/mock.c +++ b/test/ioctl/mock.c @@ -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); \ } \ }) diff --git a/test/ioctl/mock.h b/test/ioctl/mock.h index 192eba8..c4b4bd6 100644 --- a/test/ioctl/mock.h +++ b/test/ioctl/mock.h @@ -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; }; diff --git a/test/meson.build b/test/meson.build index 93e6999..ca2a792 100644 --- a/test/meson.build +++ b/test/meson.build @@ -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 @@ -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) diff --git a/test/sysfs/data/nvme-sysfs-tw-carbon-6.8.0-rc1+.out b/test/sysfs/data/tree-pcie.out index 1cb6de4..4a755a7 100644 --- a/test/sysfs/data/nvme-sysfs-tw-carbon-6.8.0-rc1+.out +++ b/test/sysfs/data/tree-pcie.out @@ -5,24 +5,24 @@ "hostid":"ce4fee3e-c02c-11ee-8442-830d068a36c6", "subsystems":[ { - "name":"nvme-subsys1", - "nqn":"nqn.2019-08.org.qemu:nvme-0", + "name":"nvme-subsys0", + "nqn":"nqn.2019-08.org.qemu:subsys1", "controllers":[ { - "name":"nvme1", + "name":"nvme0", "transport":"pcie", - "traddr":"0000:00:05.0" + "traddr":"0000:0f:00.0" } ] }, { - "name":"nvme-subsys0", - "nqn":"nqn.2019-08.org.qemu:subsys1", + "name":"nvme-subsys1", + "nqn":"nqn.2019-08.org.qemu:nvme-0", "controllers":[ { - "name":"nvme0", + "name":"nvme1", "transport":"pcie", - "traddr":"0000:0f:00.0" + "traddr":"0000:00:05.0" } ] } diff --git a/test/sysfs/data/tree-pcie.tar.xz b/test/sysfs/data/tree-pcie.tar.xz Binary files differnew file mode 100644 index 0000000..ee11fde --- /dev/null +++ b/test/sysfs/data/tree-pcie.tar.xz diff --git a/test/sysfs/meson.build b/test/sysfs/meson.build index 119fa97..2a7e8e3 100644 --- a/test/sysfs/meson.build +++ b/test/sysfs/meson.build @@ -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 diff --git a/test/sysfs/sysfs-tree-diff.sh b/test/sysfs/sysfs-tree-diff.sh deleted file mode 100644 index dfe3cb3..0000000 --- a/test/sysfs/sysfs-tree-diff.sh +++ /dev/null @@ -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" diff --git a/test/sysfs/sysfs.c b/test/sysfs/sysfs.c deleted file mode 100644 index 06d0035..0000000 --- a/test/sysfs/sysfs.c +++ /dev/null @@ -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); -} diff --git a/test/sysfs/tree-diff.sh b/test/sysfs/tree-diff.sh new file mode 100644 index 0000000..39b8d0d --- /dev/null +++ b/test/sysfs/tree-diff.sh @@ -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}" diff --git a/test/sysfs/tree-dump.c b/test/sysfs/tree-dump.c new file mode 100644 index 0000000..bcf871b --- /dev/null +++ b/test/sysfs/tree-dump.c @@ -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); +} diff --git a/test/test.c b/test/test.c index 23036bb..c0146d3 100644 --- a/test/test.c +++ b/test/test.c @@ -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 }; diff --git a/test/uriparser.c b/test/uriparser.c new file mode 100644 index 0000000..09b2a73 --- /dev/null +++ b/test/uriparser.c @@ -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; +} |