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