github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/file/fsnodefuse/handle.go (about) 1 package fsnodefuse 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "syscall" 8 9 "github.com/Schaudge/grailbase/errors" 10 "github.com/Schaudge/grailbase/file/fsnode" 11 "github.com/Schaudge/grailbase/file/fsnodefuse/trailingbuf" 12 "github.com/Schaudge/grailbase/file/internal/kernel" 13 "github.com/Schaudge/grailbase/ioctx" 14 "github.com/Schaudge/grailbase/ioctx/fsctx" 15 "github.com/Schaudge/grailbase/ioctx/spliceio" 16 "github.com/Schaudge/grailbase/log" 17 "github.com/Schaudge/grailbase/sync/loadingcache" 18 "github.com/Schaudge/grailbase/sync/loadingcache/ctxloadingcache" 19 "github.com/hanwen/go-fuse/v2/fs" 20 "github.com/hanwen/go-fuse/v2/fuse" 21 ) 22 23 // makeHandle makes a fs.FileHandle for the given file, constructing an 24 // appropriate implementation given the flags and file implementation. 25 func makeHandle(n *regInode, flags uint32, file fsctx.File) (fs.FileHandle, error) { 26 var ( 27 spliceioReaderAt, isSpliceioReaderAt = file.(spliceio.ReaderAt) 28 ioctxReaderAt, isIoctxReaderAt = file.(ioctx.ReaderAt) 29 ) 30 if (flags&fuse.O_ANYWRITE) == 0 && !isSpliceioReaderAt && !isIoctxReaderAt { 31 tbReaderAt := trailingbuf.New(file, 0, kernel.MaxReadAhead) 32 return sizingHandle{ 33 n: n, 34 f: file, 35 r: tbReaderAt, 36 cache: &n.cache, 37 }, nil 38 } 39 var r fs.FileReader 40 switch { 41 case isSpliceioReaderAt: 42 r = fileReaderSpliceio{spliceioReaderAt} 43 case isIoctxReaderAt: 44 r = fileReaderIoctx{ioctxReaderAt} 45 case (flags & syscall.O_WRONLY) != syscall.O_WRONLY: 46 return nil, errors.E( 47 errors.NotSupported, 48 fmt.Sprintf("%T must implement spliceio.SpliceReaderAt or ioctx.ReaderAt", file), 49 ) 50 } 51 w, _ := file.(Writable) 52 return &handle{ 53 f: file, 54 r: r, 55 w: w, 56 cache: &n.cache, 57 }, nil 58 } 59 60 // sizingHandle infers the size of the underlying fsctx.File stream based on 61 // EOF. 62 type sizingHandle struct { 63 n *regInode 64 f fsctx.File 65 r *trailingbuf.ReaderAt 66 cache *loadingcache.Map 67 } 68 69 var ( 70 _ fs.FileGetattrer = (*sizingHandle)(nil) 71 _ fs.FileReader = (*sizingHandle)(nil) 72 _ fs.FileReleaser = (*sizingHandle)(nil) 73 ) 74 75 func (h sizingHandle) Getattr(ctx context.Context, a *fuse.AttrOut) (errno syscall.Errno) { 76 defer handlePanicErrno(&errno) 77 ctx = ctxloadingcache.With(ctx, h.cache) 78 79 // Note: Implementations that don't know the exact data size in advance may used some fixed 80 // overestimate for size. 81 statInfo, err := h.f.Stat(ctx) 82 if err != nil { 83 return errToErrno(err) 84 } 85 info := fsnode.CopyFileInfo(statInfo) 86 87 localSize, localKnown, err := h.r.Size(ctx) 88 if err != nil { 89 return errToErrno(err) 90 } 91 92 h.n.defaultSizeMu.RLock() 93 sharedKnown := h.n.defaultSizeKnown 94 sharedSize := h.n.defaultSize 95 h.n.defaultSizeMu.RUnlock() 96 97 if localKnown && !sharedKnown { 98 // This may be the first handle to reach EOF. Update the shared data. 99 h.n.defaultSizeMu.Lock() 100 if !h.n.defaultSizeKnown { 101 h.n.defaultSizeKnown = true 102 h.n.defaultSize = localSize 103 sharedSize = localSize 104 } else { 105 sharedSize = h.n.defaultSize 106 } 107 h.n.defaultSizeMu.Unlock() 108 sharedKnown = true 109 } 110 if sharedKnown { 111 if localKnown && localSize != sharedSize { 112 log.Error.Printf( 113 "fsnodefuse.sizingHandle.Getattr: size-at-EOF mismatch: this handle: %d, earlier: %d", 114 localSize, sharedSize) 115 return syscall.EIO 116 } 117 info = info.WithSize(sharedSize) 118 } 119 setAttrFromFileInfo(&a.Attr, info) 120 return fs.OK 121 } 122 123 func (h sizingHandle) Read(ctx context.Context, dst []byte, off int64) (_ fuse.ReadResult, errno syscall.Errno) { 124 defer handlePanicErrno(&errno) 125 ctx = ctxloadingcache.With(ctx, h.cache) 126 127 n, err := h.r.ReadAt(ctx, dst, off) 128 if err == io.EOF { 129 err = nil 130 } 131 return fuse.ReadResultData(dst[:n]), errToErrno(err) 132 } 133 134 func (h *sizingHandle) Release(ctx context.Context) (errno syscall.Errno) { 135 defer handlePanicErrno(&errno) 136 if h.f == nil { 137 return syscall.EBADF 138 } 139 ctx = ctxloadingcache.With(ctx, h.cache) 140 141 err := h.f.Close(ctx) 142 h.f = nil 143 h.r = nil 144 h.cache = nil 145 return errToErrno(err) 146 } 147 148 type fileReaderSpliceio struct{ spliceio.ReaderAt } 149 150 func (r fileReaderSpliceio) Read( 151 ctx context.Context, 152 dest []byte, 153 off int64, 154 ) (_ fuse.ReadResult, errno syscall.Errno) { 155 fd, fdSize, fdOff, err := r.SpliceReadAt(ctx, len(dest), off) 156 if err != nil { 157 return nil, errToErrno(err) 158 } 159 return fuse.ReadResultFd(fd, fdOff, fdSize), fs.OK 160 } 161 162 type fileReaderIoctx struct{ ioctx.ReaderAt } 163 164 func (r fileReaderIoctx) Read( 165 ctx context.Context, 166 dest []byte, 167 off int64, 168 ) (_ fuse.ReadResult, errno syscall.Errno) { 169 n, err := r.ReadAt(ctx, dest, off) 170 if err == io.EOF { 171 err = nil 172 } 173 return fuse.ReadResultData(dest[:n]), errToErrno(err) 174 } 175 176 type ( 177 // Writable is the interface that must be implemented by files returned by 178 // (fsnode.Leaf).OpenFile to support writing. 179 Writable interface { 180 WriteAt(ctx context.Context, p []byte, off int64) (n int, err error) 181 Truncate(ctx context.Context, n int64) error 182 // Flush is called on (FileFlusher).Flush, i.e. on the close(2) call on 183 // a file descriptor. Implementors can assume that no writes happen 184 // between Flush and (fsctx.File).Close. 185 Flush(ctx context.Context) error 186 Fsync(ctx context.Context) error 187 } 188 // handle is an implementation of fs.FileHandle that wraps an fsctx.File. 189 // The behavior of the handle depends on the functions implemented by the 190 // fsctx.File value. 191 handle struct { 192 f fsctx.File 193 r fs.FileReader 194 w Writable 195 cache *loadingcache.Map 196 } 197 ) 198 199 var ( 200 _ fs.FileFlusher = (*handle)(nil) 201 _ fs.FileFsyncer = (*handle)(nil) 202 _ fs.FileGetattrer = (*handle)(nil) 203 _ fs.FileLseeker = (*handle)(nil) 204 _ fs.FileReader = (*handle)(nil) 205 _ fs.FileReleaser = (*handle)(nil) 206 _ fs.FileSetattrer = (*handle)(nil) 207 _ fs.FileWriter = (*handle)(nil) 208 ) 209 210 func (h handle) Getattr(ctx context.Context, out *fuse.AttrOut) (errno syscall.Errno) { 211 defer handlePanicErrno(&errno) 212 if h.f == nil { 213 return syscall.EBADF 214 } 215 ctx = ctxloadingcache.With(ctx, h.cache) 216 info, err := h.f.Stat(ctx) 217 if err != nil { 218 return errToErrno(err) 219 } 220 if statT := fuse.ToStatT(info); statT != nil { 221 // Stat returned a *syscall.Stat_t, so just plumb that through. 222 out.FromStat(statT) 223 } else { 224 setAttrFromFileInfo(&out.Attr, info) 225 } 226 out.SetTimeout(getCacheTimeout(h.f)) 227 return fs.OK 228 } 229 230 func (h handle) Setattr( 231 ctx context.Context, 232 in *fuse.SetAttrIn, 233 out *fuse.AttrOut, 234 ) (errno syscall.Errno) { 235 defer handlePanicErrno(&errno) 236 if h.f == nil { 237 return syscall.EBADF 238 } 239 if h.w == nil { 240 return syscall.ENOSYS 241 } 242 h.cache.DeleteAll() 243 if usize, ok := in.GetSize(); ok { 244 return errToErrno(h.w.Truncate(ctx, int64(usize))) 245 } 246 return fs.OK 247 } 248 249 func (h handle) Read( 250 ctx context.Context, 251 dst []byte, 252 off int64, 253 ) (_ fuse.ReadResult, errno syscall.Errno) { 254 defer handlePanicErrno(&errno) 255 if h.f == nil { 256 return nil, syscall.EBADF 257 } 258 if h.r == nil { 259 return nil, syscall.ENOSYS 260 } 261 ctx = ctxloadingcache.With(ctx, h.cache) 262 return h.r.Read(ctx, dst, off) 263 } 264 265 func (h handle) Lseek( 266 ctx context.Context, 267 off uint64, 268 whence uint32, 269 ) (_ uint64, errno syscall.Errno) { 270 defer handlePanicErrno(&errno) 271 if h.f == nil { 272 return 0, syscall.EBADF 273 } 274 // We expect this to only be called with {SEEK_DATA,SEEK_HOLE}. 275 // https://github.com/torvalds/linux/blob/v5.13/fs/fuse/file.c#L2619-L2648 276 const ( 277 // Copied from https://github.com/torvalds/linux/blob/v5.13/include/uapi/linux/fs.h#L46-L47 278 SEEK_DATA = 3 279 SEEK_HOLE = 4 280 ) 281 switch whence { 282 case SEEK_DATA: 283 return off, fs.OK // We don't support holes so current offset is correct. 284 case SEEK_HOLE: 285 info, err := h.f.Stat(ctx) 286 if err != nil { 287 return 0, errToErrno(err) 288 } 289 return uint64(info.Size()), fs.OK 290 } 291 return 0, syscall.ENOTSUP 292 } 293 294 func (h handle) Write( 295 ctx context.Context, 296 p []byte, 297 off int64, 298 ) (_ uint32, errno syscall.Errno) { 299 defer handlePanicErrno(&errno) 300 if h.f == nil { 301 return 0, syscall.EBADF 302 } 303 if h.w == nil { 304 return 0, syscall.ENOSYS 305 } 306 ctx = ctxloadingcache.With(ctx, h.cache) 307 n, err := h.w.WriteAt(ctx, p, off) 308 return uint32(n), errToErrno(err) 309 } 310 311 func (h handle) Flush(ctx context.Context) (errno syscall.Errno) { 312 defer handlePanicErrno(&errno) 313 if h.f == nil { 314 return syscall.EBADF 315 } 316 if h.w == nil { 317 return fs.OK 318 } 319 ctx = ctxloadingcache.With(ctx, h.cache) 320 err := h.w.Flush(ctx) 321 return errToErrno(err) 322 } 323 324 func (h handle) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) { 325 defer handlePanicErrno(&errno) 326 if h.f == nil { 327 return syscall.EBADF 328 } 329 if h.w == nil { 330 return fs.OK 331 } 332 ctx = ctxloadingcache.With(ctx, h.cache) 333 err := h.w.Fsync(ctx) 334 return errToErrno(err) 335 } 336 337 func (h *handle) Release(ctx context.Context) (errno syscall.Errno) { 338 defer handlePanicErrno(&errno) 339 if h.f == nil { 340 return syscall.EBADF 341 } 342 ctx = ctxloadingcache.With(ctx, h.cache) 343 err := h.f.Close(ctx) 344 h.f = nil 345 h.r = nil 346 h.w = nil 347 h.cache = nil 348 return errToErrno(err) 349 }