diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index cbece1221417bb..b66c19db0c29f0 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1490,6 +1490,31 @@ static int fuse_notify_inval_inode(struct fuse_conn *fc, unsigned int size, return err; } +static int +fuse_notify_inval_dircache_entries(struct fuse_conn *fc, unsigned int size, + struct fuse_copy_state *cs) +{ + struct fuse_notify_inval_dircache_entries_out outarg; + int err = -EINVAL; + + if (size < sizeof(outarg)) + goto err; + err = fuse_copy_one(cs, &outarg, sizeof(outarg)); + if (err) + goto err; + fuse_copy_finish(cs); + down_read(&fc->killsb); + err = -ENOENT; + if (fc->sb) + err = fuse_reverse_inval_dircache_entries(fc->sb, + outarg.parent); + up_read(&fc->killsb); + return err; +err: + fuse_copy_finish(cs); + return err; +} + static int fuse_notify_inval_entry(struct fuse_conn *fc, unsigned int size, struct fuse_copy_state *cs) { @@ -1815,6 +1840,9 @@ static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code, case FUSE_NOTIFY_DELETE: return fuse_notify_delete(fc, size, cs); + case FUSE_NOTIFY_INVAL_DIRCACHE_ENTRIES: + return fuse_notify_inval_dircache_entries(fc, size, cs); + default: fuse_copy_finish(cs); return -EINVAL; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 4b855b65d4577a..4e4f86bfde73f8 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -932,6 +932,38 @@ int fuse_update_attributes(struct inode *inode, struct kstat *stat, return err; } +int fuse_reverse_inval_dircache_entries(struct super_block *sb, + u64 parent_nodeid) +{ + int err = -ENOTDIR; + struct inode *parent; + struct dentry *dir; + struct dentry *child; + + parent = ilookup5(sb, parent_nodeid, fuse_inode_eq, &parent_nodeid); + if (!parent) + return -ENOENT; + + inode_lock(parent); + if (!S_ISDIR(parent->i_mode)) + goto unlock; + + err = -ENOENT; + dir = d_find_alias(parent); + if (!dir) + goto unlock; + err = 0; + spin_lock(&dir->d_lock); + list_for_each_entry(child, &dir->d_subdirs, d_child) + fuse_invalidate_entry_cache(child); + spin_unlock(&dir->d_lock); + dput(dir); + unlock: + inode_unlock(parent); + iput(parent); + return err; +} + int fuse_reverse_inval_entry(struct super_block *sb, u64 parent_nodeid, u64 child_nodeid, struct qstr *name) { diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index eddbe02c402892..7cf0a3f456a825 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -924,6 +924,9 @@ int fuse_reverse_inval_inode(struct super_block *sb, u64 nodeid, int fuse_reverse_inval_entry(struct super_block *sb, u64 parent_nodeid, u64 child_nodeid, struct qstr *name); +int fuse_reverse_inval_dircache_entries(struct super_block *sb, + u64 parent_nodeid); + int fuse_do_open(struct fuse_conn *fc, u64 nodeid, struct file *file, bool isdir); diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index 5974fae54e12c5..c74fc3cf93fb56 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -374,6 +374,7 @@ enum fuse_notify_code { FUSE_NOTIFY_STORE = 4, FUSE_NOTIFY_RETRIEVE = 5, FUSE_NOTIFY_DELETE = 6, + FUSE_NOTIFY_INVAL_DIRCACHE_ENTRIES = 7, FUSE_NOTIFY_CODE_MAX, }; @@ -715,6 +716,10 @@ struct fuse_direntplus { #define FUSE_DIRENTPLUS_SIZE(d) \ FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen) +struct fuse_notify_inval_dircache_entries_out { + uint64_t parent; +}; + struct fuse_notify_inval_inode_out { uint64_t ino; int64_t off;