Blame


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