commit 65a9b341699f585edfce4a150914284ea6b61224
parent cb0b19d1028d8f537cc4b1272478c530d4bb9ab4
Author: Matsuda Kenji <info@mtkn.jp>
Date: Fri, 29 Sep 2023 11:48:13 +0900
merge client
Diffstat:
7 files changed, 190 insertions(+), 256 deletions(-)
diff --git a/client.go b/client.go
@@ -0,0 +1,77 @@
+package lib9p
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "sync"
+)
+
+type Client struct {
+ mSize uint32
+ uname string
+ fPool *FidPool
+ rPool *ReqPool
+ reader io.Reader
+ rlock *sync.Mutex
+ writer io.Writer
+ wlock *sync.Mutex
+}
+
+func NewClient(mSize uint32, uname string, r io.Reader, w io.Writer) *Client {
+ return &Client{
+ mSize: mSize,
+ uname: uname,
+ fPool: allocFidPool(),
+ rPool: allocReqPool(),
+ reader: r,
+ rlock: new(sync.Mutex),
+ writer: w,
+ wlock: new(sync.Mutex),
+ }
+}
+
+func transact(ctx context.Context, w io.Writer, r io.Reader, tmsg Msg) (<-chan Msg, error) {
+ if err := send(tmsg, w); err != nil {
+ return nil, fmt.Errorf("send: %v", err)
+ }
+ mc := make(chan Msg)
+ go func() {
+ defer close(mc)
+ rmc := make(chan Msg)
+ var err error
+ go func() {
+ defer close(rmc)
+ var rmsg Msg
+ rmsg, err = recv(r)
+ if err != nil {
+ return
+ }
+ rmc <- rmsg
+ }()
+ select {
+ case rmsg := <-rmc:
+ mc <- rmsg
+ case <-ctx.Done():
+ return
+ }
+ }()
+ return mc, nil
+}
+
+func (c *Client) Version(mSize uint32, version string) (<-chan *RVersion, error) {
+ tmsg := &TVersion{
+ tag: ^uint16(0),
+ mSize: mSize,
+ version: version,
+ }
+ rmsgc, err := transact(context.TODO(), c.writer, c.reader, tmsg)
+ if err != nil {
+ return nil, err
+ }
+ rversionc := make(chan *RVersion)
+ go func() {
+ rversionc<- (<-rmsgc).(*RVersion)
+ }()
+ return rversionc, nil
+}
+\ No newline at end of file
diff --git a/client_test.go b/client_test.go
@@ -1,263 +1,46 @@
package lib9p
import (
- "fmt"
"io"
- "path/filepath"
+ "reflect"
"testing"
)
-var fsys *testFS
-
-func init() {
- fsys = &testFS{
- root: &testFile{
- stat: Stat{
- Qid: Qid{Path: 0, Type: QTDIR},
- Mode: FileMode(DMDIR | 0755),
- Name: "root",
- Uid: "glenda",
- Gid: "glenda",
- Muid: "glenda",
- },
- children: []*testFile{
- &testFile{
- content: []byte("a\n"),
- stat: Stat{
- Qid: Qid{Path: 1, Type: QTFILE},
- Mode: FileMode(0644),
- Name: "a",
- Uid: "glenda",
- Gid: "glenda",
- Muid: "glenda",
- },
- },
- &testFile{
- content: []byte("b\n"),
- stat: Stat{
- Qid: Qid{Path: 2, Type: QTFILE},
- Mode: FileMode(0400),
- Name: "b",
- Uid: "ken",
- Gid: "ken",
- Muid: "ken",
- },
- },
- &testFile{
- stat: Stat{
- Qid: Qid{Path: 3, Type: QTDIR},
- Mode: FileMode(DMDIR | 0755),
- Name: "dir",
- Uid: "rob",
- Gid: "rob",
- Muid: "rob",
- },
- children: []*testFile{
- &testFile{
- content: []byte("unko\n"),
- stat: Stat{
- Qid: Qid{Path: 4, Type: QTFILE},
- Mode: FileMode(0666),
- Name: "file",
- Uid: "brian",
- Gid: "brian",
- Muid: "dennis",
- },
- },
- },
- },
- },
- },
- }
- setFsysAndParent(fsys, nil)
-}
-
-// SetFsysAndParent sets file.fsys and file.parent for every file in the fsys.
-// Pass nil as file to setup entire file system.
-func setFsysAndParent(fsys *testFS, file *testFile) {
- if file == nil {
- file = fsys.root
- file.parent = fsys.root
- file.fsys = fsys
- }
- for _, child := range file.children {
- child.parent = file
- child.fsys = fsys
- setFsysAndParent(fsys, child)
- }
-}
-
-func newReq(s *Server, msg Msg) (*Req, error) {
- r, err := s.rPool.add(msg.Tag())
- if err != nil {
- return nil, fmt.Errorf("ReqPool.add(%d): %w", msg.Tag(), err)
- }
- r.srv = s
- r.tag = msg.Tag()
- r.ifcall = msg
- return r, nil
-}
-
-func handleReq(s *Server, r *Req) {
- switch r.ifcall.(type) {
- default:
- respond(r, fmt.Errorf("unknown message type: %d", r.ifcall.Type()))
- case *TVersion:
- sVersion(s, r)
- case *TAuth:
- sAuth(s, r)
- case *TAttach:
- sAttach(s, r)
- case *TWalk:
- sWalk(s, r)
- case *TOpen:
- sOpen(s, r)
- case *TCreate:
- sCreate(s, r)
- case *TRead:
- sRead(s, r)
- case *TWrite:
- sWrite(s, r)
- case *TClunk:
- sClunk(s, r)
- case *TRemove:
- sRemove(s, r)
- case *TStat:
- sStat(s, r)
- case *TWStat:
- sWStat(s, r)
- }
-}
-
-// This function does the actual work for TestWalk().
-func testWalk(t *testing.T, fs *testFS, path string, file *testFile) {
- t.Logf("walk %s", path)
- f, err := fs.walk(split9path(path))
- if err != nil {
- t.Errorf("open %s: %v", path, err)
- }
- if f != file {
- t.Errorf("open %s: wrong file", path)
- }
- for _, child := range file.children {
- childpath := filepath.Join(path, child.stat.Name)
- testWalk(t, fs, childpath, child)
- }
-}
-
-// TestWalk walk through testFS in depth-first fassion,
-// checks if all files can be opened without error and if
-// the opened file is the same as the file accessed via testFS.child
-func TestWalk(t *testing.T) {
- testWalk(t, fsys, ".", fsys.root)
-}
-
-func TestServer(t *testing.T) {
- sr, _ := io.Pipe()
- cr, sw := io.Pipe()
- msg := []Msg{
- &TVersion{
- tag: ^uint16(0),
- mSize: 1024,
- version: "9P2000",
- },
- &TAttach{
- tag: 0,
- fid: 0,
- afid: NOFID,
- uname: "glenda",
- aname: "",
- },
- &TStat{
- tag: 0,
- fid: 0,
- },
- &TWalk{
- tag: 0,
- fid: 0,
- newFid: 1,
- wname: []string{},
- },
- &TOpen{
- tag: 0,
- fid: 1,
- mode: OREAD,
- },
- &TRead{
- tag: 0,
- fid: 1,
- offset: 0,
- count: 1024,
- },
- &TClunk{
- fid: 1,
- },
- &TWalk{
- tag: 0,
- fid: 0,
- newFid: 1,
- wname: []string{"dir", "file"},
- },
- &TOpen{
- tag: 0,
- fid: 1,
- mode: ORDWR,
- },
- &TRead{
- tag: 0,
- fid: 1,
- offset: 0,
- count: 1024,
- },
- &TWrite{
- tag: 0,
- fid: 1,
- offset: 2,
- count: 4,
- data: []byte("a"),
- },
- &TRead{
- tag: 0,
- fid: 1,
- offset: 0,
- count: 1024,
- },
- }
-
- s := NewServer(fsys, 1024, sr, sw)
-
- for _, m := range msg {
- r, err := newReq(s, m)
- if err != nil {
- t.Fatalf("newReq: %v", err)
- return
- }
- t.Logf("<-- %v\n", r.ifcall)
- go handleReq(s, r)
- buf := make([]byte, 1024)
-
- _, err = cr.Read(buf)
+func TestServerVersion(t *testing.T) {
+ const (
+ mSize = 8192
+ noTag = ^uint16(0)
+ )
+ tests := []struct {
+ name string
+ mSize uint32
+ version string
+ want Msg
+ }{
+ {"size", 4096, "9P2000",
+ &RVersion{tag: noTag, mSize: 4096, version: "9P2000"}},
+ {"valid", mSize, "9P2000",
+ &RVersion{tag: noTag, mSize: mSize, version: "9P2000"}},
+ {"invalid", mSize, "unko",
+ &RVersion{tag: noTag, mSize: mSize, version: "unknown"}},
+ }
+
+ for _, test := range tests {
+ t.Logf("%v\n", test)
+ cr, sw := io.Pipe()
+ sr, cw := io.Pipe()
+
+ server := NewServer(fsys, mSize, sr, sw)
+ client := NewClient(mSize, "kenji", cr, cw)
+ go server.Serve()
+ rvc, err := client.Version(test.mSize, test.version)
if err != nil {
- t.Fatalf("read: %v", err)
+ t.Fatal(err)
}
- t.Logf("--> %v\n", bufMsg(buf))
- if bufMsg(buf).Type() == Rread {
- rread := newRRead(buf)
- data := rread.Data()
- fid, ok := s.fPool.lookup(m.(*TRead).Fid())
- if !ok {
- t.Fatalf("lookup fid %d", m.(*TRead).Fid())
- }
- if fid.File.Qid().Type&QTDIR != 0 {
- for i := 0; i < len(data); {
- stat := newStat(data[i:])
- t.Logf("stat: %v", stat)
- i += int(stat.size()) + 2
- }
- } else {
- t.Logf("content: %s", string(data))
- }
+ got := <-rvc
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("%s: want %v, get %v\n", test.name, test.want, got)
}
}
}
diff --git a/fcall.go b/fcall.go
@@ -2,6 +2,7 @@ package lib9p
import (
"fmt"
+ "io"
)
type MsgType uint8
@@ -60,6 +61,23 @@ type Msg interface {
String() string
}
+func send(msg Msg, w io.Writer) error {
+ if _, err := w.Write(msg.marshal()); err != nil {
+ return fmt.Errorf("write: %v", err)
+ }
+ return nil
+}
+
+func recv(r io.Reader) (Msg, error) {
+ b, err := read9PMsg(r)
+ if err == io.EOF {
+ return nil, err
+ } else if err != nil {
+ return nil, fmt.Errorf("read9PMsg: %v", err)
+ }
+ return unmarshal(b)
+}
+
// bufMsg is Msg with just an array of bytes.
type bufMsg []byte
diff --git a/file.go b/file.go
@@ -1,6 +1,9 @@
package lib9p
-import "fmt"
+import (
+ "fmt"
+ "io"
+)
type File interface {
Parent() (File, error)
@@ -58,3 +61,46 @@ func walkfile(f File, name string) (File, error) {
}
return nil, fmt.Errorf("not found")
}
+
+type ClientFile struct {
+ stat Stat
+ name string
+ path string
+ parent *ClientFile
+ children []*ClientFile
+ fid *Fid
+}
+
+func (cf *ClientFile) Parent() (File, error) {
+ return cf.parent, nil
+}
+
+func (cf *ClientFile) Child() ([]File, error) {
+ children := make([]File, len(cf.children))
+ for i := 0; i < len(children); i++ {
+ children[i] = cf.children[i]
+ }
+ return children, nil
+}
+
+func (cf *ClientFile) Stat() (*FileInfo, error) {
+ return &FileInfo{cf.stat}, nil
+}
+
+func (cf *ClientFile) Qid() Qid {
+ return cf.stat.Qid
+}
+
+func (cf *ClientFile) Open(mode OpenMode) error {
+ cf.fid.OMode = mode
+ return nil
+}
+
+func (cf *ClientFile) Close() error {
+ cf.fid.OMode = -1
+ return nil
+}
+
+func (cf *ClientFile) Read(b []byte) (int, error) {
+ return 0, io.EOF
+}
+\ No newline at end of file
diff --git a/fs.go b/fs.go
@@ -8,6 +8,14 @@ type FS interface {
Root() File
}
+type ClientFS struct {
+ root *ClientFile
+}
+
+func (cfs ClientFS) Root() File {
+ return cfs.root
+}
+
func FSModeToQidType(fm fs.FileMode) QidType {
var qt QidType
if fm&fs.ModeDir != 0 {
diff --git a/server.go b/server.go
@@ -108,12 +108,11 @@ func getReq(r io.Reader, s *Server) (*Req, error) {
if err == io.EOF {
return nil, err
}
- return nil, fmt.Errorf("read9PMsg: %v", err)
+ return nil, fmt.Errorf("recv: %v", err)
}
req, err := s.rPool.add(bufMsg(buf).Tag())
if err != nil {
- log.Printf("addReq(%d): %v", bufMsg(buf).Tag(), err)
// duplicate tag: cons up a fake Req
req := new(Req)
req.srv = s
@@ -860,9 +859,6 @@ func respond(r *Req, err error) {
}
r.ofcall.SetTag(r.tag)
- if chatty9P {
- fmt.Fprintf(os.Stderr, "--> %s\n", r.ofcall)
- }
// free tag.
if r.pool == nil && err != ErrDupTag {
@@ -878,4 +874,8 @@ func respond(r *Req, err error) {
err := <-r.srv.speakErrChan
log.Fatalf("speak: %v", err)
}()
+
+ if chatty9P {
+ fmt.Fprintf(os.Stderr, "--> %s\n", r.ofcall)
+ }
}
diff --git a/client_test.go b/server_test.go