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 test covers the following aspects of the locking behavior:
18  *
19  *  o A controller write lock blocks controller read/write locks
20  *  o A controller write lock blocks namespace read/write locks
21  *  o A controller read lock blocks controller write locks
22  *  o A controller read lock does not block namespace write locks
23  *  o A namespace write lock blocks namespace read/write locks
24  *  o A namespace write lock blocks controller write locks, but not read locks
25  *  o A namespace read lock blocks namespace write locks
26  *  o A namespace read lock blocks controller write locks
27  *
28  * The interaction of various read locks is tested in multi-reader-lock.c.
29  */
30 
31 #include <err.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <stdbool.h>
35 #include <sys/sysmacros.h>
36 #include <sys/debug.h>
37 
38 #include "nvme_ioctl_util.h"
39 
40 typedef enum {
41 	CBF_FD_CTRL,
42 	CBF_FD_NS
43 } ctrl_block_fd_t;
44 
45 /*
46  * This structure describes a given test case. We expect to always succeed in
47  * locking fd0 and then we will expect the return value in cbt_ret1 when we try
48  * to take lock1.
49  */
50 typedef struct {
51 	const char *cbt_desc;
52 	ctrl_block_fd_t cbt_fd0;
53 	ctrl_block_fd_t cbt_fd1;
54 	const nvme_ioctl_lock_t *cbt_lock0;
55 	const nvme_ioctl_lock_t *cbt_lock1;
56 	nvme_ioctl_errno_t cbt_ret1;
57 } ctrl_block_test_t;
58 
59 static const ctrl_block_test_t ctrl_block_tests[] = { {
60 	.cbt_desc = "controller write blocks controller read",
61 	.cbt_fd0 = CBF_FD_CTRL,
62 	.cbt_fd1 = CBF_FD_CTRL,
63 	.cbt_lock0 = &nvme_test_ctrl_wrlock,
64 	.cbt_lock1 = &nvme_test_ctrl_rdlock,
65 	.cbt_ret1 = NVME_IOCTL_E_LOCK_WOULD_BLOCK
66 }, {
67 	.cbt_desc = "controller write blocks controller write",
68 	.cbt_fd0 = CBF_FD_CTRL,
69 	.cbt_fd1 = CBF_FD_CTRL,
70 	.cbt_lock0 = &nvme_test_ctrl_wrlock,
71 	.cbt_lock1 = &nvme_test_ctrl_wrlock,
72 	.cbt_ret1 = NVME_IOCTL_E_LOCK_WOULD_BLOCK
73 }, {
74 	.cbt_desc = "controller write blocks namespace read",
75 	.cbt_fd0 = CBF_FD_CTRL,
76 	.cbt_fd1 = CBF_FD_NS,
77 	.cbt_lock0 = &nvme_test_ctrl_wrlock,
78 	.cbt_lock1 = &nvme_test_ns_rdlock,
79 	.cbt_ret1 = NVME_IOCTL_E_LOCK_WOULD_BLOCK
80 }, {
81 	.cbt_desc = "controller write blocks namespace write",
82 	.cbt_fd0 = CBF_FD_CTRL,
83 	.cbt_fd1 = CBF_FD_NS,
84 	.cbt_lock0 = &nvme_test_ctrl_wrlock,
85 	.cbt_lock1 = &nvme_test_ns_wrlock,
86 	.cbt_ret1 = NVME_IOCTL_E_LOCK_WOULD_BLOCK
87 }, {
88 	.cbt_desc = "controller read blocks controller write",
89 	.cbt_fd0 = CBF_FD_CTRL,
90 	.cbt_fd1 = CBF_FD_CTRL,
91 	.cbt_lock0 = &nvme_test_ctrl_rdlock,
92 	.cbt_lock1 = &nvme_test_ctrl_wrlock,
93 	.cbt_ret1 = NVME_IOCTL_E_LOCK_WOULD_BLOCK
94 }, {
95 	.cbt_desc = "controller read does not block namespace write",
96 	.cbt_fd0 = CBF_FD_CTRL,
97 	.cbt_fd1 = CBF_FD_NS,
98 	.cbt_lock0 = &nvme_test_ctrl_rdlock,
99 	.cbt_lock1 = &nvme_test_ns_wrlock,
100 	.cbt_ret1 = NVME_IOCTL_E_OK
101 }, {
102 	.cbt_desc = "namespace write blocks namespace read",
103 	.cbt_fd0 = CBF_FD_NS,
104 	.cbt_fd1 = CBF_FD_NS,
105 	.cbt_lock0 = &nvme_test_ns_wrlock,
106 	.cbt_lock1 = &nvme_test_ns_rdlock,
107 	.cbt_ret1 = NVME_IOCTL_E_LOCK_WOULD_BLOCK
108 }, {
109 	.cbt_desc = "namespace write blocks namespace read",
110 	.cbt_fd0 = CBF_FD_NS,
111 	.cbt_fd1 = CBF_FD_NS,
112 	.cbt_lock0 = &nvme_test_ns_wrlock,
113 	.cbt_lock1 = &nvme_test_ns_rdlock,
114 	.cbt_ret1 = NVME_IOCTL_E_LOCK_WOULD_BLOCK
115 }, {
116 	.cbt_desc = "namespace write blocks namespace write",
117 	.cbt_fd0 = CBF_FD_NS,
118 	.cbt_fd1 = CBF_FD_NS,
119 	.cbt_lock0 = &nvme_test_ns_wrlock,
120 	.cbt_lock1 = &nvme_test_ns_wrlock,
121 	.cbt_ret1 = NVME_IOCTL_E_LOCK_WOULD_BLOCK
122 }, {
123 	.cbt_desc = "namespace write blocks controller write",
124 	.cbt_fd0 = CBF_FD_NS,
125 	.cbt_fd1 = CBF_FD_CTRL,
126 	.cbt_lock0 = &nvme_test_ns_wrlock,
127 	.cbt_lock1 = &nvme_test_ctrl_wrlock,
128 	.cbt_ret1 = NVME_IOCTL_E_LOCK_WOULD_BLOCK
129 }, {
130 	.cbt_desc = "namespace write does not block controller read",
131 	.cbt_fd0 = CBF_FD_NS,
132 	.cbt_fd1 = CBF_FD_CTRL,
133 	.cbt_lock0 = &nvme_test_ns_wrlock,
134 	.cbt_lock1 = &nvme_test_ctrl_rdlock,
135 	.cbt_ret1 = NVME_IOCTL_E_OK
136 }, {
137 	.cbt_desc = "namespace read blocks namespace write",
138 	.cbt_fd0 = CBF_FD_NS,
139 	.cbt_fd1 = CBF_FD_NS,
140 	.cbt_lock0 = &nvme_test_ns_rdlock,
141 	.cbt_lock1 = &nvme_test_ns_wrlock,
142 	.cbt_ret1 = NVME_IOCTL_E_LOCK_WOULD_BLOCK
143 }, {
144 	.cbt_desc = "namespace read blocks controller write",
145 	.cbt_fd0 = CBF_FD_NS,
146 	.cbt_fd1 = CBF_FD_CTRL,
147 	.cbt_lock0 = &nvme_test_ns_rdlock,
148 	.cbt_lock1 = &nvme_test_ctrl_wrlock,
149 	.cbt_ret1 = NVME_IOCTL_E_LOCK_WOULD_BLOCK
150 } };
151 
152 static bool
153 ctrl_block_test_one(int fd0, int fd1, const ctrl_block_test_t *test)
154 {
155 	nvme_ioctl_lock_t lock0 = *test->cbt_lock0;
156 	nvme_ioctl_lock_t lock1 = *test->cbt_lock1;
157 
158 	if (ioctl(fd0, NVME_IOC_LOCK, &lock0) != 0) {
159 		warn("TEST FAILED: %s: failed to issue lock ioctl for fd0",
160 		    test->cbt_desc);
161 		return (false);
162 	} else if (lock0.nil_common.nioc_drv_err != NVME_IOCTL_E_OK) {
163 		warnx("TEST FAILED: %s: fd0 lock ioctl failed with 0x%x, "
164 		    "expected success", test->cbt_desc,
165 		    lock0.nil_common.nioc_drv_err);
166 		return (false);
167 	}
168 
169 	if (ioctl(fd1, NVME_IOC_LOCK, &lock1) != 0) {
170 		warn("TEST FAILED: %s: failed to issue lock ioctl for fd1",
171 		    test->cbt_desc);
172 		return (false);
173 	} else if (lock1.nil_common.nioc_drv_err != test->cbt_ret1) {
174 		warnx("TEST FAILED: %s: fd1 lock ioctl returned with 0x%x, "
175 		    "expected 0x%x", test->cbt_desc,
176 		    lock1.nil_common.nioc_drv_err, test->cbt_ret1);
177 		return (false);
178 	}
179 
180 	(void) printf("TEST PASSED: %s\n", test->cbt_desc);
181 	return (true);
182 }
183 
184 int
185 main(void)
186 {
187 	int ret = EXIT_SUCCESS;
188 
189 	/*
190 	 * We purposefully open and close the fds every iteration of this loop
191 	 * so we don't have to explicitly issue conditional unlocks.
192 	 */
193 	for (size_t i = 0; i < ARRAY_SIZE(ctrl_block_tests); i++) {
194 		int fd0, fd1;
195 
196 		if (ctrl_block_tests[i].cbt_fd0 == CBF_FD_CTRL) {
197 			fd0 = nvme_ioctl_test_get_fd(0);
198 		} else {
199 			fd0 = nvme_ioctl_test_get_fd(1);
200 		}
201 
202 		if (ctrl_block_tests[i].cbt_fd1 == CBF_FD_CTRL) {
203 			fd1 = nvme_ioctl_test_get_fd(0);
204 		} else {
205 			fd1 = nvme_ioctl_test_get_fd(1);
206 		}
207 
208 		if (!ctrl_block_test_one(fd0, fd1, &ctrl_block_tests[i])) {
209 			ret = EXIT_FAILURE;
210 		}
211 
212 		VERIFY0(close(fd0));
213 		VERIFY0(close(fd1));
214 	}
215 
216 	return (ret);
217 }
218