commit 74ec7ac9af14822d5ae949cdd3cb58e9ffb6ea76 from: Stephen J Day date: Fri Oct 30 04:18:10 2015 UTC fs/p9p/new: stat implementation and correct dir/Rstat encoding Signed-off-by: Stephen J Day 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