Several meta-identifiers are provided to perform operations on the
sysctl tree itself, or support alternate means of accessing the data instrumented by the
sysctl tree.
CTL_QUERY
Retrieve a mapping of names to numbers below a given node
CTL_CREATE
Create a new node
CTL_CREATESYM
Create a new node by its kernel symbol
CTL_DESTROY
Destroy a node
CTL_DESCRIBE
Retrieve node descriptions
The core interface to all of these meta-functions is the structure that the kernel uses to describe the tree internally, as defined in
<sys/sysctl.h> as:
struct sysctlnode {
uint32_t sysctl_flags; /* flags and type */
int32_t sysctl_num; /* mib number */
char sysctl_name[SYSCTL_NAMELEN]; /* node name */
uint32_t sysctl_ver; /* node's version vs. rest of tree */
uint32_t __rsvd;
union {
struct {
uint32_t suc_csize; /* size of child node array */
uint32_t suc_clen; /* number of valid children */
struct sysctlnode* suc_child; /* array of child nodes */
} scu_child;
struct {
void *sud_data; /* pointer to external data */
size_t sud_offset; /* offset to data */
} scu_data;
int32_t scu_alias; /* node this node refers to */
int32_t scu_idata; /* immediate "int" data */
u_quad_t scu_qdata; /* immediate "u_quad_t" data */
} sysctl_un;
size_t _sysctl_size; /* size of instrumented data */
sysctlfn _sysctl_func; /* access helper function */
struct sysctlnode *sysctl_parent; /* parent of this node */
const char *sysctl_desc; /* description of node */
};
#define sysctl_csize sysctl_un.scu_child.suc_csize
#define sysctl_clen sysctl_un.scu_child.suc_clen
#define sysctl_child sysctl_un.scu_child.suc_child
#define sysctl_data sysctl_un.scu_data.sud_data
#define sysctl_offset sysctl_un.scu_data.sud_offset
#define sysctl_alias sysctl_un.scu_alias
#define sysctl_idata sysctl_un.scu_idata
#define sysctl_qdata sysctl_un.scu_qdata
Querying the tree to discover the name to number mapping permits dynamic discovery of all the data that the tree currently has instrumented. For example, to discover all the nodes below the CTL_VFS node:
struct sysctlnode query, vfs[128];
int mib[2];
size_t len;
mib[0] = CTL_VFS;
mib[1] = CTL_QUERY;
memset(&query, 0, sizeof(query));
query.sysctl_flags = SYSCTL_VERSION;
len = sizeof(vfs);
sysctl(mib, 2, &vfs[0], &len, &query, sizeof(query));
Note that a reference to an empty node with
sysctl_flags set to
SYSCTL_VERSION is passed to sysctl in order to indicate the version that the program is using. All dynamic operations passing nodes into sysctl require that the version be explicitly specified.
Creation and destruction of nodes works by constructing part of a new node description (or a description of the existing node) and invoking CTL_CREATE (or CTL_CREATESYM) or CTL_DESTROY at the parent of the new node, with a pointer to the new node passed via the
new and
newlen arguments. If valid values for
old and
oldlenp are passed, a copy of the new node once in the tree will be returned. If the create operation fails because a node with the same name or MIB number exists, a copy of the conflicting node will be returned.
The minimum requirements for creating a node are setting the
sysctl_flags to indicate the new node's type,
sysctl_num to either the new node's number (or CTL_CREATE or CTL_CREATESYM if a dynamically allocated MIB number is acceptable),
sysctl_size to the size of the data to be instrumented (which must agree with the given type), and
sysctl_name must be set to the new node's name. Nodes that are not of type “node” must also have some description of the data to be instrumented, which will vary depending on what is to be instrumented.
If existing kernel data is to be covered by this new node, its address should be given in
sysctl_data or, if CTL_CREATESYM is used,
sysctl_data should be set to a string containing its name from the kernel's symbol table. If new data is to be instrumented and an initial value is available, the new integer or quad type data should be placed into either
sysctl_idata or
sysctl_qdata, respectively, along with the SYSCTL_IMMEDIATE flag being set, or
sysctl_data should be set to point to a copy of the new data, and the SYSCTL_OWNDATA flag must be set. This latter method is the only way that new string and struct type nodes can be initialized. Invalid kernel addresses are accepted, but any attempt to access those nodes will return an error.
The
sysctl_csize,
sysctl_clen,
sysctl_child,
sysctl_parent, and
sysctl_alias members are used by the kernel to link the tree together and must be
NULL or 0. Nodes created in this manner cannot have helper functions, so
sysctl_func must also be
NULL. If the
sysctl_ver member is non-zero, it must match either the version of the parent or the version at the root of the MIB or an error is returned. This can be used to ensure that nodes are only added or removed from a known state of the tree. Note: It may not be possible to determine the version at the root of the tree.
This example creates a new subtree and adds a node to it that controls the
audiodebug kernel variable, thereby making it tunable at at any time, without needing to use
ddb(4) or
kvm(3) to alter the kernel's memory directly.
struct sysctlnode node;
int mib[2];
size_t len;
mib[0] = CTL_CREATE; /* create at top-level */
len = sizeof(node);
memset(&node, 0, len);
node.sysctl_flags = SYSCTL_VERSION|CTLFLAG_READWRITE|CTLTYPE_NODE;
snprintf(node.sysctl_name, sizeof(node.sysctl_name), "local");
node.sysctl_num = CTL_CREATE; /* request dynamic MIB number */
sysctl(&mib[0], 1, &node, &len, &node, len);
mib[0] = node.sysctl_num; /* use new MIB number */
mib[1] = CTL_CREATESYM; /* create at second level */
len = sizeof(node);
memset(&node, 0, len);
node.sysctl_flags = SYSCTL_VERSION|CTLFLAG_READWRITE|CTLTYPE_INT;
snprintf(node.sysctl_name, sizeof(node.sysctl_name), "audiodebug");
node.sysctl_num = CTL_CREATE;
node.sysctl_data = "audiodebug"; /* kernel symbol to be used */
sysctl(&mib[0], 2, NULL, NULL, &node, len);
The process for deleting nodes is similar, but less data needs to be supplied. Only the
sysctl_num field needs to be filled in; almost all other fields must be left blank. The
sysctl_name and/or
sysctl_ver fields can be filled in with the name and version of the existing node as additional checks on what will be deleted. If all the given data fail to match any node, nothing will be deleted. If valid values for
old and
oldlenp are supplied and a node is deleted, a copy of what was in the MIB tree will be returned.
This sample code shows the deletion of the two nodes created in the above example:
int mib[2];
len = sizeof(node);
memset(&node, 0, len);
node.sysctl_flags = SYSCTL_VERSION;
mib[0] = 3214; /* assumed number for "local" */
mib[1] = CTL_DESTROY;
node.sysctl_num = 3215; /* assumed number for "audiodebug" */
sysctl(&mib[0], 2, NULL, NULL, &node, len);
mib[0] = CTL_DESTROY;
node.sysctl_num = 3214; /* now deleting "local" */
sysctl(&mib[0], 1, NULL, NULL, &node, len);
Descriptions of each of the nodes can also be retrieved, if they are available. Descriptions can be retrieved in bulk at each level or on a per-node basis. The layout of the buffer into which the descriptions are returned is a series of variable length structures, each of which describes its own size. The length indicated includes the terminating ‘nul' character. Nodes that have no description or where the description is not available are indicated by an empty string. The
descr_ver will match the
sysctl_ver value for a given node, so that descriptions for nodes whose number have been recycled can be detected and ignored or discarded.
struct sysctldesc {
int32_t descr_num; /* mib number of node */
uint32_t descr_ver; /* version of node */
uint32_t descr_len; /* length of description string */
char descr_str[1]; /* not really 1...see above */
};
The
NEXT_DESCR() macro can be used to skip to the next description in the retrieved list.
struct sysctlnode desc;
struct sysctldesc *d;
char buf[1024];
int mib[2];
size_t len;
/* retrieve kern-level descriptions */
mib[0] = CTL_KERN;
mib[1] = CTL_DESCRIBE;
d = (struct sysctldesc *)&buf[0];
len = sizeof(buf);
sysctl(mib, 2, d, &len, NULL, 0);
while ((caddr_t)d < (caddr_t)&buf[len]) {
printf("node %d: %.*s\n", d->descr_num, d->descr_len,
d->descr_str);
d = NEXT_DESCR(d);
}
/* retrieve description for kern.securelevel */
memset(&desc, 0, sizeof(desc));
desc.sysctl_flags = SYSCTL_VERSION;
desc.sysctl_num = KERN_SECURELEVEL;
d = (struct sysctldesc *)&buf[0];
len = sizeof(buf);
sysctl(mib, 2, d, &len, &desc, sizeof(desc));
printf("kern.securelevel: %.*s\n", d->descr_len, d->descr_str);
Descriptions can also be set as follows, subject to the following rules:
•
The kernel securelevel is at zero or lower
•
The caller has super-user privileges
•
The node does not currently have a description
•
The node is not marked as “permanent”
struct sysctlnode desc;
int mib[2];
/* presuming the given top-level node was just added... */
mib[0] = 3214; /* mib numbers taken from previous examples */
mib[1] = CTL_DESCRIBE;
memset(&desc, 0, sizeof(desc));
desc.sysctl_flags = SYSCTL_VERSION;
desc.sysctl_num = 3215;
desc.sysctl_desc = "audio debug control knob";
sysctl(mib, 2, NULL, NULL, &desc, sizeof(desc));
Upon successfully setting a description, the new description will be returned in the space indicated by the
oldp and
oldlenp arguments.
The
sysctl_flags field in the struct sysctlnode contains the sysctl version, node type information, and a number of flags. The macros
SYSCTL_VERS(),
SYSCTL_TYPE(), and
SYSCTL_FLAGS() can be used to access the different fields. Valid flags are:
CTLFLAG_READONLY
Node is read-only
CTLFLAG_READWRITE
Node is writable by the superuser
CTLFLAG_ANYWRITE
Node is writable by anyone
CTLFLAG_PRIVATE
Node is readable only by the superuser
CTLFLAG_PERMANENT
Node cannot be removed (cannot be set by processes)
CTLFLAG_OWNDATA
Node owns data and does not instrument existing data
CTLFLAG_IMMEDIATE
Node contains instrumented data and does not instrument existing data
CTLFLAG_HEX
Node's contents should be displayed in a hexadecimal form
CTLFLAG_ROOT
Node is the root of a tree (cannot be set at any time)
CTLFLAG_ANYNUMBER
Node matches any MIB number (cannot be set by processes)
CTLFLAG_HIDDEN
Node not displayed by default
CTLFLAG_ALIAS
Node refers to a sibling node (cannot be set by processes)
CTLFLAG_OWNDESC
Node owns its own description string space