Commit Diff


commit - 0a9b2988af0043c8cace650ce0a94b3e41f55e21
commit + 74ec7ac9af14822d5ae949cdd3cb58e9ffb6ea76
blob - d6e65d033474011bd20818b6180c6152fe523906
blob + 52786490fc789cd9c9104fda5820ec0ddf135dbe
--- channel.go
+++ channel.go
@@ -115,8 +115,11 @@ func (ch *channel) readFcall(ctx context.Context, fcal
 		return fmt.Errorf("message large than buffer:", n)
 	}
 
-	log.Println("channel: readFcall", fcall)
-	return ch.codec.Unmarshal(ch.rdbuf[:n], fcall)
+	if err := ch.codec.Unmarshal(ch.rdbuf[:n], fcall); err != nil {
+		return err
+	}
+	log.Println("channel: recv", fcall)
+	return nil
 }
 
 func (ch *channel) writeFcall(ctx context.Context, fcall *Fcall) error {
@@ -125,7 +128,7 @@ func (ch *channel) writeFcall(ctx context.Context, fca
 		return ErrClosed
 	default:
 	}
-	log.Println("channel: writeFcall", fcall)
+	log.Println("channel: send", fcall)
 
 	deadline, ok := ctx.Deadline()
 	if !ok {
@@ -187,7 +190,6 @@ func readmsg(rd io.Reader, p []byte) (n int, err error
 		}
 	}
 
-	log.Println("msg", n, msize, mbody, len(p), p)
 	return n, nil
 }
 
blob - 4a1ab7014946f4804f93da8f94cc92ba0d4d27a4
blob + 9e1f5f447816c2a1ba015bc2513c4dc1638d1d4f
--- client.go
+++ client.go
@@ -205,8 +205,20 @@ func (c *client) Create(ctx context.Context, parent Fi
 	panic("not implemented")
 }
 
