ANDROID: fuse-bpf: Follow mounts in lookups

Bug: 292925770
Bug: 326149926
Test: fuse_test run. The following steps on Android also now pass:

	Create /data/123 and /data/media/0/Android/data/45 directories
	Mount /data/123 directory to /data/media/0/Android/data/45 directory
	Create 1.txt under the /data/123 directory

	File 1.txt should appear in /storage/emulated/0/Android/data/45

Bug: 326149926

Change-Id: I1fe27d743ca2981e624a9aa87d9ab6deb313aadc
Signed-off-by: Paul Lawrence <paullawrence@google.com>
(cherry picked from commit 613d8368e39f4cd67f2ad629ebb870d3e3b4bc6e)
diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
index e16457c..6ca74987 100644
--- a/fs/fuse/backing.c
+++ b/fs/fuse/backing.c
@@ -1117,7 +1117,6 @@ int fuse_lookup_backing(struct fuse_bpf_args *fa, struct inode *dir,
 	struct kstat stat;
 	int err;
 
-	/* TODO this will not handle lookups over mount points */
 	inode_lock_nested(dir_backing_inode, I_MUTEX_PARENT);
 	backing_entry = lookup_one_len(entry->d_name.name, dir_backing_entry,
 					strlen(entry->d_name.name));
@@ -1136,16 +1135,22 @@ int fuse_lookup_backing(struct fuse_bpf_args *fa, struct inode *dir,
 		return 0;
 	}
 
+	err = follow_down(&fuse_entry->backing_path);
+	if (err)
+		goto err_out;
+
 	err = vfs_getattr(&fuse_entry->backing_path, &stat,
 				  STATX_BASIC_STATS, 0);
-	if (err) {
-		path_put_init(&fuse_entry->backing_path);
-		return err;
-	}
+	if (err)
+		goto err_out;
 
 	fuse_stat_to_attr(get_fuse_conn(dir),
 			  backing_entry->d_inode, &stat, &feo->attr);
 	return 0;
+
+err_out:
+	path_put_init(&fuse_entry->backing_path);
+	return err;
 }
 
 int fuse_handle_backing(struct fuse_entry_bpf *feb, struct inode **backing_inode,
diff --git a/tools/testing/selftests/filesystems/fuse/bpf_loader.c b/tools/testing/selftests/filesystems/fuse/bpf_loader.c
index 5bf26ea..94f884c 100644
--- a/tools/testing/selftests/filesystems/fuse/bpf_loader.c
+++ b/tools/testing/selftests/filesystems/fuse/bpf_loader.c
@@ -394,6 +394,29 @@ int s_rename(struct s oldpathname, struct s newpathname)
 	return res;
 }
 
+int s_mount(struct s source, struct s target, struct s filesystem,
+	    unsigned long mountflags, struct s data)
+{
+	int res;
+
+	res = mount(source.s, target.s, filesystem.s, mountflags, data.s);
+	free(source.s);
+	free(target.s);
+	free(filesystem.s);
+	free(data.s);
+
+	return res;
+}
+
+int s_umount(struct s target)
+{
+	int res;
+
+	res = umount(target.s);
+	free(target.s);
+	return res;
+}
+
 int s_fuse_attr(struct s pathname, struct fuse_attr *fuse_attr_out)
 {
 
@@ -574,7 +597,10 @@ static int mount_fuse_maybe_init(const char *mount_dir, int bpf_fd, int dir_fd,
 		}));
 	}
 
-	*fuse_dev_ptr = fuse_dev;
+	if (fuse_dev_ptr)
+		*fuse_dev_ptr = fuse_dev;
+	else
+		TESTSYSCALL(close(fuse_dev));
 	fuse_dev = -1;
 	result = TEST_SUCCESS;
 out:
diff --git a/tools/testing/selftests/filesystems/fuse/fuse_test.c b/tools/testing/selftests/filesystems/fuse/fuse_test.c
index 528595a..ad24ed4 100644
--- a/tools/testing/selftests/filesystems/fuse/fuse_test.c
+++ b/tools/testing/selftests/filesystems/fuse/fuse_test.c
@@ -2114,6 +2114,50 @@ static int bpf_test_readahead(const char *mount_dir)
 	return result;
 }
 
+/**
+ * Test that fuse passthrough correctly traverses a mount point on the lower fs
+ */
+static int bpf_test_follow_mounts(const char *mount_dir)
+{
+	const char *bind_src = "bind_src";
+	const char *bind_dst = "bind_dst";
+	const char *file = "file";
+	int fd = -1;
+	int src_fd = -1;
+	int result = TEST_FAILURE;
+
+	TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(bind_src)), 0777));
+	TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(bind_dst)), 0777));
+	TEST(fd = s_creat(s_pathn(3, s(ft_src), s(bind_src), s(file)), 0777),
+	     fd != -1);
+	TESTSYSCALL(close(fd));
+	fd = -1;
+	TESTSYSCALL(s_mount(s_path(s(ft_src), s(bind_src)),
+			    s_path(s(ft_src), s(bind_dst)),
+			    s(NULL), MS_BIND, s(NULL)));
+	TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
+	     src_fd != -1);
+	TESTEQUAL(mount_fuse_no_init(mount_dir, -1, src_fd, NULL), 0);
+	TEST(fd = s_open(s_pathn(3, s(mount_dir), s(bind_src), s(file)),
+			 O_RDONLY),
+	     fd != -1);
+	TESTSYSCALL(close(fd));
+	fd = -1;
+	TEST(fd = s_open(s_pathn(3, s(mount_dir), s(bind_dst), s(file)),
+			 O_RDONLY),
+	     fd != -1);
+	TESTSYSCALL(close(fd));
+	fd = -1;
+
+	result = TEST_SUCCESS;
+out:
+	umount(mount_dir);
+	close(src_fd);
+	s_umount(s_path(s(ft_src), s(bind_dst)));
+	close(fd);
+	return result;
+}
+
 static void parse_range(const char *ranges, bool *run_test, size_t tests)
 {
 	size_t i;
@@ -2244,6 +2288,7 @@ int main(int argc, char *argv[])
 		MAKE_TEST(bpf_test_create_and_remove_bpf),
 		MAKE_TEST(bpf_test_mkdir_and_remove_bpf),
 		MAKE_TEST(bpf_test_readahead),
+		MAKE_TEST(bpf_test_follow_mounts),
 	};
 #undef MAKE_TEST
 
diff --git a/tools/testing/selftests/filesystems/fuse/test_fuse.h b/tools/testing/selftests/filesystems/fuse/test_fuse.h
index 69dadc9..e62e2ee 100644
--- a/tools/testing/selftests/filesystems/fuse/test_fuse.h
+++ b/tools/testing/selftests/filesystems/fuse/test_fuse.h
@@ -64,6 +64,9 @@ int s_setxattr(struct s pathname, const char name[], const void *value,
 	       size_t size, int flags);
 int s_removexattr(struct s pathname, const char name[]);
 int s_rename(struct s oldpathname, struct s newpathname);
+int s_mount(struct s source, struct s target, struct s filesystem,
+	    unsigned long mountflags, struct s data);
+int s_umount(struct s target);
 
 struct s tracing_folder(void);
 int tracing_on(void);