519 lines
14 KiB
Go
519 lines
14 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.
|
|
|
|
// The srv package go9provides definitions and functions used to implement
|
|
// a 9P2000 file server.
|
|
package go9p
|
|
|
|
import (
|
|
"net"
|
|
"runtime"
|
|
"sync"
|
|
)
|
|
|
|
type reqStatus int
|
|
|
|
const (
|
|
reqFlush reqStatus = (1 << iota) /* request is flushed (no response will be sent) */
|
|
reqWork /* goroutine is currently working on it */
|
|
reqResponded /* response is already produced */
|
|
reqSaved /* no response was produced after the request is worked on */
|
|
)
|
|
|
|
var Eunknownfid error = &Error{"unknown fid", EINVAL}
|
|
var Enoauth error = &Error{"no authentication required", EINVAL}
|
|
var Einuse error = &Error{"fid already in use", EINVAL}
|
|
var Ebaduse error = &Error{"bad use of fid", EINVAL}
|
|
var Eopen error = &Error{"fid already opened", EINVAL}
|
|
var Enotdir error = &Error{"not a directory", ENOTDIR}
|
|
var Eperm error = &Error{"permission denied", EPERM}
|
|
var Etoolarge error = &Error{"i/o count too large", EINVAL}
|
|
var Ebadoffset error = &Error{"bad offset in directory read", EINVAL}
|
|
var Edirchange error = &Error{"cannot convert between files and directories", EINVAL}
|
|
var Enouser error = &Error{"unknown user", EINVAL}
|
|
var Enotimpl error = &Error{"not implemented", EINVAL}
|
|
|
|
// Authentication operations. The file server should implement them if
|
|
// it requires user authentication. The authentication in 9P2000 is
|
|
// done by creating special authentication fids and performing I/O
|
|
// operations on them. Once the authentication is done, the authentication
|
|
// fid can be used by the user to get access to the actual files.
|
|
type AuthOps interface {
|
|
// AuthInit is called when the user starts the authentication
|
|
// process on SrvFid afid. The user that is being authenticated
|
|
// is referred by afid.User. The function should return the Qid
|
|
// for the authentication file, or an Error if the user can't be
|
|
// authenticated
|
|
AuthInit(afid *SrvFid, aname string) (*Qid, error)
|
|
|
|
// AuthDestroy is called when an authentication fid is destroyed.
|
|
AuthDestroy(afid *SrvFid)
|
|
|
|
// AuthCheck is called after the authentication process is finished
|
|
// when the user tries to attach to the file server. If the function
|
|
// returns nil, the authentication was successful and the user has
|
|
// permission to access the files.
|
|
AuthCheck(fid *SrvFid, afid *SrvFid, aname string) error
|
|
|
|
// AuthRead is called when the user attempts to read data from an
|
|
// authentication fid.
|
|
AuthRead(afid *SrvFid, offset uint64, data []byte) (count int, err error)
|
|
|
|
// AuthWrite is called when the user attempts to write data to an
|
|
// authentication fid.
|
|
AuthWrite(afid *SrvFid, offset uint64, data []byte) (count int, err error)
|
|
}
|
|
|
|
// Connection operations. These should be implemented if the file server
|
|
// needs to be called when a connection is opened or closed.
|
|
type ConnOps interface {
|
|
ConnOpened(*Conn)
|
|
ConnClosed(*Conn)
|
|
}
|
|
|
|
// SrvFid operations. This interface should be implemented if the file server
|
|
// needs to be called when a SrvFid is destroyed.
|
|
type SrvFidOps interface {
|
|
FidDestroy(*SrvFid)
|
|
}
|
|
|
|
// Request operations. This interface should be implemented if the file server
|
|
// needs to bypass the default request process, or needs to perform certain
|
|
// operations before the (any) request is processed, or before (any) response
|
|
// sent back to the client.
|
|
type SrvReqProcessOps interface {
|
|
// Called when a new request is received from the client. If the
|
|
// interface is not implemented, (req *SrvReq) srv.Process() method is
|
|
// called. If the interface is implemented, it is the user's
|
|
// responsibility to call srv.Process. If srv.Process isn't called,
|
|
// SrvFid, Afid and Newfid fields in SrvReq are not set, and the SrvReqOps
|
|
// methods are not called.
|
|
SrvReqProcess(*SrvReq)
|
|
|
|
// Called when a request is responded, i.e. when (req *SrvReq)srv.Respond()
|
|
// is called and before the response is sent. If the interface is not
|
|
// implemented, (req *SrvReq) srv.PostProcess() method is called to finalize
|
|
// the request. If the interface is implemented and SrvReqProcess calls
|
|
// the srv.Process method, SrvReqRespond should call the srv.PostProcess
|
|
// method.
|
|
SrvReqRespond(*SrvReq)
|
|
}
|
|
|
|
// Flush operation. This interface should be implemented if the file server
|
|
// can flush pending requests. If the interface is not implemented, requests
|
|
// that were passed to the file server implementation won't be flushed.
|
|
// The flush method should call the (req *SrvReq) srv.Flush() method if the flush
|
|
// was successful so the request can be marked appropriately.
|
|
type FlushOp interface {
|
|
Flush(*SrvReq)
|
|
}
|
|
|
|
// The Srv type contains the basic fields used to control the 9P2000
|
|
// file server. Each file server implementation should create a value
|
|
// of Srv type, initialize the values it cares about and pass the
|
|
// struct to the (Srv *) srv.Start(ops) method together with the object
|
|
// that implements the file server operations.
|
|
type Srv struct {
|
|
sync.Mutex
|
|
Id string // Used for debugging and stats
|
|
Msize uint32 // Maximum size of the 9P2000 messages supported by the server
|
|
Dotu bool // If true, the server supports the 9P2000.u extension
|
|
Debuglevel int // debug level
|
|
Upool Users // Interface for finding users and groups known to the file server
|
|
Maxpend int // Maximum pending outgoing requests
|
|
Log *Logger
|
|
|
|
ops interface{} // operations
|
|
conns map[*Conn]*Conn // List of connections
|
|
}
|
|
|
|
// The Conn type represents a connection from a client to the file server
|
|
type Conn struct {
|
|
sync.Mutex
|
|
Srv *Srv
|
|
Msize uint32 // maximum size of 9P2000 messages for the connection
|
|
Dotu bool // if true, both the client and the server speak 9P2000.u
|
|
Id string // used for debugging and stats
|
|
Debuglevel int
|
|
|
|
conn net.Conn
|
|
fidpool map[uint32]*SrvFid
|
|
reqs map[uint16]*SrvReq // all outstanding requests
|
|
|
|
reqout chan *SrvReq
|
|
rchan chan *Fcall
|
|
done chan bool
|
|
|
|
// stats
|
|
nreqs int // number of requests processed by the server
|
|
tsz uint64 // total size of the T messages received
|
|
rsz uint64 // total size of the R messages sent
|
|
npend int // number of currently pending messages
|
|
maxpend int // maximum number of pending messages
|
|
nreads int // number of reads
|
|
nwrites int // number of writes
|
|
}
|
|
|
|
// The SrvFid type identifies a file on the file server.
|
|
// A new SrvFid is created when the user attaches to the file server (the Attach
|
|
// operation), or when Walk-ing to a file. The SrvFid values are created
|
|
// automatically by the srv implementation. The SrvFidDestroy operation is called
|
|
// when a SrvFid is destroyed.
|
|
type SrvFid struct {
|
|
sync.Mutex
|
|
fid uint32
|
|
refcount int
|
|
opened bool // True if the SrvFid is opened
|
|
Fconn *Conn // Connection the SrvFid belongs to
|
|
Omode uint8 // Open mode (O* flags), if the fid is opened
|
|
Type uint8 // SrvFid type (QT* flags)
|
|
Diroffset uint64 // If directory, the next valid read position
|
|
Dirents []byte // If directory, the serialized dirents
|
|
User User // The SrvFid's user
|
|
Aux interface{} // Can be used by the file server implementation for per-SrvFid data
|
|
}
|
|
|
|
// The SrvReq type represents a 9P2000 request. Each request has a
|
|
// T-message (Tc) and a R-message (Rc). If the SrvReqProcessOps don't
|
|
// override the default behavior, the implementation initializes SrvFid,
|
|
// Afid and Newfid values and automatically keeps track on when the SrvFids
|
|
// should be destroyed.
|
|
type SrvReq struct {
|
|
sync.Mutex
|
|
Tc *Fcall // Incoming 9P2000 message
|
|
Rc *Fcall // Outgoing 9P2000 response
|
|
Fid *SrvFid // The SrvFid value for all messages that contain fid[4]
|
|
Afid *SrvFid // The SrvFid value for the messages that contain afid[4] (Tauth and Tattach)
|
|
Newfid *SrvFid // The SrvFid value for the messages that contain newfid[4] (Twalk)
|
|
Conn *Conn // Connection that the request belongs to
|
|
|
|
status reqStatus
|
|
flushreq *SrvReq
|
|
prev, next *SrvReq
|
|
}
|
|
|
|
// The Start method should be called once the file server implementor
|
|
// initializes the Srv struct with the preferred values. It sets default
|
|
// values to the fields that are not initialized and creates the goroutines
|
|
// required for the server's operation. The method receives an empty
|
|
// interface value, ops, that should implement the interfaces the file server is
|
|
// interested in. Ops must implement the SrvReqOps interface.
|
|
func (srv *Srv) Start(ops interface{}) bool {
|
|
if _, ok := (ops).(SrvReqOps); !ok {
|
|
return false
|
|
}
|
|
|
|
srv.ops = ops
|
|
if srv.Upool == nil {
|
|
srv.Upool = OsUsers
|
|
}
|
|
|
|
if srv.Msize < IOHDRSZ {
|
|
srv.Msize = MSIZE
|
|
}
|
|
|
|
if srv.Log == nil {
|
|
srv.Log = NewLogger(1024)
|
|
}
|
|
|
|
if sop, ok := (interface{}(srv)).(StatsOps); ok {
|
|
sop.statsRegister()
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (srv *Srv) String() string {
|
|
return srv.Id
|
|
}
|
|
|
|
func (req *SrvReq) process() {
|
|
req.Lock()
|
|
flushed := (req.status & reqFlush) != 0
|
|
if !flushed {
|
|
req.status |= reqWork
|
|
}
|
|
req.Unlock()
|
|
|
|
if flushed {
|
|
req.Respond()
|
|
}
|
|
|
|
if rop, ok := (req.Conn.Srv.ops).(SrvReqProcessOps); ok {
|
|
rop.SrvReqProcess(req)
|
|
} else {
|
|
req.Process()
|
|
}
|
|
|
|
req.Lock()
|
|
req.status &= ^reqWork
|
|
if !(req.status&reqResponded != 0) {
|
|
req.status |= reqSaved
|
|
}
|
|
req.Unlock()
|
|
}
|
|
|
|
// Performs the default processing of a request. Initializes
|
|
// the SrvFid, Afid and Newfid fields and calls the appropriate
|
|
// SrvReqOps operation for the message. The file server implementer
|
|
// should call it only if the file server implements the SrvReqProcessOps
|
|
// within the SrvReqProcess operation.
|
|
func (req *SrvReq) Process() {
|
|
conn := req.Conn
|
|
srv := conn.Srv
|
|
tc := req.Tc
|
|
|
|
if tc.Fid != NOFID && tc.Type != Tattach {
|
|
srv.Lock()
|
|
req.Fid = conn.FidGet(tc.Fid)
|
|
srv.Unlock()
|
|
if req.Fid == nil {
|
|
req.RespondError(Eunknownfid)
|
|
return
|
|
}
|
|
}
|
|
|
|
switch req.Tc.Type {
|
|
default:
|
|
req.RespondError(&Error{"unknown message type", EINVAL})
|
|
|
|
case Tversion:
|
|
srv.version(req)
|
|
|
|
case Tauth:
|
|
if runtime.GOOS == "windows" {
|
|
return
|
|
}
|
|
srv.auth(req)
|
|
|
|
case Tattach:
|
|
srv.attach(req)
|
|
|
|
case Tflush:
|
|
srv.flush(req)
|
|
|
|
case Twalk:
|
|
srv.walk(req)
|
|
|
|
case Topen:
|
|
srv.open(req)
|
|
|
|
case Tcreate:
|
|
srv.create(req)
|
|
|
|
case Tread:
|
|
srv.read(req)
|
|
|
|
case Twrite:
|
|
srv.write(req)
|
|
|
|
case Tclunk:
|
|
srv.clunk(req)
|
|
|
|
case Tremove:
|
|
srv.remove(req)
|
|
|
|
case Tstat:
|
|
srv.stat(req)
|
|
|
|
case Twstat:
|
|
srv.wstat(req)
|
|
}
|
|
}
|
|
|
|
// Performs the post processing required if the (*SrvReq) Process() method
|
|
// is called for a request. The file server implementer should call it
|
|
// only if the file server implements the SrvReqProcessOps within the
|
|
// SrvReqRespond operation.
|
|
func (req *SrvReq) PostProcess() {
|
|
srv := req.Conn.Srv
|
|
|
|
/* call the post-handlers (if needed) */
|
|
switch req.Tc.Type {
|
|
case Tauth:
|
|
srv.authPost(req)
|
|
|
|
case Tattach:
|
|
srv.attachPost(req)
|
|
|
|
case Twalk:
|
|
srv.walkPost(req)
|
|
|
|
case Topen:
|
|
srv.openPost(req)
|
|
|
|
case Tcreate:
|
|
srv.createPost(req)
|
|
|
|
case Tread:
|
|
srv.readPost(req)
|
|
|
|
case Tclunk:
|
|
srv.clunkPost(req)
|
|
|
|
case Tremove:
|
|
srv.removePost(req)
|
|
}
|
|
|
|
if req.Fid != nil {
|
|
req.Fid.DecRef()
|
|
req.Fid = nil
|
|
}
|
|
|
|
if req.Afid != nil {
|
|
req.Afid.DecRef()
|
|
req.Afid = nil
|
|
}
|
|
|
|
if req.Newfid != nil {
|
|
req.Newfid.DecRef()
|
|
req.Newfid = nil
|
|
}
|
|
}
|
|
|
|
// The Respond method sends response back to the client. The req.Rc value
|
|
// should be initialized and contain valid 9P2000 message. In most cases
|
|
// the file server implementer shouldn't call this method directly. Instead
|
|
// one of the RespondR* methods should be used.
|
|
func (req *SrvReq) Respond() {
|
|
var flushreqs *SrvReq
|
|
|
|
conn := req.Conn
|
|
req.Lock()
|
|
status := req.status
|
|
req.status |= reqResponded
|
|
req.status &= ^reqWork
|
|
req.Unlock()
|
|
|
|
if (status & reqResponded) != 0 {
|
|
return
|
|
}
|
|
|
|
/* remove the request and all requests flushing it */
|
|
conn.Lock()
|
|
nextreq := req.prev
|
|
if nextreq != nil {
|
|
nextreq.next = nil
|
|
// if there are flush requests, move them to the next request
|
|
if req.flushreq != nil {
|
|
var p *SrvReq = nil
|
|
r := nextreq.flushreq
|
|
for ; r != nil; p, r = r, r.flushreq {
|
|
}
|
|
|
|
if p == nil {
|
|
nextreq.flushreq = req.flushreq
|
|
} else {
|
|
nextreq = req.flushreq
|
|
}
|
|
}
|
|
|
|
flushreqs = nil
|
|
} else {
|
|
delete(conn.reqs, req.Tc.Tag)
|
|
flushreqs = req.flushreq
|
|
}
|
|
conn.Unlock()
|
|
|
|
if rop, ok := (req.Conn.Srv.ops).(SrvReqProcessOps); ok {
|
|
rop.SrvReqRespond(req)
|
|
} else {
|
|
req.PostProcess()
|
|
}
|
|
|
|
if (status & reqFlush) == 0 {
|
|
conn.reqout <- req
|
|
}
|
|
|
|
// process the next request with the same tag (if available)
|
|
if nextreq != nil {
|
|
go nextreq.process()
|
|
}
|
|
|
|
// respond to the flush messages
|
|
// can't send the responses directly to conn.reqout, because the
|
|
// flushes may be in a tag group too
|
|
for freq := flushreqs; freq != nil; freq = freq.flushreq {
|
|
freq.Respond()
|
|
}
|
|
}
|
|
|
|
// Should be called to cancel a request. Should only be called
|
|
// from the Flush operation if the FlushOp is implemented.
|
|
func (req *SrvReq) Flush() {
|
|
req.Lock()
|
|
req.status |= reqFlush
|
|
req.Unlock()
|
|
req.Respond()
|
|
}
|
|
|
|
// Lookup a SrvFid struct based on the 32-bit identifier sent over the wire.
|
|
// Returns nil if the fid is not found. Increases the reference count of
|
|
// the returned fid. The user is responsible to call DecRef once it no
|
|
// longer needs it.
|
|
func (conn *Conn) FidGet(fidno uint32) *SrvFid {
|
|
conn.Lock()
|
|
fid, present := conn.fidpool[fidno]
|
|
conn.Unlock()
|
|
if present {
|
|
fid.IncRef()
|
|
}
|
|
|
|
return fid
|
|
}
|
|
|
|
// Creates a new SrvFid struct for the fidno integer. Returns nil
|
|
// if the SrvFid for that number already exists. The returned fid
|
|
// has reference count set to 1.
|
|
func (conn *Conn) FidNew(fidno uint32) *SrvFid {
|
|
conn.Lock()
|
|
_, present := conn.fidpool[fidno]
|
|
if present {
|
|
conn.Unlock()
|
|
return nil
|
|
}
|
|
|
|
fid := new(SrvFid)
|
|
fid.fid = fidno
|
|
fid.refcount = 1
|
|
fid.Fconn = conn
|
|
conn.fidpool[fidno] = fid
|
|
conn.Unlock()
|
|
|
|
return fid
|
|
}
|
|
|
|
func (conn *Conn) String() string {
|
|
return conn.Srv.Id + "/" + conn.Id
|
|
}
|
|
|
|
// Increase the reference count for the fid.
|
|
func (fid *SrvFid) IncRef() {
|
|
fid.Lock()
|
|
fid.refcount++
|
|
fid.Unlock()
|
|
}
|
|
|
|
// Decrease the reference count for the fid. When the
|
|
// reference count reaches 0, the fid is no longer valid.
|
|
func (fid *SrvFid) DecRef() {
|
|
fid.Lock()
|
|
fid.refcount--
|
|
n := fid.refcount
|
|
fid.Unlock()
|
|
|
|
if n > 0 {
|
|
return
|
|
}
|
|
|
|
conn := fid.Fconn
|
|
conn.Lock()
|
|
delete(conn.fidpool, fid.fid)
|
|
conn.Unlock()
|
|
|
|
if fop, ok := (conn.Srv.ops).(SrvFidOps); ok {
|
|
fop.FidDestroy(fid)
|
|
}
|
|
}
|