diff options
Diffstat (limited to 'fs/proc')
-rw-r--r-- | fs/proc/proc_sysctl.c | 397 |
1 files changed, 295 insertions, 102 deletions
diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c index a78556514a87..ec54a57c4690 100644 --- a/fs/proc/proc_sysctl.c +++ b/fs/proc/proc_sysctl.c | |||
@@ -32,26 +32,26 @@ static struct ctl_table root_table[] = { | |||
32 | }, | 32 | }, |
33 | { } | 33 | { } |
34 | }; | 34 | }; |
35 | static struct ctl_table_root sysctl_table_root; | 35 | static struct ctl_table_root sysctl_table_root = { |
36 | static struct ctl_dir sysctl_root_dir = { | 36 | .default_set.list = LIST_HEAD_INIT(sysctl_table_root.default_set.dir.header.ctl_entry), |
37 | .header = { | 37 | .default_set.dir.header = { |
38 | {{.count = 1, | 38 | {{.count = 1, |
39 | .nreg = 1, | 39 | .nreg = 1, |
40 | .ctl_table = root_table, | 40 | .ctl_table = root_table, |
41 | .ctl_entry = LIST_HEAD_INIT(sysctl_table_root.default_set.list),}}, | 41 | .ctl_entry = LIST_HEAD_INIT(sysctl_table_root.default_set.list),}}, |
42 | .ctl_table_arg = root_table, | ||
42 | .root = &sysctl_table_root, | 43 | .root = &sysctl_table_root, |
43 | .set = &sysctl_table_root.default_set, | 44 | .set = &sysctl_table_root.default_set, |
44 | }, | 45 | }, |
45 | }; | 46 | }; |
46 | static struct ctl_table_root sysctl_table_root = { | ||
47 | .root_list = LIST_HEAD_INIT(sysctl_table_root.root_list), | ||
48 | .default_set.list = LIST_HEAD_INIT(sysctl_root_dir.header.ctl_entry), | ||
49 | .default_set.root = &sysctl_table_root, | ||
50 | }; | ||
51 | 47 | ||
52 | static DEFINE_SPINLOCK(sysctl_lock); | 48 | static DEFINE_SPINLOCK(sysctl_lock); |
53 | 49 | ||
54 | static void drop_sysctl_table(struct ctl_table_header *header); | 50 | static void drop_sysctl_table(struct ctl_table_header *header); |
51 | static int sysctl_follow_link(struct ctl_table_header **phead, | ||
52 | struct ctl_table **pentry, struct nsproxy *namespaces); | ||
53 | static int insert_links(struct ctl_table_header *head); | ||
54 | static void put_links(struct ctl_table_header *header); | ||
55 | 55 | ||
56 | static void sysctl_print_dir(struct ctl_dir *dir) | 56 | static void sysctl_print_dir(struct ctl_dir *dir) |
57 | { | 57 | { |
@@ -76,9 +76,9 @@ static int namecmp(const char *name1, int len1, const char *name2, int len2) | |||
76 | } | 76 | } |
77 | 77 | ||
78 | static struct ctl_table *find_entry(struct ctl_table_header **phead, | 78 | static struct ctl_table *find_entry(struct ctl_table_header **phead, |
79 | struct ctl_table_set *set, struct ctl_dir *dir, | 79 | struct ctl_dir *dir, const char *name, int namelen) |
80 | const char *name, int namelen) | ||
81 | { | 80 | { |
81 | struct ctl_table_set *set = dir->header.set; | ||
82 | struct ctl_table_header *head; | 82 | struct ctl_table_header *head; |
83 | struct ctl_table *entry; | 83 | struct ctl_table *entry; |
84 | 84 | ||
@@ -119,11 +119,21 @@ static void erase_header(struct ctl_table_header *head) | |||
119 | list_del_init(&head->ctl_entry); | 119 | list_del_init(&head->ctl_entry); |
120 | } | 120 | } |
121 | 121 | ||
122 | static void insert_header(struct ctl_dir *dir, struct ctl_table_header *header) | 122 | static int insert_header(struct ctl_dir *dir, struct ctl_table_header *header) |
123 | { | 123 | { |
124 | int err; | ||
125 | |||
126 | dir->header.nreg++; | ||
124 | header->parent = dir; | 127 | header->parent = dir; |
125 | header->parent->header.nreg++; | 128 | err = insert_links(header); |
129 | if (err) | ||
130 | goto fail_links; | ||
126 | list_add_tail(&header->ctl_entry, &header->set->list); | 131 | list_add_tail(&header->ctl_entry, &header->set->list); |
132 | return 0; | ||
133 | fail_links: | ||
134 | header->parent = NULL; | ||
135 | drop_sysctl_table(&dir->header); | ||
136 | return err; | ||
127 | } | 137 | } |
128 | 138 | ||
129 | /* called under sysctl_lock */ | 139 | /* called under sysctl_lock */ |
@@ -212,72 +222,39 @@ lookup_header_set(struct ctl_table_root *root, struct nsproxy *namespaces) | |||
212 | return set; | 222 | return set; |
213 | } | 223 | } |
214 | 224 | ||
215 | static struct list_head * | ||
216 | lookup_header_list(struct ctl_table_root *root, struct nsproxy *namespaces) | ||
217 | { | ||
218 | struct ctl_table_set *set = lookup_header_set(root, namespaces); | ||
219 | return &set->list; | ||
220 | } | ||
221 | |||
222 | static struct ctl_table *lookup_entry(struct ctl_table_header **phead, | 225 | static struct ctl_table *lookup_entry(struct ctl_table_header **phead, |
223 | struct ctl_dir *dir, | 226 | struct ctl_dir *dir, |
224 | const char *name, int namelen) | 227 | const char *name, int namelen) |
225 | { | 228 | { |
226 | struct ctl_table_header *head; | 229 | struct ctl_table_header *head; |
227 | struct ctl_table *entry; | 230 | struct ctl_table *entry; |
228 | struct ctl_table_root *root; | ||
229 | struct ctl_table_set *set; | ||
230 | 231 | ||
231 | spin_lock(&sysctl_lock); | 232 | spin_lock(&sysctl_lock); |
232 | root = &sysctl_table_root; | 233 | entry = find_entry(&head, dir, name, namelen); |
233 | do { | 234 | if (entry && use_table(head)) |
234 | set = lookup_header_set(root, current->nsproxy); | 235 | *phead = head; |
235 | entry = find_entry(&head, set, dir, name, namelen); | 236 | else |
236 | if (entry && use_table(head)) | 237 | entry = NULL; |
237 | *phead = head; | ||
238 | else | ||
239 | entry = NULL; | ||
240 | root = list_entry(root->root_list.next, | ||
241 | struct ctl_table_root, root_list); | ||
242 | } while (!entry && root != &sysctl_table_root); | ||
243 | spin_unlock(&sysctl_lock); | 238 | spin_unlock(&sysctl_lock); |
244 | return entry; | 239 | return entry; |
245 | } | 240 | } |
246 | 241 | ||
247 | static struct ctl_table_header *next_usable_entry(struct ctl_dir *dir, | 242 | static struct ctl_table_header *next_usable_entry(struct ctl_dir *dir, |
248 | struct ctl_table_root *root, struct list_head *tmp) | 243 | struct list_head *tmp) |
249 | { | 244 | { |
250 | struct nsproxy *namespaces = current->nsproxy; | 245 | struct ctl_table_set *set = dir->header.set; |
251 | struct list_head *header_list; | ||
252 | struct ctl_table_header *head; | 246 | struct ctl_table_header *head; |
253 | 247 | ||
254 | goto next; | 248 | for (tmp = tmp->next; tmp != &set->list; tmp = tmp->next) { |
255 | for (;;) { | ||
256 | head = list_entry(tmp, struct ctl_table_header, ctl_entry); | 249 | head = list_entry(tmp, struct ctl_table_header, ctl_entry); |
257 | root = head->root; | ||
258 | 250 | ||
259 | if (head->parent != dir || | 251 | if (head->parent != dir || |
260 | !head->ctl_table->procname || | 252 | !head->ctl_table->procname || |
261 | !use_table(head)) | 253 | !use_table(head)) |
262 | goto next; | ||
263 | |||
264 | return head; | ||
265 | next: | ||
266 | tmp = tmp->next; | ||
267 | header_list = lookup_header_list(root, namespaces); | ||
268 | if (tmp != header_list) | ||
269 | continue; | 254 | continue; |
270 | 255 | ||
271 | do { | 256 | return head; |
272 | root = list_entry(root->root_list.next, | ||
273 | struct ctl_table_root, root_list); | ||
274 | if (root == &sysctl_table_root) | ||
275 | goto out; | ||
276 | header_list = lookup_header_list(root, namespaces); | ||
277 | } while (list_empty(header_list)); | ||
278 | tmp = header_list->next; | ||
279 | } | 257 | } |
280 | out: | ||
281 | return NULL; | 258 | return NULL; |
282 | } | 259 | } |
283 | 260 | ||
@@ -288,8 +265,7 @@ static void first_entry(struct ctl_dir *dir, | |||
288 | struct ctl_table *entry = NULL; | 265 | struct ctl_table *entry = NULL; |
289 | 266 | ||
290 | spin_lock(&sysctl_lock); | 267 | spin_lock(&sysctl_lock); |
291 | head = next_usable_entry(dir, &sysctl_table_root, | 268 | head = next_usable_entry(dir, &dir->header.set->list); |
292 | &sysctl_table_root.default_set.list); | ||
293 | spin_unlock(&sysctl_lock); | 269 | spin_unlock(&sysctl_lock); |
294 | if (head) | 270 | if (head) |
295 | entry = head->ctl_table; | 271 | entry = head->ctl_table; |
@@ -306,7 +282,7 @@ static void next_entry(struct ctl_table_header **phead, struct ctl_table **pentr | |||
306 | if (!entry->procname) { | 282 | if (!entry->procname) { |
307 | spin_lock(&sysctl_lock); | 283 | spin_lock(&sysctl_lock); |
308 | unuse_table(head); | 284 | unuse_table(head); |
309 | head = next_usable_entry(head->parent, head->root, &head->ctl_entry); | 285 | head = next_usable_entry(head->parent, &head->ctl_entry); |
310 | spin_unlock(&sysctl_lock); | 286 | spin_unlock(&sysctl_lock); |
311 | if (head) | 287 | if (head) |
312 | entry = head->ctl_table; | 288 | entry = head->ctl_table; |
@@ -317,9 +293,6 @@ static void next_entry(struct ctl_table_header **phead, struct ctl_table **pentr | |||
317 | 293 | ||
318 | void register_sysctl_root(struct ctl_table_root *root) | 294 | void register_sysctl_root(struct ctl_table_root *root) |
319 | { | 295 | { |
320 | spin_lock(&sysctl_lock); | ||
321 | list_add_tail(&root->root_list, &sysctl_table_root.root_list); | ||
322 | spin_unlock(&sysctl_lock); | ||
323 | } | 296 | } |
324 | 297 | ||
325 | /* | 298 | /* |
@@ -386,7 +359,7 @@ static struct ctl_table_header *grab_header(struct inode *inode) | |||
386 | { | 359 | { |
387 | struct ctl_table_header *head = PROC_I(inode)->sysctl; | 360 | struct ctl_table_header *head = PROC_I(inode)->sysctl; |
388 | if (!head) | 361 | if (!head) |
389 | head = &sysctl_root_dir.header; | 362 | head = &sysctl_table_root.default_set.dir.header; |
390 | return sysctl_head_grab(head); | 363 | return sysctl_head_grab(head); |
391 | } | 364 | } |
392 | 365 | ||
@@ -400,6 +373,7 @@ static struct dentry *proc_sys_lookup(struct inode *dir, struct dentry *dentry, | |||
400 | struct inode *inode; | 373 | struct inode *inode; |
401 | struct dentry *err = ERR_PTR(-ENOENT); | 374 | struct dentry *err = ERR_PTR(-ENOENT); |
402 | struct ctl_dir *ctl_dir; | 375 | struct ctl_dir *ctl_dir; |
376 | int ret; | ||
403 | 377 | ||
404 | if (IS_ERR(head)) | 378 | if (IS_ERR(head)) |
405 | return ERR_CAST(head); | 379 | return ERR_CAST(head); |
@@ -410,6 +384,11 @@ static struct dentry *proc_sys_lookup(struct inode *dir, struct dentry *dentry, | |||
410 | if (!p) | 384 | if (!p) |
411 | goto out; | 385 | goto out; |
412 | 386 | ||
387 | ret = sysctl_follow_link(&h, &p, current->nsproxy); | ||
388 | err = ERR_PTR(ret); | ||
389 | if (ret) | ||
390 | goto out; | ||
391 | |||
413 | err = ERR_PTR(-ENOMEM); | 392 | err = ERR_PTR(-ENOMEM); |
414 | inode = proc_sys_make_inode(dir->i_sb, h ? h : head, p); | 393 | inode = proc_sys_make_inode(dir->i_sb, h ? h : head, p); |
415 | if (h) | 394 | if (h) |
@@ -547,6 +526,25 @@ static int proc_sys_fill_cache(struct file *filp, void *dirent, | |||
547 | return !!filldir(dirent, qname.name, qname.len, filp->f_pos, ino, type); | 526 | return !!filldir(dirent, qname.name, qname.len, filp->f_pos, ino, type); |
548 | } | 527 | } |
549 | 528 | ||
529 | static int proc_sys_link_fill_cache(struct file *filp, void *dirent, | ||
530 | filldir_t filldir, | ||
531 | struct ctl_table_header *head, | ||
532 | struct ctl_table *table) | ||
533 | { | ||
534 | int err, ret = 0; | ||
535 | head = sysctl_head_grab(head); | ||
536 | |||
537 | /* It is not an error if we can not follow the link ignore it */ | ||
538 | err = sysctl_follow_link(&head, &table, current->nsproxy); | ||
539 | if (err) | ||
540 | goto out; | ||
541 | |||
542 | ret = proc_sys_fill_cache(filp, dirent, filldir, head, table); | ||
543 | out: | ||
544 | sysctl_head_finish(head); | ||
545 | return ret; | ||
546 | } | ||
547 | |||
550 | static int scan(struct ctl_table_header *head, ctl_table *table, | 548 | static int scan(struct ctl_table_header *head, ctl_table *table, |
551 | unsigned long *pos, struct file *file, | 549 | unsigned long *pos, struct file *file, |
552 | void *dirent, filldir_t filldir) | 550 | void *dirent, filldir_t filldir) |
@@ -556,7 +554,10 @@ static int scan(struct ctl_table_header *head, ctl_table *table, | |||
556 | if ((*pos)++ < file->f_pos) | 554 | if ((*pos)++ < file->f_pos) |
557 | return 0; | 555 | return 0; |
558 | 556 | ||
559 | res = proc_sys_fill_cache(file, dirent, filldir, head, table); | 557 | if (unlikely(S_ISLNK(table->mode))) |
558 | res = proc_sys_link_fill_cache(file, dirent, filldir, head, table); | ||
559 | else | ||
560 | res = proc_sys_fill_cache(file, dirent, filldir, head, table); | ||
560 | 561 | ||
561 | if (res == 0) | 562 | if (res == 0) |
562 | file->f_pos = *pos; | 563 | file->f_pos = *pos; |
@@ -757,13 +758,13 @@ static const struct dentry_operations proc_sys_dentry_operations = { | |||
757 | .d_compare = proc_sys_compare, | 758 | .d_compare = proc_sys_compare, |
758 | }; | 759 | }; |
759 | 760 | ||
760 | static struct ctl_dir *find_subdir(struct ctl_table_set *set, struct ctl_dir *dir, | 761 | static struct ctl_dir *find_subdir(struct ctl_dir *dir, |
761 | const char *name, int namelen) | 762 | const char *name, int namelen) |
762 | { | 763 | { |
763 | struct ctl_table_header *head; | 764 | struct ctl_table_header *head; |
764 | struct ctl_table *entry; | 765 | struct ctl_table *entry; |
765 | 766 | ||
766 | entry = find_entry(&head, set, dir, name, namelen); | 767 | entry = find_entry(&head, dir, name, namelen); |
767 | if (!entry) | 768 | if (!entry) |
768 | return ERR_PTR(-ENOENT); | 769 | return ERR_PTR(-ENOENT); |
769 | if (S_ISDIR(entry->mode)) | 770 | if (S_ISDIR(entry->mode)) |
@@ -772,7 +773,7 @@ static struct ctl_dir *find_subdir(struct ctl_table_set *set, struct ctl_dir *di | |||
772 | } | 773 | } |
773 | 774 | ||
774 | static struct ctl_dir *new_dir(struct ctl_table_set *set, | 775 | static struct ctl_dir *new_dir(struct ctl_table_set *set, |
775 | const char *name, int namelen) | 776 | const char *name, int namelen) |
776 | { | 777 | { |
777 | struct ctl_table *table; | 778 | struct ctl_table *table; |
778 | struct ctl_dir *new; | 779 | struct ctl_dir *new; |
@@ -789,22 +790,19 @@ static struct ctl_dir *new_dir(struct ctl_table_set *set, | |||
789 | new_name[namelen] = '\0'; | 790 | new_name[namelen] = '\0'; |
790 | table[0].procname = new_name; | 791 | table[0].procname = new_name; |
791 | table[0].mode = S_IFDIR|S_IRUGO|S_IXUGO; | 792 | table[0].mode = S_IFDIR|S_IRUGO|S_IXUGO; |
792 | init_header(&new->header, set->root, set, table); | 793 | init_header(&new->header, set->dir.header.root, set, table); |
793 | 794 | ||
794 | return new; | 795 | return new; |
795 | } | 796 | } |
796 | 797 | ||
797 | static struct ctl_dir *get_subdir(struct ctl_table_set *set, | 798 | static struct ctl_dir *get_subdir(struct ctl_dir *dir, |
798 | struct ctl_dir *dir, const char *name, int namelen) | 799 | const char *name, int namelen) |
799 | { | 800 | { |
801 | struct ctl_table_set *set = dir->header.set; | ||
800 | struct ctl_dir *subdir, *new = NULL; | 802 | struct ctl_dir *subdir, *new = NULL; |
801 | 803 | ||
802 | spin_lock(&sysctl_lock); | 804 | spin_lock(&sysctl_lock); |
803 | subdir = find_subdir(dir->header.set, dir, name, namelen); | 805 | subdir = find_subdir(dir, name, namelen); |
804 | if (!IS_ERR(subdir)) | ||
805 | goto found; | ||
806 | if ((PTR_ERR(subdir) == -ENOENT) && set != dir->header.set) | ||
807 | subdir = find_subdir(set, dir, name, namelen); | ||
808 | if (!IS_ERR(subdir)) | 806 | if (!IS_ERR(subdir)) |
809 | goto found; | 807 | goto found; |
810 | if (PTR_ERR(subdir) != -ENOENT) | 808 | if (PTR_ERR(subdir) != -ENOENT) |
@@ -817,13 +815,14 @@ static struct ctl_dir *get_subdir(struct ctl_table_set *set, | |||
817 | if (!new) | 815 | if (!new) |
818 | goto failed; | 816 | goto failed; |
819 | 817 | ||
820 | subdir = find_subdir(set, dir, name, namelen); | 818 | subdir = find_subdir(dir, name, namelen); |
821 | if (!IS_ERR(subdir)) | 819 | if (!IS_ERR(subdir)) |
822 | goto found; | 820 | goto found; |
823 | if (PTR_ERR(subdir) != -ENOENT) | 821 | if (PTR_ERR(subdir) != -ENOENT) |
824 | goto failed; | 822 | goto failed; |
825 | 823 | ||
826 | insert_header(dir, &new->header); | 824 | if (insert_header(dir, &new->header)) |
825 | goto failed; | ||
827 | subdir = new; | 826 | subdir = new; |
828 | found: | 827 | found: |
829 | subdir->header.nreg++; | 828 | subdir->header.nreg++; |
@@ -841,6 +840,57 @@ failed: | |||
841 | return subdir; | 840 | return subdir; |
842 | } | 841 | } |
843 | 842 | ||
843 | static struct ctl_dir *xlate_dir(struct ctl_table_set *set, struct ctl_dir *dir) | ||
844 | { | ||
845 | struct ctl_dir *parent; | ||
846 | const char *procname; | ||
847 | if (!dir->header.parent) | ||
848 | return &set->dir; | ||
849 | parent = xlate_dir(set, dir->header.parent); | ||
850 | if (IS_ERR(parent)) | ||
851 | return parent; | ||
852 | procname = dir->header.ctl_table[0].procname; | ||
853 | return find_subdir(parent, procname, strlen(procname)); | ||
854 | } | ||
855 | |||
856 | static int sysctl_follow_link(struct ctl_table_header **phead, | ||
857 | struct ctl_table **pentry, struct nsproxy *namespaces) | ||
858 | { | ||
859 | struct ctl_table_header *head; | ||
860 | struct ctl_table_root *root; | ||
861 | struct ctl_table_set *set; | ||
862 | struct ctl_table *entry; | ||
863 | struct ctl_dir *dir; | ||
864 | int ret; | ||
865 | |||
866 | /* Get out quickly if not a link */ | ||
867 | if (!S_ISLNK((*pentry)->mode)) | ||
868 | return 0; | ||
869 | |||
870 | ret = 0; | ||
871 | spin_lock(&sysctl_lock); | ||
872 | root = (*pentry)->data; | ||
873 | set = lookup_header_set(root, namespaces); | ||
874 | dir = xlate_dir(set, (*phead)->parent); | ||
875 | if (IS_ERR(dir)) | ||
876 | ret = PTR_ERR(dir); | ||
877 | else { | ||
878 | const char *procname = (*pentry)->procname; | ||
879 | head = NULL; | ||
880 | entry = find_entry(&head, dir, procname, strlen(procname)); | ||
881 | ret = -ENOENT; | ||
882 | if (entry && use_table(head)) { | ||
883 | unuse_table(*phead); | ||
884 | *phead = head; | ||
885 | *pentry = entry; | ||
886 | ret = 0; | ||
887 | } | ||
888 | } | ||
889 | |||
890 | spin_unlock(&sysctl_lock); | ||
891 | return ret; | ||
892 | } | ||
893 | |||
844 | static int sysctl_check_table_dups(const char *path, struct ctl_table *old, | 894 | static int sysctl_check_table_dups(const char *path, struct ctl_table *old, |
845 | struct ctl_table *table) | 895 | struct ctl_table *table) |
846 | { | 896 | { |
@@ -859,30 +909,21 @@ static int sysctl_check_table_dups(const char *path, struct ctl_table *old, | |||
859 | return error; | 909 | return error; |
860 | } | 910 | } |
861 | 911 | ||
862 | static int sysctl_check_dups(struct nsproxy *namespaces, | 912 | static int sysctl_check_dups(struct ctl_dir *dir, |
863 | struct ctl_dir *dir, | ||
864 | const char *path, struct ctl_table *table) | 913 | const char *path, struct ctl_table *table) |
865 | { | 914 | { |
866 | struct ctl_table_root *root; | ||
867 | struct ctl_table_set *set; | 915 | struct ctl_table_set *set; |
868 | struct ctl_table_header *head; | 916 | struct ctl_table_header *head; |
869 | int error = 0; | 917 | int error = 0; |
870 | 918 | ||
871 | root = &sysctl_table_root; | 919 | set = dir->header.set; |
872 | do { | 920 | list_for_each_entry(head, &set->list, ctl_entry) { |
873 | set = lookup_header_set(root, namespaces); | 921 | if (head->unregistering) |
874 | 922 | continue; | |
875 | list_for_each_entry(head, &set->list, ctl_entry) { | 923 | if (head->parent != dir) |
876 | if (head->unregistering) | 924 | continue; |
877 | continue; | 925 | error = sysctl_check_table_dups(path, head->ctl_table, table); |
878 | if (head->parent != dir) | 926 | } |
879 | continue; | ||
880 | error = sysctl_check_table_dups(path, head->ctl_table, | ||
881 | table); | ||
882 | } | ||
883 | root = list_entry(root->root_list.next, | ||
884 | struct ctl_table_root, root_list); | ||
885 | } while (root != &sysctl_table_root); | ||
886 | return error; | 927 | return error; |
887 | } | 928 | } |
888 | 929 | ||
@@ -932,6 +973,115 @@ static int sysctl_check_table(const char *path, struct ctl_table *table) | |||
932 | return err; | 973 | return err; |
933 | } | 974 | } |
934 | 975 | ||
976 | static struct ctl_table_header *new_links(struct ctl_dir *dir, struct ctl_table *table, | ||
977 | struct ctl_table_root *link_root) | ||
978 | { | ||
979 | struct ctl_table *link_table, *entry, *link; | ||
980 | struct ctl_table_header *links; | ||
981 | char *link_name; | ||
982 | int nr_entries, name_bytes; | ||
983 | |||
984 | name_bytes = 0; | ||
985 | nr_entries = 0; | ||
986 | for (entry = table; entry->procname; entry++) { | ||
987 | nr_entries++; | ||
988 | name_bytes += strlen(entry->procname) + 1; | ||
989 | } | ||
990 | |||
991 | links = kzalloc(sizeof(struct ctl_table_header) + | ||
992 | sizeof(struct ctl_table)*(nr_entries + 1) + | ||
993 | name_bytes, | ||
994 | GFP_KERNEL); | ||
995 | |||
996 | if (!links) | ||
997 | return NULL; | ||
998 | |||
999 | link_table = (struct ctl_table *)(links + 1); | ||
1000 | link_name = (char *)&link_table[nr_entries + 1]; | ||
1001 | |||
1002 | for (link = link_table, entry = table; entry->procname; link++, entry++) { | ||
1003 | int len = strlen(entry->procname) + 1; | ||
1004 | memcpy(link_name, entry->procname, len); | ||
1005 | link->procname = link_name; | ||
1006 | link->mode = S_IFLNK|S_IRWXUGO; | ||
1007 | link->data = link_root; | ||
1008 | link_name += len; | ||
1009 | } | ||
1010 | init_header(links, dir->header.root, dir->header.set, link_table); | ||
1011 | links->nreg = nr_entries; | ||
1012 | |||
1013 | return links; | ||
1014 | } | ||
1015 | |||
1016 | static bool get_links(struct ctl_dir *dir, | ||
1017 | struct ctl_table *table, struct ctl_table_root *link_root) | ||
1018 | { | ||
1019 | struct ctl_table_header *head; | ||
1020 | struct ctl_table *entry, *link; | ||
1021 | |||
1022 | /* Are there links available for every entry in table? */ | ||
1023 | for (entry = table; entry->procname; entry++) { | ||
1024 | const char *procname = entry->procname; | ||
1025 | link = find_entry(&head, dir, procname, strlen(procname)); | ||
1026 | if (!link) | ||
1027 | return false; | ||
1028 | if (S_ISDIR(link->mode) && S_ISDIR(entry->mode)) | ||
1029 | continue; | ||
1030 | if (S_ISLNK(link->mode) && (link->data == link_root)) | ||
1031 | continue; | ||
1032 | return false; | ||
1033 | } | ||
1034 | |||
1035 | /* The checks passed. Increase the registration count on the links */ | ||
1036 | for (entry = table; entry->procname; entry++) { | ||
1037 | const char *procname = entry->procname; | ||
1038 | link = find_entry(&head, dir, procname, strlen(procname)); | ||
1039 | head->nreg++; | ||
1040 | } | ||
1041 | return true; | ||
1042 | } | ||
1043 | |||
1044 | static int insert_links(struct ctl_table_header *head) | ||
1045 | { | ||
1046 | struct ctl_table_set *root_set = &sysctl_table_root.default_set; | ||
1047 | struct ctl_dir *core_parent = NULL; | ||
1048 | struct ctl_table_header *links; | ||
1049 | int err; | ||
1050 | |||
1051 | if (head->set == root_set) | ||
1052 | return 0; | ||
1053 | |||
1054 | core_parent = xlate_dir(root_set, head->parent); | ||
1055 | if (IS_ERR(core_parent)) | ||
1056 | return 0; | ||
1057 | |||
1058 | if (get_links(core_parent, head->ctl_table, head->root)) | ||
1059 | return 0; | ||
1060 | |||
1061 | core_parent->header.nreg++; | ||
1062 | spin_unlock(&sysctl_lock); | ||
1063 | |||
1064 | links = new_links(core_parent, head->ctl_table, head->root); | ||
1065 | |||
1066 | spin_lock(&sysctl_lock); | ||
1067 | err = -ENOMEM; | ||
1068 | if (!links) | ||
1069 | goto out; | ||
1070 | |||
1071 | err = 0; | ||
1072 | if (get_links(core_parent, head->ctl_table, head->root)) { | ||
1073 | kfree(links); | ||
1074 | goto out; | ||
1075 | } | ||
1076 | |||
1077 | err = insert_header(core_parent, links); | ||
1078 | if (err) | ||
1079 | kfree(links); | ||
1080 | out: | ||
1081 | drop_sysctl_table(&core_parent->header); | ||
1082 | return err; | ||
1083 | } | ||
1084 | |||
935 | /** | 1085 | /** |
936 | * __register_sysctl_table - register a leaf sysctl table | 1086 | * __register_sysctl_table - register a leaf sysctl table |
937 | * @root: List of sysctl headers to register on | 1087 | * @root: List of sysctl headers to register on |
@@ -980,6 +1130,7 @@ struct ctl_table_header *__register_sysctl_table( | |||
980 | struct nsproxy *namespaces, | 1130 | struct nsproxy *namespaces, |
981 | const char *path, struct ctl_table *table) | 1131 | const char *path, struct ctl_table *table) |
982 | { | 1132 | { |
1133 | struct ctl_table_header *links = NULL; | ||
983 | struct ctl_table_header *header; | 1134 | struct ctl_table_header *header; |
984 | const char *name, *nextname; | 1135 | const char *name, *nextname; |
985 | struct ctl_table_set *set; | 1136 | struct ctl_table_set *set; |
@@ -995,7 +1146,7 @@ struct ctl_table_header *__register_sysctl_table( | |||
995 | 1146 | ||
996 | spin_lock(&sysctl_lock); | 1147 | spin_lock(&sysctl_lock); |
997 | header->set = set = lookup_header_set(root, namespaces); | 1148 | header->set = set = lookup_header_set(root, namespaces); |
998 | dir = &sysctl_root_dir; | 1149 | dir = &set->dir; |
999 | dir->header.nreg++; | 1150 | dir->header.nreg++; |
1000 | spin_unlock(&sysctl_lock); | 1151 | spin_unlock(&sysctl_lock); |
1001 | 1152 | ||
@@ -1012,22 +1163,28 @@ struct ctl_table_header *__register_sysctl_table( | |||
1012 | if (namelen == 0) | 1163 | if (namelen == 0) |
1013 | continue; | 1164 | continue; |
1014 | 1165 | ||
1015 | dir = get_subdir(set, dir, name, namelen); | 1166 | dir = get_subdir(dir, name, namelen); |
1016 | if (IS_ERR(dir)) | 1167 | if (IS_ERR(dir)) |
1017 | goto fail; | 1168 | goto fail; |
1018 | } | 1169 | } |
1170 | |||
1019 | spin_lock(&sysctl_lock); | 1171 | spin_lock(&sysctl_lock); |
1020 | if (sysctl_check_dups(namespaces, dir, path, table)) | 1172 | if (sysctl_check_dups(dir, path, table)) |
1173 | goto fail_put_dir_locked; | ||
1174 | |||
1175 | if (insert_header(dir, header)) | ||
1021 | goto fail_put_dir_locked; | 1176 | goto fail_put_dir_locked; |
1022 | insert_header(dir, header); | 1177 | |
1023 | drop_sysctl_table(&dir->header); | 1178 | drop_sysctl_table(&dir->header); |
1024 | spin_unlock(&sysctl_lock); | 1179 | spin_unlock(&sysctl_lock); |
1025 | 1180 | ||
1026 | return header; | 1181 | return header; |
1182 | |||
1027 | fail_put_dir_locked: | 1183 | fail_put_dir_locked: |
1028 | drop_sysctl_table(&dir->header); | 1184 | drop_sysctl_table(&dir->header); |
1029 | spin_unlock(&sysctl_lock); | 1185 | spin_unlock(&sysctl_lock); |
1030 | fail: | 1186 | fail: |
1187 | kfree(links); | ||
1031 | kfree(header); | 1188 | kfree(header); |
1032 | dump_stack(); | 1189 | dump_stack(); |
1033 | return NULL; | 1190 | return NULL; |
@@ -1249,6 +1406,40 @@ struct ctl_table_header *register_sysctl_table(struct ctl_table *table) | |||
1249 | } | 1406 | } |
1250 | EXPORT_SYMBOL(register_sysctl_table); | 1407 | EXPORT_SYMBOL(register_sysctl_table); |
1251 | 1408 | ||
1409 | static void put_links(struct ctl_table_header *header) | ||
1410 | { | ||
1411 | struct ctl_table_set *root_set = &sysctl_table_root.default_set; | ||
1412 | struct ctl_table_root *root = header->root; | ||
1413 | struct ctl_dir *parent = header->parent; | ||
1414 | struct ctl_dir *core_parent; | ||
1415 | struct ctl_table *entry; | ||
1416 | |||
1417 | if (header->set == root_set) | ||
1418 | return; | ||
1419 | |||
1420 | core_parent = xlate_dir(root_set, parent); | ||
1421 | if (IS_ERR(core_parent)) | ||
1422 | return; | ||
1423 | |||
1424 | for (entry = header->ctl_table; entry->procname; entry++) { | ||
1425 | struct ctl_table_header *link_head; | ||
1426 | struct ctl_table *link; | ||
1427 | const char *name = entry->procname; | ||
1428 | |||
1429 | link = find_entry(&link_head, core_parent, name, strlen(name)); | ||
1430 | if (link && | ||
1431 | ((S_ISDIR(link->mode) && S_ISDIR(entry->mode)) || | ||
1432 | (S_ISLNK(link->mode) && (link->data == root)))) { | ||
1433 | drop_sysctl_table(link_head); | ||
1434 | } | ||
1435 | else { | ||
1436 | printk(KERN_ERR "sysctl link missing during unregister: "); | ||
1437 | sysctl_print_dir(parent); | ||
1438 | printk(KERN_CONT "/%s\n", name); | ||
1439 | } | ||
1440 | } | ||
1441 | } | ||
1442 | |||
1252 | static void drop_sysctl_table(struct ctl_table_header *header) | 1443 | static void drop_sysctl_table(struct ctl_table_header *header) |
1253 | { | 1444 | { |
1254 | struct ctl_dir *parent = header->parent; | 1445 | struct ctl_dir *parent = header->parent; |
@@ -1256,6 +1447,7 @@ static void drop_sysctl_table(struct ctl_table_header *header) | |||
1256 | if (--header->nreg) | 1447 | if (--header->nreg) |
1257 | return; | 1448 | return; |
1258 | 1449 | ||
1450 | put_links(header); | ||
1259 | start_unregistering(header); | 1451 | start_unregistering(header); |
1260 | if (!--header->count) | 1452 | if (!--header->count) |
1261 | kfree_rcu(header, rcu); | 1453 | kfree_rcu(header, rcu); |
@@ -1301,13 +1493,14 @@ void unregister_sysctl_table(struct ctl_table_header * header) | |||
1301 | } | 1493 | } |
1302 | EXPORT_SYMBOL(unregister_sysctl_table); | 1494 | EXPORT_SYMBOL(unregister_sysctl_table); |
1303 | 1495 | ||
1304 | void setup_sysctl_set(struct ctl_table_set *p, | 1496 | void setup_sysctl_set(struct ctl_table_set *set, |
1305 | struct ctl_table_root *root, | 1497 | struct ctl_table_root *root, |
1306 | int (*is_seen)(struct ctl_table_set *)) | 1498 | int (*is_seen)(struct ctl_table_set *)) |
1307 | { | 1499 | { |
1308 | INIT_LIST_HEAD(&p->list); | 1500 | memset(set, sizeof(*set), 0); |
1309 | p->root = root; | 1501 | INIT_LIST_HEAD(&set->list); |
1310 | p->is_seen = is_seen; | 1502 | set->is_seen = is_seen; |
1503 | init_header(&set->dir.header, root, set, root_table); | ||
1311 | } | 1504 | } |
1312 | 1505 | ||
1313 | void retire_sysctl_set(struct ctl_table_set *set) | 1506 | void retire_sysctl_set(struct ctl_table_set *set) |