summaryrefslogtreecommitdiffstats
path: root/sys
diff options
context:
space:
mode:
authorabial <abial@FreeBSD.org>2000-07-15 10:26:04 +0000
committerabial <abial@FreeBSD.org>2000-07-15 10:26:04 +0000
commitc7bf2569fa53882ac3f6a6d420425de0c7f8a490 (patch)
treef296f4156d4f32d397b59fa2297d1fc077e7b275 /sys
parentba6bef6211a46c4c72c4df1b86bfb8b591437184 (diff)
downloadFreeBSD-src-c7bf2569fa53882ac3f6a6d420425de0c7f8a490.zip
FreeBSD-src-c7bf2569fa53882ac3f6a6d420425de0c7f8a490.tar.gz
These patches implement dynamic sysctls. It's possible now to add
and remove sysctl oids at will during runtime - they don't rely on linker sets. Also, the node oids can be referenced by more than one kernel user, which means that it's possible to create partially overlapping trees. Add sysctl contexts to help programmers manage multiple dynamic oids in convenient way. Please see the manpages for detailed discussion, and example module for typical use. This work is based on ideas and code snippets coming from many people, among them: Arun Sharma, Jonathan Lemon, Doug Rabson, Brian Feldman, Kelly Yancey, Poul-Henning Kamp and others. I'd like to specially thank Brian Feldman for detailed review and style fixes. PR: kern/16928 Reviewed by: dfr, green, phk
Diffstat (limited to 'sys')
-rw-r--r--sys/kern/kern_sysctl.c252
-rw-r--r--sys/sys/sysctl.h78
2 files changed, 329 insertions, 1 deletions
diff --git a/sys/kern/kern_sysctl.c b/sys/kern/kern_sysctl.c
index 807d4ae..a827d55 100644
--- a/sys/kern/kern_sysctl.c
+++ b/sys/kern/kern_sysctl.c
@@ -53,6 +53,7 @@
#include <vm/vm_extern.h>
static MALLOC_DEFINE(M_SYSCTL, "sysctl", "sysctl internal magic");
+static MALLOC_DEFINE(M_SYSCTLOID, "sysctloid", "sysctl dynamic oids");
/*
* Locking and stats
@@ -67,6 +68,19 @@ static int sysctl_root(SYSCTL_HANDLER_ARGS);
struct sysctl_oid_list sysctl__children; /* root list */
+static struct sysctl_oid *
+sysctl_find_oidname(const char *name, struct sysctl_oid_list *list)
+{
+ struct sysctl_oid *oidp;
+
+ SLIST_FOREACH(oidp, list, oid_link) {
+ if (strcmp(oidp->oid_name, name) == 0) {
+ return (oidp);
+ }
+ }
+ return (NULL);
+}
+
/*
* Initialization of the MIB tree.
*
@@ -81,6 +95,20 @@ void sysctl_register_oid(struct sysctl_oid *oidp)
int n;
/*
+ * First check if another oid with the same name already
+ * exists in the parent's list.
+ */
+ p = sysctl_find_oidname(oidp->oid_name, parent);
+ if (p != NULL) {
+ if ((p->oid_kind & CTLTYPE) == CTLTYPE_NODE) {
+ p->oid_refcnt++;
+ return;
+ } else {
+ printf("can't re-use a leaf (%s)!\n", p->oid_name);
+ return;
+ }
+ }
+ /*
* If this oid has a number OID_AUTO, give it a number which
* is greater than any current oid. Make sure it is at least
* 100 to leave space for pre-assigned oid numbers.
@@ -115,6 +143,230 @@ void sysctl_unregister_oid(struct sysctl_oid *oidp)
SLIST_REMOVE(oidp->oid_parent, oidp, sysctl_oid, oid_link);
}
+/* Initialize a new context to keep track of dynamically added sysctls. */
+int
+sysctl_ctx_init(struct sysctl_ctx_list *c)
+{
+
+ if (c == NULL) {
+ return (EINVAL);
+ }
+ TAILQ_INIT(c);
+ return (0);
+}
+
+/* Free the context, and destroy all dynamic oids registered in this context */
+int
+sysctl_ctx_free(struct sysctl_ctx_list *clist)
+{
+ struct sysctl_ctx_entry *e, *e1;
+ int error;
+
+ error = 0;
+ /*
+ * First perform a "dry run" to check if it's ok to remove oids.
+ * XXX FIXME
+ * XXX This algorithm is a hack. But I don't know any
+ * XXX better solution for now...
+ */
+ TAILQ_FOREACH(e, clist, link) {
+ error = sysctl_remove_oid(e->entry, 0, 0);
+ if (error)
+ break;
+ }
+ /*
+ * Restore deregistered entries, either from the end,
+ * or from the place where error occured.
+ * e contains the entry that was not unregistered
+ */
+ if (error)
+ e1 = TAILQ_PREV(e, sysctl_ctx_list, link);
+ else
+ e1 = TAILQ_LAST(clist, sysctl_ctx_list);
+ while (e1 != NULL) {
+ sysctl_register_oid(e1->entry);
+ e1 = TAILQ_PREV(e1, sysctl_ctx_list, link);
+ }
+ if (error)
+ return(EBUSY);
+ /* Now really delete the entries */
+ e = TAILQ_FIRST(clist);
+ while (e != NULL) {
+ e1 = TAILQ_NEXT(e, link);
+ error = sysctl_remove_oid(e->entry, 1, 0);
+ if (error)
+ panic("sysctl_remove_oid: corrupt tree, entry: %s",
+ e->entry->oid_name);
+ free(e, M_SYSCTLOID);
+ e = e1;
+ }
+ return (error);
+}
+
+/* Add an entry to the context */
+struct sysctl_ctx_entry *
+sysctl_ctx_entry_add(struct sysctl_ctx_list *clist, struct sysctl_oid *oidp)
+{
+ struct sysctl_ctx_entry *e;
+
+ if (clist == NULL || oidp == NULL)
+ return(NULL);
+ e = malloc (sizeof(struct sysctl_ctx_entry), M_SYSCTLOID, M_WAITOK);
+ e->entry = oidp;
+ TAILQ_INSERT_HEAD(clist, e, link);
+ return (e);
+}
+
+/* Find an entry in the context */
+struct sysctl_ctx_entry *
+sysctl_ctx_entry_find(struct sysctl_ctx_list *clist, struct sysctl_oid *oidp)
+{
+ struct sysctl_ctx_entry *e;
+
+ if (clist == NULL || oidp == NULL)
+ return(NULL);
+ for (e = TAILQ_FIRST(clist); e != NULL; e = TAILQ_NEXT(e, link)) {
+ if(e->entry == oidp)
+ return(e);
+ }
+ return (e);
+}
+
+/*
+ * Delete an entry from the context.
+ * NOTE: this function doesn't free oidp! You have to remove it
+ * with sysctl_remove_oid().
+ */
+int
+sysctl_ctx_entry_del(struct sysctl_ctx_list *clist, struct sysctl_oid *oidp)
+{
+ struct sysctl_ctx_entry *e;
+
+ if (clist == NULL || oidp == NULL)
+ return (EINVAL);
+ e = sysctl_ctx_entry_find(clist, oidp);
+ if (e != NULL) {
+ TAILQ_REMOVE(clist, e, link);
+ free(e, M_SYSCTLOID);
+ return (0);
+ } else
+ return (ENOENT);
+}
+
+/*
+ * Remove dynamically created sysctl trees.
+ * oidp - top of the tree to be removed
+ * del - if 0 - just deregister, otherwise free up entries as well
+ * recurse - if != 0 traverse the subtree to be deleted
+ */
+int
+sysctl_remove_oid(struct sysctl_oid *oidp, int del, int recurse)
+{
+ struct sysctl_oid *p;
+ int error;
+
+ if (oidp == NULL)
+ return(EINVAL);
+ if ((oidp->oid_kind & CTLFLAG_DYN) == 0) {
+ printf("can't remove non-dynamic nodes!\n");
+ return (EINVAL);
+ }
+ /*
+ * WARNING: normal method to do this should be through
+ * sysctl_ctx_free(). Use recursing as the last resort
+ * method to purge your sysctl tree of leftovers...
+ * However, if some other code still references these nodes,
+ * it will panic.
+ */
+ if ((oidp->oid_kind & CTLTYPE) == CTLTYPE_NODE) {
+ if (oidp->oid_refcnt == 1) {
+ SLIST_FOREACH(p, SYSCTL_CHILDREN(oidp), oid_link) {
+ if (!recurse)
+ return (ENOTEMPTY);
+ error = sysctl_remove_oid(p, del, recurse);
+ if (error)
+ return (error);
+ }
+ if (del)
+ free(SYSCTL_CHILDREN(oidp), M_SYSCTLOID);
+ }
+ }
+ if (oidp->oid_refcnt > 1 ) {
+ oidp->oid_refcnt--;
+ } else {
+ if (oidp->oid_refcnt == 0) {
+ printf("Warning: bad oid_refcnt=%u (%s)!\n",
+ oidp->oid_refcnt, oidp->oid_name);
+ return (EINVAL);
+ }
+ sysctl_unregister_oid(oidp);
+ if (del) {
+ free ((char *)oidp->oid_name, M_SYSCTLOID);
+ free(oidp, M_SYSCTLOID);
+ }
+ }
+ return (0);
+}
+
+/*
+ * Create new sysctls at run time.
+ * clist may point to a valid context initialized with sysctl_ctx_init().
+ */
+struct sysctl_oid *
+sysctl_add_oid(struct sysctl_ctx_list *clist, struct sysctl_oid_list *parent,
+ int number, char *name, int kind, void *arg1, int arg2,
+ int (*handler)(SYSCTL_HANDLER_ARGS), char *fmt, char *descr)
+{
+ struct sysctl_oid *oidp;
+ ssize_t len;
+
+ /* You have to hook up somewhere.. */
+ if (parent == NULL)
+ return(NULL);
+ /* Check if the node already exists, otherwise create it */
+ oidp = sysctl_find_oidname(name, parent);
+ if (oidp != NULL) {
+ if ((oidp->oid_kind & CTLTYPE) == CTLTYPE_NODE) {
+ oidp->oid_refcnt++;
+ /* Update the context */
+ if (clist != NULL)
+ sysctl_ctx_entry_add(clist, oidp);
+ return (oidp);
+ } else {
+ printf("can't re-use a leaf (%s)!\n", name);
+ return (NULL);
+ }
+ }
+ oidp = malloc(sizeof(struct sysctl_oid), M_SYSCTLOID, M_WAITOK);
+ bzero(oidp, sizeof(struct sysctl_oid));
+ oidp->oid_parent = parent;
+ SLIST_NEXT(oidp, oid_link) = NULL;
+ oidp->oid_number = number;
+ oidp->oid_refcnt = 1;
+ len = strlen(name);
+ oidp->oid_name = (const char *)malloc(len + 1, M_SYSCTLOID, M_WAITOK);
+ bcopy(name, (char *)oidp->oid_name, len + 1);
+ (char)oidp->oid_name[len] = '\0';
+ oidp->oid_handler = handler;
+ oidp->oid_kind = CTLFLAG_DYN | kind;
+ if ((kind & CTLTYPE) == CTLTYPE_NODE) {
+ /* Allocate space for children */
+ SYSCTL_CHILDREN(oidp) = malloc(sizeof(struct sysctl_oid_list),
+ M_SYSCTLOID, M_WAITOK);
+ SLIST_INIT(SYSCTL_CHILDREN(oidp));
+ } else {
+ oidp->oid_arg1 = arg1;
+ oidp->oid_arg2 = arg2;
+ }
+ oidp->oid_fmt = fmt;
+ /* Update the context, if used */
+ if (clist != NULL)
+ sysctl_ctx_entry_add(clist, oidp);
+ /* Register this oid */
+ sysctl_register_oid(oidp);
+ return (oidp);
+}
+
/*
* Bulk-register all the oids in a linker_set.
*/
diff --git a/sys/sys/sysctl.h b/sys/sys/sysctl.h
index 642dd40..e2afc47 100644
--- a/sys/sys/sysctl.h
+++ b/sys/sys/sysctl.h
@@ -81,6 +81,7 @@ struct ctlname {
#define CTLFLAG_ANYBODY 0x10000000 /* All users can set this var */
#define CTLFLAG_SECURE 0x08000000 /* Permit set only if securelevel<=0 */
#define CTLFLAG_PRISON 0x04000000 /* Prisoned roots can fiddle */
+#define CTLFLAG_DYN 0x02000000 /* Dynamic oid - can be freed */
/*
* USE THIS instead of a hardwired number from the categories below
@@ -128,6 +129,7 @@ struct sysctl_oid {
const char *oid_name;
int (*oid_handler)(SYSCTL_HANDLER_ARGS);
const char *oid_fmt;
+ int oid_refcnt;
};
#define SYSCTL_IN(r, p, l) (r->newfunc)(r, p, l)
@@ -145,10 +147,26 @@ int sysctl_handle_opaque(SYSCTL_HANDLER_ARGS);
void sysctl_register_oid(struct sysctl_oid *oidp);
void sysctl_unregister_oid(struct sysctl_oid *oidp);
-/* Declare an oid to allow child oids to be added to it. */
+/* Declare a static oid to allow child oids to be added to it. */
#define SYSCTL_DECL(name) \
extern struct sysctl_oid_list sysctl_##name##_children
+/* Hide these in macros */
+#define SYSCTL_CHILDREN(oid_ptr) (struct sysctl_oid_list *) \
+ (oid_ptr)->oid_arg1
+#define SYSCTL_STATIC_CHILDREN(oid_name) \
+ (&sysctl_##oid_name##_children)
+
+/* === Structs and macros related to context handling === */
+
+/* All dynamically created sysctls can be tracked in a context list. */
+struct sysctl_ctx_entry {
+ struct sysctl_oid *entry;
+ TAILQ_ENTRY(sysctl_ctx_entry) link;
+};
+
+TAILQ_HEAD(sysctl_ctx_list, sysctl_ctx_entry);
+
/* This constructs a "raw" MIB oid. */
#define SYSCTL_OID(parent, nbr, name, kind, a1, a2, handler, fmt, descr) \
static struct sysctl_oid sysctl__##parent##_##name = { \
@@ -156,6 +174,9 @@ void sysctl_unregister_oid(struct sysctl_oid *oidp);
nbr, kind, a1, a2, #name, handler, fmt }; \
DATA_SET(sysctl_set, sysctl__##parent##_##name);
+#define SYSCTL_ADD_OID(ctx, parent, nbr, name, kind, a1, a2, handler, fmt, descr) \
+ sysctl_add_oid(ctx, parent, nbr, #name, kind, a1, a2, handler, fmt, descr);
+
/* This constructs a node from which other oids can hang. */
#define SYSCTL_NODE(parent, nbr, name, access, handler, descr) \
struct sysctl_oid_list sysctl_##parent##_##name##_children; \
@@ -163,46 +184,83 @@ void sysctl_unregister_oid(struct sysctl_oid *oidp);
(void*)&sysctl_##parent##_##name##_children, 0, handler, \
"N", descr);
+#define SYSCTL_ADD_NODE(ctx, parent, nbr, name, access, handler, descr) \
+ sysctl_add_oid(ctx, parent, nbr, #name, CTLTYPE_NODE|access, \
+ 0, 0, handler, "N", descr);
+
/* Oid for a string. len can be 0 to indicate '\0' termination. */
#define SYSCTL_STRING(parent, nbr, name, access, arg, len, descr) \
SYSCTL_OID(parent, nbr, name, CTLTYPE_STRING|access, \
arg, len, sysctl_handle_string, "A", descr)
+#define SYSCTL_ADD_STRING(ctx, parent, nbr, name, access, arg, len, descr) \
+ sysctl_add_oid(ctx, parent, nbr, #name, CTLTYPE_STRING|access, \
+ arg, len, sysctl_handle_string, "A", descr);
+
/* Oid for an int. If ptr is NULL, val is returned. */
#define SYSCTL_INT(parent, nbr, name, access, ptr, val, descr) \
SYSCTL_OID(parent, nbr, name, CTLTYPE_INT|access, \
ptr, val, sysctl_handle_int, "I", descr)
+#define SYSCTL_ADD_INT(ctx, parent, nbr, name, access, ptr, val, descr) \
+ sysctl_add_oid(ctx, parent, nbr, #name, CTLTYPE_INT|access, \
+ ptr, val, sysctl_handle_int, "I", descr);
+
/* Oid for an unsigned int. If ptr is NULL, val is returned. */
#define SYSCTL_UINT(parent, nbr, name, access, ptr, val, descr) \
SYSCTL_OID(parent, nbr, name, CTLTYPE_INT|access, \
ptr, val, sysctl_handle_int, "IU", descr)
+#define SYSCTL_ADD_UINT(ctx, parent, nbr, name, access, ptr, val, descr) \
+ sysctl_add_oid(ctx, parent, nbr, #name, CTLTYPE_INT|access, \
+ ptr, val, sysctl_handle_int, "IU", descr);
+
/* Oid for a long. The pointer must be non NULL. */
#define SYSCTL_LONG(parent, nbr, name, access, ptr, val, descr) \
SYSCTL_OID(parent, nbr, name, CTLTYPE_INT|access, \
ptr, val, sysctl_handle_long, "L", descr)
+#define SYSCTL_ADD_LONG(ctx, parent, nbr, name, access, ptr, descr) \
+ sysctl_add_oid(ctx, parent, nbr, #name, CTLTYPE_INT|access, \
+ ptr, 0, sysctl_handle_long, "L", descr);
+
/* Oid for a long. The pointer must be non NULL. */
#define SYSCTL_ULONG(parent, nbr, name, access, ptr, val, descr) \
SYSCTL_OID(parent, nbr, name, CTLTYPE_INT|access, \
ptr, val, sysctl_handle_long, "LU", descr)
+#define SYSCTL_ADD_ULONG(ctx, parent, nbr, name, access, ptr, descr) \
+ sysctl_add_oid(ctx, parent, nbr, #name, CTLTYPE_INT|access, \
+ ptr, 0, sysctl_handle_long, "LU", descr);
+
/* Oid for an opaque object. Specified by a pointer and a length. */
#define SYSCTL_OPAQUE(parent, nbr, name, access, ptr, len, fmt, descr) \
SYSCTL_OID(parent, nbr, name, CTLTYPE_OPAQUE|access, \
ptr, len, sysctl_handle_opaque, fmt, descr)
+#define SYSCTL_ADD_OPAQUE(ctx, parent, nbr, name, access, ptr, len, fmt, descr)\
+ sysctl_add_oid(ctx, parent, nbr, #name, CTLTYPE_OPAQUE|access, \
+ ptr, len, sysctl_handle_opaque, fmt, descr);
+
/* Oid for a struct. Specified by a pointer and a type. */
#define SYSCTL_STRUCT(parent, nbr, name, access, ptr, type, descr) \
SYSCTL_OID(parent, nbr, name, CTLTYPE_OPAQUE|access, \
ptr, sizeof(struct type), sysctl_handle_opaque, \
"S," #type, descr)
+#define SYSCTL_ADD_STRUCT(ctx, parent, nbr, name, access, ptr, type, descr) \
+ sysctl_add_oid(ctx, parent, nbr, #name, CTLTYPE_OPAQUE|access, \
+ ptr, sizeof(struct type), sysctl_handle_opaque, "S," #type, descr);
+
/* Oid for a procedure. Specified by a pointer and an arg. */
#define SYSCTL_PROC(parent, nbr, name, access, ptr, arg, handler, fmt, descr) \
SYSCTL_OID(parent, nbr, name, access, \
ptr, arg, handler, fmt, descr)
+
+#define SYSCTL_ADD_PROC(ctx, parent, nbr, name, access, ptr, arg, handler, fmt, descr) \
+ sysctl_add_oid(ctx, parent, nbr, #name, access, \
+ ptr, arg, handler, fmt, descr);
+
#endif /* _KERNEL */
/*
@@ -501,8 +559,26 @@ extern char ostype[];
struct linker_set;
+/* Dynamic oid handling */
+struct sysctl_oid *sysctl_add_oid(struct sysctl_ctx_list *clist,
+ struct sysctl_oid_list *parent, int nbr, char *name,
+ int kind, void *arg1, int arg2,
+ int (*handler) (SYSCTL_HANDLER_ARGS),
+ char *fmt, char *descr);
+int sysctl_remove_oid(struct sysctl_oid *oidp, int del, int recurse);
+int sysctl_ctx_init(struct sysctl_ctx_list *clist);
+int sysctl_ctx_free(struct sysctl_ctx_list *clist);
+struct sysctl_ctx_entry *sysctl_ctx_entry_add(struct sysctl_ctx_list *clist,
+ struct sysctl_oid *oidp);
+struct sysctl_ctx_entry *sysctl_ctx_entry_find(struct sysctl_ctx_list *clist,
+ struct sysctl_oid *oidp);
+int sysctl_ctx_entry_del(struct sysctl_ctx_list *clist,
+ struct sysctl_oid *oidp);
+
+/* Linker set based oid handling */
void sysctl_register_set(struct linker_set *lsp);
void sysctl_unregister_set(struct linker_set *lsp);
+
int kernel_sysctl(struct proc *p, int *name, u_int namelen, void *old,
size_t *oldlenp, void *new, size_t newlen,
size_t *retval);
OpenPOWER on IntegriCloud