|
@@ -1264,6 +1264,71 @@ void shrink_dcache_parent(struct dentry *parent)
|
|
|
}
|
|
|
EXPORT_SYMBOL(shrink_dcache_parent);
|
|
|
|
|
|
+static enum d_walk_ret check_and_collect(void *_data, struct dentry *dentry)
|
|
|
+{
|
|
|
+ struct select_data *data = _data;
|
|
|
+
|
|
|
+ if (d_mountpoint(dentry)) {
|
|
|
+ data->found = -EBUSY;
|
|
|
+ return D_WALK_QUIT;
|
|
|
+ }
|
|
|
+
|
|
|
+ return select_collect(_data, dentry);
|
|
|
+}
|
|
|
+
|
|
|
+static void check_and_drop(void *_data)
|
|
|
+{
|
|
|
+ struct select_data *data = _data;
|
|
|
+
|
|
|
+ if (d_mountpoint(data->start))
|
|
|
+ data->found = -EBUSY;
|
|
|
+ if (!data->found)
|
|
|
+ __d_drop(data->start);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * check_submounts_and_drop - prune dcache, check for submounts and drop
|
|
|
+ *
|
|
|
+ * All done as a single atomic operation relative to has_unlinked_ancestor().
|
|
|
+ * Returns 0 if successfully unhashed @parent. If there were submounts then
|
|
|
+ * return -EBUSY.
|
|
|
+ *
|
|
|
+ * @dentry: dentry to prune and drop
|
|
|
+ */
|
|
|
+int check_submounts_and_drop(struct dentry *dentry)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ /* Negative dentries can be dropped without further checks */
|
|
|
+ if (!dentry->d_inode) {
|
|
|
+ d_drop(dentry);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (;;) {
|
|
|
+ struct select_data data;
|
|
|
+
|
|
|
+ INIT_LIST_HEAD(&data.dispose);
|
|
|
+ data.start = dentry;
|
|
|
+ data.found = 0;
|
|
|
+
|
|
|
+ d_walk(dentry, &data, check_and_collect, check_and_drop);
|
|
|
+ ret = data.found;
|
|
|
+
|
|
|
+ if (!list_empty(&data.dispose))
|
|
|
+ shrink_dentry_list(&data.dispose);
|
|
|
+
|
|
|
+ if (ret <= 0)
|
|
|
+ break;
|
|
|
+
|
|
|
+ cond_resched();
|
|
|
+ }
|
|
|
+
|
|
|
+out:
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(check_submounts_and_drop);
|
|
|
+
|
|
|
/**
|
|
|
* __d_alloc - allocate a dcache entry
|
|
|
* @sb: filesystem it will belong to
|