github.com/grailbio/base@v0.0.11/file/fsnodefuse/dirstream.go (about)

     1  package fsnodefuse
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"syscall"
     7  
     8  	"github.com/grailbio/base/errors"
     9  	"github.com/grailbio/base/file/fsnode"
    10  	"github.com/grailbio/base/log"
    11  	"github.com/hanwen/go-fuse/v2/fs"
    12  	"github.com/hanwen/go-fuse/v2/fuse"
    13  )
    14  
    15  type dirStream struct {
    16  	ctx     context.Context
    17  	dir     *dirInode
    18  	entries []fsnode.T
    19  	// prev is the node of the previous entry, i.e. the node of the most recent
    20  	// entry returned by Next.  We cache this node to service LOOKUP operations
    21  	// that go-fuse issues when servicing READDIRPLUS.  See lookupCache.
    22  	prev fsnode.T
    23  }
    24  
    25  var _ fs.DirStream = (*dirStream)(nil)
    26  
    27  // newDirStream returns a dirStream whose entries are the children of dir.
    28  // Children are loaded eagerly so that any errors are reported before any
    29  // entries are returned by the stream.  If Next returns a non-OK Errno after
    30  // a call that returned an OK Errno, the READDIR operation returns an EIO,
    31  // regardless of the returned Errno.  See
    32  // https://github.com/hanwen/go-fuse/issues/436 .
    33  func newDirStream(
    34  	ctx context.Context,
    35  	dir *dirInode,
    36  ) (_ *dirStream, err error) {
    37  	var (
    38  		entries []fsnode.T
    39  		iter    = dir.n.Children()
    40  	)
    41  	defer errors.CleanUpCtx(ctx, iter.Close, &err)
    42  	for {
    43  		n, err := iter.Next(ctx)
    44  		if err == io.EOF {
    45  			return &dirStream{
    46  				ctx:     ctx,
    47  				dir:     dir,
    48  				entries: entries,
    49  			}, nil
    50  		}
    51  		if err != nil {
    52  			return nil, err
    53  		}
    54  		entries = append(entries, n)
    55  	}
    56  }
    57  
    58  func (d *dirStream) HasNext() bool {
    59  	return len(d.entries) != 0
    60  }
    61  
    62  func (d *dirStream) Next() (_ fuse.DirEntry, errno syscall.Errno) {
    63  	defer handlePanicErrno(&errno)
    64  	var next fsnode.T
    65  	next, d.entries = d.entries[0], d.entries[1:]
    66  	if d.prev != nil {
    67  		d.dir.readdirplusCache.Drop(d.prev)
    68  	}
    69  	d.prev = next
    70  	d.dir.readdirplusCache.Put(next)
    71  	name := next.Info().Name()
    72  	return fuse.DirEntry{
    73  		Name: name,
    74  		Mode: mode(next),
    75  		Ino:  hashIno(d.dir, name),
    76  	}, fs.OK
    77  }
    78  
    79  func (d *dirStream) Close() {
    80  	var err error
    81  	defer handlePanicErr(&err)
    82  	defer func() {
    83  		if err != nil {
    84  			log.Error.Printf("fsnodefuse.dirStream: error on close: %v", err)
    85  		}
    86  	}()
    87  	if d.prev != nil {
    88  		d.dir.readdirplusCache.Drop(d.prev)
    89  		d.prev = nil
    90  	}
    91  }