aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiklos Szeredi <mszeredi@suse.cz>2013-09-05 05:44:36 -0400
committerAl Viro <viro@zeniv.linux.org.uk>2013-09-05 16:23:41 -0400
commit848ac114e847af3f1f9141c90a39ebe79bdb13b3 (patch)
tree3673b97171b0e0bc5b179908b4ad15ede9a91b35
parentdb14fc3abcd5dcc9b32ad5b9dd5b8f9e16295a39 (diff)
vfs: check submounts and drop atomically
We check submounts before doing d_drop() on a non-empty directory dentry in NFS (have_submounts()), but we do not exclude a racing mount. Process A: have_submounts() -> returns false Process B: mount() -> success Process A: d_drop() This patch prepares the ground for the fix by doing the following operations all under the same rename lock: have_submounts() shrink_dcache_parent() d_drop() This is actually an optimization since have_submounts() and shrink_dcache_parent() both traverse the same dentry tree separately. Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> CC: David Howells <dhowells@redhat.com> CC: Steven Whitehouse <swhiteho@redhat.com> CC: Trond Myklebust <Trond.Myklebust@netapp.com> CC: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
-rw-r--r--fs/dcache.c65
-rw-r--r--include/linux/dcache.h1
2 files changed, 66 insertions, 0 deletions
diff --git a/fs/dcache.c b/fs/dcache.c
index 043c5b478a77..ce5a7e6d84cd 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1264,6 +1264,71 @@ void shrink_dcache_parent(struct dentry *parent)
1264} 1264}
1265EXPORT_SYMBOL(shrink_dcache_parent); 1265EXPORT_SYMBOL(shrink_dcache_parent);
1266 1266
1267static enum d_walk_ret check_and_collect(void *_data, struct dentry *dentry)
1268{
1269 struct select_data *data = _data;
1270
1271 if (d_mountpoint(dentry)) {
1272 data->found = -EBUSY;
1273 return D_WALK_QUIT;
1274 }
1275
1276 return select_collect(_data, dentry);
1277}
1278
1279static void check_and_drop(void *_data)
1280{
1281 struct select_data *data = _data;
1282
1283 if (d_mountpoint(data->start))
1284 data->found = -EBUSY;
1285 if (!data->found)
1286 __d_drop(data->start);
1287}
1288
1289/**
1290 * check_submounts_and_drop - prune dcache, check for submounts and drop
1291 *
1292 * All done as a single atomic operation relative to has_unlinked_ancestor().
1293 * Returns 0 if successfully unhashed @parent. If there were submounts then
1294 * return -EBUSY.
1295 *
1296 * @dentry: dentry to prune and drop
1297 */
1298int check_submounts_and_drop(struct dentry *dentry)
1299{
1300 int ret = 0;
1301
1302 /* Negative dentries can be dropped without further checks */
1303 if (!dentry->d_inode) {
1304 d_drop(dentry);
1305 goto out;
1306 }
1307
1308 for (;;) {
1309 struct select_data data;
1310
1311 INIT_LIST_HEAD(&data.dispose);
1312 data.start = dentry;
1313 data.found = 0;
1314
1315 d_walk(dentry, &data, check_and_collect, check_and_drop);
1316 ret = data.found;
1317
1318 if (!list_empty(&data.dispose))
1319 shrink_dentry_list(&data.dispose);
1320
1321 if (ret <= 0)
1322 break;
1323
1324 cond_resched();
1325 }
1326
1327out:
1328 return ret;
1329}
1330EXPORT_SYMBOL(check_submounts_and_drop);
1331
1267/** 1332/**
1268 * __d_alloc - allocate a dcache entry 1333 * __d_alloc - allocate a dcache entry
1269 * @sb: filesystem it will belong to 1334 * @sb: filesystem it will belong to
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index 9169b91ea2d2..c6d3fe50ff96 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -253,6 +253,7 @@ extern void d_prune_aliases(struct inode *);
253 253
254/* test whether we have any submounts in a subdir tree */ 254/* test whether we have any submounts in a subdir tree */
255extern int have_submounts(struct dentry *); 255extern int have_submounts(struct dentry *);
256extern int check_submounts_and_drop(struct dentry *);
256 257
257/* 258/*
258 * This adds the entry to the hash queues. 259 * This adds the entry to the hash queues.