Changeset View
Changeset View
Standalone View
Standalone View
head/sys/kern/kern_sysctl.c
Show All 35 Lines | |||||
* | * | ||||
* @(#)kern_sysctl.c 8.4 (Berkeley) 4/14/94 | * @(#)kern_sysctl.c 8.4 (Berkeley) 4/14/94 | ||||
*/ | */ | ||||
#include <sys/cdefs.h> | #include <sys/cdefs.h> | ||||
__FBSDID("$FreeBSD$"); | __FBSDID("$FreeBSD$"); | ||||
#include "opt_capsicum.h" | #include "opt_capsicum.h" | ||||
#include "opt_ddb.h" | |||||
#include "opt_ktrace.h" | #include "opt_ktrace.h" | ||||
#include <sys/param.h> | #include <sys/param.h> | ||||
#include <sys/fail.h> | #include <sys/fail.h> | ||||
#include <sys/systm.h> | #include <sys/systm.h> | ||||
#include <sys/capsicum.h> | #include <sys/capsicum.h> | ||||
#include <sys/kernel.h> | #include <sys/kernel.h> | ||||
#include <sys/limits.h> | |||||
#include <sys/sysctl.h> | #include <sys/sysctl.h> | ||||
#include <sys/malloc.h> | #include <sys/malloc.h> | ||||
#include <sys/priv.h> | #include <sys/priv.h> | ||||
#include <sys/proc.h> | #include <sys/proc.h> | ||||
#include <sys/jail.h> | #include <sys/jail.h> | ||||
#include <sys/lock.h> | #include <sys/lock.h> | ||||
#include <sys/mutex.h> | #include <sys/mutex.h> | ||||
#include <sys/rmlock.h> | #include <sys/rmlock.h> | ||||
#include <sys/sbuf.h> | #include <sys/sbuf.h> | ||||
#include <sys/sx.h> | #include <sys/sx.h> | ||||
#include <sys/sysproto.h> | #include <sys/sysproto.h> | ||||
#include <sys/uio.h> | #include <sys/uio.h> | ||||
#ifdef KTRACE | #ifdef KTRACE | ||||
#include <sys/ktrace.h> | #include <sys/ktrace.h> | ||||
#endif | #endif | ||||
#ifdef DDB | |||||
#include <sys/kdb.h> | |||||
#include <ddb/ddb.h> | |||||
#include <ddb/db_lex.h> | |||||
#endif | |||||
#include <net/vnet.h> | #include <net/vnet.h> | ||||
#include <security/mac/mac_framework.h> | #include <security/mac/mac_framework.h> | ||||
#include <vm/vm.h> | #include <vm/vm.h> | ||||
#include <vm/vm_extern.h> | #include <vm/vm_extern.h> | ||||
static MALLOC_DEFINE(M_SYSCTL, "sysctl", "sysctl internal magic"); | static MALLOC_DEFINE(M_SYSCTL, "sysctl", "sysctl internal magic"); | ||||
▲ Show 20 Lines • Show All 1,529 Lines • ▼ Show 20 Lines | |||||
int | int | ||||
sysctl_handle_string(SYSCTL_HANDLER_ARGS) | sysctl_handle_string(SYSCTL_HANDLER_ARGS) | ||||
{ | { | ||||
size_t outlen; | size_t outlen; | ||||
int error = 0, ro_string = 0; | int error = 0, ro_string = 0; | ||||
/* | /* | ||||
* A zero-length buffer indicates a fixed size read-only | * A zero-length buffer indicates a fixed size read-only | ||||
* string: | * string. In ddb, don't worry about trying to make a malloced | ||||
* snapshot. | |||||
*/ | */ | ||||
if (arg2 == 0) { | if (arg2 == 0 || kdb_active) { | ||||
arg2 = strlen((char *)arg1) + 1; | arg2 = strlen((char *)arg1) + 1; | ||||
ro_string = 1; | ro_string = 1; | ||||
} | } | ||||
if (req->oldptr != NULL) { | if (req->oldptr != NULL) { | ||||
char *tmparg; | char *tmparg; | ||||
if (ro_string) { | if (ro_string) { | ||||
▲ Show 20 Lines • Show All 612 Lines • ▼ Show 20 Lines | sbuf_new_for_sysctl(struct sbuf *s, char *buf, int length, | ||||
/* Supply a default buffer size if none given. */ | /* Supply a default buffer size if none given. */ | ||||
if (buf == NULL && length == 0) | if (buf == NULL && length == 0) | ||||
length = 64; | length = 64; | ||||
s = sbuf_new(s, buf, length, SBUF_FIXEDLEN | SBUF_INCLUDENUL); | s = sbuf_new(s, buf, length, SBUF_FIXEDLEN | SBUF_INCLUDENUL); | ||||
sbuf_set_drain(s, sbuf_sysctl_drain, req); | sbuf_set_drain(s, sbuf_sysctl_drain, req); | ||||
return (s); | return (s); | ||||
} | } | ||||
#ifdef DDB | |||||
/* The current OID the debugger is working with */ | |||||
static struct sysctl_oid *g_ddb_oid; | |||||
/* The current flags specified by the user */ | |||||
static int g_ddb_sysctl_flags; | |||||
/* Check to see if the last sysctl printed */ | |||||
static int g_ddb_sysctl_printed; | |||||
static const int ctl_sign[CTLTYPE+1] = { | |||||
[CTLTYPE_INT] = 1, | |||||
[CTLTYPE_LONG] = 1, | |||||
[CTLTYPE_S8] = 1, | |||||
[CTLTYPE_S16] = 1, | |||||
[CTLTYPE_S32] = 1, | |||||
[CTLTYPE_S64] = 1, | |||||
}; | |||||
static const int ctl_size[CTLTYPE+1] = { | |||||
[CTLTYPE_INT] = sizeof(int), | |||||
[CTLTYPE_UINT] = sizeof(u_int), | |||||
[CTLTYPE_LONG] = sizeof(long), | |||||
[CTLTYPE_ULONG] = sizeof(u_long), | |||||
[CTLTYPE_S8] = sizeof(int8_t), | |||||
[CTLTYPE_S16] = sizeof(int16_t), | |||||
[CTLTYPE_S32] = sizeof(int32_t), | |||||
[CTLTYPE_S64] = sizeof(int64_t), | |||||
[CTLTYPE_U8] = sizeof(uint8_t), | |||||
[CTLTYPE_U16] = sizeof(uint16_t), | |||||
[CTLTYPE_U32] = sizeof(uint32_t), | |||||
[CTLTYPE_U64] = sizeof(uint64_t), | |||||
}; | |||||
#define DB_SYSCTL_NAME_ONLY 0x001 /* Compare with -N */ | |||||
#define DB_SYSCTL_VALUE_ONLY 0x002 /* Compare with -n */ | |||||
#define DB_SYSCTL_OPAQUE 0x004 /* Compare with -o */ | |||||
#define DB_SYSCTL_HEX 0x008 /* Compare with -x */ | |||||
#define DB_SYSCTL_SAFE_ONLY 0x100 /* Only simple types */ | |||||
static const char db_sysctl_modifs[] = { | |||||
'N', 'n', 'o', 'x', | |||||
}; | |||||
static const int db_sysctl_modif_values[] = { | |||||
DB_SYSCTL_NAME_ONLY, DB_SYSCTL_VALUE_ONLY, | |||||
DB_SYSCTL_OPAQUE, DB_SYSCTL_HEX, | |||||
}; | |||||
/* Handlers considered safe to print while recursing */ | |||||
static int (* const db_safe_handlers[])(SYSCTL_HANDLER_ARGS) = { | |||||
sysctl_handle_bool, | |||||
sysctl_handle_8, | |||||
sysctl_handle_16, | |||||
sysctl_handle_32, | |||||
sysctl_handle_64, | |||||
sysctl_handle_int, | |||||
sysctl_handle_long, | |||||
sysctl_handle_string, | |||||
sysctl_handle_opaque, | |||||
}; | |||||
/* | |||||
* Use in place of sysctl_old_kernel to print sysctl values. | |||||
* | |||||
* Compare to the output handling in show_var from sbin/sysctl/sysctl.c | |||||
*/ | |||||
static int | |||||
sysctl_old_ddb(struct sysctl_req *req, const void *ptr, size_t len) | |||||
{ | |||||
const u_char *val, *p; | |||||
const char *sep1; | |||||
size_t intlen, slen; | |||||
uintmax_t umv; | |||||
intmax_t mv; | |||||
int sign, ctltype, hexlen, xflag, error; | |||||
/* Suppress false-positive GCC uninitialized variable warnings */ | |||||
mv = 0; | |||||
umv = 0; | |||||
slen = len; | |||||
val = p = ptr; | |||||
if (ptr == NULL) { | |||||
error = 0; | |||||
goto out; | |||||
} | |||||
/* We are going to print */ | |||||
g_ddb_sysctl_printed = 1; | |||||
xflag = g_ddb_sysctl_flags & DB_SYSCTL_HEX; | |||||
ctltype = (g_ddb_oid->oid_kind & CTLTYPE); | |||||
sign = ctl_sign[ctltype]; | |||||
intlen = ctl_size[ctltype]; | |||||
switch (ctltype) { | |||||
case CTLTYPE_NODE: | |||||
case CTLTYPE_STRING: | |||||
db_printf("%.*s", (int) len, (const char *) p); | |||||
error = 0; | |||||
goto out; | |||||
case CTLTYPE_INT: | |||||
case CTLTYPE_UINT: | |||||
case CTLTYPE_LONG: | |||||
case CTLTYPE_ULONG: | |||||
case CTLTYPE_S8: | |||||
case CTLTYPE_S16: | |||||
case CTLTYPE_S32: | |||||
case CTLTYPE_S64: | |||||
case CTLTYPE_U8: | |||||
case CTLTYPE_U16: | |||||
case CTLTYPE_U32: | |||||
case CTLTYPE_U64: | |||||
hexlen = 2 + (intlen * CHAR_BIT + 3) / 4; | |||||
sep1 = ""; | |||||
while (len >= intlen) { | |||||
switch (ctltype) { | |||||
case CTLTYPE_INT: | |||||
case CTLTYPE_UINT: | |||||
umv = *(const u_int *)p; | |||||
mv = *(const int *)p; | |||||
break; | |||||
case CTLTYPE_LONG: | |||||
case CTLTYPE_ULONG: | |||||
umv = *(const u_long *)p; | |||||
mv = *(const long *)p; | |||||
break; | |||||
case CTLTYPE_S8: | |||||
case CTLTYPE_U8: | |||||
umv = *(const uint8_t *)p; | |||||
mv = *(const int8_t *)p; | |||||
break; | |||||
case CTLTYPE_S16: | |||||
case CTLTYPE_U16: | |||||
umv = *(const uint16_t *)p; | |||||
mv = *(const int16_t *)p; | |||||
break; | |||||
case CTLTYPE_S32: | |||||
case CTLTYPE_U32: | |||||
umv = *(const uint32_t *)p; | |||||
mv = *(const int32_t *)p; | |||||
break; | |||||
case CTLTYPE_S64: | |||||
case CTLTYPE_U64: | |||||
umv = *(const uint64_t *)p; | |||||
mv = *(const int64_t *)p; | |||||
break; | |||||
} | |||||
db_printf("%s", sep1); | |||||
if (xflag) | |||||
db_printf("%#0*jx", hexlen, umv); | |||||
else if (!sign) | |||||
db_printf("%ju", umv); | |||||
else if (g_ddb_oid->oid_fmt[1] == 'K') { | |||||
/* Kelvins are currently unsupported. */ | |||||
error = EOPNOTSUPP; | |||||
goto out; | |||||
} else | |||||
db_printf("%jd", mv); | |||||
sep1 = " "; | |||||
len -= intlen; | |||||
p += intlen; | |||||
} | |||||
error = 0; | |||||
goto out; | |||||
case CTLTYPE_OPAQUE: | |||||
/* TODO: Support struct functions. */ | |||||
/* FALLTHROUGH */ | |||||
default: | |||||
db_printf("Format:%s Length:%zu Dump:0x", | |||||
g_ddb_oid->oid_fmt, len); | |||||
while (len-- && (xflag || p < val + 16)) | |||||
db_printf("%02x", *p++); | |||||
if (!xflag && len > 16) | |||||
db_printf("..."); | |||||
error = 0; | |||||
goto out; | |||||
} | |||||
out: | |||||
req->oldidx += slen; | |||||
return (error); | |||||
} | |||||
/* | |||||
* Avoid setting new sysctl values from the debugger | |||||
*/ | |||||
static int | |||||
sysctl_new_ddb(struct sysctl_req *req, void *p, size_t l) | |||||
{ | |||||
if (!req->newptr) | |||||
return (0); | |||||
/* Changing sysctls from the debugger is currently unsupported */ | |||||
return (EPERM); | |||||
} | |||||
/* | |||||
* Run a sysctl handler with the DDB oldfunc and newfunc attached. | |||||
* Instead of copying any output to a buffer we'll dump it right to | |||||
* the console. | |||||
*/ | |||||
static int | |||||
db_sysctl(struct sysctl_oid *oidp, int *name, u_int namelen, | |||||
void *old, size_t *oldlenp, size_t *retval, int flags) | |||||
{ | |||||
struct sysctl_req req; | |||||
int error; | |||||
/* Setup the request */ | |||||
bzero(&req, sizeof req); | |||||
req.td = kdb_thread; | |||||
req.oldfunc = sysctl_old_ddb; | |||||
req.newfunc = sysctl_new_ddb; | |||||
req.lock = REQ_UNWIRED; | |||||
if (oldlenp) { | |||||
req.oldlen = *oldlenp; | |||||
} | |||||
req.validlen = req.oldlen; | |||||
if (old) { | |||||
req.oldptr = old; | |||||
} | |||||
/* Setup our globals for sysctl_old_ddb */ | |||||
g_ddb_oid = oidp; | |||||
g_ddb_sysctl_flags = flags; | |||||
g_ddb_sysctl_printed = 0; | |||||
error = sysctl_root(0, name, namelen, &req); | |||||
/* Reset globals */ | |||||
g_ddb_oid = NULL; | |||||
g_ddb_sysctl_flags = 0; | |||||
if (retval) { | |||||
if (req.oldptr && req.oldidx > req.validlen) | |||||
*retval = req.validlen; | |||||
else | |||||
*retval = req.oldidx; | |||||
} | |||||
return (error); | |||||
} | |||||
/* | |||||
* Show a sysctl's name | |||||
*/ | |||||
static void | |||||
db_show_oid_name(int *oid, size_t nlen) | |||||
{ | |||||
struct sysctl_oid *oidp; | |||||
int qoid[CTL_MAXNAME+2]; | |||||
int error; | |||||
qoid[0] = 0; | |||||
memcpy(qoid + 2, oid, nlen * sizeof(int)); | |||||
qoid[1] = 1; | |||||
error = sysctl_find_oid(qoid, nlen + 2, &oidp, NULL, NULL); | |||||
if (error) | |||||
db_error("sysctl name oid"); | |||||
error = db_sysctl(oidp, qoid, nlen + 2, NULL, NULL, NULL, 0); | |||||
if (error) | |||||
db_error("sysctl name"); | |||||
} | |||||
/* | |||||
* Check to see if an OID is safe to print from ddb. | |||||
*/ | |||||
static bool | |||||
db_oid_safe(const struct sysctl_oid *oidp) | |||||
{ | |||||
for (unsigned int i = 0; i < nitems(db_safe_handlers); ++i) { | |||||
if (oidp->oid_handler == db_safe_handlers[i]) | |||||
return (true); | |||||
} | |||||
return (false); | |||||
} | |||||
/* | |||||
* Show a sysctl at a specific OID | |||||
* Compare to the input handling in show_var from sbin/sysctl/sysctl.c | |||||
*/ | |||||
static int | |||||
db_show_oid(struct sysctl_oid *oidp, int *oid, size_t nlen, int flags) | |||||
{ | |||||
int error, xflag, oflag, Nflag, nflag; | |||||
size_t len; | |||||
xflag = flags & DB_SYSCTL_HEX; | |||||
oflag = flags & DB_SYSCTL_OPAQUE; | |||||
nflag = flags & DB_SYSCTL_VALUE_ONLY; | |||||
Nflag = flags & DB_SYSCTL_NAME_ONLY; | |||||
if ((oidp->oid_kind & CTLTYPE) == CTLTYPE_OPAQUE && | |||||
(!xflag && !oflag)) | |||||
return (0); | |||||
if (Nflag) { | |||||
db_show_oid_name(oid, nlen); | |||||
error = 0; | |||||
goto out; | |||||
} | |||||
if (!nflag) { | |||||
db_show_oid_name(oid, nlen); | |||||
db_printf(": "); | |||||
} | |||||
if ((flags & DB_SYSCTL_SAFE_ONLY) && !db_oid_safe(oidp)) { | |||||
db_printf("Skipping, unsafe to print while recursing."); | |||||
error = 0; | |||||
goto out; | |||||
} | |||||
/* Try once, and ask about the size */ | |||||
len = 0; | |||||
error = db_sysctl(oidp, oid, nlen, | |||||
NULL, NULL, &len, flags); | |||||
if (error) | |||||
goto out; | |||||
if (!g_ddb_sysctl_printed) | |||||
/* Lie about the size */ | |||||
error = db_sysctl(oidp, oid, nlen, | |||||
(void *) 1, &len, NULL, flags); | |||||
out: | |||||
db_printf("\n"); | |||||
return (error); | |||||
} | |||||
/* | |||||
* Show all sysctls under a specific OID | |||||
* Compare to sysctl_all from sbin/sysctl/sysctl.c | |||||
*/ | |||||
static int | |||||
db_show_sysctl_all(int *oid, size_t len, int flags) | |||||
{ | |||||
struct sysctl_oid *oidp; | |||||
int name1[CTL_MAXNAME + 2], name2[CTL_MAXNAME + 2]; | |||||
size_t l1, l2; | |||||
name1[0] = 0; | |||||
name1[1] = 2; | |||||
l1 = 2; | |||||
if (len) { | |||||
memcpy(name1+2, oid, len * sizeof(int)); | |||||
l1 +=len; | |||||
} else { | |||||
name1[2] = 1; | |||||
l1++; | |||||
} | |||||
for (;;) { | |||||
int i, error; | |||||
l2 = sizeof(name2); | |||||
error = kernel_sysctl(kdb_thread, name1, l1, | |||||
name2, &l2, NULL, 0, &l2, 0); | |||||
if (error != 0) { | |||||
if (error == ENOENT) | |||||
return (0); | |||||
else | |||||
db_error("sysctl(getnext)"); | |||||
} | |||||
l2 /= sizeof(int); | |||||
if (l2 < (unsigned int)len) | |||||
return (0); | |||||
for (i = 0; i < len; i++) | |||||
if (name2[i] != oid[i]) | |||||
return (0); | |||||
/* Find the OID in question */ | |||||
error = sysctl_find_oid(name2, l2, &oidp, NULL, NULL); | |||||
if (error) | |||||
return (error); | |||||
i = db_show_oid(oidp, name2, l2, flags | DB_SYSCTL_SAFE_ONLY); | |||||
if (db_pager_quit) | |||||
return (0); | |||||
memcpy(name1+2, name2, l2 * sizeof(int)); | |||||
l1 = 2 + l2; | |||||
} | |||||
} | |||||
/* | |||||
* Show a sysctl by its user facing string | |||||
*/ | |||||
static int | |||||
db_sysctlbyname(char *name, int flags) | |||||
{ | |||||
struct sysctl_oid *oidp; | |||||
int oid[CTL_MAXNAME]; | |||||
int error, nlen; | |||||
error = name2oid(name, oid, &nlen, &oidp); | |||||
if (error) { | |||||
return (error); | |||||
} | |||||
if ((oidp->oid_kind & CTLTYPE) == CTLTYPE_NODE) { | |||||
db_show_sysctl_all(oid, nlen, flags); | |||||
} else { | |||||
error = db_show_oid(oidp, oid, nlen, flags); | |||||
} | |||||
return (error); | |||||
} | |||||
static void | |||||
db_sysctl_cmd_usage(void) | |||||
{ | |||||
db_printf( | |||||
" sysctl [/Nnox] <sysctl> \n" | |||||
" \n" | |||||
" <sysctl> The name of the sysctl to show. \n" | |||||
" \n" | |||||
" Show a sysctl by hooking into SYSCTL_IN and SYSCTL_OUT. \n" | |||||
" This will work for most sysctls, but should not be used \n" | |||||
" with sysctls that are known to malloc. \n" | |||||
" \n" | |||||
" While recursing any \"unsafe\" sysctls will be skipped. \n" | |||||
" Call sysctl directly on the sysctl to try printing the \n" | |||||
" skipped sysctl. This is unsafe and may make the ddb \n" | |||||
" session unusable. \n" | |||||
" \n" | |||||
" Arguments: \n" | |||||
" /N Display only the name of the sysctl. \n" | |||||
" /n Display only the value of the sysctl. \n" | |||||
" /o Display opaque values. \n" | |||||
" /x Display the sysctl in hex. \n" | |||||
" \n" | |||||
"For example: \n" | |||||
"sysctl vm.v_free_min \n" | |||||
"vn.v_free_min: 12669 \n" | |||||
); | |||||
} | |||||
/* | |||||
* Show a specific sysctl similar to sysctl (8). | |||||
*/ | |||||
DB_FUNC(sysctl, db_sysctl_cmd, db_cmd_table, CS_OWN, NULL) | |||||
{ | |||||
char name[TOK_STRING_SIZE]; | |||||
int error, i, t, flags; | |||||
/* Parse the modifiers */ | |||||
t = db_read_token(); | |||||
if (t == tSLASH || t == tMINUS) { | |||||
t = db_read_token(); | |||||
if (t != tIDENT) { | |||||
db_printf("Bad modifier\n"); | |||||
error = EINVAL; | |||||
goto out; | |||||
} | |||||
db_strcpy(modif, db_tok_string); | |||||
} | |||||
else { | |||||
db_unread_token(t); | |||||
modif[0] = '\0'; | |||||
} | |||||
flags = 0; | |||||
for (i = 0; i < nitems(db_sysctl_modifs); i++) { | |||||
if (strchr(modif, db_sysctl_modifs[i])) { | |||||
flags |= db_sysctl_modif_values[i]; | |||||
} | |||||
} | |||||
/* Parse the sysctl names */ | |||||
t = db_read_token(); | |||||
if (t != tIDENT) { | |||||
db_printf("Need sysctl name\n"); | |||||
error = EINVAL; | |||||
goto out; | |||||
} | |||||
/* Copy the name into a temporary buffer */ | |||||
db_strcpy(name, db_tok_string); | |||||
/* Ensure there is no trailing cruft */ | |||||
t = db_read_token(); | |||||
if (t != tEOL) { | |||||
db_printf("Unexpected sysctl argument\n"); | |||||
error = EINVAL; | |||||
goto out; | |||||
} | |||||
error = db_sysctlbyname(name, flags); | |||||
if (error == ENOENT) { | |||||
db_printf("unknown oid: '%s'\n", db_tok_string); | |||||
goto out; | |||||
} else if (error) { | |||||
db_printf("%s: error: %d\n", db_tok_string, error); | |||||
goto out; | |||||
} | |||||
out: | |||||
/* Ensure we eat all of our text */ | |||||
db_flush_lex(); | |||||
if (error == EINVAL) { | |||||
db_sysctl_cmd_usage(); | |||||
} | |||||
} | |||||
#endif /* DDB */ |