272 lines
6.6 KiB
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
|
|
}
|