summaryrefslogtreecommitdiff
path: root/sys/kern/kern_sysctl.c
diff options
context:
space:
mode:
authorJohn Baldwin <jhb@FreeBSD.org>2009-03-10 18:57:10 +0000
committerJohn Baldwin <jhb@FreeBSD.org>2009-03-10 18:57:10 +0000
commit772cc2acbf3dda4f332de34d7fd26a14bb2821dc (patch)
treefb39df995a4815414e97633e67475be7aefd41fd /sys/kern/kern_sysctl.c
parentf68d8092dc0eb994f58ea423c3985551786ba3c2 (diff)
Notes
Diffstat (limited to 'sys/kern/kern_sysctl.c')
-rw-r--r--sys/kern/kern_sysctl.c124
1 files changed, 103 insertions, 21 deletions
diff --git a/sys/kern/kern_sysctl.c b/sys/kern/kern_sysctl.c
index c981135c1c58..51b976255423 100644
--- a/sys/kern/kern_sysctl.c
+++ b/sys/kern/kern_sysctl.c
@@ -64,24 +64,41 @@ static MALLOC_DEFINE(M_SYSCTLOID, "sysctloid", "sysctl dynamic oids");
static MALLOC_DEFINE(M_SYSCTLTMP, "sysctltmp", "sysctl temp output buffer");
/*
- * Locking - this locks the sysctl tree in memory.
+ * The sysctllock protects the MIB tree. It also protects sysctl
+ * contexts used with dynamic sysctls. The sysctl_register_oid() and
+ * sysctl_unregister_oid() routines require the sysctllock to already
+ * be held, so the sysctl_lock() and sysctl_unlock() routines are
+ * provided for the few places in the kernel which need to use that
+ * API rather than using the dynamic API. Use of the dynamic API is
+ * strongly encouraged for most code.
+ *
+ * This lock is also used to serialize userland sysctl requests. Some
+ * sysctls wire user memory, and serializing the requests limits the
+ * amount of wired user memory in use.
*/
static struct sx sysctllock;
-#define SYSCTL_LOCK() sx_xlock(&sysctllock)
-#define SYSCTL_UNLOCK() sx_xunlock(&sysctllock)
-#define SYSCTL_LOCK_ASSERT() sx_assert(&sysctllock, SX_XLOCKED)
+#define SYSCTL_SLOCK() sx_slock(&sysctllock)
+#define SYSCTL_SUNLOCK() sx_sunlock(&sysctllock)
+#define SYSCTL_XLOCK() sx_xlock(&sysctllock)
+#define SYSCTL_XUNLOCK() sx_xunlock(&sysctllock)
+#define SYSCTL_ASSERT_XLOCKED() sx_assert(&sysctllock, SA_XLOCKED)
+#define SYSCTL_ASSERT_LOCKED() sx_assert(&sysctllock, SA_LOCKED)
#define SYSCTL_INIT() sx_init(&sysctllock, "sysctl lock")
static int sysctl_root(SYSCTL_HANDLER_ARGS);
struct sysctl_oid_list sysctl__children; /* root list */
+static int sysctl_remove_oid_locked(struct sysctl_oid *oidp, int del,
+ int recurse);
+
static struct sysctl_oid *
sysctl_find_oidname(const char *name, struct sysctl_oid_list *list)
{
struct sysctl_oid *oidp;
+ SYSCTL_ASSERT_LOCKED();
SLIST_FOREACH(oidp, list, oid_link) {
if (strcmp(oidp->oid_name, name) == 0) {
return (oidp);
@@ -95,6 +112,19 @@ sysctl_find_oidname(const char *name, struct sysctl_oid_list *list)
*
* Order by number in each list.
*/
+void
+sysctl_lock(void)
+{
+
+ SYSCTL_XLOCK();
+}
+
+void
+sysctl_unlock(void)
+{
+
+ SYSCTL_XUNLOCK();
+}
void
sysctl_register_oid(struct sysctl_oid *oidp)
@@ -107,6 +137,7 @@ sysctl_register_oid(struct sysctl_oid *oidp)
* First check if another oid with the same name already
* exists in the parent's list.
*/
+ SYSCTL_ASSERT_XLOCKED();
p = sysctl_find_oidname(oidp->oid_name, parent);
if (p != NULL) {
if ((p->oid_kind & CTLTYPE) == CTLTYPE_NODE) {
@@ -159,6 +190,7 @@ sysctl_unregister_oid(struct sysctl_oid *oidp)
struct sysctl_oid *p;
int error;
+ SYSCTL_ASSERT_XLOCKED();
error = ENOENT;
if (oidp->oid_number == OID_AUTO) {
error = EINVAL;
@@ -190,6 +222,12 @@ sysctl_ctx_init(struct sysctl_ctx_list *c)
if (c == NULL) {
return (EINVAL);
}
+
+ /*
+ * No locking here, the caller is responsible for not adding
+ * new nodes to a context until after this function has
+ * returned.
+ */
TAILQ_INIT(c);
return (0);
}
@@ -208,8 +246,9 @@ sysctl_ctx_free(struct sysctl_ctx_list *clist)
* XXX This algorithm is a hack. But I don't know any
* XXX better solution for now...
*/
+ SYSCTL_XLOCK();
TAILQ_FOREACH(e, clist, link) {
- error = sysctl_remove_oid(e->entry, 0, 0);
+ error = sysctl_remove_oid_locked(e->entry, 0, 0);
if (error)
break;
}
@@ -226,19 +265,22 @@ sysctl_ctx_free(struct sysctl_ctx_list *clist)
sysctl_register_oid(e1->entry);
e1 = TAILQ_PREV(e1, sysctl_ctx_list, link);
}
- if (error)
+ if (error) {
+ SYSCTL_XUNLOCK();
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);
+ error = sysctl_remove_oid_locked(e->entry, 1, 0);
if (error)
panic("sysctl_remove_oid: corrupt tree, entry: %s",
e->entry->oid_name);
free(e, M_SYSCTLOID);
e = e1;
}
+ SYSCTL_XUNLOCK();
return (error);
}
@@ -248,6 +290,7 @@ sysctl_ctx_entry_add(struct sysctl_ctx_list *clist, struct sysctl_oid *oidp)
{
struct sysctl_ctx_entry *e;
+ SYSCTL_ASSERT_XLOCKED();
if (clist == NULL || oidp == NULL)
return(NULL);
e = malloc(sizeof(struct sysctl_ctx_entry), M_SYSCTLOID, M_WAITOK);
@@ -262,6 +305,7 @@ sysctl_ctx_entry_find(struct sysctl_ctx_list *clist, struct sysctl_oid *oidp)
{
struct sysctl_ctx_entry *e;
+ SYSCTL_ASSERT_LOCKED();
if (clist == NULL || oidp == NULL)
return(NULL);
TAILQ_FOREACH(e, clist, link) {
@@ -283,13 +327,17 @@ sysctl_ctx_entry_del(struct sysctl_ctx_list *clist, struct sysctl_oid *oidp)
if (clist == NULL || oidp == NULL)
return (EINVAL);
+ SYSCTL_XLOCK();
e = sysctl_ctx_entry_find(clist, oidp);
if (e != NULL) {
TAILQ_REMOVE(clist, e, link);
+ SYSCTL_XUNLOCK();
free(e, M_SYSCTLOID);
return (0);
- } else
+ } else {
+ SYSCTL_XUNLOCK();
return (ENOENT);
+ }
}
/*
@@ -301,9 +349,21 @@ sysctl_ctx_entry_del(struct sysctl_ctx_list *clist, struct sysctl_oid *oidp)
int
sysctl_remove_oid(struct sysctl_oid *oidp, int del, int recurse)
{
+ int error;
+
+ SYSCTL_XLOCK();
+ error = sysctl_remove_oid_locked(oidp, del, recurse);
+ SYSCTL_XUNLOCK();
+ return (error);
+}
+
+static int
+sysctl_remove_oid_locked(struct sysctl_oid *oidp, int del, int recurse)
+{
struct sysctl_oid *p;
int error;
+ SYSCTL_ASSERT_XLOCKED();
if (oidp == NULL)
return(EINVAL);
if ((oidp->oid_kind & CTLFLAG_DYN) == 0) {
@@ -322,7 +382,8 @@ sysctl_remove_oid(struct sysctl_oid *oidp, int del, int recurse)
SLIST_FOREACH(p, SYSCTL_CHILDREN(oidp), oid_link) {
if (!recurse)
return (ENOTEMPTY);
- error = sysctl_remove_oid(p, del, recurse);
+ error = sysctl_remove_oid_locked(p, del,
+ recurse);
if (error)
return (error);
}
@@ -367,6 +428,7 @@ sysctl_add_oid(struct sysctl_ctx_list *clist, struct sysctl_oid_list *parent,
if (parent == NULL)
return(NULL);
/* Check if the node already exists, otherwise create it */
+ SYSCTL_XLOCK();
oidp = sysctl_find_oidname(name, parent);
if (oidp != NULL) {
if ((oidp->oid_kind & CTLTYPE) == CTLTYPE_NODE) {
@@ -374,8 +436,10 @@ sysctl_add_oid(struct sysctl_ctx_list *clist, struct sysctl_oid_list *parent,
/* Update the context */
if (clist != NULL)
sysctl_ctx_entry_add(clist, oidp);
+ SYSCTL_XUNLOCK();
return (oidp);
} else {
+ SYSCTL_XUNLOCK();
printf("can't re-use a leaf (%s)!\n", name);
return (NULL);
}
@@ -413,6 +477,7 @@ sysctl_add_oid(struct sysctl_ctx_list *clist, struct sysctl_oid_list *parent,
sysctl_ctx_entry_add(clist, oidp);
/* Register this oid */
sysctl_register_oid(oidp);
+ SYSCTL_XUNLOCK();
return (oidp);
}
@@ -426,12 +491,14 @@ sysctl_rename_oid(struct sysctl_oid *oidp, const char *name)
char *newname;
void *oldname;
- oldname = (void *)(uintptr_t)(const void *)oidp->oid_name;
len = strlen(name);
newname = malloc(len + 1, M_SYSCTLOID, M_WAITOK);
bcopy(name, newname, len + 1);
newname[len] = '\0';
+ SYSCTL_XLOCK();
+ oldname = (void *)(uintptr_t)(const void *)oidp->oid_name;
oidp->oid_name = newname;
+ SYSCTL_XUNLOCK();
free(oldname, M_SYSCTLOID);
}
@@ -443,15 +510,21 @@ sysctl_move_oid(struct sysctl_oid *oid, struct sysctl_oid_list *parent)
{
struct sysctl_oid *oidp;
- if (oid->oid_parent == parent)
+ SYSCTL_XLOCK();
+ if (oid->oid_parent == parent) {
+ SYSCTL_XUNLOCK();
return (0);
+ }
oidp = sysctl_find_oidname(oid->oid_name, parent);
- if (oidp != NULL)
+ if (oidp != NULL) {
+ SYSCTL_XUNLOCK();
return (EEXIST);
+ }
sysctl_unregister_oid(oid);
oid->oid_parent = parent;
oid->oid_number = OID_AUTO;
sysctl_register_oid(oid);
+ SYSCTL_XUNLOCK();
return (0);
}
@@ -466,8 +539,10 @@ sysctl_register_all(void *arg)
struct sysctl_oid **oidp;
SYSCTL_INIT();
+ SYSCTL_XLOCK();
SET_FOREACH(oidp, sysctl_set)
sysctl_register_oid(*oidp);
+ SYSCTL_XUNLOCK();
}
SYSINIT(sysctl, SI_SUB_KMEM, SI_ORDER_ANY, sysctl_register_all, 0);
@@ -497,6 +572,7 @@ sysctl_sysctl_debug_dump_node(struct sysctl_oid_list *l, int i)
int k;
struct sysctl_oid *oidp;
+ SYSCTL_ASSERT_LOCKED();
SLIST_FOREACH(oidp, l, oid_link) {
for (k=0; k<i; k++)
@@ -555,6 +631,7 @@ sysctl_sysctl_name(SYSCTL_HANDLER_ARGS)
struct sysctl_oid_list *lsp = &sysctl__children, *lsp2;
char buf[10];
+ SYSCTL_ASSERT_LOCKED();
while (namelen) {
if (!lsp) {
snprintf(buf,sizeof(buf),"%d",*name);
@@ -606,6 +683,7 @@ sysctl_sysctl_next_ls(struct sysctl_oid_list *lsp, int *name, u_int namelen,
{
struct sysctl_oid *oidp;
+ SYSCTL_ASSERT_LOCKED();
*len = level;
SLIST_FOREACH(oidp, lsp, oid_link) {
*next = oidp->oid_number;
@@ -686,7 +764,7 @@ name2oid (char *name, int *oid, int *len, struct sysctl_oid **oidpp)
struct sysctl_oid_list *lsp = &sysctl__children;
char *p;
- SYSCTL_LOCK_ASSERT();
+ SYSCTL_ASSERT_LOCKED();
if (!*name)
return (ENOENT);
@@ -744,7 +822,7 @@ sysctl_sysctl_name2oid(SYSCTL_HANDLER_ARGS)
int error, oid[CTL_MAXNAME], len;
struct sysctl_oid *op = 0;
- SYSCTL_LOCK_ASSERT();
+ SYSCTL_ASSERT_LOCKED();
if (!req->newlen)
return (ENOENT);
@@ -1089,9 +1167,9 @@ kernel_sysctl(struct thread *td, int *name, u_int namelen, void *old,
req.newfunc = sysctl_new_kernel;
req.lock = REQ_LOCKED;
- SYSCTL_LOCK();
+ SYSCTL_SLOCK();
error = sysctl_root(0, name, namelen, &req);
- SYSCTL_UNLOCK();
+ SYSCTL_SUNLOCK();
if (req.lock == REQ_WIRED && req.validlen > 0)
vsunlock(req.oldptr, req.validlen);
@@ -1123,6 +1201,9 @@ kernel_sysctlbyname(struct thread *td, char *name, void *old, size_t *oldlenp,
/*
* XXX: Prone to a possible race condition between lookup and
* execution? Maybe put locking around it?
+ *
+ * Userland is just as racy, so I think the current implementation
+ * is fine.
*/
error = kernel_sysctl(td, oid, 2, oid, &oidlen,
@@ -1234,6 +1315,7 @@ sysctl_find_oid(int *name, u_int namelen, struct sysctl_oid **noid,
struct sysctl_oid *oid;
int indx;
+ SYSCTL_ASSERT_LOCKED();
oid = SLIST_FIRST(&sysctl__children);
indx = 0;
while (oid && indx < CTL_MAXNAME) {
@@ -1277,7 +1359,7 @@ sysctl_root(SYSCTL_HANDLER_ARGS)
struct sysctl_oid *oid;
int error, indx, lvl;
- SYSCTL_LOCK_ASSERT();
+ SYSCTL_ASSERT_LOCKED();
error = sysctl_find_oid(arg1, arg2, &oid, &indx, req);
if (error)
@@ -1355,7 +1437,7 @@ struct sysctl_args {
int
__sysctl(struct thread *td, struct sysctl_args *uap)
{
- int error, name[CTL_MAXNAME];
+ int error, i, name[CTL_MAXNAME];
size_t j;
if (uap->namelen > CTL_MAXNAME || uap->namelen < 2)
@@ -1371,7 +1453,7 @@ __sysctl(struct thread *td, struct sysctl_args *uap)
if (error && error != ENOMEM)
return (error);
if (uap->oldlenp) {
- int i = copyout(&j, uap->oldlenp, sizeof(j));
+ i = copyout(&j, uap->oldlenp, sizeof(j));
if (i)
return (i);
}
@@ -1423,7 +1505,7 @@ userland_sysctl(struct thread *td, int *name, u_int namelen, void *old,
req.newfunc = sysctl_new_user;
req.lock = REQ_LOCKED;
- SYSCTL_LOCK();
+ SYSCTL_XLOCK();
for (;;) {
req.oldidx = 0;
@@ -1434,7 +1516,7 @@ userland_sysctl(struct thread *td, int *name, u_int namelen, void *old,
uio_yield();
}
- SYSCTL_UNLOCK();
+ SYSCTL_XUNLOCK();
if (req.lock == REQ_WIRED && req.validlen > 0)
vsunlock(req.oldptr, req.validlen);