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 }