package mount import ( "context" "os/user" "strconv" "sync" "syscall" "9fans.net/go/plan9" "9fans.net/go/plan9/client" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" ) // p9Dir is a FUSE directory node backed by a 9P path. type p9Dir struct { fs.Inode fsys *client.Fsys path string // 9P path (empty string = root) } // p9File is a FUSE file node backed by a 9P path. type p9File struct { fs.Inode fsys *client.Fsys path string mu sync.Mutex } var _ = (fs.NodeGetattrer)((*p9Dir)(nil)) var _ = (fs.NodeReaddirer)((*p9Dir)(nil)) var _ = (fs.NodeLookuper)((*p9Dir)(nil)) var _ = (fs.NodeCreater)((*p9Dir)(nil)) var _ = (fs.NodeUnlinker)((*p9Dir)(nil)) var _ = (fs.NodeMkdirer)((*p9Dir)(nil)) var _ = (fs.NodeRmdirer)((*p9Dir)(nil)) var _ = (fs.NodeRenamer)((*p9Dir)(nil)) var _ = (fs.NodeGetattrer)((*p9File)(nil)) var _ = (fs.NodeOpener)((*p9File)(nil)) var _ = (fs.NodeReader)((*p9File)(nil)) var _ = (fs.NodeWriter)((*p9File)(nil)) var _ = (fs.NodeSetattrer)((*p9File)(nil)) func p9path(base, name string) string { if base == "" { return name } return base + "/" + name } // --- p9Dir --- func (d *p9Dir) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno { path := d.path if path == "" { path = "/" } dir, err := d.fsys.Stat(path) if err != nil { return syscall.ENOENT } out.Mode = uint32(dir.Mode)&0777 | syscall.S_IFDIR out.Uid = lookupUid(dir.Uid) out.Gid = lookupGid(dir.Gid) return 0 } func (d *p9Dir) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) { path := d.path if path == "" { path = "/" } fid, err := d.fsys.Open(path, plan9.OREAD) if err != nil { return nil, syscall.EIO } defer fid.Close() dirs, err := fid.Dirreadall() if err != nil { return nil, syscall.EIO } entries := make([]fuse.DirEntry, len(dirs)) for i, dir := range dirs { m := uint32(dir.Mode) & 0777 if dir.Mode&plan9.DMDIR != 0 { m |= syscall.S_IFDIR } entries[i] = fuse.DirEntry{Name: dir.Name, Mode: m} } return fs.NewListDirStream(entries), 0 } func (d *p9Dir) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) { child := p9path(d.path, name) dir, err := d.fsys.Stat(child) if err != nil { return nil, syscall.ENOENT } out.Uid = lookupUid(dir.Uid) out.Gid = lookupGid(dir.Gid) if dir.Mode&plan9.DMDIR != 0 { out.Mode = uint32(dir.Mode)&0777 | syscall.S_IFDIR node := &p9Dir{fsys: d.fsys, path: child} return d.NewInode(ctx, node, fs.StableAttr{Mode: syscall.S_IFDIR}), 0 } out.Mode = uint32(dir.Mode) & 0777 out.Size = dir.Length node := &p9File{fsys: d.fsys, path: child} return d.NewInode(ctx, node, fs.StableAttr{}), 0 } func (d *p9Dir) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (*fs.Inode, fs.FileHandle, uint32, syscall.Errno) { child := p9path(d.path, name) fid, err := d.fsys.Create(child, plan9.OWRITE, plan9.Perm(mode&0777)) if err != nil { return nil, nil, 0, syscall.EIO } fid.Close() out.Mode = mode & 0777 node := &p9File{fsys: d.fsys, path: child} inode := d.NewInode(ctx, node, fs.StableAttr{}) return inode, nil, fuse.FOPEN_DIRECT_IO, 0 } func (d *p9Dir) Unlink(ctx context.Context, name string) syscall.Errno { child := p9path(d.path, name) if err := d.fsys.Remove(child); err != nil { return syscall.EIO } return 0 } func (d *p9Dir) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) { child := p9path(d.path, name) fid, err := d.fsys.Create(child, plan9.OREAD, plan9.DMDIR|plan9.Perm(mode&0777)) if err != nil { return nil, syscall.EIO } fid.Close() out.Mode = mode&0777 | syscall.S_IFDIR node := &p9Dir{fsys: d.fsys, path: child} return d.NewInode(ctx, node, fs.StableAttr{Mode: syscall.S_IFDIR}), 0 } func (d *p9Dir) Rmdir(ctx context.Context, name string) syscall.Errno { child := p9path(d.path, name) if err := d.fsys.Remove(child); err != nil { return syscall.EIO } return 0 } func (d *p9Dir) Rename(ctx context.Context, name string, newParent fs.InodeEmbedder, newName string, flags uint32) syscall.Errno { child := p9path(d.path, name) var dir plan9.Dir dir.Null() dir.Name = newName if err := d.fsys.Wstat(child, &dir); err != nil { return syscall.EIO } return 0 } // --- p9File --- func (f *p9File) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno { dir, err := f.fsys.Stat(f.path) if err != nil { return syscall.ENOENT } out.Mode = uint32(dir.Mode) & 0777 out.Size = dir.Length out.Uid = lookupUid(dir.Uid) out.Gid = lookupGid(dir.Gid) return 0 } func (f *p9File) Setattr(ctx context.Context, fh fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno { // Accept truncate silently — 9P files are append/overwrite on write. dir, err := f.fsys.Stat(f.path) if err != nil { return syscall.ENOENT } out.Mode = uint32(dir.Mode) & 0777 return 0 } func (f *p9File) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) { return nil, fuse.FOPEN_DIRECT_IO, 0 } func (f *p9File) Read(ctx context.Context, fh fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) { f.mu.Lock() defer f.mu.Unlock() fid, err := f.fsys.Open(f.path, plan9.OREAD) if err != nil { return nil, syscall.EIO } defer fid.Close() if off > 0 { buf := make([]byte, min(off, 8192)) remaining := off for remaining > 0 { n := int64(len(buf)) if n > remaining { n = remaining } nr, err := fid.Read(buf[:n]) if err != nil || nr == 0 { break } remaining -= int64(nr) } } n, err := fid.Read(dest) if n == 0 { return fuse.ReadResultData(nil), 0 } return fuse.ReadResultData(dest[:n]), 0 } func (f *p9File) Write(ctx context.Context, fh fs.FileHandle, data []byte, off int64) (uint32, syscall.Errno) { f.mu.Lock() defer f.mu.Unlock() fid, err := f.fsys.Open(f.path, plan9.OWRITE) if err != nil { return 0, syscall.EACCES } n, err := fid.Write(data) if err != nil { fid.Close() return 0, syscall.EIO } if err := fid.Close(); err != nil { return 0, syscall.EACCES } return uint32(n), 0 } func min(a, b int64) int64 { if a < b { return a } return b } func lookupUid(name string) uint32 { if id, err := strconv.ParseUint(name, 10, 32); err == nil { return uint32(id) } if u, err := user.Lookup(name); err == nil { if id, err := strconv.ParseUint(u.Uid, 10, 32); err == nil { return uint32(id) } } return 0 } func lookupGid(name string) uint32 { if id, err := strconv.ParseUint(name, 10, 32); err == nil { return uint32(id) } if g, err := user.LookupGroup(name); err == nil { if id, err := strconv.ParseUint(g.Gid, 10, 32); err == nil { return uint32(id) } } return 0 }