1 // SPDX-License-Identifier: CDDL-1.0 2 /* 3 * CDDL HEADER START 4 * 5 * The contents of this file are subject to the terms of the 6 * Common Development and Distribution License (the "License"). 7 * You may not use this file except in compliance with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or https://opensource.org/licenses/CDDL-1.0. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 23 /* 24 * Copyright (c) 2025, Rob Norris <robn@despairlabs.com> 25 */ 26 27 #include <stddef.h> 28 #include <string.h> 29 #include <stdlib.h> 30 #include <stdio.h> 31 #include <errno.h> 32 #include <limits.h> 33 #include <inttypes.h> 34 #include <sys/tunables.h> 35 36 /* 37 * Userspace tunables. 38 * 39 * Tunables are external pointers to global variables that are wired up to the 40 * host environment in some way that allows the operator to directly change 41 * their values "under the hood". 42 * 43 * In userspace, the "host environment" is the program using libzpool.so. So 44 * that it can manipulate tunables if it wants, we provide an API to access 45 * them. 46 * 47 * Tunables are declared through the ZFS_MODULE_PARAM* macros, which associate 48 * a global variable with some metadata we can use to describe and access the 49 * tunable. This is done by creating a uniquely-named zfs_tunable_t. 50 * 51 * At runtime, we need a way to discover these zfs_tunable_t items. Since they 52 * are declared globally, all over the codebase, there's no central place to 53 * record or list them. So, we take advantage of the compiler's "linker set" 54 * feature. 55 * 56 * In the ZFS_MODULE_PARAM macro, after we create the zfs_tunable_t, we also 57 * create a zfs_tunable_t* pointing to it. That pointer is forced into the 58 * "zfs_tunables" ELF section in compiled object. At link time, the linker will 59 * collect all these pointers into one single big "zfs_tunable" section, and 60 * will generate two new symbols in the final object: __start_zfs_tunable and 61 * __stop_zfs_tunable. These point to the first and last item in that section, 62 * which allows us to access the pointers in that section like an array, and 63 * through those pointers access the tunable metadata, and from there the 64 * actual C variable that the tunable describes. 65 */ 66 67 extern const zfs_tunable_t *__start_zfs_tunables; 68 extern const zfs_tunable_t *__stop_zfs_tunables; 69 70 /* 71 * Because there are no tunables in libspl itself, the above symbols will not 72 * be generated, which will stop libspl being linked at all. To work around 73 * that, we force a symbol into that section, and then when iterating, skip 74 * any NULL pointers. 75 */ 76 static void *__zfs_tunable__placeholder 77 __attribute__((__section__("zfs_tunables"))) 78 __attribute__((__used__)) = NULL; 79 80 /* 81 * Find the name tunable by walking through the linker set and comparing names, 82 * as described above. This is not particularly efficient but it's a fairly 83 * rare task, so it shouldn't be a big deal. 84 */ 85 const zfs_tunable_t * 86 zfs_tunable_lookup(const char *name) 87 { 88 for (const zfs_tunable_t **ztp = &__start_zfs_tunables; 89 ztp != &__stop_zfs_tunables; ztp++) { 90 const zfs_tunable_t *zt = *ztp; 91 if (zt == NULL) 92 continue; 93 if (strcmp(name, zt->zt_name) == 0) 94 return (zt); 95 } 96 97 return (NULL); 98 } 99 100 /* 101 * Like zfs_tunable_lookup, but call the provided callback for each tunable. 102 */ 103 void 104 zfs_tunable_iter(zfs_tunable_iter_t cb, void *arg) 105 { 106 for (const zfs_tunable_t **ztp = &__start_zfs_tunables; 107 ztp != &__stop_zfs_tunables; ztp++) { 108 const zfs_tunable_t *zt = *ztp; 109 if (zt == NULL) 110 continue; 111 if (cb(zt, arg)) 112 return; 113 } 114 } 115 116 /* 117 * Parse a string into an int or uint. It's easier to have a pair of "generic" 118 * functions that clamp to a given min and max rather than have multiple 119 * functions for each width of type. 120 */ 121 static int 122 zfs_tunable_parse_int(const char *val, intmax_t *np, 123 intmax_t min, intmax_t max) 124 { 125 intmax_t n; 126 char *end; 127 errno = 0; 128 n = strtoimax(val, &end, 0); 129 if (errno != 0) 130 return (errno); 131 if (*end != '\0') 132 return (EINVAL); 133 if (n < min || n > max) 134 return (ERANGE); 135 *np = n; 136 return (0); 137 } 138 139 static int 140 zfs_tunable_parse_uint(const char *val, uintmax_t *np, 141 uintmax_t min, uintmax_t max) 142 { 143 uintmax_t n; 144 char *end; 145 errno = 0; 146 n = strtoumax(val, &end, 0); 147 if (errno != 0) 148 return (errno); 149 if (*end != '\0') 150 return (EINVAL); 151 if (strchr(val, '-')) 152 return (ERANGE); 153 if (n < min || n > max) 154 return (ERANGE); 155 *np = n; 156 return (0); 157 } 158 159 /* 160 * Set helpers for each tunable type. Parses the string, and if produces a 161 * valid value for the tunable, sets it. No effort is made to make sure the 162 * tunable is of the right type; that's done in zfs_tunable_set() below. 163 */ 164 static int 165 zfs_tunable_set_int(const zfs_tunable_t *zt, const char *val) 166 { 167 intmax_t n; 168 int err = zfs_tunable_parse_int(val, &n, INT_MIN, INT_MAX); 169 if (err != 0) 170 return (err); 171 *(int *)zt->zt_varp = n; 172 return (0); 173 } 174 175 static int 176 zfs_tunable_set_uint(const zfs_tunable_t *zt, const char *val) 177 { 178 uintmax_t n; 179 int err = zfs_tunable_parse_uint(val, &n, 0, UINT_MAX); 180 if (err != 0) 181 return (err); 182 *(unsigned int *)zt->zt_varp = n; 183 return (0); 184 } 185 186 static int 187 zfs_tunable_set_ulong(const zfs_tunable_t *zt, const char *val) 188 { 189 uintmax_t n; 190 int err = zfs_tunable_parse_uint(val, &n, 0, ULONG_MAX); 191 if (err != 0) 192 return (err); 193 *(unsigned long *)zt->zt_varp = n; 194 return (0); 195 } 196 197 static int 198 zfs_tunable_set_u64(const zfs_tunable_t *zt, const char *val) 199 { 200 uintmax_t n; 201 int err = zfs_tunable_parse_uint(val, &n, 0, UINT64_MAX); 202 if (err != 0) 203 return (err); 204 *(uint64_t *)zt->zt_varp = n; 205 return (0); 206 } 207 208 static int 209 zfs_tunable_set_string(const zfs_tunable_t *zt, const char *val) 210 { 211 (void) zt, (void) val; 212 /* 213 * We can't currently handle strings. String tunables are pointers 214 * into read-only memory, so we can update the pointer, but not the 215 * contents. That would mean taking an allocation, but we don't have 216 * an obvious place to free it. 217 * 218 * For now, it's no big deal as there's only a couple of string 219 * tunables anyway. 220 */ 221 return (ENOTSUP); 222 } 223 224 /* 225 * Get helpers for each tunable type. Converts the value to a string if 226 * necessary and writes it into the provided buffer. The type is assumed to 227 * be correct; zfs_tunable_get() below will call the correct function for the 228 * type. 229 */ 230 static int 231 zfs_tunable_get_int(const zfs_tunable_t *zt, char *val, size_t valsz) 232 { 233 snprintf(val, valsz, "%d", *(int *)zt->zt_varp); 234 return (0); 235 } 236 237 static int 238 zfs_tunable_get_uint(const zfs_tunable_t *zt, char *val, size_t valsz) 239 { 240 snprintf(val, valsz, "%u", *(unsigned int *)zt->zt_varp); 241 return (0); 242 } 243 244 static int 245 zfs_tunable_get_ulong(const zfs_tunable_t *zt, char *val, size_t valsz) 246 { 247 snprintf(val, valsz, "%lu", *(unsigned long *)zt->zt_varp); 248 return (0); 249 } 250 251 static int 252 zfs_tunable_get_u64(const zfs_tunable_t *zt, char *val, size_t valsz) 253 { 254 snprintf(val, valsz, "%"PRIu64, *(uint64_t *)zt->zt_varp); 255 return (0); 256 } 257 258 static int 259 zfs_tunable_get_string(const zfs_tunable_t *zt, char *val, size_t valsz) 260 { 261 strlcpy(val, *(char **)zt->zt_varp, valsz); 262 return (0); 263 } 264 265 /* The public set function. Delegates to the type-specific version. */ 266 int 267 zfs_tunable_set(const zfs_tunable_t *zt, const char *val) 268 { 269 int err; 270 switch (zt->zt_type) { 271 case ZFS_TUNABLE_TYPE_INT: 272 err = zfs_tunable_set_int(zt, val); 273 break; 274 case ZFS_TUNABLE_TYPE_UINT: 275 err = zfs_tunable_set_uint(zt, val); 276 break; 277 case ZFS_TUNABLE_TYPE_ULONG: 278 err = zfs_tunable_set_ulong(zt, val); 279 break; 280 case ZFS_TUNABLE_TYPE_U64: 281 err = zfs_tunable_set_u64(zt, val); 282 break; 283 case ZFS_TUNABLE_TYPE_STRING: 284 err = zfs_tunable_set_string(zt, val); 285 break; 286 default: 287 err = EOPNOTSUPP; 288 break; 289 } 290 return (err); 291 } 292 293 /* The public get function. Delegates to the type-specific version. */ 294 int 295 zfs_tunable_get(const zfs_tunable_t *zt, char *val, size_t valsz) 296 { 297 int err; 298 switch (zt->zt_type) { 299 case ZFS_TUNABLE_TYPE_INT: 300 err = zfs_tunable_get_int(zt, val, valsz); 301 break; 302 case ZFS_TUNABLE_TYPE_UINT: 303 err = zfs_tunable_get_uint(zt, val, valsz); 304 break; 305 case ZFS_TUNABLE_TYPE_ULONG: 306 err = zfs_tunable_get_ulong(zt, val, valsz); 307 break; 308 case ZFS_TUNABLE_TYPE_U64: 309 err = zfs_tunable_get_u64(zt, val, valsz); 310 break; 311 case ZFS_TUNABLE_TYPE_STRING: 312 err = zfs_tunable_get_string(zt, val, valsz); 313 break; 314 default: 315 err = EOPNOTSUPP; 316 break; 317 } 318 return (err); 319 } 320