Blob


1 package main
3 import (
4 "bytes"
5 "flag"
6 "fmt"
7 "io"
8 "log"
9 "net"
10 "net/http"
11 _ "net/http/pprof"
12 "os"
13 "path"
14 "strings"
15 "text/tabwriter"
16 "time"
18 "github.com/chzyer/readline"
19 "github.com/docker/pinata/v1/fs/p9p/new"
20 "golang.org/x/net/context"
21 )
23 var addr string
25 func init() {
26 flag.StringVar(&addr, "addr", ":5640", "addr of 9p service")
27 }
29 func main() {
30 go func() {
31 log.Println(http.ListenAndServe("localhost:6060", nil))
32 }()
34 ctx := context.Background()
35 log.SetFlags(0)
36 flag.Parse()
38 proto := "tcp"
39 if strings.HasPrefix(addr, "unix:") {
40 proto = "unix"
41 addr = addr[5:]
42 }
44 log.Println("dialing", addr)
45 conn, err := net.Dial(proto, addr)
46 if err != nil {
47 log.Fatal(err)
48 }
50 csession, err := p9pnew.NewSession(ctx, conn)
51 if err != nil {
52 log.Fatalln(err)
53 }
55 commander := &fsCommander{
56 ctx: context.Background(),
57 session: csession,
58 pwd: "/",
59 stdout: os.Stdout,
60 stderr: os.Stderr,
61 }
63 completer := readline.NewPrefixCompleter(
64 readline.PcItem("ls"),
65 // readline.PcItem("find"),
66 readline.PcItem("stat"),
67 readline.PcItem("cat"),
68 readline.PcItem("cd"),
69 readline.PcItem("pwd"),
70 )
72 rl, err := readline.NewEx(&readline.Config{
73 HistoryFile: ".history",
74 AutoComplete: completer,
75 })
76 if err != nil {
77 log.Fatalln(err)
78 }
79 commander.readline = rl
81 msize, version := commander.session.Version()
82 if err != nil {
83 log.Fatalln(err)
84 }
85 log.Println("9p version", version, msize)
87 // attach root
88 commander.nextfid = 1
89 if _, err := commander.session.Attach(commander.ctx, commander.nextfid, p9pnew.NOFID, "anyone", "/"); err != nil {
90 log.Fatalln(err)
91 }
92 commander.rootfid = commander.nextfid
93 commander.nextfid++
95 // clone the pwd fid so we can clunk it
96 if _, err := commander.session.Walk(commander.ctx, commander.rootfid, commander.nextfid); err != nil {
97 log.Fatalln(err)
98 }
99 commander.pwdfid = commander.nextfid
100 commander.nextfid++
102 for {
103 commander.readline.SetPrompt(fmt.Sprintf("%s 🐳 > ", commander.pwd))
105 line, err := rl.Readline()
106 if err != nil {
107 log.Fatalln("error: ", err)
110 if line == "" {
111 continue
114 args := strings.Fields(line)
116 name := args[0]
117 var cmd func(ctx context.Context, args ...string) error
119 switch name {
120 case "ls":
121 cmd = commander.cmdls
122 case "cd":
123 cmd = commander.cmdcd
124 case "pwd":
125 cmd = commander.cmdpwd
126 case "cat":
127 cmd = commander.cmdcat
128 case "stat":
129 cmd = commander.cmdstat
130 default:
131 cmd = func(ctx context.Context, args ...string) error {
132 return fmt.Errorf("command not implemented")
136 ctx, _ = context.WithTimeout(commander.ctx, 5*time.Second)
137 if err := cmd(ctx, args[1:]...); err != nil {
138 log.Printf("👹 %s: %v", name, err)
143 type fsCommander struct {
144 ctx context.Context
145 session p9pnew.Session
146 pwd string
147 pwdfid p9pnew.Fid
148 rootfid p9pnew.Fid
150 nextfid p9pnew.Fid
152 readline *readline.Instance
153 stdout io.Writer
154 stderr io.Writer
157 func (c *fsCommander) cmdls(ctx context.Context, args ...string) error {
158 ps := []string{c.pwd}
159 if len(args) > 0 {
160 ps = args
163 wr := tabwriter.NewWriter(c.stdout, 0, 8, 8, ' ', 0)
165 for _, p := range ps {
166 // create a header if have more than one path.
167 if len(ps) > 1 {
168 fmt.Fprintln(wr, p+":")
171 if !path.IsAbs(p) {
172 p = path.Join(c.pwd, p)
175 targetfid := c.nextfid
176 c.nextfid++
177 components := strings.Split(strings.Trim(p, "/"), "/")
178 if _, err := c.session.Walk(ctx, c.rootfid, targetfid, components...); err != nil {
179 return err
181 defer c.session.Clunk(ctx, targetfid)
183 _, iounit, err := c.session.Open(ctx, targetfid, p9pnew.OREAD)
184 if err != nil {
185 return err
188 if iounit < 1 {
189 msize, _ := c.session.Version()
190 iounit = uint32(msize - 24) // size of message max minus fcall io header (Rread)
193 p := make([]byte, iounit)
195 n, err := c.session.Read(ctx, targetfid, p, 0)
196 if err != nil {
197 return err
200 rd := bytes.NewReader(p[:n])
201 codec := p9pnew.NewCodec() // TODO(stevvooe): Need way to resolve codec based on session.
202 for {
203 var d p9pnew.Dir
204 if err := p9pnew.DecodeDir(codec, rd, &d); err != nil {
205 if err == io.EOF {
206 break
209 return err
212 fmt.Fprintf(wr, "%v\t%v\t%v\t%s\n", os.FileMode(d.Mode), d.Length, d.ModTime, d.Name)
215 if len(ps) > 1 {
216 fmt.Fprintln(wr, "")
220 // all output is dumped only after success.
221 return wr.Flush()
224 func (c *fsCommander) cmdcd(ctx context.Context, args ...string) error {
225 var p string
226 switch len(args) {
227 case 0:
228 p = "/"
229 case 1:
230 p = args[0]
231 default:
232 return fmt.Errorf("cd: invalid args: %v", args)
235 if !path.IsAbs(p) {
236 p = path.Join(c.pwd, p)
239 targetfid := c.nextfid
240 c.nextfid++
241 components := strings.Split(strings.TrimSpace(strings.Trim(p, "/")), "/")
242 if _, err := c.session.Walk(c.ctx, c.rootfid, targetfid, components...); err != nil {
243 return err
245 defer c.session.Clunk(c.ctx, c.pwdfid)
247 log.Println("cd", p, targetfid)
248 c.pwd = p
249 c.pwdfid = targetfid
251 return nil
254 func (c *fsCommander) cmdstat(ctx context.Context, args ...string) error {
255 ps := []string{c.pwd}
256 if len(args) > 0 {
257 ps = args
260 wr := tabwriter.NewWriter(c.stdout, 0, 8, 8, ' ', 0)
262 for _, p := range ps {
263 targetfid := c.nextfid
264 c.nextfid++
265 components := strings.Split(strings.Trim(p, "/"), "/")
266 if _, err := c.session.Walk(ctx, c.rootfid, targetfid, components...); err != nil {
267 return err
269 defer c.session.Clunk(ctx, targetfid)
271 d, err := c.session.Stat(ctx, targetfid)
272 if err != nil {
273 return err
276 fmt.Fprintf(wr, "%v\t%v\t%v\t%s\n", os.FileMode(d.Mode), d.Length, d.ModTime, d.Name)
279 return wr.Flush()
282 func (c *fsCommander) cmdpwd(ctx context.Context, args ...string) error {
283 if len(args) != 0 {
284 return fmt.Errorf("pwd takes no arguments")
287 fmt.Println(c.pwd)
288 return nil
291 func (c *fsCommander) cmdcat(ctx context.Context, args ...string) error {
292 var p string
293 switch len(args) {
294 case 0:
295 p = "/"
296 case 1:
297 p = args[0]
298 default:
299 return fmt.Errorf("cd: invalid args: %v", args)
302 if !path.IsAbs(p) {
303 p = path.Join(c.pwd, p)
306 targetfid := c.nextfid
307 c.nextfid++
308 components := strings.Split(strings.TrimSpace(strings.Trim(p, "/")), "/")
309 if _, err := c.session.Walk(ctx, c.rootfid, targetfid, components...); err != nil {
310 return err
312 defer c.session.Clunk(ctx, c.pwdfid)
314 _, iounit, err := c.session.Open(ctx, targetfid, p9pnew.OREAD)
315 if err != nil {
316 return err
319 if iounit < 1 {
320 msize, _ := c.session.Version()
321 iounit = uint32(msize - 24) // size of message max minus fcall io header (Rread)
324 b := make([]byte, iounit)
326 n, err := c.session.Read(ctx, targetfid, b, 0)
327 if err != nil {
328 return err
331 if _, err := os.Stdout.Write(b[:n]); err != nil {
332 return err
335 os.Stdout.Write([]byte("\n"))
337 return nil