
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
950 lines
26 KiB
C
950 lines
26 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.
|
|
*
|
|
* 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 could
|
|
* implement re-registration by first calling rmib_reset() and then making the
|
|
* appropriate rmib_register() calls again. TODO: it would be nicer if this
|
|
* module implemented re-registration, but that requires saving the MIB path
|
|
* for each of the registered 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 rmib_node *rnodes[RMIB_MAX_SUBTREES] = { NULL };
|
|
|
|
/*
|
|
* 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 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));
|
|
|
|
/*
|
|
* The RMIB implementation does not overload flags, so it also need not
|
|
* hide any of them from the user.
|
|
*/
|
|
scn.sysctl_flags = SYSCTL_VERSION | rnode->rnode_flags;
|
|
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 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 (id = 0; id < rparent->rnode_size; id++) {
|
|
rnode = &rparent->rnode_cptr[id];
|
|
|
|
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));
|
|
}
|
|
|
|
/*
|
|
* 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 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 ((unsigned int)scn.sysctl_num >= rparent->rnode_size)
|
|
return ENOENT;
|
|
rnode = &rparent->rnode_cptr[scn.sysctl_num];
|
|
if (rnode->rnode_flags == 0)
|
|
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 (id = 0; id < rparent->rnode_size; id++) {
|
|
rnode = &rparent->rnode_cptr[id];
|
|
|
|
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, 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) || rnodes[root_id] == NULL)
|
|
return ERESTART;
|
|
rnode = rnodes[root_id];
|
|
|
|
/*
|
|
* 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 (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,
|
|
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_name = name;
|
|
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 ((unsigned int)id >= rparent->rnode_size)
|
|
return ENOENT;
|
|
rnode = &rparent->rnode_cptr[id];
|
|
if (rnode->rnode_flags == 0)
|
|
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 * rnode)
|
|
{
|
|
struct rmib_node *rchild;
|
|
unsigned int id;
|
|
|
|
rchild = rnode->rnode_cptr;
|
|
|
|
for (id = 0; id < rnode->rnode_size; id++, rchild++) {
|
|
if (rchild->rnode_flags == 0)
|
|
continue;
|
|
|
|
rnode->rnode_clen++;
|
|
|
|
if (SYSCTL_TYPE(rchild->rnode_flags) == CTLTYPE_NODE)
|
|
rmib_init(rchild); /* recurse */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
message m;
|
|
unsigned int id, free_id;
|
|
int r;
|
|
|
|
/* A few basic sanity checks. */
|
|
if (namelen == 0 || namelen >= CTL_SHORTNAME)
|
|
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] == rnode)
|
|
return EEXIST;
|
|
else if (rnodes[id] == NULL && rnodes[free_id] != NULL)
|
|
free_id = id;
|
|
}
|
|
|
|
if (rnodes[free_id] != NULL)
|
|
return ENOMEM;
|
|
|
|
/*
|
|
* Initialize the entire subtree. This will also compute rnode_clen
|
|
* for the given rnode, so do this before sending the message.
|
|
*/
|
|
rmib_init(rnode);
|
|
|
|
/*
|
|
* Request that the MIB service mount this subtree. 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.
|
|
*/
|
|
memset(&m, 0, sizeof(m));
|
|
|
|
m.m_type = MIB_REGISTER;
|
|
m.m_lsys_mib_register.root_id = free_id;
|
|
m.m_lsys_mib_register.flags = SYSCTL_VERSION | rnode->rnode_flags;
|
|
m.m_lsys_mib_register.csize = rnode->rnode_size;
|
|
m.m_lsys_mib_register.clen = rnode->rnode_clen;
|
|
m.m_lsys_mib_register.miblen = namelen;
|
|
memcpy(m.m_lsys_mib_register.mib, name, sizeof(name[0]) * namelen);
|
|
|
|
if ((r = asynsend3(MIB_PROC_NR, &m, AMF_NOREPLY)) == OK)
|
|
rnodes[free_id] = rnode;
|
|
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* 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] == rnode)
|
|
break;
|
|
|
|
if (id == __arraycount(rnodes))
|
|
return ENOENT;
|
|
|
|
rnodes[id] = 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;
|
|
}
|
|
|
|
/*
|
|
* Reset all registrations, without involving MIB communication. This call
|
|
* must be issued only when the caller has determined that the MIB service has
|
|
* restarted, and is about to reregister its subtrees.
|
|
*/
|
|
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] == NULL)
|
|
return ENOENT;
|
|
rnode = rnodes[id];
|
|
|
|
/* 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: %d\n",
|
|
m_in->m_source, r);
|
|
}
|