[RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation

Luis Henriques posted 6 patches 1 month, 4 weeks ago
[RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Luis Henriques 1 month, 4 weeks ago
The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
an extra inarg: the file handle for the parent directory (if it is
available).  Also, because fuse_entry_out now has a extra variable size
struct (the actual handle), it also sets the out_argvar flag to true.

Most of the other modifications in this patch are a fallout from these
changes: because fuse_entry_out has been modified to include a variable size
struct, every operation that receives such a parameter have to take this
into account:

  CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE

Signed-off-by: Luis Henriques <luis@igalia.com>
---
 fs/fuse/dev.c             | 16 +++++++
 fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
 fs/fuse/fuse_i.h          | 34 +++++++++++++--
 fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
 fs/fuse/readdir.c         | 10 ++---
 include/uapi/linux/fuse.h |  8 ++++
 6 files changed, 189 insertions(+), 35 deletions(-)

diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 629e8a043079..fc6acf45ae27 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -606,6 +606,22 @@ static void fuse_adjust_compat(struct fuse_conn *fc, struct fuse_args *args)
 	if (fc->minor < 4 && args->opcode == FUSE_STATFS)
 		args->out_args[0].size = FUSE_COMPAT_STATFS_SIZE;
 
+	if (fc->minor < 45) {
+		switch (args->opcode) {
+		case FUSE_CREATE:
+		case FUSE_LINK:
+		case FUSE_LOOKUP:
+		case FUSE_MKDIR:
+		case FUSE_MKNOD:
+		/* XXX case FUSE_READDIRPLUS: */
+		case FUSE_SYMLINK:
+		case FUSE_TMPFILE:
+			if (!WARN_ON_ONCE(args->in_numargs == 0))
+				args->in_numargs--;
+			args->out_args[0].size = FUSE_COMPAT_45_ENTRY_OUT_SIZE;
+			break;
+		}
+	}
 	if (fc->minor < 9) {
 		switch (args->opcode) {
 		case FUSE_LOOKUP:
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index e3fd5d148741..a6edb444180f 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -169,7 +169,8 @@ static void fuse_invalidate_entry(struct dentry *entry)
 }
 
 static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
-			     u64 nodeid, const struct qstr *name,
+			     u64 nodeid, struct inode *dir,
+			     const struct qstr *name,
 			     struct fuse_entry_out *outarg)
 {
 	args->opcode = FUSE_LOOKUP;
@@ -181,8 +182,24 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
 	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].size = sizeof(*outarg) + outarg->fh.size;
 	args->out_args[0].value = outarg;
+
+	if (fc->lookup_handle) {
+		struct fuse_inode *fi = NULL;
+
+		args->opcode = FUSE_LOOKUP_HANDLE;
+		args->out_argvar = true;
+
+		if (dir)
+			fi = get_fuse_inode(dir);
+
+		if (fi && fi->fh) {
+			args->in_numargs = 4;
+			args->in_args[3].size = sizeof(*fi->fh) + fi->fh->size;
+			args->in_args[3].value = fi->fh;
+		}
+	}
 }
 
 /*
@@ -240,7 +257,7 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
 
 		attr_version = fuse_get_attr_version(fm->fc);
 
-		fuse_lookup_init(fm->fc, &args, get_node_id(dir),
+		fuse_lookup_init(fm->fc, &args, get_node_id(dir), dir,
 				 name, outarg);
 		ret = fuse_simple_request(fm, &args);
 		/* Zero nodeid is same as -ENOENT */
@@ -248,7 +265,8 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
 			ret = -ENOENT;
 		if (!ret) {
 			fi = get_fuse_inode(inode);
-			if (outarg->nodeid != get_node_id(inode) ||
+			if (!fuse_file_handle_is_equal(fm->fc, fi->fh, &outarg->fh) ||
+			    outarg->nodeid != get_node_id(inode) ||
 			    (bool) IS_AUTOMOUNT(inode) != (bool) (outarg->attr.flags & FUSE_ATTR_SUBMOUNT)) {
 				fuse_queue_forget(fm->fc, forget,
 						  outarg->nodeid, 1);
@@ -365,8 +383,9 @@ bool fuse_invalid_attr(struct fuse_attr *attr)
 	return !fuse_valid_type(attr->mode) || !fuse_valid_size(attr->size);
 }
 
-int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name,
-		     struct fuse_entry_out *outarg, struct inode **inode)
+int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct inode *dir,
+		     const struct qstr *name, struct fuse_entry_out *outarg,
+		     struct inode **inode)
 {
 	struct fuse_mount *fm = get_fuse_mount_super(sb);
 	FUSE_ARGS(args);
@@ -388,14 +407,15 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
 	attr_version = fuse_get_attr_version(fm->fc);
 	evict_ctr = fuse_get_evict_ctr(fm->fc);
 
-	fuse_lookup_init(fm->fc, &args, nodeid, name, outarg);
+	fuse_lookup_init(fm->fc, &args, nodeid, dir, name, outarg);
 	err = fuse_simple_request(fm, &args);
 	/* Zero nodeid is same as -ENOENT, but with valid timeout */
-	if (err || !outarg->nodeid)
+	if (err < 0 || !outarg->nodeid) // XXX err = size if args->out_argvar = true
 		goto out_put_forget;
 
 	err = -EIO;
-	if (fuse_invalid_attr(&outarg->attr))
+	if (fuse_invalid_attr(&outarg->attr) ||
+	    fuse_invalid_file_handle(fm->fc, &outarg->fh))
 		goto out_put_forget;
 	if (outarg->nodeid == FUSE_ROOT_ID && outarg->generation != 0) {
 		pr_warn_once("root generation should be zero\n");
@@ -404,7 +424,8 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
 
 	*inode = fuse_iget(sb, outarg->nodeid, outarg->generation,
 			   &outarg->attr, ATTR_TIMEOUT(outarg),
-			   attr_version, evict_ctr);
+			   attr_version, evict_ctr,
+			   &outarg->fh);
 	err = -ENOMEM;
 	if (!*inode) {
 		fuse_queue_forget(fm->fc, forget, outarg->nodeid, 1);
@@ -440,14 +461,14 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
 		return ERR_PTR(-ENOMEM);
 
 	locked = fuse_lock_inode(dir);
-	err = fuse_lookup_name(dir->i_sb, get_node_id(dir), &entry->d_name,
+	err = fuse_lookup_name(dir->i_sb, get_node_id(dir), dir, &entry->d_name,
 			       outarg, &inode);
 	fuse_unlock_inode(dir, locked);
 	if (err == -ENOENT) {
 		outarg_valid = false;
 		err = 0;
 	}
-	if (err)
+	if (err < 0) // XXX err = size if args->out_argvar = true
 		goto out_err;
 
 	err = -EIO;
@@ -689,24 +710,36 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
 	args.in_args[1].size = entry->d_name.len + 1;
 	args.in_args[1].value = entry->d_name.name;
 	args.out_numargs = 2;
-	args.out_args[0].size = sizeof(*outentry);
+	args.out_args[0].size = sizeof(*outentry) + outentry->fh.size;
 	args.out_args[0].value = outentry;
 	/* Store outarg for fuse_finish_open() */
 	outopenp = &ff->args->open_outarg;
 	args.out_args[1].size = sizeof(*outopenp);
 	args.out_args[1].value = outopenp;
 
+	if (fm->fc->lookup_handle) {
+		fi = get_fuse_inode(dir);
+		args.out_argvar = true;
+		args.out_argvar_idx = 0;
+		if (fi->fh) {
+			args.in_numargs = 3;
+			args.in_args[2].size = sizeof(*fi->fh) + fi->fh->size;
+			args.in_args[2].value = fi->fh;
+		}
+	}
+
 	err = get_create_ext(idmap, &args, dir, entry, mode);
 	if (err)
 		goto out_free_outentry;
 
 	err = fuse_simple_idmap_request(idmap, fm, &args);
 	free_ext_value(&args);
-	if (err)
+	if (err < 0) // XXX err = size if args->out_argvar = true
 		goto out_free_outentry;
 
 	err = -EIO;
 	if (!S_ISREG(outentry->attr.mode) || invalid_nodeid(outentry->nodeid) ||
+	    fuse_invalid_file_handle(fm->fc, &outentry->fh) ||
 	    fuse_invalid_attr(&outentry->attr))
 		goto out_free_outentry;
 
@@ -714,7 +747,8 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
 	ff->nodeid = outentry->nodeid;
 	ff->open_flags = outopenp->open_flags;
 	inode = fuse_iget(dir->i_sb, outentry->nodeid, outentry->generation,
-			  &outentry->attr, ATTR_TIMEOUT(outentry), 0, 0);
+			  &outentry->attr, ATTR_TIMEOUT(outentry), 0, 0,
+			  &outentry->fh);
 	if (!inode) {
 		flags &= ~(O_CREAT | O_EXCL | O_TRUNC);
 		fuse_sync_release(NULL, ff, flags);
@@ -830,9 +864,22 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun
 
 	args->nodeid = get_node_id(dir);
 	args->out_numargs = 1;
-	args->out_args[0].size = sizeof(*outarg);
+	args->out_args[0].size = sizeof(*outarg) + outarg->fh.size;
 	args->out_args[0].value = outarg;
 
+	if (fm->fc->lookup_handle) {
+		struct fuse_inode *fi = get_fuse_inode(dir);
+		int idx = args->in_numargs;
+
+		args->out_argvar = true;
+		args->out_argvar_idx = 0;
+		if (fi->fh && !WARN_ON_ONCE(idx >= 4)) {		
+			args->in_args[idx].size = sizeof(*fi->fh) + fi->fh->size;
+			args->in_args[idx].value = fi->fh;
+			args->in_numargs++;
+		}
+	}
+
 	if (args->opcode != FUSE_LINK) {
 		err = get_create_ext(idmap, args, dir, entry, mode);
 		if (err)
@@ -841,18 +888,20 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun
 
 	err = fuse_simple_idmap_request(idmap, fm, args);
 	free_ext_value(args);
-	if (err)
+	if (err < 0) // XXX err = size if args->out_argvar = true
 		goto out_free_outarg;
 
 	err = -EIO;
-	if (invalid_nodeid(outarg->nodeid) || fuse_invalid_attr(&outarg->attr))
+	if (invalid_nodeid(outarg->nodeid) || fuse_invalid_attr(&outarg->attr) ||
+	    fuse_invalid_file_handle(fm->fc, &outarg->fh))
 		goto out_free_outarg;
 
 	if ((outarg->attr.mode ^ mode) & S_IFMT)
 		goto out_free_outarg;
 
 	inode = fuse_iget(dir->i_sb, outarg->nodeid, outarg->generation,
-			  &outarg->attr, ATTR_TIMEOUT(outarg), 0, 0);
+			  &outarg->attr, ATTR_TIMEOUT(outarg), 0, 0,
+			  &outarg->fh);
 	if (!inode) {
 		fuse_queue_forget(fm->fc, forget, outarg->nodeid, 1);
 		kfree(outarg);
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index fad05fae7e54..d0f3c81b5612 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -216,6 +216,8 @@ struct fuse_inode {
 	 * so preserve the blocksize specified by the server.
 	 */
 	u8 cached_i_blkbits;
+
+	struct fuse_file_handle *fh;
 };
 
 /** FUSE inode state bits */
@@ -1067,6 +1069,26 @@ static inline int invalid_nodeid(u64 nodeid)
 	return !nodeid || nodeid == FUSE_ROOT_ID;
 }
 
+static inline bool fuse_invalid_file_handle(struct fuse_conn *fc,
+					    struct fuse_file_handle *handle)
+{
+	if (!fc->lookup_handle)
+		return false;
+
+	return !handle->size || (handle->size >= FUSE_MAX_HANDLE_SZ);
+}
+
+static inline bool fuse_file_handle_is_equal(struct fuse_conn *fc,
+					     struct fuse_file_handle *fh1,
+					     struct fuse_file_handle *fh2)
+{
+	if (!fc->lookup_handle || !fh2->size || // XXX more OPs without handle
+	    ((fh1->size == fh2->size) &&
+	     (!memcmp(fh1->handle, fh2->handle, fh1->size))))
+		return true;
+	return false;
+}
+
 static inline u64 fuse_get_attr_version(struct fuse_conn *fc)
 {
 	return atomic64_read(&fc->attr_version);
@@ -1098,7 +1120,10 @@ static inline struct fuse_entry_out *fuse_entry_out_alloc(struct fuse_conn *fc)
 {
 	struct fuse_entry_out *entryout;
 
-	entryout = kzalloc(sizeof(*entryout), GFP_KERNEL_ACCOUNT);
+	entryout = kzalloc(sizeof(*entryout) + fc->max_handle_sz,
+			   GFP_KERNEL_ACCOUNT);
+	if (entryout)
+		entryout->fh.size = fc->max_handle_sz;
 
 	return entryout;
 }
@@ -1145,10 +1170,11 @@ extern const struct dentry_operations fuse_dentry_operations;
 struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
 			int generation, struct fuse_attr *attr,
 			u64 attr_valid, u64 attr_version,
-			u64 evict_ctr);
+			u64 evict_ctr, struct fuse_file_handle *fh);
 
-int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name,
-		     struct fuse_entry_out *outarg, struct inode **inode);
+int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct inode *dir,
+		     const struct qstr *name, struct fuse_entry_out *outarg,
+		     struct inode **inode);
 
 /**
  * Send FORGET command
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index bc84e7ed1e3d..f565f7e8118d 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -95,6 +95,25 @@ static struct fuse_submount_lookup *fuse_alloc_submount_lookup(void)
 	return NULL;
 }
 
+/*
+ * XXX postpone this allocation and later use the real size instead of max
+ */
+static bool fuse_inode_handle_alloc(struct super_block *sb,
+				    struct fuse_inode *fi)
+{
+	struct fuse_conn *fc = get_fuse_conn_super(sb);
+
+	fi->fh = NULL;
+	if (fc->lookup_handle) {
+		fi->fh = kzalloc(sizeof(*fi->fh) + fc->max_handle_sz,
+				 GFP_KERNEL_ACCOUNT);
+		if (!fi->fh)
+			return false;
+	}
+
+	return true;
+}
+
 static struct inode *fuse_alloc_inode(struct super_block *sb)
 {
 	struct fuse_inode *fi;
@@ -120,8 +139,15 @@ static struct inode *fuse_alloc_inode(struct super_block *sb)
 	if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
 		fuse_inode_backing_set(fi, NULL);
 
+	if (!fuse_inode_handle_alloc(sb, fi))
+		goto out_free_dax;
+
 	return &fi->inode;
 
+out_free_dax:
+#ifdef CONFIG_FUSE_DAX
+	kfree(fi->dax);
+#endif
 out_free_forget:
 	kfree(fi->forget);
 out_free:
@@ -132,6 +158,7 @@ static struct inode *fuse_alloc_inode(struct super_block *sb)
 static void fuse_free_inode(struct inode *inode)
 {
 	struct fuse_inode *fi = get_fuse_inode(inode);
+	struct fuse_conn *fc = get_fuse_conn(inode);
 
 	mutex_destroy(&fi->mutex);
 	kfree(fi->forget);
@@ -141,6 +168,9 @@ static void fuse_free_inode(struct inode *inode)
 	if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
 		fuse_backing_put(fuse_inode_backing(fi));
 
+	if (fc->lookup_handle)
+		kfree(fi->fh);
+
 	kmem_cache_free(fuse_inode_cachep, fi);
 }
 
@@ -465,7 +495,7 @@ static int fuse_inode_set(struct inode *inode, void *_nodeidp)
 struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
 			int generation, struct fuse_attr *attr,
 			u64 attr_valid, u64 attr_version,
-			u64 evict_ctr)
+			u64 evict_ctr, struct fuse_file_handle *fh)
 {
 	struct inode *inode;
 	struct fuse_inode *fi;
@@ -505,6 +535,30 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
 	if (!inode)
 		return NULL;
 
+	fi = get_fuse_inode(inode);
+	if (fc->lookup_handle) {
+		if ((fh == NULL) && (nodeid != FUSE_ROOT_ID)) {
+			pr_err("NULL file handle for nodeid %llu\n", nodeid);
+			iput(inode);
+			return NULL;
+		}
+		if (fi->fh->size)
+			pr_warn_ratelimited(
+				"Handle already set for nodeid %llu (size: %u)\n",
+				nodeid, fi->fh->size);
+		if (fh) {
+			if (fh->size >= fc->max_handle_sz) {
+				pr_err("File handle too big (%u)\n", fh->size);
+				iput(inode);
+				return NULL;
+			}
+			fi->fh->size = fh->size;
+			memcpy(fi->fh->handle, fh->handle, fi->fh->size);
+		} else {
+			fi->fh->size = 0;
+			memset(fi->fh, 0, fc->max_handle_sz);
+		}
+	}
 	if ((inode->i_state & I_NEW)) {
 		inode->i_flags |= S_NOATIME;
 		if (!fc->writeback_cache || !S_ISREG(attr->mode))
@@ -512,7 +566,8 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
 		inode->i_generation = generation;
 		fuse_init_inode(inode, attr, fc);
 		unlock_new_inode(inode);
-	} else if (fuse_stale_inode(inode, generation, attr)) {
+	} else if (fuse_stale_inode(inode, generation, attr) ||
+		   !fuse_file_handle_is_equal(fc, fi->fh, fh)) {
 		/* nodeid was reused, any I/O on the old inode should fail */
 		fuse_make_bad(inode);
 		if (inode != d_inode(sb->s_root)) {
@@ -521,7 +576,6 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
 			goto retry;
 		}
 	}
-	fi = get_fuse_inode(inode);
 	spin_lock(&fi->lock);
 	fi->nlookup++;
 	spin_unlock(&fi->lock);
@@ -1059,7 +1113,7 @@ static struct inode *fuse_get_root_inode(struct super_block *sb, unsigned int mo
 	attr.mode = mode;
 	attr.ino = FUSE_ROOT_ID;
 	attr.nlink = 1;
-	return fuse_iget(sb, FUSE_ROOT_ID, 0, &attr, 0, 0, 0);
+	return fuse_iget(sb, FUSE_ROOT_ID, 0, &attr, 0, 0, 0, NULL); // XXX
 }
 
 struct fuse_inode_handle {
@@ -1092,7 +1146,7 @@ static struct dentry *fuse_get_dentry(struct super_block *sb,
 			goto out_err;
 		}
 
-		err = fuse_lookup_name(sb, handle->nodeid, &name, outarg,
+		err = fuse_lookup_name(sb, handle->nodeid, NULL, &name, outarg,
 				       &inode);
 		kfree(outarg);
 		if (err && err != -ENOENT)
@@ -1199,7 +1253,7 @@ static struct dentry *fuse_get_parent(struct dentry *child)
 		return ERR_PTR(-ENOMEM);
 
 	err = fuse_lookup_name(child_inode->i_sb, get_node_id(child_inode),
-			       &dotdot_name, outarg, &inode);
+			       child_inode, &dotdot_name, outarg, &inode);
 	kfree(outarg);
 	if (err) {
 		if (err == -ENOENT)
@@ -1757,8 +1811,9 @@ static int fuse_fill_super_submount(struct super_block *sb,
 		return -ENOMEM;
 
 	fuse_fill_attr_from_inode(&root_attr, parent_fi);
+	/* XXX using parent fh */
 	root = fuse_iget(sb, parent_fi->nodeid, 0, &root_attr, 0, 0,
-			 fuse_get_evict_ctr(fm->fc));
+			 fuse_get_evict_ctr(fm->fc), parent_fi->fh);
 	/*
 	 * This inode is just a duplicate, so it is not looked up and
 	 * its nlookup should not be incremented.  fuse_iget() does
diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c
index c2aae2eef086..04fb6636c4c0 100644
--- a/fs/fuse/readdir.c
+++ b/fs/fuse/readdir.c
@@ -185,12 +185,12 @@ static int fuse_direntplus_link(struct file *file,
 			return 0;
 	}
 
-	if (invalid_nodeid(o->nodeid))
-		return -EIO;
-	if (fuse_invalid_attr(&o->attr))
+	fc = get_fuse_conn(dir);
+
+	if (invalid_nodeid(o->nodeid) || fuse_invalid_attr(&o->attr) ||
+	    fuse_invalid_file_handle(fc, &o->fh))
 		return -EIO;
 
-	fc = get_fuse_conn(dir);
 	epoch = atomic_read(&fc->epoch);
 
 	name.hash = full_name_hash(parent, name.name, name.len);
@@ -235,7 +235,7 @@ static int fuse_direntplus_link(struct file *file,
 	} else {
 		inode = fuse_iget(dir->i_sb, o->nodeid, o->generation,
 				  &o->attr, ATTR_TIMEOUT(o),
-				  attr_version, evict_ctr);
+				  attr_version, evict_ctr, &o->fh);
 		if (!inode)
 			inode = ERR_PTR(-ENOMEM);
 
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 4acf71b407c9..b75744d2d75d 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -690,6 +690,13 @@ enum fuse_notify_code {
 #define FUSE_MIN_READ_BUFFER 8192
 
 #define FUSE_COMPAT_ENTRY_OUT_SIZE 120
+#define FUSE_COMPAT_45_ENTRY_OUT_SIZE 128
+
+struct fuse_file_handle {
+	uint32_t	size;
+	uint32_t	type;
+	char		handle[0];
+};
 
 struct fuse_entry_out {
 	uint64_t	nodeid;		/* Inode ID */
@@ -700,6 +707,7 @@ struct fuse_entry_out {
 	uint32_t	entry_valid_nsec;
 	uint32_t	attr_valid_nsec;
 	struct fuse_attr attr;
+	struct fuse_file_handle fh;
 };
 
 struct fuse_forget_in {
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Miklos Szeredi 1 month, 3 weeks ago
On Fri, 12 Dec 2025 at 19:12, Luis Henriques <luis@igalia.com> wrote:
>
> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
> an extra inarg: the file handle for the parent directory (if it is
> available).  Also, because fuse_entry_out now has a extra variable size
> struct (the actual handle), it also sets the out_argvar flag to true.

How about adding this as an extension header (FUSE_EXT_HANDLE)?  That
would allow any operation to take a handle instead of a nodeid.

Yeah, the infrastructure for adding extensions is inadequate, but I
think the API is ready for this.

> @@ -181,8 +182,24 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
>         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].size = sizeof(*outarg) + outarg->fh.size;
> +
> +       if (fc->lookup_handle) {
> +               struct fuse_inode *fi = NULL;
> +
> +               args->opcode = FUSE_LOOKUP_HANDLE;
> +               args->out_argvar = true;

How about allocating variable length arguments on demand?  That would
allow getting rid of max_handle_size negotiation.

        args->out_var_alloc  = true;
        args->out_args[1].size = MAX_HANDLE_SZ;
        args->out_args[1].value = NULL; /* Will be allocated to the
actual size of the handle */

> diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
> index 4acf71b407c9..b75744d2d75d 100644
> --- a/include/uapi/linux/fuse.h
> +++ b/include/uapi/linux/fuse.h
> @@ -690,6 +690,13 @@ enum fuse_notify_code {
>  #define FUSE_MIN_READ_BUFFER 8192
>
>  #define FUSE_COMPAT_ENTRY_OUT_SIZE 120
> +#define FUSE_COMPAT_45_ENTRY_OUT_SIZE 128
> +
> +struct fuse_file_handle {
> +       uint32_t        size;

uint16_t should be enough for everyone ;)

> +       uint32_t        type;

Please make "type" just be a part of the opaque handle.  Makes
conversion from struct file_handle to struct fuse_file_handle slightly
more complex, but api clarity is more important imo.

> +       char            handle[0];

uint8_t handle[];

Thanks,
Miklos
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Luis Henriques 1 month ago
Hi Miklos,

On Tue, Dec 16 2025, Miklos Szeredi wrote:

> On Fri, 12 Dec 2025 at 19:12, Luis Henriques <luis@igalia.com> wrote:
>>
>> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
>> an extra inarg: the file handle for the parent directory (if it is
>> available).  Also, because fuse_entry_out now has a extra variable size
>> struct (the actual handle), it also sets the out_argvar flag to true.
>
> How about adding this as an extension header (FUSE_EXT_HANDLE)?  That
> would allow any operation to take a handle instead of a nodeid.
>
> Yeah, the infrastructure for adding extensions is inadequate, but I
> think the API is ready for this.
>
>> @@ -181,8 +182,24 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
>>         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].size = sizeof(*outarg) + outarg->fh.size;
>> +
>> +       if (fc->lookup_handle) {
>> +               struct fuse_inode *fi = NULL;
>> +
>> +               args->opcode = FUSE_LOOKUP_HANDLE;
>> +               args->out_argvar = true;
>
> How about allocating variable length arguments on demand?  That would
> allow getting rid of max_handle_size negotiation.
>
>         args->out_var_alloc  = true;
>         args->out_args[1].size = MAX_HANDLE_SZ;
>         args->out_args[1].value = NULL; /* Will be allocated to the actual size of the handle */

I've been trying to wrap my head around all the suggested changes, and
experimenting with a few options.  Since there are some major things that
need to be modified, I'd like to confirm that I got them right:

1. In the old FUSE_LOOKUP, the args->in_args[0] will continue to use the
   struct fuse_entry_out, which won't be changed and will continue to have
   a static size.

2. FUSE_LOOKUP_HANDLE will add a new out_arg, which will be dynamically
   allocated (using your suggestion: 'args->out_var_alloc').  This will be
   a new struct fuse_entry_handle_out, similar to fuse_entry_out, but
   replacing the struct fuse_attr by a struct fuse_statx, and adding the
   file handle struct.

3. FUSE_LOOKUP_HANDLE will use the args->in_args[0] as an extension header
   (FUSE_EXT_HANDLE).  Note that other operations (e.g. those in function
   create_new_entry()) will actually need to *add* an extra extension
   header, as extension headers are already being used there.
   This extension header will use the new struct fuse_entry_handle_out.

The above items seem to require some heavy changes on my current design.
That's why I'd like to make sure I got those right so that v3 is on the
right path.

Thanks in advance for any feedback!

Cheers,
-- 
Luís
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Miklos Szeredi 1 month ago
On Fri, 9 Jan 2026 at 12:57, Luis Henriques <luis@igalia.com> wrote:

> I've been trying to wrap my head around all the suggested changes, and
> experimenting with a few options.  Since there are some major things that
> need to be modified, I'd like to confirm that I got them right:
>
> 1. In the old FUSE_LOOKUP, the args->in_args[0] will continue to use the
>    struct fuse_entry_out, which won't be changed and will continue to have
>    a static size.

Yes.

> 2. FUSE_LOOKUP_HANDLE will add a new out_arg, which will be dynamically
>    allocated (using your suggestion: 'args->out_var_alloc').  This will be
>    a new struct fuse_entry_handle_out, similar to fuse_entry_out, but
>    replacing the struct fuse_attr by a struct fuse_statx, and adding the
>    file handle struct.

Another idea: let's simplify the interface by removing the attributes
from the lookup reply entirely.  To get back the previous
functionality, compound requests can be used: LOOKUP_HANDLE + STATX.

> 3. FUSE_LOOKUP_HANDLE will use the args->in_args[0] as an extension header

No, extensions go after the regular request data: headers, payload,
extension(s).

We could think about changing that for uring, where it would make
sense to put the extensions after the regular headers, but currently
it doesn't work that way and goes into the payload section.

In any case LOOKUP_HANDLE should follow the existing practice.

>    (FUSE_EXT_HANDLE).  Note that other operations (e.g. those in function
>    create_new_entry()) will actually need to *add* an extra extension
>    header, as extension headers are already being used there.

Right.

>    This extension header will use the new struct fuse_entry_handle_out.

Why _out?

It should just be a struct fuse_ext_header followed by a struct
fuse_file_handle.

Thanks,
Miklos
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Amir Goldstein 1 month ago
On Fri, Jan 9, 2026 at 1:38 PM Miklos Szeredi <miklos@szeredi.hu> wrote:
>
> On Fri, 9 Jan 2026 at 12:57, Luis Henriques <luis@igalia.com> wrote:
>
> > I've been trying to wrap my head around all the suggested changes, and
> > experimenting with a few options.  Since there are some major things that
> > need to be modified, I'd like to confirm that I got them right:
> >
> > 1. In the old FUSE_LOOKUP, the args->in_args[0] will continue to use the
> >    struct fuse_entry_out, which won't be changed and will continue to have
> >    a static size.
>
> Yes.
>
> > 2. FUSE_LOOKUP_HANDLE will add a new out_arg, which will be dynamically
> >    allocated (using your suggestion: 'args->out_var_alloc').  This will be
> >    a new struct fuse_entry_handle_out, similar to fuse_entry_out, but
> >    replacing the struct fuse_attr by a struct fuse_statx, and adding the
> >    file handle struct.
>
> Another idea: let's simplify the interface by removing the attributes
> from the lookup reply entirely.  To get back the previous
> functionality, compound requests can be used: LOOKUP_HANDLE + STATX.

What about FUSE_CREATE? FUSE_TMPFILE?
and more importantly READDIRPLUS dirents?

How do you envision the protocol extension for those if fuse_entry_handle
does not contain fuse_statx?

Thanks,
Amir.

>
> > 3. FUSE_LOOKUP_HANDLE will use the args->in_args[0] as an extension header
>
> No, extensions go after the regular request data: headers, payload,
> extension(s).
>
> We could think about changing that for uring, where it would make
> sense to put the extensions after the regular headers, but currently
> it doesn't work that way and goes into the payload section.
>
> In any case LOOKUP_HANDLE should follow the existing practice.
>
> >    (FUSE_EXT_HANDLE).  Note that other operations (e.g. those in function
> >    create_new_entry()) will actually need to *add* an extra extension
> >    header, as extension headers are already being used there.
>
> Right.
>
> >    This extension header will use the new struct fuse_entry_handle_out.
>
> Why _out?
>
> It should just be a struct fuse_ext_header followed by a struct
> fuse_file_handle.
>
> Thanks,
> Miklos
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Miklos Szeredi 1 month ago
On Fri, 9 Jan 2026 at 16:03, Amir Goldstein <amir73il@gmail.com> wrote:

> What about FUSE_CREATE? FUSE_TMPFILE?

FUSE_CREATE could be decomposed to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN.

FUSE_TMPFILE is special, the create and open needs to be atomic.   So
the best we can do is FUSE_TMPFILE_H + FUSE_STATX.

> and more importantly READDIRPLUS dirents?

I was never satisfied with FUSE_READDIRPLUS, I'd prefer something more
flexible, where policy is moved from the kernel to the fuse server.

How about a push style interface with FUSE_NOTIFY_ENTRY setting up the
dentry and the inode?

Thanks,
Miklos
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Bernd Schubert 1 month ago

On 1/9/26 16:37, Miklos Szeredi wrote:
> On Fri, 9 Jan 2026 at 16:03, Amir Goldstein <amir73il@gmail.com> wrote:
> 
>> What about FUSE_CREATE? FUSE_TMPFILE?
> 
> FUSE_CREATE could be decomposed to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN.
> 
> FUSE_TMPFILE is special, the create and open needs to be atomic.   So
> the best we can do is FUSE_TMPFILE_H + FUSE_STATX.
> 
>> and more importantly READDIRPLUS dirents?
> 
> I was never satisfied with FUSE_READDIRPLUS, I'd prefer something more
> flexible, where policy is moved from the kernel to the fuse server.
> 
> How about a push style interface with FUSE_NOTIFY_ENTRY setting up the
> dentry and the inode?

Feasible, but we should extend io-uring to FUSE_NOTIFY first, otherwise
this will have a painful overhead.


Thanks,
Bernd
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Amir Goldstein 1 month ago
On Fri, Jan 9, 2026 at 4:56 PM Bernd Schubert <bschubert@ddn.com> wrote:
>
>
>
> On 1/9/26 16:37, Miklos Szeredi wrote:
> > On Fri, 9 Jan 2026 at 16:03, Amir Goldstein <amir73il@gmail.com> wrote:
> >
> >> What about FUSE_CREATE? FUSE_TMPFILE?
> >
> > FUSE_CREATE could be decomposed to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN.
> >
> > FUSE_TMPFILE is special, the create and open needs to be atomic.   So
> > the best we can do is FUSE_TMPFILE_H + FUSE_STATX.
> >

I thought that the idea of FUSE_CREATE is that it is atomic_open()
is it not?
If we decompose that to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN
it won't be atomic on the server, would it?

> >> and more importantly READDIRPLUS dirents?
> >
> > I was never satisfied with FUSE_READDIRPLUS, I'd prefer something more
> > flexible, where policy is moved from the kernel to the fuse server.
> >
> > How about a push style interface with FUSE_NOTIFY_ENTRY setting up the
> > dentry and the inode?
>
> Feasible, but we should extend io-uring to FUSE_NOTIFY first, otherwise
> this will have a painful overhead.
>
>

I admit that the guesswork with readdirplus auto is not always
what serves users the best, but why change to push?
If the server had actually written the dirents with some header
it could just as well decide per dirent if it wants to return
dirent or direntplus or direntplus_handle.

What is the expected benefit of using push in this scenario?

My own take on READDIRPLUS is that it cries for a user API
so that "ls" could opt-out and "ls -l" could opt-in to readdirplus.

I hacked my own server to use open(O_SYNC) indication for
directories as a signal to choose between readdirplus and
kernel readdir passthrough (not plus) and I have applications
that opt-out of readdirplus.

Thanks,
Amir.
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Miklos Szeredi 1 month ago
On Fri, 9 Jan 2026 at 19:29, Amir Goldstein <amir73il@gmail.com> wrote:

> I thought that the idea of FUSE_CREATE is that it is atomic_open()
> is it not?
> If we decompose that to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN
> it won't be atomic on the server, would it?

This won't change anything wrt atomicity, since the request and the
reply are still a single blob.  The reason to do this is that the
interface itself is cleaner and we can swap out one part (like we want
now with the file handle stuff) and reuse the other parts.

So I mostly look on this as a trick to make the interface more
modular, not something that the servers will care about.

> I admit that the guesswork with readdirplus auto is not always
> what serves users the best, but why change to push?
> If the server had actually written the dirents with some header
> it could just as well decide per dirent if it wants to return
> dirent or direntplus or direntplus_handle.
>
> What is the expected benefit of using push in this scenario?

I was thinking of detaching the lookup + getattr from the directory data.

Now I realize that doing it with a FUSE_NOTIFY is not really useful,
we can just attach the array of entries at the end of the readdir
reply as a separate blob.

> My own take on READDIRPLUS is that it cries for a user API
> so that "ls" could opt-out and "ls -l" could opt-in to readdirplus.

Yeah, that would be nice.  What about fadvise flag?

Thanks,
Miklos
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Amir Goldstein 1 month ago
On Fri, Jan 9, 2026 at 8:01 PM Miklos Szeredi <miklos@szeredi.hu> wrote:
>
> On Fri, 9 Jan 2026 at 19:29, Amir Goldstein <amir73il@gmail.com> wrote:
>
> > I thought that the idea of FUSE_CREATE is that it is atomic_open()
> > is it not?
> > If we decompose that to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN
> > it won't be atomic on the server, would it?
>
> This won't change anything wrt atomicity, since the request and the
> reply are still a single blob.  The reason to do this is that the
> interface itself is cleaner and we can swap out one part (like we want
> now with the file handle stuff) and reuse the other parts.
>
> So I mostly look on this as a trick to make the interface more
> modular, not something that the servers will care about.
>
> > I admit that the guesswork with readdirplus auto is not always
> > what serves users the best, but why change to push?
> > If the server had actually written the dirents with some header
> > it could just as well decide per dirent if it wants to return
> > dirent or direntplus or direntplus_handle.
> >
> > What is the expected benefit of using push in this scenario?
>
> I was thinking of detaching the lookup + getattr from the directory data.
>
> Now I realize that doing it with a FUSE_NOTIFY is not really useful,
> we can just attach the array of entries at the end of the readdir
> reply as a separate blob.
>

Sure, that works, as long as there is some header to the array,
it could contain the new entry_handle and statx payloads.

> > My own take on READDIRPLUS is that it cries for a user API
> > so that "ls" could opt-out and "ls -l" could opt-in to readdirplus.
>
> Yeah, that would be nice.  What about fadvise flag?
>

Definitely, like readahead we would need to support
NO (RANDOM), YES (SEQUENTIAL) and AUTO (NORMAL).

Honestly, I got discouraged from completing readdir passthrough
because of the complexity involved wrt readdirplus, but
maybe I will just post the patches I have for readdir(not plus)
passthrough with the fadvise to facilitate it.

Thanks,
Amir.
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Miklos Szeredi 1 month ago
On Fri, 9 Jan 2026 at 16:56, Bernd Schubert <bschubert@ddn.com> wrote:

> Feasible, but we should extend io-uring to FUSE_NOTIFY first, otherwise
> this will have a painful overhead.

We don't want to do the lock/add/unlock for individual dentries
anyway, so might as well make this FUSE_NOTIFY_ENTRIES.  That would
lock the directory, add a bunch of dentries, then unlock.

The fun part is that locking the directory must not be done from the
READDIR context, as rwsem read locks are apparently not nestable.
This would make the interface a pain to use.  We could work around
that by checking if a READDIR is currently in progress and then
synchronizing the two such that the entries are added with the
directory lock held.   It's a bit of complexity, but maybe worth it as
it's going to be the common usage.

Thanks,
Miklos
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Luis Henriques 1 month ago
On Fri, Jan 09 2026, Miklos Szeredi wrote:

> On Fri, 9 Jan 2026 at 16:56, Bernd Schubert <bschubert@ddn.com> wrote:
>
>> Feasible, but we should extend io-uring to FUSE_NOTIFY first, otherwise
>> this will have a painful overhead.
>
> We don't want to do the lock/add/unlock for individual dentries
> anyway, so might as well make this FUSE_NOTIFY_ENTRIES.  That would
> lock the directory, add a bunch of dentries, then unlock.
>
> The fun part is that locking the directory must not be done from the
> READDIR context, as rwsem read locks are apparently not nestable.
> This would make the interface a pain to use.  We could work around
> that by checking if a READDIR is currently in progress and then
> synchronizing the two such that the entries are added with the
> directory lock held.   It's a bit of complexity, but maybe worth it as
> it's going to be the common usage.

Yikes!  Things are not getting any simpler :-(

OK, looks like there's a lot of things I'll need to figure out before
proceeding.

/me schedules some time to learn and play a bit with all this.

Cheers,
-- 
Luís
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Luis Henriques 1 month ago
On Fri, Jan 09 2026, Miklos Szeredi wrote:

> On Fri, 9 Jan 2026 at 12:57, Luis Henriques <luis@igalia.com> wrote:
>
>> I've been trying to wrap my head around all the suggested changes, and
>> experimenting with a few options.  Since there are some major things that
>> need to be modified, I'd like to confirm that I got them right:
>>
>> 1. In the old FUSE_LOOKUP, the args->in_args[0] will continue to use the
>>    struct fuse_entry_out, which won't be changed and will continue to have
>>    a static size.
>
> Yes.
>
>> 2. FUSE_LOOKUP_HANDLE will add a new out_arg, which will be dynamically
>>    allocated (using your suggestion: 'args->out_var_alloc').  This will be
>>    a new struct fuse_entry_handle_out, similar to fuse_entry_out, but
>>    replacing the struct fuse_attr by a struct fuse_statx, and adding the
>>    file handle struct.
>
> Another idea: let's simplify the interface by removing the attributes
> from the lookup reply entirely.  To get back the previous
> functionality, compound requests can be used: LOOKUP_HANDLE + STATX.

OK, interesting idea.  So, in that case we would have:

struct fuse_entry_handle_out {
	uint64_t nodeid;
	uint64_t generation;
	uint64_t entry_valid;
	struct fuse_file_handle fh;
}

I'll then need to have a look at the compound requests closely. (I had
previously skimmed through the patches that add open+getattr but didn't
gone too deep into it.)

>> 3. FUSE_LOOKUP_HANDLE will use the args->in_args[0] as an extension header
>
> No, extensions go after the regular request data: headers, payload,
> extension(s).
>
> We could think about changing that for uring, where it would make
> sense to put the extensions after the regular headers, but currently
> it doesn't work that way and goes into the payload section.
>
> In any case LOOKUP_HANDLE should follow the existing practice.

Hmm... I _think_ I had it right in my head, but totally messed up my text.
English is hard.  What I meant was that args->ext_idx would be set to the
in_args[] index that would describe the extension (for the lookup
operation, that would be '3', not '0' as I mentioned above).

And then the extension header would be created similarly to what's being
done for FUSE_EXT_GROUPS, using the same helper extend_arg().  That way, I
think we would have: headers - payload - extensions.

>>    (FUSE_EXT_HANDLE).  Note that other operations (e.g. those in function
>>    create_new_entry()) will actually need to *add* an extra extension
>>    header, as extension headers are already being used there.
>
> Right.
>
>>    This extension header will use the new struct fuse_entry_handle_out.
>
> Why _out?
>
> It should just be a struct fuse_ext_header followed by a struct
> fuse_file_handle.

Yes, of course.  My English was totally messed-up.  And I meant
'fuse_file_handle', not 'fuse_entry_handle_out'.

Cheer,
-- 
Luís
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Miklos Szeredi 1 month ago
On Fri, 9 Jan 2026 at 15:45, Luis Henriques <luis@igalia.com> wrote:

> struct fuse_entry_handle_out {
>         uint64_t nodeid;
>         uint64_t generation;
>         uint64_t entry_valid;
>         struct fuse_file_handle fh;
> }

I'd do it this way:

struct fuse_entry2_out {
        uint64_t nodeid;
        uint64_t generation;
        uint64_t entry_valid;
        uint32_t entry_valid_nsec;
        uint32_t flags;
        uint64_t spare;
};

and the file handle would be placed in out_args[1].

> I'll then need to have a look at the compound requests closely. (I had
> previously skimmed through the patches that add open+getattr but didn't
> gone too deep into it.)

It should work as two separate requests, just not as optimal.

> And then the extension header would be created similarly to what's being
> done for FUSE_EXT_GROUPS, using the same helper extend_arg().  That way, I
> think we would have: headers - payload - extensions.

Right.

Thanks,
Miklos
Re: Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Horst Birthelmer 1 month ago
On Fri, Jan 09, 2026 at 02:45:21PM +0000, Luis Henriques wrote:
> On Fri, Jan 09 2026, Miklos Szeredi wrote:
> 
> > On Fri, 9 Jan 2026 at 12:57, Luis Henriques <luis@igalia.com> wrote:
> >
> >> I've been trying to wrap my head around all the suggested changes, and
> >> experimenting with a few options.  Since there are some major things that
> >> need to be modified, I'd like to confirm that I got them right:
> >>
> >> 1. In the old FUSE_LOOKUP, the args->in_args[0] will continue to use the
> >>    struct fuse_entry_out, which won't be changed and will continue to have
> >>    a static size.
> >
> > Yes.
> >
> >> 2. FUSE_LOOKUP_HANDLE will add a new out_arg, which will be dynamically
> >>    allocated (using your suggestion: 'args->out_var_alloc').  This will be
> >>    a new struct fuse_entry_handle_out, similar to fuse_entry_out, but
> >>    replacing the struct fuse_attr by a struct fuse_statx, and adding the
> >>    file handle struct.
> >
> > Another idea: let's simplify the interface by removing the attributes
> > from the lookup reply entirely.  To get back the previous
> > functionality, compound requests can be used: LOOKUP_HANDLE + STATX.
> 
> OK, interesting idea.  So, in that case we would have:
> 
> struct fuse_entry_handle_out {
> 	uint64_t nodeid;
> 	uint64_t generation;
> 	uint64_t entry_valid;
> 	struct fuse_file_handle fh;
> }
> 
> I'll then need to have a look at the compound requests closely. (I had
> previously skimmed through the patches that add open+getattr but didn't
> gone too deep into it.)
> 

I am preparing the pull request for libfuse today, so you can have a look at how it will be handled on the libfuse
side. 
That contains a patch to passthrough_hp as well so it supports compounds and you will have something to test, if you want to go that way.

> 
> Cheer,
> -- 
> Luís
> 
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Luis Henriques 1 month ago
On Fri, Jan 09 2026, Horst Birthelmer wrote:

> On Fri, Jan 09, 2026 at 02:45:21PM +0000, Luis Henriques wrote:
>> On Fri, Jan 09 2026, Miklos Szeredi wrote:
>> 
>> > On Fri, 9 Jan 2026 at 12:57, Luis Henriques <luis@igalia.com> wrote:
>> >
>> >> I've been trying to wrap my head around all the suggested changes, and
>> >> experimenting with a few options.  Since there are some major things that
>> >> need to be modified, I'd like to confirm that I got them right:
>> >>
>> >> 1. In the old FUSE_LOOKUP, the args->in_args[0] will continue to use the
>> >>    struct fuse_entry_out, which won't be changed and will continue to have
>> >>    a static size.
>> >
>> > Yes.
>> >
>> >> 2. FUSE_LOOKUP_HANDLE will add a new out_arg, which will be dynamically
>> >>    allocated (using your suggestion: 'args->out_var_alloc').  This will be
>> >>    a new struct fuse_entry_handle_out, similar to fuse_entry_out, but
>> >>    replacing the struct fuse_attr by a struct fuse_statx, and adding the
>> >>    file handle struct.
>> >
>> > Another idea: let's simplify the interface by removing the attributes
>> > from the lookup reply entirely.  To get back the previous
>> > functionality, compound requests can be used: LOOKUP_HANDLE + STATX.
>> 
>> OK, interesting idea.  So, in that case we would have:
>> 
>> struct fuse_entry_handle_out {
>> 	uint64_t nodeid;
>> 	uint64_t generation;
>> 	uint64_t entry_valid;
>> 	struct fuse_file_handle fh;
>> }
>> 
>> I'll then need to have a look at the compound requests closely. (I had
>> previously skimmed through the patches that add open+getattr but didn't
>> gone too deep into it.)
>> 
>
> I am preparing the pull request for libfuse today, so you can have a look at how it will be handled on the libfuse
> side. 
> That contains a patch to passthrough_hp as well so it supports compounds and you will have something to test, if you want to go that way.

Awesome, thanks for the hint, Horst.  I'll definitely have a look into it.

Cheer,
-- 
Luís
Re: Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Horst Birthelmer 3 weeks, 6 days ago
On Fri, Jan 09, 2026 at 05:07:10PM +0000, Luis Henriques wrote:
> On Fri, Jan 09 2026, Horst Birthelmer wrote:
> 
> > On Fri, Jan 09, 2026 at 02:45:21PM +0000, Luis Henriques wrote:
> >> On Fri, Jan 09 2026, Miklos Szeredi wrote:
> >> 
> >> > On Fri, 9 Jan 2026 at 12:57, Luis Henriques <luis@igalia.com> wrote:
> >> >
> >> >> I've been trying to wrap my head around all the suggested changes, and
> >> >> experimenting with a few options.  Since there are some major things that
> >> >> need to be modified, I'd like to confirm that I got them right:
> >> >>
> >> >> 1. In the old FUSE_LOOKUP, the args->in_args[0] will continue to use the
> >> >>    struct fuse_entry_out, which won't be changed and will continue to have
> >> >>    a static size.
> >> >
> >> > Yes.
> >> >
> >> >> 2. FUSE_LOOKUP_HANDLE will add a new out_arg, which will be dynamically
> >> >>    allocated (using your suggestion: 'args->out_var_alloc').  This will be
> >> >>    a new struct fuse_entry_handle_out, similar to fuse_entry_out, but
> >> >>    replacing the struct fuse_attr by a struct fuse_statx, and adding the
> >> >>    file handle struct.
> >> >
> >> > Another idea: let's simplify the interface by removing the attributes
> >> > from the lookup reply entirely.  To get back the previous
> >> > functionality, compound requests can be used: LOOKUP_HANDLE + STATX.
> >> 
> >> OK, interesting idea.  So, in that case we would have:
> >> 
> >> struct fuse_entry_handle_out {
> >> 	uint64_t nodeid;
> >> 	uint64_t generation;
> >> 	uint64_t entry_valid;
> >> 	struct fuse_file_handle fh;
> >> }
> >> 
> >> I'll then need to have a look at the compound requests closely. (I had
> >> previously skimmed through the patches that add open+getattr but didn't
> >> gone too deep into it.)
> >> 
> >
> > I am preparing the pull request for libfuse today, so you can have a look at how it will be handled on the libfuse
> > side. 
> > That contains a patch to passthrough_hp as well so it supports compounds and you will have something to test, if you want to go that way.
> 
> Awesome, thanks for the hint, Horst.  I'll definitely have a look into it.
> 
FWIW, I have updated the PR for libfuse so that passthrough_hp now demoes the two ways we can handle compounds.
The one where we just call the requests in the compound sequencially (here we have a helper in the lib now) and
the  way where we create a special function to take care of atomic combinations of requests.
I hope that helps to understand the idea better.

> Cheer,
> -- 
> Luís

Thanks,
Horst
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Amir Goldstein 1 month, 3 weeks ago
On Tue, Dec 16, 2025 at 11:40 AM Miklos Szeredi <miklos@szeredi.hu> wrote:
>
> On Fri, 12 Dec 2025 at 19:12, Luis Henriques <luis@igalia.com> wrote:
> >
> > The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
> > an extra inarg: the file handle for the parent directory (if it is
> > available).  Also, because fuse_entry_out now has a extra variable size
> > struct (the actual handle), it also sets the out_argvar flag to true.
>
> How about adding this as an extension header (FUSE_EXT_HANDLE)?  That
> would allow any operation to take a handle instead of a nodeid.
>
> Yeah, the infrastructure for adding extensions is inadequate, but I
> think the API is ready for this.
>
> > @@ -181,8 +182,24 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
> >         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].size = sizeof(*outarg) + outarg->fh.size;
> > +
> > +       if (fc->lookup_handle) {
> > +               struct fuse_inode *fi = NULL;
> > +
> > +               args->opcode = FUSE_LOOKUP_HANDLE;
> > +               args->out_argvar = true;
>
> How about allocating variable length arguments on demand?  That would
> allow getting rid of max_handle_size negotiation.
>
>         args->out_var_alloc  = true;
>         args->out_args[1].size = MAX_HANDLE_SZ;
>         args->out_args[1].value = NULL; /* Will be allocated to the
> actual size of the handle */
>

Keep in mind that we will need to store the file handle in the fuse_inode.
Don't you think that it is better to negotiate the max_handle_size even
if only as an upper limit?

Note that MAX_HANDLE_SZ is not even UAPI.
It is the upper limit of the moment for the open_by_handle_at() syscall.
FUSE protocol is by no means obligated to it, but sure we can use that
as the default upper limit.

From man open_by_handle_at.2:
       It  is  the caller's responsibility to allocate the structure
with a size large enough to hold the handle returned in f_handle.
       ...(The constant MAX_HANDLE_SZ, defined in <fcntl.h>, specifies
the maximum expected size for a file handle.
       It is not a guaranteed upper limit as future filesystems may
require more space.)

Thanks,
Amir.
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Miklos Szeredi 1 month, 3 weeks ago
On Tue, 16 Dec 2025 at 11:52, Amir Goldstein <amir73il@gmail.com> wrote:

> Keep in mind that we will need to store the file handle in the fuse_inode.
> Don't you think that it is better to negotiate the max_handle_size even
> if only as an upper limit?

I don't see the point.  The handle will be allocated after the lookup
has completed, and by that time will will know the exact size, so the
maximum is irrelevant.  What am I missing?

> Note that MAX_HANDLE_SZ is not even UAPI.
> It is the upper limit of the moment for the open_by_handle_at() syscall.
> FUSE protocol is by no means obligated to it, but sure we can use that
> as the default upper limit.

Yeah, but even that is excessive, since this will be a non-connectable
one, and need to fit two of them plus a header into a connectable fuse
file handle.

Thanks,
Miklos
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Joanne Koong 1 month, 3 weeks ago
On Sat, Dec 13, 2025 at 2:14 AM Luis Henriques <luis@igalia.com> wrote:
>
> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
> an extra inarg: the file handle for the parent directory (if it is
> available).  Also, because fuse_entry_out now has a extra variable size
> struct (the actual handle), it also sets the out_argvar flag to true.
>
> Most of the other modifications in this patch are a fallout from these
> changes: because fuse_entry_out has been modified to include a variable size
> struct, every operation that receives such a parameter have to take this
> into account:
>
>   CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
>
> Signed-off-by: Luis Henriques <luis@igalia.com>
> ---
>  fs/fuse/dev.c             | 16 +++++++
>  fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
>  fs/fuse/fuse_i.h          | 34 +++++++++++++--
>  fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
>  fs/fuse/readdir.c         | 10 ++---
>  include/uapi/linux/fuse.h |  8 ++++
>  6 files changed, 189 insertions(+), 35 deletions(-)
>

Could you explain why the file handle size needs to be dynamically set
by the server instead of just from the kernel-side stipulating that
the file handle size is FUSE_HANDLE_SZ (eg 128 bytes)? It seems to me
like that would simplify a lot of the code logic here.

Thanks,
Joanne
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Bernd Schubert 1 month, 3 weeks ago
On 12/12/25 19:12, Luis Henriques wrote:
> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
> an extra inarg: the file handle for the parent directory (if it is
> available).  Also, because fuse_entry_out now has a extra variable size
> struct (the actual handle), it also sets the out_argvar flag to true.
> 
> Most of the other modifications in this patch are a fallout from these
> changes: because fuse_entry_out has been modified to include a variable size
> struct, every operation that receives such a parameter have to take this
> into account:
> 
>    CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
> 
> Signed-off-by: Luis Henriques <luis@igalia.com>
> ---
>   fs/fuse/dev.c             | 16 +++++++
>   fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
>   fs/fuse/fuse_i.h          | 34 +++++++++++++--
>   fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
>   fs/fuse/readdir.c         | 10 ++---
>   include/uapi/linux/fuse.h |  8 ++++
>   6 files changed, 189 insertions(+), 35 deletions(-)
> 
> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
> index 629e8a043079..fc6acf45ae27 100644
> --- a/fs/fuse/dev.c
> +++ b/fs/fuse/dev.c
> @@ -606,6 +606,22 @@ static void fuse_adjust_compat(struct fuse_conn *fc, struct fuse_args *args)
>   	if (fc->minor < 4 && args->opcode == FUSE_STATFS)
>   		args->out_args[0].size = FUSE_COMPAT_STATFS_SIZE;
>   
> +	if (fc->minor < 45) {

Could we use fc->lookup_handle here? Numbers are hard with backports

> +		switch (args->opcode) {
> +		case FUSE_CREATE:
> +		case FUSE_LINK:
> +		case FUSE_LOOKUP:
> +		case FUSE_MKDIR:
> +		case FUSE_MKNOD:
> +		/* XXX case FUSE_READDIRPLUS: */
> +		case FUSE_SYMLINK:
> +		case FUSE_TMPFILE:
> +			if (!WARN_ON_ONCE(args->in_numargs == 0))
> +				args->in_numargs--;
> +			args->out_args[0].size = FUSE_COMPAT_45_ENTRY_OUT_SIZE;
> +			break;
> +		}
> +	}
>   	if (fc->minor < 9) {
>   		switch (args->opcode) {
>   		case FUSE_LOOKUP:
> diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
> index e3fd5d148741..a6edb444180f 100644
> --- a/fs/fuse/dir.c
> +++ b/fs/fuse/dir.c
> @@ -169,7 +169,8 @@ static void fuse_invalidate_entry(struct dentry *entry)
>   }
>   
>   static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
> -			     u64 nodeid, const struct qstr *name,
> +			     u64 nodeid, struct inode *dir,
> +			     const struct qstr *name,
>   			     struct fuse_entry_out *outarg)
>   {
>   	args->opcode = FUSE_LOOKUP;
> @@ -181,8 +182,24 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
>   	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].size = sizeof(*outarg) + outarg->fh.size;
>   	args->out_args[0].value = outarg;
> +
> +	if (fc->lookup_handle) {
> +		struct fuse_inode *fi = NULL;
> +
> +		args->opcode = FUSE_LOOKUP_HANDLE;
> +		args->out_argvar = true;
> +
> +		if (dir)
> +			fi = get_fuse_inode(dir);
> +
> +		if (fi && fi->fh) {
> +			args->in_numargs = 4;
> +			args->in_args[3].size = sizeof(*fi->fh) + fi->fh->size;
> +			args->in_args[3].value = fi->fh;
> +		}
> +	}
>   }
>   
>   /*
> @@ -240,7 +257,7 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
>   
>   		attr_version = fuse_get_attr_version(fm->fc);
>   
> -		fuse_lookup_init(fm->fc, &args, get_node_id(dir),
> +		fuse_lookup_init(fm->fc, &args, get_node_id(dir), dir,
>   				 name, outarg);
>   		ret = fuse_simple_request(fm, &args);
>   		/* Zero nodeid is same as -ENOENT */
> @@ -248,7 +265,8 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
>   			ret = -ENOENT;
>   		if (!ret) {
>   			fi = get_fuse_inode(inode);
> -			if (outarg->nodeid != get_node_id(inode) ||
> +			if (!fuse_file_handle_is_equal(fm->fc, fi->fh, &outarg->fh) ||
> +			    outarg->nodeid != get_node_id(inode) ||
>   			    (bool) IS_AUTOMOUNT(inode) != (bool) (outarg->attr.flags & FUSE_ATTR_SUBMOUNT)) {
>   				fuse_queue_forget(fm->fc, forget,
>   						  outarg->nodeid, 1);
> @@ -365,8 +383,9 @@ bool fuse_invalid_attr(struct fuse_attr *attr)
>   	return !fuse_valid_type(attr->mode) || !fuse_valid_size(attr->size);
>   }
>   
> -int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name,
> -		     struct fuse_entry_out *outarg, struct inode **inode)
> +int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct inode *dir,
> +		     const struct qstr *name, struct fuse_entry_out *outarg,
> +		     struct inode **inode)
>   {
>   	struct fuse_mount *fm = get_fuse_mount_super(sb);
>   	FUSE_ARGS(args);
> @@ -388,14 +407,15 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
>   	attr_version = fuse_get_attr_version(fm->fc);
>   	evict_ctr = fuse_get_evict_ctr(fm->fc);
>   
> -	fuse_lookup_init(fm->fc, &args, nodeid, name, outarg);
> +	fuse_lookup_init(fm->fc, &args, nodeid, dir, name, outarg);
>   	err = fuse_simple_request(fm, &args);
>   	/* Zero nodeid is same as -ENOENT, but with valid timeout */
> -	if (err || !outarg->nodeid)
> +	if (err < 0 || !outarg->nodeid) // XXX err = size if args->out_argvar = true
>   		goto out_put_forget;
>   
>   	err = -EIO;
> -	if (fuse_invalid_attr(&outarg->attr))
> +	if (fuse_invalid_attr(&outarg->attr) ||
> +	    fuse_invalid_file_handle(fm->fc, &outarg->fh))
>   		goto out_put_forget;
>   	if (outarg->nodeid == FUSE_ROOT_ID && outarg->generation != 0) {
>   		pr_warn_once("root generation should be zero\n");
> @@ -404,7 +424,8 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
>   
>   	*inode = fuse_iget(sb, outarg->nodeid, outarg->generation,
>   			   &outarg->attr, ATTR_TIMEOUT(outarg),
> -			   attr_version, evict_ctr);
> +			   attr_version, evict_ctr,
> +			   &outarg->fh);
>   	err = -ENOMEM;
>   	if (!*inode) {
>   		fuse_queue_forget(fm->fc, forget, outarg->nodeid, 1);
> @@ -440,14 +461,14 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
>   		return ERR_PTR(-ENOMEM);
>   
>   	locked = fuse_lock_inode(dir);
> -	err = fuse_lookup_name(dir->i_sb, get_node_id(dir), &entry->d_name,
> +	err = fuse_lookup_name(dir->i_sb, get_node_id(dir), dir, &entry->d_name,
>   			       outarg, &inode);
>   	fuse_unlock_inode(dir, locked);
>   	if (err == -ENOENT) {
>   		outarg_valid = false;
>   		err = 0;
>   	}
> -	if (err)
> +	if (err < 0) // XXX err = size if args->out_argvar = true
>   		goto out_err;
>   
>   	err = -EIO;
> @@ -689,24 +710,36 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
>   	args.in_args[1].size = entry->d_name.len + 1;
>   	args.in_args[1].value = entry->d_name.name;
>   	args.out_numargs = 2;
> -	args.out_args[0].size = sizeof(*outentry);
> +	args.out_args[0].size = sizeof(*outentry) + outentry->fh.size;
>   	args.out_args[0].value = outentry;
>   	/* Store outarg for fuse_finish_open() */
>   	outopenp = &ff->args->open_outarg;
>   	args.out_args[1].size = sizeof(*outopenp);
>   	args.out_args[1].value = outopenp;
>   
> +	if (fm->fc->lookup_handle) {
> +		fi = get_fuse_inode(dir);
> +		args.out_argvar = true;
> +		args.out_argvar_idx = 0;
> +		if (fi->fh) {
> +			args.in_numargs = 3;
> +			args.in_args[2].size = sizeof(*fi->fh) + fi->fh->size;
> +			args.in_args[2].value = fi->fh;
> +		}
> +	}
> +
>   	err = get_create_ext(idmap, &args, dir, entry, mode);
>   	if (err)
>   		goto out_free_outentry;
>   
>   	err = fuse_simple_idmap_request(idmap, fm, &args);
>   	free_ext_value(&args);
> -	if (err)
> +	if (err < 0) // XXX err = size if args->out_argvar = true
>   		goto out_free_outentry;
>   
>   	err = -EIO;
>   	if (!S_ISREG(outentry->attr.mode) || invalid_nodeid(outentry->nodeid) ||
> +	    fuse_invalid_file_handle(fm->fc, &outentry->fh) ||
>   	    fuse_invalid_attr(&outentry->attr))
>   		goto out_free_outentry;
>   
> @@ -714,7 +747,8 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
>   	ff->nodeid = outentry->nodeid;
>   	ff->open_flags = outopenp->open_flags;
>   	inode = fuse_iget(dir->i_sb, outentry->nodeid, outentry->generation,
> -			  &outentry->attr, ATTR_TIMEOUT(outentry), 0, 0);
> +			  &outentry->attr, ATTR_TIMEOUT(outentry), 0, 0,
> +			  &outentry->fh);
>   	if (!inode) {
>   		flags &= ~(O_CREAT | O_EXCL | O_TRUNC);
>   		fuse_sync_release(NULL, ff, flags);
> @@ -830,9 +864,22 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun
>   
>   	args->nodeid = get_node_id(dir);
>   	args->out_numargs = 1;
> -	args->out_args[0].size = sizeof(*outarg);
> +	args->out_args[0].size = sizeof(*outarg) + outarg->fh.size;
>   	args->out_args[0].value = outarg;
>   
> +	if (fm->fc->lookup_handle) {
> +		struct fuse_inode *fi = get_fuse_inode(dir);
> +		int idx = args->in_numargs;
> +
> +		args->out_argvar = true;
> +		args->out_argvar_idx = 0;
> +		if (fi->fh && !WARN_ON_ONCE(idx >= 4)) {		
> +			args->in_args[idx].size = sizeof(*fi->fh) + fi->fh->size;
> +			args->in_args[idx].value = fi->fh;
> +			args->in_numargs++;
> +		}
> +	}
> +
>   	if (args->opcode != FUSE_LINK) {
>   		err = get_create_ext(idmap, args, dir, entry, mode);
>   		if (err)
> @@ -841,18 +888,20 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun
>   
>   	err = fuse_simple_idmap_request(idmap, fm, args);
>   	free_ext_value(args);
> -	if (err)
> +	if (err < 0) // XXX err = size if args->out_argvar = true
>   		goto out_free_outarg;
>   
>   	err = -EIO;
> -	if (invalid_nodeid(outarg->nodeid) || fuse_invalid_attr(&outarg->attr))
> +	if (invalid_nodeid(outarg->nodeid) || fuse_invalid_attr(&outarg->attr) ||
> +	    fuse_invalid_file_handle(fm->fc, &outarg->fh))
>   		goto out_free_outarg;
>   
>   	if ((outarg->attr.mode ^ mode) & S_IFMT)
>   		goto out_free_outarg;
>   
>   	inode = fuse_iget(dir->i_sb, outarg->nodeid, outarg->generation,
> -			  &outarg->attr, ATTR_TIMEOUT(outarg), 0, 0);
> +			  &outarg->attr, ATTR_TIMEOUT(outarg), 0, 0,
> +			  &outarg->fh);
>   	if (!inode) {
>   		fuse_queue_forget(fm->fc, forget, outarg->nodeid, 1);
>   		kfree(outarg);
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index fad05fae7e54..d0f3c81b5612 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -216,6 +216,8 @@ struct fuse_inode {
>   	 * so preserve the blocksize specified by the server.
>   	 */
>   	u8 cached_i_blkbits;
> +
> +	struct fuse_file_handle *fh;
>   };
>   
>   /** FUSE inode state bits */
> @@ -1067,6 +1069,26 @@ static inline int invalid_nodeid(u64 nodeid)
>   	return !nodeid || nodeid == FUSE_ROOT_ID;
>   }
>   
> +static inline bool fuse_invalid_file_handle(struct fuse_conn *fc,
> +					    struct fuse_file_handle *handle)
> +{
> +	if (!fc->lookup_handle)
> +		return false;
> +
> +	return !handle->size || (handle->size >= FUSE_MAX_HANDLE_SZ);
> +}
> +
> +static inline bool fuse_file_handle_is_equal(struct fuse_conn *fc,
> +					     struct fuse_file_handle *fh1,
> +					     struct fuse_file_handle *fh2)
> +{
> +	if (!fc->lookup_handle || !fh2->size || // XXX more OPs without handle
> +	    ((fh1->size == fh2->size) &&
> +	     (!memcmp(fh1->handle, fh2->handle, fh1->size))))
> +		return true;
> +	return false;
> +}
> +
>   static inline u64 fuse_get_attr_version(struct fuse_conn *fc)
>   {
>   	return atomic64_read(&fc->attr_version);
> @@ -1098,7 +1120,10 @@ static inline struct fuse_entry_out *fuse_entry_out_alloc(struct fuse_conn *fc)
>   {
>   	struct fuse_entry_out *entryout;
>   
> -	entryout = kzalloc(sizeof(*entryout), GFP_KERNEL_ACCOUNT);
> +	entryout = kzalloc(sizeof(*entryout) + fc->max_handle_sz,
> +			   GFP_KERNEL_ACCOUNT);
> +	if (entryout)
> +		entryout->fh.size = fc->max_handle_sz;
>   
>   	return entryout;
>   }
> @@ -1145,10 +1170,11 @@ extern const struct dentry_operations fuse_dentry_operations;
>   struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
>   			int generation, struct fuse_attr *attr,
>   			u64 attr_valid, u64 attr_version,
> -			u64 evict_ctr);
> +			u64 evict_ctr, struct fuse_file_handle *fh);
>   
> -int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name,
> -		     struct fuse_entry_out *outarg, struct inode **inode);
> +int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct inode *dir,
> +		     const struct qstr *name, struct fuse_entry_out *outarg,
> +		     struct inode **inode);
>   
>   /**
>    * Send FORGET command
> diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
> index bc84e7ed1e3d..f565f7e8118d 100644
> --- a/fs/fuse/inode.c
> +++ b/fs/fuse/inode.c
> @@ -95,6 +95,25 @@ static struct fuse_submount_lookup *fuse_alloc_submount_lookup(void)
>   	return NULL;
>   }
>   
> +/*
> + * XXX postpone this allocation and later use the real size instead of max
> + */
> +static bool fuse_inode_handle_alloc(struct super_block *sb,
> +				    struct fuse_inode *fi)
> +{
> +	struct fuse_conn *fc = get_fuse_conn_super(sb);
> +
> +	fi->fh = NULL;
> +	if (fc->lookup_handle) {
> +		fi->fh = kzalloc(sizeof(*fi->fh) + fc->max_handle_sz,
> +				 GFP_KERNEL_ACCOUNT);
> +		if (!fi->fh)
> +			return false;
> +	}
> +
> +	return true;
> +}
> +
>   static struct inode *fuse_alloc_inode(struct super_block *sb)
>   {
>   	struct fuse_inode *fi;
> @@ -120,8 +139,15 @@ static struct inode *fuse_alloc_inode(struct super_block *sb)
>   	if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
>   		fuse_inode_backing_set(fi, NULL);
>   
> +	if (!fuse_inode_handle_alloc(sb, fi))
> +		goto out_free_dax;
> +
>   	return &fi->inode;
>   
> +out_free_dax:
> +#ifdef CONFIG_FUSE_DAX
> +	kfree(fi->dax);
> +#endif
>   out_free_forget:
>   	kfree(fi->forget);
>   out_free:
> @@ -132,6 +158,7 @@ static struct inode *fuse_alloc_inode(struct super_block *sb)
>   static void fuse_free_inode(struct inode *inode)
>   {
>   	struct fuse_inode *fi = get_fuse_inode(inode);
> +	struct fuse_conn *fc = get_fuse_conn(inode);
>   
>   	mutex_destroy(&fi->mutex);
>   	kfree(fi->forget);
> @@ -141,6 +168,9 @@ static void fuse_free_inode(struct inode *inode)
>   	if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
>   		fuse_backing_put(fuse_inode_backing(fi));
>   
> +	if (fc->lookup_handle)
> +		kfree(fi->fh);
> +
>   	kmem_cache_free(fuse_inode_cachep, fi);
>   }
>   
> @@ -465,7 +495,7 @@ static int fuse_inode_set(struct inode *inode, void *_nodeidp)
>   struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
>   			int generation, struct fuse_attr *attr,
>   			u64 attr_valid, u64 attr_version,
> -			u64 evict_ctr)
> +			u64 evict_ctr, struct fuse_file_handle *fh)
>   {
>   	struct inode *inode;
>   	struct fuse_inode *fi;
> @@ -505,6 +535,30 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
>   	if (!inode)
>   		return NULL;
>   
> +	fi = get_fuse_inode(inode);
> +	if (fc->lookup_handle) {
> +		if ((fh == NULL) && (nodeid != FUSE_ROOT_ID)) {
> +			pr_err("NULL file handle for nodeid %llu\n", nodeid);
> +			iput(inode);
> +			return NULL;

Hmm, so there are conditions like "if (fi && fi->fh) {" in lookup and I
was thinking "nice, fuse-server can decide to skip the fh for some
inodes like FUSE_ROOT_ID. But now it gets forbidden here. In combination
with the other comment in fuse_inode_handle_alloc(), could be allocate
here to the needed size and allow fuse-server to not send the handle
for some files?

Thanks,
Bernd
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Luis Henriques 1 month, 3 weeks ago
On Mon, Dec 15 2025, Bernd Schubert wrote:

> On 12/12/25 19:12, Luis Henriques wrote:
>> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
>> an extra inarg: the file handle for the parent directory (if it is
>> available).  Also, because fuse_entry_out now has a extra variable size
>> struct (the actual handle), it also sets the out_argvar flag to true.
>> 
>> Most of the other modifications in this patch are a fallout from these
>> changes: because fuse_entry_out has been modified to include a variable size
>> struct, every operation that receives such a parameter have to take this
>> into account:
>> 
>>    CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
>> 
>> Signed-off-by: Luis Henriques <luis@igalia.com>
>> ---
>>   fs/fuse/dev.c             | 16 +++++++
>>   fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
>>   fs/fuse/fuse_i.h          | 34 +++++++++++++--
>>   fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
>>   fs/fuse/readdir.c         | 10 ++---
>>   include/uapi/linux/fuse.h |  8 ++++
>>   6 files changed, 189 insertions(+), 35 deletions(-)
>> 
>> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
>> index 629e8a043079..fc6acf45ae27 100644
>> --- a/fs/fuse/dev.c
>> +++ b/fs/fuse/dev.c
>> @@ -606,6 +606,22 @@ static void fuse_adjust_compat(struct fuse_conn *fc, struct fuse_args *args)
>>   	if (fc->minor < 4 && args->opcode == FUSE_STATFS)
>>   		args->out_args[0].size = FUSE_COMPAT_STATFS_SIZE;
>>   
>> +	if (fc->minor < 45) {
>
> Could we use fc->lookup_handle here? Numbers are hard with backports

To be honest, I'm not sure this code is correct.  I just followed the
pattern.  I'll need to dedicate some more time looking into this,
specially because the READDIRPLUS op handling is still TBD.

<snip>

>> @@ -505,6 +535,30 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
>>   	if (!inode)
>>   		return NULL;
>>   
>> +	fi = get_fuse_inode(inode);
>> +	if (fc->lookup_handle) {
>> +		if ((fh == NULL) && (nodeid != FUSE_ROOT_ID)) {
>> +			pr_err("NULL file handle for nodeid %llu\n", nodeid);
>> +			iput(inode);
>> +			return NULL;
>
> Hmm, so there are conditions like "if (fi && fi->fh) {" in lookup and I
> was thinking "nice, fuse-server can decide to skip the fh for some
> inodes like FUSE_ROOT_ID. But now it gets forbidden here. In combination
> with the other comment in fuse_inode_handle_alloc(), could be allocate
> here to the needed size and allow fuse-server to not send the handle
> for some files?

I'm not sure the code is consistent with this regard, but here I'm doing
exactly that: allowing the fh to be NULL only for FUSE_ROOT_ID.  Or did I
misunderstood your comment?

Regarding the comment in fuse_inode_handle_alloc(), I believe I'll need to
rethink about it anyway, specially after some of the comments I've already
seen from Miklos (which I'm still going through).

Cheers,
-- 
Luís
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Bernd Schubert 1 month, 3 weeks ago

On 12/16/25 12:48, Luis Henriques wrote:
> On Mon, Dec 15 2025, Bernd Schubert wrote:
> 
>> On 12/12/25 19:12, Luis Henriques wrote:
>>> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
>>> an extra inarg: the file handle for the parent directory (if it is
>>> available).  Also, because fuse_entry_out now has a extra variable size
>>> struct (the actual handle), it also sets the out_argvar flag to true.
>>>
>>> Most of the other modifications in this patch are a fallout from these
>>> changes: because fuse_entry_out has been modified to include a variable size
>>> struct, every operation that receives such a parameter have to take this
>>> into account:
>>>
>>>    CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
>>>
>>> Signed-off-by: Luis Henriques <luis@igalia.com>
>>> ---
>>>   fs/fuse/dev.c             | 16 +++++++
>>>   fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
>>>   fs/fuse/fuse_i.h          | 34 +++++++++++++--
>>>   fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
>>>   fs/fuse/readdir.c         | 10 ++---
>>>   include/uapi/linux/fuse.h |  8 ++++
>>>   6 files changed, 189 insertions(+), 35 deletions(-)
>>>
>>> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
>>> index 629e8a043079..fc6acf45ae27 100644
>>> --- a/fs/fuse/dev.c
>>> +++ b/fs/fuse/dev.c
>>> @@ -606,6 +606,22 @@ static void fuse_adjust_compat(struct fuse_conn *fc, struct fuse_args *args)
>>>   	if (fc->minor < 4 && args->opcode == FUSE_STATFS)
>>>   		args->out_args[0].size = FUSE_COMPAT_STATFS_SIZE;
>>>   
>>> +	if (fc->minor < 45) {
>>
>> Could we use fc->lookup_handle here? Numbers are hard with backports
> 
> To be honest, I'm not sure this code is correct.  I just followed the
> pattern.  I'll need to dedicate some more time looking into this,
> specially because the READDIRPLUS op handling is still TBD.
> 
> <snip>
> 
>>> @@ -505,6 +535,30 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
>>>   	if (!inode)
>>>   		return NULL;
>>>   
>>> +	fi = get_fuse_inode(inode);
>>> +	if (fc->lookup_handle) {
>>> +		if ((fh == NULL) && (nodeid != FUSE_ROOT_ID)) {
>>> +			pr_err("NULL file handle for nodeid %llu\n", nodeid);
>>> +			iput(inode);
>>> +			return NULL;
>>
>> Hmm, so there are conditions like "if (fi && fi->fh) {" in lookup and I
>> was thinking "nice, fuse-server can decide to skip the fh for some
>> inodes like FUSE_ROOT_ID. But now it gets forbidden here. In combination
>> with the other comment in fuse_inode_handle_alloc(), could be allocate
>> here to the needed size and allow fuse-server to not send the handle
>> for some files?
> 
> I'm not sure the code is consistent with this regard, but here I'm doing
> exactly that: allowing the fh to be NULL only for FUSE_ROOT_ID.  Or did I
> misunderstood your comment?

Sorry for late reply.

Yeah sorry, what I meant is that the file handle size might be different
for any of the inodes, in between 0 and max-size for any of the inodes?


Thanks,
Bernd
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Luis Henriques 1 month, 3 weeks ago
On Wed, Dec 17 2025, Bernd Schubert wrote:

> On 12/16/25 12:48, Luis Henriques wrote:
>> On Mon, Dec 15 2025, Bernd Schubert wrote:
>> 
>>> On 12/12/25 19:12, Luis Henriques wrote:
>>>> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
>>>> an extra inarg: the file handle for the parent directory (if it is
>>>> available).  Also, because fuse_entry_out now has a extra variable size
>>>> struct (the actual handle), it also sets the out_argvar flag to true.
>>>>
>>>> Most of the other modifications in this patch are a fallout from these
>>>> changes: because fuse_entry_out has been modified to include a variable size
>>>> struct, every operation that receives such a parameter have to take this
>>>> into account:
>>>>
>>>>    CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
>>>>
>>>> Signed-off-by: Luis Henriques <luis@igalia.com>
>>>> ---
>>>>   fs/fuse/dev.c             | 16 +++++++
>>>>   fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
>>>>   fs/fuse/fuse_i.h          | 34 +++++++++++++--
>>>>   fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
>>>>   fs/fuse/readdir.c         | 10 ++---
>>>>   include/uapi/linux/fuse.h |  8 ++++
>>>>   6 files changed, 189 insertions(+), 35 deletions(-)
>>>>
>>>> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
>>>> index 629e8a043079..fc6acf45ae27 100644
>>>> --- a/fs/fuse/dev.c
>>>> +++ b/fs/fuse/dev.c
>>>> @@ -606,6 +606,22 @@ static void fuse_adjust_compat(struct fuse_conn *fc, struct fuse_args *args)
>>>>   	if (fc->minor < 4 && args->opcode == FUSE_STATFS)
>>>>   		args->out_args[0].size = FUSE_COMPAT_STATFS_SIZE;
>>>>   
>>>> +	if (fc->minor < 45) {
>>>
>>> Could we use fc->lookup_handle here? Numbers are hard with backports
>> 
>> To be honest, I'm not sure this code is correct.  I just followed the
>> pattern.  I'll need to dedicate some more time looking into this,
>> specially because the READDIRPLUS op handling is still TBD.
>> 
>> <snip>
>> 
>>>> @@ -505,6 +535,30 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
>>>>   	if (!inode)
>>>>   		return NULL;
>>>>   
>>>> +	fi = get_fuse_inode(inode);
>>>> +	if (fc->lookup_handle) {
>>>> +		if ((fh == NULL) && (nodeid != FUSE_ROOT_ID)) {
>>>> +			pr_err("NULL file handle for nodeid %llu\n", nodeid);
>>>> +			iput(inode);
>>>> +			return NULL;
>>>
>>> Hmm, so there are conditions like "if (fi && fi->fh) {" in lookup and I
>>> was thinking "nice, fuse-server can decide to skip the fh for some
>>> inodes like FUSE_ROOT_ID. But now it gets forbidden here. In combination
>>> with the other comment in fuse_inode_handle_alloc(), could be allocate
>>> here to the needed size and allow fuse-server to not send the handle
>>> for some files?
>> 
>> I'm not sure the code is consistent with this regard, but here I'm doing
>> exactly that: allowing the fh to be NULL only for FUSE_ROOT_ID.  Or did I
>> misunderstood your comment?
>
> Sorry for late reply.
>
> Yeah sorry, what I meant is that the file handle size might be different
> for any of the inodes, in between 0 and max-size for any of the inodes?

So, as per the other discussion of this patch, Miklos was suggesting the
maximum size negotiation could be totally removed, and the allocation
would be done on-demand [1].  (But probably still keeping an hard limit on
MAX_HANDLE_SZ.)

In that case, different inodes could indeed have different file handle
sizes, defined by the server.  Which would be nice, I guess.

But as I also mentioned in other places, there are already a bunch of
changes, and I need going back to the drawing board :-)

[1] https://lore.kernel.org/all/CAJfpegszP+2XA=vADK4r09KU30BQd-r9sNu2Dog88yLG8iV7WQ@mail.gmail.com

Cheers,
-- 
Luís
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Amir Goldstein 1 month, 3 weeks ago
On Tue, Dec 16, 2025 at 12:48 PM Luis Henriques <luis@igalia.com> wrote:
>
> On Mon, Dec 15 2025, Bernd Schubert wrote:
>
> > On 12/12/25 19:12, Luis Henriques wrote:
> >> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
> >> an extra inarg: the file handle for the parent directory (if it is
> >> available).  Also, because fuse_entry_out now has a extra variable size
> >> struct (the actual handle), it also sets the out_argvar flag to true.
> >>
> >> Most of the other modifications in this patch are a fallout from these
> >> changes: because fuse_entry_out has been modified to include a variable size
> >> struct, every operation that receives such a parameter have to take this
> >> into account:
> >>
> >>    CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
> >>
> >> Signed-off-by: Luis Henriques <luis@igalia.com>
> >> ---
> >>   fs/fuse/dev.c             | 16 +++++++
> >>   fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
> >>   fs/fuse/fuse_i.h          | 34 +++++++++++++--
> >>   fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
> >>   fs/fuse/readdir.c         | 10 ++---
> >>   include/uapi/linux/fuse.h |  8 ++++
> >>   6 files changed, 189 insertions(+), 35 deletions(-)
> >>
> >> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
> >> index 629e8a043079..fc6acf45ae27 100644
> >> --- a/fs/fuse/dev.c
> >> +++ b/fs/fuse/dev.c
> >> @@ -606,6 +606,22 @@ static void fuse_adjust_compat(struct fuse_conn *fc, struct fuse_args *args)
> >>      if (fc->minor < 4 && args->opcode == FUSE_STATFS)
> >>              args->out_args[0].size = FUSE_COMPAT_STATFS_SIZE;
> >>
> >> +    if (fc->minor < 45) {
> >
> > Could we use fc->lookup_handle here? Numbers are hard with backports
>
> To be honest, I'm not sure this code is correct.  I just followed the
> pattern.  I'll need to dedicate some more time looking into this,
> specially because the READDIRPLUS op handling is still TBD.
>
> <snip>
>
> >> @@ -505,6 +535,30 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
> >>      if (!inode)
> >>              return NULL;
> >>
> >> +    fi = get_fuse_inode(inode);
> >> +    if (fc->lookup_handle) {
> >> +            if ((fh == NULL) && (nodeid != FUSE_ROOT_ID)) {
> >> +                    pr_err("NULL file handle for nodeid %llu\n", nodeid);
> >> +                    iput(inode);
> >> +                    return NULL;
> >
> > Hmm, so there are conditions like "if (fi && fi->fh) {" in lookup and I
> > was thinking "nice, fuse-server can decide to skip the fh for some
> > inodes like FUSE_ROOT_ID. But now it gets forbidden here. In combination
> > with the other comment in fuse_inode_handle_alloc(), could be allocate
> > here to the needed size and allow fuse-server to not send the handle
> > for some files?
>
> I'm not sure the code is consistent with this regard, but here I'm doing
> exactly that: allowing the fh to be NULL only for FUSE_ROOT_ID.  Or did I
> misunderstood your comment?
>

root inode is a special case.
The NFS server also does not encode the file handle for export root as
far as a I know
it just sends the special file handle type FILEID_ROOT to describe the
root inode
without any blob unique, so FUSE can do the same.

There is not much point in "looking up" the root inode neither by nodeid
nor by handle. unless is for making the code more generic.

I am not sure if FUSE server restart is supposed to revalidate the
root inode by file handle. That's kind of an administrative question about
the feature. My feeling is that it is not needed.

Thanks,
Amir.
Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
Posted by Luis Henriques 1 month, 3 weeks ago
On Wed, Dec 17 2025, Amir Goldstein wrote:

> On Tue, Dec 16, 2025 at 12:48 PM Luis Henriques <luis@igalia.com> wrote:
>>
>> On Mon, Dec 15 2025, Bernd Schubert wrote:
>>
>> > On 12/12/25 19:12, Luis Henriques wrote:
>> >> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
>> >> an extra inarg: the file handle for the parent directory (if it is
>> >> available).  Also, because fuse_entry_out now has a extra variable size
>> >> struct (the actual handle), it also sets the out_argvar flag to true.
>> >>
>> >> Most of the other modifications in this patch are a fallout from these
>> >> changes: because fuse_entry_out has been modified to include a variable size
>> >> struct, every operation that receives such a parameter have to take this
>> >> into account:
>> >>
>> >>    CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
>> >>
>> >> Signed-off-by: Luis Henriques <luis@igalia.com>
>> >> ---
>> >>   fs/fuse/dev.c             | 16 +++++++
>> >>   fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
>> >>   fs/fuse/fuse_i.h          | 34 +++++++++++++--
>> >>   fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
>> >>   fs/fuse/readdir.c         | 10 ++---
>> >>   include/uapi/linux/fuse.h |  8 ++++
>> >>   6 files changed, 189 insertions(+), 35 deletions(-)
>> >>
>> >> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
>> >> index 629e8a043079..fc6acf45ae27 100644
>> >> --- a/fs/fuse/dev.c
>> >> +++ b/fs/fuse/dev.c
>> >> @@ -606,6 +606,22 @@ static void fuse_adjust_compat(struct fuse_conn *fc, struct fuse_args *args)
>> >>      if (fc->minor < 4 && args->opcode == FUSE_STATFS)
>> >>              args->out_args[0].size = FUSE_COMPAT_STATFS_SIZE;
>> >>
>> >> +    if (fc->minor < 45) {
>> >
>> > Could we use fc->lookup_handle here? Numbers are hard with backports
>>
>> To be honest, I'm not sure this code is correct.  I just followed the
>> pattern.  I'll need to dedicate some more time looking into this,
>> specially because the READDIRPLUS op handling is still TBD.
>>
>> <snip>
>>
>> >> @@ -505,6 +535,30 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
>> >>      if (!inode)
>> >>              return NULL;
>> >>
>> >> +    fi = get_fuse_inode(inode);
>> >> +    if (fc->lookup_handle) {
>> >> +            if ((fh == NULL) && (nodeid != FUSE_ROOT_ID)) {
>> >> +                    pr_err("NULL file handle for nodeid %llu\n", nodeid);
>> >> +                    iput(inode);
>> >> +                    return NULL;
>> >
>> > Hmm, so there are conditions like "if (fi && fi->fh) {" in lookup and I
>> > was thinking "nice, fuse-server can decide to skip the fh for some
>> > inodes like FUSE_ROOT_ID. But now it gets forbidden here. In combination
>> > with the other comment in fuse_inode_handle_alloc(), could be allocate
>> > here to the needed size and allow fuse-server to not send the handle
>> > for some files?
>>
>> I'm not sure the code is consistent with this regard, but here I'm doing
>> exactly that: allowing the fh to be NULL only for FUSE_ROOT_ID.  Or did I
>> misunderstood your comment?
>>
>
> root inode is a special case.
> The NFS server also does not encode the file handle for export root as
> far as a I know
> it just sends the special file handle type FILEID_ROOT to describe the
> root inode
> without any blob unique, so FUSE can do the same.

OK, that makes sense.

> There is not much point in "looking up" the root inode neither by nodeid
> nor by handle. unless is for making the code more generic.
>
> I am not sure if FUSE server restart is supposed to revalidate the
> root inode by file handle. That's kind of an administrative question about
> the feature. My feeling is that it is not needed.

Thanks, Amir.  Looks like there's a lot in these v2 review comments that
I'll need to go through.  I'll try to put everything together and see what
I can cook for v3.

Cheers,
-- 
Luís