David van Moolenbroek 0f03189a6a RMIB: add indirection support for sparse subtrees
Normally, each RMIB subtree consists of an array of nodes, indexed
by node identifier.  In a sparsely filled subtree, most of the array
is empty and just wasting memory.  In that case, it may be beneficial
to have a level of indirection, with an intermediate array containing
pairs of node IDs and pointers to the actual nodes.  This patch adds
support for such indirection.

For the use cases that inspired this patch, net.inet and net.inet6,
the indirection shaves off a little under 16KB of memory from the
TCP/IP service.

Change-Id: Ic68ca3fee1a0f2032f77eef6df42728f9b9400e8
2017-04-30 13:16:00 +00:00

1090 lines
30 KiB
C

/* Service support for remote MIB subtrees - by D.C. van Moolenbroek */
/*
* In effect, this is a lightweight version of the MIB service's main and tree
* code. Some parts of the code have even been copied almost as is, even
* though the copy here operates on slightly different data structures in order
* to keep the implementation more lightweight. For clarification on many
* aspects of the source code here, see the source code of the MIB service.
* One unique feature here is support for sparse nodes, which is needed for
* net.inet/inet6 as those are using subtrees with protocol-based identifiers.
*
* There is no way for this module to get to know about MIB service deaths
* without possibly interfering with the main code of the service this module
* is a part of. As a result, re-registration of mount points after a MIB
* service restart is not automatic. Instead, the main service code should
* provide detection of MIB service restarts, and call rmib_reregister() after
* such a restart in order to remount any previously mounted subtrees.
*/
#include <minix/drivers.h>
#include <minix/sysctl.h>
#include <minix/rmib.h>
/* Structures for outgoing and incoming data, deliberately distinctly named. */
struct rmib_oldp {
cp_grant_id_t oldp_grant;
size_t oldp_len;
};
struct rmib_newp {
cp_grant_id_t newp_grant;
size_t newp_len;
};
/*
* The maximum field size, in bytes, for which updates (i.e., writes) to the
* field do not require dynamic memory allocation. By policy, non-root users
* may not update fields exceeding this size at all. For strings, this size
* includes an extra byte for adding a null terminator if missing. As the name
* indicates, a buffer of this size is placed on the stack.
*/
#define RMIB_STACKBUF 257
/*
* The maximum number of subtrees that this service can mount. This value can
* be increased without any problems, but it is already quite high in practice.
*/
#define RMIB_MAX_SUBTREES 16
/*
* The array of subtree root nodes. Each root node's array index is the root
* identifier used in communication with the MIB service.
*/
static struct {
struct rmib_node *rno_node;
unsigned int rno_namelen;
int rno_name[CTL_SHORTNAME];
} rnodes[RMIB_MAX_SUBTREES] = { { NULL, 0, { 0 } } };
/*
* Return TRUE or FALSE indicating whether the given offset is within the range
* of data that is to be copied out. This call can be used to test whether
* certain bits of data need to be prepared for copying at all.
*/
int
rmib_inrange(struct rmib_oldp * oldp, size_t off)
{
if (oldp == NULL)
return FALSE;
return (off < oldp->oldp_len);
}
/*
* Return the total length of the requested data. This should not be used
* directly except in highly unusual cases, such as particular node requests
* where the request semantics blatantly violate overall sysctl(2) semantics.
*/
size_t
rmib_getoldlen(struct rmib_oldp * oldp)
{
if (oldp == NULL)
return 0;
return oldp->oldp_len;
}
/*
* Copy out (partial) data to the user. The copy is automatically limited to
* the range of data requested by the user. Return the requested length on
* success (for the caller's convenience) or an error code on failure.
*/
ssize_t
rmib_copyout(struct rmib_oldp * __restrict oldp, size_t off,
const void * __restrict buf, size_t size)
{
size_t len;
int r;
len = size;
assert(len <= SSIZE_MAX);
if (oldp == NULL || off >= oldp->oldp_len)
return size; /* nothing to do */
if (len > oldp->oldp_len - off)
len = oldp->oldp_len - off;
if ((r = sys_safecopyto(MIB_PROC_NR, oldp->oldp_grant, off,
(vir_bytes)buf, len)) != OK)
return r;
return size;
}
/*
* Copy out (partial) data to the user, from a vector of up to RMIB_IOV_MAX
* local buffers. The copy is automatically limited to the range of data
* requested by the user. Return the total requested length on success or an
* error code on failure.
*/
ssize_t
rmib_vcopyout(struct rmib_oldp * oldp, size_t off, const iovec_t * iov,
unsigned int iovcnt)
{
static struct vscp_vec vec[RMIB_IOV_MAX];
size_t size, chunk;
unsigned int i;
ssize_t r;
assert(iov != NULL);
assert(iovcnt <= __arraycount(vec));
/* Take a shortcut for single-vector elements, saving a kernel copy. */
if (iovcnt == 1)
return rmib_copyout(oldp, off, (const void *)iov->iov_addr,
iov->iov_size);
/*
* Iterate through the full vector even if we cannot copy out all of
* it, because we need to compute the total length.
*/
for (size = i = 0; iovcnt > 0; iov++, iovcnt--) {
if (oldp != NULL && off < oldp->oldp_len) {
chunk = oldp->oldp_len - off;
if (chunk > iov->iov_size)
chunk = iov->iov_size;
vec[i].v_from = SELF;
vec[i].v_to = MIB_PROC_NR;
vec[i].v_gid = oldp->oldp_grant;
vec[i].v_offset = off;
vec[i].v_addr = iov->iov_addr;
vec[i].v_bytes = chunk;
off += chunk;
i++;
}
size += iov->iov_size;
}
/* Perform the copy, if there is anything to copy, that is. */
if (i > 0 && (r = sys_vsafecopy(vec, i)) != OK)
return r;
return size;
}
/*
* Copy in data from the user. The given length must match exactly the length
* given by the user. Return OK or an error code.
*/
int
rmib_copyin(struct rmib_newp * __restrict newp, void * __restrict buf,
size_t len)
{
if (newp == NULL || len != newp->newp_len)
return EINVAL;
if (len == 0)
return OK;
return sys_safecopyfrom(MIB_PROC_NR, newp->newp_grant, 0,
(vir_bytes)buf, len);
}
/*
* 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.
*/
static ssize_t
rmib_copyout_node(struct rmib_call * call, struct rmib_oldp * oldp,
ssize_t off, unsigned int id, const struct rmib_node * rnode)
{
struct sysctlnode scn;
int visible;
if (!rmib_inrange(oldp, off))
return sizeof(scn); /* nothing to do */
memset(&scn, 0, sizeof(scn));
/*
* We use CTLFLAG_SPARSE internally only. NetBSD uses these flags for
* different purposes. Either way, do not expose it to userland.
* hide any of them from the user.
*/
scn.sysctl_flags = SYSCTL_VERSION |
(rnode->rnode_flags & ~CTLFLAG_SPARSE);
scn.sysctl_num = id;
strlcpy(scn.sysctl_name, rnode->rnode_name, sizeof(scn.sysctl_name));
scn.sysctl_ver = call->call_rootver;
scn.sysctl_size = rnode->rnode_size;
/* Some information is only visible if the user can access the node. */
visible = (!(rnode->rnode_flags & CTLFLAG_PRIVATE) ||
(call->call_flags & RMIB_FLAG_AUTH));
/*
* For immediate types, store the immediate value in the resulting
* structure, unless the caller is not authorized to obtain the value.
*/
if ((rnode->rnode_flags & CTLFLAG_IMMEDIATE) && visible) {
switch (SYSCTL_TYPE(rnode->rnode_flags)) {
case CTLTYPE_BOOL:
scn.sysctl_bdata = rnode->rnode_bool;
break;
case CTLTYPE_INT:
scn.sysctl_idata = rnode->rnode_int;
break;
case CTLTYPE_QUAD:
scn.sysctl_qdata = rnode->rnode_quad;
break;
}
}
/* Special rules apply to parent nodes. */
if (SYSCTL_TYPE(rnode->rnode_flags) == CTLTYPE_NODE) {
/* Report the node size the way NetBSD does, just in case. */
scn.sysctl_size = sizeof(scn);
/*
* For real parent nodes, report child information, but only if
* the node itself is accessible by the caller. For function-
* driven nodes, set a nonzero function address, for trace(1).
*/
if (rnode->rnode_func == NULL && visible) {
scn.sysctl_csize = rnode->rnode_size;
scn.sysctl_clen = rnode->rnode_clen;
} else if (rnode->rnode_func != NULL)
scn.sysctl_func = SYSCTL_NODE_FN;
}
/* Copy out the resulting node. */
return rmib_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
rmib_query(struct rmib_call * call, struct rmib_node * rparent,
struct rmib_oldp * oldp, struct rmib_newp * newp)
{
struct sysctlnode scn;
struct rmib_node *rnode;
unsigned int i, id;
ssize_t r, off;
/* If the user passed in version numbers, check them. */
if (newp != NULL) {
if ((r = rmib_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 subtree or the root of the entire MIB version.
*/
if (scn.sysctl_ver != 0 &&
scn.sysctl_ver != call->call_rootver &&
scn.sysctl_ver != call->call_treever)
return EINVAL;
}
/* Enumerate the child nodes of the given parent node. */
off = 0;
for (i = 0; i < rparent->rnode_size; i++) {
if (rparent->rnode_flags & CTLFLAG_SPARSE) {
id = rparent->rnode_icptr[i].rindir_id;
rnode = rparent->rnode_icptr[i].rindir_node;
} else {
id = i;
rnode = &rparent->rnode_cptr[i];
if (rnode->rnode_flags == 0)
continue;
}
if ((r = rmib_copyout_node(call, oldp, off, id, rnode)) < 0)
return r;
off += r;
}
return off;
}
/*
* 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
rmib_copyout_desc(struct rmib_call * call, struct rmib_oldp * oldp,
ssize_t off, unsigned int id, const struct rmib_node * rnode)
{
struct sysctldesc scd;
size_t len, size;
ssize_t r;
/* Descriptions of private nodes are considered private too. */
if ((rnode->rnode_flags & CTLFLAG_PRIVATE) &&
!(call->call_flags & RMIB_FLAG_AUTH))
return 0;
/*
* Unfortunately, we do not have a scratch buffer here. Instead, copy
* out the description structure and the actual description string
* separately. This is more costly, but remote subtrees are already
* not going to give the best performance ever. We do optimize for the
* case that there is no description, because that is relatively easy.
*/
/* The description length includes the null terminator. */
if (rnode->rnode_desc != NULL)
len = strlen(rnode->rnode_desc) + 1;
else
len = 1;
memset(&scd, 0, sizeof(scd));
scd.descr_num = id;
scd.descr_ver = call->call_rootver;
scd.descr_len = len;
size = offsetof(struct sysctldesc, descr_str);
if (len == 1) {
scd.descr_str[0] = '\0'; /* superfluous */
size++;
}
/* Copy out the structure, possibly including a null terminator. */
if ((r = rmib_copyout(oldp, off, &scd, size)) < 0)
return r;
if (len > 1) {
/* Copy out the description itself. */
if ((r = rmib_copyout(oldp, off + size, rnode->rnode_desc,
len)) < 0)
return r;
size += len;
}
/*
* 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));
}
/*
* Look up a child node given a parent node and a child node identifier.
* Return a pointer to the child node if found, or NULL otherwise. The lookup
* procedure differs based on whether the parent node is sparse or not.
*/
static struct rmib_node *
rmib_lookup(struct rmib_node * rparent, unsigned int id)
{
struct rmib_node *rnode;
struct rmib_indir *rindir;
unsigned int i;
if (rparent->rnode_flags & CTLFLAG_SPARSE) {
rindir = rparent->rnode_icptr;
for (i = 0; i < rparent->rnode_size; i++, rindir++)
if (rindir->rindir_id == id)
return rindir->rindir_node;
} else {
if (id >= rparent->rnode_size)
return NULL;
rnode = &rparent->rnode_cptr[id];
if (rnode->rnode_flags != 0)
return rnode;
}
return NULL;
}
/*
* Retrieve node descriptions in bulk, or retrieve a particular node's
* description.
*/
static ssize_t
rmib_describe(struct rmib_call * call, struct rmib_node * rparent,
struct rmib_oldp * oldp, struct rmib_newp * newp)
{
struct sysctlnode scn;
struct rmib_node *rnode;
unsigned int i, id;
ssize_t r, off;
if (newp != NULL) {
if ((r = rmib_copyin(newp, &scn, sizeof(scn))) != OK)
return r;
if (SYSCTL_VERS(scn.sysctl_flags) != SYSCTL_VERSION)
return EINVAL;
/* Locate the child node. */
if ((rnode = rmib_lookup(rparent, scn.sysctl_num)) == NULL)
return ENOENT;
/* Descriptions of private nodes are considered private too. */
if ((rnode->rnode_flags & CTLFLAG_PRIVATE) &&
!(call->call_flags & RMIB_FLAG_AUTH))
return EPERM;
/*
* If a description pointer was given, this is a request to
* set the node's description. We do not allow this, nor would
* we be able to support it, since we cannot access the data.
*/
if (scn.sysctl_desc != NULL)
return EPERM;
/*
* Copy out the requested node's description. At this point we
* should be sure that this call does not return zero.
*/
return rmib_copyout_desc(call, oldp, 0, scn.sysctl_num, rnode);
}
/* Describe the child nodes of the given parent node. */
off = 0;
for (i = 0; i < rparent->rnode_size; i++) {
if (rparent->rnode_flags & CTLFLAG_SPARSE) {
id = rparent->rnode_icptr[i].rindir_id;
rnode = rparent->rnode_icptr[i].rindir_node;
} else {
id = i;
rnode = &rparent->rnode_cptr[i];
if (rnode->rnode_flags == 0)
continue;
}
if ((r = rmib_copyout_desc(call, oldp, off, id, rnode)) < 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 *
rmib_getptr(struct rmib_node * rnode)
{
switch (SYSCTL_TYPE(rnode->rnode_flags)) {
case CTLTYPE_BOOL:
if (rnode->rnode_flags & CTLFLAG_IMMEDIATE)
return &rnode->rnode_bool;
break;
case CTLTYPE_INT:
if (rnode->rnode_flags & CTLFLAG_IMMEDIATE)
return &rnode->rnode_int;
break;
case CTLTYPE_QUAD:
if (rnode->rnode_flags & CTLFLAG_IMMEDIATE)
return &rnode->rnode_quad;
break;
case CTLTYPE_STRING:
case CTLTYPE_STRUCT:
if (rnode->rnode_flags & CTLFLAG_IMMEDIATE)
return NULL;
break;
default:
return NULL;
}
return rnode->rnode_data;
}
/*
* Read current (old) data from a regular data node, if requested. Return the
* old data length.
*/
static ssize_t
rmib_read(struct rmib_node * rnode, struct rmib_oldp * oldp)
{
void *ptr;
size_t oldlen;
int r;
if ((ptr = rmib_getptr(rnode)) == NULL)
return EINVAL;
if (SYSCTL_TYPE(rnode->rnode_flags) == CTLTYPE_STRING)
oldlen = strlen(rnode->rnode_data) + 1;
else
oldlen = rnode->rnode_size;
if (oldlen > SSIZE_MAX)
return EINVAL;
/* Copy out the current data, if requested at all. */
if (oldp != NULL && (r = rmib_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
rmib_write(struct rmib_call * call, struct rmib_node * rnode,
struct rmib_newp * newp)
{
bool b[(sizeof(bool) == sizeof(char)) ? 1 : -1]; /* for sanitizing */
char *src, *dst, buf[RMIB_STACKBUF];
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.
*/
newlen = newp->newp_len;
if ((dst = rmib_getptr(rnode)) == NULL)
return EINVAL;
switch (SYSCTL_TYPE(rnode->rnode_flags)) {
case CTLTYPE_BOOL:
case CTLTYPE_INT:
case CTLTYPE_QUAD:
case CTLTYPE_STRUCT:
/* Non-string types must have an exact size match. */
if (newlen != rnode->rnode_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 > rnode->rnode_size)
return EINVAL;
break;
default:
return EINVAL;
}
/*
* If we cannot fit the data in the small stack 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!
*/
if (newlen + 1 > sizeof(buf)) {
/*
* For regular users, we do not want to perform dynamic memory
* allocation. Thus, for CTLTYPE_ANYWRITE nodes, only the
* superuser may set values exceeding the small buffer in size.
*/
if (!(call->call_flags & RMIB_FLAG_AUTH))
return EPERM;
/* Do not return ENOMEM on allocation failure. */
if ((src = malloc(newlen + 1)) == NULL)
return EINVAL;
} else
src = buf;
/* Copy in the data. Note that the given new length may be zero. */
if ((r = rmib_copyin(newp, src, newlen)) == OK) {
/* Check and, if acceptable, store the new value. */
switch (SYSCTL_TYPE(rnode->rnode_flags)) {
case CTLTYPE_BOOL:
/* Sanitize booleans. See the MIB code for details. */
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, rnode->rnode_size);
break;
case CTLTYPE_STRING:
if (newlen == rnode->rnode_size &&
src[newlen - 1] != '\0') {
/* Our null terminator does not fit! */
r = EINVAL;
break;
}
src[newlen] = '\0';
strlcpy(dst, src, rnode->rnode_size);
break;
default:
r = EINVAL;
}
}
if (src != buf)
free(src);
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
rmib_readwrite(struct rmib_call * call, struct rmib_node * rnode,
struct rmib_oldp * oldp, struct rmib_newp * newp)
{
ssize_t len;
int r;
/* Copy out old data, if requested. Always get the old data length. */
if ((r = len = rmib_read(rnode, oldp)) < 0)
return r;
/* Copy in new data, if requested. */
if ((r = rmib_write(call, rnode, newp)) != OK)
return r;
/* Return the old data length. */
return len;
}
/*
* Handle a sysctl(2) call from a user process, relayed by the MIB service to
* us. If the call succeeds, return the old length. The MIB service will
* perform a check against the given old length and return ENOMEM to the caller
* when applicable, so we do not have to do that here. If the call fails,
* return a negative error code.
*/
static ssize_t
rmib_call(const message * m_in)
{
struct rmib_node *rnode, *rparent;
struct rmib_call call;
struct rmib_oldp oldp_data, *oldp;
struct rmib_newp newp_data, *newp;
unsigned int root_id, prefixlen, namelen;
int r, id, is_leaf, has_func, name[CTL_MAXNAME];
/*
* Look up the root of the subtree that is the subject of the call. If
* the call is for a subtree that is not registered, return ERESTART to
* indicate to the MIB service that it should deregister the subtree it
* thinks we have. This case may occur in practice if a deregistration
* request from us crosses a sysctl call request from the MIB service.
*/
root_id = m_in->m_mib_lsys_call.root_id;
if (root_id >= __arraycount(rnodes) ||
(rnode = rnodes[root_id].rno_node) == NULL)
return ERESTART;
/*
* Use the name of the mounted subtree as prefix to the given name, so
* that call_oname will point to the complete name of the node. This
* is necessary for the few queries that make use of call_oname.
*/
prefixlen = rnodes[root_id].rno_namelen;
memcpy(name, rnodes[root_id].rno_name, prefixlen * sizeof(name[0]));
/*
* Set up all data structures that we need to use while handling the
* call processing. Start by copying in the remainder of the MIB name.
*/
/* A zero name length is valid and should always yield EISDIR. */
namelen = m_in->m_mib_lsys_call.name_len;
if (prefixlen + namelen > __arraycount(name))
return EINVAL;
if (namelen > 0) {
r = sys_safecopyfrom(m_in->m_source,
m_in->m_mib_lsys_call.name_grant, 0,
(vir_bytes)&name[prefixlen], sizeof(name[0]) * namelen);
if (r != OK)
return r;
}
oldp_data.oldp_grant = m_in->m_mib_lsys_call.oldp_grant;
oldp_data.oldp_len = m_in->m_mib_lsys_call.oldp_len;
oldp = (GRANT_VALID(oldp_data.oldp_grant)) ? &oldp_data : NULL;
newp_data.newp_grant = m_in->m_mib_lsys_call.newp_grant;
newp_data.newp_len = m_in->m_mib_lsys_call.newp_len;
newp = (GRANT_VALID(newp_data.newp_grant)) ? &newp_data : NULL;
call.call_endpt = m_in->m_mib_lsys_call.user_endpt;
call.call_oname = name;
call.call_name = &name[prefixlen];
call.call_namelen = namelen;
call.call_flags = m_in->m_mib_lsys_call.flags;
call.call_rootver = m_in->m_mib_lsys_call.root_ver;
call.call_treever = m_in->m_mib_lsys_call.tree_ver;
/*
* Dispatch the call.
*/
for (rparent = rnode; call.call_namelen > 0; rparent = rnode) {
id = call.call_name[0];
call.call_name++;
call.call_namelen--;
assert(SYSCTL_TYPE(rparent->rnode_flags) == CTLTYPE_NODE);
/* Check for meta-identifiers. */
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 rmib_query(&call, rparent, oldp, newp);
case CTL_DESCRIBE:
return rmib_describe(&call, rparent, oldp,
newp);
case CTL_CREATE:
case CTL_DESTROY:
/* We support fully static subtrees only. */
return EPERM;
default:
return EOPNOTSUPP;
}
}
/* Locate the child node. */
if ((rnode = rmib_lookup(rparent, id)) == NULL)
return ENOENT;
/* Check if access is permitted at this level. */
if ((rnode->rnode_flags & CTLFLAG_PRIVATE) &&
!(call.call_flags & RMIB_FLAG_AUTH))
return EPERM;
/*
* Is this a leaf node, and/or is this node handled by a
* function? If either is true, resolution ends at this level.
*/
is_leaf = (SYSCTL_TYPE(rnode->rnode_flags) != CTLTYPE_NODE);
has_func = (rnode->rnode_func != NULL);
/*
* 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.
*/
if ((is_leaf || has_func) && newp != NULL) {
if (!(rnode->rnode_flags & CTLFLAG_READWRITE))
return EPERM;
if (!(rnode->rnode_flags & CTLFLAG_ANYWRITE) &&
!(call.call_flags & RMIB_FLAG_AUTH))
return EPERM;
}
/* If this node has a handler function, let it do the work. */
if (has_func)
return rnode->rnode_func(&call, rnode, oldp, newp);
/* For regular data leaf nodes, handle generic access. */
if (is_leaf)
return rmib_readwrite(&call, rnode, oldp, newp);
/* No function and not a leaf? Descend further. */
}
/* If we get here, the name refers to a node array. */
return EISDIR;
}
/*
* Initialize the given node and recursively all its node-type children,
* assigning the proper child length value to each of them.
*/
static void
rmib_init(struct rmib_node * rparent)
{
struct rmib_node *rnode;
unsigned int i;
for (i = 0; i < rparent->rnode_size; i++) {
if (rparent->rnode_flags & CTLFLAG_SPARSE) {
/* Indirect lists must be sorted ascending by ID. */
assert(i == 0 || rparent->rnode_icptr[i].rindir_id >
rparent->rnode_icptr[i - 1].rindir_id);
rnode = rparent->rnode_icptr[i].rindir_node;
} else {
rnode = &rparent->rnode_cptr[i];
if (rnode->rnode_flags == 0)
continue;
}
rparent->rnode_clen++;
if (SYSCTL_TYPE(rnode->rnode_flags) == CTLTYPE_NODE)
rmib_init(rnode); /* recurse */
}
}
/*
* Request that the MIB service (re)mount the subtree identified by the given
* identifier. This is a one-way request, so we never hear whether mounting
* succeeds. There is not that much we can do if it fails anyway though.
*/
static void
rmib_send_reg(int id)
{
message m;
int r;
memset(&m, 0, sizeof(m));
m.m_type = MIB_REGISTER;
m.m_lsys_mib_register.root_id = id;
m.m_lsys_mib_register.flags = SYSCTL_VERSION |
(rnodes[id].rno_node->rnode_flags & ~CTLFLAG_SPARSE);
m.m_lsys_mib_register.csize = rnodes[id].rno_node->rnode_size;
m.m_lsys_mib_register.clen = rnodes[id].rno_node->rnode_clen;
m.m_lsys_mib_register.miblen = rnodes[id].rno_namelen;
memcpy(m.m_lsys_mib_register.mib, rnodes[id].rno_name,
sizeof(rnodes[id].rno_name[0]) * rnodes[id].rno_namelen);
if ((r = asynsend3(MIB_PROC_NR, &m, AMF_NOREPLY)) != OK)
panic("asynsend3 call to MIB service failed: %d", r);
}
/*
* Register a MIB subtree. Initialize the subtree, add it to the local set,
* and send a registration request for it to the MIB service.
*/
int
rmib_register(const int * name, unsigned int namelen, struct rmib_node * rnode)
{
unsigned int id, free_id;
/* A few basic sanity checks. */
if (namelen == 0 || namelen >= __arraycount(rnodes[0].rno_name))
return EINVAL;
if (SYSCTL_TYPE(rnode->rnode_flags) != CTLTYPE_NODE)
return EINVAL;
/* Make sure this is a new subtree, and find a free slot for it. */
for (id = free_id = 0; id < __arraycount(rnodes); id++) {
if (rnodes[id].rno_node == rnode)
return EEXIST;
else if (rnodes[id].rno_node == NULL &&
rnodes[free_id].rno_node != NULL)
free_id = id;
}
if (rnodes[free_id].rno_node != NULL)
return ENOMEM;
rnodes[free_id].rno_node = rnode;
rnodes[free_id].rno_namelen = namelen;
memcpy(rnodes[free_id].rno_name, name, sizeof(name[0]) * namelen);
/*
* Initialize the entire subtree. This will also compute rnode_clen
* for the given rnode, so do this before sending the message.
*/
rmib_init(rnode);
/* Send the registration request to the MIB service. */
rmib_send_reg(free_id);
return OK;
}
/*
* Deregister a previously registered subtree, both internally and with the MIB
* service. Return OK if the deregistration procedure has been started, in
* which case the given subtree is guaranteed to no longer be accessed. Return
* a negative error code on failure.
*/
int
rmib_deregister(struct rmib_node * rnode)
{
message m;
unsigned int id;
for (id = 0; id < __arraycount(rnodes); id++)
if (rnodes[id].rno_node == rnode)
break;
if (id == __arraycount(rnodes))
return ENOENT;
rnodes[id].rno_node = NULL;
/*
* Request that the MIB service unmount the subtree. We completely
* ignore failure here, because the caller would not be able to do
* anything about it anyway. We may also still receive sysctl call
* requests for the node we just deregistered, but this is caught
* during request processing. Reuse of the rnodes[] slot could be a
* potential problem though. We could use sequence numbers in the root
* identifiers to resolve that problem if it ever occurs in reality.
*/
memset(&m, 0, sizeof(m));
m.m_type = MIB_DEREGISTER;
m.m_lsys_mib_register.root_id = id;
(void)asynsend3(MIB_PROC_NR, &m, AMF_NOREPLY);
return OK;
}
/*
* Reregister all previously registered subtrees. This routine should be
* called after the main program has determined that the MIB service has been
* restarted.
*/
void
rmib_reregister(void)
{
unsigned int id;
for (id = 0; id < __arraycount(rnodes); id++)
if (rnodes[id].rno_node != NULL)
rmib_send_reg(id);
}
/*
* Reset all registrations, without involving MIB communication. This routine
* exists for testing purposes only, and may disappear in the future.
*/
void
rmib_reset(void)
{
memset(rnodes, 0, sizeof(rnodes));
}
/*
* Process a request from the MIB service for information about the root node
* of a subtree, specifically its name and description.
*/
static int
rmib_info(const message * m_in)
{
struct rmib_node *rnode;
unsigned int id;
const char *ptr;
size_t size;
int r;
id = m_in->m_mib_lsys_info.root_id;
if (id >= __arraycount(rnodes) || rnodes[id].rno_node == NULL)
return ENOENT;
rnode = rnodes[id].rno_node;
/* The name must fit. If it does not, the service writer messed up. */
size = strlen(rnode->rnode_name) + 1;
if (size > m_in->m_mib_lsys_info.name_size)
return ENAMETOOLONG;
r = sys_safecopyto(m_in->m_source, m_in->m_mib_lsys_info.name_grant, 0,
(vir_bytes)rnode->rnode_name, size);
if (r != OK)
return r;
/* If there is no (optional) description, copy out an empty string. */
ptr = (rnode->rnode_desc != NULL) ? rnode->rnode_desc : "";
size = strlen(ptr) + 1;
if (size > m_in->m_mib_lsys_info.desc_size)
size = m_in->m_mib_lsys_info.desc_size;
return sys_safecopyto(m_in->m_source, m_in->m_mib_lsys_info.desc_grant,
0, (vir_bytes)ptr, size);
}
/*
* Process a request from the MIB service. The given message should originate
* from the MIB service and have one of the COMMON_MIB_ requests as type.
*/
void
rmib_process(const message * m_in, int ipc_status)
{
message m_out;
uint32_t req_id;
ssize_t r;
/* Only the MIB service may issue these requests. */
if (m_in->m_source != MIB_PROC_NR)
return;
/* Process the actual request. */
switch (m_in->m_type) {
case COMMON_MIB_INFO:
req_id = m_in->m_mib_lsys_info.req_id;
r = rmib_info(m_in);
break;
case COMMON_MIB_CALL:
req_id = m_in->m_mib_lsys_call.req_id;
r = rmib_call(m_in);
break;
default:
/*
* HACK: assume that for all current and future requests, the
* request ID field is in the same place. We could create a
* m_mib_lsys_unknown pseudo message type for this, but, eh.
*/
req_id = m_in->m_mib_lsys_info.req_id;
r = ENOSYS;
}
/* Construct and send a reply message to the MIB service. */
memset(&m_out, 0, sizeof(m_out));
m_out.m_type = COMMON_MIB_REPLY;
m_out.m_lsys_mib_reply.req_id = req_id;
m_out.m_lsys_mib_reply.status = r;
if (IPC_STATUS_CALL(ipc_status) == SENDREC)
r = ipc_sendnb(m_in->m_source, &m_out);
else
r = asynsend3(m_in->m_source, &m_out, AMF_NOREPLY);
if (r != OK)
printf("lsys:rmib: unable to send reply to %d: %zd\n",
m_in->m_source, r);
}