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 *
zfs_tunable_lookup(const char * name)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
zfs_tunable_iter(zfs_tunable_iter_t cb,void * arg)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
zfs_tunable_parse_int(const char * val,intmax_t * np,intmax_t min,intmax_t max)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
zfs_tunable_parse_uint(const char * val,uintmax_t * np,uintmax_t min,uintmax_t max)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
zfs_tunable_set_int(const zfs_tunable_t * zt,const char * val)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
zfs_tunable_set_uint(const zfs_tunable_t * zt,const char * val)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
zfs_tunable_set_ulong(const zfs_tunable_t * zt,const char * val)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
zfs_tunable_set_u64(const zfs_tunable_t * zt,const char * val)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
zfs_tunable_set_string(const zfs_tunable_t * zt,const char * val)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
zfs_tunable_get_int(const zfs_tunable_t * zt,char * val,size_t valsz)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
zfs_tunable_get_uint(const zfs_tunable_t * zt,char * val,size_t valsz)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
zfs_tunable_get_ulong(const zfs_tunable_t * zt,char * val,size_t valsz)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
zfs_tunable_get_u64(const zfs_tunable_t * zt,char * val,size_t valsz)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
zfs_tunable_get_string(const zfs_tunable_t * zt,char * val,size_t valsz)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
zfs_tunable_set(const zfs_tunable_t * zt,const char * val)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
zfs_tunable_get(const zfs_tunable_t * zt,char * val,size_t valsz)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