This repository has been archived on 2026-08-16. You can view files and clone it, but cannot push or open issues or pull requests.
ollie-9p/mount/node.go

272 lines
6.6 KiB
Go

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
}