1*3fe5961aSDevin Teske /*
2*3fe5961aSDevin Teske * Copyright (c) 2013-2026 Devin Teske <dteske@FreeBSD.org>
3*3fe5961aSDevin Teske * Copyright (c) 2021-2026 Faraz Vahedi <kfv@FreeBSD.org>
4*3fe5961aSDevin Teske *
5*3fe5961aSDevin Teske * SPDX-License-Identifier: BSD-2-Clause
6*3fe5961aSDevin Teske */
7*3fe5961aSDevin Teske
8*3fe5961aSDevin Teske /*
9*3fe5961aSDevin Teske * Running-kernel sysctl OID descriptions and writability checks.
10*3fe5961aSDevin Teske */
11*3fe5961aSDevin Teske
12*3fe5961aSDevin Teske #include "sysconf_priv.h"
13*3fe5961aSDevin Teske
14*3fe5961aSDevin Teske #if defined(__FreeBSD__)
15*3fe5961aSDevin Teske
16*3fe5961aSDevin Teske /*
17*3fe5961aSDevin Teske * Fetch the running kernel's description of the sysctl OID `name' into
18*3fe5961aSDevin Teske * `buf' via CTL_SYSCTL_OIDDESCR (the same information sysctl(8) prints
19*3fe5961aSDevin Teske * with its own -d; an OID with no description yields the empty string).
20*3fe5961aSDevin Teske * Returns zero on success; -1 when the OID does not resolve.
21*3fe5961aSDevin Teske */
22*3fe5961aSDevin Teske int
sysctl_descr(const char * name,char * buf,size_t bufsize)23*3fe5961aSDevin Teske sysctl_descr(const char *name, char *buf, size_t bufsize)
24*3fe5961aSDevin Teske {
25*3fe5961aSDevin Teske int mib[CTL_MAXNAME];
26*3fe5961aSDevin Teske int qoid[CTL_MAXNAME + 2];
27*3fe5961aSDevin Teske size_t len;
28*3fe5961aSDevin Teske size_t size;
29*3fe5961aSDevin Teske
30*3fe5961aSDevin Teske buf[0] = '\0';
31*3fe5961aSDevin Teske
32*3fe5961aSDevin Teske len = CTL_MAXNAME;
33*3fe5961aSDevin Teske if (sysctlnametomib(name, mib, &len) != 0)
34*3fe5961aSDevin Teske return (-1);
35*3fe5961aSDevin Teske
36*3fe5961aSDevin Teske qoid[0] = CTL_SYSCTL;
37*3fe5961aSDevin Teske qoid[1] = CTL_SYSCTL_OIDDESCR;
38*3fe5961aSDevin Teske memcpy(qoid + 2, mib, len * sizeof(int));
39*3fe5961aSDevin Teske size = bufsize - 1;
40*3fe5961aSDevin Teske if (sysctl(qoid, (u_int)len + 2, buf, &size, 0, 0) != 0)
41*3fe5961aSDevin Teske buf[0] = '\0';
42*3fe5961aSDevin Teske buf[bufsize - 1] = '\0';
43*3fe5961aSDevin Teske
44*3fe5961aSDevin Teske return (0);
45*3fe5961aSDevin Teske }
46*3fe5961aSDevin Teske
47*3fe5961aSDevin Teske /*
48*3fe5961aSDevin Teske * Process `-d' with explicit names for the sysctl target: report each
49*3fe5961aSDevin Teske * requested OID's description from the running kernel. Returns
50*3fe5961aSDevin Teske * EXIT_SUCCESS or EXIT_FAILURE.
51*3fe5961aSDevin Teske */
52*3fe5961aSDevin Teske int
describe_sysctl(void)53*3fe5961aSDevin Teske describe_sysctl(void)
54*3fe5961aSDevin Teske {
55*3fe5961aSDevin Teske int rv = EXIT_SUCCESS;
56*3fe5961aSDevin Teske unsigned int n;
57*3fe5961aSDevin Teske char buf[BUFSIZ];
58*3fe5961aSDevin Teske
59*3fe5961aSDevin Teske for (n = 0; n < nreqs; n++) {
60*3fe5961aSDevin Teske if (sysctl_descr(reqs[n].name, buf, sizeof(buf)) != 0) {
61*3fe5961aSDevin Teske if (ignore_unknown)
62*3fe5961aSDevin Teske continue;
63*3fe5961aSDevin Teske if (!quiet)
64*3fe5961aSDevin Teske warnx("unknown oid '%s'", reqs[n].name);
65*3fe5961aSDevin Teske rv = EXIT_FAILURE;
66*3fe5961aSDevin Teske continue;
67*3fe5961aSDevin Teske }
68*3fe5961aSDevin Teske if (name_only)
69*3fe5961aSDevin Teske puts(reqs[n].name);
70*3fe5961aSDevin Teske else if (value_only)
71*3fe5961aSDevin Teske puts(buf);
72*3fe5961aSDevin Teske else
73*3fe5961aSDevin Teske printf("%s: %s\n", reqs[n].name, buf);
74*3fe5961aSDevin Teske }
75*3fe5961aSDevin Teske
76*3fe5961aSDevin Teske return (rv);
77*3fe5961aSDevin Teske }
78*3fe5961aSDevin Teske
79*3fe5961aSDevin Teske /*
80*3fe5961aSDevin Teske * Return a short name for a CTLTYPE_* value (for warnings).
81*3fe5961aSDevin Teske */
82*3fe5961aSDevin Teske static const char *
sysctl_typename(u_int kind)83*3fe5961aSDevin Teske sysctl_typename(u_int kind)
84*3fe5961aSDevin Teske {
85*3fe5961aSDevin Teske
86*3fe5961aSDevin Teske switch (kind & CTLTYPE) {
87*3fe5961aSDevin Teske case CTLTYPE_INT: return ("integer");
88*3fe5961aSDevin Teske case CTLTYPE_UINT: return ("unsigned integer");
89*3fe5961aSDevin Teske case CTLTYPE_LONG: return ("long integer");
90*3fe5961aSDevin Teske case CTLTYPE_ULONG: return ("unsigned long");
91*3fe5961aSDevin Teske case CTLTYPE_S8: return ("int8_t");
92*3fe5961aSDevin Teske case CTLTYPE_S16: return ("int16_t");
93*3fe5961aSDevin Teske case CTLTYPE_S32: return ("int32_t");
94*3fe5961aSDevin Teske case CTLTYPE_S64: return ("int64_t");
95*3fe5961aSDevin Teske case CTLTYPE_U8: return ("uint8_t");
96*3fe5961aSDevin Teske case CTLTYPE_U16: return ("uint16_t");
97*3fe5961aSDevin Teske case CTLTYPE_U32: return ("uint32_t");
98*3fe5961aSDevin Teske case CTLTYPE_U64: return ("uint64_t");
99*3fe5961aSDevin Teske case CTLTYPE_STRING: return ("string");
100*3fe5961aSDevin Teske case CTLTYPE_OPAQUE: return ("opaque");
101*3fe5961aSDevin Teske default: return ("unknown");
102*3fe5961aSDevin Teske }
103*3fe5961aSDevin Teske }
104*3fe5961aSDevin Teske
105*3fe5961aSDevin Teske /*
106*3fe5961aSDevin Teske * True if `value' can be represented as a signed integer of the given
107*3fe5961aSDevin Teske * inclusive [min, max] range. Requires a full-string numeric parse.
108*3fe5961aSDevin Teske */
109*3fe5961aSDevin Teske static int
sysctl_signed_ok(const char * value,intmax_t min,intmax_t max)110*3fe5961aSDevin Teske sysctl_signed_ok(const char *value, intmax_t min, intmax_t max)
111*3fe5961aSDevin Teske {
112*3fe5961aSDevin Teske char *end;
113*3fe5961aSDevin Teske intmax_t v;
114*3fe5961aSDevin Teske
115*3fe5961aSDevin Teske errno = 0;
116*3fe5961aSDevin Teske v = strtoimax(value, &end, 0);
117*3fe5961aSDevin Teske if (errno != 0 || end == value || *end != '\0')
118*3fe5961aSDevin Teske return (0);
119*3fe5961aSDevin Teske return (v >= min && v <= max);
120*3fe5961aSDevin Teske }
121*3fe5961aSDevin Teske
122*3fe5961aSDevin Teske /*
123*3fe5961aSDevin Teske * True if `value' can be represented as an unsigned integer <= max.
124*3fe5961aSDevin Teske * Leading whitespace is not accepted (sysctl.conf values are trimmed by
125*3fe5961aSDevin Teske * the caller before we see them); a leading minus is rejected so that
126*3fe5961aSDevin Teske * strtoumax() cannot wrap a negative into a large positive.
127*3fe5961aSDevin Teske */
128*3fe5961aSDevin Teske static int
sysctl_unsigned_ok(const char * value,uintmax_t max)129*3fe5961aSDevin Teske sysctl_unsigned_ok(const char *value, uintmax_t max)
130*3fe5961aSDevin Teske {
131*3fe5961aSDevin Teske char *end;
132*3fe5961aSDevin Teske uintmax_t v;
133*3fe5961aSDevin Teske
134*3fe5961aSDevin Teske if (value[0] == '\0' || value[0] == '-')
135*3fe5961aSDevin Teske return (0);
136*3fe5961aSDevin Teske errno = 0;
137*3fe5961aSDevin Teske v = strtoumax(value, &end, 0);
138*3fe5961aSDevin Teske if (errno != 0 || end == value || *end != '\0')
139*3fe5961aSDevin Teske return (0);
140*3fe5961aSDevin Teske return (v <= max);
141*3fe5961aSDevin Teske }
142*3fe5961aSDevin Teske
143*3fe5961aSDevin Teske /*
144*3fe5961aSDevin Teske * Validate that `value' fits the numeric CTLTYPE described by `kind'.
145*3fe5961aSDevin Teske * `fmt' is the OIDFMT format string (may start with "IK" for Kelvin
146*3fe5961aSDevin Teske * temperatures); those are left to sysctl(8) at apply time. String and
147*3fe5961aSDevin Teske * opaque OIDs are accepted as-is. Returns zero if acceptable; -1 if not.
148*3fe5961aSDevin Teske */
149*3fe5961aSDevin Teske static int
sysctl_value_ok(u_int kind,const char * fmt,const char * value)150*3fe5961aSDevin Teske sysctl_value_ok(u_int kind, const char *fmt, const char *value)
151*3fe5961aSDevin Teske {
152*3fe5961aSDevin Teske
153*3fe5961aSDevin Teske if (value == NULL)
154*3fe5961aSDevin Teske return (0);
155*3fe5961aSDevin Teske
156*3fe5961aSDevin Teske switch (kind & CTLTYPE) {
157*3fe5961aSDevin Teske case CTLTYPE_STRING:
158*3fe5961aSDevin Teske case CTLTYPE_OPAQUE:
159*3fe5961aSDevin Teske return (0);
160*3fe5961aSDevin Teske case CTLTYPE_INT:
161*3fe5961aSDevin Teske /*
162*3fe5961aSDevin Teske * Temperature OIDs use an "IK" / "IK<digit>" display format
163*3fe5961aSDevin Teske * with a specialized parser in sysctl(8); do not apply a
164*3fe5961aSDevin Teske * plain integer range here.
165*3fe5961aSDevin Teske */
166*3fe5961aSDevin Teske if (fmt != NULL && strncmp(fmt, "IK", 2) == 0)
167*3fe5961aSDevin Teske return (0);
168*3fe5961aSDevin Teske if (sysctl_signed_ok(value, INT_MIN, INT_MAX))
169*3fe5961aSDevin Teske return (0);
170*3fe5961aSDevin Teske break;
171*3fe5961aSDevin Teske case CTLTYPE_UINT:
172*3fe5961aSDevin Teske if (sysctl_unsigned_ok(value, UINT_MAX))
173*3fe5961aSDevin Teske return (0);
174*3fe5961aSDevin Teske break;
175*3fe5961aSDevin Teske case CTLTYPE_LONG:
176*3fe5961aSDevin Teske if (sysctl_signed_ok(value, LONG_MIN, LONG_MAX))
177*3fe5961aSDevin Teske return (0);
178*3fe5961aSDevin Teske break;
179*3fe5961aSDevin Teske case CTLTYPE_ULONG:
180*3fe5961aSDevin Teske if (sysctl_unsigned_ok(value, ULONG_MAX))
181*3fe5961aSDevin Teske return (0);
182*3fe5961aSDevin Teske break;
183*3fe5961aSDevin Teske case CTLTYPE_S8:
184*3fe5961aSDevin Teske if (sysctl_signed_ok(value, INT8_MIN, INT8_MAX))
185*3fe5961aSDevin Teske return (0);
186*3fe5961aSDevin Teske break;
187*3fe5961aSDevin Teske case CTLTYPE_S16:
188*3fe5961aSDevin Teske if (sysctl_signed_ok(value, INT16_MIN, INT16_MAX))
189*3fe5961aSDevin Teske return (0);
190*3fe5961aSDevin Teske break;
191*3fe5961aSDevin Teske case CTLTYPE_S32:
192*3fe5961aSDevin Teske if (sysctl_signed_ok(value, INT32_MIN, INT32_MAX))
193*3fe5961aSDevin Teske return (0);
194*3fe5961aSDevin Teske break;
195*3fe5961aSDevin Teske case CTLTYPE_S64:
196*3fe5961aSDevin Teske if (sysctl_signed_ok(value, INT64_MIN, INT64_MAX))
197*3fe5961aSDevin Teske return (0);
198*3fe5961aSDevin Teske break;
199*3fe5961aSDevin Teske case CTLTYPE_U8:
200*3fe5961aSDevin Teske if (sysctl_unsigned_ok(value, UINT8_MAX))
201*3fe5961aSDevin Teske return (0);
202*3fe5961aSDevin Teske break;
203*3fe5961aSDevin Teske case CTLTYPE_U16:
204*3fe5961aSDevin Teske if (sysctl_unsigned_ok(value, UINT16_MAX))
205*3fe5961aSDevin Teske return (0);
206*3fe5961aSDevin Teske break;
207*3fe5961aSDevin Teske case CTLTYPE_U32:
208*3fe5961aSDevin Teske if (sysctl_unsigned_ok(value, UINT32_MAX))
209*3fe5961aSDevin Teske return (0);
210*3fe5961aSDevin Teske break;
211*3fe5961aSDevin Teske case CTLTYPE_U64:
212*3fe5961aSDevin Teske if (sysctl_unsigned_ok(value, UINT64_MAX))
213*3fe5961aSDevin Teske return (0);
214*3fe5961aSDevin Teske break;
215*3fe5961aSDevin Teske default:
216*3fe5961aSDevin Teske /* NODE already rejected; unknown types: do not block */
217*3fe5961aSDevin Teske return (0);
218*3fe5961aSDevin Teske }
219*3fe5961aSDevin Teske return (-1);
220*3fe5961aSDevin Teske }
221*3fe5961aSDevin Teske
222*3fe5961aSDevin Teske /*
223*3fe5961aSDevin Teske * Determine whether the sysctl OID `name' can take effect when set from
224*3fe5961aSDevin Teske * sysctl.conf(5) to `value': if the OID resolves it must be a leaf and
225*3fe5961aSDevin Teske * writable at run time, and `value' must fit the OID's CTLTYPE (so an
226*3fe5961aSDevin Teske * overflowing assignment cannot land in the conf file and surprise
227*3fe5961aSDevin Teske * sysctl(8) at boot). An OID that is only tunable from the boot loader is
228*3fe5961aSDevin Teske * reported as such (pointing at the loader target). An OID that does not
229*3fe5961aSDevin Teske * resolve is still written -- sysctl.conf(5) commonly names OIDs from
230*3fe5961aSDevin Teske * modules that are not yet loaded, and init(8) only warns -- so we warn
231*3fe5961aSDevin Teske * (unless `quiet_unknown') and allow the assignment through; flag and
232*3fe5961aSDevin Teske * range checks are only possible against a resolvable OID. Returns zero
233*3fe5961aSDevin Teske * when the value should be written; -1 (after warning) otherwise.
234*3fe5961aSDevin Teske */
235*3fe5961aSDevin Teske int
sysctl_writable(const char * name,const char * value,int quiet_unknown)236*3fe5961aSDevin Teske sysctl_writable(const char *name, const char *value, int quiet_unknown)
237*3fe5961aSDevin Teske {
238*3fe5961aSDevin Teske int mib[CTL_MAXNAME];
239*3fe5961aSDevin Teske int qoid[CTL_MAXNAME + 2];
240*3fe5961aSDevin Teske size_t len;
241*3fe5961aSDevin Teske size_t size;
242*3fe5961aSDevin Teske u_int kind;
243*3fe5961aSDevin Teske u_char buf[BUFSIZ];
244*3fe5961aSDevin Teske const char *fmt;
245*3fe5961aSDevin Teske
246*3fe5961aSDevin Teske len = CTL_MAXNAME;
247*3fe5961aSDevin Teske if (sysctlnametomib(name, mib, &len) != 0) {
248*3fe5961aSDevin Teske if (!quiet_unknown)
249*3fe5961aSDevin Teske warnx("WARNING: unknown oid '%s'", name);
250*3fe5961aSDevin Teske return (0);
251*3fe5961aSDevin Teske }
252*3fe5961aSDevin Teske
253*3fe5961aSDevin Teske qoid[0] = CTL_SYSCTL;
254*3fe5961aSDevin Teske qoid[1] = CTL_SYSCTL_OIDFMT;
255*3fe5961aSDevin Teske memcpy(qoid + 2, mib, len * sizeof(int));
256*3fe5961aSDevin Teske size = sizeof(buf);
257*3fe5961aSDevin Teske if (sysctl(qoid, (u_int)len + 2, buf, &size, 0, 0) != 0) {
258*3fe5961aSDevin Teske warnx("couldn't find format of oid '%s'", name);
259*3fe5961aSDevin Teske return (-1);
260*3fe5961aSDevin Teske }
261*3fe5961aSDevin Teske kind = *(u_int *)(void *)buf;
262*3fe5961aSDevin Teske fmt = (const char *)(buf + sizeof(u_int));
263*3fe5961aSDevin Teske
264*3fe5961aSDevin Teske if ((kind & CTLTYPE) == CTLTYPE_NODE) {
265*3fe5961aSDevin Teske warnx("oid '%s' isn't a leaf node", name);
266*3fe5961aSDevin Teske return (-1);
267*3fe5961aSDevin Teske }
268*3fe5961aSDevin Teske if ((kind & CTLFLAG_WR) == 0) {
269*3fe5961aSDevin Teske if ((kind & CTLFLAG_TUN) != 0) {
270*3fe5961aSDevin Teske warnx("oid '%s' is a read only tunable", name);
271*3fe5961aSDevin Teske warnx("use: %s loader %s=<value>", pgm, name);
272*3fe5961aSDevin Teske } else
273*3fe5961aSDevin Teske warnx("oid '%s' is read only", name);
274*3fe5961aSDevin Teske return (-1);
275*3fe5961aSDevin Teske }
276*3fe5961aSDevin Teske if (sysctl_value_ok(kind, fmt, value) != 0) {
277*3fe5961aSDevin Teske warnx("oid '%s' value '%s' is invalid or out of range for %s",
278*3fe5961aSDevin Teske name, value, sysctl_typename(kind));
279*3fe5961aSDevin Teske return (-1);
280*3fe5961aSDevin Teske }
281*3fe5961aSDevin Teske
282*3fe5961aSDevin Teske return (0);
283*3fe5961aSDevin Teske }
284*3fe5961aSDevin Teske
285*3fe5961aSDevin Teske #endif /* __FreeBSD__ */
286