ANDROID: fuse-bpf: Fix readdir

Fuse uses generic_file_llseek, so we must account for that in readdir to
ensure we read from the correct offset in the lower filesystem.

Bug: 226655281
Test: generic/257, fuse_test
Signed-off-by: Daniel Rosenberg <drosen@google.com>
Change-Id: Ie752c1c645e95b7c03ef9497562758a5c42b514a
This commit is contained in:
Daniel Rosenberg
2022-03-23 16:43:25 -07:00
parent 68c9936883
commit 0c37c1459a
4 changed files with 49 additions and 19 deletions

View File

@@ -2189,7 +2189,7 @@ void *fuse_symlink_finalize(
int fuse_readdir_initialize(struct fuse_args *fa, struct fuse_read_io *frio,
struct file *file, struct dir_context *ctx,
bool *force_again, bool *allow_force)
bool *force_again, bool *allow_force, bool is_continued)
{
struct fuse_file *ff = file->private_data;
u8 *page = (u8 *)__get_free_page(GFP_KERNEL);
@@ -2259,9 +2259,35 @@ static int filldir(struct dir_context *ctx, const char *name, int namelen,
return 0;
}
static int parse_dirfile(char *buf, size_t nbytes, struct dir_context *ctx)
{
while (nbytes >= FUSE_NAME_OFFSET) {
struct fuse_dirent *dirent = (struct fuse_dirent *) buf;
size_t reclen = FUSE_DIRENT_SIZE(dirent);
if (!dirent->namelen || dirent->namelen > FUSE_NAME_MAX)
return -EIO;
if (reclen > nbytes)
break;
if (memchr(dirent->name, '/', dirent->namelen) != NULL)
return -EIO;
ctx->pos = dirent->off;
if (!dir_emit(ctx, dirent->name, dirent->namelen, dirent->ino,
dirent->type))
break;
buf += reclen;
nbytes -= reclen;
}
return 0;
}
int fuse_readdir_backing(struct fuse_args *fa,
struct file *file, struct dir_context *ctx,
bool *force_again, bool *allow_force)
bool *force_again, bool *allow_force, bool is_continued)
{
struct fuse_file *ff = file->private_data;
struct file *backing_dir = ff->backing_file;
@@ -2278,6 +2304,9 @@ int fuse_readdir_backing(struct fuse_args *fa,
if (!ec.addr)
return -ENOMEM;
if (!is_continued)
backing_dir->f_pos = file->f_pos;
err = iterate_dir(backing_dir, &ec.ctx);
if (ec.offset == 0)
*allow_force = false;
@@ -2290,18 +2319,19 @@ int fuse_readdir_backing(struct fuse_args *fa,
void *fuse_readdir_finalize(struct fuse_args *fa,
struct file *file, struct dir_context *ctx,
bool *force_again, bool *allow_force)
bool *force_again, bool *allow_force, bool is_continued)
{
int err = 0;
struct fuse_read_out *fro = fa->out_args[0].value;
struct fuse_file *ff = file->private_data;
struct file *backing_dir = ff->backing_file;
struct fuse_read_out *fro = fa->out_args[0].value;
int err = 0;
err = fuse_parse_dirfile(fa->out_args[1].value,
fa->out_args[1].size, file, ctx);
err = parse_dirfile(fa->out_args[1].value, fa->out_args[1].size, ctx);
*force_again = !!fro->again;
if (*force_again && !*allow_force)
err = -EINVAL;
ctx->pos = fro->offset;
backing_dir->f_pos = fro->offset;
free_page((unsigned long) fa->out_args[1].value);

View File

@@ -987,10 +987,6 @@ struct fuse_io_args {
void fuse_read_args_fill(struct fuse_io_args *ia, struct file *file, loff_t pos,
size_t count, int opcode);
int fuse_parse_dirfile(char *buf, size_t nbytes, struct file *file,
struct dir_context *ctx);
/**
* Send OPEN or OPENDIR request
*/
@@ -1637,13 +1633,13 @@ struct fuse_read_io {
int fuse_readdir_initialize(struct fuse_args *fa, struct fuse_read_io *frio,
struct file *file, struct dir_context *ctx,
bool *force_again, bool *allow_force);
bool *force_again, bool *allow_force, bool is_continued);
int fuse_readdir_backing(struct fuse_args *fa,
struct file *file, struct dir_context *ctx,
bool *force_again, bool *allow_force);
bool *force_again, bool *allow_force, bool is_continued);
void *fuse_readdir_finalize(struct fuse_args *fa,
struct file *file, struct dir_context *ctx,
bool *force_again, bool *allow_force);
bool *force_again, bool *allow_force, bool is_continued);
int fuse_access_initialize(struct fuse_args *fa, struct fuse_access_in *fai,
struct inode *inode, int mask);

View File

@@ -121,7 +121,7 @@ static bool fuse_emit(struct file *file, struct dir_context *ctx,
dirent->type);
}
int fuse_parse_dirfile(char *buf, size_t nbytes, struct file *file,
static int parse_dirfile(char *buf, size_t nbytes, struct file *file,
struct dir_context *ctx)
{
while (nbytes >= FUSE_NAME_OFFSET) {
@@ -360,7 +360,7 @@ static int fuse_readdir_uncached(struct file *file, struct dir_context *ctx)
res = parse_dirplusfile(page_address(page), res,
file, ctx, attr_version);
} else {
res = fuse_parse_dirfile(page_address(page), res, file,
res = parse_dirfile(page_address(page), res, file,
ctx);
}
}
@@ -574,13 +574,17 @@ int fuse_readdir(struct file *file, struct dir_context *ctx)
#ifdef CONFIG_FUSE_BPF
struct fuse_err_ret fer;
bool force_again, allow_force;
bool is_continued = false;
again:
fer = fuse_bpf_backing(inode, struct fuse_read_io,
fuse_readdir_initialize, fuse_readdir_backing,
fuse_readdir_finalize,
file, ctx, &force_again, &allow_force);
if (force_again && !IS_ERR(fer.result))
file, ctx, &force_again, &allow_force, is_continued);
if (force_again && !IS_ERR(fer.result)) {
is_continued = true;
goto again;
}
if (fer.ret)
return PTR_ERR(fer.result);

View File

@@ -485,7 +485,7 @@ static int bpf_test_redact_readdir(const char *mount_dir)
TESTSYSCALL(closedir(dir));
dir = NULL;
FUSE_DAEMON
bool skip = true;
bool skip = true;
for (int i = 0; i < ARRAY_SIZE(names) + 1; i++) {
uint8_t bytes_in[FUSE_MIN_READ_BUFFER];
uint8_t bytes_out[FUSE_MIN_READ_BUFFER];