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