diff options
-rw-r--r-- | fs/exportfs/expfs.c | 45 |
1 files changed, 40 insertions, 5 deletions
diff --git a/fs/exportfs/expfs.c b/fs/exportfs/expfs.c index c65b748688ff..6b5ddd5492bc 100644 --- a/fs/exportfs/expfs.c +++ b/fs/exportfs/expfs.c | |||
@@ -90,6 +90,23 @@ find_disconnected_root(struct dentry *dentry) | |||
90 | return dentry; | 90 | return dentry; |
91 | } | 91 | } |
92 | 92 | ||
93 | static bool dentry_connected(struct dentry *dentry) | ||
94 | { | ||
95 | dget(dentry); | ||
96 | while (dentry->d_flags & DCACHE_DISCONNECTED) { | ||
97 | struct dentry *parent = dget_parent(dentry); | ||
98 | |||
99 | dput(dentry); | ||
100 | if (IS_ROOT(dentry)) { | ||
101 | dput(parent); | ||
102 | return false; | ||
103 | } | ||
104 | dentry = parent; | ||
105 | } | ||
106 | dput(dentry); | ||
107 | return true; | ||
108 | } | ||
109 | |||
93 | static void clear_disconnected(struct dentry *dentry) | 110 | static void clear_disconnected(struct dentry *dentry) |
94 | { | 111 | { |
95 | dget(dentry); | 112 | dget(dentry); |
@@ -189,9 +206,9 @@ reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf) | |||
189 | dput(pd); | 206 | dput(pd); |
190 | if (err == -ENOENT) | 207 | if (err == -ENOENT) |
191 | /* some race between get_parent and | 208 | /* some race between get_parent and |
192 | * get_name? just try again | 209 | * get_name? |
193 | */ | 210 | */ |
194 | continue; | 211 | goto out_reconnected; |
195 | break; | 212 | break; |
196 | } | 213 | } |
197 | dprintk("%s: found name: %s\n", __func__, nbuf); | 214 | dprintk("%s: found name: %s\n", __func__, nbuf); |
@@ -211,12 +228,12 @@ reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf) | |||
211 | * hopefully, npd == pd, though it isn't really | 228 | * hopefully, npd == pd, though it isn't really |
212 | * a problem if it isn't | 229 | * a problem if it isn't |
213 | */ | 230 | */ |
231 | dput(npd); | ||
232 | dput(ppd); | ||
214 | if (npd == pd) | 233 | if (npd == pd) |
215 | noprogress = 0; | 234 | noprogress = 0; |
216 | else | 235 | else |
217 | printk("%s: npd != pd\n", __func__); | 236 | goto out_reconnected; |
218 | dput(npd); | ||
219 | dput(ppd); | ||
220 | if (IS_ROOT(pd)) { | 237 | if (IS_ROOT(pd)) { |
221 | /* something went wrong, we have to give up */ | 238 | /* something went wrong, we have to give up */ |
222 | dput(pd); | 239 | dput(pd); |
@@ -234,6 +251,24 @@ reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf) | |||
234 | } | 251 | } |
235 | 252 | ||
236 | return 0; | 253 | return 0; |
254 | out_reconnected: | ||
255 | /* | ||
256 | * Someone must have renamed our entry into another parent, in | ||
257 | * which case it has been reconnected by the rename. | ||
258 | * | ||
259 | * Or someone removed it entirely, in which case filehandle | ||
260 | * lookup will succeed but the directory is now IS_DEAD and | ||
261 | * subsequent operations on it will fail. | ||
262 | * | ||
263 | * Alternatively, maybe there was no race at all, and the | ||
264 | * filesystem is just corrupt and gave us a parent that doesn't | ||
265 | * actually contain any entry pointing to this inode. So, | ||
266 | * double check that this worked and return -ESTALE if not: | ||
267 | */ | ||
268 | if (!dentry_connected(target_dir)) | ||
269 | return -ESTALE; | ||
270 | clear_disconnected(target_dir); | ||
271 | return 0; | ||
237 | } | 272 | } |
238 | 273 | ||
239 | struct getdents_callback { | 274 | struct getdents_callback { |