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
rec_lock_test(int fd,const rec_lock_test_t * test,bool nsfd)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
ns_lock_test(int fd,const ns_lock_test_t * test,bool rdlock)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
main(void)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