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()
 | 
						|
	}
 | 
						|
}
 |