lib9p

Go 9P library.
Log | Files | Refs

commit 65a9b341699f585edfce4a150914284ea6b61224
parent cb0b19d1028d8f537cc4b1272478c530d4bb9ab4
Author: Matsuda Kenji <info@mtkn.jp>
Date:   Fri, 29 Sep 2023 11:48:13 +0900

merge client

Diffstat:
Aclient.go | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mclient_test.go | 283++++++++++---------------------------------------------------------------------
Mfcall.go | 18++++++++++++++++++
Mfile.go | 49++++++++++++++++++++++++++++++++++++++++++++++++-
Mfs.go | 8++++++++
Mserver.go | 10+++++-----
Cclient_test.go -> server_test.go | 0
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