175 lines
4.2 KiB
Go
175 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"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.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 {
|
|
out.Mode = 0555 | syscall.S_IFDIR
|
|
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
|
|
}
|
|
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
|
|
}
|
|
|
|
// --- 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
|
|
}
|
|
// Report all files as rw so the kernel doesn't block writes;
|
|
// the 9P server enforces actual permissions.
|
|
out.Mode = 0666
|
|
out.Size = dir.Length
|
|
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.
|
|
out.Mode = 0666
|
|
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()
|
|
// The 9fans client doesn't support Seek; read from offset by reading and discarding.
|
|
// For small offsets this is fine; large seeks on big files would need a smarter approach.
|
|
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 err != nil && n == 0 {
|
|
return nil, syscall.EIO
|
|
}
|
|
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
|
|
}
|