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 }