github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/file/fsnodefuse/reg.go (about)

     1  package fsnodefuse
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"os"
     7  	"sync"
     8  	"syscall"
     9  
    10  	"github.com/Schaudge/grailbase/errors"
    11  	"github.com/Schaudge/grailbase/file/fsnode"
    12  	"github.com/Schaudge/grailbase/ioctx"
    13  	"github.com/Schaudge/grailbase/log"
    14  	"github.com/Schaudge/grailbase/sync/loadingcache"
    15  	"github.com/Schaudge/grailbase/sync/loadingcache/ctxloadingcache"
    16  	"github.com/hanwen/go-fuse/v2/fs"
    17  	"github.com/hanwen/go-fuse/v2/fuse"
    18  )
    19  
    20  // TODO: Fix BXDS-1029. Without this, readers of non-constant files may see staleness and
    21  // concurrent readers of such files may see corruption.
    22  type regInode struct {
    23  	fs.Inode
    24  	cache loadingcache.Map
    25  
    26  	mu sync.Mutex
    27  	n  fsnode.Leaf
    28  
    29  	// defaultSize is a shared record of file size for all file handles of type sizingHandle
    30  	// created for this inode. The first sizingHandle to reach EOF (for its io.Reader) sets
    31  	// defaultSizeKnown and defaultSize and after that all other handles will return the same size
    32  	// from Getattr calls.
    33  	//
    34  	// sizingHandle returns incorrect size information until the underlying Reader reaches EOF. The
    35  	// kernel issues concurrent reads to prepopulate the page cache, for performance, and also
    36  	// interleaves Getattr calls to confirm where EOF really is. Complicating matters, multiple open
    37  	// handles share the page cache, allowing situations where one handle has populated the page
    38  	// cache, reached EOF, and knows the right size, whereas another handle's Reader is not there
    39  	// yet so it continues to use the fake size (which we may choose to be some giant number so
    40  	// users keep going until the end). This seems to cause bugs where user programs think they got
    41  	// real data past EOF (which is probably just padded/zeros).
    42  	//
    43  	// To avoid this problem, all open sizingHandles share a size value, after first EOF.
    44  	// TODO: Document more loudly the requirement that fsnode.Leaf.Open's files must return
    45  	// identical data (same size, same bytes) to avoid corrupt page cache interactions.
    46  	//
    47  	// TODO: Investigate more thoroughly or at least with a newer kernel (this was observed on
    48  	// 4.15.0-1099-aws).
    49  	defaultSizeMu    sync.RWMutex
    50  	defaultSizeKnown bool
    51  	defaultSize      int64
    52  }
    53  
    54  var (
    55  	_ fs.InodeEmbedder = (*regInode)(nil)
    56  
    57  	_ fs.NodeReadlinker = (*regInode)(nil)
    58  	_ fs.NodeOpener     = (*regInode)(nil)
    59  	_ fs.NodeGetattrer  = (*regInode)(nil)
    60  	_ fs.NodeSetattrer  = (*regInode)(nil)
    61  )
    62  
    63  func (n *regInode) Open(ctx context.Context, inFlags uint32) (_ fs.FileHandle, outFlags uint32, errno syscall.Errno) {
    64  	defer handlePanicErrno(&errno)
    65  	ctx = ctxloadingcache.With(ctx, &n.cache)
    66  	file, err := n.n.OpenFile(ctx, int(inFlags))
    67  	if err != nil {
    68  		return nil, 0, errToErrno(err)
    69  	}
    70  	h, err := makeHandle(n, inFlags, file)
    71  	return h, 0, errToErrno(err)
    72  }
    73  
    74  func (n *regInode) Readlink(ctx context.Context) (_ []byte, errno syscall.Errno) {
    75  	defer handlePanicErrno(&errno)
    76  	ctx = ctxloadingcache.With(ctx, &n.cache)
    77  	file, err := n.n.OpenFile(ctx, 0)
    78  	if err != nil {
    79  		return nil, errToErrno(err)
    80  	}
    81  	defer func() {
    82  		if errClose := file.Close(ctx); errClose != nil && errno == fs.OK {
    83  			errno = errToErrno(errClose)
    84  		}
    85  	}()
    86  	content, err := io.ReadAll(ioctx.ToStdReader(ctx, file))
    87  	if err != nil {
    88  		return nil, errToErrno(err)
    89  	}
    90  	return content, fs.OK
    91  }
    92  
    93  func (n *regInode) Getattr(ctx context.Context, h fs.FileHandle, a *fuse.AttrOut) (errno syscall.Errno) {
    94  	defer handlePanicErrno(&errno)
    95  	ctx = ctxloadingcache.With(ctx, &n.cache)
    96  
    97  	if h != nil {
    98  		if hg, ok := h.(fs.FileGetattrer); ok {
    99  			return hg.Getattr(ctx, a)
   100  		}
   101  	}
   102  
   103  	setAttrFromFileInfo(&a.Attr, n.n.Info())
   104  	a.SetTimeout(getCacheTimeout(n.n))
   105  	return fs.OK
   106  }
   107  
   108  func (n *regInode) Setattr(ctx context.Context, h fs.FileHandle, in *fuse.SetAttrIn, a *fuse.AttrOut) (errno syscall.Errno) {
   109  	defer handlePanicErrno(&errno)
   110  	if h, ok := h.(fs.FileSetattrer); ok {
   111  		return h.Setattr(ctx, in, a)
   112  	}
   113  	if usize, ok := in.GetSize(); ok {
   114  		if usize != 0 {
   115  			// We only support setting the size to 0.
   116  			return syscall.ENOTSUP
   117  		}
   118  		err := func() (err error) {
   119  			f, err := n.n.OpenFile(ctx, os.O_WRONLY|os.O_TRUNC)
   120  			if err != nil {
   121  				return errToErrno(err)
   122  			}
   123  			defer errors.CleanUpCtx(ctx, f.Close, &err)
   124  			w, ok := f.(Writable)
   125  			if !ok {
   126  				return syscall.ENOTSUP
   127  			}
   128  			return w.Flush(ctx)
   129  		}()
   130  		if err != nil {
   131  			return errToErrno(err)
   132  		}
   133  	}
   134  	n.cache.DeleteAll()
   135  	if errno := n.NotifyContent(0 /* offset */, 0 /* len, zero means all */); errno != fs.OK {
   136  		log.Error.Printf("regInode.Setattr %s: error from NotifyContent: %v", n.Path(nil), errno)
   137  		return errToErrno(errno)
   138  	}
   139  	// TODO(josh): Is this the right invalidation, and does it work? Maybe page cache only matters
   140  	// if we set some other flags in open or read to enable it?
   141  	setAttrFromFileInfo(&a.Attr, n.n.Info())
   142  	a.SetTimeout(getCacheTimeout(n.n))
   143  	return fs.OK
   144  }