diff options
Diffstat (limited to 'fs/exportfs/expfs.c')
-rw-r--r-- | fs/exportfs/expfs.c | 164 |
1 files changed, 86 insertions, 78 deletions
diff --git a/fs/exportfs/expfs.c b/fs/exportfs/expfs.c index d8ba88ac10e5..d32ead9026f0 100644 --- a/fs/exportfs/expfs.c +++ b/fs/exportfs/expfs.c | |||
@@ -126,6 +126,86 @@ static void clear_disconnected(struct dentry *dentry) | |||
126 | } | 126 | } |
127 | 127 | ||
128 | /* | 128 | /* |
129 | * Reconnect a directory dentry with its parent. | ||
130 | * | ||
131 | * This can return a dentry, or NULL, or an error. | ||
132 | * | ||
133 | * In the first case the returned dentry is the parent of the given | ||
134 | * dentry, and may itself need to be reconnected to its parent. | ||
135 | * | ||
136 | * In the NULL case, a concurrent VFS operation has either renamed or | ||
137 | * removed this directory. The concurrent operation has reconnected our | ||
138 | * dentry, so we no longer need to. | ||
139 | */ | ||
140 | static struct dentry *reconnect_one(struct vfsmount *mnt, | ||
141 | struct dentry *dentry, char *nbuf) | ||
142 | { | ||
143 | struct dentry *parent; | ||
144 | struct dentry *tmp; | ||
145 | int err; | ||
146 | |||
147 | parent = ERR_PTR(-EACCES); | ||
148 | mutex_lock(&dentry->d_inode->i_mutex); | ||
149 | if (mnt->mnt_sb->s_export_op->get_parent) | ||
150 | parent = mnt->mnt_sb->s_export_op->get_parent(dentry); | ||
151 | mutex_unlock(&dentry->d_inode->i_mutex); | ||
152 | |||
153 | if (IS_ERR(parent)) { | ||
154 | dprintk("%s: get_parent of %ld failed, err %d\n", | ||
155 | __func__, dentry->d_inode->i_ino, PTR_ERR(parent)); | ||
156 | return parent; | ||
157 | } | ||
158 | |||
159 | dprintk("%s: find name of %lu in %lu\n", __func__, | ||
160 | dentry->d_inode->i_ino, parent->d_inode->i_ino); | ||
161 | err = exportfs_get_name(mnt, parent, nbuf, dentry); | ||
162 | if (err == -ENOENT) | ||
163 | goto out_reconnected; | ||
164 | if (err) | ||
165 | goto out_err; | ||
166 | dprintk("%s: found name: %s\n", __func__, nbuf); | ||
167 | mutex_lock(&parent->d_inode->i_mutex); | ||
168 | tmp = lookup_one_len(nbuf, parent, strlen(nbuf)); | ||
169 | mutex_unlock(&parent->d_inode->i_mutex); | ||
170 | if (IS_ERR(tmp)) { | ||
171 | dprintk("%s: lookup failed: %d\n", __func__, PTR_ERR(tmp)); | ||
172 | goto out_err; | ||
173 | } | ||
174 | if (tmp != dentry) { | ||
175 | dput(tmp); | ||
176 | goto out_reconnected; | ||
177 | } | ||
178 | dput(tmp); | ||
179 | if (IS_ROOT(dentry)) { | ||
180 | err = -ESTALE; | ||
181 | goto out_err; | ||
182 | } | ||
183 | return parent; | ||
184 | |||
185 | out_err: | ||
186 | dput(parent); | ||
187 | return ERR_PTR(err); | ||
188 | out_reconnected: | ||
189 | dput(parent); | ||
190 | /* | ||
191 | * Someone must have renamed our entry into another parent, in | ||
192 | * which case it has been reconnected by the rename. | ||
193 | * | ||
194 | * Or someone removed it entirely, in which case filehandle | ||
195 | * lookup will succeed but the directory is now IS_DEAD and | ||
196 | * subsequent operations on it will fail. | ||
197 | * | ||
198 | * Alternatively, maybe there was no race at all, and the | ||
199 | * filesystem is just corrupt and gave us a parent that doesn't | ||
200 | * actually contain any entry pointing to this inode. So, | ||
201 | * double check that this worked and return -ESTALE if not: | ||
202 | */ | ||
203 | if (!dentry_connected(dentry)) | ||
204 | return ERR_PTR(-ESTALE); | ||
205 | return NULL; | ||
206 | } | ||
207 | |||
208 | /* | ||
129 | * Make sure target_dir is fully connected to the dentry tree. | 209 | * Make sure target_dir is fully connected to the dentry tree. |
130 | * | 210 | * |
131 | * On successful return, DCACHE_DISCONNECTED will be cleared on | 211 | * On successful return, DCACHE_DISCONNECTED will be cleared on |
@@ -158,76 +238,19 @@ reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf) | |||
158 | dput(pd); | 238 | dput(pd); |
159 | break; | 239 | break; |
160 | } else { | 240 | } else { |
241 | struct dentry *parent; | ||
161 | /* | 242 | /* |
162 | * We have hit the top of a disconnected path, try to | 243 | * We have hit the top of a disconnected path, try to |
163 | * find parent and connect. | 244 | * find parent and connect. |
164 | * | ||
165 | * Racing with some other process renaming a directory | ||
166 | * isn't much of a problem here. If someone renames | ||
167 | * the directory, it will end up properly connected, | ||
168 | * which is what we want | ||
169 | * | ||
170 | * Getting the parent can't be supported generically, | ||
171 | * the locking is too icky. | ||
172 | * | ||
173 | * Instead we just return EACCES. If server reboots | ||
174 | * or inodes get flushed, you lose | ||
175 | */ | ||
176 | struct dentry *ppd = ERR_PTR(-EACCES); | ||
177 | struct dentry *npd; | ||
178 | |||
179 | mutex_lock(&pd->d_inode->i_mutex); | ||
180 | if (mnt->mnt_sb->s_export_op->get_parent) | ||
181 | ppd = mnt->mnt_sb->s_export_op->get_parent(pd); | ||
182 | mutex_unlock(&pd->d_inode->i_mutex); | ||
183 | |||
184 | if (IS_ERR(ppd)) { | ||
185 | err = PTR_ERR(ppd); | ||
186 | dprintk("%s: get_parent of %ld failed, err %d\n", | ||
187 | __func__, pd->d_inode->i_ino, err); | ||
188 | dput(pd); | ||
189 | break; | ||
190 | } | ||
191 | |||
192 | dprintk("%s: find name of %lu in %lu\n", __func__, | ||
193 | pd->d_inode->i_ino, ppd->d_inode->i_ino); | ||
194 | err = exportfs_get_name(mnt, ppd, nbuf, pd); | ||
195 | if (err) { | ||
196 | dput(ppd); | ||
197 | dput(pd); | ||
198 | if (err == -ENOENT) | ||
199 | /* some race between get_parent and | ||
200 | * get_name? | ||
201 | */ | ||
202 | goto out_reconnected; | ||
203 | break; | ||
204 | } | ||
205 | dprintk("%s: found name: %s\n", __func__, nbuf); | ||
206 | mutex_lock(&ppd->d_inode->i_mutex); | ||
207 | npd = lookup_one_len(nbuf, ppd, strlen(nbuf)); | ||
208 | mutex_unlock(&ppd->d_inode->i_mutex); | ||
209 | if (IS_ERR(npd)) { | ||
210 | err = PTR_ERR(npd); | ||
211 | dprintk("%s: lookup failed: %d\n", | ||
212 | __func__, err); | ||
213 | dput(ppd); | ||
214 | dput(pd); | ||
215 | break; | ||
216 | } | ||
217 | /* we didn't really want npd, we really wanted | ||
218 | * a side-effect of the lookup. | ||
219 | * hopefully, npd == pd, though it isn't really | ||
220 | * a problem if it isn't | ||
221 | */ | 245 | */ |
222 | dput(npd); | 246 | parent = reconnect_one(mnt, pd, nbuf); |
223 | dput(ppd); | 247 | if (!parent) |
224 | if (npd != pd) | ||
225 | goto out_reconnected; | 248 | goto out_reconnected; |
226 | if (IS_ROOT(pd)) { | 249 | if (IS_ERR(parent)) { |
227 | /* something went wrong, we have to give up */ | 250 | err = PTR_ERR(parent); |
228 | dput(pd); | ||
229 | break; | 251 | break; |
230 | } | 252 | } |
253 | dput(parent); | ||
231 | } | 254 | } |
232 | dput(pd); | 255 | dput(pd); |
233 | } | 256 | } |
@@ -241,21 +264,6 @@ reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf) | |||
241 | 264 | ||
242 | return 0; | 265 | return 0; |
243 | out_reconnected: | 266 | out_reconnected: |
244 | /* | ||
245 | * Someone must have renamed our entry into another parent, in | ||
246 | * which case it has been reconnected by the rename. | ||
247 | * | ||
248 | * Or someone removed it entirely, in which case filehandle | ||
249 | * lookup will succeed but the directory is now IS_DEAD and | ||
250 | * subsequent operations on it will fail. | ||
251 | * | ||
252 | * Alternatively, maybe there was no race at all, and the | ||
253 | * filesystem is just corrupt and gave us a parent that doesn't | ||
254 | * actually contain any entry pointing to this inode. So, | ||
255 | * double check that this worked and return -ESTALE if not: | ||
256 | */ | ||
257 | if (!dentry_connected(target_dir)) | ||
258 | return -ESTALE; | ||
259 | clear_disconnected(target_dir); | 267 | clear_disconnected(target_dir); |
260 | return 0; | 268 | return 0; |
261 | } | 269 | } |