xref: /freebsd/sys/contrib/openzfs/lib/libspl/tunables.c (revision 66e85755595a451db490d2fe24267d85db4b09c2)
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