Here's a series for fixing issues with d_drop on a directory dentry with
children and adding support for such dropped directories in fuse.
This one fixes a couple of issues I found with the previous incarnation and
split out the filesystem parts into individual patches.
Thanks,
Miklos
---
Anand Avati (1):
fuse: drop dentry on failed revalidate
Miklos Szeredi (8):
vfs: check submounts and drop atomically
vfs: check unlinked ancestors before mount
afs: use check_submounts_and_drop()
gfs2: use check_submounts_and_drop()
nfs: use check_submounts_and_drop()
sysfs: use check_submounts_and_drop()
fuse: use d_materialise_unique()
fuse: clean up return in fuse_dentry_revalidate()
---
fs/afs/dir.c | 10 +--
fs/dcache.c | 192 +++++++++++++++++++++++++++++++++++++++++++++++++
fs/fuse/dir.c | 97 ++++++++++++-------------
fs/gfs2/dentry.c | 9 +--
fs/internal.h | 1 +
fs/namespace.c | 9 +++
fs/nfs/dir.c | 9 ++-
fs/sysfs/dir.c | 20 +++---
include/linux/dcache.h | 1 +
9 files changed, 267 insertions(+), 81 deletions(-)
From: Miklos Szeredi <redacted>
We check submounts before doing d_drop() on a non-empty directory dentry in
NFS (have_submounts()), but we do not exclude a racing mount.
Process A: have_submounts() -> returns false
Process B: mount() -> success
Process A: d_drop()
This patch prepares the ground for the fix by doing the following
operations all under the same rename lock:
have_submounts()
shrink_dcache_parent()
d_drop()
This is actually an optimization since have_submounts() and
shrink_dcache_parent() both traverse the same dentry tree separately.
Signed-off-by: Miklos Szeredi <redacted>
CC: David Howells <dhowells@redhat.com>
CC: Steven Whitehouse <redacted>
CC: Trond Myklebust <redacted>
CC: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
fs/dcache.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/dcache.h | 1 +
2 files changed, 158 insertions(+)
@@ -1224,6 +1224,163 @@ void shrink_dcache_parent(struct dentry * parent)}EXPORT_SYMBOL(shrink_dcache_parent);+staticint__check_submounts_and_drop(structdentry*parent,+structlist_head*dispose)+{+structdentry*this_parent;+structlist_head*next;+unsignedseq;+intfound=0;+intlocked=0;++seq=read_seqbegin(&rename_lock);+again:+this_parent=parent;+spin_lock(&this_parent->d_lock);+repeat:+next=this_parent->d_subdirs.next;+resume:+while(next!=&this_parent->d_subdirs){+structlist_head*tmp=next;+structdentry*dentry;++dentry=list_entry(tmp,structdentry,d_u.d_child);+next=tmp->next;++spin_lock_nested(&dentry->d_lock,DENTRY_D_LOCK_NESTED);+if(d_mountpoint(dentry)){+spin_unlock(&dentry->d_lock);+found=-EBUSY;+gotoout;+}++/*+*moveonlyzerorefcountdentriestothedisposelist.+*+*Thosewhicharepresentlyontheshrinklist,beingprocessed+*byshrink_dentry_list(),shouldn'tbemoved.Otherwisethe+*loopinshrink_dcache_parent()mightnotmakeanyprogress+*andloopforever.+*/+if(dentry->d_count){+dentry_lru_del(dentry);+}elseif(!(dentry->d_flags&DCACHE_SHRINK_LIST)){+dentry_lru_move_list(dentry,dispose);+dentry->d_flags|=DCACHE_SHRINK_LIST;+found++;+}+/*+*Wecanreturntothecallerifwehavefoundsome(this+*ensuresforwardprogress).We'llbecomingbacktofind+*therest.+*/+if(found&&need_resched()){+spin_unlock(&dentry->d_lock);+gotoout;+}++/*+*Descendalevelifthed_subdirslistisnon-empty.+*/+if(!list_empty(&dentry->d_subdirs)){+spin_unlock(&this_parent->d_lock);+spin_release(&dentry->d_lock.dep_map,1,_RET_IP_);+this_parent=dentry;+spin_acquire(&this_parent->d_lock.dep_map,0,1,_RET_IP_);+gotorepeat;+}++spin_unlock(&dentry->d_lock);+}+/*+*Alldoneatthislevel...ascendandresumethesearch.+*/+if(this_parent!=parent){+structdentry*child=this_parent;+this_parent=try_to_ascend(this_parent,locked,seq);+if(!this_parent)+gotorename_retry;+next=child->d_u.d_child.next;+gotoresume;+}+if(!locked&&read_seqretry(&rename_lock,seq)){+spin_unlock(&this_parent->d_lock);+gotorename_retry;+}+if(d_mountpoint(this_parent))+found=-EBUSY;+if(!found)+__d_drop(this_parent);+out:+spin_unlock(&this_parent->d_lock);++if(locked)+write_sequnlock(&rename_lock);+returnfound;++rename_retry:+if(found)+returnfound;+if(locked)+gotoagain;+locked=1;+write_seqlock(&rename_lock);+gotoagain;+}++/**+*check_submounts_and_drop-prunedcache,checkforsubmountsanddrop+*+*Alldoneasasingleatomicoperationrelativetohas_unlinked_ancestor().+*Returns0ifsuccessfullyunhashed@parent.Ifthereweresubmountsthen+*return-EBUSY.+*+*@dentry:dentrytopruneanddrop+*/+intcheck_submounts_and_drop(structdentry*dentry)+{+intret=0;++/* Negative dentries can be dropped without further checks */+if(!dentry->d_inode){+d_drop(dentry);+gotoout;+}++spin_lock(&dentry->d_lock);+if(d_unhashed(dentry))+gotoout_unlock;+if(list_empty(&dentry->d_subdirs)){+if(d_mountpoint(dentry)){+ret=-EBUSY;+gotoout_unlock;+}+__d_drop(dentry);+gotoout_unlock;+}+spin_unlock(&dentry->d_lock);++for(;;){+LIST_HEAD(dispose);+ret=__check_submounts_and_drop(dentry,&dispose);+if(!list_empty(&dispose))+shrink_dentry_list(&dispose);++if(ret<=0)+break;++cond_resched();+}++out:+returnret;++out_unlock:+spin_unlock(&dentry->d_lock);+gotoout;+}+EXPORT_SYMBOL(check_submounts_and_drop);+/***__d_alloc-allocateadcacheentry*@sb:filesystemitwillbelongto
@@ -251,6 +251,7 @@ extern void d_prune_aliases(struct inode *);/* test whether we have any submounts in a subdir tree */externinthave_submounts(structdentry*);+externintcheck_submounts_and_drop(structdentry*);/**Thisaddstheentrytothehashqueues.
From: Miklos Szeredi <redacted>
We check submounts before doing d_drop() on a non-empty directory dentry in
NFS (have_submounts()), but we do not exclude a racing mount. Nor do we
prevent mounts to be added to the disconnected subtree using relative paths
after the d_drop().
This patch fixes these issues by checking for unlinked (unhashed, non-root)
ancestors before proceeding with the mount. This is done after setting
DCACHE_MOUNTED on the soon-to-be mountpoint and with the rename seqlock
taken for write. This ensures that the only one of
check_submounts_and_drop() or has_unlinked_ancestor() can succeed.
Signed-off-by: Miklos Szeredi <redacted>
CC: David Howells <dhowells@redhat.com>
CC: Steven Whitehouse <redacted>
CC: Trond Myklebust <redacted>
CC: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
fs/dcache.c | 35 +++++++++++++++++++++++++++++++++++
fs/internal.h | 1 +
fs/namespace.c | 9 +++++++++
3 files changed, 45 insertions(+)
@@ -685,16 +685,12 @@ not_found:spin_unlock(&dentry->d_lock);out_bad:-if(dentry->d_inode){-/* don't unhash if we have submounts */-if(have_submounts(dentry))-gotoout_skip;-}+/* don't unhash if we have submounts */+if(check_submounts_and_drop(dentry)!=0)+gotoout_skip;_debug("dropping dentry %s/%s",parent->d_name.name,dentry->d_name.name);-shrink_dcache_parent(dentry);-d_drop(dentry);dput(parent);key_put(key);
From: Miklos Szeredi <redacted>
Do have_submounts(), shrink_dcache_parent() and d_drop() atomically.
check_submounts_and_drop() can deal with negative dentries and
non-directories as well.
Non-directories can also be mounted on. And just like directories we don't
want these to disappear with invalidation.
Signed-off-by: Miklos Szeredi <redacted>
CC: Steven Whitehouse <redacted>
---
fs/gfs2/dentry.c | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
From: Miklos Szeredi <redacted>
Do have_submounts(), shrink_dcache_parent() and d_drop() atomically.
check_submounts_and_drop() can deal with negative dentries and
non-directories as well.
Non-directories can also be mounted on. And just like directories we don't
want these to disappear with invalidation.
Signed-off-by: Miklos Szeredi <redacted>
CC: Trond Myklebust <redacted>
---
fs/nfs/dir.c | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
@@ -1135,14 +1135,13 @@ out_zap_parent:if(inode&&S_ISDIR(inode->i_mode)){/* Purge readdir caches. */nfs_zap_caches(inode);-/* If we have submounts, don't unhash ! */-if(have_submounts(dentry))-gotoout_valid;if(dentry->d_flags&DCACHE_DISCONNECTED)gotoout_valid;-shrink_dcache_parent(dentry);}-d_drop(dentry);+/* If we have submounts, don't unhash ! */+if(check_submounts_and_drop(dentry)!=0)+gotoout_valid;+dput(parent);dfprintk(LOOKUPCACHE,"NFS: %s(%s/%s) is invalid\n",__func__,dentry->d_parent->d_name.name,
From: Miklos Szeredi <redacted>
Do have_submounts(), shrink_dcache_parent() and d_drop() atomically.
check_submounts_and_drop() can deal with negative dentries and
non-directories as well.
Non-directories can also be mounted on. And just like directories we don't
want these to disappear with invalidation.
Signed-off-by: Miklos Szeredi <redacted>
---
fs/sysfs/dir.c | 20 ++++++++------------
1 file changed, 8 insertions(+), 12 deletions(-)
@@ -297,7 +297,6 @@ static int sysfs_dentry_delete(const struct dentry *dentry)staticintsysfs_dentry_revalidate(structdentry*dentry,unsignedintflags){structsysfs_dirent*sd;-intis_dir;inttype;if(flags&LOOKUP_RCU)
@@ -341,18 +340,15 @@ out_bad:*isperformedatitsnewnamethedentrywillbereadded*tothedcachehashes.*/-is_dir=(sysfs_type(sd)==SYSFS_DIR);mutex_unlock(&sysfs_mutex);-if(is_dir){-/* If we have submounts we must allow the vfs caches-*tolieaboutthestateofthefilesystemtoprevent-*leaksandothernastythings.-*/-if(have_submounts(dentry))-gotoout_valid;-shrink_dcache_parent(dentry);-}-d_drop(dentry);++/* If we have submounts we must allow the vfs caches+*tolieaboutthestateofthefilesystemtoprevent+*leaksandothernastythings.+*/+if(check_submounts_and_drop(dentry)!=0)+gotoout_valid;+return0;}
From: Miklos Szeredi <redacted>
On errors unrelated to the filesystem's state (ENOMEM, ENOTCONN) return the
error itself from ->d_revalidate() insted of returning zero (invalid).
Also make a common label for invalidating the dentry. This will be used by
the next patch.
Signed-off-by: Miklos Szeredi <redacted>
---
fs/fuse/dir.c | 26 ++++++++++++++++++--------
1 file changed, 18 insertions(+), 8 deletions(-)
@@ -182,10 +182,11 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)structinode*inode;structdentry*parent;structfuse_conn*fc;+intret;inode=ACCESS_ONCE(entry->d_inode);if(inode&&is_bad_inode(inode))-return0;+gotoinvalid;elseif(fuse_dentry_time(entry)<get_jiffies_64()){interr;structfuse_entry_outoutarg;
@@ -195,20 +196,23 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)/* For negative dentries, always do a fresh lookup */if(!inode)-return0;+gotoinvalid;+ret=-ECHILD;if(flags&LOOKUP_RCU)-return-ECHILD;+gotoout;fc=get_fuse_conn(inode);req=fuse_get_req_nopages(fc);+ret=PTR_ERR(req);if(IS_ERR(req))-return0;+gotoout;forget=fuse_alloc_forget();if(!forget){fuse_put_request(fc,req);-return0;+ret=-ENOMEM;+gotoout;}attr_version=fuse_get_attr_version(fc);
@@ -227,7 +231,7 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)structfuse_inode*fi=get_fuse_inode(inode);if(outarg.nodeid!=get_node_id(inode)){fuse_queue_forget(fc,forget,outarg.nodeid,1);-return0;+gotoinvalid;}spin_lock(&fc->lock);fi->nlookup++;
@@ -235,7 +239,7 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)}kfree(forget);if(err||(outarg.attr.mode^inode->i_mode)&S_IFMT)-return0;+gotoinvalid;fuse_change_attributes(inode,&outarg.attr,entry_attr_timeout(&outarg),
@@ -249,7 +253,13 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)dput(parent);}}-return1;+ret=1;+out:+returnret;++invalid:+ret=0;+gotoout;}staticintinvalid_nodeid(u64nodeid)
From: Anand Avati <redacted>
Drop a subtree when we find that it has moved or been delated. This can be
done as long as there are no submounts under this location.
If the directory was moved and we come across the same directory in a
future lookup it will be reconnected by d_materialise_unique().
Signed-off-by: Anand Avati <redacted>
Signed-off-by: Miklos Szeredi <redacted>
---
fs/fuse/dir.c | 2 ++
1 file changed, 2 insertions(+)
From: Miklos Szeredi <redacted>
Use d_materialise_unique() instead of d_splice_alias(). This allows dentry
subtrees to be moved to a new place if there moved, even if something is
referencing a dentry in the subtree (open fd, cwd, etc..).
This will also allow us to drop a subtree if it is found to be replaced by
something else. In this case the disconnected subtree can later be
reconnected to its new location.
d_materialise_unique() ensures that a directory entry only ever has one
alias. We keep fc->inst_mutex around the calls for d_materialise_unique()
on directories to prevent a race with mkdir "stealing" the inode.
Signed-off-by: Miklos Szeredi <redacted>
---
fs/fuse/dir.c | 69 ++++++++++++++++++++++-------------------------------------
1 file changed, 26 insertions(+), 43 deletions(-)
@@ -267,26 +267,6 @@ int fuse_valid_type(int m)S_ISBLK(m)||S_ISFIFO(m)||S_ISSOCK(m);}-/*-*Addadirectoryinodetoadentry,ensuringthatnootherdentry-*referstothisinode.Calledwithfc->inst_mutex.-*/-staticstructdentry*fuse_d_add_directory(structdentry*entry,-structinode*inode)-{-structdentry*alias=d_find_alias(inode);-if(alias&&!(alias->d_flags&DCACHE_DISCONNECTED)){-/* This tries to shrink the subtree below alias */-fuse_invalidate_entry(alias);-dput(alias);-if(!hlist_empty(&inode->i_dentry))-returnERR_PTR(-EBUSY);-}else{-dput(alias);-}-returnd_splice_alias(inode,entry);-}-intfuse_lookup_name(structsuper_block*sb,u64nodeid,structqstr*name,structfuse_entry_out*outarg,structinode**inode){
From: Jeff Layton <hidden> Date: 2013-08-12 13:27:41
On Thu, 8 Aug 2013 17:24:42 +0200
Miklos Szeredi [off-list ref] wrote:
quoted hunk
From: Miklos Szeredi <redacted>
We check submounts before doing d_drop() on a non-empty directory dentry in
NFS (have_submounts()), but we do not exclude a racing mount.
Process A: have_submounts() -> returns false
Process B: mount() -> success
Process A: d_drop()
This patch prepares the ground for the fix by doing the following
operations all under the same rename lock:
have_submounts()
shrink_dcache_parent()
d_drop()
This is actually an optimization since have_submounts() and
shrink_dcache_parent() both traverse the same dentry tree separately.
Signed-off-by: Miklos Szeredi <redacted>
CC: David Howells <dhowells@redhat.com>
CC: Steven Whitehouse <redacted>
CC: Trond Myklebust <redacted>
CC: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
fs/dcache.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/dcache.h | 1 +
2 files changed, 158 insertions(+)
@@ -1224,6 +1224,163 @@ void shrink_dcache_parent(struct dentry * parent)}EXPORT_SYMBOL(shrink_dcache_parent);+staticint__check_submounts_and_drop(structdentry*parent,+structlist_head*dispose)+{+structdentry*this_parent;+structlist_head*next;+unsignedseq;+intfound=0;+intlocked=0;++seq=read_seqbegin(&rename_lock);+again:+this_parent=parent;+spin_lock(&this_parent->d_lock);+repeat:+next=this_parent->d_subdirs.next;+resume:+while(next!=&this_parent->d_subdirs){+structlist_head*tmp=next;+structdentry*dentry;++dentry=list_entry(tmp,structdentry,d_u.d_child);+next=tmp->next;++spin_lock_nested(&dentry->d_lock,DENTRY_D_LOCK_NESTED);+if(d_mountpoint(dentry)){+spin_unlock(&dentry->d_lock);+found=-EBUSY;+gotoout;+}++/*+*moveonlyzerorefcountdentriestothedisposelist.+*+*Thosewhicharepresentlyontheshrinklist,beingprocessed+*byshrink_dentry_list(),shouldn'tbemoved.Otherwisethe+*loopinshrink_dcache_parent()mightnotmakeanyprogress+*andloopforever.+*/+if(dentry->d_count){+dentry_lru_del(dentry);+}elseif(!(dentry->d_flags&DCACHE_SHRINK_LIST)){+dentry_lru_move_list(dentry,dispose);+dentry->d_flags|=DCACHE_SHRINK_LIST;+found++;+}+/*+*Wecanreturntothecallerifwehavefoundsome(this+*ensuresforwardprogress).We'llbecomingbacktofind+*therest.+*/+if(found&&need_resched()){+spin_unlock(&dentry->d_lock);+gotoout;+}++/*+*Descendalevelifthed_subdirslistisnon-empty.+*/+if(!list_empty(&dentry->d_subdirs)){+spin_unlock(&this_parent->d_lock);+spin_release(&dentry->d_lock.dep_map,1,_RET_IP_);+this_parent=dentry;+spin_acquire(&this_parent->d_lock.dep_map,0,1,_RET_IP_);+gotorepeat;+}++spin_unlock(&dentry->d_lock);+}+/*+*Alldoneatthislevel...ascendandresumethesearch.+*/+if(this_parent!=parent){+structdentry*child=this_parent;+this_parent=try_to_ascend(this_parent,locked,seq);+if(!this_parent)+gotorename_retry;+next=child->d_u.d_child.next;+gotoresume;+}+if(!locked&&read_seqretry(&rename_lock,seq)){+spin_unlock(&this_parent->d_lock);+gotorename_retry;+}+if(d_mountpoint(this_parent))+found=-EBUSY;+if(!found)+__d_drop(this_parent);+out:+spin_unlock(&this_parent->d_lock);++if(locked)+write_sequnlock(&rename_lock);+returnfound;++rename_retry:+if(found)+returnfound;+if(locked)+gotoagain;+locked=1;+write_seqlock(&rename_lock);+gotoagain;+}++/**+*check_submounts_and_drop-prunedcache,checkforsubmountsanddrop+*+*Alldoneasasingleatomicoperationrelativetohas_unlinked_ancestor().+*Returns0ifsuccessfullyunhashed@parent.Ifthereweresubmountsthen+*return-EBUSY.+*+*@dentry:dentrytopruneanddrop+*/+intcheck_submounts_and_drop(structdentry*dentry)+{+intret=0;++/* Negative dentries can be dropped without further checks */+if(!dentry->d_inode){+d_drop(dentry);+gotoout;+}++spin_lock(&dentry->d_lock);+if(d_unhashed(dentry))+gotoout_unlock;+if(list_empty(&dentry->d_subdirs)){+if(d_mountpoint(dentry)){+ret=-EBUSY;+gotoout_unlock;+}+__d_drop(dentry);+gotoout_unlock;+}+spin_unlock(&dentry->d_lock);++for(;;){+LIST_HEAD(dispose);+ret=__check_submounts_and_drop(dentry,&dispose);+if(!list_empty(&dispose))+shrink_dentry_list(&dispose);++if(ret<=0)+break;++cond_resched();+}++out:+returnret;++out_unlock:+spin_unlock(&dentry->d_lock);+gotoout;+}+EXPORT_SYMBOL(check_submounts_and_drop);+/***__d_alloc-allocateadcacheentry*@sb:filesystemitwillbelongto
@@ -251,6 +251,7 @@ extern void d_prune_aliases(struct inode *);/* test whether we have any submounts in a subdir tree */externinthave_submounts(structdentry*);+externintcheck_submounts_and_drop(structdentry*);/**Thisaddstheentrytothehashqueues.
Nice work...
Looks reasonable overall. The only (extremely minor) nit I have is that
it might be nice to replace some of the direct struct list_head
accesses with helper macros.
Acked-by: Jeff Layton <redacted>
From: Jeff Layton <hidden> Date: 2013-08-12 13:33:45
On Thu, 8 Aug 2013 17:24:43 +0200
Miklos Szeredi [off-list ref] wrote:
quoted hunk
From: Miklos Szeredi <redacted>
We check submounts before doing d_drop() on a non-empty directory dentry in
NFS (have_submounts()), but we do not exclude a racing mount. Nor do we
prevent mounts to be added to the disconnected subtree using relative paths
after the d_drop().
This patch fixes these issues by checking for unlinked (unhashed, non-root)
ancestors before proceeding with the mount. This is done after setting
DCACHE_MOUNTED on the soon-to-be mountpoint and with the rename seqlock
taken for write. This ensures that the only one of
check_submounts_and_drop() or has_unlinked_ancestor() can succeed.
Signed-off-by: Miklos Szeredi <redacted>
CC: David Howells <dhowells@redhat.com>
CC: Steven Whitehouse <redacted>
CC: Trond Myklebust <redacted>
CC: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
fs/dcache.c | 35 +++++++++++++++++++++++++++++++++++
fs/internal.h | 1 +
fs/namespace.c | 9 +++++++++
3 files changed, 45 insertions(+)
Looks reasonable. For posterity, it might be worth mentioning the
potential regression that you described earlier (racing with rename
from another host). That way if someone hits it in the future they
might be able to zero in on this change as the culprit more easily.
Acked-by: Jeff Layton <redacted>
From: Jeff Layton <hidden> Date: 2013-08-12 13:34:50
On Thu, 8 Aug 2013 17:24:46 +0200
Miklos Szeredi [off-list ref] wrote:
quoted hunk
From: Miklos Szeredi <redacted>
Do have_submounts(), shrink_dcache_parent() and d_drop() atomically.
check_submounts_and_drop() can deal with negative dentries and
non-directories as well.
Non-directories can also be mounted on. And just like directories we don't
want these to disappear with invalidation.
Signed-off-by: Miklos Szeredi <redacted>
CC: Trond Myklebust <redacted>
---
fs/nfs/dir.c | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
@@ -1135,14 +1135,13 @@ out_zap_parent:if(inode&&S_ISDIR(inode->i_mode)){/* Purge readdir caches. */nfs_zap_caches(inode);-/* If we have submounts, don't unhash ! */-if(have_submounts(dentry))-gotoout_valid;if(dentry->d_flags&DCACHE_DISCONNECTED)gotoout_valid;-shrink_dcache_parent(dentry);}-d_drop(dentry);+/* If we have submounts, don't unhash ! */+if(check_submounts_and_drop(dentry)!=0)+gotoout_valid;+dput(parent);dfprintk(LOOKUPCACHE,"NFS: %s(%s/%s) is invalid\n",__func__,dentry->d_parent->d_name.name,
From: Al Viro <viro@ZenIV.linux.org.uk> Date: 2013-08-21 05:40:56
On Thu, Aug 08, 2013 at 05:24:41PM +0200, Miklos Szeredi wrote:
Here's a series for fixing issues with d_drop on a directory dentry with
children and adding support for such dropped directories in fuse.
This one fixes a couple of issues I found with the previous incarnation and
split out the filesystem parts into individual patches.
OK in general, but I'm not happy with the proliferation of such iterators in
fs/dcache.c ;-/
We have have_submounts(), select_parent() and d_genocide(), with one more
such sucker added to the pile. Sure, most of the callers of have_submounts()
are gone after that patchset, but we still have several left:
fs/autofs4/dev-ioctl.c:542: err = have_submounts(path.dentry);
fs/autofs4/root.c:381: if (have_submounts(dentry)) {
fs/autofs4/waitq.c:338: if (have_submounts(dentry))
fs/nilfs2/super.c:1010: if (have_submounts(root_dentry))
The thing is, the same race with mount() does, AFAICS, apply at least to
autofs4 call sites.
And I would like to understand what nilfs one is trying to do...
Unless I'm seriously misreading that code, it's *not* on any kind of a hot
path, so I really wonder why don't we simply do shrink_dcache_parent() +
check if d_count has dropped to 1, without trying to look for submounts
first - if we have any, shrink_dcache_parent() is simply going to leave
us with d_count > 1 and that's it. Actually, it's cheaper that way -
no need to walk the tree twice. Moreover, checking for d_count == 1
case first is also pointless - in that case we have no children at all
and shrink_dcache_parent() will return immediately. Could nilfs folks
explain why (and if) we need to bother with all that complexity there?
We are really getting too many tree walkers in fs/dcache.c and
all that duplication is the prime breeding ground for bugs ;-/
On Wed, 21 Aug 2013 06:40:56 +0100, Al Viro wrote:
And I would like to understand what nilfs one is trying to do...
Unless I'm seriously misreading that code, it's *not* on any kind of a hot
path, so I really wonder why don't we simply do shrink_dcache_parent() +
check if d_count has dropped to 1, without trying to look for submounts
first - if we have any, shrink_dcache_parent() is simply going to leave
us with d_count > 1 and that's it. Actually, it's cheaper that way -
no need to walk the tree twice.
We are really getting too many tree walkers in fs/dcache.c and
all that duplication is the prime breeding ground for bugs ;-/
I agree that we can eliminate have_submounts() from nilfs. Please
apply the following patch if you hope so.
Moreover, checking for d_count == 1
case first is also pointless - in that case we have no children at all
and shrink_dcache_parent() will return immediately.
This also looks true. I will confirm whether we can remove the
pre-check for d_count == 1 case.
Regards,
Ryusuke Konishi
--
From: Ryusuke Konishi <redacted>
Date: Thu, 22 Aug 2013 01:53:03 +0900
Subject: [PATCH] nilfs2: do not use have_submounts()
The pre-check with have_submounts() in
nilfs_try_to_shrink_tree(root_dentry) is eliminable since the return
value of the function does not depend on whether the root_dentry has
sub-mounts or not. Removing the have_submount() pre-check may cause
unnecessary dcache shrinking, but the impact looks limited because
nilfs_try_to_shrink_tree() is not called in usual operation.
So, we remove use of have_submounts() from nilfs.
Signed-off-by: Ryusuke Konishi <redacted>
---
fs/nilfs2/super.c | 2 --
1 file changed, 2 deletions(-)
From: Al Viro <viro@ZenIV.linux.org.uk> Date: 2013-08-21 21:00:38
On Thu, Aug 22, 2013 at 05:04:59AM +0900, Ryusuke Konishi wrote:
On Wed, 21 Aug 2013 06:40:56 +0100, Al Viro wrote:
quoted
And I would like to understand what nilfs one is trying to do...
Unless I'm seriously misreading that code, it's *not* on any kind of a hot
path, so I really wonder why don't we simply do shrink_dcache_parent() +
check if d_count has dropped to 1, without trying to look for submounts
first - if we have any, shrink_dcache_parent() is simply going to leave
us with d_count > 1 and that's it. Actually, it's cheaper that way -
no need to walk the tree twice.
quoted
We are really getting too many tree walkers in fs/dcache.c and
all that duplication is the prime breeding ground for bugs ;-/
I agree that we can eliminate have_submounts() from nilfs. Please
apply the following patch if you hope so.
quoted
Moreover, checking for d_count == 1
case first is also pointless - in that case we have no children at all
and shrink_dcache_parent() will return immediately.
This also looks true. I will confirm whether we can remove the
pre-check for d_count == 1 case.
On Wed, 21 Aug 2013 22:00:38 +0100, Al Viro wrote:
On Thu, Aug 22, 2013 at 05:04:59AM +0900, Ryusuke Konishi wrote:
quoted
On Wed, 21 Aug 2013 06:40:56 +0100, Al Viro wrote:
quoted
And I would like to understand what nilfs one is trying to do...
Unless I'm seriously misreading that code, it's *not* on any kind of a hot
path, so I really wonder why don't we simply do shrink_dcache_parent() +
check if d_count has dropped to 1, without trying to look for submounts
first - if we have any, shrink_dcache_parent() is simply going to leave
us with d_count > 1 and that's it. Actually, it's cheaper that way -
no need to walk the tree twice.
quoted
We are really getting too many tree walkers in fs/dcache.c and
all that duplication is the prime breeding ground for bugs ;-/
I agree that we can eliminate have_submounts() from nilfs. Please
apply the following patch if you hope so.
quoted
Moreover, checking for d_count == 1
case first is also pointless - in that case we have no children at all
and shrink_dcache_parent() will return immediately.
This also looks true. I will confirm whether we can remove the
pre-check for d_count == 1 case.
Umm... How about the following, then?
I confirmed the pre-check is also eliminable.
Then, yes, the following change is ok.
Reviewed-by: Ryusuke Konishi <redacted>
Regards,
Ryusuke Konishi
This is an ioctl() asking whether we have anything mounted on the autofs
mount. Using have_submounts() and then a separate follow_down() looks
racy. have_submounts() could succeed and then follow_down() could fail.
Or the other way round. Shouldn't the two cases be handled separately
here? If the autofs is a just a simple trigger then use follow_down().
If it's a multi-mount thing, then use have_submounts().
What is the userspace automount daemon using this for? Do we really
need the recursive check for submounts?
fs/autofs4/root.c:381: if (have_submounts(dentry)) {
Here it explicitly says it's for v5 and for rootless mutli-mount. So
for example:
/mnt/auto/ root of an indirect mount
/mnt/auto/foo directory with DCACHE_NEED_AUTOMOUNT
/mnt/auto/foo/bar directory without DCACHE_NEED_AUTOMOUNT
/mnt/auto/foo/bar/baz directory with an automount trigger mounted on it
In this case when d_automount for "foo" is called we don't call the
userspace daemon because things are mounted under foo. If there was no
trigger under baz, then we would try to handle "foo" as an indirect
mount and call userspace.
But it's pretty confusing. Do we really *ever* need to call automount
on "foo" if it was part of a multi-mount thing?
fs/autofs4/waitq.c:338: if (have_submounts(dentry))
And here we re-validate the thing after taking another autofs4 lock.
Why this double checking?
Thanks,
Miklos
This is an ioctl() asking whether we have anything mounted on the autofs
mount. Using have_submounts() and then a separate follow_down() looks
racy. have_submounts() could succeed and then follow_down() could fail.
Or the other way round. Shouldn't the two cases be handled separately
here? If the autofs is a just a simple trigger then use follow_down().
If it's a multi-mount thing, then use have_submounts().
Right but IIRC I don't think I actually use the returned s_magic ATM but
I use the return of have_submounts() a lot.
What is the userspace automount daemon using this for? Do we really
need the recursive check for submounts?
quoted
fs/autofs4/root.c:381: if (have_submounts(dentry)) {
Here it explicitly says it's for v5 and for rootless mutli-mount. So
for example:
/mnt/auto/ root of an indirect mount
or the root of direct mount for that matter.
/mnt/auto/foo directory with DCACHE_NEED_AUTOMOUNT
/mnt/auto/foo/bar directory without DCACHE_NEED_AUTOMOUNT
/mnt/auto/foo/bar/baz directory with an automount trigger mounted on it
In this case when d_automount for "foo" is called we don't call the
userspace daemon because things are mounted under foo. If there was no
trigger under baz, then we would try to handle "foo" as an indirect
mount and call userspace.
But it's pretty confusing. Do we really *ever* need to call automount
on "foo" if it was part of a multi-mount thing?
That's right, the directory isn't simple_empty() so there's no callback.
The problem is we can't just use the fact that the directory is empty to
determine that there are no mounts at all underneath.
I understand your thinking, about deciding whether to callback to the
daemon, but that's not what the ioctl above is used for.
The main use is to be able to find out if the given directory is a
mountpoint as defined by the description in the comment above the
function. This saves having to scan the mount table to find out and is a
huge saving on systems with lots of mounts. In the past I've often
needed an answer the question "is this an autofs mount or some other
type" and that's why I stick s_magic in the return as well.
quoted
fs/autofs4/waitq.c:338: if (have_submounts(dentry))
And here we re-validate the thing after taking another autofs4 lock.
Why this double checking?
This is a different case and is often not in play at times when autofs
is checking if the directory is a "mountpoint". Such as when trying to
re-construct a tree of mounts at startup.
The check in waitq.c above "is" used to validate the need to callback to
the daemon to request a mount.
As I said, any suggestions how to achieve this without calling
have_submounts() are welcome.
Ian
This is an ioctl() asking whether we have anything mounted on the autofs
mount. Using have_submounts() and then a separate follow_down() looks
racy. have_submounts() could succeed and then follow_down() could fail.
Or the other way round. Shouldn't the two cases be handled separately
here? If the autofs is a just a simple trigger then use follow_down().
If it's a multi-mount thing, then use have_submounts().
Right but IIRC I don't think I actually use the returned s_magic ATM but
I use the return of have_submounts() a lot.
quoted
What is the userspace automount daemon using this for? Do we really
need the recursive check for submounts?
quoted
fs/autofs4/root.c:381: if (have_submounts(dentry)) {
Here it explicitly says it's for v5 and for rootless mutli-mount. So
for example:
/mnt/auto/ root of an indirect mount
or the root of direct mount for that matter.
quoted
/mnt/auto/foo directory with DCACHE_NEED_AUTOMOUNT
/mnt/auto/foo/bar directory without DCACHE_NEED_AUTOMOUNT
/mnt/auto/foo/bar/baz directory with an automount trigger mounted on it
In this case when d_automount for "foo" is called we don't call the
userspace daemon because things are mounted under foo. If there was no
trigger under baz, then we would try to handle "foo" as an indirect
mount and call userspace.
But it's pretty confusing. Do we really *ever* need to call automount
on "foo" if it was part of a multi-mount thing?
That's right, the directory isn't simple_empty() so there's no callback.
The problem is we can't just use the fact that the directory is empty to
determine that there are no mounts at all underneath.
I understand your thinking, about deciding whether to callback to the
daemon, but that's not what the ioctl above is used for.
The main use is to be able to find out if the given directory is a
mountpoint as defined by the description in the comment above the
function. This saves having to scan the mount table to find out and is a
huge saving on systems with lots of mounts. In the past I've often
needed an answer the question "is this an autofs mount or some other
type" and that's why I stick s_magic in the return as well.
quoted
quoted
fs/autofs4/waitq.c:338: if (have_submounts(dentry))
And here we re-validate the thing after taking another autofs4 lock.
Why this double checking?
This is a different case and is often not in play at times when autofs
is checking if the directory is a "mountpoint". Such as when trying to
re-construct a tree of mounts at startup.
The check in waitq.c above "is" used to validate the need to callback to
the daemon to request a mount.
As I said, any suggestions how to achieve this without calling
have_submounts() are welcome.
You know, may_umount_tree() would do this for me (I think) and would be
much less expensive ....
From: Ian Kent <raven@themaw.net> Date: 2013-09-01 00:56:29
On Fri, 2013-08-30 at 10:59 +0200, Miklos Szeredi wrote:
On Fri, Aug 30, 2013 at 1:44 AM, Ian Kent [off-list ref] wrote:
quoted
quoted
The check in waitq.c above "is" used to validate the need to callback to
the daemon to request a mount.
Okay. But then shouldn't the check be "if (d_mountpoint(dentry)) valid = 0;" ?
I don't think so.
But again, may_umount() might do what's needed here too.
I don't think this is enough because it doesn't cover the case where the
dentry is not simple_empty() but has no mounts below. That's not a
normal use case but could happen if the daemon crashed at just the wrong
time, encountered an error condition that didn't allow it to cleanup
directories, or a user umounted triggers within the tree before starting
the daemon.
From: Ian Kent <raven@themaw.net> Date: 2013-09-01 01:00:29
On Sun, 2013-09-01 at 08:56 +0800, Ian Kent wrote:
On Fri, 2013-08-30 at 10:59 +0200, Miklos Szeredi wrote:
quoted
On Fri, Aug 30, 2013 at 1:44 AM, Ian Kent [off-list ref] wrote:
quoted
quoted
The check in waitq.c above "is" used to validate the need to callback to
the daemon to request a mount.
Okay. But then shouldn't the check be "if (d_mountpoint(dentry)) valid = 0;" ?
I don't think so.
But again, may_umount() might do what's needed here too.
Oh, hang on, may_umount() can't be used for the root-less multi-mount
case at all, since there's no vfsmount at the base of the tree the check
can't be restricted to just the tree of subdirs that needs to be
checked. That's been a problem for me for a long time.
I don't think this is enough because it doesn't cover the case where the
dentry is not simple_empty() but has no mounts below. That's not a
normal use case but could happen if the daemon crashed at just the wrong
time, encountered an error condition that didn't allow it to cleanup
directories, or a user umounted triggers within the tree before starting
the daemon.