NFSD: fix cannot umounting mount points under pseudo root
Subsystems (3)
RFC PATCH 1/1 NFSD: fix cannot umounting mount points under pseudo root
If there are some mount points(not exported for nfs) under a pseudo root, after client's operation of those entry under the root, anyone can unmount those mount points until nfsd stop.
# cat /etc/exports /nfs/xfs *(rw,insecure,no_subtree_check,no_root_squash) /nfs/pnfs *(rw,insecure,no_subtree_check,no_root_squash) # ll /nfs/ total 0 drwxr-xr-x. 3 root root 84 Apr 21 22:27 pnfs drwxr-xr-x. 3 root root 84 Apr 21 22:27 test drwxr-xr-x. 2 root root 6 Apr 20 22:01 xfs # mount /dev/sde /nfs/test # df Filesystem 1K-blocks Used Available Use% Mounted on ...... /dev/sdd 1038336 32944 1005392 4% /nfs/pnfs /dev/sdc 10475520 32928 10442592 1% /nfs/xfs /dev/sde 999320 1284 929224 1% /nfs/test # mount -t nfs 127.0.0.1:/nfs/ /mnt # ll /mnt/*/ /mnt/pnfs/: total 0 -rw-r--r--. 1 root root 0 Apr 21 22:23 attr drwxr-xr-x. 2 root root 6 Apr 21 22:19 tmp
/mnt/xfs/:
total 0
# umount /nfs/test/
umount: /nfs/test/: target is busy
(In some cases useful info about processes that
use the device is found by lsof(8) or fuser(1).)I don't think that's user expect, they want umount /nfs/test/.
It's caused by exports cache of nfsd holds the reference of the path (here is /nfs/test/), so, it can't umounted until nfsd stop.
There is one strange thing exist, the export cache status of /nfs/test/ is CACHE_NEGATIVE, also hold the reference of the path. I think nfsd should not hold the reference when CACHE_NEGATIVE.
I can't find a better way to put the reference, just path it after svc_export_update().
Any comments are welcome, thanks.
--- fs/nfsd/export.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-)
fs/nfsd/export.c +9 -3
@@ -793,9 +793,15 @@ svc_export_update(struct svc_export *new, struct svc_export *old)int hash =svc_export_hash (old); ch =sunrpc_cache_update (old->cd, &new->h, &old->h, hash); -if (ch) - return container_of(ch, struct svc_export, h); -else +if (ch) { + struct svc_export *exp = container_of(ch, struct svc_export, h); +if (test_bit (CACHE_NEGATIVE , &exp->h.flags)) { +path_put (&exp->ex_path); + exp->ex_path.mnt =NULL ; + exp->ex_path.dentry =NULL ; + } +return exp; + }else return NULL ; }
Signature · 1 line
2.3.5
Quoted 26 lines · Kinglong Mee
If there are some mount points(not exported for nfs) under a pseudo root, after client's operation of those entry under the root, anyone can unmount those mount points until nfsd stop.
# cat /etc/exports /nfs/xfs *(rw,insecure,no_subtree_check,no_root_squash) /nfs/pnfs *(rw,insecure,no_subtree_check,no_root_squash) # ll /nfs/ total 0 drwxr-xr-x. 3 root root 84 Apr 21 22:27 pnfs drwxr-xr-x. 3 root root 84 Apr 21 22:27 test drwxr-xr-x. 2 root root 6 Apr 20 22:01 xfs # mount /dev/sde /nfs/test # df Filesystem 1K-blocks Used Available Use% Mounted on ...... /dev/sdd 1038336 32944 1005392 4% /nfs/pnfs /dev/sdc 10475520 32928 10442592 1% /nfs/xfs /dev/sde 999320 1284 929224 1% /nfs/test # mount -t nfs 127.0.0.1:/nfs/ /mnt # ll /mnt/*/ /mnt/pnfs/: total 0 -rw-r--r--. 1 root root 0 Apr 21 22:23 attr drwxr-xr-x. 2 root root 6 Apr 21 22:19 tmp
Quoted 8 lines
/mnt/xfs/:
total 0# umount /nfs/test/ umount: /nfs/test/: target is busy (In some cases useful info about processes that use the device is found by lsof(8) or fuser(1).)I don't think that's user expect, they want umount /nfs/test/.
I agree that this is annoying.
Quoted 2 lines
It's caused by exports cache of nfsd holds the reference of the path (here is /nfs/test/), so, it can't umounted until nfsd stop.
A cache flush (exportfs -f) should also do the job, as long as you then manage to unmount before the cache entry is re-added. (So I suppose if you wanted to be completely safe:
exportfs -f
killall -STOP rpc.mountd
umount /nfs/test/
killall -CONT rpc.mountd
)
Quoted 6 lines
There is one strange thing exist, the export cache status of /nfs/test/ is
CACHE_NEGATIVE, also hold the reference of the path. I think nfsd should not hold the reference whenCACHE_NEGATIVE.I can't find a better way to put the reference, just path it after
svc_export_update().
Unfortunately ex_path is part of the key--it's what we need to do lookups in this cache. I'm surprised something like ls /mnt/ still works after this.
I'm not sure what to do. I wonder if we really need the struct path as part of the key, or if we could get away with just a string path? That's what we're actually passing to userspace, after all.
--b.
Quoted 33 lines
Any comments are welcome, thanks.
Signed-off-by Kinglong Mee <[email protected]>--- fs/nfsd/export.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-)fs/nfsd/export.c +9
-3@@ -793,9 +793,15 @@ svc_export_update(struct svc_export *new, struct svc_export *old)int hash =svc_export_hash (old); ch =sunrpc_cache_update (old->cd, &new->h, &old->h, hash); -if (ch) - return container_of(ch, struct svc_export, h); -else +if (ch) { + struct svc_export *exp = container_of(ch, struct svc_export, h); +if (test_bit (CACHE_NEGATIVE , &exp->h.flags)) { +path_put (&exp->ex_path); + exp->ex_path.mnt =NULL ; + exp->ex_path.dentry =NULL ; + } +return exp; + }else return NULL ; }Signature · 1 line
2.3.5
On Tue, 21 Apr 2015 17:54:17 -0400 "J. Bruce Fields" <[email protected]>
Quoted 8 lines
Unfortunately
ex_pathis part of the key--it's what we need to do lookups in this cache. I'm surprised something like ls/mnt/still works after this.I'm not sure what to do. I wonder if we really need the struct path as part of the key, or if we could get away with just a string path? That's what we're actually passing to userspace, after all.
I wonder if we can use Al's new 'fs_pin' stuff, so that when the filesystem is unmounted we get a call-back and can release the filesystem...
NeilBrown
Quoted 45 lines · J. Bruce Fields
Quoted 26 lines · Kinglong Mee
If there are some mount points(not exported for nfs) under a pseudo root, after client's operation of those entry under the root, anyone can unmount those mount points until nfsd stop.
# cat /etc/exports /nfs/xfs *(rw,insecure,no_subtree_check,no_root_squash) /nfs/pnfs *(rw,insecure,no_subtree_check,no_root_squash) # ll /nfs/ total 0 drwxr-xr-x. 3 root root 84 Apr 21 22:27 pnfs drwxr-xr-x. 3 root root 84 Apr 21 22:27 test drwxr-xr-x. 2 root root 6 Apr 20 22:01 xfs # mount /dev/sde /nfs/test # df Filesystem 1K-blocks Used Available Use% Mounted on ...... /dev/sdd 1038336 32944 1005392 4% /nfs/pnfs /dev/sdc 10475520 32928 10442592 1% /nfs/xfs /dev/sde 999320 1284 929224 1% /nfs/test # mount -t nfs 127.0.0.1:/nfs/ /mnt # ll /mnt/*/ /mnt/pnfs/: total 0 -rw-r--r--. 1 root root 0 Apr 21 22:23 attr drwxr-xr-x. 2 root root 6 Apr 21 22:19 tmpQuoted 8 lines
/mnt/xfs/:
total 0# umount /nfs/test/ umount: /nfs/test/: target is busy (In some cases useful info about processes that use the device is found by lsof(8) or fuser(1).)I don't think that's user expect, they want umount /nfs/test/.
I agree that this is annoying.
Quoted 2 lines
It's caused by exports cache of nfsd holds the reference of the path (here is /nfs/test/), so, it can't umounted until nfsd stop.
A cache flush (exportfs -f) should also do the job, as long as you then manage to unmount before the cache entry is re-added. (So I suppose if you wanted to be completely safe:
Quoted 5 lines
exportfs -f
killall -STOP rpc.mountd
umount /nfs/test/
killall -CONT rpc.mountd
)
Yes, that's right.
Quoted 11 lines
Quoted 7 lines
There is one strange thing exist, the export cache status of /nfs/test/ is
CACHE_NEGATIVE, also hold the reference of the path. I think nfsd should not hold the reference whenCACHE_NEGATIVE.I can't find a better way to put the reference, just path it after
svc_export_update().Unfortunately
ex_pathis part of the key--it's what we need to do lookups in this cache. I'm surprised something like ls/mnt/still works after this.
I just do some simplify tests, and can't make sure everything is right.
Quoted 4 lines
I'm not sure what to do. I wonder if we really need the struct path as part of the key, or if we could get away with just a string path? That's what we're actually passing to userspace, after all.
I have do a simplify grep of ex_path, it is used in many places. I don't think a string path can resolve the problem. Reference of dentry/mnt is like a cache, avoids re-finding of them, it is necessary to store them in svc_export.
Neil points out another way of 'fs_pin', I will check that.
thanks,
Kinglong Mee
Quoted 37 lines
--b.
Quoted 33 lines
Any comments are welcome, thanks.
Signed-off-by Kinglong Mee <[email protected]>--- fs/nfsd/export.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-)fs/nfsd/export.c +9
-3@@ -793,9 +793,15 @@ svc_export_update(struct svc_export *new, struct svc_export *old)int hash =svc_export_hash (old); ch =sunrpc_cache_update (old->cd, &new->h, &old->h, hash); -if (ch) - return container_of(ch, struct svc_export, h); -else +if (ch) { + struct svc_export *exp = container_of(ch, struct svc_export, h); +if (test_bit (CACHE_NEGATIVE , &exp->h.flags)) { +path_put (&exp->ex_path); + exp->ex_path.mnt =NULL ; + exp->ex_path.dentry =NULL ; + } +return exp; + }else return NULL ; }Signature · 1 line
2.3.5
Quoted 69 lines · Kinglong Mee
Quoted 45 lines · J. Bruce Fields
Quoted 26 lines · Kinglong Mee
If there are some mount points(not exported for nfs) under a pseudo root, after client's operation of those entry under the root, anyone can unmount those mount points until nfsd stop.
# cat /etc/exports /nfs/xfs *(rw,insecure,no_subtree_check,no_root_squash) /nfs/pnfs *(rw,insecure,no_subtree_check,no_root_squash) # ll /nfs/ total 0 drwxr-xr-x. 3 root root 84 Apr 21 22:27 pnfs drwxr-xr-x. 3 root root 84 Apr 21 22:27 test drwxr-xr-x. 2 root root 6 Apr 20 22:01 xfs # mount /dev/sde /nfs/test # df Filesystem 1K-blocks Used Available Use% Mounted on ...... /dev/sdd 1038336 32944 1005392 4% /nfs/pnfs /dev/sdc 10475520 32928 10442592 1% /nfs/xfs /dev/sde 999320 1284 929224 1% /nfs/test # mount -t nfs 127.0.0.1:/nfs/ /mnt # ll /mnt/*/ /mnt/pnfs/: total 0 -rw-r--r--. 1 root root 0 Apr 21 22:23 attr drwxr-xr-x. 2 root root 6 Apr 21 22:19 tmpQuoted 8 lines
/mnt/xfs/:
total 0# umount /nfs/test/ umount: /nfs/test/: target is busy (In some cases useful info about processes that use the device is found by lsof(8) or fuser(1).)I don't think that's user expect, they want umount /nfs/test/.
I agree that this is annoying.
Quoted 2 lines
It's caused by exports cache of nfsd holds the reference of the path (here is /nfs/test/), so, it can't umounted until nfsd stop.
A cache flush (exportfs -f) should also do the job, as long as you then manage to unmount before the cache entry is re-added. (So I suppose if you wanted to be completely safe:
Quoted 5 lines
exportfs -f
killall -STOP rpc.mountd
umount /nfs/test/
killall -CONT rpc.mountd
)Yes, that's right.
Quoted 11 lines
Quoted 7 lines
There is one strange thing exist, the export cache status of /nfs/test/ is
CACHE_NEGATIVE, also hold the reference of the path. I think nfsd should not hold the reference whenCACHE_NEGATIVE.I can't find a better way to put the reference, just path it after
svc_export_update().Unfortunately
ex_pathis part of the key--it's what we need to do lookups in this cache. I'm surprised something like ls/mnt/still works after this.I just do some simplify tests, and can't make sure everything is right.
Quoted 1 line
Quoted 6 lines
Quoted 3 lines
I'm not sure what to do. I wonder if we really need the struct path as part of the key, or if we could get away with just a string path? That's what we're actually passing to userspace, after all.
I have do a simplify grep of
ex_path, it is used in many places. I don't think a string path can resolve the problem.
I was thinking we could still keep ex_path, but as part of the "value" rather than part of the "key"? I'm not sure if that works.
Quoted 4 lines
Reference of dentry/mnt is like a cache, avoids re-finding of them, it is necessary to store them in
svc_export.Neil points out another way of '
fs_pin', I will check that.
Yes, that'd be interesting. On a very quick look--I don't understand what it's meant to do at all. But if it does provide a way to get a callback on umount, that'd certainly be interesting....
--b.
Quoted 45 lines
thanks,
Kinglong MeeQuoted 34 lines
--b.
Quoted 31 lines
Any comments are welcome, thanks.
Signed-off-by Kinglong Mee <[email protected]>--- fs/nfsd/export.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-)fs/nfsd/export.c +9
-3@@ -793,9 +793,15 @@ svc_export_update(struct svc_export *new, struct svc_export *old)int hash =svc_export_hash (old); ch =sunrpc_cache_update (old->cd, &new->h, &old->h, hash); -if (ch) - return container_of(ch, struct svc_export, h); -else +if (ch) { + struct svc_export *exp = container_of(ch, struct svc_export, h); +if (test_bit (CACHE_NEGATIVE , &exp->h.flags)) { +path_put (&exp->ex_path); + exp->ex_path.mnt =NULL ; + exp->ex_path.dentry =NULL ; + } +return exp; + }else return NULL ; }Quoted 3 lines
Quoted 2 lines
Signature · 1 line
2.3.5--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Quoted 8 lines · On Wed, 22 Apr 2015 11:07:03 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 4 lines
Reference of dentry/mnt is like a cache, avoids re-finding of them, it is necessary to store them in
svc_export.Neil points out another way of '
fs_pin', I will check that.Yes, that'd be interesting. On a very quick look--I don't understand what it's meant to do at all. But if it does provide a way to get a callback on umount, that'd certainly be interesting....
Yeah, on a quick look it isn't really obvious at all.
But I didn't read the code. I read
https://lwn.net/Articles/636730/
which says:
In its place is a mechanism by which an object can be added to a vfsmount structure (which represents a mounted filesystem); that object supports only one method: kill(). These "pin" objects hang around until the final reference to the vfsmount goes away, at which point each one's kill() function is called. It thus is a clean mechanism for performing cleanup when a filesystem goes away.
This is used to close "BSD process accounting" files on umount, and sound like the perfect interface for flushing things from the sunrpc cache on umount.
NeilBrown
Quoted 25 lines · NeilBrown
Quoted 8 lines · On Wed, 22 Apr 2015 11:07:03 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 4 lines
Reference of dentry/mnt is like a cache, avoids re-finding of them, it is necessary to store them in
svc_export.Neil points out another way of '
fs_pin', I will check that.Yes, that'd be interesting. On a very quick look--I don't understand what it's meant to do at all. But if it does provide a way to get a callback on umount, that'd certainly be interesting....
Yeah, on a quick look it isn't really obvious at all.
But I didn't read the code. I read
https://lwn.net/Articles/636730/which says:
In its place is a mechanism by which an object can be added to a vfsmount structure (which represents a mounted filesystem); that object supports only one method:
kill(). These "pin" objects hang around until the final reference to the vfsmount goes away, at which point each one'skill()function is called. It thus is a clean mechanism for performing cleanup when a filesystem goes away.
Quoted 3 lines
This is used to close "BSD process accounting" files on umount, and sound like the perfect interface for flushing things from the sunrpc cache on umount.
I have review those codes in fs/fs_pin.c and kernel/acct.c. 'fs_pin' is used to fix the race between file->f_path.mnt for acct and umount, file->f_path.mnt just holds the reference of vfsmount but not keep busy, umount will check the reference and return busy.
The logical is same as sunrpc cache for exports.
Before commit 3064c3563b ("death to mnt_pinned"), acct uses a hack method, acct get a pin to vfsmount and then put the reference, so umount finds no other reference and callback those pins in vfsmount, at last umount success.
After that commit, besides pin to original vfsmount and put the reference of the original vfsmount, acct clone the vfsmount storing in file->f_path.mnt.
The different between those two methods is the value of file->f_path.mnt, the first one, it contains the original vfsmnt without reference, the second one, contains a cloned vfsmnt with reference.
I have test the first method, pins to vfsmount for all exports cache, nfsd can work but sometimes will delay or hang at rpc.mountd's calling of name_to_handle_at, I can't find the reason.
Also test the second method, there are many problem caused by the cloned vfsmount, mnt_clone_internal() change mnt_root values to the path dentry.
Those cases are tested for all exports cache, but, I think nfsd should put the reference of vfsmount when finding exports upcall fail. The success exports cache should also take it.
The following is a draft of the first method.
thanks,
Kinglong Mee
----------------------------------------------------------------------
fs/fs_pin.c +3 -0
@@ -17,6 +17,7 @@ void pin_remove(struct fs_pin *pin)wake_up_locked (&pin->wait);spin_unlock_irq (&pin->wait.lock); } +EXPORT_SYMBOL (pin_remove);void pin_insert_group (struct fs_pin *pin,struct vfsmount *m,struct hlist_head *p) {
@@ -26,11 +27,13 @@ void pin_insert_group(struct fs_pin *pin, struct vfsmount *m, struct hlist_headhlist_add_head (&pin->m_list, &real_mount (m)->mnt_pins);spin_unlock (&pin_lock); } +EXPORT_SYMBOL (pin_insert_group);void pin_insert (struct fs_pin *pin,struct vfsmount *m) {pin_insert_group (pin, m, &m->mnt_sb->s_pins); } +EXPORT_SYMBOL (pin_insert);void pin_kill (struct fs_pin *p) {
fs/nfsd/export.c +13 -2
@@ -309,7 +309,7 @@ static void nfsd4_fslocs_free(struct nfsd4_fs_locations *fsloc)static void svc_export_put (struct kref *ref) { struct svc_export *exp = container_of(ref, struct svc_export, h.ref); -path_put (&exp->ex_path); +pin_remove (&exp->ex_pin);auth_domain_put (exp->ex_client);nfsd4_fslocs_free (&exp->ex_fslocs);kfree (exp->ex_uuid);
@@ -695,15 +695,26 @@ static int svc_export_match(struct cache_head *a, struct cache_head *b) orig->ex_path.mnt == new->ex_path.mnt; } +static void export_pin_kill (struct fs_pin *pin) +{ + struct svc_export *exp = container_of(pin, struct svc_export, ex_pin); + +write_lock (&exp->cd->hash_lock); +cache_mark_expired (&exp->h); +pin_remove (pin); +write_unlock (&exp->cd->hash_lock); +} +static void svc_export_init (struct cache_head *cnew,struct cache_head *citem) { struct svc_export *new = container_of(cnew, struct svc_export, h); struct svc_export *item = container_of(citem, struct svc_export, h); +init_fs_pin (&new->ex_pin, export_pin_kill);kref_get (&item->ex_client->ref); new->ex_client = item->ex_client; new->ex_path = item->ex_path; -path_get (&item->ex_path); +pin_insert_group (&new->ex_pin, new->ex_path.mnt,NULL ); new->ex_fslocs.locations =NULL ; new->ex_fslocs.locations_count =0 ; new->ex_fslocs.migrated =0 ;
fs/nfsd/export.h +2 -0
@@ -4,6 +4,7 @@#ifndef NFSD_EXPORT_H #define NFSD_EXPORT_H +#include <linux/fs_pin.h> #include <linux/sunrpc/cache.h> #include <uapi/linux/nfsd/export.h>
@@ -49,6 +50,7 @@ struct svc_export {struct auth_domain * ex_client;int ex_flags;struct path ex_path; +struct fs_pin ex_pin;kuid_t ex_anon_uid;kgid_t ex_anon_gid;int ex_fsid;
include/linux/sunrpc/cache.h +5 -0
@@ -206,6 +206,11 @@ static inline int cache_is_expired(struct cache_detail *detail, struct cache_hea (detail->flush_time > h->last_refresh); } +static inline void cache_mark_expired (struct cache_head *h) +{ + h->expiry_time =seconds_since_boot () -1 ; +} +extern int cache_check (struct cache_detail *detail,struct cache_head *h,struct cache_req *rqstp);extern void cache_flush (void );
Quoted 45 lines · On Thu, 23 Apr 2015 20:52:40 +0800 Kinglong Mee <[email protected]> wrote:
Quoted 25 lines · NeilBrown
Quoted 8 lines · On Wed, 22 Apr 2015 11:07:03 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 4 lines
Reference of dentry/mnt is like a cache, avoids re-finding of them, it is necessary to store them in
svc_export.Neil points out another way of '
fs_pin', I will check that.Yes, that'd be interesting. On a very quick look--I don't understand what it's meant to do at all. But if it does provide a way to get a callback on umount, that'd certainly be interesting....
Yeah, on a quick look it isn't really obvious at all.
But I didn't read the code. I read
https://lwn.net/Articles/636730/which says:
In its place is a mechanism by which an object can be added to a vfsmount structure (which represents a mounted filesystem); that object supports only one method:
kill(). These "pin" objects hang around until the final reference to the vfsmount goes away, at which point each one'skill()function is called. It thus is a clean mechanism for performing cleanup when a filesystem goes away.Quoted 3 lines
This is used to close "BSD process accounting" files on umount, and sound like the perfect interface for flushing things from the sunrpc cache on umount.
I have review those codes in
fs/fs_pin.candkernel/acct.c. 'fs_pin' is used to fix the race betweenfile->f_path.mntfor acct and umount,file->f_path.mntjust holds the reference of vfsmount but not keep busy, umount will check the reference and return busy.The logical is same as sunrpc cache for exports.
Before commit
3064c3563b("death tomnt_pinned"), acct uses a hack method, acct get a pin to vfsmount and then put the reference, so umount finds no other reference and callback those pins in vfsmount, at last umount success.After that commit, besides pin to original vfsmount and put the reference of the original vfsmount, acct clone the vfsmount storing in
file->f_path.mnt.
Quoted 7 lines
The different between those two methods is the value of
file->f_path.mnt, the first one, it contains the original vfsmnt without reference, the second one, contains a cloned vfsmnt with reference.I have test the first method, pins to vfsmount for all exports cache, nfsd can work but sometimes will delay or hang at rpc.mountd's calling of
name_to_handle_at, I can't find the reason.
A kernel stack trace of exactly where it is hanging would help a lot.
Quoted 9 lines
Also test the second method, there are many problem caused by the cloned vfsmount,
mnt_clone_internal()changemnt_rootvalues to the path dentry.Those cases are tested for all exports cache, but, I think nfsd should put the reference of vfsmount when finding exports upcall fail. The success exports cache should also take it.
The following is a draft of the first method.
I think the first method sounds a lot better than the second.
Quoted 47 lines
thanks,
Kinglong Mee----------------------------------------------------------------------
fs/fs_pin.c +3
-0@@ -17,6 +17,7 @@ void pin_remove(struct fs_pin *pin)wake_up_locked (&pin->wait);spin_unlock_irq (&pin->wait.lock); } +EXPORT_SYMBOL (pin_remove);void pin_insert_group (struct fs_pin *pin,struct vfsmount *m,struct hlist_head *p) {@@ -26,11 +27,13 @@ void pin_insert_group(struct fs_pin *pin, struct vfsmount *m, struct hlist_headhlist_add_head (&pin->m_list, &real_mount (m)->mnt_pins);spin_unlock (&pin_lock); } +EXPORT_SYMBOL (pin_insert_group);void pin_insert (struct fs_pin *pin,struct vfsmount *m) {pin_insert_group (pin, m, &m->mnt_sb->s_pins); } +EXPORT_SYMBOL (pin_insert);void pin_kill (struct fs_pin *p) {fs/nfsd/export.c +1
-1@@ -309,7 +309,7 @@ static void nfsd4_fslocs_free(struct nfsd4_fs_locations *fsloc)static void svc_export_put (struct kref *ref) { struct svc_export *exp = container_of(ref, struct svc_export, h.ref); -path_put (&exp->ex_path); +pin_remove (&exp->ex_pin);auth_domain_put (exp->ex_client);nfsd4_fslocs_free (&exp->ex_fslocs);kfree (exp->ex_uuid);@@ -695,15 +695,26 @@ static int svc_export_match(struct cache_head *a, struct cache_head *b) orig->ex_path.mnt == new->ex_path.mnt; }
fs/nfsd/export.c static int svc_export_match(struct cache_head *a, struct cache_head *b) · line 706
Quoted 10 lines
fs/nfsd/export.c +9
-0+static void export_pin_kill (struct fs_pin *pin) +{ + struct svc_export *exp = container_of(pin, struct svc_export, ex_pin); + +write_lock (&exp->cd->hash_lock); +cache_mark_expired (&exp->h); +pin_remove (pin); +write_unlock (&exp->cd->hash_lock); +}
I think you need to add some sort of delay here. The svc_export entry might still be in active use by an nfsd thread and you need to wait for that to complete.
I think you need to wait for the refcnt to drop to '1'. Maybe create a global wait_queue to wait for that.
Actually... svc_expkey contains a reference to a 'path' too, so you need to use pinning to purge those when the filesystem is unmounted.
Oh.. and you don't want to call 'pin_remove' from export_pin_kill(). It is called from svc_export_put() and calling from both could be problematic.
Hmmm... Ahhhh.
If you get export_pin_kill to only call cache_mark_expired() (maybe move the locking into that function) and *not* call pin_remove(), then the pin will remain on the list and ->done will be -1.
So mnt_pin_kill will loop around and this time pin_kill() will wait for p->done to be set.
So we really need that thing to be removed from cache promptly. I don't think we can wait for the next time cache_clean will be run. We could call cache_flush, but I think I'd rather remove just that one entry.
Also.... this requires that the pin (and the struct containing it) be freed using RCU.
So:
- add an rcu_head to svc_export and svc_expkey
- use rcu_kfree to free both those objects
- write a 'cache_force_expire()' which sets the expiry time, and then removes the entry from the cache.
- Use pin_insert_group rather then mntget for both svc_export and svc_expkey
- have the 'kill' functions for both call cache_force_expire(), but *not* pin_remove
fs/nfsd/export.c static int svc_export_match(struct cache_head *a, struct cache_head *b) · line 717
Quoted 12 lines
fs/nfsd/export.c +3
-1+static void svc_export_init (struct cache_head *cnew,struct cache_head *citem) { struct svc_export *new = container_of(cnew, struct svc_export, h); struct svc_export *item = container_of(citem, struct svc_export, h); +init_fs_pin (&new->ex_pin, export_pin_kill);kref_get (&item->ex_client->ref); new->ex_client = item->ex_client; new->ex_path = item->ex_path; -path_get (&item->ex_path); +pin_insert_group (&new->ex_pin, new->ex_path.mnt,NULL );
This doesn't look right. path_get does a 'mntget()' and a 'dget()'. You are replacing 'mntget()' with 'pin_insert_group()', but I think you still need the dget().
Similarly you need a dput() up where you removed path_put().
Thanks for working on this!
NeilBrown
Quoted 44 lines
fs/nfsd/export.c +0
-0new->ex_fslocs.locations =NULL ; new->ex_fslocs.locations_count =0 ; new->ex_fslocs.migrated =0 ;fs/nfsd/export.h +2
-0@@ -4,6 +4,7 @@#ifndef NFSD_EXPORT_H #define NFSD_EXPORT_H +#include <linux/fs_pin.h> #include <linux/sunrpc/cache.h> #include <uapi/linux/nfsd/export.h> @@ -49,6 +50,7 @@ struct svc_export {struct auth_domain * ex_client;int ex_flags;struct path ex_path; +struct fs_pin ex_pin;kuid_t ex_anon_uid;kgid_t ex_anon_gid;int ex_fsid;include/linux/sunrpc/cache.h +5
-1@@ -206,6 +206,11 @@ static inline int cache_is_expired(struct cache_detail *detail, struct cache_hea (detail->flush_time > h->last_refresh); } +static inline void cache_mark_expired (struct cache_head *h) +{ + h->expiry_time =seconds_since_boot () -1 ; +} +extern int cache_check (struct cache_detail *detail,struct cache_head *h,struct cache_req *rqstp);extern void cache_flush (void ); --To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Quoted 51 lines · NeilBrown
Quoted 49 lines · On Thu, 23 Apr 2015 20:52:40 +0800 Kinglong Mee <[email protected]> wrote:
Quoted 25 lines · NeilBrown
Quoted 8 lines · On Wed, 22 Apr 2015 11:07:03 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 4 lines
Reference of dentry/mnt is like a cache, avoids re-finding of them, it is necessary to store them in
svc_export.Neil points out another way of '
fs_pin', I will check that.Yes, that'd be interesting. On a very quick look--I don't understand what it's meant to do at all. But if it does provide a way to get a callback on umount, that'd certainly be interesting....
Yeah, on a quick look it isn't really obvious at all.
But I didn't read the code. I read
https://lwn.net/Articles/636730/which says:
In its place is a mechanism by which an object can be added to a vfsmount structure (which represents a mounted filesystem); that object supports only one method:
kill(). These "pin" objects hang around until the final reference to the vfsmount goes away, at which point each one'skill()function is called. It thus is a clean mechanism for performing cleanup when a filesystem goes away.Quoted 3 lines
This is used to close "BSD process accounting" files on umount, and sound like the perfect interface for flushing things from the sunrpc cache on umount.
I have review those codes in
fs/fs_pin.candkernel/acct.c. 'fs_pin' is used to fix the race betweenfile->f_path.mntfor acct and umount,file->f_path.mntjust holds the reference of vfsmount but not keep busy, umount will check the reference and return busy.The logical is same as sunrpc cache for exports.
Before commit
3064c3563b("death tomnt_pinned"), acct uses a hack method, acct get a pin to vfsmount and then put the reference, so umount finds no other reference and callback those pins in vfsmount, at last umount success.After that commit, besides pin to original vfsmount and put the reference of the original vfsmount, acct clone the vfsmount storing in
file->f_path.mnt.The different between those two methods is the value of
file->f_path.mnt, the first one, it contains the original vfsmnt without reference, the second one, contains a cloned vfsmnt with reference.
Quoted 5 lines
Quoted 3 lines
I have test the first method, pins to vfsmount for all exports cache, nfsd can work but sometimes will delay or hang at rpc.mountd's calling of
name_to_handle_at, I can't find the reason.A kernel stack trace of exactly where it is hanging would help a lot.
Witch adding fs_pin to exports, it seems there is a mutex race between nfsd and rpc.mountd for locking parent inode.
Apr 27 19:57:03 ntest kernel: rpc.mountd D ffff88006ac5fc28 0 1152 1 0x00000000 Apr 27 19:57:03 ntest kernel: ffff88006ac5fc28 ffff880068c38970 ffff880068c3de60 ffff88006ac5fc08 Apr 27 19:57:03 ntest kernel: ffff88006ac60000 ffff880035ecf694 ffff880068c3de60 00000000ffffffff Apr 27 19:57:03 ntest kernel: ffff880035ecf698 ffff88006ac5fc48 ffffffff8177ffd7 0000000000000000 Apr 27 19:57:03 ntest kernel: Call Trace: Apr 27 19:57:03 ntest kernel: [<ffffffff8177ffd7>] schedule+0x37/0x90 Apr 27 19:57:03 ntest kernel: [<ffffffff8178031e>] schedule_preempt_disabled+0xe/0x10 Apr 27 19:57:03 ntest kernel: [<ffffffff81781e92>] __mutex_lock_slowpath+0xb2/0x120 Apr 27 19:57:03 ntest kernel: [<ffffffff81781f23>] mutex_lock+0x23/0x40 Apr 27 19:57:03 ntest kernel: [<ffffffff81230714>] lookup_slow+0x34/0xc0 Apr 27 19:57:03 ntest kernel: [<ffffffff812358ee>] path_lookupat+0x89e/0xc60 Apr 27 19:57:03 ntest kernel: [<ffffffff81205192>] ? kmem_cache_alloc+0x1e2/0x260 Apr 27 19:57:03 ntest kernel: [<ffffffff812367a6>] ? getname_flags+0x56/0x200 Apr 27 19:57:03 ntest kernel: [<ffffffff81235cd7>] filename_lookup+0x27/0xc0 Apr 27 19:57:03 ntest kernel: [<ffffffff81237b53>] user_path_at_empty+0x63/0xd0 Apr 27 19:57:03 ntest kernel: [<ffffffff8121d722>] ? put_object+0x32/0x60 Apr 27 19:57:03 ntest kernel: [<ffffffff8121d94d>] ? delete_object_full+0x2d/0x40 Apr 27 19:57:03 ntest kernel: [<ffffffff8123e273>] ? dput+0x33/0x230 Apr 27 19:57:03 ntest kernel: [<ffffffff81237bd1>] user_path_at+0x11/0x20 Apr 27 19:57:03 ntest kernel: [<ffffffff81286489>] SyS_name_to_handle_at+0x59/0x200 Apr 27 19:57:03 ntest kernel: [<ffffffff81783f6e>] system_call_fastpath+0x12/0x71 Apr 27 19:57:03 ntest kernel: nfsd S ffff88006e92b708 0 1170 2 0x00000000 Apr 27 19:57:03 ntest kernel: ffff88006e92b708 ffffffff81c12480 ffff88006acbcb80 ffff88006e92b758 Apr 27 19:57:03 ntest kernel: ffff88006e92c000 ffffffff82290040 ffff88006e92b758 ffffffff82290040 Apr 27 19:57:03 ntest kernel: 00000000fffffff5 ffff88006e92b728 ffffffff8177ffd7 0000000100043a09 Apr 27 19:57:03 ntest kernel: Call Trace: Apr 27 19:57:03 ntest kernel: [<ffffffff8177ffd7>] schedule+0x37/0x90 Apr 27 19:57:03 ntest kernel: [<ffffffff81782e97>] schedule_timeout+0x117/0x230 Apr 27 19:57:03 ntest kernel: [<ffffffff8110b240>] ? internal_add_timer+0xb0/0xb0 Apr 27 19:57:03 ntest kernel: [<ffffffff81780ff3>] wait_for_completion_interruptible_timeout+0xf3/0x150 Apr 27 19:57:03 ntest kernel: [<ffffffff810cb090>] ? wake_up_state+0x20/0x20 Apr 27 19:57:03 ntest kernel: [<ffffffffa01350cc>] cache_wait_req.isra.10+0x9c/0x110 [sunrpc] Apr 27 19:57:03 ntest kernel: [<ffffffffa0134760>] ? sunrpc_init_cache_detail+0xc0/0xc0 [sunrpc] Apr 27 19:57:03 ntest kernel: [<ffffffffa0135c54>] cache_check+0x1d4/0x380 [sunrpc] Apr 27 19:57:03 ntest kernel: [<ffffffff8133248c>] ? inode_doinit_with_dentry+0x48c/0x6a0 Apr 27 19:57:03 ntest kernel: [<ffffffffa045e132>] exp_get_by_name+0x82/0xb0 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffff81776d0a>] ? kmemleak_free+0x3a/0xa0 Apr 27 19:57:03 ntest kernel: [<ffffffff81332210>] ? inode_doinit_with_dentry+0x210/0x6a0 Apr 27 19:57:03 ntest kernel: [<ffffffff813326bc>] ? selinux_d_instantiate+0x1c/0x20 Apr 27 19:57:03 ntest kernel: [<ffffffff8123def7>] ? _d_rehash+0x37/0x40 Apr 27 19:57:03 ntest kernel: [<ffffffff8123f776>] ? d_splice_alias+0xa6/0x2d0 Apr 27 19:57:03 ntest kernel: [<ffffffff812be90b>] ? ext4_lookup+0xdb/0x160 Apr 27 19:57:03 ntest kernel: [<ffffffffa0460114>] rqst_exp_get_by_name+0x64/0x140 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa045a8f6>] nfsd_cross_mnt+0x76/0x1b0 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa0475300>] nfsd4_encode_dirent+0x160/0x3d0 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa04751a0>] ? nfsd4_encode_getattr+0x40/0x40 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa045c631>] nfsd_readdir+0x1c1/0x2a0 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa045a1e0>] ? nfsd_direct_splice_actor+0x20/0x20 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa046bd70>] nfsd4_encode_readdir+0x120/0x220 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa047573d>] nfsd4_encode_operation+0x7d/0x190 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa046a51d>] nfsd4_proc_compound+0x24d/0x6f0 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa0455f83>] nfsd_dispatch+0xc3/0x220 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa012a01b>] svc_process_common+0x43b/0x690 [sunrpc] Apr 27 19:57:03 ntest kernel: [<ffffffffa012b133>] svc_process+0x103/0x1b0 [sunrpc] Apr 27 19:57:03 ntest kernel: [<ffffffffa045596f>] nfsd+0xff/0x170 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa0455870>] ? nfsd_destroy+0x80/0x80 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffff810bd378>] kthread+0xd8/0xf0 Apr 27 19:57:03 ntest kernel: [<ffffffff810bd2a0>] ? kthread_worker_fn+0x180/0x180 Apr 27 19:57:03 ntest kernel: [<ffffffff81784362>] ret_from_fork+0x42/0x70 Apr 27 19:57:03 ntest kernel: [<ffffffff810bd2a0>] ? kthread_worker_fn+0x180/0x180
Quoted 97 lines
Quoted 9 lines
Also test the second method, there are many problem caused by the cloned vfsmount,
mnt_clone_internal()changemnt_rootvalues to the path dentry.Those cases are tested for all exports cache, but, I think nfsd should put the reference of vfsmount when finding exports upcall fail. The success exports cache should also take it.
The following is a draft of the first method.
I think the first method sounds a lot better than the second.
Quoted 47 lines
thanks,
Kinglong Mee----------------------------------------------------------------------
fs/fs_pin.c +3
-0@@ -17,6 +17,7 @@ void pin_remove(struct fs_pin *pin)wake_up_locked (&pin->wait);spin_unlock_irq (&pin->wait.lock); } +EXPORT_SYMBOL (pin_remove);void pin_insert_group (struct fs_pin *pin,struct vfsmount *m,struct hlist_head *p) {@@ -26,11 +27,13 @@ void pin_insert_group(struct fs_pin *pin, struct vfsmount *m, struct hlist_headhlist_add_head (&pin->m_list, &real_mount (m)->mnt_pins);spin_unlock (&pin_lock); } +EXPORT_SYMBOL (pin_insert_group);void pin_insert (struct fs_pin *pin,struct vfsmount *m) {pin_insert_group (pin, m, &m->mnt_sb->s_pins); } +EXPORT_SYMBOL (pin_insert);void pin_kill (struct fs_pin *p) {fs/nfsd/export.c +1
-1@@ -309,7 +309,7 @@ static void nfsd4_fslocs_free(struct nfsd4_fs_locations *fsloc)static void svc_export_put (struct kref *ref) { struct svc_export *exp = container_of(ref, struct svc_export, h.ref); -path_put (&exp->ex_path); +pin_remove (&exp->ex_pin);auth_domain_put (exp->ex_client);nfsd4_fslocs_free (&exp->ex_fslocs);kfree (exp->ex_uuid);@@ -695,15 +695,26 @@ static int svc_export_match(struct cache_head *a, struct cache_head *b) orig->ex_path.mnt == new->ex_path.mnt; }fs/nfsd/export.c static int svc_export_match(struct cache_head *a, struct cache_head *b) · line 706
Quoted 10 lines
fs/nfsd/export.c +9
-0+static void export_pin_kill (struct fs_pin *pin) +{ + struct svc_export *exp = container_of(pin, struct svc_export, ex_pin); + +write_lock (&exp->cd->hash_lock); +cache_mark_expired (&exp->h); +pin_remove (pin); +write_unlock (&exp->cd->hash_lock); +}I think you need to add some sort of delay here. The
svc_exportentry might still be in active use by an nfsd thread and you need to wait for that to complete.I think you need to wait for the refcnt to drop to '1'. Maybe create a global
wait_queueto wait for that.Actually...
svc_expkeycontains a reference to a 'path' too, so you need to use pinning to purge those when the filesystem is unmounted.Oh.. and you don't want to call '
pin_remove' fromexport_pin_kill(). It is called fromsvc_export_put()and calling from both could be problematic.Hmmm... Ahhhh.
If you get
export_pin_killto only callcache_mark_expired()(maybe move the locking into that function) and *not* callpin_remove(), then the pin will remain on the list and ->done will be -1.
Somnt_pin_killwill loop around and this timepin_kill()will wait forp->doneto be set.
So we really need that thing to be removed from cache promptly. I don't think we can wait for the next timecache_cleanwill be run. We could callcache_flush, but I think I'd rather remove just that one entry.Also.... this requires that the pin (and the struct containing it) be freed using RCU.
Quoted 8 lines
So:
- add anrcu_headtosvc_exportandsvc_expkey
- usercu_kfreeto free both those objects
- write a 'cache_force_expire()' which sets the expiry time, and then removes the entry from the cache.
- Usepin_insert_grouprather then mntget for bothsvc_exportandsvc_expkey
- have the 'kill' functions for both callcache_force_expire(), but *not*pin_remove
Thanks very much for your comment in great detail. I will send a patch after fix the above race and basic tests.
thanks,
Kinglong Mee
Quoted 72 lines
fs/nfsd/export.c static int svc_export_match(struct cache_head *a, struct cache_head *b) · line 717
Quoted 13 lines
fs/nfsd/export.c +3
-1+static void svc_export_init (struct cache_head *cnew,struct cache_head *citem) { struct svc_export *new = container_of(cnew, struct svc_export, h); struct svc_export *item = container_of(citem, struct svc_export, h); +init_fs_pin (&new->ex_pin, export_pin_kill);kref_get (&item->ex_client->ref); new->ex_client = item->ex_client; new->ex_path = item->ex_path; -path_get (&item->ex_path); +pin_insert_group (&new->ex_pin, new->ex_path.mnt,NULL );This doesn't look right.
path_getdoes a 'mntget()' and a 'dget()'. You are replacing 'mntget()' with 'pin_insert_group()', but I think you still need thedget().Similarly you need a
dput()up where you removedpath_put().Thanks for working on this!
NeilBrown
Quoted 44 lines
fs/nfsd/export.c +0
-0new->ex_fslocs.locations =NULL ; new->ex_fslocs.locations_count =0 ; new->ex_fslocs.migrated =0 ;fs/nfsd/export.h +2
-0@@ -4,6 +4,7 @@#ifndef NFSD_EXPORT_H #define NFSD_EXPORT_H +#include <linux/fs_pin.h> #include <linux/sunrpc/cache.h> #include <uapi/linux/nfsd/export.h> @@ -49,6 +50,7 @@ struct svc_export {struct auth_domain * ex_client;int ex_flags;struct path ex_path; +struct fs_pin ex_pin;kuid_t ex_anon_uid;kgid_t ex_anon_gid;int ex_fsid;include/linux/sunrpc/cache.h +5
-1@@ -206,6 +206,11 @@ static inline int cache_is_expired(struct cache_detail *detail, struct cache_hea (detail->flush_time > h->last_refresh); } +static inline void cache_mark_expired (struct cache_head *h) +{ + h->expiry_time =seconds_since_boot () -1 ; +} +extern int cache_check (struct cache_detail *detail,struct cache_head *h,struct cache_req *rqstp);extern void cache_flush (void ); --To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Quoted 113 lines · On Mon, 27 Apr 2015 20:11:48 +0800 Kinglong Mee <[email protected]> wrote:
Quoted 51 lines · NeilBrown
Quoted 49 lines · On Thu, 23 Apr 2015 20:52:40 +0800 Kinglong Mee <[email protected]> wrote:
Quoted 25 lines · NeilBrown
Quoted 8 lines · On Wed, 22 Apr 2015 11:07:03 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 4 lines
Reference of dentry/mnt is like a cache, avoids re-finding of them, it is necessary to store them in
svc_export.Neil points out another way of '
fs_pin', I will check that.Yes, that'd be interesting. On a very quick look--I don't understand what it's meant to do at all. But if it does provide a way to get a callback on umount, that'd certainly be interesting....
Yeah, on a quick look it isn't really obvious at all.
But I didn't read the code. I read
https://lwn.net/Articles/636730/which says:
In its place is a mechanism by which an object can be added to a vfsmount structure (which represents a mounted filesystem); that object supports only one method:
kill(). These "pin" objects hang around until the final reference to the vfsmount goes away, at which point each one'skill()function is called. It thus is a clean mechanism for performing cleanup when a filesystem goes away.Quoted 3 lines
This is used to close "BSD process accounting" files on umount, and sound like the perfect interface for flushing things from the sunrpc cache on umount.
I have review those codes in
fs/fs_pin.candkernel/acct.c. 'fs_pin' is used to fix the race betweenfile->f_path.mntfor acct and umount,file->f_path.mntjust holds the reference of vfsmount but not keep busy, umount will check the reference and return busy.The logical is same as sunrpc cache for exports.
Before commit
3064c3563b("death tomnt_pinned"), acct uses a hack method, acct get a pin to vfsmount and then put the reference, so umount finds no other reference and callback those pins in vfsmount, at last umount success.After that commit, besides pin to original vfsmount and put the reference of the original vfsmount, acct clone the vfsmount storing in
file->f_path.mnt.The different between those two methods is the value of
file->f_path.mnt, the first one, it contains the original vfsmnt without reference, the second one, contains a cloned vfsmnt with reference.Quoted 5 lines
Quoted 3 lines
I have test the first method, pins to vfsmount for all exports cache, nfsd can work but sometimes will delay or hang at rpc.mountd's calling of
name_to_handle_at, I can't find the reason.A kernel stack trace of exactly where it is hanging would help a lot.
Witch adding
fs_pinto exports, it seems there is a mutex race between nfsd and rpc.mountd for locking parent inode.Apr 27 19:57:03 ntest kernel: rpc.mountd D ffff88006ac5fc28 0 1152 1 0x00000000 Apr 27 19:57:03 ntest kernel: ffff88006ac5fc28 ffff880068c38970 ffff880068c3de60 ffff88006ac5fc08 Apr 27 19:57:03 ntest kernel: ffff88006ac60000 ffff880035ecf694 ffff880068c3de60 00000000ffffffff Apr 27 19:57:03 ntest kernel: ffff880035ecf698 ffff88006ac5fc48 ffffffff8177ffd7 0000000000000000 Apr 27 19:57:03 ntest kernel: Call Trace: Apr 27 19:57:03 ntest kernel: [<ffffffff8177ffd7>] schedule+0x37/0x90 Apr 27 19:57:03 ntest kernel: [<ffffffff8178031e>] schedule_preempt_disabled+0xe/0x10 Apr 27 19:57:03 ntest kernel: [<ffffffff81781e92>] __mutex_lock_slowpath+0xb2/0x120 Apr 27 19:57:03 ntest kernel: [<ffffffff81781f23>] mutex_lock+0x23/0x40 Apr 27 19:57:03 ntest kernel: [<ffffffff81230714>] lookup_slow+0x34/0xc0 Apr 27 19:57:03 ntest kernel: [<ffffffff812358ee>] path_lookupat+0x89e/0xc60 Apr 27 19:57:03 ntest kernel: [<ffffffff81205192>] ? kmem_cache_alloc+0x1e2/0x260 Apr 27 19:57:03 ntest kernel: [<ffffffff812367a6>] ? getname_flags+0x56/0x200 Apr 27 19:57:03 ntest kernel: [<ffffffff81235cd7>] filename_lookup+0x27/0xc0 Apr 27 19:57:03 ntest kernel: [<ffffffff81237b53>] user_path_at_empty+0x63/0xd0 Apr 27 19:57:03 ntest kernel: [<ffffffff8121d722>] ? put_object+0x32/0x60 Apr 27 19:57:03 ntest kernel: [<ffffffff8121d94d>] ? delete_object_full+0x2d/0x40 Apr 27 19:57:03 ntest kernel: [<ffffffff8123e273>] ? dput+0x33/0x230 Apr 27 19:57:03 ntest kernel: [<ffffffff81237bd1>] user_path_at+0x11/0x20 Apr 27 19:57:03 ntest kernel: [<ffffffff81286489>] SyS_name_to_handle_at+0x59/0x200 Apr 27 19:57:03 ntest kernel: [<ffffffff81783f6e>] system_call_fastpath+0x12/0x71 Apr 27 19:57:03 ntest kernel: nfsd S ffff88006e92b708 0 1170 2 0x00000000 Apr 27 19:57:03 ntest kernel: ffff88006e92b708 ffffffff81c12480 ffff88006acbcb80 ffff88006e92b758 Apr 27 19:57:03 ntest kernel: ffff88006e92c000 ffffffff82290040 ffff88006e92b758 ffffffff82290040 Apr 27 19:57:03 ntest kernel: 00000000fffffff5 ffff88006e92b728 ffffffff8177ffd7 0000000100043a09 Apr 27 19:57:03 ntest kernel: Call Trace: Apr 27 19:57:03 ntest kernel: [<ffffffff8177ffd7>] schedule+0x37/0x90 Apr 27 19:57:03 ntest kernel: [<ffffffff81782e97>] schedule_timeout+0x117/0x230 Apr 27 19:57:03 ntest kernel: [<ffffffff8110b240>] ? internal_add_timer+0xb0/0xb0 Apr 27 19:57:03 ntest kernel: [<ffffffff81780ff3>] wait_for_completion_interruptible_timeout+0xf3/0x150 Apr 27 19:57:03 ntest kernel: [<ffffffff810cb090>] ? wake_up_state+0x20/0x20 Apr 27 19:57:03 ntest kernel: [<ffffffffa01350cc>] cache_wait_req.isra.10+0x9c/0x110 [sunrpc] Apr 27 19:57:03 ntest kernel: [<ffffffffa0134760>] ? sunrpc_init_cache_detail+0xc0/0xc0 [sunrpc] Apr 27 19:57:03 ntest kernel: [<ffffffffa0135c54>] cache_check+0x1d4/0x380 [sunrpc] Apr 27 19:57:03 ntest kernel: [<ffffffff8133248c>] ? inode_doinit_with_dentry+0x48c/0x6a0 Apr 27 19:57:03 ntest kernel: [<ffffffffa045e132>] exp_get_by_name+0x82/0xb0 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffff81776d0a>] ? kmemleak_free+0x3a/0xa0 Apr 27 19:57:03 ntest kernel: [<ffffffff81332210>] ? inode_doinit_with_dentry+0x210/0x6a0 Apr 27 19:57:03 ntest kernel: [<ffffffff813326bc>] ? selinux_d_instantiate+0x1c/0x20 Apr 27 19:57:03 ntest kernel: [<ffffffff8123def7>] ? _d_rehash+0x37/0x40 Apr 27 19:57:03 ntest kernel: [<ffffffff8123f776>] ? d_splice_alias+0xa6/0x2d0 Apr 27 19:57:03 ntest kernel: [<ffffffff812be90b>] ? ext4_lookup+0xdb/0x160 Apr 27 19:57:03 ntest kernel: [<ffffffffa0460114>] rqst_exp_get_by_name+0x64/0x140 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa045a8f6>] nfsd_cross_mnt+0x76/0x1b0 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa0475300>] nfsd4_encode_dirent+0x160/0x3d0 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa04751a0>] ? nfsd4_encode_getattr+0x40/0x40 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa045c631>] nfsd_readdir+0x1c1/0x2a0 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa045a1e0>] ? nfsd_direct_splice_actor+0x20/0x20 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa046bd70>] nfsd4_encode_readdir+0x120/0x220 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa047573d>] nfsd4_encode_operation+0x7d/0x190 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa046a51d>] nfsd4_proc_compound+0x24d/0x6f0 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa0455f83>] nfsd_dispatch+0xc3/0x220 [nfsd]
Quoted 9 lines
Apr 27 19:57:03 ntest kernel: [<ffffffffa012a01b>] svc_process_common+0x43b/0x690 [sunrpc] Apr 27 19:57:03 ntest kernel: [<ffffffffa012b133>] svc_process+0x103/0x1b0 [sunrpc] Apr 27 19:57:03 ntest kernel: [<ffffffffa045596f>] nfsd+0xff/0x170 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa0455870>] ? nfsd_destroy+0x80/0x80 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffff810bd378>] kthread+0xd8/0xf0 Apr 27 19:57:03 ntest kernel: [<ffffffff810bd2a0>] ? kthread_worker_fn+0x180/0x180 Apr 27 19:57:03 ntest kernel: [<ffffffff81784362>] ret_from_fork+0x42/0x70 Apr 27 19:57:03 ntest kernel: [<ffffffff810bd2a0>] ? kthread_worker_fn+0x180/0x180
Hmm... That looks bad. I wonder why we haven't hit the deadlock before.
nfsd gets a LOOKUP request for $DIR/name which is a mountpoint, or a READDIR request for $DIR which contains 'name' which is a mountpoint.
nfsd_lookup_dentry of nfsd_buffered_readdir lock i_mutex on $DIR, then do the mountpoint check. If the mounted filesystem is not exported, but the $DIR filesystem has 'crossmnt', an upcall to mountd asks for export information. When mountd try to do a lookup on the name, it works fine if lookup_fast finds the child, but it deadlocks on the parent's i_mutex if it has to go to lookup_slow.
It should only go to lookup_slow() if:
- the child isn't in cache .. but as it is mounted on, it must be
- d_revalidate returns 0
This probably means that $DIR is on an NFS filesystem and it failed the revalidation for some reason....
Kinglong: can you reproduce this easily? If so can you enable nfs debugging and collect the output?
I want to confirm that the dfprintk in nfs_lookup_revalidate is saying that the name is invalid.
In any case, holding a mutex while waiting for an upcall is probably a bad idea. We should try to find a way to work around that...
Any ideas Bruce ?-)
NeilBrown
Quoted 125 lines · NeilBrown
Quoted 113 lines · On Mon, 27 Apr 2015 20:11:48 +0800 Kinglong Mee <[email protected]> wrote:
Quoted 51 lines · NeilBrown
Quoted 49 lines · On Thu, 23 Apr 2015 20:52:40 +0800 Kinglong Mee <[email protected]> wrote:
Quoted 25 lines · NeilBrown
Quoted 8 lines · On Wed, 22 Apr 2015 11:07:03 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 4 lines
Reference of dentry/mnt is like a cache, avoids re-finding of them, it is necessary to store them in
svc_export.Neil points out another way of '
fs_pin', I will check that.Yes, that'd be interesting. On a very quick look--I don't understand what it's meant to do at all. But if it does provide a way to get a callback on umount, that'd certainly be interesting....
Yeah, on a quick look it isn't really obvious at all.
But I didn't read the code. I read
https://lwn.net/Articles/636730/which says:
In its place is a mechanism by which an object can be added to a vfsmount structure (which represents a mounted filesystem); that object supports only one method:
kill(). These "pin" objects hang around until the final reference to the vfsmount goes away, at which point each one'skill()function is called. It thus is a clean mechanism for performing cleanup when a filesystem goes away.Quoted 3 lines
This is used to close "BSD process accounting" files on umount, and sound like the perfect interface for flushing things from the sunrpc cache on umount.
I have review those codes in
fs/fs_pin.candkernel/acct.c. 'fs_pin' is used to fix the race betweenfile->f_path.mntfor acct and umount,file->f_path.mntjust holds the reference of vfsmount but not keep busy, umount will check the reference and return busy.The logical is same as sunrpc cache for exports.
Before commit
3064c3563b("death tomnt_pinned"), acct uses a hack method, acct get a pin to vfsmount and then put the reference, so umount finds no other reference and callback those pins in vfsmount, at last umount success.After that commit, besides pin to original vfsmount and put the reference of the original vfsmount, acct clone the vfsmount storing in
file->f_path.mnt.The different between those two methods is the value of
file->f_path.mnt, the first one, it contains the original vfsmnt without reference, the second one, contains a cloned vfsmnt with reference.Quoted 5 lines
Quoted 3 lines
I have test the first method, pins to vfsmount for all exports cache, nfsd can work but sometimes will delay or hang at rpc.mountd's calling of
name_to_handle_at, I can't find the reason.A kernel stack trace of exactly where it is hanging would help a lot.
Witch adding
fs_pinto exports, it seems there is a mutex race between nfsd and rpc.mountd for locking parent inode.Apr 27 19:57:03 ntest kernel: rpc.mountd D ffff88006ac5fc28 0 1152 1 0x00000000 Apr 27 19:57:03 ntest kernel: ffff88006ac5fc28 ffff880068c38970 ffff880068c3de60 ffff88006ac5fc08 Apr 27 19:57:03 ntest kernel: ffff88006ac60000 ffff880035ecf694 ffff880068c3de60 00000000ffffffff Apr 27 19:57:03 ntest kernel: ffff880035ecf698 ffff88006ac5fc48 ffffffff8177ffd7 0000000000000000 Apr 27 19:57:03 ntest kernel: Call Trace: Apr 27 19:57:03 ntest kernel: [<ffffffff8177ffd7>] schedule+0x37/0x90 Apr 27 19:57:03 ntest kernel: [<ffffffff8178031e>] schedule_preempt_disabled+0xe/0x10 Apr 27 19:57:03 ntest kernel: [<ffffffff81781e92>] __mutex_lock_slowpath+0xb2/0x120 Apr 27 19:57:03 ntest kernel: [<ffffffff81781f23>] mutex_lock+0x23/0x40 Apr 27 19:57:03 ntest kernel: [<ffffffff81230714>] lookup_slow+0x34/0xc0 Apr 27 19:57:03 ntest kernel: [<ffffffff812358ee>] path_lookupat+0x89e/0xc60 Apr 27 19:57:03 ntest kernel: [<ffffffff81205192>] ? kmem_cache_alloc+0x1e2/0x260 Apr 27 19:57:03 ntest kernel: [<ffffffff812367a6>] ? getname_flags+0x56/0x200 Apr 27 19:57:03 ntest kernel: [<ffffffff81235cd7>] filename_lookup+0x27/0xc0 Apr 27 19:57:03 ntest kernel: [<ffffffff81237b53>] user_path_at_empty+0x63/0xd0 Apr 27 19:57:03 ntest kernel: [<ffffffff8121d722>] ? put_object+0x32/0x60 Apr 27 19:57:03 ntest kernel: [<ffffffff8121d94d>] ? delete_object_full+0x2d/0x40 Apr 27 19:57:03 ntest kernel: [<ffffffff8123e273>] ? dput+0x33/0x230 Apr 27 19:57:03 ntest kernel: [<ffffffff81237bd1>] user_path_at+0x11/0x20 Apr 27 19:57:03 ntest kernel: [<ffffffff81286489>] SyS_name_to_handle_at+0x59/0x200 Apr 27 19:57:03 ntest kernel: [<ffffffff81783f6e>] system_call_fastpath+0x12/0x71 Apr 27 19:57:03 ntest kernel: nfsd S ffff88006e92b708 0 1170 2 0x00000000 Apr 27 19:57:03 ntest kernel: ffff88006e92b708 ffffffff81c12480 ffff88006acbcb80 ffff88006e92b758 Apr 27 19:57:03 ntest kernel: ffff88006e92c000 ffffffff82290040 ffff88006e92b758 ffffffff82290040 Apr 27 19:57:03 ntest kernel: 00000000fffffff5 ffff88006e92b728 ffffffff8177ffd7 0000000100043a09 Apr 27 19:57:03 ntest kernel: Call Trace: Apr 27 19:57:03 ntest kernel: [<ffffffff8177ffd7>] schedule+0x37/0x90 Apr 27 19:57:03 ntest kernel: [<ffffffff81782e97>] schedule_timeout+0x117/0x230 Apr 27 19:57:03 ntest kernel: [<ffffffff8110b240>] ? internal_add_timer+0xb0/0xb0 Apr 27 19:57:03 ntest kernel: [<ffffffff81780ff3>] wait_for_completion_interruptible_timeout+0xf3/0x150 Apr 27 19:57:03 ntest kernel: [<ffffffff810cb090>] ? wake_up_state+0x20/0x20 Apr 27 19:57:03 ntest kernel: [<ffffffffa01350cc>] cache_wait_req.isra.10+0x9c/0x110 [sunrpc] Apr 27 19:57:03 ntest kernel: [<ffffffffa0134760>] ? sunrpc_init_cache_detail+0xc0/0xc0 [sunrpc] Apr 27 19:57:03 ntest kernel: [<ffffffffa0135c54>] cache_check+0x1d4/0x380 [sunrpc] Apr 27 19:57:03 ntest kernel: [<ffffffff8133248c>] ? inode_doinit_with_dentry+0x48c/0x6a0 Apr 27 19:57:03 ntest kernel: [<ffffffffa045e132>] exp_get_by_name+0x82/0xb0 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffff81776d0a>] ? kmemleak_free+0x3a/0xa0 Apr 27 19:57:03 ntest kernel: [<ffffffff81332210>] ? inode_doinit_with_dentry+0x210/0x6a0 Apr 27 19:57:03 ntest kernel: [<ffffffff813326bc>] ? selinux_d_instantiate+0x1c/0x20 Apr 27 19:57:03 ntest kernel: [<ffffffff8123def7>] ? _d_rehash+0x37/0x40 Apr 27 19:57:03 ntest kernel: [<ffffffff8123f776>] ? d_splice_alias+0xa6/0x2d0 Apr 27 19:57:03 ntest kernel: [<ffffffff812be90b>] ? ext4_lookup+0xdb/0x160 Apr 27 19:57:03 ntest kernel: [<ffffffffa0460114>] rqst_exp_get_by_name+0x64/0x140 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa045a8f6>] nfsd_cross_mnt+0x76/0x1b0 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa0475300>] nfsd4_encode_dirent+0x160/0x3d0 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa04751a0>] ? nfsd4_encode_getattr+0x40/0x40 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa045c631>] nfsd_readdir+0x1c1/0x2a0 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa045a1e0>] ? nfsd_direct_splice_actor+0x20/0x20 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa046bd70>] nfsd4_encode_readdir+0x120/0x220 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa047573d>] nfsd4_encode_operation+0x7d/0x190 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa046a51d>] nfsd4_proc_compound+0x24d/0x6f0 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa0455f83>] nfsd_dispatch+0xc3/0x220 [nfsd]Quoted 9 lines
Apr 27 19:57:03 ntest kernel: [<ffffffffa012a01b>] svc_process_common+0x43b/0x690 [sunrpc] Apr 27 19:57:03 ntest kernel: [<ffffffffa012b133>] svc_process+0x103/0x1b0 [sunrpc] Apr 27 19:57:03 ntest kernel: [<ffffffffa045596f>] nfsd+0xff/0x170 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffffa0455870>] ? nfsd_destroy+0x80/0x80 [nfsd] Apr 27 19:57:03 ntest kernel: [<ffffffff810bd378>] kthread+0xd8/0xf0 Apr 27 19:57:03 ntest kernel: [<ffffffff810bd2a0>] ? kthread_worker_fn+0x180/0x180 Apr 27 19:57:03 ntest kernel: [<ffffffff81784362>] ret_from_fork+0x42/0x70 Apr 27 19:57:03 ntest kernel: [<ffffffff810bd2a0>] ? kthread_worker_fn+0x180/0x180
Quoted 1 line
Hmm... That looks bad. I wonder why we haven't hit the deadlock before.
It appears after nfs-utils commit 6091c0a4c4 ("mountd: add support for case-insensitive file names") adds using name_to_handle_at which will locking parent.
Quoted 10 lines
nfsd gets a LOOKUP request for $DIR/name which is a mountpoint, or a READDIR request for $DIR which contains 'name' which is a mountpoint.
nfsd_lookup_dentryofnfsd_buffered_readdirlocki_mutexon $DIR, then do the mountpoint check. If the mounted filesystem is not exported, but the $DIR filesystem has 'crossmnt', an upcall to mountd asks for export information. When mountd try to do a lookup on the name, it works fine iflookup_fastfinds the child, but it deadlocks on the parent'si_mutexif it has to go tolookup_slow.
I have send a patch for it,
"[PATCH] NFSD: Avoid race of locking parent's mutex at cross mount"
Quoted 3 lines
It should only go to
lookup_slow()if:
- the child isn't in cache .. but as it is mounted on, it must be
Yes, it is.
If after some operations of those files, they are cached, the race will not appear.
Quoted 4 lines
-
d_revalidatereturns 0This probably means that $DIR is on an NFS filesystem and it failed the revalidation for some reason....
It is caused by readdir of pseudo root, there are many unexported files in it.
Quoted 5 lines
Kinglong: can you reproduce this easily? If so can you enable nfs debugging and collect the output?
I want to confirm that the dfprintk innfs_lookup_revalidateis saying that the name is invalid.
Yes, I will send the reproduce method in the other threads, add cc you.
thanks,
Kinglong Mee
Quoted 2 lines · NeilBrown
In any case, holding a mutex while waiting for an upcall is probably a bad idea. We should try to find a way to work around that...
Yeah, it sounds fragile even if we could fix this particular issue in mountd.
Quoted 1 line
Any ideas Bruce ?-)
Looking at nfsd_buffered_readdir():
/*
* Various filldir functions may end up calling back into
* lookup_one_len() and the file system's ->lookup() method.
* These expect i_mutex to be held, as it would within readdir.
*/
host_err = mutex_lock_killable(&dir_inode->i_mutex);and as far as I can tell Kinglong's approach (adding an unlock and lock around nfsd_cross_mnt() calls might actually be OK.
Though in general I expect
lock() ...code... unlock()
to mark a critical section and would be unpleasantly surprised to discover that a function somewhere in the middle had a buried unlock/lock pair.
Maybe drop the locking from nfsd_buffered_readdir and *just* take the i_mutex around lookup_one_len(), if that's the only place we need it?
--b.
Quoted 18 lines · On Wed, 29 Apr 2015 15:19:34 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 2 lines · NeilBrown
In any case, holding a mutex while waiting for an upcall is probably a bad idea. We should try to find a way to work around that...
Yeah, it sounds fragile even if we could fix this particular issue in mountd.
Quoted 1 line
Any ideas Bruce ?-)
Looking at
nfsd_buffered_readdir():/* * Various filldir functions may end up calling back into * lookup_one_len() and the file system's ->lookup() method. * These expect i_mutex to be held, as it would within readdir. */ host_err = mutex_lock_killable(&dir_inode->i_mutex);
Quoted 2 lines
and as far as I can tell Kinglong's approach (adding an unlock and lock around
nfsd_cross_mnt()calls might actually be OK.
Yes, I think now that it would work. It would look odd though, as you suggest.
There would need to be very clear comments explaining why the lock is being managed that way.
Quoted 7 lines
Though in general I expect
lock() ...code... unlock()
Quoted 6 lines
to mark a critical section and would be unpleasantly surprised to discover that a function somewhere in the middle had a buried unlock/lock pair.
Maybe drop the locking from
nfsd_buffered_readdirand *just* take thei_mutexaroundlookup_one_len(), if that's the only place we need it?
I think it is the only place needed. Certainly normal path lookup only takes i_mutex very briefly around __lookup_hash().
One cost would be that we would take the mutex for every name instead of once for a whole set of names. That is generally frowned upon for performance reasons.
An alternative might be to stop using lookup_one_len, and use kern_path() instead. Or kern_path_mountpoint if we don't want to cross a mountpoint.
They would only take the i_mutex if absolutely necessary.
The draw back is that they don't take a 'len' argument, so we would need to make sure the name is nul terminated (and maybe double-check that there is no '/'??). It would be fairly easy to nul-terminate names from readdir - just use a bit more space in the buffer in nfsd_buffered_filldir().
I'm less sure about the locking needed in nfsd_lookup_dentry(). The comments suggests that there is good reason to keep the lock for an extended period. But that reasoning might only apply to files, and nfsd_mountpoint should only be true for directories... I hope.
A different approach would be to pass NULL for the rqstp to nfsd_cross_mnt(). This will ensure it doesn't block, but it might fail incorrectly. If it does fail, we drop the lock, retry with the real rqstp, retake the lock and .... no, I think that gets a bit too messy.
I think I'm in favour of:
- not taking the lock in readdir
- maybe not taking it in lookup
- using kern_path_mountpoint or kern_path
- nul terminating then names, copying the nfsd_lookup name to __getname() if necessary.
Sound reasonable?
NeilBrown
Quoted 16 lines · NeilBrown
Quoted 2 lines · On Wed, 29 Apr 2015 15:19:34 -0400 "J. Bruce Fields" <[email protected]>…
Maybe drop the locking from
nfsd_buffered_readdirand *just* take thei_mutexaroundlookup_one_len(), if that's the only place we need it?I think it is the only place needed. Certainly normal path lookup only takes
i_mutexvery briefly around__lookup_hash().One cost would be that we would take the mutex for every name instead of once for a whole set of names. That is generally frowned upon for performance reasons.
An alternative might be to stop using
lookup_one_len, and usekern_path()instead. Orkern_path_mountpointif we don't want to cross a mountpoint.
Quoted 7 lines
They would only take the
i_mutexif absolutely necessary.The draw back is that they don't take a 'len' argument, so we would need to make sure the name is nul terminated (and maybe double-check that there is no '/'??). It would be fairly easy to nul-terminate names from readdir - just use a bit more space in the buffer in
nfsd_buffered_filldir().
They're also a lot more complicated than lookup_one_len. Is any of this extra stuff they do (audit?) going to bite us?
For me understanding that would be harder than writing a variant of lookup_one_len that took the i_mutex when required. But I guess that's my problem, I should try to understand the lookup code better....
Quoted 4 lines
I'm less sure about the locking needed in
nfsd_lookup_dentry(). The comments suggests that there is good reason to keep the lock for an extended period. But that reasoning might only apply to files, andnfsd_mountpointshould only be true for directories... I hope.
I thought d_mountpoint() could be true for files, but certainly we won't be doing nfs4 opens on directories.
Quoted 5 lines
A different approach would be to pass NULL for the rqstp to
nfsd_cross_mnt(). This will ensure it doesn't block, but it might fail incorrectly. If it does fail, we drop the lock, retry with the real rqstp, retake the lock and .... no, I think that gets a bit too messy.
Yes.
Quoted 8 lines
I think I'm in favour of:
- not taking the lock in readdir
- maybe not taking it in lookup
- usingkern_path_mountpointorkern_path
- nul terminating then names, copying thenfsd_lookupname to__getname()if necessary.Sound reasonable?
I guess so, though I wish I understood kern_path_mountpoint better.
And nfsd_lookup_dentry looks like work for another day. No, wait, I forgot the goal here: you're right, nfsd_lookup_dentry has the same upcall-under-i_mutex problem, so we need to fix it too.
OK, agreed.
--b.
Quoted 25 lines · On Thu, 30 Apr 2015 17:36:02 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 16 lines · NeilBrown
Quoted 2 lines · On Wed, 29 Apr 2015 15:19:34 -0400 "J. Bruce Fields" <[email protected]>…
Maybe drop the locking from
nfsd_buffered_readdirand *just* take thei_mutexaroundlookup_one_len(), if that's the only place we need it?I think it is the only place needed. Certainly normal path lookup only takes
i_mutexvery briefly around__lookup_hash().One cost would be that we would take the mutex for every name instead of once for a whole set of names. That is generally frowned upon for performance reasons.
An alternative might be to stop using
lookup_one_len, and usekern_path()instead. Orkern_path_mountpointif we don't want to cross a mountpoint.Quoted 7 lines
They would only take the
i_mutexif absolutely necessary.The draw back is that they don't take a 'len' argument, so we would need to make sure the name is nul terminated (and maybe double-check that there is no '/'??). It would be fairly easy to nul-terminate names from readdir - just use a bit more space in the buffer in
nfsd_buffered_filldir().
Quoted 6 lines
They're also a lot more complicated than
lookup_one_len. Is any of this extra stuff they do (audit?) going to bite us?For me understanding that would be harder than writing a variant of
lookup_one_lenthat took thei_mutexwhen required. But I guess that's my problem, I should try to understand the lookup code better....
Tempting though ... see below (untested).
While writing that I began to wonder if lookup_one_len is really the right interface to be used, even though it was introduced (in 2.3.99pre2-4) specifically for nfsd.
The problem is that it assumes things about the filesystem. So it makes perfect sense for various filesystems to use it on themselves, but I'm not sure how *right* it is for nfsd (or cachefiles etc) to use it on some *other* filesystem.
The particular issue is that it avoids the d_revalidate call. Both vfat and reiserfs have that call ... I wonder if that could ever be a problem.
So I'm really leaning towards creating a variant of kern_path_mountpoint and using a variant of that which takes a length.
I think adding the d_revalidate is correct and even adding auditing is probably a good idea.
Maybe I'll try that (unless someone beats me to it...)
NeilBrown
Quoted 28 lines
Quoted 5 lines
I'm less sure about the locking needed in
nfsd_lookup_dentry(). The comments suggests that there is good reason to keep the lock for an extended period. But that reasoning might only apply to files, andnfsd_mountpointshould only be true for directories... I hope.I thought
d_mountpoint()could be true for files, but certainly we won't be doing nfs4 opens on directories.Quoted 5 lines
A different approach would be to pass NULL for the rqstp to
nfsd_cross_mnt(). This will ensure it doesn't block, but it might fail incorrectly. If it does fail, we drop the lock, retry with the real rqstp, retake the lock and .... no, I think that gets a bit too messy.Yes.
Quoted 8 lines
I think I'm in favour of:
- not taking the lock in readdir
- maybe not taking it in lookup
- usingkern_path_mountpointorkern_path
- nul terminating then names, copying thenfsd_lookupname to__getname()if necessary.Sound reasonable?
I guess so, though I wish I understood
kern_path_mountpointbetter.
Quoted 7 lines
And
nfsd_lookup_dentrylooks like work for another day. No, wait, I forgot the goal here: you're right,nfsd_lookup_dentryhas the same upcall-under-i_mutex problem, so we need to fix it too.OK, agreed.
--b.
fs/namei.c +50 -0
@@ -2182,6 +2182,56 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) }EXPORT_SYMBOL (lookup_one_len); +struct dentry *lookup_one_len_unlocked (const char *name, +struct dentry *base,int len) +{ +struct qstr this; +unsigned int c; +int err; +struct dentry *ret; + +WARN_ON_ONCE (!mutex_is_locked (&base->d_inode->i_mutex)); + + this.name = name; + this.len = len; + this.hash =full_name_hash (name, len); +if (!len) +return ERR_PTR (-EACCES ); + +if (unlikely (name[0 ] =='.' )) { +if (len <2 || (len ==2 && name[1 ] =='.' )) +return ERR_PTR (-EACCES ); + } + +while (len--) { + c = *(const unsigned char *)name++; +if (c =='/' || c =='\0' ) +return ERR_PTR (-EACCES ); + } +/* +* See if the low-level filesystem might want +* to use its own hash.. +*/ +if (base->d_flags &DCACHE_OP_HASH ) { +int err = base->d_op->d_hash (base, &this); +if (err <0 ) +return ERR_PTR (err); + } + + err =inode_permission (base->d_inode,MAY_EXEC ); +if (err) +return ERR_PTR (err); + + ret =__d_lookup (base, &this); +if (ret) + retrun ret; +mutex_lock (&base->d_inode->i_mutex); + ret =__lookup_hash (&this, base,0 ); +mutex_unlock (&base->d_inode->i_mutex); +return ret; +} +EXPORT_SYMBOL (lookup_one_len_unlocked); +int user_path_at_empty (int dfd,const char __user *name,unsigned flags,struct path *path,int *empty) {
Al Viro
maintainerQuoted 11 lines · NeilBrown
While writing that I began to wonder if
lookup_one_lenis really the right interface to be used, even though it was introduced (in 2.3.99pre2-4) specifically for nfsd.
The problem is that it assumes things about the filesystem. So it makes perfect sense for various filesystems to use it on themselves, but I'm not sure how *right* it is for nfsd (or cachefiles etc) to use it on some *other* filesystem.
The particular issue is that it avoids thed_revalidatecall. Both vfat and reiserfs have that call ... I wonder if that could ever be a problem.
Quoted 2 lines
So I'm really leaning towards creating a variant of
kern_path_mountpointand using a variant of that which takes a length.
NAK. As in, "no way in hell". And yes, lookup_one_len() *does* revalidate - RTFS(lookup_dcache), please.
What kind of consistency warranties do callers expect, BTW? You do realize that between iterate_dir() and callbacks an entry might have been removed and/or replaced?
Quoted 12 lines · On Fri, 1 May 2015 03:03:24 +0100 Al Viro <[email protected]> wrote:
Quoted 11 lines · NeilBrown
While writing that I began to wonder if
lookup_one_lenis really the right interface to be used, even though it was introduced (in 2.3.99pre2-4) specifically for nfsd.
The problem is that it assumes things about the filesystem. So it makes perfect sense for various filesystems to use it on themselves, but I'm not sure how *right* it is for nfsd (or cachefiles etc) to use it on some *other* filesystem.
The particular issue is that it avoids thed_revalidatecall. Both vfat and reiserfs have that call ... I wonder if that could ever be a problem.
Quoted 5 lines
Quoted 2 lines
So I'm really leaning towards creating a variant of
kern_path_mountpointand using a variant of that which takes a length.NAK. As in, "no way in hell". And yes,
lookup_one_len()*does* revalidate -RTFS(lookup_dcache), please.
Damn - I always seems to get lost when I'm following those call paths.
lookup_one_len -> __lookup_hash -> lookup_dcache -> d_lookup,d_revalidate -> __d_lookup
-> lookup_real -> i_op->lookup
I think I was confusing __lookup_hash with __d_lookup in my thoughts.
Quoted 4 lines
What kind of consistency warranties do callers expect, BTW? You do realize that between
iterate_dir()and callbacks an entry might have been removed and/or replaced?
For READDIR_PLUS, lookup_one_len is called on each name and it requires i_mutex, so the code currently holds i_mutex over the whole sequence. This is triggering a deadlock.
We could just grab/drop i_mutex over each call to lookup_one_len(), but that sort of thing is usually frowned upon, and we don't really always *need* i_mutex if the lookup can be served from the d_cache.
So I'm looking for the best way to perform the lookup without holding i_mutex for too long.
It sounds like you are suggesting something like lookup_one_len_unlocked(), which .... uhm...
I was going to say uses lookup_dcache, but that needs i_mutex. It calls d_lookup(), which doesn't seem to really need i_mutex, and d_revalidate().
Does the later need i_mutex? I don't think so.
So maybe it is just how d_lookup handles failure that needs i_mutex.
So lookup_one_len_unlocked() could call d_lookup and d_revalidate and if that all worked nicely, return the result. If it didn't, grab i_mutex and try again??
Or do we just wear the cost of taking i_mutex for each name in the directory during READDIR_PLUS?
Thanks,
NeilBrown
Quoted 4 lines
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Al Viro
maintainerQuoted 7 lines · NeilBrown
Quoted 3 lines
What kind of consistency warranties do callers expect, BTW? You do realize that between
iterate_dir()and callbacks an entry might have been removed and/or replaced?For
READDIR_PLUS,lookup_one_lenis called on each name and it requiresi_mutex, so the code currently holdsi_mutexover the whole sequence. This is triggering a deadlock.
Yes, I've seen the context. However, you are _not_ holding it between actual iterate_dir() and those callbacks, which opens a window when directory might have been changed.
Again, what kind of consistency is expected by callers? Are they ready to cope with "there's no such entry anymore" or "inumber is nothing like what we'd put in ->ino, since it's no the same object" or "->d_type is completely unrelated to what we'd found, since the damn thing had been removed and created from scratch"?
Quoted 13 lines · On Fri, 1 May 2015 03:29:40 +0100 Al Viro <[email protected]> wrote:
Quoted 7 lines · NeilBrown
Quoted 3 lines
What kind of consistency warranties do callers expect, BTW? You do realize that between
iterate_dir()and callbacks an entry might have been removed and/or replaced?For
READDIR_PLUS,lookup_one_lenis called on each name and it requiresi_mutex, so the code currently holdsi_mutexover the whole sequence. This is triggering a deadlock.Yes, I've seen the context. However, you are _not_ holding it between actual
iterate_dir()and those callbacks, which opens a window when directory might have been changed.
Quoted 5 lines
Again, what kind of consistency is expected by callers? Are they ready to cope with "there's no such entry anymore" or "inumber is nothing like what we'd put in ->ino, since it's no the same object" or "->d_type is completely unrelated to what we'd found, since the damn thing had been removed and created from scratch"?
Ah, sorry.
Yes, the callers are prepared for "there's no such entry anymore". They don't use d_type, so don't care if it might be meaningless. NFSv4 doesn't use ino either, but NFSv3 does and isn't properly cautious about ino changing.
In nfs3xdr, we should probably pass 'ino' to encode_entryplus_baggage() and thence to compose_entry_fh() and it should report failure if dchild->d_inode->i_ino doesn't match.
Simply not returning the extra attributes is perfectly acceptable in NFSv3.
So it looks like we are mostly OK here - we don't really need i_mutex to be held for very long.
NeilBrown
Quoted 23 lines · NeilBrown
Quoted 13 lines · On Fri, 1 May 2015 03:29:40 +0100 Al Viro <[email protected]> wrote:
Quoted 7 lines · NeilBrown
Quoted 3 lines
What kind of consistency warranties do callers expect, BTW? You do realize that between
iterate_dir()and callbacks an entry might have been removed and/or replaced?For
READDIR_PLUS,lookup_one_lenis called on each name and it requiresi_mutex, so the code currently holdsi_mutexover the whole sequence. This is triggering a deadlock.Yes, I've seen the context. However, you are _not_ holding it between actual
iterate_dir()and those callbacks, which opens a window when directory might have been changed.Quoted 5 lines
Again, what kind of consistency is expected by callers? Are they ready to cope with "there's no such entry anymore" or "inumber is nothing like what we'd put in ->ino, since it's no the same object" or "->d_type is completely unrelated to what we'd found, since the damn thing had been removed and created from scratch"?
Ah, sorry.
Quoted 8 lines
Yes, the callers are prepared for "there's no such entry anymore". They don't use
d_type, so don't care if it might be meaningless. NFSv4 doesn't use ino either, but NFSv3 does and isn't properly cautious about ino changing.In nfs3xdr, we should probably pass 'ino' to
encode_entryplus_baggage()and thence tocompose_entry_fh()and it should report failure ifdchild->d_inode->i_inodoesn't match.
Just to make sure I understand the concern..... So it shouldn't really be a problem if readdir and lookup find different objects for the same name, the problem is just when we mix attributes from the two objects, right? Looks like the v3 code could return an inode number derived from the readdir and a filehandle from the lookup, which is a problem. The v4 code will get everything from the result of the lookup, which should be OK.
Quoted 1 line
Simply not returning the extra attributes is perfectly acceptable in NFSv3.
Right, so no big deal anyway.--b.
Quoted 5 lines
So it looks like we are mostly OK here - we don't really need
i_mutexto be held for very long.NeilBrown
Quoted 33 lines · On Fri, 1 May 2015 09:29:53 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 23 lines · NeilBrown
Quoted 13 lines · On Fri, 1 May 2015 03:29:40 +0100 Al Viro <[email protected]> wrote:
Quoted 7 lines · NeilBrown
Quoted 3 lines
What kind of consistency warranties do callers expect, BTW? You do realize that between
iterate_dir()and callbacks an entry might have been removed and/or replaced?For
READDIR_PLUS,lookup_one_lenis called on each name and it requiresi_mutex, so the code currently holdsi_mutexover the whole sequence. This is triggering a deadlock.Yes, I've seen the context. However, you are _not_ holding it between actual
iterate_dir()and those callbacks, which opens a window when directory might have been changed.Quoted 5 lines
Again, what kind of consistency is expected by callers? Are they ready to cope with "there's no such entry anymore" or "inumber is nothing like what we'd put in ->ino, since it's no the same object" or "->d_type is completely unrelated to what we'd found, since the damn thing had been removed and created from scratch"?
Ah, sorry.
Quoted 8 lines
Yes, the callers are prepared for "there's no such entry anymore". They don't use
d_type, so don't care if it might be meaningless. NFSv4 doesn't use ino either, but NFSv3 does and isn't properly cautious about ino changing.In nfs3xdr, we should probably pass 'ino' to
encode_entryplus_baggage()and thence tocompose_entry_fh()and it should report failure ifdchild->d_inode->i_inodoesn't match.
Quoted 7 lines
Just to make sure I understand the concern..... So it shouldn't really be a problem if readdir and lookup find different objects for the same name, the problem is just when we mix attributes from the two objects, right? Looks like the v3 code could return an inode number derived from the readdir and a filehandle from the lookup, which is a problem. The v4 code will get everything from the result of the lookup, which should be OK.
That agrees with my understanding, yes.
I did wonder for a little while about the possibility of a directory containing both 'a' and 'b', and NFSv4 doing the readdir and the stat of 'a', and the a "mv a b" happening before the stat of 'b'.
Then the readdir response will show both 'a' and 'b' referring to the same object with a link count of 1.
I can't quite decide if that is a problem or not.
Quoted 4 lines
Quoted 2 lines
Simply not returning the extra attributes is perfectly acceptable in NFSv3.
Right, so no big deal anyway.--b.
Not a big deal, but we should really add a patch like the following ("like" as in "actually compile tested and documented" which this one isn't).
NeilBrown
Quoted 7 lines
Quoted 6 lines
So it looks like we are mostly OK here - we don't really need
i_mutexto be held for very long.NeilBrown
fs/nfsd/nfs3xdr.c +7 -5
@@ -805,7 +805,7 @@ encode_entry_baggage(struct nfsd3_readdirres *cd, __be32 *p, const char *name,static __be32 compose_entry_fh (struct nfsd3_readdirres *cd,struct svc_fh *fhp, -const char *name,int namlen) +const char *name,int namlen,u64 ino) {struct svc_export *exp;struct dentry *dparent, *dchild;
@@ -830,19 +830,21 @@ compose_entry_fh(struct nfsd3_readdirres *cd, struct svc_fh *fhp, goto out;if (d_really_is_negative (dchild)) goto out; +if (dchild->d_inode->i_ino != ino) + goto out; rv =fh_compose (fhp, exp, dchild, &cd->fh); out:dput (dchild);return rv; } -static __be32 *encode_entryplus_baggage (struct nfsd3_readdirres *cd,__be32 *p,const char *name,int namlen) +static __be32 *encode_entryplus_baggage (struct nfsd3_readdirres *cd,__be32 *p,const char *name,int namlen,u64 ino) {struct svc_fh *fh = &cd->scratch;__be32 err;fh_init (fh,NFS3_FHSIZE ); - err =compose_entry_fh (cd, fh, name, namlen); + err =compose_entry_fh (cd, fh, name, namlen, ino);if (err) { *p++ =0 ; *p++ =0 ;
@@ -927,7 +929,7 @@ encode_entry(struct readdir_cd *ccd, const char *name, int namlen, p =encode_entry_baggage (cd, p, name, namlen, ino);if (plus) - p =encode_entryplus_baggage (cd, p, name, namlen); + p =encode_entryplus_baggage (cd, p, name, namlen, ino); num_entry_words = p - cd->buffer; }else if (*(page+1 ) !=NULL ) {/* temporarily encode entry into next page, then move back to
@@ -941,7 +943,7 @@ encode_entry(struct readdir_cd *ccd, const char *name, int namlen, p1 =encode_entry_baggage (cd, p1, name, namlen, ino);if (plus) - p1 =encode_entryplus_baggage (cd, p1, name, namlen); + p1 =encode_entryplus_baggage (cd, p1, name, namlen, ino);/* determine entry word length and lengths to go in pages */ num_entry_words = p1 - tmp;
Quoted 46 lines · NeilBrown
Quoted 33 lines · On Fri, 1 May 2015 09:29:53 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 23 lines · NeilBrown
Quoted 13 lines · On Fri, 1 May 2015 03:29:40 +0100 Al Viro <[email protected]> wrote:
Quoted 7 lines · NeilBrown
Quoted 3 lines
What kind of consistency warranties do callers expect, BTW? You do realize that between
iterate_dir()and callbacks an entry might have been removed and/or replaced?For
READDIR_PLUS,lookup_one_lenis called on each name and it requiresi_mutex, so the code currently holdsi_mutexover the whole sequence. This is triggering a deadlock.Yes, I've seen the context. However, you are _not_ holding it between actual
iterate_dir()and those callbacks, which opens a window when directory might have been changed.Quoted 5 lines
Again, what kind of consistency is expected by callers? Are they ready to cope with "there's no such entry anymore" or "inumber is nothing like what we'd put in ->ino, since it's no the same object" or "->d_type is completely unrelated to what we'd found, since the damn thing had been removed and created from scratch"?
Ah, sorry.
Quoted 8 lines
Yes, the callers are prepared for "there's no such entry anymore". They don't use
d_type, so don't care if it might be meaningless. NFSv4 doesn't use ino either, but NFSv3 does and isn't properly cautious about ino changing.In nfs3xdr, we should probably pass 'ino' to
encode_entryplus_baggage()and thence tocompose_entry_fh()and it should report failure ifdchild->d_inode->i_inodoesn't match.Quoted 7 lines
Just to make sure I understand the concern..... So it shouldn't really be a problem if readdir and lookup find different objects for the same name, the problem is just when we mix attributes from the two objects, right? Looks like the v3 code could return an inode number derived from the readdir and a filehandle from the lookup, which is a problem. The v4 code will get everything from the result of the lookup, which should be OK.
That agrees with my understanding, yes.
Quoted 8 lines
I did wonder for a little while about the possibility of a directory containing both 'a' and 'b', and NFSv4 doing the readdir and the stat of 'a', and the a "mv a b" happening before the stat of 'b'.
Then the readdir response will show both 'a' and 'b' referring to the same object with a link count of 1.
I can't quite decide if that is a problem or not.
My understanding is that that's completely normal behavior for lots of filesystems.
Googling around.... Here's Ted on the question:
http://yarchive.net/comp/linux/readdir_nonatomicity.html
In some cases it won't even just get lost, but the old and new name can both be returned. For example, if you assume the use of a simple non-tree, linked-list implementation of a directory, such can be found in Solaris's ufs, BSD 4.3's FFS, Linux's ext2 and minix filesystems, and many others, and you have a fully tightly packed directory (i.e., no gaps), with the directory entry "foo" at the beginning of the file, and readdir() has already returned the first "foo" entry when some other application renames it "Supercalifragilisticexpialidocious", the new name will not fit in the old name's directory location, so it will be placed at the end of the directory --- where it will be returned by readdir() a second time.
This is not a bug; the POSIX specification explicitly allows this behavior. If a filename is renamed during a readdir() session of a directory, it is undefined where that neither, either, or both of the new and old filenames will be returned.
--b.
Quoted 76 lines · On Sat, 2 May 2015 20:37:43 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 46 lines · NeilBrown
Quoted 33 lines · On Fri, 1 May 2015 09:29:53 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 23 lines · NeilBrown
Quoted 13 lines · On Fri, 1 May 2015 03:29:40 +0100 Al Viro <[email protected]> wrote:
Quoted 7 lines · NeilBrown
Quoted 3 lines
What kind of consistency warranties do callers expect, BTW? You do realize that between
iterate_dir()and callbacks an entry might have been removed and/or replaced?For
READDIR_PLUS,lookup_one_lenis called on each name and it requiresi_mutex, so the code currently holdsi_mutexover the whole sequence. This is triggering a deadlock.Yes, I've seen the context. However, you are _not_ holding it between actual
iterate_dir()and those callbacks, which opens a window when directory might have been changed.Quoted 5 lines
Again, what kind of consistency is expected by callers? Are they ready to cope with "there's no such entry anymore" or "inumber is nothing like what we'd put in ->ino, since it's no the same object" or "->d_type is completely unrelated to what we'd found, since the damn thing had been removed and created from scratch"?
Ah, sorry.
Quoted 8 lines
Yes, the callers are prepared for "there's no such entry anymore". They don't use
d_type, so don't care if it might be meaningless. NFSv4 doesn't use ino either, but NFSv3 does and isn't properly cautious about ino changing.In nfs3xdr, we should probably pass 'ino' to
encode_entryplus_baggage()and thence tocompose_entry_fh()and it should report failure ifdchild->d_inode->i_inodoesn't match.Quoted 7 lines
Just to make sure I understand the concern..... So it shouldn't really be a problem if readdir and lookup find different objects for the same name, the problem is just when we mix attributes from the two objects, right? Looks like the v3 code could return an inode number derived from the readdir and a filehandle from the lookup, which is a problem. The v4 code will get everything from the result of the lookup, which should be OK.
That agrees with my understanding, yes.
Quoted 8 lines
I did wonder for a little while about the possibility of a directory containing both 'a' and 'b', and NFSv4 doing the readdir and the stat of 'a', and the a "mv a b" happening before the stat of 'b'.
Then the readdir response will show both 'a' and 'b' referring to the same object with a link count of 1.
I can't quite decide if that is a problem or not.
My understanding is that that's completely normal behavior for lots of filesystems.
Googling around.... Here's Ted on the question:
http://yarchive.net/comp/linux/readdir_nonatomicity.html
In some cases it won't even just get lost, but the old and new name can both be returned. For example, if you assume the use of a simple non-tree, linked-list implementation of a directory, such can be found in Solaris's ufs, BSD 4.3's FFS, Linux's ext2 and minix filesystems, and many others, and you have a fully tightly packed directory (i.e., no gaps), with the directory entry "foo" at the beginning of the file, and
readdir()has already returned the first "foo" entry when some other application renames it "Supercalifragilisticexpialidocious", the new name will not fit in the old name's directory location, so it will be placed at the end of the directory --- where it will be returned byreaddir()a second time.
Quoted 5 lines
This is not a bug; the POSIX specification explicitly allows this behavior. If a filename is renamed during a
readdir()session of a directory, it is undefined where that neither, either, or both of the new and old filenames will be returned.
I think that is a slightly different situation to the one I was imagining. Ted's observation here is completely about readdir results.
A NFS READDIR_PLUS result can be used to satisfy subsequence stat() requests. I don't think it would ever be correct to
stat('a')
stat('b')
stat('a')
and get exactly the same stat info in every case, including inode number and ctime and link count of '1'.
If those stats were served from the READDIRPLUS results that I described above, that is exactly what you would get.
I'm not sure if the post-op attributes would be enough to tell the client it needs to do a GETATTR again straight away to verify things.
But if the attr info stays cached (which is kind-of the point of READDIR_PLUS), this is a very different circumstance than the one Ted described.
Still not sure how important it is, but I like the NFSv3 option of just not returning attributes if we aren't certain of them.
An option for NFSv4 might be to abort/retry the readdir op if the directory has changed at all(?).
NeilBrown
Quoted 57 lines · NeilBrown
Quoted 33 lines · On Fri, 1 May 2015 09:29:53 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 23 lines · NeilBrown
Quoted 13 lines · On Fri, 1 May 2015 03:29:40 +0100 Al Viro <[email protected]> wrote:
Quoted 7 lines · NeilBrown
Quoted 3 lines
What kind of consistency warranties do callers expect, BTW? You do realize that between
iterate_dir()and callbacks an entry might have been removed and/or replaced?For
READDIR_PLUS,lookup_one_lenis called on each name and it requiresi_mutex, so the code currently holdsi_mutexover the whole sequence. This is triggering a deadlock.Yes, I've seen the context. However, you are _not_ holding it between actual
iterate_dir()and those callbacks, which opens a window when directory might have been changed.Quoted 5 lines
Again, what kind of consistency is expected by callers? Are they ready to cope with "there's no such entry anymore" or "inumber is nothing like what we'd put in ->ino, since it's no the same object" or "->d_type is completely unrelated to what we'd found, since the damn thing had been removed and created from scratch"?
Ah, sorry.
Quoted 8 lines
Yes, the callers are prepared for "there's no such entry anymore". They don't use
d_type, so don't care if it might be meaningless. NFSv4 doesn't use ino either, but NFSv3 does and isn't properly cautious about ino changing.In nfs3xdr, we should probably pass 'ino' to
encode_entryplus_baggage()and thence tocompose_entry_fh()and it should report failure ifdchild->d_inode->i_inodoesn't match.Quoted 7 lines
Just to make sure I understand the concern..... So it shouldn't really be a problem if readdir and lookup find different objects for the same name, the problem is just when we mix attributes from the two objects, right? Looks like the v3 code could return an inode number derived from the readdir and a filehandle from the lookup, which is a problem. The v4 code will get everything from the result of the lookup, which should be OK.
That agrees with my understanding, yes.
I did wonder for a little while about the possibility of a directory containing both 'a' and 'b', and NFSv4 doing the readdir and the stat of 'a', and the a "mv a b" happening before the stat of 'b'.
Then the readdir response will show both 'a' and 'b' referring to the same object with a link count of 1.
I can't quite decide if that is a problem or not.
Quoted 1 line
Quoted 6 lines
Quoted 3 lines
Quoted 1 line
Simply not returning the extra attributes is perfectly acceptable in NFSv3.
Right, so no big deal anyway.--b.
Not a big deal, but we should really add a patch like the following ("like" as in "actually compile tested and documented" which this one isn't).
Doesn't seem to break anything. Any second thoughts, or can I add a signed-off-by?
--b.
commit e11f8acace69
Author: NeilBrown <[email protected]> Date: Sun May 3 09:16:53 2015 +1000
nfsd: stop READDIRPLUS returning inconsistent attributes
The NFSv3 READDIRPLUS gets some of the returned attributes from the readdir, and some from an inode returned from a new lookup. The two objects could be different thanks to intervening renames.
The attributes in READDIRPLUS are optional, so let's just skip them if we notice this case.
fs/nfsd/nfs3xdr.c +7 -5
@@ -805,7 +805,7 @@ encode_entry_baggage(struct nfsd3_readdirres *cd, __be32 *p, const char *name,static __be32 compose_entry_fh (struct nfsd3_readdirres *cd,struct svc_fh *fhp, -const char *name,int namlen) +const char *name,int namlen,u64 ino) {struct svc_export *exp;struct dentry *dparent, *dchild;
@@ -830,19 +830,21 @@ compose_entry_fh(struct nfsd3_readdirres *cd, struct svc_fh *fhp, goto out;if (d_really_is_negative (dchild)) goto out; +if (dchild->d_inode->i_ino != ino) + goto out; rv =fh_compose (fhp, exp, dchild, &cd->fh); out:dput (dchild);return rv; } -static __be32 *encode_entryplus_baggage (struct nfsd3_readdirres *cd,__be32 *p,const char *name,int namlen) +static __be32 *encode_entryplus_baggage (struct nfsd3_readdirres *cd,__be32 *p,const char *name,int namlen,u64 ino) {struct svc_fh *fh = &cd->scratch;__be32 err;fh_init (fh,NFS3_FHSIZE ); - err =compose_entry_fh (cd, fh, name, namlen); + err =compose_entry_fh (cd, fh, name, namlen, ino);if (err) { *p++ =0 ; *p++ =0 ;
@@ -927,7 +929,7 @@ encode_entry(struct readdir_cd *ccd, const char *name, int namlen, p =encode_entry_baggage (cd, p, name, namlen, ino);if (plus) - p =encode_entryplus_baggage (cd, p, name, namlen); + p =encode_entryplus_baggage (cd, p, name, namlen, ino); num_entry_words = p - cd->buffer; }else if (*(page+1 ) !=NULL ) {/* temporarily encode entry into next page, then move back to
@@ -941,7 +943,7 @@ encode_entry(struct readdir_cd *ccd, const char *name, int namlen, p1 =encode_entry_baggage (cd, p1, name, namlen, ino);if (plus) - p1 =encode_entryplus_baggage (cd, p1, name, namlen); + p1 =encode_entryplus_baggage (cd, p1, name, namlen, ino);/* determine entry word length and lengths to go in pages */ num_entry_words = p1 - tmp;
Quoted 60 lines · On Mon, 4 May 2015 17:48:22 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 59 lines · NeilBrown
Quoted 33 lines · On Fri, 1 May 2015 09:29:53 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 23 lines · NeilBrown
Quoted 13 lines · On Fri, 1 May 2015 03:29:40 +0100 Al Viro <[email protected]> wrote:
Quoted 7 lines · NeilBrown
Quoted 3 lines
What kind of consistency warranties do callers expect, BTW? You do realize that between
iterate_dir()and callbacks an entry might have been removed and/or replaced?For
READDIR_PLUS,lookup_one_lenis called on each name and it requiresi_mutex, so the code currently holdsi_mutexover the whole sequence. This is triggering a deadlock.Yes, I've seen the context. However, you are _not_ holding it between actual
iterate_dir()and those callbacks, which opens a window when directory might have been changed.Quoted 5 lines
Again, what kind of consistency is expected by callers? Are they ready to cope with "there's no such entry anymore" or "inumber is nothing like what we'd put in ->ino, since it's no the same object" or "->d_type is completely unrelated to what we'd found, since the damn thing had been removed and created from scratch"?
Ah, sorry.
Quoted 8 lines
Yes, the callers are prepared for "there's no such entry anymore". They don't use
d_type, so don't care if it might be meaningless. NFSv4 doesn't use ino either, but NFSv3 does and isn't properly cautious about ino changing.In nfs3xdr, we should probably pass 'ino' to
encode_entryplus_baggage()and thence tocompose_entry_fh()and it should report failure ifdchild->d_inode->i_inodoesn't match.Quoted 7 lines
Just to make sure I understand the concern..... So it shouldn't really be a problem if readdir and lookup find different objects for the same name, the problem is just when we mix attributes from the two objects, right? Looks like the v3 code could return an inode number derived from the readdir and a filehandle from the lookup, which is a problem. The v4 code will get everything from the result of the lookup, which should be OK.
That agrees with my understanding, yes.
I did wonder for a little while about the possibility of a directory containing both 'a' and 'b', and NFSv4 doing the readdir and the stat of 'a', and the a "mv a b" happening before the stat of 'b'.
Then the readdir response will show both 'a' and 'b' referring to the same object with a link count of 1.
I can't quite decide if that is a problem or not.
Quoted 3 lines
Quoted 2 lines
Simply not returning the extra attributes is perfectly acceptable in NFSv3.
Quoted 7 lines
Quoted 4 lines
Quoted 1 line
Right, so no big deal anyway.--b.
Not a big deal, but we should really add a patch like the following ("like" as in "actually compile tested and documented" which this one isn't).
Doesn't seem to break anything. Any second thoughts, or can I add a signed-off-by?
No second thoughts.
Thanks.
NeilBrown
Quoted 73 lines
--b.
commit
e11f8acace69Author: NeilBrown <[email protected]> Date: Sun May 3 09:16:53 2015 +1000nfsd: stop READDIRPLUS returning inconsistent attributes
The NFSv3 READDIRPLUS gets some of the returned attributes from the readdir, and some from an inode returned from a new lookup. The two objects could be different thanks to intervening renames.
The attributes in READDIRPLUS are optional, so let's just skip them if we notice this case.
Signed-off-by J. Bruce Fields <[email protected]>fs/nfsd/nfs3xdr.c +7
-5@@ -805,7 +805,7 @@ encode_entry_baggage(struct nfsd3_readdirres *cd, __be32 *p, const char *name,static __be32 compose_entry_fh (struct nfsd3_readdirres *cd,struct svc_fh *fhp, -const char *name,int namlen) +const char *name,int namlen,u64 ino) {struct svc_export *exp;struct dentry *dparent, *dchild;@@ -830,19 +830,21 @@ compose_entry_fh(struct nfsd3_readdirres *cd, struct svc_fh *fhp, goto out;if (d_really_is_negative (dchild)) goto out; +if (dchild->d_inode->i_ino != ino) + goto out; rv =fh_compose (fhp, exp, dchild, &cd->fh); out:dput (dchild);return rv; } -static __be32 *encode_entryplus_baggage (struct nfsd3_readdirres *cd,__be32 *p,const char *name,int namlen) +static __be32 *encode_entryplus_baggage (struct nfsd3_readdirres *cd,__be32 *p,const char *name,int namlen,u64 ino) {struct svc_fh *fh = &cd->scratch;__be32 err;fh_init (fh,NFS3_FHSIZE ); - err =compose_entry_fh (cd, fh, name, namlen); + err =compose_entry_fh (cd, fh, name, namlen, ino);if (err) { *p++ =0 ; *p++ =0 ;@@ -927,7 +929,7 @@ encode_entry(struct readdir_cd *ccd, const char *name, int namlen, p =encode_entry_baggage (cd, p, name, namlen, ino);if (plus) - p =encode_entryplus_baggage (cd, p, name, namlen); + p =encode_entryplus_baggage (cd, p, name, namlen, ino); num_entry_words = p - cd->buffer; }else if (*(page+1 ) !=NULL ) {/* temporarily encode entry into next page, then move back to @@ -941,7 +943,7 @@ encode_entry(struct readdir_cd *ccd, const char *name, int namlen, p1 =encode_entry_baggage (cd, p1, name, namlen, ino);if (plus) - p1 =encode_entryplus_baggage (cd, p1, name, namlen); + p1 =encode_entryplus_baggage (cd, p1, name, namlen, ino);/* determine entry word length and lengths to go in pages */ num_entry_words = p1 - tmp;
Quoted 28 lines · NeilBrown
Quoted 25 lines · On Thu, 30 Apr 2015 17:36:02 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 23 lines · NeilBrown
Quoted 2 lines · On Wed, 29 Apr 2015 15:19:34 -0400 "J. Bruce Fields" <[email protected]>…
Maybe drop the locking from
nfsd_buffered_readdirand *just* take thei_mutexaroundlookup_one_len(), if that's the only place we need it?I think it is the only place needed. Certainly normal path lookup only takes
i_mutexvery briefly around__lookup_hash().One cost would be that we would take the mutex for every name instead of once for a whole set of names. That is generally frowned upon for performance reasons.
An alternative might be to stop using
lookup_one_len, and usekern_path()instead. Orkern_path_mountpointif we don't want to cross a mountpoint.They would only take the
i_mutexif absolutely necessary.The draw back is that they don't take a 'len' argument, so we would need to make sure the name is nul terminated (and maybe double-check that there is no '/'??). It would be fairly easy to nul-terminate names from readdir - just use a bit more space in the buffer in
nfsd_buffered_filldir().
Quoted 8 lines
Quoted 6 lines
They're also a lot more complicated than
lookup_one_len. Is any of this extra stuff they do (audit?) going to bite us?For me understanding that would be harder than writing a variant of
lookup_one_lenthat took thei_mutexwhen required. But I guess that's my problem, I should try to understand the lookup code better....Tempting though ... see below (untested).
With documentation and a stab at the nfsd stuff (also untested. OK, and pretty much unread. Compiles, though!)
--b.
commit 6023d45abd1a
Author: NeilBrown <[email protected]> Date: Fri May 1 11:53:26 2015 +1000
nfsd: don't hold i_mutex over userspace upcalls
We need information about exports when crossing mountpoints during lookup or NFSv4 readdir. If we don't already have that information cached, we may have to ask (and wait for) rpc.mountd.
In both cases we currently hold the i_mutex on the parent of the directory we're asking rpc.mountd about. We've seen situations where rpc.mountd performs some operation on that directory that tries to take the i_mutex again, resulting in deadlock.
With some care, we may be able to avoid that in rpc.mountd. But it seems better just to avoid holding a mutex while waiting on userspace.
It appears that lookup_one_len is pretty much the only operation that needs the i_mutex. So we could just drop the i_mutex elsewhere and do something like
mutex_lock()
lookup_one_len()
mutex_unlock()
In may cases though the lookup would have been cached and not required the i_mutex, so it's more efficient to create a lookup_one_len() variant that only takes the i_mutex when necessary.
fs/namei.c +64 -0
@@ -2139,6 +2139,8 @@ static struct dentry *lookup_hash(struct nameidata *nd)* * Note that this routine is purely a helper for filesystem usage and should * not be called by generic code. +* +* Must be called with the parented i_mutex held. */ struct dentry *lookup_one_len (const char *name,struct dentry *base,int len) {
@@ -2182,6 +2184,68 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) }EXPORT_SYMBOL (lookup_one_len); +/** +* lookup_one_len - filesystem helper to lookup single pathname component +* @name: pathname component to lookup +* @base: base directory to lookup from +* @len: maximum length @len should be interpreted to +* +* Note that this routine is purely a helper for filesystem usage and should +* not be called by generic code. +* +* Unlike lookup_one_len, it should be called without the parent +* i_mutex held, and will take the i_mutex itself if necessary. +*/ +struct dentry *lookup_one_len_unlocked (const char *name, +struct dentry *base,int len) +{ +struct qstr this; +unsigned int c; +int err; +struct dentry *ret; + +WARN_ON_ONCE (!mutex_is_locked (&base->d_inode->i_mutex)); + + this.name = name; + this.len = len; + this.hash =full_name_hash (name, len); +if (!len) +return ERR_PTR (-EACCES ); + +if (unlikely (name[0 ] =='.' )) { +if (len <2 || (len ==2 && name[1 ] =='.' )) +return ERR_PTR (-EACCES ); + } + +while (len--) { + c = *(const unsigned char *)name++; +if (c =='/' || c =='\0' ) +return ERR_PTR (-EACCES ); + } +/* +* See if the low-level filesystem might want +* to use its own hash.. +*/ +if (base->d_flags &DCACHE_OP_HASH ) { +int err = base->d_op->d_hash (base, &this); +if (err <0 ) +return ERR_PTR (err); + } + + err =inode_permission (base->d_inode,MAY_EXEC ); +if (err) +return ERR_PTR (err); + + ret =__d_lookup (base, &this); +if (ret) +return ret; +mutex_lock (&base->d_inode->i_mutex); + ret =__lookup_hash (&this, base,0 ); +mutex_unlock (&base->d_inode->i_mutex); +return ret; +} +EXPORT_SYMBOL (lookup_one_len_unlocked); +int user_path_at_empty (int dfd,const char __user *name,unsigned flags,struct path *path,int *empty) {
fs/nfsd/nfs4xdr.c +4 -4
@@ -2804,14 +2804,14 @@ nfsd4_encode_dirent_fattr(struct xdr_stream *xdr, struct nfsd4_readdir *cd,__be32 nfserr;int ignore_crossmnt =0 ; - dentry =lookup_one_len (name, cd->rd_fhp->fh_dentry, namlen); + dentry =lookup_one_len_unlocked (name, cd->rd_fhp->fh_dentry, namlen);if (IS_ERR (dentry))return nfserrno (PTR_ERR (dentry));if (d_really_is_negative (dentry)) {/* -* nfsd_buffered_readdir drops the i_mutex between -* readdir and calling this callback, leaving a window -* where this directory entry could have gone away. +* we're not holding the i_mutex here, so there's +* a window where this directory entry could have gone +* away. */ dput (dentry);return nfserr_noent;
fs/nfsd/vfs.c +7 -11
@@ -217,6 +217,13 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, host_err =PTR_ERR (dentry);if (IS_ERR (dentry)) goto out_nfserr; +if (!S_ISREG (d_inode (dentry)->i_mode)) { +/* +* Never mind, we're not going to open this +* anyway. And we don't want to hold it over +* the userspace upcalls in nfsd_mountpoint. */ +fh_unlock (fhp); + }/* * check if we have crossed a mount point ... */
@@ -1876,7 +1883,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func, offset = *offsetp;while (1 ) { -struct inode *dir_inode =file_inode (file);unsigned int reclen; cdp->err = nfserr_eof;/* will be cleared on successful read */
@@ -1895,15 +1901,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func,if (!size)break ; -/* -* Various filldir functions may end up calling back into -* lookup_one_len() and the file system's ->lookup() method. -* These expect i_mutex to be held, as it would within readdir. -*/ - host_err =mutex_lock_killable (&dir_inode->i_mutex); -if (host_err) -break ; - de = (struct buffered_dirent *)buf.dirent;while (size >0 ) { offset = de->offset;
@@ -1920,7 +1917,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func, size -= reclen; de = (struct buffered_dirent *)((char *)de + reclen); } -mutex_unlock (&dir_inode->i_mutex);if (size >0 )/* We bailed out early */ break ;
include/linux/namei.h +1 -0
@@ -62,6 +62,7 @@ extern struct dentry *kern_path_locked(const char *, struct path *);extern int kern_path_mountpoint (int ,const char *,struct path *,unsigned int );extern struct dentry *lookup_one_len (const char *,struct dentry *,int ); +extern struct dentry *lookup_one_len_unlocked (const char *,struct dentry *,int );extern int follow_down_one (struct path *);extern int follow_down (struct path *);
Quoted 103 lines · J. Bruce Fields
Quoted 28 lines · NeilBrown
Quoted 25 lines · On Thu, 30 Apr 2015 17:36:02 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 23 lines · NeilBrown
Quoted 2 lines · On Wed, 29 Apr 2015 15:19:34 -0400 "J. Bruce Fields" <[email protected]>…
Maybe drop the locking from
nfsd_buffered_readdirand *just* take thei_mutexaroundlookup_one_len(), if that's the only place we need it?I think it is the only place needed. Certainly normal path lookup only takes
i_mutexvery briefly around__lookup_hash().One cost would be that we would take the mutex for every name instead of once for a whole set of names. That is generally frowned upon for performance reasons.
An alternative might be to stop using
lookup_one_len, and usekern_path()instead. Orkern_path_mountpointif we don't want to cross a mountpoint.They would only take the
i_mutexif absolutely necessary.The draw back is that they don't take a 'len' argument, so we would need to make sure the name is nul terminated (and maybe double-check that there is no '/'??). It would be fairly easy to nul-terminate names from readdir - just use a bit more space in the buffer in
nfsd_buffered_filldir().Quoted 8 lines
Quoted 6 lines
They're also a lot more complicated than
lookup_one_len. Is any of this extra stuff they do (audit?) going to bite us?For me understanding that would be harder than writing a variant of
lookup_one_lenthat took thei_mutexwhen required. But I guess that's my problem, I should try to understand the lookup code better....Tempting though ... see below (untested).
With documentation and a stab at the nfsd stuff (also untested. OK, and pretty much unread. Compiles, though!)
--b.
commit
6023d45abd1aAuthor: NeilBrown <[email protected]> Date: Fri May 1 11:53:26 2015 +1000nfsd: don't hold
i_mutexover userspace upcallsWe need information about exports when crossing mountpoints during lookup or NFSv4 readdir. If we don't already have that information cached, we may have to ask (and wait for) rpc.mountd.
In both cases we currently hold the
i_mutexon the parent of the directory we're asking rpc.mountd about. We've seen situations where rpc.mountd performs some operation on that directory that tries to take thei_mutexagain, resulting in deadlock.With some care, we may be able to avoid that in rpc.mountd. But it seems better just to avoid holding a mutex while waiting on userspace.
It appears that
lookup_one_lenis pretty much the only operation that needs thei_mutex. So we could just drop thei_mutexelsewhere and do something like
mutex_lock()
lookup_one_len()
mutex_unlock()In may cases though the lookup would have been cached and not required the
i_mutex, so it's more efficient to create alookup_one_len()variant that only takes thei_mutexwhen necessary.Signed-off-by J. Bruce Fields <[email protected]>fs/namei.c +13
-0@@ -2139,6 +2139,8 @@ static struct dentry *lookup_hash(struct nameidata *nd)* * Note that this routine is purely a helper for filesystem usage and should * not be called by generic code. +* +* Must be called with the parented i_mutex held. */ struct dentry *lookup_one_len (const char *name,struct dentry *base,int len) {@@ -2182,6 +2184,68 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) }EXPORT_SYMBOL (lookup_one_len); +/** +* lookup_one_len - filesystem helper to lookup single pathname component +* @name: pathname component to lookup +* @base: base directory to lookup from +* @len: maximum length @len should be interpreted to +* +* Note that this routine is purely a helper for filesystem usage and should +* not be called by generic code. +* +* Unlike lookup_one_len, it should be called without the parent +* i_mutex held, and will take the i_mutex itself if necessary.
fs/namei.c struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) · line 2207
Quoted 10 lines
fs/namei.c +10
-0+*/ +struct dentry *lookup_one_len_unlocked (const char *name, +struct dentry *base,int len) +{ +struct qstr this; +unsigned int c; +int err; +struct dentry *ret; + +WARN_ON_ONCE (!mutex_is_locked (&base->d_inode->i_mutex));
Remove this line.
Quoted 72 lines
fs/namei.c +41
-0+ + this.name = name; + this.len = len; + this.hash =full_name_hash (name, len); +if (!len) +return ERR_PTR (-EACCES ); + +if (unlikely (name[0 ] =='.' )) { +if (len <2 || (len ==2 && name[1 ] =='.' )) +return ERR_PTR (-EACCES ); + } + +while (len--) { + c = *(const unsigned char *)name++; +if (c =='/' || c =='\0' ) +return ERR_PTR (-EACCES ); + } +/* +* See if the low-level filesystem might want +* to use its own hash.. +*/ +if (base->d_flags &DCACHE_OP_HASH ) { +int err = base->d_op->d_hash (base, &this); +if (err <0 ) +return ERR_PTR (err); + } + + err =inode_permission (base->d_inode,MAY_EXEC ); +if (err) +return ERR_PTR (err); + + ret =__d_lookup (base, &this); +if (ret) +return ret; +mutex_lock (&base->d_inode->i_mutex); + ret =__lookup_hash (&this, base,0 ); +mutex_unlock (&base->d_inode->i_mutex); +return ret; +} +EXPORT_SYMBOL (lookup_one_len_unlocked); +int user_path_at_empty (int dfd,const char __user *name,unsigned flags,struct path *path,int *empty) {fs/nfsd/nfs4xdr.c +4
-4@@ -2804,14 +2804,14 @@ nfsd4_encode_dirent_fattr(struct xdr_stream *xdr, struct nfsd4_readdir *cd,__be32 nfserr;int ignore_crossmnt =0 ; - dentry =lookup_one_len (name, cd->rd_fhp->fh_dentry, namlen); + dentry =lookup_one_len_unlocked (name, cd->rd_fhp->fh_dentry, namlen);if (IS_ERR (dentry))return nfserrno (PTR_ERR (dentry));if (d_really_is_negative (dentry)) {/* -* nfsd_buffered_readdir drops the i_mutex between -* readdir and calling this callback, leaving a window -* where this directory entry could have gone away. +* we're not holding the i_mutex here, so there's +* a window where this directory entry could have gone +* away. */ dput (dentry);return nfserr_noent;fs/nfsd/vfs.c +0
-0
fs/nfsd/vfs.c nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, · line 220
Quoted 4 lines
fs/nfsd/vfs.c +1
-0@@ -217,6 +217,13 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, host_err =PTR_ERR (dentry);if (IS_ERR (dentry)) goto out_nfserr; +if (!S_ISREG (d_inode (dentry)->i_mode)) {
Got a crash here tested by pynfs,
# ./testserver.py 127.0.0.1:/nfs/pnfs/ --rundeps --maketree open
PID: 2314 TASK: ffff88006ad2cfc0 CPU: 0 COMMAND: "nfsd"
#0 [ffff88006adc7870] machine_kexec at ffffffff8104debb #1 [ffff88006adc78e0] crash_kexec at ffffffff810f92a2 #2 [ffff88006adc79b0] oops_end at ffffffff81015c28
#3 [ffff88006adc79e0] no_context at ffffffff8105a9af #4 [ffff88006adc7a50] __bad_area_nosemaphore at ffffffff8105ac90 #5 [ffff88006adc7aa0] bad_area_nosemaphore at ffffffff8105ae23 #6 [ffff88006adc7ab0] __do_page_fault at ffffffff8105b13e #7 [ffff88006adc7b10] do_page_fault at ffffffff8105b4df #8 [ffff88006adc7b50] page_fault at ffffffff81713258
[exception RIP: nfsd_lookup_dentry+231]
RIP: ffffffffa0447b87 RSP: ffff88006adc7c08 RFLAGS: 00010283
RAX: ffff88006a9d2180 RBX: ffff88006ad80068 RCX: 0000000000000000
RDX: 0000000000000000 RSI: ffff88007fdf4eb0 RDI: ffff88006a9d2180
RBP: ffff88006adc7c88 R8: ffff88006b13dcc0 R9: 0000000000000000
R10: ffff88006adb6cc0 R11: ffff88006adb6cc0 R12: ffff88006a9d0cc0
R13: ffff88006adc7ca0 R14: ffff88006adc7ca8 R15: ffff88006ad640bc
ORIG_RAX: ffffffffffffffff CS: 0010 SS: 0018 #9 [ffff88006adc7c90] nfsd_lookup at ffffffffa0448029 [nfsd]
#10 [ffff88006adc7cf0] nfsd4_open at ffffffffa0456cbb [nfsd] #11 [ffff88006adc7d60] nfsd4_proc_compound at ffffffffa0457317 [nfsd] #12 [ffff88006adc7dc0] nfsd_dispatch at ffffffffa0442f83 [nfsd] #13 [ffff88006adc7e00] svc_process_common at ffffffffa0160c5b [sunrpc] #14 [ffff88006adc7e70] svc_process at ffffffffa0161cc3 [sunrpc] #15 [ffff88006adc7ea0] nfsd at ffffffffa044294f [nfsd]
#16 [ffff88006adc7ed0] kthread at ffffffff810a9d07
#17 [ffff88006adc7f50] ret_from_fork at ffffffff81711b62
thanks,
Kinglong Mee
Quoted 15 lines · Kinglong Mee
fs/namei.c struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) · line 2207
Quoted 12 lines · J. Bruce Fields
+ * Unlike
lookup_one_len, it should be called without the parent
+ *i_mutexheld, and will take thei_mutexitself if necessary.
+ */fs/namei.c +9
-0+struct dentry *lookup_one_len_unlocked (const char *name, +struct dentry *base,int len) +{ +struct qstr this; +unsigned int c; +int err; +struct dentry *ret; + +WARN_ON_ONCE (!mutex_is_locked (&base->d_inode->i_mutex));Remove this line.
Whoops, thanks.
Quoted 11 lines
fs/nfsd/vfs.c nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, · line 220
Quoted 9 lines
fs/nfsd/vfs.c +1
-0@@ -217,6 +217,13 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, host_err =PTR_ERR (dentry);if (IS_ERR (dentry)) goto out_nfserr; +if (!S_ISREG (d_inode (dentry)->i_mode)) {Got a crash here tested by pynfs,
OK, I guess I just forgot to take into account negative dentries. Testing that now with (!(d_inode(dentry) && S_ISREG(..))).
--b.
Quoted 32 lines
# ./testserver.py 127.0.0.1:/nfs/pnfs/ --rundeps --maketree openPID: 2314 TASK:
ffff88006ad2cfc0CPU: 0 COMMAND: "nfsd"
#0 [ffff88006adc7870]machine_kexecatffffffff8104debb#1 [ffff88006adc78e0]crash_kexecatffffffff810f92a2#2 [ffff88006adc79b0]oops_endatffffffff81015c28
#3 [ffff88006adc79e0]no_contextatffffffff8105a9af#4 [ffff88006adc7a50]__bad_area_nosemaphoreatffffffff8105ac90#5 [ffff88006adc7aa0]bad_area_nosemaphoreatffffffff8105ae23#6 [ffff88006adc7ab0]__do_page_faultatffffffff8105b13e#7 [ffff88006adc7b10]do_page_faultatffffffff8105b4df#8 [ffff88006adc7b50]page_faultatffffffff81713258[exception RIP: nfsd_lookup_dentry+231] RIP: ffffffffa0447b87 RSP: ffff88006adc7c08 RFLAGS: 00010283 RAX: ffff88006a9d2180 RBX: ffff88006ad80068 RCX: 0000000000000000 RDX: 0000000000000000 RSI: ffff88007fdf4eb0 RDI: ffff88006a9d2180 RBP: ffff88006adc7c88 R8: ffff88006b13dcc0 R9: 0000000000000000 R10: ffff88006adb6cc0 R11: ffff88006adb6cc0 R12: ffff88006a9d0cc0 R13: ffff88006adc7ca0 R14: ffff88006adc7ca8 R15: ffff88006ad640bc ORIG_RAX: ffffffffffffffff CS: 0010 SS: 0018#9 [
ffff88006adc7c90]nfsd_lookupatffffffffa0448029[nfsd]
#10 [ffff88006adc7cf0]nfsd4_openatffffffffa0456cbb[nfsd] #11 [ffff88006adc7d60]nfsd4_proc_compoundatffffffffa0457317[nfsd] #12 [ffff88006adc7dc0]nfsd_dispatchatffffffffa0442f83[nfsd] #13 [ffff88006adc7e00]svc_process_commonatffffffffa0160c5b[sunrpc] #14 [ffff88006adc7e70]svc_processatffffffffa0161cc3[sunrpc] #15 [ffff88006adc7ea0] nfsd atffffffffa044294f[nfsd]
#16 [ffff88006adc7ed0] kthread atffffffff810a9d07
#17 [ffff88006adc7f50]ret_from_forkatffffffff81711b62thanks,
Kinglong Mee
Quoted 31 lines · J. Bruce Fields
Quoted 15 lines · Kinglong Mee
Review
Quoted 12 lines · J. Bruce Fields
+ * Unlike
lookup_one_len, it should be called without the parent
+ *i_mutexheld, and will take thei_mutexitself if necessary.
+ */(quoted diff) +9
-0+struct dentry *lookup_one_len_unlocked(const char *name, + struct dentry *base, int len) +{ + struct qstr this; + unsigned int c; + int err; + struct dentry *ret; + + WARN_ON_ONCE(!mutex_is_locked(&base->d_inode->i_mutex));Remove this line.
Whoops, thanks.
Quoted 11 lines
fs/nfsd/vfs.c nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, · line 220
Quoted 9 lines
fs/nfsd/vfs.c +1
-0@@ -217,6 +217,13 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, host_err =PTR_ERR (dentry);if (IS_ERR (dentry)) goto out_nfserr; +if (!S_ISREG (d_inode (dentry)->i_mode)) {Got a crash here tested by pynfs,
Quoted 2 lines
OK, I guess I just forgot to take into account negative dentries. Testing that now with (!(d_inode(dentry) &&
S_ISREG(..))).
And I also forgot nfs3xdr.c.
--b.
commit 6c942d342f9f
Author: NeilBrown <[email protected]> Date: Fri May 1 11:53:26 2015 +1000
nfsd: don't hold i_mutex over userspace upcalls
We need information about exports when crossing mountpoints during lookup or NFSv4 readdir. If we don't already have that information cached, we may have to ask (and wait for) rpc.mountd.
In both cases we currently hold the i_mutex on the parent of the directory we're asking rpc.mountd about. We've seen situations where rpc.mountd performs some operation on that directory that tries to take the i_mutex again, resulting in deadlock.
With some care, we may be able to avoid that in rpc.mountd. But it seems better just to avoid holding a mutex while waiting on userspace.
It appears that lookup_one_len is pretty much the only operation that needs the i_mutex. So we could just drop the i_mutex elsewhere and do something like
mutex_lock()
lookup_one_len()
mutex_unlock()
In many cases though the lookup would have been cached and not required the i_mutex, so it's more efficient to create a lookup_one_len() variant that only takes the i_mutex when necessary.
fs/namei.c +62 -0
@@ -2139,6 +2139,8 @@ static struct dentry *lookup_hash(struct nameidata *nd)* * Note that this routine is purely a helper for filesystem usage and should * not be called by generic code. +* +* Must be called with the parented i_mutex held. */ struct dentry *lookup_one_len (const char *name,struct dentry *base,int len) {
@@ -2182,6 +2184,66 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) }EXPORT_SYMBOL (lookup_one_len); +/** +* lookup_one_len - filesystem helper to lookup single pathname component +* @name: pathname component to lookup +* @base: base directory to lookup from +* @len: maximum length @len should be interpreted to +* +* Note that this routine is purely a helper for filesystem usage and should +* not be called by generic code. +* +* Unlike lookup_one_len, it should be called without the parent +* i_mutex held, and will take the i_mutex itself if necessary. +*/ +struct dentry *lookup_one_len_unlocked (const char *name, +struct dentry *base,int len) +{ +struct qstr this; +unsigned int c; +int err; +struct dentry *ret; + + this.name = name; + this.len = len; + this.hash =full_name_hash (name, len); +if (!len) +return ERR_PTR (-EACCES ); + +if (unlikely (name[0 ] =='.' )) { +if (len <2 || (len ==2 && name[1 ] =='.' )) +return ERR_PTR (-EACCES ); + } + +while (len--) { + c = *(const unsigned char *)name++; +if (c =='/' || c =='\0' ) +return ERR_PTR (-EACCES ); + } +/* +* See if the low-level filesystem might want +* to use its own hash.. +*/ +if (base->d_flags &DCACHE_OP_HASH ) { +int err = base->d_op->d_hash (base, &this); +if (err <0 ) +return ERR_PTR (err); + } + + err =inode_permission (base->d_inode,MAY_EXEC ); +if (err) +return ERR_PTR (err); + + ret =__d_lookup (base, &this); +if (ret) +return ret; +mutex_lock (&base->d_inode->i_mutex); + ret =__lookup_hash (&this, base,0 ); +mutex_unlock (&base->d_inode->i_mutex); +return ret; +} +EXPORT_SYMBOL (lookup_one_len_unlocked); +int user_path_at_empty (int dfd,const char __user *name,unsigned flags,struct path *path,int *empty) {
fs/nfsd/nfs3xdr.c +1 -1
@@ -823,7 +823,7 @@ compose_entry_fh(struct nfsd3_readdirres *cd, struct svc_fh *fhp, }else dchild =dget (dparent); }else - dchild =lookup_one_len (name, dparent, namlen); + dchild =lookup_one_len_unlocked (name, dparent, namlen);if (IS_ERR (dchild))return rv;if (d_mountpoint (dchild))
fs/nfsd/nfs4xdr.c +4 -4
@@ -2804,14 +2804,14 @@ nfsd4_encode_dirent_fattr(struct xdr_stream *xdr, struct nfsd4_readdir *cd,__be32 nfserr;int ignore_crossmnt =0 ; - dentry =lookup_one_len (name, cd->rd_fhp->fh_dentry, namlen); + dentry =lookup_one_len_unlocked (name, cd->rd_fhp->fh_dentry, namlen);if (IS_ERR (dentry))return nfserrno (PTR_ERR (dentry));if (d_really_is_negative (dentry)) {/* -* nfsd_buffered_readdir drops the i_mutex between -* readdir and calling this callback, leaving a window -* where this directory entry could have gone away. +* we're not holding the i_mutex here, so there's +* a window where this directory entry could have gone +* away. */ dput (dentry);return nfserr_noent;
fs/nfsd/vfs.c +7 -11
@@ -217,6 +217,13 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, host_err =PTR_ERR (dentry);if (IS_ERR (dentry)) goto out_nfserr; +if (!(d_inode (dentry) &&S_ISREG (d_inode (dentry)->i_mode))) { +/* +* Never mind, we're not going to open this +* anyway. And we don't want to hold it over +* the userspace upcalls in nfsd_mountpoint. */ +fh_unlock (fhp); + }/* * check if we have crossed a mount point ... */
@@ -1876,7 +1883,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func, offset = *offsetp;while (1 ) { -struct inode *dir_inode =file_inode (file);unsigned int reclen; cdp->err = nfserr_eof;/* will be cleared on successful read */
@@ -1895,15 +1901,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func,if (!size)break ; -/* -* Various filldir functions may end up calling back into -* lookup_one_len() and the file system's ->lookup() method. -* These expect i_mutex to be held, as it would within readdir. -*/ - host_err =mutex_lock_killable (&dir_inode->i_mutex); -if (host_err) -break ; - de = (struct buffered_dirent *)buf.dirent;while (size >0 ) { offset = de->offset;
@@ -1920,7 +1917,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func, size -= reclen; de = (struct buffered_dirent *)((char *)de + reclen); } -mutex_unlock (&dir_inode->i_mutex);if (size >0 )/* We bailed out early */ break ;
include/linux/namei.h +1 -0
@@ -62,6 +62,7 @@ extern struct dentry *kern_path_locked(const char *, struct path *);extern int kern_path_mountpoint (int ,const char *,struct path *,unsigned int );extern struct dentry *lookup_one_len (const char *,struct dentry *,int ); +extern struct dentry *lookup_one_len_unlocked (const char *,struct dentry *,int );extern int follow_down_one (struct path *);extern int follow_down (struct path *);
Quoted 76 lines · On Tue, 5 May 2015 11:52:03 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 31 lines · J. Bruce Fields
Quoted 15 lines · Kinglong Mee
fs/namei.c struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
Quoted 12 lines · J. Bruce Fields
+ * Unlike
lookup_one_len, it should be called without the parent
+ *i_mutexheld, and will take thei_mutexitself if necessary.
+ */fs/namei.c +9
-0+struct dentry *lookup_one_len_unlocked (const char *name, +struct dentry *base,int len) +{ +struct qstr this; +unsigned int c; +int err; +struct dentry *ret; + +WARN_ON_ONCE (!mutex_is_locked (&base->d_inode->i_mutex));Remove this line.
Whoops, thanks.
Quoted 11 lines
fs/nfsd/vfs.c nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, · line 220
Quoted 9 lines
fs/nfsd/vfs.c +1
-0@@ -217,6 +217,13 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, host_err =PTR_ERR (dentry);if (IS_ERR (dentry)) goto out_nfserr; +if (!S_ISREG (d_inode (dentry)->i_mode)) {Got a crash here tested by pynfs,
Quoted 2 lines
OK, I guess I just forgot to take into account negative dentries. Testing that now with (!(d_inode(dentry) &&
S_ISREG(..))).And I also forgot
nfs3xdr.c.--b.
commit
6c942d342f9fAuthor: NeilBrown <[email protected]> Date: Fri May 1 11:53:26 2015 +1000nfsd: don't hold
i_mutexover userspace upcallsWe need information about exports when crossing mountpoints during lookup or NFSv4 readdir. If we don't already have that information cached, we may have to ask (and wait for) rpc.mountd.
In both cases we currently hold the
i_mutexon the parent of the directory we're asking rpc.mountd about. We've seen situations where rpc.mountd performs some operation on that directory that tries to take thei_mutexagain, resulting in deadlock.With some care, we may be able to avoid that in rpc.mountd. But it seems better just to avoid holding a mutex while waiting on userspace.
It appears that
lookup_one_lenis pretty much the only operation that needs thei_mutex. So we could just drop thei_mutexelsewhere and do something like
mutex_lock()
lookup_one_len()
mutex_unlock()In many cases though the lookup would have been cached and not required the
i_mutex, so it's more efficient to create alookup_one_len()variant that only takes thei_mutexwhen necessary.Signed-off-by J. Bruce Fields <[email protected]>fs/namei.c +0
-0
fs/namei.c static struct dentry *lookup_hash(struct nameidata *nd) · line 2143
Quoted 5 lines
fs/namei.c +2
-0@@ -2139,6 +2139,8 @@ static struct dentry *lookup_hash(struct nameidata *nd)* * Note that this routine is purely a helper for filesystem usage and should * not be called by generic code. +* +* Must be called with the parented i_mutex held.
"parented"?
* base->i_mutex must be held by caller.
??
fs/namei.c struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) · line 2188
Quoted 9 lines
fs/namei.c +2
-0*/ struct dentry *lookup_one_len (const char *name,struct dentry *base,int len) {@@ -2182,6 +2184,66 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) }EXPORT_SYMBOL (lookup_one_len); +/** +* lookup_one_len - filesystem helper to lookup single pathname component
lookup_one_len_unlocked - ..
Quoted 41 lines
fs/namei.c +41
-0+* @name: pathname component to lookup +* @base: base directory to lookup from +* @len: maximum length @len should be interpreted to +* +* Note that this routine is purely a helper for filesystem usage and should +* not be called by generic code. +* +* Unlike lookup_one_len, it should be called without the parent +* i_mutex held, and will take the i_mutex itself if necessary. +*/ +struct dentry *lookup_one_len_unlocked (const char *name, +struct dentry *base,int len) +{ +struct qstr this; +unsigned int c; +int err; +struct dentry *ret; + + this.name = name; + this.len = len; + this.hash =full_name_hash (name, len); +if (!len) +return ERR_PTR (-EACCES ); + +if (unlikely (name[0 ] =='.' )) { +if (len <2 || (len ==2 && name[1 ] =='.' )) +return ERR_PTR (-EACCES ); + } + +while (len--) { + c = *(const unsigned char *)name++; +if (c =='/' || c =='\0' ) +return ERR_PTR (-EACCES ); + } +/* +* See if the low-level filesystem might want +* to use its own hash.. +*/ +if (base->d_flags &DCACHE_OP_HASH ) { +int err = base->d_op->d_hash (base, &this); +if (err <0 )
fs/namei.c struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) · line 2239
Quoted 10 lines
fs/namei.c +10
-0+return ERR_PTR (err); + } + + err =inode_permission (base->d_inode,MAY_EXEC ); +if (err) +return ERR_PTR (err); + + ret =__d_lookup (base, &this); +if (ret) +return ret;
__d_lookup() is a little too low level, as it doesn't call d_revalidate() and it doesn't retry if the rename_lock seqlock says it should.
The latter doesn't really matter as we would fall through to the safe mutex-protected version.
The former isn't much of an issue for most filesystems under nfsd, but still needs to be handled to some extent.
Also, the comment for __d_lookup says
* __d_lookup callers must be commented.
The simplest resolution would be:
/* __d_lookup() is used to try to get a quick answer and avoid the
* mutex. A false-negative does no harm.
*/
ret = __d_lookup(base, &this);
if (ret && ret->d_flags & DCACHE_OP_REVALIDATE) {
dput(ret);
ret = NULL;
}
if (ret)
return ret;It probably wouldn't be too much harder to add the d_revalidate() call, and call d_invalidate() if it returns zero.
Maybe.
if (ret && ret->d_flags & DCACHE_OP_REVALIDATE) {
int err = d_revalidate(ret, 0);
if (err == 0) {
d_invalidate(ret);
dput(ret);
ret = NULL;
} else if (err < 0) {
dput(ret);
return ERR_PTR(err);
}
}Quoted 51 lines
fs/namei.c +7
-0+mutex_lock (&base->d_inode->i_mutex); + ret =__lookup_hash (&this, base,0 ); +mutex_unlock (&base->d_inode->i_mutex); +return ret; +} +EXPORT_SYMBOL (lookup_one_len_unlocked); +int user_path_at_empty (int dfd,const char __user *name,unsigned flags,struct path *path,int *empty) {fs/nfsd/nfs3xdr.c +1
-1@@ -823,7 +823,7 @@ compose_entry_fh(struct nfsd3_readdirres *cd, struct svc_fh *fhp, }else dchild =dget (dparent); }else - dchild =lookup_one_len (name, dparent, namlen); + dchild =lookup_one_len_unlocked (name, dparent, namlen);if (IS_ERR (dchild))return rv;if (d_mountpoint (dchild))fs/nfsd/nfs4xdr.c +4
-4@@ -2804,14 +2804,14 @@ nfsd4_encode_dirent_fattr(struct xdr_stream *xdr, struct nfsd4_readdir *cd,__be32 nfserr;int ignore_crossmnt =0 ; - dentry =lookup_one_len (name, cd->rd_fhp->fh_dentry, namlen); + dentry =lookup_one_len_unlocked (name, cd->rd_fhp->fh_dentry, namlen);if (IS_ERR (dentry))return nfserrno (PTR_ERR (dentry));if (d_really_is_negative (dentry)) {/* -* nfsd_buffered_readdir drops the i_mutex between -* readdir and calling this callback, leaving a window -* where this directory entry could have gone away. +* we're not holding the i_mutex here, so there's +* a window where this directory entry could have gone +* away. */ dput (dentry);return nfserr_noent;fs/nfsd/vfs.c +0
-0
fs/nfsd/vfs.c nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, · line 226
Quoted 10 lines
fs/nfsd/vfs.c +7
-0@@ -217,6 +217,13 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, host_err =PTR_ERR (dentry);if (IS_ERR (dentry)) goto out_nfserr; +if (!(d_inode (dentry) &&S_ISREG (d_inode (dentry)->i_mode))) { +/* +* Never mind, we're not going to open this +* anyway. And we don't want to hold it over +* the userspace upcalls in nfsd_mountpoint. */ +fh_unlock (fhp); + }
You could avoid the test by just putting the fh_unlock(fhp) inside the
if (nfsd_mountpoint(dentry, exp)) {block. It might also be appropriate for nfsd_mountpoint to fail on non-directories.
Up to you though.
Thanks. Feel free to add
*-by: NeilBrown <[email protected]>
as you see fit.
NeilBrown
Quoted 51 lines
fs/nfsd/vfs.c +0
-11/* * check if we have crossed a mount point ... */ @@ -1876,7 +1883,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func, offset = *offsetp;while (1 ) { -struct inode *dir_inode =file_inode (file);unsigned int reclen; cdp->err = nfserr_eof;/* will be cleared on successful read */ @@ -1895,15 +1901,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func,if (!size)break ; -/* -* Various filldir functions may end up calling back into -* lookup_one_len() and the file system's ->lookup() method. -* These expect i_mutex to be held, as it would within readdir. -*/ - host_err =mutex_lock_killable (&dir_inode->i_mutex); -if (host_err) -break ; - de = (struct buffered_dirent *)buf.dirent;while (size >0 ) { offset = de->offset;@@ -1920,7 +1917,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func, size -= reclen; de = (struct buffered_dirent *)((char *)de + reclen); } -mutex_unlock (&dir_inode->i_mutex);if (size >0 )/* We bailed out early */ break ;include/linux/namei.h +1
-1@@ -62,6 +62,7 @@ extern struct dentry *kern_path_locked(const char *, struct path *);extern int kern_path_mountpoint (int ,const char *,struct path *,unsigned int );extern struct dentry *lookup_one_len (const char *,struct dentry *,int ); +extern struct dentry *lookup_one_len_unlocked (const char *,struct dentry *,int );extern int follow_down_one (struct path *);extern int follow_down (struct path *); --To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Quoted 14 lines · NeilBrown
On Tue, 5 May 2015 11:52:03 -0400 "J. Bruce Fields" <[email protected]>
fs/namei.c static struct dentry *lookup_hash(struct nameidata *nd) · line 2143
Quoted 10 lines
fs/namei.c +2
-0@@ -2139,6 +2139,8 @@ static struct dentry *lookup_hash(struct nameidata *nd)* * Note that this routine is purely a helper for filesystem usage and should * not be called by generic code. +* +* Must be called with the parented i_mutex held. "parented"?
Quoted 3 lines
*
base->i_mutexmust be held by caller.??
Thanks.
Quoted 1 line
Quoted 1 line
fs/namei.c +1
-0+struct dentry *lookup_one_len_unlocked(const char *name,
...
Quoted 17 lines
fs/namei.c struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) · line 2239
Quoted 3 lines
fs/namei.c +3
-0+ ret =__d_lookup (base, &this); +if (ret) +return ret;
__d_lookup()is a little too low level, as it doesn't calld_revalidate()and it doesn't retry if therename_lockseqlock says it should.The latter doesn't really matter as we would fall through to the safe mutex-protected version.
The former isn't much of an issue for most filesystems under nfsd, but still needs to be handled to some extent.
Also, the comment for__d_lookupsays*
__d_lookupcallers must be commented.The simplest resolution would be:
Quoted 10 lines
/* __d_lookup() is used to try to get a quick answer and avoid the
* mutex. A false-negative does no harm.
*/
ret = __d_lookup(base, &this);
if (ret && ret->d_flags & DCACHE_OP_REVALIDATE) {
dput(ret);
ret = NULL;
}
if (ret)
return ret;That looks right to me.... I'll probably take the simple option.
Quoted 18 lines
Quoted 5 lines
fs/nfsd/vfs.c +0
-0fs/nfsd/vfs.c nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, · line 226
Quoted 10 lines
fs/nfsd/vfs.c +7
-0@@ -217,6 +217,13 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, host_err =PTR_ERR (dentry);if (IS_ERR (dentry)) goto out_nfserr; +if (!(d_inode (dentry) &&S_ISREG (d_inode (dentry)->i_mode))) { +/* +* Never mind, we're not going to open this +* anyway. And we don't want to hold it over +* the userspace upcalls in nfsd_mountpoint. */ +fh_unlock (fhp); + }You could avoid the test by just putting the
fh_unlock(fhp)inside the
Quoted 4 lines
if (nfsd_mountpoint(dentry, exp)) {block. It might also be appropriate for
nfsd_mountpointto fail on non-directories.
If check_export()'s to be believed, our support for regular-file exports is intentional. So even if nfsd_mountpoint() is true, it's possible we might still be about to open the result.
But I think we're OK:
The only reason for keeping the i_mutex here in the open case is to avoid the situation where a client does OPEN(dirfh, "foo") and gets a reply that grants a delegation even though the file has actually been renamed to "bar". (Thus violating the protocol, since the delegation's supposed to guarantee you'll be notified before the file's renamed.)
(So the i_mutex is actually overkill, it would be enough to detect a rename and then refuse the delegation (which we're always allowed to do).)
But actually, if this is a mountpoint then I suppose we're safe from a rename. So, right, we can just move that unlock into the mountpoint case.
--b.
PATCH 1/1 nfsd: don't hold i_mutex over userspace upcalls
From: NeilBrown <[email protected]>
We need information about exports when crossing mountpoints during lookup or NFSv4 readdir. If we don't already have that information cached, we may have to ask (and wait for) rpc.mountd.
In both cases we currently hold the i_mutex on the parent of the directory we're asking rpc.mountd about. We've seen situations where rpc.mountd performs some operation on that directory that tries to take the i_mutex again, resulting in deadlock.
With some care, we may be able to avoid that in rpc.mountd. But it seems better just to avoid holding a mutex while waiting on userspace.
It appears that lookup_one_len is pretty much the only operation that needs the i_mutex. So we could just drop the i_mutex elsewhere and do something like
mutex_lock()
lookup_one_len()
mutex_unlock()
In many cases though the lookup would have been cached and not required the i_mutex, so it's more efficient to create a lookup_one_len() variant that only takes the i_mutex when necessary.
--- fs/namei.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++ fs/nfsd/nfs3xdr.c | 2 +- fs/nfsd/nfs4xdr.c | 8 +++--- fs/nfsd/vfs.c | 23 +++++++--------- include/linux/namei.h | 1 + 5 files changed, 89 insertions(+), 19 deletions(-)
Here's an updated patch.
fs/namei.c +74 -0
@@ -2139,6 +2139,8 @@ static struct dentry *lookup_hash(struct nameidata *nd)* * Note that this routine is purely a helper for filesystem usage and should * not be called by generic code. +* +* The caller must hold base->i_mutex. */ struct dentry *lookup_one_len (const char *name,struct dentry *base,int len) {
@@ -2182,6 +2184,78 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) }EXPORT_SYMBOL (lookup_one_len); +/** +* lookup_one_len_unlocked - filesystem helper to lookup single pathname component +* @name: pathname component to lookup +* @base: base directory to lookup from +* @len: maximum length @len should be interpreted to +* +* Note that this routine is purely a helper for filesystem usage and should +* not be called by generic code. +* +* Unlike lookup_one_len, it should be called without the parent +* i_mutex held, and will take the i_mutex itself if necessary. +*/ +struct dentry *lookup_one_len_unlocked (const char *name, +struct dentry *base,int len) +{ +struct qstr this; +unsigned int c; +int err; +struct dentry *ret; + + this.name = name; + this.len = len; + this.hash =full_name_hash (name, len); +if (!len) +return ERR_PTR (-EACCES ); + +if (unlikely (name[0 ] =='.' )) { +if (len <2 || (len ==2 && name[1 ] =='.' )) +return ERR_PTR (-EACCES ); + } + +while (len--) { + c = *(const unsigned char *)name++; +if (c =='/' || c =='\0' ) +return ERR_PTR (-EACCES ); + } +/* +* See if the low-level filesystem might want +* to use its own hash.. +*/ +if (base->d_flags &DCACHE_OP_HASH ) { +int err = base->d_op->d_hash (base, &this); +if (err <0 ) +return ERR_PTR (err); + } + + err =inode_permission (base->d_inode,MAY_EXEC ); +if (err) +return ERR_PTR (err); + + ret =__d_lookup (base, &this); +if (ret) +return ret; +/* +* __d_lookup() is used to try to get a quick answer and avoid the +* mutex. A false-negative does no harm. +*/ + ret =__d_lookup (base, &this); +if (ret && ret->d_flags &DCACHE_OP_REVALIDATE ) { +dput (ret); + ret =NULL ; + } +if (ret) +return ret; + +mutex_lock (&base->d_inode->i_mutex); + ret =__lookup_hash (&this, base,0 ); +mutex_unlock (&base->d_inode->i_mutex); +return ret; +} +EXPORT_SYMBOL (lookup_one_len_unlocked); +int user_path_at_empty (int dfd,const char __user *name,unsigned flags,struct path *path,int *empty) {
fs/nfsd/nfs3xdr.c +1 -1
@@ -823,7 +823,7 @@ compose_entry_fh(struct nfsd3_readdirres *cd, struct svc_fh *fhp, }else dchild =dget (dparent); }else - dchild =lookup_one_len (name, dparent, namlen); + dchild =lookup_one_len_unlocked (name, dparent, namlen);if (IS_ERR (dchild))return rv;if (d_mountpoint (dchild))
fs/nfsd/nfs4xdr.c +4 -4
@@ -2804,14 +2804,14 @@ nfsd4_encode_dirent_fattr(struct xdr_stream *xdr, struct nfsd4_readdir *cd,__be32 nfserr;int ignore_crossmnt =0 ; - dentry =lookup_one_len (name, cd->rd_fhp->fh_dentry, namlen); + dentry =lookup_one_len_unlocked (name, cd->rd_fhp->fh_dentry, namlen);if (IS_ERR (dentry))return nfserrno (PTR_ERR (dentry));if (d_really_is_negative (dentry)) {/* -* nfsd_buffered_readdir drops the i_mutex between -* readdir and calling this callback, leaving a window -* where this directory entry could have gone away. +* we're not holding the i_mutex here, so there's +* a window where this directory entry could have gone +* away. */ dput (dentry);return nfserr_noent;
fs/nfsd/vfs.c +9 -14
@@ -217,10 +217,16 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, host_err =PTR_ERR (dentry);if (IS_ERR (dentry)) goto out_nfserr; -/* -* check if we have crossed a mount point ... -*/ if (nfsd_mountpoint (dentry, exp)) { +/* +* We don't need the i_mutex after all. It's +* still possible we could open this (regular +* files can be mountpoints too), but the +* i_mutex is just there to prevent renames of +* something that we might be about to delegate, +* and a mountpoint won't be renamed: +*/ +fh_unlock (fhp);if ((host_err =nfsd_cross_mnt (rqstp, &dentry, &exp))) {dput (dentry); goto out_nfserr;
@@ -1876,7 +1882,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func, offset = *offsetp;while (1 ) { -struct inode *dir_inode =file_inode (file);unsigned int reclen; cdp->err = nfserr_eof;/* will be cleared on successful read */
@@ -1895,15 +1900,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func,if (!size)break ; -/* -* Various filldir functions may end up calling back into -* lookup_one_len() and the file system's ->lookup() method. -* These expect i_mutex to be held, as it would within readdir. -*/ - host_err =mutex_lock_killable (&dir_inode->i_mutex); -if (host_err) -break ; - de = (struct buffered_dirent *)buf.dirent;while (size >0 ) { offset = de->offset;
@@ -1920,7 +1916,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func, size -= reclen; de = (struct buffered_dirent *)((char *)de + reclen); } -mutex_unlock (&dir_inode->i_mutex);if (size >0 )/* We bailed out early */ break ;
include/linux/namei.h +1 -0
@@ -62,6 +62,7 @@ extern struct dentry *kern_path_locked(const char *, struct path *);extern int kern_path_mountpoint (int ,const char *,struct path *,unsigned int );extern struct dentry *lookup_one_len (const char *,struct dentry *,int ); +extern struct dentry *lookup_one_len_unlocked (const char *,struct dentry *,int );extern int follow_down_one (struct path *);extern int follow_down (struct path *);
Signature · 1 line
1.9.3
Re: [PATCH] nfsd: don't hold i_mutex over userspace upcalls
This passes my review, but it needs an ACK from Al or someone for the addition of the new lookup_one_len_unlocked (which is the same as lookup_one_len except that it takes the i_mutex itself when required instead of requiring the caller to).
--b.
Quoted 236 lines · J. Bruce Fields
From: NeilBrown <[email protected]>
We need information about exports when crossing mountpoints during lookup or NFSv4 readdir. If we don't already have that information cached, we may have to ask (and wait for) rpc.mountd.
In both cases we currently hold the
i_mutexon the parent of the directory we're asking rpc.mountd about. We've seen situations where rpc.mountd performs some operation on that directory that tries to take thei_mutexagain, resulting in deadlock.With some care, we may be able to avoid that in rpc.mountd. But it seems better just to avoid holding a mutex while waiting on userspace.
It appears that
lookup_one_lenis pretty much the only operation that needs thei_mutex. So we could just drop thei_mutexelsewhere and do something like
mutex_lock()
lookup_one_len()
mutex_unlock()In many cases though the lookup would have been cached and not required the
i_mutex, so it's more efficient to create alookup_one_len()variant that only takes thei_mutexwhen necessary.Signed-off-by J. Bruce Fields <[email protected]>--- fs/namei.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++ fs/nfsd/nfs3xdr.c | 2 +- fs/nfsd/nfs4xdr.c | 8 +++--- fs/nfsd/vfs.c | 23 +++++++--------- include/linux/namei.h | 1 + 5 files changed, 89 insertions(+), 19 deletions(-)Here's an updated patch.
fs/namei.c +74
-0@@ -2139,6 +2139,8 @@ static struct dentry *lookup_hash(struct nameidata *nd)* * Note that this routine is purely a helper for filesystem usage and should * not be called by generic code. +* +* The caller must hold base->i_mutex. */ struct dentry *lookup_one_len (const char *name,struct dentry *base,int len) {@@ -2182,6 +2184,78 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) }EXPORT_SYMBOL (lookup_one_len); +/** +* lookup_one_len_unlocked - filesystem helper to lookup single pathname component +* @name: pathname component to lookup +* @base: base directory to lookup from +* @len: maximum length @len should be interpreted to +* +* Note that this routine is purely a helper for filesystem usage and should +* not be called by generic code. +* +* Unlike lookup_one_len, it should be called without the parent +* i_mutex held, and will take the i_mutex itself if necessary. +*/ +struct dentry *lookup_one_len_unlocked (const char *name, +struct dentry *base,int len) +{ +struct qstr this; +unsigned int c; +int err; +struct dentry *ret; + + this.name = name; + this.len = len; + this.hash =full_name_hash (name, len); +if (!len) +return ERR_PTR (-EACCES ); + +if (unlikely (name[0 ] =='.' )) { +if (len <2 || (len ==2 && name[1 ] =='.' )) +return ERR_PTR (-EACCES ); + } + +while (len--) { + c = *(const unsigned char *)name++; +if (c =='/' || c =='\0' ) +return ERR_PTR (-EACCES ); + } +/* +* See if the low-level filesystem might want +* to use its own hash.. +*/ +if (base->d_flags &DCACHE_OP_HASH ) { +int err = base->d_op->d_hash (base, &this); +if (err <0 ) +return ERR_PTR (err); + } + + err =inode_permission (base->d_inode,MAY_EXEC ); +if (err) +return ERR_PTR (err); + + ret =__d_lookup (base, &this); +if (ret) +return ret; +/* +* __d_lookup() is used to try to get a quick answer and avoid the +* mutex. A false-negative does no harm. +*/ + ret =__d_lookup (base, &this); +if (ret && ret->d_flags &DCACHE_OP_REVALIDATE ) { +dput (ret); + ret =NULL ; + } +if (ret) +return ret; + +mutex_lock (&base->d_inode->i_mutex); + ret =__lookup_hash (&this, base,0 ); +mutex_unlock (&base->d_inode->i_mutex); +return ret; +} +EXPORT_SYMBOL (lookup_one_len_unlocked); +int user_path_at_empty (int dfd,const char __user *name,unsigned flags,struct path *path,int *empty) {fs/nfsd/nfs3xdr.c +1
-1@@ -823,7 +823,7 @@ compose_entry_fh(struct nfsd3_readdirres *cd, struct svc_fh *fhp, }else dchild =dget (dparent); }else - dchild =lookup_one_len (name, dparent, namlen); + dchild =lookup_one_len_unlocked (name, dparent, namlen);if (IS_ERR (dchild))return rv;if (d_mountpoint (dchild))fs/nfsd/nfs4xdr.c +4
-4@@ -2804,14 +2804,14 @@ nfsd4_encode_dirent_fattr(struct xdr_stream *xdr, struct nfsd4_readdir *cd,__be32 nfserr;int ignore_crossmnt =0 ; - dentry =lookup_one_len (name, cd->rd_fhp->fh_dentry, namlen); + dentry =lookup_one_len_unlocked (name, cd->rd_fhp->fh_dentry, namlen);if (IS_ERR (dentry))return nfserrno (PTR_ERR (dentry));if (d_really_is_negative (dentry)) {/* -* nfsd_buffered_readdir drops the i_mutex between -* readdir and calling this callback, leaving a window -* where this directory entry could have gone away. +* we're not holding the i_mutex here, so there's +* a window where this directory entry could have gone +* away. */ dput (dentry);return nfserr_noent;fs/nfsd/vfs.c +9
-14@@ -217,10 +217,16 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, host_err =PTR_ERR (dentry);if (IS_ERR (dentry)) goto out_nfserr; -/* -* check if we have crossed a mount point ... -*/ if (nfsd_mountpoint (dentry, exp)) { +/* +* We don't need the i_mutex after all. It's +* still possible we could open this (regular +* files can be mountpoints too), but the +* i_mutex is just there to prevent renames of +* something that we might be about to delegate, +* and a mountpoint won't be renamed: +*/ +fh_unlock (fhp);if ((host_err =nfsd_cross_mnt (rqstp, &dentry, &exp))) {dput (dentry); goto out_nfserr;@@ -1876,7 +1882,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func, offset = *offsetp;while (1 ) { -struct inode *dir_inode =file_inode (file);unsigned int reclen; cdp->err = nfserr_eof;/* will be cleared on successful read */ @@ -1895,15 +1900,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func,if (!size)break ; -/* -* Various filldir functions may end up calling back into -* lookup_one_len() and the file system's ->lookup() method. -* These expect i_mutex to be held, as it would within readdir. -*/ - host_err =mutex_lock_killable (&dir_inode->i_mutex); -if (host_err) -break ; - de = (struct buffered_dirent *)buf.dirent;while (size >0 ) { offset = de->offset;@@ -1920,7 +1916,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func, size -= reclen; de = (struct buffered_dirent *)((char *)de + reclen); } -mutex_unlock (&dir_inode->i_mutex);if (size >0 )/* We bailed out early */ break ;include/linux/namei.h +1
-0@@ -62,6 +62,7 @@ extern struct dentry *kern_path_locked(const char *, struct path *);extern int kern_path_mountpoint (int ,const char *,struct path *,unsigned int );extern struct dentry *lookup_one_len (const char *,struct dentry *,int ); +extern struct dentry *lookup_one_len_unlocked (const char *,struct dentry *,int );extern int follow_down_one (struct path *);extern int follow_down (struct path *);Signature · 1 line
1.9.3
A message that is not in the archive (sent off-list or not yet received): [email protected]
Re: [PATCH] nfsd: don't hold i_mutex over userspace upcalls
Ping...
What's the state of this patch ?
Without modify nfs-utils, this one is make sense.
thanks,
Kinglong Mee
Quoted 236 lines · J. Bruce Fields
This passes my review, but it needs an ACK from Al or someone for the addition of the new
lookup_one_len_unlocked(which is the same aslookup_one_lenexcept that it takes thei_mutexitself when required instead of requiring the caller to).--b.
Quoted 228 lines · J. Bruce Fields
From: NeilBrown <[email protected]>
We need information about exports when crossing mountpoints during lookup or NFSv4 readdir. If we don't already have that information cached, we may have to ask (and wait for) rpc.mountd.
In both cases we currently hold the
i_mutexon the parent of the directory we're asking rpc.mountd about. We've seen situations where rpc.mountd performs some operation on that directory that tries to take thei_mutexagain, resulting in deadlock.With some care, we may be able to avoid that in rpc.mountd. But it seems better just to avoid holding a mutex while waiting on userspace.
It appears that
lookup_one_lenis pretty much the only operation that needs thei_mutex. So we could just drop thei_mutexelsewhere and do something like
mutex_lock()
lookup_one_len()
mutex_unlock()In many cases though the lookup would have been cached and not required the
i_mutex, so it's more efficient to create alookup_one_len()variant that only takes thei_mutexwhen necessary.Signed-off-by J. Bruce Fields <[email protected]>--- fs/namei.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++ fs/nfsd/nfs3xdr.c | 2 +- fs/nfsd/nfs4xdr.c | 8 +++--- fs/nfsd/vfs.c | 23 +++++++--------- include/linux/namei.h | 1 + 5 files changed, 89 insertions(+), 19 deletions(-)Here's an updated patch.
fs/namei.c +74
-0@@ -2139,6 +2139,8 @@ static struct dentry *lookup_hash(struct nameidata *nd)* * Note that this routine is purely a helper for filesystem usage and should * not be called by generic code. +* +* The caller must hold base->i_mutex. */ struct dentry *lookup_one_len (const char *name,struct dentry *base,int len) {@@ -2182,6 +2184,78 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) }EXPORT_SYMBOL (lookup_one_len); +/** +* lookup_one_len_unlocked - filesystem helper to lookup single pathname component +* @name: pathname component to lookup +* @base: base directory to lookup from +* @len: maximum length @len should be interpreted to +* +* Note that this routine is purely a helper for filesystem usage and should +* not be called by generic code. +* +* Unlike lookup_one_len, it should be called without the parent +* i_mutex held, and will take the i_mutex itself if necessary. +*/ +struct dentry *lookup_one_len_unlocked (const char *name, +struct dentry *base,int len) +{ +struct qstr this; +unsigned int c; +int err; +struct dentry *ret; + + this.name = name; + this.len = len; + this.hash =full_name_hash (name, len); +if (!len) +return ERR_PTR (-EACCES ); + +if (unlikely (name[0 ] =='.' )) { +if (len <2 || (len ==2 && name[1 ] =='.' )) +return ERR_PTR (-EACCES ); + } + +while (len--) { + c = *(const unsigned char *)name++; +if (c =='/' || c =='\0' ) +return ERR_PTR (-EACCES ); + } +/* +* See if the low-level filesystem might want +* to use its own hash.. +*/ +if (base->d_flags &DCACHE_OP_HASH ) { +int err = base->d_op->d_hash (base, &this); +if (err <0 ) +return ERR_PTR (err); + } + + err =inode_permission (base->d_inode,MAY_EXEC ); +if (err) +return ERR_PTR (err); + + ret =__d_lookup (base, &this); +if (ret) +return ret; +/* +* __d_lookup() is used to try to get a quick answer and avoid the +* mutex. A false-negative does no harm. +*/ + ret =__d_lookup (base, &this); +if (ret && ret->d_flags &DCACHE_OP_REVALIDATE ) { +dput (ret); + ret =NULL ; + } +if (ret) +return ret; + +mutex_lock (&base->d_inode->i_mutex); + ret =__lookup_hash (&this, base,0 ); +mutex_unlock (&base->d_inode->i_mutex); +return ret; +} +EXPORT_SYMBOL (lookup_one_len_unlocked); +int user_path_at_empty (int dfd,const char __user *name,unsigned flags,struct path *path,int *empty) {fs/nfsd/nfs3xdr.c +1
-1@@ -823,7 +823,7 @@ compose_entry_fh(struct nfsd3_readdirres *cd, struct svc_fh *fhp, }else dchild =dget (dparent); }else - dchild =lookup_one_len (name, dparent, namlen); + dchild =lookup_one_len_unlocked (name, dparent, namlen);if (IS_ERR (dchild))return rv;if (d_mountpoint (dchild))fs/nfsd/nfs4xdr.c +4
-4@@ -2804,14 +2804,14 @@ nfsd4_encode_dirent_fattr(struct xdr_stream *xdr, struct nfsd4_readdir *cd,__be32 nfserr;int ignore_crossmnt =0 ; - dentry =lookup_one_len (name, cd->rd_fhp->fh_dentry, namlen); + dentry =lookup_one_len_unlocked (name, cd->rd_fhp->fh_dentry, namlen);if (IS_ERR (dentry))return nfserrno (PTR_ERR (dentry));if (d_really_is_negative (dentry)) {/* -* nfsd_buffered_readdir drops the i_mutex between -* readdir and calling this callback, leaving a window -* where this directory entry could have gone away. +* we're not holding the i_mutex here, so there's +* a window where this directory entry could have gone +* away. */ dput (dentry);return nfserr_noent;fs/nfsd/vfs.c +9
-14@@ -217,10 +217,16 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, host_err =PTR_ERR (dentry);if (IS_ERR (dentry)) goto out_nfserr; -/* -* check if we have crossed a mount point ... -*/ if (nfsd_mountpoint (dentry, exp)) { +/* +* We don't need the i_mutex after all. It's +* still possible we could open this (regular +* files can be mountpoints too), but the +* i_mutex is just there to prevent renames of +* something that we might be about to delegate, +* and a mountpoint won't be renamed: +*/ +fh_unlock (fhp);if ((host_err =nfsd_cross_mnt (rqstp, &dentry, &exp))) {dput (dentry); goto out_nfserr;@@ -1876,7 +1882,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func, offset = *offsetp;while (1 ) { -struct inode *dir_inode =file_inode (file);unsigned int reclen; cdp->err = nfserr_eof;/* will be cleared on successful read */ @@ -1895,15 +1900,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func,if (!size)break ; -/* -* Various filldir functions may end up calling back into -* lookup_one_len() and the file system's ->lookup() method. -* These expect i_mutex to be held, as it would within readdir. -*/ - host_err =mutex_lock_killable (&dir_inode->i_mutex); -if (host_err) -break ; - de = (struct buffered_dirent *)buf.dirent;while (size >0 ) { offset = de->offset;@@ -1920,7 +1916,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func, size -= reclen; de = (struct buffered_dirent *)((char *)de + reclen); } -mutex_unlock (&dir_inode->i_mutex);if (size >0 )/* We bailed out early */ break ;include/linux/namei.h +0
-0@@ -62,6 +62,7 @@ extern struct dentry *kern_path_locked(const char *, struct path *);extern int kern_path_mountpoint (int ,const char *,struct path *,unsigned int );
Quoted 9 lines
Quoted 8 lines
include/linux/namei.h +1
-0extern struct dentry *lookup_one_len (const char *,struct dentry *,int ); +extern struct dentry *lookup_one_len_unlocked (const char *,struct dentry *,int );extern int follow_down_one (struct path *);extern int follow_down (struct path *);Signature · 1 line
1.9.3
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [PATCH] nfsd: don't hold i_mutex over userspace upcalls
Quoted 3 lines · Kinglong Mee
Ping...
What's the state of this patch ?
I think I still need an ACK from Al for the addition of lookup_one_len_unlocked.
--b.
Quoted 246 lines · J. Bruce Fields
Quoted 245 lines
This passes my review, but it needs an ACK from Al or someone for the addition of the new
lookup_one_len_unlocked(which is the same aslookup_one_lenexcept that it takes thei_mutexitself when required instead of requiring the caller to).--b.
Quoted 236 lines · J. Bruce Fields
From: NeilBrown <[email protected]>
We need information about exports when crossing mountpoints during lookup or NFSv4 readdir. If we don't already have that information cached, we may have to ask (and wait for) rpc.mountd.
In both cases we currently hold the
i_mutexon the parent of the directory we're asking rpc.mountd about. We've seen situations where rpc.mountd performs some operation on that directory that tries to take thei_mutexagain, resulting in deadlock.With some care, we may be able to avoid that in rpc.mountd. But it seems better just to avoid holding a mutex while waiting on userspace.
It appears that
lookup_one_lenis pretty much the only operation that needs thei_mutex. So we could just drop thei_mutexelsewhere and do something like
mutex_lock()
lookup_one_len()
mutex_unlock()In many cases though the lookup would have been cached and not required the
i_mutex, so it's more efficient to create alookup_one_len()variant that only takes thei_mutexwhen necessary.Signed-off-by J. Bruce Fields <[email protected]>--- fs/namei.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++ fs/nfsd/nfs3xdr.c | 2 +- fs/nfsd/nfs4xdr.c | 8 +++--- fs/nfsd/vfs.c | 23 +++++++--------- include/linux/namei.h | 1 + 5 files changed, 89 insertions(+), 19 deletions(-)Here's an updated patch.
fs/namei.c +74
-0@@ -2139,6 +2139,8 @@ static struct dentry *lookup_hash(struct nameidata *nd)* * Note that this routine is purely a helper for filesystem usage and should * not be called by generic code. +* +* The caller must hold base->i_mutex. */ struct dentry *lookup_one_len (const char *name,struct dentry *base,int len) {@@ -2182,6 +2184,78 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) }EXPORT_SYMBOL (lookup_one_len); +/** +* lookup_one_len_unlocked - filesystem helper to lookup single pathname component +* @name: pathname component to lookup +* @base: base directory to lookup from +* @len: maximum length @len should be interpreted to +* +* Note that this routine is purely a helper for filesystem usage and should +* not be called by generic code. +* +* Unlike lookup_one_len, it should be called without the parent +* i_mutex held, and will take the i_mutex itself if necessary. +*/ +struct dentry *lookup_one_len_unlocked (const char *name, +struct dentry *base,int len) +{ +struct qstr this; +unsigned int c; +int err; +struct dentry *ret; + + this.name = name; + this.len = len; + this.hash =full_name_hash (name, len); +if (!len) +return ERR_PTR (-EACCES ); + +if (unlikely (name[0 ] =='.' )) { +if (len <2 || (len ==2 && name[1 ] =='.' )) +return ERR_PTR (-EACCES ); + } + +while (len--) { + c = *(const unsigned char *)name++; +if (c =='/' || c =='\0' ) +return ERR_PTR (-EACCES ); + } +/* +* See if the low-level filesystem might want +* to use its own hash.. +*/ +if (base->d_flags &DCACHE_OP_HASH ) { +int err = base->d_op->d_hash (base, &this); +if (err <0 ) +return ERR_PTR (err); + } + + err =inode_permission (base->d_inode,MAY_EXEC ); +if (err) +return ERR_PTR (err); + + ret =__d_lookup (base, &this); +if (ret) +return ret; +/* +* __d_lookup() is used to try to get a quick answer and avoid the +* mutex. A false-negative does no harm. +*/ + ret =__d_lookup (base, &this); +if (ret && ret->d_flags &DCACHE_OP_REVALIDATE ) { +dput (ret); + ret =NULL ; + } +if (ret) +return ret; + +mutex_lock (&base->d_inode->i_mutex); + ret =__lookup_hash (&this, base,0 ); +mutex_unlock (&base->d_inode->i_mutex); +return ret; +} +EXPORT_SYMBOL (lookup_one_len_unlocked); +int user_path_at_empty (int dfd,const char __user *name,unsigned flags,struct path *path,int *empty) {fs/nfsd/nfs3xdr.c +1
-1@@ -823,7 +823,7 @@ compose_entry_fh(struct nfsd3_readdirres *cd, struct svc_fh *fhp, }else dchild =dget (dparent); }else - dchild =lookup_one_len (name, dparent, namlen); + dchild =lookup_one_len_unlocked (name, dparent, namlen);if (IS_ERR (dchild))return rv;if (d_mountpoint (dchild))fs/nfsd/nfs4xdr.c +4
-4@@ -2804,14 +2804,14 @@ nfsd4_encode_dirent_fattr(struct xdr_stream *xdr, struct nfsd4_readdir *cd,__be32 nfserr;int ignore_crossmnt =0 ; - dentry =lookup_one_len (name, cd->rd_fhp->fh_dentry, namlen); + dentry =lookup_one_len_unlocked (name, cd->rd_fhp->fh_dentry, namlen);if (IS_ERR (dentry))return nfserrno (PTR_ERR (dentry));if (d_really_is_negative (dentry)) {/* -* nfsd_buffered_readdir drops the i_mutex between -* readdir and calling this callback, leaving a window -* where this directory entry could have gone away. +* we're not holding the i_mutex here, so there's +* a window where this directory entry could have gone +* away. */ dput (dentry);return nfserr_noent;fs/nfsd/vfs.c +9
-14@@ -217,10 +217,16 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, host_err =PTR_ERR (dentry);if (IS_ERR (dentry)) goto out_nfserr; -/* -* check if we have crossed a mount point ... -*/ if (nfsd_mountpoint (dentry, exp)) { +/* +* We don't need the i_mutex after all. It's +* still possible we could open this (regular +* files can be mountpoints too), but the +* i_mutex is just there to prevent renames of +* something that we might be about to delegate, +* and a mountpoint won't be renamed: +*/ +fh_unlock (fhp);if ((host_err =nfsd_cross_mnt (rqstp, &dentry, &exp))) {dput (dentry); goto out_nfserr;@@ -1876,7 +1882,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func, offset = *offsetp;while (1 ) { -struct inode *dir_inode =file_inode (file);unsigned int reclen; cdp->err = nfserr_eof;/* will be cleared on successful read */ @@ -1895,15 +1900,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func,if (!size)break ; -/* -* Various filldir functions may end up calling back into -* lookup_one_len() and the file system's ->lookup() method. -* These expect i_mutex to be held, as it would within readdir. -*/ - host_err =mutex_lock_killable (&dir_inode->i_mutex); -if (host_err) -break ; - de = (struct buffered_dirent *)buf.dirent;while (size >0 ) { offset = de->offset;@@ -1920,7 +1916,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func, size -= reclen; de = (struct buffered_dirent *)((char *)de + reclen); } -mutex_unlock (&dir_inode->i_mutex);if (size >0 )/* We bailed out early */ break ;include/linux/namei.h +1
-0@@ -62,6 +62,7 @@ extern struct dentry *kern_path_locked(const char *, struct path *);extern int kern_path_mountpoint (int ,const char *,struct path *,unsigned int );extern struct dentry *lookup_one_len (const char *,struct dentry *,int ); +extern struct dentry *lookup_one_len_unlocked (const char *,struct dentry *,int );extern int follow_down_one (struct path *);extern int follow_down (struct path *);Signature · 1 line
1.9.3
Re: [PATCH] nfsd: don't hold i_mutex over userspace upcalls
In reply to a message not in the archiveAl, can I get an ACK/NACK for this?
--b.
commit 5494e10316e3
Author: NeilBrown <[email protected]> Date: Fri May 1 11:53:26 2015 +1000
nfsd: don't hold i_mutex over userspace upcalls
We need information about exports when crossing mountpoints during lookup or NFSv4 readdir. If we don't already have that information cached, we may have to ask (and wait for) rpc.mountd.
In both cases we currently hold the i_mutex on the parent of the directory we're asking rpc.mountd about. We've seen situations where rpc.mountd performs some operation on that directory that tries to take the i_mutex again, resulting in deadlock.
With some care, we may be able to avoid that in rpc.mountd. But it seems better just to avoid holding a mutex while waiting on userspace.
It appears that lookup_one_len is pretty much the only operation that needs the i_mutex. So we could just drop the i_mutex elsewhere and do something like
mutex_lock()
lookup_one_len()
mutex_unlock()
In many cases though the lookup would have been cached and not required the i_mutex, so it's more efficient to create a lookup_one_len() variant that only takes the i_mutex when necessary.
fs/namei.c +74 -0
@@ -2250,6 +2250,8 @@ EXPORT_SYMBOL(vfs_path_lookup);* * Note that this routine is purely a helper for filesystem usage and should * not be called by generic code. +* +* The caller must hold base->i_mutex. */ struct dentry *lookup_one_len (const char *name,struct dentry *base,int len) {
@@ -2293,6 +2295,78 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) }EXPORT_SYMBOL (lookup_one_len); +/** +* lookup_one_len_unlocked - filesystem helper to lookup single pathname component +* @name: pathname component to lookup +* @base: base directory to lookup from +* @len: maximum length @len should be interpreted to +* +* Note that this routine is purely a helper for filesystem usage and should +* not be called by generic code. +* +* Unlike lookup_one_len, it should be called without the parent +* i_mutex held, and will take the i_mutex itself if necessary. +*/ +struct dentry *lookup_one_len_unlocked (const char *name, +struct dentry *base,int len) +{ +struct qstr this; +unsigned int c; +int err; +struct dentry *ret; + + this.name = name; + this.len = len; + this.hash =full_name_hash (name, len); +if (!len) +return ERR_PTR (-EACCES ); + +if (unlikely (name[0 ] =='.' )) { +if (len <2 || (len ==2 && name[1 ] =='.' )) +return ERR_PTR (-EACCES ); + } + +while (len--) { + c = *(const unsigned char *)name++; +if (c =='/' || c =='\0' ) +return ERR_PTR (-EACCES ); + } +/* +* See if the low-level filesystem might want +* to use its own hash.. +*/ +if (base->d_flags &DCACHE_OP_HASH ) { +int err = base->d_op->d_hash (base, &this); +if (err <0 ) +return ERR_PTR (err); + } + + err =inode_permission (base->d_inode,MAY_EXEC ); +if (err) +return ERR_PTR (err); + + ret =__d_lookup (base, &this); +if (ret) +return ret; +/* +* __d_lookup() is used to try to get a quick answer and avoid the +* mutex. A false-negative does no harm. +*/ + ret =__d_lookup (base, &this); +if (ret && ret->d_flags &DCACHE_OP_REVALIDATE ) { +dput (ret); + ret =NULL ; + } +if (ret) +return ret; + +mutex_lock (&base->d_inode->i_mutex); + ret =__lookup_hash (&this, base,0 ); +mutex_unlock (&base->d_inode->i_mutex); +return ret; +} +EXPORT_SYMBOL (lookup_one_len_unlocked); +int user_path_at_empty (int dfd,const char __user *name,unsigned flags,struct path *path,int *empty) {
fs/nfsd/nfs3xdr.c +1 -1
@@ -823,7 +823,7 @@ compose_entry_fh(struct nfsd3_readdirres *cd, struct svc_fh *fhp, }else dchild =dget (dparent); }else - dchild =lookup_one_len (name, dparent, namlen); + dchild =lookup_one_len_unlocked (name, dparent, namlen);if (IS_ERR (dchild))return rv;if (d_mountpoint (dchild))
fs/nfsd/nfs4xdr.c +4 -4
@@ -2838,14 +2838,14 @@ nfsd4_encode_dirent_fattr(struct xdr_stream *xdr, struct nfsd4_readdir *cd,__be32 nfserr;int ignore_crossmnt =0 ; - dentry =lookup_one_len (name, cd->rd_fhp->fh_dentry, namlen); + dentry =lookup_one_len_unlocked (name, cd->rd_fhp->fh_dentry, namlen);if (IS_ERR (dentry))return nfserrno (PTR_ERR (dentry));if (d_really_is_negative (dentry)) {/* -* nfsd_buffered_readdir drops the i_mutex between -* readdir and calling this callback, leaving a window -* where this directory entry could have gone away. +* we're not holding the i_mutex here, so there's +* a window where this directory entry could have gone +* away. */ dput (dentry);return nfserr_noent;
fs/nfsd/vfs.c +9 -14
@@ -217,10 +217,16 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, host_err =PTR_ERR (dentry);if (IS_ERR (dentry)) goto out_nfserr; -/* -* check if we have crossed a mount point ... -*/ if (nfsd_mountpoint (dentry, exp)) { +/* +* We don't need the i_mutex after all. It's +* still possible we could open this (regular +* files can be mountpoints too), but the +* i_mutex is just there to prevent renames of +* something that we might be about to delegate, +* and a mountpoint won't be renamed: +*/ +fh_unlock (fhp);if ((host_err =nfsd_cross_mnt (rqstp, &dentry, &exp))) {dput (dentry); goto out_nfserr;
@@ -1809,7 +1815,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func, offset = *offsetp;while (1 ) { -struct inode *dir_inode =file_inode (file);unsigned int reclen; cdp->err = nfserr_eof;/* will be cleared on successful read */
@@ -1828,15 +1833,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func,if (!size)break ; -/* -* Various filldir functions may end up calling back into -* lookup_one_len() and the file system's ->lookup() method. -* These expect i_mutex to be held, as it would within readdir. -*/ - host_err =mutex_lock_killable (&dir_inode->i_mutex); -if (host_err) -break ; - de = (struct buffered_dirent *)buf.dirent;while (size >0 ) { offset = de->offset;
@@ -1853,7 +1849,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func, size -= reclen; de = (struct buffered_dirent *)((char *)de + reclen); } -mutex_unlock (&dir_inode->i_mutex);if (size >0 )/* We bailed out early */ break ;
include/linux/namei.h +1 -0
@@ -77,6 +77,7 @@ extern struct dentry *kern_path_locked(const char *, struct path *);extern int kern_path_mountpoint (int ,const char *,struct path *,unsigned int );extern struct dentry *lookup_one_len (const char *,struct dentry *,int ); +extern struct dentry *lookup_one_len_unlocked (const char *,struct dentry *,int );extern int follow_down_one (struct path *);extern int follow_down (struct path *);
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
A message that is not in the archive (sent off-list or not yet received): [email protected]
PATCH 1/1 nfsd: don't hold i_mutex over userspace upcalls
From: NeilBrown <[email protected]>
Subject: [PATCH] nfsd: don't hold i_mutex over userspace upcalls
We need information about exports when crossing mountpoints during lookup or NFSv4 readdir. If we don't already have that information cached, we may have to ask (and wait for) rpc.mountd.
In both cases we currently hold the i_mutex on the parent of the directory we're asking rpc.mountd about. We've seen situations where rpc.mountd performs some operation on that directory that tries to take the i_mutex again, resulting in deadlock.
With some care, we may be able to avoid that in rpc.mountd. But it seems better just to avoid holding a mutex while waiting on userspace.
It appears that lookup_one_len is pretty much the only operation that needs the i_mutex. So we could just drop the i_mutex elsewhere and do something like
mutex_lock()
lookup_one_len()
mutex_unlock()
In many cases though the lookup would have been cached and not required the i_mutex, so it's more efficient to create a lookup_one_len() variant that only takes the i_mutex when necessary.
--- fs/namei.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++ fs/nfsd/nfs3xdr.c | 2 +- fs/nfsd/nfs4xdr.c | 8 +++--- fs/nfsd/vfs.c | 23 +++++++--------- include/linux/namei.h | 1 + 5 files changed, 89 insertions(+), 19 deletions(-)
fs/namei.c +74 -0
@@ -2278,6 +2278,8 @@ EXPORT_SYMBOL(vfs_path_lookup);* * Note that this routine is purely a helper for filesystem usage and should * not be called by generic code. +* +* The caller must hold base->i_mutex. */ struct dentry *lookup_one_len (const char *name,struct dentry *base,int len) {
@@ -2321,6 +2323,78 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) }EXPORT_SYMBOL (lookup_one_len); +/** +* lookup_one_len_unlocked - filesystem helper to lookup single pathname component +* @name: pathname component to lookup +* @base: base directory to lookup from +* @len: maximum length @len should be interpreted to +* +* Note that this routine is purely a helper for filesystem usage and should +* not be called by generic code. +* +* Unlike lookup_one_len, it should be called without the parent +* i_mutex held, and will take the i_mutex itself if necessary. +*/ +struct dentry *lookup_one_len_unlocked (const char *name, +struct dentry *base,int len) +{ +struct qstr this; +unsigned int c; +int err; +struct dentry *ret; + + this.name = name; + this.len = len; + this.hash =full_name_hash (name, len); +if (!len) +return ERR_PTR (-EACCES ); + +if (unlikely (name[0 ] =='.' )) { +if (len <2 || (len ==2 && name[1 ] =='.' )) +return ERR_PTR (-EACCES ); + } + +while (len--) { + c = *(const unsigned char *)name++; +if (c =='/' || c =='\0' ) +return ERR_PTR (-EACCES ); + } +/* +* See if the low-level filesystem might want +* to use its own hash.. +*/ +if (base->d_flags &DCACHE_OP_HASH ) { +int err = base->d_op->d_hash (base, &this); +if (err <0 ) +return ERR_PTR (err); + } + + err =inode_permission (base->d_inode,MAY_EXEC ); +if (err) +return ERR_PTR (err); + + ret =__d_lookup (base, &this); +if (ret) +return ret; +/* +* __d_lookup() is used to try to get a quick answer and avoid the +* mutex. A false-negative does no harm. +*/ + ret =__d_lookup (base, &this); +if (ret && ret->d_flags &DCACHE_OP_REVALIDATE ) { +dput (ret); + ret =NULL ; + } +if (ret) +return ret; + +mutex_lock (&base->d_inode->i_mutex); + ret =__lookup_hash (&this, base,0 ); +mutex_unlock (&base->d_inode->i_mutex); +return ret; +} +EXPORT_SYMBOL (lookup_one_len_unlocked); +int user_path_at_empty (int dfd,const char __user *name,unsigned flags,struct path *path,int *empty) {
fs/nfsd/nfs3xdr.c +1 -1
@@ -823,7 +823,7 @@ compose_entry_fh(struct nfsd3_readdirres *cd, struct svc_fh *fhp, }else dchild =dget (dparent); }else - dchild =lookup_one_len (name, dparent, namlen); + dchild =lookup_one_len_unlocked (name, dparent, namlen);if (IS_ERR (dchild))return rv;if (d_mountpoint (dchild))
fs/nfsd/nfs4xdr.c +4 -4
@@ -2838,14 +2838,14 @@ nfsd4_encode_dirent_fattr(struct xdr_stream *xdr, struct nfsd4_readdir *cd,__be32 nfserr;int ignore_crossmnt =0 ; - dentry =lookup_one_len (name, cd->rd_fhp->fh_dentry, namlen); + dentry =lookup_one_len_unlocked (name, cd->rd_fhp->fh_dentry, namlen);if (IS_ERR (dentry))return nfserrno (PTR_ERR (dentry));if (d_really_is_negative (dentry)) {/* -* nfsd_buffered_readdir drops the i_mutex between -* readdir and calling this callback, leaving a window -* where this directory entry could have gone away. +* we're not holding the i_mutex here, so there's +* a window where this directory entry could have gone +* away. */ dput (dentry);return nfserr_noent;
fs/nfsd/vfs.c +9 -14
@@ -217,10 +217,16 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, host_err =PTR_ERR (dentry);if (IS_ERR (dentry)) goto out_nfserr; -/* -* check if we have crossed a mount point ... -*/ if (nfsd_mountpoint (dentry, exp)) { +/* +* We don't need the i_mutex after all. It's +* still possible we could open this (regular +* files can be mountpoints too), but the +* i_mutex is just there to prevent renames of +* something that we might be about to delegate, +* and a mountpoint won't be renamed: +*/ +fh_unlock (fhp);if ((host_err =nfsd_cross_mnt (rqstp, &dentry, &exp))) {dput (dentry); goto out_nfserr;
@@ -1809,7 +1815,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func, offset = *offsetp;while (1 ) { -struct inode *dir_inode =file_inode (file);unsigned int reclen; cdp->err = nfserr_eof;/* will be cleared on successful read */
@@ -1828,15 +1833,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func,if (!size)break ; -/* -* Various filldir functions may end up calling back into -* lookup_one_len() and the file system's ->lookup() method. -* These expect i_mutex to be held, as it would within readdir. -*/ - host_err =mutex_lock_killable (&dir_inode->i_mutex); -if (host_err) -break ; - de = (struct buffered_dirent *)buf.dirent;while (size >0 ) { offset = de->offset;
@@ -1853,7 +1849,6 @@ static __be32 nfsd_buffered_readdir(struct file *file, nfsd_filldir_t func, size -= reclen; de = (struct buffered_dirent *)((char *)de + reclen); } -mutex_unlock (&dir_inode->i_mutex);if (size >0 )/* We bailed out early */ break ;
include/linux/namei.h +1 -0
@@ -77,6 +77,7 @@ extern struct dentry *kern_path_locked(const char *, struct path *);extern int kern_path_mountpoint (int ,const char *,struct path *,unsigned int );extern struct dentry *lookup_one_len (const char *,struct dentry *,int ); +extern struct dentry *lookup_one_len_unlocked (const char *,struct dentry *,int );extern int follow_down_one (struct path *);extern int follow_down (struct path *);
Signature · 6 lines
2.5.0 -- To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
Quoted 32 lines · J. Bruce Fields
Quoted 31 lines · J. Bruce Fields
Quoted 15 lines · Kinglong Mee
fs/namei.c struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
Quoted 12 lines · J. Bruce Fields
+ * Unlike
lookup_one_len, it should be called without the parent
+ *i_mutexheld, and will take thei_mutexitself if necessary.
+ */fs/namei.c +9
-0+struct dentry *lookup_one_len_unlocked (const char *name, +struct dentry *base,int len) +{ +struct qstr this; +unsigned int c; +int err; +struct dentry *ret; + +WARN_ON_ONCE (!mutex_is_locked (&base->d_inode->i_mutex));Remove this line.
Whoops, thanks.
Quoted 11 lines
fs/nfsd/vfs.c nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, · line 220
Quoted 9 lines
fs/nfsd/vfs.c +1
-0@@ -217,6 +217,13 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, host_err =PTR_ERR (dentry);if (IS_ERR (dentry)) goto out_nfserr; +if (!S_ISREG (d_inode (dentry)->i_mode)) {Got a crash here tested by pynfs,
Quoted 4 lines
Quoted 2 lines
OK, I guess I just forgot to take into account negative dentries. Testing that now with (!(d_inode(dentry) &&
S_ISREG(..))).And I also forgot
nfs3xdr.c.
But, this doesn't look good.
OK, to be fair I'm not sure whether this was already happening before this patch.
--b.
[57287.226846] ======================================================
[57287.227499] [ INFO: possible circular locking dependency detected ]
[57287.228009] 4.1.0-rc2-22946-g6c942d3 #174 Not tainted
[57287.228009] -------------------------------------------------------
[57287.228009] updatedb/19312 is trying to acquire lock:
[57287.228009] (&mm->mmap_sem){++++++}, at: [<ffffffff811980bf>] might_fault+0x5f/0xb0
[57287.228009]
but task is already holding lock:
[57287.228009] (&xfs_dir_ilock_class){++++..}, at: [<ffffffff81400c1a>] xfs_ilock+0x10a/0x2e0
[57287.228009] which lock already depends on the new lock.
[57287.228009]
the existing dependency chain (in reverse order) is:
[57287.228009]
-> #2 (&xfs_dir_ilock_class){++++..}:
[57287.228009] [<ffffffff810cf1c2>] __lock_acquire+0x15f2/0x1920
[57287.228009] [<ffffffff810cfe40>] lock_acquire+0xc0/0x280
[57287.228009] [<ffffffff810c8e0d>] down_read_nested+0x4d/0x70
[57287.228009] [<ffffffff81400c1a>] xfs_ilock+0x10a/0x2e0
[57287.228009] [<ffffffff81400e68>] xfs_ilock_attr_map_shared+0x38/0x50
[57287.228009] [<ffffffff8139b9e1>] xfs_attr_get+0xc1/0x180
[57287.228009] [<ffffffff8140ff87>] xfs_xattr_get+0x37/0x50
[57287.228009] [<ffffffff811e866f>] generic_getxattr+0x4f/0x70
[57287.228009] [<ffffffff816535d2>] inode_doinit_with_dentry+0x152/0x670
[57287.228009] [<ffffffff81657f8b>] sb_finish_set_opts+0xdb/0x260
[57287.228009] [<ffffffff81658604>] selinux_set_mnt_opts+0x2c4/0x600
[57287.228009] [<ffffffff816589ff>] superblock_doinit+0xbf/0xd0
[57287.228009] [<ffffffff81658a6d>] selinux_sb_kern_mount+0x3d/0xa0
[57287.228009] [<ffffffff8164efa6>] security_sb_kern_mount+0x16/0x20
[57287.228009] [<ffffffff811c29dd>] mount_fs+0x7d/0x190
[57287.228009] [<ffffffff811e2258>] vfs_kern_mount+0x68/0x160
[57287.228009] [<ffffffff811e58b4>] do_mount+0x204/0xc60
[57287.228009] [<ffffffff811e660f>] SyS_mount+0x6f/0xb0
[57287.228009] [<ffffffff81b6b697>] system_call_fastpath+0x12/0x6f
[57287.228009]
-> #1 (&isec->lock){+.+.+.}:
[57287.228009] [<ffffffff810cf1c2>] __lock_acquire+0x15f2/0x1920
[57287.228009] [<ffffffff810cfe40>] lock_acquire+0xc0/0x280
[57287.228009] [<ffffffff81b675f3>] mutex_lock_nested+0x63/0x400
[57287.228009] [<ffffffff81653525>] inode_doinit_with_dentry+0xa5/0x670
[57287.228009] [<ffffffff81653b0c>] selinux_d_instantiate+0x1c/0x20
[57287.228009] [<ffffffff8164eb2b>] security_d_instantiate+0x1b/0x30
[57287.228009] [<ffffffff811d9ce4>] d_instantiate+0x54/0x80
[57287.228009] [<ffffffff811894fe>] __shmem_file_setup+0xce/0x250
[57287.228009] [<ffffffff8118c5f8>] shmem_zero_setup+0x28/0x70
[57287.228009] [<ffffffff811a1818>] mmap_region+0x5c8/0x5e0
[57287.228009] [<ffffffff811a1ba3>] do_mmap_pgoff+0x373/0x420
[57287.228009] [<ffffffff8118cb00>] vm_mmap_pgoff+0x90/0xc0
[57287.228009] [<ffffffff811a0000>] SyS_mmap_pgoff+0x1b0/0x240
[57287.228009] [<ffffffff81008c92>] SyS_mmap+0x22/0x30
[57287.228009] [<ffffffff81b6b697>] system_call_fastpath+0x12/0x6f
[57287.228009]
-> #0 (&mm->mmap_sem){++++++}:
[57287.228009] [<ffffffff810ccafb>] check_prev_add+0x51b/0x860
[57287.228009] [<ffffffff810cf1c2>] __lock_acquire+0x15f2/0x1920
[57287.228009] [<ffffffff810cfe40>] lock_acquire+0xc0/0x280
[57287.228009] [<ffffffff811980ec>] might_fault+0x8c/0xb0
[57287.228009] [<ffffffff811d3ff2>] filldir+0x92/0x120
[57287.228009] [<ffffffff813ec51a>] xfs_dir2_sf_getdents.isra.10+0x1ba/0x220
[57287.228009] [<ffffffff813ed0de>] xfs_readdir+0x15e/0x300
[57287.228009] [<ffffffff813f116b>] xfs_file_readdir+0x2b/0x30
[57287.228009] [<ffffffff811d3dca>] iterate_dir+0x9a/0x140
[57287.228009] [<ffffffff811d42b1>] SyS_getdents+0x81/0x100
[57287.228009] [<ffffffff81b6b697>] system_call_fastpath+0x12/0x6f
[57287.228009] other info that might help us debug this:
[57287.228009] Chain exists of: &mm->mmap_sem --> &isec->lock --> &xfs_dir_ilock_class
[57287.228009] Possible unsafe locking scenario:
[57287.228009] CPU0 CPU1 [57287.228009] ---- ---- [57287.228009] lock(&xfs_dir_ilock_class); [57287.228009] lock(&isec->lock); [57287.228009] lock(&xfs_dir_ilock_class); [57287.228009] lock(&mm->mmap_sem); [57287.228009] *** DEADLOCK ***
[57287.228009] 2 locks held by updatedb/19312:
[57287.228009] #0: (&type->i_mutex_dir_key#5){+.+.+.}, at: [<ffffffff811d3d91>] iterate_dir+0x61/0x140
[57287.228009] #1: (&xfs_dir_ilock_class){++++..}, at: [<ffffffff81400c1a>] xfs_ilock+0x10a/0x2e0
[57287.228009]
stack backtrace:
[57287.228009] CPU: 0 PID: 19312 Comm: updatedb Not tainted 4.1.0-rc2-22946-g6c942d3 #174
[57287.228009] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.7.5-20140709_153950- 04/01/2014
[57287.228009] ffffffff82c61ed0 ffff880060d0faa8 ffffffff81b61a1a 0000000080000001
[57287.228009] ffffffff82c345d0 ffff880060d0faf8 ffffffff810cb843 0000000000000000
[57287.228009] ffff880060d0fb28 0000000000000000 0000000000000001 0000000000000000
[57287.228009] Call Trace:
[57287.228009] [<ffffffff81b61a1a>] dump_stack+0x4f/0x7b
[57287.228009] [<ffffffff810cb843>] print_circular_bug+0x203/0x310
[57287.228009] [<ffffffff810ccafb>] check_prev_add+0x51b/0x860
[57287.228009] [<ffffffff810ce018>] ? __lock_acquire+0x448/0x1920
[57287.228009] [<ffffffff810cf1c2>] __lock_acquire+0x15f2/0x1920
[57287.228009] [<ffffffff810cfe40>] lock_acquire+0xc0/0x280
[57287.228009] [<ffffffff811980bf>] ? might_fault+0x5f/0xb0
[57287.228009] [<ffffffff811980ec>] might_fault+0x8c/0xb0
[57287.228009] [<ffffffff811980bf>] ? might_fault+0x5f/0xb0
[57287.228009] [<ffffffff811d3ff2>] filldir+0x92/0x120
[57287.228009] [<ffffffff81400e24>] ? xfs_ilock_data_map_shared+0x34/0x40
[57287.228009] [<ffffffff813ec51a>] xfs_dir2_sf_getdents.isra.10+0x1ba/0x220
[57287.228009] [<ffffffff81400c1a>] ? xfs_ilock+0x10a/0x2e0
[57287.228009] [<ffffffff813ed0de>] xfs_readdir+0x15e/0x300
[57287.228009] [<ffffffff810cd6b5>] ? mark_held_locks+0x75/0xa0
[57287.228009] [<ffffffff81b6800a>] ? mutex_lock_killable_nested+0x27a/0x4e0
[57287.228009] [<ffffffff813f116b>] xfs_file_readdir+0x2b/0x30
[57287.228009] [<ffffffff811d3dca>] iterate_dir+0x9a/0x140
[57287.228009] [<ffffffff811dfceb>] ? __fget_light+0x7b/0xa0
[57287.228009] [<ffffffff811d42b1>] SyS_getdents+0x81/0x100
[57287.228009] [<ffffffff811d3f60>] ? fillonedir+0xf0/0xf0
[57287.228009] [<ffffffff81b6b697>] system_call_fastpath+0x12/0x6fQuoted 36 lines · On Thu, 7 May 2015 11:31:16 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 35 lines · J. Bruce Fields
Quoted 33 lines · J. Bruce Fields
Quoted 15 lines · Kinglong Mee
fs/namei.c struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
Quoted 12 lines · J. Bruce Fields
+ * Unlike
lookup_one_len, it should be called without the parent
+ *i_mutexheld, and will take thei_mutexitself if necessary.
+ */fs/namei.c +9
-0+struct dentry *lookup_one_len_unlocked (const char *name, +struct dentry *base,int len) +{ +struct qstr this; +unsigned int c; +int err; +struct dentry *ret; + +WARN_ON_ONCE (!mutex_is_locked (&base->d_inode->i_mutex));Remove this line.
Whoops, thanks.
Quoted 11 lines
fs/nfsd/vfs.c nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, · line 220
Quoted 9 lines
fs/nfsd/vfs.c +1
-0@@ -217,6 +217,13 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, host_err =PTR_ERR (dentry);if (IS_ERR (dentry)) goto out_nfserr; +if (!S_ISREG (d_inode (dentry)->i_mode)) {Got a crash here tested by pynfs,
OK, I guess I just forgot to take into account negative dentries. Testing that now with (!(d_inode(dentry) &&
S_ISREG(..))).
Quoted 6 lines
Quoted 1 line
And I also forgot
nfs3xdr.c.But, this doesn't look good.
OK, to be fair I'm not sure whether this was already happening before this patch.
None of the stacks show any code that we changed, so I'm guessing it is a separate issues as you hint at.
If I didn't have a list as long as my arm of things I really should be doing, I'd go diving into the XFS code ....
NeilBrown
Quoted 123 lines
--b.
[57287.226846] ====================================================== [57287.227499] [ INFO: possible circular locking dependency detected ] [57287.228009] 4.1.0-rc2-22946-g6c942d3 #174 Not tainted [57287.228009] ------------------------------------------------------- [57287.228009] updatedb/19312 is trying to acquire lock: [57287.228009] (&mm->mmap_sem){++++++}, at: [<ffffffff811980bf>] might_fault+0x5f/0xb0 [57287.228009] but task is already holding lock: [57287.228009] (&xfs_dir_ilock_class){++++..}, at: [<ffffffff81400c1a>] xfs_ilock+0x10a/0x2e0 [57287.228009]which lock already depends on the new lock.
[57287.228009] the existing dependency chain (in reverse order) is: [57287.228009] -> #2 (&xfs_dir_ilock_class){++++..}: [57287.228009] [<ffffffff810cf1c2>] __lock_acquire+0x15f2/0x1920 [57287.228009] [<ffffffff810cfe40>] lock_acquire+0xc0/0x280 [57287.228009] [<ffffffff810c8e0d>] down_read_nested+0x4d/0x70 [57287.228009] [<ffffffff81400c1a>] xfs_ilock+0x10a/0x2e0 [57287.228009] [<ffffffff81400e68>] xfs_ilock_attr_map_shared+0x38/0x50 [57287.228009] [<ffffffff8139b9e1>] xfs_attr_get+0xc1/0x180 [57287.228009] [<ffffffff8140ff87>] xfs_xattr_get+0x37/0x50 [57287.228009] [<ffffffff811e866f>] generic_getxattr+0x4f/0x70 [57287.228009] [<ffffffff816535d2>] inode_doinit_with_dentry+0x152/0x670 [57287.228009] [<ffffffff81657f8b>] sb_finish_set_opts+0xdb/0x260 [57287.228009] [<ffffffff81658604>] selinux_set_mnt_opts+0x2c4/0x600 [57287.228009] [<ffffffff816589ff>] superblock_doinit+0xbf/0xd0 [57287.228009] [<ffffffff81658a6d>] selinux_sb_kern_mount+0x3d/0xa0 [57287.228009] [<ffffffff8164efa6>] security_sb_kern_mount+0x16/0x20 [57287.228009] [<ffffffff811c29dd>] mount_fs+0x7d/0x190 [57287.228009] [<ffffffff811e2258>] vfs_kern_mount+0x68/0x160 [57287.228009] [<ffffffff811e58b4>] do_mount+0x204/0xc60 [57287.228009] [<ffffffff811e660f>] SyS_mount+0x6f/0xb0 [57287.228009] [<ffffffff81b6b697>] system_call_fastpath+0x12/0x6f [57287.228009] -> #1 (&isec->lock){+.+.+.}: [57287.228009] [<ffffffff810cf1c2>] __lock_acquire+0x15f2/0x1920 [57287.228009] [<ffffffff810cfe40>] lock_acquire+0xc0/0x280 [57287.228009] [<ffffffff81b675f3>] mutex_lock_nested+0x63/0x400 [57287.228009] [<ffffffff81653525>] inode_doinit_with_dentry+0xa5/0x670 [57287.228009] [<ffffffff81653b0c>] selinux_d_instantiate+0x1c/0x20 [57287.228009] [<ffffffff8164eb2b>] security_d_instantiate+0x1b/0x30 [57287.228009] [<ffffffff811d9ce4>] d_instantiate+0x54/0x80 [57287.228009] [<ffffffff811894fe>] __shmem_file_setup+0xce/0x250 [57287.228009] [<ffffffff8118c5f8>] shmem_zero_setup+0x28/0x70 [57287.228009] [<ffffffff811a1818>] mmap_region+0x5c8/0x5e0 [57287.228009] [<ffffffff811a1ba3>] do_mmap_pgoff+0x373/0x420 [57287.228009] [<ffffffff8118cb00>] vm_mmap_pgoff+0x90/0xc0 [57287.228009] [<ffffffff811a0000>] SyS_mmap_pgoff+0x1b0/0x240 [57287.228009] [<ffffffff81008c92>] SyS_mmap+0x22/0x30 [57287.228009] [<ffffffff81b6b697>] system_call_fastpath+0x12/0x6f [57287.228009] -> #0 (&mm->mmap_sem){++++++}: [57287.228009] [<ffffffff810ccafb>] check_prev_add+0x51b/0x860 [57287.228009] [<ffffffff810cf1c2>] __lock_acquire+0x15f2/0x1920 [57287.228009] [<ffffffff810cfe40>] lock_acquire+0xc0/0x280 [57287.228009] [<ffffffff811980ec>] might_fault+0x8c/0xb0 [57287.228009] [<ffffffff811d3ff2>] filldir+0x92/0x120 [57287.228009] [<ffffffff813ec51a>] xfs_dir2_sf_getdents.isra.10+0x1ba/0x220 [57287.228009] [<ffffffff813ed0de>] xfs_readdir+0x15e/0x300 [57287.228009] [<ffffffff813f116b>] xfs_file_readdir+0x2b/0x30 [57287.228009] [<ffffffff811d3dca>] iterate_dir+0x9a/0x140 [57287.228009] [<ffffffff811d42b1>] SyS_getdents+0x81/0x100 [57287.228009] [<ffffffff81b6b697>] system_call_fastpath+0x12/0x6f [57287.228009]other info that might help us debug this:
[57287.228009] Chain exists of: &mm->mmap_sem --> &isec->lock --> &xfs_dir_ilock_class[57287.228009] Possible unsafe locking scenario:[57287.228009] CPU0 CPU1 [57287.228009] ---- ---- [57287.228009] lock(&xfs_dir_ilock_class); [57287.228009] lock(&isec->lock); [57287.228009] lock(&xfs_dir_ilock_class); [57287.228009] lock(&mm->mmap_sem); [57287.228009] *** DEADLOCK ***[57287.228009] 2 locks held by updatedb/19312: [57287.228009] #0: (&type->i_mutex_dir_key#5){+.+.+.}, at: [<ffffffff811d3d91>] iterate_dir+0x61/0x140 [57287.228009] #1: (&xfs_dir_ilock_class){++++..}, at: [<ffffffff81400c1a>] xfs_ilock+0x10a/0x2e0 [57287.228009] stack backtrace: [57287.228009] CPU: 0 PID: 19312 Comm: updatedb Not tainted 4.1.0-rc2-22946-g6c942d3 #174 [57287.228009] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.7.5-20140709_153950- 04/01/2014 [57287.228009] ffffffff82c61ed0 ffff880060d0faa8 ffffffff81b61a1a 0000000080000001 [57287.228009] ffffffff82c345d0 ffff880060d0faf8 ffffffff810cb843 0000000000000000 [57287.228009] ffff880060d0fb28 0000000000000000 0000000000000001 0000000000000000 [57287.228009] Call Trace: [57287.228009] [<ffffffff81b61a1a>] dump_stack+0x4f/0x7b [57287.228009] [<ffffffff810cb843>] print_circular_bug+0x203/0x310 [57287.228009] [<ffffffff810ccafb>] check_prev_add+0x51b/0x860 [57287.228009] [<ffffffff810ce018>] ? __lock_acquire+0x448/0x1920 [57287.228009] [<ffffffff810cf1c2>] __lock_acquire+0x15f2/0x1920 [57287.228009] [<ffffffff810cfe40>] lock_acquire+0xc0/0x280 [57287.228009] [<ffffffff811980bf>] ? might_fault+0x5f/0xb0 [57287.228009] [<ffffffff811980ec>] might_fault+0x8c/0xb0 [57287.228009] [<ffffffff811980bf>] ? might_fault+0x5f/0xb0 [57287.228009] [<ffffffff811d3ff2>] filldir+0x92/0x120 [57287.228009] [<ffffffff81400e24>] ? xfs_ilock_data_map_shared+0x34/0x40 [57287.228009] [<ffffffff813ec51a>] xfs_dir2_sf_getdents.isra.10+0x1ba/0x220 [57287.228009] [<ffffffff81400c1a>] ? xfs_ilock+0x10a/0x2e0 [57287.228009] [<ffffffff813ed0de>] xfs_readdir+0x15e/0x300 [57287.228009] [<ffffffff810cd6b5>] ? mark_held_locks+0x75/0xa0 [57287.228009] [<ffffffff81b6800a>] ? mutex_lock_killable_nested+0x27a/0x4e0 [57287.228009] [<ffffffff813f116b>] xfs_file_readdir+0x2b/0x30 [57287.228009] [<ffffffff811d3dca>] iterate_dir+0x9a/0x140 [57287.228009] [<ffffffff811dfceb>] ? __fget_light+0x7b/0xa0 [57287.228009] [<ffffffff811d42b1>] SyS_getdents+0x81/0x100 [57287.228009] [<ffffffff811d3f60>] ? fillonedir+0xf0/0xf0 [57287.228009] [<ffffffff81b6b697>] system_call_fastpath+0x12/0x6f--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Quoted 43 lines · NeilBrown
Quoted 40 lines · On Thu, 7 May 2015 11:31:16 -0400 "J. Bruce Fields" <[email protected]>…
Quoted 32 lines · J. Bruce Fields
Quoted 31 lines · J. Bruce Fields
Quoted 15 lines · Kinglong Mee
fs/namei.c struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
Quoted 12 lines · J. Bruce Fields
+ * Unlike
lookup_one_len, it should be called without the parent
+ *i_mutexheld, and will take thei_mutexitself if necessary.
+ */fs/namei.c +9
-0+struct dentry *lookup_one_len_unlocked (const char *name, +struct dentry *base,int len) +{ +struct qstr this; +unsigned int c; +int err; +struct dentry *ret; + +WARN_ON_ONCE (!mutex_is_locked (&base->d_inode->i_mutex));Remove this line.
Whoops, thanks.
Quoted 11 lines
fs/nfsd/vfs.c nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, · line 220
Quoted 9 lines
fs/nfsd/vfs.c +1
-0@@ -217,6 +217,13 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, host_err =PTR_ERR (dentry);if (IS_ERR (dentry)) goto out_nfserr; +if (!S_ISREG (d_inode (dentry)->i_mode)) {Got a crash here tested by pynfs,
Quoted 4 lines
Quoted 2 lines
OK, I guess I just forgot to take into account negative dentries. Testing that now with (!(d_inode(dentry) &&
S_ISREG(..))).And I also forgot
nfs3xdr.c.But, this doesn't look good.
Quoted 8 lines
Quoted 2 lines
OK, to be fair I'm not sure whether this was already happening before this patch.
None of the stacks show any code that we changed, so I'm guessing it is a separate issues as you hint at.
If I didn't have a list as long as my arm of things I really should be doing, I'd go diving into the XFS code ....
Yeah. Looks like it might be intermittent. I'll keep an eye on it and report to xfs people, I guess....
--b.
Cc Steve, Viro,
Quoted 5 lines · J. Bruce Fields
Quoted 4 lines · NeilBrown
Quoted 2 lines · On Wed, 29 Apr 2015 15:19:34 -0400 "J. Bruce Fields" <[email protected]>…
Maybe drop the locking from
nfsd_buffered_readdirand *just* take thei_mutexaroundlookup_one_len(), if that's the only place we need it?
As description in other thread, before the upcall to rpc.mountd, nfsd have call lookup_one_len() for the file, but why rpc.mountd also blocked in lookup ?
There is a bug in rpc.mountd when checking sub-directory, it sets bad patch length for child.
If parent if "/nfs/xfs" and child is "/nfs/test", the child name will be truncated to "/nfs/tes" for strlen(parent), "/nfs/test" have exist in kernel's cache for the lookup_one_len(), but "/nfs/tes" is a bad path, which needs lookup_slow(), so blocked.
static int is_subdirectory(char *child, char *parent)
{
/* Check is child is strictly a subdirectory of
* parent or a more distant descendant.
*/
size_t l = strlen(parent);
if (strcmp(parent, "/") == 0 && child[1] != 0)
return 1;
return (same_path(child, parent, l) && child[l] == '/');
}The following path makes a correct path, not a truncated path. Have be tested, everything is OK.
thanks,
Kinglong Mee
-----------------------------------------------------------------------------------
Quoted 13 lines · On Tue, 05 May 2015 11:53:27 +0800 Kinglong Mee <[email protected]> wrote:
Cc Steve, Viro,
Quoted 5 lines · J. Bruce Fields
Quoted 4 lines · NeilBrown
Quoted 2 lines · On Wed, 29 Apr 2015 15:19:34 -0400 "J. Bruce Fields" <[email protected]>…
Maybe drop the locking from
nfsd_buffered_readdirand *just* take thei_mutexaroundlookup_one_len(), if that's the only place we need it?As description in other thread, before the upcall to rpc.mountd, nfsd have call
lookup_one_len()for the file, but why rpc.mountd also blocked in lookup ?
Quoted 7 lines
There is a bug in rpc.mountd when checking sub-directory, it sets bad patch length for child.
If parent if "/nfs/xfs" and child is "/nfs/test", the child name will be truncated to "/nfs/tes" for strlen(parent), "/nfs/test" have exist in kernel's cache for the
lookup_one_len(), but "/nfs/tes" is a bad path, which needslookup_slow(), so blocked.
Testing for "/nfs/tes" certain seems like a wrong thing to do.
Quoted 12 lines
static int
is_subdirectory(char *child, char *parent){ /* Check is child is strictly a subdirectory of * parent or a more distant descendant. */ size_t l = strlen(parent); if (strcmp(parent, "/") == 0 && child[1] != 0) return 1; return (same_path(child, parent, l) && child[l] == '/');
I guess this should be:
child[l] == '/' && same_path(child, parent, l)
That way there would be no risk of truncating anything.
Can you please test if that one-line change removes the problem?
Quoted 8 lines
}
The following path makes a correct path, not a truncated path. Have be tested, everything is OK.
thanks,
Kinglong Mee
Quoted 8 lines
-----------------------------------------------------------------------------------
Quoted 1 line
From
70b9d1d93a24db8a7837998cb7eb0ff4e98480a6Mon Sep 17 00:00:00 2001From: Kinglong Mee <[email protected]>
Date: Tue, 5 May 2015 11:47:20 +0800
Subject: [PATCH] mountd: Case-insensitive path length must equal to parentCommit
6091c0a4c4(mountd: add support for case-insensitive file names) introdues a bug cause mutex race when looking bad path.
I think we should be clear that the mutex race is already present. I think you are right that there is a bug here which is making it easy to trigger, but it isn't exactly "causing" the bug.
Thanks,
NeilBrown
Quoted 48 lines
Signed-off-by Kinglong Mee <[email protected]>--- utils/mountd/cache.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-)utils/mountd/cache.c +21
-1@@ -468,16 +468,36 @@ fallback:return 1 ; } +static int subdir_len (char *name,int count_slashes) +{ +char *ptr =NULL ; +int i; + + ptr = name; +for (i =0 ; i < count_slashes +1 ; i++) { + ptr =strchr (ptr,'/' ); +if (NULL == ptr) +return strlen (name); + ptr++; + } + +return ptr - name; +} +static int is_subdirectory (char *child,char *parent) {/* Check is child is strictly a subdirectory of * parent or a more distant descendant. */ -size_t l =strlen (parent); +size_t l =subdir_len (child,count_slashes (parent));if (strcmp (parent,"/" ) ==0 && child[1 ] !=0 )return 1 ; +/* Case-insensitive path length must equal to parent */ +if (l !=strlen (parent)) +return 0 ; +return (same_path (child, parent, l) && child[l] =='/' ); }
Quoted 38 lines · NeilBrown
Quoted 13 lines · On Tue, 05 May 2015 11:53:27 +0800 Kinglong Mee <[email protected]> wrote:
Cc Steve, Viro,
Quoted 5 lines · J. Bruce Fields
Quoted 4 lines · NeilBrown
Quoted 2 lines · On Wed, 29 Apr 2015 15:19:34 -0400 "J. Bruce Fields" <[email protected]>…
Maybe drop the locking from
nfsd_buffered_readdirand *just* take thei_mutexaroundlookup_one_len(), if that's the only place we need it?As description in other thread, before the upcall to rpc.mountd, nfsd have call
lookup_one_len()for the file, but why rpc.mountd also blocked in lookup ?Quoted 7 lines
There is a bug in rpc.mountd when checking sub-directory, it sets bad patch length for child.
If parent if "/nfs/xfs" and child is "/nfs/test", the child name will be truncated to "/nfs/tes" for strlen(parent), "/nfs/test" have exist in kernel's cache for the
lookup_one_len(), but "/nfs/tes" is a bad path, which needslookup_slow(), so blocked.Testing for "/nfs/tes" certain seems like a wrong thing to do.
Quoted 12 lines
static int
is_subdirectory(char *child, char *parent){ /* Check is child is strictly a subdirectory of * parent or a more distant descendant. */ size_t l = strlen(parent); if (strcmp(parent, "/") == 0 && child[1] != 0) return 1; return (same_path(child, parent, l) && child[l] == '/');
Quoted 7 lines
I guess this should be:
child[l] == '/' && same_path(child, parent, l)That way there would be no risk of truncating anything.
Can you please test if that one-line change removes the problem?
Yes, that's OK.
I have think about the "/nfs/nfs" and "/nfs/123" of the first patch, yes, that's redundant.
Quoted 15 lines
Quoted 15 lines
}
The following path makes a correct path, not a truncated path. Have be tested, everything is OK.
thanks,
Kinglong Mee-----------------------------------------------------------------------------------
Quoted 1 line
From
70b9d1d93a24db8a7837998cb7eb0ff4e98480a6Mon Sep 17 00:00:00 2001From: Kinglong Mee <[email protected]>
Date: Tue, 5 May 2015 11:47:20 +0800
Subject: [PATCH] mountd: Case-insensitive path length must equal to parent
Quoted 6 lines
Quoted 2 lines
Commit
6091c0a4c4(mountd: add support for case-insensitive file names) introdues a bug cause mutex race when looking bad path.I think we should be clear that the mutex race is already present. I think you are right that there is a bug here which is making it easy to trigger, but it isn't exactly "causing" the bug.
Thanks for the comments.
thanks,
Kinglong Mee
-----------------------------------------------------------------------------------
Quoted 8 lines · Kinglong Mee
-----------------------------------------------------------------------------------
Quoted 1 line
From
d831154bb7e527f9003e16ac049526be5ed90228Mon Sep 17 00:00:00 2001From: Kinglong Mee <[email protected]>
Date: Tue, 5 May 2015 16:24:16 +0800
Subject: [PATCH] mountd: Case-insensitive path length must equals to parentCommit
6091c0a4c4(mountd: add support for case-insensitive file names) introduces looking up bad path which is easy to trigger a present mutex race.
ACK to this patch. (Steved, did you get this?)
--b.
Quoted 21 lines
Signed-off-by Kinglong Mee <[email protected]>--- utils/mountd/cache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)utils/mountd/cache.c +1
-1@@ -478,7 +478,7 @@ static int is_subdirectory(char *child, char *parent)if (strcmp (parent,"/" ) ==0 && child[1 ] !=0 )return 1 ; -return (same_path (child, parent, l) && child[l] =='/' ); +return (child[l] =='/' &&same_path (child, parent, l)); }static int path_matches (nfs_export *exp,char *path)Signature · 1 line
2.4.0
ping ....
Quoted 28 lines
-----------------------------------------------------------------------------------
Quoted 1 line
From
d831154bb7e527f9003e16ac049526be5ed90228Mon Sep 17 00:00:00 2001From: Kinglong Mee <[email protected]>
Date: Tue, 5 May 2015 16:24:16 +0800
Subject: [PATCH] mountd: Case-insensitive path length must equals to parentCommit
6091c0a4c4(mountd: add support for case-insensitive file names) introduces looking up bad path which is easy to trigger a present mutex race.Signed-off-by Kinglong Mee <[email protected]>--- utils/mountd/cache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)utils/mountd/cache.c +1
-1@@ -478,7 +478,7 @@ static int is_subdirectory(char *child, char *parent)if (strcmp (parent,"/" ) ==0 && child[1 ] !=0 )return 1 ; -return (same_path (child, parent, l) && child[l] =='/' ); +return (child[l] =='/' &&same_path (child, parent, l)); }static int path_matches (nfs_export *exp,char *path)
Quoted 11 lines · On Sat, 27 Jun 2015 07:14:00 +0800 Kinglong Mee <[email protected]> wrote:
ping ....
Quoted 9 lines
-----------------------------------------------------------------------------------
Quoted 1 line
From
d831154bb7e527f9003e16ac049526be5ed90228Mon Sep 17 00:00:00 2001From: Kinglong Mee <[email protected]>
Date: Tue, 5 May 2015 16:24:16 +0800
Subject: [PATCH] mountd: Case-insensitive path length must equals to parentCommit
6091c0a4c4(mountd: add support for case-insensitive file names) introduces looking up bad path which is easy to trigger a present mutex race.
How do you know that every file system that treats multiple different strings as the same, imposes the rule that the strings must be the same length?
Given how complex unicode case rules can be, I certainly wouldn't be certain of that.
If the only purpose of this patch is to avoid triggering a bug in a separate piece of code, then I am not in favour of it.
Thanks,
NeilBrown
Quoted 19 lines
Quoted 19 lines
Signed-off-by Kinglong Mee <[email protected]>--- utils/mountd/cache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)utils/mountd/cache.c +1
-1@@ -478,7 +478,7 @@ static int is_subdirectory(char *child, char *parent)if (strcmp (parent,"/" ) ==0 && child[1 ] !=0 )return 1 ; -return (same_path (child, parent, l) && child[l] =='/' ); +return (child[l] =='/' &&same_path (child, parent, l)); }static int path_matches (nfs_export *exp,char *path)
Quoted 11 lines · NeilBrown
Quoted 8 lines · On Sat, 27 Jun 2015 07:14:00 +0800 Kinglong Mee <[email protected]> wrote:
ping ....
Quoted 6 lines
-----------------------------------------------------------------------------------
Quoted 1 line
From
d831154bb7e527f9003e16ac049526be5ed90228Mon Sep 17 00:00:00 2001From: Kinglong Mee <[email protected]>
Date: Tue, 5 May 2015 16:24:16 +0800
Subject: [PATCH] mountd: Case-insensitive path length must equals to parent
Quoted 7 lines
Quoted 3 lines
Quoted 3 lines
Commit
6091c0a4c4(mountd: add support for case-insensitive file names) introduces looking up bad path which is easy to trigger a present mutex race.How do you know that every file system that treats multiple different strings as the same, imposes the rule that the strings must be the same length?
Agree with you.
With your suggestion, there is a bug exist.
static int is_subdirectory(char *child, char *parent)
{
/* Check is child is strictly a subdirectory of
* parent or a more distant descendant.
*/
size_t l = strlen(parent);# rpc.mountd get parent's length.
if (strcmp(parent, "/") == 0 && child[1] != 0)
return 1;
return (same_path(child, parent, l) && child[l] == '/');# will truncate child's name by parent's length, also checking child[l]. # I think the length should be calculated by the count of '/'. }
Quoted 6 lines
Given how complex unicode case rules can be, I certainly wouldn't be certain of that.
If the only purpose of this patch is to avoid triggering a bug in a separate piece of code, then I am not in favour of it.
Yes,
This patch just change two condition's position.
Please ignore this patch,
maybe a new patch for NeilBrown's suggestion will be post.
thanks,
Kinglong Mee
Quoted 26 lines
Thanks,
NeilBrownQuoted 19 lines
Quoted 19 lines
Signed-off-by Kinglong Mee <[email protected]>--- utils/mountd/cache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)utils/mountd/cache.c +1
-1@@ -478,7 +478,7 @@ static int is_subdirectory(char *child, char *parent)if (strcmp (parent,"/" ) ==0 && child[1 ] !=0 )return 1 ; -return (same_path (child, parent, l) && child[l] =='/' ); +return (child[l] =='/' &&same_path (child, parent, l)); }static int path_matches (nfs_export *exp,char *path)
Al Viro
maintainerQuoted 2 lines · NeilBrown
An alternative might be to stop using
lookup_one_len, and usekern_path()instead. Orkern_path_mountpointif we don't want to cross a mountpoint.
NAK. This is a monumentally bad idea - please, do not do it. Not to mention anything else, kern_path() is a _lot_ heavier, including much worse stack footprint.