@@ -196,6 +196,7 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
* 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 dentry *entry, unsigned int flags)
{
struct inode *inode;
@@ -224,6 +225,17 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
fm = get_fuse_mount(inode);
+ /* If atomic open is supported by FUSE then use this opportunity
+ * (only for non-dir) to avoid this lookup and combine
+ * lookup + open into single call.
+ */
+ if (!fm->fc->default_permissions && fm->fc->do_atomic_open &&
+ !(flags & (LOOKUP_EXCL | LOOKUP_REVAL)) &&
+ (flags & LOOKUP_OPEN) && !S_ISDIR(inode->i_mode)) {
+ ret = 1;
+ goto out;
+ }
+
forget = fuse_alloc_forget();
ret = -ENOMEM;
if (!forget)
@@ -515,13 +527,50 @@ static int get_security_context(struct dentry *entry, umode_t mode,
return err;
}
+/*
+ * Revalidate the inode after we got fresh attributes from user space.
+ */
+static int fuse_atomic_open_revalidate_inode(struct inode *reval_inode,
+ struct dentry *entry,
+ struct fuse_inode *fi,
+ struct fuse_forget_link *forget,
+ struct fuse_entry_out *outentry,
+ u64 attr_version)
+{
+ struct fuse_conn *fc = get_fuse_conn(reval_inode);
+ /* Mode should be other than directory */
+ BUG_ON(S_ISDIR(reval_inode->i_mode));
+
+ if (outentry->nodeid != get_node_id(reval_inode)) {
+ fuse_queue_forget(fc, forget, outentry->nodeid, 1);
+ return -ESTALE;
+ }
+ if (fuse_stale_inode(reval_inode, outentry->generation,
+ &outentry->attr)) {
+ fuse_make_bad(reval_inode);
+ return -ESTALE;
+ }
+ fi = get_fuse_inode(reval_inode);
+ spin_lock(&fi->lock);
+ fi->nlookup++;
+ spin_unlock(&fi->lock);
+
+ forget_all_cached_acls(reval_inode);
+ fuse_change_attributes(reval_inode, &outentry->attr,
+ entry_attr_timeout(outentry), attr_version);
+ fuse_change_entry_timeout(entry, outentry);
+ return 0;
+}
+
+
+
/*
* Perform create + open or lookup + open in single call to libfuse
*/
-static int fuse_atomic_open_common(struct inode *dir, struct dentry *entry,
- struct dentry **alias, struct file *file,
- unsigned int flags, umode_t mode,
- uint32_t opcode)
+int fuse_atomic_open_common(struct inode *dir, struct dentry *entry,
+ struct dentry **alias, struct file *file,
+ struct inode *reval_inode, unsigned int flags,
+ umode_t mode, uint32_t opcode)
{
bool create = (opcode == FUSE_CREATE ? true : false);
struct inode *inode;
@@ -536,6 +585,7 @@ static int fuse_atomic_open_common(struct inode *dir, struct dentry *entry,
struct dentry *res = NULL;
void *security_ctx = NULL;
u32 security_ctxlen;
+ u64 attr_version = fuse_get_attr_version(fm->fc);
int err;
if (alias)
@@ -616,6 +666,20 @@ static int fuse_atomic_open_common(struct inode *dir, struct dentry *entry,
ff->fh = outopen.fh;
ff->nodeid = outentry.nodeid;
ff->open_flags = outopen.open_flags;
+
+ /* Inode revalidation was bypassed previously for type other than
+ * directories, revalidate now as we got fresh attributes.
+ */
+ if (reval_inode) {
+ err = fuse_atomic_open_revalidate_inode(reval_inode, entry, fi,
+ forget, &outentry,
+ attr_version);
+ if (err)
+ goto out_free_ff;
+ inode = reval_inode;
+ kfree(forget);
+ goto out_finish_open;
+ }
inode = fuse_iget(dir->i_sb, outentry.nodeid, outentry.generation,
&outentry.attr, entry_attr_timeout(&outentry), 0);
if (!inode) {
@@ -652,6 +716,7 @@ static int fuse_atomic_open_common(struct inode *dir, struct dentry *entry,
if (create)
fuse_dir_changed(dir);
err = finish_open(file, entry, generic_file_open);
+out_finish_open:
if (err) {
fi = get_fuse_inode(inode);
fuse_sync_release(fi, ff, flags);
@@ -682,7 +747,7 @@ static int fuse_do_atomic_open(struct inode *dir, struct dentry *entry,
if (!fc->do_atomic_open)
return -ENOSYS;
- err = fuse_atomic_open_common(dir, entry, alias, file,
+ err = fuse_atomic_open_common(dir, entry, alias, file, NULL,
flags, mode, FUSE_ATOMIC_OPEN);
return err;
}
@@ -703,7 +768,8 @@ static int fuse_atomic_open(struct inode *dir, struct dentry *entry,
/* Atomic lookup + open - dentry might be File or Directory */
if (!create) {
- err = fuse_do_atomic_open(dir, entry, &alias, file, flags, mode);
+ err = fuse_do_atomic_open(dir, entry, &alias, file,
+ flags, mode);
res = alias;
if (!err)
goto out_dput;
@@ -737,8 +803,8 @@ static int fuse_atomic_open(struct inode *dir, struct dentry *entry,
* If the filesystem doesn't support atomic create + open, then fall
* back to separate 'mknod' + 'open' requests.
*/
- err = fuse_atomic_open_common(dir, entry, NULL, file, flags, mode,
- FUSE_CREATE);
+ err = fuse_atomic_open_common(dir, entry, NULL, file, NULL, flags,
+ mode, FUSE_CREATE);
if (err == -ENOSYS) {
fc->no_create = 1;
goto mknod;
@@ -125,11 +125,15 @@ static void fuse_file_put(struct fuse_file *ff, bool sync, bool isdir)
}
struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
- unsigned int open_flags, bool isdir)
+ struct file *file, unsigned int open_flags,
+ bool isdir)
{
struct fuse_conn *fc = fm->fc;
struct fuse_file *ff;
+ struct dentry *dentry = NULL;
+ struct dentry *parent = NULL;
int opcode = isdir ? FUSE_OPENDIR : FUSE_OPEN;
+ int ret;
ff = fuse_file_alloc(fm);
if (!ff)
@@ -138,6 +142,11 @@ struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
ff->fh = 0;
/* Default for no-open */
ff->open_flags = FOPEN_KEEP_CACHE | (isdir ? FOPEN_CACHE_DIR : 0);
+
+ /* For directories we already had lookup */
+ if (!isdir && fc->do_atomic_open && file != NULL)
+ goto revalidate_atomic_open;
+
if (isdir ? !fc->no_opendir : !fc->no_open) {
struct fuse_open_out outarg;
int err;
@@ -164,12 +173,27 @@ struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
ff->nodeid = nodeid;
return ff;
+
+revalidate_atomic_open:
+ dentry = file->f_path.dentry;
+ /* Get ref on parent */
+ parent = dget_parent(dentry);
+ ret = fuse_atomic_open_common(d_inode_rcu(parent), dentry, NULL, file,
+ d_inode_rcu(dentry), open_flags, 0,
+ FUSE_ATOMIC_OPEN);
+ dput(parent);
+ if (ret)
+ goto err_out;
+ ff = file->private_data;
+ return ff;
+err_out:
+ return ERR_PTR(ret);
}
int fuse_do_open(struct fuse_mount *fm, u64 nodeid, struct file *file,
bool isdir)
{
- struct fuse_file *ff = fuse_file_open(fm, nodeid, file->f_flags, isdir);
+ struct fuse_file *ff = fuse_file_open(fm, nodeid, file, file->f_flags, isdir);
if (!IS_ERR(ff))
file->private_data = ff;
@@ -252,7 +276,7 @@ int fuse_open_common(struct inode *inode, struct file *file, bool isdir)
}
err = fuse_do_open(fm, get_node_id(inode), file, isdir);
- if (!err)
+ if (!err && (!fc->do_atomic_open || isdir))
fuse_finish_open(inode, file);
out:
@@ -1011,6 +1011,13 @@ void fuse_finish_open(struct inode *inode, struct file *file);
void fuse_sync_release(struct fuse_inode *fi, struct fuse_file *ff,
unsigned int flags);
+/**
+ * Send atomic create + open or lookup + open
+ */
+int fuse_atomic_open_common(struct inode *dir, struct dentry *entry,
+ struct dentry **alias, struct file *file,
+ struct inode *reval_inode, unsigned int flags,
+ umode_t mode, uint32_t opcode);
/**
* Send RELEASE or RELEASEDIR request
*/
@@ -1314,7 +1321,8 @@ int fuse_fileattr_set(struct user_namespace *mnt_userns,
/* file.c */
struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
- unsigned int open_flags, bool isdir);
+ struct file *file, unsigned int open_flags,
+ bool isdir);
void fuse_file_release(struct inode *inode, struct fuse_file *ff,
unsigned int open_flags, fl_owner_t id, bool isdir);
@@ -408,7 +408,7 @@ static struct fuse_file *fuse_priv_ioctl_prepare(struct inode *inode)
if (!S_ISREG(inode->i_mode) && !isdir)
return ERR_PTR(-ENOTTY);
- return fuse_file_open(fm, get_node_id(inode), O_RDONLY, isdir);
+ return fuse_file_open(fm, get_node_id(inode), NULL, O_RDONLY, isdir);
}
static void fuse_priv_ioctl_cleanup(struct inode *inode, struct fuse_file *ff)