On Wed, 18 Oct 2017 10:47:59 +0200
Martin Pieuchot <[email protected]> wrote:

> On 16/10/17(Mon) 14:37, Helg Bredow wrote:
> > [...] 
> > Patch is below.
> 
> Do you mind explaining the problem and the solution you implemented?
> 

There is a regression introduced in 6.2 that can cause a call to getcwd(3) to 
result in an endless loop. fusefs_readdir() has a TODO that returns success 
even if a directory is not open, so getcwd(3) keeps calling it to fetch more 
dirents but none ever get supplied. This patch opens and closes the directory 
if it's not already open. The check for an invalid file handle was inside the 
loop but I've moved it outside since it's not going to become invalid once it's 
been opened.

This doesn't resolve the problem however. This is because ifuse_ops_readdir() 
does not support being called across multiple calls to VOP_READDIR(9). This 
occurs if the buffer is too small to contain all the entries and getcwd(3) only 
supplies a 512 byte buffer. If the current directory is not returned in the 
first call to VOP_READDIR(9) then getcwd(3) will continue to call it but the 
eofflag is never set. ifuse_ops_readdir() now correctly starts scanning at the 
next dirent even across multiple VOP_READDIR(9) calls.

In addition, if the entries don't evenly fit into the buffer then 
fusefs_readdir() would prematurely detect that the entire directory had been 
scanned and set the eofflag. This fix handles this condition so the entire 
directory is always scanned.

ifuse_fill_readdir() returns the ino of the file system instead of its own ino, 
which it returns from other calls such as stat(2). getcwd(3) compares these 
inos when scanning the directory and will never find a match since they are 
sourced from different file systems. fuse has the use_ino option to allow a 
file system to use its own inos but this is not implemented in OpenBSD yet. 
This patch forces the fuse ino to be used in ifuse_fill_readdir(). This is 
still only paritally implemented though, it will only set a valid ino if it can 
find the file or directory in the name cache. This is sufficient for getcwd(3) 
since the parent directory will always be in the cache.

Thanks for your feedback mpi@; I've made the changes you suggested. It's safe 
to store the fuse struct across calls since this is returned from fuse_setup() 
and never changes. Without it, I'm not sure how I would implement a solution 
since you need this in the filler function to access the name dictionary.


