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