569 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			569 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
// Copyright 2009 The Go9p Authors.  All rights reserved.
 | 
						|
// Use of this source code is governed by a BSD-style
 | 
						|
// license that can be found in the LICENSE file.
 | 
						|
 | 
						|
package go9p
 | 
						|
 | 
						|
import (
 | 
						|
	"log"
 | 
						|
	"runtime"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
// The FStatOp interface provides a single operation (Stat) that will be
 | 
						|
// called before a file stat is sent back to the client. If implemented,
 | 
						|
// the operation should update the data in the srvFile struct.
 | 
						|
type FStatOp interface {
 | 
						|
	Stat(fid *FFid) error
 | 
						|
}
 | 
						|
 | 
						|
// The FWstatOp interface provides a single operation (Wstat) that will be
 | 
						|
// called when the client requests the srvFile metadata to be modified. If
 | 
						|
// implemented, the operation will be called when Twstat message is received.
 | 
						|
// If not implemented, "permission denied" error will be sent back. If the
 | 
						|
// operation returns an Error, the error is send back to the client.
 | 
						|
type FWstatOp interface {
 | 
						|
	Wstat(*FFid, *Dir) error
 | 
						|
}
 | 
						|
 | 
						|
// If the FReadOp interface is implemented, the Read operation will be called
 | 
						|
// to read from the file. If not implemented, "permission denied" error will
 | 
						|
// be send back. The operation returns the number of bytes read, or the
 | 
						|
// error occurred while reading.
 | 
						|
type FReadOp interface {
 | 
						|
	Read(fid *FFid, buf []byte, offset uint64) (int, error)
 | 
						|
}
 | 
						|
 | 
						|
// If the FWriteOp interface is implemented, the Write operation will be called
 | 
						|
// to write to the file. If not implemented, "permission denied" error will
 | 
						|
// be send back. The operation returns the number of bytes written, or the
 | 
						|
// error occurred while writing.
 | 
						|
type FWriteOp interface {
 | 
						|
	Write(fid *FFid, data []byte, offset uint64) (int, error)
 | 
						|
}
 | 
						|
 | 
						|
// If the FCreateOp interface is implemented, the Create operation will be called
 | 
						|
// when the client attempts to create a file in the srvFile implementing the interface.
 | 
						|
// If not implemented, "permission denied" error will be send back. If successful,
 | 
						|
// the operation should call (*File)Add() to add the created file to the directory.
 | 
						|
// The operation returns the created file, or the error occurred while creating it.
 | 
						|
type FCreateOp interface {
 | 
						|
	Create(fid *FFid, name string, perm uint32) (*srvFile, error)
 | 
						|
}
 | 
						|
 | 
						|
// If the FRemoveOp interface is implemented, the Remove operation will be called
 | 
						|
// when the client attempts to create a file in the srvFile implementing the interface.
 | 
						|
// If not implemented, "permission denied" error will be send back.
 | 
						|
// The operation returns nil if successful, or the error that occurred while removing
 | 
						|
// the file.
 | 
						|
type FRemoveOp interface {
 | 
						|
	Remove(*FFid) error
 | 
						|
}
 | 
						|
 | 
						|
type FOpenOp interface {
 | 
						|
	Open(fid *FFid, mode uint8) error
 | 
						|
}
 | 
						|
 | 
						|
type FClunkOp interface {
 | 
						|
	Clunk(fid *FFid) error
 | 
						|
}
 | 
						|
 | 
						|
type FDestroyOp interface {
 | 
						|
	FidDestroy(fid *FFid)
 | 
						|
}
 | 
						|
 | 
						|
type FFlags int
 | 
						|
 | 
						|
const (
 | 
						|
	Fremoved FFlags = 1 << iota
 | 
						|
)
 | 
						|
 | 
						|
// The srvFile type represents a file (or directory) served by the file server.
 | 
						|
type srvFile struct {
 | 
						|
	sync.Mutex
 | 
						|
	Dir
 | 
						|
	flags FFlags
 | 
						|
 | 
						|
	Parent        *srvFile // parent
 | 
						|
	next, prev    *srvFile // siblings, guarded by parent.Lock
 | 
						|
	cfirst, clast *srvFile // children (if directory)
 | 
						|
	ops           interface{}
 | 
						|
}
 | 
						|
 | 
						|
type FFid struct {
 | 
						|
	F       *srvFile
 | 
						|
	Fid     *SrvFid
 | 
						|
	dirs    []*srvFile // used for readdir
 | 
						|
	dirents []byte     // serialized version of dirs
 | 
						|
}
 | 
						|
 | 
						|
// The Fsrv can be used to create file servers that serve
 | 
						|
// simple trees of synthetic files.
 | 
						|
type Fsrv struct {
 | 
						|
	Srv
 | 
						|
	Root *srvFile
 | 
						|
}
 | 
						|
 | 
						|
var lock sync.Mutex
 | 
						|
var qnext uint64
 | 
						|
var Eexist = &Error{"file already exists", EEXIST}
 | 
						|
var Enoent = &Error{"file not found", ENOENT}
 | 
						|
var Enotempty = &Error{"directory not empty", EPERM}
 | 
						|
 | 
						|
// Creates a file server with root as root directory
 | 
						|
func NewsrvFileSrv(root *srvFile) *Fsrv {
 | 
						|
	srv := new(Fsrv)
 | 
						|
	srv.Root = root
 | 
						|
	root.Parent = root // make sure we can .. in root
 | 
						|
 | 
						|
	return srv
 | 
						|
}
 | 
						|
 | 
						|
// Initializes the fields of a file and add it to a directory.
 | 
						|
// Returns nil if successful, or an error.
 | 
						|
func (f *srvFile) Add(dir *srvFile, name string, uid User, gid Group, mode uint32, ops interface{}) error {
 | 
						|
 | 
						|
	lock.Lock()
 | 
						|
	qpath := qnext
 | 
						|
	qnext++
 | 
						|
	lock.Unlock()
 | 
						|
 | 
						|
	f.Qid.Type = uint8(mode >> 24)
 | 
						|
	f.Qid.Version = 0
 | 
						|
	f.Qid.Path = qpath
 | 
						|
	f.Mode = mode
 | 
						|
	// macOS filesystem st_mtime values are only accurate to the second
 | 
						|
	// without truncating, 9p will invent a changing fractional time #1375
 | 
						|
	if runtime.GOOS == "darwin" {
 | 
						|
		f.Atime = uint32(time.Now().Truncate(time.Second).Unix())
 | 
						|
	} else {
 | 
						|
		f.Atime = uint32(time.Now().Unix())
 | 
						|
	}
 | 
						|
	f.Mtime = f.Atime
 | 
						|
	f.Length = 0
 | 
						|
	f.Name = name
 | 
						|
	if uid != nil {
 | 
						|
		f.Uid = uid.Name()
 | 
						|
		f.Uidnum = uint32(uid.Id())
 | 
						|
	} else {
 | 
						|
		f.Uid = "none"
 | 
						|
		f.Uidnum = NOUID
 | 
						|
	}
 | 
						|
 | 
						|
	if gid != nil {
 | 
						|
		f.Gid = gid.Name()
 | 
						|
		f.Gidnum = uint32(gid.Id())
 | 
						|
	} else {
 | 
						|
		f.Gid = "none"
 | 
						|
		f.Gidnum = NOUID
 | 
						|
	}
 | 
						|
 | 
						|
	f.Muid = ""
 | 
						|
	f.Muidnum = NOUID
 | 
						|
	f.Ext = ""
 | 
						|
 | 
						|
	if dir != nil {
 | 
						|
		f.Parent = dir
 | 
						|
		dir.Lock()
 | 
						|
		for p := dir.cfirst; p != nil; p = p.next {
 | 
						|
			if name == p.Name {
 | 
						|
				dir.Unlock()
 | 
						|
				return Eexist
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if dir.clast != nil {
 | 
						|
			dir.clast.next = f
 | 
						|
		} else {
 | 
						|
			dir.cfirst = f
 | 
						|
		}
 | 
						|
 | 
						|
		f.prev = dir.clast
 | 
						|
		f.next = nil
 | 
						|
		dir.clast = f
 | 
						|
		dir.Unlock()
 | 
						|
	} else {
 | 
						|
		f.Parent = f
 | 
						|
	}
 | 
						|
 | 
						|
	f.ops = ops
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Removes a file from its parent directory.
 | 
						|
func (f *srvFile) Remove() {
 | 
						|
	f.Lock()
 | 
						|
	if (f.flags & Fremoved) != 0 {
 | 
						|
		f.Unlock()
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	f.flags |= Fremoved
 | 
						|
	f.Unlock()
 | 
						|
 | 
						|
	p := f.Parent
 | 
						|
	p.Lock()
 | 
						|
	if f.next != nil {
 | 
						|
		f.next.prev = f.prev
 | 
						|
	} else {
 | 
						|
		p.clast = f.prev
 | 
						|
	}
 | 
						|
 | 
						|
	if f.prev != nil {
 | 
						|
		f.prev.next = f.next
 | 
						|
	} else {
 | 
						|
		p.cfirst = f.next
 | 
						|
	}
 | 
						|
 | 
						|
	f.next = nil
 | 
						|
	f.prev = nil
 | 
						|
	p.Unlock()
 | 
						|
}
 | 
						|
 | 
						|
func (f *srvFile) Rename(name string) error {
 | 
						|
	p := f.Parent
 | 
						|
	p.Lock()
 | 
						|
	defer p.Unlock()
 | 
						|
	for c := p.cfirst; c != nil; c = c.next {
 | 
						|
		if name == c.Name {
 | 
						|
			return Eexist
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	f.Name = name
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Looks for a file in a directory. Returns nil if the file is not found.
 | 
						|
func (p *srvFile) Find(name string) *srvFile {
 | 
						|
	var f *srvFile
 | 
						|
 | 
						|
	p.Lock()
 | 
						|
	for f = p.cfirst; f != nil; f = f.next {
 | 
						|
		if name == f.Name {
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	p.Unlock()
 | 
						|
	return f
 | 
						|
}
 | 
						|
 | 
						|
// Checks if the specified user has permission to perform
 | 
						|
// certain operation on a file. Perm contains one or more
 | 
						|
// of DMREAD, DMWRITE, and DMEXEC.
 | 
						|
func (f *srvFile) CheckPerm(user User, perm uint32) bool {
 | 
						|
	if user == nil {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	perm &= 7
 | 
						|
 | 
						|
	/* other permissions */
 | 
						|
	fperm := f.Mode & 7
 | 
						|
	if (fperm & perm) == perm {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
 | 
						|
	/* user permissions */
 | 
						|
	if f.Uid == user.Name() || f.Uidnum == uint32(user.Id()) {
 | 
						|
		fperm |= (f.Mode >> 6) & 7
 | 
						|
	}
 | 
						|
 | 
						|
	if (fperm & perm) == perm {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
 | 
						|
	/* group permissions */
 | 
						|
	groups := user.Groups()
 | 
						|
	if groups != nil && len(groups) > 0 {
 | 
						|
		for i := 0; i < len(groups); i++ {
 | 
						|
			if f.Gid == groups[i].Name() || f.Gidnum == uint32(groups[i].Id()) {
 | 
						|
				fperm |= (f.Mode >> 3) & 7
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (fperm & perm) == perm {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
func (s *Fsrv) Attach(req *SrvReq) {
 | 
						|
	fid := new(FFid)
 | 
						|
	fid.F = s.Root
 | 
						|
	fid.Fid = req.Fid
 | 
						|
	req.Fid.Aux = fid
 | 
						|
	req.RespondRattach(&s.Root.Qid)
 | 
						|
}
 | 
						|
 | 
						|
func (*Fsrv) Walk(req *SrvReq) {
 | 
						|
	fid := req.Fid.Aux.(*FFid)
 | 
						|
	tc := req.Tc
 | 
						|
 | 
						|
	if req.Newfid.Aux == nil {
 | 
						|
		nfid := new(FFid)
 | 
						|
		nfid.Fid = req.Newfid
 | 
						|
		req.Newfid.Aux = nfid
 | 
						|
	}
 | 
						|
 | 
						|
	nfid := req.Newfid.Aux.(*FFid)
 | 
						|
	wqids := make([]Qid, len(tc.Wname))
 | 
						|
	i := 0
 | 
						|
	f := fid.F
 | 
						|
	for ; i < len(tc.Wname); i++ {
 | 
						|
		if tc.Wname[i] == ".." {
 | 
						|
			// handle dotdot
 | 
						|
			f = f.Parent
 | 
						|
			wqids[i] = f.Qid
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if (wqids[i].Type & QTDIR) > 0 {
 | 
						|
			if !f.CheckPerm(req.Fid.User, DMEXEC) {
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		p := f.Find(tc.Wname[i])
 | 
						|
		if p == nil {
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		f = p
 | 
						|
		wqids[i] = f.Qid
 | 
						|
	}
 | 
						|
 | 
						|
	if len(tc.Wname) > 0 && i == 0 {
 | 
						|
		req.RespondError(Enoent)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	nfid.F = f
 | 
						|
	req.RespondRwalk(wqids[0:i])
 | 
						|
}
 | 
						|
 | 
						|
func mode2Perm(mode uint8) uint32 {
 | 
						|
	var perm uint32 = 0
 | 
						|
 | 
						|
	switch mode & 3 {
 | 
						|
	case OREAD:
 | 
						|
		perm = DMREAD
 | 
						|
	case OWRITE:
 | 
						|
		perm = DMWRITE
 | 
						|
	case ORDWR:
 | 
						|
		perm = DMREAD | DMWRITE
 | 
						|
	}
 | 
						|
 | 
						|
	if (mode & OTRUNC) != 0 {
 | 
						|
		perm |= DMWRITE
 | 
						|
	}
 | 
						|
 | 
						|
	return perm
 | 
						|
}
 | 
						|
 | 
						|
func (*Fsrv) Open(req *SrvReq) {
 | 
						|
	fid := req.Fid.Aux.(*FFid)
 | 
						|
	tc := req.Tc
 | 
						|
 | 
						|
	if !fid.F.CheckPerm(req.Fid.User, mode2Perm(tc.Mode)) {
 | 
						|
		req.RespondError(Eperm)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if op, ok := (fid.F.ops).(FOpenOp); ok {
 | 
						|
		err := op.Open(fid, tc.Mode)
 | 
						|
		if err != nil {
 | 
						|
			req.RespondError(err)
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
	req.RespondRopen(&fid.F.Qid, 0)
 | 
						|
}
 | 
						|
 | 
						|
func (*Fsrv) Create(req *SrvReq) {
 | 
						|
	fid := req.Fid.Aux.(*FFid)
 | 
						|
	tc := req.Tc
 | 
						|
 | 
						|
	dir := fid.F
 | 
						|
	if !dir.CheckPerm(req.Fid.User, DMWRITE) {
 | 
						|
		req.RespondError(Eperm)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if cop, ok := (dir.ops).(FCreateOp); ok {
 | 
						|
		f, err := cop.Create(fid, tc.Name, tc.Perm)
 | 
						|
		if err != nil {
 | 
						|
			req.RespondError(err)
 | 
						|
		} else {
 | 
						|
			fid.F = f
 | 
						|
			req.RespondRcreate(&fid.F.Qid, 0)
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		req.RespondError(Eperm)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (*Fsrv) Read(req *SrvReq) {
 | 
						|
	var n int
 | 
						|
	var err error
 | 
						|
 | 
						|
	fid := req.Fid.Aux.(*FFid)
 | 
						|
	f := fid.F
 | 
						|
	tc := req.Tc
 | 
						|
	rc := req.Rc
 | 
						|
	InitRread(rc, tc.Count)
 | 
						|
 | 
						|
	if f.Mode&DMDIR != 0 {
 | 
						|
		// Get all the directory entries and
 | 
						|
		// serialize them all into an output buffer.
 | 
						|
		// This greatly simplifies the directory read.
 | 
						|
		if tc.Offset == 0 {
 | 
						|
			var g *srvFile
 | 
						|
			fid.dirents = nil
 | 
						|
			f.Lock()
 | 
						|
			for n, g = 0, f.cfirst; g != nil; n, g = n+1, g.next {
 | 
						|
			}
 | 
						|
			fid.dirs = make([]*srvFile, n)
 | 
						|
			for n, g = 0, f.cfirst; g != nil; n, g = n+1, g.next {
 | 
						|
				fid.dirs[n] = g
 | 
						|
				fid.dirents = append(fid.dirents,
 | 
						|
					PackDir(&g.Dir, req.Conn.Dotu)...)
 | 
						|
			}
 | 
						|
			f.Unlock()
 | 
						|
		}
 | 
						|
 | 
						|
		switch {
 | 
						|
		case tc.Offset > uint64(len(fid.dirents)):
 | 
						|
			n = 0
 | 
						|
		case len(fid.dirents[tc.Offset:]) > int(tc.Size):
 | 
						|
			n = int(tc.Size)
 | 
						|
		default:
 | 
						|
			n = len(fid.dirents[tc.Offset:])
 | 
						|
		}
 | 
						|
		copy(rc.Data, fid.dirents[tc.Offset:int(tc.Offset)+1+n])
 | 
						|
 | 
						|
	} else {
 | 
						|
		// file
 | 
						|
		if rop, ok := f.ops.(FReadOp); ok {
 | 
						|
			n, err = rop.Read(fid, rc.Data, tc.Offset)
 | 
						|
			if err != nil {
 | 
						|
				req.RespondError(err)
 | 
						|
				return
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			req.RespondError(Eperm)
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	SetRreadCount(rc, uint32(n))
 | 
						|
	req.Respond()
 | 
						|
}
 | 
						|
 | 
						|
func (*Fsrv) Write(req *SrvReq) {
 | 
						|
	fid := req.Fid.Aux.(*FFid)
 | 
						|
	f := fid.F
 | 
						|
	tc := req.Tc
 | 
						|
 | 
						|
	if wop, ok := (f.ops).(FWriteOp); ok {
 | 
						|
		n, err := wop.Write(fid, tc.Data, tc.Offset)
 | 
						|
		if err != nil {
 | 
						|
			req.RespondError(err)
 | 
						|
		} else {
 | 
						|
			req.RespondRwrite(uint32(n))
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		req.RespondError(Eperm)
 | 
						|
	}
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
func (*Fsrv) Clunk(req *SrvReq) {
 | 
						|
	fid := req.Fid.Aux.(*FFid)
 | 
						|
 | 
						|
	if op, ok := (fid.F.ops).(FClunkOp); ok {
 | 
						|
		err := op.Clunk(fid)
 | 
						|
		if err != nil {
 | 
						|
			req.RespondError(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	req.RespondRclunk()
 | 
						|
}
 | 
						|
 | 
						|
func (*Fsrv) Remove(req *SrvReq) {
 | 
						|
	fid := req.Fid.Aux.(*FFid)
 | 
						|
	f := fid.F
 | 
						|
	f.Lock()
 | 
						|
	if f.cfirst != nil {
 | 
						|
		f.Unlock()
 | 
						|
		req.RespondError(Enotempty)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	f.Unlock()
 | 
						|
 | 
						|
	if rop, ok := (f.ops).(FRemoveOp); ok {
 | 
						|
		err := rop.Remove(fid)
 | 
						|
		if err != nil {
 | 
						|
			req.RespondError(err)
 | 
						|
		} else {
 | 
						|
			f.Remove()
 | 
						|
			req.RespondRremove()
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		log.Println("remove not implemented")
 | 
						|
		req.RespondError(Eperm)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (*Fsrv) Stat(req *SrvReq) {
 | 
						|
	fid := req.Fid.Aux.(*FFid)
 | 
						|
	f := fid.F
 | 
						|
 | 
						|
	if sop, ok := (f.ops).(FStatOp); ok {
 | 
						|
		err := sop.Stat(fid)
 | 
						|
		if err != nil {
 | 
						|
			req.RespondError(err)
 | 
						|
		} else {
 | 
						|
			req.RespondRstat(&f.Dir)
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		req.RespondRstat(&f.Dir)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (*Fsrv) Wstat(req *SrvReq) {
 | 
						|
	tc := req.Tc
 | 
						|
	fid := req.Fid.Aux.(*FFid)
 | 
						|
	f := fid.F
 | 
						|
 | 
						|
	if wop, ok := (f.ops).(FWstatOp); ok {
 | 
						|
		err := wop.Wstat(fid, &tc.Dir)
 | 
						|
		if err != nil {
 | 
						|
			req.RespondError(err)
 | 
						|
		} else {
 | 
						|
			req.RespondRwstat()
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		req.RespondError(Eperm)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (*Fsrv) FidDestroy(ffid *SrvFid) {
 | 
						|
	if ffid.Aux == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	fid := ffid.Aux.(*FFid)
 | 
						|
	f := fid.F
 | 
						|
 | 
						|
	if f == nil {
 | 
						|
		return // otherwise errs in bad walks
 | 
						|
	}
 | 
						|
 | 
						|
	if op, ok := (f.ops).(FDestroyOp); ok {
 | 
						|
		op.FidDestroy(fid)
 | 
						|
	}
 | 
						|
}
 |