Index: lib/libfuse/fuse.h
===================================================================
RCS file: /cvs/src/lib/libfuse/fuse.h,v
retrieving revision 1.12
diff -u -p -r1.12 fuse.h
--- lib/libfuse/fuse.h  20 Jan 2014 15:01:59 -0000      1.12
+++ lib/libfuse/fuse.h  18 Oct 2017 13:24:53 -0000
@@ -89,6 +89,7 @@ typedef int (*fuse_fill_dir_t)(void *, c
     off_t);
 
 typedef struct fuse_dirhandle {
+       struct fuse *fuse;
        fuse_fill_dir_t filler;
        void *buf;
        int filled;
Index: lib/libfuse/fuse_ops.c
===================================================================
RCS file: /cvs/src/lib/libfuse/fuse_ops.c,v
retrieving revision 1.26
diff -u -p -r1.26 fuse_ops.c
--- lib/libfuse/fuse_ops.c      7 Sep 2016 17:53:35 -0000       1.26
+++ lib/libfuse/fuse_ops.c      18 Oct 2017 13:24:53 -0000
@@ -217,17 +217,20 @@ static int
 ifuse_fill_readdir(void *dh, const char *name, const struct stat *stbuf,
     off_t off)
 {
+       struct fuse *f;
        struct fuse_dirhandle *fd = dh;
+       struct fuse_vnode *v;
        struct fusebuf *fbuf;
        struct dirent *dir;
        uint32_t namelen;
        uint32_t len;
 
+       f = fd->fuse;
        fbuf = fd->buf;
        namelen = strnlen(name, MAXNAMLEN);
        len = GENERIC_DIRSIZ(namelen);
 
-       if (fd->full || (fbuf->fb_len + len > fd->size)) {
+       if (fd->full || fbuf->fb_len + len > fd->size) {
                fd->full = 1;
                return (0);
        }
@@ -242,13 +245,21 @@ ifuse_fill_readdir(void *dh, const char 
        if (off)
                fd->filled = 0;
 
-       if (stbuf) {
-               dir->d_fileno = stbuf->st_ino;
+       /* TODO Add support for use_ino and readdir_ino */
+       v = get_vn_by_name_and_parent(f, (uint8_t *)name, fbuf->fb_ino);
+       if (v == NULL) {
+               if (strcmp(name, ".") == 0)
+                       dir->d_fileno = fbuf->fb_ino;
+               else
+                       dir->d_fileno = 0xffffffff;
+       } else
+               dir->d_fileno = v->ino;
+
+       if (stbuf)
                dir->d_type = IFTODT(stbuf->st_mode);
-       } else {
-               dir->d_fileno = 0xffffffff;
+       else
                dir->d_type = DT_UNKNOWN;
-       }
+
        dir->d_reclen = len;
        dir->d_off = off + len;         /* XXX */
        strlcpy(dir->d_name, name, sizeof(dir->d_name));
@@ -295,7 +306,7 @@ ifuse_ops_readdir(struct fuse *f, struct
        ffi.fh = fbuf->fb_io_fd;
        offset = fbuf->fb_io_off;
        size = fbuf->fb_io_len;
-       startsave = 0;
+       startsave = offset;
 
        fbuf->fb_dat = calloc(1, size);
 
@@ -319,6 +330,8 @@ ifuse_ops_readdir(struct fuse *f, struct
                vn->fd->size = size;
                vn->fd->off = offset;
                vn->fd->idx = 0;
+               vn->fd->fuse = f;
+               vn->fd->start = offset;
                startsave = vn->fd->start;
 
                realname = build_realname(f, vn->ino);
@@ -345,7 +358,8 @@ ifuse_ops_readdir(struct fuse *f, struct
        if (fbuf->fb_err) {
                fbuf->fb_len = 0;
                vn->fd->filled = 1;
-       }
+       } else if (vn->fd->full && fbuf->fb_len == 0)
+               fbuf->fb_err = -ENOBUFS;
 
        if (fbuf->fb_len == 0)
                free(fbuf->fb_dat);
Index: sys/miscfs/fuse/fuse_vnops.c
===================================================================
RCS file: /cvs/src/sys/miscfs/fuse/fuse_vnops.c,v
retrieving revision 1.33
diff -u -p -r1.33 fuse_vnops.c
--- sys/miscfs/fuse/fuse_vnops.c        7 Sep 2016 17:53:35 -0000       1.33
+++ sys/miscfs/fuse/fuse_vnops.c        18 Oct 2017 13:25:04 -0000
@@ -680,7 +680,7 @@ fusefs_readdir(void *v)
        struct vnode *vp;
        struct proc *p;
        struct uio *uio;
-       int error = 0, eofflag = 0;
+       int error = 0, eofflag = 0, diropen = 0;
 
        vp = ap->a_vp;
        uio = ap->a_uio;
@@ -695,14 +695,18 @@ fusefs_readdir(void *v)
        if (uio->uio_resid < sizeof(struct dirent))
                return (EINVAL);
 
-       while (uio->uio_resid > 0) {
-               fbuf = fb_setup(0, ip->ufs_ino.i_number, FBT_READDIR, p);
-
-               if (ip->fufh[FUFH_RDONLY].fh_type == FUFH_INVALID) {
-                       /* TODO open the file */
+       if (ip->fufh[FUFH_RDONLY].fh_type == FUFH_INVALID) {
+               error = fusefs_file_open(fmp, ip, FUFH_RDONLY, O_RDONLY, 1, p);
+               if (error) {
                        fb_delete(fbuf);
                        return (error);
                }
+               diropen = 1;
+       }
+
+       while (uio->uio_resid > 0) {
+               fbuf = fb_setup(0, ip->ufs_ino.i_number, FBT_READDIR, p);
+
                fbuf->fb_io_fd = ip->fufh[FUFH_RDONLY].fh_id;
                fbuf->fb_io_off = uio->uio_offset;
                fbuf->fb_io_len = MIN(uio->uio_resid, fmp->max_read);
@@ -710,6 +714,13 @@ fusefs_readdir(void *v)
                error = fb_queue(fmp->dev, fbuf);
 
                if (error) {
+                       /*
+                        * dirent was larger than residual space left in
+                        * buffer.
+                        */
+                       if (error == ENOBUFS && fbuf->fb_len == 0)
+                               error = 0;
+
                        fb_delete(fbuf);
                        break;
                }
@@ -731,6 +742,9 @@ fusefs_readdir(void *v)
 
        if (!error && ap->a_eofflag != NULL)
                *ap->a_eofflag = eofflag;
+
+       if (diropen)
+               fusefs_file_close(fmp, ip, FUFH_RDONLY, O_RDONLY, 1, p);
 
        return (error);
 }

Reply via email to