463 lines
8.8 KiB
Go
463 lines
8.8 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 clnt package go9provides definitions and functions used to implement
|
|
// a 9P2000 file client.
|
|
package go9p
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"sync"
|
|
"sync/atomic"
|
|
)
|
|
|
|
// The Clnt type represents a 9P2000 client. The client is connected to
|
|
// a 9P2000 file server and its methods can be used to access and manipulate
|
|
// the files exported by the server.
|
|
type Clnt struct {
|
|
sync.Mutex
|
|
Debuglevel int // =0 don't print anything, >0 print Fcalls, >1 print raw packets
|
|
Msize uint32 // Maximum size of the 9P messages
|
|
Dotu bool // If true, 9P2000.u protocol is spoken
|
|
Root *Fid // Fid that points to the root directory
|
|
Id string // Used when printing debug messages
|
|
Log *Logger
|
|
|
|
conn net.Conn
|
|
tagpool *pool
|
|
fidpool *pool
|
|
reqout chan *Req
|
|
done chan bool
|
|
reqfirst *Req
|
|
reqlast *Req
|
|
err error
|
|
|
|
reqchan chan *Req
|
|
tchan chan *Fcall
|
|
|
|
next, prev *Clnt
|
|
}
|
|
|
|
// A Fid type represents a file on the server. Fids are used for the
|
|
// low level methods that correspond directly to the 9P2000 message requests
|
|
type Fid struct {
|
|
sync.Mutex
|
|
Clnt *Clnt // Client the fid belongs to
|
|
Iounit uint32
|
|
Qid // The Qid description for the file
|
|
Mode uint8 // Open mode (one of O* values) (if file is open)
|
|
Fid uint32 // Fid number
|
|
User // The user the fid belongs to
|
|
walked bool // true if the fid points to a walked file on the server
|
|
}
|
|
|
|
// The file is similar to the Fid, but is used in the high-level client
|
|
// interface. We expose the Fid so that client code can use Remove
|
|
// on a fid, the same way a kernel can.
|
|
type File struct {
|
|
Fid *Fid
|
|
offset uint64
|
|
}
|
|
|
|
type Req struct {
|
|
sync.Mutex
|
|
Clnt *Clnt
|
|
Tc *Fcall
|
|
Rc *Fcall
|
|
Err error
|
|
Done chan *Req
|
|
tag uint16
|
|
prev, next *Req
|
|
fid *Fid
|
|
}
|
|
|
|
type ClntList struct {
|
|
sync.Mutex
|
|
clntList, clntLast *Clnt
|
|
}
|
|
|
|
var clnts *ClntList
|
|
var DefaultDebuglevel int
|
|
var DefaultLogger *Logger
|
|
|
|
func (clnt *Clnt) Rpcnb(r *Req) error {
|
|
var tag uint16
|
|
|
|
if r.Tc.Type == Tversion {
|
|
tag = NOTAG
|
|
} else {
|
|
tag = r.tag
|
|
}
|
|
|
|
SetTag(r.Tc, tag)
|
|
clnt.Lock()
|
|
if clnt.err != nil {
|
|
clnt.Unlock()
|
|
return clnt.err
|
|
}
|
|
|
|
if clnt.reqlast != nil {
|
|
clnt.reqlast.next = r
|
|
} else {
|
|
clnt.reqfirst = r
|
|
}
|
|
|
|
r.prev = clnt.reqlast
|
|
clnt.reqlast = r
|
|
clnt.Unlock()
|
|
|
|
clnt.reqout <- r
|
|
return nil
|
|
}
|
|
|
|
func (clnt *Clnt) Rpc(tc *Fcall) (rc *Fcall, err error) {
|
|
r := clnt.ReqAlloc()
|
|
r.Tc = tc
|
|
r.Done = make(chan *Req)
|
|
err = clnt.Rpcnb(r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
<-r.Done
|
|
rc = r.Rc
|
|
err = r.Err
|
|
clnt.ReqFree(r)
|
|
return
|
|
}
|
|
|
|
func (clnt *Clnt) recv() {
|
|
var err error
|
|
var buf []byte
|
|
|
|
err = nil
|
|
pos := 0
|
|
for {
|
|
// Connect can change the client Msize.
|
|
clntmsize := int(atomic.LoadUint32(&clnt.Msize))
|
|
if len(buf) < clntmsize {
|
|
b := make([]byte, clntmsize*8)
|
|
copy(b, buf[0:pos])
|
|
buf = b
|
|
b = nil
|
|
}
|
|
|
|
n, oerr := clnt.conn.Read(buf[pos:])
|
|
if oerr != nil || n == 0 {
|
|
err = &Error{oerr.Error(), EIO}
|
|
clnt.Lock()
|
|
clnt.err = err
|
|
clnt.Unlock()
|
|
goto closed
|
|
}
|
|
|
|
pos += n
|
|
for pos > 4 {
|
|
sz, _ := Gint32(buf)
|
|
if pos < int(sz) {
|
|
if len(buf) < int(sz) {
|
|
b := make([]byte, atomic.LoadUint32(&clnt.Msize)*8)
|
|
copy(b, buf[0:pos])
|
|
buf = b
|
|
b = nil
|
|
}
|
|
|
|
break
|
|
}
|
|
|
|
fc, err, fcsize := Unpack(buf, clnt.Dotu)
|
|
clnt.Lock()
|
|
if err != nil {
|
|
clnt.err = err
|
|
clnt.conn.Close()
|
|
clnt.Unlock()
|
|
goto closed
|
|
}
|
|
|
|
if clnt.Debuglevel > 0 {
|
|
clnt.logFcall(fc)
|
|
if clnt.Debuglevel&DbgPrintPackets != 0 {
|
|
log.Println("}-}", clnt.Id, fmt.Sprint(fc.Pkt))
|
|
}
|
|
|
|
if clnt.Debuglevel&DbgPrintFcalls != 0 {
|
|
log.Println("}}}", clnt.Id, fc.String())
|
|
}
|
|
}
|
|
|
|
var r *Req = nil
|
|
for r = clnt.reqfirst; r != nil; r = r.next {
|
|
if r.Tc.Tag == fc.Tag {
|
|
break
|
|
}
|
|
}
|
|
|
|
if r == nil {
|
|
clnt.err = &Error{"unexpected response", EINVAL}
|
|
clnt.conn.Close()
|
|
clnt.Unlock()
|
|
goto closed
|
|
}
|
|
|
|
r.Rc = fc
|
|
if r.prev != nil {
|
|
r.prev.next = r.next
|
|
} else {
|
|
clnt.reqfirst = r.next
|
|
}
|
|
|
|
if r.next != nil {
|
|
r.next.prev = r.prev
|
|
} else {
|
|
clnt.reqlast = r.prev
|
|
}
|
|
clnt.Unlock()
|
|
|
|
if r.Tc.Type != r.Rc.Type-1 {
|
|
if r.Rc.Type != Rerror {
|
|
r.Err = &Error{"invalid response", EINVAL}
|
|
log.Println(fmt.Sprintf("TTT %v", r.Tc))
|
|
log.Println(fmt.Sprintf("RRR %v", r.Rc))
|
|
} else {
|
|
if r.Err == nil {
|
|
r.Err = &Error{r.Rc.Error, r.Rc.Errornum}
|
|
}
|
|
}
|
|
}
|
|
|
|
if r.Done != nil {
|
|
r.Done <- r
|
|
}
|
|
|
|
pos -= fcsize
|
|
buf = buf[fcsize:]
|
|
}
|
|
}
|
|
|
|
closed:
|
|
clnt.done <- true
|
|
|
|
/* send error to all pending requests */
|
|
clnt.Lock()
|
|
r := clnt.reqfirst
|
|
clnt.reqfirst = nil
|
|
clnt.reqlast = nil
|
|
if err == nil {
|
|
err = clnt.err
|
|
}
|
|
clnt.Unlock()
|
|
for ; r != nil; r = r.next {
|
|
r.Err = err
|
|
if r.Done != nil {
|
|
r.Done <- r
|
|
}
|
|
}
|
|
|
|
clnts.Lock()
|
|
if clnt.prev != nil {
|
|
clnt.prev.next = clnt.next
|
|
} else {
|
|
clnts.clntList = clnt.next
|
|
}
|
|
|
|
if clnt.next != nil {
|
|
clnt.next.prev = clnt.prev
|
|
} else {
|
|
clnts.clntLast = clnt.prev
|
|
}
|
|
clnts.Unlock()
|
|
|
|
if sop, ok := (interface{}(clnt)).(StatsOps); ok {
|
|
sop.statsUnregister()
|
|
}
|
|
}
|
|
|
|
func (clnt *Clnt) send() {
|
|
for {
|
|
select {
|
|
case <-clnt.done:
|
|
return
|
|
|
|
case req := <-clnt.reqout:
|
|
if clnt.Debuglevel > 0 {
|
|
clnt.logFcall(req.Tc)
|
|
if clnt.Debuglevel&DbgPrintPackets != 0 {
|
|
log.Println("{-{", clnt.Id, fmt.Sprint(req.Tc.Pkt))
|
|
}
|
|
|
|
if clnt.Debuglevel&DbgPrintFcalls != 0 {
|
|
log.Println("{{{", clnt.Id, req.Tc.String())
|
|
}
|
|
}
|
|
|
|
for buf := req.Tc.Pkt; len(buf) > 0; {
|
|
n, err := clnt.conn.Write(buf)
|
|
if err != nil {
|
|
/* just close the socket, will get signal on clnt.done */
|
|
clnt.conn.Close()
|
|
break
|
|
}
|
|
|
|
buf = buf[n:]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Creates and initializes a new Clnt object. Doesn't send any data
|
|
// on the wire.
|
|
func NewClnt(c net.Conn, msize uint32, dotu bool) *Clnt {
|
|
clnt := new(Clnt)
|
|
clnt.conn = c
|
|
clnt.Msize = msize
|
|
clnt.Dotu = dotu
|
|
clnt.Debuglevel = DefaultDebuglevel
|
|
clnt.Log = DefaultLogger
|
|
clnt.Id = c.RemoteAddr().String() + ":"
|
|
clnt.tagpool = newPool(uint32(NOTAG))
|
|
clnt.fidpool = newPool(NOFID)
|
|
clnt.reqout = make(chan *Req)
|
|
clnt.done = make(chan bool)
|
|
clnt.reqchan = make(chan *Req, 16)
|
|
clnt.tchan = make(chan *Fcall, 16)
|
|
|
|
go clnt.recv()
|
|
go clnt.send()
|
|
|
|
clnts.Lock()
|
|
if clnts.clntLast != nil {
|
|
clnts.clntLast.next = clnt
|
|
} else {
|
|
clnts.clntList = clnt
|
|
}
|
|
|
|
clnt.prev = clnts.clntLast
|
|
clnts.clntLast = clnt
|
|
clnts.Unlock()
|
|
|
|
if sop, ok := (interface{}(clnt)).(StatsOps); ok {
|
|
sop.statsRegister()
|
|
}
|
|
|
|
return clnt
|
|
}
|
|
|
|
// Establishes a new socket connection to the 9P server and creates
|
|
// a client object for it. Negotiates the dialect and msize for the
|
|
// connection. Returns a Clnt object, or Error.
|
|
func Connect(c net.Conn, msize uint32, dotu bool) (*Clnt, error) {
|
|
clnt := NewClnt(c, msize, dotu)
|
|
ver := "9P2000"
|
|
if clnt.Dotu {
|
|
ver = "9P2000.u"
|
|
}
|
|
|
|
clntmsize := atomic.LoadUint32(&clnt.Msize)
|
|
tc := NewFcall(clntmsize)
|
|
err := PackTversion(tc, clntmsize, ver)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rc, err := clnt.Rpc(tc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if rc.Msize < atomic.LoadUint32(&clnt.Msize) {
|
|
atomic.StoreUint32(&clnt.Msize, rc.Msize)
|
|
}
|
|
|
|
clnt.Dotu = rc.Version == "9P2000.u" && clnt.Dotu
|
|
return clnt, nil
|
|
}
|
|
|
|
// Creates a new Fid object for the client
|
|
func (clnt *Clnt) FidAlloc() *Fid {
|
|
fid := new(Fid)
|
|
fid.Fid = clnt.fidpool.getId()
|
|
fid.Clnt = clnt
|
|
|
|
return fid
|
|
}
|
|
|
|
func (clnt *Clnt) NewFcall() *Fcall {
|
|
select {
|
|
case tc := <-clnt.tchan:
|
|
return tc
|
|
default:
|
|
}
|
|
return NewFcall(atomic.LoadUint32(&clnt.Msize))
|
|
}
|
|
|
|
func (clnt *Clnt) FreeFcall(fc *Fcall) {
|
|
if fc != nil && len(fc.Buf) >= int(atomic.LoadUint32(&clnt.Msize)) {
|
|
select {
|
|
case clnt.tchan <- fc:
|
|
break
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
|
|
func (clnt *Clnt) ReqAlloc() *Req {
|
|
var req *Req
|
|
select {
|
|
case req = <-clnt.reqchan:
|
|
break
|
|
default:
|
|
req = new(Req)
|
|
req.Clnt = clnt
|
|
req.tag = uint16(clnt.tagpool.getId())
|
|
}
|
|
return req
|
|
}
|
|
|
|
func (clnt *Clnt) ReqFree(req *Req) {
|
|
clnt.FreeFcall(req.Tc)
|
|
req.Tc = nil
|
|
req.Rc = nil
|
|
req.Err = nil
|
|
req.Done = nil
|
|
req.next = nil
|
|
req.prev = nil
|
|
|
|
select {
|
|
case clnt.reqchan <- req:
|
|
break
|
|
default:
|
|
clnt.tagpool.putId(uint32(req.tag))
|
|
}
|
|
}
|
|
|
|
func (clnt *Clnt) logFcall(fc *Fcall) {
|
|
if clnt.Debuglevel&DbgLogPackets != 0 {
|
|
pkt := make([]byte, len(fc.Pkt))
|
|
copy(pkt, fc.Pkt)
|
|
clnt.Log.Log(pkt, clnt, DbgLogPackets)
|
|
}
|
|
|
|
if clnt.Debuglevel&DbgLogFcalls != 0 {
|
|
f := new(Fcall)
|
|
*f = *fc
|
|
f.Pkt = nil
|
|
clnt.Log.Log(f, clnt, DbgLogFcalls)
|
|
}
|
|
}
|
|
|
|
// FidFile returns a File that represents the given Fid, initially at the given
|
|
// offset.
|
|
func FidFile(fid *Fid, offset uint64) *File {
|
|
return &File{fid, offset}
|
|
}
|
|
|
|
func init() {
|
|
clnts = new(ClntList)
|
|
if sop, ok := (interface{}(clnts)).(StatsOps); ok {
|
|
sop.statsRegister()
|
|
}
|
|
}
|