tractor.dev/toolkit-go@v0.0.0-20241010005851-214d91207d07/engine/fs/fuser/fuser.go (about)

     1  package fuser
     2  
     3  import (
     4  	"context"
     5  	"hash/fnv"
     6  	"io"
     7  	iofs "io/fs"
     8  	"log"
     9  	"os"
    10  	"path/filepath"
    11  	"syscall"
    12  
    13  	"github.com/hanwen/go-fuse/v2/fs"
    14  	"github.com/hanwen/go-fuse/v2/fuse"
    15  )
    16  
    17  func fakeIno(s string) uint64 {
    18  	h := fnv.New64a() // FNV-1a 64-bit hash
    19  	h.Write([]byte(s))
    20  	return h.Sum64()
    21  }
    22  
    23  func applyStat(out *fuse.Attr, fi iofs.FileInfo) {
    24  	stat := fi.Sys()
    25  	if s, ok := stat.(*syscall.Stat_t); ok {
    26  		out.FromStat(s)
    27  		return
    28  	}
    29  	out.Mtime = uint64(fi.ModTime().Unix())
    30  	out.Mtimensec = uint32(fi.ModTime().UnixNano())
    31  	out.Mode = uint32(fi.Mode())
    32  	out.Size = uint64(fi.Size())
    33  }
    34  
    35  type Node struct {
    36  	fs.Inode
    37  	FS   iofs.FS
    38  	path string
    39  }
    40  
    41  var _ = (fs.NodeGetattrer)((*Node)(nil))
    42  
    43  func (n *Node) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
    44  	log.Println("getattr", n.path)
    45  
    46  	fi, err := iofs.Stat(n.FS, ".")
    47  	if err != nil {
    48  		return sysErrno(err)
    49  	}
    50  	applyStat(&out.Attr, fi)
    51  
    52  	return 0
    53  }
    54  
    55  var _ = (fs.NodeReaddirer)((*Node)(nil))
    56  
    57  func (n *Node) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
    58  	log.Println("readdir", n.path)
    59  
    60  	entries, err := iofs.ReadDir(n.FS, ".")
    61  	if err != nil {
    62  		return nil, sysErrno(err)
    63  	}
    64  
    65  	var fentries []fuse.DirEntry
    66  	for _, entry := range entries {
    67  		fentries = append(fentries, fuse.DirEntry{
    68  			Name: entry.Name(),
    69  			Mode: uint32(entry.Type()),
    70  			Ino:  fakeIno(filepath.Join(n.path, entry.Name())),
    71  		})
    72  	}
    73  
    74  	return fs.NewListDirStream(fentries), 0
    75  }
    76  
    77  var _ = (fs.NodeOpendirer)((*Node)(nil))
    78  
    79  func (r *Node) Opendir(ctx context.Context) syscall.Errno {
    80  	log.Println("opendir", r.path)
    81  	return 0
    82  }
    83  
    84  var _ = (fs.NodeLookuper)((*Node)(nil))
    85  
    86  func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
    87  	log.Println("lookup", n.path, name)
    88  
    89  	fi, err := iofs.Stat(n.FS, name)
    90  	if err != nil {
    91  		return nil, sysErrno(err)
    92  	}
    93  
    94  	applyStat(&out.Attr, fi)
    95  
    96  	subfs, err := iofs.Sub(n.FS, name)
    97  	if err != nil {
    98  		return nil, sysErrno(err)
    99  	}
   100  
   101  	mode := fuse.S_IFREG
   102  	if fi.IsDir() {
   103  		mode = fuse.S_IFDIR
   104  	}
   105  
   106  	return n.Inode.NewPersistentInode(ctx, &Node{
   107  		FS:   subfs,
   108  		path: filepath.Join(n.path, name),
   109  	}, fs.StableAttr{
   110  		Mode: uint32(mode),
   111  		Ino:  fakeIno(filepath.Join(n.path, name)),
   112  	}), 0
   113  }
   114  
   115  var _ = (fs.NodeOpener)((*Node)(nil))
   116  
   117  func (n *Node) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
   118  	log.Println("open", n.path)
   119  
   120  	f, err := n.FS.Open(".") // should be OpenFile
   121  	if err != nil {
   122  		return nil, 0, sysErrno(err)
   123  	}
   124  
   125  	return &handle{file: f, path: n.path}, 0, 0
   126  }
   127  
   128  type handle struct {
   129  	file iofs.File
   130  	path string
   131  }
   132  
   133  var _ = (fs.FileReader)((*handle)(nil))
   134  
   135  func (h *handle) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
   136  	log.Println("read", h.path)
   137  
   138  	if ra, ok := h.file.(io.ReaderAt); ok {
   139  		n, err := ra.ReadAt(dest, off)
   140  		if err != nil {
   141  			return nil, sysErrno(err)
   142  		}
   143  		return fuse.ReadResultData(dest[:n]), 0
   144  	}
   145  
   146  	if off > 0 {
   147  		return nil, sysErrno(iofs.ErrPermission)
   148  	}
   149  
   150  	n, err := h.file.Read(dest)
   151  	if err != nil {
   152  		return nil, sysErrno(err)
   153  	}
   154  
   155  	return fuse.ReadResultData(dest[:n]), 0
   156  }
   157  
   158  var _ = (fs.FileFlusher)((*handle)(nil))
   159  
   160  func (h *handle) Flush(ctx context.Context) syscall.Errno {
   161  	log.Println("flush", h.path)
   162  
   163  	if err := h.file.Close(); err != nil {
   164  		return sysErrno(err)
   165  	}
   166  
   167  	return 0
   168  }
   169  
   170  func sysErrno(err error) syscall.Errno {
   171  	log.Println("ERR:", err)
   172  	switch err {
   173  	case nil:
   174  		return syscall.Errno(0)
   175  	case os.ErrPermission:
   176  		return syscall.EPERM
   177  	case os.ErrExist:
   178  		return syscall.EEXIST
   179  	case os.ErrNotExist:
   180  		return syscall.ENOENT
   181  	case os.ErrInvalid:
   182  		return syscall.EINVAL
   183  	}
   184  
   185  	switch t := err.(type) {
   186  	case syscall.Errno:
   187  		return t
   188  	case *os.SyscallError:
   189  		return t.Err.(syscall.Errno)
   190  	case *os.PathError:
   191  		return sysErrno(t.Err)
   192  	case *os.LinkError:
   193  		return sysErrno(t.Err)
   194  	}
   195  	log.Println("!! unsupported error type:", err)
   196  	return syscall.EINVAL
   197  }