#include "inc.h" #include struct sysctl_tab { int id; size_t size; const struct sysctl_tab *tab; int (*proc)(struct trace_proc *, const char *, int, const void *, vir_bytes, size_t); }; #define NODE(i,t) { .id = i, .size = __arraycount(t), .tab = t } #define PROC(i,s,p) { .id = i, .size = s, .proc = p } /* The CTL_KERN table. */ static const struct sysctl_tab kern_tab[] = { }; /* The top-level table, which is indexed by identifier. */ static const struct sysctl_tab root_tab[] = { [CTL_KERN] = NODE(0, kern_tab), }; /* * This buffer should be large enough to avoid having to perform dynamic * allocation in all but highly exceptional cases. The CTL_KERN subtree is * currently the largest, so we base the buffer size on its length. * TODO: merge this buffer with ioctlbuf. */ static char sysctlbuf[sizeof(struct sysctlnode) * KERN_MAXID]; static const struct flags sysctl_flags[] = { FLAG_MASK(SYSCTL_VERS_MASK, SYSCTL_VERS_0), FLAG_MASK(SYSCTL_VERS_MASK, SYSCTL_VERSION), #define SYSCTL_VER_ENTRIES 2 /* the first N entries are for SYSCTL_VERS_MASK */ FLAG(CTLFLAG_UNSIGNED), FLAG(CTLFLAG_OWNDESC), FLAG(CTLFLAG_MMAP), FLAG(CTLFLAG_ALIAS), FLAG(CTLFLAG_ANYNUMBER), FLAG(CTLFLAG_ROOT), FLAG(CTLFLAG_HEX), FLAG(CTLFLAG_IMMEDIATE), FLAG(CTLFLAG_OWNDATA), FLAG(CTLFLAG_HIDDEN), FLAG(CTLFLAG_PERMANENT), FLAG(CTLFLAG_PRIVATE), FLAG(CTLFLAG_ANYWRITE), FLAG_MASK(CTLFLAG_READWRITE, CTLFLAG_READONLY), FLAG_MASK(CTLFLAG_READWRITE, CTLFLAG_READWRITE), FLAG_MASK(SYSCTL_TYPEMASK, CTLTYPE_NODE), FLAG_MASK(SYSCTL_TYPEMASK, CTLTYPE_INT), FLAG_MASK(SYSCTL_TYPEMASK, CTLTYPE_STRING), FLAG_MASK(SYSCTL_TYPEMASK, CTLTYPE_QUAD), FLAG_MASK(SYSCTL_TYPEMASK, CTLTYPE_STRUCT), FLAG_MASK(SYSCTL_TYPEMASK, CTLTYPE_BOOL), }; /* * Print the immediate value of a sysctl node. */ static void put_sysctl_imm(struct trace_proc * proc, struct sysctlnode * scn, int use_name) { char *name; name = NULL; switch (SYSCTL_TYPE(scn->sysctl_flags)) { case CTLTYPE_INT: if (use_name) name = "sysctl_idata"; if (scn->sysctl_flags & CTLFLAG_HEX) put_value(proc, name, "0x%x", scn->sysctl_idata); else if (scn->sysctl_flags & CTLFLAG_UNSIGNED) put_value(proc, name, "%u", scn->sysctl_idata); else put_value(proc, name, "%d", scn->sysctl_idata); break; case CTLTYPE_BOOL: if (use_name) name = "sysctl_bdata"; put_field(proc, name, (scn->sysctl_bdata) ? "true" : "false"); break; case CTLTYPE_QUAD: if (use_name) name = "sysctl_qdata"; if (scn->sysctl_flags & CTLFLAG_HEX) put_value(proc, name, "0x%"PRIx64, scn->sysctl_qdata); else put_value(proc, name, "%"PRIu64, scn->sysctl_qdata); break; } } /* * Printer for CTL_QUERY data. */ static int put_sysctl_query(struct trace_proc * proc, const char * name, int type, const void * data __unused, vir_bytes addr, size_t size) { struct sysctlnode scn; if (type == ST_NEWP) { if (!put_open_struct(proc, name, 0, addr, &scn, sizeof(scn))) return TRUE; /* Print just the protocol version, that's all there is. */ if (verbose > 1) put_flags(proc, "sysctl_flags", sysctl_flags, SYSCTL_VER_ENTRIES, "0x%x", scn.sysctl_flags); put_close_struct(proc, FALSE /*all*/); } else { /* TODO: optionally dump struct sysctlnode array */ put_open(proc, name, 0, "[", ", "); if (size > 0) put_tail(proc, size / sizeof(scn), 0); put_close(proc, "]"); } return TRUE; } /* * Printer for CTL_CREATE data. */ static int put_sysctl_create(struct trace_proc * proc, const char * name, int type, const void * data __unused, vir_bytes addr, size_t size) { struct sysctlnode scn; if (!put_open_struct(proc, name, 0, addr, &scn, sizeof(scn))) return TRUE; if (type == ST_NEWP) put_flags(proc, "sysctl_flags", sysctl_flags, COUNT(sysctl_flags), "0x%x", scn.sysctl_flags); if (scn.sysctl_num == CTL_CREATE && type == ST_NEWP && !valuesonly) put_field(proc, "sysctl_num", "CTL_CREATE"); else put_value(proc, "sysctl_num", "%d", scn.sysctl_num); if (type == ST_NEWP) { put_buf(proc, "sysctl_name", PF_LOCADDR | PF_STRING, (vir_bytes)scn.sysctl_name, sizeof(scn.sysctl_name)); } if (scn.sysctl_ver != 0 && verbose > 0) put_value(proc, "sysctl_ver", "%u", scn.sysctl_ver); if (type == ST_NEWP) { if (scn.sysctl_flags & CTLFLAG_IMMEDIATE) put_sysctl_imm(proc, &scn, TRUE /*use_name*/); switch (SYSCTL_TYPE(scn.sysctl_flags)) { case CTLTYPE_NODE: break; case CTLTYPE_STRING: if (scn.sysctl_data != NULL) put_buf(proc, "sysctl_data", PF_STRING, (vir_bytes)scn.sysctl_data, (scn.sysctl_size > 0) ? scn.sysctl_size : SSIZE_MAX /* hopefully it stops early */); if (scn.sysctl_data != NULL || verbose == 0) break; /* FALLTHROUGH */ default: if (!(scn.sysctl_flags & CTLFLAG_IMMEDIATE) && verbose > 0) put_ptr(proc, "sysctl_data", (vir_bytes)scn.sysctl_data); break; } if (SYSCTL_TYPE(scn.sysctl_flags) == CTLTYPE_STRUCT || verbose > 0) put_value(proc, "sysctl_size", "%zu", scn.sysctl_size); } put_close_struct(proc, FALSE /*all*/); return TRUE; } /* * Printer for CTL_DESTROY data. */ static int put_sysctl_destroy(struct trace_proc * proc, const char * name, int type, const void * data __unused, vir_bytes addr, size_t size) { struct sysctlnode scn; if (!put_open_struct(proc, name, 0, addr, &scn, sizeof(scn))) return TRUE; if (type == ST_NEWP) { put_value(proc, "sysctl_num", "%d", scn.sysctl_num); if (scn.sysctl_name[0] != '\0') put_buf(proc, "sysctl_name", PF_LOCADDR | PF_STRING, (vir_bytes)scn.sysctl_name, sizeof(scn.sysctl_name)); if (scn.sysctl_ver != 0 && verbose > 0) put_value(proc, "sysctl_ver", "%u", scn.sysctl_ver); } put_close_struct(proc, FALSE /*all*/); return TRUE; } /* * Printer for CTL_CREATE data. */ static int put_sysctl_describe(struct trace_proc * proc, const char * name, int type, const void * data __unused, vir_bytes addr, size_t size) { struct sysctlnode scn; if (type == ST_NEWP) { if (!put_open_struct(proc, name, 0, addr, &scn, sizeof(scn))) return TRUE; /* Print just the protocol version, that's all there is. */ if (verbose > 1) put_flags(proc, "sysctl_flags", sysctl_flags, SYSCTL_VER_ENTRIES, "0x%x", scn.sysctl_flags); put_value(proc, "sysctl_num", "%d", scn.sysctl_num); if (scn.sysctl_desc != NULL) put_buf(proc, "sysctl_desc", PF_STRING, (vir_bytes)scn.sysctl_desc, 1024 /*no constant!*/); else if (verbose > 0) put_ptr(proc, "sysctl_desc", (vir_bytes)scn.sysctl_desc); put_close_struct(proc, FALSE /*all*/); } else { /* TODO: optionally dump struct sysctldesc array */ put_field(proc, name, (size == 0) ? "[]" : "[..]"); } return TRUE; } /* * Printer for generic data, using the node flags stored in proc->sysctl_flags. */ static int put_sysctl_generic(struct trace_proc * proc, const char * name, int type, const void * data __unused, vir_bytes addr, size_t size) { struct sysctlnode scn; void *ptr; int i; bool b; u_quad_t q; size_t len; switch (SYSCTL_TYPE(proc->sctl_flags)) { case CTLTYPE_STRING: put_buf(proc, name, PF_STRING, addr, size); return TRUE; case CTLTYPE_INT: ptr = &scn.sysctl_idata; len = sizeof(scn.sysctl_idata); break; case CTLTYPE_BOOL: ptr = &scn.sysctl_bdata; len = sizeof(scn.sysctl_bdata); break; case CTLTYPE_QUAD: ptr = &scn.sysctl_qdata; len = sizeof(scn.sysctl_qdata); break; case CTLTYPE_STRUCT: default: ptr = NULL; len = 0; break; } if (ptr == NULL || len != size || mem_get_data(proc->pid, addr, ptr, len) < 0) { put_ptr(proc, name, addr); return TRUE; } put_open(proc, name, PF_NONAME, "{", ", "); scn.sysctl_flags = proc->sctl_flags; put_sysctl_imm(proc, &scn, FALSE); put_close(proc, "}"); return TRUE; } /* * Obtain information about a particular node 'id' in the node directory * identified by the MIB path 'name' (length 'namelen'). Return TRUE if the * node was found, in which case it is copied into 'scnp'. Return FALSE if the * node was not found or another error occurred. */ static int get_sysctl_node(const int * name, unsigned int namelen, int id, struct sysctlnode * scnp) { struct sysctlnode *scn, *escn, *fscn; char *buf; size_t len, elen; int r, mib[CTL_MAXNAME]; assert(namelen < CTL_MAXNAME); assert(id >= 0); /* Query the parent, first using our static buffer for the results. */ memcpy(mib, name, sizeof(mib[0]) * namelen); mib[namelen] = CTL_QUERY; len = sizeof(sysctlbuf); r = sysctl(mib, namelen + 1, sysctlbuf, &len, NULL, 0); if (r == -1 && (errno != ENOMEM || len == 0)) return FALSE; /* Even with partial results, check if we already found the node. */ elen = MIN(len, sizeof(sysctlbuf)); scn = (struct sysctlnode *)sysctlbuf; escn = (struct sysctlnode *)&sysctlbuf[elen]; fscn = NULL; /* pointer to the node once found, NULL until then */ for (; scn < escn && fscn == NULL; scn++) if (scn->sysctl_num == id) fscn = scn; /* If our buffer was too small, use a temporary buffer. */ if (fscn == NULL && r == -1) { if ((buf = malloc(len)) == NULL) return FALSE; if (sysctl(mib, namelen, buf, &len, NULL, 0) == 0) { scn = (struct sysctlnode *)sysctlbuf; escn = (struct sysctlnode *)&sysctlbuf[len]; for (; scn < escn && fscn != NULL; scn++) if (scn->sysctl_num == id) fscn = scn; } free(buf); } if (fscn != NULL) { memcpy(scnp, fscn, sizeof(*scnp)); return TRUE; } else return FALSE; } /* * Print the name string of one level of a sysctl(2) name, while also gathering * information about the target node. Return 1 if name interpretation should * continue as before, meaning this function will also be called for the next * name component (if any). Return 0 if the rest of the name should be printed * as numbers, without interpretation. Return -1 if printing the name is now * complete. */ static int put_sysctl_namestr(struct trace_proc * proc, const int * name, unsigned int namelen, unsigned int n, int all, const struct sysctl_tab ** sctp) { const struct sysctl_tab *sct; struct sysctlnode scn; const char *namestr; int i, r, id, is_last; assert(n < namelen); id = name[n]; is_last = (n == namelen - 1 && all); namestr = NULL; /* Negative identifiers are meta-identifiers. */ if (id < 0) { switch (id) { case CTL_EOL: namestr = ""; break; case CTL_QUERY: namestr = ""; break; case CTL_CREATE: namestr = ""; break; case CTL_CREATESYM: namestr = ""; break; case CTL_DESTROY: namestr = ""; break; case CTL_MMAP: namestr = ""; break; case CTL_DESCRIBE: namestr = ""; break; } /* For some of them, we can print their parameters. */ if (is_last) { switch (id) { case CTL_QUERY: proc->sctl_proc = put_sysctl_query; break; case CTL_CREATE: proc->sctl_proc = put_sysctl_create; break; case CTL_DESTROY: proc->sctl_proc = put_sysctl_destroy; break; case CTL_DESCRIBE: proc->sctl_proc = put_sysctl_describe; break; } } /* * Meta-identifiers are allowed only at the very end of a name, * so if anything follows a meta-identifier, there is no good * way to interpret it. We just print numbers. */ r = 0; } else if (get_sysctl_node(name, n, id, &scn)) { /* * For regular identifiers, first see if we have a callback * function that does the interpretation. The use of the * callback function depends on whether the current node is of * type CTLTYPE_NODE: if it is, the callback function is * responsible for printing the rest of the name (and we return * -1 here after we are done, #1); if it isn't, then we just * use the callback function to interpret the node value (#2). * If we do not have a callback function, but the current node * is of type CTLTYPE_NODE *and* has a non-NULL callback * function registered in the MIB service, the remote callback * function would interpret the rest of the name, so we simply * print the rest of the name as numbers (returning 0 once we * are done, #3). Without a MIB-service callback function, * such nodes are just taken as path components and thus we * return 1 to continue resolution (#4). Finally, if we do not * have a callback function, and the current node is a data * node (i.e., *not* of type CTLTYPE_NODE), we try to interpret * it generically if it is the last component (#5), or we give * up and just print numbers otherwise (#6). */ /* Okay, so start by looking up the node in our own tables. */ sct = NULL; if (n == 0) { /* The top level is ID-indexed for performance. */ if ((unsigned int)id < __arraycount(root_tab)) *sctp = &root_tab[id]; else *sctp = NULL; } else if (*sctp != NULL) { /* Other levels are searched, because of sparseness. */ sct = (*sctp)->tab; /* NULL if missing or leaf */ for (i = (int)(*sctp)->size; sct != NULL && i > 0; i--, sct++) if (sct->id == id) break; if (i == 0) sct = NULL; *sctp = sct; } /* Now determine what to do. */ if (SYSCTL_TYPE(scn.sysctl_flags) == CTLTYPE_NODE) { if (sct != NULL && sct->proc != NULL) { proc->sctl_size = sct->size; proc->sctl_proc = sct->proc; r = -1; /* #1 */ } else if (scn.sysctl_func != NULL) r = 0; /* #3 */ else r = 1; /* #4 */ } else { if (!is_last) r = 0; /* #6 */ else if (sct != NULL && sct->proc != NULL) { /* A nonzero size must match the node size. */ if (sct->size == 0 || sct->size == scn.sysctl_size) { proc->sctl_size = sct->size; proc->sctl_proc = sct->proc; } r = 0; /* #2 */ } else { proc->sctl_flags = scn.sysctl_flags; proc->sctl_proc = put_sysctl_generic; r = 0; /* #5 */ } } namestr = scn.sysctl_name; } else { /* * The node was not found. This basically means that we will * not be able to get any information about deeper nodes * either. We do not even try: just print numbers. */ r = 0; } if (!valuesonly && namestr != NULL) put_field(proc, NULL, namestr); else put_value(proc, NULL, "%d", id); /* * Did we determine that the rest of the name should be printed by the * callback function? Then we might as well make that happen. The * abuse of the parameter types is not great, oh well. */ if (r == -1) (void)proc->sctl_proc(proc, NULL, ST_NAME, &name[n + 1], 0, namelen - n - 1); return r; } /* * Print the sysctl(2) name parameter, and gather information needed to print * the oldp and newp parameters later. */ static void put_sysctl_name(struct trace_proc * proc, const char * name, int flags, vir_bytes addr, unsigned int namelen) { const struct sysctl_tab *sct = NULL; const char *namestr; int r, all, namebuf[CTL_MAXNAME]; unsigned int n; if (namelen > CTL_MAXNAME) { namelen = CTL_MAXNAME; all = 0; } else all = 1; if ((flags & PF_FAILED) || valuesonly > 1 || namelen > CTL_MAXNAME || (namelen > 0 && !(flags & PF_LOCADDR) && mem_get_data(proc->pid, addr, namebuf, namelen * sizeof(namebuf[0])) < 0)) { if (flags & PF_LOCADDR) put_field(proc, name, "&.."); else put_ptr(proc, name, addr); return; } else if (namelen > 0 && (flags & PF_LOCADDR)) memcpy(namebuf, (void *)addr, sizeof(namebuf[0]) * namelen); /* * Print the path name of the node as possible, and find information * about the target node as we go along. See put_sysctl_namestr() for * the meaning of 'r'. */ put_open(proc, name, PF_NONAME, "[", "."); for (n = 0, r = 1; n < namelen; n++) { if (r == 1) { if ((r = put_sysctl_namestr(proc, namebuf, namelen, n, all, &sct)) < 0) break; } else put_value(proc, NULL, "%d", namebuf[n]); } if (!all) put_field(proc, NULL, ".."); put_close(proc, "]"); } /* * Print the sysctl(2) oldp or newp parameter. PF_ALT means that the given * parameter is newp rather than oldp, in which case PF_FAILED will not be set. */ static void put_sysctl_data(struct trace_proc * proc, const char * name, int flags, vir_bytes addr, size_t len) { char *ptr; int type, all; if ((flags & PF_FAILED) || addr == 0 || valuesonly > 1 || proc->sctl_proc == NULL || proc->sctl_size > sizeof(sysctlbuf) || (proc->sctl_size > 0 && (proc->sctl_size != len || mem_get_data(proc->pid, addr, sysctlbuf, proc->sctl_size) < 0))) { put_ptr(proc, name, addr); return; } type = (flags & PF_ALT) ? ST_NEWP : ST_OLDP; ptr = (proc->sctl_size > 0) ? sysctlbuf : NULL; /* * The rough idea here: we have a "simple" mode and a "flexible" mode, * depending on whether a size was specified in our table. For the * simple mode, we only call the callback function when we have been * able to copy in the data. A surrounding {} block will be printed * automatically, the callback function only has to print the data * fields. The simple mode is basically for structures. In contrast, * the flexible mode leaves both the copying and the printing entirely * to the callback function, which thus may print the pointer on copy * failure (in which case the surrounding {}s would get in the way). */ if (ptr != NULL) put_open(proc, name, 0, "{", ", "); all = proc->sctl_proc(proc, name, type, ptr, addr, len); if (ptr != NULL) { if (all == FALSE) put_field(proc, NULL, ".."); put_close(proc, "}"); } } static int mib_sysctl_out(struct trace_proc * proc, const message * m_out) { unsigned int namelen; /* Reset the sysctl-related state. */ proc->sctl_flags = 0; proc->sctl_size = 0; proc->sctl_proc = NULL; proc->sctl_arg = 0; namelen = m_out->m_lc_mib_sysctl.namelen; /* As part of processing the name, we initialize the state. */ if (namelen <= CTL_SHORTNAME) put_sysctl_name(proc, "name", PF_LOCADDR, (vir_bytes)&m_out->m_lc_mib_sysctl.name, namelen); else put_sysctl_name(proc, "name", 0, m_out->m_lc_mib_sysctl.namep, namelen); put_value(proc, "namelen", "%u", namelen); if (m_out->m_lc_mib_sysctl.oldp == 0 || valuesonly > 1) { put_sysctl_data(proc, "oldp", 0, m_out->m_lc_mib_sysctl.oldp, m_out->m_lc_mib_sysctl.oldlen); /* If oldp is NULL, oldlen may contain garbage; don't print. */ if (m_out->m_lc_mib_sysctl.oldp != 0) put_value(proc, "oldlen", "%zu", /* {%zu} is more */ m_out->m_lc_mib_sysctl.oldlen); /* correct.. */ else put_value(proc, "oldlen", "%d", 0); put_sysctl_data(proc, "newp", PF_ALT, m_out->m_lc_mib_sysctl.newp, m_out->m_lc_mib_sysctl.newlen); put_value(proc, "newlen", "%zu", m_out->m_lc_mib_sysctl.newlen); return CT_DONE; } else return CT_NOTDONE; } static void mib_sysctl_in(struct trace_proc * proc, const message * m_out, const message * m_in, int failed) { int err; if (m_out->m_lc_mib_sysctl.oldp != 0 && valuesonly <= 1) { put_sysctl_data(proc, "oldp", failed, m_out->m_lc_mib_sysctl.oldp, m_in->m_mib_lc_sysctl.oldlen /* the returned length */); put_value(proc, "oldlen", "%zu", /* {%zu} is more correct.. */ m_out->m_lc_mib_sysctl.oldlen); put_sysctl_data(proc, "newp", PF_ALT, m_out->m_lc_mib_sysctl.newp, m_out->m_lc_mib_sysctl.newlen); put_value(proc, "newlen", "%zu", m_out->m_lc_mib_sysctl.newlen); put_equals(proc); } put_result(proc); /* * We want to print the returned old length in the following cases: * 1. the call succeeded, the old pointer was NULL, and no new data was * supplied; * 2. the call succeeded, the old pointer was not NULL, and the * returned old length is different from the supplied old length. * 3. the call failed with ENOMEM or EEXIST, and the old pointer was * not NULL (an undocumented NetBSD feature, used by sysctl(8)). */ if (/*#1*/ (!failed && m_out->m_lc_mib_sysctl.oldp == 0 && (m_out->m_lc_mib_sysctl.newp == 0 || m_out->m_lc_mib_sysctl.newlen == 0)) || /*#2*/ (!failed && m_out->m_lc_mib_sysctl.oldp != 0 && m_out->m_lc_mib_sysctl.oldlen != m_in->m_mib_lc_sysctl.oldlen) || /*#3*/ (failed && call_errno(proc, &err) && (err == ENOMEM || err == EEXIST) && m_out->m_lc_mib_sysctl.oldp != 0)) { put_open(proc, NULL, 0, "(", ", "); put_value(proc, "oldlen", "%zu", m_in->m_mib_lc_sysctl.oldlen); put_close(proc, ")"); } } #define MIB_CALL(c) [((MIB_ ## c) - MIB_BASE)] static const struct call_handler mib_map[] = { MIB_CALL(SYSCTL) = HANDLER("sysctl", mib_sysctl_out, mib_sysctl_in), }; const struct calls mib_calls = { .endpt = MIB_PROC_NR, .base = MIB_BASE, .map = mib_map, .count = COUNT(mib_map) };