Support fuse-bpf

Add struct fuse_args
Introduce bpf_arg in fuse_reply_entry

Currently, the opcode filters are zeroed to preserve the libfuse
compatibility.

Bug: 202785178
Test: Along with other changes, file
/sys/fs/bpf/prog_fuse_media_fuse_media
appears.
Signed-off-by: Alessio Balsini <balsini@google.com>
Signed-off-by: Paul Lawrence <paullawrence@google.com>
Change-Id: I38cd80d702813f4e2b45d69af2ca451fca0984c6
diff --git a/include/fuse_kernel.h b/include/fuse_kernel.h
index bd8794e..2c8e141 100644
--- a/include/fuse_kernel.h
+++ b/include/fuse_kernel.h
@@ -457,6 +457,17 @@
 	struct fuse_attr attr;
 };
 
+#define FUSE_ACTION_KEEP        0
+#define FUSE_ACTION_REMOVE      1
+#define FUSE_ACTION_REPLACE     2
+
+struct fuse_entry_bpf_out {
+        uint64_t        backing_action;
+        uint64_t        backing_fd;
+        uint64_t        bpf_action;
+        uint64_t        bpf_fd;
+};
+
 struct fuse_forget_in {
 	uint64_t	nlookup;
 };
@@ -859,4 +870,51 @@
 	uint64_t	flags;
 };
 
+/** Export fuse_args only for bpf */
+#ifdef __KERNEL__
+struct fuse_mount;
+
+/** One input argument of a request */
+struct fuse_in_arg {
+  unsigned size;
+  const void *value;
+};
+
+/** One output argument of a request */
+struct fuse_arg {
+  unsigned size;
+  void *value;
+};
+
+struct fuse_args {
+  uint64_t nodeid;
+  uint32_t opcode;
+  unsigned short in_numargs;
+  unsigned short out_numargs;
+  int force:1;
+  int noreply:1;
+  int nocreds:1;
+  int in_pages:1;
+  int out_pages:1;
+  int out_argvar:1;
+  int page_zeroing:1;
+  int page_replace:1;
+  int may_block:1;
+  struct fuse_in_arg in_args[5];
+  struct fuse_arg out_args[3];
+  void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error);
+
+  /* Path used for completing d_canonical_path */
+  struct path *canonical_path;
+};
+#endif
+
+#define FUSE_BPF_USER_FILTER    1
+#define FUSE_BPF_BACKING        2
+#define FUSE_BPF_POST_FILTER    4
+
+#define FUSE_OPCODE_FILTER      0x0ffff
+#define FUSE_PREFILTER          0x10000
+#define FUSE_POSTFILTER         0x20000
+
 #endif /* _LINUX_FUSE_H */
diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h
index e916112..d203157 100644
--- a/include/fuse_lowlevel.h
+++ b/include/fuse_lowlevel.h
@@ -98,6 +98,10 @@
 	    that come through the kernel, this should be set to a very
 	    large value. */
 	double entry_timeout;
+        uint64_t        backing_action;
+        uint64_t        backing_fd;
+        uint64_t        bpf_action;
+        uint64_t        bpf_fd;
 };
 
 /**
diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
index a4a4df0..459fca5 100644
--- a/lib/fuse_lowlevel.c
+++ b/lib/fuse_lowlevel.c
@@ -400,20 +400,45 @@
 		arg->open_flags |= FOPEN_NONSEEKABLE;
 }
 
-int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
-{
-	struct fuse_entry_out arg;
-	size_t size = req->se->conn.proto_minor < 9 ?
-		FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg);
+int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param* e) {
+    struct {
+        struct fuse_entry_out arg;
+        struct fuse_entry_bpf_out bpf_arg;
+    } __attribute__((packed)) arg_ext = {0};
 
-	/* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant
-	   negative entry */
-	if (!e->ino && req->se->conn.proto_minor < 4)
-		return fuse_reply_err(req, ENOENT);
+    struct fuse_entry_out arg;
+    struct fuse_entry_bpf_out bpf_arg;
+    size_t size;
+    int extended_args = e->bpf_action || bpf_arg.bpf_fd || e->backing_action || e->backing_fd;
 
-	memset(&arg, 0, sizeof(arg));
-	fill_entry(&arg, e);
-	return send_reply_ok(req, &arg, size);
+    if (extended_args) {
+        size = req->se->conn.proto_minor < 9 ? FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg_ext);
+    } else {
+        size = req->se->conn.proto_minor < 9 ? FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg);
+    }
+
+    /* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant
+       negative entry */
+    if (!e->ino && req->se->conn.proto_minor < 4) return fuse_reply_err(req, ENOENT);
+
+    memset(&arg, 0, sizeof(arg));
+
+    if (extended_args) {
+        memset(&bpf_arg, 0, sizeof(bpf_arg));
+
+        bpf_arg.bpf_action = e->bpf_action;
+        bpf_arg.bpf_fd = e->bpf_fd;
+        bpf_arg.backing_action = e->backing_action;
+        bpf_arg.backing_fd = e->backing_fd;
+
+        arg_ext.arg = arg;
+        arg_ext.bpf_arg = bpf_arg;
+
+        return send_reply_ok(req, &arg_ext, size);
+    } else {
+        fill_entry(&arg, e);
+        return send_reply_ok(req, &arg, size);
+    }
 }
 
 int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
@@ -2604,6 +2629,22 @@
 		return fuse_ll_ops[opcode].name;
 }
 
+static const char *opfiltername(int filter)
+{
+	switch (filter) {
+	case 0:
+		return "NONE";
+	case FUSE_PREFILTER:
+		return "FUSE_PREFILTER";
+	case FUSE_POSTFILTER:
+		return "FUSE_POSTFILTER";
+	case FUSE_PREFILTER | FUSE_POSTFILTER:
+		return "FUSE_PREFILTER | FUSE_POSTFILTER";
+	default:
+		return "???";
+	}
+}
+
 static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst,
 				  struct fuse_bufvec *src)
 {
@@ -2638,6 +2679,7 @@
 	void *mbuf = NULL;
 	int err;
 	int res;
+	int opcode_filter;
 
 	if (buf->flags & FUSE_BUF_IS_FD) {
 		if (buf->size < tmpbuf.buf[0].size)
@@ -2659,11 +2701,16 @@
 		in = buf->mem;
 	}
 
+	/* Cleanup opcode most significant bits used by FUSE BPF */
+	opcode_filter = in->opcode & ~FUSE_OPCODE_FILTER;
+	in->opcode &= FUSE_OPCODE_FILTER;
+
 	if (se->debug) {
 		fuse_log(FUSE_LOG_DEBUG,
-			"unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n",
+			"unique: %llu, opcode: %s (%i), opcode filter: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n",
 			(unsigned long long) in->unique,
 			opname((enum fuse_opcode) in->opcode), in->opcode,
+			opfiltername((enum fuse_opcode) opcode_filter), opcode_filter,
 			(unsigned long long) in->nodeid, buf->size, in->pid);
 	}