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 2025 Oxide Computer Company
14 */
15
16 /*
17 * Various destructive options require a write lock. These include:
18 *
19 * - namespace creation and deletion
20 * - attaching or detaching a controller to a namespace
21 * - manipulating blkdev state
22 * - formatting a namespace
23 *
24 * While firmware operations also require this, that is harder to test in this
25 * case as we don't have valid firmware files or want to manipulate the device.
26 * We check that operations fail in sevearl different situations:
27 *
28 * 1) With no locks held.
29 * 2) With a namespace read lock.
30 * 3) With a controller read lock.
31 * 4) With a namespace write lock (note, some items succeed here).
32 *
33 * This test starts from the device-empty profile.
34 */
35
36 #include <err.h>
37 #include <stdlib.h>
38 #include <sys/sysmacros.h>
39 #include "libnvme_test_common.h"
40
41 #define LOCK_NSID 1
42
43 static uint32_t lock_lbaf;
44
45 typedef enum {
46 LOCK_NONE,
47 LOCK_NS_RD,
48 LOCK_NS_WR,
49 LOCK_CTRL_RD,
50 LOCK_MAX
51 } lock_type_t;
52
53 typedef struct write_test {
54 const char *wt_desc;
55 nvme_err_t wt_errs[LOCK_MAX];
56 nvme_ns_disc_level_t wt_disc;
57 bool (*wt_func)(nvme_ctrl_t *, const char *, nvme_err_t);
58 } write_test_t;
59
60 static bool
write_lock_ns_create(nvme_ctrl_t * ctrl,const char * desc,nvme_err_t exp)61 write_lock_ns_create(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp)
62 {
63 uint64_t create_size = NVME_TEST_NS_SIZE / NVME_TEST_LBA_SIZE;
64 uint32_t nsid;
65 nvme_err_t act;
66
67 if (!libnvme_test_ns_create(ctrl, create_size, lock_lbaf, &nsid,
68 &act)) {
69 warnx("TEST FAILED: failed to initialize namespace create"
70 "request in lock %s iteration", desc);
71 return (false);
72 } else if (act != exp) {
73 warnx("TEST FAILED: namespace create with %s returned %s "
74 "(0x%x), but expected %s (0x%x)", desc,
75 nvme_ctrl_errtostr(ctrl, act), act,
76 nvme_ctrl_errtostr(ctrl, exp), exp);
77 return (false);
78 }
79
80 (void) printf("TEST PASSED: namespace delete with %s returned %s\n",
81 desc, nvme_ctrl_errtostr(ctrl, act));
82
83 return (true);
84 }
85
86 static bool
write_lock_ns_delete(nvme_ctrl_t * ctrl,const char * desc,nvme_err_t exp)87 write_lock_ns_delete(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp)
88 {
89 nvme_err_t act;
90
91 if (!libnvme_test_ns_delete(ctrl, LOCK_NSID, &act)) {
92 warnx("TEST FAILED: failed to initialize namespace delete "
93 "request in lock %s iteration", desc);
94 return (false);
95 } else if (act != exp) {
96 warnx("TEST FAILED: namespace delete with %s returned %s "
97 "(0x%x), but expected %s (0x%x)", desc,
98 nvme_ctrl_errtostr(ctrl, act), act,
99 nvme_ctrl_errtostr(ctrl, exp), exp);
100 return (false);
101 }
102
103 (void) printf("TEST PASSED: namespace delete with %s returned %s\n",
104 desc, nvme_ctrl_errtostr(ctrl, act));
105 return (true);
106 }
107
108 static bool
write_lock_ctrl_detach(nvme_ctrl_t * ctrl,const char * desc,nvme_err_t exp)109 write_lock_ctrl_detach(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp)
110 {
111 nvme_err_t act;
112
113 if (!libnvme_test_ctrl_attach(ctrl, LOCK_NSID,
114 NVME_NS_ATTACH_CTRL_DETACH, &act)) {
115 warnx("TEST FAILED: failed to initialize controller detach "
116 "request in lock %s iteration", desc);
117 return (false);
118 } else if (act != exp) {
119 warnx("TEST FAILED: controller detach with %s returned %s "
120 "(0x%x), but expected %s (0x%x)", desc,
121 nvme_ctrl_errtostr(ctrl, act), act,
122 nvme_ctrl_errtostr(ctrl, exp), exp);
123 return (false);
124 }
125
126 (void) printf("TEST PASSED: controller detach with %s returned %s\n",
127 desc, nvme_ctrl_errtostr(ctrl, act));
128 return (true);
129 }
130
131 static bool
write_lock_ctrl_attach(nvme_ctrl_t * ctrl,const char * desc,nvme_err_t exp)132 write_lock_ctrl_attach(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp)
133 {
134 nvme_err_t act;
135
136 if (!libnvme_test_ctrl_attach(ctrl, LOCK_NSID,
137 NVME_NS_ATTACH_CTRL_ATTACH, &act)) {
138 warnx("TEST FAILED: failed to initialize controller attach "
139 "request in lock %s iteration", desc);
140 return (false);
141 } else if (act != exp) {
142 warnx("TEST FAILED: controller attach with %s returned %s "
143 "(0x%x), but expected %s (0x%x)", desc,
144 nvme_ctrl_errtostr(ctrl, act), act,
145 nvme_ctrl_errtostr(ctrl, exp), exp);
146 return (false);
147 }
148
149 (void) printf("TEST PASSED: controller attach with %s returned %s\n",
150 desc, nvme_ctrl_errtostr(ctrl, act));
151 return (true);
152 }
153
154 static bool
write_lock_blkdev_detach(nvme_ctrl_t * ctrl,const char * desc,nvme_err_t exp)155 write_lock_blkdev_detach(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp)
156 {
157 nvme_err_t act;
158
159 if (!libnvme_test_ns_blkdev(ctrl, LOCK_NSID, false, &act)) {
160 warnx("TEST FAILED: failed to initialize blkdev detach "
161 "request in lock %s iteration", desc);
162 return (false);
163 } else if (act != exp) {
164 warnx("TEST FAILED: blkdev detach with %s returned %s (0x%x), "
165 "but expected %s (0x%x)", desc,
166 nvme_ctrl_errtostr(ctrl, act), act,
167 nvme_ctrl_errtostr(ctrl, exp), exp);
168 return (false);
169 }
170
171 (void) printf("TEST PASSED: blkdev detach with %s returned %s\n", desc,
172 nvme_ctrl_errtostr(ctrl, act));
173 return (true);
174 }
175
176 static bool
write_lock_blkdev_attach(nvme_ctrl_t * ctrl,const char * desc,nvme_err_t exp)177 write_lock_blkdev_attach(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp)
178 {
179 nvme_err_t act;
180
181 if (!libnvme_test_ns_blkdev(ctrl, LOCK_NSID, true, &act)) {
182 warnx("TEST FAILED: failed to initialize blkdev attach "
183 "request in lock %s iteration", desc);
184 return (false);
185 } else if (act != exp) {
186 warnx("TEST FAILED: blkdev attach with %s returned %s (0x%x), "
187 "but expected %s (0x%x)", desc,
188 nvme_ctrl_errtostr(ctrl, act), act,
189 nvme_ctrl_errtostr(ctrl, exp), exp);
190 return (false);
191 }
192
193 (void) printf("TEST PASSED: blkdev attach with %s returned %s\n", desc,
194 nvme_ctrl_errtostr(ctrl, act));
195 return (true);
196 }
197
198 static const write_test_t write_tests[] = { {
199 .wt_desc = "namespace create",
200 .wt_errs = {
201 [LOCK_NONE] = NVME_ERR_NEED_CTRL_WRLOCK,
202 [LOCK_NS_RD] = NVME_ERR_NEED_CTRL_WRLOCK,
203 [LOCK_NS_WR] = NVME_ERR_NEED_CTRL_WRLOCK,
204 [LOCK_CTRL_RD] = NVME_ERR_NEED_CTRL_WRLOCK
205 },
206 .wt_disc = NVME_NS_DISC_F_ALL,
207 .wt_func = write_lock_ns_create
208 }, {
209 .wt_desc = "namespace delete",
210 .wt_errs = {
211 [LOCK_NONE] = NVME_ERR_NEED_NS_WRLOCK,
212 [LOCK_NS_RD] = NVME_ERR_NEED_NS_WRLOCK,
213 [LOCK_NS_WR] = NVME_ERR_OK,
214 [LOCK_CTRL_RD] = NVME_ERR_NEED_NS_WRLOCK
215 },
216 .wt_disc = NVME_NS_DISC_F_ALLOCATED,
217 .wt_func = write_lock_ns_delete
218 }, {
219 .wt_desc = "controller attach",
220 .wt_errs = {
221 [LOCK_NONE] = NVME_ERR_NEED_NS_WRLOCK,
222 [LOCK_NS_RD] = NVME_ERR_NEED_NS_WRLOCK,
223 [LOCK_NS_WR] = NVME_ERR_OK,
224 [LOCK_CTRL_RD] = NVME_ERR_NEED_NS_WRLOCK
225 },
226 .wt_disc = NVME_NS_DISC_F_ALLOCATED,
227 .wt_func = write_lock_ctrl_attach
228 }, {
229 .wt_desc = "controller detach",
230 .wt_errs = {
231 [LOCK_NONE] = NVME_ERR_NEED_NS_WRLOCK,
232 [LOCK_NS_RD] = NVME_ERR_NEED_NS_WRLOCK,
233 [LOCK_NS_WR] = NVME_ERR_OK,
234 [LOCK_CTRL_RD] = NVME_ERR_NEED_NS_WRLOCK
235 },
236 .wt_disc = NVME_NS_DISC_F_ACTIVE,
237 .wt_func = write_lock_ctrl_detach
238 }, {
239 .wt_desc = "blkdev attach",
240 .wt_errs = {
241 [LOCK_NONE] = NVME_ERR_NEED_NS_WRLOCK,
242 [LOCK_NS_RD] = NVME_ERR_NEED_NS_WRLOCK,
243 [LOCK_NS_WR] = NVME_ERR_OK,
244 [LOCK_CTRL_RD] = NVME_ERR_NEED_NS_WRLOCK
245 },
246 .wt_disc = NVME_NS_DISC_F_NOT_IGNORED,
247 .wt_func = write_lock_blkdev_attach
248 }, {
249 .wt_desc = "blkdev detach",
250 .wt_errs = {
251 [LOCK_NONE] = NVME_ERR_NEED_NS_WRLOCK,
252 [LOCK_NS_RD] = NVME_ERR_NEED_NS_WRLOCK,
253 [LOCK_NS_WR] = NVME_ERR_OK,
254 [LOCK_CTRL_RD] = NVME_ERR_NEED_NS_WRLOCK
255 },
256 .wt_disc = NVME_NS_DISC_F_BLKDEV,
257 .wt_func = write_lock_blkdev_detach
258 } };
259
260 typedef struct lock_info {
261 const char *li_desc;
262 bool (*li_lock_f)(nvme_ctrl_t *, nvme_ns_t *);
263 void (*li_unlock_f)(nvme_ctrl_t *, nvme_ns_t *);
264 } lock_info_t;
265
266 static bool
lock_none_lock(nvme_ctrl_t * ctrl,nvme_ns_t * ns)267 lock_none_lock(nvme_ctrl_t *ctrl, nvme_ns_t *ns)
268 {
269 return (true);
270 }
271
272 static bool
lock_ns_read_lock(nvme_ctrl_t * ctrl,nvme_ns_t * ns)273 lock_ns_read_lock(nvme_ctrl_t *ctrl, nvme_ns_t *ns)
274 {
275 return (nvme_ns_lock(ns, NVME_LOCK_L_READ, NVME_LOCK_F_DONT_BLOCK));
276 }
277
278 static bool
lock_ns_write_lock(nvme_ctrl_t * ctrl,nvme_ns_t * ns)279 lock_ns_write_lock(nvme_ctrl_t *ctrl, nvme_ns_t *ns)
280 {
281 return (nvme_ns_lock(ns, NVME_LOCK_L_WRITE, NVME_LOCK_F_DONT_BLOCK));
282 }
283
284 static bool
lock_ctrl_read_lock(nvme_ctrl_t * ctrl,nvme_ns_t * ns)285 lock_ctrl_read_lock(nvme_ctrl_t *ctrl, nvme_ns_t *ns)
286 {
287 return (nvme_ctrl_lock(ctrl, NVME_LOCK_L_READ, NVME_LOCK_F_DONT_BLOCK));
288 }
289
290 static void
lock_none_unlock(nvme_ctrl_t * ctrl,nvme_ns_t * ns)291 lock_none_unlock(nvme_ctrl_t *ctrl, nvme_ns_t *ns)
292 {
293 }
294
295 static void
lock_ns_unlock(nvme_ctrl_t * ctrl,nvme_ns_t * ns)296 lock_ns_unlock(nvme_ctrl_t *ctrl, nvme_ns_t *ns)
297 {
298 nvme_ns_unlock(ns);
299 }
300
301 static void
lock_ctrl_unlock(nvme_ctrl_t * ctrl,nvme_ns_t * ns)302 lock_ctrl_unlock(nvme_ctrl_t *ctrl, nvme_ns_t *ns)
303 {
304 nvme_ctrl_unlock(ctrl);
305 }
306
307 lock_info_t lock_info[LOCK_MAX] = {
308 [LOCK_NONE] = {
309 .li_desc = "no lock",
310 .li_lock_f = lock_none_lock,
311 .li_unlock_f = lock_none_unlock,
312 },
313 [LOCK_NS_RD] = {
314 .li_desc = "namespace read lock",
315 .li_lock_f = lock_ns_read_lock,
316 .li_unlock_f = lock_ns_unlock,
317 },
318 [LOCK_NS_WR] = {
319 .li_desc = "namespace write lock",
320 .li_lock_f = lock_ns_write_lock,
321 .li_unlock_f = lock_ns_unlock,
322 },
323 [LOCK_CTRL_RD] = {
324 .li_desc = "controller read lock",
325 .li_lock_f = lock_ctrl_read_lock,
326 .li_unlock_f = lock_ctrl_unlock,
327 }
328
329 };
330
331 static bool
write_test_one(const write_test_t * test,nvme_ctrl_t * ctrl,nvme_ns_t * ns)332 write_test_one(const write_test_t *test, nvme_ctrl_t *ctrl, nvme_ns_t *ns)
333 {
334 bool ret = true;
335
336 for (lock_type_t i = LOCK_NONE; i < LOCK_MAX; i++) {
337 if (!nvme_ctrl_lock(ctrl, NVME_LOCK_L_WRITE,
338 NVME_LOCK_F_DONT_BLOCK)) {
339 libnvme_test_ctrl_fatal(ctrl, "failed to obtain write "
340 "lock");
341 }
342
343 if (!libnvme_test_setup_ns(ctrl, test->wt_disc, LOCK_NSID,
344 lock_lbaf)) {
345 libnvme_test_ctrl_fatal(ctrl, "failed to change state "
346 "to 0x%x", test->wt_disc);
347 }
348
349 nvme_ctrl_unlock(ctrl);
350
351 if (!lock_info[i].li_lock_f(ctrl, ns)) {
352 ret = false;
353 continue;
354 }
355
356 if (!test->wt_func(ctrl, lock_info[i].li_desc,
357 test->wt_errs[i])) {
358 ret = false;
359 }
360
361 lock_info[i].li_unlock_f(ctrl, ns);
362 }
363
364 return (ret);
365 }
366
367 int
main(void)368 main(void)
369 {
370 int ret = EXIT_SUCCESS;
371 nvme_t *nvme;
372 nvme_ctrl_t *ctrl;
373 nvme_ctrl_info_t *info;
374 nvme_ns_t *ns;
375
376 libnvme_test_init(&nvme, &ctrl);
377
378 if (!nvme_ctrl_info_snap(ctrl, &info)) {
379 libnvme_test_ctrl_fatal(ctrl, "failed to get info snapshot");
380 }
381
382 if (!nvme_ns_init(ctrl, LOCK_NSID, &ns)) {
383 libnvme_test_ctrl_fatal(ctrl, "failed to create ns %u "
384 "nvme_ns_t", LOCK_NSID);
385 }
386
387 if (!libnvme_test_lbaf(info, NVME_TEST_LBA_SIZE, &lock_lbaf)) {
388 errx(EXIT_FAILURE, "failed to find 4K LBA format, cannot "
389 "continue");
390 }
391
392 for (size_t i = 0; i < ARRAY_SIZE(write_tests); i++) {
393 if (!write_test_one(&write_tests[i], ctrl, ns))
394 ret = EXIT_FAILURE;
395 }
396
397 nvme_ns_fini(ns);
398 nvme_ctrl_info_free(info);
399 nvme_ctrl_fini(ctrl);
400 nvme_fini(nvme);
401
402 if (ret == EXIT_SUCCESS) {
403 (void) printf("All tests passed successfully\n");
404 }
405
406 return (ret);
407 }
408