Commit f7e6ced4 authored by Al Viro's avatar Al Viro

[PATCH] allow delayed freeing of ctl_table_header

Refcount the sucker; instead of freeing it by the end of unregistration
just drop the refcount and free only when it hits zero.  Make sure that
we _always_ make ->unregistering non-NULL in start_unregistering().

That allows anybody to get a reference to such puppy, preventing its
freeing and reuse.  It does *not* block unregistration.  Anybody who
holds such a reference can
	* try to grab a "use" reference (ctl_head_grab()); that will
succeeds if and only if it hadn't entered unregistration yet.  If it
succeeds, we can use it in all normal ways until we release the "use"
reference (with ctl_head_finish()).  Note that this relies on having
->unregistering become non-NULL in all cases when one starts to unregister
the sucker.
	* keep pointers to ctl_table entries; they *can* be freed if
the entire thing is unregistered.  However, if ctl_head_grab() succeeds,
we know that unregistration had not happened (and will not happen until
ctl_head_finish()) and such pointers can be used safely.

IOW, now we can have inodes under /proc/sys keep references to ctl_table
entries, protecting them with references to ctl_table_header and
grabbing the latter for the duration of operations that require access
to ctl_table.  That won't cause deadlocks, since unregistration will not
be stopped by mere keeping a reference to ctl_table_header.
Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent 73455092
...@@ -957,6 +957,11 @@ extern void setup_sysctl_set(struct ctl_table_set *p, ...@@ -957,6 +957,11 @@ extern void setup_sysctl_set(struct ctl_table_set *p,
struct ctl_table_set *parent, struct ctl_table_set *parent,
int (*is_seen)(struct ctl_table_set *)); int (*is_seen)(struct ctl_table_set *));
struct ctl_table_header;
extern void sysctl_head_get(struct ctl_table_header *);
extern void sysctl_head_put(struct ctl_table_header *);
extern struct ctl_table_header *sysctl_head_grab(struct ctl_table_header *);
extern struct ctl_table_header *sysctl_head_next(struct ctl_table_header *prev); extern struct ctl_table_header *sysctl_head_next(struct ctl_table_header *prev);
extern struct ctl_table_header *__sysctl_head_next(struct nsproxy *namespaces, extern struct ctl_table_header *__sysctl_head_next(struct nsproxy *namespaces,
struct ctl_table_header *prev); struct ctl_table_header *prev);
...@@ -1073,6 +1078,7 @@ struct ctl_table_header ...@@ -1073,6 +1078,7 @@ struct ctl_table_header
struct ctl_table *ctl_table; struct ctl_table *ctl_table;
struct list_head ctl_entry; struct list_head ctl_entry;
int used; int used;
int count;
struct completion *unregistering; struct completion *unregistering;
struct ctl_table *ctl_table_arg; struct ctl_table *ctl_table_arg;
struct ctl_table_root *root; struct ctl_table_root *root;
......
...@@ -1387,6 +1387,9 @@ static void start_unregistering(struct ctl_table_header *p) ...@@ -1387,6 +1387,9 @@ static void start_unregistering(struct ctl_table_header *p)
spin_unlock(&sysctl_lock); spin_unlock(&sysctl_lock);
wait_for_completion(&wait); wait_for_completion(&wait);
spin_lock(&sysctl_lock); spin_lock(&sysctl_lock);
} else {
/* anything non-NULL; we'll never dereference it */
p->unregistering = ERR_PTR(-EINVAL);
} }
/* /*
* do not remove from the list until nobody holds it; walking the * do not remove from the list until nobody holds it; walking the
...@@ -1395,6 +1398,32 @@ static void start_unregistering(struct ctl_table_header *p) ...@@ -1395,6 +1398,32 @@ static void start_unregistering(struct ctl_table_header *p)
list_del_init(&p->ctl_entry); list_del_init(&p->ctl_entry);
} }
void sysctl_head_get(struct ctl_table_header *head)
{
spin_lock(&sysctl_lock);
head->count++;
spin_unlock(&sysctl_lock);
}
void sysctl_head_put(struct ctl_table_header *head)
{
spin_lock(&sysctl_lock);
if (!--head->count)
kfree(head);
spin_unlock(&sysctl_lock);
}
struct ctl_table_header *sysctl_head_grab(struct ctl_table_header *head)
{
if (!head)
BUG();
spin_lock(&sysctl_lock);
if (!use_table(head))
head = ERR_PTR(-ENOENT);
spin_unlock(&sysctl_lock);
return head;
}
void sysctl_head_finish(struct ctl_table_header *head) void sysctl_head_finish(struct ctl_table_header *head)
{ {
if (!head) if (!head)
...@@ -1771,6 +1800,7 @@ struct ctl_table_header *__register_sysctl_paths( ...@@ -1771,6 +1800,7 @@ struct ctl_table_header *__register_sysctl_paths(
header->unregistering = NULL; header->unregistering = NULL;
header->root = root; header->root = root;
sysctl_set_parent(NULL, header->ctl_table); sysctl_set_parent(NULL, header->ctl_table);
header->count = 1;
#ifdef CONFIG_SYSCTL_SYSCALL_CHECK #ifdef CONFIG_SYSCTL_SYSCALL_CHECK
if (sysctl_check_table(namespaces, header->ctl_table)) { if (sysctl_check_table(namespaces, header->ctl_table)) {
kfree(header); kfree(header);
...@@ -1834,8 +1864,9 @@ void unregister_sysctl_table(struct ctl_table_header * header) ...@@ -1834,8 +1864,9 @@ void unregister_sysctl_table(struct ctl_table_header * header)
spin_lock(&sysctl_lock); spin_lock(&sysctl_lock);
start_unregistering(header); start_unregistering(header);
if (!--header->count)
kfree(header);
spin_unlock(&sysctl_lock); spin_unlock(&sysctl_lock);
kfree(header);
} }
void setup_sysctl_set(struct ctl_table_set *p, void setup_sysctl_set(struct ctl_table_set *p,
...@@ -1869,6 +1900,10 @@ void setup_sysctl_set(struct ctl_table_set *p, ...@@ -1869,6 +1900,10 @@ void setup_sysctl_set(struct ctl_table_set *p,
{ {
} }
void sysctl_head_put(struct ctl_table_header *head)
{
}
#endif /* CONFIG_SYSCTL */ #endif /* CONFIG_SYSCTL */
/* /*
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment