
Most of the nodes in the general sysctl tree will be managed directly by the MIB service, which obtains the necessary information as needed. However, in certain cases, it makes more sense to let another service manage a part of the sysctl tree itself, in order to avoid replicating part of that other service in the MIB service. This patch adds the basic support for such delegation: remote services may now register their own subtrees within the full sysctl tree with the MIB service, which will then forward any sysctl(2) requests on such subtrees to the remote services. The system works much like mounting a file system, but in addition to support for shadowing an existing node, the MIB service also supports creating temporary mount point nodes. Each have their own use cases. A remote "kern.ipc" would use the former, because even when such a subtree were not mounted, userland would still expect some of its children to exist and return default values. A remote "net.inet" would use the latter, as there is no reason to precreate nodes for all possible supported networking protocols in the MIB "net" subtree. A standard remote MIB (RMIB) implementation is provided for services that wish to make use of this functionality. It is essentially a simplified and somewhat more lightweight version of the MIB service's internals, and works more or less the same from a programmer's point of view. The most important difference is the "rmib" prefix instead of the "mib" prefix. Documentation will hopefully follow later. Overall, the RMIB functionality should not be used lightly, for several reasons. First, despite being more lightweight than the MIB service, the RMIB module still adds substantially to the code footprint of the containing service. Second, the RMIB protocol not only adds extra IPC for sysctl(2), but has also not been optimized for performance in other ways. Third, and most importantly, the RMIB implementation also several limitations. The main limitation is that remote MIB subtrees must be fully static. Not only may the user not create or destroy nodes, the service itself may not either, as this would clash with the simplified remote node versioning system and the cached subtree root node child counts. Other limitations exist, such as the fact that the root of a remote subtree may only be a node-type node, and a stricter limit on the highest node identifier of any child in this subtree root (currently 4095). The current implementation was born out of necessity, and therefore it leaves several improvements to future work. Most importantly, support for exit and crash notification is missing, primarily in the MIB service. This means that remote subtrees may not be cleaned up immediately, but instead only when the MIB service attempts to talk to the dead remote service. In addition, if the MIB service itself crashes, re-registration of remote subtrees is currently left up to the individual RMIB users. Finally, the MIB service uses synchronous (sendrec-based) calls to the remote services, which while convenient may cause cascading service hangs. The underlying protocol is ready for conversion to an asynchronous implementation already, though. A new test set, testrmib.sh, tests the basic RMIB functionality. To this end it uses a test service, rmibtest, and also reuses part of the existing test87 MIB service test. Change-Id: I3378fe04f2e090ab231705bde7e13d6289a9183e
1843 lines
53 KiB
C
1843 lines
53 KiB
C
/* MIB service - tree.c - tree access and management */
|
|
|
|
#include "mib.h"
|
|
|
|
/*
|
|
* Does the given identifier fall within the range of static identifiers in the
|
|
* given parent? This check can be used to enumerate all static array entries
|
|
* in the given parent, starting from zero. The check does not guarantee that
|
|
* the entry is actually for a valid node, nor does it guarantee that there is
|
|
* not a dynamic node with this identifier.
|
|
*/
|
|
#define IS_STATIC_ID(parent, id) ((unsigned int)(id) < (parent)->node_size)
|
|
|
|
/*
|
|
* Scratch buffer, used for various cases of temporary data storage. It must
|
|
* be large enough to fit a sysctldesc structure followed by the longest
|
|
* supported description. It must also be large enough to serve as temporary
|
|
* storage for data being written in the majority of cases. Finally, it must
|
|
* be large enough to contain an entire page, for mib_copyin_str().
|
|
*/
|
|
#define MAXDESCLEN 1024 /* from NetBSD */
|
|
#define SCRATCH_SIZE MAX(PAGE_SIZE, sizeof(struct sysctldesc) + MAXDESCLEN)
|
|
static char scratch[SCRATCH_SIZE] __aligned(sizeof(int32_t));
|
|
|
|
unsigned int mib_nodes; /* how many nodes are there in the tree? */
|
|
unsigned int mib_objects; /* how many memory objects are allocated? */
|
|
unsigned int mib_remotes; /* how many remote subtrees are there? */
|
|
|
|
/*
|
|
* Find a node through its parent node and identifier. Return the node if it
|
|
* was found, and optionally store a pointer to the pointer to its dynode
|
|
* superstructure (for removal). If no matching node was found, return NULL.
|
|
*/
|
|
static struct mib_node *
|
|
mib_find(struct mib_node * parent, int id, struct mib_dynode *** prevpp)
|
|
{
|
|
struct mib_node *node;
|
|
struct mib_dynode **dynp;
|
|
|
|
if (id < 0)
|
|
return NULL;
|
|
|
|
/*
|
|
* Is there a static node with this identifier? The static nodes are
|
|
* all in a single array, so lookup is O(1) for these nodes. We use
|
|
* the node flags field to see whether the array entry is valid.
|
|
*/
|
|
if (IS_STATIC_ID(parent, id)) {
|
|
node = &parent->node_scptr[id];
|
|
|
|
if (node->node_flags != 0) {
|
|
/* Found a matching static node. */
|
|
if (prevpp != NULL)
|
|
*prevpp = NULL;
|
|
return node;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Is there a dynamic node with this identifier? The dynamic nodes
|
|
* form a linked list. This is predominantly because userland may pick
|
|
* the identifier number at creation time, so we cannot rely on all
|
|
* dynamically created nodes falling into a small identifier range.
|
|
* That eliminates the option of a dynamic array indexed by identifier,
|
|
* and a linked list is the simplest next option. Thus, dynamic node
|
|
* lookup is O(n). However, since the list is sorted by identifier,
|
|
* we may be able to stop the search early.
|
|
*/
|
|
for (dynp = &parent->node_dcptr; *dynp != NULL;
|
|
dynp = &((*dynp)->dynode_next)) {
|
|
if ((*dynp)->dynode_id == id) {
|
|
/* Found a matching dynamic node. */
|
|
if (prevpp != NULL)
|
|
*prevpp = dynp;
|
|
return &(*dynp)->dynode_node;
|
|
} else if ((*dynp)->dynode_id > id)
|
|
break; /* no need to look further */
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Copy out a node to userland, using the exchange format for nodes (namely,
|
|
* a sysctlnode structure). Return the size of the object that is (or, if the
|
|
* node falls outside the requested data range, would be) copied out on
|
|
* success, or a negative error code on failure. The function may return 0
|
|
* to indicate that nothing was copied out after all (this is unused here).
|
|
*/
|
|
static ssize_t
|
|
mib_copyout_node(struct mib_call * call, struct mib_oldp * oldp, size_t off,
|
|
int id, const struct mib_node * node)
|
|
{
|
|
struct sysctlnode scn;
|
|
int visible;
|
|
|
|
if (!mib_inrange(oldp, off))
|
|
return sizeof(scn); /* nothing to do */
|
|
|
|
memset(&scn, 0, sizeof(scn));
|
|
|
|
/*
|
|
* We use CTLFLAG_PARENT, CTLFLAG_VERIFY, and CTLFLAG_REMOTE internally
|
|
* only. NetBSD uses the values of these flags for different purposes.
|
|
* Either way, do not expose them to userland.
|
|
*/
|
|
scn.sysctl_flags = SYSCTL_VERSION | (node->node_flags &
|
|
~(CTLFLAG_PARENT | CTLFLAG_VERIFY | CTLFLAG_REMOTE));
|
|
scn.sysctl_num = id;
|
|
strlcpy(scn.sysctl_name, node->node_name, sizeof(scn.sysctl_name));
|
|
scn.sysctl_ver = node->node_ver;
|
|
scn.sysctl_size = node->node_size;
|
|
|
|
/* Some information is only visible if the user can access the node. */
|
|
visible = (!(node->node_flags & CTLFLAG_PRIVATE) || mib_authed(call));
|
|
|
|
/*
|
|
* For immediate types, store the immediate value in the resulting
|
|
* structure, unless the caller is not authorized to obtain the value.
|
|
*/
|
|
if ((node->node_flags & CTLFLAG_IMMEDIATE) && visible) {
|
|
switch (SYSCTL_TYPE(node->node_flags)) {
|
|
case CTLTYPE_BOOL:
|
|
scn.sysctl_bdata = node->node_bool;
|
|
break;
|
|
case CTLTYPE_INT:
|
|
scn.sysctl_idata = node->node_int;
|
|
break;
|
|
case CTLTYPE_QUAD:
|
|
scn.sysctl_qdata = node->node_quad;
|
|
}
|
|
}
|
|
|
|
/* Special rules apply to parent nodes. */
|
|
if (SYSCTL_TYPE(node->node_flags) == CTLTYPE_NODE) {
|
|
/* Report the node size the way NetBSD does, just in case. */
|
|
scn.sysctl_size = sizeof(scn);
|
|
|
|
/*
|
|
* If this is a remote node, use the values we have of the root
|
|
* of the remote subtree. If we did not have these values, we
|
|
* would have to call into the remote service here, which for
|
|
* reliability purposes is a bad idea.
|
|
*
|
|
* If this is a real parent node, report child information. In
|
|
* both these cases, expose child information only if the node
|
|
* itself is accessible by the caller.
|
|
*
|
|
* If this is a function-driven node, indicate this by setting
|
|
* a nonzero function address. This allows trace(1) to
|
|
* determine that it should not attempt to descend into this
|
|
* part of the tree as usual, because a) accessing subnodes may
|
|
* have side effects, and b) meta-identifiers may not work as
|
|
* expected in these parts of the tree. Do not return the real
|
|
* function pointer, as this would leak anti-ASR information.
|
|
*/
|
|
if (node->node_flags & CTLFLAG_REMOTE) {
|
|
if (visible) {
|
|
scn.sysctl_csize = node->node_rcsize;
|
|
scn.sysctl_clen = node->node_rclen;
|
|
}
|
|
} else if (node->node_flags & CTLFLAG_PARENT) {
|
|
if (visible) {
|
|
scn.sysctl_csize = node->node_csize;
|
|
scn.sysctl_clen = node->node_clen;
|
|
}
|
|
} else
|
|
scn.sysctl_func = SYSCTL_NODE_FN;
|
|
}
|
|
|
|
/* Copy out the resulting node. */
|
|
return mib_copyout(oldp, off, &scn, sizeof(scn));
|
|
}
|
|
|
|
/*
|
|
* Given a query on a non-leaf (parent) node, provide the user with an array of
|
|
* this node's children.
|
|
*/
|
|
static ssize_t
|
|
mib_query(struct mib_call * call, struct mib_node * parent,
|
|
struct mib_oldp * oldp, struct mib_newp * newp)
|
|
{
|
|
struct sysctlnode scn;
|
|
struct mib_node *node;
|
|
struct mib_dynode *dynode;
|
|
size_t off;
|
|
int r, id;
|
|
|
|
/* If the user passed in version numbers, check them. */
|
|
if (newp != NULL) {
|
|
if ((r = mib_copyin(newp, &scn, sizeof(scn))) != OK)
|
|
return r;
|
|
|
|
if (SYSCTL_VERS(scn.sysctl_flags) != SYSCTL_VERSION)
|
|
return EINVAL;
|
|
|
|
/*
|
|
* If a node version number is given, it must match the version
|
|
* of the parent or the root.
|
|
*/
|
|
if (scn.sysctl_ver != 0 &&
|
|
scn.sysctl_ver != mib_root.node_ver &&
|
|
scn.sysctl_ver != parent->node_ver)
|
|
return EINVAL;
|
|
}
|
|
|
|
/*
|
|
* We need not return the nodes strictly in ascending order of
|
|
* identifiers, as this is not expected by userland. For example,
|
|
* sysctlgetmibinfo(3) performs its own sorting after a query.
|
|
* Thus, we can go through the static and dynamic nodes separately.
|
|
*/
|
|
off = 0;
|
|
|
|
/* First enumerate the static nodes. */
|
|
for (id = 0; IS_STATIC_ID(parent, id); id++) {
|
|
node = &parent->node_scptr[id];
|
|
|
|
if (node->node_flags == 0)
|
|
continue;
|
|
|
|
if ((r = mib_copyout_node(call, oldp, off, id, node)) < 0)
|
|
return r;
|
|
off += r;
|
|
}
|
|
|
|
/* Then enumerate the dynamic nodes. */
|
|
for (dynode = parent->node_dcptr; dynode != NULL;
|
|
dynode = dynode->dynode_next) {
|
|
node = &dynode->dynode_node;
|
|
|
|
if ((r = mib_copyout_node(call, oldp, off, dynode->dynode_id,
|
|
node)) < 0)
|
|
return r;
|
|
off += r;
|
|
}
|
|
|
|
return off;
|
|
}
|
|
|
|
/*
|
|
* Check whether the given name buffer contains a valid node name string. If
|
|
* the name is nonempty, properly terminated, and contains only acceptable
|
|
* characters, return the length of the string excluding null terminator.
|
|
* Otherwise, return zero to indicate failure.
|
|
*/
|
|
static size_t
|
|
mib_check_name(const char * name, size_t namesize)
|
|
{
|
|
size_t namelen;
|
|
char c;
|
|
|
|
/* Names must be nonempty, null terminated, C symbol style strings. */
|
|
for (namelen = 0; namelen < namesize; namelen++) {
|
|
if ((c = name[namelen]) == '\0')
|
|
break;
|
|
/* A-Z, a-z, 0-9, _ only, and no digit as first character. */
|
|
if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
|
|
c == '_' || (c >= '0' && c <= '9' && namelen > 0)))
|
|
return 0;
|
|
}
|
|
if (namelen == 0 || namelen == namesize)
|
|
return 0;
|
|
|
|
return namelen;
|
|
}
|
|
|
|
/*
|
|
* Scan a parent node's children, as part of new node creation. Search for
|
|
* either a free node identifier (if given_id < 0) or collisions with the node
|
|
* identifier to use (if given_id >= 0). Also check for name collisions. Upon
|
|
* success, return OK, with the resulting node identifier stored in 'idp' and a
|
|
* pointer to the pointer for the new dynamic node stored in 'prevpp'. Upon
|
|
* failure, return an error code. If the failure is EEXIST, 'idp' will contain
|
|
* the ID of the conflicting node, and 'nodep' will point to the node.
|
|
*/
|
|
static int
|
|
mib_scan(struct mib_node * parent, int given_id, const char * name, int * idp,
|
|
struct mib_dynode *** prevpp, struct mib_node ** nodep)
|
|
{
|
|
struct mib_dynode **prevp, **dynp;
|
|
struct mib_node *node;
|
|
int id;
|
|
|
|
/*
|
|
* We must verify that no entry already exists with the given name. In
|
|
* addition, if a nonnegative identifier is given, we should use that
|
|
* identifier and make sure it does not already exist. Otherwise, we
|
|
* must find a free identifier. Finally, we sort the dynamic nodes in
|
|
* ascending identifier order, so we must find the right place at which
|
|
* to insert the new node.
|
|
*
|
|
* For finding a free identifier, choose an ID that falls (well)
|
|
* outside the static range, both to avoid accidental retrieval by an
|
|
* application that uses a static ID, and to simplify verifying that
|
|
* the ID is indeed free. The sorting of dynamic nodes by identifier
|
|
* ensures that searching for a free identifier is O(n).
|
|
*
|
|
* At this time, we do not support some NetBSD features. We do not
|
|
* force success if the new node is sufficiently like an existing one.
|
|
* Also, we do not use global autoincrement for dynamic identifiers,
|
|
* although that could easily be changed.
|
|
*/
|
|
|
|
/* First check the static node array, just for collisions. */
|
|
for (id = 0; IS_STATIC_ID(parent, id); id++) {
|
|
node = &parent->node_scptr[id];
|
|
if (node->node_flags == 0)
|
|
continue;
|
|
if (id == given_id || !strcmp(name, node->node_name)) {
|
|
*idp = id;
|
|
*nodep = node;
|
|
return EEXIST;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Then try to find the place to insert a new dynamic node. At the
|
|
* same time, check for both identifier and name collisions.
|
|
*/
|
|
if (given_id >= 0)
|
|
id = given_id;
|
|
else
|
|
id = MAX(CREATE_BASE, parent->node_size);
|
|
|
|
for (prevp = &parent->node_dcptr; *prevp != NULL;
|
|
prevp = &((*prevp)->dynode_next)) {
|
|
if ((*prevp)->dynode_id > id)
|
|
break;
|
|
if ((*prevp)->dynode_id == id) {
|
|
if (given_id >= 0) {
|
|
*idp = id;
|
|
*nodep = &(*prevp)->dynode_node;
|
|
return EEXIST;
|
|
} else
|
|
id++;
|
|
}
|
|
if (!strcmp(name, (*prevp)->dynode_node.node_name)) {
|
|
*idp = (*prevp)->dynode_id;
|
|
*nodep = &(*prevp)->dynode_node;
|
|
return EEXIST;
|
|
}
|
|
}
|
|
|
|
/* Finally, check the rest of the dynamic nodes for name collisions. */
|
|
for (dynp = prevp; *dynp != NULL; dynp = &((*dynp)->dynode_next)) {
|
|
assert((*dynp)->dynode_id > id);
|
|
|
|
if (!strcmp(name, (*dynp)->dynode_node.node_name)) {
|
|
*idp = (*dynp)->dynode_id;
|
|
*nodep = &(*dynp)->dynode_node;
|
|
return EEXIST;
|
|
}
|
|
}
|
|
|
|
*idp = id;
|
|
*prevpp = prevp;
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* Copy in a string from the user process, located at the given remote address,
|
|
* into the given local buffer. If no buffer is given, just compute the length
|
|
* of the string. On success, return OK. If 'sizep' is not NULL, it will be
|
|
* filled with the string size, including the null terminator. If a non-NULL
|
|
* buffer was given, the string will be copied into the provided buffer (also
|
|
* including null terminator). Return an error code on failure, which includes
|
|
* the case that no null terminator was found within the range of bytes that
|
|
* would fit in the given buffer.
|
|
*/
|
|
static int
|
|
mib_copyin_str(struct mib_newp * __restrict newp, vir_bytes addr,
|
|
char * __restrict buf, size_t bufsize, size_t * __restrict sizep)
|
|
{
|
|
char *ptr, *endp;
|
|
size_t chunk, len;
|
|
int r;
|
|
|
|
assert(newp != NULL);
|
|
assert(bufsize <= SSIZE_MAX);
|
|
|
|
if (addr == 0)
|
|
return EINVAL;
|
|
|
|
/*
|
|
* NetBSD has a kernel routine for copying in a string from userland.
|
|
* MINIX3 does not, since its system call interface has always relied
|
|
* on userland passing in string lengths. The sysctl(2) API does not
|
|
* provide the string length, and thus, we have to do a bit of guess
|
|
* work. If we copy too little at once, performance suffers. If we
|
|
* copy too much at once, we may trigger an unneeded page fault. Make
|
|
* use of page boundaries to strike a balance between those two. If we
|
|
* are requested to just get the string length, use the scratch buffer.
|
|
*/
|
|
len = 0;
|
|
|
|
while (bufsize > 0) {
|
|
chunk = PAGE_SIZE - (addr % PAGE_SIZE);
|
|
if (chunk > bufsize)
|
|
chunk = bufsize;
|
|
|
|
ptr = (buf != NULL) ? &buf[len] : scratch;
|
|
if ((r = mib_copyin_aux(newp, addr, ptr, chunk)) != OK)
|
|
return r;
|
|
|
|
if ((endp = memchr(ptr, '\0', chunk)) != NULL) {
|
|
/* A null terminator was found - success. */
|
|
if (sizep != NULL)
|
|
*sizep = len + (size_t)(endp - ptr) + 1;
|
|
return OK;
|
|
}
|
|
|
|
addr += chunk;
|
|
len += chunk;
|
|
bufsize -= chunk;
|
|
}
|
|
|
|
/* No null terminator found. */
|
|
return EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Increase the version of the root node, and copy this new version to all
|
|
* nodes on the path to the given node, including that node itself.
|
|
*/
|
|
static void
|
|
mib_upgrade(struct mib_node * node)
|
|
{
|
|
uint32_t ver;
|
|
|
|
assert(node != NULL);
|
|
|
|
/*
|
|
* The root node determines the version of the entire tree. Do not use
|
|
* version number 0, as a zero version number indicates no interest in
|
|
* versions elsewhere.
|
|
*/
|
|
|
|
ver = mib_root.node_ver + 1;
|
|
if (ver == 0)
|
|
ver = 1;
|
|
|
|
/* Copy the new version to all the nodes on the path. */
|
|
do {
|
|
node->node_ver = ver;
|
|
|
|
node = node->node_parent;
|
|
} while (node != NULL);
|
|
}
|
|
|
|
/*
|
|
* Add a new dynamically allocated node into the tree, inserting it into the
|
|
* linked-list position of the parent tree as given by 'prevp'. Also update
|
|
* versions and counters accordingly. This function never fails.
|
|
*/
|
|
static void
|
|
mib_add(struct mib_dynode * dynode, struct mib_dynode ** prevp)
|
|
{
|
|
struct mib_node *parent;
|
|
|
|
parent = dynode->dynode_node.node_parent;
|
|
assert(parent != NULL);
|
|
|
|
/* Link the dynamic node into the list, in the right place. */
|
|
assert(prevp != NULL);
|
|
dynode->dynode_next = *prevp;
|
|
*prevp = dynode;
|
|
|
|
/* The parent node now has one more child. */
|
|
parent->node_csize++;
|
|
parent->node_clen++;
|
|
|
|
/* There is now one more node in the tree. */
|
|
mib_nodes++;
|
|
|
|
/*
|
|
* Bump the version of all nodes on the path to the new node, including
|
|
* the node itself.
|
|
*/
|
|
mib_upgrade(&dynode->dynode_node);
|
|
}
|
|
|
|
/*
|
|
* Create a node.
|
|
*/
|
|
static ssize_t
|
|
mib_create(struct mib_call * call, struct mib_node * parent,
|
|
struct mib_oldp * oldp, struct mib_newp * newp)
|
|
{
|
|
struct mib_dynode *dynode, **prevp;
|
|
struct mib_node *node;
|
|
struct sysctlnode scn;
|
|
size_t namelen, size;
|
|
ssize_t len;
|
|
bool b;
|
|
char c;
|
|
int r, id;
|
|
|
|
/* This is a privileged operation. */
|
|
if (!mib_authed(call))
|
|
return EPERM;
|
|
|
|
/*
|
|
* The parent must not be a remote node, but this is already implied by
|
|
* the fact that we got here at all.
|
|
*/
|
|
assert(SYSCTL_TYPE(parent->node_flags) == CTLTYPE_NODE);
|
|
assert(!(parent->node_flags & CTLFLAG_REMOTE));
|
|
|
|
/* The parent node must not be marked as read-only. */
|
|
if (!(parent->node_flags & CTLFLAG_READWRITE))
|
|
return EPERM;
|
|
|
|
/*
|
|
* Has the parent reached its child node limit? This check is entirely
|
|
* theoretical as long as we support only 32-bit virtual memory.
|
|
*/
|
|
if (parent->node_csize == INT_MAX)
|
|
return EINVAL;
|
|
assert(parent->node_clen <= parent->node_csize);
|
|
|
|
/* The caller must supply information on the child node to create. */
|
|
if (newp == NULL)
|
|
return EINVAL;
|
|
|
|
if ((r = mib_copyin(newp, &scn, sizeof(scn))) != OK)
|
|
return r;
|
|
|
|
/*
|
|
* We perform as many checks as possible before we start allocating
|
|
* memory. Then again, after allocation, copying in data may still
|
|
* fail. Unlike when setting values, we do not first copy data into a
|
|
* temporary buffer here, because we do not need to: if the copy fails,
|
|
* the entire create operation fails, so atomicity is not an issue.
|
|
*/
|
|
if (SYSCTL_VERS(scn.sysctl_flags) != SYSCTL_VERSION)
|
|
return EINVAL;
|
|
|
|
/*
|
|
* If a node version number is given, it must match the version of
|
|
* either the parent or the root node. The given version number is
|
|
* *not* used for the node being created.
|
|
*/
|
|
if (scn.sysctl_ver != 0 && scn.sysctl_ver != mib_root.node_ver &&
|
|
scn.sysctl_ver != parent->node_ver)
|
|
return EINVAL;
|
|
|
|
/*
|
|
* Validate the node flags. In addition to the NetBSD-allowed flags,
|
|
* we also allow UNSIGNED, and leave its interpretation to userland.
|
|
*/
|
|
if (SYSCTL_FLAGS(scn.sysctl_flags) &
|
|
~(SYSCTL_USERFLAGS | CTLFLAG_UNSIGNED))
|
|
return EINVAL;
|
|
|
|
if (!(scn.sysctl_flags & CTLFLAG_IMMEDIATE)) {
|
|
/*
|
|
* Without either IMMEDIATE or OWNDATA, data pointers are
|
|
* actually kernel addresses--a concept we do not support.
|
|
* Otherwise, if IMMEDIATE is not set, we are going to have to
|
|
* allocate extra memory for the data, so force OWNDATA to be.
|
|
* set. Node-type nodes have no data, though.
|
|
*/
|
|
if (SYSCTL_TYPE(scn.sysctl_flags) != CTLTYPE_NODE) {
|
|
if (!(scn.sysctl_flags & CTLFLAG_OWNDATA) &&
|
|
scn.sysctl_data != NULL)
|
|
return EINVAL; /* not meaningful on MINIX3 */
|
|
|
|
scn.sysctl_flags |= CTLFLAG_OWNDATA;
|
|
}
|
|
} else if (scn.sysctl_flags & CTLFLAG_OWNDATA)
|
|
return EINVAL;
|
|
|
|
/* The READWRITE flag consists of multiple bits. Sanitize. */
|
|
if (scn.sysctl_flags & CTLFLAG_READWRITE)
|
|
scn.sysctl_flags |= CTLFLAG_READWRITE;
|
|
|
|
/* Validate the node type and size, and do some additional checks. */
|
|
switch (SYSCTL_TYPE(scn.sysctl_flags)) {
|
|
case CTLTYPE_BOOL:
|
|
if (scn.sysctl_size != sizeof(bool))
|
|
return EINVAL;
|
|
break;
|
|
case CTLTYPE_INT:
|
|
if (scn.sysctl_size != sizeof(int))
|
|
return EINVAL;
|
|
break;
|
|
case CTLTYPE_QUAD:
|
|
if (scn.sysctl_size != sizeof(u_quad_t))
|
|
return EINVAL;
|
|
break;
|
|
case CTLTYPE_STRING:
|
|
/*
|
|
* For strings, a zero length means that we are supposed to
|
|
* allocate a buffer size based on the given string size.
|
|
*/
|
|
if (scn.sysctl_size == 0 && scn.sysctl_data != NULL) {
|
|
if ((r = mib_copyin_str(newp,
|
|
(vir_bytes)scn.sysctl_data, NULL, SSIZE_MAX,
|
|
&size)) != OK)
|
|
return r;
|
|
scn.sysctl_size = size;
|
|
}
|
|
/* FALLTHROUGH */
|
|
case CTLTYPE_STRUCT:
|
|
/*
|
|
* We do not set an upper size on the data size, since it would
|
|
* still be possible to create a large number of nodes, and
|
|
* this is a privileged operation ayway.
|
|
*/
|
|
if (scn.sysctl_size == 0 || scn.sysctl_size > SSIZE_MAX)
|
|
return EINVAL;
|
|
if (scn.sysctl_flags & CTLFLAG_IMMEDIATE)
|
|
return EINVAL;
|
|
break;
|
|
case CTLTYPE_NODE:
|
|
/*
|
|
* The zero size is an API requirement, but we also rely on the
|
|
* zero value internally, as the node has no static children.
|
|
*/
|
|
if (scn.sysctl_size != 0)
|
|
return EINVAL;
|
|
if (scn.sysctl_flags & (CTLFLAG_IMMEDIATE | CTLFLAG_OWNDATA))
|
|
return EINVAL;
|
|
if (scn.sysctl_csize != 0 || scn.sysctl_clen != 0 ||
|
|
scn.sysctl_child != NULL)
|
|
return EINVAL;
|
|
break;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
|
|
if (scn.sysctl_func != NULL || scn.sysctl_parent != NULL)
|
|
return EINVAL;
|
|
|
|
/* Verify that the given name is valid, and get its string length. */
|
|
namelen = mib_check_name(scn.sysctl_name, sizeof(scn.sysctl_name));
|
|
|
|
if (namelen == 0)
|
|
return EINVAL;
|
|
|
|
/*
|
|
* Find a free identifier, or check for ID collisions if a specific
|
|
* identifier was given. At the same time, scan for name collisions,
|
|
* and find the location at which to insert the new node in the list.
|
|
*/
|
|
r = mib_scan(parent, scn.sysctl_num, scn.sysctl_name, &id, &prevp,
|
|
&node);
|
|
|
|
if (r != OK) {
|
|
/*
|
|
* On collisions, if requested, copy out the existing node.
|
|
* This does not quite fit the general interaction model, as
|
|
* the service must now return a nonzero old length from a call
|
|
* that actually failed (in contrast to ENOMEM failures).
|
|
*/
|
|
if (r == EEXIST && oldp != NULL) {
|
|
len = mib_copyout_node(call, oldp, 0, id, node);
|
|
|
|
if (len > 0)
|
|
mib_setoldlen(call, len);
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* All checks so far have passed. "id" now contains the new node
|
|
* identifier, and "prevp" points to the pointer at which to insert the
|
|
* new node in its parent's linked list of dynamic nodes.
|
|
*
|
|
* We can now attempt to create and initialize a new dynamic node.
|
|
* Allocating nodes this way may cause heavy memory fragmentation over
|
|
* time, but we do not expect the tree to see heavy modification at run
|
|
* time, and the superuser has easier ways to get the MIB service in
|
|
* trouble. We note that even in low-memory conditions, the MIB
|
|
* service is always able to provide basic functionality.
|
|
*/
|
|
size = sizeof(*dynode) + namelen;
|
|
if (!(scn.sysctl_flags & CTLFLAG_IMMEDIATE))
|
|
size += scn.sysctl_size;
|
|
|
|
if ((dynode = malloc(size)) == NULL)
|
|
return EINVAL; /* do not return ENOMEM */
|
|
mib_objects++;
|
|
|
|
/* From here on, we have to free "dynode" before returning an error. */
|
|
r = OK;
|
|
|
|
memset(dynode, 0, sizeof(*dynode)); /* no need to zero all of "size" */
|
|
dynode->dynode_id = id;
|
|
strlcpy(dynode->dynode_name, scn.sysctl_name, namelen + 1);
|
|
|
|
node = &dynode->dynode_node;
|
|
node->node_flags = scn.sysctl_flags & ~SYSCTL_VERS_MASK;
|
|
if (SYSCTL_TYPE(scn.sysctl_flags) == CTLTYPE_NODE)
|
|
node->node_flags |= CTLFLAG_PARENT;
|
|
node->node_size = scn.sysctl_size;
|
|
node->node_parent = parent;
|
|
node->node_name = dynode->dynode_name;
|
|
|
|
/* Initialize the node value. */
|
|
if (scn.sysctl_flags & CTLFLAG_IMMEDIATE) {
|
|
switch (SYSCTL_TYPE(scn.sysctl_flags)) {
|
|
case CTLTYPE_BOOL:
|
|
/* Sanitize booleans. See the C99 _Bool comment. */
|
|
memcpy(&c, &scn.sysctl_bdata, sizeof(c));
|
|
node->node_bool = (bool)c;
|
|
break;
|
|
case CTLTYPE_INT:
|
|
node->node_int = scn.sysctl_idata;
|
|
break;
|
|
case CTLTYPE_QUAD:
|
|
node->node_quad = scn.sysctl_qdata;
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
} else if (SYSCTL_TYPE(scn.sysctl_flags) != CTLTYPE_NODE) {
|
|
node->node_data = dynode->dynode_name + namelen + 1;
|
|
|
|
/* Did the user supply initial data? If not, use zeroes. */
|
|
if (scn.sysctl_data != NULL) {
|
|
/*
|
|
* For strings, do not copy in more than needed. This
|
|
* is just a nice feature which allows initialization
|
|
* of large string buffers with short strings.
|
|
*/
|
|
if (SYSCTL_TYPE(scn.sysctl_flags) == CTLTYPE_STRING)
|
|
r = mib_copyin_str(newp,
|
|
(vir_bytes)scn.sysctl_data,
|
|
node->node_data, scn.sysctl_size, NULL);
|
|
else
|
|
r = mib_copyin_aux(newp,
|
|
(vir_bytes)scn.sysctl_data,
|
|
node->node_data, scn.sysctl_size);
|
|
} else
|
|
memset(node->node_data, 0, scn.sysctl_size);
|
|
|
|
/*
|
|
* Sanitize booleans. See the C99 _Bool comment elsewhere.
|
|
* In this case it is not as big of a deal, as we will not be
|
|
* accessing the boolean value directly ourselves.
|
|
*/
|
|
if (r == OK && SYSCTL_TYPE(scn.sysctl_flags) == CTLTYPE_BOOL) {
|
|
b = (bool)*(char *)node->node_data;
|
|
memcpy(node->node_data, &b, sizeof(b));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Even though it would be entirely possible to set a description right
|
|
* away as well, this does not seem to be supported on NetBSD at all.
|
|
*/
|
|
|
|
/* Deal with earlier failures now. */
|
|
if (r != OK) {
|
|
free(dynode);
|
|
mib_objects--;
|
|
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* At this point, actual creation can no longer fail. Add the node
|
|
* into the tree, and update versions and counters.
|
|
*/
|
|
mib_add(dynode, prevp);
|
|
|
|
/*
|
|
* Copy out the newly created node as resulting ("old") data. Do not
|
|
* undo the creation if this fails, though.
|
|
*/
|
|
return mib_copyout_node(call, oldp, 0, id, node);
|
|
}
|
|
|
|
/*
|
|
* Remove the given node from the tree. If 'prevp' is NULL, the node is a
|
|
* static node which should be zeroed out. If 'prevp' is not NULL, the node is
|
|
* a dynamic node which should be freed; 'prevp' will then point to the pointer
|
|
* to its dynode container. Also update versions and counters as appropriate.
|
|
* This function never fails.
|
|
*/
|
|
static void
|
|
mib_remove(struct mib_node * node, struct mib_dynode ** prevp)
|
|
{
|
|
struct mib_dynode *dynode;
|
|
struct mib_node *parent;
|
|
|
|
parent = node->node_parent;
|
|
assert(parent != NULL);
|
|
|
|
/* If the description was allocated, free it. */
|
|
if (node->node_flags & CTLFLAG_OWNDESC) {
|
|
free(__UNCONST(node->node_desc));
|
|
mib_objects--;
|
|
}
|
|
|
|
/*
|
|
* Static nodes only use static memory, and dynamic nodes have the data
|
|
* area embedded in the dynode object. In neither case is data memory
|
|
* allocated separately, and thus, it need never be freed separately.
|
|
* Therefore we *must not* check CTLFLAG_OWNDATA here.
|
|
*/
|
|
|
|
assert(parent->node_csize > 0);
|
|
assert(parent->node_clen > 0);
|
|
|
|
/*
|
|
* Dynamic nodes must be freed. Freeing the dynode object also frees
|
|
* the node name and any associated data. Static nodes are zeroed out,
|
|
* and the static memory they referenced will become inaccessible.
|
|
*/
|
|
if (prevp != NULL) {
|
|
dynode = *prevp;
|
|
*prevp = dynode->dynode_next;
|
|
|
|
assert(node == &dynode->dynode_node);
|
|
|
|
free(dynode);
|
|
mib_objects--;
|
|
|
|
parent->node_csize--;
|
|
} else
|
|
memset(node, 0, sizeof(*node));
|
|
|
|
parent->node_clen--;
|
|
|
|
mib_nodes--;
|
|
|
|
/* Bump the version of all nodes on the path to the destroyed node. */
|
|
mib_upgrade(parent);
|
|
}
|
|
|
|
/*
|
|
* Destroy a node.
|
|
*/
|
|
static ssize_t
|
|
mib_destroy(struct mib_call * call, struct mib_node * parent,
|
|
struct mib_oldp * oldp, struct mib_newp * newp)
|
|
{
|
|
struct mib_dynode **prevp;
|
|
struct mib_node *node;
|
|
struct sysctlnode scn;
|
|
ssize_t r;
|
|
|
|
/* This is a privileged operation. */
|
|
if (!mib_authed(call))
|
|
return EPERM;
|
|
|
|
/* The parent node must not be marked as read-only. */
|
|
if (!(parent->node_flags & CTLFLAG_READWRITE))
|
|
return EPERM;
|
|
|
|
/* The caller must specify which child node to destroy. */
|
|
if (newp == NULL)
|
|
return EINVAL;
|
|
|
|
if ((r = mib_copyin(newp, &scn, sizeof(scn))) != OK)
|
|
return r;
|
|
|
|
if (SYSCTL_VERS(scn.sysctl_flags) != SYSCTL_VERSION)
|
|
return EINVAL;
|
|
|
|
/* Locate the child node. */
|
|
if ((node = mib_find(parent, scn.sysctl_num, &prevp)) == NULL)
|
|
return ENOENT;
|
|
|
|
/* The node must not be marked as permanent. */
|
|
if (node->node_flags & CTLFLAG_PERMANENT)
|
|
return EPERM;
|
|
|
|
/* For node-type nodes, extra rules apply. */
|
|
if (SYSCTL_TYPE(node->node_flags) == CTLTYPE_NODE) {
|
|
/* The node must not be a mount point. */
|
|
if (node->node_flags & CTLFLAG_REMOTE)
|
|
return EBUSY;
|
|
|
|
/* The node must not have an associated function. */
|
|
if (!(node->node_flags & CTLFLAG_PARENT))
|
|
return EPERM;
|
|
|
|
/* The target node must itself not have child nodes. */
|
|
if (node->node_clen != 0)
|
|
return ENOTEMPTY;
|
|
}
|
|
|
|
/* If the user supplied a version, it must match the node version. */
|
|
if (scn.sysctl_ver != 0 && scn.sysctl_ver != node->node_ver)
|
|
return EINVAL; /* NetBSD inconsistently throws ENOENT here */
|
|
|
|
/* If the user supplied a name, it must match the node name. */
|
|
if (scn.sysctl_name[0] != '\0') {
|
|
if (memchr(scn.sysctl_name, '\0',
|
|
sizeof(scn.sysctl_name)) == NULL)
|
|
return EINVAL;
|
|
if (strcmp(scn.sysctl_name, node->node_name))
|
|
return EINVAL; /* also ENOENT on NetBSD */
|
|
}
|
|
|
|
/*
|
|
* Copy out the old node if requested, otherwise return the length
|
|
* anyway. The node will be destroyed even if this call fails,
|
|
* because that is how NetBSD behaves.
|
|
*/
|
|
r = mib_copyout_node(call, oldp, 0, scn.sysctl_num, node);
|
|
|
|
/*
|
|
* Remove the node from the tree. The procedure depends on whether the
|
|
* node is static (prevp == NULL) or dynamic (prevp != NULL). Also
|
|
* update versions and counters.
|
|
*/
|
|
mib_remove(node, prevp);
|
|
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* Copy out a node description to userland, using the exchange format for node
|
|
* descriptions (namely, a sysctldesc structure). Return the size of the
|
|
* object that is (or, if the description falls outside the requested data
|
|
* range, would be) copied out on success, or a negative error code on failure.
|
|
* The function may return 0 to indicate that nothing was copied out after all.
|
|
*/
|
|
static ssize_t
|
|
mib_copyout_desc(struct mib_call * call, struct mib_oldp * oldp, size_t off,
|
|
int id, const struct mib_node * node)
|
|
{
|
|
struct sysctldesc *scd;
|
|
size_t size;
|
|
int r;
|
|
|
|
/* Descriptions of private nodes are considered private too. */
|
|
if ((node->node_flags & CTLFLAG_PRIVATE) && !mib_authed(call))
|
|
return 0;
|
|
|
|
/* The description length includes the null terminator. */
|
|
if (node->node_desc != NULL)
|
|
size = strlen(node->node_desc) + 1;
|
|
else
|
|
size = 1;
|
|
|
|
assert(sizeof(*scd) + size <= sizeof(scratch));
|
|
|
|
scd = (struct sysctldesc *)scratch;
|
|
memset(scd, 0, sizeof(*scd));
|
|
scd->descr_num = id;
|
|
scd->descr_ver = node->node_ver;
|
|
scd->descr_len = size;
|
|
if (node->node_desc != NULL)
|
|
strlcpy(scd->descr_str, node->node_desc,
|
|
sizeof(scratch) - sizeof(*scd));
|
|
else
|
|
scd->descr_str[0] = '\0';
|
|
|
|
size += offsetof(struct sysctldesc, descr_str);
|
|
|
|
if ((r = mib_copyout(oldp, off, scratch, size)) < 0)
|
|
return r;
|
|
|
|
/*
|
|
* By aligning just the size, we may leave garbage between the entries
|
|
* copied out, which is fine because it is userland's own data.
|
|
*/
|
|
return roundup2(size, sizeof(int32_t));
|
|
}
|
|
|
|
/*
|
|
* Retrieve node descriptions in bulk, or retrieve or assign a particular
|
|
* node's description.
|
|
*/
|
|
static ssize_t
|
|
mib_describe(struct mib_call * call, struct mib_node * parent,
|
|
struct mib_oldp * oldp, struct mib_newp * newp)
|
|
{
|
|
struct sysctlnode scn;
|
|
struct mib_node *node;
|
|
struct mib_dynode *dynode;
|
|
size_t off;
|
|
int r, id;
|
|
|
|
/* If new data are given, they identify a particular target node. */
|
|
if (newp != NULL) {
|
|
if ((r = mib_copyin(newp, &scn, sizeof(scn))) != OK)
|
|
return r;
|
|
|
|
if (SYSCTL_VERS(scn.sysctl_flags) != SYSCTL_VERSION)
|
|
return EINVAL;
|
|
|
|
/* Locate the child node. */
|
|
if ((node = mib_find(parent, scn.sysctl_num, NULL)) == NULL)
|
|
return ENOENT;
|
|
|
|
/* Descriptions of private nodes are considered private too. */
|
|
if ((node->node_flags & CTLFLAG_PRIVATE) && !mib_authed(call))
|
|
return EPERM;
|
|
|
|
/*
|
|
* If a description pointer was given, this is a request to
|
|
* set the node's description.
|
|
*/
|
|
if (scn.sysctl_desc != NULL) {
|
|
/* Such a request requires superuser privileges. */
|
|
if (!mib_authed(call))
|
|
return EPERM;
|
|
|
|
/*
|
|
* The node must not be a mount point. Arguably this
|
|
* check is not necessary, since we use the description
|
|
* of the preexisting underlying node anyway.
|
|
*/
|
|
if (SYSCTL_TYPE(node->node_flags) == CTLTYPE_NODE &&
|
|
(node->node_flags & CTLFLAG_REMOTE))
|
|
return EBUSY;
|
|
|
|
/* The node must not already have a description. */
|
|
if (node->node_desc != NULL)
|
|
return EPERM;
|
|
|
|
/* The node must not be marked as permanent. */
|
|
if (node->node_flags & CTLFLAG_PERMANENT)
|
|
return EPERM;
|
|
|
|
/*
|
|
* If the user supplied a version, it must match.
|
|
* NetBSD performs this check only when setting
|
|
* descriptions, and thus, so do we..
|
|
*/
|
|
if (scn.sysctl_ver != 0 &&
|
|
scn.sysctl_ver != node->node_ver)
|
|
return EINVAL;
|
|
|
|
/*
|
|
* Copy in the description to the scratch buffer.
|
|
* The length of the description must be reasonable.
|
|
*/
|
|
if ((r = mib_copyin_str(newp,
|
|
(vir_bytes)scn.sysctl_desc, scratch, MAXDESCLEN,
|
|
NULL)) != OK)
|
|
return r;
|
|
|
|
/* Allocate memory and store the description. */
|
|
if ((node->node_desc = strdup(scratch)) == NULL) {
|
|
printf("MIB: out of memory!\n");
|
|
|
|
return EINVAL; /* do not return ENOMEM */
|
|
}
|
|
mib_objects++;
|
|
|
|
/* The description must now be freed with the node. */
|
|
node->node_flags |= CTLFLAG_OWNDESC;
|
|
}
|
|
|
|
/*
|
|
* Either way, copy out the requested node's description, which
|
|
* should indeed be the new description if one was just set.
|
|
* Note that we have already performed the permission check
|
|
* that could make this call return zero, so here it will not.
|
|
*/
|
|
return mib_copyout_desc(call, oldp, 0, scn.sysctl_num, node);
|
|
}
|
|
|
|
/* See also the considerations laid out in mib_query(). */
|
|
off = 0;
|
|
|
|
/* First describe the static nodes. */
|
|
for (id = 0; IS_STATIC_ID(parent, id); id++) {
|
|
node = &parent->node_scptr[id];
|
|
|
|
if (node->node_flags == 0)
|
|
continue;
|
|
|
|
if ((r = mib_copyout_desc(call, oldp, off, id, node)) < 0)
|
|
return r;
|
|
off += r;
|
|
}
|
|
|
|
/* Then describe the dynamic nodes. */
|
|
for (dynode = parent->node_dcptr; dynode != NULL;
|
|
dynode = dynode->dynode_next) {
|
|
node = &dynode->dynode_node;
|
|
|
|
if ((r = mib_copyout_desc(call, oldp, off, dynode->dynode_id,
|
|
node)) < 0)
|
|
return r;
|
|
off += r;
|
|
}
|
|
|
|
return off;
|
|
}
|
|
|
|
/*
|
|
* Return a pointer to the data associated with the given node, or NULL if the
|
|
* node has no associated data. Actual calls to this function should never
|
|
* result in NULL - as long as the proper rules are followed elsewhere.
|
|
*/
|
|
static void *
|
|
mib_getptr(struct mib_node * node)
|
|
{
|
|
|
|
switch (SYSCTL_TYPE(node->node_flags)) {
|
|
case CTLTYPE_BOOL:
|
|
if (node->node_flags & CTLFLAG_IMMEDIATE)
|
|
return &node->node_bool;
|
|
break;
|
|
case CTLTYPE_INT:
|
|
if (node->node_flags & CTLFLAG_IMMEDIATE)
|
|
return &node->node_int;
|
|
break;
|
|
case CTLTYPE_QUAD:
|
|
if (node->node_flags & CTLFLAG_IMMEDIATE)
|
|
return &node->node_quad;
|
|
break;
|
|
case CTLTYPE_STRING:
|
|
case CTLTYPE_STRUCT:
|
|
if (node->node_flags & CTLFLAG_IMMEDIATE)
|
|
return NULL;
|
|
break;
|
|
default:
|
|
return NULL;
|
|
}
|
|
|
|
return node->node_data;
|
|
}
|
|
|
|
/*
|
|
* Read current (old) data from a regular data node, if requested. Return the
|
|
* old data length.
|
|
*/
|
|
static ssize_t
|
|
mib_read(struct mib_node * node, struct mib_oldp * oldp)
|
|
{
|
|
void *ptr;
|
|
size_t oldlen;
|
|
int r;
|
|
|
|
if ((ptr = mib_getptr(node)) == NULL)
|
|
return EINVAL;
|
|
|
|
if (SYSCTL_TYPE(node->node_flags) == CTLTYPE_STRING)
|
|
oldlen = strlen(node->node_data) + 1;
|
|
else
|
|
oldlen = node->node_size;
|
|
|
|
if (oldlen > SSIZE_MAX)
|
|
return EINVAL;
|
|
|
|
/* Copy out the current data, if requested at all. */
|
|
if (oldp != NULL && (r = mib_copyout(oldp, 0, ptr, oldlen)) < 0)
|
|
return r;
|
|
|
|
/* Return the current length in any case. */
|
|
return (ssize_t)oldlen;
|
|
}
|
|
|
|
/*
|
|
* Write new data into a regular data node, if requested.
|
|
*/
|
|
static int
|
|
mib_write(struct mib_call * call, struct mib_node * node,
|
|
struct mib_newp * newp, mib_verify_ptr verify)
|
|
{
|
|
bool b[(sizeof(bool) == sizeof(char)) ? 1 : -1]; /* explained below */
|
|
char *src, *dst;
|
|
size_t newlen;
|
|
int r;
|
|
|
|
if (newp == NULL)
|
|
return OK; /* nothing to do */
|
|
|
|
/*
|
|
* When setting a new value, we cannot risk doing an in-place update:
|
|
* the copy from userland may fail halfway through, in which case an
|
|
* in-place update could leave the node value in a corrupted state.
|
|
* Thus, we must first fetch any new data into a temporary buffer.
|
|
*
|
|
* Given that we use intermediate data storage, we could support value
|
|
* swapping, where the user provides the same buffer for new and old
|
|
* data. We choose not to: NetBSD does not support it, it would make
|
|
* trace(1)'s job a lot harder, and it would convolute the code here.
|
|
*/
|
|
newlen = mib_getnewlen(newp);
|
|
|
|
if ((dst = mib_getptr(node)) == NULL)
|
|
return EINVAL;
|
|
|
|
switch (SYSCTL_TYPE(node->node_flags)) {
|
|
case CTLTYPE_BOOL:
|
|
case CTLTYPE_INT:
|
|
case CTLTYPE_QUAD:
|
|
case CTLTYPE_STRUCT:
|
|
/* Non-string types must have an exact size match. */
|
|
if (newlen != node->node_size)
|
|
return EINVAL;
|
|
break;
|
|
case CTLTYPE_STRING:
|
|
/*
|
|
* Strings must not exceed their buffer size. There is a
|
|
* second check further below, because we allow userland to
|
|
* give us an unterminated string. In that case we terminate
|
|
* it ourselves, but then the null terminator must fit as well.
|
|
*/
|
|
if (newlen > node->node_size)
|
|
return EINVAL;
|
|
break;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
|
|
/*
|
|
* If we cannot fit the data in the scratch buffer, then allocate a
|
|
* temporary buffer. We add one extra byte so that we can add a null
|
|
* terminator at the end of strings in case userland did not supply
|
|
* one. Either way, we must free the temporary buffer later!
|
|
*
|
|
* The alternative is to ensure that the given memory is accessible
|
|
* before starting the copy, but that would break if we ever add kernel
|
|
* threads or anything that allows asynchronous memory unmapping, etc.
|
|
*/
|
|
if (newlen + 1 > sizeof(scratch)) {
|
|
/*
|
|
* In practice, the temporary buffer is at least an entire
|
|
* memory page, which is reasonable by any standard. As a
|
|
* result, we can get away with refusing to perform dynamic
|
|
* allocation for unprivileged users. This limits the impact
|
|
* that unprivileged users can have on our memory space.
|
|
*/
|
|
if (!mib_authed(call))
|
|
return EPERM;
|
|
|
|
/*
|
|
* Do not return ENOMEM on allocation failure, because ENOMEM
|
|
* implies that a valid old length was returned.
|
|
*/
|
|
if ((src = malloc(newlen + 1)) == NULL) {
|
|
printf("MIB: out of memory!\n");
|
|
|
|
return EINVAL;
|
|
}
|
|
mib_objects++;
|
|
} else
|
|
src = scratch;
|
|
|
|
/* Copy in the data. Note that newlen may be zero. */
|
|
r = mib_copyin(newp, src, newlen);
|
|
|
|
if (r == OK && verify != NULL && !verify(call, node, src, newlen))
|
|
r = EINVAL;
|
|
|
|
if (r == OK) {
|
|
/* Check and, if acceptable, store the new value. */
|
|
switch (SYSCTL_TYPE(node->node_flags)) {
|
|
case CTLTYPE_BOOL:
|
|
/*
|
|
* Due to the nature of the C99 _Bool type, we can not
|
|
* test directly whether the given boolean value is a
|
|
* value that is not "true" and not "false". In the
|
|
* worst case, another value could invoke undefined
|
|
* behavior. We try our best to sanitize the value
|
|
* without looking at it directly, which unfortunately
|
|
* requires us to test for the size of the bool type.
|
|
* We do that at compile time, hence the 'b' "array".
|
|
* Any size other than one byte is an ABI violation.
|
|
*/
|
|
b[0] = (bool)src[0];
|
|
memcpy(dst, &b[0], sizeof(b[0]));
|
|
break;
|
|
case CTLTYPE_INT:
|
|
case CTLTYPE_QUAD:
|
|
case CTLTYPE_STRUCT:
|
|
memcpy(dst, src, node->node_size);
|
|
break;
|
|
case CTLTYPE_STRING:
|
|
if (newlen == node->node_size &&
|
|
src[newlen - 1] != '\0') {
|
|
/* Our null terminator does not fit! */
|
|
r = EINVAL;
|
|
break;
|
|
}
|
|
/*
|
|
* We do not mind null characters in the middle. In
|
|
* general, the buffer may contain garbage after the
|
|
* first null terminator, but such garbage will never
|
|
* end up being copied out.
|
|
*/
|
|
src[newlen] = '\0';
|
|
strlcpy(dst, src, node->node_size);
|
|
break;
|
|
default:
|
|
r = EINVAL;
|
|
}
|
|
}
|
|
|
|
if (src != scratch) {
|
|
free(src);
|
|
mib_objects--;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* Read and/or write the value of a regular data node. A regular data node is
|
|
* a leaf node. Typically, a leaf node has no associated function, in which
|
|
* case this function will be used instead. In addition, this function may be
|
|
* used from handler functions as part of their functionality.
|
|
*/
|
|
ssize_t
|
|
mib_readwrite(struct mib_call * call, struct mib_node * node,
|
|
struct mib_oldp * oldp, struct mib_newp * newp, mib_verify_ptr verify)
|
|
{
|
|
ssize_t len;
|
|
int r;
|
|
|
|
/* Copy out old data, if requested. Always get the old data length. */
|
|
if ((r = len = mib_read(node, oldp)) < 0)
|
|
return r;
|
|
|
|
/* Copy in new data, if requested. */
|
|
if ((r = mib_write(call, node, newp, verify)) != OK)
|
|
return r;
|
|
|
|
/* Return the old data length. */
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* Dispatch a sysctl call, by looking up the target node by its MIB name and
|
|
* taking the appropriate action on the resulting node, if found. Return the
|
|
* old data length on success, or a negative error code on failure.
|
|
*/
|
|
ssize_t
|
|
mib_dispatch(struct mib_call * call, struct mib_oldp * oldp,
|
|
struct mib_newp * newp)
|
|
{
|
|
struct mib_node *parent, *node;
|
|
ssize_t r;
|
|
int id, is_leaf, can_restart, has_verify, has_func;
|
|
|
|
assert(call->call_namelen <= CTL_MAXNAME);
|
|
|
|
/*
|
|
* Resolve the name by descending into the node tree, level by level,
|
|
* starting at the MIB root.
|
|
*/
|
|
for (parent = &mib_root; call->call_namelen > 0; parent = node) {
|
|
id = call->call_name[0];
|
|
call->call_name++;
|
|
call->call_namelen--;
|
|
|
|
assert(SYSCTL_TYPE(parent->node_flags) == CTLTYPE_NODE);
|
|
assert(parent->node_flags & CTLFLAG_PARENT);
|
|
|
|
/*
|
|
* Check for meta-identifiers. Regular identifiers are never
|
|
* negative, although node handler functions may take subpaths
|
|
* with negative identifiers that are not meta-identifiers
|
|
* (e.g., see KERN_PROC2).
|
|
*/
|
|
if (id < 0) {
|
|
/*
|
|
* A meta-identifier must always be the last name
|
|
* component.
|
|
*/
|
|
if (call->call_namelen > 0)
|
|
return EINVAL;
|
|
|
|
switch (id) {
|
|
case CTL_QUERY:
|
|
return mib_query(call, parent, oldp, newp);
|
|
case CTL_CREATE:
|
|
return mib_create(call, parent, oldp, newp);
|
|
case CTL_DESTROY:
|
|
return mib_destroy(call, parent, oldp, newp);
|
|
case CTL_DESCRIBE:
|
|
return mib_describe(call, parent, oldp, newp);
|
|
case CTL_CREATESYM:
|
|
case CTL_MMAP:
|
|
default:
|
|
return EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
/* Locate the child node. */
|
|
if ((node = mib_find(parent, id, NULL /*prevp*/)) == NULL)
|
|
return ENOENT;
|
|
|
|
/* Check if access is permitted at this level. */
|
|
if ((node->node_flags & CTLFLAG_PRIVATE) && !mib_authed(call))
|
|
return EPERM;
|
|
|
|
/*
|
|
* Start by checking if the node is a remote node. If so, let
|
|
* a remote service handle the remainder of this request.
|
|
* However, as part of attempting the remote call, we may
|
|
* discover that the remote service has died or that it is
|
|
* unmounting the subtree. If the node was not a temporary
|
|
* mountpoint, we should (and do) continue with the request
|
|
* locally - if it was, it will already be deallocated and we
|
|
* must be very careful not to access 'node' again!
|
|
*/
|
|
is_leaf = (SYSCTL_TYPE(node->node_flags) != CTLTYPE_NODE);
|
|
|
|
if (!is_leaf && (node->node_flags & CTLFLAG_REMOTE)) {
|
|
/* Determine this before 'node' may disappear.. */
|
|
can_restart = (node->node_flags & CTLFLAG_PARENT);
|
|
|
|
r = mib_remote_call(call, node, oldp, newp);
|
|
|
|
if (r != ERESTART || !can_restart)
|
|
return (r != ERESTART) ? r : ENOENT;
|
|
|
|
/* Service died, subtree is unmounted, keep going. */
|
|
assert(SYSCTL_TYPE(node->node_flags) == CTLTYPE_NODE);
|
|
assert(!(node->node_flags & CTLFLAG_REMOTE));
|
|
}
|
|
|
|
/*
|
|
* Is this a leaf node, and/or is this node handled by a
|
|
* function? If either is true, resolution ends at this level.
|
|
* In order to save a few bytes of memory per node, we use
|
|
* different ways to determine whether there is a function
|
|
* depending on whether the node is a leaf or not.
|
|
*/
|
|
if (is_leaf) {
|
|
has_verify = (node->node_flags & CTLFLAG_VERIFY);
|
|
has_func = (!has_verify && node->node_func != NULL);
|
|
} else {
|
|
has_verify = FALSE;
|
|
has_func = !(node->node_flags & CTLFLAG_PARENT);
|
|
}
|
|
|
|
/*
|
|
* The name may be longer only if the node is not a leaf. That
|
|
* also applies to leaves with functions, so check this first.
|
|
*/
|
|
if (is_leaf && call->call_namelen > 0)
|
|
return ENOTDIR;
|
|
|
|
/*
|
|
* If resolution indeed ends here, and the user supplied new
|
|
* data, check if writing is allowed. For functions, it is
|
|
* arguable whether we should do this check here already.
|
|
* However, for now, this approach covers all our use cases.
|
|
*/
|
|
if ((is_leaf || has_func) && newp != NULL) {
|
|
if (!(node->node_flags & CTLFLAG_READWRITE))
|
|
return EPERM;
|
|
|
|
/*
|
|
* Unless nonprivileged users may write to this node,
|
|
* ensure that the user has superuser privileges. The
|
|
* ANYWRITE flag does not override the READWRITE flag.
|
|
*/
|
|
if (!(node->node_flags & CTLFLAG_ANYWRITE) &&
|
|
!mib_authed(call))
|
|
return EPERM;
|
|
}
|
|
|
|
/* If this node has a handler function, let it do the work. */
|
|
if (has_func)
|
|
return node->node_func(call, node, oldp, newp);
|
|
|
|
/* For regular data leaf nodes, handle generic access. */
|
|
if (is_leaf)
|
|
return mib_readwrite(call, node, oldp, newp,
|
|
has_verify ? node->node_verify : NULL);
|
|
|
|
/* No function and not a leaf? Descend further. */
|
|
}
|
|
|
|
/* If we get here, the name refers to a node array. */
|
|
return EISDIR;
|
|
}
|
|
|
|
/*
|
|
* Recursively initialize the static tree at initialization time.
|
|
*/
|
|
static void
|
|
mib_tree_recurse(struct mib_node * parent)
|
|
{
|
|
struct mib_node *node;
|
|
int id;
|
|
|
|
assert(SYSCTL_TYPE(parent->node_flags) == CTLTYPE_NODE);
|
|
assert(parent->node_flags & CTLFLAG_PARENT);
|
|
|
|
/*
|
|
* Later on, node_csize and node_clen will also include dynamically
|
|
* created nodes. This means that we cannot use node_csize to iterate
|
|
* over the static nodes.
|
|
*/
|
|
parent->node_csize = parent->node_size;
|
|
|
|
node = parent->node_scptr;
|
|
|
|
for (id = 0; IS_STATIC_ID(parent, id); id++, node++) {
|
|
if (node->node_flags == 0)
|
|
continue;
|
|
|
|
mib_nodes++;
|
|
|
|
parent->node_clen++;
|
|
|
|
node->node_ver = parent->node_ver;
|
|
node->node_parent = parent;
|
|
|
|
/* Recursively apply this function to all node children. */
|
|
if (SYSCTL_TYPE(node->node_flags) == CTLTYPE_NODE &&
|
|
(node->node_flags & CTLFLAG_PARENT))
|
|
mib_tree_recurse(node);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Go through the entire static tree, recursively, initializing some values
|
|
* that could not be assigned at compile time.
|
|
*/
|
|
void
|
|
mib_tree_init(void)
|
|
{
|
|
|
|
/* Initialize some variables. */
|
|
mib_nodes = 1; /* the root node itself */
|
|
mib_objects = 0;
|
|
|
|
/*
|
|
* The entire tree starts with the same, nonzero node version.
|
|
* The root node is the only node without a parent.
|
|
*/
|
|
mib_root.node_ver = 1;
|
|
mib_root.node_parent = NULL;
|
|
|
|
/* Recursively initialize the static tree. */
|
|
mib_tree_recurse(&mib_root);
|
|
}
|
|
|
|
/*
|
|
* Process a subtree mount request from a remote service. Return OK on
|
|
* success, with a pointer to the resulting static-node structure stored in
|
|
* 'nodep'. Return a negative error code on failure.
|
|
*/
|
|
int
|
|
mib_mount(const int * mib, unsigned int miblen, unsigned int eid, uint32_t rid,
|
|
uint32_t flags, unsigned int csize, unsigned int clen,
|
|
struct mib_node ** nodep)
|
|
{
|
|
struct mib_dynode *dynode, **prevp;
|
|
struct mib_node *parent, *node;
|
|
char name[SYSCTL_NAMELEN], *desc;
|
|
size_t size, namelen, desclen;
|
|
unsigned int n;
|
|
int r, id;
|
|
|
|
/*
|
|
* Perform initial verification of the given parameters. Even stricter
|
|
* checks may be performed later.
|
|
*/
|
|
/*
|
|
* By policy, we forbid mounting top-level nodes. This is in effect
|
|
* also the only security-like restriction: a service should not be
|
|
* able to just take over, say, the entire "kern" subtree. There is
|
|
* currently little in the way of a service taking over an important
|
|
* set of second-level nodes, though.
|
|
*
|
|
* TODO: allow mounting of predefined mount points only, for example by
|
|
* having an internal node flag that permits mounting the subtree or
|
|
* any node in it. As an even better alternative, allow this to be
|
|
* controlled through a policy specification; unfortunately, this would
|
|
* also add a substantial amount of infrastructure.
|
|
*/
|
|
if (miblen < 2) {
|
|
MIB_DEBUG_MOUNT(("MIB: mounting failed, path too short\n"));
|
|
|
|
return EPERM;
|
|
}
|
|
|
|
/*
|
|
* The flags field is highly restricted right now. Only a few flags
|
|
* may be given at all, and then when using an existing node as mount
|
|
* point, the flag must exactly match the existing node's flags.
|
|
*/
|
|
if (SYSCTL_VERS(flags) != SYSCTL_VERSION ||
|
|
SYSCTL_TYPE(flags) != CTLTYPE_NODE ||
|
|
(SYSCTL_FLAGS(flags) & ~(CTLFLAG_READONLY | CTLFLAG_READWRITE |
|
|
CTLFLAG_PERMANENT | CTLFLAG_HIDDEN)) != 0) {
|
|
MIB_DEBUG_MOUNT(("MIB: mounting failed, invalid flags %"PRIx32
|
|
"\n", flags));
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
if (csize > (1U << MIB_RC_BITS) || clen > csize) {
|
|
MIB_DEBUG_MOUNT(("MIB: mounting failed, invalid child size or "
|
|
"length (%u, %u)\n", csize, clen));
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Look up the parent node of the mount point. This parent node must
|
|
* exist - we don't want to create more than one temporary node in any
|
|
* case. All the nodes leading up to and including the parent node
|
|
* must be real, local, non-private, node-type nodes. The path may not
|
|
* be private, because that would allow an unprivileged service to
|
|
* intercept writes to privileged nodes--currently a total nonissue in
|
|
* practice, but still. Note that the service may itself restrict
|
|
* access to nodes in its own mounted subtree in any way it wishes.
|
|
*/
|
|
parent = &mib_root;
|
|
|
|
for (n = 0; n < miblen - 1; n++) {
|
|
/* Meta-identifiers are obviously not allowed in the path. */
|
|
if ((id = mib[n]) < 0) {
|
|
MIB_DEBUG_MOUNT(("MIB: mounting failed, meta-ID in "
|
|
"path\n"));
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
/* Locate the child node. */
|
|
if ((node = mib_find(parent, id, NULL /*prevp*/)) == NULL) {
|
|
MIB_DEBUG_MOUNT(("MIB: mounting failed, path not "
|
|
"found\n"));
|
|
|
|
return ENOENT;
|
|
}
|
|
|
|
/* Make sure it is a regular node-type node. */
|
|
if (SYSCTL_TYPE(node->node_flags) != CTLTYPE_NODE ||
|
|
!(node->node_flags & CTLFLAG_PARENT) ||
|
|
(node->node_flags & (CTLFLAG_REMOTE | CTLFLAG_PRIVATE))) {
|
|
MIB_DEBUG_MOUNT(("MIB: mounting failed, unacceptable "
|
|
"node on path\n"));
|
|
|
|
return EPERM;
|
|
}
|
|
|
|
parent = node;
|
|
}
|
|
|
|
/* Now see if the mount point itself exists. */
|
|
if ((id = mib[miblen - 1]) < 0) {
|
|
MIB_DEBUG_MOUNT(("MIB: mounting failed, meta-ID in path\n"));
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
/*
|
|
* If the target node exists and passes all tests, it will simply be
|
|
* converted to a mount point. If the target node does not exist, we
|
|
* have to allocate a temporary node as mount point.
|
|
*/
|
|
if ((node = mib_find(parent, id, NULL /*prevp*/)) != NULL) {
|
|
/*
|
|
* We are about to mount on an existing node. As stated above,
|
|
* the node flags must match the given flags exactly.
|
|
*/
|
|
if (SYSCTL_TYPE(node->node_flags) != CTLTYPE_NODE ||
|
|
SYSCTL_FLAGS(node->node_flags) !=
|
|
(SYSCTL_FLAGS(flags) | CTLFLAG_PARENT)) {
|
|
MIB_DEBUG_MOUNT(("MIB: mounting failed, target node "
|
|
"mismatch (%"PRIx32", %"PRIx32")\n",
|
|
node->node_flags, flags));
|
|
|
|
return EPERM;
|
|
}
|
|
|
|
/*
|
|
* If the node has dynamically added children, we will not be
|
|
* able to restore the node to its old state when unmounting.
|
|
*/
|
|
if (node->node_size != node->node_csize) {
|
|
MIB_DEBUG_MOUNT(("MIB: mounting failed, node has "
|
|
"dynamic children\n"));
|
|
|
|
return EBUSY;
|
|
}
|
|
|
|
mib_upgrade(node);
|
|
} else {
|
|
/*
|
|
* We are going to create a temporary mount point. Much of the
|
|
* procedure that follows is a rather selective extract from
|
|
* mib_create(). Start with a check for the impossible.
|
|
*/
|
|
if (parent->node_csize == INT_MAX) {
|
|
MIB_DEBUG_MOUNT(("MIB: mounting failed, parent node "
|
|
"full\n"));
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
/*
|
|
* In order to create the new node, we also need the node's
|
|
* name and description; those did not fit in the request
|
|
* message. Ask the caller to copy these strings to us.
|
|
*/
|
|
name[0] = '\0';
|
|
scratch[0] = '\0';
|
|
|
|
if ((r = mib_remote_info(eid, rid, name, sizeof(name), scratch,
|
|
MAXDESCLEN)) != OK) {
|
|
MIB_DEBUG_MOUNT(("MIB: mounting failed, node info "
|
|
"request yielded %d\n", r));
|
|
|
|
return r;
|
|
}
|
|
|
|
/* Make sure the name is valid. */
|
|
if ((namelen = mib_check_name(name, sizeof(name))) == 0) {
|
|
printf("MIB: mounting failed, bad name\n");
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
/* Just forcefully terminate the description. */
|
|
scratch[MAXDESCLEN - 1] = '\0';
|
|
desclen = strlen(scratch);
|
|
|
|
/*
|
|
* We know the identifier is not in use yet; make sure that the
|
|
* name is not, either. As a side effect, find out where the
|
|
* new node should be inserted upon success.
|
|
*/
|
|
if (mib_scan(parent, id, name, &id /*unused*/, &prevp,
|
|
&node /*unused*/) != OK) {
|
|
MIB_DEBUG_MOUNT(("MIB: mounting failed, name "
|
|
"conflict\n"));
|
|
|
|
return EEXIST;
|
|
}
|
|
|
|
/*
|
|
* Allocate a dynamic node. Unlike for user-created dynamic
|
|
* nodes, temporary mount points also include the description
|
|
* in the dynode object.
|
|
*/
|
|
size = sizeof(*dynode) + namelen + desclen + 1;
|
|
|
|
if ((dynode = malloc(size)) == NULL) {
|
|
printf("MIB: out of memory!\n");
|
|
|
|
return ENOMEM;
|
|
}
|
|
mib_objects++;
|
|
|
|
/* Initialize the dynamic node. */
|
|
memset(dynode, 0, sizeof(*dynode));
|
|
dynode->dynode_id = id;
|
|
strlcpy(dynode->dynode_name, name, namelen + 1);
|
|
desc = &dynode->dynode_name[namelen + 1];
|
|
strlcpy(desc, scratch, desclen + 1);
|
|
|
|
node = &dynode->dynode_node;
|
|
node->node_flags = flags & ~SYSCTL_VERS_MASK;
|
|
node->node_size = 0;
|
|
node->node_parent = parent;
|
|
node->node_name = dynode->dynode_name;
|
|
node->node_desc = desc;
|
|
|
|
/*
|
|
* Add the new dynamic node into the tree, and adjust versions
|
|
* and counters.
|
|
*/
|
|
mib_add(dynode, prevp);
|
|
}
|
|
|
|
/* Success! Perform the actual mount, and return the target node. */
|
|
node->node_flags |= CTLFLAG_REMOTE;
|
|
node->node_eid = eid;
|
|
node->node_rcsize = csize;
|
|
node->node_rclen = clen;
|
|
node->node_rid = rid;
|
|
|
|
mib_remotes++;
|
|
|
|
*nodep = node;
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* Unmount the remote subtree identified by the given node. Release the mount
|
|
* point by reversing the action performed while mounting. Also bump the
|
|
* version numbers on the path, so that userland knows that it is to expect a
|
|
* change of contents in the subtree. This function always succeeds, and may
|
|
* deallocate the given node.
|
|
*/
|
|
void
|
|
mib_unmount(struct mib_node * node)
|
|
{
|
|
struct mib_dynode **prevp;
|
|
struct mib_node *child;
|
|
int id;
|
|
|
|
assert(SYSCTL_TYPE(node->node_flags) == CTLTYPE_NODE);
|
|
assert(node->node_flags & CTLFLAG_REMOTE);
|
|
|
|
/*
|
|
* Given that the node has the CTLFLAG_REMOTE flag set, we can now tell
|
|
* whether the remote subtree obscured a preexisting node or we created
|
|
* a temporary mount point, by checking its CTLFLAG_PARENT flag.
|
|
*/
|
|
if (node->node_flags & CTLFLAG_PARENT) {
|
|
/*
|
|
* Return the node to its former pre-mount state. Restore the
|
|
* original node_clen field by recomputing it.
|
|
*/
|
|
node->node_flags &= ~CTLFLAG_REMOTE;
|
|
node->node_csize = node->node_size;
|
|
node->node_clen = 0;
|
|
|
|
for (id = 0; IS_STATIC_ID(node, id); id++) {
|
|
child = &node->node_scptr[id];
|
|
|
|
if (child->node_flags != 0)
|
|
node->node_clen++;
|
|
}
|
|
|
|
node->node_dcptr = NULL;
|
|
|
|
/* Increase version numbers on the path to the node. */
|
|
mib_upgrade(node);
|
|
} else {
|
|
/*
|
|
* We know that we dynamically allocated this node; find its
|
|
* parent's pointer to it.
|
|
*/
|
|
for (prevp = &node->node_parent->node_dcptr; *prevp != NULL;
|
|
prevp = &(*prevp)->dynode_next) {
|
|
if (&(*prevp)->dynode_node == node)
|
|
break;
|
|
}
|
|
assert(*prevp != NULL);
|
|
|
|
/* Free the node, and adjust counts and versions. */
|
|
mib_remove(node, prevp);
|
|
}
|
|
|
|
assert(mib_remotes > 0);
|
|
mib_remotes--;
|
|
}
|