aboutsummaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
authorAl Viro <viro@zeniv.linux.org.uk>2008-07-15 01:44:23 -0400
committerAl Viro <viro@zeniv.linux.org.uk>2008-07-26 20:53:09 -0400
commitf7e6ced4061da509f737541ca4dbd44d83a6e82f (patch)
tree065a6907a085854da1eb176da1314c472e69a364 /kernel
parent734550921e9b7ab924a43aa3d0bd4239dac4fbf1 (diff)
[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: Al Viro <viro@zeniv.linux.org.uk>
Diffstat (limited to 'kernel')
-rw-r--r--kernel/sysctl.c37
1 files changed, 36 insertions, 1 deletions
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index 8ee4a0619fbb..60d9357e7172 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -1387,6 +1387,9 @@ static void start_unregistering(struct ctl_table_header *p)
1387 spin_unlock(&sysctl_lock); 1387 spin_unlock(&sysctl_lock);
1388 wait_for_completion(&wait); 1388 wait_for_completion(&wait);
1389 spin_lock(&sysctl_lock); 1389 spin_lock(&sysctl_lock);
1390 } else {
1391 /* anything non-NULL; we'll never dereference it */
1392 p->unregistering = ERR_PTR(-EINVAL);
1390 } 1393 }
1391 /* 1394 /*
1392 * do not remove from the list until nobody holds it; walking the 1395 * 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 list_del_init(&p->ctl_entry); 1398 list_del_init(&p->ctl_entry);
1396} 1399}
1397 1400
1401void sysctl_head_get(struct ctl_table_header *head)
1402{
1403 spin_lock(&sysctl_lock);
1404 head->count++;
1405 spin_unlock(&sysctl_lock);
1406}
1407
1408void sysctl_head_put(struct ctl_table_header *head)
1409{
1410 spin_lock(&sysctl_lock);
1411 if (!--head->count)
1412 kfree(head);
1413 spin_unlock(&sysctl_lock);
1414}
1415
1416struct ctl_table_header *sysctl_head_grab(struct ctl_table_header *head)
1417{
1418 if (!head)
1419 BUG();
1420 spin_lock(&sysctl_lock);
1421 if (!use_table(head))
1422 head = ERR_PTR(-ENOENT);
1423 spin_unlock(&sysctl_lock);
1424 return head;
1425}
1426
1398void sysctl_head_finish(struct ctl_table_header *head) 1427void sysctl_head_finish(struct ctl_table_header *head)
1399{ 1428{
1400 if (!head) 1429 if (!head)
@@ -1771,6 +1800,7 @@ struct ctl_table_header *__register_sysctl_paths(
1771 header->unregistering = NULL; 1800 header->unregistering = NULL;
1772 header->root = root; 1801 header->root = root;
1773 sysctl_set_parent(NULL, header->ctl_table); 1802 sysctl_set_parent(NULL, header->ctl_table);
1803 header->count = 1;
1774#ifdef CONFIG_SYSCTL_SYSCALL_CHECK 1804#ifdef CONFIG_SYSCTL_SYSCALL_CHECK
1775 if (sysctl_check_table(namespaces, header->ctl_table)) { 1805 if (sysctl_check_table(namespaces, header->ctl_table)) {
1776 kfree(header); 1806 kfree(header);
@@ -1834,8 +1864,9 @@ void unregister_sysctl_table(struct ctl_table_header * header)
1834 1864
1835 spin_lock(&sysctl_lock); 1865 spin_lock(&sysctl_lock);
1836 start_unregistering(header); 1866 start_unregistering(header);
1867 if (!--header->count)
1868 kfree(header);
1837 spin_unlock(&sysctl_lock); 1869 spin_unlock(&sysctl_lock);
1838 kfree(header);
1839} 1870}
1840 1871
1841void setup_sysctl_set(struct ctl_table_set *p, 1872void setup_sysctl_set(struct ctl_table_set *p,
@@ -1869,6 +1900,10 @@ void setup_sysctl_set(struct ctl_table_set *p,
1869{ 1900{
1870} 1901}
1871 1902
1903void sysctl_head_put(struct ctl_table_header *head)
1904{
1905}
1906
1872#endif /* CONFIG_SYSCTL */ 1907#endif /* CONFIG_SYSCTL */
1873 1908
1874/* 1909/*