--- /dev/null
+/* 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 nodes; /* how many nodes are there in the tree? */
+unsigned int objects; /* how many allocated memory objects 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 and CTLFLAG_VERIFY 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));
+ 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 real parent node, report child information. */
+ if ((node->node_flags & CTLFLAG_PARENT) && visible) {
+ scn.sysctl_csize = node->node_csize;
+ scn.sysctl_clen = node->node_clen;
+ }
+
+ /*
+ * 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_PARENT))
+ 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 mib_node * root)
+{
+ 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 != 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;
+}
+
+/*
+ * 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 a node, as well as (optionally) that node itself.
+ */
+static void
+mib_upgrade(struct mib_node ** stack, int depth, struct mib_node * node)
+{
+ uint32_t ver;
+
+ /*
+ * The bottom of the stack is always the root node, which determines
+ * the version of the entire tree. Do not use version number 0, as a
+ * zero version number indicates no interest in versions elsewhere.
+ */
+ assert(depth > 0);
+
+ ver = stack[0]->node_ver + 1;
+ if (ver == 0)
+ ver = 1;
+
+ /* Copy the new version to all the nodes on the path. */
+ while (depth-- > 0)
+ stack[depth]->node_ver = ver;
+
+ if (node != NULL)
+ node->node_ver = stack[0]->node_ver;
+}
+
+/*
+ * 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_node ** stack, int depth)
+{
+ 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 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 the
+ * parent or the root (which is always the bottom of the node stack).
+ * The given version number is *not* used for the node being created.
+ */
+ assert(depth > 0);
+
+ if (scn.sysctl_ver != 0 && scn.sysctl_ver != stack[0]->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;
+
+ /* Names must be nonempty, null terminated, C symbol style strings. */
+ for (namelen = 0; namelen < sizeof(scn.sysctl_name); namelen++) {
+ if ((c = scn.sysctl_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 EINVAL;
+ }
+ if (namelen == 0 || namelen == sizeof(scn.sysctl_name))
+ 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 */
+ 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_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);
+ objects--;
+
+ return r;
+ }
+
+ /* At this point, actual creation can no longer fail. */
+
+ /* 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++;
+
+ nodes++;
+
+ /*
+ * Bump the version of all nodes on the path to the new node, including
+ * the node itself.
+ */
+ mib_upgrade(stack, depth, node);
+
+ /*
+ * 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);
+}
+
+/*
+ * 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_node ** stack, int depth)
+{
+ struct mib_dynode *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 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);
+
+ /* If the description was allocated, free it. */
+ if (node->node_flags & CTLFLAG_OWNDESC) {
+ free(__UNCONST(node->node_desc));
+ 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;
+
+ free(dynode);
+ objects--;
+
+ parent->node_csize--;
+ } else
+ memset(node, 0, sizeof(*node));
+
+ parent->node_clen--;
+
+ nodes--;
+
+ /* Bump the version of all nodes on the path to the destroyed node. */
+ mib_upgrade(stack, depth, NULL);
+
+ 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 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 */
+ }
+ 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_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;
+ 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;
+ 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!
+ *
+ * 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;
+ }
+ 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);
+ 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_node * root,
+ struct mib_oldp * oldp, struct mib_newp * newp)
+{
+ struct mib_node *stack[CTL_MAXNAME];
+ struct mib_node *parent, *node;
+ int id, depth, is_leaf, 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.
+ */
+ depth = 0;
+
+ for (parent = root; call->call_namelen > 0; parent = node) {
+ /*
+ * For node creation and destruction, build a node stack, to
+ * allow for up-propagation of new node version numbers.
+ */
+ stack[depth++] = parent;
+
+ 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,
+ root);
+ case CTL_CREATE:
+ return mib_create(call, parent, oldp, newp,
+ stack, depth);
+ case CTL_DESTROY:
+ return mib_destroy(call, parent, oldp, newp,
+ stack, depth);
+ 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;
+
+ /*
+ * 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.
+ */
+ is_leaf = (SYSCTL_TYPE(node->node_flags) != CTLTYPE_NODE);
+ 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;
+
+ nodes++;
+
+ parent->node_clen++;
+
+ node->node_ver = parent->node_ver;
+
+ /* 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(struct mib_node * root)
+{
+
+ /* Initialize some variables. */
+ nodes = 1; /* the root node itself */
+ objects = 0;
+
+ /* The entire tree starts with the same, nonzero node version. */
+ root->node_ver = 1;
+
+ /* Recursively initialize the static tree. */
+ mib_tree_recurse(root);
+}
--- /dev/null
+/* Tests for sysctl(2) and the MIB service - by D.C. van Moolenbroek */
+/* This test needs to run as root: many sysctl(2) calls are privileged. */
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <minix/sysctl.h>
+#include <assert.h>
+
+#define ITERATIONS 2
+
+#include "common.h"
+
+#define NONROOT_USER "bin" /* name of any unprivileged user */
+
+#define NEXT_VER(n) (((n) + 1 == 0) ? 1 : ((n) + 1)) /* node version + 1 */
+
+static void *bad_ptr; /* a pointer to unmapped memory */
+static unsigned int nodes, objects; /* stats for pre/post test check */
+
+/*
+ * Spawn a child process that drops privileges and then executes the given
+ * procedure. The returned PID value is of the dead, cleaned-up child, and
+ * should be used only to check whether the child could store its own PID.
+ */
+static pid_t
+test_nonroot(void (* proc)(void))
+{
+ struct passwd *pw;
+ pid_t pid;
+ int status;
+
+ pid = fork();
+
+ switch (pid) {
+ case -1:
+ e(0);
+ break;
+ case 0:
+ errct = 0;
+
+ if ((pw = getpwnam(NONROOT_USER)) == NULL) e(0);
+
+ /* FIXME: this may rely on a MINIXism. */
+ if (setuid(pw->pw_uid) != 0) e(0);
+ if (seteuid(pw->pw_uid) != 0) e(0);
+
+ proc();
+
+ exit(errct);
+ default:
+ if (wait(&status) != pid) e(0);
+ if (!WIFEXITED(status)) e(0);
+ if (WEXITSTATUS(status) != 0) e(0);
+ }
+
+ return pid;
+}
+
+/*
+ * Test basic operations from an unprivileged process.
+ */
+static void
+sub87a(void)
+{
+ size_t oldlen;
+ pid_t pid;
+ bool b;
+ int i, mib[4];
+
+ pid = getpid();
+
+ mib[0] = CTL_MINIX;
+ mib[1] = MINIX_TEST;
+
+ /* Regular reads should succeed. */
+ mib[2] = TEST_INT;
+ oldlen = sizeof(i);
+ if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(i)) e(0);
+ if (i != 0x01020304) e(0);
+
+ mib[2] = TEST_BOOL;
+ oldlen = sizeof(b);
+ if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(b)) e(0);
+ if (b != false) e(0);
+
+ /* Regular writes should fail. */
+ b = true;
+ if (sysctl(mib, 3, NULL, NULL, &b, sizeof(b)) != -1) e(0);
+ if (errno != EPERM) e(0);
+
+ oldlen = sizeof(b);
+ if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(b)) e(0);
+ if (b != false) e(0);
+
+ /* Privileged reads and writes should fail. */
+ mib[2] = TEST_PRIVATE;
+ if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != EPERM) e(0);
+
+ oldlen = sizeof(i);
+ i = 1;
+ if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != EPERM) e(0);
+ if (i != 1) e(0);
+
+ if (sysctl(mib, 3, NULL, NULL, &i, sizeof(i)) != -1) e(0);
+ if (errno != EPERM) e(0);
+
+ mib[2] = TEST_SECRET;
+ mib[3] = SECRET_VALUE;
+ i = 0;
+ oldlen = sizeof(i);
+ if (sysctl(mib, 4, &i, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != EPERM) e(0);
+ if (i == 12345) e(0);
+
+ mib[3]++;
+ i = 0;
+ oldlen = sizeof(i);
+ if (sysctl(mib, 4, &i, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != EPERM) e(0);
+
+ /* Free-for-all writes should succeed. */
+ mib[2] = TEST_ANYWRITE;
+ if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(i)) e(0);
+
+ i = pid;
+ if (sysctl(mib, 3, NULL, NULL, &i, sizeof(i)) != 0) e(0);
+
+ i = 0;
+ oldlen = sizeof(i);
+ if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(i)) e(0);
+ if (i != pid) e(0);
+}
+
+/*
+ * Test the basic sysctl(2) interface.
+ */
+static void
+test87a(void)
+{
+ char buf[32];
+ size_t len, oldlen;
+ pid_t pid;
+ u_quad_t q;
+ bool b, b2;
+ int i, va[2], lastva, mib[CTL_MAXNAME + 1];
+
+ subtest = 0;
+
+ mib[0] = INT_MAX; /* some root-level identifier that does not exist */
+ for (i = 1; i <= CTL_MAXNAME; i++)
+ mib[i] = i;
+
+ /*
+ * We cannot test for invalid 'name' and 'oldlenp' pointers, because
+ * those may be accessed directly by the libc system call stub. The
+ * NetBSD part of the stub even accesses name[0] without checking
+ * namelen first.
+ */
+ if (sysctl(mib, 0, NULL, NULL, NULL, 0) != -1) e(0);
+ if (errno != EINVAL) e(0);
+ if (sysctl(mib, INT_MAX, NULL, NULL, NULL, 0) != -1) e(0);
+ if (errno != EINVAL) e(0);
+ if (sysctl(mib, UINT_MAX, NULL, NULL, NULL, 0) != -1) e(0);
+ if (errno != EINVAL) e(0);
+ for (i = 1; i <= CTL_MAXNAME; i++) {
+ if (sysctl(mib, i, NULL, NULL, NULL, 0) != -1) e(i);
+ if (errno != ENOENT) e(i);
+ }
+ if (sysctl(mib, i, NULL, NULL, NULL, 0) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ /* Test names that are too short, right, and too long. */
+ mib[0] = CTL_MINIX;
+ if (sysctl(mib, 1, NULL, NULL, NULL, 0) != -1) e(0);
+ if (errno != EISDIR) e(0);
+ mib[1] = MINIX_TEST;
+ if (sysctl(mib, 2, NULL, NULL, NULL, 0) != -1) e(0);
+ if (errno != EISDIR) e(0);
+ mib[2] = TEST_INT;
+ if (sysctl(mib, 3, NULL, NULL, NULL, 0) != 0) e(0);
+ mib[3] = 0;
+ if (sysctl(mib, 4, NULL, NULL, NULL, 0) != -1) e(0);
+ if (errno != ENOTDIR) e(0);
+
+ /* Do some tests with meta-identifiers (special keys). */
+ mib[3] = CTL_QUERY;
+ if (sysctl(mib, 4, NULL, NULL, NULL, 0) != -1) e(0);
+ if (errno != ENOTDIR) e(0);
+
+ mib[2] = CTL_QUERY;
+ mib[3] = 0;
+ if (sysctl(mib, 4, NULL, NULL, NULL, 0) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ mib[2] = CTL_EOL; /* a known-invalid meta-identifier */
+ if (sysctl(mib, 3, NULL, NULL, NULL, 0) != -1) e(0);
+ if (errno != EOPNOTSUPP) e(0);
+
+ /* This case returns EINVAL now but might as well return EOPNOTSUPP. */
+ mib[3] = 0;
+ if (sysctl(mib, 4, NULL, NULL, NULL, 0) != -1) e(0);
+ if (errno != EOPNOTSUPP && errno != EINVAL) e(0);
+
+ /* Make sure the given oldlen value is ignored when unused. */
+ mib[2] = TEST_INT;
+ oldlen = 0;
+ if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(int)) e(0);
+ oldlen = 1;
+ if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(int)) e(0);
+ oldlen = SSIZE_MAX;
+ if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(int)) e(0);
+ oldlen = SIZE_MAX;
+ if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(int)) e(0);
+
+ /* Test retrieval with the exact length. */
+ oldlen = sizeof(va[0]);
+ va[0] = va[1] = -1;
+ if (sysctl(mib, 3, va, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(va[0])) e(0);
+ if (va[0] != 0x01020304) e(0);
+ if (va[1] != -1) e(0);
+
+ /* Test retrieval with a length that is too short. */
+ for (i = 0; i < sizeof(va[0]); i++) {
+ va[0] = -1;
+ oldlen = i;
+ if (sysctl(mib, 3, va, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != ENOMEM) e(0);
+ if (oldlen != sizeof(va[0])) e(0);
+ if (i == 0 && va[0] != -1) e(0);
+ if (i > 0 && va[0] >= lastva) e(0);
+ if (va[1] != -1) e(0);
+ lastva = va[0];
+ }
+
+ /* Test retrieval with a length that is too long. */
+ oldlen = sizeof(va[0]) + 1;
+ va[0] = -1;
+ if (sysctl(mib, 3, va, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(va[0])) e(0);
+ if (va[0] != 0x01020304) e(0);
+ if (va[1] != -1) e(0);
+
+ oldlen = SSIZE_MAX;
+ va[0] = -1;
+ if (sysctl(mib, 3, va, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(va[0])) e(0);
+ if (va[0] != 0x01020304) e(0);
+ if (va[1] != -1) e(0);
+
+ oldlen = SIZE_MAX;
+ va[0] = -1;
+ if (sysctl(mib, 3, va, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(va[0])) e(0);
+ if (va[0] != 0x01020304) e(0);
+ if (va[1] != -1) e(0);
+
+ /*
+ * Ensure that we cannot overwrite this read-only integer. A write
+ * request must have both a pointer and a nonzero length, though.
+ */
+ va[0] = 0x05060708;
+ if (sysctl(mib, 3, NULL, NULL, NULL, 1) != 0) e(0);
+ if (sysctl(mib, 3, NULL, NULL, va, 0) != 0) e(0);
+ if (sysctl(mib, 3, NULL, NULL, va, sizeof(va[0])) != -1) e(0);
+ if (errno != EPERM) e(0);
+
+ oldlen = sizeof(va[0]);
+ va[0] = -1;
+ if (sysctl(mib, 3, va, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(va[0])) e(0);
+ if (va[0] != 0x01020304) e(0);
+ if (va[1] != -1) e(0);
+
+ /* Test retrieval into a bad pointer. */
+ oldlen = sizeof(int);
+ if (sysctl(mib, 3, bad_ptr, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ /*
+ * Test reading and writing booleans. Booleans may actually be an int,
+ * a char, or just one bit of a char. As a result, the MIB service can
+ * not test properly for non-bool values being passed in bool fields,
+ * and we can not do effective testing on this either, because in both
+ * cases our efforts may simply be optimized away, and result in
+ * unexpected success.
+ */
+ mib[2] = TEST_BOOL;
+ oldlen = sizeof(b);
+ if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(b)) e(0);
+ if (b != false && b != true) e(0);
+
+ b = true;
+ if (sysctl(mib, 3, NULL, &oldlen, &b, sizeof(b)) != 0) e(0);
+ if (oldlen != sizeof(b)) e(0);
+
+ b = false;
+ if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(b)) e(0);
+ if (b != true) e(0);
+
+ b = false;
+ b2 = false;
+ oldlen = sizeof(b2);
+ if (sysctl(mib, 3, &b2, &oldlen, &b, sizeof(b)) != 0) e(0);
+ if (oldlen != sizeof(b2)) e(0);
+ if (b != false) e(0);
+ if (b2 != true) e(0);
+
+ if (sysctl(mib, 3, NULL, NULL, &b, sizeof(b) + 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ /*
+ * The MIB service does not support value swaps. If we pass in the
+ * same buffer for old and new data, we expect that the old data stays.
+ */
+ b = true;
+ oldlen = sizeof(b);
+ if (sysctl(mib, 3, &b, &oldlen, &b, sizeof(b)) != 0) e(0);
+ if (oldlen != sizeof(b)) e(0);
+ if (b != false) e(0);
+
+ b = true;
+ oldlen = sizeof(b);
+ if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(b)) e(0);
+ if (b != false) e(0);
+
+ /* Test reading and writing a quad. */
+ mib[2] = TEST_QUAD;
+ if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(q)) e(0);
+
+ q = 0x1234567890abcdefULL;
+ if (sysctl(mib, 3, NULL, NULL, &q, sizeof(q)) != 0) e(0);
+
+ q = 0ULL;
+ oldlen = sizeof(q);
+ if (sysctl(mib, 3, &q, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(q)) e(0);
+ if (q != 0x1234567890abcdefULL) e(0);
+
+ q = ~0ULL;
+ if (sysctl(mib, 3, NULL, NULL, &q, sizeof(q)) != 0) e(0);
+
+ /* Test writing with a bad pointer. The value must stay. */
+ if (sysctl(mib, 3, NULL, NULL, bad_ptr, sizeof(q)) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ q = 0ULL;
+ oldlen = sizeof(q);
+ if (sysctl(mib, 3, &q, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(q)) e(0);
+ if (q != ~0ULL) e(0);
+
+ q = 0ULL;
+ if (sysctl(mib, 3, NULL, NULL, &q, sizeof(q)) != 0) e(0);
+
+ q = 1ULL;
+ oldlen = sizeof(q);
+ if (sysctl(mib, 3, &q, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(q)) e(0);
+ if (q != 0ULL) e(0);
+
+ /* Test reading and writing a string. */
+ mib[2] = TEST_STRING;
+ strlcpy(buf, "test", sizeof(buf));
+ len = strlen(buf);
+ if (sysctl(mib, 3, NULL, NULL, buf, len + 1) != 0) e(0);
+
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != len + 1) e(0);
+
+ memset(buf, 0x07, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (strcmp(buf, "test")) e(0);
+ if (oldlen != len + 1) e(0);
+ if (buf[len + 1] != 0x07) e(0);
+
+ strlcpy(buf, "abc123", sizeof(buf));
+ oldlen = 2;
+ if (sysctl(mib, 3, NULL, &oldlen, buf, strlen(buf) + 1) != 0) e(0);
+ if (oldlen != len + 1) e(0);
+ len = strlen(buf);
+
+ memset(buf, 0x07, sizeof(buf));
+ oldlen = len - 1;
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != ENOMEM) e(0);
+ if (oldlen != len + 1) e(0);
+ if (strncmp(buf, "abc12", len - 1)) e(0);
+ if (buf[len - 1] != 0x07 || buf[len] != 0x07) e(0);
+
+ memset(buf, 0x07, sizeof(buf));
+ oldlen = len + 1;
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != len + 1) e(0);
+ if (strcmp(buf, "abc123")) e(0);
+
+ /*
+ * Now put in a shorter string, without null terminator. The string
+ * must be accepted; the null terminator must be added automatically.
+ */
+ strlcpy(buf, "foolproof", sizeof(buf));
+ len = strlen("foo");
+ if (sysctl(mib, 3, NULL, NULL, buf, len) != 0) e(0);
+
+ if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != len + 1) e(0);
+
+ memset(buf, 0x07, sizeof(buf));
+ oldlen = len;
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != ENOMEM) e(0);
+ if (oldlen != len + 1) e(0);
+ if (strncmp(buf, "foo", len)) e(0);
+ if (buf[len] != 0x07) e(0);
+
+ memset(buf, 0x07, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != len + 1) e(0);
+ if (strcmp(buf, "foo")) e(0);
+ if (buf[len + 1] != 0x07) e(0);
+
+ /*
+ * Passing in more data after the string is fine, but whatever comes
+ * after the first null terminator is disregarded.
+ */
+ strlcpy(buf, "barbapapa", sizeof(buf));
+ len = strlen(buf);
+ buf[3] = '\0';
+ if (sysctl(mib, 3, NULL, NULL, buf, len + 1)) e(0);
+ len = strlen(buf);
+
+ if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != len + 1) e(0);
+
+ memset(buf, 0x07, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != len + 1) e(0);
+ if (strcmp(buf, "bar")) e(0);
+ if (buf[len + 1] != 0x07) e(0);
+
+ /* Test the maximum string length. */
+ strlcpy(buf, "0123456789abcdef", sizeof(buf));
+ len = strlen(buf);
+ if (sysctl(mib, 3, NULL, NULL, buf, len + 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+ if (sysctl(mib, 3, NULL, NULL, buf, len) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ buf[--len] = '\0';
+ if (sysctl(mib, 3, NULL, NULL, buf, len + 1) != 0) e(0);
+ memset(buf, 0x07, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != len + 1) e(0);
+ if (strcmp(buf, "0123456789abcde")) e(0);
+ if (buf[len + 1] != 0x07) e(0);
+
+ /*
+ * Clearing out the field with zero-length data is not possible,
+ * because zero-length updates are disregarded at a higher level.
+ */
+ if (sysctl(mib, 3, NULL, NULL, "", 0) != 0) e(0);
+ memset(buf, 0x07, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != len + 1) e(0);
+ if (strcmp(buf, "0123456789abcde")) e(0);
+
+ /* To clear the field, the null terminator is required. */
+ if (sysctl(mib, 3, NULL, NULL, "", 1) != 0) e(0);
+ memset(buf, 0x07, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != 1) e(0);
+ if (buf[0] != '\0') e(0);
+ if (buf[1] != 0x07) e(0);
+
+ /*
+ * Test reading and writing structures. Structures are just blobs of
+ * data, with no special handling by default. They can only be read
+ * and written all at once.
+ */
+ mib[2] = TEST_STRUCT;
+ if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != 12) e(0);
+ len = oldlen;
+
+ for (i = 0; i < len + 1; i++)
+ buf[i] = i + 1;
+ if (sysctl(mib, 3, NULL, NULL, buf, len - 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+ if (sysctl(mib, 3, NULL, NULL, buf, len + 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+ if (sysctl(mib, 3, NULL, NULL, buf, len) != 0) e(0);
+
+ memset(buf, 0x7f, sizeof(buf));
+ oldlen = len - 1;
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != ENOMEM) e(0);
+ if (oldlen != len) e(0);
+ for (i = 0; i < len - 1; i++)
+ if (buf[i] != i + 1) e(0);
+ if (buf[i] != 0x7f) e(0);
+
+ memset(buf, 0x7f, sizeof(buf));
+ oldlen = len + 1;
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != len) e(0);
+ for (i = 0; i < len; i++)
+ if (buf[i] != i + 1) e(0);
+ if (buf[i] != 0x7f) e(0);
+
+ memset(buf, 0x7f, sizeof(buf));
+ oldlen = len;
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ for (i = 0; i < len; i++)
+ if (buf[i] != i + 1) e(0);
+ if (buf[len] != 0x7f) e(0);
+
+ /* Null characters are not treated in any special way. */
+ for (i = 0; i < len; i++)
+ buf[i] = !!i;
+ if (sysctl(mib, 3, NULL, NULL, buf, len) != 0) e(0);
+
+ oldlen = len;
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != len) e(0);
+ for (i = 0; i < len; i++)
+ if (buf[i] != !!i) e(0);
+ if (buf[len] != 0x7f) e(0);
+
+ memset(buf, 0, len);
+ if (sysctl(mib, 3, NULL, NULL, buf, len) != 0) e(0);
+
+ oldlen = len;
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != len) e(0);
+ for (i = 0; i < len; i++)
+ if (buf[i] != 0) e(0);
+ if (buf[len] != 0x7f) e(0);
+
+ /*
+ * Test private read and free-for-all write operations. For starters,
+ * this test should run with superuser privileges, and thus should be
+ * able to read and write private fields.
+ */
+ mib[2] = TEST_PRIVATE;
+ if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(va[0])) e(0);
+ if (sysctl(mib, 3, va, &oldlen, NULL, 0) != 0) e(0);
+ if (va[0] != -5375) e(0);
+ if (sysctl(mib, 3, NULL, NULL, va, sizeof(va[0])) != 0) e(0);
+
+ mib[2] = TEST_SECRET;
+ mib[3] = SECRET_VALUE;
+ oldlen = sizeof(va[0]);
+ if (sysctl(mib, 4, va, &oldlen, NULL, 0) != 0) e(0);
+ if (va[0] != 12345) e(0);
+ if (sysctl(mib, 4, NULL, NULL, va, sizeof(va[0])) != -1) e(0);
+ if (errno != EPERM) e(0);
+
+ mib[3]++;
+ i = 0;
+ oldlen = sizeof(i);
+ if (sysctl(mib, 4, &i, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != ENOENT) e(0);
+
+ /* Use a child process to test operations without root privileges. */
+ pid = test_nonroot(sub87a);
+
+ /* The change made by the child should be visible to the parent. */
+ mib[2] = TEST_ANYWRITE;
+ va[0] = 0;
+ oldlen = sizeof(va[0]);
+ if (sysctl(mib, 3, va, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(va[0])) e(0);
+ if (va[0] != pid) e(0);
+}
+
+/*
+ * Test queries from an unprivileged process.
+ */
+static void
+sub87b(void)
+{
+ struct sysctlnode scn[32];
+ unsigned int count;
+ size_t oldlen;
+ int i, mib[4];
+
+ /* Query minix.test and make sure we do not get privileged values. */
+ mib[0] = CTL_MINIX;
+ mib[1] = MINIX_TEST;
+ mib[2] = CTL_QUERY;
+
+ oldlen = sizeof(scn);
+ if (sysctl(mib, 3, scn, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen % sizeof(scn[0])) e(0);
+ count = oldlen / sizeof(scn[0]);
+ if (count < 8) e(0);
+
+ /*
+ * Do not bother doing the entire check again, but test enough to
+ * inspire confidence that only the right values are hidden.
+ */
+ if (scn[0].sysctl_num != TEST_INT) e(0);
+ if (SYSCTL_TYPE(scn[0].sysctl_flags) != CTLTYPE_INT) e(0);
+ if (SYSCTL_FLAGS(scn[0].sysctl_flags) !=
+ (CTLFLAG_READONLY | CTLFLAG_IMMEDIATE | CTLFLAG_HEX)) e(0);
+ if (SYSCTL_VERS(scn[0].sysctl_flags) != SYSCTL_VERSION) e(0);
+ if (strcmp(scn[0].sysctl_name, "int")) e(0);
+ if (scn[0].sysctl_ver == 0) e(0);
+ if (scn[0].sysctl_size != sizeof(int)) e(0);
+ if (scn[0].sysctl_idata != 0x01020304) e(0);
+
+ for (i = 0; i < count; i++)
+ if (scn[i].sysctl_num == TEST_PRIVATE)
+ break;
+ if (i == count) e(0);
+ if (SYSCTL_TYPE(scn[i].sysctl_flags) != CTLTYPE_INT) e(0);
+ if (SYSCTL_FLAGS(scn[i].sysctl_flags) !=
+ (CTLFLAG_READWRITE | CTLFLAG_PRIVATE | CTLFLAG_IMMEDIATE)) e(0);
+ if (SYSCTL_VERS(scn[i].sysctl_flags) != SYSCTL_VERSION) e(0);
+ if (strcmp(scn[i].sysctl_name, "private")) e(0);
+ if (scn[i].sysctl_size != sizeof(int)) e(0);
+ if (scn[i].sysctl_idata != 0) e(0); /* private */
+
+ for (i = 0; i < count; i++)
+ if (scn[i].sysctl_num == TEST_SECRET)
+ break;
+ if (i == count) e(0);
+ if (SYSCTL_TYPE(scn[i].sysctl_flags) != CTLTYPE_NODE) e(0);
+ if (SYSCTL_FLAGS(scn[i].sysctl_flags) !=
+ (CTLFLAG_READONLY | CTLFLAG_PRIVATE)) e(0);
+ if (SYSCTL_VERS(scn[i].sysctl_flags) != SYSCTL_VERSION) e(0);
+ if (strcmp(scn[i].sysctl_name, "secret")) e(0);
+ if (scn[i].sysctl_ver == 0) e(0);
+ if (scn[i].sysctl_size != sizeof(scn[0])) e(0);
+ if (scn[i].sysctl_csize != 0) e(0); /* private */
+ if (scn[i].sysctl_clen != 0) e(0); /* private */
+
+ /* Make sure that a query on minix.test.secret fails. */
+ mib[2] = TEST_SECRET;
+ mib[3] = CTL_QUERY;
+ if (sysctl(mib, 4, NULL, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != EPERM) e(0);
+}
+
+/*
+ * Test sysctl(2) queries.
+ */
+static void
+test87b(void)
+{
+ struct sysctlnode scn[32];
+ unsigned int count;
+ size_t len, oldlen;
+ u_quad_t q;
+ bool b;
+ int i, mib[4];
+
+ subtest = 1;
+
+ /* We should be able to query the root key. */
+ mib[0] = CTL_QUERY;
+
+ oldlen = 0;
+ if (sysctl(mib, 1, NULL, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen <= sizeof(scn[0])) e(0);
+ if (oldlen % sizeof(scn[0])) e(0);
+
+ oldlen = sizeof(scn[0]);
+ if (sysctl(mib, 1, scn, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != ENOMEM);
+ if (oldlen <= sizeof(scn[0])) e(0);
+ if (oldlen % sizeof(scn[0])) e(0);
+
+ /*
+ * We assume that the root node's first child is always CTL_KERN, which
+ * must be read-only and may have only the CTLFLAG_PERMANENT flag set.
+ */
+ if (scn[0].sysctl_num != CTL_KERN) e(0);
+ if (SYSCTL_TYPE(scn[0].sysctl_flags) != CTLTYPE_NODE) e(0);
+ if ((SYSCTL_FLAGS(scn[0].sysctl_flags) & ~CTLFLAG_PERMANENT) !=
+ CTLFLAG_READONLY) e(0);
+ if (SYSCTL_VERS(scn[0].sysctl_flags) != SYSCTL_VERSION) e(0);
+ if (strcmp(scn[0].sysctl_name, "kern")) e(0);
+ if (scn[0].sysctl_ver == 0) e(0);
+ if (scn[0].sysctl_size != sizeof(scn[0])) e(0);
+ if ((int)scn[0].sysctl_csize <= 0) e(0);
+ if ((int)scn[0].sysctl_clen <= 0) e(0);
+ if (scn[0].sysctl_csize < scn[0].sysctl_clen) e(0);
+
+ /* Now do a more complete test on the minix.test subtree. */
+ mib[0] = CTL_MINIX;
+ mib[1] = MINIX_TEST;
+
+ /*
+ * Initialize a few immediate fields to nonzero so that we can test
+ * that their values are returned as a result of the query.
+ */
+ mib[2] = TEST_BOOL;
+ b = true;
+ if (sysctl(mib, 3, NULL, NULL, &b, sizeof(b)) != 0) e(0);
+
+ mib[2] = TEST_QUAD;
+ q = ~0;
+ if (sysctl(mib, 3, NULL, NULL, &q, sizeof(q)) != 0) e(0);
+
+ mib[2] = CTL_QUERY;
+
+ oldlen = 1;
+ if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen % sizeof(scn[0])) e(0);
+ if (oldlen >= sizeof(scn)) e(0);
+ len = oldlen;
+ count = len / sizeof(scn[0]);
+ if (count < 8) e(0);
+
+ memset(scn, 0x7e, sizeof(scn));
+ if (sysctl(mib, 3, scn, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != len) e(0);
+ if (scn[count].sysctl_name[0] != 0x7e) e(0);
+
+ /*
+ * Again, we rely on the MIB service returning entries in ascending
+ * order for at least the static nodes. We do not make assumptions
+ * about whether dynamic nodes are merged in or (as is the case as of
+ * writing) returned after the static nodes. At this point there
+ * should be no dynamic nodes here yet anyway.
+ */
+ if (scn[0].sysctl_num != TEST_INT) e(0);
+ if (SYSCTL_TYPE(scn[0].sysctl_flags) != CTLTYPE_INT) e(0);
+ if (SYSCTL_FLAGS(scn[0].sysctl_flags) !=
+ (CTLFLAG_READONLY | CTLFLAG_IMMEDIATE | CTLFLAG_HEX)) e(0);
+ if (SYSCTL_VERS(scn[0].sysctl_flags) != SYSCTL_VERSION) e(0);
+ if (strcmp(scn[0].sysctl_name, "int")) e(0);
+ if (scn[0].sysctl_ver == 0) e(0);
+ if (scn[0].sysctl_size != sizeof(int)) e(0);
+ if (scn[0].sysctl_idata != 0x01020304) e(0);
+
+ if (scn[1].sysctl_num != TEST_BOOL) e(0);
+ if (SYSCTL_TYPE(scn[1].sysctl_flags) != CTLTYPE_BOOL) e(0);
+ if (SYSCTL_FLAGS(scn[1].sysctl_flags) !=
+ (CTLFLAG_READWRITE | CTLFLAG_IMMEDIATE)) e(0);
+ if (SYSCTL_VERS(scn[1].sysctl_flags) != SYSCTL_VERSION) e(0);
+ if (strcmp(scn[1].sysctl_name, "bool")) e(0);
+ if (scn[1].sysctl_ver == 0) e(0);
+ if (scn[1].sysctl_size != sizeof(bool)) e(0);
+ if (scn[1].sysctl_bdata != true) e(0);
+
+ if (scn[2].sysctl_num != TEST_QUAD) e(0);
+ if (SYSCTL_TYPE(scn[2].sysctl_flags) != CTLTYPE_QUAD) e(0);
+ if (SYSCTL_FLAGS(scn[2].sysctl_flags) !=
+ (CTLFLAG_READWRITE | CTLFLAG_IMMEDIATE)) e(0);
+ if (SYSCTL_VERS(scn[2].sysctl_flags) != SYSCTL_VERSION) e(0);
+ if (strcmp(scn[2].sysctl_name, "quad")) e(0);
+ if (scn[2].sysctl_ver == 0) e(0);
+ if (scn[2].sysctl_size != sizeof(u_quad_t)) e(0);
+ if (scn[2].sysctl_qdata != q) e(0);
+
+ if (scn[3].sysctl_num != TEST_STRING) e(0);
+ if (SYSCTL_TYPE(scn[3].sysctl_flags) != CTLTYPE_STRING) e(0);
+ if (SYSCTL_FLAGS(scn[3].sysctl_flags) != CTLFLAG_READWRITE) e(0);
+ if (SYSCTL_VERS(scn[3].sysctl_flags) != SYSCTL_VERSION) e(0);
+ if (strcmp(scn[3].sysctl_name, "string")) e(0);
+ if (scn[3].sysctl_ver == 0) e(0);
+ if (scn[3].sysctl_size != 16) e(0);
+
+ if (scn[4].sysctl_num != TEST_STRUCT) e(0);
+ if (SYSCTL_TYPE(scn[4].sysctl_flags) != CTLTYPE_STRUCT) e(0);
+ if (SYSCTL_FLAGS(scn[4].sysctl_flags) != CTLFLAG_READWRITE) e(0);
+ if (SYSCTL_VERS(scn[4].sysctl_flags) != SYSCTL_VERSION) e(0);
+ if (strcmp(scn[4].sysctl_name, "struct")) e(0);
+ if (scn[4].sysctl_ver == 0) e(0);
+ if (scn[4].sysctl_size != 12) e(0);
+
+ if (scn[5].sysctl_num != TEST_PRIVATE) e(0);
+ if (SYSCTL_TYPE(scn[5].sysctl_flags) != CTLTYPE_INT) e(0);
+ if (SYSCTL_FLAGS(scn[5].sysctl_flags) !=
+ (CTLFLAG_READWRITE | CTLFLAG_PRIVATE | CTLFLAG_IMMEDIATE)) e(0);
+ if (SYSCTL_VERS(scn[5].sysctl_flags) != SYSCTL_VERSION) e(0);
+ if (strcmp(scn[5].sysctl_name, "private")) e(0);
+ if (scn[5].sysctl_ver == 0) e(0);
+ if (scn[5].sysctl_size != sizeof(int)) e(0);
+ if (scn[5].sysctl_idata != -5375) e(0);
+
+ if (scn[6].sysctl_num != TEST_ANYWRITE) e(0);
+ if (SYSCTL_TYPE(scn[6].sysctl_flags) != CTLTYPE_INT) e(0);
+ if (SYSCTL_FLAGS(scn[6].sysctl_flags) !=
+ (CTLFLAG_READWRITE | CTLFLAG_ANYWRITE | CTLFLAG_IMMEDIATE)) e(0);
+ if (SYSCTL_VERS(scn[6].sysctl_flags) != SYSCTL_VERSION) e(0);
+ if (strcmp(scn[6].sysctl_name, "anywrite")) e(0);
+ if (scn[6].sysctl_ver == 0) e(0);
+ if (scn[6].sysctl_size != sizeof(int)) e(0);
+
+ i = (scn[7].sysctl_num == TEST_DYNAMIC) ? 8 : 7;
+
+ if (scn[i].sysctl_num != TEST_SECRET) e(0);
+ if (SYSCTL_TYPE(scn[i].sysctl_flags) != CTLTYPE_NODE) e(0);
+ if (SYSCTL_FLAGS(scn[i].sysctl_flags) !=
+ (CTLFLAG_READONLY | CTLFLAG_PRIVATE)) e(0);
+ if (SYSCTL_VERS(scn[i].sysctl_flags) != SYSCTL_VERSION) e(0);
+ if (strcmp(scn[i].sysctl_name, "secret")) e(0);
+ if (scn[i].sysctl_ver == 0) e(0);
+ if (scn[i].sysctl_size != sizeof(scn[0])) e(0);
+ if (scn[i].sysctl_csize != 1) e(0);
+ if (scn[i].sysctl_clen != 1) e(0);
+
+ /*
+ * Now that we know how many entries there are in minix.test, also look
+ * at whether the right child length is returned in a query on its
+ * parent. While doing that, see whether data structure versioning
+ * works as expected as well. MINIX_TEST is hardcoded to zero so we
+ * expect it to be the first entry returned from a query.
+ */
+ mib[1] = CTL_QUERY;
+
+ memset(scn, 0, sizeof(scn));
+ scn[1].sysctl_flags = SYSCTL_VERS_0;
+ if (sysctl(mib, 2, NULL, &oldlen, &scn[1], sizeof(scn[1])) != -1) e(0);
+ if (errno != EINVAL) e(0);
+ scn[1].sysctl_flags = SYSCTL_VERS_1;
+ if (sysctl(mib, 2, NULL, &oldlen, &scn[1], sizeof(scn[1]) - 1) != -1)
+ e(0);
+ if (errno != EINVAL) e(0);
+ if (sysctl(mib, 2, NULL, &oldlen, &scn[1], sizeof(scn[1]) + 1) != -1)
+ e(0);
+ if (errno != EINVAL) e(0);
+ if (sysctl(mib, 2, NULL, &oldlen, &scn[1], sizeof(scn[1])) != 0) e(0);
+ if (oldlen == 0) e(0);
+ if (oldlen % sizeof(scn[0])) e(0);
+
+ oldlen = sizeof(scn[0]);
+ scn[1].sysctl_flags = SYSCTL_VERS_0;
+ if (sysctl(mib, 2, scn, &oldlen, &scn[1], sizeof(scn[1])) != -1) e(0);
+ if (errno != EINVAL) e(0);
+ oldlen = sizeof(scn[0]);
+ scn[1].sysctl_flags = SYSCTL_VERS_1;
+ if (sysctl(mib, 2, scn, &oldlen, &scn[1], sizeof(scn[1])) != 0 &&
+ errno != ENOMEM) e(0);
+ if (oldlen == 0) e(0);
+ if (oldlen % sizeof(scn[0])) e(0);
+
+ if (scn[0].sysctl_num != MINIX_TEST) e(0);
+ if (SYSCTL_TYPE(scn[0].sysctl_flags) != CTLTYPE_NODE) e(0);
+ if ((SYSCTL_FLAGS(scn[0].sysctl_flags) & ~CTLFLAG_PERMANENT) !=
+ (CTLFLAG_READWRITE | CTLFLAG_HIDDEN)) e(0);
+ if (SYSCTL_VERS(scn[0].sysctl_flags) != SYSCTL_VERSION) e(0);
+ if (strcmp(scn[0].sysctl_name, "test")) e(0);
+ if (scn[0].sysctl_ver == 0) e(0);
+ if (scn[0].sysctl_size != sizeof(scn[0])) e(0);
+ if ((int)scn[0].sysctl_clen != count) e(0);
+ if (scn[0].sysctl_csize < scn[0].sysctl_clen) e(0);
+
+ /*
+ * Test querying minix.test.secret, which should have exactly one node.
+ * At the same time, test bad pointers.
+ */
+ mib[1] = MINIX_TEST;
+ mib[2] = TEST_SECRET;
+ mib[3] = CTL_QUERY;
+ oldlen = sizeof(scn);
+ if (sysctl(mib, 4, NULL, &oldlen, bad_ptr, sizeof(scn[0])) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ oldlen = sizeof(scn[0]) * 2;
+ if (sysctl(mib, 4, bad_ptr, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ memset(scn, 0x7, sizeof(scn[0]) * 2);
+ oldlen = sizeof(scn[0]) * 2;
+ if (sysctl(mib, 4, scn, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(scn[0])) e(0);
+
+ if (scn[0].sysctl_num != SECRET_VALUE) e(0);
+ if (SYSCTL_TYPE(scn[0].sysctl_flags) != CTLTYPE_INT) e(0);
+ if (SYSCTL_FLAGS(scn[0].sysctl_flags) !=
+ (CTLFLAG_READONLY | CTLFLAG_IMMEDIATE)) e(0);
+ if (SYSCTL_VERS(scn[0].sysctl_flags) != SYSCTL_VERSION) e(0);
+ if (strcmp(scn[0].sysctl_name, "value")) e(0);
+ if (scn[0].sysctl_ver == 0) e(0);
+ if (scn[0].sysctl_size != sizeof(int)) e(0);
+ if (scn[0].sysctl_idata != 12345) e(0);
+ if (scn[1].sysctl_name[0] != 0x07) e(0);
+
+ /* Use a child process to test queries without root privileges. */
+ (void)test_nonroot(sub87b);
+
+ /* Do some more path-related error code tests unrelated to the rest. */
+ mib[1] = INT_MAX;
+ mib[2] = CTL_QUERY;
+ oldlen = sizeof(scn[0]);
+ if (sysctl(mib, 3, &scn, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != ENOENT) e(0);
+
+ mib[1] = MINIX_TEST;
+ mib[2] = TEST_INT;
+ mib[3] = CTL_QUERY;
+ oldlen = sizeof(scn[0]);
+ if (sysctl(mib, 4, &scn, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != ENOTDIR) e(0); /* ..and not EPERM (_INT is read-only) */
+
+ mib[2] = TEST_BOOL;
+ oldlen = sizeof(scn[0]);
+ if (sysctl(mib, 4, &scn, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != ENOTDIR) e(0); /* (_BOOL is read-write) */
+
+ mib[2] = CTL_QUERY;
+ oldlen = sizeof(scn[0]);
+ if (sysctl(mib, 4, &scn, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != EINVAL) e(0);
+}
+
+/*
+ * Attempt to create a node, using a given node template, identifier, and name
+ * string. If other_id is nonnegative, the creation is expected to fail due to
+ * a collision with an existing node, which should have the ID other_id and the
+ * name string in other_name. Otherwise, the creation may succeed or fail, and
+ * the caller must perform the appropriate checks. On success, return the new
+ * node identifier. On failure, return -1, with errno set.
+ */
+static int
+create_node(const int * path, unsigned int pathlen, struct sysctlnode * tmpscn,
+ int id, const char * name, int other_id, const char * other_name)
+{
+ struct sysctlnode scn, oldscn;
+ size_t oldlen;
+ int r, mib[CTL_MAXNAME];
+
+ assert(pathlen < CTL_MAXNAME);
+ memcpy(mib, path, sizeof(mib[0]) * pathlen);
+ mib[pathlen] = CTL_CREATE;
+
+ memcpy(&scn, tmpscn, sizeof(scn));
+ scn.sysctl_num = id;
+ strlcpy(scn.sysctl_name, name, sizeof(scn.sysctl_name));
+ oldlen = sizeof(oldscn);
+ r = sysctl(mib, pathlen + 1, &oldscn, &oldlen, &scn, sizeof(scn));
+ if (other_id >= 0) { /* conflict expected */
+ if (oldlen != sizeof(oldscn)) e(0);
+ if (r != -1) e(0);
+ if (errno != EEXIST) e(0);
+ if (oldscn.sysctl_num != other_id) e(0);
+ if (strcmp(oldscn.sysctl_name, other_name)) e(0);
+ return -1;
+ } else {
+ if (r != 0)
+ return r;
+ if (oldlen != sizeof(oldscn)) e(0);
+ return oldscn.sysctl_num;
+ }
+}
+
+/*
+ * Destroy a node by identifier in the given named node directory. Return 0 on
+ * success. Return -1 on failure, with errno set.
+ */
+static int
+destroy_node(const int * path, unsigned int pathlen, int id)
+{
+ struct sysctlnode scn;
+ int mib[CTL_MAXNAME];
+
+ assert(pathlen < CTL_MAXNAME);
+ memcpy(mib, path, sizeof(mib[0]) * pathlen);
+ mib[pathlen] = CTL_DESTROY;
+
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION;
+ scn.sysctl_num = id;
+
+ return sysctl(mib, pathlen + 1, NULL, NULL, &scn, sizeof(scn));
+}
+
+/*
+ * Obtain the node data for one particular node in a node directory, by its
+ * parent path and identifier. Return 0 on success, with the node details
+ * stored in 'scn', or -1 on failure.
+ */
+static int
+query_node(const int * path, unsigned int pathlen, int id,
+ struct sysctlnode * scn)
+{
+ struct sysctlnode scnset[32];
+ size_t oldlen;
+ unsigned int i;
+ int r, mib[CTL_MAXNAME];
+
+ assert(pathlen < CTL_MAXNAME);
+ memcpy(mib, path, sizeof(mib[0]) * pathlen);
+ mib[pathlen] = CTL_QUERY;
+
+ oldlen = sizeof(scnset);
+ if ((r = sysctl(mib, pathlen + 1, scnset, &oldlen, NULL, 0)) != 0 &&
+ errno != ENOMEM) e(0);
+ if (oldlen == 0 || oldlen % sizeof(scnset[0])) e(0);
+ for (i = 0; i < oldlen / sizeof(scnset[0]); i++)
+ if (scnset[i].sysctl_num == id)
+ break;
+ if (i == oldlen / sizeof(scnset[0])) {
+ if (r != 0) e(0); /* if this triggers, make scnset[] bigger! */
+ return -1;
+ }
+ memcpy(scn, &scnset[i], sizeof(*scn));
+ return 0;
+}
+
+/*
+ * Test unprivileged node creation.
+ */
+static void
+sub87c(void)
+{
+ struct sysctlnode scn;
+ int mib[4];
+
+ mib[0] = CTL_MINIX;
+ mib[1] = MINIX_TEST;
+ mib[2] = TEST_DYNAMIC;
+ mib[3] = CTL_CREATE;
+
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_IMMEDIATE |
+ CTLFLAG_READONLY | CTLTYPE_INT;
+ scn.sysctl_size = sizeof(int);
+ scn.sysctl_num = CTL_CREATE;
+ scn.sysctl_idata = 777;
+ strlcpy(scn.sysctl_name, "nonroot", sizeof(scn.sysctl_name));
+ if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EPERM) e(0);
+
+ mib[0] = CTL_CREATE;
+ scn.sysctl_num = CTL_MINIX + 1;
+ if (sysctl(mib, 1, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EPERM) e(0);
+}
+
+/*
+ * Test sysctl(2) node creation.
+ */
+static void
+test87c(void)
+{
+ static const uint32_t badflags[] = {
+ SYSCTL_VERS_MASK, SYSCTL_TYPEMASK, CTLFLAG_PERMANENT,
+ CTLFLAG_ROOT, CTLFLAG_ANYNUMBER, CTLFLAG_ALIAS, CTLFLAG_MMAP,
+ CTLFLAG_OWNDESC
+ };
+ static const size_t badintsizes[] = {
+ 0, 1, sizeof(int) - 1, sizeof(int) + 1, sizeof(int) * 2,
+ sizeof(int) * 4, SSIZE_MAX, SIZE_MAX
+ };
+ static const char *goodnames[] = {
+ "_", "a", "test_name", "_____foo", "bar_0_1_2_3", "_2bornot2b",
+ "abcdefghijklmnopqrstuvwxyz12345",
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ67890",
+ };
+ static const char *badnames[] = {
+ "", "0", "test.name", "2bornot2b", "@a", "b[", "c`d", "{",
+ "\n", "\xff", "dir/name", "foo:bar",
+ "abcdefghijklmnopqrstuvwxyz123456"
+ };
+ struct sysctlnode scn, pscn, oldscn, newscn, tmpscn, scnset[32];
+ size_t oldlen, len;
+ char buf[32], seen[5];
+ bool b;
+ u_quad_t q;
+ int i, mib[CTL_MAXNAME], id[3];
+
+ subtest = 2;
+
+ /*
+ * On the first run of this test, this call with actually destroy a
+ * static node. On subsequent runs, it may clean up the most likely
+ * leftover from a previous failed test.
+ */
+ mib[0] = CTL_MINIX;
+ mib[1] = MINIX_TEST;
+ (void)destroy_node(mib, 2, TEST_DYNAMIC);
+
+ /* Get child statistics about the parent node, for later comparison. */
+ if (query_node(mib, 1, MINIX_TEST, &pscn) != 0) e(0);
+ if (pscn.sysctl_clen == 0) e(0);
+ if (pscn.sysctl_csize <= pscn.sysctl_clen) e(0);
+
+ /* Start by testing if we can actually create a node at all. */
+ mib[2] = CTL_CREATE;
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_IMMEDIATE |
+ CTLFLAG_READONLY | CTLTYPE_INT;
+ scn.sysctl_size = sizeof(int);
+ scn.sysctl_num = TEST_DYNAMIC;
+ scn.sysctl_idata = 777;
+ strlcpy(scn.sysctl_name, "dynamic", sizeof(scn.sysctl_name));
+ oldlen = sizeof(newscn);
+ if (sysctl(mib, 3, &newscn, &oldlen, &scn, sizeof(scn)) != 0) e(0);
+ if (oldlen != sizeof(newscn)) e(0);
+
+ memcpy(&tmpscn, &scn, sizeof(scn));
+
+ if (newscn.sysctl_num != TEST_DYNAMIC) e(0);
+ if (SYSCTL_TYPE(newscn.sysctl_flags) != CTLTYPE_INT) e(0);
+ if (SYSCTL_FLAGS(newscn.sysctl_flags) !=
+ (CTLFLAG_READONLY | CTLFLAG_IMMEDIATE)) e(0);
+ if (SYSCTL_VERS(newscn.sysctl_flags) != SYSCTL_VERSION) e(0);
+ if (strcmp(newscn.sysctl_name, "dynamic")) e(0);
+ if (newscn.sysctl_ver == 0) e(0);
+ if (newscn.sysctl_size != sizeof(int)) e(0);
+ if (newscn.sysctl_idata != 777) e(0);
+
+ /* Can we also read its value? */
+ mib[2] = TEST_DYNAMIC;
+ i = 0;
+ oldlen = sizeof(i);
+ if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(i)) e(0);
+ if (i != 777) e(0);
+
+ /* For now, we assume that basic node destruction works. */
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /* Try some variants of invalid new node data. */
+ mib[2] = CTL_CREATE;
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ if (sysctl(mib, 3, NULL, NULL, NULL, 0) != -1) e(0);
+ if (errno != EINVAL) e(0);
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn) - 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn) + 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+ if (sysctl(mib, 3, NULL, NULL, bad_ptr, sizeof(scn)) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ /* Try with an invalid flags field. */
+ scn.sysctl_flags =
+ (scn.sysctl_flags & ~SYSCTL_VERS_MASK) | SYSCTL_VERS_0;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_flags &= ~SYSCTL_TYPEMASK; /* type 0 does not exist */
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ for (i = 0; i < __arraycount(badflags); i++) {
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_flags |= badflags[i];
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(i);
+ if (errno != EINVAL) e(i);
+ }
+
+ /* Try successful creation (and destruction) once more. */
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /* Try a combination of most valid flags. */
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_flags &= ~CTLFLAG_READONLY; /* noop */
+ scn.sysctl_flags |= CTLFLAG_READWRITE | CTLFLAG_ANYWRITE |
+ CTLFLAG_PRIVATE | CTLFLAG_HEX | CTLFLAG_HIDDEN | CTLFLAG_UNSIGNED;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /* Try invalid integer sizes. We will get to other types in a bit. */
+ for (i = 0; i < __arraycount(badintsizes); i++) {
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_size = badintsizes[i];
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(i);
+ if (errno != EINVAL) e(i);
+ }
+
+ /*
+ * For the value, we can supply IMMEDIATE, OWNDATA, or neither. For
+ * IMMEDIATE, the integer value is taken directly from sysctl_idata.
+ * If OWNDATA is set, sysctl_data may be set, in which case the integer
+ * value is copied in from there. If sysctl_data is NULL, the integer
+ * is initalized to zero. If neither flag is set, sysctl_data must be
+ * NULL, since we do not support kernel addresses, and the integer will
+ * similarly be initialized to zero. If both flags are set, the call
+ * fails with EINVAL.
+ */
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_flags |= CTLFLAG_OWNDATA; /* both flags are now set */
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_flags &= ~(CTLFLAG_IMMEDIATE | CTLFLAG_OWNDATA);
+ scn.sysctl_data = &i;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_data = NULL;
+ oldlen = sizeof(newscn);
+ if (sysctl(mib, 3, &newscn, &oldlen, &scn, sizeof(scn)) != 0) e(0);
+ if (oldlen != sizeof(newscn)) e(0);
+ if (newscn.sysctl_flags & CTLFLAG_IMMEDIATE) e(0);
+ if (!(newscn.sysctl_flags & CTLFLAG_OWNDATA)) e(0); /* auto-set */
+ if (newscn.sysctl_idata != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ oldlen = sizeof(i);
+ if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(i)) e(0);
+ if (i != 0) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ mib[2] = CTL_CREATE;
+ scn.sysctl_flags |= CTLFLAG_OWNDATA;
+ scn.sysctl_data = NULL;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ i = -1;
+ oldlen = sizeof(i);
+ if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(i)) e(0);
+ if (i != 0) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ mib[2] = CTL_CREATE;
+ scn.sysctl_data = bad_ptr;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ i = 999;
+ scn.sysctl_data = (void *)&i;
+ oldlen = sizeof(newscn);
+ if (sysctl(mib, 3, &newscn, &oldlen, &scn, sizeof(scn)) != 0) e(0);
+ if (oldlen != sizeof(newscn)) e(0);
+ if ((newscn.sysctl_flags & (CTLFLAG_IMMEDIATE | CTLFLAG_OWNDATA)) !=
+ CTLFLAG_OWNDATA) e(0);
+ if (newscn.sysctl_idata != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ oldlen = sizeof(i);
+ if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(i)) e(0);
+ if (i != 999) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /* The user may never supply a function pointer or a parent. */
+ mib[2] = CTL_CREATE;
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_func = (sysctlfn)test87c;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_parent = &scn;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ /* Test some good and bad node names. */
+ for (i = 0; i < __arraycount(goodnames); i++) {
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ len = strlen(goodnames[i]);
+ memcpy(scn.sysctl_name, goodnames[i], len);
+ memset(&scn.sysctl_name[len], 0, SYSCTL_NAMELEN - len);
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(i);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(i);
+ }
+
+ for (i = 0; i < __arraycount(badnames); i++) {
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ len = strlen(badnames[i]);
+ memcpy(scn.sysctl_name, badnames[i], len);
+ memset(&scn.sysctl_name[len], 0, SYSCTL_NAMELEN - len);
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(i);
+ if (errno != EINVAL) e(i);
+ }
+
+ /*
+ * Check for ID and name conflicts with existing nodes, starting with
+ * the basics.
+ */
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EEXIST) e(0);
+
+ oldlen = sizeof(oldscn);
+ if (sysctl(mib, 3, &oldscn, &oldlen, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EEXIST) e(0);
+ if (oldlen != sizeof(oldscn)) e(0);
+ if (oldscn.sysctl_ver == 0) e(0);
+ oldscn.sysctl_ver = 0;
+ if (memcmp(&oldscn, &tmpscn, sizeof(oldscn))) e(0);
+
+ oldlen = sizeof(oldscn) - 1;
+ if (sysctl(mib, 3, &oldscn, &oldlen, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EEXIST) e(0); /* ..we should not get ENOMEM now */
+ if (oldlen != sizeof(oldscn)) e(0);
+
+ oldlen = sizeof(oldscn);
+ if (sysctl(mib, 3, bad_ptr, &oldlen, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EEXIST) e(0); /* ..we should not get EFAULT now */
+ if (oldlen != 0) e(0); /* this is arguably an implementation detail */
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /* Test ID and name conflicts against static nodes. */
+ if (create_node(mib, 2, &tmpscn, TEST_INT, "dynamic", TEST_INT,
+ "int") != -1) e(0);
+ if (create_node(mib, 2, &tmpscn, TEST_SECRET, "dynamic", TEST_SECRET,
+ "secret") != -1) e(0);
+ if (create_node(mib, 2, &tmpscn, TEST_DYNAMIC, "quad", TEST_QUAD,
+ "quad") != -1) e(0);
+
+ if (create_node(mib, 2, &tmpscn, TEST_DYNAMIC, "dynamic", -1,
+ NULL) != TEST_DYNAMIC) e(0);
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /* Test unique ID generation and LL back insertion. */
+ if ((id[0] = create_node(mib, 2, &tmpscn, CTL_CREATE, "id0", -1,
+ NULL)) == -1) e(0);
+ if ((id[1] = create_node(mib, 2, &tmpscn, CTL_CREATE, "id1", -1,
+ NULL)) == -1) e(0);
+ if ((id[2] = create_node(mib, 2, &tmpscn, CTL_CREATE, "id2", -1,
+ NULL)) == -1) e(0);
+ if (id[0] < CREATE_BASE || id[1] < CREATE_BASE || id[2] < CREATE_BASE)
+ e(0);
+ if (id[0] == id[1] || id[1] == id[2] || id[0] == id[2]) e(0);
+
+ if (destroy_node(mib, 2, id[1]) != 0) e(0);
+
+ /* Test ID and name conflicts against dynamic nodes. */
+ if (create_node(mib, 2, &tmpscn, id[0], "id1", id[0],
+ "id0") != -1) e(0);
+ if (create_node(mib, 2, &tmpscn, id[2], "id1", id[2],
+ "id2") != -1) e(0);
+ if (create_node(mib, 2, &tmpscn, id[1], "id0", id[0],
+ "id0") != -1) e(0);
+ if (create_node(mib, 2, &tmpscn, id[1], "id2", id[2],
+ "id2") != -1) e(0);
+
+ /* Test name conflicts before and after LL insertion point. */
+ if (create_node(mib, 2, &tmpscn, CTL_CREATE, "id0", id[0],
+ "id0") != -1) e(0);
+ if (create_node(mib, 2, &tmpscn, CTL_CREATE, "id2", id[2],
+ "id2") != -1) e(0);
+
+ /* Test recreation by ID and LL middle insertion. */
+ if (create_node(mib, 2, &tmpscn, id[1], "id1", -1, NULL) == -1) e(0);
+ if (destroy_node(mib, 2, id[1]) != 0) e(0);
+
+ /* Test dynamic recreation and more LL middle insertion. */
+ if ((id[1] = create_node(mib, 2, &tmpscn, CTL_CREATE, "id1", -1,
+ NULL)) == -1) e(0);
+ if (id[1] < CREATE_BASE) e(0);
+ if (id[1] == id[0] || id[1] == id[2]) e(0);
+
+ /* Test LL front insertion. */
+ if (create_node(mib, 2, &tmpscn, TEST_DYNAMIC, "dynamic", -1,
+ NULL) == -1) e(0);
+
+ /* Ensure that all dynamic nodes show up in a query. */
+ mib[2] = CTL_QUERY;
+ oldlen = sizeof(scnset);
+ memset(seen, 0, sizeof(seen));
+ memset(scnset, 0, sizeof(scnset));
+ if (sysctl(mib, 3, scnset, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen % sizeof(scn)) e(0);
+ for (i = 0; (unsigned int)i < oldlen / sizeof(scn); i++) {
+ if (scnset[i].sysctl_num == TEST_INT) {
+ if (strcmp(scnset[i].sysctl_name, "int")) e(0);
+ seen[0]++;
+ } else if (scnset[i].sysctl_num == TEST_DYNAMIC) {
+ if (strcmp(scnset[i].sysctl_name, "dynamic")) e(0);
+ seen[1]++;
+ } else if (scnset[i].sysctl_num == id[0]) {
+ if (strcmp(scnset[i].sysctl_name, "id0")) e(0);
+ seen[2]++;
+ } else if (scnset[i].sysctl_num == id[1]) {
+ if (strcmp(scnset[i].sysctl_name, "id1")) e(0);
+ seen[3]++;
+ } else if (scnset[i].sysctl_num == id[2]) {
+ if (strcmp(scnset[i].sysctl_name, "id2")) e(0);
+ seen[4]++;
+ }
+ }
+ for (i = 0; i < 5; i++)
+ if (seen[i] != 1) e(i);
+
+ /* Compare the parent's statistics with those obtained earlier. */
+ if (query_node(mib, 1, MINIX_TEST, &scn) != 0) e(0);
+ if (scn.sysctl_clen != pscn.sysctl_clen + 4) e(0);
+ if (scn.sysctl_csize != pscn.sysctl_csize + 4) e(0);
+
+ /* Clean up. */
+ if (destroy_node(mib, 2, id[0]) != 0) e(0);
+ if (destroy_node(mib, 2, id[1]) != 0) e(0);
+ if (destroy_node(mib, 2, id[2]) != 0) e(0);
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /* Copy-out errors should not result in the node not being created. */
+ mib[2] = CTL_CREATE;
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ oldlen = sizeof(newscn) - 1;
+ if (sysctl(mib, 3, &newscn, &oldlen, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != ENOMEM) e(0);
+ if (oldlen != sizeof(newscn)) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ oldlen = sizeof(newscn);
+ if (sysctl(mib, 3, bad_ptr, &oldlen, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ oldlen = sizeof(newscn) + 1;
+ if (sysctl(mib, 3, &newscn, &oldlen, &scn, sizeof(scn)) != 0) e(0);
+ if (oldlen != sizeof(newscn)) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /*
+ * Now that we are done with the integer template, try other data
+ * types, starting with booleans. A big part of these tests is that
+ * the creation results in a usable node, regardless of the way its
+ * contents were initialized.
+ */
+ tmpscn.sysctl_flags =
+ SYSCTL_VERSION | CTLFLAG_READWRITE | CTLTYPE_BOOL;
+ tmpscn.sysctl_size = sizeof(b);
+ tmpscn.sysctl_data = NULL;
+
+ mib[2] = CTL_CREATE;
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ oldlen = sizeof(b);
+ if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(b)) e(0);
+ if (b != false) e(0);
+
+ b = true;
+ if (sysctl(mib, 3, NULL, NULL, &b, sizeof(b)) != 0) e(0);
+
+ oldlen = sizeof(b);
+ b = false;
+ if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(b)) e(0);
+ if (b != true) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ mib[2] = CTL_CREATE;
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_flags |= CTLFLAG_IMMEDIATE;
+ scn.sysctl_bdata = true;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ oldlen = sizeof(b);
+ if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(b)) e(0);
+ if (b != true) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ mib[2] = CTL_CREATE;
+ scn.sysctl_bdata = false;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ oldlen = sizeof(b);
+ if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(b)) e(0);
+ if (b != false) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ mib[2] = CTL_CREATE;
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_data = &b;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_flags |= CTLFLAG_OWNDATA;
+ scn.sysctl_size++;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_size--;
+ scn.sysctl_data = bad_ptr;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ b = true;
+ scn.sysctl_data = &b;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ oldlen = sizeof(b);
+ if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(b)) e(0);
+ if (b != true) e(0);
+
+ b = false;
+ oldlen = sizeof(b);
+ if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(b)) e(0);
+ if (b != true) e(0);
+
+ b = false;
+ if (sysctl(mib, 3, NULL, NULL, &b, sizeof(b)) != 0) e(0);
+
+ oldlen = sizeof(b);
+ b = true;
+ if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(b)) e(0);
+ if (b != false) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /* Test quads next. */
+ tmpscn.sysctl_flags =
+ SYSCTL_VERSION | CTLFLAG_READWRITE | CTLTYPE_QUAD;
+ tmpscn.sysctl_size = sizeof(q);
+
+ mib[2] = CTL_CREATE;
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ oldlen = sizeof(q);
+ if (sysctl(mib, 3, &q, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(q)) e(0);
+ if (q != 0) e(0);
+
+ q = ~0ULL;
+ if (sysctl(mib, 3, NULL, NULL, &q, sizeof(q)) != 0) e(0);
+
+ oldlen = sizeof(q);
+ q = 0;
+ if (sysctl(mib, 3, &q, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(q)) e(0);
+ if (q != ~0ULL) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ mib[2] = CTL_CREATE;
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_flags |= CTLFLAG_IMMEDIATE;
+ scn.sysctl_qdata = 1ULL << 48;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ oldlen = sizeof(q);
+ if (sysctl(mib, 3, &q, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(q)) e(0);
+ if (q != (1ULL << 48)) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ mib[2] = CTL_CREATE;
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_data = &q;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_flags |= CTLFLAG_OWNDATA;
+ scn.sysctl_size <<= 1;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_size >>= 1;
+ scn.sysctl_data = bad_ptr;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ q = 123ULL << 31;
+ scn.sysctl_data = &q;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ oldlen = sizeof(q);
+ if (sysctl(mib, 3, &q, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(q)) e(0);
+ if (q != (123ULL << 31)) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /* Test strings. */
+ tmpscn.sysctl_flags =
+ SYSCTL_VERSION | CTLFLAG_READWRITE | CTLTYPE_STRING;
+ tmpscn.sysctl_size = 7;
+
+ mib[2] = CTL_CREATE;
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_data = buf;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_data = NULL;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ memset(buf, 0x7f, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != 1) e(0);
+ if (buf[0] != '\0') e(0);
+ if (buf[1] != 0x7f) e(0);
+
+ if (sysctl(mib, 3, NULL, NULL, "woobie!", 8) != -1) e(0);
+ if (errno != EINVAL) e(0);
+ if (sysctl(mib, 3, NULL, NULL, "woobie!", 7) != -1) e(0);
+ if (errno != EINVAL) e(0);
+ if (sysctl(mib, 3, NULL, NULL, "woobie", 7) != 0) e(0);
+
+ memset(buf, 0x7f, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != 7) e(0);
+ if (strcmp(buf, "woobie")) e(0);
+ if (buf[7] != 0x7f) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ mib[2] = CTL_CREATE;
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_flags |= CTLFLAG_IMMEDIATE;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_size = 0;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_data = buf;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_size = SSIZE_MAX + 1;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_flags |= CTLFLAG_OWNDATA;
+ scn.sysctl_data = bad_ptr;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_flags |= CTLFLAG_OWNDATA;
+ scn.sysctl_data = "abc123?";
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_data = "abc123";
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ memset(buf, 0x7f, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != 7) e(0);
+ if (strcmp(buf, "abc123")) e(0);
+ if (buf[7] != 0x7f) e(0);
+
+ if (sysctl(mib, 3, NULL, NULL, "", 1) != 0) e(0);
+
+ memset(buf, 0x7f, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != 1) e(0);
+ if (buf[0] != '\0') e(0);
+ if (buf[1] != 0x7f) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ mib[2] = CTL_CREATE;
+ scn.sysctl_data = "";
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ memset(buf, 0x7f, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != 1) e(0);
+ if (buf[0] != '\0') e(0);
+ if (buf[7] != 0x7f) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /*
+ * For strings, a zero node size means that the string length
+ * determines the buffer size.
+ */
+ mib[2] = CTL_CREATE;
+ scn.sysctl_size = 0;
+ scn.sysctl_data = NULL;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_data = bad_ptr;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ scn.sysctl_data = "This is a string initializer."; /* size 29+1 */
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ memset(buf, 0x7f, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != strlen(scn.sysctl_data) + 1) e(0);
+ if (buf[oldlen - 1] != '\0') e(0);
+ if (buf[oldlen] != 0x7f) e(0);
+
+ if (query_node(mib, 2, TEST_DYNAMIC, &newscn) != 0) e(0);
+ if (newscn.sysctl_size != strlen(scn.sysctl_data) + 1) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /* Test structs. */
+ tmpscn.sysctl_flags =
+ SYSCTL_VERSION | CTLFLAG_READWRITE | CTLTYPE_STRUCT;
+ tmpscn.sysctl_size = 21;
+
+ mib[2] = CTL_CREATE;
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_data = buf;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_data = NULL;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ memset(buf, 0x7f, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != 21) e(0);
+ for (i = 0; i < 21; i++)
+ if (buf[i] != 0) e(i);
+ if (buf[i] != 0x7f) e(0);
+
+ memset(buf, 'x', 32);
+ if (sysctl(mib, 3, NULL, NULL, buf, 20) != -1) e(0);
+ if (errno != EINVAL) e(0);
+ if (sysctl(mib, 3, NULL, NULL, buf, 22) != -1) e(0);
+ if (errno != EINVAL) e(0);
+ if (sysctl(mib, 3, NULL, NULL, buf, 21) != 0) e(0);
+
+ memset(buf, 0x7f, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != 21) e(0);
+ for (i = 0; i < 21; i++)
+ if (buf[i] != 'x') e(i);
+ if (buf[i] != 0x7f) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ mib[2] = CTL_CREATE;
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_flags |= CTLFLAG_IMMEDIATE;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_size = 0;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_data = buf;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_size = SSIZE_MAX + 1;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_flags |= CTLFLAG_OWNDATA;
+ scn.sysctl_data = bad_ptr;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_flags |= CTLFLAG_OWNDATA;
+ for (i = 0; i < sizeof(buf); i++)
+ buf[i] = i;
+ scn.sysctl_data = buf;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ memset(buf, 0x7f, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != 21) e(0);
+ for (i = 0; i < 21; i++)
+ if (buf[i] != i) e(i);
+ if (buf[i] != 0x7f) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /* Finally, test node-type nodes. */
+ tmpscn.sysctl_flags =
+ SYSCTL_VERSION | CTLFLAG_READWRITE | CTLTYPE_NODE;
+ tmpscn.sysctl_size = 0;
+
+ mib[2] = CTL_CREATE;
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ scn.sysctl_flags |= CTLFLAG_IMMEDIATE;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_flags |= CTLFLAG_IMMEDIATE;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_size = sizeof(scn);
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_flags &= ~CTLFLAG_IMMEDIATE;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_flags |= CTLFLAG_OWNDATA;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_csize = 8;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_clen = 1;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_child = &scn;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_parent = &scn;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_func = (sysctlfn)test87c;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ if (query_node(mib, 2, TEST_DYNAMIC, &scn) != 0) e(0);
+ if (scn.sysctl_csize != 0) e(0);
+ if (scn.sysctl_clen != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+
+ for (i = 3; i < CTL_MAXNAME; i++) {
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ if (i % 2)
+ scn.sysctl_num = i - 3;
+ else
+ scn.sysctl_num = CTL_CREATE;
+ /*
+ * Test both names with different length (depthN vs depthNN)
+ * and cross-directory name duplicates (depth7.depth7).
+ */
+ snprintf(scn.sysctl_name, sizeof(scn.sysctl_name), "depth%u",
+ 7 + i / 2);
+ mib[i] = CTL_CREATE;
+
+ oldlen = sizeof(newscn);
+ if (sysctl(mib, i + 1, &newscn, &oldlen, &scn,
+ sizeof(scn)) != 0) e(0);
+ mib[i] = newscn.sysctl_num;
+ }
+
+ id[0] = mib[i - 1];
+ mib[i - 1] = CTL_CREATE;
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_READONLY |
+ CTLFLAG_OWNDATA | CTLTYPE_STRING;
+ scn.sysctl_num = id[0] + 1;
+ scn.sysctl_data = "bar";
+ scn.sysctl_size = strlen(scn.sysctl_data) + 1;
+ strlcpy(scn.sysctl_name, "foo", sizeof(scn.sysctl_name));
+ if (sysctl(mib, i, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+ mib[i - 1] = id[0] + 1;
+
+ oldlen = sizeof(buf);
+ if (sysctl(mib, i, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != strlen(scn.sysctl_data) + 1) e(0);
+ if (strcmp(buf, scn.sysctl_data)) e(0);
+
+ if (query_node(mib, i - 2, mib[i - 2], &scn) != 0) e(0);
+ if (scn.sysctl_csize != 2) e(0);
+ if (scn.sysctl_clen != 2) e(0);
+
+ if (query_node(mib, 2, TEST_DYNAMIC, &scn) != 0) e(0);
+ if (scn.sysctl_csize != 1) e(0);
+ if (scn.sysctl_clen != 1) e(0);
+
+ if (destroy_node(mib, i - 1, mib[i - 1]) != 0) e(0);
+ mib[i - 1]--;
+
+ for (i--; i > 2; i--)
+ if (destroy_node(mib, i, mib[i]) != 0) e(0);
+
+ if (query_node(mib, 2, TEST_DYNAMIC, &scn) != 0) e(0);
+ if (scn.sysctl_csize != 0) e(0);
+ if (scn.sysctl_clen != 0) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /*
+ * Finally, ensure that unprivileged processes cannot create nodes,
+ * even in the most friendly place possible.
+ */
+ mib[2] = CTL_CREATE;
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_flags |= CTLFLAG_ANYWRITE;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ (void)test_nonroot(sub87c);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /*
+ * Now that we are done, compare the parent's statistics with those
+ * obtained earlier once more. There must be no differences.
+ */
+ if (query_node(mib, 1, MINIX_TEST, &scn) != 0) e(0);
+ if (scn.sysctl_clen != pscn.sysctl_clen) e(0);
+ if (scn.sysctl_csize != pscn.sysctl_csize) e(0);
+
+ /* Do some more path-related error code tests unrelated to the rest. */
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ mib[1] = INT_MAX;
+ if (create_node(mib, 2, &scn, TEST_DYNAMIC, "d", -1, NULL) != -1) e(0);
+ if (errno != ENOENT) e(0);
+
+ mib[1] = MINIX_TEST;
+ mib[2] = TEST_INT;
+ if (create_node(mib, 3, &scn, TEST_DYNAMIC, "d", -1, NULL) != -1) e(0);
+ if (errno != ENOTDIR) e(0);
+
+ mib[2] = TEST_BOOL;
+ if (create_node(mib, 3, &scn, TEST_DYNAMIC, "d", -1, NULL) != -1) e(0);
+ if (errno != ENOTDIR) e(0);
+
+ mib[2] = CTL_CREATE;
+ if (create_node(mib, 3, &scn, TEST_DYNAMIC, "d", -1, NULL) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ /* Finally, try to create a node in a read-only directory node. */
+ mib[2] = TEST_SECRET;
+ if (create_node(mib, 3, &scn, -1, "d", -1, NULL) != -1) e(0);
+ if (errno != EPERM) e(0);
+}
+
+/*
+ * Test unprivileged node destruction.
+ */
+static void
+sub87d(void)
+{
+ struct sysctlnode scn;
+ int mib[3];
+
+ mib[0] = CTL_MINIX;
+ mib[1] = MINIX_TEST;
+ mib[2] = CTL_DESTROY;
+
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION;
+ scn.sysctl_num = TEST_ANYWRITE;
+
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EPERM) e(0);
+
+ mib[0] = CTL_DESTROY;
+ scn.sysctl_num = CTL_MINIX;
+ if (sysctl(mib, 1, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EPERM) e(0);
+}
+
+/*
+ * Test sysctl(2) node destruction.
+ */
+static void
+test87d(void)
+{
+ struct sysctlnode scn, oldscn, newscn, tmpscn;
+ size_t oldlen;
+ char buf[16];
+ int i, r, mib[4], id[15];
+
+ subtest = 3;
+
+ mib[0] = CTL_MINIX;
+ mib[1] = MINIX_TEST;
+ (void)destroy_node(mib, 2, TEST_DYNAMIC);
+
+ /* Start with the path-related error code tests this time. */
+ mib[1] = INT_MAX;
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != -1) e(0);
+ if (errno != ENOENT) e(0);
+
+ mib[1] = MINIX_TEST;
+ mib[2] = TEST_INT;
+ if (destroy_node(mib, 3, TEST_DYNAMIC) != -1) e(0);
+ if (errno != ENOTDIR) e(0);
+
+ mib[2] = TEST_BOOL;
+ if (destroy_node(mib, 3, TEST_DYNAMIC) != -1) e(0);
+ if (errno != ENOTDIR) e(0);
+
+ mib[2] = CTL_DESTROY;
+ if (destroy_node(mib, 3, TEST_DYNAMIC) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ /* Actual API tests. */
+ mib[1] = MINIX_TEST;
+ mib[2] = CTL_CREATE;
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_IMMEDIATE |
+ CTLFLAG_READONLY | CTLTYPE_INT;
+ scn.sysctl_size = sizeof(int);
+ scn.sysctl_num = TEST_DYNAMIC;
+ scn.sysctl_idata = 31415926;
+ strlcpy(scn.sysctl_name, "dynamic", sizeof(scn.sysctl_name));
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ memcpy(&tmpscn, &scn, sizeof(scn));
+
+ mib[2] = CTL_DESTROY;
+ if (sysctl(mib, 3, NULL, NULL, NULL, 0) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (sysctl(mib, 3, NULL, NULL, bad_ptr, sizeof(scn)) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERS_0;
+ scn.sysctl_num = TEST_DYNAMIC;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_flags = SYSCTL_VERSION;
+ scn.sysctl_num = INT_MAX; /* anything not valid */
+ oldlen = sizeof(scn);
+ if (sysctl(mib, 3, NULL, &oldlen, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != ENOENT) e(0);
+ if (oldlen != 0) e(0);
+
+ scn.sysctl_num = TEST_PERM;
+ oldlen = sizeof(scn);
+ if (sysctl(mib, 3, &oldscn, &oldlen, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EPERM) e(0);
+ if (oldlen != 0) e(0);
+
+ scn.sysctl_num = TEST_SECRET;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != ENOTEMPTY) e(0);
+
+ scn.sysctl_num = -1;
+ strlcpy(scn.sysctl_name, "dynamic", sizeof(scn.sysctl_name));
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != ENOENT) e(0);
+
+ scn.sysctl_num = TEST_DYNAMIC;
+ strlcpy(scn.sysctl_name, "dynami", sizeof(scn.sysctl_name));
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ strlcpy(scn.sysctl_name, "dynamic2", sizeof(scn.sysctl_name));
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ memset(scn.sysctl_name, 'd', sizeof(scn.sysctl_name));
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ strlcpy(scn.sysctl_name, "dynamic", sizeof(scn.sysctl_name));
+ oldlen = sizeof(oldscn);
+ if (sysctl(mib, 3, &oldscn, &oldlen, &scn, sizeof(scn)) != 0) e(0);
+ if (oldlen != sizeof(oldscn)) e(0);
+ if (oldscn.sysctl_ver == 0) e(0);
+ oldscn.sysctl_ver = 0;
+ if (memcmp(&oldscn, &tmpscn, sizeof(oldscn))) e(0);
+
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != ENOENT) e(0);
+
+ /*
+ * We already tested destruction of one static node, by destroying
+ * TEST_DYNAMIC on the first run. We now do a second deletion of a
+ * static node, TEST_DESTROY2, to test proper adjustment of parent
+ * stats. We do a third static node deletion (on TEST_DESTROY1) later,
+ * to see that static nodes with dynamic descriptions can be freed.
+ */
+ if (query_node(mib, 1, MINIX_TEST, &oldscn) != 0) e(0);
+
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION;
+ scn.sysctl_num = TEST_DESTROY2;
+ r = sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn));
+ if (r != 0 && r != -1) e(0);
+ if (r == -1 && errno != ENOENT) e(0);
+
+ if (query_node(mib, 1, MINIX_TEST, &newscn) != 0) e(0);
+
+ if (newscn.sysctl_csize != oldscn.sysctl_csize) e(0);
+ if (newscn.sysctl_clen != oldscn.sysctl_clen - !r) e(0);
+
+ /* Try to destroy a (static) node in a read-only directory node. */
+ mib[2] = TEST_SECRET;
+ if (destroy_node(mib, 3, SECRET_VALUE) != -1) e(0);
+ if (errno != EPERM) e(0);
+
+ /*
+ * Errors during data copy-out of the destroyed node should not undo
+ * its destruction.
+ */
+ mib[2] = CTL_CREATE;
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ i = 0;
+ oldlen = sizeof(i);
+ if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(i)) e(0);
+ if (i != 31415926) e(0);
+
+ mib[2] = CTL_DESTROY;
+ oldlen = sizeof(scn);
+ if (sysctl(mib, 3, bad_ptr, &oldlen, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ i = 0;
+ oldlen = sizeof(i);
+ if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != ENOENT) e(0);
+ if (oldlen != 0) e(0);
+ if (i != 0) e(0);
+
+ mib[2] = CTL_CREATE;
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ mib[2] = CTL_DESTROY;
+ oldlen = sizeof(scn) - 1;
+ if (sysctl(mib, 3, &scn, &oldlen, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != ENOMEM) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ oldlen = sizeof(i);
+ if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != ENOENT) e(0);
+
+ /*
+ * Now create and destroy a whole bunch of nodes in a subtree, mostly
+ * test linked list manipulation, but also to ensure that a nonempty
+ * tree node cannot be destroyed.
+ */
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_READWRITE | CTLTYPE_NODE;
+ if (create_node(mib, 2, &scn, TEST_DYNAMIC, "dynamic", -1, NULL) == -1)
+ e(0);
+
+ for (i = 0; i < 15; i++) {
+ snprintf(buf, sizeof(buf), "node%d", i);
+ if ((id[i] = create_node(mib, 3, &scn, -1, buf, -1,
+ NULL)) == -1) e(i);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != -1) e(i);
+ if (errno != ENOTEMPTY) e(i);
+ }
+
+ for (i = 0; i < 15; i += 2)
+ if (destroy_node(mib, 3, id[i]) != 0) e(i);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != -1) e(0);
+ if (errno != ENOTEMPTY) e(0);
+
+ for (i = 0; i < 15; i += 2) {
+ snprintf(buf, sizeof(buf), "node%d", i);
+ if ((id[i] = create_node(mib, 3, &scn, -1, buf, -1,
+ NULL)) == -1) e(i);
+ }
+
+ for (i = 0; i < 3; i++)
+ if (destroy_node(mib, 3, id[i]) != 0) e(i);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != -1) e(0);
+ if (errno != ENOTEMPTY) e(0);
+
+ for (i = 12; i < 15; i++)
+ if (destroy_node(mib, 3, id[i]) != 0) e(i);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != -1) e(0);
+ if (errno != ENOTEMPTY) e(0);
+
+ for (i = 6; i < 9; i++)
+ if (destroy_node(mib, 3, id[i]) != 0) e(i);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != -1) e(0);
+ if (errno != ENOTEMPTY) e(0);
+
+ for (i = 3; i < 6; i++)
+ if (destroy_node(mib, 3, id[i]) != 0) e(i);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != -1) e(0);
+ if (errno != ENOTEMPTY) e(0);
+
+ for (i = 9; i < 12; i++)
+ if (destroy_node(mib, 3, id[i]) != 0) e(i);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /* Finally, ensure that unprivileged users cannot destroy nodes. */
+ (void)test_nonroot(sub87d);
+}
+
+/*
+ * Get or a set the description for a particular node. Compare the results
+ * with the given description. Return 0 on success, or -1 on failure with
+ * errno set.
+ */
+static int
+describe_node(const int * path, unsigned int pathlen, int id,
+ const char * desc, int set)
+{
+ char buf[256], *p;
+ struct sysctlnode scn;
+ struct sysctldesc *scd;
+ size_t oldlen;
+ int mib[CTL_MAXNAME];
+
+ if (pathlen >= CTL_MAXNAME) e(0);
+ memcpy(mib, path, sizeof(mib[0]) * pathlen);
+ mib[pathlen] = CTL_DESCRIBE;
+
+ memset(&scn, 0, sizeof(scn));
+ memset(buf, 0, sizeof(buf));
+ oldlen = sizeof(buf);
+ scn.sysctl_flags = SYSCTL_VERSION;
+ scn.sysctl_num = id;
+ if (set)
+ scn.sysctl_desc = desc;
+ if (sysctl(mib, pathlen + 1, buf, &oldlen, &scn, sizeof(scn)) != 0)
+ return -1;
+
+ scd = (struct sysctldesc *)buf;
+ if (scd->descr_num != id) e(0);
+ if (scd->descr_ver == 0) e(0);
+ if (scd->descr_str[scd->descr_len - 1] != '\0') e(0);
+ if (scd->descr_len != strlen(scd->descr_str) + 1) e(0);
+ if (strcmp(scd->descr_str, desc)) e(0);
+ if (oldlen != (size_t)((char *)NEXT_DESCR(scd) - buf)) e(0);
+ for (p = scd->descr_str + scd->descr_len; p != &buf[oldlen]; p++)
+ if (*p != '\0') e(0);
+ return 0;
+}
+
+/*
+ * Test getting descriptions from an unprivileged process.
+ */
+static void
+sub87e(void)
+{
+ static char buf[2048];
+ char seen[32], *p;
+ struct sysctldesc *scd, *endscd;
+ size_t oldlen;
+ int mib[4];
+
+ mib[0] = CTL_MINIX;
+ mib[1] = MINIX_TEST;
+ mib[2] = CTL_DESCRIBE;
+
+ memset(buf, 0, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen == 0) e(0);
+
+ scd = (struct sysctldesc *)buf;
+ endscd = (struct sysctldesc *)&buf[oldlen];
+ memset(seen, 0, sizeof(seen));
+
+ while (scd < endscd) {
+ if (scd->descr_num >= __arraycount(seen)) e(0);
+ if (seen[scd->descr_num]++) e(0);
+
+ if (scd->descr_ver == 0) e(0);
+ if (scd->descr_str[scd->descr_len - 1] != '\0') e(0);
+ if (scd->descr_len != strlen(scd->descr_str) + 1) e(0);
+
+ p = scd->descr_str + scd->descr_len;
+ while (p != (char *)NEXT_DESCR(scd))
+ if (*p++ != '\0') e(0);
+
+ scd = NEXT_DESCR(scd);
+ }
+ if (scd != endscd) e(0);
+
+ if (!seen[TEST_INT]) e(0);
+ if (!seen[TEST_BOOL]) e(0);
+ if (!seen[TEST_QUAD]) e(0);
+ if (!seen[TEST_STRING]) e(0);
+ if (!seen[TEST_STRUCT]) e(0);
+ if (seen[TEST_PRIVATE]) e(0);
+ if (!seen[TEST_ANYWRITE]) e(0);
+ if (seen[TEST_SECRET]) e(0);
+ if (!seen[TEST_PERM]) e(0);
+
+ if (describe_node(mib, 2, TEST_INT, "Value test field", 0) != 0) e(0);
+ if (describe_node(mib, 2, TEST_PRIVATE, "", 0) != -1) e(0);
+ if (errno != EPERM) e(0);
+ if (describe_node(mib, 2, TEST_SECRET, "", 0) != -1) e(0);
+ if (errno != EPERM) e(0);
+ if (describe_node(mib, 2, TEST_PERM, "", 0) != 0) e(0);
+
+ mib[2] = TEST_SECRET;
+ mib[3] = CTL_DESCRIBE;
+ if (sysctl(mib, 3, NULL, NULL, NULL, 0) != -1) e(0);
+ if (errno != EPERM) e(0);
+
+ if (describe_node(mib, 3, SECRET_VALUE, "", 0) != -1) e(0);
+ if (errno != EPERM) e(0);
+}
+
+/*
+ * Test sysctl(2) node descriptions, part 1: getting descriptions.
+ */
+static void
+test87e(void)
+{
+ static char buf[2048];
+ char seen[32], *p;
+ struct sysctldesc *scd, *endscd;
+ struct sysctlnode scn;
+ size_t oldlen, len, sublen;
+ int mib[4];
+
+ subtest = 4;
+
+ mib[0] = CTL_MINIX;
+ mib[1] = MINIX_TEST;
+ mib[2] = CTL_DESCRIBE;
+ memset(&scn, 0, sizeof(scn));
+
+ /* Start with tests for getting a description listing. */
+ if (sysctl(mib, 3, NULL, NULL, NULL, 0) != 0) e(0);
+
+ if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen == 0) e(0);
+ len = oldlen;
+
+ memset(buf, 0, sizeof(buf));
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != len) e(0);
+
+ scd = (struct sysctldesc *)buf;
+ endscd = (struct sysctldesc *)&buf[len];
+ memset(seen, 0, sizeof(seen));
+
+ sublen = (size_t)((char *)NEXT_DESCR(scd) - buf);
+
+ while (scd < endscd) {
+ if (scd->descr_num >= __arraycount(seen)) e(0);
+ if (seen[scd->descr_num]++) e(0);
+
+ if (scd->descr_ver == 0) e(0);
+ if (scd->descr_str[scd->descr_len - 1] != '\0') e(0);
+ if (scd->descr_len != strlen(scd->descr_str) + 1) e(0);
+
+ /*
+ * This is not supposed to be complete. We test different
+ * string lengths, private fields, and empty descriptions.
+ */
+ switch (scd->descr_num) {
+ case TEST_INT:
+ if (strcmp(scd->descr_str, "Value test field")) e(0);
+ break;
+ case TEST_BOOL:
+ if (strcmp(scd->descr_str, "Boolean test field")) e(0);
+ break;
+ case TEST_QUAD:
+ if (strcmp(scd->descr_str, "Quad test field")) e(0);
+ break;
+ case TEST_STRING:
+ if (strcmp(scd->descr_str, "String test field")) e(0);
+ break;
+ case TEST_PRIVATE:
+ if (strcmp(scd->descr_str, "Private test field")) e(0);
+ break;
+ case TEST_SECRET:
+ if (strcmp(scd->descr_str, "Private subtree")) e(0);
+ break;
+ case TEST_PERM:
+ if (strcmp(scd->descr_str, "")) e(0);
+ break;
+ }
+
+ /*
+ * If there are padding bytes, they must be zero, whether it is
+ * because we set them or the MIB service copied out zeroes.
+ */
+ p = scd->descr_str + scd->descr_len;
+ while (p != (char *)NEXT_DESCR(scd))
+ if (*p++ != '\0') e(0);
+
+ scd = NEXT_DESCR(scd);
+ }
+ if (scd != endscd) e(0);
+
+ if (!seen[TEST_INT]) e(0);
+ if (!seen[TEST_BOOL]) e(0);
+ if (!seen[TEST_QUAD]) e(0);
+ if (!seen[TEST_STRING]) e(0);
+ if (!seen[TEST_STRUCT]) e(0);
+ if (!seen[TEST_PRIVATE]) e(0);
+ if (!seen[TEST_ANYWRITE]) e(0);
+ if (!seen[TEST_SECRET]) e(0);
+ if (!seen[TEST_PERM]) e(0);
+
+ memset(buf, 0, sizeof(buf));
+ oldlen = sublen;
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != ENOMEM) e(0);
+
+ scd = (struct sysctldesc *)buf;
+ if (scd->descr_num != TEST_INT) e(0);
+ if (scd->descr_ver == 0) e(0);
+ if (scd->descr_str[scd->descr_len - 1] != '\0') e(0);
+ if (scd->descr_len != strlen(scd->descr_str) + 1) e(0);
+ if (strcmp(scd->descr_str, "Value test field")) e(0);
+
+ /* Next up, tests for getting a particular node's description. */
+ memset(buf, 0, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, bad_ptr, &oldlen, NULL, 0) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ if (sysctl(mib, 3, NULL, NULL, bad_ptr, sizeof(scn) - 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+ if (sysctl(mib, 3, NULL, NULL, bad_ptr, sizeof(scn) + 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+ if (sysctl(mib, 3, NULL, NULL, bad_ptr, sizeof(scn)) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERS_0;
+ scn.sysctl_num = INT_MAX;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_flags = SYSCTL_VERSION;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != ENOENT) e(0);
+
+ scn.sysctl_num = TEST_BOOL;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ oldlen = sizeof(buf);
+ scn.sysctl_num = TEST_INT;
+ if (sysctl(mib, 3, bad_ptr, &oldlen, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ oldlen = sublen - 1;
+ scn.sysctl_num = TEST_INT;
+ if (sysctl(mib, 3, buf, &oldlen, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != ENOMEM) e(0);
+ if (oldlen != sublen) e(0);
+
+ if (describe_node(mib, 2, TEST_INT, "Value test field", 0) != 0) e(0);
+ if (describe_node(mib, 2, TEST_QUAD, "Quad test field", 0) != 0) e(0);
+ if (describe_node(mib, 2, TEST_PRIVATE, "Private test field",
+ 0) != 0) e(0);
+ if (describe_node(mib, 2, TEST_SECRET, "Private subtree",
+ 0) != 0) e(0);
+ if (describe_node(mib, 2, TEST_PERM, "", 0) != 0) e(0);
+
+ /*
+ * Make sure that unprivileged users cannot access privileged nodes'
+ * descriptions. It doesn't sound too bad to me if they could, but
+ * these are apparently the rules..
+ */
+ (void)test_nonroot(sub87e);
+
+ /* Do some more path-related error code tests unrelated to the rest. */
+ mib[1] = INT_MAX;
+ if (describe_node(mib, 2, TEST_DYNAMIC, "", 0) != -1) e(0);
+ if (errno != ENOENT) e(0);
+
+ mib[1] = MINIX_TEST;
+ mib[2] = TEST_INT;
+ if (describe_node(mib, 3, TEST_DYNAMIC, "", 0) != -1) e(0);
+ if (errno != ENOTDIR) e(0);
+
+ mib[2] = TEST_BOOL;
+ if (describe_node(mib, 3, TEST_DYNAMIC, "", 0) != -1) e(0);
+ if (errno != ENOTDIR) e(0);
+
+ mib[2] = CTL_DESCRIBE;
+ if (describe_node(mib, 3, TEST_DYNAMIC, "", 0) != -1) e(0);
+ if (errno != EINVAL) e(0);
+}
+
+/*
+ * Test setting descriptions from an unprivileged process.
+ */
+static void
+sub87f(void)
+{
+ struct sysctlnode scn;
+ int mib[3];
+
+ mib[0] = CTL_MINIX;
+ mib[1] = MINIX_TEST;
+ mib[2] = CTL_DESCRIBE;
+
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION;
+ scn.sysctl_num = TEST_DYNAMIC;
+ scn.sysctl_desc = "Description.";
+
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EPERM) e(0);
+}
+
+/*
+ * Test sysctl(2) node descriptions, part 2: setting descriptions.
+ */
+static void
+test87f(void)
+{
+ static char buf[2048];
+ char seen, *p;
+ struct sysctlnode scn, tmpscn, scnset[3];
+ struct sysctldesc *scd, *endscd, *scdset[2];
+ size_t oldlen, len;
+ int i, r, mib[4], id[2];
+
+ subtest = 5;
+
+ /*
+ * All tests that experiment with dynamic nodes must start with trying
+ * to destroy the TEST_DYNAMIC node first, as tests may be run
+ * individually, and this node exists as a static node after booting.
+ */
+ mib[0] = CTL_MINIX;
+ mib[1] = MINIX_TEST;
+ (void)destroy_node(mib, 2, TEST_DYNAMIC);
+
+ /*
+ * First try setting and retrieving the description of a dynamic node
+ * in a directory full of static nodes.
+ */
+ mib[2] = CTL_CREATE;
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_IMMEDIATE |
+ CTLFLAG_READONLY | CTLTYPE_INT;
+ scn.sysctl_size = sizeof(int);
+ scn.sysctl_num = TEST_DYNAMIC;
+ scn.sysctl_idata = 27182818;
+ strlcpy(scn.sysctl_name, "dynamic", sizeof(scn.sysctl_name));
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ memcpy(&tmpscn, &scn, sizeof(tmpscn));
+
+ /* We should get an empty description for the node in a listing. */
+ mib[2] = CTL_DESCRIBE;
+ memset(buf, 0, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+
+ scd = (struct sysctldesc *)buf;
+ endscd = (struct sysctldesc *)&buf[oldlen];
+ seen = 0;
+
+ while (scd < endscd) {
+ if (scd->descr_num == TEST_DYNAMIC) {
+ if (seen++) e(0);
+
+ if (scd->descr_len != 1) e(0);
+ if (scd->descr_str[0] != '\0') e(0);
+ }
+
+ if (scd->descr_ver == 0) e(0);
+ if (scd->descr_str[scd->descr_len - 1] != '\0') e(0);
+ if (scd->descr_len != strlen(scd->descr_str) + 1) e(0);
+
+ p = scd->descr_str + scd->descr_len;
+ while (p != (char *)NEXT_DESCR(scd))
+ if (*p++ != '\0') e(0);
+
+ scd = NEXT_DESCR(scd);
+ }
+ if (scd != endscd) e(0);
+
+ if (!seen) e(0);
+
+ /* We should get an empty description quering the node directly. */
+ if (describe_node(mib, 2, TEST_DYNAMIC, "", 0) != 0) e(0);
+
+ /* Attempt to set a description with a bad description pointer. */
+ if (describe_node(mib, 2, TEST_DYNAMIC, bad_ptr, 1) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ /* Attempt to set a description that is longer than allowed. */
+ memset(buf, 'A', sizeof(buf) - 1);
+ buf[sizeof(buf) - 1] = '\0';
+ if (describe_node(mib, 2, TEST_DYNAMIC, buf, 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ /* Now actually set a description. */
+ if (describe_node(mib, 2, TEST_DYNAMIC, "Dynamic node", 1) != 0) e(0);
+ len = strlen("Dynamic node") + 1;
+
+ /* We should get the new description for the node in a listing. */
+ memset(buf, 0, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0);
+
+ scd = (struct sysctldesc *)buf;
+ endscd = (struct sysctldesc *)&buf[oldlen];
+ seen = 0;
+
+ while (scd < endscd) {
+ if (scd->descr_num == TEST_DYNAMIC) {
+ if (seen++) e(0);
+
+ if (scd->descr_len != len) e(0);
+ if (strcmp(scd->descr_str, "Dynamic node")) e(0);
+ }
+
+ if (scd->descr_ver == 0) e(0);
+ if (scd->descr_str[scd->descr_len - 1] != '\0') e(0);
+ if (scd->descr_len != strlen(scd->descr_str) + 1) e(0);
+
+ p = scd->descr_str + scd->descr_len;
+ while (p != (char *)NEXT_DESCR(scd))
+ if (*p++ != '\0') e(0);
+
+ scd = NEXT_DESCR(scd);
+ }
+ if (scd != endscd) e(0);
+
+ if (!seen) e(0);
+
+ /* We should get the new description quering the node directly. */
+ if (describe_node(mib, 2, TEST_DYNAMIC, "Dynamic node", 0) != 0) e(0);
+
+ mib[2] = CTL_DESCRIBE;
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERS_0;
+ scn.sysctl_num = TEST_INT;
+ scn.sysctl_desc = "Test description";
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ /* It is not possible to replace an existing static description. */
+ scn.sysctl_flags = SYSCTL_VERSION;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EPERM) e(0);
+
+ /* Nonexistent nodes cannot be given a description. */
+ scn.sysctl_num = INT_MAX;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != ENOENT) e(0);
+
+ /* It is not possible to replace an existing dynamic description. */
+ scn.sysctl_num = TEST_DYNAMIC;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EPERM) e(0);
+
+ /* It is not possible to set a description on a permanent node. */
+ scn.sysctl_num = TEST_PERM;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EPERM) e(0);
+
+ /* Verify that TEST_DYNAMIC now has CTLFLAG_OWNDESC set. */
+ if (query_node(mib, 2, TEST_DYNAMIC, &scn) != 0) e(0);
+ if (!(scn.sysctl_flags & CTLFLAG_OWNDESC)) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /*
+ * Set a description on a static node, ensure that CTLFLAG_OWNDESC is
+ * set, and then destroy the static node. This should still free the
+ * memory allocated for the description. We cannot test whether the
+ * memory is really freed, but at least we can trigger this case at
+ * all, and leave the rest up to memory checkers or whatever. Since we
+ * destroy the static node, we can not do this more than once, and thus
+ * we skip this test if the static node does not exist.
+ */
+ r = describe_node(mib, 2, TEST_DESTROY1, "Destroy me", 1);
+
+ if (r == -1 && errno != ENOENT) e(0);
+ else if (r == 0) {
+ if (query_node(mib, 2, TEST_DESTROY1, &scn) != 0) e(0);
+ if (!(scn.sysctl_flags & CTLFLAG_OWNDESC)) e(0);
+
+ if (describe_node(mib, 2, TEST_DESTROY1, "Destroy me", 0) != 0)
+ e(0);
+
+ if (destroy_node(mib, 2, TEST_DESTROY1) != 0) e(0);
+ }
+
+ /*
+ * Test queries and description listings in subtrees.
+ */
+ mib[2] = CTL_CREATE;
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_READWRITE | CTLTYPE_NODE;
+ scn.sysctl_num = TEST_DYNAMIC;
+ strlcpy(scn.sysctl_name, "dynamic", sizeof(scn.sysctl_name));
+ scn.sysctl_desc = "This will not be set.";
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ /* Setting sysctl_desc should have no effect during creation. */
+ if (describe_node(mib, 2, TEST_DYNAMIC, "", 0) != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ id[0] = create_node(mib, 3, &tmpscn, CTL_CREATE, "NodeA", -1, NULL);
+ if (id[0] < 0) e(0);
+ id[1] = create_node(mib, 3, &tmpscn, CTL_CREATE, "NodeB", -1, NULL);
+ if (id[1] < 0) e(0);
+ if (id[0] == id[1]) e(0);
+
+ mib[3] = CTL_QUERY;
+ oldlen = sizeof(scnset);
+ if (sysctl(mib, 4, scnset, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(scnset[0]) * 2) e(0);
+ i = (scnset[0].sysctl_num != id[0]);
+ if (scnset[i].sysctl_num != id[0]) e(0);
+ if (scnset[1 - i].sysctl_num != id[1]) e(0);
+ if (scnset[i].sysctl_flags & CTLFLAG_OWNDESC) e(0);
+ if (scnset[1 - i].sysctl_flags & CTLFLAG_OWNDESC) e(0);
+
+ mib[3] = CTL_DESCRIBE;
+ memset(buf, 0, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 4, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen == 0) e(0);
+
+ scdset[0] = (struct sysctldesc *)buf;
+ scdset[1] = NEXT_DESCR(scdset[0]);
+ if ((char *)NEXT_DESCR(scdset[1]) != &buf[oldlen]) e(0);
+ i = (scdset[0]->descr_num != id[0]);
+ if (scdset[i]->descr_num != id[0]) e(0);
+ if (scdset[i]->descr_ver == 0) e(0);
+ if (scdset[i]->descr_len != 1) e(0);
+ if (scdset[i]->descr_str[0] != '\0') e(0);
+ if (scdset[1 - i]->descr_num != id[1]) e(0);
+ if (scdset[1 - i]->descr_ver == 0) e(0);
+ if (scdset[1 - i]->descr_len != 1) e(0);
+ if (scdset[1 - i]->descr_str[0] != '\0') e(0);
+
+ if (describe_node(mib, 3, id[0], "Description A", 1) != 0) e(0);
+
+ mib[3] = CTL_QUERY;
+ oldlen = sizeof(scnset);
+ if (sysctl(mib, 4, scnset, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(scnset[0]) * 2) e(0);
+ i = (scnset[0].sysctl_num != id[0]);
+ if (scnset[i].sysctl_num != id[0]) e(0);
+ if (scnset[1 - i].sysctl_num != id[1]) e(0);
+ if (!(scnset[i].sysctl_flags & CTLFLAG_OWNDESC)) e(0);
+ if (scnset[1 - i].sysctl_flags & CTLFLAG_OWNDESC) e(0);
+
+ mib[3] = CTL_DESCRIBE;
+ memset(buf, 0, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 4, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen == 0) e(0);
+
+ scdset[0] = (struct sysctldesc *)buf;
+ scdset[1] = NEXT_DESCR(scdset[0]);
+ if ((char *)NEXT_DESCR(scdset[1]) != &buf[oldlen]) e(0);
+ i = (scdset[0]->descr_num != id[0]);
+ if (scdset[i]->descr_num != id[0]) e(0);
+ if (scdset[i]->descr_ver == 0) e(0);
+ if (strcmp(scdset[i]->descr_str, "Description A")) e(0);
+ if (scdset[i]->descr_len != strlen(scdset[i]->descr_str) + 1) e(0);
+ if (scdset[1 - i]->descr_num != id[1]) e(0);
+ if (scdset[1 - i]->descr_ver == 0) e(0);
+ if (scdset[1 - i]->descr_len != 1) e(0);
+ if (scdset[1 - i]->descr_str[0] != '\0') e(0);
+
+ if (describe_node(mib, 3, id[1], "Description B", 1) != 0) e(0);
+
+ mib[3] = CTL_QUERY;
+ oldlen = sizeof(scnset);
+ if (sysctl(mib, 4, scnset, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(scnset[0]) * 2) e(0);
+ i = (scnset[0].sysctl_num != id[0]);
+ if (scnset[i].sysctl_num != id[0]) e(0);
+ if (scnset[1 - i].sysctl_num != id[1]) e(0);
+ if (!(scnset[i].sysctl_flags & CTLFLAG_OWNDESC)) e(0);
+ if (!(scnset[1 - i].sysctl_flags & CTLFLAG_OWNDESC)) e(0);
+
+ mib[3] = CTL_DESCRIBE;
+ memset(buf, 0, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 4, buf, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen == 0) e(0);
+
+ scdset[0] = (struct sysctldesc *)buf;
+ scdset[1] = NEXT_DESCR(scdset[0]);
+ if ((char *)NEXT_DESCR(scdset[1]) != &buf[oldlen]) e(0);
+ i = (scdset[0]->descr_num != id[0]);
+ if (scdset[i]->descr_num != id[0]) e(0);
+ if (scdset[i]->descr_ver == 0) e(0);
+ if (strcmp(scdset[i]->descr_str, "Description A")) e(0);
+ if (scdset[i]->descr_len != strlen(scdset[i]->descr_str) + 1) e(0);
+ if (scdset[1 - i]->descr_num != id[1]) e(0);
+ if (scdset[1 - i]->descr_ver == 0) e(0);
+ if (strcmp(scdset[1 - i]->descr_str, "Description B")) e(0);
+ if (scdset[1 - i]->descr_len != strlen(scdset[1 - i]->descr_str) + 1)
+ e(0);
+
+ if (destroy_node(mib, 3, id[0]) != 0) e(0);
+ if (destroy_node(mib, 3, id[1]) != 0) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /*
+ * Test that the resulting description is copied out after setting it,
+ * and that copy failures do not undo the description getting set.
+ */
+ if (create_node(mib, 2, &tmpscn, TEST_DYNAMIC, "dynamic", -1,
+ NULL) == -1) e(0);
+
+ mib[2] = CTL_DESCRIBE;
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION;
+ scn.sysctl_num = TEST_DYNAMIC;
+ scn.sysctl_desc = "Testing..";
+ memset(buf, 0, sizeof(buf));
+ oldlen = sizeof(buf);
+ if (sysctl(mib, 3, buf, &oldlen, &scn, sizeof(scn)) != 0) e(0);
+ if (oldlen == 0) e(0);
+ len = oldlen;
+
+ scd = (struct sysctldesc *)buf;
+ if (scd->descr_str[scd->descr_len - 1] != '\0') e(0);
+ if (scd->descr_len != strlen(scn.sysctl_desc) + 1) e(0);
+ if (strcmp(scd->descr_str, scn.sysctl_desc)) e(0);
+ if (oldlen != (size_t)((char *)NEXT_DESCR(scd) - buf)) e(0);
+ p = scd->descr_str + scd->descr_len;
+ while (p != (char *)NEXT_DESCR(scd))
+ if (*p++ != '\0') e(0);
+
+ if (describe_node(mib, 2, TEST_DYNAMIC, "Testing..", 0) != 0) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ if (create_node(mib, 2, &tmpscn, TEST_DYNAMIC, "dynamic", -1,
+ NULL) == -1) e(0);
+
+ memset(buf, 0, sizeof(buf));
+ oldlen = len - 1;
+ if (sysctl(mib, 3, buf, &oldlen, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != ENOMEM) e(0);
+ if (oldlen != len) e(0);
+
+ if (describe_node(mib, 2, TEST_DYNAMIC, "Testing..", 0) != 0) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ if (create_node(mib, 2, &tmpscn, TEST_DYNAMIC, "dynamic", -1,
+ NULL) == -1) e(0);
+
+ memset(buf, 0, sizeof(buf));
+ oldlen = len;
+ if (sysctl(mib, 3, bad_ptr, &oldlen, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ if (describe_node(mib, 2, TEST_DYNAMIC, "Testing..", 0) != 0) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /* Finally, ensure that unprivileged users cannot set descriptions. */
+ memcpy(&scn, &tmpscn, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_IMMEDIATE |
+ CTLFLAG_READWRITE | CTLFLAG_ANYWRITE | CTLTYPE_INT;
+ if (create_node(mib, 2, &scn, TEST_DYNAMIC, "dynamic", -1,
+ NULL) == -1) e(0);
+
+ (void)test_nonroot(sub87f);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+}
+
+/*
+ * Set or test buffer contents. When setting, the buffer is filled with a
+ * sequence of bytes that is a) free of null characters and b) likely to cause
+ * detection of wrongly copied subsequences. When testing, for any size up to
+ * the size used to set the buffer contents, 0 is returned if the buffer
+ * contents match expectations, or -1 if they do not.
+ */
+static int
+test_buf(unsigned char * buf, unsigned char c, size_t size, int set)
+{
+ int step;
+
+ for (step = 1; size > 0; size--) {
+ if (set)
+ *buf++ = c;
+ else if (*buf++ != c)
+ return -1;
+
+ c += step;
+ if (c == 0) {
+ if (++step == 256)
+ step = 1;
+ c += step;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Test large data sizes from an unprivileged process.
+ */
+static void
+sub87g(void)
+{
+ char *ptr;
+ size_t size, oldlen;
+ int id, mib[3];
+
+ size = getpagesize() * 3;
+
+ if ((ptr = mmap(NULL, size, PROT_READ, MAP_ANON | MAP_PRIVATE, -1,
+ 0)) == MAP_FAILED) e(0);
+ memset(ptr, 0x2f, size);
+
+ mib[0] = CTL_MINIX;
+ mib[1] = MINIX_TEST;
+ mib[2] = TEST_DYNAMIC;
+ oldlen = size - 2;
+ if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != size - 2) e(0);
+ if (test_buf(ptr, 'D', size - 2, 0) != 0) e(0);
+
+ /*
+ * Given the large data size, we currently expect this attempt to
+ * write to the structure to be blocked by the MIB service.
+ */
+ if (sysctl(mib, 3, NULL, NULL, ptr, oldlen) != -1) e(0);
+ if (errno != EPERM) e(0);
+
+ /* Get the ID of the second dynamic node. */
+ mib[2] = TEST_ANYWRITE;
+ oldlen = sizeof(id);
+ if (sysctl(mib, 3, &id, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(id)) e(0);
+ if (id < 0) e(0);
+
+ /*
+ * Test data size limits for strings as well, although here we can also
+ * ensure that we hit the right check by testing with a shorter string.
+ */
+ mib[2] = id;
+ oldlen = size;
+ if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != size) e(0);
+ if (test_buf(ptr, 'f', size - 1, 0) != 0) e(0);
+ if (ptr[size - 1] != '\0') e(0);
+
+ test_buf(ptr, 'h', size - 1, 1);
+ if (sysctl(mib, 3, NULL, NULL, ptr, size) != -1) e(0);
+ if (errno != EPERM) e(0);
+
+ if (sysctl(mib, 3, NULL, NULL, ptr, getpagesize() - 1) != 0) e(0);
+
+ if (munmap(ptr, size) != 0) e(0);
+}
+
+/*
+ * Test large data sizes and mid-data page faults.
+ */
+static void
+test87g(void)
+{
+ struct sysctlnode scn, newscn;
+ char *ptr;
+ size_t pgsz, size, oldlen;
+ int id, mib[3];
+
+ subtest = 6;
+
+ /*
+ * No need to go overboard with sizes here; it will just cause the MIB
+ * service's memory usage to grow - permanently. Three pages followed
+ * by an unmapped page is plenty for this test.
+ */
+ pgsz = getpagesize();
+ size = pgsz * 3;
+
+ if ((ptr = mmap(NULL, size + pgsz, PROT_READ, MAP_ANON | MAP_PRIVATE,
+ -1, 0)) == MAP_FAILED) e(0);
+ if (munmap(ptr + size, pgsz) != 0) e(0);
+
+ (void)destroy_node(mib, 2, TEST_DYNAMIC);
+
+ /* Test string creation initializers with an accurate length. */
+ mib[0] = CTL_MINIX;
+ mib[1] = MINIX_TEST;
+ mib[2] = CTL_CREATE;
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_OWNDATA |
+ CTLFLAG_READWRITE | CTLTYPE_STRING;
+ scn.sysctl_num = TEST_DYNAMIC;
+ scn.sysctl_data = ptr;
+ scn.sysctl_size = size;
+ strlcpy(scn.sysctl_name, "dynamic", sizeof(scn.sysctl_name));
+ test_buf(ptr, 'a', size, 1);
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0); /* no null terminator */
+
+ scn.sysctl_size++;
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ scn.sysctl_size--;
+ ptr[size - 1] = '\0';
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != size) e(0);
+
+ memset(ptr, 0, size);
+ if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != size) e(0);
+ if (ptr[size - 1] != '\0') e(0);
+ if (test_buf(ptr, 'a', size - 1, 0) != 0) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /* Test string creation initializers with no length. */
+ mib[2] = CTL_CREATE;
+ scn.sysctl_size = 0;
+ test_buf(ptr, 'b', size, 1);
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ test_buf(ptr, 'b', size - 1, 1);
+ ptr[size - 1] = '\0';
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ if (query_node(mib, 2, TEST_DYNAMIC, &newscn) != 0) e(0);
+ if (newscn.sysctl_size != size) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != size) e(0);
+
+ memset(ptr, 0x7e, size);
+ if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != size) e(0);
+ if (ptr[size - 1] != '\0') e(0);
+ if (test_buf(ptr, 'b', size - 1, 0) != 0) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /*
+ * Test string creation initializers with a length exceeding the string
+ * length. If the string is properly null terminated, this should not
+ * result in a fault.
+ */
+ mib[2] = CTL_CREATE;
+ scn.sysctl_size = size;
+ scn.sysctl_data = &ptr[size - pgsz - 5];
+ test_buf(&ptr[size - pgsz - 5], 'c', pgsz + 5, 1);
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ ptr[size - 1] = '\0';
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ if (query_node(mib, 2, TEST_DYNAMIC, &newscn) != 0) e(0);
+ if (newscn.sysctl_size != size) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ oldlen = size - pgsz - 6;
+ if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != pgsz + 5) e(0);
+ /* We rely on only the actual string getting copied out here. */
+ if (memcmp(ptr, &ptr[size - pgsz - 5], pgsz + 5)) e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+
+ /* Test structure creation initializers. */
+ mib[2] = CTL_CREATE;
+ scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_OWNDATA |
+ CTLFLAG_ANYWRITE | CTLFLAG_READWRITE | CTLTYPE_STRUCT;
+ scn.sysctl_size = size - 2;
+ scn.sysctl_data = &ptr[3];
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ scn.sysctl_data = &ptr[2];
+ test_buf(&ptr[2], 'd', size - 2, 1);
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ mib[2] = TEST_DYNAMIC;
+ memset(ptr, 0x3b, size);
+ oldlen = size - 2;
+ if (sysctl(mib, 3, &ptr[3], &oldlen, NULL, 0) != -1) e(0);
+ if (errno != EFAULT) e(0);
+ oldlen = size - 2;
+ if (sysctl(mib, 3, &ptr[2], &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != size - 2) e(0);
+ if (test_buf(&ptr[2], 'd', size - 2, 0) != 0) e(0);
+
+ /*
+ * Test setting new values. We already have a structure node, so let's
+ * start there.
+ */
+ test_buf(&ptr[2], 'D', size - 2, 1);
+ if (sysctl(mib, 3, NULL, NULL, &ptr[3], size - 2) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ /* Did the mid-data fault cause a partial update? It better not. */
+ memset(ptr, 0x4c, size);
+ oldlen = size - 2;
+ if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != size - 2) e(0);
+ if (test_buf(ptr, 'd', size - 2, 0) != 0) e(0);
+
+ test_buf(&ptr[2], 'D', size - 2, 1);
+ if (sysctl(mib, 3, NULL, NULL, &ptr[2], size - 2) != 0) e(0);
+
+ memset(ptr, 0x5d, size);
+ oldlen = size - 2;
+ if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != size - 2) e(0);
+ if (test_buf(ptr, 'D', size - 2, 0) != 0) e(0);
+
+ /*
+ * We are going to reuse TEST_DYNAMIC for the non-root test later, so
+ * create a new node for string tests.
+ */
+ mib[2] = CTL_CREATE;
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_OWNDATA |
+ CTLFLAG_ANYWRITE | CTLFLAG_READWRITE | CTLTYPE_STRING;
+ scn.sysctl_num = CTL_CREATE;
+ scn.sysctl_size = size;
+ scn.sysctl_data = ptr;
+ test_buf(ptr, 'e', size - 1, 1);
+ ptr[size - 1] = '\0';
+ strlcpy(scn.sysctl_name, "dynamic2", sizeof(scn.sysctl_name));
+ oldlen = sizeof(newscn);
+ if (sysctl(mib, 3, &newscn, &oldlen, &scn, sizeof(scn)) != 0) e(0);
+ if (oldlen != sizeof(newscn)) e(0);
+ id = newscn.sysctl_num;
+ if (id < 0) e(0);
+
+ /*
+ * Test setting a short but faulty string, ensuring that no partial
+ * update on the field contents takes place.
+ */
+ mib[2] = id;
+ memcpy(&ptr[size - 3], "XYZ", 3);
+ if (sysctl(mib, 3, NULL, NULL, &ptr[size - 3], 4) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ oldlen = size;
+ if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != size) e(0);
+ if (test_buf(ptr, 'e', size - 1, 0) != 0) e(0);
+ if (ptr[size - 1] != '\0') e(0);
+
+ memcpy(&ptr[size - 3], "XYZ", 3);
+ if (sysctl(mib, 3, NULL, NULL, &ptr[size - 3], 3) != 0) e(0);
+
+ oldlen = size;
+ if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != 4) e(0);
+ if (strcmp(ptr, "XYZ")) e(0);
+
+ test_buf(&ptr[1], 'f', size - 1, 1);
+ if (sysctl(mib, 3, NULL, NULL, &ptr[1], size - 1) != 0) e(0);
+
+ test_buf(&ptr[1], 'G', size - 1, 1);
+ if (sysctl(mib, 3, NULL, NULL, &ptr[1], size) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ oldlen = size;
+ if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != size) e(0);
+ if (test_buf(ptr, 'f', size - 1, 0) != 0) e(0);
+ if (ptr[size - 1] != '\0') e(0);
+
+ /*
+ * Test descriptions as well. First, the MIB service does not allow
+ * for overly long descriptions, although the limit is not exposed.
+ * Three memory pages worth of text is way too long though.
+ */
+ memset(ptr, 'A', size);
+ if (describe_node(mib, 2, id, ptr, 1) != -1) e(0);
+ if (errno != EINVAL) e(0); /* not EFAULT, should never get that far */
+
+ ptr[size - 1] = '\0';
+ if (describe_node(mib, 2, id, ptr, 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (describe_node(mib, 2, id, "", 0) != 0) e(0);
+
+ /*
+ * Second, the description routine must deal with faults occurring
+ * while it is trying to find the string end.
+ */
+ ptr[size - 2] = 'B';
+ ptr[size - 1] = 'C';
+ if (describe_node(mib, 2, id, &ptr[size - 3], 1) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ if (describe_node(mib, 2, id, "", 0) != 0) e(0);
+
+ ptr[size - 1] = '\0';
+ if (describe_node(mib, 2, id, &ptr[size - 3], 1) != 0) e(0);
+
+ if (describe_node(mib, 2, id, "AB", 0) != 0) e(0);
+
+ /* Pass the second dynamic node ID to the unprivileged child. */
+ mib[2] = TEST_ANYWRITE;
+ if (sysctl(mib, 3, NULL, NULL, &id, sizeof(id)) != 0) e(0);
+
+ (void)test_nonroot(sub87g);
+
+ mib[2] = id;
+ oldlen = size;
+ if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != pgsz) e(0);
+ if (test_buf(ptr, 'h', pgsz - 1, 1) != 0) e(0);
+ if (ptr[pgsz - 1] != '\0') e(0);
+
+ if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0);
+ if (destroy_node(mib, 2, id) != 0) e(0);
+
+ munmap(ptr, size);
+}
+
+/*
+ * Verify whether the given node on the given path has the given node version.
+ * Return 0 if the version matches, or -1 if it does not or a failure occurred.
+ */
+static int
+check_version(const int * path, unsigned int pathlen, int id, uint32_t ver)
+{
+ struct sysctlnode scn;
+ struct sysctldesc scd;
+ size_t oldlen;
+ int r, mib[CTL_MAXNAME];
+
+ assert(pathlen < CTL_MAXNAME);
+ memcpy(mib, path, sizeof(mib[0]) * pathlen);
+ mib[pathlen] = CTL_DESCRIBE;
+
+ /*
+ * For some reason, when retrieving a particular description (as
+ * opposed to setting one), the node version number is not checked.
+ * In order to test this, we deliberately pass in a node version number
+ * that, if checked, would eventually cause failures.
+ */
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION;
+ scn.sysctl_num = id;
+ scn.sysctl_ver = 1;
+ oldlen = sizeof(scd);
+ r = sysctl(mib, pathlen + 1, &scd, &oldlen, &scn, sizeof(scn));
+ if (r == -1 && errno != ENOMEM) e(0);
+
+ return (scd.descr_ver == ver) ? 0 : -1;
+}
+
+/*
+ * Test sysctl(2) node versioning.
+ */
+static void
+test87h(void)
+{
+ struct sysctlnode scn, oldscn;
+ size_t oldlen;
+ uint32_t ver[4];
+ int mib[4], id[4];
+
+ /*
+ * The other tests have already tested sufficiently that a zero version
+ * is always accepted in calls. Here, we test that node versions
+ * actually change when creating and destroying nodes, and that the
+ * right version test is implemented for all of the four node meta-
+ * operations (query, create, destroy, describe). Why did we not do
+ * this earlier, you ask? Well, versioning was implemented later on.
+ */
+ subtest = 7;
+
+ /*
+ * Test versioning with node creation.
+ */
+ mib[0] = CTL_MINIX;
+ mib[1] = MINIX_TEST;
+ mib[2] = CTL_CREATE;
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_READWRITE | CTLTYPE_NODE;
+ scn.sysctl_num = CTL_CREATE;
+ strlcpy(scn.sysctl_name, "NodeA", sizeof(scn.sysctl_name));
+ oldlen = sizeof(oldscn);
+ if (sysctl(mib, 3, &oldscn, &oldlen, &scn, sizeof(scn)) != 0) e(0);
+ if (oldlen != sizeof(oldscn)) e(0);
+ id[0] = oldscn.sysctl_num;
+ ver[0] = oldscn.sysctl_ver;
+ if (ver[0] == 0) e(0);
+
+ if (check_version(mib, 0, CTL_MINIX, ver[0]) != 0) e(0);
+ if (check_version(mib, 1, MINIX_TEST, ver[0]) != 0) e(0);
+ if (check_version(mib, 2, id[0], ver[0]) != 0) e(0);
+
+ strlcpy(scn.sysctl_name, "NodeB", sizeof(scn.sysctl_name));
+ oldlen = sizeof(oldscn);
+ if (sysctl(mib, 3, &oldscn, &oldlen, &scn, sizeof(scn)) != 0) e(0);
+ if (oldlen != sizeof(oldscn)) e(0);
+ id[1] = oldscn.sysctl_num;
+ ver[1] = oldscn.sysctl_ver;
+ if (ver[1] == 0) e(0);
+ if (ver[1] != NEXT_VER(ver[0])) e(0);
+
+ if (check_version(mib, 0, CTL_MINIX, ver[1]) != 0) e(0);
+ if (check_version(mib, 1, MINIX_TEST, ver[1]) != 0) e(0);
+ if (check_version(mib, 2, id[0], ver[0]) != 0) e(0);
+ if (check_version(mib, 2, id[1], ver[1]) != 0) e(0);
+
+ /* A version that is too high should be rejected. */
+ mib[2] = id[0];
+ mib[3] = CTL_CREATE;
+ scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_IMMEDIATE |
+ CTLFLAG_READWRITE | CTLTYPE_INT;
+ scn.sysctl_size = sizeof(int);
+ scn.sysctl_ver = NEXT_VER(ver[1]);
+ strlcpy(scn.sysctl_name, "ValueA", sizeof(scn.sysctl_name));
+ if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ /* The version of the parent node should be accepted. */
+ scn.sysctl_ver = ver[0]; /* different from the root node version */
+ oldlen = sizeof(oldscn);
+ if (sysctl(mib, 4, &oldscn, &oldlen, &scn, sizeof(scn)) != 0) e(0);
+ if (oldlen != sizeof(oldscn)) e(0);
+ id[2] = oldscn.sysctl_num;
+ ver[2] = oldscn.sysctl_ver;
+ if (ver[2] == 0) e(0);
+ if (ver[2] != NEXT_VER(ver[1])) e(0);
+
+ if (check_version(mib, 0, CTL_MINIX, ver[2]) != 0) e(0);
+ if (check_version(mib, 1, MINIX_TEST, ver[2]) != 0) e(0);
+ if (check_version(mib, 2, id[0], ver[2]) != 0) e(0);
+ if (check_version(mib, 3, id[2], ver[2]) != 0) e(0);
+ if (check_version(mib, 2, id[1], ver[1]) != 0) e(0);
+
+ /* A version that is too low (old) should be rejected. */
+ mib[2] = id[1];
+
+ scn.sysctl_ver = ver[0];
+ strlcpy(scn.sysctl_name, "ValueB", sizeof(scn.sysctl_name));
+ if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ /* The version of the root node should be accepted. */
+ scn.sysctl_ver = ver[2]; /* different from the parent node version */
+ oldlen = sizeof(oldscn);
+ if (sysctl(mib, 4, &oldscn, &oldlen, &scn, sizeof(scn)) != 0) e(0);
+ if (oldlen != sizeof(oldscn)) e(0);
+ id[3] = oldscn.sysctl_num;
+ ver[3] = oldscn.sysctl_ver;
+ if (ver[3] == 0) e(0);
+ if (ver[3] != NEXT_VER(ver[2])) e(0);
+
+ if (check_version(mib, 0, CTL_MINIX, ver[3]) != 0) e(0);
+ if (check_version(mib, 1, MINIX_TEST, ver[3]) != 0) e(0);
+ if (check_version(mib, 2, id[0], ver[2]) != 0) e(0);
+ if (check_version(mib, 2, id[1], ver[3]) != 0) e(0);
+ if (check_version(mib, 3, id[3], ver[3]) != 0) e(0);
+ mib[2] = id[0];
+ if (check_version(mib, 3, id[2], ver[2]) != 0) e(0);
+
+ /*
+ * Test versioning with node queries.
+ */
+ mib[3] = CTL_QUERY;
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION;
+ scn.sysctl_ver = ver[0]; /* previous parent version */
+ if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_ver = ver[2]; /* parent version */
+ if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ scn.sysctl_ver = ver[2]; /* root version */
+ if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ scn.sysctl_ver = NEXT_VER(ver[3]); /* nonexistent version */
+ if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ /*
+ * Test versioning with node description.
+ */
+ mib[2] = CTL_DESCRIBE;
+ scn.sysctl_num = id[0];
+ scn.sysctl_ver = ver[3]; /* root and parent, but not target version */
+ scn.sysctl_desc = "Parent A";
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_ver = ver[1]; /* another bad version */
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_ver = ver[2]; /* target version */
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ /* Neither querying nor description should have changed versions. */
+ if (check_version(mib, 0, CTL_MINIX, ver[3]) != 0) e(0);
+ if (check_version(mib, 1, MINIX_TEST, ver[3]) != 0) e(0);
+ if (check_version(mib, 2, id[0], ver[2]) != 0) e(0);
+ if (check_version(mib, 2, id[1], ver[3]) != 0) e(0);
+ mib[2] = id[1];
+ if (check_version(mib, 3, id[3], ver[3]) != 0) e(0);
+ mib[2] = id[0];
+ if (check_version(mib, 3, id[2], ver[2]) != 0) e(0);
+
+ /*
+ * Test versioning with node destruction.
+ */
+ mib[3] = CTL_DESTROY;
+ memset(&scn, 0, sizeof(scn));
+ scn.sysctl_flags = SYSCTL_VERSION;
+ scn.sysctl_num = id[2];
+ scn.sysctl_ver = ver[3]; /* root but not target version */
+ if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_ver = ver[2]; /* target (and parent) version */
+ if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ /* Fortunately, versions are predictable. */
+ ver[0] = NEXT_VER(ver[3]);
+
+ if (check_version(mib, 0, CTL_MINIX, ver[0]) != 0) e(0);
+ if (check_version(mib, 1, MINIX_TEST, ver[0]) != 0) e(0);
+ if (check_version(mib, 2, id[0], ver[0]) != 0) e(0);
+ if (check_version(mib, 2, id[1], ver[3]) != 0) e(0);
+
+ mib[2] = id[1];
+ scn.sysctl_num = id[3];
+ scn.sysctl_ver = ver[0]; /* root but not target version */
+ if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_ver = ver[3]; /* target (and parent) version */
+ if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != 0) e(0);
+
+ ver[1] = NEXT_VER(ver[0]);
+
+ if (check_version(mib, 0, CTL_MINIX, ver[1]) != 0) e(0);
+ if (check_version(mib, 1, MINIX_TEST, ver[1]) != 0) e(0);
+ if (check_version(mib, 2, id[0], ver[0]) != 0) e(0);
+ if (check_version(mib, 2, id[1], ver[1]) != 0) e(0);
+
+ mib[2] = CTL_DESTROY;
+ scn.sysctl_num = id[0];
+ scn.sysctl_ver = ver[1]; /* root and parent, but not target version */
+ if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ scn.sysctl_ver = ver[0]; /* target version */
+ oldlen = sizeof(oldscn);
+ if (sysctl(mib, 3, &oldscn, &oldlen, &scn, sizeof(scn)) != 0) e(0);
+ if (oldlen != sizeof(oldscn)) e(0);
+ if (oldscn.sysctl_num != id[0]) e(0);
+ if (oldscn.sysctl_ver != ver[0]) e(0);
+
+ ver[2] = NEXT_VER(ver[1]);
+
+ if (check_version(mib, 0, CTL_MINIX, ver[2]) != 0) e(0);
+ if (check_version(mib, 1, MINIX_TEST, ver[2]) != 0) e(0);
+ if (check_version(mib, 2, id[1], ver[1]) != 0) e(0);
+
+ /* For the last destruction, just see if we get the old version. */
+ scn.sysctl_num = id[1];
+ scn.sysctl_ver = 0;
+ oldlen = sizeof(oldscn);
+ if (sysctl(mib, 3, &oldscn, &oldlen, &scn, sizeof(scn)) != 0) e(0);
+ if (oldlen != sizeof(oldscn)) e(0);
+ if (oldscn.sysctl_num != id[1]) e(0);
+ if (oldscn.sysctl_ver != ver[1]) e(0);
+
+ ver[3] = NEXT_VER(ver[2]);
+
+ if (check_version(mib, 0, CTL_MINIX, ver[3]) != 0) e(0);
+ if (check_version(mib, 1, MINIX_TEST, ver[3]) != 0) e(0);
+}
+
+/*
+ * Perform pre-test initialization.
+ */
+static void
+test87_init(void)
+{
+ size_t oldlen;
+ int mib[3];
+
+ subtest = 99;
+
+ if ((bad_ptr = mmap(NULL, getpagesize(), PROT_READ,
+ MAP_ANON | MAP_PRIVATE, -1, 0)) == MAP_FAILED) e(0);
+ if (munmap(bad_ptr, getpagesize()) != 0) e(0);
+
+ mib[0] = CTL_MINIX;
+ mib[1] = MINIX_MIB;
+ mib[2] = MIB_NODES;
+ oldlen = sizeof(nodes);
+ if (sysctl(mib, 3, &nodes, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(nodes)) e(0);
+
+ mib[2] = MIB_OBJECTS;
+ oldlen = sizeof(objects);
+ if (sysctl(mib, 3, &objects, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(objects)) e(0);
+}
+
+/*
+ * Perform post-test checks.
+ */
+static void
+test87_check(void)
+{
+ unsigned int newnodes, newobjects;
+ size_t oldlen;
+ int mib[3];
+
+ subtest = 99;
+
+ mib[0] = CTL_MINIX;
+ mib[1] = MINIX_MIB;
+ mib[2] = MIB_NODES;
+ oldlen = sizeof(newnodes);
+ if (sysctl(mib, 3, &newnodes, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(newnodes)) e(0);
+
+ /*
+ * Upon the first run, the total number of nodes must actually go down,
+ * as we destroy number of static nodes. Upon subsequent runs, the
+ * number of nodes should remain stable. Thus, we can safely test that
+ * the number of nodes has not gone up as a result of the test.
+ */
+ if (newnodes > nodes) e(0);
+
+ mib[2] = MIB_OBJECTS;
+ oldlen = sizeof(newobjects);
+ if (sysctl(mib, 3, &newobjects, &oldlen, NULL, 0) != 0) e(0);
+ if (oldlen != sizeof(newobjects)) e(0);
+
+ /*
+ * The number of dynamically allocated objects should remain the same
+ * across the test.
+ */
+ if (newobjects != objects) e(0);
+}
+
+/*
+ * Test program for sysctl(2).
+ */
+int
+main(int argc, char ** argv)
+{
+ int i, m;
+
+ start(87);
+
+ if (argc == 2)
+ m = atoi(argv[1]);
+ else
+ m = 0xFF;
+
+ test87_init();
+
+ for (i = 0; i < ITERATIONS; i++) {
+ if (m & 0x001) test87a();
+ if (m & 0x002) test87b();
+ if (m & 0x004) test87c();
+ if (m & 0x008) test87d();
+ if (m & 0x010) test87e();
+ if (m & 0x020) test87f();
+ if (m & 0x040) test87g();
+ if (m & 0x080) test87h();
+ }
+
+ test87_check();
+
+ quit();
+}