minikube/third_party/go9p/srv_srv.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)
}
}