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 int err;
128
129 errno = 0;
130 n = strtoimax(val, &end, 0);
131 if ((err = errno) != 0)
132 return (err);
133 if (*end != '\0')
134 return (EINVAL);
135 if (n < min || n > max)
136 return (ERANGE);
137 *np = n;
138 return (0);
139 }
140
141 static int
zfs_tunable_parse_uint(const char * val,uintmax_t * np,uintmax_t min,uintmax_t max)142 zfs_tunable_parse_uint(const char *val, uintmax_t *np,
143 uintmax_t min, uintmax_t max)
144 {
145 uintmax_t n;
146 char *end;
147 int err;
148
149 errno = 0;
150 n = strtoumax(val, &end, 0);
151 if ((err = errno) != 0)
152 return (err);
153 if (*end != '\0')
154 return (EINVAL);
155 if (strchr(val, '-'))
156 return (ERANGE);
157 if (n < min || n > max)
158 return (ERANGE);
159 *np = n;
160 return (0);
161 }
162
163 /*
164 * Set helpers for each tunable type. Parses the string, and if produces a
165 * valid value for the tunable, sets it. No effort is made to make sure the
166 * tunable is of the right type; that's done in zfs_tunable_set() below.
167 */
168 static int
zfs_tunable_set_int(const zfs_tunable_t * zt,const char * val)169 zfs_tunable_set_int(const zfs_tunable_t *zt, const char *val)
170 {
171 intmax_t n;
172 int err = zfs_tunable_parse_int(val, &n, INT_MIN, INT_MAX);
173 if (err != 0)
174 return (err);
175 *(int *)zt->zt_varp = n;
176 return (0);
177 }
178
179 static int
zfs_tunable_set_uint(const zfs_tunable_t * zt,const char * val)180 zfs_tunable_set_uint(const zfs_tunable_t *zt, const char *val)
181 {
182 uintmax_t n;
183 int err = zfs_tunable_parse_uint(val, &n, 0, UINT_MAX);
184 if (err != 0)
185 return (err);
186 *(unsigned int *)zt->zt_varp = n;
187 return (0);
188 }
189
190 static int
zfs_tunable_set_ulong(const zfs_tunable_t * zt,const char * val)191 zfs_tunable_set_ulong(const zfs_tunable_t *zt, const char *val)
192 {
193 uintmax_t n;
194 int err = zfs_tunable_parse_uint(val, &n, 0, ULONG_MAX);
195 if (err != 0)
196 return (err);
197 *(unsigned long *)zt->zt_varp = n;
198 return (0);
199 }
200
201 static int
zfs_tunable_set_u64(const zfs_tunable_t * zt,const char * val)202 zfs_tunable_set_u64(const zfs_tunable_t *zt, const char *val)
203 {
204 uintmax_t n;
205 int err = zfs_tunable_parse_uint(val, &n, 0, UINT64_MAX);
206 if (err != 0)
207 return (err);
208 *(uint64_t *)zt->zt_varp = n;
209 return (0);
210 }
211
212 static int
zfs_tunable_set_string(const zfs_tunable_t * zt,const char * val)213 zfs_tunable_set_string(const zfs_tunable_t *zt, const char *val)
214 {
215 (void) zt, (void) val;
216 /*
217 * We can't currently handle strings. String tunables are pointers
218 * into read-only memory, so we can update the pointer, but not the
219 * contents. That would mean taking an allocation, but we don't have
220 * an obvious place to free it.
221 *
222 * For now, it's no big deal as there's only a couple of string
223 * tunables anyway.
224 */
225 return (ENOTSUP);
226 }
227
228 /*
229 * Get helpers for each tunable type. Converts the value to a string if
230 * necessary and writes it into the provided buffer. The type is assumed to
231 * be correct; zfs_tunable_get() below will call the correct function for the
232 * type.
233 */
234 static int
zfs_tunable_get_int(const zfs_tunable_t * zt,char * val,size_t valsz)235 zfs_tunable_get_int(const zfs_tunable_t *zt, char *val, size_t valsz)
236 {
237 snprintf(val, valsz, "%d", *(int *)zt->zt_varp);
238 return (0);
239 }
240
241 static int
zfs_tunable_get_uint(const zfs_tunable_t * zt,char * val,size_t valsz)242 zfs_tunable_get_uint(const zfs_tunable_t *zt, char *val, size_t valsz)
243 {
244 snprintf(val, valsz, "%u", *(unsigned int *)zt->zt_varp);
245 return (0);
246 }
247
248 static int
zfs_tunable_get_ulong(const zfs_tunable_t * zt,char * val,size_t valsz)249 zfs_tunable_get_ulong(const zfs_tunable_t *zt, char *val, size_t valsz)
250 {
251 snprintf(val, valsz, "%lu", *(unsigned long *)zt->zt_varp);
252 return (0);
253 }
254
255 static int
zfs_tunable_get_u64(const zfs_tunable_t * zt,char * val,size_t valsz)256 zfs_tunable_get_u64(const zfs_tunable_t *zt, char *val, size_t valsz)
257 {
258 snprintf(val, valsz, "%"PRIu64, *(uint64_t *)zt->zt_varp);
259 return (0);
260 }
261
262 static int
zfs_tunable_get_string(const zfs_tunable_t * zt,char * val,size_t valsz)263 zfs_tunable_get_string(const zfs_tunable_t *zt, char *val, size_t valsz)
264 {
265 strlcpy(val, *(char **)zt->zt_varp, valsz);
266 return (0);
267 }
268
269 /* The public set function. Delegates to the type-specific version. */
270 int
zfs_tunable_set(const zfs_tunable_t * zt,const char * val)271 zfs_tunable_set(const zfs_tunable_t *zt, const char *val)
272 {
273 int err;
274 switch (zt->zt_type) {
275 case ZFS_TUNABLE_TYPE_INT:
276 err = zfs_tunable_set_int(zt, val);
277 break;
278 case ZFS_TUNABLE_TYPE_UINT:
279 err = zfs_tunable_set_uint(zt, val);
280 break;
281 case ZFS_TUNABLE_TYPE_ULONG:
282 err = zfs_tunable_set_ulong(zt, val);
283 break;
284 case ZFS_TUNABLE_TYPE_U64:
285 err = zfs_tunable_set_u64(zt, val);
286 break;
287 case ZFS_TUNABLE_TYPE_STRING:
288 err = zfs_tunable_set_string(zt, val);
289 break;
290 default:
291 err = EOPNOTSUPP;
292 break;
293 }
294 return (err);
295 }
296
297 /* The public get function. Delegates to the type-specific version. */
298 int
zfs_tunable_get(const zfs_tunable_t * zt,char * val,size_t valsz)299 zfs_tunable_get(const zfs_tunable_t *zt, char *val, size_t valsz)
300 {
301 int err;
302 switch (zt->zt_type) {
303 case ZFS_TUNABLE_TYPE_INT:
304 err = zfs_tunable_get_int(zt, val, valsz);
305 break;
306 case ZFS_TUNABLE_TYPE_UINT:
307 err = zfs_tunable_get_uint(zt, val, valsz);
308 break;
309 case ZFS_TUNABLE_TYPE_ULONG:
310 err = zfs_tunable_get_ulong(zt, val, valsz);
311 break;
312 case ZFS_TUNABLE_TYPE_U64:
313 err = zfs_tunable_get_u64(zt, val, valsz);
314 break;
315 case ZFS_TUNABLE_TYPE_STRING:
316 err = zfs_tunable_get_string(zt, val, valsz);
317 break;
318 default:
319 err = EOPNOTSUPP;
320 break;
321 }
322 return (err);
323 }
324