diff options
author | Sage Weil <sage@newdream.net> | 2010-05-11 23:56:31 -0400 |
---|---|---|
committer | Sage Weil <sage@newdream.net> | 2010-05-11 23:56:31 -0400 |
commit | f818a73674c5d197f66b636a46d7d578d7258129 (patch) | |
tree | 90c485b5ca0a211b84ad8feddfce4f301de3d5c9 /fs/ceph/caps.c | |
parent | 45c6ceb547ad2d98215351974a4686bf8cb13e14 (diff) |
ceph: fix cap removal races
The iterate_session_caps helper traverses the session caps list and tries
to grab an inode reference. However, the __ceph_remove_cap was clearing
the inode backpointer _before_ removing itself from the session list,
causing a null pointer dereference.
Clear cap->ci under protection of s_cap_lock to avoid the race, and to
tightly couple the list and backpointer state. Use a local flag to
indicate whether we are releasing the cap, as cap->session may be modified
by a racing thread in iterate_session_caps.
Signed-off-by: Sage Weil <sage@newdream.net>
Diffstat (limited to 'fs/ceph/caps.c')
-rw-r--r-- | fs/ceph/caps.c | 19 |
1 files changed, 12 insertions, 7 deletions
diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index 0c1681806867..d9400534b279 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c | |||
@@ -858,6 +858,8 @@ static int __ceph_is_any_caps(struct ceph_inode_info *ci) | |||
858 | } | 858 | } |
859 | 859 | ||
860 | /* | 860 | /* |
861 | * Remove a cap. Take steps to deal with a racing iterate_session_caps. | ||
862 | * | ||
861 | * caller should hold i_lock. | 863 | * caller should hold i_lock. |
862 | * caller will not hold session s_mutex if called from destroy_inode. | 864 | * caller will not hold session s_mutex if called from destroy_inode. |
863 | */ | 865 | */ |
@@ -866,15 +868,10 @@ void __ceph_remove_cap(struct ceph_cap *cap) | |||
866 | struct ceph_mds_session *session = cap->session; | 868 | struct ceph_mds_session *session = cap->session; |
867 | struct ceph_inode_info *ci = cap->ci; | 869 | struct ceph_inode_info *ci = cap->ci; |
868 | struct ceph_mds_client *mdsc = &ceph_client(ci->vfs_inode.i_sb)->mdsc; | 870 | struct ceph_mds_client *mdsc = &ceph_client(ci->vfs_inode.i_sb)->mdsc; |
871 | int removed = 0; | ||
869 | 872 | ||
870 | dout("__ceph_remove_cap %p from %p\n", cap, &ci->vfs_inode); | 873 | dout("__ceph_remove_cap %p from %p\n", cap, &ci->vfs_inode); |
871 | 874 | ||
872 | /* remove from inode list */ | ||
873 | rb_erase(&cap->ci_node, &ci->i_caps); | ||
874 | cap->ci = NULL; | ||
875 | if (ci->i_auth_cap == cap) | ||
876 | ci->i_auth_cap = NULL; | ||
877 | |||
878 | /* remove from session list */ | 875 | /* remove from session list */ |
879 | spin_lock(&session->s_cap_lock); | 876 | spin_lock(&session->s_cap_lock); |
880 | if (session->s_cap_iterator == cap) { | 877 | if (session->s_cap_iterator == cap) { |
@@ -885,10 +882,18 @@ void __ceph_remove_cap(struct ceph_cap *cap) | |||
885 | list_del_init(&cap->session_caps); | 882 | list_del_init(&cap->session_caps); |
886 | session->s_nr_caps--; | 883 | session->s_nr_caps--; |
887 | cap->session = NULL; | 884 | cap->session = NULL; |
885 | removed = 1; | ||
888 | } | 886 | } |
887 | /* protect backpointer with s_cap_lock: see iterate_session_caps */ | ||
888 | cap->ci = NULL; | ||
889 | spin_unlock(&session->s_cap_lock); | 889 | spin_unlock(&session->s_cap_lock); |
890 | 890 | ||
891 | if (cap->session == NULL) | 891 | /* remove from inode list */ |
892 | rb_erase(&cap->ci_node, &ci->i_caps); | ||
893 | if (ci->i_auth_cap == cap) | ||
894 | ci->i_auth_cap = NULL; | ||
895 | |||
896 | if (removed) | ||
892 | ceph_put_cap(cap); | 897 | ceph_put_cap(cap); |
893 | 898 | ||
894 | if (!__ceph_is_any_caps(ci) && ci->i_snap_realm) { | 899 | if (!__ceph_is_any_caps(ci) && ci->i_snap_realm) { |