summaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--test/config/config-diff.sh24
-rw-r--r--test/config/config-dump.c53
-rw-r--r--test/config/data/config-pcie-with-tcp-config.json48
-rw-r--r--test/config/data/config-pcie-with-tcp-config.out48
-rw-r--r--test/config/data/config-pcie-with-tcp-config.tar.xzbin0 -> 11316 bytes
-rw-r--r--test/config/data/config-pcie.json0
-rw-r--r--test/config/data/config-pcie.out0
-rw-r--r--test/config/data/config-pcie.tar.xz (renamed from test/sysfs/data/nvme-sysfs-tw-carbon-6.8.0-rc1+.tar.xz)bin19712 -> 19712 bytes
-rw-r--r--test/config/data/hostnqn-order.json48
-rw-r--r--test/config/data/hostnqn-order.out0
-rw-r--r--test/config/data/hostnqn-order.tar.xzbin0 -> 11316 bytes
-rw-r--r--test/config/hostnqn-order.c164
-rw-r--r--test/config/meson.build59
-rw-r--r--test/ioctl/ana.c643
-rw-r--r--test/ioctl/meson.build10
-rw-r--r--test/ioctl/mock.c27
-rw-r--r--test/ioctl/mock.h5
-rw-r--r--test/meson.build12
-rw-r--r--test/mi.c53
-rw-r--r--test/sysfs/data/tree-pcie.out (renamed from test/sysfs/data/nvme-sysfs-tw-carbon-6.8.0-rc1+.out)16
-rw-r--r--test/sysfs/data/tree-pcie.tar.xzbin0 -> 19712 bytes
-rw-r--r--test/sysfs/meson.build22
-rw-r--r--test/sysfs/sysfs-tree-diff.sh22
-rw-r--r--test/sysfs/sysfs.c24
-rw-r--r--test/sysfs/tree-diff.sh22
-rw-r--r--test/sysfs/tree-dump.c49
-rw-r--r--test/test.c2
-rw-r--r--test/uriparser.c221
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
new file mode 100644
index 0000000..73c9f62
--- /dev/null
+++ b/test/config/data/config-pcie-with-tcp-config.tar.xz
Binary files differ
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
index 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
Binary files differ
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
new file mode 100644
index 0000000..73c9f62
--- /dev/null
+++ b/test/config/data/hostnqn-order.tar.xz
Binary files differ
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
diff --git a/test/mi.c b/test/mi.c
index a86ba15..0dc72f1 100644
--- a/test/mi.c
+++ b/test/mi.c
@@ -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
new file mode 100644
index 0000000..ee11fde
--- /dev/null
+++ b/test/sysfs/data/tree-pcie.tar.xz
Binary files differ
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;
+}