-func (c *client) Stat(context.Context, Fid) (Dir, error) {
-	panic("not implemented")
+func (c *client) Stat(ctx context.Context, fid Fid) (Dir, error) {
+	fcall := newFcall(MessageTstat{Fid: fid})
+
+	resp, err := c.transport.send(ctx, fcall)
+	if err != nil {
+		return Dir{}, err
+	}
+
+	respmsg, ok := resp.Message.(*MessageRstat)
+	if !ok {
+		return Dir{}, fmt.Errorf("invalid rpc response for stat message: %v", resp)
+	}
+
+	return respmsg.Stat, nil
 }
 
 func (c *client) WStat(context.Context, Fid, Dir) error {
blob - 9c73f8688214ada8d9cd4d8faf0ac0083b926f72
blob + 6b1b96c9ed0775de822f9583ffd5c41b2da5ad13
--- cmd/9pr/main.go
+++ cmd/9pr/main.go
@@ -59,7 +59,7 @@ func main() {
 	completer := readline.NewPrefixCompleter(
 		readline.PcItem("ls"),
 		// readline.PcItem("find"),
-		// readline.PcItem("stat"),
+		readline.PcItem("stat"),
 		readline.PcItem("cat"),
 		readline.PcItem("cd"),
 		readline.PcItem("pwd"),
@@ -123,6 +123,8 @@ func main() {
 			cmd = commander.cmdpwd
 		case "cat":
 			cmd = commander.cmdcat
+		case "stat":
+			cmd = commander.cmdstat
 		default:
 			cmd = func(ctx context.Context, args ...string) error {
 				return fmt.Errorf("command not implemented")
@@ -176,7 +178,7 @@ func (c *fsCommander) cmdls(ctx context.Context, args 
 		}
 		defer c.session.Clunk(ctx, targetfid)
 
-		qid, iounit, err := c.session.Open(ctx, targetfid, p9pnew.OREAD)
+		_, iounit, err := c.session.Open(ctx, targetfid, p9pnew.OREAD)
 		if err != nil {
 			return err
 		}
@@ -245,6 +247,34 @@ func (c *fsCommander) cmdcd(ctx context.Context, args 
 	c.pwdfid = targetfid
 
 	return nil
+}
+
+func (c *fsCommander) cmdstat(ctx context.Context, args ...string) error {
+	ps := []string{c.pwd}
+	if len(args) > 0 {
+		ps = args
+	}
+
+	wr := tabwriter.NewWriter(c.stdout, 0, 8, 8, ' ', 0)
+
+	for _, p := range ps {
+		targetfid := c.nextfid
+		c.nextfid++
+		components := strings.Split(strings.Trim(p, "/"), "/")
+		if _, err := c.session.Walk(ctx, c.rootfid, targetfid, components...); err != nil {
+			return err
+		}
+		defer c.session.Clunk(ctx, targetfid)
+
+		d, err := c.session.Stat(ctx, targetfid)
+		if err != nil {
+			return err
+		}
+
+		fmt.Fprintf(wr, "%v\t%v\t%v\t%s\n", os.FileMode(d.Mode), d.Length, d.ModTime, d.Name)
+	}
+
+	return wr.Flush()
 }
 
 func (c *fsCommander) cmdpwd(ctx context.Context, args ...string) error {
blob - 165a918e4c7c269d32ed20dab90b79e57a3aa8e1
blob + 2946cb9286fea0999eccc58cd13bb1147f8ca13d
--- encoding.go
+++ encoding.go
@@ -104,20 +104,44 @@ func (e *encoder) encode(vs ...interface{}) error {
 			if err != nil {
 				return err
 			}
-		case Message, *Qid, *Dir:
-			// BUG(stevvooe): The encoding for Dir is incorrect. Under certain
-			// cases, we need to include size field and in other cases, such
-			// as Twstat, we need the size twice. See bugs in
-			// http://man.cat-v.org/plan_9/5/stat to make sense of this.
+		case *Dir:
+			// NOTE(stevvooe): See bugs in http://man.cat-v.org/plan_9/5/stat
+			// to make sense of this. The field has been included here but we
+			// need to make sure to double emit it for Rstat.
 
 			elements, err := fields9p(v)
 			if err != nil {
 				return err
 			}
 
+			elements = append([]interface{}{uint16(size9p(elements...))}, elements...)
+
 			if err := e.encode(elements...); err != nil {
 				return err
 			}
+		case Message:
+			elements, err := fields9p(v)
+			if err != nil {
+				return err
+			}
+
+			switch v.(type) {
+			case MessageRstat, *MessageRstat:
+				// encode an size header in front of the dir field
+				elements = append([]interface{}{uint16(size9p(elements...))}, elements...)
+			}
+
+			if err := e.encode(elements...); err != nil {
+				return err
+			}
+		case *Qid:
+			if err := e.encode(*v); err != nil {
+				return err
+			}
+		case Qid:
+			if err := e.encode(v.Type, v.Version, v.Path); err != nil {
+				return err
+			}
 		case *[]Qid:
 			if err := e.encode(*v); err != nil {
 				return err
@@ -284,15 +308,30 @@ func (d *decoder) decode(vs ...interface{}) error {
 			if err := dec.decode(elements...); err != nil {
 				return err
 			}
-		case Message, *Qid:
+		case Message:
 			elements, err := fields9p(v)
 			if err != nil {
 				return err
+			}
+
+			// special case twstat and rstat for size fields. See bugs in
+			// http://man.cat-v.org/plan_9/5/stat to make sense of this.
+			switch v.(type) {
+			case *MessageRstat, MessageRstat:
+				// decode extra size header for stat structure.
+				var ll uint16
+				if err := d.decode(&ll); err != nil {
+					return err
+				}
 			}
 
 			if err := d.decode(elements...); err != nil {
 				return err
 			}
+		case *Qid:
+			if err := d.decode(&v.Type, &v.Version, &v.Path); err != nil {
+				return err
+			}
 		default:
 			if err := binary.Read(d.rd, binary.LittleEndian, v); err != nil {
 				return err
@@ -330,7 +369,7 @@ func size9p(vs ...interface{}) uint32 {
 
 			s += size9p(elements...)
 		case *[]byte:
-			s += size9p(uint16(0), *v)
+			s += size9p(uint32(0), *v)
 		case *[]Qid:
 			s += size9p(*v)
 		case []Qid:
@@ -342,7 +381,9 @@ func size9p(vs ...interface{}) uint32 {
 			s += size9p(elements...)
 		case time.Time, *time.Time:
 			s += size9p(uint32(0))
-		case Message, *Qid, *Dir:
+		case Qid:
+			s += size9p(v.Type, v.Version, v.Path)
+		case Dir:
 			// walk the fields of the message to get the total size. we just
 			// use the field order from the message struct. We may add tag
 			// ignoring if needed.
@@ -356,7 +397,34 @@ func size9p(vs ...interface{}) uint32 {
 				panic(err)
 			}
 
+			s += size9p(elements...) + size9p(uint16(0))
+		case Message:
+
+			// special case twstat and rstat for size fields. See bugs in
+			// http://man.cat-v.org/plan_9/5/stat to make sense of this.
+			switch v.(type) {
+			case *MessageRstat, MessageRstat:
+				s += size9p(uint16(0)) // for extra size field before dir
+			}
+
+			// walk the fields of the message to get the total size. we just
+			// use the field order from the message struct. We may add tag
+			// ignoring if needed.
+			elements, err := fields9p(v)
+			if err != nil {
+				// BUG(stevvooe): The options here are to return 0, panic or
+				// make this return an error. Ideally, we make it safe to
+				// return 0 and have the rest of the package do the right
+				// thing. For now, we do this, but may want to panic until
+				// things are stable.
+				panic(err)
+			}
+
 			s += size9p(elements...)
+		case *Qid:
+			s += size9p(*v)
+		case *Dir:
+			s += size9p(*v)
 		case Fcall:
 			s += size9p(v.Type, v.Tag, v.Message)
 		case *Fcall:
@@ -414,7 +482,7 @@ func string9p(v interface{}) string {
 	for i := 0; i < rv.NumField(); i++ {
 		f := rv.Field(i)
 
-		s += fmt.Sprintf(" %v=%v", strings.ToLower(rv.Type().Field(i).Name), f)
+		s += fmt.Sprintf(" %v=%v", strings.ToLower(rv.Type().Field(i).Name), f.Interface())
 	}
 
 	return s
blob - 31ff66b1312bc7cae6d6b1ec9f8cc17ece4b5744
blob + 2310a2c5f859b1b9f2486c722d01e703a67ca0a7
--- encoding_test.go
+++ encoding_test.go
@@ -110,7 +110,7 @@ func TestEncodeDecode(t *testing.T) {
 			},
 			marshaled: []byte{
 				0x75, 0xb4, 0x15,
-				0x12, 0x0,
+				0x12, 0x0, 0x0, 0x0,
 				0x61, 0x20, 0x6c, 0x6f, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x62, 0x79, 0x74, 0x65, 0x20, 0x64, 0x61, 0x74, 0x61},
 		},
 		{
@@ -140,7 +140,8 @@ func TestEncodeDecode(t *testing.T) {
 			},
 			marshaled: []byte{
 				0x7d, 0xb4, 0x15,
-				// 0x40, 0x0, // TODO(stevvooe): Include Dir size. Not straightforward.
+				0x42, 0x0, // TODO(stevvooe): Include Dir size. Not straightforward.
+				0x40, 0x0, // TODO(stevvooe): Include Dir size. Not straightforward.
 				0xff, 0xff, // type
 				0xff, 0xff, 0xff, 0xff, // dev
 				0x80, 0xff, 0xff, 0xff, 0xff, // qid.type, qid.version
blob - 5689477107327c6ad5e089a80ad44d7096a206a3
blob + 9d1fc1aba43ebbc74dc6a62fe3de23ff731f0761
--- fcall.go
+++ fcall.go
@@ -107,15 +107,22 @@ type Fcall struct {
 }
 
 func newFcall(msg Message) *Fcall {
+	var tag Tag
+
+	switch msg.Type() {
+	case Tversion, Rversion:
+		tag = NOTAG
+	}
+
 	return &Fcall{
 		Type:    msg.Type(),
-		Tag:     NOTAG,
+		Tag:     tag,
 		Message: msg,
 	}
 }
 
 func (fc *Fcall) String() string {
-	return fmt.Sprintf("%8d %v(%v) %v", size9p(fc), fc.Type, fc.Tag, string9p(fc.Message))
+	return fmt.Sprintf("%v(%v) %v", fc.Type, fc.Tag, string9p(fc.Message))
 }
 
 type Message interface {
blob - f7b340c21172f8a3c4fa08da574a2301fc5d9910
blob + 5bdc01d70056b90a64fa6838de37153d3aec0cdb
--- transport.go
+++ transport.go
@@ -148,21 +148,28 @@ func (t *transport) handle() {
 		log.Println("wait...")
 		select {
 		case req := <-t.requests:
+			if req.fcall.Tag == NOTAG {
+				// NOTE(stevvooe): We disallow fcalls with NOTAG to come
+				// through this path since we can't join the tagged response
+				// with the waiting caller. This is typically used for the
+				// Tversion/Rversion round trip to setup a session.
+				//
+				// It may be better to allow these through but block all
+				// requests until a notag message has a response.
 
-			log.Println("send", req.fcall)
-			if req.fcall.Type != Tversion {
-				tags++
-				req.fcall.Tag = tags
-				outstanding[req.fcall.Tag] = req
-			} else {
-				// TODO(stevvooe): Man this protocol is bad. Version messages
-				// have no response tag. Effectively, the client can only have
-				// one version call outstanding at a time. We have to create
-				// an entire special code path to handle it. The client
-				// shouldn't proceed until the version reply is completed.
-				req.fcall.Tag = NOTAG
+				req.err <- fmt.Errorf("disallowed tag through transport")
+				continue
 			}
 
+			// BUG(stevvooe): This is an awful tag allocation procedure.
+			// Replace this with something that let's us allocate tags and
+			// associate data with them, returning to them to a pool when
+			// complete. Such a system would provide a lot of information
+			// about outstanding requests.
+			tags++
+			req.fcall.Tag = tags
+			outstanding[req.fcall.Tag] = req
+
 			// TODO(stevvooe): Consider the case of requests that never
 			// receive a response. We need to remove the fcall context from
 			// the tag map and dealloc the tag. We may also want to send a
blob - 604d967a1ddcf792eb4bba17b3110e4ac5814534
blob + 3eb82c83c1cc430459f85eda47c9ceefcff1f5d6
--- types.go
+++ types.go
@@ -90,7 +90,7 @@ type Qid struct {
 }
 
 func (qid Qid) String() string {
-	return fmt.Sprintf("Qid(%v, version=%x, path=%x)",
+	return fmt.Sprintf("qid(%v, v=%x, p=%x)",
 		qid.Type, qid.Version, qid.Path)
 }
 
@@ -114,5 +114,10 @@ type Dir struct {
 	MUID   string
 }
 
+func (d Dir) String() string {
+	return fmt.Sprintf("dir(%v mode=%v atime=%v mtime=%v length=%v name=%v uid=%v gid=%v muid=%v)",
+		d.Qid, d.Mode, d.AccessTime, d.ModTime, d.Length, d.Name, d.UID, d.GID, d.MUID)
+}
+
 //
 type Tag uint16