1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2024 Oxide Computer Company
14  */
15 
16 /*
17  * This tests various pieces around trying to hold multiple locks or perform
18  * recursive locks. This includes:
19  *
20  *   o Recursively grabbing any kind of lock
21  *   o Trying to take any controller lock while holding any namespace lock
22  *     (controller only)
23  *   o Trying to take a namespace lock while holding the controller write lock
24  *     (controller only)
25  *
26  * This is organized as taking a given lock type and then trying all the things
27  * that should fail. We currently don't test the following here because we don't
28  * have tests that easily allow for determining devices with or without multiple
29  * namespaces:
30  *
31  *   o Asking to unlock a namespace that isn't the one you have locked
32  *     (controller only)
33  */
34 
35 #include <err.h>
36 #include <stdlib.h>
37 #include <unistd.h>
38 #include <stdbool.h>
39 #include <sys/sysmacros.h>
40 #include <sys/debug.h>
41 
42 #include "nvme_ioctl_util.h"
43 
44 typedef struct {
45 	const char *rlt_desc;
46 	bool rlt_ctrl_only;
47 	const nvme_ioctl_lock_t *rlt_lock;
48 } rec_lock_test_t;
49 
50 static const rec_lock_test_t rec_lock_tests[] = { {
51 	.rlt_desc = "recursive controller write lock",
52 	.rlt_ctrl_only = true,
53 	.rlt_lock = &nvme_test_ctrl_wrlock
54 }, {
55 	.rlt_desc = "recursive controller read lock",
56 	.rlt_ctrl_only = true,
57 	.rlt_lock = &nvme_test_ctrl_rdlock
58 }, {
59 	.rlt_desc = "recursive namespace write lock",
60 	.rlt_lock = &nvme_test_ns_wrlock
61 }, {
62 	.rlt_desc = "recursive namespace read lock",
63 	.rlt_lock = &nvme_test_ns_rdlock
64 } };
65 
66 typedef struct {
67 	const char *nlt_desc;
68 	const nvme_ioctl_lock_t *nlt_lock;
69 	nvme_ioctl_errno_t nlt_err;
70 } ns_lock_test_t;
71 
72 static const ns_lock_test_t ns_lock_tests[] = { {
73 	.nlt_desc = "take controller read lock w/ ns lock",
74 	.nlt_lock = &nvme_test_ctrl_rdlock,
75 	.nlt_err = NVME_IOCTL_E_LOCK_NO_CTRL_WITH_NS
76 }, {
77 	.nlt_desc = "take controller read lock w/ ns lock",
78 	.nlt_lock = &nvme_test_ctrl_wrlock,
79 	.nlt_err = NVME_IOCTL_E_LOCK_NO_CTRL_WITH_NS
80 } };
81 
82 static const ns_lock_test_t ns_ctrl_tests[] = { {
83 	.nlt_desc = "attempt ns read lock with controller write lock",
84 	.nlt_lock = &nvme_test_ns_rdlock,
85 	.nlt_err = NVME_IOCTL_LOCK_NO_NS_WITH_CTRL_WRLOCK
86 }, {
87 	.nlt_desc = "attempt ns write lock with controller write lock",
88 	.nlt_lock = &nvme_test_ns_wrlock,
89 	.nlt_err = NVME_IOCTL_LOCK_NO_NS_WITH_CTRL_WRLOCK
90 } };
91 
92 static bool
93 rec_lock_test(int fd, const rec_lock_test_t *test, bool nsfd)
94 {
95 	nvme_ioctl_lock_t lock = *test->rlt_lock;
96 	const char *type = nsfd ? "(ns)" : "(ctrl)";
97 
98 	if (ioctl(fd, NVME_IOC_LOCK, &lock) != 0) {
99 		warn("TEST FAILED: %s %s: failed to issue initial lock ioctl",
100 		    test->rlt_desc, type);
101 		return (false);
102 	} else if (lock.nil_common.nioc_drv_err != NVME_IOCTL_E_OK) {
103 		warnx("TEST FAILED: %s %s: initial lock ioctl failed with "
104 		    "0x%x, expected success", test->rlt_desc, type,
105 		    lock.nil_common.nioc_drv_err);
106 		return (false);
107 	}
108 
109 	lock = *test->rlt_lock;
110 
111 	if (ioctl(fd, NVME_IOC_LOCK, &lock) != 0) {
112 		warn("TEST FAILED: %s %s: failed to issue recursive lock ioctl",
113 		    test->rlt_desc, type);
114 		return (false);
115 	} else if (lock.nil_common.nioc_drv_err !=
116 	    NVME_IOCTL_E_LOCK_ALREADY_HELD) {
117 		warnx("TEST FAILED: %s %s: recursive lock ioctl failed with "
118 		    "0x%x, expected 0x%x (NVME_IOCTL_E_LOCK_ALREADY_HELD)",
119 		    test->rlt_desc, type, lock.nil_common.nioc_drv_err,
120 		    NVME_IOCTL_E_LOCK_ALREADY_HELD);
121 		return (false);
122 	}
123 
124 	return (true);
125 }
126 
127 static bool
128 ns_lock_test(int fd, const ns_lock_test_t *test, bool rdlock)
129 {
130 	nvme_ioctl_lock_t lock = *test->nlt_lock;
131 	const char *type = rdlock ? "(ns read lock)" : "(ns write lock)";
132 
133 	if (ioctl(fd, NVME_IOC_LOCK, &lock) != 0) {
134 		warn("TEST FAILED: %s %s: failed to issue lock ioctl",
135 		    test->nlt_desc, type);
136 		return (false);
137 	} else if (lock.nil_common.nioc_drv_err != test->nlt_err) {
138 		warnx("TEST FAILED: %s %s: recursive lock ioctl failed with "
139 		    "0x%x, expected 0x%x",
140 		    test->nlt_desc, type, lock.nil_common.nioc_drv_err,
141 		    test->nlt_err);
142 		return (false);
143 	} else {
144 		(void) printf("TEST PASSED: %s\n", test->nlt_desc);
145 	}
146 
147 	return (true);
148 }
149 
150 int
151 main(void)
152 {
153 	int ret = EXIT_SUCCESS;
154 
155 	/*
156 	 * Recusive lock tests
157 	 */
158 	for (size_t i = 0; i < ARRAY_SIZE(rec_lock_tests); i++) {
159 		int fd = nvme_ioctl_test_get_fd(0);
160 		if (!rec_lock_test(fd, &rec_lock_tests[i], false)) {
161 			ret = EXIT_FAILURE;
162 		}
163 		VERIFY0(close(fd));
164 
165 		if (rec_lock_tests[i].rlt_ctrl_only)
166 			continue;
167 
168 		fd = nvme_ioctl_test_get_fd(1);
169 		if (!rec_lock_test(fd, &rec_lock_tests[i], true)) {
170 			ret = EXIT_FAILURE;
171 		}
172 		VERIFY0(close(fd));
173 	}
174 
175 	/*
176 	 * Second lock attempts while holding namespace locks. We do two passes
177 	 * to make sure there's no difference between the read and write side.
178 	 * This can only happen on controller fd's.
179 	 */
180 	int fd = nvme_ioctl_test_get_fd(0);
181 	nvme_ioctl_test_lock(fd, &nvme_test_ns_rdlock);
182 	for (size_t i = 0; i < ARRAY_SIZE(ns_lock_tests); i++) {
183 		if (!ns_lock_test(fd, &ns_lock_tests[i], true)) {
184 			ret = EXIT_FAILURE;
185 		}
186 	}
187 	VERIFY0(close(fd));
188 
189 	fd = nvme_ioctl_test_get_fd(0);
190 	nvme_ioctl_test_lock(fd, &nvme_test_ns_wrlock);
191 	for (size_t i = 0; i < ARRAY_SIZE(ns_lock_tests); i++) {
192 		if (!ns_lock_test(fd, &ns_lock_tests[i], false)) {
193 			ret = EXIT_FAILURE;
194 		}
195 	}
196 	VERIFY0(close(fd));
197 
198 	fd = nvme_ioctl_test_get_fd(0);
199 	nvme_ioctl_test_lock(fd, &nvme_test_ctrl_wrlock);
200 	for (size_t i = 0; i < ARRAY_SIZE(ns_ctrl_tests); i++) {
201 		if (!ns_lock_test(fd, &ns_ctrl_tests[i], true)) {
202 			ret = EXIT_FAILURE;
203 		}
204 	}
205 	VERIFY0(close(fd));
206 
207 	return (ret);
208 }
209