diff options
Diffstat (limited to 'fs/nfs/unlink.c')
-rw-r--r-- | fs/nfs/unlink.c | 114 |
1 files changed, 99 insertions, 15 deletions
diff --git a/fs/nfs/unlink.c b/fs/nfs/unlink.c index 1aed850d18f2..6ecd46c967c8 100644 --- a/fs/nfs/unlink.c +++ b/fs/nfs/unlink.c | |||
@@ -14,6 +14,7 @@ | |||
14 | 14 | ||
15 | 15 | ||
16 | struct nfs_unlinkdata { | 16 | struct nfs_unlinkdata { |
17 | struct hlist_node list; | ||
17 | struct nfs_removeargs args; | 18 | struct nfs_removeargs args; |
18 | struct nfs_removeres res; | 19 | struct nfs_removeres res; |
19 | struct inode *dir; | 20 | struct inode *dir; |
@@ -52,6 +53,20 @@ static int nfs_copy_dname(struct dentry *dentry, struct nfs_unlinkdata *data) | |||
52 | return 0; | 53 | return 0; |
53 | } | 54 | } |
54 | 55 | ||
56 | static void nfs_free_dname(struct nfs_unlinkdata *data) | ||
57 | { | ||
58 | kfree(data->args.name.name); | ||
59 | data->args.name.name = NULL; | ||
60 | data->args.name.len = 0; | ||
61 | } | ||
62 | |||
63 | static void nfs_dec_sillycount(struct inode *dir) | ||
64 | { | ||
65 | struct nfs_inode *nfsi = NFS_I(dir); | ||
66 | if (atomic_dec_return(&nfsi->silly_count) == 1) | ||
67 | wake_up(&nfsi->waitqueue); | ||
68 | } | ||
69 | |||
55 | /** | 70 | /** |
56 | * nfs_async_unlink_init - Initialize the RPC info | 71 | * nfs_async_unlink_init - Initialize the RPC info |
57 | * task: rpc_task of the sillydelete | 72 | * task: rpc_task of the sillydelete |
@@ -95,6 +110,8 @@ static void nfs_async_unlink_done(struct rpc_task *task, void *calldata) | |||
95 | static void nfs_async_unlink_release(void *calldata) | 110 | static void nfs_async_unlink_release(void *calldata) |
96 | { | 111 | { |
97 | struct nfs_unlinkdata *data = calldata; | 112 | struct nfs_unlinkdata *data = calldata; |
113 | |||
114 | nfs_dec_sillycount(data->dir); | ||
98 | nfs_free_unlinkdata(data); | 115 | nfs_free_unlinkdata(data); |
99 | } | 116 | } |
100 | 117 | ||
@@ -104,33 +121,100 @@ static const struct rpc_call_ops nfs_unlink_ops = { | |||
104 | .rpc_release = nfs_async_unlink_release, | 121 | .rpc_release = nfs_async_unlink_release, |
105 | }; | 122 | }; |
106 | 123 | ||
107 | static int nfs_call_unlink(struct dentry *dentry, struct nfs_unlinkdata *data) | 124 | static int nfs_do_call_unlink(struct dentry *parent, struct inode *dir, struct nfs_unlinkdata *data) |
108 | { | 125 | { |
109 | struct rpc_task *task; | 126 | struct rpc_task *task; |
127 | struct dentry *alias; | ||
128 | |||
129 | alias = d_lookup(parent, &data->args.name); | ||
130 | if (alias != NULL) { | ||
131 | int ret = 0; | ||
132 | /* | ||
133 | * Hey, we raced with lookup... See if we need to transfer | ||
134 | * the sillyrename information to the aliased dentry. | ||
135 | */ | ||
136 | nfs_free_dname(data); | ||
137 | spin_lock(&alias->d_lock); | ||
138 | if (!(alias->d_flags & DCACHE_NFSFS_RENAMED)) { | ||
139 | alias->d_fsdata = data; | ||
140 | alias->d_flags ^= DCACHE_NFSFS_RENAMED; | ||
141 | ret = 1; | ||
142 | } | ||
143 | spin_unlock(&alias->d_lock); | ||
144 | nfs_dec_sillycount(dir); | ||
145 | dput(alias); | ||
146 | return ret; | ||
147 | } | ||
148 | data->dir = igrab(dir); | ||
149 | if (!data->dir) { | ||
150 | nfs_dec_sillycount(dir); | ||
151 | return 0; | ||
152 | } | ||
153 | data->args.fh = NFS_FH(dir); | ||
154 | nfs_fattr_init(&data->res.dir_attr); | ||
155 | |||
156 | task = rpc_run_task(NFS_CLIENT(dir), RPC_TASK_ASYNC, &nfs_unlink_ops, data); | ||
157 | if (!IS_ERR(task)) | ||
158 | rpc_put_task(task); | ||
159 | return 1; | ||
160 | } | ||
161 | |||
162 | static int nfs_call_unlink(struct dentry *dentry, struct nfs_unlinkdata *data) | ||
163 | { | ||
110 | struct dentry *parent; | 164 | struct dentry *parent; |
111 | struct inode *dir; | 165 | struct inode *dir; |
166 | int ret = 0; | ||
112 | 167 | ||
113 | if (nfs_copy_dname(dentry, data) < 0) | ||
114 | goto out_free; | ||
115 | 168 | ||
116 | parent = dget_parent(dentry); | 169 | parent = dget_parent(dentry); |
117 | if (parent == NULL) | 170 | if (parent == NULL) |
118 | goto out_free; | 171 | goto out_free; |
119 | dir = igrab(parent->d_inode); | 172 | dir = parent->d_inode; |
173 | if (nfs_copy_dname(dentry, data) == 0) | ||
174 | goto out_dput; | ||
175 | /* Non-exclusive lock protects against concurrent lookup() calls */ | ||
176 | spin_lock(&dir->i_lock); | ||
177 | if (atomic_inc_not_zero(&NFS_I(dir)->silly_count) == 0) { | ||
178 | /* Deferred delete */ | ||
179 | hlist_add_head(&data->list, &NFS_I(dir)->silly_list); | ||
180 | spin_unlock(&dir->i_lock); | ||
181 | ret = 1; | ||
182 | goto out_dput; | ||
183 | } | ||
184 | spin_unlock(&dir->i_lock); | ||
185 | ret = nfs_do_call_unlink(parent, dir, data); | ||
186 | out_dput: | ||
120 | dput(parent); | 187 | dput(parent); |
121 | if (dir == NULL) | 188 | out_free: |
122 | goto out_free; | 189 | return ret; |
190 | } | ||
123 | 191 | ||
124 | data->dir = dir; | 192 | void nfs_block_sillyrename(struct dentry *dentry) |
125 | data->args.fh = NFS_FH(dir); | 193 | { |
126 | nfs_fattr_init(&data->res.dir_attr); | 194 | struct nfs_inode *nfsi = NFS_I(dentry->d_inode); |
127 | 195 | ||
128 | task = rpc_run_task(NFS_CLIENT(dir), RPC_TASK_ASYNC, &nfs_unlink_ops, data); | 196 | wait_event(nfsi->waitqueue, atomic_cmpxchg(&nfsi->silly_count, 1, 0) == 1); |
129 | if (!IS_ERR(task)) | 197 | } |
130 | rpc_put_task(task); | 198 | |
131 | return 1; | 199 | void nfs_unblock_sillyrename(struct dentry *dentry) |
132 | out_free: | 200 | { |
133 | return 0; | 201 | struct inode *dir = dentry->d_inode; |
202 | struct nfs_inode *nfsi = NFS_I(dir); | ||
203 | struct nfs_unlinkdata *data; | ||
204 | |||
205 | atomic_inc(&nfsi->silly_count); | ||
206 | spin_lock(&dir->i_lock); | ||
207 | while (!hlist_empty(&nfsi->silly_list)) { | ||
208 | if (!atomic_inc_not_zero(&nfsi->silly_count)) | ||
209 | break; | ||
210 | data = hlist_entry(nfsi->silly_list.first, struct nfs_unlinkdata, list); | ||
211 | hlist_del(&data->list); | ||
212 | spin_unlock(&dir->i_lock); | ||
213 | if (nfs_do_call_unlink(dentry, dir, data) == 0) | ||
214 | nfs_free_unlinkdata(data); | ||
215 | spin_lock(&dir->i_lock); | ||
216 | } | ||
217 | spin_unlock(&dir->i_lock); | ||
134 | } | 218 | } |
135 | 219 | ||
136 | /** | 220 | /** |