// SPDX-License-Identifier: GPL-2.0/* FUSE: Filesystem in Userspace Copyright (C) 2001-2008 Miklos Szeredi <miklos@szeredi.hu>*/ #include "dev.h"#include "fuse_i.h" #include <linux/pagemap.h>#include <linux/file.h>#include <linux/fs_context.h>#include <linux/moduleparam.h>#include <linux/sched.h>#include <linux/namei.h>#include <linux/slab.h>#include <linux/xattr.h>#include <linux/iversion.h>#include <linux/posix_acl.h>#include <linux/security.h>#include <linux/types.h>#include <linux/kernel.h> static bool __read_mostly allow_sys_admin_access;module_param(allow_sys_admin_access, bool, 0644);MODULE_PARM_DESC(allow_sys_admin_access, "Allow users with CAP_SYS_ADMIN in initial userns to bypass allow_other access check"); struct dentry_bucket { struct rb_root tree; spinlock_t lock;}; #define FUSE_HASH_BITS 5#define FUSE_HASH_SIZE (1 << FUSE_HASH_BITS)static struct dentry_bucket dentry_hash[FUSE_HASH_SIZE];static struct delayed_work dentry_tree_work; /* Minimum invalidation work queue frequency */#define FUSE_DENTRY_INVAL_FREQ_MIN 5 unsigned __read_mostly inval_wq;static int inval_wq_set(const char *val, const struct kernel_param *kp){ unsigned int num; unsigned int old = inval_wq; int ret; if (!val) return -EINVAL; ret = kstrtouint(val, 0, &num); if (ret) return ret; if ((num < FUSE_DENTRY_INVAL_FREQ_MIN) && (num != 0)) return -EINVAL; /* This should prevent overflow in secs_to_jiffies() */ if (num > USHRT_MAX) return -EINVAL; *((unsigned int *)kp->arg) = num; if (num && !old) schedule_delayed_work(&dentry_tree_work, secs_to_jiffies(num)); else if (!num && old) cancel_delayed_work_sync(&dentry_tree_work); return 0;}static const struct kernel_param_ops inval_wq_ops = { .set = inval_wq_set, .get = param_get_uint,};module_param_cb(inval_wq, &inval_wq_ops, &inval_wq, 0644);__MODULE_PARM_TYPE(inval_wq, "uint");MODULE_PARM_DESC(inval_wq, "Dentries invalidation work queue period in secs (>= " __stringify(FUSE_DENTRY_INVAL_FREQ_MIN) ")."); static inline struct dentry_bucket *get_dentry_bucket(struct dentry *dentry){ int i = hash_ptr(dentry, FUSE_HASH_BITS); return &dentry_hash[i];} static void fuse_advise_use_readdirplus(struct inode *dir){ struct fuse_inode *fi = get_fuse_inode(dir); set_bit(FUSE_I_ADVISE_RDPLUS, &fi->state);} struct fuse_dentry { u64 time; u64 epoch; union { struct rcu_head rcu; struct rb_node node; }; struct dentry *dentry;}; static void __fuse_dentry_tree_del_node(struct fuse_dentry *fd, struct dentry_bucket *bucket){ if (!RB_EMPTY_NODE(&fd->node)) { rb_erase(&fd->node, &bucket->tree); RB_CLEAR_NODE(&fd->node); }} static void fuse_dentry_tree_del_node(struct dentry *dentry){ struct fuse_dentry *fd = dentry->d_fsdata; struct dentry_bucket *bucket = get_dentry_bucket(dentry); spin_lock(&bucket->lock); __fuse_dentry_tree_del_node(fd, bucket); spin_unlock(&bucket->lock);} static void fuse_dentry_tree_add_node(struct dentry *dentry){ struct fuse_dentry *fd = dentry->d_fsdata; struct dentry_bucket *bucket; struct fuse_dentry *cur; struct rb_node **p, *parent = NULL; if (!inval_wq) return; bucket = get_dentry_bucket(dentry); spin_lock(&bucket->lock); __fuse_dentry_tree_del_node(fd, bucket); p = &bucket->tree.rb_node; while (*p) { parent = *p; cur = rb_entry(*p, struct fuse_dentry, node); if (fd->time < cur->time) p = &(*p)->rb_left; else p = &(*p)->rb_right; } rb_link_node(&fd->node, parent, p); rb_insert_color(&fd->node, &bucket->tree); spin_unlock(&bucket->lock);} /* * work queue which, when enabled, will periodically check for expired dentries * in the dentries tree. */static void fuse_dentry_tree_work(struct work_struct *work){ LIST_HEAD(dispose); struct fuse_dentry *fd; struct rb_node *node; int i; for (i = 0; i < FUSE_HASH_SIZE; i++) { spin_lock(&dentry_hash[i].lock); node = rb_first(&dentry_hash[i].tree); while (node) { fd = rb_entry(node, struct fuse_dentry, node); if (!time_before64(fd->time, get_jiffies_64())) break; rb_erase(&fd->node, &dentry_hash[i].tree); RB_CLEAR_NODE(&fd->node); spin_lock(&fd->dentry->d_lock); /* If dentry is still referenced, let next dput release it */ fd->dentry->d_flags |= DCACHE_OP_DELETE; __move_to_shrink_list(fd->dentry, &dispose); spin_unlock(&fd->dentry->d_lock); if (need_resched()) { spin_unlock(&dentry_hash[i].lock); cond_resched(); spin_lock(&dentry_hash[i].lock); } node = rb_first(&dentry_hash[i].tree); } spin_unlock(&dentry_hash[i].lock); } shrink_dentry_list(&dispose); if (inval_wq) schedule_delayed_work(&dentry_tree_work, secs_to_jiffies(inval_wq));} void fuse_epoch_work(struct work_struct *work){ struct fuse_conn *fc = container_of(work, struct fuse_conn, epoch_work); struct fuse_mount *fm; struct inode *inode; down_read(&fc->killsb); inode = fuse_ilookup(fc, FUSE_ROOT_ID, &fm); if (inode) { iput(inode); /* Remove all possible active references to cached inodes */ shrink_dcache_sb(fm->sb); } else pr_warn("Failed to get root inode"); up_read(&fc->killsb);} void fuse_dentry_tree_init(void){ int i; for (i = 0; i < FUSE_HASH_SIZE; i++) { spin_lock_init(&dentry_hash[i].lock); dentry_hash[i].tree = RB_ROOT; } INIT_DELAYED_WORK(&dentry_tree_work, fuse_dentry_tree_work);} void fuse_dentry_tree_cleanup(void){ int i; inval_wq = 0; cancel_delayed_work_sync(&dentry_tree_work); for (i = 0; i < FUSE_HASH_SIZE; i++) WARN_ON_ONCE(!RB_EMPTY_ROOT(&dentry_hash[i].tree));} void fuse_dentry_set_epoch(struct dentry *dentry, u64 epoch){ struct fuse_dentry *fd = dentry->d_fsdata; fd->epoch = epoch;} static inline void __fuse_dentry_settime(struct dentry *dentry, u64 time){ ((struct fuse_dentry *) dentry->d_fsdata)->time = time;} static inline u64 fuse_dentry_time(const struct dentry *entry){ return ((struct fuse_dentry *) entry->d_fsdata)->time;} static void fuse_dentry_settime(struct dentry *dentry, u64 time){ struct fuse_conn *fc = get_fuse_conn_super(dentry->d_sb); bool delete = !time && fc->delete_stale; /* * Mess with DCACHE_OP_DELETE because dput() will be faster without it. * Don't care about races, either way it's just an optimization */ if ((!delete && (dentry->d_flags & DCACHE_OP_DELETE)) || (delete && !(dentry->d_flags & DCACHE_OP_DELETE))) { spin_lock(&dentry->d_lock); if (!delete) dentry->d_flags &= ~DCACHE_OP_DELETE; else dentry->d_flags |= DCACHE_OP_DELETE; spin_unlock(&dentry->d_lock); } __fuse_dentry_settime(dentry, time); fuse_dentry_tree_add_node(dentry);} /* * FUSE caches dentries and attributes with separate timeout. The * time in jiffies until the dentry/attributes are valid is stored in * dentry->d_fsdata and fuse_inode->i_time respectively. */ /* * Calculate the time in jiffies until a dentry/attributes are valid */u64 fuse_time_to_jiffies(u64 sec, u32 nsec){ if (sec || nsec) { struct timespec64 ts = { sec, min_t(u32, nsec, NSEC_PER_SEC - 1) }; return get_jiffies_64() + timespec64_to_jiffies(&ts); } else return 0;} /* * Set dentry and possibly attribute timeouts from the lookup/mk* * replies */void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out *o){ fuse_dentry_settime(entry, fuse_time_to_jiffies(o->entry_valid, o->entry_valid_nsec));} void fuse_invalidate_attr_mask(struct inode *inode, u32 mask){ set_mask_bits(&get_fuse_inode(inode)->inval_mask, 0, mask);} /* * Mark the attributes as stale, so that at the next call to * ->getattr() they will be fetched from userspace */void fuse_invalidate_attr(struct inode *inode){ fuse_invalidate_attr_mask(inode, STATX_BASIC_STATS);} static void fuse_dir_changed(struct inode *dir){ fuse_invalidate_attr_mask(dir, FUSE_STATX_MODDIR); inode_maybe_inc_iversion(dir, false);} /* * Mark the attributes as stale due to an atime change. Avoid the invalidate if * atime is not used. */void fuse_invalidate_atime(struct inode *inode){ if (!IS_RDONLY(inode)) fuse_invalidate_attr_mask(inode, STATX_ATIME);} /* * Just mark the entry as stale, so that a next attempt to look it up * will result in a new lookup call to userspace * * This is called when a dentry is about to become negative and the * timeout is unknown (unlink, rmdir, rename and in some cases * lookup) */void fuse_invalidate_entry_cache(struct dentry *entry){ fuse_dentry_settime(entry, 0);} /* * Same as fuse_invalidate_entry_cache(), but also try to remove the * dentry from the hash */static void fuse_invalidate_entry(struct dentry *entry){ d_invalidate(entry); fuse_invalidate_entry_cache(entry);} static void fuse_lookup_init(struct fuse_args *args, u64 nodeid, const struct qstr *name, struct fuse_entry_out *outarg){ memset(outarg, 0, sizeof(struct fuse_entry_out)); args->opcode = FUSE_LOOKUP; args->nodeid = nodeid; args->in_numargs = 3; fuse_set_zero_arg0(args); args->in_args[1].size = name->len; args->in_args[1].value = name->name; args->in_args[2].size = 1; args->in_args[2].value = ""; args->out_numargs = 1; args->out_args[0].size = sizeof(struct fuse_entry_out); args->out_args[0].value = outarg;} /* * Check whether the dentry is still valid * * If the entry validity timeout has expired and the dentry is * positive, try to redo the lookup. If the lookup results in a * different inode, then let the VFS invalidate the dentry and redo * the lookup once more. If the lookup results in the same inode, * then refresh the attributes, timeouts and mark the dentry valid. */static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name, struct dentry *entry, unsigned int flags){ struct inode *inode; struct fuse_mount *fm; struct fuse_conn *fc; struct fuse_inode *fi; struct fuse_dentry *fd = entry->d_fsdata; int ret; fc = get_fuse_conn_super(dir->i_sb); if (fd->epoch < atomic_read(&fc->epoch)) goto invalid; inode = d_inode_rcu(entry); if (inode && fuse_is_bad(inode)) goto invalid; else if (time_before64(fuse_dentry_time(entry), get_jiffies_64()) || (flags & (LOOKUP_EXCL | LOOKUP_REVAL | LOOKUP_RENAME_TARGET))) { struct fuse_entry_out outarg; FUSE_ARGS(args); struct fuse_forget_link *forget; u64 attr_version; /* For negative dentries, always do a fresh lookup */ if (!inode) goto invalid; ret = -ECHILD; if (flags & LOOKUP_RCU) goto out; fm = get_fuse_mount(inode); forget = fuse_alloc_forget(); ret = -ENOMEM; if (!forget) goto out; attr_version = fuse_get_attr_version(fm->fc); fuse_lookup_init(&args, get_node_id(dir), name, &outarg); ret = fuse_simple_request(fm, &args); /* Zero nodeid is same as -ENOENT */ if (!ret && !outarg.nodeid) ret = -ENOENT; if (!ret) { fi = get_fuse_inode(inode); if (outarg.nodeid != get_node_id(inode) || (bool) IS_AUTOMOUNT(inode) != (bool) (outarg.attr.flags & FUSE_ATTR_SUBMOUNT)) { fuse_chan_queue_forget(fm->fc->chan, forget, outarg.nodeid, 1); goto invalid; } spin_lock(&fi->lock); fi->nlookup++; spin_unlock(&fi->lock); } kfree(forget); if (ret == -ENOMEM || ret == -EINTR) goto out; if (ret || fuse_invalid_attr(&outarg.attr) || fuse_stale_inode(inode, outarg.generation, &outarg.attr)) goto invalid; forget_all_cached_acls(inode); fuse_change_attributes(inode, &outarg.attr, NULL, ATTR_TIMEOUT(&outarg), attr_version); fuse_change_entry_timeout(entry, &outarg); } else if (inode) { fi = get_fuse_inode(inode); if (flags & LOOKUP_RCU) { if (test_bit(FUSE_I_INIT_RDPLUS, &fi->state)) return -ECHILD; } else if (test_and_clear_bit(FUSE_I_INIT_RDPLUS, &fi->state)) { fuse_advise_use_readdirplus(dir); } } ret = 1;out: return ret; invalid: ret = 0; goto out;} static int fuse_dentry_init(struct dentry *dentry){ struct fuse_dentry *fd; fd = kzalloc_obj(struct fuse_dentry, GFP_KERNEL_ACCOUNT | __GFP_RECLAIMABLE); if (!fd) return -ENOMEM; fd->dentry = dentry; RB_CLEAR_NODE(&fd->node); dentry->d_fsdata = fd; /* * Initialising epoch to '0' ensures the dentry is invalid * if compared to fc->epoch, which is initialized to '1'. */ fuse_dentry_set_epoch(dentry, 0); return 0;} static void fuse_dentry_release(struct dentry *dentry)Lines 1–500 of 2508. Load more
// SPDX-License-Identifier: GPL-2.0 /* FUSE: Filesystem in Userspace Copyright (C) 2001-2008 Miklos Szeredi <miklos@szeredi.hu> */ #include "dev.h" #include "fuse_i.h" #include <linux/pagemap.h> #include <linux/file.h> #include <linux/fs_context.h> #include <linux/moduleparam.h> #include <linux/sched.h> #include <linux/namei.h> #include <linux/slab.h> #include <linux/xattr.h> #include <linux/iversion.h> #include <linux/posix_acl.h> #include <linux/security.h> #include <linux/types.h> #include <linux/kernel.h> static bool __read_mostly allow_sys_admin_access; module_param(allow_sys_admin_access, bool, 0644); MODULE_PARM_DESC(allow_sys_admin_access, "Allow users with CAP_SYS_ADMIN in initial userns to bypass allow_other access check"); struct dentry_bucket { struct rb_root tree; spinlock_t lock; }; #define FUSE_HASH_BITS 5 #define FUSE_HASH_SIZE (1 << FUSE_HASH_BITS) static struct dentry_bucket dentry_hash[FUSE_HASH_SIZE]; static struct delayed_work dentry_tree_work; /* Minimum invalidation work queue frequency */ #define FUSE_DENTRY_INVAL_FREQ_MIN 5 unsigned __read_mostly inval_wq; static int inval_wq_set(const char *val, const struct kernel_param *kp) { unsigned int num; unsigned int old = inval_wq; int ret; if (!val) return -EINVAL; ret = kstrtouint(val, 0, &num); if (ret) return ret; if ((num < FUSE_DENTRY_INVAL_FREQ_MIN) && (num != 0)) return -EINVAL; /* This should prevent overflow in secs_to_jiffies() */ if (num > USHRT_MAX) return -EINVAL; *((unsigned int *)kp->arg) = num; if (num && !old) schedule_delayed_work(&dentry_tree_work, secs_to_jiffies(num)); else if (!num && old) cancel_delayed_work_sync(&dentry_tree_work); return 0; } static const struct kernel_param_ops inval_wq_ops = { .set = inval_wq_set, .get = param_get_uint, }; module_param_cb(inval_wq, &inval_wq_ops, &inval_wq, 0644); __MODULE_PARM_TYPE(inval_wq, "uint"); MODULE_PARM_DESC(inval_wq, "Dentries invalidation work queue period in secs (>= " __stringify(FUSE_DENTRY_INVAL_FREQ_MIN) ")."); static inline struct dentry_bucket *get_dentry_bucket(struct dentry *dentry) { int i = hash_ptr(dentry, FUSE_HASH_BITS); return &dentry_hash[i]; } static void fuse_advise_use_readdirplus(struct inode *dir) { struct fuse_inode *fi = get_fuse_inode(dir); set_bit(FUSE_I_ADVISE_RDPLUS, &fi->state); } struct fuse_dentry { u64 time; u64 epoch; union { struct rcu_head rcu; struct rb_node node; }; struct dentry *dentry; }; static void __fuse_dentry_tree_del_node(struct fuse_dentry *fd, struct dentry_bucket *bucket) { if (!RB_EMPTY_NODE(&fd->node)) { rb_erase(&fd->node, &bucket->tree); RB_CLEAR_NODE(&fd->node); } } static void fuse_dentry_tree_del_node(struct dentry *dentry) { struct fuse_dentry *fd = dentry->d_fsdata; struct dentry_bucket *bucket = get_dentry_bucket(dentry); spin_lock(&bucket->lock); __fuse_dentry_tree_del_node(fd, bucket); spin_unlock(&bucket->lock); } static void fuse_dentry_tree_add_node(struct dentry *dentry) { struct fuse_dentry *fd = dentry->d_fsdata; struct dentry_bucket *bucket; struct fuse_dentry *cur; struct rb_node **p, *parent = NULL; if (!inval_wq) return; bucket = get_dentry_bucket(dentry); spin_lock(&bucket->lock); __fuse_dentry_tree_del_node(fd, bucket); p = &bucket->tree.rb_node; while (*p) { parent = *p; cur = rb_entry(*p, struct fuse_dentry, node); if (fd->time < cur->time) p = &(*p)->rb_left; else p = &(*p)->rb_right; } rb_link_node(&fd->node, parent, p); rb_insert_color(&fd->node, &bucket->tree); spin_unlock(&bucket->lock); } /* * work queue which, when enabled, will periodically check for expired dentries * in the dentries tree. */ static void fuse_dentry_tree_work(struct work_struct *work) { LIST_HEAD(dispose); struct fuse_dentry *fd; struct rb_node *node; int i; for (i = 0; i < FUSE_HASH_SIZE; i++) { spin_lock(&dentry_hash[i].lock); node = rb_first(&dentry_hash[i].tree); while (node) { fd = rb_entry(node, struct fuse_dentry, node); if (!time_before64(fd->time, get_jiffies_64())) break; rb_erase(&fd->node, &dentry_hash[i].tree); RB_CLEAR_NODE(&fd->node); spin_lock(&fd->dentry->d_lock); /* If dentry is still referenced, let next dput release it */ fd->dentry->d_flags |= DCACHE_OP_DELETE; __move_to_shrink_list(fd->dentry, &dispose); spin_unlock(&fd->dentry->d_lock); if (need_resched()) { spin_unlock(&dentry_hash[i].lock); cond_resched(); spin_lock(&dentry_hash[i].lock); } node = rb_first(&dentry_hash[i].tree); } spin_unlock(&dentry_hash[i].lock); } shrink_dentry_list(&dispose); if (inval_wq) schedule_delayed_work(&dentry_tree_work, secs_to_jiffies(inval_wq)); } void fuse_epoch_work(struct work_struct *work) { struct fuse_conn *fc = container_of(work, struct fuse_conn, epoch_work); struct fuse_mount *fm; struct inode *inode; down_read(&fc->killsb); inode = fuse_ilookup(fc, FUSE_ROOT_ID, &fm); if (inode) { iput(inode); /* Remove all possible active references to cached inodes */ shrink_dcache_sb(fm->sb); } else pr_warn("Failed to get root inode"); up_read(&fc->killsb); } void fuse_dentry_tree_init(void) { int i; for (i = 0; i < FUSE_HASH_SIZE; i++) { spin_lock_init(&dentry_hash[i].lock); dentry_hash[i].tree = RB_ROOT; } INIT_DELAYED_WORK(&dentry_tree_work, fuse_dentry_tree_work); } void fuse_dentry_tree_cleanup(void) { int i; inval_wq = 0; cancel_delayed_work_sync(&dentry_tree_work); for (i = 0; i < FUSE_HASH_SIZE; i++) WARN_ON_ONCE(!RB_EMPTY_ROOT(&dentry_hash[i].tree)); } void fuse_dentry_set_epoch(struct dentry *dentry, u64 epoch) { struct fuse_dentry *fd = dentry->d_fsdata; fd->epoch = epoch; } static inline void __fuse_dentry_settime(struct dentry *dentry, u64 time) { ((struct fuse_dentry *) dentry->d_fsdata)->time = time; } static inline u64 fuse_dentry_time(const struct dentry *entry) { return ((struct fuse_dentry *) entry->d_fsdata)->time; } static void fuse_dentry_settime(struct dentry *dentry, u64 time) { struct fuse_conn *fc = get_fuse_conn_super(dentry->d_sb); bool delete = !time && fc->delete_stale; /* * Mess with DCACHE_OP_DELETE because dput() will be faster without it. * Don't care about races, either way it's just an optimization */ if ((!delete && (dentry->d_flags & DCACHE_OP_DELETE)) || (delete && !(dentry->d_flags & DCACHE_OP_DELETE))) { spin_lock(&dentry->d_lock); if (!delete) dentry->d_flags &= ~DCACHE_OP_DELETE; else dentry->d_flags |= DCACHE_OP_DELETE; spin_unlock(&dentry->d_lock); } __fuse_dentry_settime(dentry, time); fuse_dentry_tree_add_node(dentry); } /* * FUSE caches dentries and attributes with separate timeout. The * time in jiffies until the dentry/attributes are valid is stored in * dentry->d_fsdata and fuse_inode->i_time respectively. */ /* * Calculate the time in jiffies until a dentry/attributes are valid */ u64 fuse_time_to_jiffies(u64 sec, u32 nsec) { if (sec || nsec) { struct timespec64 ts = { sec, min_t(u32, nsec, NSEC_PER_SEC - 1) }; return get_jiffies_64() + timespec64_to_jiffies(&ts); } else return 0; } /* * Set dentry and possibly attribute timeouts from the lookup/mk* * replies */ void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out *o) { fuse_dentry_settime(entry, fuse_time_to_jiffies(o->entry_valid, o->entry_valid_nsec)); } void fuse_invalidate_attr_mask(struct inode *inode, u32 mask) { set_mask_bits(&get_fuse_inode(inode)->inval_mask, 0, mask); } /* * Mark the attributes as stale, so that at the next call to * ->getattr() they will be fetched from userspace */ void fuse_invalidate_attr(struct inode *inode) { fuse_invalidate_attr_mask(inode, STATX_BASIC_STATS); } static void fuse_dir_changed(struct inode *dir) { fuse_invalidate_attr_mask(dir, FUSE_STATX_MODDIR); inode_maybe_inc_iversion(dir, false); } /* * Mark the attributes as stale due to an atime change. Avoid the invalidate if * atime is not used. */ void fuse_invalidate_atime(struct inode *inode) { if (!IS_RDONLY(inode)) fuse_invalidate_attr_mask(inode, STATX_ATIME); } /* * Just mark the entry as stale, so that a next attempt to look it up * will result in a new lookup call to userspace * * This is called when a dentry is about to become negative and the * timeout is unknown (unlink, rmdir, rename and in some cases * lookup) */ void fuse_invalidate_entry_cache(struct dentry *entry) { fuse_dentry_settime(entry, 0); } /* * Same as fuse_invalidate_entry_cache(), but also try to remove the * dentry from the hash */ static void fuse_invalidate_entry(struct dentry *entry) { d_invalidate(entry); fuse_invalidate_entry_cache(entry); } static void fuse_lookup_init(struct fuse_args *args, u64 nodeid, const struct qstr *name, struct fuse_entry_out *outarg) { memset(outarg, 0, sizeof(struct fuse_entry_out)); args->opcode = FUSE_LOOKUP; args->nodeid = nodeid; args->in_numargs = 3; fuse_set_zero_arg0(args); args->in_args[1].size = name->len; args->in_args[1].value = name->name; args->in_args[2].size = 1; args->in_args[2].value = ""; args->out_numargs = 1; args->out_args[0].size = sizeof(struct fuse_entry_out); args->out_args[0].value = outarg; } /* * Check whether the dentry is still valid * * If the entry validity timeout has expired and the dentry is * positive, try to redo the lookup. If the lookup results in a * different inode, then let the VFS invalidate the dentry and redo * the lookup once more. If the lookup results in the same inode, * then refresh the attributes, timeouts and mark the dentry valid. */ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name, struct dentry *entry, unsigned int flags) { struct inode *inode; struct fuse_mount *fm; struct fuse_conn *fc; struct fuse_inode *fi; struct fuse_dentry *fd = entry->d_fsdata; int ret; fc = get_fuse_conn_super(dir->i_sb); if (fd->epoch < atomic_read(&fc->epoch)) goto invalid; inode = d_inode_rcu(entry); if (inode && fuse_is_bad(inode)) goto invalid; else if (time_before64(fuse_dentry_time(entry), get_jiffies_64()) || (flags & (LOOKUP_EXCL | LOOKUP_REVAL | LOOKUP_RENAME_TARGET))) { struct fuse_entry_out outarg; FUSE_ARGS(args); struct fuse_forget_link *forget; u64 attr_version; /* For negative dentries, always do a fresh lookup */ if (!inode) goto invalid; ret = -ECHILD; if (flags & LOOKUP_RCU) goto out; fm = get_fuse_mount(inode); forget = fuse_alloc_forget(); ret = -ENOMEM; if (!forget) goto out; attr_version = fuse_get_attr_version(fm->fc); fuse_lookup_init(&args, get_node_id(dir), name, &outarg); ret = fuse_simple_request(fm, &args); /* Zero nodeid is same as -ENOENT */ if (!ret && !outarg.nodeid) ret = -ENOENT; if (!ret) { fi = get_fuse_inode(inode); if (outarg.nodeid != get_node_id(inode) || (bool) IS_AUTOMOUNT(inode) != (bool) (outarg.attr.flags & FUSE_ATTR_SUBMOUNT)) { fuse_chan_queue_forget(fm->fc->chan, forget, outarg.nodeid, 1); goto invalid; } spin_lock(&fi->lock); fi->nlookup++; spin_unlock(&fi->lock); } kfree(forget); if (ret == -ENOMEM || ret == -EINTR) goto out; if (ret || fuse_invalid_attr(&outarg.attr) || fuse_stale_inode(inode, outarg.generation, &outarg.attr)) goto invalid; forget_all_cached_acls(inode); fuse_change_attributes(inode, &outarg.attr, NULL, ATTR_TIMEOUT(&outarg), attr_version); fuse_change_entry_timeout(entry, &outarg); } else if (inode) { fi = get_fuse_inode(inode); if (flags & LOOKUP_RCU) { if (test_bit(FUSE_I_INIT_RDPLUS, &fi->state)) return -ECHILD; } else if (test_and_clear_bit(FUSE_I_INIT_RDPLUS, &fi->state)) { fuse_advise_use_readdirplus(dir); } } ret = 1; out: return ret; invalid: ret = 0; goto out; } static int fuse_dentry_init(struct dentry *dentry) { struct fuse_dentry *fd; fd = kzalloc_obj(struct fuse_dentry, GFP_KERNEL_ACCOUNT | __GFP_RECLAIMABLE); if (!fd) return -ENOMEM; fd->dentry = dentry; RB_CLEAR_NODE(&fd->node); dentry->d_fsdata = fd; /* * Initialising epoch to '0' ensures the dentry is invalid * if compared to fc->epoch, which is initialized to '1'. */ fuse_dentry_set_epoch(dentry, 0); return 0; } static void fuse_dentry_release(struct dentry *dentry)