Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/contrib/openzfs/lib/libspl/tunables.c
105585 views
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 <[email protected]>
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 *
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
325