commit 6b84ea70bad42d1dc151a3547b21c8818527e78d from: Stephen J Day date: Wed Oct 28 22:41:56 2015 UTC fs/p9p/new: add repl for connecting to 9p servers Signed-off-by: Stephen J Day commit - 96ad2b22c286a0147125e76a113442a1121d7d0d commit + 6b84ea70bad42d1dc151a3547b21c8818527e78d blob - /dev/null blob + dbc20c3115cb222fa2a80d65f3472d7425979d5a (mode 644) --- /dev/null +++ cmd/9pr/main.go @@ -0,0 +1,469 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "log" + "os" + "path" + "strings" + "text/tabwriter" + "time" + + "github.com/chzyer/readline" + "github.com/docker/pinata/v1/fs/p9p/new" + "golang.org/x/net/context" +) + +func main() { + log.SetFlags(0) + + // addr := os.Args[1] + + // TODO(stevvooe): Use a dialer once we have the server session working + // and running. + + // session, err := p9pnew.Dial(ctx, addr) + // if err != nil { + // log.Fatalln(err) + // } + + commander := &fsCommander{ + ctx: context.Background(), + session: newSimpleSession(), + pwd: "/", + stdout: os.Stdout, + stderr: os.Stderr, + } + + completer := readline.NewPrefixCompleter( + readline.PcItem("ls"), + // readline.PcItem("find"), + // readline.PcItem("stat"), + readline.PcItem("cat"), + readline.PcItem("cd"), + readline.PcItem("pwd"), + ) + + rl, err := readline.NewEx(&readline.Config{ + HistoryFile: ".history", + AutoComplete: completer, + }) + if err != nil { + log.Fatalln(err) + } + commander.readline = rl + + // attach root + commander.nextfid = 1 + if _, err := commander.session.Attach(commander.ctx, commander.nextfid, p9pnew.NOFID, "anyone", "/"); err != nil { + log.Fatalln(err) + } + commander.rootfid = commander.nextfid + commander.nextfid++ + + // clone the pwd fid so we can clunk it + if _, err := commander.session.Walk(commander.ctx, commander.rootfid, commander.nextfid); err != nil { + log.Fatalln(err) + } + commander.pwdfid = commander.nextfid + commander.nextfid++ + + for { + commander.readline.SetPrompt(fmt.Sprintf("%s 🐳 > ", commander.pwd)) + + line, err := rl.Readline() + if err != nil { + log.Fatalln("error: ", err) + } + + if line == "" { + continue + } + + args := strings.Fields(line) + + name := args[0] + var cmd func(args ...string) error + + switch name { + case "ls": + cmd = commander.cmdls + case "cd": + cmd = commander.cmdcd + case "pwd": + cmd = commander.cmdpwd + case "cat": + cmd = commander.cmdcat + default: + cmd = func(args ...string) error { + return fmt.Errorf("command not implemented") + } + } + + if err := cmd(args[1:]...); err != nil { + log.Printf("👹 %s: %v", name, err) + } + } +} + +type fsCommander struct { + ctx context.Context + session p9pnew.Session + pwd string + pwdfid p9pnew.Fid + rootfid p9pnew.Fid + + nextfid p9pnew.Fid + + readline *readline.Instance + stdout io.Writer + stderr io.Writer +} + +func (c *fsCommander) cmdls(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 { + // create a header if have more than one path. + if len(ps) > 1 { + fmt.Fprintln(wr, p+":") + } + + if !path.IsAbs(p) { + p = path.Join(c.pwd, p) + } + + targetfid := c.nextfid + c.nextfid++ + components := strings.Split(strings.Trim(p, "/"), "/") + if _, err := c.session.Walk(c.ctx, c.rootfid, targetfid, components...); err != nil { + return err + } + defer c.session.Clunk(c.ctx, targetfid) + + if _, err := c.session.Open(c.ctx, targetfid, p9pnew.OREAD); err != nil { + return err + } + + p := make([]byte, 4<<20) + + n, err := c.session.Read(c.ctx, targetfid, p, 0) + if err != nil { + return err + } + + rd := bytes.NewReader(p[:n]) + + for { + var d p9pnew.Dir + if err := p9pnew.DecodeDir(rd, &d); err != nil { + if err == io.EOF { + break + } + + return err + } + + fmt.Fprintf(wr, "%v\t%v\t%v\t%s\n", os.FileMode(d.Mode), d.Length, d.ModTime, d.Name) + } + + if len(ps) > 1 { + fmt.Fprintln(wr, "") + } + } + + // all output is dumped only after success. + return wr.Flush() +} + +func (c *fsCommander) cmdcd(args ...string) error { + var p string + switch len(args) { + case 0: + p = "/" + case 1: + p = args[0] + default: + return fmt.Errorf("cd: invalid args: %v", args) + } + + if !path.IsAbs(p) { + p = path.Join(c.pwd, p) + } + + targetfid := c.nextfid + c.nextfid++ + components := strings.Split(strings.TrimSpace(strings.Trim(p, "/")), "/") + if _, err := c.session.Walk(c.ctx, c.rootfid, targetfid, components...); err != nil { + return err + } + defer c.session.Clunk(c.ctx, c.pwdfid) + + log.Println("cd", p, targetfid) + c.pwd = p + c.pwdfid = targetfid + + return nil +} + +func (c *fsCommander) cmdpwd(args ...string) error { + if len(args) != 0 { + return fmt.Errorf("pwd takes no arguments") + } + + fmt.Println(c.pwd) + return nil +} + +func (c *fsCommander) cmdcat(args ...string) error { + var p string + switch len(args) { + case 0: + p = "/" + case 1: + p = args[0] + default: + return fmt.Errorf("cd: invalid args: %v", args) + } + + if !path.IsAbs(p) { + p = path.Join(c.pwd, p) + } + + targetfid := c.nextfid + c.nextfid++ + components := strings.Split(strings.TrimSpace(strings.Trim(p, "/")), "/") + if _, err := c.session.Walk(c.ctx, c.rootfid, targetfid, components...); err != nil { + return err + } + defer c.session.Clunk(c.ctx, c.pwdfid) + + if _, err := c.session.Open(c.ctx, targetfid, p9pnew.OREAD); err != nil { + return err + } + + b := make([]byte, 4<<20) + + n, err := c.session.Read(c.ctx, targetfid, b, 0) + if err != nil { + return err + } + + if _, err := os.Stdout.Write(b[:n]); err != nil { + return err + } + + os.Stdout.Write([]byte("\n")) + + return nil +} + +type simpleSession struct { + root *fidinfo + fids map[p9pnew.Fid]*fidinfo + opened map[p9pnew.Fid]struct{} +} + +type fidinfo struct { + name string + dir p9pnew.Dir + + children map[string]*fidinfo + data []byte +} + +func (fi *fidinfo) add(child *fidinfo) { + log.Println("add", fi.name, child.name) + fi.children[child.name] = child +} + +var _ p9pnew.Session = &simpleSession{} + +var pathnum uint64 + +func mkdir(name string) *fidinfo { + log.Println("mkdir", name) + pathnum++ + return &fidinfo{ + name: name, + dir: p9pnew.Dir{ + Name: name, + Qid: p9pnew.Qid{Type: p9pnew.QTDIR, Path: pathnum, Version: 1}, + Mode: p9pnew.DMDIR | p9pnew.DMREAD | p9pnew.DMEXEC, + ModTime: time.Now().UTC(), + AccessTime: time.Now().UTC(), + }, + children: make(map[string]*fidinfo), + } +} + +func mkfile(name string, p []byte) *fidinfo { + log.Println("mkfile", name) + pathnum++ + return &fidinfo{ + name: name, + dir: p9pnew.Dir{ + Name: name, + Qid: p9pnew.Qid{Type: p9pnew.QTFILE, Path: pathnum, Version: 1}, + Mode: p9pnew.DMREAD, + ModTime: time.Now().UTC(), + AccessTime: time.Now().UTC(), + }, + data: p, + } +} + +func newSimpleSession() p9pnew.Session { + root := mkdir("/") + a := mkdir("a") + ab := mkfile("b", []byte("the b child")) + a.add(ab) + b := mkdir("b") + bc := mkfile("c", []byte("the c child")) + b.add(bc) + root.add(a) + root.add(b) + log.Println(root.children) + log.Println(a.children) + log.Println(b.children) + + return &simpleSession{ + root: root, + fids: make(map[p9pnew.Fid]*fidinfo), + opened: make(map[p9pnew.Fid]struct{}), + } +} + +func (s *simpleSession) Auth(ctx context.Context, afid p9pnew.Fid, uname, aname string) (p9pnew.Qid, error) { + panic("not implemented") +} + +func (s *simpleSession) Attach(ctx context.Context, fid, afid p9pnew.Fid, uname, aname string) (p9pnew.Qid, error) { + log.Println("attach", fid, aname) + if aname != "/" { + return p9pnew.Qid{}, p9pnew.ErrBadattach + } + + s.fids[fid] = s.root + log.Println("root", s.root, fid) + + return s.fids[fid].dir.Qid, nil +} + +func (s *simpleSession) Clunk(ctx context.Context, fid p9pnew.Fid) error { + log.Println("clunk", fid) + delete(s.fids, fid) + return nil +} + +func (s *simpleSession) Remove(ctx context.Context, fid p9pnew.Fid) error { + panic("not implemented") +} + +func (s *simpleSession) Walk(ctx context.Context, fid, newfid p9pnew.Fid, names ...string) ([]p9pnew.Qid, error) { + log.Println("walk", fid, newfid, names) + fi, ok := s.fids[fid] + if !ok { + log.Println("source fid not found") + return nil, p9pnew.ErrUnknownfid + } + + if len(names) == 0 { + // clone the fid + s.fids[newfid] = fi + return []p9pnew.Qid{fi.dir.Qid}, nil + } + + var qids []p9pnew.Qid + for i, name := range names { + var chfi *fidinfo + + if name == "" || name == "." { + chfi = fi + } else { + var ok bool + chfi, ok = fi.children[name] + if !ok { + if i == 0 { + return nil, p9pnew.ErrUnknownfid + } + + return qids, nil + } + } + + qids = append(qids, chfi.dir.Qid) + fi = chfi + } + + // assign the fid + s.fids[newfid] = fi + + return qids, nil +} + +func (s *simpleSession) Read(ctx context.Context, fid p9pnew.Fid, p []byte, offset int64) (n int, err error) { + fi, ok := s.fids[fid] + if !ok { + return 0, p9pnew.ErrUnknownfid + } + + if _, ok := s.opened[fid]; !ok { + return 0, p9pnew.ErrClosed + } + + if fi.dir.Qid.Type == p9pnew.QTDIR { + var b bytes.Buffer + for _, child := range fi.children { + if err := p9pnew.EncodeDir(&b, &child.dir); err != nil { + return 0, err + } + } + n := copy(p, b.Bytes()) // just truncate for now. + + return n, nil + } + + return copy(p, fi.data[int(offset):]), nil +} + +func (s *simpleSession) Write(ctx context.Context, fid p9pnew.Fid, p []byte, offset int64) (n int, err error) { + panic("not implemented") +} + +func (s *simpleSession) Open(ctx context.Context, fid p9pnew.Fid, mode int32) (p9pnew.Qid, error) { + fi, ok := s.fids[fid] + if !ok { + return p9pnew.Qid{}, p9pnew.ErrUnknownfid + } + + s.opened[fid] = struct{}{} + + return fi.dir.Qid, nil +} + +func (s *simpleSession) Create(ctx context.Context, parent p9pnew.Fid, name string, perm uint32, mode uint32) (p9pnew.Qid, error) { + panic("not implemented") +} + +func (s *simpleSession) Stat(context.Context, p9pnew.Fid) (p9pnew.Dir, error) { + panic("not implemented") +} + +func (s *simpleSession) WStat(context.Context, p9pnew.Fid, p9pnew.Dir) error { + panic("not implemented") +} + +// TODO(stevvooe): The version message affects a lot of protocol behavior. +// Consider hiding it behind the implementation, letting the version get +// negotiated. The API user should still be able to query it. +func (s *simpleSession) Version(ctx context.Context, msize uint32, version string) (uint32, string, error) { + return 4096, "9P2000", nil +}