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  }