From: Eric W. Biederman <hidden> Date: 2012-01-27 04:45:20
The problem:
The current implementation of sysctl has data structures and semantics
that are hard to understand, and causes sysctl to run slowly.
Sysctl running slowly is a scaling problem in general and the network
stack in particular.
After some heavy testing and way way too much patch rewriting I believe I
have a patchset that seems sane and reasonable and gets to what looks to
my weary eyes a sane result.
My patchset was heavily inspired by the previous work done by Lucian
Grinjincu. Lucian kept the need for this work alive, and did a much
better job than I ever though he did. Unfortunately I missed a lot
of the good things Lucian did my rushed attempts at code review.
This code is also available at:
kernel.org/pub/scm/linux/kernel/git/ebiederm/sysctl.git
Eric W. Biederman (28):
sysctl: Consolidate !CONFIG_SYSCTL handling
sysctl: Register the base sysctl table like any other sysctl table.
sysctl: Move the implementation into fs/proc/proc_sysctl.c
sysctl: Make the directories have nlink == 1
sysctl: Implement retire_sysctl_set
sysctl: Remove the unnecessary sysctl_set parent concept.
sysctl: Create local copies of directory names used in paths
sysctl: Add support for register sysctl tables with a normal cstring path.
sysctl: Add ctl_table chains into cstring paths
sysctl: register only tables of sysctl files
sysctl: Improve the sysctl sanity checks
sysctl: Remove the now unused ctl_table parent field.
sysctl: A more obvious version of grab_header.
sysctl: Initial support for auto-unregistering sysctl tables.
sysctl: Factor out init_header from __register_sysctl_paths
sysctl: Factor out insert_header and erase_header
sysctl: Normalize the root_table data structure.
sysctl: Rewrite proc_sys_lookup introducing find_entry and lookup_entry.
sysctl: Rewrite proc_sys_readdir in terms of first_entry and next_entry
sysctl: Add a root pointer to ctl_table_set
sysctl: Stop requiring explicit management of sysctl directories
sysctl: Add sysctl_print_dir and use it in get_subdir
sysctl: Replace root_list with links between sysctl_table_sets.
sysctl: Modify __register_sysctl_paths to take a set instead of a root and an nsproxy
sysctl: Move sysctl_check_dups into insert_header
sysctl: Make the header lists per directory.
sysctl: Index sysctl directories with rbtrees.
sysctl: Add register_sysctl for normal sysctl users
Lucas De Marchi (1):
sysctl: remove impossible condition check
fs/proc/internal.h | 3 +
fs/proc/proc_sysctl.c | 1240 +++++++++++++++++++++++++++++++++++++++++++++---
include/linux/sysctl.h | 106 +++--
kernel/Makefile | 1 -
kernel/sysctl.c | 501 +-------------------
kernel/sysctl_check.c | 160 -------
lib/Kconfig.debug | 8 -
net/sysctl_net.c | 24 +-
8 files changed, 1247 insertions(+), 796 deletions(-)
Eric
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:47:49
Simplify the code by treating the base sysctl table like any other
sysctl table and register it with register_sysctl_table.
To ensure this table is registered early enough to avoid problems
call sysctl_init from proc_sys_init.
Rename sysctl_net.c:sysctl_init() to net_sysctl_init() to avoid
name conflicts now that kernel/sysctl.c:sysctl_init() is no longer
static.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 3 ++-
include/linux/sysctl.h | 1 +
kernel/sysctl.c | 13 ++++---------
net/sysctl_net.c | 4 ++--
4 files changed, 9 insertions(+), 12 deletions(-)
@@ -192,7 +192,7 @@ static int sysrq_sysctl_handler(ctl_table *table, int write,#endif-staticstructctl_tableroot_table[];+staticstructctl_tableroot_table[1];staticstructctl_table_rootsysctl_table_root;staticstructctl_table_headerroot_table_header={{{.count=1,
@@ -222,7 +222,7 @@ int sysctl_legacy_va_layout;/* The default sysctl tables: */-staticstructctl_tableroot_table[]={+staticstructctl_tablesysctl_base_table[]={{.procname="kernel",.mode=0555,
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:47:49
Move the core sysctl code from kernel/sysctl.c and kernel/sysctl_check.c
into fs/proc/proc_sysctl.c.
Currently sysctl maintenance is hampered by the sysctl implementation
being split across 3 files with artificial layering between them.
Consolidate the entire sysctl implementation into 1 file so that
it is easier to see what is going on and hopefully allowing for
simpler maintenance.
For functions that are now only used in fs/proc/proc_sysctl.c remove
their declarations from sysctl.h and make them static in fs/proc/proc_sysctl.c
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/internal.h | 3 +
fs/proc/proc_sysctl.c | 622 ++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/sysctl.h | 16 --
kernel/Makefile | 1 -
kernel/sysctl.c | 464 ------------------------------------
kernel/sysctl_check.c | 160 -------------
6 files changed, 625 insertions(+), 641 deletions(-)
delete mode 100644 kernel/sysctl_check.c
@@ -24,6 +25,209 @@ void proc_sys_poll_notify(struct ctl_table_poll *poll)wake_up_interruptible(&poll->wait);}+staticstructctl_tableroot_table[1];+staticstructctl_table_rootsysctl_table_root;+staticstructctl_table_headerroot_table_header={+{{.count=1,+.ctl_table=root_table,+.ctl_entry=LIST_HEAD_INIT(sysctl_table_root.default_set.list),}},+.root=&sysctl_table_root,+.set=&sysctl_table_root.default_set,+};+staticstructctl_table_rootsysctl_table_root={+.root_list=LIST_HEAD_INIT(sysctl_table_root.root_list),+.default_set.list=LIST_HEAD_INIT(root_table_header.ctl_entry),+};++staticDEFINE_SPINLOCK(sysctl_lock);++/* called under sysctl_lock */+staticintuse_table(structctl_table_header*p)+{+if(unlikely(p->unregistering))+return0;+p->used++;+return1;+}++/* called under sysctl_lock */+staticvoidunuse_table(structctl_table_header*p)+{+if(!--p->used)+if(unlikely(p->unregistering))+complete(p->unregistering);+}++/* called under sysctl_lock, will reacquire if has to wait */+staticvoidstart_unregistering(structctl_table_header*p)+{+/*+*ifp->usedis0,nobodywillevertouchthatentryagain;+*we'lleliminateallpathstoitbeforedroppingsysctl_lock+*/+if(unlikely(p->used)){+structcompletionwait;+init_completion(&wait);+p->unregistering=&wait;+spin_unlock(&sysctl_lock);+wait_for_completion(&wait);+spin_lock(&sysctl_lock);+}else{+/* anything non-NULL; we'll never dereference it */+p->unregistering=ERR_PTR(-EINVAL);+}+/*+*donotremovefromthelistuntilnobodyholdsit;walkingthe+*listindo_sysctl()reliesonthat.+*/+list_del_init(&p->ctl_entry);+}++staticvoidsysctl_head_get(structctl_table_header*head)+{+spin_lock(&sysctl_lock);+head->count++;+spin_unlock(&sysctl_lock);+}++voidsysctl_head_put(structctl_table_header*head)+{+spin_lock(&sysctl_lock);+if(!--head->count)+kfree_rcu(head,rcu);+spin_unlock(&sysctl_lock);+}++staticstructctl_table_header*sysctl_head_grab(structctl_table_header*head)+{+if(!head)+BUG();+spin_lock(&sysctl_lock);+if(!use_table(head))+head=ERR_PTR(-ENOENT);+spin_unlock(&sysctl_lock);+returnhead;+}++staticvoidsysctl_head_finish(structctl_table_header*head)+{+if(!head)+return;+spin_lock(&sysctl_lock);+unuse_table(head);+spin_unlock(&sysctl_lock);+}++staticstructctl_table_set*+lookup_header_set(structctl_table_root*root,structnsproxy*namespaces)+{+structctl_table_set*set=&root->default_set;+if(root->lookup)+set=root->lookup(root,namespaces);+returnset;+}++staticstructlist_head*+lookup_header_list(structctl_table_root*root,structnsproxy*namespaces)+{+structctl_table_set*set=lookup_header_set(root,namespaces);+return&set->list;+}++staticstructctl_table_header*__sysctl_head_next(structnsproxy*namespaces,+structctl_table_header*prev)+{+structctl_table_root*root;+structlist_head*header_list;+structctl_table_header*head;+structlist_head*tmp;++spin_lock(&sysctl_lock);+if(prev){+head=prev;+tmp=&prev->ctl_entry;+unuse_table(prev);+gotonext;+}+tmp=&root_table_header.ctl_entry;+for(;;){+head=list_entry(tmp,structctl_table_header,ctl_entry);++if(!use_table(head))+gotonext;+spin_unlock(&sysctl_lock);+returnhead;+next:+root=head->root;+tmp=tmp->next;+header_list=lookup_header_list(root,namespaces);+if(tmp!=header_list)+continue;++do{+root=list_entry(root->root_list.next,+structctl_table_root,root_list);+if(root==&sysctl_table_root)+gotoout;+header_list=lookup_header_list(root,namespaces);+}while(list_empty(header_list));+tmp=header_list->next;+}+out:+spin_unlock(&sysctl_lock);+returnNULL;+}++staticstructctl_table_header*sysctl_head_next(structctl_table_header*prev)+{+return__sysctl_head_next(current->nsproxy,prev);+}++voidregister_sysctl_root(structctl_table_root*root)+{+spin_lock(&sysctl_lock);+list_add_tail(&root->root_list,&sysctl_table_root.root_list);+spin_unlock(&sysctl_lock);+}++/*+*sysctl_permdoesNOTgrantthesuperuserallrightsautomatically,because+*somesysctlvariablesarereadonlyeventoroot.+*/++staticinttest_perm(intmode,intop)+{+if(!current_euid())+mode>>=6;+elseif(in_egroup_p(0))+mode>>=3;+if((op&~mode&(MAY_READ|MAY_WRITE|MAY_EXEC))==0)+return0;+return-EACCES;+}++staticintsysctl_perm(structctl_table_root*root,structctl_table*table,intop)+{+intmode;++if(root->permissions)+mode=root->permissions(root,current->nsproxy,table);+else+mode=table->mode;++returntest_perm(mode,op);+}++staticvoidsysctl_set_parent(structctl_table*parent,structctl_table*table)+{+for(;table->procname;table++){+table->parent=parent;+if(table->child)+sysctl_set_parent(table,table->child);+}+}++staticstructinode*proc_sys_make_inode(structsuper_block*sb,structctl_table_header*head,structctl_table*table){
@@ -435,6 +639,21 @@ static int proc_sys_delete(const struct dentry *dentry)return!!PROC_I(dentry->d_inode)->sysctl->unregistering;}+staticintsysctl_is_seen(structctl_table_header*p)+{+structctl_table_set*set=p->set;+intres;+spin_lock(&sysctl_lock);+if(p->unregistering)+res=0;+elseif(!set->is_seen)+res=1;+else+res=set->is_seen(set);+spin_unlock(&sysctl_lock);+returnres;+}+staticintproc_sys_compare(conststructdentry*parent,conststructinode*pinode,conststructdentry*dentry,conststructinode*inode,
@@ -460,6 +679,409 @@ static const struct dentry_operations proc_sys_dentry_operations = {.d_compare=proc_sys_compare,};+staticstructctl_table*is_branch_in(structctl_table*branch,+structctl_table*table)+{+structctl_table*p;+constchar*s=branch->procname;++/* branch should have named subdirectory as its first element */+if(!s||!branch->child)+returnNULL;++/* ... and nothing else */+if(branch[1].procname)+returnNULL;++/* table should contain subdirectory with the same name */+for(p=table;p->procname;p++){+if(!p->child)+continue;+if(p->procname&&strcmp(p->procname,s)==0)+returnp;+}+returnNULL;+}++/* see if attaching q to p would be an improvement */+staticvoidtry_attach(structctl_table_header*p,structctl_table_header*q)+{+structctl_table*to=p->ctl_table,*by=q->ctl_table;+structctl_table*next;+intis_better=0;+intnot_in_parent=!p->attached_by;++while((next=is_branch_in(by,to))!=NULL){+if(by==q->attached_by)+is_better=1;+if(to==p->attached_by)+not_in_parent=1;+by=by->child;+to=next->child;+}++if(is_better&¬_in_parent){+q->attached_by=by;+q->attached_to=to;+q->parent=p;+}+}++#ifdef CONFIG_SYSCTL_SYSCALL_CHECK+staticintsysctl_depth(structctl_table*table)+{+structctl_table*tmp;+intdepth;++depth=0;+for(tmp=table;tmp->parent;tmp=tmp->parent)+depth++;++returndepth;+}++staticstructctl_table*sysctl_parent(structctl_table*table,intn)+{+inti;++for(i=0;table&&i<n;i++)+table=table->parent;++returntable;+}+++staticvoidsysctl_print_path(structctl_table*table)+{+structctl_table*tmp;+intdepth,i;+depth=sysctl_depth(table);+if(table->procname){+for(i=depth;i>=0;i--){+tmp=sysctl_parent(table,i);+printk("/%s",tmp->procname?tmp->procname:"");+}+}+printk(" ");+}++staticstructctl_table*sysctl_check_lookup(structnsproxy*namespaces,+structctl_table*table)+{+structctl_table_header*head;+structctl_table*ref,*test;+intdepth,cur_depth;++depth=sysctl_depth(table);++for(head=__sysctl_head_next(namespaces,NULL);head;+head=__sysctl_head_next(namespaces,head)){+cur_depth=depth;+ref=head->ctl_table;+repeat:+test=sysctl_parent(table,cur_depth);+for(;ref->procname;ref++){+intmatch=0;+if(cur_depth&&!ref->child)+continue;++if(test->procname&&ref->procname&&+(strcmp(test->procname,ref->procname)==0))+match++;++if(match){+if(cur_depth!=0){+cur_depth--;+ref=ref->child;+gotorepeat;+}+gotoout;+}+}+}+ref=NULL;+out:+sysctl_head_finish(head);+returnref;+}++staticvoidset_fail(constchar**fail,structctl_table*table,constchar*str)+{+if(*fail){+printk(KERN_ERR"sysctl table check failed: ");+sysctl_print_path(table);+printk(" %s\n",*fail);+dump_stack();+}+*fail=str;+}++staticvoidsysctl_check_leaf(structnsproxy*namespaces,+structctl_table*table,constchar**fail)+{+structctl_table*ref;++ref=sysctl_check_lookup(namespaces,table);+if(ref&&(ref!=table))+set_fail(fail,table,"Sysctl already exists");+}++staticintsysctl_check_table(structnsproxy*namespaces,structctl_table*table)+{+interror=0;+for(;table->procname;table++){+constchar*fail=NULL;++if(table->parent){+if(!table->parent->procname)+set_fail(&fail,table,"Parent without procname");+}+if(table->child){+if(table->data)+set_fail(&fail,table,"Directory with data?");+if(table->maxlen)+set_fail(&fail,table,"Directory with maxlen?");+if((table->mode&(S_IRUGO|S_IXUGO))!=table->mode)+set_fail(&fail,table,"Writable sysctl directory");+if(table->proc_handler)+set_fail(&fail,table,"Directory with proc_handler");+if(table->extra1)+set_fail(&fail,table,"Directory with extra1");+if(table->extra2)+set_fail(&fail,table,"Directory with extra2");+}else{+if((table->proc_handler==proc_dostring)||+(table->proc_handler==proc_dointvec)||+(table->proc_handler==proc_dointvec_minmax)||+(table->proc_handler==proc_dointvec_jiffies)||+(table->proc_handler==proc_dointvec_userhz_jiffies)||+(table->proc_handler==proc_dointvec_ms_jiffies)||+(table->proc_handler==proc_doulongvec_minmax)||+(table->proc_handler==proc_doulongvec_ms_jiffies_minmax)){+if(!table->data)+set_fail(&fail,table,"No data");+if(!table->maxlen)+set_fail(&fail,table,"No maxlen");+}+#ifdef CONFIG_PROC_SYSCTL+if(!table->proc_handler)+set_fail(&fail,table,"No proc_handler");+#endif+sysctl_check_leaf(namespaces,table,&fail);+}+if(table->mode>0777)+set_fail(&fail,table,"bogus .mode");+if(fail){+set_fail(&fail,table,NULL);+error=-EINVAL;+}+if(table->child)+error|=sysctl_check_table(namespaces,table->child);+}+returnerror;+}+#endif /* CONFIG_SYSCTL_SYSCALL_CHECK */++/**+*__register_sysctl_paths-registerasysctlhierarchy+*@root:Listofsysctlheaderstoregisteron+*@namespaces:Datatocomputewhichlistsofsysctlentriesarevisible+*@path:Thepathtothedirectorythesysctltableisin.+*@table:thetop-leveltablestructure+*+*Registerasysctltablehierarchy.@tableshouldbeafilledinctl_table+*array.Acompletely0filledentryterminatesthetable.+*+*Themembersofthe&structctl_tablestructureareusedasfollows:+*+*procname-thenameofthesysctlfileunder/proc/sys.Setto%NULLtonot+*enterasysctlfile+*+*data-apointertodataforusebyproc_handler+*+*maxlen-themaximumsizeinbytesofthedata+*+*mode-thefilepermissionsforthe/proc/sysfile,andforsysctl(2)+*+*child-apointertothechildsysctltableifthisentryisadirectory,or+*%NULL.+*+*proc_handler-thetexthandlerroutine(describedbelow)+*+*de-forinternalusebythesysctlroutines+*+*extra1,extra2-extrapointersusablebytheprochandlerroutines+*+*Leafnodesinthesysctltreewillberepresentedbyasinglefile+*under/proc;non-leafnodeswillberepresentedbydirectories.+*+*sysctl(2)canautomaticallymanagereadandwriterequeststhrough+*thesysctltable.Thedataandmaxlenfieldsofthectl_table+*structenableminimalvalidationofthevaluesbeingwrittentobe+*performed,andthemodefieldallowsminimalauthentication.+*+*Theremustbeaproc_handlerroutineforanyterminalnodes+*mirroredunder/proc/sys(non-terminalsarehandledbyabuilt-in+*directoryhandler).Severaldefaulthandlersareavailableto+*covercommoncases-+*+*proc_dostring(),proc_dointvec(),proc_dointvec_jiffies(),+*proc_dointvec_userhz_jiffies(),proc_dointvec_minmax(),+*proc_doulongvec_ms_jiffies_minmax(),proc_doulongvec_minmax()+*+*Itisthehandler'sjobtoreadtheinputbufferfromusermemory+*andprocessit.Thehandlershouldreturn0onsuccess.+*+*Thisroutinereturns%NULLonafailuretoregister,andapointer+*tothetableheaderonsuccess.+*/+structctl_table_header*__register_sysctl_paths(+structctl_table_root*root,+structnsproxy*namespaces,+conststructctl_path*path,structctl_table*table)+{+structctl_table_header*header;+structctl_table*new,**prevp;+unsignedintn,npath;+structctl_table_set*set;++/* Count the path components */+for(npath=0;path[npath].procname;++npath)+;++/*+*Foreachpathcomponent,allocatea2-elementctl_tablearray.+*Thefirstarrayelementwillbefilledwiththesysctlentry+*forthis,thesecondwillbethesentinel(procname==0).+*+*Weallocateeverythinginonegosothatwedon'thaveto+*worryaboutfreeingadditionalmemoryinunregister_sysctl_table.+*/+header=kzalloc(sizeof(structctl_table_header)++(2*npath*sizeof(structctl_table)),GFP_KERNEL);+if(!header)+returnNULL;++new=(structctl_table*)(header+1);++/* Now connect the dots */+prevp=&header->ctl_table;+for(n=0;n<npath;++n,++path){+/* Copy the procname */+new->procname=path->procname;+new->mode=0555;++*prevp=new;+prevp=&new->child;++new+=2;+}+*prevp=table;+header->ctl_table_arg=table;++INIT_LIST_HEAD(&header->ctl_entry);+header->used=0;+header->unregistering=NULL;+header->root=root;+sysctl_set_parent(NULL,header->ctl_table);+header->count=1;+#ifdef CONFIG_SYSCTL_SYSCALL_CHECK+if(sysctl_check_table(namespaces,header->ctl_table)){+kfree(header);+returnNULL;+}+#endif+spin_lock(&sysctl_lock);+header->set=lookup_header_set(root,namespaces);+header->attached_by=header->ctl_table;+header->attached_to=root_table;+header->parent=&root_table_header;+for(set=header->set;set;set=set->parent){+structctl_table_header*p;+list_for_each_entry(p,&set->list,ctl_entry){+if(p->unregistering)+continue;+try_attach(p,header);+}+}+header->parent->count++;+list_add_tail(&header->ctl_entry,&header->set->list);+spin_unlock(&sysctl_lock);++returnheader;+}++/**+*register_sysctl_table_path-registerasysctltablehierarchy+*@path:Thepathtothedirectorythesysctltableisin.+*@table:thetop-leveltablestructure+*+*Registerasysctltablehierarchy.@tableshouldbeafilledinctl_table+*array.Acompletely0filledentryterminatesthetable.+*+*See__register_sysctl_pathsformoredetails.+*/+structctl_table_header*register_sysctl_paths(conststructctl_path*path,+structctl_table*table)+{+return__register_sysctl_paths(&sysctl_table_root,current->nsproxy,+path,table);+}+EXPORT_SYMBOL(register_sysctl_paths);++/**+*register_sysctl_table-registerasysctltablehierarchy+*@table:thetop-leveltablestructure+*+*Registerasysctltablehierarchy.@tableshouldbeafilledinctl_table+*array.Acompletely0filledentryterminatesthetable.+*+*Seeregister_sysctl_pathsformoredetails.+*/+structctl_table_header*register_sysctl_table(structctl_table*table)+{+staticconststructctl_pathnull_path[]={{}};++returnregister_sysctl_paths(null_path,table);+}+EXPORT_SYMBOL(register_sysctl_table);++/**+*unregister_sysctl_table-unregisterasysctltablehierarchy+*@header:theheaderreturnedfromregister_sysctl_table+*+*Unregistersthesysctltableandallchildren.procentriesmaynot+*actuallyberemoveduntiltheyarenolongerusedbyanyone.+*/+voidunregister_sysctl_table(structctl_table_header*header)+{+might_sleep();++if(header==NULL)+return;++spin_lock(&sysctl_lock);+start_unregistering(header);+if(!--header->parent->count){+WARN_ON(1);+kfree_rcu(header->parent,rcu);+}+if(!--header->count)+kfree_rcu(header,rcu);+spin_unlock(&sysctl_lock);+}+EXPORT_SYMBOL(unregister_sysctl_table);++voidsetup_sysctl_set(structctl_table_set*p,+structctl_table_set*parent,+int(*is_seen)(structctl_table_set*))+{+INIT_LIST_HEAD(&p->list);+p->parent=parent?parent:&sysctl_table_root.default_set;+p->is_seen=is_seen;+}++int__initproc_sys_init(void){structproc_dir_entry*proc_sys_root;
@@ -192,20 +192,6 @@ static int sysrq_sysctl_handler(ctl_table *table, int write,#endif-staticstructctl_tableroot_table[1];-staticstructctl_table_rootsysctl_table_root;-staticstructctl_table_headerroot_table_header={-{{.count=1,-.ctl_table=root_table,-.ctl_entry=LIST_HEAD_INIT(sysctl_table_root.default_set.list),}},-.root=&sysctl_table_root,-.set=&sysctl_table_root.default_set,-};-staticstructctl_table_rootsysctl_table_root={-.root_list=LIST_HEAD_INIT(sysctl_table_root.root_list),-.default_set.list=LIST_HEAD_INIT(root_table_header.ctl_entry),-};-staticstructctl_tablekern_table[];staticstructctl_tablevm_table[];staticstructctl_tablefs_table[];
@@ -1559,459 +1545,12 @@ static struct ctl_table dev_table[] = {{}};-staticDEFINE_SPINLOCK(sysctl_lock);--/* called under sysctl_lock */-staticintuse_table(structctl_table_header*p)-{-if(unlikely(p->unregistering))-return0;-p->used++;-return1;-}--/* called under sysctl_lock */-staticvoidunuse_table(structctl_table_header*p)-{-if(!--p->used)-if(unlikely(p->unregistering))-complete(p->unregistering);-}--/* called under sysctl_lock, will reacquire if has to wait */-staticvoidstart_unregistering(structctl_table_header*p)-{-/*-*ifp->usedis0,nobodywillevertouchthatentryagain;-*we'lleliminateallpathstoitbeforedroppingsysctl_lock-*/-if(unlikely(p->used)){-structcompletionwait;-init_completion(&wait);-p->unregistering=&wait;-spin_unlock(&sysctl_lock);-wait_for_completion(&wait);-spin_lock(&sysctl_lock);-}else{-/* anything non-NULL; we'll never dereference it */-p->unregistering=ERR_PTR(-EINVAL);-}-/*-*donotremovefromthelistuntilnobodyholdsit;walkingthe-*listindo_sysctl()reliesonthat.-*/-list_del_init(&p->ctl_entry);-}--voidsysctl_head_get(structctl_table_header*head)-{-spin_lock(&sysctl_lock);-head->count++;-spin_unlock(&sysctl_lock);-}--voidsysctl_head_put(structctl_table_header*head)-{-spin_lock(&sysctl_lock);-if(!--head->count)-kfree_rcu(head,rcu);-spin_unlock(&sysctl_lock);-}--structctl_table_header*sysctl_head_grab(structctl_table_header*head)-{-if(!head)-BUG();-spin_lock(&sysctl_lock);-if(!use_table(head))-head=ERR_PTR(-ENOENT);-spin_unlock(&sysctl_lock);-returnhead;-}--voidsysctl_head_finish(structctl_table_header*head)-{-if(!head)-return;-spin_lock(&sysctl_lock);-unuse_table(head);-spin_unlock(&sysctl_lock);-}--staticstructctl_table_set*-lookup_header_set(structctl_table_root*root,structnsproxy*namespaces)-{-structctl_table_set*set=&root->default_set;-if(root->lookup)-set=root->lookup(root,namespaces);-returnset;-}--staticstructlist_head*-lookup_header_list(structctl_table_root*root,structnsproxy*namespaces)-{-structctl_table_set*set=lookup_header_set(root,namespaces);-return&set->list;-}--structctl_table_header*__sysctl_head_next(structnsproxy*namespaces,-structctl_table_header*prev)-{-structctl_table_root*root;-structlist_head*header_list;-structctl_table_header*head;-structlist_head*tmp;--spin_lock(&sysctl_lock);-if(prev){-head=prev;-tmp=&prev->ctl_entry;-unuse_table(prev);-gotonext;-}-tmp=&root_table_header.ctl_entry;-for(;;){-head=list_entry(tmp,structctl_table_header,ctl_entry);--if(!use_table(head))-gotonext;-spin_unlock(&sysctl_lock);-returnhead;-next:-root=head->root;-tmp=tmp->next;-header_list=lookup_header_list(root,namespaces);-if(tmp!=header_list)-continue;--do{-root=list_entry(root->root_list.next,-structctl_table_root,root_list);-if(root==&sysctl_table_root)-gotoout;-header_list=lookup_header_list(root,namespaces);-}while(list_empty(header_list));-tmp=header_list->next;-}-out:-spin_unlock(&sysctl_lock);-returnNULL;-}--structctl_table_header*sysctl_head_next(structctl_table_header*prev)-{-return__sysctl_head_next(current->nsproxy,prev);-}--voidregister_sysctl_root(structctl_table_root*root)-{-spin_lock(&sysctl_lock);-list_add_tail(&root->root_list,&sysctl_table_root.root_list);-spin_unlock(&sysctl_lock);-}--/*-*sysctl_permdoesNOTgrantthesuperuserallrightsautomatically,because-*somesysctlvariablesarereadonlyeventoroot.-*/--staticinttest_perm(intmode,intop)-{-if(!current_euid())-mode>>=6;-elseif(in_egroup_p(0))-mode>>=3;-if((op&~mode&(MAY_READ|MAY_WRITE|MAY_EXEC))==0)-return0;-return-EACCES;-}--intsysctl_perm(structctl_table_root*root,structctl_table*table,intop)-{-intmode;--if(root->permissions)-mode=root->permissions(root,current->nsproxy,table);-else-mode=table->mode;--returntest_perm(mode,op);-}--staticvoidsysctl_set_parent(structctl_table*parent,structctl_table*table)-{-for(;table->procname;table++){-table->parent=parent;-if(table->child)-sysctl_set_parent(table,table->child);-}-}-int__initsysctl_init(void){register_sysctl_table(sysctl_base_table);return0;}-staticstructctl_table*is_branch_in(structctl_table*branch,-structctl_table*table)-{-structctl_table*p;-constchar*s=branch->procname;--/* branch should have named subdirectory as its first element */-if(!s||!branch->child)-returnNULL;--/* ... and nothing else */-if(branch[1].procname)-returnNULL;--/* table should contain subdirectory with the same name */-for(p=table;p->procname;p++){-if(!p->child)-continue;-if(p->procname&&strcmp(p->procname,s)==0)-returnp;-}-returnNULL;-}--/* see if attaching q to p would be an improvement */-staticvoidtry_attach(structctl_table_header*p,structctl_table_header*q)-{-structctl_table*to=p->ctl_table,*by=q->ctl_table;-structctl_table*next;-intis_better=0;-intnot_in_parent=!p->attached_by;--while((next=is_branch_in(by,to))!=NULL){-if(by==q->attached_by)-is_better=1;-if(to==p->attached_by)-not_in_parent=1;-by=by->child;-to=next->child;-}--if(is_better&¬_in_parent){-q->attached_by=by;-q->attached_to=to;-q->parent=p;-}-}--/**-*__register_sysctl_paths-registerasysctlhierarchy-*@root:Listofsysctlheaderstoregisteron-*@namespaces:Datatocomputewhichlistsofsysctlentriesarevisible-*@path:Thepathtothedirectorythesysctltableisin.-*@table:thetop-leveltablestructure-*-*Registerasysctltablehierarchy.@tableshouldbeafilledinctl_table-*array.Acompletely0filledentryterminatesthetable.-*-*Themembersofthe&structctl_tablestructureareusedasfollows:-*-*procname-thenameofthesysctlfileunder/proc/sys.Setto%NULLtonot-*enterasysctlfile-*-*data-apointertodataforusebyproc_handler-*-*maxlen-themaximumsizeinbytesofthedata-*-*mode-thefilepermissionsforthe/proc/sysfile,andforsysctl(2)-*-*child-apointertothechildsysctltableifthisentryisadirectory,or-*%NULL.-*-*proc_handler-thetexthandlerroutine(describedbelow)-*-*de-forinternalusebythesysctlroutines-*-*extra1,extra2-extrapointersusablebytheprochandlerroutines-*-*Leafnodesinthesysctltreewillberepresentedbyasinglefile-*under/proc;non-leafnodeswillberepresentedbydirectories.-*-*sysctl(2)canautomaticallymanagereadandwriterequeststhrough-*thesysctltable.Thedataandmaxlenfieldsofthectl_table-*structenableminimalvalidationofthevaluesbeingwrittentobe-*performed,andthemodefieldallowsminimalauthentication.-*-*Theremustbeaproc_handlerroutineforanyterminalnodes-*mirroredunder/proc/sys(non-terminalsarehandledbyabuilt-in-*directoryhandler).Severaldefaulthandlersareavailableto-*covercommoncases--*-*proc_dostring(),proc_dointvec(),proc_dointvec_jiffies(),-*proc_dointvec_userhz_jiffies(),proc_dointvec_minmax(),-*proc_doulongvec_ms_jiffies_minmax(),proc_doulongvec_minmax()-*-*Itisthehandler'sjobtoreadtheinputbufferfromusermemory-*andprocessit.Thehandlershouldreturn0onsuccess.-*-*Thisroutinereturns%NULLonafailuretoregister,andapointer-*tothetableheaderonsuccess.-*/-structctl_table_header*__register_sysctl_paths(-structctl_table_root*root,-structnsproxy*namespaces,-conststructctl_path*path,structctl_table*table)-{-structctl_table_header*header;-structctl_table*new,**prevp;-unsignedintn,npath;-structctl_table_set*set;--/* Count the path components */-for(npath=0;path[npath].procname;++npath)-;--/*-*Foreachpathcomponent,allocatea2-elementctl_tablearray.-*Thefirstarrayelementwillbefilledwiththesysctlentry-*forthis,thesecondwillbethesentinel(procname==0).-*-*Weallocateeverythinginonegosothatwedon'thaveto-*worryaboutfreeingadditionalmemoryinunregister_sysctl_table.-*/-header=kzalloc(sizeof(structctl_table_header)+-(2*npath*sizeof(structctl_table)),GFP_KERNEL);-if(!header)-returnNULL;--new=(structctl_table*)(header+1);--/* Now connect the dots */-prevp=&header->ctl_table;-for(n=0;n<npath;++n,++path){-/* Copy the procname */-new->procname=path->procname;-new->mode=0555;--*prevp=new;-prevp=&new->child;--new+=2;-}-*prevp=table;-header->ctl_table_arg=table;--INIT_LIST_HEAD(&header->ctl_entry);-header->used=0;-header->unregistering=NULL;-header->root=root;-sysctl_set_parent(NULL,header->ctl_table);-header->count=1;-#ifdef CONFIG_SYSCTL_SYSCALL_CHECK-if(sysctl_check_table(namespaces,header->ctl_table)){-kfree(header);-returnNULL;-}-#endif-spin_lock(&sysctl_lock);-header->set=lookup_header_set(root,namespaces);-header->attached_by=header->ctl_table;-header->attached_to=root_table;-header->parent=&root_table_header;-for(set=header->set;set;set=set->parent){-structctl_table_header*p;-list_for_each_entry(p,&set->list,ctl_entry){-if(p->unregistering)-continue;-try_attach(p,header);-}-}-header->parent->count++;-list_add_tail(&header->ctl_entry,&header->set->list);-spin_unlock(&sysctl_lock);--returnheader;-}--/**-*register_sysctl_table_path-registerasysctltablehierarchy-*@path:Thepathtothedirectorythesysctltableisin.-*@table:thetop-leveltablestructure-*-*Registerasysctltablehierarchy.@tableshouldbeafilledinctl_table-*array.Acompletely0filledentryterminatesthetable.-*-*See__register_sysctl_pathsformoredetails.-*/-structctl_table_header*register_sysctl_paths(conststructctl_path*path,-structctl_table*table)-{-return__register_sysctl_paths(&sysctl_table_root,current->nsproxy,-path,table);-}--/**-*register_sysctl_table-registerasysctltablehierarchy-*@table:thetop-leveltablestructure-*-*Registerasysctltablehierarchy.@tableshouldbeafilledinctl_table-*array.Acompletely0filledentryterminatesthetable.-*-*Seeregister_sysctl_pathsformoredetails.-*/-structctl_table_header*register_sysctl_table(structctl_table*table)-{-staticconststructctl_pathnull_path[]={{}};--returnregister_sysctl_paths(null_path,table);-}--/**-*unregister_sysctl_table-unregisterasysctltablehierarchy-*@header:theheaderreturnedfromregister_sysctl_table-*-*Unregistersthesysctltableandallchildren.procentriesmaynot-*actuallyberemoveduntiltheyarenolongerusedbyanyone.-*/-voidunregister_sysctl_table(structctl_table_header*header)-{-might_sleep();--if(header==NULL)-return;--spin_lock(&sysctl_lock);-start_unregistering(header);-if(!--header->parent->count){-WARN_ON(1);-kfree_rcu(header->parent,rcu);-}-if(!--header->count)-kfree_rcu(header,rcu);-spin_unlock(&sysctl_lock);-}--intsysctl_is_seen(structctl_table_header*p)-{-structctl_table_set*set=p->set;-intres;-spin_lock(&sysctl_lock);-if(p->unregistering)-res=0;-elseif(!set->is_seen)-res=1;-else-res=set->is_seen(set);-spin_unlock(&sysctl_lock);-returnres;-}--voidsetup_sysctl_set(structctl_table_set*p,-structctl_table_set*parent,-int(*is_seen)(structctl_table_set*))-{-INIT_LIST_HEAD(&p->list);-p->parent=parent?parent:&sysctl_table_root.default_set;-p->is_seen=is_seen;-}-#endif /* CONFIG_SYSCTL *//*
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:31
From: Lucas De Marchi <redacted>
Remove checks for conditions that will never happen. If procname is NULL
the loop would already had bailed out, so there's no need to check it
again.
At the same time this also compacts the function find_in_table() by
refactoring it to be easier to read.
Signed-off-by: Lucas De Marchi <redacted>
Reviewed-by: Jesper Juhl <redacted>
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 14 ++------------
1 files changed, 2 insertions(+), 12 deletions(-)
@@ -59,17 +59,11 @@ out:staticstructctl_table*find_in_table(structctl_table*p,structqstr*name){-intlen;for(;p->procname;p++){--if(!p->procname)-continue;--len=strlen(p->procname);-if(len!=name->len)+if(strlen(p->procname)!=name->len)continue;-if(memcmp(p->procname,name->name,len)!=0)+if(memcmp(p->procname,name->name,name->len)!=0)continue;/* I have a match */
@@ -266,10 +260,6 @@ static int scan(struct ctl_table_header *head, ctl_table *table,for(;table->procname;table++,(*pos)++){intres;-/* Can't do anything without a proc name */-if(!table->procname)-continue;-if(*pos<file->f_pos)continue;
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:31
Simplify the code by treating the base sysctl table like any other
sysctl table and register it with register_sysctl_table.
To ensure this table is registered early enough to avoid problems
call sysctl_init from proc_sys_init.
Rename sysctl_net.c:sysctl_init() to net_sysctl_init() to avoid
name conflicts now that kernel/sysctl.c:sysctl_init() is no longer
static.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 3 ++-
include/linux/sysctl.h | 1 +
kernel/sysctl.c | 13 ++++---------
net/sysctl_net.c | 4 ++--
4 files changed, 9 insertions(+), 12 deletions(-)
@@ -192,7 +192,7 @@ static int sysrq_sysctl_handler(ctl_table *table, int write,#endif-staticstructctl_tableroot_table[];+staticstructctl_tableroot_table[1];staticstructctl_table_rootsysctl_table_root;staticstructctl_table_headerroot_table_header={{{.count=1,
@@ -222,7 +222,7 @@ int sysctl_legacy_va_layout;/* The default sysctl tables: */-staticstructctl_tableroot_table[]={+staticstructctl_tablesysctl_base_table[]={{.procname="kernel",.mode=0555,
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:33
Creating local copies of directory names is a good idea for
two reasons.
- The dynamic names used by callers must be copied into new
strings by the callers today to ensure the strings do not
change between register and unregister of the sysctl table.
- Sysctl directories have a potentially different lifetime
than the time between register and unregister of any
particular sysctl table.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 11 ++++++++---
1 files changed, 8 insertions(+), 3 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:34
Add nreg to ctl_table_header. When nreg drops to 0 the ctl_table_header
will be unregistered.
Factor out drop_sysctl_table from unregister_sysctl_table, and add
the logic for decrementing nreg.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 28 +++++++++++++++++++---------
include/linux/sysctl.h | 1 +
2 files changed, 20 insertions(+), 9 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:34
While useful at one time for selinux and the sysctl sanity
checks those users no longer use the parent field and we can
safely remove it.
Inspired-by: Lucian Adrian Grijincu [off-list ref]
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 12 +-----------
include/linux/sysctl.h | 1 -
2 files changed, 1 insertions(+), 12 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:42
From: Lucas De Marchi <redacted>
Remove checks for conditions that will never happen. If procname is NULL
the loop would already had bailed out, so there's no need to check it
again.
At the same time this also compacts the function find_in_table() by
refactoring it to be easier to read.
Signed-off-by: Lucas De Marchi <redacted>
Reviewed-by: Jesper Juhl <redacted>
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 14 ++------------
1 files changed, 2 insertions(+), 12 deletions(-)
@@ -59,17 +59,11 @@ out:staticstructctl_table*find_in_table(structctl_table*p,structqstr*name){-intlen;for(;p->procname;p++){--if(!p->procname)-continue;--len=strlen(p->procname);-if(len!=name->len)+if(strlen(p->procname)!=name->len)continue;-if(memcmp(p->procname,name->name,len)!=0)+if(memcmp(p->procname,name->name,name->len)!=0)continue;/* I have a match */
@@ -266,10 +260,6 @@ static int scan(struct ctl_table_header *head, ctl_table *table,for(;table->procname;table++,(*pos)++){intres;-/* Can't do anything without a proc name */-if(!table->procname)-continue;-if(*pos<file->f_pos)continue;
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:43
- In sysctl.h move functions only available if CONFIG_SYSCL
is defined inside of #ifdef CONFIG_SYSCTL
- Move the stub function definitions for !CONFIG_SYSCTL
into sysctl.h and make them static inlines.
Signed-off-by: Eric W. Biederman <redacted>
---
include/linux/sysctl.h | 95 +++++++++++++++++++++++++++++++-----------------
kernel/sysctl.c | 26 -------------
2 files changed, 62 insertions(+), 59 deletions(-)
@@ -1047,15 +1023,6 @@ struct ctl_tablevoid*extra2;};-structctl_table_root{-structlist_headroot_list;-structctl_table_setdefault_set;-structctl_table_set*(*lookup)(structctl_table_root*root,-structnsproxy*namespaces);-int(*permissions)(structctl_table_root*root,-structnsproxy*namespaces,structctl_table*table);-};-/* struct ctl_table_header is used to maintain dynamic lists ofstructctl_tabletrees.*/structctl_table_header
@@ -1078,11 +1045,45 @@ struct ctl_table_headerstructctl_table_header*parent;};+structctl_table_set{+structlist_headlist;+structctl_table_set*parent;+int(*is_seen)(structctl_table_set*);+};++structctl_table_root{+structlist_headroot_list;+structctl_table_setdefault_set;+structctl_table_set*(*lookup)(structctl_table_root*root,+structnsproxy*namespaces);+int(*permissions)(structctl_table_root*root,+structnsproxy*namespaces,structctl_table*table);+};+/* struct ctl_path describes where in the hierarchy a table is added */structctl_path{constchar*procname;};+#ifdef CONFIG_SYSCTL++voidproc_sys_poll_notify(structctl_table_poll*poll);++externvoidsetup_sysctl_set(structctl_table_set*p,+structctl_table_set*parent,+int(*is_seen)(structctl_table_set*));++externvoidsysctl_head_get(structctl_table_header*);+externvoidsysctl_head_put(structctl_table_header*);+externintsysctl_is_seen(structctl_table_header*);+externstructctl_table_header*sysctl_head_grab(structctl_table_header*);+externstructctl_table_header*sysctl_head_next(structctl_table_header*prev);+externstructctl_table_header*__sysctl_head_next(structnsproxy*namespaces,+structctl_table_header*prev);+externvoidsysctl_head_finish(structctl_table_header*prev);+externintsysctl_perm(structctl_table_root*root,+structctl_table*table,intop);+voidregister_sysctl_root(structctl_table_root*root);structctl_table_header*__register_sysctl_paths(structctl_table_root*root,structnsproxy*namespaces,
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:46
I goofed when I made sysctl directories have nlink == 0.
nlink == 0 means the directory has been deleted.
nlink == 1 meands a directory does not count subdirectories.
Use the default nlink == 1 for sysctl directories.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 1 -
1 files changed, 0 insertions(+), 1 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:47
This adds a small helper retire_sysctl_set to remove the intimate knowledge about
the how a sysctl_set is implemented from net/sysct_net.c
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 4 ++++
include/linux/sysctl.h | 1 +
net/sysctl_net.c | 2 +-
3 files changed, 6 insertions(+), 1 deletions(-)
@@ -82,7 +82,7 @@ static int __net_init sysctl_net_init(struct net *net)staticvoid__net_exitsysctl_net_exit(structnet*net){-WARN_ON(!list_empty(&net->sysctls.list));+retire_sysctl_set(&net->sysctls);}staticstructpernet_operationssysctl_pernet_ops={
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:48
In sysctl_net register the two networking roots in the proper order.
In register_sysctl walk the sysctl sets in the reverse order of the
sysctl roots.
Remove parent from ctl_table_set and setup_sysctl_set as it is no
longer needed.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 11 ++++++++---
include/linux/sysctl.h | 3 ---
net/sysctl_net.c | 5 ++---
3 files changed, 10 insertions(+), 9 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:49
Creating local copies of directory names is a good idea for
two reasons.
- The dynamic names used by callers must be copied into new
strings by the callers today to ensure the strings do not
change between register and unregister of the sysctl table.
- Sysctl directories have a potentially different lifetime
than the time between register and unregister of any
particular sysctl table.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 11 ++++++++---
1 files changed, 8 insertions(+), 3 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:50
Make __register_sysctl_table the core sysctl registration operation and
make it take a char * string as path.
Now that binary paths have been banished into the real of backwards
compatibility in kernel/binary_sysctl.c where they can be safely
ignored there is no longer a need to use struct ctl_path to represent
path names when registering ctl_tables.
Start the transition to using normal char * strings to represent
pathnames when registering sysctl tables. Normal strings are easier
to deal with both in the internal sysctl implementation and for
programmers registering sysctl tables.
__register_sysctl_paths is turned into a backwards compatibility wrapper
that converts a ctl_path array into a normal char * string.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 94 ++++++++++++++++++++++++++++++++++++++++++-----
include/linux/sysctl.h | 3 ++
2 files changed, 87 insertions(+), 10 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:51
For any component of table passed to __register_sysctl_paths
that actually serves as a path, add that to the cstring path
that is passed to __register_sysctl_table.
The result is that for most calls to __register_sysctl_paths
we only pass a table to __register_sysctl_table that contains
no child directories.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 9 +++++++++
1 files changed, 9 insertions(+), 0 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:52
Split the registration of a complex ctl_table array which may have
arbitrary numbers of directories (->child != NULL) and tables of files
into a series of simpler registrations that only register tables of files.
Graphically:
register('dir', { + file-a
+ file-b
+ subdir1
+ file-c
+ subdir2
+ file-d
+ file-e })
is transformed into:
wrapper->subheaders[0] = register('dir', {file1-a, file1-b})
wrapper->subheaders[1] = register('dir/subdir1', {file-c})
wrapper->subheaders[2] = register('dir/subdir2', {file-d, file-e})
return wrapper
This guarantees that __register_sysctl_table will only see a simple
ctl_table array with all entries having (->child == NULL).
Care was taken to pass the original simple ctl_table arrays to
__register_sysctl_table whenever possible.
This change is derived from a similar patch written
by Lucrian Grijincu.
Inspired-by: Lucian Adrian Grijincu [off-list ref]
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 165 ++++++++++++++++++++++++++++++++++++++++++-----
include/linux/sysctl.h | 2 +-
2 files changed, 148 insertions(+), 19 deletions(-)
@@ -1059,6 +1049,100 @@ static char *append_path(const char *path, char *pos, const char *name)returnpos;}+staticintcount_subheaders(structctl_table*table)+{+inthas_files=0;+intnr_subheaders=0;+structctl_table*entry;++/* special case: no directory and empty directory */+if(!table||!table->procname)+return1;++for(entry=table;entry->procname;entry++){+if(entry->child)+nr_subheaders+=count_subheaders(entry->child);+else+has_files=1;+}+returnnr_subheaders+has_files;+}++staticintregister_leaf_sysctl_tables(constchar*path,char*pos,+structctl_table_header***subheader,+structctl_table_root*root,structnsproxy*namespaces,+structctl_table*table)+{+structctl_table*ctl_table_arg=NULL;+structctl_table*entry,*files;+intnr_files=0;+intnr_dirs=0;+interr=-ENOMEM;++for(entry=table;entry->procname;entry++){+if(entry->child)+nr_dirs++;+else+nr_files++;+}++files=table;+/* If there are mixed files and directories we need a new table */+if(nr_dirs&&nr_files){+structctl_table*new;+files=kzalloc(sizeof(structctl_table)*(nr_files+1),+GFP_KERNEL);+if(!files)+gotoout;++ctl_table_arg=files;+for(new=files,entry=table;entry->procname;entry++){+if(entry->child)+continue;+*new=*entry;+new++;+}+}++/* Register everything except a directory full of subdirectories */+if(nr_files||!nr_dirs){+structctl_table_header*header;+header=__register_sysctl_table(root,namespaces,path,files);+if(!header){+kfree(ctl_table_arg);+gotoout;+}++/* Remember if we need to free the file table */+header->ctl_table_arg=ctl_table_arg;+**subheader=header;+(*subheader)++;+}++/* Recurse into the subdirectories. */+for(entry=table;entry->procname;entry++){+char*child_pos;++if(!entry->child)+continue;++err=-ENAMETOOLONG;+child_pos=append_path(path,pos,entry->procname);+if(!child_pos)+gotoout;++err=register_leaf_sysctl_tables(path,child_pos,subheader,+root,namespaces,entry->child);+pos[0]='\0';+if(err)+gotoout;+}+err=0;+out:+/* On failure our caller will unregister all registered subheaders */+returnerr;+}+/***__register_sysctl_paths-registerasysctltablehierarchy*@root:Listofsysctlheaderstoregisteron
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:53
- Stop validating subdirectories now that we only register leaf tables
- Cleanup and improve the duplicate filename check.
* Run the duplicate filename check under the sysctl_lock to guarantee
we never add duplicate names.
* Reduce the duplicate filename check to nearly O(M*N) where M is the
number of entries in tthe table we are registering and N is the
number of entries in the directory before we got there.
- Move the duplicate filename check into it's own function and call
it directtly from __register_sysctl_table
- Kill the config option as the sanity checks are now cheap enough
the config option is unnecessary. The original reason for the config
option was because we had a huge table used to verify the proc filename
to binary sysctl mapping. That table has now evolved into the binary_sysctl
translation layer and is no longer part of the sysctl_check code.
- Tighten up the permission checks. Guarnateeing that files only have read
or write permissions.
- Removed redudant check for parents having a procname as now everything has
a procname.
- Generalize the backtrace logic so that we print a backtrace from
any failure of __register_sysctl_table that was not caused by
a memmory allocation failure. The backtrace allows us to track
down who erroneously registered a sysctl table.
Bechmark before (CONFIG_SYSCTL_CHECK=y):
make-dummies 0 999 -> 12s
rmmod dummy -> 0.08s
Bechmark before (CONFIG_SYSCTL_CHECK=n):
make-dummies 0 999 -> 0.7s
rmmod dummy -> 0.06s
make-dummies 0 99999 -> 1m13s
rmmod dummy -> 0.38s
Benchmark after:
make-dummies 0 999 -> 0.65s
rmmod dummy -> 0.055s
make-dummies 0 9999 -> 1m10s
rmmod dummy -> 0.39s
The sysctl sanity checks now impose no measurable cost.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 222 +++++++++++++++++++------------------------------
lib/Kconfig.debug | 8 --
2 files changed, 86 insertions(+), 144 deletions(-)
@@ -726,160 +726,106 @@ static void try_attach(struct ctl_table_header *p, struct ctl_table_header *q)}}-#ifdef CONFIG_SYSCTL_SYSCALL_CHECK-staticintsysctl_depth(structctl_table*table)+staticintsysctl_check_table_dups(constchar*path,structctl_table*old,+structctl_table*table){-structctl_table*tmp;-intdepth;--depth=0;-for(tmp=table;tmp->parent;tmp=tmp->parent)-depth++;+structctl_table*entry,*test;+interror=0;-returndepth;+for(entry=old;entry->procname;entry++){+for(test=table;test->procname;test++){+if(strcmp(entry->procname,test->procname)==0){+printk(KERN_ERR"sysctl duplicate entry: %s/%s\n",+path,test->procname);+error=-EEXIST;+}+}+}+returnerror;}-staticstructctl_table*sysctl_parent(structctl_table*table,intn)+staticintsysctl_check_dups(structnsproxy*namespaces,+structctl_table_header*header,+constchar*path,structctl_table*table){-inti;+structctl_table_root*root;+structctl_table_set*set;+structctl_table_header*dir_head,*head;+structctl_table*dir_table;+interror=0;-for(i=0;table&&i<n;i++)-table=table->parent;+/* No dups if we are the only member of our directory */+if(header->attached_by!=table)+return0;-returntable;-}+dir_head=header->parent;+dir_table=header->attached_to;+error=sysctl_check_table_dups(path,dir_table,table);-staticvoidsysctl_print_path(structctl_table*table)-{-structctl_table*tmp;-intdepth,i;-depth=sysctl_depth(table);-if(table->procname){-for(i=depth;i>=0;i--){-tmp=sysctl_parent(table,i);-printk("/%s",tmp->procname?tmp->procname:"");-}-}-printk(" ");-}+root=&sysctl_table_root;+do{+set=lookup_header_set(root,namespaces);-staticstructctl_table*sysctl_check_lookup(structnsproxy*namespaces,-structctl_table*table)-{-structctl_table_header*head;-structctl_table*ref,*test;-intdepth,cur_depth;--depth=sysctl_depth(table);--for(head=__sysctl_head_next(namespaces,NULL);head;-head=__sysctl_head_next(namespaces,head)){-cur_depth=depth;-ref=head->ctl_table;-repeat:-test=sysctl_parent(table,cur_depth);-for(;ref->procname;ref++){-intmatch=0;-if(cur_depth&&!ref->child)+list_for_each_entry(head,&set->list,ctl_entry){+if(head->unregistering)continue;--if(test->procname&&ref->procname&&-(strcmp(test->procname,ref->procname)==0))-match++;--if(match){-if(cur_depth!=0){-cur_depth--;-ref=ref->child;-gotorepeat;-}-gotoout;-}+if(head->attached_to!=dir_table)+continue;+error=sysctl_check_table_dups(path,head->attached_by,+table);}-}-ref=NULL;-out:-sysctl_head_finish(head);-returnref;+root=list_entry(root->root_list.next,+structctl_table_root,root_list);+}while(root!=&sysctl_table_root);+returnerror;}-staticvoidset_fail(constchar**fail,structctl_table*table,constchar*str)+staticintsysctl_err(constchar*path,structctl_table*table,char*fmt,...){-if(*fail){-printk(KERN_ERR"sysctl table check failed: ");-sysctl_print_path(table);-printk(" %s\n",*fail);-dump_stack();-}-*fail=str;-}+structva_formatvaf;+va_listargs;-staticvoidsysctl_check_leaf(structnsproxy*namespaces,-structctl_table*table,constchar**fail)-{-structctl_table*ref;+va_start(args,fmt);+vaf.fmt=fmt;+vaf.va=&args;++printk(KERN_ERR"sysctl table check failed: %s/%s %pV\n",+path,table->procname,&vaf);-ref=sysctl_check_lookup(namespaces,table);-if(ref&&(ref!=table))-set_fail(fail,table,"Sysctl already exists");+va_end(args);+return-EINVAL;}-staticintsysctl_check_table(structnsproxy*namespaces,structctl_table*table)+staticintsysctl_check_table(constchar*path,structctl_table*table){-interror=0;+interr=0;for(;table->procname;table++){-constchar*fail=NULL;--if(table->parent){-if(!table->parent->procname)-set_fail(&fail,table,"Parent without procname");-}-if(table->child){-if(table->data)-set_fail(&fail,table,"Directory with data?");-if(table->maxlen)-set_fail(&fail,table,"Directory with maxlen?");-if((table->mode&(S_IRUGO|S_IXUGO))!=table->mode)-set_fail(&fail,table,"Writable sysctl directory");-if(table->proc_handler)-set_fail(&fail,table,"Directory with proc_handler");-if(table->extra1)-set_fail(&fail,table,"Directory with extra1");-if(table->extra2)-set_fail(&fail,table,"Directory with extra2");-}else{-if((table->proc_handler==proc_dostring)||-(table->proc_handler==proc_dointvec)||-(table->proc_handler==proc_dointvec_minmax)||-(table->proc_handler==proc_dointvec_jiffies)||-(table->proc_handler==proc_dointvec_userhz_jiffies)||-(table->proc_handler==proc_dointvec_ms_jiffies)||-(table->proc_handler==proc_doulongvec_minmax)||-(table->proc_handler==proc_doulongvec_ms_jiffies_minmax)){-if(!table->data)-set_fail(&fail,table,"No data");-if(!table->maxlen)-set_fail(&fail,table,"No maxlen");-}-#ifdef CONFIG_PROC_SYSCTL-if(!table->proc_handler)-set_fail(&fail,table,"No proc_handler");-#endif-sysctl_check_leaf(namespaces,table,&fail);-}-if(table->mode>0777)-set_fail(&fail,table,"bogus .mode");-if(fail){-set_fail(&fail,table,NULL);-error=-EINVAL;-}if(table->child)-error|=sysctl_check_table(namespaces,table->child);+err=sysctl_err(path,table,"Not a file");++if((table->proc_handler==proc_dostring)||+(table->proc_handler==proc_dointvec)||+(table->proc_handler==proc_dointvec_minmax)||+(table->proc_handler==proc_dointvec_jiffies)||+(table->proc_handler==proc_dointvec_userhz_jiffies)||+(table->proc_handler==proc_dointvec_ms_jiffies)||+(table->proc_handler==proc_doulongvec_minmax)||+(table->proc_handler==proc_doulongvec_ms_jiffies_minmax)){+if(!table->data)+err=sysctl_err(path,table,"No data");+if(!table->maxlen)+err=sysctl_err(path,table,"No maxlen");+}+if(!table->proc_handler)+err=sysctl_err(path,table,"No proc_handler");++if((table->mode&(S_IRUGO|S_IWUGO))!=table->mode)+err=sysctl_err(path,table,"bogus .mode 0%o",+table->mode);}-returnerror;+returnerr;}-#endif /* CONFIG_SYSCTL_SYSCALL_CHECK *//***__register_sysctl_table-registeraleafsysctltable
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:54
While useful at one time for selinux and the sysctl sanity
checks those users no longer use the parent field and we can
safely remove it.
Inspired-by: Lucian Adrian Grijincu [off-list ref]
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 12 +-----------
include/linux/sysctl.h | 1 -
2 files changed, 1 insertions(+), 12 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:55
Instead of relying on sysct_head_next(NULL) to magically
return the right header for the root directory instead
explicitly transform NULL into the root directories header.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 8 ++++----
1 files changed, 4 insertions(+), 4 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:56
Add nreg to ctl_table_header. When nreg drops to 0 the ctl_table_header
will be unregistered.
Factor out drop_sysctl_table from unregister_sysctl_table, and add
the logic for decrementing nreg.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 28 +++++++++++++++++++---------
include/linux/sysctl.h | 1 +
2 files changed, 20 insertions(+), 9 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:57
Factor out a routing to initialize the sysctl_table_header.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 25 +++++++++++++++++--------
1 files changed, 17 insertions(+), 8 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:49:59
Every other directory has a .child member and we look at the .child
for our entries. Do the same for the root_table.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 15 +++++++++++----
1 files changed, 11 insertions(+), 4 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:50:00
Replace the helpers that proc_sys_lookup uses with helpers that work
in terms of an entire sysctl directory. This is worse for sysctl_lock
hold times but it is much better for code clarity and the code cleanups
to come.
find_in_table is no longer needed so it is removed.
find_entry a general helper to find entries in a directory is added.
lookup_entry is a simple wrapper around find_entry that takes the
sysctl_lock increases the use count if an entry is found and drops
the sysctl_lock.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 102 ++++++++++++++++++++++++++++++++++++------------
1 files changed, 76 insertions(+), 26 deletions(-)
@@ -284,21 +359,6 @@ out:returninode;}-staticstructctl_table*find_in_table(structctl_table*p,structqstr*name)-{-for(;p->procname;p++){-if(strlen(p->procname)!=name->len)-continue;--if(memcmp(p->procname,name->name,name->len)!=0)-continue;--/* I have a match */-returnp;-}-returnNULL;-}-staticstructctl_table_header*grab_header(structinode*inode){structctl_table_header*head=PROC_I(inode)->sysctl;
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:50:01
Replace sysctl_head_next with first_entry and next_entry. These new
iterators operate at the level of sysctl table entries and filter
out any sysctl tables that should not be shown.
Utilizing two specialized functions instead of a single function removes
conditionals for handling awkward special cases that only come up
at the beginning of iteration, making the iterators easier to read
and understand.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 98 +++++++++++++++++++++++++++++++------------------
1 files changed, 62 insertions(+), 36 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:50:02
Add a ctl_table_root pointer to ctl_table set so it is easy to
go from a ctl_table_set to a ctl_table_root.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 3 +++
include/linux/sysctl.h | 3 +++
net/sysctl_net.c | 5 ++---
3 files changed, 8 insertions(+), 3 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:50:03
Simplify the code and the sysctl semantics by autogenerating
sysctl directories when a sysctl table is registered that needs
the directories and autodeleting the directories when there are
no more sysctl tables registered that need them.
Autogenerating directories keeps sysctl tables from depending
on each other, removing all of the arcane register/unregister
ordering constraints and makes it impossible to get the order
wrong when reigsering and unregistering sysctl tables.
Autogenerating directories yields one unique entity that dentries
can point to, retaining the current effective use of the dcache.
Add struct ctl_dir as the type of these new autogenerated
directories.
The attached_by and attached_to fields in ctl_table_header are
removed as they are no longer needed.
The child field in ctl_table is no longer needed by the core of
the sysctl code. ctl_table.child can be removed once all of the
existing users have been updated.
Benchmark before:
make-dummies 0 999 -> 0.7s
rmmod dummy -> 0.07s
make-dummies 0 9999 -> 1m10s
rmmod dummy -> 0.4s
Benchmark after:
make-dummies 0 999 -> 0.44s
rmmod dummy -> 0.065s
make-dummies 0 9999 -> 1m36s
rmmod dummy -> 0.4s
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 342 ++++++++++++++++++++----------------------------
include/linux/sysctl.h | 10 +-
2 files changed, 150 insertions(+), 202 deletions(-)
@@ -779,52 +750,86 @@ static const struct dentry_operations proc_sys_dentry_operations = {.d_compare=proc_sys_compare,};-staticstructctl_table*is_branch_in(structctl_table*branch,-structctl_table*table)+staticstructctl_dir*find_subdir(structctl_table_set*set,structctl_dir*dir,+constchar*name,intnamelen){-structctl_table*p;-constchar*s=branch->procname;+structctl_table_header*head;+structctl_table*entry;-/* branch should have named subdirectory as its first element */-if(!s||!branch->child)-returnNULL;+entry=find_entry(&head,set,dir,name,namelen);+if(!entry)+returnERR_PTR(-ENOENT);+if(S_ISDIR(entry->mode))+returncontainer_of(head,structctl_dir,header);+returnERR_PTR(-ENOTDIR);+}++staticstructctl_dir*new_dir(structctl_table_set*set,+constchar*name,intnamelen)+{+structctl_table*table;+structctl_dir*new;+char*new_name;-/* ... and nothing else */-if(branch[1].procname)+new=kzalloc(sizeof(*new)+sizeof(structctl_table)*2++namelen+1,GFP_KERNEL);+if(!new)returnNULL;-/* table should contain subdirectory with the same name */-for(p=table;p->procname;p++){-if(!p->child)-continue;-if(p->procname&&strcmp(p->procname,s)==0)-returnp;-}-returnNULL;+table=(structctl_table*)(new+1);+new_name=(char*)(table+2);+memcpy(new_name,name,namelen);+new_name[namelen]='\0';+table[0].procname=new_name;+table[0].mode=S_IFDIR|S_IRUGO|S_IXUGO;+init_header(&new->header,set->root,set,table);++returnnew;}-/* see if attaching q to p would be an improvement */-staticvoidtry_attach(structctl_table_header*p,structctl_table_header*q)+staticstructctl_dir*get_subdir(structctl_table_set*set,+structctl_dir*dir,constchar*name,intnamelen){-structctl_table*to=p->ctl_table,*by=q->ctl_table;-structctl_table*next;-intis_better=0;-intnot_in_parent=!p->attached_by;--while((next=is_branch_in(by,to))!=NULL){-if(by==q->attached_by)-is_better=1;-if(to==p->attached_by)-not_in_parent=1;-by=by->child;-to=next->child;-}+structctl_dir*subdir,*new=NULL;-if(is_better&¬_in_parent){-q->attached_by=by;-q->attached_to=to;-q->parent=p;+spin_lock(&sysctl_lock);+subdir=find_subdir(dir->header.set,dir,name,namelen);+if(!IS_ERR(subdir))+gotofound;+if((PTR_ERR(subdir)==-ENOENT)&&set!=dir->header.set)+subdir=find_subdir(set,dir,name,namelen);+if(!IS_ERR(subdir))+gotofound;+if(PTR_ERR(subdir)!=-ENOENT)+gotofailed;++spin_unlock(&sysctl_lock);+new=new_dir(set,name,namelen);+spin_lock(&sysctl_lock);+subdir=ERR_PTR(-ENOMEM);+if(!new)+gotofailed;++subdir=find_subdir(set,dir,name,namelen);+if(!IS_ERR(subdir))+gotofound;+if(PTR_ERR(subdir)!=-ENOENT)+gotofailed;++insert_header(dir,&new->header);+subdir=new;+found:+subdir->header.nreg++;+failed:+if(unlikely(IS_ERR(subdir))){+printk(KERN_ERR"sysctl could not get directory: %*.*s %ld\n",+namelen,namelen,name,PTR_ERR(subdir));}+drop_sysctl_table(&dir->header);+if(new)+drop_sysctl_table(&new->header);+spin_unlock(&sysctl_lock);+returnsubdir;}staticintsysctl_check_table_dups(constchar*path,structctl_table*old,
@@ -846,24 +851,14 @@ static int sysctl_check_table_dups(const char *path, struct ctl_table *old,}staticintsysctl_check_dups(structnsproxy*namespaces,-structctl_table_header*header,+structctl_dir*dir,constchar*path,structctl_table*table){structctl_table_root*root;structctl_table_set*set;-structctl_table_header*dir_head,*head;-structctl_table*dir_table;+structctl_table_header*head;interror=0;-/* No dups if we are the only member of our directory */-if(header->attached_by!=table)-return0;--dir_head=header->parent;-dir_table=header->attached_to;--error=sysctl_check_table_dups(path,dir_table,table);-root=&sysctl_table_root;do{set=lookup_header_set(root,namespaces);
@@ -871,9 +866,9 @@ static int sysctl_check_dups(struct nsproxy *namespaces,list_for_each_entry(head,&set->list,ctl_entry){if(head->unregistering)continue;-if(head->attached_to!=dir_table)+if(head->parent!=dir)continue;-error=sysctl_check_table_dups(path,head->attached_by,+error=sysctl_check_table_dups(path,head->ctl_table,table);}root=list_entry(root->root_list.next,
@@ -977,47 +972,25 @@ struct ctl_table_header *__register_sysctl_table(constchar*path,structctl_table*table){structctl_table_header*header;-structctl_table*new,**prevp;constchar*name,*nextname;-unsignedintnpath=0;structctl_table_set*set;-size_tpath_bytes=0;-char*new_name;--/* Count the path components */-for(name=path;name;name=nextname){-intnamelen;-nextname=strchr(name,'/');-if(nextname){-namelen=nextname-name;-nextname++;-}else{-namelen=strlen(name);-}-if(namelen==0)-continue;-path_bytes+=namelen+1;-npath++;-}+structctl_dir*dir;-/*-*Foreachpathcomponent,allocatea2-elementctl_tablearray.-*Thefirstarrayelementwillbefilledwiththesysctlentry-*forthis,thesecondwillbethesentinel(procname==0).-*-*Weallocateeverythinginonegosothatwedon'thaveto-*worryaboutfreeingadditionalmemoryinunregister_sysctl_table.-*/-header=kzalloc(sizeof(structctl_table_header)+path_bytes+-(2*npath*sizeof(structctl_table)),GFP_KERNEL);+header=kzalloc(sizeof(structctl_table_header),GFP_KERNEL);if(!header)returnNULL;-new=(structctl_table*)(header+1);-new_name=(char*)(new+(2*npath));+init_header(header,root,NULL,table);+if(sysctl_check_table(path,table))+gotofail;++spin_lock(&sysctl_lock);+header->set=set=lookup_header_set(root,namespaces);+dir=&sysctl_root_dir;+dir->header.nreg++;+spin_unlock(&sysctl_lock);-/* Now connect the dots */-prevp=&header->ctl_table;+/* Find the directory for the ctl_table */for(name=path;name;name=nextname){intnamelen;nextname=strchr(name,'/');
@@ -1040,9 +1041,12 @@ struct ctl_table_headerstructctl_table*ctl_table_arg;structctl_table_root*root;structctl_table_set*set;-structctl_table*attached_by;-structctl_table*attached_to;-structctl_table_header*parent;+structctl_dir*parent;+};++structctl_dir{+/* Header must be at the start of ctl_dir */+structctl_table_headerheader;};structctl_table_set{
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:50:04
When there are errors it is very nice to know the full sysctl path.
Add a simple function that computes the sysctl path and prints it
out.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 11 ++++++++++-
1 files changed, 10 insertions(+), 1 deletions(-)
@@ -822,7 +829,9 @@ found:subdir->header.nreg++;failed:if(unlikely(IS_ERR(subdir))){-printk(KERN_ERR"sysctl could not get directory: %*.*s %ld\n",+printk(KERN_ERR"sysctl could not get directory: ");+sysctl_print_dir(dir);+printk(KERN_CONT"/%*.*s %ld\n",namelen,namelen,name,PTR_ERR(subdir));}drop_sysctl_table(&dir->header);
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:50:05
Piecing together directories by looking first in one directory
tree, than in another directory tree and finally in a third
directory tree makes it hard to verify that some directory
entries are not multiply defined and makes it hard to create
efficient implementations the sysctl filesystem.
Replace the sysctl wide list of roots with autogenerated
links from the core sysctl directory tree to the other
sysctl directory trees.
This simplifies sysctl directory reading and lookups as now
only entries in a single sysctl directory tree need to be
considered.
Benchmark before:
make-dummies 0 999 -> 0.44s
rmmod dummy -> 0.065s
make-dummies 0 9999 -> 1m36s
rmmod dummy -> 0.4s
Benchmark after:
make-dummies 0 999 -> 0.63s
rmmod dummy -> 0.12s
make-dummies 0 9999 -> 2m35s
rmmod dummy -> 18s
The slowdown is caused by the lookups used in insert_headers
and put_links to see if we need to add links or remove links.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 397 +++++++++++++++++++++++++++++++++++------------
include/linux/sysctl.h | 3 +-
2 files changed, 296 insertions(+), 104 deletions(-)
@@ -547,6 +526,25 @@ static int proc_sys_fill_cache(struct file *filp, void *dirent,return!!filldir(dirent,qname.name,qname.len,filp->f_pos,ino,type);}+staticintproc_sys_link_fill_cache(structfile*filp,void*dirent,+filldir_tfilldir,+structctl_table_header*head,+structctl_table*table)+{+interr,ret=0;+head=sysctl_head_grab(head);++/* It is not an error if we can not follow the link ignore it */+err=sysctl_follow_link(&head,&table,current->nsproxy);+if(err)+gotoout;++ret=proc_sys_fill_cache(filp,dirent,filldir,head,table);+out:+sysctl_head_finish(head);+returnret;+}+staticintscan(structctl_table_header*head,ctl_table*table,unsignedlong*pos,structfile*file,void*dirent,filldir_tfilldir)
@@ -841,6 +840,57 @@ failed:returnsubdir;}+staticstructctl_dir*xlate_dir(structctl_table_set*set,structctl_dir*dir)+{+structctl_dir*parent;+constchar*procname;+if(!dir->header.parent)+return&set->dir;+parent=xlate_dir(set,dir->header.parent);+if(IS_ERR(parent))+returnparent;+procname=dir->header.ctl_table[0].procname;+returnfind_subdir(parent,procname,strlen(procname));+}++staticintsysctl_follow_link(structctl_table_header**phead,+structctl_table**pentry,structnsproxy*namespaces)+{+structctl_table_header*head;+structctl_table_root*root;+structctl_table_set*set;+structctl_table*entry;+structctl_dir*dir;+intret;++/* Get out quickly if not a link */+if(!S_ISLNK((*pentry)->mode))+return0;++ret=0;+spin_lock(&sysctl_lock);+root=(*pentry)->data;+set=lookup_header_set(root,namespaces);+dir=xlate_dir(set,(*phead)->parent);+if(IS_ERR(dir))+ret=PTR_ERR(dir);+else{+constchar*procname=(*pentry)->procname;+head=NULL;+entry=find_entry(&head,dir,procname,strlen(procname));+ret=-ENOENT;+if(entry&&use_table(head)){+unuse_table(*phead);+*phead=head;+*pentry=entry;+ret=0;+}+}++spin_unlock(&sysctl_lock);+returnret;+}+staticintsysctl_check_table_dups(constchar*path,structctl_table*old,structctl_table*table){
@@ -932,6 +973,115 @@ static int sysctl_check_table(const char *path, struct ctl_table *table)returnerr;}+staticstructctl_table_header*new_links(structctl_dir*dir,structctl_table*table,+structctl_table_root*link_root)+{+structctl_table*link_table,*entry,*link;+structctl_table_header*links;+char*link_name;+intnr_entries,name_bytes;++name_bytes=0;+nr_entries=0;+for(entry=table;entry->procname;entry++){+nr_entries++;+name_bytes+=strlen(entry->procname)+1;+}++links=kzalloc(sizeof(structctl_table_header)++sizeof(structctl_table)*(nr_entries+1)++name_bytes,+GFP_KERNEL);++if(!links)+returnNULL;++link_table=(structctl_table*)(links+1);+link_name=(char*)&link_table[nr_entries+1];++for(link=link_table,entry=table;entry->procname;link++,entry++){+intlen=strlen(entry->procname)+1;+memcpy(link_name,entry->procname,len);+link->procname=link_name;+link->mode=S_IFLNK|S_IRWXUGO;+link->data=link_root;+link_name+=len;+}+init_header(links,dir->header.root,dir->header.set,link_table);+links->nreg=nr_entries;++returnlinks;+}++staticboolget_links(structctl_dir*dir,+structctl_table*table,structctl_table_root*link_root)+{+structctl_table_header*head;+structctl_table*entry,*link;++/* Are there links available for every entry in table? */+for(entry=table;entry->procname;entry++){+constchar*procname=entry->procname;+link=find_entry(&head,dir,procname,strlen(procname));+if(!link)+returnfalse;+if(S_ISDIR(link->mode)&&S_ISDIR(entry->mode))+continue;+if(S_ISLNK(link->mode)&&(link->data==link_root))+continue;+returnfalse;+}++/* The checks passed. Increase the registration count on the links */+for(entry=table;entry->procname;entry++){+constchar*procname=entry->procname;+link=find_entry(&head,dir,procname,strlen(procname));+head->nreg++;+}+returntrue;+}++staticintinsert_links(structctl_table_header*head)+{+structctl_table_set*root_set=&sysctl_table_root.default_set;+structctl_dir*core_parent=NULL;+structctl_table_header*links;+interr;++if(head->set==root_set)+return0;++core_parent=xlate_dir(root_set,head->parent);+if(IS_ERR(core_parent))+return0;++if(get_links(core_parent,head->ctl_table,head->root))+return0;++core_parent->header.nreg++;+spin_unlock(&sysctl_lock);++links=new_links(core_parent,head->ctl_table,head->root);++spin_lock(&sysctl_lock);+err=-ENOMEM;+if(!links)+gotoout;++err=0;+if(get_links(core_parent,head->ctl_table,head->root)){+kfree(links);+gotoout;+}++err=insert_header(core_parent,links);+if(err)+kfree(links);+out:+drop_sysctl_table(&core_parent->header);+returnerr;+}+/***__register_sysctl_table-registeraleafsysctltable*@root:Listofsysctlheaderstoregisteron
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:50:06
An nsproxy argument here has always been awkard and now the nsproxy argument
is completely unnecessary so remove it, replacing it with the set we want
the registered tables to show up in.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 30 ++++++++++++------------------
include/linux/sysctl.h | 4 ++--
net/sysctl_net.c | 10 +++-------
3 files changed, 17 insertions(+), 27 deletions(-)
@@ -1223,8 +1220,7 @@ static int count_subheaders(struct ctl_table *table)}staticintregister_leaf_sysctl_tables(constchar*path,char*pos,-structctl_table_header***subheader,-structctl_table_root*root,structnsproxy*namespaces,+structctl_table_header***subheader,structctl_table_set*set,structctl_table*table){structctl_table*ctl_table_arg=NULL;
@@ -1261,7 +1257,7 @@ static int register_leaf_sysctl_tables(const char *path, char *pos,/* Register everything except a directory full of subdirectories */if(nr_files||!nr_dirs){structctl_table_header*header;-header=__register_sysctl_table(root,namespaces,path,files);+header=__register_sysctl_table(set,path,files);if(!header){kfree(ctl_table_arg);gotoout;
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:50:07
Simplify the callers of insert_header by removing explicit calls to check
for duplicates and instead have insert_header do the work.
This makes the code slightly more maintainable by enabling changes to
data structures where the insertion of new entries without duplicate
suppression is not possible.
There is not always a convenient path string where insert_header
is called so modify sysctl_check_dups to use sysctl_print_dir
when printing the full path when a duplicate is discovered.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 20 +++++++++++---------
1 files changed, 11 insertions(+), 9 deletions(-)
@@ -1047,10 +1047,10 @@ struct ctl_table_headerstructctl_dir{/* Header must be at the start of ctl_dir */structctl_table_headerheader;+structlist_headlist;};structctl_table_set{-structlist_headlist;int(*is_seen)(structctl_table_set*);structctl_dirdir;};
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:50:09
One of the most important jobs of sysctl is to export network stack
tunables. Several of those tunables are per network device. In
several instances people are running with 1000+ network devices in
there network stacks, which makes the simple per directory linked list
in sysctl a scaling bottleneck. Replace O(N^2) sysctl insertion and
lookup times with O(NlogN) by using an rbtree to index the sysctl
directories.
Benchmark before:
make-dummies 0 999 -> 0.32s
rmmod dummy -> 0.12s
make-dummies 0 9999 -> 1m17s
rmmod dummy -> 17s
Benchmark after:
make-dummies 0 999 -> 0.074s
rmmod dummy -> 0.070s
make-dummies 0 9999 -> 3.4s
rmmod dummy -> 0.44s
Benchmark after (without dev_snmp6):
make-dummies 0 9999 -> 0.75s
rmmod dummy -> 0.44s
make-dummies 0 99999 -> 11s
rmmod dummy -> 4.3s
At 10,000 dummy devices the bottleneck becomes the time to add and
remove the files under /proc/sys/net/dev_snmp6. I have commented
out the code that adds and removes files under /proc/sys/net/dev_snmp6
and taken measurments of creating and destroying 100,000 dummies to
verify the sysctl continues to scale.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 224 +++++++++++++++++++++++++++++-------------------
include/linux/sysctl.h | 10 ++-
2 files changed, 142 insertions(+), 92 deletions(-)
@@ -932,6 +932,7 @@ enum#include<linux/list.h>#include<linux/rcupdate.h>#include<linux/wait.h>+#include<linux/rbtree.h>/* For the /proc/sys support */structctl_table;
@@ -1023,6 +1024,11 @@ struct ctl_tablevoid*extra2;};+structctl_node{+structrb_nodenode;+structctl_table_header*header;+};+/* struct ctl_table_header is used to maintain dynamic lists ofstructctl_tabletrees.*/structctl_table_header
@@ -1042,12 +1047,13 @@ struct ctl_table_headerstructctl_table_root*root;structctl_table_set*set;structctl_dir*parent;+structctl_node*node;};structctl_dir{/* Header must be at the start of ctl_dir */structctl_table_headerheader;-structlist_headlist;+structrb_rootroot;};structctl_table_set{
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:50:10
The plan is to convert all callers of register_sysctl_table
and register_sysctl_paths to register_sysctl. The interface
to register_sysctl is enough nicer this should make the callers
a bit more readable. Additionally after the conversion the
230 lines of backwards compatibility can be removed.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 17 +++++++++++++++++
include/linux/sysctl.h | 1 +
2 files changed, 18 insertions(+), 0 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:51:38
- In sysctl.h move functions only available if CONFIG_SYSCL
is defined inside of #ifdef CONFIG_SYSCTL
- Move the stub function definitions for !CONFIG_SYSCTL
into sysctl.h and make them static inlines.
Signed-off-by: Eric W. Biederman <redacted>
---
include/linux/sysctl.h | 95 +++++++++++++++++++++++++++++++-----------------
kernel/sysctl.c | 26 -------------
2 files changed, 62 insertions(+), 59 deletions(-)
@@ -1047,15 +1023,6 @@ struct ctl_tablevoid*extra2;};-structctl_table_root{-structlist_headroot_list;-structctl_table_setdefault_set;-structctl_table_set*(*lookup)(structctl_table_root*root,-structnsproxy*namespaces);-int(*permissions)(structctl_table_root*root,-structnsproxy*namespaces,structctl_table*table);-};-/* struct ctl_table_header is used to maintain dynamic lists ofstructctl_tabletrees.*/structctl_table_header
@@ -1078,11 +1045,45 @@ struct ctl_table_headerstructctl_table_header*parent;};+structctl_table_set{+structlist_headlist;+structctl_table_set*parent;+int(*is_seen)(structctl_table_set*);+};++structctl_table_root{+structlist_headroot_list;+structctl_table_setdefault_set;+structctl_table_set*(*lookup)(structctl_table_root*root,+structnsproxy*namespaces);+int(*permissions)(structctl_table_root*root,+structnsproxy*namespaces,structctl_table*table);+};+/* struct ctl_path describes where in the hierarchy a table is added */structctl_path{constchar*procname;};+#ifdef CONFIG_SYSCTL++voidproc_sys_poll_notify(structctl_table_poll*poll);++externvoidsetup_sysctl_set(structctl_table_set*p,+structctl_table_set*parent,+int(*is_seen)(structctl_table_set*));++externvoidsysctl_head_get(structctl_table_header*);+externvoidsysctl_head_put(structctl_table_header*);+externintsysctl_is_seen(structctl_table_header*);+externstructctl_table_header*sysctl_head_grab(structctl_table_header*);+externstructctl_table_header*sysctl_head_next(structctl_table_header*prev);+externstructctl_table_header*__sysctl_head_next(structnsproxy*namespaces,+structctl_table_header*prev);+externvoidsysctl_head_finish(structctl_table_header*prev);+externintsysctl_perm(structctl_table_root*root,+structctl_table*table,intop);+voidregister_sysctl_root(structctl_table_root*root);structctl_table_header*__register_sysctl_paths(structctl_table_root*root,structnsproxy*namespaces,
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:51:40
Move the core sysctl code from kernel/sysctl.c and kernel/sysctl_check.c
into fs/proc/proc_sysctl.c.
Currently sysctl maintenance is hampered by the sysctl implementation
being split across 3 files with artificial layering between them.
Consolidate the entire sysctl implementation into 1 file so that
it is easier to see what is going on and hopefully allowing for
simpler maintenance.
For functions that are now only used in fs/proc/proc_sysctl.c remove
their declarations from sysctl.h and make them static in fs/proc/proc_sysctl.c
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/internal.h | 3 +
fs/proc/proc_sysctl.c | 622 ++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/sysctl.h | 16 --
kernel/Makefile | 1 -
kernel/sysctl.c | 464 ------------------------------------
kernel/sysctl_check.c | 160 -------------
6 files changed, 625 insertions(+), 641 deletions(-)
delete mode 100644 kernel/sysctl_check.c
@@ -24,6 +25,209 @@ void proc_sys_poll_notify(struct ctl_table_poll *poll)wake_up_interruptible(&poll->wait);}+staticstructctl_tableroot_table[1];+staticstructctl_table_rootsysctl_table_root;+staticstructctl_table_headerroot_table_header={+{{.count=1,+.ctl_table=root_table,+.ctl_entry=LIST_HEAD_INIT(sysctl_table_root.default_set.list),}},+.root=&sysctl_table_root,+.set=&sysctl_table_root.default_set,+};+staticstructctl_table_rootsysctl_table_root={+.root_list=LIST_HEAD_INIT(sysctl_table_root.root_list),+.default_set.list=LIST_HEAD_INIT(root_table_header.ctl_entry),+};++staticDEFINE_SPINLOCK(sysctl_lock);++/* called under sysctl_lock */+staticintuse_table(structctl_table_header*p)+{+if(unlikely(p->unregistering))+return0;+p->used++;+return1;+}++/* called under sysctl_lock */+staticvoidunuse_table(structctl_table_header*p)+{+if(!--p->used)+if(unlikely(p->unregistering))+complete(p->unregistering);+}++/* called under sysctl_lock, will reacquire if has to wait */+staticvoidstart_unregistering(structctl_table_header*p)+{+/*+*ifp->usedis0,nobodywillevertouchthatentryagain;+*we'lleliminateallpathstoitbeforedroppingsysctl_lock+*/+if(unlikely(p->used)){+structcompletionwait;+init_completion(&wait);+p->unregistering=&wait;+spin_unlock(&sysctl_lock);+wait_for_completion(&wait);+spin_lock(&sysctl_lock);+}else{+/* anything non-NULL; we'll never dereference it */+p->unregistering=ERR_PTR(-EINVAL);+}+/*+*donotremovefromthelistuntilnobodyholdsit;walkingthe+*listindo_sysctl()reliesonthat.+*/+list_del_init(&p->ctl_entry);+}++staticvoidsysctl_head_get(structctl_table_header*head)+{+spin_lock(&sysctl_lock);+head->count++;+spin_unlock(&sysctl_lock);+}++voidsysctl_head_put(structctl_table_header*head)+{+spin_lock(&sysctl_lock);+if(!--head->count)+kfree_rcu(head,rcu);+spin_unlock(&sysctl_lock);+}++staticstructctl_table_header*sysctl_head_grab(structctl_table_header*head)+{+if(!head)+BUG();+spin_lock(&sysctl_lock);+if(!use_table(head))+head=ERR_PTR(-ENOENT);+spin_unlock(&sysctl_lock);+returnhead;+}++staticvoidsysctl_head_finish(structctl_table_header*head)+{+if(!head)+return;+spin_lock(&sysctl_lock);+unuse_table(head);+spin_unlock(&sysctl_lock);+}++staticstructctl_table_set*+lookup_header_set(structctl_table_root*root,structnsproxy*namespaces)+{+structctl_table_set*set=&root->default_set;+if(root->lookup)+set=root->lookup(root,namespaces);+returnset;+}++staticstructlist_head*+lookup_header_list(structctl_table_root*root,structnsproxy*namespaces)+{+structctl_table_set*set=lookup_header_set(root,namespaces);+return&set->list;+}++staticstructctl_table_header*__sysctl_head_next(structnsproxy*namespaces,+structctl_table_header*prev)+{+structctl_table_root*root;+structlist_head*header_list;+structctl_table_header*head;+structlist_head*tmp;++spin_lock(&sysctl_lock);+if(prev){+head=prev;+tmp=&prev->ctl_entry;+unuse_table(prev);+gotonext;+}+tmp=&root_table_header.ctl_entry;+for(;;){+head=list_entry(tmp,structctl_table_header,ctl_entry);++if(!use_table(head))+gotonext;+spin_unlock(&sysctl_lock);+returnhead;+next:+root=head->root;+tmp=tmp->next;+header_list=lookup_header_list(root,namespaces);+if(tmp!=header_list)+continue;++do{+root=list_entry(root->root_list.next,+structctl_table_root,root_list);+if(root==&sysctl_table_root)+gotoout;+header_list=lookup_header_list(root,namespaces);+}while(list_empty(header_list));+tmp=header_list->next;+}+out:+spin_unlock(&sysctl_lock);+returnNULL;+}++staticstructctl_table_header*sysctl_head_next(structctl_table_header*prev)+{+return__sysctl_head_next(current->nsproxy,prev);+}++voidregister_sysctl_root(structctl_table_root*root)+{+spin_lock(&sysctl_lock);+list_add_tail(&root->root_list,&sysctl_table_root.root_list);+spin_unlock(&sysctl_lock);+}++/*+*sysctl_permdoesNOTgrantthesuperuserallrightsautomatically,because+*somesysctlvariablesarereadonlyeventoroot.+*/++staticinttest_perm(intmode,intop)+{+if(!current_euid())+mode>>=6;+elseif(in_egroup_p(0))+mode>>=3;+if((op&~mode&(MAY_READ|MAY_WRITE|MAY_EXEC))==0)+return0;+return-EACCES;+}++staticintsysctl_perm(structctl_table_root*root,structctl_table*table,intop)+{+intmode;++if(root->permissions)+mode=root->permissions(root,current->nsproxy,table);+else+mode=table->mode;++returntest_perm(mode,op);+}++staticvoidsysctl_set_parent(structctl_table*parent,structctl_table*table)+{+for(;table->procname;table++){+table->parent=parent;+if(table->child)+sysctl_set_parent(table,table->child);+}+}++staticstructinode*proc_sys_make_inode(structsuper_block*sb,structctl_table_header*head,structctl_table*table){
@@ -435,6 +639,21 @@ static int proc_sys_delete(const struct dentry *dentry)return!!PROC_I(dentry->d_inode)->sysctl->unregistering;}+staticintsysctl_is_seen(structctl_table_header*p)+{+structctl_table_set*set=p->set;+intres;+spin_lock(&sysctl_lock);+if(p->unregistering)+res=0;+elseif(!set->is_seen)+res=1;+else+res=set->is_seen(set);+spin_unlock(&sysctl_lock);+returnres;+}+staticintproc_sys_compare(conststructdentry*parent,conststructinode*pinode,conststructdentry*dentry,conststructinode*inode,
@@ -460,6 +679,409 @@ static const struct dentry_operations proc_sys_dentry_operations = {.d_compare=proc_sys_compare,};+staticstructctl_table*is_branch_in(structctl_table*branch,+structctl_table*table)+{+structctl_table*p;+constchar*s=branch->procname;++/* branch should have named subdirectory as its first element */+if(!s||!branch->child)+returnNULL;++/* ... and nothing else */+if(branch[1].procname)+returnNULL;++/* table should contain subdirectory with the same name */+for(p=table;p->procname;p++){+if(!p->child)+continue;+if(p->procname&&strcmp(p->procname,s)==0)+returnp;+}+returnNULL;+}++/* see if attaching q to p would be an improvement */+staticvoidtry_attach(structctl_table_header*p,structctl_table_header*q)+{+structctl_table*to=p->ctl_table,*by=q->ctl_table;+structctl_table*next;+intis_better=0;+intnot_in_parent=!p->attached_by;++while((next=is_branch_in(by,to))!=NULL){+if(by==q->attached_by)+is_better=1;+if(to==p->attached_by)+not_in_parent=1;+by=by->child;+to=next->child;+}++if(is_better&¬_in_parent){+q->attached_by=by;+q->attached_to=to;+q->parent=p;+}+}++#ifdef CONFIG_SYSCTL_SYSCALL_CHECK+staticintsysctl_depth(structctl_table*table)+{+structctl_table*tmp;+intdepth;++depth=0;+for(tmp=table;tmp->parent;tmp=tmp->parent)+depth++;++returndepth;+}++staticstructctl_table*sysctl_parent(structctl_table*table,intn)+{+inti;++for(i=0;table&&i<n;i++)+table=table->parent;++returntable;+}+++staticvoidsysctl_print_path(structctl_table*table)+{+structctl_table*tmp;+intdepth,i;+depth=sysctl_depth(table);+if(table->procname){+for(i=depth;i>=0;i--){+tmp=sysctl_parent(table,i);+printk("/%s",tmp->procname?tmp->procname:"");+}+}+printk(" ");+}++staticstructctl_table*sysctl_check_lookup(structnsproxy*namespaces,+structctl_table*table)+{+structctl_table_header*head;+structctl_table*ref,*test;+intdepth,cur_depth;++depth=sysctl_depth(table);++for(head=__sysctl_head_next(namespaces,NULL);head;+head=__sysctl_head_next(namespaces,head)){+cur_depth=depth;+ref=head->ctl_table;+repeat:+test=sysctl_parent(table,cur_depth);+for(;ref->procname;ref++){+intmatch=0;+if(cur_depth&&!ref->child)+continue;++if(test->procname&&ref->procname&&+(strcmp(test->procname,ref->procname)==0))+match++;++if(match){+if(cur_depth!=0){+cur_depth--;+ref=ref->child;+gotorepeat;+}+gotoout;+}+}+}+ref=NULL;+out:+sysctl_head_finish(head);+returnref;+}++staticvoidset_fail(constchar**fail,structctl_table*table,constchar*str)+{+if(*fail){+printk(KERN_ERR"sysctl table check failed: ");+sysctl_print_path(table);+printk(" %s\n",*fail);+dump_stack();+}+*fail=str;+}++staticvoidsysctl_check_leaf(structnsproxy*namespaces,+structctl_table*table,constchar**fail)+{+structctl_table*ref;++ref=sysctl_check_lookup(namespaces,table);+if(ref&&(ref!=table))+set_fail(fail,table,"Sysctl already exists");+}++staticintsysctl_check_table(structnsproxy*namespaces,structctl_table*table)+{+interror=0;+for(;table->procname;table++){+constchar*fail=NULL;++if(table->parent){+if(!table->parent->procname)+set_fail(&fail,table,"Parent without procname");+}+if(table->child){+if(table->data)+set_fail(&fail,table,"Directory with data?");+if(table->maxlen)+set_fail(&fail,table,"Directory with maxlen?");+if((table->mode&(S_IRUGO|S_IXUGO))!=table->mode)+set_fail(&fail,table,"Writable sysctl directory");+if(table->proc_handler)+set_fail(&fail,table,"Directory with proc_handler");+if(table->extra1)+set_fail(&fail,table,"Directory with extra1");+if(table->extra2)+set_fail(&fail,table,"Directory with extra2");+}else{+if((table->proc_handler==proc_dostring)||+(table->proc_handler==proc_dointvec)||+(table->proc_handler==proc_dointvec_minmax)||+(table->proc_handler==proc_dointvec_jiffies)||+(table->proc_handler==proc_dointvec_userhz_jiffies)||+(table->proc_handler==proc_dointvec_ms_jiffies)||+(table->proc_handler==proc_doulongvec_minmax)||+(table->proc_handler==proc_doulongvec_ms_jiffies_minmax)){+if(!table->data)+set_fail(&fail,table,"No data");+if(!table->maxlen)+set_fail(&fail,table,"No maxlen");+}+#ifdef CONFIG_PROC_SYSCTL+if(!table->proc_handler)+set_fail(&fail,table,"No proc_handler");+#endif+sysctl_check_leaf(namespaces,table,&fail);+}+if(table->mode>0777)+set_fail(&fail,table,"bogus .mode");+if(fail){+set_fail(&fail,table,NULL);+error=-EINVAL;+}+if(table->child)+error|=sysctl_check_table(namespaces,table->child);+}+returnerror;+}+#endif /* CONFIG_SYSCTL_SYSCALL_CHECK */++/**+*__register_sysctl_paths-registerasysctlhierarchy+*@root:Listofsysctlheaderstoregisteron+*@namespaces:Datatocomputewhichlistsofsysctlentriesarevisible+*@path:Thepathtothedirectorythesysctltableisin.+*@table:thetop-leveltablestructure+*+*Registerasysctltablehierarchy.@tableshouldbeafilledinctl_table+*array.Acompletely0filledentryterminatesthetable.+*+*Themembersofthe&structctl_tablestructureareusedasfollows:+*+*procname-thenameofthesysctlfileunder/proc/sys.Setto%NULLtonot+*enterasysctlfile+*+*data-apointertodataforusebyproc_handler+*+*maxlen-themaximumsizeinbytesofthedata+*+*mode-thefilepermissionsforthe/proc/sysfile,andforsysctl(2)+*+*child-apointertothechildsysctltableifthisentryisadirectory,or+*%NULL.+*+*proc_handler-thetexthandlerroutine(describedbelow)+*+*de-forinternalusebythesysctlroutines+*+*extra1,extra2-extrapointersusablebytheprochandlerroutines+*+*Leafnodesinthesysctltreewillberepresentedbyasinglefile+*under/proc;non-leafnodeswillberepresentedbydirectories.+*+*sysctl(2)canautomaticallymanagereadandwriterequeststhrough+*thesysctltable.Thedataandmaxlenfieldsofthectl_table+*structenableminimalvalidationofthevaluesbeingwrittentobe+*performed,andthemodefieldallowsminimalauthentication.+*+*Theremustbeaproc_handlerroutineforanyterminalnodes+*mirroredunder/proc/sys(non-terminalsarehandledbyabuilt-in+*directoryhandler).Severaldefaulthandlersareavailableto+*covercommoncases-+*+*proc_dostring(),proc_dointvec(),proc_dointvec_jiffies(),+*proc_dointvec_userhz_jiffies(),proc_dointvec_minmax(),+*proc_doulongvec_ms_jiffies_minmax(),proc_doulongvec_minmax()+*+*Itisthehandler'sjobtoreadtheinputbufferfromusermemory+*andprocessit.Thehandlershouldreturn0onsuccess.+*+*Thisroutinereturns%NULLonafailuretoregister,andapointer+*tothetableheaderonsuccess.+*/+structctl_table_header*__register_sysctl_paths(+structctl_table_root*root,+structnsproxy*namespaces,+conststructctl_path*path,structctl_table*table)+{+structctl_table_header*header;+structctl_table*new,**prevp;+unsignedintn,npath;+structctl_table_set*set;++/* Count the path components */+for(npath=0;path[npath].procname;++npath)+;++/*+*Foreachpathcomponent,allocatea2-elementctl_tablearray.+*Thefirstarrayelementwillbefilledwiththesysctlentry+*forthis,thesecondwillbethesentinel(procname==0).+*+*Weallocateeverythinginonegosothatwedon'thaveto+*worryaboutfreeingadditionalmemoryinunregister_sysctl_table.+*/+header=kzalloc(sizeof(structctl_table_header)++(2*npath*sizeof(structctl_table)),GFP_KERNEL);+if(!header)+returnNULL;++new=(structctl_table*)(header+1);++/* Now connect the dots */+prevp=&header->ctl_table;+for(n=0;n<npath;++n,++path){+/* Copy the procname */+new->procname=path->procname;+new->mode=0555;++*prevp=new;+prevp=&new->child;++new+=2;+}+*prevp=table;+header->ctl_table_arg=table;++INIT_LIST_HEAD(&header->ctl_entry);+header->used=0;+header->unregistering=NULL;+header->root=root;+sysctl_set_parent(NULL,header->ctl_table);+header->count=1;+#ifdef CONFIG_SYSCTL_SYSCALL_CHECK+if(sysctl_check_table(namespaces,header->ctl_table)){+kfree(header);+returnNULL;+}+#endif+spin_lock(&sysctl_lock);+header->set=lookup_header_set(root,namespaces);+header->attached_by=header->ctl_table;+header->attached_to=root_table;+header->parent=&root_table_header;+for(set=header->set;set;set=set->parent){+structctl_table_header*p;+list_for_each_entry(p,&set->list,ctl_entry){+if(p->unregistering)+continue;+try_attach(p,header);+}+}+header->parent->count++;+list_add_tail(&header->ctl_entry,&header->set->list);+spin_unlock(&sysctl_lock);++returnheader;+}++/**+*register_sysctl_table_path-registerasysctltablehierarchy+*@path:Thepathtothedirectorythesysctltableisin.+*@table:thetop-leveltablestructure+*+*Registerasysctltablehierarchy.@tableshouldbeafilledinctl_table+*array.Acompletely0filledentryterminatesthetable.+*+*See__register_sysctl_pathsformoredetails.+*/+structctl_table_header*register_sysctl_paths(conststructctl_path*path,+structctl_table*table)+{+return__register_sysctl_paths(&sysctl_table_root,current->nsproxy,+path,table);+}+EXPORT_SYMBOL(register_sysctl_paths);++/**+*register_sysctl_table-registerasysctltablehierarchy+*@table:thetop-leveltablestructure+*+*Registerasysctltablehierarchy.@tableshouldbeafilledinctl_table+*array.Acompletely0filledentryterminatesthetable.+*+*Seeregister_sysctl_pathsformoredetails.+*/+structctl_table_header*register_sysctl_table(structctl_table*table)+{+staticconststructctl_pathnull_path[]={{}};++returnregister_sysctl_paths(null_path,table);+}+EXPORT_SYMBOL(register_sysctl_table);++/**+*unregister_sysctl_table-unregisterasysctltablehierarchy+*@header:theheaderreturnedfromregister_sysctl_table+*+*Unregistersthesysctltableandallchildren.procentriesmaynot+*actuallyberemoveduntiltheyarenolongerusedbyanyone.+*/+voidunregister_sysctl_table(structctl_table_header*header)+{+might_sleep();++if(header==NULL)+return;++spin_lock(&sysctl_lock);+start_unregistering(header);+if(!--header->parent->count){+WARN_ON(1);+kfree_rcu(header->parent,rcu);+}+if(!--header->count)+kfree_rcu(header,rcu);+spin_unlock(&sysctl_lock);+}+EXPORT_SYMBOL(unregister_sysctl_table);++voidsetup_sysctl_set(structctl_table_set*p,+structctl_table_set*parent,+int(*is_seen)(structctl_table_set*))+{+INIT_LIST_HEAD(&p->list);+p->parent=parent?parent:&sysctl_table_root.default_set;+p->is_seen=is_seen;+}++int__initproc_sys_init(void){structproc_dir_entry*proc_sys_root;
@@ -192,20 +192,6 @@ static int sysrq_sysctl_handler(ctl_table *table, int write,#endif-staticstructctl_tableroot_table[1];-staticstructctl_table_rootsysctl_table_root;-staticstructctl_table_headerroot_table_header={-{{.count=1,-.ctl_table=root_table,-.ctl_entry=LIST_HEAD_INIT(sysctl_table_root.default_set.list),}},-.root=&sysctl_table_root,-.set=&sysctl_table_root.default_set,-};-staticstructctl_table_rootsysctl_table_root={-.root_list=LIST_HEAD_INIT(sysctl_table_root.root_list),-.default_set.list=LIST_HEAD_INIT(root_table_header.ctl_entry),-};-staticstructctl_tablekern_table[];staticstructctl_tablevm_table[];staticstructctl_tablefs_table[];
@@ -1559,459 +1545,12 @@ static struct ctl_table dev_table[] = {{}};-staticDEFINE_SPINLOCK(sysctl_lock);--/* called under sysctl_lock */-staticintuse_table(structctl_table_header*p)-{-if(unlikely(p->unregistering))-return0;-p->used++;-return1;-}--/* called under sysctl_lock */-staticvoidunuse_table(structctl_table_header*p)-{-if(!--p->used)-if(unlikely(p->unregistering))-complete(p->unregistering);-}--/* called under sysctl_lock, will reacquire if has to wait */-staticvoidstart_unregistering(structctl_table_header*p)-{-/*-*ifp->usedis0,nobodywillevertouchthatentryagain;-*we'lleliminateallpathstoitbeforedroppingsysctl_lock-*/-if(unlikely(p->used)){-structcompletionwait;-init_completion(&wait);-p->unregistering=&wait;-spin_unlock(&sysctl_lock);-wait_for_completion(&wait);-spin_lock(&sysctl_lock);-}else{-/* anything non-NULL; we'll never dereference it */-p->unregistering=ERR_PTR(-EINVAL);-}-/*-*donotremovefromthelistuntilnobodyholdsit;walkingthe-*listindo_sysctl()reliesonthat.-*/-list_del_init(&p->ctl_entry);-}--voidsysctl_head_get(structctl_table_header*head)-{-spin_lock(&sysctl_lock);-head->count++;-spin_unlock(&sysctl_lock);-}--voidsysctl_head_put(structctl_table_header*head)-{-spin_lock(&sysctl_lock);-if(!--head->count)-kfree_rcu(head,rcu);-spin_unlock(&sysctl_lock);-}--structctl_table_header*sysctl_head_grab(structctl_table_header*head)-{-if(!head)-BUG();-spin_lock(&sysctl_lock);-if(!use_table(head))-head=ERR_PTR(-ENOENT);-spin_unlock(&sysctl_lock);-returnhead;-}--voidsysctl_head_finish(structctl_table_header*head)-{-if(!head)-return;-spin_lock(&sysctl_lock);-unuse_table(head);-spin_unlock(&sysctl_lock);-}--staticstructctl_table_set*-lookup_header_set(structctl_table_root*root,structnsproxy*namespaces)-{-structctl_table_set*set=&root->default_set;-if(root->lookup)-set=root->lookup(root,namespaces);-returnset;-}--staticstructlist_head*-lookup_header_list(structctl_table_root*root,structnsproxy*namespaces)-{-structctl_table_set*set=lookup_header_set(root,namespaces);-return&set->list;-}--structctl_table_header*__sysctl_head_next(structnsproxy*namespaces,-structctl_table_header*prev)-{-structctl_table_root*root;-structlist_head*header_list;-structctl_table_header*head;-structlist_head*tmp;--spin_lock(&sysctl_lock);-if(prev){-head=prev;-tmp=&prev->ctl_entry;-unuse_table(prev);-gotonext;-}-tmp=&root_table_header.ctl_entry;-for(;;){-head=list_entry(tmp,structctl_table_header,ctl_entry);--if(!use_table(head))-gotonext;-spin_unlock(&sysctl_lock);-returnhead;-next:-root=head->root;-tmp=tmp->next;-header_list=lookup_header_list(root,namespaces);-if(tmp!=header_list)-continue;--do{-root=list_entry(root->root_list.next,-structctl_table_root,root_list);-if(root==&sysctl_table_root)-gotoout;-header_list=lookup_header_list(root,namespaces);-}while(list_empty(header_list));-tmp=header_list->next;-}-out:-spin_unlock(&sysctl_lock);-returnNULL;-}--structctl_table_header*sysctl_head_next(structctl_table_header*prev)-{-return__sysctl_head_next(current->nsproxy,prev);-}--voidregister_sysctl_root(structctl_table_root*root)-{-spin_lock(&sysctl_lock);-list_add_tail(&root->root_list,&sysctl_table_root.root_list);-spin_unlock(&sysctl_lock);-}--/*-*sysctl_permdoesNOTgrantthesuperuserallrightsautomatically,because-*somesysctlvariablesarereadonlyeventoroot.-*/--staticinttest_perm(intmode,intop)-{-if(!current_euid())-mode>>=6;-elseif(in_egroup_p(0))-mode>>=3;-if((op&~mode&(MAY_READ|MAY_WRITE|MAY_EXEC))==0)-return0;-return-EACCES;-}--intsysctl_perm(structctl_table_root*root,structctl_table*table,intop)-{-intmode;--if(root->permissions)-mode=root->permissions(root,current->nsproxy,table);-else-mode=table->mode;--returntest_perm(mode,op);-}--staticvoidsysctl_set_parent(structctl_table*parent,structctl_table*table)-{-for(;table->procname;table++){-table->parent=parent;-if(table->child)-sysctl_set_parent(table,table->child);-}-}-int__initsysctl_init(void){register_sysctl_table(sysctl_base_table);return0;}-staticstructctl_table*is_branch_in(structctl_table*branch,-structctl_table*table)-{-structctl_table*p;-constchar*s=branch->procname;--/* branch should have named subdirectory as its first element */-if(!s||!branch->child)-returnNULL;--/* ... and nothing else */-if(branch[1].procname)-returnNULL;--/* table should contain subdirectory with the same name */-for(p=table;p->procname;p++){-if(!p->child)-continue;-if(p->procname&&strcmp(p->procname,s)==0)-returnp;-}-returnNULL;-}--/* see if attaching q to p would be an improvement */-staticvoidtry_attach(structctl_table_header*p,structctl_table_header*q)-{-structctl_table*to=p->ctl_table,*by=q->ctl_table;-structctl_table*next;-intis_better=0;-intnot_in_parent=!p->attached_by;--while((next=is_branch_in(by,to))!=NULL){-if(by==q->attached_by)-is_better=1;-if(to==p->attached_by)-not_in_parent=1;-by=by->child;-to=next->child;-}--if(is_better&¬_in_parent){-q->attached_by=by;-q->attached_to=to;-q->parent=p;-}-}--/**-*__register_sysctl_paths-registerasysctlhierarchy-*@root:Listofsysctlheaderstoregisteron-*@namespaces:Datatocomputewhichlistsofsysctlentriesarevisible-*@path:Thepathtothedirectorythesysctltableisin.-*@table:thetop-leveltablestructure-*-*Registerasysctltablehierarchy.@tableshouldbeafilledinctl_table-*array.Acompletely0filledentryterminatesthetable.-*-*Themembersofthe&structctl_tablestructureareusedasfollows:-*-*procname-thenameofthesysctlfileunder/proc/sys.Setto%NULLtonot-*enterasysctlfile-*-*data-apointertodataforusebyproc_handler-*-*maxlen-themaximumsizeinbytesofthedata-*-*mode-thefilepermissionsforthe/proc/sysfile,andforsysctl(2)-*-*child-apointertothechildsysctltableifthisentryisadirectory,or-*%NULL.-*-*proc_handler-thetexthandlerroutine(describedbelow)-*-*de-forinternalusebythesysctlroutines-*-*extra1,extra2-extrapointersusablebytheprochandlerroutines-*-*Leafnodesinthesysctltreewillberepresentedbyasinglefile-*under/proc;non-leafnodeswillberepresentedbydirectories.-*-*sysctl(2)canautomaticallymanagereadandwriterequeststhrough-*thesysctltable.Thedataandmaxlenfieldsofthectl_table-*structenableminimalvalidationofthevaluesbeingwrittentobe-*performed,andthemodefieldallowsminimalauthentication.-*-*Theremustbeaproc_handlerroutineforanyterminalnodes-*mirroredunder/proc/sys(non-terminalsarehandledbyabuilt-in-*directoryhandler).Severaldefaulthandlersareavailableto-*covercommoncases--*-*proc_dostring(),proc_dointvec(),proc_dointvec_jiffies(),-*proc_dointvec_userhz_jiffies(),proc_dointvec_minmax(),-*proc_doulongvec_ms_jiffies_minmax(),proc_doulongvec_minmax()-*-*Itisthehandler'sjobtoreadtheinputbufferfromusermemory-*andprocessit.Thehandlershouldreturn0onsuccess.-*-*Thisroutinereturns%NULLonafailuretoregister,andapointer-*tothetableheaderonsuccess.-*/-structctl_table_header*__register_sysctl_paths(-structctl_table_root*root,-structnsproxy*namespaces,-conststructctl_path*path,structctl_table*table)-{-structctl_table_header*header;-structctl_table*new,**prevp;-unsignedintn,npath;-structctl_table_set*set;--/* Count the path components */-for(npath=0;path[npath].procname;++npath)-;--/*-*Foreachpathcomponent,allocatea2-elementctl_tablearray.-*Thefirstarrayelementwillbefilledwiththesysctlentry-*forthis,thesecondwillbethesentinel(procname==0).-*-*Weallocateeverythinginonegosothatwedon'thaveto-*worryaboutfreeingadditionalmemoryinunregister_sysctl_table.-*/-header=kzalloc(sizeof(structctl_table_header)+-(2*npath*sizeof(structctl_table)),GFP_KERNEL);-if(!header)-returnNULL;--new=(structctl_table*)(header+1);--/* Now connect the dots */-prevp=&header->ctl_table;-for(n=0;n<npath;++n,++path){-/* Copy the procname */-new->procname=path->procname;-new->mode=0555;--*prevp=new;-prevp=&new->child;--new+=2;-}-*prevp=table;-header->ctl_table_arg=table;--INIT_LIST_HEAD(&header->ctl_entry);-header->used=0;-header->unregistering=NULL;-header->root=root;-sysctl_set_parent(NULL,header->ctl_table);-header->count=1;-#ifdef CONFIG_SYSCTL_SYSCALL_CHECK-if(sysctl_check_table(namespaces,header->ctl_table)){-kfree(header);-returnNULL;-}-#endif-spin_lock(&sysctl_lock);-header->set=lookup_header_set(root,namespaces);-header->attached_by=header->ctl_table;-header->attached_to=root_table;-header->parent=&root_table_header;-for(set=header->set;set;set=set->parent){-structctl_table_header*p;-list_for_each_entry(p,&set->list,ctl_entry){-if(p->unregistering)-continue;-try_attach(p,header);-}-}-header->parent->count++;-list_add_tail(&header->ctl_entry,&header->set->list);-spin_unlock(&sysctl_lock);--returnheader;-}--/**-*register_sysctl_table_path-registerasysctltablehierarchy-*@path:Thepathtothedirectorythesysctltableisin.-*@table:thetop-leveltablestructure-*-*Registerasysctltablehierarchy.@tableshouldbeafilledinctl_table-*array.Acompletely0filledentryterminatesthetable.-*-*See__register_sysctl_pathsformoredetails.-*/-structctl_table_header*register_sysctl_paths(conststructctl_path*path,-structctl_table*table)-{-return__register_sysctl_paths(&sysctl_table_root,current->nsproxy,-path,table);-}--/**-*register_sysctl_table-registerasysctltablehierarchy-*@table:thetop-leveltablestructure-*-*Registerasysctltablehierarchy.@tableshouldbeafilledinctl_table-*array.Acompletely0filledentryterminatesthetable.-*-*Seeregister_sysctl_pathsformoredetails.-*/-structctl_table_header*register_sysctl_table(structctl_table*table)-{-staticconststructctl_pathnull_path[]={{}};--returnregister_sysctl_paths(null_path,table);-}--/**-*unregister_sysctl_table-unregisterasysctltablehierarchy-*@header:theheaderreturnedfromregister_sysctl_table-*-*Unregistersthesysctltableandallchildren.procentriesmaynot-*actuallyberemoveduntiltheyarenolongerusedbyanyone.-*/-voidunregister_sysctl_table(structctl_table_header*header)-{-might_sleep();--if(header==NULL)-return;--spin_lock(&sysctl_lock);-start_unregistering(header);-if(!--header->parent->count){-WARN_ON(1);-kfree_rcu(header->parent,rcu);-}-if(!--header->count)-kfree_rcu(header,rcu);-spin_unlock(&sysctl_lock);-}--intsysctl_is_seen(structctl_table_header*p)-{-structctl_table_set*set=p->set;-intres;-spin_lock(&sysctl_lock);-if(p->unregistering)-res=0;-elseif(!set->is_seen)-res=1;-else-res=set->is_seen(set);-spin_unlock(&sysctl_lock);-returnres;-}--voidsetup_sysctl_set(structctl_table_set*p,-structctl_table_set*parent,-int(*is_seen)(structctl_table_set*))-{-INIT_LIST_HEAD(&p->list);-p->parent=parent?parent:&sysctl_table_root.default_set;-p->is_seen=is_seen;-}-#endif /* CONFIG_SYSCTL *//*
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:51:41
I goofed when I made sysctl directories have nlink == 0.
nlink == 0 means the directory has been deleted.
nlink == 1 meands a directory does not count subdirectories.
Use the default nlink == 1 for sysctl directories.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 1 -
1 files changed, 0 insertions(+), 1 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:51:42
This adds a small helper retire_sysctl_set to remove the intimate knowledge about
the how a sysctl_set is implemented from net/sysct_net.c
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 4 ++++
include/linux/sysctl.h | 1 +
net/sysctl_net.c | 2 +-
3 files changed, 6 insertions(+), 1 deletions(-)
@@ -82,7 +82,7 @@ static int __net_init sysctl_net_init(struct net *net)staticvoid__net_exitsysctl_net_exit(structnet*net){-WARN_ON(!list_empty(&net->sysctls.list));+retire_sysctl_set(&net->sysctls);}staticstructpernet_operationssysctl_pernet_ops={
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:51:43
In sysctl_net register the two networking roots in the proper order.
In register_sysctl walk the sysctl sets in the reverse order of the
sysctl roots.
Remove parent from ctl_table_set and setup_sysctl_set as it is no
longer needed.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 11 ++++++++---
include/linux/sysctl.h | 3 ---
net/sysctl_net.c | 5 ++---
3 files changed, 10 insertions(+), 9 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:51:45
Make __register_sysctl_table the core sysctl registration operation and
make it take a char * string as path.
Now that binary paths have been banished into the real of backwards
compatibility in kernel/binary_sysctl.c where they can be safely
ignored there is no longer a need to use struct ctl_path to represent
path names when registering ctl_tables.
Start the transition to using normal char * strings to represent
pathnames when registering sysctl tables. Normal strings are easier
to deal with both in the internal sysctl implementation and for
programmers registering sysctl tables.
__register_sysctl_paths is turned into a backwards compatibility wrapper
that converts a ctl_path array into a normal char * string.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 94 ++++++++++++++++++++++++++++++++++++++++++-----
include/linux/sysctl.h | 3 ++
2 files changed, 87 insertions(+), 10 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:51:46
For any component of table passed to __register_sysctl_paths
that actually serves as a path, add that to the cstring path
that is passed to __register_sysctl_table.
The result is that for most calls to __register_sysctl_paths
we only pass a table to __register_sysctl_table that contains
no child directories.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 9 +++++++++
1 files changed, 9 insertions(+), 0 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:51:47
Split the registration of a complex ctl_table array which may have
arbitrary numbers of directories (->child != NULL) and tables of files
into a series of simpler registrations that only register tables of files.
Graphically:
register('dir', { + file-a
+ file-b
+ subdir1
+ file-c
+ subdir2
+ file-d
+ file-e })
is transformed into:
wrapper->subheaders[0] = register('dir', {file1-a, file1-b})
wrapper->subheaders[1] = register('dir/subdir1', {file-c})
wrapper->subheaders[2] = register('dir/subdir2', {file-d, file-e})
return wrapper
This guarantees that __register_sysctl_table will only see a simple
ctl_table array with all entries having (->child == NULL).
Care was taken to pass the original simple ctl_table arrays to
__register_sysctl_table whenever possible.
This change is derived from a similar patch written
by Lucrian Grijincu.
Inspired-by: Lucian Adrian Grijincu [off-list ref]
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 165 ++++++++++++++++++++++++++++++++++++++++++-----
include/linux/sysctl.h | 2 +-
2 files changed, 148 insertions(+), 19 deletions(-)
@@ -1059,6 +1049,100 @@ static char *append_path(const char *path, char *pos, const char *name)returnpos;}+staticintcount_subheaders(structctl_table*table)+{+inthas_files=0;+intnr_subheaders=0;+structctl_table*entry;++/* special case: no directory and empty directory */+if(!table||!table->procname)+return1;++for(entry=table;entry->procname;entry++){+if(entry->child)+nr_subheaders+=count_subheaders(entry->child);+else+has_files=1;+}+returnnr_subheaders+has_files;+}++staticintregister_leaf_sysctl_tables(constchar*path,char*pos,+structctl_table_header***subheader,+structctl_table_root*root,structnsproxy*namespaces,+structctl_table*table)+{+structctl_table*ctl_table_arg=NULL;+structctl_table*entry,*files;+intnr_files=0;+intnr_dirs=0;+interr=-ENOMEM;++for(entry=table;entry->procname;entry++){+if(entry->child)+nr_dirs++;+else+nr_files++;+}++files=table;+/* If there are mixed files and directories we need a new table */+if(nr_dirs&&nr_files){+structctl_table*new;+files=kzalloc(sizeof(structctl_table)*(nr_files+1),+GFP_KERNEL);+if(!files)+gotoout;++ctl_table_arg=files;+for(new=files,entry=table;entry->procname;entry++){+if(entry->child)+continue;+*new=*entry;+new++;+}+}++/* Register everything except a directory full of subdirectories */+if(nr_files||!nr_dirs){+structctl_table_header*header;+header=__register_sysctl_table(root,namespaces,path,files);+if(!header){+kfree(ctl_table_arg);+gotoout;+}++/* Remember if we need to free the file table */+header->ctl_table_arg=ctl_table_arg;+**subheader=header;+(*subheader)++;+}++/* Recurse into the subdirectories. */+for(entry=table;entry->procname;entry++){+char*child_pos;++if(!entry->child)+continue;++err=-ENAMETOOLONG;+child_pos=append_path(path,pos,entry->procname);+if(!child_pos)+gotoout;++err=register_leaf_sysctl_tables(path,child_pos,subheader,+root,namespaces,entry->child);+pos[0]='\0';+if(err)+gotoout;+}+err=0;+out:+/* On failure our caller will unregister all registered subheaders */+returnerr;+}+/***__register_sysctl_paths-registerasysctltablehierarchy*@root:Listofsysctlheaderstoregisteron
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:51:48
- Stop validating subdirectories now that we only register leaf tables
- Cleanup and improve the duplicate filename check.
* Run the duplicate filename check under the sysctl_lock to guarantee
we never add duplicate names.
* Reduce the duplicate filename check to nearly O(M*N) where M is the
number of entries in tthe table we are registering and N is the
number of entries in the directory before we got there.
- Move the duplicate filename check into it's own function and call
it directtly from __register_sysctl_table
- Kill the config option as the sanity checks are now cheap enough
the config option is unnecessary. The original reason for the config
option was because we had a huge table used to verify the proc filename
to binary sysctl mapping. That table has now evolved into the binary_sysctl
translation layer and is no longer part of the sysctl_check code.
- Tighten up the permission checks. Guarnateeing that files only have read
or write permissions.
- Removed redudant check for parents having a procname as now everything has
a procname.
- Generalize the backtrace logic so that we print a backtrace from
any failure of __register_sysctl_table that was not caused by
a memmory allocation failure. The backtrace allows us to track
down who erroneously registered a sysctl table.
Bechmark before (CONFIG_SYSCTL_CHECK=y):
make-dummies 0 999 -> 12s
rmmod dummy -> 0.08s
Bechmark before (CONFIG_SYSCTL_CHECK=n):
make-dummies 0 999 -> 0.7s
rmmod dummy -> 0.06s
make-dummies 0 99999 -> 1m13s
rmmod dummy -> 0.38s
Benchmark after:
make-dummies 0 999 -> 0.65s
rmmod dummy -> 0.055s
make-dummies 0 9999 -> 1m10s
rmmod dummy -> 0.39s
The sysctl sanity checks now impose no measurable cost.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 222 +++++++++++++++++++------------------------------
lib/Kconfig.debug | 8 --
2 files changed, 86 insertions(+), 144 deletions(-)
@@ -726,160 +726,106 @@ static void try_attach(struct ctl_table_header *p, struct ctl_table_header *q)}}-#ifdef CONFIG_SYSCTL_SYSCALL_CHECK-staticintsysctl_depth(structctl_table*table)+staticintsysctl_check_table_dups(constchar*path,structctl_table*old,+structctl_table*table){-structctl_table*tmp;-intdepth;--depth=0;-for(tmp=table;tmp->parent;tmp=tmp->parent)-depth++;+structctl_table*entry,*test;+interror=0;-returndepth;+for(entry=old;entry->procname;entry++){+for(test=table;test->procname;test++){+if(strcmp(entry->procname,test->procname)==0){+printk(KERN_ERR"sysctl duplicate entry: %s/%s\n",+path,test->procname);+error=-EEXIST;+}+}+}+returnerror;}-staticstructctl_table*sysctl_parent(structctl_table*table,intn)+staticintsysctl_check_dups(structnsproxy*namespaces,+structctl_table_header*header,+constchar*path,structctl_table*table){-inti;+structctl_table_root*root;+structctl_table_set*set;+structctl_table_header*dir_head,*head;+structctl_table*dir_table;+interror=0;-for(i=0;table&&i<n;i++)-table=table->parent;+/* No dups if we are the only member of our directory */+if(header->attached_by!=table)+return0;-returntable;-}+dir_head=header->parent;+dir_table=header->attached_to;+error=sysctl_check_table_dups(path,dir_table,table);-staticvoidsysctl_print_path(structctl_table*table)-{-structctl_table*tmp;-intdepth,i;-depth=sysctl_depth(table);-if(table->procname){-for(i=depth;i>=0;i--){-tmp=sysctl_parent(table,i);-printk("/%s",tmp->procname?tmp->procname:"");-}-}-printk(" ");-}+root=&sysctl_table_root;+do{+set=lookup_header_set(root,namespaces);-staticstructctl_table*sysctl_check_lookup(structnsproxy*namespaces,-structctl_table*table)-{-structctl_table_header*head;-structctl_table*ref,*test;-intdepth,cur_depth;--depth=sysctl_depth(table);--for(head=__sysctl_head_next(namespaces,NULL);head;-head=__sysctl_head_next(namespaces,head)){-cur_depth=depth;-ref=head->ctl_table;-repeat:-test=sysctl_parent(table,cur_depth);-for(;ref->procname;ref++){-intmatch=0;-if(cur_depth&&!ref->child)+list_for_each_entry(head,&set->list,ctl_entry){+if(head->unregistering)continue;--if(test->procname&&ref->procname&&-(strcmp(test->procname,ref->procname)==0))-match++;--if(match){-if(cur_depth!=0){-cur_depth--;-ref=ref->child;-gotorepeat;-}-gotoout;-}+if(head->attached_to!=dir_table)+continue;+error=sysctl_check_table_dups(path,head->attached_by,+table);}-}-ref=NULL;-out:-sysctl_head_finish(head);-returnref;+root=list_entry(root->root_list.next,+structctl_table_root,root_list);+}while(root!=&sysctl_table_root);+returnerror;}-staticvoidset_fail(constchar**fail,structctl_table*table,constchar*str)+staticintsysctl_err(constchar*path,structctl_table*table,char*fmt,...){-if(*fail){-printk(KERN_ERR"sysctl table check failed: ");-sysctl_print_path(table);-printk(" %s\n",*fail);-dump_stack();-}-*fail=str;-}+structva_formatvaf;+va_listargs;-staticvoidsysctl_check_leaf(structnsproxy*namespaces,-structctl_table*table,constchar**fail)-{-structctl_table*ref;+va_start(args,fmt);+vaf.fmt=fmt;+vaf.va=&args;++printk(KERN_ERR"sysctl table check failed: %s/%s %pV\n",+path,table->procname,&vaf);-ref=sysctl_check_lookup(namespaces,table);-if(ref&&(ref!=table))-set_fail(fail,table,"Sysctl already exists");+va_end(args);+return-EINVAL;}-staticintsysctl_check_table(structnsproxy*namespaces,structctl_table*table)+staticintsysctl_check_table(constchar*path,structctl_table*table){-interror=0;+interr=0;for(;table->procname;table++){-constchar*fail=NULL;--if(table->parent){-if(!table->parent->procname)-set_fail(&fail,table,"Parent without procname");-}-if(table->child){-if(table->data)-set_fail(&fail,table,"Directory with data?");-if(table->maxlen)-set_fail(&fail,table,"Directory with maxlen?");-if((table->mode&(S_IRUGO|S_IXUGO))!=table->mode)-set_fail(&fail,table,"Writable sysctl directory");-if(table->proc_handler)-set_fail(&fail,table,"Directory with proc_handler");-if(table->extra1)-set_fail(&fail,table,"Directory with extra1");-if(table->extra2)-set_fail(&fail,table,"Directory with extra2");-}else{-if((table->proc_handler==proc_dostring)||-(table->proc_handler==proc_dointvec)||-(table->proc_handler==proc_dointvec_minmax)||-(table->proc_handler==proc_dointvec_jiffies)||-(table->proc_handler==proc_dointvec_userhz_jiffies)||-(table->proc_handler==proc_dointvec_ms_jiffies)||-(table->proc_handler==proc_doulongvec_minmax)||-(table->proc_handler==proc_doulongvec_ms_jiffies_minmax)){-if(!table->data)-set_fail(&fail,table,"No data");-if(!table->maxlen)-set_fail(&fail,table,"No maxlen");-}-#ifdef CONFIG_PROC_SYSCTL-if(!table->proc_handler)-set_fail(&fail,table,"No proc_handler");-#endif-sysctl_check_leaf(namespaces,table,&fail);-}-if(table->mode>0777)-set_fail(&fail,table,"bogus .mode");-if(fail){-set_fail(&fail,table,NULL);-error=-EINVAL;-}if(table->child)-error|=sysctl_check_table(namespaces,table->child);+err=sysctl_err(path,table,"Not a file");++if((table->proc_handler==proc_dostring)||+(table->proc_handler==proc_dointvec)||+(table->proc_handler==proc_dointvec_minmax)||+(table->proc_handler==proc_dointvec_jiffies)||+(table->proc_handler==proc_dointvec_userhz_jiffies)||+(table->proc_handler==proc_dointvec_ms_jiffies)||+(table->proc_handler==proc_doulongvec_minmax)||+(table->proc_handler==proc_doulongvec_ms_jiffies_minmax)){+if(!table->data)+err=sysctl_err(path,table,"No data");+if(!table->maxlen)+err=sysctl_err(path,table,"No maxlen");+}+if(!table->proc_handler)+err=sysctl_err(path,table,"No proc_handler");++if((table->mode&(S_IRUGO|S_IWUGO))!=table->mode)+err=sysctl_err(path,table,"bogus .mode 0%o",+table->mode);}-returnerror;+returnerr;}-#endif /* CONFIG_SYSCTL_SYSCALL_CHECK *//***__register_sysctl_table-registeraleafsysctltable
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:51:50
Instead of relying on sysct_head_next(NULL) to magically
return the right header for the root directory instead
explicitly transform NULL into the root directories header.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 8 ++++----
1 files changed, 4 insertions(+), 4 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:51:52
Factor out a routing to initialize the sysctl_table_header.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 25 +++++++++++++++++--------
1 files changed, 17 insertions(+), 8 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:51:54
Every other directory has a .child member and we look at the .child
for our entries. Do the same for the root_table.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 15 +++++++++++----
1 files changed, 11 insertions(+), 4 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:51:55
Replace the helpers that proc_sys_lookup uses with helpers that work
in terms of an entire sysctl directory. This is worse for sysctl_lock
hold times but it is much better for code clarity and the code cleanups
to come.
find_in_table is no longer needed so it is removed.
find_entry a general helper to find entries in a directory is added.
lookup_entry is a simple wrapper around find_entry that takes the
sysctl_lock increases the use count if an entry is found and drops
the sysctl_lock.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 102 ++++++++++++++++++++++++++++++++++++------------
1 files changed, 76 insertions(+), 26 deletions(-)
@@ -284,21 +359,6 @@ out:returninode;}-staticstructctl_table*find_in_table(structctl_table*p,structqstr*name)-{-for(;p->procname;p++){-if(strlen(p->procname)!=name->len)-continue;--if(memcmp(p->procname,name->name,name->len)!=0)-continue;--/* I have a match */-returnp;-}-returnNULL;-}-staticstructctl_table_header*grab_header(structinode*inode){structctl_table_header*head=PROC_I(inode)->sysctl;
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:51:56
Replace sysctl_head_next with first_entry and next_entry. These new
iterators operate at the level of sysctl table entries and filter
out any sysctl tables that should not be shown.
Utilizing two specialized functions instead of a single function removes
conditionals for handling awkward special cases that only come up
at the beginning of iteration, making the iterators easier to read
and understand.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 98 +++++++++++++++++++++++++++++++------------------
1 files changed, 62 insertions(+), 36 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:51:57
Add a ctl_table_root pointer to ctl_table set so it is easy to
go from a ctl_table_set to a ctl_table_root.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 3 +++
include/linux/sysctl.h | 3 +++
net/sysctl_net.c | 5 ++---
3 files changed, 8 insertions(+), 3 deletions(-)
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:51:58
Simplify the code and the sysctl semantics by autogenerating
sysctl directories when a sysctl table is registered that needs
the directories and autodeleting the directories when there are
no more sysctl tables registered that need them.
Autogenerating directories keeps sysctl tables from depending
on each other, removing all of the arcane register/unregister
ordering constraints and makes it impossible to get the order
wrong when reigsering and unregistering sysctl tables.
Autogenerating directories yields one unique entity that dentries
can point to, retaining the current effective use of the dcache.
Add struct ctl_dir as the type of these new autogenerated
directories.
The attached_by and attached_to fields in ctl_table_header are
removed as they are no longer needed.
The child field in ctl_table is no longer needed by the core of
the sysctl code. ctl_table.child can be removed once all of the
existing users have been updated.
Benchmark before:
make-dummies 0 999 -> 0.7s
rmmod dummy -> 0.07s
make-dummies 0 9999 -> 1m10s
rmmod dummy -> 0.4s
Benchmark after:
make-dummies 0 999 -> 0.44s
rmmod dummy -> 0.065s
make-dummies 0 9999 -> 1m36s
rmmod dummy -> 0.4s
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 342 ++++++++++++++++++++----------------------------
include/linux/sysctl.h | 10 +-
2 files changed, 150 insertions(+), 202 deletions(-)
@@ -779,52 +750,86 @@ static const struct dentry_operations proc_sys_dentry_operations = {.d_compare=proc_sys_compare,};-staticstructctl_table*is_branch_in(structctl_table*branch,-structctl_table*table)+staticstructctl_dir*find_subdir(structctl_table_set*set,structctl_dir*dir,+constchar*name,intnamelen){-structctl_table*p;-constchar*s=branch->procname;+structctl_table_header*head;+structctl_table*entry;-/* branch should have named subdirectory as its first element */-if(!s||!branch->child)-returnNULL;+entry=find_entry(&head,set,dir,name,namelen);+if(!entry)+returnERR_PTR(-ENOENT);+if(S_ISDIR(entry->mode))+returncontainer_of(head,structctl_dir,header);+returnERR_PTR(-ENOTDIR);+}++staticstructctl_dir*new_dir(structctl_table_set*set,+constchar*name,intnamelen)+{+structctl_table*table;+structctl_dir*new;+char*new_name;-/* ... and nothing else */-if(branch[1].procname)+new=kzalloc(sizeof(*new)+sizeof(structctl_table)*2++namelen+1,GFP_KERNEL);+if(!new)returnNULL;-/* table should contain subdirectory with the same name */-for(p=table;p->procname;p++){-if(!p->child)-continue;-if(p->procname&&strcmp(p->procname,s)==0)-returnp;-}-returnNULL;+table=(structctl_table*)(new+1);+new_name=(char*)(table+2);+memcpy(new_name,name,namelen);+new_name[namelen]='\0';+table[0].procname=new_name;+table[0].mode=S_IFDIR|S_IRUGO|S_IXUGO;+init_header(&new->header,set->root,set,table);++returnnew;}-/* see if attaching q to p would be an improvement */-staticvoidtry_attach(structctl_table_header*p,structctl_table_header*q)+staticstructctl_dir*get_subdir(structctl_table_set*set,+structctl_dir*dir,constchar*name,intnamelen){-structctl_table*to=p->ctl_table,*by=q->ctl_table;-structctl_table*next;-intis_better=0;-intnot_in_parent=!p->attached_by;--while((next=is_branch_in(by,to))!=NULL){-if(by==q->attached_by)-is_better=1;-if(to==p->attached_by)-not_in_parent=1;-by=by->child;-to=next->child;-}+structctl_dir*subdir,*new=NULL;-if(is_better&¬_in_parent){-q->attached_by=by;-q->attached_to=to;-q->parent=p;+spin_lock(&sysctl_lock);+subdir=find_subdir(dir->header.set,dir,name,namelen);+if(!IS_ERR(subdir))+gotofound;+if((PTR_ERR(subdir)==-ENOENT)&&set!=dir->header.set)+subdir=find_subdir(set,dir,name,namelen);+if(!IS_ERR(subdir))+gotofound;+if(PTR_ERR(subdir)!=-ENOENT)+gotofailed;++spin_unlock(&sysctl_lock);+new=new_dir(set,name,namelen);+spin_lock(&sysctl_lock);+subdir=ERR_PTR(-ENOMEM);+if(!new)+gotofailed;++subdir=find_subdir(set,dir,name,namelen);+if(!IS_ERR(subdir))+gotofound;+if(PTR_ERR(subdir)!=-ENOENT)+gotofailed;++insert_header(dir,&new->header);+subdir=new;+found:+subdir->header.nreg++;+failed:+if(unlikely(IS_ERR(subdir))){+printk(KERN_ERR"sysctl could not get directory: %*.*s %ld\n",+namelen,namelen,name,PTR_ERR(subdir));}+drop_sysctl_table(&dir->header);+if(new)+drop_sysctl_table(&new->header);+spin_unlock(&sysctl_lock);+returnsubdir;}staticintsysctl_check_table_dups(constchar*path,structctl_table*old,
@@ -846,24 +851,14 @@ static int sysctl_check_table_dups(const char *path, struct ctl_table *old,}staticintsysctl_check_dups(structnsproxy*namespaces,-structctl_table_header*header,+structctl_dir*dir,constchar*path,structctl_table*table){structctl_table_root*root;structctl_table_set*set;-structctl_table_header*dir_head,*head;-structctl_table*dir_table;+structctl_table_header*head;interror=0;-/* No dups if we are the only member of our directory */-if(header->attached_by!=table)-return0;--dir_head=header->parent;-dir_table=header->attached_to;--error=sysctl_check_table_dups(path,dir_table,table);-root=&sysctl_table_root;do{set=lookup_header_set(root,namespaces);
@@ -871,9 +866,9 @@ static int sysctl_check_dups(struct nsproxy *namespaces,list_for_each_entry(head,&set->list,ctl_entry){if(head->unregistering)continue;-if(head->attached_to!=dir_table)+if(head->parent!=dir)continue;-error=sysctl_check_table_dups(path,head->attached_by,+error=sysctl_check_table_dups(path,head->ctl_table,table);}root=list_entry(root->root_list.next,
@@ -977,47 +972,25 @@ struct ctl_table_header *__register_sysctl_table(constchar*path,structctl_table*table){structctl_table_header*header;-structctl_table*new,**prevp;constchar*name,*nextname;-unsignedintnpath=0;structctl_table_set*set;-size_tpath_bytes=0;-char*new_name;--/* Count the path components */-for(name=path;name;name=nextname){-intnamelen;-nextname=strchr(name,'/');-if(nextname){-namelen=nextname-name;-nextname++;-}else{-namelen=strlen(name);-}-if(namelen==0)-continue;-path_bytes+=namelen+1;-npath++;-}+structctl_dir*dir;-/*-*Foreachpathcomponent,allocatea2-elementctl_tablearray.-*Thefirstarrayelementwillbefilledwiththesysctlentry-*forthis,thesecondwillbethesentinel(procname==0).-*-*Weallocateeverythinginonegosothatwedon'thaveto-*worryaboutfreeingadditionalmemoryinunregister_sysctl_table.-*/-header=kzalloc(sizeof(structctl_table_header)+path_bytes+-(2*npath*sizeof(structctl_table)),GFP_KERNEL);+header=kzalloc(sizeof(structctl_table_header),GFP_KERNEL);if(!header)returnNULL;-new=(structctl_table*)(header+1);-new_name=(char*)(new+(2*npath));+init_header(header,root,NULL,table);+if(sysctl_check_table(path,table))+gotofail;++spin_lock(&sysctl_lock);+header->set=set=lookup_header_set(root,namespaces);+dir=&sysctl_root_dir;+dir->header.nreg++;+spin_unlock(&sysctl_lock);-/* Now connect the dots */-prevp=&header->ctl_table;+/* Find the directory for the ctl_table */for(name=path;name;name=nextname){intnamelen;nextname=strchr(name,'/');
@@ -1040,9 +1041,12 @@ struct ctl_table_headerstructctl_table*ctl_table_arg;structctl_table_root*root;structctl_table_set*set;-structctl_table*attached_by;-structctl_table*attached_to;-structctl_table_header*parent;+structctl_dir*parent;+};++structctl_dir{+/* Header must be at the start of ctl_dir */+structctl_table_headerheader;};structctl_table_set{
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:51:59
When there are errors it is very nice to know the full sysctl path.
Add a simple function that computes the sysctl path and prints it
out.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 11 ++++++++++-
1 files changed, 10 insertions(+), 1 deletions(-)
@@ -822,7 +829,9 @@ found:subdir->header.nreg++;failed:if(unlikely(IS_ERR(subdir))){-printk(KERN_ERR"sysctl could not get directory: %*.*s %ld\n",+printk(KERN_ERR"sysctl could not get directory: ");+sysctl_print_dir(dir);+printk(KERN_CONT"/%*.*s %ld\n",namelen,namelen,name,PTR_ERR(subdir));}drop_sysctl_table(&dir->header);
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:52:00
Piecing together directories by looking first in one directory
tree, than in another directory tree and finally in a third
directory tree makes it hard to verify that some directory
entries are not multiply defined and makes it hard to create
efficient implementations the sysctl filesystem.
Replace the sysctl wide list of roots with autogenerated
links from the core sysctl directory tree to the other
sysctl directory trees.
This simplifies sysctl directory reading and lookups as now
only entries in a single sysctl directory tree need to be
considered.
Benchmark before:
make-dummies 0 999 -> 0.44s
rmmod dummy -> 0.065s
make-dummies 0 9999 -> 1m36s
rmmod dummy -> 0.4s
Benchmark after:
make-dummies 0 999 -> 0.63s
rmmod dummy -> 0.12s
make-dummies 0 9999 -> 2m35s
rmmod dummy -> 18s
The slowdown is caused by the lookups used in insert_headers
and put_links to see if we need to add links or remove links.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 397 +++++++++++++++++++++++++++++++++++------------
include/linux/sysctl.h | 3 +-
2 files changed, 296 insertions(+), 104 deletions(-)
@@ -547,6 +526,25 @@ static int proc_sys_fill_cache(struct file *filp, void *dirent,return!!filldir(dirent,qname.name,qname.len,filp->f_pos,ino,type);}+staticintproc_sys_link_fill_cache(structfile*filp,void*dirent,+filldir_tfilldir,+structctl_table_header*head,+structctl_table*table)+{+interr,ret=0;+head=sysctl_head_grab(head);++/* It is not an error if we can not follow the link ignore it */+err=sysctl_follow_link(&head,&table,current->nsproxy);+if(err)+gotoout;++ret=proc_sys_fill_cache(filp,dirent,filldir,head,table);+out:+sysctl_head_finish(head);+returnret;+}+staticintscan(structctl_table_header*head,ctl_table*table,unsignedlong*pos,structfile*file,void*dirent,filldir_tfilldir)
@@ -841,6 +840,57 @@ failed:returnsubdir;}+staticstructctl_dir*xlate_dir(structctl_table_set*set,structctl_dir*dir)+{+structctl_dir*parent;+constchar*procname;+if(!dir->header.parent)+return&set->dir;+parent=xlate_dir(set,dir->header.parent);+if(IS_ERR(parent))+returnparent;+procname=dir->header.ctl_table[0].procname;+returnfind_subdir(parent,procname,strlen(procname));+}++staticintsysctl_follow_link(structctl_table_header**phead,+structctl_table**pentry,structnsproxy*namespaces)+{+structctl_table_header*head;+structctl_table_root*root;+structctl_table_set*set;+structctl_table*entry;+structctl_dir*dir;+intret;++/* Get out quickly if not a link */+if(!S_ISLNK((*pentry)->mode))+return0;++ret=0;+spin_lock(&sysctl_lock);+root=(*pentry)->data;+set=lookup_header_set(root,namespaces);+dir=xlate_dir(set,(*phead)->parent);+if(IS_ERR(dir))+ret=PTR_ERR(dir);+else{+constchar*procname=(*pentry)->procname;+head=NULL;+entry=find_entry(&head,dir,procname,strlen(procname));+ret=-ENOENT;+if(entry&&use_table(head)){+unuse_table(*phead);+*phead=head;+*pentry=entry;+ret=0;+}+}++spin_unlock(&sysctl_lock);+returnret;+}+staticintsysctl_check_table_dups(constchar*path,structctl_table*old,structctl_table*table){
@@ -932,6 +973,115 @@ static int sysctl_check_table(const char *path, struct ctl_table *table)returnerr;}+staticstructctl_table_header*new_links(structctl_dir*dir,structctl_table*table,+structctl_table_root*link_root)+{+structctl_table*link_table,*entry,*link;+structctl_table_header*links;+char*link_name;+intnr_entries,name_bytes;++name_bytes=0;+nr_entries=0;+for(entry=table;entry->procname;entry++){+nr_entries++;+name_bytes+=strlen(entry->procname)+1;+}++links=kzalloc(sizeof(structctl_table_header)++sizeof(structctl_table)*(nr_entries+1)++name_bytes,+GFP_KERNEL);++if(!links)+returnNULL;++link_table=(structctl_table*)(links+1);+link_name=(char*)&link_table[nr_entries+1];++for(link=link_table,entry=table;entry->procname;link++,entry++){+intlen=strlen(entry->procname)+1;+memcpy(link_name,entry->procname,len);+link->procname=link_name;+link->mode=S_IFLNK|S_IRWXUGO;+link->data=link_root;+link_name+=len;+}+init_header(links,dir->header.root,dir->header.set,link_table);+links->nreg=nr_entries;++returnlinks;+}++staticboolget_links(structctl_dir*dir,+structctl_table*table,structctl_table_root*link_root)+{+structctl_table_header*head;+structctl_table*entry,*link;++/* Are there links available for every entry in table? */+for(entry=table;entry->procname;entry++){+constchar*procname=entry->procname;+link=find_entry(&head,dir,procname,strlen(procname));+if(!link)+returnfalse;+if(S_ISDIR(link->mode)&&S_ISDIR(entry->mode))+continue;+if(S_ISLNK(link->mode)&&(link->data==link_root))+continue;+returnfalse;+}++/* The checks passed. Increase the registration count on the links */+for(entry=table;entry->procname;entry++){+constchar*procname=entry->procname;+link=find_entry(&head,dir,procname,strlen(procname));+head->nreg++;+}+returntrue;+}++staticintinsert_links(structctl_table_header*head)+{+structctl_table_set*root_set=&sysctl_table_root.default_set;+structctl_dir*core_parent=NULL;+structctl_table_header*links;+interr;++if(head->set==root_set)+return0;++core_parent=xlate_dir(root_set,head->parent);+if(IS_ERR(core_parent))+return0;++if(get_links(core_parent,head->ctl_table,head->root))+return0;++core_parent->header.nreg++;+spin_unlock(&sysctl_lock);++links=new_links(core_parent,head->ctl_table,head->root);++spin_lock(&sysctl_lock);+err=-ENOMEM;+if(!links)+gotoout;++err=0;+if(get_links(core_parent,head->ctl_table,head->root)){+kfree(links);+gotoout;+}++err=insert_header(core_parent,links);+if(err)+kfree(links);+out:+drop_sysctl_table(&core_parent->header);+returnerr;+}+/***__register_sysctl_table-registeraleafsysctltable*@root:Listofsysctlheaderstoregisteron
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:52:01
An nsproxy argument here has always been awkard and now the nsproxy argument
is completely unnecessary so remove it, replacing it with the set we want
the registered tables to show up in.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 30 ++++++++++++------------------
include/linux/sysctl.h | 4 ++--
net/sysctl_net.c | 10 +++-------
3 files changed, 17 insertions(+), 27 deletions(-)
@@ -1223,8 +1220,7 @@ static int count_subheaders(struct ctl_table *table)}staticintregister_leaf_sysctl_tables(constchar*path,char*pos,-structctl_table_header***subheader,-structctl_table_root*root,structnsproxy*namespaces,+structctl_table_header***subheader,structctl_table_set*set,structctl_table*table){structctl_table*ctl_table_arg=NULL;
@@ -1261,7 +1257,7 @@ static int register_leaf_sysctl_tables(const char *path, char *pos,/* Register everything except a directory full of subdirectories */if(nr_files||!nr_dirs){structctl_table_header*header;-header=__register_sysctl_table(root,namespaces,path,files);+header=__register_sysctl_table(set,path,files);if(!header){kfree(ctl_table_arg);gotoout;
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:52:02
Simplify the callers of insert_header by removing explicit calls to check
for duplicates and instead have insert_header do the work.
This makes the code slightly more maintainable by enabling changes to
data structures where the insertion of new entries without duplicate
suppression is not possible.
There is not always a convenient path string where insert_header
is called so modify sysctl_check_dups to use sysctl_print_dir
when printing the full path when a duplicate is discovered.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 20 +++++++++++---------
1 files changed, 11 insertions(+), 9 deletions(-)
@@ -1047,10 +1047,10 @@ struct ctl_table_headerstructctl_dir{/* Header must be at the start of ctl_dir */structctl_table_headerheader;+structlist_headlist;};structctl_table_set{-structlist_headlist;int(*is_seen)(structctl_table_set*);structctl_dirdir;};
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:52:04
One of the most important jobs of sysctl is to export network stack
tunables. Several of those tunables are per network device. In
several instances people are running with 1000+ network devices in
there network stacks, which makes the simple per directory linked list
in sysctl a scaling bottleneck. Replace O(N^2) sysctl insertion and
lookup times with O(NlogN) by using an rbtree to index the sysctl
directories.
Benchmark before:
make-dummies 0 999 -> 0.32s
rmmod dummy -> 0.12s
make-dummies 0 9999 -> 1m17s
rmmod dummy -> 17s
Benchmark after:
make-dummies 0 999 -> 0.074s
rmmod dummy -> 0.070s
make-dummies 0 9999 -> 3.4s
rmmod dummy -> 0.44s
Benchmark after (without dev_snmp6):
make-dummies 0 9999 -> 0.75s
rmmod dummy -> 0.44s
make-dummies 0 99999 -> 11s
rmmod dummy -> 4.3s
At 10,000 dummy devices the bottleneck becomes the time to add and
remove the files under /proc/sys/net/dev_snmp6. I have commented
out the code that adds and removes files under /proc/sys/net/dev_snmp6
and taken measurments of creating and destroying 100,000 dummies to
verify the sysctl continues to scale.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 224 +++++++++++++++++++++++++++++-------------------
include/linux/sysctl.h | 10 ++-
2 files changed, 142 insertions(+), 92 deletions(-)
@@ -932,6 +932,7 @@ enum#include<linux/list.h>#include<linux/rcupdate.h>#include<linux/wait.h>+#include<linux/rbtree.h>/* For the /proc/sys support */structctl_table;
@@ -1023,6 +1024,11 @@ struct ctl_tablevoid*extra2;};+structctl_node{+structrb_nodenode;+structctl_table_header*header;+};+/* struct ctl_table_header is used to maintain dynamic lists ofstructctl_tabletrees.*/structctl_table_header
@@ -1042,12 +1047,13 @@ struct ctl_table_headerstructctl_table_root*root;structctl_table_set*set;structctl_dir*parent;+structctl_node*node;};structctl_dir{/* Header must be at the start of ctl_dir */structctl_table_headerheader;-structlist_headlist;+structrb_rootroot;};structctl_table_set{
From: Eric W. Biederman <hidden> Date: 2012-01-27 04:52:05
The plan is to convert all callers of register_sysctl_table
and register_sysctl_paths to register_sysctl. The interface
to register_sysctl is enough nicer this should make the callers
a bit more readable. Additionally after the conversion the
230 lines of backwards compatibility can be removed.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 17 +++++++++++++++++
include/linux/sysctl.h | 1 +
2 files changed, 18 insertions(+), 0 deletions(-)
From: Lucian Adrian Grijincu <hidden> Date: 2012-01-29 15:50:10
On Fri, Jan 27, 2012 at 6:51 AM, Eric W. Biederman
[off-list ref] wrote:
quoted hunk
Replace the helpers that proc_sys_lookup uses with helpers that work
in terms of an entire sysctl directory. This is worse for sysctl_lock
hold times but it is much better for code clarity and the code cleanups
to come.
find_in_table is no longer needed so it is removed.
find_entry a general helper to find entries in a directory is added.
lookup_entry is a simple wrapper around find_entry that takes the
sysctl_lock increases the use count if an entry is found and drops
the sysctl_lock.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 102 ++++++++++++++++++++++++++++++++++++------------
1 files changed, 76 insertions(+), 26 deletions(-)
From: Lucian Adrian Grijincu <hidden> Date: 2012-01-29 17:20:01
On Fri, Jan 27, 2012 at 6:51 AM, Eric W. Biederman
[off-list ref] wrote:
Add a ctl_table_root pointer to ctl_table set so it is easy to
"ctl_table set" -> "ctl_table_set"
quoted hunk
go from a ctl_table_set to a ctl_table_root.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 3 +++
include/linux/sysctl.h | 3 +++
net/sysctl_net.c | 5 ++---
3 files changed, 8 insertions(+), 3 deletions(-)
static int __net_init sysctl_net_init(struct net *net)
{
- setup_sysctl_set(&net->sysctls,
- is_seen);
+ setup_sysctl_set(&net->sysctls, &net_sysctl_root, is_seen);
return 0;
}
@@ -95,7 +94,7 @@ static __init int net_sysctl_init(void)
ret = register_pernet_subsys(&sysctl_pernet_ops);
if (ret)
goto out;
- setup_sysctl_set(&net_sysctl_ro_root.default_set, NULL);
+ setup_sysctl_set(&net_sysctl_ro_root.default_set, &net_sysctl_ro_root, NULL);
register_sysctl_root(&net_sysctl_ro_root);
register_sysctl_root(&net_sysctl_root);
out:
--
1.7.2.5
--
.
..: Lucian
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
From: Lucian Adrian Grijincu <hidden> Date: 2012-01-29 17:36:37
On Fri, Jan 27, 2012 at 6:51 AM, Eric W. Biederman
[off-list ref] wrote:
quoted hunk
Every other directory has a .child member and we look at the .child
for our entries. Do the same for the root_table.
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 15 +++++++++++----
1 files changed, 11 insertions(+), 4 deletions(-)
Why not: .mode = S_IFDIR|S_IRUGO|S_IXUGO ?
You change it later and add IFDIR in patch 22/29 because of this
change (from 22/29):
- if (!table->child) {
+ if (!S_ISDIR(table->mode)) {
but if might as well be done here.
Somewhere above we have
struct ctl_table *table = PROC_I(inode)->sysctl_entry;
and sysctl_entry can take two values:
- NULL: fs/proc/inode.c:proc_alloc_inode()
- a non-NULL table in: proc_sys_make_inode()
The only inode that can be passed to proc_sys_lookup that can have
table=NULL is the root inode.
In that case head will be &root_table_header.
So head->ctl_table[1] == root_table_header.ctl_table[1] == root_table[1].
I think this could be improved to something like this:
/* table == NULL only for the procfs root directory */
table = table ? table->child : &root_table[1];
It's not that important, as this code will go away in a few patches.
--
.
..: Lucian
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
From: Lucian Adrian Grijincu <hidden> Date: 2012-01-29 19:32:13
On Fri, Jan 27, 2012 at 6:51 AM, Eric W. Biederman
[off-list ref] wrote:
quoted hunk
Simplify the code and the sysctl semantics by autogenerating
sysctl directories when a sysctl table is registered that needs
the directories and autodeleting the directories when there are
no more sysctl tables registered that need them.
Autogenerating directories keeps sysctl tables from depending
on each other, removing all of the arcane register/unregister
ordering constraints and makes it impossible to get the order
wrong when reigsering and unregistering sysctl tables.
Autogenerating directories yields one unique entity that dentries
can point to, retaining the current effective use of the dcache.
Add struct ctl_dir as the type of these new autogenerated
directories.
The attached_by and attached_to fields in ctl_table_header are
removed as they are no longer needed.
The child field in ctl_table is no longer needed by the core of
the sysctl code. ctl_table.child can be removed once all of the
existing users have been updated.
Benchmark before:
make-dummies 0 999 -> 0.7s
rmmod dummy -> 0.07s
make-dummies 0 9999 -> 1m10s
rmmod dummy -> 0.4s
Benchmark after:
make-dummies 0 999 -> 0.44s
rmmod dummy -> 0.065s
make-dummies 0 9999 -> 1m36s
rmmod dummy -> 0.4s
Signed-off-by: Eric W. Biederman <redacted>
---
fs/proc/proc_sysctl.c | 342 ++++++++++++++++++++----------------------------
include/linux/sysctl.h | 10 +-
2 files changed, 150 insertions(+), 202 deletions(-)
.d_compare = proc_sys_compare,
};
-static struct ctl_table *is_branch_in(struct ctl_table *branch,
- struct ctl_table *table)
+static struct ctl_dir *find_subdir(struct ctl_table_set *set, struct ctl_dir *dir,
+ const char *name, int namelen)
{
- struct ctl_table *p;
- const char *s = branch->procname;
+ struct ctl_table_header *head;
+ struct ctl_table *entry;
- /* branch should have named subdirectory as its first element */
- if (!s || !branch->child)
- return NULL;
+ entry = find_entry(&head, set, dir, name, namelen);
+ if (!entry)
+ return ERR_PTR(-ENOENT);
+ if (S_ISDIR(entry->mode))
+ return container_of(head, struct ctl_dir, header);
+ return ERR_PTR(-ENOTDIR);
+}
I find this easier to read:
entry = find_entry(&head, dir, name, namelen);
if (!entry)
return ERR_PTR(-ENOENT);
if (!S_ISDIR(entry->mode))
return ERR_PTR(-ENOTDIR);
return container_of(head, struct ctl_dir, header);
+
+static struct ctl_dir *new_dir(struct ctl_table_set *set,
+ const char *name, int namelen)
+{
+ struct ctl_table *table;
+ struct ctl_dir *new;
+ char *new_name;
- /* ... and nothing else */
- if (branch[1].procname)
+ new = kzalloc(sizeof(*new) + sizeof(struct ctl_table)*2 +
+ namelen + 1, GFP_KERNEL);
+ if (!new)
return NULL;
- /* table should contain subdirectory with the same name */
- for (p = table; p->procname; p++) {
- if (!p->child)
- continue;
- if (p->procname && strcmp(p->procname, s) == 0)
- return p;
- }
- return NULL;
+ table = (struct ctl_table *)(new + 1);
+ new_name = (char *)(table + 2);
+ memcpy(new_name, name, namelen);
+ new_name[namelen] = '\0';
+ table[0].procname = new_name;
+ table[0].mode = S_IFDIR|S_IRUGO|S_IXUGO;
+ init_header(&new->header, set->root, set, table);
+
+ return new;
}
-/* see if attaching q to p would be an improvement */
-static void try_attach(struct ctl_table_header *p, struct ctl_table_header *q)
{
- struct ctl_table *to = p->ctl_table, *by = q->ctl_table;
- struct ctl_table *next;
- int is_better = 0;
- int not_in_parent = !p->attached_by;
-
- while ((next = is_branch_in(by, to)) != NULL) {
- if (by == q->attached_by)
- is_better = 1;
- if (to == p->attached_by)
- not_in_parent = 1;
- by = by->child;
- to = next->child;
- }
+ struct ctl_dir *subdir, *new = NULL;
- if (is_better && not_in_parent) {
- q->attached_by = by;
- q->attached_to = to;
- q->parent = p;
This one's hard to understand :) I would add these comments:
/* get_subdir - find and take a reference of a given subdir
* - if no entry with the same name found, create one
* - set - the set in which to look for the subdir (after looking in
the parent dir's set)
* - dir - the parent dir. NOTE: before calling this function
* make sure to have a increased the count on the parent dir's nreg
* as this function will drop a reference!
*/
const char *path, struct ctl_table *table)
{
struct ctl_table_header *header;
- struct ctl_table *new, **prevp;
const char *name, *nextname;
- unsigned int npath = 0;
struct ctl_table_set *set;
- size_t path_bytes = 0;
- char *new_name;
-
- /* Count the path components */
- for (name = path; name; name = nextname) {
- int namelen;
- nextname = strchr(name, '/');
- if (nextname) {
- namelen = nextname - name;
- nextname++;
- } else {
- namelen = strlen(name);
- }
- if (namelen == 0)
- continue;
- path_bytes += namelen + 1;
- npath++;
- }
+ struct ctl_dir *dir;
- /*
- * For each path component, allocate a 2-element ctl_table array.
- * The first array element will be filled with the sysctl entry
- * for this, the second will be the sentinel (procname == 0).
- *
- * We allocate everything in one go so that we don't have to
- * worry about freeing additional memory in unregister_sysctl_table.
- */
- header = kzalloc(sizeof(struct ctl_table_header) + path_bytes +
- (2 * npath * sizeof(struct ctl_table)), GFP_KERNEL);
+ header = kzalloc(sizeof(struct ctl_table_header), GFP_KERNEL);
if (!header)
return NULL;
- new = (struct ctl_table *) (header + 1);
- new_name = (char *)(new + (2 * npath));
+ init_header(header, root, NULL, table);
+ if (sysctl_check_table(path, table))
+ goto fail;
+
+ spin_lock(&sysctl_lock);
+ header->set = set = lookup_header_set(root, namespaces);
+ dir = &sysctl_root_dir;
/* get_subdir will drop a reference to this dir as it dives into the subdir */
quoted hunk
+ dir->header.nreg++;
+ spin_unlock(&sysctl_lock);
- /* Now connect the dots */
- prevp = &header->ctl_table;
+ /* Find the directory for the ctl_table */
for (name = path; name; name = nextname) {
int namelen;
nextname = strchr(name, '/');
--
.
..: Lucian
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
From: Lucian Adrian Grijincu <hidden> Date: 2012-01-30 00:01:48
Very nice way to handle netns specific files with links between sets!
You did a much better job than I did at dealing with them.
It took me a while to understand how the code works. I'll try to write
something for Documentation/ because the inner workings are a bit
intertwined.
A few comments bellow.
On Fri, Jan 27, 2012 at 6:52 AM, Eric W. Biederman
[off-list ref] wrote:
Piecing together directories by looking first in one directory
tree, than in another directory tree and finally in a third
than -> then
directory tree makes it hard to verify that some directory
entries are not multiply defined and makes it hard to create
efficient implementations the sysctl filesystem.
Replace the sysctl wide list of roots with autogenerated
links from the core sysctl directory tree to the other
sysctl directory trees.
This simplifies sysctl directory reading and lookups as now
only entries in a single sysctl directory tree need to be
considered.
Benchmark before:
make-dummies 0 999 -> 0.44s
rmmod dummy -> 0.065s
make-dummies 0 9999 -> 1m36s
rmmod dummy -> 0.4s
Benchmark after:
make-dummies 0 999 -> 0.63s
rmmod dummy -> 0.12s
make-dummies 0 9999 -> 2m35s
rmmod dummy -> 18s
The slowdown is caused by the lookups used in insert_headers
insert_headers -> insert_header
and put_links to see if we need to add links or remove links.
"sysctl_follow_link" implies that it will follow the link. I would
pull out the check whether the header is a link or not. This wouldn't
save much (a function call), but it would make the code easier to
read:
/* Get out quickly if not a link */
if (S_ISLNK(p->mode)) {
ret = sysctl_follow_link(&h, &p, current->nsproxy);
err = ERR_PTR(ret);
if (ret)
goto out;
}
quoted hunk
+ ret = sysctl_follow_link(&h, &p, current->nsproxy);
+ err = ERR_PTR(ret);
+ if (ret)
+ goto out;
+
err = ERR_PTR(-ENOMEM);
inode = proc_sys_make_inode(dir->i_sb, h ? h : head, p);
if (h)
return !!filldir(dirent, qname.name, qname.len, filp->f_pos, ino, type);
}
+static int proc_sys_link_fill_cache(struct file *filp, void *dirent,
+ filldir_t filldir,
+ struct ctl_table_header *head,
+ struct ctl_table *table)
+{
+ int err, ret = 0;
+ head = sysctl_head_grab(head);
+
+ /* It is not an error if we can not follow the link ignore it */
+ err = sysctl_follow_link(&head, &table, current->nsproxy);
if (!new)
goto failed;
- subdir = find_subdir(set, dir, name, namelen);
+ subdir = find_subdir(dir, name, namelen);
if (!IS_ERR(subdir))
goto found;
if (PTR_ERR(subdir) != -ENOENT)
goto failed;
I think you're returning the wrong error here. If we got to this point
then subdir == ERR_PTR(-ENOENT).
We want to create a new dir here even if one doesn't exist.
So if we have an error in insert_header() we don't return that error,
but ENOENT.
From: Dave Jones <hidden> Date: 2012-01-30 17:51:19
On Thu, Jan 26, 2012 at 08:52:00PM -0800, Eric W. Biederman wrote:
> + memset(set, sizeof(*set), 0);
checkpatch should warn you about this mistake.
Dave