├── .gitignore ├── LICENSE ├── README.md ├── clnt_clnt.go ├── clnt_close.go ├── clnt_mount.go ├── clnt_open.go ├── clnt_pool.go ├── clnt_read.go ├── clnt_remove.go ├── clnt_stat.go ├── clnt_stats_http.go ├── clnt_tag.go ├── clnt_walk.go ├── clnt_write.go ├── debug.go ├── fmt.go ├── go9p_test.go ├── log.go ├── osusers.go ├── p9.go ├── packr.go ├── packt.go ├── srv_conn.go ├── srv_fcall.go ├── srv_file.go ├── srv_pipe.go ├── srv_respond.go ├── srv_srv.go ├── srv_stats_http.go ├── stats.go ├── ufs.go ├── ufs └── ufs.go └── unpack.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.orig 2 | *.rej 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 The Go9p Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * The names of Go9p's contributors may not be used to endorse 14 | or promote products derived from this software without specific prior 15 | written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is go9p done in a way that I can understand. 2 | 3 | To install: 4 | export GOPATH=~rminnich/go 5 | go get -a github.com/rminnich/go9p 6 | go get -a github.com/rminnich/go9p/ufs 7 | go install -a github.com/rminnich/go9p/ufs 8 | 9 | ~/go/bin/ufs 10 | 11 | 12 | -------------------------------------------------------------------------------- /clnt_clnt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // The clnt package go9provides definitions and functions used to implement 6 | // a 9P2000 file client. 7 | package go9p 8 | 9 | import ( 10 | "fmt" 11 | "log" 12 | "net" 13 | "sync" 14 | "sync/atomic" 15 | ) 16 | 17 | // The Clnt type represents a 9P2000 client. The client is connected to 18 | // a 9P2000 file server and its methods can be used to access and manipulate 19 | // the files exported by the server. 20 | type Clnt struct { 21 | sync.Mutex 22 | Debuglevel int // =0 don't print anything, >0 print Fcalls, >1 print raw packets 23 | Msize uint32 // Maximum size of the 9P messages 24 | Dotu bool // If true, 9P2000.u protocol is spoken 25 | Root *Fid // Fid that points to the rood directory 26 | Id string // Used when printing debug messages 27 | Log *Logger 28 | 29 | conn net.Conn 30 | tagpool *pool 31 | fidpool *pool 32 | reqout chan *Req 33 | done chan bool 34 | reqfirst *Req 35 | reqlast *Req 36 | err error 37 | 38 | reqchan chan *Req 39 | tchan chan *Fcall 40 | 41 | next, prev *Clnt 42 | } 43 | 44 | // A Fid type represents a file on the server. Fids are used for the 45 | // low level methods that correspond directly to the 9P2000 message requests 46 | type Fid struct { 47 | sync.Mutex 48 | Clnt *Clnt // Client the fid belongs to 49 | Iounit uint32 50 | Qid // The Qid description for the file 51 | Mode uint8 // Open mode (one of O* values) (if file is open) 52 | Fid uint32 // Fid number 53 | User // The user the fid belongs to 54 | walked bool // true if the fid points to a walked file on the server 55 | } 56 | 57 | // The file is similar to the Fid, but is used in the high-level client 58 | // interface. We expose the Fid so that client code can use Remove 59 | // on a fid, the same way a kernel can. 60 | type File struct { 61 | Fid *Fid 62 | offset uint64 63 | } 64 | 65 | type Req struct { 66 | sync.Mutex 67 | Clnt *Clnt 68 | Tc *Fcall 69 | Rc *Fcall 70 | Err error 71 | Done chan *Req 72 | tag uint16 73 | prev, next *Req 74 | fid *Fid 75 | } 76 | 77 | type ClntList struct { 78 | sync.Mutex 79 | clntList, clntLast *Clnt 80 | } 81 | 82 | var clnts *ClntList 83 | var DefaultDebuglevel int 84 | var DefaultLogger *Logger 85 | 86 | func (clnt *Clnt) Rpcnb(r *Req) error { 87 | var tag uint16 88 | 89 | if r.Tc.Type == Tversion { 90 | tag = NOTAG 91 | } else { 92 | tag = r.tag 93 | } 94 | 95 | SetTag(r.Tc, tag) 96 | clnt.Lock() 97 | if clnt.err != nil { 98 | clnt.Unlock() 99 | return clnt.err 100 | } 101 | 102 | if clnt.reqlast != nil { 103 | clnt.reqlast.next = r 104 | } else { 105 | clnt.reqfirst = r 106 | } 107 | 108 | r.prev = clnt.reqlast 109 | clnt.reqlast = r 110 | clnt.Unlock() 111 | 112 | clnt.reqout <- r 113 | return nil 114 | } 115 | 116 | func (clnt *Clnt) Rpc(tc *Fcall) (rc *Fcall, err error) { 117 | r := clnt.ReqAlloc() 118 | r.Tc = tc 119 | r.Done = make(chan *Req) 120 | err = clnt.Rpcnb(r) 121 | if err != nil { 122 | return 123 | } 124 | 125 | <-r.Done 126 | rc = r.Rc 127 | err = r.Err 128 | clnt.ReqFree(r) 129 | return 130 | } 131 | 132 | func (clnt *Clnt) recv() { 133 | var err error 134 | var buf []byte 135 | 136 | err = nil 137 | pos := 0 138 | for { 139 | // Connect can change the client Msize. 140 | clntmsize := int(atomic.LoadUint32(&clnt.Msize)) 141 | if len(buf) < clntmsize { 142 | b := make([]byte, clntmsize*8) 143 | copy(b, buf[0:pos]) 144 | buf = b 145 | b = nil 146 | } 147 | 148 | n, oerr := clnt.conn.Read(buf[pos:]) 149 | if oerr != nil || n == 0 { 150 | err = &Error{oerr.Error(), EIO} 151 | clnt.Lock() 152 | clnt.err = err 153 | clnt.Unlock() 154 | goto closed 155 | } 156 | 157 | pos += n 158 | for pos > 4 { 159 | sz, _ := Gint32(buf) 160 | if pos < int(sz) { 161 | if len(buf) < int(sz) { 162 | b := make([]byte, atomic.LoadUint32(&clnt.Msize)*8) 163 | copy(b, buf[0:pos]) 164 | buf = b 165 | b = nil 166 | } 167 | 168 | break 169 | } 170 | 171 | fc, err, fcsize := Unpack(buf, clnt.Dotu) 172 | clnt.Lock() 173 | if err != nil { 174 | clnt.err = err 175 | clnt.conn.Close() 176 | clnt.Unlock() 177 | goto closed 178 | } 179 | 180 | if clnt.Debuglevel > 0 { 181 | clnt.logFcall(fc) 182 | if clnt.Debuglevel&DbgPrintPackets != 0 { 183 | log.Println("}-}", clnt.Id, fmt.Sprint(fc.Pkt)) 184 | } 185 | 186 | if clnt.Debuglevel&DbgPrintFcalls != 0 { 187 | log.Println("}}}", clnt.Id, fc.String()) 188 | } 189 | } 190 | 191 | var r *Req = nil 192 | for r = clnt.reqfirst; r != nil; r = r.next { 193 | if r.Tc.Tag == fc.Tag { 194 | break 195 | } 196 | } 197 | 198 | if r == nil { 199 | clnt.err = &Error{"unexpected response", EINVAL} 200 | clnt.conn.Close() 201 | clnt.Unlock() 202 | goto closed 203 | } 204 | 205 | r.Rc = fc 206 | if r.prev != nil { 207 | r.prev.next = r.next 208 | } else { 209 | clnt.reqfirst = r.next 210 | } 211 | 212 | if r.next != nil { 213 | r.next.prev = r.prev 214 | } else { 215 | clnt.reqlast = r.prev 216 | } 217 | clnt.Unlock() 218 | 219 | if r.Tc.Type != r.Rc.Type-1 { 220 | if r.Rc.Type != Rerror { 221 | r.Err = &Error{"invalid response", EINVAL} 222 | log.Println(fmt.Sprintf("TTT %v", r.Tc)) 223 | log.Println(fmt.Sprintf("RRR %v", r.Rc)) 224 | } else { 225 | if r.Err == nil { 226 | r.Err = &Error{r.Rc.Error, r.Rc.Errornum} 227 | } 228 | } 229 | } 230 | 231 | if r.Done != nil { 232 | r.Done <- r 233 | } 234 | 235 | pos -= fcsize 236 | buf = buf[fcsize:] 237 | } 238 | } 239 | 240 | closed: 241 | clnt.done <- true 242 | 243 | /* send error to all pending requests */ 244 | clnt.Lock() 245 | r := clnt.reqfirst 246 | clnt.reqfirst = nil 247 | clnt.reqlast = nil 248 | if err == nil { 249 | err = clnt.err 250 | } 251 | clnt.Unlock() 252 | for ; r != nil; r = r.next { 253 | r.Err = err 254 | if r.Done != nil { 255 | r.Done <- r 256 | } 257 | } 258 | 259 | clnts.Lock() 260 | if clnt.prev != nil { 261 | clnt.prev.next = clnt.next 262 | } else { 263 | clnts.clntList = clnt.next 264 | } 265 | 266 | if clnt.next != nil { 267 | clnt.next.prev = clnt.prev 268 | } else { 269 | clnts.clntLast = clnt.prev 270 | } 271 | clnts.Unlock() 272 | 273 | if sop, ok := (interface{}(clnt)).(StatsOps); ok { 274 | sop.statsUnregister() 275 | } 276 | } 277 | 278 | func (clnt *Clnt) send() { 279 | for { 280 | select { 281 | case <-clnt.done: 282 | return 283 | 284 | case req := <-clnt.reqout: 285 | if clnt.Debuglevel > 0 { 286 | clnt.logFcall(req.Tc) 287 | if clnt.Debuglevel&DbgPrintPackets != 0 { 288 | log.Println("{-{", clnt.Id, fmt.Sprint(req.Tc.Pkt)) 289 | } 290 | 291 | if clnt.Debuglevel&DbgPrintFcalls != 0 { 292 | log.Println("{{{", clnt.Id, req.Tc.String()) 293 | } 294 | } 295 | 296 | for buf := req.Tc.Pkt; len(buf) > 0; { 297 | n, err := clnt.conn.Write(buf) 298 | if err != nil { 299 | /* just close the socket, will get signal on clnt.done */ 300 | clnt.conn.Close() 301 | break 302 | } 303 | 304 | buf = buf[n:] 305 | } 306 | } 307 | } 308 | } 309 | 310 | // Creates and initializes a new Clnt object. Doesn't send any data 311 | // on the wire. 312 | func NewClnt(c net.Conn, msize uint32, dotu bool) *Clnt { 313 | clnt := new(Clnt) 314 | clnt.conn = c 315 | clnt.Msize = msize 316 | clnt.Dotu = dotu 317 | clnt.Debuglevel = DefaultDebuglevel 318 | clnt.Log = DefaultLogger 319 | clnt.Id = c.RemoteAddr().String() + ":" 320 | clnt.tagpool = newPool(uint32(NOTAG)) 321 | clnt.fidpool = newPool(NOFID) 322 | clnt.reqout = make(chan *Req) 323 | clnt.done = make(chan bool) 324 | clnt.reqchan = make(chan *Req, 16) 325 | clnt.tchan = make(chan *Fcall, 16) 326 | 327 | go clnt.recv() 328 | go clnt.send() 329 | 330 | clnts.Lock() 331 | if clnts.clntLast != nil { 332 | clnts.clntLast.next = clnt 333 | } else { 334 | clnts.clntList = clnt 335 | } 336 | 337 | clnt.prev = clnts.clntLast 338 | clnts.clntLast = clnt 339 | clnts.Unlock() 340 | 341 | if sop, ok := (interface{}(clnt)).(StatsOps); ok { 342 | sop.statsRegister() 343 | } 344 | 345 | return clnt 346 | } 347 | 348 | // Establishes a new socket connection to the 9P server and creates 349 | // a client object for it. Negotiates the dialect and msize for the 350 | // connection. Returns a Clnt object, or Error. 351 | func Connect(c net.Conn, msize uint32, dotu bool) (*Clnt, error) { 352 | clnt := NewClnt(c, msize, dotu) 353 | ver := "9P2000" 354 | if clnt.Dotu { 355 | ver = "9P2000.u" 356 | } 357 | 358 | clntmsize := atomic.LoadUint32(&clnt.Msize) 359 | tc := NewFcall(clntmsize) 360 | err := PackTversion(tc, clntmsize, ver) 361 | if err != nil { 362 | return nil, err 363 | } 364 | 365 | rc, err := clnt.Rpc(tc) 366 | if err != nil { 367 | return nil, err 368 | } 369 | 370 | if rc.Msize < atomic.LoadUint32(&clnt.Msize) { 371 | atomic.StoreUint32(&clnt.Msize, rc.Msize) 372 | } 373 | 374 | clnt.Dotu = rc.Version == "9P2000.u" && clnt.Dotu 375 | return clnt, nil 376 | } 377 | 378 | // Creates a new Fid object for the client 379 | func (clnt *Clnt) FidAlloc() *Fid { 380 | fid := new(Fid) 381 | fid.Fid = clnt.fidpool.getId() 382 | fid.Clnt = clnt 383 | 384 | return fid 385 | } 386 | 387 | func (clnt *Clnt) NewFcall() *Fcall { 388 | select { 389 | case tc := <-clnt.tchan: 390 | return tc 391 | default: 392 | } 393 | return NewFcall(atomic.LoadUint32(&clnt.Msize)) 394 | } 395 | 396 | func (clnt *Clnt) FreeFcall(fc *Fcall) { 397 | if fc != nil && len(fc.Buf) >= int(atomic.LoadUint32(&clnt.Msize)) { 398 | select { 399 | case clnt.tchan <- fc: 400 | break 401 | default: 402 | } 403 | } 404 | } 405 | 406 | func (clnt *Clnt) ReqAlloc() *Req { 407 | var req *Req 408 | select { 409 | case req = <-clnt.reqchan: 410 | break 411 | default: 412 | req = new(Req) 413 | req.Clnt = clnt 414 | req.tag = uint16(clnt.tagpool.getId()) 415 | } 416 | return req 417 | } 418 | 419 | func (clnt *Clnt) ReqFree(req *Req) { 420 | clnt.FreeFcall(req.Tc) 421 | req.Tc = nil 422 | req.Rc = nil 423 | req.Err = nil 424 | req.Done = nil 425 | req.next = nil 426 | req.prev = nil 427 | 428 | select { 429 | case clnt.reqchan <- req: 430 | break 431 | default: 432 | clnt.tagpool.putId(uint32(req.tag)) 433 | } 434 | } 435 | 436 | func (clnt *Clnt) logFcall(fc *Fcall) { 437 | if clnt.Debuglevel&DbgLogPackets != 0 { 438 | pkt := make([]byte, len(fc.Pkt)) 439 | copy(pkt, fc.Pkt) 440 | clnt.Log.Log(pkt, clnt, DbgLogPackets) 441 | } 442 | 443 | if clnt.Debuglevel&DbgLogFcalls != 0 { 444 | f := new(Fcall) 445 | *f = *fc 446 | f.Pkt = nil 447 | clnt.Log.Log(f, clnt, DbgLogFcalls) 448 | } 449 | } 450 | 451 | // FidFile returns a File that represents the given Fid, initially at the given 452 | // offset. 453 | func FidFile(fid *Fid, offset uint64) *File { 454 | return &File{fid, offset} 455 | } 456 | 457 | func init() { 458 | clnts = new(ClntList) 459 | if sop, ok := (interface{}(clnts)).(StatsOps); ok { 460 | sop.statsRegister() 461 | } 462 | } 463 | -------------------------------------------------------------------------------- /clnt_close.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | // Clunks a fid. Returns nil if successful. 8 | func (clnt *Clnt) Clunk(fid *Fid) (err error) { 9 | err = nil 10 | if fid.walked { 11 | tc := clnt.NewFcall() 12 | err := PackTclunk(tc, fid.Fid) 13 | if err != nil { 14 | return err 15 | } 16 | 17 | _, err = clnt.Rpc(tc) 18 | } 19 | 20 | clnt.fidpool.putId(fid.Fid) 21 | fid.walked = false 22 | fid.Fid = NOFID 23 | return 24 | } 25 | 26 | // Closes a file. Returns nil if successful. 27 | func (file *File) Close() error { 28 | // Should we cancel all pending requests for the File 29 | return file.Fid.Clnt.Clunk(file.Fid) 30 | } 31 | -------------------------------------------------------------------------------- /clnt_mount.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | import ( 8 | "net" 9 | ) 10 | 11 | // Creates an authentication fid for the specified user. Returns the fid, if 12 | // successful, or an Error. 13 | func (clnt *Clnt) Auth(user User, aname string) (*Fid, error) { 14 | fid := clnt.FidAlloc() 15 | tc := clnt.NewFcall() 16 | err := PackTauth(tc, fid.Fid, user.Name(), aname, uint32(user.Id()), clnt.Dotu) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | _, err = clnt.Rpc(tc) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | fid.User = user 27 | fid.walked = true 28 | return fid, nil 29 | } 30 | 31 | // Creates a fid for the specified user that points to the root 32 | // of the file server's file tree. Returns a Fid pointing to the root, 33 | // if successful, or an Error. 34 | func (clnt *Clnt) Attach(afid *Fid, user User, aname string) (*Fid, error) { 35 | var afno uint32 36 | 37 | if afid != nil { 38 | afno = afid.Fid 39 | } else { 40 | afno = NOFID 41 | } 42 | 43 | fid := clnt.FidAlloc() 44 | tc := clnt.NewFcall() 45 | err := PackTattach(tc, fid.Fid, afno, user.Name(), aname, uint32(user.Id()), clnt.Dotu) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | rc, err := clnt.Rpc(tc) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | fid.Qid = rc.Qid 56 | fid.User = user 57 | fid.walked = true 58 | return fid, nil 59 | } 60 | 61 | // Connects to a file server and attaches to it as the specified user. 62 | func Mount(ntype, addr, aname string, msize uint32, user User) (*Clnt, error) { 63 | c, e := net.Dial(ntype, addr) 64 | if e != nil { 65 | return nil, &Error{e.Error(), EIO} 66 | } 67 | 68 | return MountConn(c, aname, msize, user) 69 | } 70 | 71 | func MountConn(c net.Conn, aname string, msize uint32, user User) (*Clnt, error) { 72 | clnt, err := Connect(c, msize+IOHDRSZ, true) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | fid, err := clnt.Attach(nil, user, aname) 78 | if err != nil { 79 | clnt.Unmount() 80 | return nil, err 81 | } 82 | 83 | clnt.Root = fid 84 | return clnt, nil 85 | } 86 | 87 | // Closes the connection to the file sever. 88 | func (clnt *Clnt) Unmount() { 89 | clnt.Lock() 90 | clnt.err = &Error{"connection closed", EIO} 91 | clnt.conn.Close() 92 | clnt.Unlock() 93 | } 94 | -------------------------------------------------------------------------------- /clnt_open.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | import ( 8 | "strings" 9 | ) 10 | 11 | // Opens the file associated with the fid. Returns nil if 12 | // the operation is successful. 13 | func (clnt *Clnt) Open(fid *Fid, mode uint8) error { 14 | tc := clnt.NewFcall() 15 | err := PackTopen(tc, fid.Fid, mode) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | rc, err := clnt.Rpc(tc) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | fid.Qid = rc.Qid 26 | fid.Iounit = rc.Iounit 27 | if fid.Iounit == 0 || fid.Iounit > clnt.Msize-IOHDRSZ { 28 | fid.Iounit = clnt.Msize - IOHDRSZ 29 | } 30 | fid.Mode = mode 31 | return nil 32 | } 33 | 34 | // Creates a file in the directory associated with the fid. Returns nil 35 | // if the operation is successful. 36 | func (clnt *Clnt) Create(fid *Fid, name string, perm uint32, mode uint8, ext string) error { 37 | tc := clnt.NewFcall() 38 | err := PackTcreate(tc, fid.Fid, name, perm, mode, ext, clnt.Dotu) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | rc, err := clnt.Rpc(tc) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | fid.Qid = rc.Qid 49 | fid.Iounit = rc.Iounit 50 | if fid.Iounit == 0 || fid.Iounit > clnt.Msize-IOHDRSZ { 51 | fid.Iounit = clnt.Msize - IOHDRSZ 52 | } 53 | fid.Mode = mode 54 | return nil 55 | } 56 | 57 | // Creates and opens a named file. 58 | // Returns the file if the operation is successful, or an Error. 59 | func (clnt *Clnt) FCreate(path string, perm uint32, mode uint8) (*File, error) { 60 | n := strings.LastIndex(path, "/") 61 | if n < 0 { 62 | n = 0 63 | } 64 | 65 | fid, err := clnt.FWalk(path[0:n]) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | if path[n] == '/' { 71 | n++ 72 | } 73 | 74 | err = clnt.Create(fid, path[n:], perm, mode, "") 75 | if err != nil { 76 | clnt.Clunk(fid) 77 | return nil, err 78 | } 79 | 80 | return &File{fid, 0}, nil 81 | } 82 | 83 | // Opens a named file. Returns the opened file, or an Error. 84 | func (clnt *Clnt) FOpen(path string, mode uint8) (*File, error) { 85 | fid, err := clnt.FWalk(path) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | err = clnt.Open(fid, mode) 91 | if err != nil { 92 | clnt.Clunk(fid) 93 | return nil, err 94 | } 95 | 96 | return &File{fid, 0}, nil 97 | } 98 | -------------------------------------------------------------------------------- /clnt_pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | import "sync" 8 | 9 | var m2id = [...]uint8{ 10 | 0, 1, 0, 2, 0, 1, 0, 3, 11 | 0, 1, 0, 2, 0, 1, 0, 4, 12 | 0, 1, 0, 2, 0, 1, 0, 3, 13 | 0, 1, 0, 2, 0, 1, 0, 5, 14 | 0, 1, 0, 2, 0, 1, 0, 3, 15 | 0, 1, 0, 2, 0, 1, 0, 4, 16 | 0, 1, 0, 2, 0, 1, 0, 3, 17 | 0, 1, 0, 2, 0, 1, 0, 6, 18 | 0, 1, 0, 2, 0, 1, 0, 3, 19 | 0, 1, 0, 2, 0, 1, 0, 4, 20 | 0, 1, 0, 2, 0, 1, 0, 3, 21 | 0, 1, 0, 2, 0, 1, 0, 5, 22 | 0, 1, 0, 2, 0, 1, 0, 3, 23 | 0, 1, 0, 2, 0, 1, 0, 4, 24 | 0, 1, 0, 2, 0, 1, 0, 3, 25 | 0, 1, 0, 2, 0, 1, 0, 7, 26 | 0, 1, 0, 2, 0, 1, 0, 3, 27 | 0, 1, 0, 2, 0, 1, 0, 4, 28 | 0, 1, 0, 2, 0, 1, 0, 3, 29 | 0, 1, 0, 2, 0, 1, 0, 5, 30 | 0, 1, 0, 2, 0, 1, 0, 3, 31 | 0, 1, 0, 2, 0, 1, 0, 4, 32 | 0, 1, 0, 2, 0, 1, 0, 3, 33 | 0, 1, 0, 2, 0, 1, 0, 6, 34 | 0, 1, 0, 2, 0, 1, 0, 3, 35 | 0, 1, 0, 2, 0, 1, 0, 4, 36 | 0, 1, 0, 2, 0, 1, 0, 3, 37 | 0, 1, 0, 2, 0, 1, 0, 5, 38 | 0, 1, 0, 2, 0, 1, 0, 3, 39 | 0, 1, 0, 2, 0, 1, 0, 4, 40 | 0, 1, 0, 2, 0, 1, 0, 3, 41 | 0, 1, 0, 2, 0, 1, 0, 0, 42 | } 43 | 44 | type pool struct { 45 | sync.Mutex 46 | need int 47 | nchan chan uint32 48 | maxid uint32 49 | imap []byte 50 | } 51 | 52 | func newPool(maxid uint32) *pool { 53 | p := new(pool) 54 | p.maxid = maxid 55 | p.nchan = make(chan uint32) 56 | 57 | return p 58 | } 59 | 60 | func (p *pool) getId() uint32 { 61 | var n uint32 = 0 62 | var ret uint32 63 | 64 | p.Lock() 65 | for n = 0; n < uint32(len(p.imap)); n++ { 66 | if p.imap[n] != 0xFF { 67 | break 68 | } 69 | } 70 | 71 | if int(n) >= len(p.imap) { 72 | m := uint32(len(p.imap) + 32) 73 | if uint32(m*8) > p.maxid { 74 | m = p.maxid/8 + 1 75 | } 76 | 77 | b := make([]byte, m) 78 | copy(b, p.imap) 79 | p.imap = b 80 | } 81 | 82 | if n >= uint32(len(p.imap)) { 83 | p.need++ 84 | p.Unlock() 85 | ret = <-p.nchan 86 | } else { 87 | ret = uint32(m2id[p.imap[n]]) 88 | p.imap[n] |= 1 << ret 89 | ret += n * 8 90 | p.Unlock() 91 | } 92 | 93 | return ret 94 | } 95 | 96 | func (p *pool) putId(id uint32) { 97 | p.Lock() 98 | if p.need > 0 { 99 | p.nchan <- id 100 | p.need-- 101 | p.Unlock() 102 | return 103 | } 104 | 105 | p.imap[id/8] &= ^(1 << (id % 8)) 106 | p.Unlock() 107 | } 108 | -------------------------------------------------------------------------------- /clnt_read.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | import ( 8 | "io" 9 | ) 10 | 11 | // Reads count bytes starting from offset from the file associated with the fid. 12 | // Returns a slice with the data read, if the operation was successful, or an 13 | // Error. 14 | func (clnt *Clnt) Read(fid *Fid, offset uint64, count uint32) ([]byte, error) { 15 | if count > fid.Iounit { 16 | count = fid.Iounit 17 | } 18 | 19 | tc := clnt.NewFcall() 20 | err := PackTread(tc, fid.Fid, offset, count) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | rc, err := clnt.Rpc(tc) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | return rc.Data, nil 31 | } 32 | 33 | // Reads up to len(buf) bytes from the File. Returns the number 34 | // of bytes read, or an Error. 35 | func (file *File) Read(buf []byte) (int, error) { 36 | n, err := file.ReadAt(buf, int64(file.offset)) 37 | if err == nil { 38 | file.offset += uint64(n) 39 | } 40 | 41 | return n, err 42 | } 43 | 44 | // Reads up to len(buf) bytes from the file starting from offset. 45 | // Returns the number of bytes read, or an Error. 46 | func (file *File) ReadAt(buf []byte, offset int64) (int, error) { 47 | b, err := file.Fid.Clnt.Read(file.Fid, uint64(offset), uint32(len(buf))) 48 | if err != nil { 49 | return 0, err 50 | } 51 | 52 | if len(b) == 0 { 53 | return 0, io.EOF 54 | } 55 | 56 | copy(buf, b) 57 | return len(b), nil 58 | } 59 | 60 | // Reads exactly len(buf) bytes from the File starting from offset. 61 | // Returns the number of bytes read (could be less than len(buf) if 62 | // end-of-file is reached), or an Error. 63 | func (file *File) Readn(buf []byte, offset uint64) (int, error) { 64 | ret := 0 65 | for len(buf) > 0 { 66 | n, err := file.ReadAt(buf, int64(offset)) 67 | if err != nil { 68 | return 0, err 69 | } 70 | 71 | if n == 0 { 72 | break 73 | } 74 | 75 | buf = buf[n:] 76 | offset += uint64(n) 77 | ret += n 78 | } 79 | 80 | return ret, nil 81 | } 82 | 83 | // Reads the content of the directory associated with the File. 84 | // Returns an array of maximum num entries (if num is 0, returns 85 | // all entries from the directory). If the operation fails, returns 86 | // an Error. 87 | func (file *File) Readdir(num int) ([]*Dir, error) { 88 | buf := make([]byte, file.Fid.Clnt.Msize-IOHDRSZ) 89 | dirs := make([]*Dir, 32) 90 | pos := 0 91 | offset := file.offset 92 | defer func() { 93 | file.offset = offset 94 | }() 95 | for { 96 | n, err := file.Read(buf) 97 | if err != nil && err != io.EOF { 98 | return nil, err 99 | } 100 | 101 | if n == 0 { 102 | break 103 | } 104 | 105 | for b := buf[0:n]; len(b) > 0; { 106 | d, _, _, perr := UnpackDir(b, file.Fid.Clnt.Dotu) 107 | if perr != nil { 108 | // If we have unpacked anything, it is almost certainly 109 | // a too-short buffer. So return what we got. 110 | if pos > 0 { 111 | return dirs[0:pos], nil 112 | } 113 | return nil, perr 114 | } 115 | 116 | b = b[d.Size+2:] 117 | offset += uint64(d.Size + 2) 118 | if pos >= len(dirs) { 119 | s := make([]*Dir, len(dirs)+32) 120 | copy(s, dirs) 121 | dirs = s 122 | } 123 | 124 | dirs[pos] = d 125 | pos++ 126 | if num != 0 && pos >= num { 127 | break 128 | } 129 | } 130 | } 131 | 132 | return dirs[0:pos], nil 133 | } 134 | -------------------------------------------------------------------------------- /clnt_remove.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | // Removes the file associated with the Fid. Returns nil if the 8 | // operation is successful. 9 | func (clnt *Clnt) Remove(fid *Fid) error { 10 | tc := clnt.NewFcall() 11 | err := PackTremove(tc, fid.Fid) 12 | if err != nil { 13 | return err 14 | } 15 | 16 | _, err = clnt.Rpc(tc) 17 | clnt.fidpool.putId(fid.Fid) 18 | fid.Fid = NOFID 19 | 20 | return err 21 | } 22 | 23 | // Removes the named file. Returns nil if the operation is successful. 24 | func (clnt *Clnt) FRemove(path string) error { 25 | var err error 26 | fid, err := clnt.FWalk(path) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | err = clnt.Remove(fid) 32 | return err 33 | } 34 | -------------------------------------------------------------------------------- /clnt_stat.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | // Returns the metadata for the file associated with the Fid, or an Error. 8 | func (clnt *Clnt) Stat(fid *Fid) (*Dir, error) { 9 | tc := clnt.NewFcall() 10 | err := PackTstat(tc, fid.Fid) 11 | if err != nil { 12 | return nil, err 13 | } 14 | 15 | rc, err := clnt.Rpc(tc) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | return &rc.Dir, nil 21 | } 22 | 23 | // Returns the metadata for a named file, or an Error. 24 | func (clnt *Clnt) FStat(path string) (*Dir, error) { 25 | fid, err := clnt.FWalk(path) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | d, err := clnt.Stat(fid) 31 | clnt.Clunk(fid) 32 | return d, err 33 | } 34 | 35 | // Modifies the data of the file associated with the Fid, or an Error. 36 | func (clnt *Clnt) Wstat(fid *Fid, dir *Dir) error { 37 | tc := clnt.NewFcall() 38 | err := PackTwstat(tc, fid.Fid, dir, clnt.Dotu) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | _, err = clnt.Rpc(tc) 44 | return err 45 | } 46 | -------------------------------------------------------------------------------- /clnt_stats_http.go: -------------------------------------------------------------------------------- 1 | // +build httpstats 2 | 3 | package go9p 4 | 5 | import ( 6 | "fmt" 7 | "io" 8 | "net/http" 9 | ) 10 | 11 | func (clnt *Clnt) ServeHTTP(c http.ResponseWriter, r *http.Request) { 12 | io.WriteString(c, fmt.Sprintf("

Client %s

", clnt.Id)) 13 | defer io.WriteString(c, "") 14 | 15 | // fcalls 16 | if clnt.Debuglevel&DbgLogFcalls != 0 { 17 | fs := clnt.Log.Filter(clnt, DbgLogFcalls) 18 | io.WriteString(c, fmt.Sprintf("

Last %d 9P messages

", len(fs))) 19 | for _, l := range fs { 20 | fc := l.Data.(*Fcall) 21 | if fc.Type != 0 { 22 | io.WriteString(c, fmt.Sprintf("
%s", fc)) 23 | } 24 | } 25 | } 26 | } 27 | 28 | func clntServeHTTP(c http.ResponseWriter, r *http.Request) { 29 | io.WriteString(c, fmt.Sprintf("")) 30 | defer io.WriteString(c, "") 31 | 32 | clnts.Lock() 33 | if clnts.clntList == nil { 34 | io.WriteString(c, "no clients") 35 | } 36 | 37 | for clnt := clnts.clntList; clnt != nil; clnt = clnt.next { 38 | io.WriteString(c, fmt.Sprintf("%s
", clnt.Id, clnt.Id)) 39 | } 40 | clnts.Unlock() 41 | } 42 | 43 | func (clnt *Clnt) statsRegister() { 44 | http.Handle("/go9p/clnt/"+clnt.Id, clnt) 45 | } 46 | 47 | func (clnt *Clnt) statsUnregister() { 48 | http.Handle("/go9p/clnt/"+clnt.Id, nil) 49 | } 50 | 51 | func (c *ClntList) statsRegister() { 52 | http.HandleFunc("/go9p/clnt", clntServeHTTP) 53 | } 54 | 55 | func (c *ClntList) statsUnregister() { 56 | http.HandleFunc("/go9p/clnt", nil) 57 | } 58 | -------------------------------------------------------------------------------- /clnt_tag.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | type Tag struct { 8 | clnt *Clnt 9 | tag uint16 10 | reqchan chan *Req 11 | respchan chan *Req 12 | donechan chan bool 13 | } 14 | 15 | func (clnt *Clnt) TagAlloc(reqchan chan *Req) *Tag { 16 | tag := new(Tag) 17 | tag.clnt = clnt 18 | tag.tag = uint16(clnt.tagpool.getId()) 19 | tag.reqchan = reqchan 20 | tag.respchan = make(chan *Req, 16) 21 | tag.donechan = make(chan bool) 22 | go tag.reqproc() 23 | 24 | return tag 25 | } 26 | 27 | func (clnt *Clnt) TagFree(tag *Tag) { 28 | tag.donechan <- true 29 | clnt.tagpool.putId(uint32(tag.tag)) 30 | } 31 | 32 | func (tag *Tag) reqAlloc() *Req { 33 | r := new(Req) 34 | r.tag = tag.tag 35 | r.Clnt = tag.clnt 36 | r.Done = tag.respchan 37 | r.Tc = tag.clnt.NewFcall() 38 | 39 | return r 40 | } 41 | 42 | func (tag *Tag) ReqFree(r *Req) { 43 | tag.clnt.FreeFcall(r.Tc) 44 | } 45 | 46 | func (tag *Tag) reqproc() { 47 | for { 48 | select { 49 | case <-tag.donechan: 50 | return 51 | 52 | case r := <-tag.respchan: 53 | rc := r.Rc 54 | fid := r.fid 55 | err := r.Rc.Type == Rerror 56 | 57 | switch r.Tc.Type { 58 | case Tauth: 59 | if err { 60 | fid.User = nil 61 | } 62 | 63 | case Tattach: 64 | if !err { 65 | fid.Qid = rc.Qid 66 | } else { 67 | fid.User = nil 68 | } 69 | 70 | case Twalk: 71 | if !err { 72 | fid.walked = true 73 | if len(rc.Wqid) > 0 { 74 | fid.Qid = rc.Wqid[len(rc.Wqid)-1] 75 | } 76 | } else { 77 | fid.User = nil 78 | } 79 | 80 | case Topen: 81 | case Tcreate: 82 | if !err { 83 | fid.Iounit = rc.Iounit 84 | fid.Qid = rc.Qid 85 | } else { 86 | fid.Mode = 0 87 | } 88 | 89 | case Tclunk: 90 | case Tremove: 91 | tag.clnt.fidpool.putId(fid.Fid) 92 | } 93 | 94 | tag.reqchan <- r 95 | } 96 | } 97 | } 98 | 99 | func (tag *Tag) Auth(afid *Fid, user User, aname string) error { 100 | req := tag.reqAlloc() 101 | req.fid = afid 102 | err := PackTauth(req.Tc, afid.Fid, user.Name(), aname, uint32(user.Id()), tag.clnt.Dotu) 103 | if err != nil { 104 | return err 105 | } 106 | 107 | afid.User = user 108 | return tag.clnt.Rpcnb(req) 109 | } 110 | 111 | func (tag *Tag) Attach(fid, afid *Fid, user User, aname string) error { 112 | var afno uint32 113 | 114 | if afid != nil { 115 | afno = afid.Fid 116 | } else { 117 | afno = NOFID 118 | } 119 | 120 | req := tag.reqAlloc() 121 | req.fid = fid 122 | err := PackTattach(req.Tc, fid.Fid, afno, user.Name(), aname, uint32(user.Id()), tag.clnt.Dotu) 123 | if err != nil { 124 | return err 125 | } 126 | 127 | fid.User = user 128 | return tag.clnt.Rpcnb(req) 129 | } 130 | 131 | func (tag *Tag) Walk(fid *Fid, newfid *Fid, wnames []string) error { 132 | req := tag.reqAlloc() 133 | req.fid = newfid 134 | if len(wnames) == 0 { 135 | newfid.Qid = fid.Qid 136 | } 137 | 138 | err := PackTwalk(req.Tc, fid.Fid, newfid.Fid, wnames) 139 | if err != nil { 140 | return err 141 | } 142 | 143 | newfid.User = fid.User 144 | return tag.clnt.Rpcnb(req) 145 | } 146 | 147 | func (tag *Tag) Open(fid *Fid, mode uint8) error { 148 | req := tag.reqAlloc() 149 | req.fid = fid 150 | err := PackTopen(req.Tc, fid.Fid, mode) 151 | if err != nil { 152 | return err 153 | } 154 | 155 | fid.Mode = mode 156 | return tag.clnt.Rpcnb(req) 157 | } 158 | 159 | func (tag *Tag) Create(fid *Fid, name string, perm uint32, mode uint8, ext string) error { 160 | req := tag.reqAlloc() 161 | req.fid = fid 162 | err := PackTcreate(req.Tc, fid.Fid, name, perm, mode, ext, tag.clnt.Dotu) 163 | if err != nil { 164 | return err 165 | } 166 | 167 | fid.Mode = mode 168 | return tag.clnt.Rpcnb(req) 169 | } 170 | 171 | func (tag *Tag) Read(fid *Fid, offset uint64, count uint32) error { 172 | req := tag.reqAlloc() 173 | req.fid = fid 174 | err := PackTread(req.Tc, fid.Fid, offset, count) 175 | if err != nil { 176 | return err 177 | } 178 | 179 | return tag.clnt.Rpcnb(req) 180 | } 181 | 182 | func (tag *Tag) Write(fid *Fid, data []byte, offset uint64) error { 183 | req := tag.reqAlloc() 184 | req.fid = fid 185 | err := PackTwrite(req.Tc, fid.Fid, offset, uint32(len(data)), data) 186 | if err != nil { 187 | return err 188 | } 189 | 190 | return tag.clnt.Rpcnb(req) 191 | } 192 | 193 | func (tag *Tag) Clunk(fid *Fid) error { 194 | req := tag.reqAlloc() 195 | req.fid = fid 196 | err := PackTclunk(req.Tc, fid.Fid) 197 | if err != nil { 198 | return err 199 | } 200 | 201 | return tag.clnt.Rpcnb(req) 202 | } 203 | 204 | func (tag *Tag) Remove(fid *Fid) error { 205 | req := tag.reqAlloc() 206 | req.fid = fid 207 | err := PackTremove(req.Tc, fid.Fid) 208 | if err != nil { 209 | return err 210 | } 211 | 212 | return tag.clnt.Rpcnb(req) 213 | } 214 | 215 | func (tag *Tag) Stat(fid *Fid) error { 216 | req := tag.reqAlloc() 217 | req.fid = fid 218 | err := PackTstat(req.Tc, fid.Fid) 219 | if err != nil { 220 | return err 221 | } 222 | 223 | return tag.clnt.Rpcnb(req) 224 | } 225 | 226 | func (tag *Tag) Wstat(fid *Fid, dir *Dir) error { 227 | req := tag.reqAlloc() 228 | req.fid = fid 229 | err := PackTwstat(req.Tc, fid.Fid, dir, tag.clnt.Dotu) 230 | if err != nil { 231 | return err 232 | } 233 | 234 | return tag.clnt.Rpcnb(req) 235 | } 236 | -------------------------------------------------------------------------------- /clnt_walk.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | import ( 8 | "strings" 9 | ) 10 | 11 | // Starting from the file associated with fid, walks all wnames in 12 | // sequence and associates the resulting file with newfid. If no wnames 13 | // were walked successfully, an Error is returned. Otherwise a slice with a 14 | // Qid for each walked name is returned. 15 | func (clnt *Clnt) Walk(fid *Fid, newfid *Fid, wnames []string) ([]Qid, error) { 16 | tc := clnt.NewFcall() 17 | err := PackTwalk(tc, fid.Fid, newfid.Fid, wnames) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | rc, err := clnt.Rpc(tc) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | newfid.walked = true 28 | return rc.Wqid, nil 29 | } 30 | 31 | // Walks to a named file. Returns a Fid associated with the file, 32 | // or an Error. 33 | func (clnt *Clnt) FWalk(path string) (*Fid, error) { 34 | var err error = nil 35 | 36 | var i, m int 37 | for i = 0; i < len(path); i++ { 38 | if path[i] != '/' { 39 | break 40 | } 41 | } 42 | 43 | if i > 0 { 44 | path = path[i:] 45 | } 46 | 47 | wnames := strings.Split(path, "/") 48 | newfid := clnt.FidAlloc() 49 | fid := clnt.Root 50 | newfid.User = fid.User 51 | 52 | /* get rid of the empty names */ 53 | for i, m = 0, 0; i < len(wnames); i++ { 54 | if wnames[i] != "" { 55 | wnames[m] = wnames[i] 56 | m++ 57 | } 58 | } 59 | 60 | wnames = wnames[0:m] 61 | for { 62 | n := len(wnames) 63 | if n > 16 { 64 | n = 16 65 | } 66 | 67 | tc := clnt.NewFcall() 68 | err = PackTwalk(tc, fid.Fid, newfid.Fid, wnames[0:n]) 69 | if err != nil { 70 | goto error 71 | } 72 | 73 | var rc *Fcall 74 | rc, err = clnt.Rpc(tc) 75 | if err != nil { 76 | goto error 77 | } 78 | 79 | newfid.walked = true 80 | if len(rc.Wqid) != n { 81 | err = &Error{"file not found", ENOENT} 82 | goto error 83 | } 84 | 85 | if len(rc.Wqid) > 0 { 86 | newfid.Qid = rc.Wqid[len(rc.Wqid)-1] 87 | } else { 88 | newfid.Qid = fid.Qid 89 | } 90 | 91 | wnames = wnames[n:] 92 | fid = newfid 93 | if len(wnames) == 0 { 94 | break 95 | } 96 | } 97 | 98 | return newfid, nil 99 | 100 | error: 101 | clnt.Clunk(newfid) 102 | return nil, err 103 | } 104 | -------------------------------------------------------------------------------- /clnt_write.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | // Write up to len(data) bytes starting from offset. Returns the 8 | // number of bytes written, or an Error. 9 | func (clnt *Clnt) Write(fid *Fid, data []byte, offset uint64) (int, error) { 10 | if uint32(len(data)) > fid.Iounit { 11 | data = data[0:fid.Iounit] 12 | } 13 | 14 | tc := clnt.NewFcall() 15 | err := PackTwrite(tc, fid.Fid, offset, uint32(len(data)), data) 16 | if err != nil { 17 | return 0, err 18 | } 19 | 20 | rc, err := clnt.Rpc(tc) 21 | if err != nil { 22 | return 0, err 23 | } 24 | 25 | return int(rc.Count), nil 26 | } 27 | 28 | // Writes up to len(buf) bytes to a file. Returns the number of 29 | // bytes written, or an Error. 30 | func (file *File) Write(buf []byte) (int, error) { 31 | n, err := file.WriteAt(buf, int64(file.offset)) 32 | if err == nil { 33 | file.offset += uint64(n) 34 | } 35 | 36 | return n, err 37 | } 38 | 39 | // Writes up to len(buf) bytes starting from offset. Returns the number 40 | // of bytes written, or an Error. 41 | func (file *File) WriteAt(buf []byte, offset int64) (int, error) { 42 | return file.Fid.Clnt.Write(file.Fid, buf, uint64(offset)) 43 | } 44 | 45 | // Writes exactly len(buf) bytes starting from offset. Returns the number of 46 | // bytes written. If Error is returned the number of bytes can be less 47 | // than len(buf). 48 | func (file *File) Writen(buf []byte, offset uint64) (int, error) { 49 | ret := 0 50 | for len(buf) > 0 { 51 | n, err := file.WriteAt(buf, int64(offset)) 52 | if err != nil { 53 | return ret, err 54 | } 55 | 56 | if n == 0 { 57 | break 58 | } 59 | 60 | buf = buf[n:] 61 | offset += uint64(n) 62 | ret += n 63 | } 64 | 65 | return ret, nil 66 | } 67 | -------------------------------------------------------------------------------- /debug.go: -------------------------------------------------------------------------------- 1 | package go9p 2 | 3 | // Debug flags 4 | const ( 5 | DbgPrintFcalls = (1 << iota) // print all 9P messages on stderr 6 | DbgPrintPackets // print the raw packets on stderr 7 | DbgLogFcalls // keep the last N 9P messages (can be accessed over http) 8 | DbgLogPackets // keep the last N 9P messages (can be accessed over http) 9 | ) 10 | -------------------------------------------------------------------------------- /fmt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | import "fmt" 8 | 9 | func permToString(perm uint32) string { 10 | ret := "" 11 | 12 | if perm&DMDIR != 0 { 13 | ret += "d" 14 | } 15 | 16 | if perm&DMAPPEND != 0 { 17 | ret += "a" 18 | } 19 | 20 | if perm&DMAUTH != 0 { 21 | ret += "A" 22 | } 23 | 24 | if perm&DMEXCL != 0 { 25 | ret += "l" 26 | } 27 | 28 | if perm&DMTMP != 0 { 29 | ret += "t" 30 | } 31 | 32 | if perm&DMDEVICE != 0 { 33 | ret += "D" 34 | } 35 | 36 | if perm&DMSOCKET != 0 { 37 | ret += "S" 38 | } 39 | 40 | if perm&DMNAMEDPIPE != 0 { 41 | ret += "P" 42 | } 43 | 44 | if perm&DMSYMLINK != 0 { 45 | ret += "L" 46 | } 47 | 48 | ret += fmt.Sprintf("%o", perm&0777) 49 | return ret 50 | } 51 | 52 | func (qid *Qid) String() string { 53 | b := "" 54 | if qid.Type&QTDIR != 0 { 55 | b += "d" 56 | } 57 | if qid.Type&QTAPPEND != 0 { 58 | b += "a" 59 | } 60 | if qid.Type&QTAUTH != 0 { 61 | b += "A" 62 | } 63 | if qid.Type&QTEXCL != 0 { 64 | b += "l" 65 | } 66 | if qid.Type&QTTMP != 0 { 67 | b += "t" 68 | } 69 | if qid.Type&QTSYMLINK != 0 { 70 | b += "L" 71 | } 72 | 73 | return fmt.Sprintf("(%x %x '%s')", qid.Path, qid.Version, b) 74 | } 75 | 76 | func (d *Dir) String() string { 77 | ret := fmt.Sprintf("'%s' '%s' '%s' '%s' q ", d.Name, d.Uid, d.Gid, d.Muid) 78 | ret += d.Qid.String() + " m " + permToString(d.Mode) 79 | ret += fmt.Sprintf(" at %d mt %d l %d t %d d %d", d.Atime, d.Mtime, 80 | d.Length, d.Type, d.Dev) 81 | 82 | /* dotu ? */ 83 | ret += " ext " + d.Ext 84 | 85 | return ret 86 | } 87 | 88 | func (fc *Fcall) String() string { 89 | ret := "" 90 | 91 | switch fc.Type { 92 | default: 93 | ret = fmt.Sprintf("invalid call: %d", fc.Type) 94 | case Tversion: 95 | ret = fmt.Sprintf("Tversion tag %d msize %d version '%s'", fc.Tag, fc.Msize, fc.Version) 96 | case Rversion: 97 | ret = fmt.Sprintf("Rversion tag %d msize %d version '%s'", fc.Tag, fc.Msize, fc.Version) 98 | case Tauth: 99 | ret = fmt.Sprintf("Tauth tag %d afid %d uname '%s' nuname %d aname '%s'", 100 | fc.Tag, fc.Afid, fc.Uname, fc.Unamenum, fc.Aname) 101 | case Rauth: 102 | ret = fmt.Sprintf("Rauth tag %d aqid %v", fc.Tag, &fc.Qid) 103 | case Rattach: 104 | ret = fmt.Sprintf("Rattach tag %d aqid %v", fc.Tag, &fc.Qid) 105 | case Tattach: 106 | ret = fmt.Sprintf("Tattach tag %d fid %d afid %d uname '%s' nuname %d aname '%s'", 107 | fc.Tag, fc.Fid, fc.Afid, fc.Uname, fc.Unamenum, fc.Aname) 108 | case Tflush: 109 | ret = fmt.Sprintf("Tflush tag %d oldtag %d", fc.Tag, fc.Oldtag) 110 | case Rerror: 111 | ret = fmt.Sprintf("Rerror tag %d ename '%s' ecode %d", fc.Tag, fc.Error, fc.Errornum) 112 | case Twalk: 113 | ret = fmt.Sprintf("Twalk tag %d fid %d newfid %d ", fc.Tag, fc.Fid, fc.Newfid) 114 | for i := 0; i < len(fc.Wname); i++ { 115 | ret += fmt.Sprintf("%d:'%s' ", i, fc.Wname[i]) 116 | } 117 | case Rwalk: 118 | ret = fmt.Sprintf("Rwalk tag %d ", fc.Tag) 119 | for i := 0; i < len(fc.Wqid); i++ { 120 | ret += fmt.Sprintf("%v ", &fc.Wqid[i]) 121 | } 122 | case Topen: 123 | ret = fmt.Sprintf("Topen tag %d fid %d mode %x", fc.Tag, fc.Fid, fc.Mode) 124 | case Ropen: 125 | ret = fmt.Sprintf("Ropen tag %d qid %v iounit %d", fc.Tag, &fc.Qid, fc.Iounit) 126 | case Rcreate: 127 | ret = fmt.Sprintf("Rcreate tag %d qid %v iounit %d", fc.Tag, &fc.Qid, fc.Iounit) 128 | case Tcreate: 129 | ret = fmt.Sprintf("Tcreate tag %d fid %d name '%s' perm ", fc.Tag, fc.Fid, fc.Name) 130 | ret += permToString(fc.Perm) 131 | ret += fmt.Sprintf(" mode %x ", fc.Mode) 132 | case Tread: 133 | ret = fmt.Sprintf("Tread tag %d fid %d offset %d count %d", fc.Tag, fc.Fid, fc.Offset, fc.Count) 134 | case Rread: 135 | ret = fmt.Sprintf("Rread tag %d count %d", fc.Tag, fc.Count) 136 | case Twrite: 137 | ret = fmt.Sprintf("Twrite tag %d fid %d offset %d count %d", fc.Tag, fc.Fid, fc.Offset, fc.Count) 138 | case Rwrite: 139 | ret = fmt.Sprintf("Rwrite tag %d count %d", fc.Tag, fc.Count) 140 | case Tclunk: 141 | ret = fmt.Sprintf("Tclunk tag %d fid %d", fc.Tag, fc.Fid) 142 | case Rclunk: 143 | ret = fmt.Sprintf("Rclunk tag %d", fc.Tag) 144 | case Tremove: 145 | ret = fmt.Sprintf("Tremove tag %d fid %d", fc.Tag, fc.Fid) 146 | case Tstat: 147 | ret = fmt.Sprintf("Tstat tag %d fid %d", fc.Tag, fc.Fid) 148 | case Rstat: 149 | ret = fmt.Sprintf("Rstat tag %d st (%v)", fc.Tag, &fc.Dir) 150 | case Twstat: 151 | ret = fmt.Sprintf("Twstat tag %d fid %d st (%v)", fc.Tag, fc.Fid, &fc.Dir) 152 | case Rflush: 153 | ret = fmt.Sprintf("Rflush tag %d", fc.Tag) 154 | case Rremove: 155 | ret = fmt.Sprintf("Rremove tag %d", fc.Tag) 156 | case Rwstat: 157 | ret = fmt.Sprintf("Rwstat tag %d", fc.Tag) 158 | } 159 | 160 | return ret 161 | } 162 | -------------------------------------------------------------------------------- /go9p_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "io/ioutil" 11 | "net" 12 | "os" 13 | "path" 14 | "syscall" 15 | "testing" 16 | ) 17 | 18 | const numDir = 16384 19 | 20 | var addr = flag.String("addr", ":5640", "network address") 21 | var pipefsaddr = flag.String("pipefsaddr", ":5641", "pipefs network address") 22 | var debug = flag.Int("debug", 0, "print debug messages") 23 | var root = flag.String("root", "/", "root filesystem") 24 | 25 | // Two files, dotu was true. 26 | var testunpackbytes = []byte{ 27 | 79, 0, 0, 0, 0, 0, 0, 0, 0, 228, 193, 233, 248, 44, 145, 3, 0, 0, 0, 0, 0, 164, 1, 0, 0, 0, 0, 0, 0, 47, 117, 180, 83, 102, 3, 0, 0, 0, 0, 0, 0, 6, 0, 112, 97, 115, 115, 119, 100, 4, 0, 110, 111, 110, 101, 4, 0, 110, 111, 110, 101, 4, 0, 110, 111, 110, 101, 0, 0, 232, 3, 0, 0, 232, 3, 0, 0, 255, 255, 255, 255, 78, 0, 0, 0, 0, 0, 0, 0, 0, 123, 171, 233, 248, 42, 145, 3, 0, 0, 0, 0, 0, 164, 1, 0, 0, 0, 0, 0, 0, 41, 117, 180, 83, 195, 0, 0, 0, 0, 0, 0, 0, 5, 0, 104, 111, 115, 116, 115, 4, 0, 110, 111, 110, 101, 4, 0, 110, 111, 110, 101, 4, 0, 110, 111, 110, 101, 0, 0, 232, 3, 0, 0, 232, 3, 0, 0, 255, 255, 255, 255, 28 | } 29 | 30 | func TestUnpackDir(t *testing.T) { 31 | b := testunpackbytes 32 | for len(b) > 0 { 33 | var err error 34 | if _, b, _, err = UnpackDir(b, true); err != nil { 35 | t.Fatalf("Unpackdir: %v", err) 36 | } 37 | } 38 | } 39 | 40 | func TestAttachOpenReaddir(t *testing.T) { 41 | var err error 42 | flag.Parse() 43 | ufs := new(Ufs) 44 | ufs.Dotu = false 45 | ufs.Id = "ufs" 46 | ufs.Root = *root 47 | ufs.Debuglevel = *debug 48 | ufs.Start(ufs) 49 | 50 | t.Log("ufs starting\n") 51 | // determined by build tags 52 | //extraFuncs() 53 | go func() { 54 | if err = ufs.StartNetListener("tcp", *addr); err != nil { 55 | t.Fatalf("Can not start listener: %v", err) 56 | } 57 | }() 58 | /* this may take a few tries ... */ 59 | var conn net.Conn 60 | for i := 0; i < 16; i++ { 61 | if conn, err = net.Dial("tcp", *addr); err != nil { 62 | t.Logf("Try go connect, %d'th try, %v", i, err) 63 | } else { 64 | t.Logf("Got a conn, %v\n", conn) 65 | break 66 | } 67 | } 68 | if err != nil { 69 | t.Fatalf("Connect failed after many tries ...") 70 | } 71 | 72 | root := OsUsers.Uid2User(0) 73 | 74 | dir, err := ioutil.TempDir("", "go9p") 75 | if err != nil { 76 | t.Fatalf("got %v, want nil", err) 77 | } 78 | defer os.RemoveAll(dir) 79 | 80 | // Now create a whole bunch of files to test readdir 81 | for i := 0; i < numDir; i++ { 82 | f := fmt.Sprintf(path.Join(dir, fmt.Sprintf("%d", i))) 83 | if err := ioutil.WriteFile(f, []byte(f), 0600); err != nil { 84 | t.Fatalf("Create %v: got %v, want nil", f, err) 85 | } 86 | } 87 | 88 | var clnt *Clnt 89 | for i := 0; i < 16; i++ { 90 | clnt, err = Mount("tcp", *addr, dir, 8192, root) 91 | } 92 | if err != nil { 93 | t.Fatalf("Connect failed: %v\n", err) 94 | } 95 | 96 | defer clnt.Unmount() 97 | t.Logf("attached, rootfid %v\n", clnt.Root) 98 | 99 | dirfid := clnt.FidAlloc() 100 | if _, err = clnt.Walk(clnt.Root, dirfid, []string{"."}); err != nil { 101 | t.Fatalf("%v", err) 102 | } 103 | if err = clnt.Open(dirfid, 0); err != nil { 104 | t.Fatalf("%v", err) 105 | } 106 | var b []byte 107 | var i, amt int 108 | var offset uint64 109 | for i < numDir { 110 | if b, err = clnt.Read(dirfid, offset, 64*1024); err != nil { 111 | t.Fatalf("%v", err) 112 | } 113 | for b != nil && len(b) > 0 { 114 | if _, b, amt, err = UnpackDir(b, ufs.Dotu); err != nil { 115 | break 116 | } else { 117 | i++ 118 | offset += uint64(amt) 119 | } 120 | } 121 | } 122 | if i != numDir { 123 | t.Fatalf("Reading %v: got %d entries, wanted %d", dir, i, numDir) 124 | } 125 | 126 | // Alternate form, using readdir and File 127 | var dirfile *File 128 | if dirfile, err = clnt.FOpen(".", OREAD); err != nil { 129 | t.Fatalf("%v", err) 130 | } 131 | i, amt, offset = 0, 0, 0 132 | for i < numDir { 133 | if d, err := dirfile.Readdir(numDir); err != nil { 134 | t.Fatalf("%v", err) 135 | } else { 136 | i += len(d) 137 | } 138 | } 139 | if i != numDir { 140 | t.Fatalf("Readdir %v: got %d entries, wanted %d", dir, i, numDir) 141 | } 142 | 143 | // now test partial reads. 144 | // Read 128 bytes at a time. Remember the last successful offset. 145 | // if UnpackDir fails, read again from that offset 146 | t.Logf("NOW TRY PARTIAL") 147 | i, amt, offset = 0, 0, 0 148 | for { 149 | var b []byte 150 | var d *Dir 151 | if b, err = clnt.Read(dirfid, offset, 128); err != nil { 152 | t.Fatalf("%v", err) 153 | } 154 | if len(b) == 0 { 155 | break 156 | } 157 | t.Logf("b %v\n", b) 158 | for b != nil && len(b) > 0 { 159 | t.Logf("len(b) %v\n", len(b)) 160 | if d, b, amt, err = UnpackDir(b, ufs.Dotu); err != nil { 161 | // this error is expected ... 162 | t.Logf("unpack failed (it's ok!). retry at offset %v\n", 163 | offset) 164 | break 165 | } else { 166 | t.Logf("d %v\n", d) 167 | offset += uint64(amt) 168 | } 169 | } 170 | } 171 | 172 | t.Logf("NOW TRY WAY TOO SMALL") 173 | i, amt, offset = 0, 0, 0 174 | for { 175 | var b []byte 176 | if b, err = clnt.Read(dirfid, offset, 32); err != nil { 177 | t.Logf("dirread fails as expected: %v\n", err) 178 | break 179 | } 180 | if offset == 0 && len(b) == 0 { 181 | t.Fatalf("too short dirread returns 0 (no error)") 182 | } 183 | if len(b) == 0 { 184 | break 185 | } 186 | // todo: add entry accumulation and validation here.. 187 | offset += uint64(len(b)) 188 | } 189 | } 190 | 191 | var f *File 192 | var b = make([]byte, 1048576/8) 193 | 194 | // Not sure we want this, and the test has issues. Revive it if we ever find a use for it. 195 | func TestPipefs(t *testing.T) { 196 | pipefs := new(Pipefs) 197 | pipefs.Dotu = false 198 | pipefs.Msize = 1048576 199 | pipefs.Id = "pipefs" 200 | pipefs.Root = *root 201 | pipefs.Debuglevel = *debug 202 | pipefs.Start(pipefs) 203 | 204 | t.Logf("pipefs starting\n") 205 | d, err := ioutil.TempDir("", "TestPipeFS") 206 | if err != nil { 207 | t.Fatalf("%v", err) 208 | } 209 | defer func() { 210 | if err := os.Remove(d); err != nil { 211 | t.Fatalf("%v", err) 212 | } 213 | }() 214 | fn := path.Join(d, "fifo") 215 | if err := syscall.Mkfifo(fn, 0600); err != nil { 216 | t.Fatalf("%v", err) 217 | } 218 | defer func() { 219 | if err := os.Remove(fn); err != nil { 220 | t.Fatalf("%v", err) 221 | } 222 | }() 223 | // determined by build tags 224 | //extraFuncs() 225 | go func() { 226 | err := pipefs.StartNetListener("tcp", *pipefsaddr) 227 | if err != nil { 228 | t.Fatalf("StartNetListener failed: %v\n", err) 229 | } 230 | }() 231 | root := OsUsers.Uid2User(0) 232 | 233 | var c *Clnt 234 | for i := 0; i < 16; i++ { 235 | c, err = Mount("tcp", *pipefsaddr, "/", uint32(len(b)), root) 236 | } 237 | if err != nil { 238 | t.Fatalf("Connect failed: %v\n", err) 239 | } 240 | t.Logf("Connected to %v\n", *c) 241 | if f, err = c.FOpen(fn, ORDWR); err != nil { 242 | t.Fatalf("Open failed: %v\n", err) 243 | } else { 244 | for i := 0; i < 1048576/8; i++ { 245 | b[i] = byte(i) 246 | } 247 | t.Logf("f %v \n", f) 248 | if n, err := f.Write(b); err != nil { 249 | t.Fatalf("write failed: %v\n", err) 250 | } else { 251 | t.Logf("Wrote %v bytes\n", n) 252 | } 253 | if n, err := f.Read(b); err != nil { 254 | t.Fatalf("read failed: %v\n", err) 255 | } else { 256 | t.Logf("read %v bytes\n", n) 257 | } 258 | 259 | } 260 | } 261 | 262 | func BenchmarkPipeFS(bb *testing.B) { 263 | for i := 0; i < bb.N; i++ { 264 | if _, err := f.Write(b); err != nil { 265 | bb.Errorf("write failed: %v\n", err) 266 | } 267 | if _, err := f.Read(b); err != nil { 268 | bb.Errorf("read failed: %v\n", err) 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package go9p 2 | 3 | type Log struct { 4 | Data interface{} 5 | Owner interface{} 6 | Type int 7 | } 8 | 9 | type Logger struct { 10 | items []*Log 11 | idx int 12 | logchan chan *Log 13 | fltchan chan *flt 14 | rszchan chan int 15 | } 16 | 17 | type flt struct { 18 | owner interface{} 19 | itype int 20 | fltchan chan []*Log 21 | } 22 | 23 | func NewLogger(sz int) *Logger { 24 | if sz == 0 { 25 | return nil 26 | } 27 | 28 | l := new(Logger) 29 | l.items = make([]*Log, sz) 30 | l.logchan = make(chan *Log, 16) 31 | l.fltchan = make(chan *flt) 32 | l.rszchan = make(chan int) 33 | 34 | go l.doLog() 35 | return l 36 | } 37 | 38 | func (l *Logger) Resize(sz int) { 39 | if sz == 0 { 40 | return 41 | } 42 | 43 | l.rszchan <- sz 44 | } 45 | 46 | func (l *Logger) Log(data, owner interface{}, itype int) { 47 | l.logchan <- &Log{data, owner, itype} 48 | } 49 | 50 | func (l *Logger) Filter(owner interface{}, itype int) []*Log { 51 | c := make(chan []*Log) 52 | l.fltchan <- &flt{owner, itype, c} 53 | return <-c 54 | } 55 | 56 | func (l *Logger) doLog() { 57 | for { 58 | select { 59 | case it := <-l.logchan: 60 | if l.idx >= len(l.items) { 61 | l.idx = 0 62 | } 63 | 64 | l.items[l.idx] = it 65 | l.idx++ 66 | 67 | case sz := <-l.rszchan: 68 | it := make([]*Log, sz) 69 | for i, j := l.idx, 0; j < len(it); j++ { 70 | if i >= len(l.items) { 71 | i = 0 72 | } 73 | 74 | it[j] = l.items[i] 75 | i++ 76 | if i == l.idx { 77 | break 78 | } 79 | } 80 | 81 | l.items = it 82 | l.idx = 0 83 | 84 | case flt := <-l.fltchan: 85 | n := 0 86 | // we don't care about the order while counting 87 | for _, it := range l.items { 88 | if it == nil { 89 | continue 90 | } 91 | 92 | if (flt.owner == nil || it.Owner == flt.owner) && 93 | (flt.itype == 0 || it.Type == flt.itype) { 94 | n++ 95 | } 96 | } 97 | 98 | its := make([]*Log, n) 99 | for i, m := l.idx, 0; m < len(its); i++ { 100 | if i >= len(l.items) { 101 | i = 0 102 | } 103 | 104 | it := l.items[i] 105 | if it != nil && (flt.owner == nil || it.Owner == flt.owner) && 106 | (flt.itype == 0 || it.Type == flt.itype) { 107 | its[m] = it 108 | m++ 109 | } 110 | } 111 | 112 | flt.fltchan <- its 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /osusers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | import "sync" 8 | 9 | var once sync.Once 10 | 11 | type osUser struct { 12 | uid int 13 | } 14 | 15 | type osUsers struct { 16 | users map[int]*osUser 17 | groups map[int]*osGroup 18 | sync.Mutex 19 | } 20 | 21 | // Simple Users implementation that fakes looking up users and groups 22 | // by uid only. The names and groups memberships are empty 23 | var OsUsers *osUsers 24 | 25 | func (u *osUser) Name() string { return "" } 26 | 27 | func (u *osUser) Id() int { return u.uid } 28 | 29 | func (u *osUser) Groups() []Group { return nil } 30 | 31 | func (u *osUser) IsMember(g Group) bool { return false } 32 | 33 | type osGroup struct { 34 | gid int 35 | } 36 | 37 | func (g *osGroup) Name() string { return "" } 38 | 39 | func (g *osGroup) Id() int { return g.gid } 40 | 41 | func (g *osGroup) Members() []User { return nil } 42 | 43 | func initOsusers() { 44 | OsUsers = new(osUsers) 45 | OsUsers.users = make(map[int]*osUser) 46 | OsUsers.groups = make(map[int]*osGroup) 47 | } 48 | 49 | func (up *osUsers) Uid2User(uid int) User { 50 | once.Do(initOsusers) 51 | OsUsers.Lock() 52 | defer OsUsers.Unlock() 53 | user, present := OsUsers.users[uid] 54 | if present { 55 | return user 56 | } 57 | 58 | user = new(osUser) 59 | user.uid = uid 60 | OsUsers.users[uid] = user 61 | return user 62 | } 63 | 64 | func (up *osUsers) Uname2User(uname string) User { 65 | // unimplemented 66 | return nil 67 | } 68 | 69 | func (up *osUsers) Gid2Group(gid int) Group { 70 | once.Do(initOsusers) 71 | OsUsers.Lock() 72 | group, present := OsUsers.groups[gid] 73 | if present { 74 | OsUsers.Unlock() 75 | return group 76 | } 77 | 78 | group = new(osGroup) 79 | group.gid = gid 80 | OsUsers.groups[gid] = group 81 | OsUsers.Unlock() 82 | return group 83 | } 84 | 85 | func (up *osUsers) Gname2Group(gname string) Group { 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /p9.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // The p9 package go9provides the definitions and functions used to implement 6 | // the 9P2000 protocol. 7 | // TODO. 8 | // All the packet conversion code in this file is crap and needs a rewrite. 9 | package go9p 10 | 11 | import ( 12 | "fmt" 13 | ) 14 | 15 | // 9P2000 message types 16 | const ( 17 | Tversion = 100 + iota 18 | Rversion 19 | Tauth 20 | Rauth 21 | Tattach 22 | Rattach 23 | Terror 24 | Rerror 25 | Tflush 26 | Rflush 27 | Twalk 28 | Rwalk 29 | Topen 30 | Ropen 31 | Tcreate 32 | Rcreate 33 | Tread 34 | Rread 35 | Twrite 36 | Rwrite 37 | Tclunk 38 | Rclunk 39 | Tremove 40 | Rremove 41 | Tstat 42 | Rstat 43 | Twstat 44 | Rwstat 45 | Tlast 46 | ) 47 | 48 | const ( 49 | MSIZE = 1048576 + IOHDRSZ // default message size (1048576+IOHdrSz) 50 | IOHDRSZ = 24 // the non-data size of the Twrite messages 51 | PORT = 564 // default port for 9P file servers 52 | ) 53 | 54 | // Qid types 55 | const ( 56 | QTDIR = 0x80 // directories 57 | QTAPPEND = 0x40 // append only files 58 | QTEXCL = 0x20 // exclusive use files 59 | QTMOUNT = 0x10 // mounted channel 60 | QTAUTH = 0x08 // authentication file 61 | QTTMP = 0x04 // non-backed-up file 62 | QTSYMLINK = 0x02 // symbolic link (Unix, 9P2000.u) 63 | QTLINK = 0x01 // hard link (Unix, 9P2000.u) 64 | QTFILE = 0x00 65 | ) 66 | 67 | // Flags for the mode field in Topen and Tcreate messages 68 | const ( 69 | OREAD = 0 // open read-only 70 | OWRITE = 1 // open write-only 71 | ORDWR = 2 // open read-write 72 | OEXEC = 3 // execute (== read but check execute permission) 73 | OTRUNC = 16 // or'ed in (except for exec), truncate file first 74 | OCEXEC = 32 // or'ed in, close on exec 75 | ORCLOSE = 64 // or'ed in, remove on close 76 | ) 77 | 78 | // File modes 79 | const ( 80 | DMDIR = 0x80000000 // mode bit for directories 81 | DMAPPEND = 0x40000000 // mode bit for append only files 82 | DMEXCL = 0x20000000 // mode bit for exclusive use files 83 | DMMOUNT = 0x10000000 // mode bit for mounted channel 84 | DMAUTH = 0x08000000 // mode bit for authentication file 85 | DMTMP = 0x04000000 // mode bit for non-backed-up file 86 | DMSYMLINK = 0x02000000 // mode bit for symbolic link (Unix, 9P2000.u) 87 | DMLINK = 0x01000000 // mode bit for hard link (Unix, 9P2000.u) 88 | DMDEVICE = 0x00800000 // mode bit for device file (Unix, 9P2000.u) 89 | DMNAMEDPIPE = 0x00200000 // mode bit for named pipe (Unix, 9P2000.u) 90 | DMSOCKET = 0x00100000 // mode bit for socket (Unix, 9P2000.u) 91 | DMSETUID = 0x00080000 // mode bit for setuid (Unix, 9P2000.u) 92 | DMSETGID = 0x00040000 // mode bit for setgid (Unix, 9P2000.u) 93 | DMREAD = 0x4 // mode bit for read permission 94 | DMWRITE = 0x2 // mode bit for write permission 95 | DMEXEC = 0x1 // mode bit for execute permission 96 | ) 97 | 98 | const ( 99 | NOTAG uint16 = 0xFFFF // no tag specified 100 | NOFID uint32 = 0xFFFFFFFF // no fid specified 101 | NOUID uint32 = 0xFFFFFFFF // no uid specified 102 | ) 103 | 104 | // Error values 105 | const ( 106 | EPERM = 1 107 | ENOENT = 2 108 | EIO = 5 109 | EEXIST = 17 110 | ENOTDIR = 20 111 | EINVAL = 22 112 | ) 113 | 114 | // Error represents a 9P2000 (and 9P2000.u) error 115 | type Error struct { 116 | Err string // textual representation of the error 117 | Errornum uint32 // numeric representation of the error (9P2000.u) 118 | } 119 | 120 | // File identifier 121 | type Qid struct { 122 | Type uint8 // type of the file (high 8 bits of the mode) 123 | Version uint32 // version number for the path 124 | Path uint64 // server's unique identification of the file 125 | } 126 | 127 | // Dir describes a file 128 | type Dir struct { 129 | Size uint16 // size-2 of the Dir on the wire 130 | Type uint16 131 | Dev uint32 132 | Qid // file's Qid 133 | Mode uint32 // permissions and flags 134 | Atime uint32 // last access time in seconds 135 | Mtime uint32 // last modified time in seconds 136 | Length uint64 // file length in bytes 137 | Name string // file name 138 | Uid string // owner name 139 | Gid string // group name 140 | Muid string // name of the last user that modified the file 141 | 142 | /* 9P2000.u extension */ 143 | Ext string // special file's descriptor 144 | Uidnum uint32 // owner ID 145 | Gidnum uint32 // group ID 146 | Muidnum uint32 // ID of the last user that modified the file 147 | } 148 | 149 | // Fcall represents a 9P2000 message 150 | type Fcall struct { 151 | Size uint32 // size of the message 152 | Type uint8 // message type 153 | Fid uint32 // file identifier 154 | Tag uint16 // message tag 155 | Msize uint32 // maximum message size (used by Tversion, Rversion) 156 | Version string // protocol version (used by Tversion, Rversion) 157 | Oldtag uint16 // tag of the message to flush (used by Tflush) 158 | Error string // error (used by Rerror) 159 | Qid // file Qid (used by Rauth, Rattach, Ropen, Rcreate) 160 | Iounit uint32 // maximum bytes read without breaking in multiple messages (used by Ropen, Rcreate) 161 | Afid uint32 // authentication fid (used by Tauth, Tattach) 162 | Uname string // user name (used by Tauth, Tattach) 163 | Aname string // attach name (used by Tauth, Tattach) 164 | Perm uint32 // file permission (mode) (used by Tcreate) 165 | Name string // file name (used by Tcreate) 166 | Mode uint8 // open mode (used by Topen, Tcreate) 167 | Newfid uint32 // the fid that represents the file walked to (used by Twalk) 168 | Wname []string // list of names to walk (used by Twalk) 169 | Wqid []Qid // list of Qids for the walked files (used by Rwalk) 170 | Offset uint64 // offset in the file to read/write from/to (used by Tread, Twrite) 171 | Count uint32 // number of bytes read/written (used by Tread, Rread, Twrite, Rwrite) 172 | Data []uint8 // data read/to-write (used by Rread, Twrite) 173 | Dir // file description (used by Rstat, Twstat) 174 | 175 | /* 9P2000.u extensions */ 176 | Errornum uint32 // error code, 9P2000.u only (used by Rerror) 177 | Ext string // special file description, 9P2000.u only (used by Tcreate) 178 | Unamenum uint32 // user ID, 9P2000.u only (used by Tauth, Tattach) 179 | 180 | Pkt []uint8 // raw packet data 181 | Buf []uint8 // buffer to put the raw data in 182 | } 183 | 184 | // Interface for accessing users and groups 185 | type Users interface { 186 | Uid2User(uid int) User 187 | Uname2User(uname string) User 188 | Gid2Group(gid int) Group 189 | Gname2Group(gname string) Group 190 | } 191 | 192 | // Represents a user 193 | type User interface { 194 | Name() string // user name 195 | Id() int // user id 196 | Groups() []Group // groups the user belongs to (can return nil) 197 | IsMember(g Group) bool // returns true if the user is member of the specified group 198 | } 199 | 200 | // Represents a group of users 201 | type Group interface { 202 | Name() string // group name 203 | Id() int // group id 204 | Members() []User // list of members that belong to the group (can return nil) 205 | } 206 | 207 | // minimum size of a 9P2000 message for a type 208 | var minFcsize = [...]uint32{ 209 | 6, /* Tversion msize[4] version[s] */ 210 | 6, /* Rversion msize[4] version[s] */ 211 | 8, /* Tauth fid[4] uname[s] aname[s] */ 212 | 13, /* Rauth aqid[13] */ 213 | 12, /* Tattach fid[4] afid[4] uname[s] aname[s] */ 214 | 13, /* Rattach qid[13] */ 215 | 0, /* Terror */ 216 | 2, /* Rerror ename[s] (ecode[4]) */ 217 | 2, /* Tflush oldtag[2] */ 218 | 0, /* Rflush */ 219 | 10, /* Twalk fid[4] newfid[4] nwname[2] */ 220 | 2, /* Rwalk nwqid[2] */ 221 | 5, /* Topen fid[4] mode[1] */ 222 | 17, /* Ropen qid[13] iounit[4] */ 223 | 11, /* Tcreate fid[4] name[s] perm[4] mode[1] */ 224 | 17, /* Rcreate qid[13] iounit[4] */ 225 | 16, /* Tread fid[4] offset[8] count[4] */ 226 | 4, /* Rread count[4] */ 227 | 16, /* Twrite fid[4] offset[8] count[4] */ 228 | 4, /* Rwrite count[4] */ 229 | 4, /* Tclunk fid[4] */ 230 | 0, /* Rclunk */ 231 | 4, /* Tremove fid[4] */ 232 | 0, /* Rremove */ 233 | 4, /* Tstat fid[4] */ 234 | 4, /* Rstat stat[n] */ 235 | 8, /* Twstat fid[4] stat[n] */ 236 | 0, /* Rwstat */ 237 | 20, /* Tbread fileid[8] offset[8] count[4] */ 238 | 4, /* Rbread count[4] */ 239 | 20, /* Tbwrite fileid[8] offset[8] count[4] */ 240 | 4, /* Rbwrite count[4] */ 241 | 16, /* Tbtrunc fileid[8] offset[8] */ 242 | 0, /* Rbtrunc */ 243 | } 244 | 245 | // minimum size of a 9P2000.u message for a type 246 | var minFcusize = [...]uint32{ 247 | 6, /* Tversion msize[4] version[s] */ 248 | 6, /* Rversion msize[4] version[s] */ 249 | 12, /* Tauth fid[4] uname[s] aname[s] */ 250 | 13, /* Rauth aqid[13] */ 251 | 16, /* Tattach fid[4] afid[4] uname[s] aname[s] */ 252 | 13, /* Rattach qid[13] */ 253 | 0, /* Terror */ 254 | 6, /* Rerror ename[s] (ecode[4]) */ 255 | 2, /* Tflush oldtag[2] */ 256 | 0, /* Rflush */ 257 | 10, /* Twalk fid[4] newfid[4] nwname[2] */ 258 | 2, /* Rwalk nwqid[2] */ 259 | 5, /* Topen fid[4] mode[1] */ 260 | 17, /* Ropen qid[13] iounit[4] */ 261 | 13, /* Tcreate fid[4] name[s] perm[4] mode[1] */ 262 | 17, /* Rcreate qid[13] iounit[4] */ 263 | 16, /* Tread fid[4] offset[8] count[4] */ 264 | 4, /* Rread count[4] */ 265 | 16, /* Twrite fid[4] offset[8] count[4] */ 266 | 4, /* Rwrite count[4] */ 267 | 4, /* Tclunk fid[4] */ 268 | 0, /* Rclunk */ 269 | 4, /* Tremove fid[4] */ 270 | 0, /* Rremove */ 271 | 4, /* Tstat fid[4] */ 272 | 4, /* Rstat stat[n] */ 273 | 8, /* Twstat fid[4] stat[n] */ 274 | 20, /* Tbread fileid[8] offset[8] count[4] */ 275 | 4, /* Rbread count[4] */ 276 | 20, /* Tbwrite fileid[8] offset[8] count[4] */ 277 | 4, /* Rbwrite count[4] */ 278 | 16, /* Tbtrunc fileid[8] offset[8] */ 279 | 0, /* Rbtrunc */ 280 | } 281 | 282 | func gint8(buf []byte) (uint8, []byte) { return buf[0], buf[1:] } 283 | 284 | func gint16(buf []byte) (uint16, []byte) { 285 | return uint16(buf[0]) | (uint16(buf[1]) << 8), buf[2:] 286 | } 287 | 288 | func gint32(buf []byte) (uint32, []byte) { 289 | return uint32(buf[0]) | (uint32(buf[1]) << 8) | (uint32(buf[2]) << 16) | 290 | (uint32(buf[3]) << 24), 291 | buf[4:] 292 | } 293 | 294 | func Gint32(buf []byte) (uint32, []byte) { return gint32(buf) } 295 | 296 | func gint64(buf []byte) (uint64, []byte) { 297 | return uint64(buf[0]) | (uint64(buf[1]) << 8) | (uint64(buf[2]) << 16) | 298 | (uint64(buf[3]) << 24) | (uint64(buf[4]) << 32) | (uint64(buf[5]) << 40) | 299 | (uint64(buf[6]) << 48) | (uint64(buf[7]) << 56), 300 | buf[8:] 301 | } 302 | 303 | func gstr(buf []byte) (string, []byte) { 304 | var n uint16 305 | 306 | if buf == nil || len(buf) < 2 { 307 | return "", nil 308 | } 309 | 310 | n, buf = gint16(buf) 311 | 312 | if int(n) > len(buf) { 313 | return "", nil 314 | } 315 | 316 | return string(buf[0:n]), buf[n:] 317 | } 318 | 319 | func gqid(buf []byte, qid *Qid) []byte { 320 | qid.Type, buf = gint8(buf) 321 | qid.Version, buf = gint32(buf) 322 | qid.Path, buf = gint64(buf) 323 | 324 | return buf 325 | } 326 | 327 | func gstat(buf []byte, d *Dir, dotu bool) ([]byte, error) { 328 | sz := len(buf) 329 | d.Size, buf = gint16(buf) 330 | d.Type, buf = gint16(buf) 331 | d.Dev, buf = gint32(buf) 332 | buf = gqid(buf, &d.Qid) 333 | d.Mode, buf = gint32(buf) 334 | d.Atime, buf = gint32(buf) 335 | d.Mtime, buf = gint32(buf) 336 | d.Length, buf = gint64(buf) 337 | d.Name, buf = gstr(buf) 338 | if buf == nil { 339 | s := fmt.Sprintf("Buffer too short for basic 9p: need %d, have %d", 340 | 49, sz) 341 | return nil, &Error{s, EINVAL} 342 | } 343 | 344 | d.Uid, buf = gstr(buf) 345 | if buf == nil { 346 | return nil, &Error{"d.Uid failed", EINVAL} 347 | } 348 | d.Gid, buf = gstr(buf) 349 | if buf == nil { 350 | return nil, &Error{"d.Gid failed", EINVAL} 351 | } 352 | 353 | d.Muid, buf = gstr(buf) 354 | if buf == nil { 355 | return nil, &Error{"d.Muid failed", EINVAL} 356 | } 357 | 358 | if dotu { 359 | d.Ext, buf = gstr(buf) 360 | if buf == nil { 361 | return nil, &Error{"d.Ext failed", EINVAL} 362 | } 363 | 364 | d.Uidnum, buf = gint32(buf) 365 | d.Gidnum, buf = gint32(buf) 366 | d.Muidnum, buf = gint32(buf) 367 | } else { 368 | d.Uidnum = NOUID 369 | d.Gidnum = NOUID 370 | d.Muidnum = NOUID 371 | } 372 | 373 | return buf, nil 374 | } 375 | 376 | func pint8(val uint8, buf []byte) []byte { 377 | buf[0] = val 378 | return buf[1:] 379 | } 380 | 381 | func pint16(val uint16, buf []byte) []byte { 382 | buf[0] = uint8(val) 383 | buf[1] = uint8(val >> 8) 384 | return buf[2:] 385 | } 386 | 387 | func pint32(val uint32, buf []byte) []byte { 388 | buf[0] = uint8(val) 389 | buf[1] = uint8(val >> 8) 390 | buf[2] = uint8(val >> 16) 391 | buf[3] = uint8(val >> 24) 392 | return buf[4:] 393 | } 394 | 395 | func pint64(val uint64, buf []byte) []byte { 396 | buf[0] = uint8(val) 397 | buf[1] = uint8(val >> 8) 398 | buf[2] = uint8(val >> 16) 399 | buf[3] = uint8(val >> 24) 400 | buf[4] = uint8(val >> 32) 401 | buf[5] = uint8(val >> 40) 402 | buf[6] = uint8(val >> 48) 403 | buf[7] = uint8(val >> 56) 404 | return buf[8:] 405 | } 406 | 407 | func pstr(val string, buf []byte) []byte { 408 | n := uint16(len(val)) 409 | buf = pint16(n, buf) 410 | b := []byte(val) 411 | copy(buf, b) 412 | return buf[n:] 413 | } 414 | 415 | func pqid(val *Qid, buf []byte) []byte { 416 | buf = pint8(val.Type, buf) 417 | buf = pint32(val.Version, buf) 418 | buf = pint64(val.Path, buf) 419 | 420 | return buf 421 | } 422 | 423 | func statsz(d *Dir, dotu bool) int { 424 | sz := 2 + 2 + 4 + 13 + 4 + 4 + 4 + 8 + 2 + 2 + 2 + 2 + len(d.Name) + len(d.Uid) + len(d.Gid) + len(d.Muid) 425 | if dotu { 426 | sz += 2 + 4 + 4 + 4 + len(d.Ext) 427 | } 428 | 429 | return sz 430 | } 431 | 432 | func pstat(d *Dir, buf []byte, dotu bool) []byte { 433 | sz := statsz(d, dotu) 434 | buf = pint16(uint16(sz-2), buf) 435 | buf = pint16(d.Type, buf) 436 | buf = pint32(d.Dev, buf) 437 | buf = pqid(&d.Qid, buf) 438 | buf = pint32(d.Mode, buf) 439 | buf = pint32(d.Atime, buf) 440 | buf = pint32(d.Mtime, buf) 441 | buf = pint64(d.Length, buf) 442 | buf = pstr(d.Name, buf) 443 | buf = pstr(d.Uid, buf) 444 | buf = pstr(d.Gid, buf) 445 | buf = pstr(d.Muid, buf) 446 | if dotu { 447 | buf = pstr(d.Ext, buf) 448 | buf = pint32(d.Uidnum, buf) 449 | buf = pint32(d.Gidnum, buf) 450 | buf = pint32(d.Muidnum, buf) 451 | } 452 | 453 | return buf 454 | } 455 | 456 | // Converts a Dir value to its on-the-wire representation and writes it to 457 | // the buf. Returns the number of bytes written, 0 if there is not enough space. 458 | func PackDir(d *Dir, dotu bool) []byte { 459 | sz := statsz(d, dotu) 460 | buf := make([]byte, sz) 461 | pstat(d, buf, dotu) 462 | return buf 463 | } 464 | 465 | // Converts the on-the-wire representation of a stat to Stat value. 466 | // Returns an error if the conversion is impossible, otherwise 467 | // a pointer to a Stat value. 468 | func UnpackDir(buf []byte, dotu bool) (d *Dir, b []byte, amt int, err error) { 469 | sz := 2 + 2 + 4 + 13 + 4 + /* size[2] type[2] dev[4] qid[13] mode[4] */ 470 | 4 + 4 + 8 + /* atime[4] mtime[4] length[8] */ 471 | 2 + 2 + 2 + 2 /* name[s] uid[s] gid[s] muid[s] */ 472 | 473 | if dotu { 474 | sz += 2 + 4 + 4 + 4 /* extension[s] n_uid[4] n_gid[4] n_muid[4] */ 475 | } 476 | 477 | if len(buf) < sz { 478 | s := fmt.Sprintf("short buffer: Need %d and have %v", sz, len(buf)) 479 | return nil, nil, 0, &Error{s, EINVAL} 480 | } 481 | 482 | d = new(Dir) 483 | b, err = gstat(buf, d, dotu) 484 | if err != nil { 485 | return nil, nil, 0, err 486 | } 487 | 488 | return d, b, len(buf) - len(b), nil 489 | 490 | } 491 | 492 | // Allocates a new Fcall. 493 | func NewFcall(sz uint32) *Fcall { 494 | fc := new(Fcall) 495 | fc.Buf = make([]byte, sz) 496 | 497 | return fc 498 | } 499 | 500 | // Sets the tag of a Fcall. 501 | func SetTag(fc *Fcall, tag uint16) { 502 | fc.Tag = tag 503 | pint16(tag, fc.Pkt[5:]) 504 | } 505 | 506 | func packCommon(fc *Fcall, size int, id uint8) ([]byte, error) { 507 | size += 4 + 1 + 2 /* size[4] id[1] tag[2] */ 508 | if len(fc.Buf) < int(size) { 509 | return nil, &Error{"buffer too small", EINVAL} 510 | } 511 | 512 | fc.Size = uint32(size) 513 | fc.Type = id 514 | fc.Tag = NOTAG 515 | p := fc.Buf 516 | p = pint32(uint32(size), p) 517 | p = pint8(id, p) 518 | p = pint16(NOTAG, p) 519 | fc.Pkt = fc.Buf[0:size] 520 | 521 | return p, nil 522 | } 523 | 524 | func (err *Error) Error() string { 525 | if err != nil { 526 | return err.Err 527 | } 528 | 529 | return "" 530 | } 531 | -------------------------------------------------------------------------------- /packr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | import "fmt" 8 | 9 | // Create a Rversion message in the specified Fcall. 10 | func PackRversion(fc *Fcall, msize uint32, version string) error { 11 | size := 4 + 2 + len(version) /* msize[4] version[s] */ 12 | p, err := packCommon(fc, size, Rversion) 13 | if err != nil { 14 | return err 15 | } 16 | 17 | fc.Msize = msize 18 | fc.Version = version 19 | p = pint32(msize, p) 20 | p = pstr(version, p) 21 | 22 | return nil 23 | } 24 | 25 | // Create a Rauth message in the specified Fcall. 26 | func PackRauth(fc *Fcall, aqid *Qid) error { 27 | size := 13 /* aqid[13] */ 28 | p, err := packCommon(fc, size, Rauth) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | fc.Qid = *aqid 34 | p = pqid(aqid, p) 35 | return nil 36 | } 37 | 38 | // Create a Rerror message in the specified Fcall. If dotu is true, 39 | // the function will create a 9P2000.u message. If false, errornum is 40 | // ignored. 41 | // The Akaros global is hurl-inducing but this whole blob of code 42 | // needs a redo. dotu is even worse, since it bakes in ONE PARTICULAR 43 | // EXTENSION ... 44 | 45 | func PackRerror(fc *Fcall, error string, errornum uint32, dotu bool) error { 46 | if *Akaros { 47 | error = fmt.Sprintf("%04X %v", errornum, error) 48 | } 49 | 50 | size := 2 + len(error) /* ename[s] */ 51 | if dotu { 52 | size += 4 /* ecode[4] */ 53 | } 54 | 55 | p, err := packCommon(fc, size, Rerror) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | fc.Error = error 61 | p = pstr(error, p) 62 | if dotu { 63 | fc.Errornum = errornum 64 | p = pint32(errornum, p) 65 | } 66 | 67 | return nil 68 | } 69 | 70 | // Create a Rflush message in the specified Fcall. 71 | func PackRflush(fc *Fcall) error { 72 | _, err := packCommon(fc, 0, Rflush) 73 | 74 | return err 75 | } 76 | 77 | // Create a Rattach message in the specified Fcall. 78 | func PackRattach(fc *Fcall, aqid *Qid) error { 79 | size := 13 /* aqid[13] */ 80 | p, err := packCommon(fc, size, Rattach) 81 | if err != nil { 82 | return err 83 | } 84 | 85 | fc.Qid = *aqid 86 | p = pqid(aqid, p) 87 | return nil 88 | } 89 | 90 | // Create a Rwalk message in the specified Fcall. 91 | func PackRwalk(fc *Fcall, wqids []Qid) error { 92 | nwqid := len(wqids) 93 | size := 2 + nwqid*13 /* nwqid[2] nwname*wqid[13] */ 94 | p, err := packCommon(fc, size, Rwalk) 95 | if err != nil { 96 | return err 97 | } 98 | 99 | p = pint16(uint16(nwqid), p) 100 | fc.Wqid = make([]Qid, nwqid) 101 | for i := 0; i < nwqid; i++ { 102 | fc.Wqid[i] = wqids[i] 103 | p = pqid(&wqids[i], p) 104 | } 105 | 106 | return nil 107 | } 108 | 109 | // Create a Ropen message in the specified Fcall. 110 | func PackRopen(fc *Fcall, qid *Qid, iounit uint32) error { 111 | size := 13 + 4 /* qid[13] iounit[4] */ 112 | p, err := packCommon(fc, size, Ropen) 113 | if err != nil { 114 | return err 115 | } 116 | 117 | fc.Qid = *qid 118 | fc.Iounit = iounit 119 | p = pqid(qid, p) 120 | p = pint32(iounit, p) 121 | return nil 122 | } 123 | 124 | // Create a Rcreate message in the specified Fcall. 125 | func PackRcreate(fc *Fcall, qid *Qid, iounit uint32) error { 126 | size := 13 + 4 /* qid[13] iounit[4] */ 127 | p, err := packCommon(fc, size, Rcreate) 128 | if err != nil { 129 | return err 130 | } 131 | 132 | fc.Qid = *qid 133 | fc.Iounit = iounit 134 | p = pqid(qid, p) 135 | p = pint32(iounit, p) 136 | return nil 137 | } 138 | 139 | // Initializes the specified Fcall value to contain Rread message. 140 | // The user should copy the returned data to the slice pointed by 141 | // fc.Data and call SetRreadCount to update the data size to the 142 | // actual value. 143 | func InitRread(fc *Fcall, count uint32) error { 144 | size := int(4 + count) /* count[4] data[count] */ 145 | p, err := packCommon(fc, size, Rread) 146 | if err != nil { 147 | return err 148 | } 149 | 150 | fc.Count = count 151 | fc.Data = p[4 : fc.Count+4] 152 | p = pint32(count, p) 153 | return nil 154 | } 155 | 156 | // Updates the size of the data returned by Rread. Expects that 157 | // the Fcall value is already initialized by InitRread. 158 | func SetRreadCount(fc *Fcall, count uint32) { 159 | /* we need to update both the packet size as well as the data count */ 160 | size := 4 + 1 + 2 + 4 + count /* size[4] id[1] tag[2] count[4] data[count] */ 161 | pint32(size, fc.Pkt) 162 | pint32(count, fc.Pkt[7:]) 163 | fc.Size = size 164 | fc.Count = count 165 | fc.Pkt = fc.Pkt[0:size] 166 | fc.Data = fc.Data[0:count] 167 | fc.Size = size 168 | } 169 | 170 | // Create a Rread message in the specified Fcall. 171 | func PackRread(fc *Fcall, data []byte) error { 172 | count := uint32(len(data)) 173 | err := InitRread(fc, count) 174 | if err != nil { 175 | return err 176 | } 177 | 178 | copy(fc.Data, data) 179 | return nil 180 | } 181 | 182 | // Create a Rwrite message in the specified Fcall. 183 | func PackRwrite(fc *Fcall, count uint32) error { 184 | p, err := packCommon(fc, 4, Rwrite) /* count[4] */ 185 | if err != nil { 186 | return err 187 | } 188 | 189 | fc.Count = count 190 | 191 | p = pint32(count, p) 192 | return nil 193 | } 194 | 195 | // Create a Rclunk message in the specified Fcall. 196 | func PackRclunk(fc *Fcall) error { 197 | _, err := packCommon(fc, 0, Rclunk) 198 | return err 199 | } 200 | 201 | // Create a Rremove message in the specified Fcall. 202 | func PackRremove(fc *Fcall) error { 203 | _, err := packCommon(fc, 0, Rremove) 204 | return err 205 | } 206 | 207 | // Create a Rstat message in the specified Fcall. If dotu is true, the 208 | // function will create a 9P2000.u stat representation that includes 209 | // st.Nuid, st.Ngid, st.Nmuid and st.Ext. Otherwise these values will be 210 | // ignored. 211 | func PackRstat(fc *Fcall, d *Dir, dotu bool) error { 212 | stsz := statsz(d, dotu) 213 | size := 2 + stsz /* stat[n] */ 214 | p, err := packCommon(fc, size, Rstat) 215 | if err != nil { 216 | return err 217 | } 218 | 219 | p = pint16(uint16(stsz), p) 220 | p = pstat(d, p, dotu) 221 | fc.Dir = *d 222 | return nil 223 | } 224 | 225 | // Create a Rwstat message in the specified Fcall. 226 | func PackRwstat(fc *Fcall) error { 227 | _, err := packCommon(fc, 0, Rwstat) 228 | return err 229 | } 230 | -------------------------------------------------------------------------------- /packt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | // Create a Tversion message in the specified Fcall. 8 | func PackTversion(fc *Fcall, msize uint32, version string) error { 9 | size := 4 + 2 + len(version) /* msize[4] version[s] */ 10 | p, err := packCommon(fc, size, Tversion) 11 | if err != nil { 12 | return err 13 | } 14 | 15 | fc.Msize = msize 16 | fc.Version = version 17 | p = pint32(msize, p) 18 | p = pstr(version, p) 19 | 20 | return nil 21 | } 22 | 23 | // Create a Tauth message in the specified Fcall. 24 | func PackTauth(fc *Fcall, fid uint32, uname string, aname string, unamenum uint32, dotu bool) error { 25 | size := 4 + 2 + 2 + len(uname) + len(aname) /* fid[4] uname[s] aname[s] */ 26 | if dotu { 27 | size += 4 /* n_uname[4] */ 28 | } 29 | 30 | p, err := packCommon(fc, size, Tauth) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | fc.Fid = fid 36 | fc.Uname = uname 37 | fc.Aname = aname 38 | p = pint32(fid, p) 39 | p = pstr(uname, p) 40 | p = pstr(aname, p) 41 | if dotu { 42 | fc.Unamenum = unamenum 43 | p = pint32(unamenum, p) 44 | } 45 | 46 | return nil 47 | } 48 | 49 | // Create a Tflush message in the specified Fcall. 50 | func PackTflush(fc *Fcall, oldtag uint16) error { 51 | p, err := packCommon(fc, 2, Tflush) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | fc.Oldtag = oldtag 57 | p = pint16(oldtag, p) 58 | return nil 59 | } 60 | 61 | // Create a Tattach message in the specified Fcall. If dotu is true, 62 | // the function will create 9P2000.u including the nuname value, otherwise 63 | // nuname is ignored. 64 | func PackTattach(fc *Fcall, fid uint32, afid uint32, uname string, aname string, unamenum uint32, dotu bool) error { 65 | size := 4 + 4 + 2 + len(uname) + 2 + len(aname) /* fid[4] afid[4] uname[s] aname[s] */ 66 | if dotu { 67 | size += 4 68 | } 69 | 70 | p, err := packCommon(fc, size, Tattach) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | fc.Fid = fid 76 | fc.Afid = afid 77 | fc.Uname = uname 78 | fc.Aname = aname 79 | p = pint32(fid, p) 80 | p = pint32(afid, p) 81 | p = pstr(uname, p) 82 | p = pstr(aname, p) 83 | if dotu { 84 | fc.Unamenum = unamenum 85 | p = pint32(unamenum, p) 86 | } 87 | 88 | return nil 89 | } 90 | 91 | // Create a Twalk message in the specified Fcall. 92 | func PackTwalk(fc *Fcall, fid uint32, newfid uint32, wnames []string) error { 93 | nwname := len(wnames) 94 | size := 4 + 4 + 2 + nwname*2 /* fid[4] newfid[4] nwname[2] nwname*wname[s] */ 95 | for i := 0; i < nwname; i++ { 96 | size += len(wnames[i]) 97 | } 98 | 99 | p, err := packCommon(fc, size, Twalk) 100 | if err != nil { 101 | return err 102 | } 103 | 104 | fc.Fid = fid 105 | fc.Newfid = newfid 106 | p = pint32(fid, p) 107 | p = pint32(newfid, p) 108 | p = pint16(uint16(nwname), p) 109 | fc.Wname = make([]string, nwname) 110 | for i := 0; i < nwname; i++ { 111 | fc.Wname[i] = wnames[i] 112 | p = pstr(wnames[i], p) 113 | } 114 | 115 | return nil 116 | } 117 | 118 | // Create a Topen message in the specified Fcall. 119 | func PackTopen(fc *Fcall, fid uint32, mode uint8) error { 120 | size := 4 + 1 /* fid[4] mode[1] */ 121 | p, err := packCommon(fc, size, Topen) 122 | if err != nil { 123 | return err 124 | } 125 | 126 | fc.Fid = fid 127 | fc.Mode = mode 128 | p = pint32(fid, p) 129 | p = pint8(mode, p) 130 | return nil 131 | } 132 | 133 | // Create a Tcreate message in the specified Fcall. If dotu is true, 134 | // the function will create a 9P2000.u message that includes ext. 135 | // Otherwise the ext value is ignored. 136 | func PackTcreate(fc *Fcall, fid uint32, name string, perm uint32, mode uint8, ext string, dotu bool) error { 137 | size := 4 + 2 + len(name) + 4 + 1 /* fid[4] name[s] perm[4] mode[1] */ 138 | 139 | if dotu { 140 | size += 2 + len(ext) 141 | } 142 | 143 | p, err := packCommon(fc, size, Tcreate) 144 | if err != nil { 145 | return err 146 | } 147 | 148 | fc.Fid = fid 149 | fc.Name = name 150 | fc.Perm = perm 151 | fc.Mode = mode 152 | p = pint32(fid, p) 153 | p = pstr(name, p) 154 | p = pint32(perm, p) 155 | p = pint8(mode, p) 156 | 157 | if dotu { 158 | fc.Ext = ext 159 | p = pstr(ext, p) 160 | } 161 | 162 | return nil 163 | } 164 | 165 | // Create a Tread message in the specified Fcall. 166 | func PackTread(fc *Fcall, fid uint32, offset uint64, count uint32) error { 167 | size := 4 + 8 + 4 /* fid[4] offset[8] count[4] */ 168 | p, err := packCommon(fc, size, Tread) 169 | if err != nil { 170 | return err 171 | } 172 | 173 | fc.Fid = fid 174 | fc.Offset = offset 175 | fc.Count = count 176 | p = pint32(fid, p) 177 | p = pint64(offset, p) 178 | p = pint32(count, p) 179 | return nil 180 | } 181 | 182 | // Create a Twrite message in the specified Fcall. 183 | func PackTwrite(fc *Fcall, fid uint32, offset uint64, count uint32, data []byte) error { 184 | c := len(data) 185 | size := 4 + 8 + 4 + c /* fid[4] offset[8] count[4] data[count] */ 186 | p, err := packCommon(fc, size, Twrite) 187 | if err != nil { 188 | return err 189 | } 190 | 191 | fc.Fid = fid 192 | fc.Offset = offset 193 | fc.Count = count 194 | p = pint32(fid, p) 195 | p = pint64(offset, p) 196 | p = pint32(count, p) 197 | fc.Data = p 198 | copy(fc.Data, data) 199 | return nil 200 | } 201 | 202 | // Create a Tclunk message in the specified Fcall. 203 | func PackTclunk(fc *Fcall, fid uint32) error { 204 | p, err := packCommon(fc, 4, Tclunk) /* fid[4] */ 205 | if err != nil { 206 | return err 207 | } 208 | 209 | fc.Fid = fid 210 | p = pint32(fid, p) 211 | return nil 212 | } 213 | 214 | // Create a Tremove message in the specified Fcall. 215 | func PackTremove(fc *Fcall, fid uint32) error { 216 | p, err := packCommon(fc, 4, Tremove) /* fid[4] */ 217 | if err != nil { 218 | return err 219 | } 220 | 221 | fc.Fid = fid 222 | p = pint32(fid, p) 223 | return nil 224 | } 225 | 226 | // Create a Tstat message in the specified Fcall. 227 | func PackTstat(fc *Fcall, fid uint32) error { 228 | p, err := packCommon(fc, 4, Tstat) /* fid[4] */ 229 | if err != nil { 230 | return err 231 | } 232 | 233 | fc.Fid = fid 234 | p = pint32(fid, p) 235 | return nil 236 | } 237 | 238 | // Create a Twstat message in the specified Fcall. If dotu is true 239 | // the function will create 9P2000.u message, otherwise the 9P2000.u 240 | // specific fields from the Stat value will be ignored. 241 | func PackTwstat(fc *Fcall, fid uint32, d *Dir, dotu bool) error { 242 | stsz := statsz(d, dotu) 243 | size := 4 + 2 + stsz /* fid[4] stat[n] */ 244 | p, err := packCommon(fc, size, Twstat) 245 | if err != nil { 246 | return err 247 | } 248 | 249 | fc.Fid = fid 250 | fc.Dir = *d 251 | p = pint32(fid, p) 252 | p = pint16(uint16(stsz), p) 253 | p = pstat(d, p, dotu) 254 | return nil 255 | } 256 | -------------------------------------------------------------------------------- /srv_conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "net" 11 | ) 12 | 13 | func (srv *Srv) NewConn(c net.Conn) { 14 | conn := new(Conn) 15 | conn.Srv = srv 16 | conn.Msize = srv.Msize 17 | conn.Dotu = srv.Dotu 18 | conn.Debuglevel = srv.Debuglevel 19 | conn.conn = c 20 | conn.fidpool = make(map[uint32]*SrvFid) 21 | conn.reqs = make(map[uint16]*SrvReq) 22 | conn.reqout = make(chan *SrvReq, srv.Maxpend) 23 | conn.done = make(chan bool) 24 | conn.rchan = make(chan *Fcall, 64) 25 | 26 | srv.Lock() 27 | if srv.conns == nil { 28 | srv.conns = make(map[*Conn]*Conn) 29 | } 30 | srv.conns[conn] = conn 31 | srv.Unlock() 32 | 33 | conn.Id = c.RemoteAddr().String() 34 | if op, ok := (conn.Srv.ops).(ConnOps); ok { 35 | op.ConnOpened(conn) 36 | } 37 | 38 | if sop, ok := (interface{}(conn)).(StatsOps); ok { 39 | sop.statsRegister() 40 | } 41 | 42 | go conn.recv() 43 | go conn.send() 44 | } 45 | 46 | func (conn *Conn) close() { 47 | conn.done <- true 48 | conn.Srv.Lock() 49 | delete(conn.Srv.conns, conn) 50 | conn.Srv.Unlock() 51 | 52 | if sop, ok := (interface{}(conn)).(StatsOps); ok { 53 | sop.statsUnregister() 54 | } 55 | if op, ok := (conn.Srv.ops).(ConnOps); ok { 56 | op.ConnClosed(conn) 57 | } 58 | 59 | /* call FidDestroy for all remaining fids */ 60 | if op, ok := (conn.Srv.ops).(SrvFidOps); ok { 61 | for _, fid := range conn.fidpool { 62 | op.FidDestroy(fid) 63 | } 64 | } 65 | } 66 | 67 | func (conn *Conn) recv() { 68 | var err error 69 | var n int 70 | 71 | buf := make([]byte, conn.Msize*8) 72 | pos := 0 73 | for { 74 | if len(buf) < int(conn.Msize) { 75 | b := make([]byte, conn.Msize*8) 76 | copy(b, buf[0:pos]) 77 | buf = b 78 | b = nil 79 | } 80 | 81 | n, err = conn.conn.Read(buf[pos:]) 82 | if err != nil || n == 0 { 83 | conn.close() 84 | return 85 | } 86 | 87 | pos += n 88 | for pos > 4 { 89 | sz, _ := Gint32(buf) 90 | if sz > conn.Msize { 91 | log.Println("bad client connection: ", conn.conn.RemoteAddr()) 92 | conn.conn.Close() 93 | conn.close() 94 | return 95 | } 96 | if pos < int(sz) { 97 | if len(buf) < int(sz) { 98 | b := make([]byte, conn.Msize*8) 99 | copy(b, buf[0:pos]) 100 | buf = b 101 | b = nil 102 | } 103 | 104 | break 105 | } 106 | fc, err, fcsize := Unpack(buf, conn.Dotu) 107 | if err != nil { 108 | log.Println(fmt.Sprintf("invalid packet : %v %v", err, buf)) 109 | conn.conn.Close() 110 | conn.close() 111 | return 112 | } 113 | 114 | tag := fc.Tag 115 | req := new(SrvReq) 116 | select { 117 | case req.Rc = <-conn.rchan: 118 | break 119 | default: 120 | req.Rc = NewFcall(conn.Msize) 121 | } 122 | 123 | req.Conn = conn 124 | req.Tc = fc 125 | // req.Rc = rc 126 | if conn.Debuglevel > 0 { 127 | conn.logFcall(req.Tc) 128 | if conn.Debuglevel&DbgPrintPackets != 0 { 129 | log.Println(">->", conn.Id, fmt.Sprint(req.Tc.Pkt)) 130 | } 131 | 132 | if conn.Debuglevel&DbgPrintFcalls != 0 { 133 | log.Println(">>>", conn.Id, req.Tc.String()) 134 | } 135 | } 136 | 137 | conn.Lock() 138 | conn.nreqs++ 139 | conn.tsz += uint64(fc.Size) 140 | conn.npend++ 141 | if conn.npend > conn.maxpend { 142 | conn.maxpend = conn.npend 143 | } 144 | 145 | req.next = conn.reqs[tag] 146 | conn.reqs[tag] = req 147 | process := req.next == nil 148 | if req.next != nil { 149 | req.next.prev = req 150 | } 151 | conn.Unlock() 152 | if process { 153 | // Tversion may change some attributes of the 154 | // connection, so we block on it. Otherwise, 155 | // we may loop back to reading and that is a race. 156 | // This fix brought to you by the race detector. 157 | if req.Tc.Type == Tversion { 158 | req.process() 159 | } else { 160 | go req.process() 161 | } 162 | } 163 | 164 | buf = buf[fcsize:] 165 | pos -= fcsize 166 | } 167 | } 168 | 169 | } 170 | 171 | func (conn *Conn) send() { 172 | for { 173 | select { 174 | case <-conn.done: 175 | return 176 | 177 | case req := <-conn.reqout: 178 | SetTag(req.Rc, req.Tc.Tag) 179 | conn.Lock() 180 | conn.rsz += uint64(req.Rc.Size) 181 | conn.npend-- 182 | conn.Unlock() 183 | if conn.Debuglevel > 0 { 184 | conn.logFcall(req.Rc) 185 | if conn.Debuglevel&DbgPrintPackets != 0 { 186 | log.Println("<-<", conn.Id, fmt.Sprint(req.Rc.Pkt)) 187 | } 188 | 189 | if conn.Debuglevel&DbgPrintFcalls != 0 { 190 | log.Println("<<<", conn.Id, req.Rc.String()) 191 | } 192 | } 193 | 194 | for buf := req.Rc.Pkt; len(buf) > 0; { 195 | n, err := conn.conn.Write(buf) 196 | if err != nil { 197 | /* just close the socket, will get signal on conn.done */ 198 | log.Println("error while writing") 199 | conn.conn.Close() 200 | break 201 | } 202 | 203 | buf = buf[n:] 204 | } 205 | 206 | select { 207 | case conn.rchan <- req.Rc: 208 | break 209 | default: 210 | } 211 | } 212 | } 213 | 214 | panic("unreached") 215 | } 216 | 217 | func (conn *Conn) RemoteAddr() net.Addr { 218 | return conn.conn.RemoteAddr() 219 | } 220 | 221 | func (conn *Conn) LocalAddr() net.Addr { 222 | return conn.conn.LocalAddr() 223 | } 224 | 225 | func (conn *Conn) logFcall(fc *Fcall) { 226 | if conn.Debuglevel&DbgLogPackets != 0 { 227 | pkt := make([]byte, len(fc.Pkt)) 228 | copy(pkt, fc.Pkt) 229 | conn.Srv.Log.Log(pkt, conn, DbgLogPackets) 230 | } 231 | 232 | if conn.Debuglevel&DbgLogFcalls != 0 { 233 | f := new(Fcall) 234 | *f = *fc 235 | f.Pkt = nil 236 | conn.Srv.Log.Log(f, conn, DbgLogFcalls) 237 | } 238 | } 239 | 240 | func (srv *Srv) StartNetListener(ntype, addr string) error { 241 | l, err := net.Listen(ntype, addr) 242 | if err != nil { 243 | return &Error{err.Error(), EIO} 244 | } 245 | 246 | return srv.StartListener(l) 247 | } 248 | 249 | // Start listening on the specified network and address for incoming 250 | // connections. Once a connection is established, create a new Conn 251 | // value, read messages from the socket, send them to the specified 252 | // server, and send back responses received from the server. 253 | func (srv *Srv) StartListener(l net.Listener) error { 254 | for { 255 | c, err := l.Accept() 256 | if err != nil { 257 | return &Error{err.Error(), EIO} 258 | } 259 | 260 | srv.NewConn(c) 261 | } 262 | return nil 263 | } 264 | -------------------------------------------------------------------------------- /srv_fcall.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | func (srv *Srv) version(req *SrvReq) { 8 | tc := req.Tc 9 | conn := req.Conn 10 | 11 | if tc.Msize < IOHDRSZ { 12 | req.RespondError(&Error{"msize too small", EINVAL}) 13 | return 14 | } 15 | 16 | if tc.Msize < conn.Msize { 17 | conn.Msize = tc.Msize 18 | } 19 | 20 | conn.Dotu = tc.Version == "9P2000.u" && srv.Dotu 21 | ver := "9P2000" 22 | if conn.Dotu { 23 | ver = "9P2000.u" 24 | } 25 | 26 | /* make sure that the responses of all current requests will be ignored */ 27 | conn.Lock() 28 | for tag, r := range conn.reqs { 29 | if tag == NOTAG { 30 | continue 31 | } 32 | 33 | for rr := r; rr != nil; rr = rr.next { 34 | rr.Lock() 35 | rr.status |= reqFlush 36 | rr.Unlock() 37 | } 38 | } 39 | conn.Unlock() 40 | 41 | req.RespondRversion(conn.Msize, ver) 42 | } 43 | 44 | func (srv *Srv) auth(req *SrvReq) { 45 | tc := req.Tc 46 | conn := req.Conn 47 | if tc.Afid == NOFID { 48 | req.RespondError(Eunknownfid) 49 | return 50 | } 51 | 52 | req.Afid = conn.FidNew(tc.Afid) 53 | if req.Afid == nil { 54 | req.RespondError(Einuse) 55 | return 56 | } 57 | 58 | var user User = nil 59 | if tc.Unamenum != NOUID || conn.Dotu { 60 | user = srv.Upool.Uid2User(int(tc.Unamenum)) 61 | } else if tc.Uname != "" { 62 | user = srv.Upool.Uname2User(tc.Uname) 63 | } 64 | 65 | if user == nil { 66 | req.RespondError(Enouser) 67 | return 68 | } 69 | 70 | req.Afid.User = user 71 | req.Afid.Type = QTAUTH 72 | if aop, ok := (srv.ops).(AuthOps); ok { 73 | aqid, err := aop.AuthInit(req.Afid, tc.Aname) 74 | if err != nil { 75 | req.RespondError(err) 76 | } else { 77 | aqid.Type |= QTAUTH // just in case 78 | req.RespondRauth(aqid) 79 | } 80 | } else { 81 | req.RespondError(Enoauth) 82 | } 83 | 84 | } 85 | 86 | func (srv *Srv) authPost(req *SrvReq) { 87 | if req.Rc != nil && req.Rc.Type == Rauth { 88 | req.Afid.IncRef() 89 | } 90 | } 91 | 92 | func (srv *Srv) attach(req *SrvReq) { 93 | tc := req.Tc 94 | conn := req.Conn 95 | if tc.Fid == NOFID { 96 | req.RespondError(Eunknownfid) 97 | return 98 | } 99 | 100 | req.Fid = conn.FidNew(tc.Fid) 101 | if req.Fid == nil { 102 | req.RespondError(Einuse) 103 | return 104 | } 105 | 106 | if tc.Afid != NOFID { 107 | req.Afid = conn.FidGet(tc.Afid) 108 | if req.Afid == nil { 109 | req.RespondError(Eunknownfid) 110 | } 111 | } 112 | 113 | var user User = nil 114 | if tc.Unamenum != NOUID || conn.Dotu { 115 | user = srv.Upool.Uid2User(int(tc.Unamenum)) 116 | } else if tc.Uname != "" { 117 | user = srv.Upool.Uname2User(tc.Uname) 118 | } 119 | 120 | if user == nil { 121 | req.RespondError(Enouser) 122 | return 123 | } 124 | 125 | req.Fid.User = user 126 | if aop, ok := (srv.ops).(AuthOps); ok { 127 | err := aop.AuthCheck(req.Fid, req.Afid, tc.Aname) 128 | if err != nil { 129 | req.RespondError(err) 130 | return 131 | } 132 | } 133 | 134 | (srv.ops).(SrvReqOps).Attach(req) 135 | } 136 | 137 | func (srv *Srv) attachPost(req *SrvReq) { 138 | if req.Rc != nil && req.Rc.Type == Rattach { 139 | req.Fid.Type = req.Rc.Qid.Type 140 | req.Fid.IncRef() 141 | } 142 | } 143 | 144 | func (srv *Srv) flush(req *SrvReq) { 145 | conn := req.Conn 146 | tag := req.Tc.Oldtag 147 | PackRflush(req.Rc) 148 | conn.Lock() 149 | r := conn.reqs[tag] 150 | if r != nil { 151 | req.flushreq = r.flushreq 152 | r.flushreq = req 153 | } 154 | conn.Unlock() 155 | 156 | if r == nil { 157 | // there are no requests with that tag 158 | req.Respond() 159 | return 160 | } 161 | 162 | r.Lock() 163 | status := r.status 164 | if (status & (reqWork | reqSaved)) == 0 { 165 | /* the request is not worked on yet */ 166 | r.status |= reqFlush 167 | } 168 | r.Unlock() 169 | 170 | if (status & (reqWork | reqSaved)) == 0 { 171 | r.Respond() 172 | } else { 173 | if op, ok := (srv.ops).(FlushOp); ok { 174 | op.Flush(r) 175 | } 176 | } 177 | } 178 | 179 | func (srv *Srv) walk(req *SrvReq) { 180 | conn := req.Conn 181 | tc := req.Tc 182 | fid := req.Fid 183 | 184 | /* we can't walk regular files, only clone them */ 185 | if len(tc.Wname) > 0 && (fid.Type&QTDIR) == 0 { 186 | req.RespondError(Enotdir) 187 | return 188 | } 189 | 190 | /* we can't walk open files */ 191 | if fid.opened { 192 | req.RespondError(Ebaduse) 193 | return 194 | } 195 | 196 | if tc.Fid != tc.Newfid { 197 | req.Newfid = conn.FidNew(tc.Newfid) 198 | if req.Newfid == nil { 199 | req.RespondError(Einuse) 200 | return 201 | } 202 | 203 | req.Newfid.User = fid.User 204 | req.Newfid.Type = fid.Type 205 | } else { 206 | req.Newfid = req.Fid 207 | req.Newfid.IncRef() 208 | } 209 | 210 | (req.Conn.Srv.ops).(SrvReqOps).Walk(req) 211 | } 212 | 213 | func (srv *Srv) walkPost(req *SrvReq) { 214 | rc := req.Rc 215 | if rc == nil || rc.Type != Rwalk || req.Newfid == nil { 216 | return 217 | } 218 | 219 | n := len(rc.Wqid) 220 | if n > 0 { 221 | req.Newfid.Type = rc.Wqid[n-1].Type 222 | } else { 223 | req.Newfid.Type = req.Fid.Type 224 | } 225 | 226 | // Don't retain the fid if only a partial walk succeeded 227 | if n != len(req.Tc.Wname) { 228 | return 229 | } 230 | 231 | if req.Newfid.fid != req.Fid.fid { 232 | req.Newfid.IncRef() 233 | } 234 | } 235 | 236 | func (srv *Srv) open(req *SrvReq) { 237 | fid := req.Fid 238 | tc := req.Tc 239 | if fid.opened { 240 | req.RespondError(Eopen) 241 | return 242 | } 243 | 244 | if (fid.Type&QTDIR) != 0 && tc.Mode != OREAD { 245 | req.RespondError(Eperm) 246 | return 247 | } 248 | 249 | fid.Omode = tc.Mode 250 | (req.Conn.Srv.ops).(SrvReqOps).Open(req) 251 | } 252 | 253 | func (srv *Srv) openPost(req *SrvReq) { 254 | if req.Fid != nil { 255 | req.Fid.opened = req.Rc != nil && req.Rc.Type == Ropen 256 | } 257 | } 258 | 259 | func (srv *Srv) create(req *SrvReq) { 260 | fid := req.Fid 261 | tc := req.Tc 262 | if fid.opened { 263 | req.RespondError(Eopen) 264 | return 265 | } 266 | 267 | if (fid.Type & QTDIR) == 0 { 268 | req.RespondError(Enotdir) 269 | return 270 | } 271 | 272 | /* can't open directories for other than reading */ 273 | if (tc.Perm&DMDIR) != 0 && tc.Mode != OREAD { 274 | req.RespondError(Eperm) 275 | return 276 | } 277 | 278 | /* can't create special files if not 9P2000.u */ 279 | if (tc.Perm&(DMNAMEDPIPE|DMSYMLINK|DMLINK|DMDEVICE|DMSOCKET)) != 0 && !req.Conn.Dotu { 280 | req.RespondError(Eperm) 281 | return 282 | } 283 | 284 | fid.Omode = tc.Mode 285 | (req.Conn.Srv.ops).(SrvReqOps).Create(req) 286 | } 287 | 288 | func (srv *Srv) createPost(req *SrvReq) { 289 | if req.Rc != nil && req.Rc.Type == Rcreate && req.Fid != nil { 290 | req.Fid.Type = req.Rc.Qid.Type 291 | req.Fid.opened = true 292 | } 293 | } 294 | 295 | func (srv *Srv) read(req *SrvReq) { 296 | tc := req.Tc 297 | fid := req.Fid 298 | if tc.Count+IOHDRSZ > req.Conn.Msize { 299 | req.RespondError(Etoolarge) 300 | return 301 | } 302 | 303 | if (fid.Type & QTAUTH) != 0 { 304 | var n int 305 | 306 | rc := req.Rc 307 | err := InitRread(rc, tc.Count) 308 | if err != nil { 309 | req.RespondError(err) 310 | return 311 | } 312 | 313 | if op, ok := (req.Conn.Srv.ops).(AuthOps); ok { 314 | n, err = op.AuthRead(fid, tc.Offset, rc.Data) 315 | if err != nil { 316 | req.RespondError(err) 317 | return 318 | } 319 | 320 | SetRreadCount(rc, uint32(n)) 321 | req.Respond() 322 | } else { 323 | req.RespondError(Enotimpl) 324 | } 325 | 326 | return 327 | } 328 | 329 | if (fid.Type & QTDIR) != 0 { 330 | if tc.Offset == 0 { 331 | fid.Diroffset = 0 332 | } else if tc.Offset != fid.Diroffset { 333 | fid.Diroffset = tc.Offset 334 | } 335 | } 336 | 337 | (req.Conn.Srv.ops).(SrvReqOps).Read(req) 338 | } 339 | 340 | func (srv *Srv) readPost(req *SrvReq) { 341 | if req.Rc != nil && req.Rc.Type == Rread && (req.Fid.Type&QTDIR) != 0 { 342 | req.Fid.Diroffset += uint64(req.Rc.Count) 343 | } 344 | } 345 | 346 | func (srv *Srv) write(req *SrvReq) { 347 | fid := req.Fid 348 | tc := req.Tc 349 | if (fid.Type & QTAUTH) != 0 { 350 | tc := req.Tc 351 | if op, ok := (req.Conn.Srv.ops).(AuthOps); ok { 352 | n, err := op.AuthWrite(req.Fid, tc.Offset, tc.Data) 353 | if err != nil { 354 | req.RespondError(err) 355 | } else { 356 | req.RespondRwrite(uint32(n)) 357 | } 358 | } else { 359 | req.RespondError(Enotimpl) 360 | } 361 | 362 | return 363 | } 364 | 365 | if !fid.opened || (fid.Type&QTDIR) != 0 || (fid.Omode&3) == OREAD { 366 | req.RespondError(Ebaduse) 367 | return 368 | } 369 | 370 | if tc.Count+IOHDRSZ > req.Conn.Msize { 371 | req.RespondError(Etoolarge) 372 | return 373 | } 374 | 375 | (req.Conn.Srv.ops).(SrvReqOps).Write(req) 376 | } 377 | 378 | func (srv *Srv) clunk(req *SrvReq) { 379 | fid := req.Fid 380 | if (fid.Type & QTAUTH) != 0 { 381 | if op, ok := (req.Conn.Srv.ops).(AuthOps); ok { 382 | op.AuthDestroy(fid) 383 | req.RespondRclunk() 384 | } else { 385 | req.RespondError(Enotimpl) 386 | } 387 | 388 | return 389 | } 390 | 391 | (req.Conn.Srv.ops).(SrvReqOps).Clunk(req) 392 | } 393 | 394 | func (srv *Srv) clunkPost(req *SrvReq) { 395 | if req.Rc != nil && req.Rc.Type == Rclunk && req.Fid != nil { 396 | req.Fid.DecRef() 397 | } 398 | } 399 | 400 | func (srv *Srv) remove(req *SrvReq) { (req.Conn.Srv.ops).(SrvReqOps).Remove(req) } 401 | 402 | func (srv *Srv) removePost(req *SrvReq) { 403 | if req.Rc != nil && req.Fid != nil { 404 | req.Fid.DecRef() 405 | } 406 | } 407 | 408 | func (srv *Srv) stat(req *SrvReq) { (req.Conn.Srv.ops).(SrvReqOps).Stat(req) } 409 | 410 | func (srv *Srv) wstat(req *SrvReq) { 411 | /* 412 | fid := req.Fid 413 | d := &req.Tc.Dir 414 | if d.Type != uint16(0xFFFF) || d.Dev != uint32(0xFFFFFFFF) || d.Version != uint32(0xFFFFFFFF) || 415 | d.Path != uint64(0xFFFFFFFFFFFFFFFF) { 416 | req.RespondError(Eperm) 417 | return 418 | } 419 | 420 | if (d.Mode != 0xFFFFFFFF) && (((fid.Type&QTDIR) != 0 && (d.Mode&DMDIR) == 0) || 421 | ((d.Type&QTDIR) == 0 && (d.Mode&DMDIR) != 0)) { 422 | req.RespondError(Edirchange) 423 | return 424 | } 425 | */ 426 | 427 | (req.Conn.Srv.ops).(SrvReqOps).Wstat(req) 428 | } 429 | -------------------------------------------------------------------------------- /srv_file.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | import ( 8 | "log" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | // The FStatOp interface provides a single operation (Stat) that will be 14 | // called before a file stat is sent back to the client. If implemented, 15 | // the operation should update the data in the srvFile struct. 16 | type FStatOp interface { 17 | Stat(fid *FFid) error 18 | } 19 | 20 | // The FWstatOp interface provides a single operation (Wstat) that will be 21 | // called when the client requests the srvFile metadata to be modified. If 22 | // implemented, the operation will be called when Twstat message is received. 23 | // If not implemented, "permission denied" error will be sent back. If the 24 | // operation returns an Error, the error is send back to the client. 25 | type FWstatOp interface { 26 | Wstat(*FFid, *Dir) error 27 | } 28 | 29 | // If the FReadOp interface is implemented, the Read operation will be called 30 | // to read from the file. If not implemented, "permission denied" error will 31 | // be send back. The operation returns the number of bytes read, or the 32 | // error occured while reading. 33 | type FReadOp interface { 34 | Read(fid *FFid, buf []byte, offset uint64) (int, error) 35 | } 36 | 37 | // If the FWriteOp interface is implemented, the Write operation will be called 38 | // to write to the file. If not implemented, "permission denied" error will 39 | // be send back. The operation returns the number of bytes written, or the 40 | // error occured while writing. 41 | type FWriteOp interface { 42 | Write(fid *FFid, data []byte, offset uint64) (int, error) 43 | } 44 | 45 | // If the FCreateOp interface is implemented, the Create operation will be called 46 | // when the client attempts to create a file in the srvFile implementing the interface. 47 | // If not implemented, "permission denied" error will be send back. If successful, 48 | // the operation should call (*File)Add() to add the created file to the directory. 49 | // The operation returns the created file, or the error occured while creating it. 50 | type FCreateOp interface { 51 | Create(fid *FFid, name string, perm uint32) (*srvFile, error) 52 | } 53 | 54 | // If the FRemoveOp interface is implemented, the Remove operation will be called 55 | // when the client attempts to create a file in the srvFile implementing the interface. 56 | // If not implemented, "permission denied" error will be send back. 57 | // The operation returns nil if successful, or the error that occured while removing 58 | // the file. 59 | type FRemoveOp interface { 60 | Remove(*FFid) error 61 | } 62 | 63 | type FOpenOp interface { 64 | Open(fid *FFid, mode uint8) error 65 | } 66 | 67 | type FClunkOp interface { 68 | Clunk(fid *FFid) error 69 | } 70 | 71 | type FDestroyOp interface { 72 | FidDestroy(fid *FFid) 73 | } 74 | 75 | type FFlags int 76 | 77 | const ( 78 | Fremoved FFlags = 1 << iota 79 | ) 80 | 81 | // The srvFile type represents a file (or directory) served by the file server. 82 | type srvFile struct { 83 | sync.Mutex 84 | Dir 85 | flags FFlags 86 | 87 | Parent *srvFile // parent 88 | next, prev *srvFile // siblings, guarded by parent.Lock 89 | cfirst, clast *srvFile // children (if directory) 90 | ops interface{} 91 | } 92 | 93 | type FFid struct { 94 | F *srvFile 95 | Fid *SrvFid 96 | dirs []*srvFile // used for readdir 97 | dirents []byte // serialized version of dirs 98 | } 99 | 100 | // The Fsrv can be used to create file servers that serve 101 | // simple trees of synthetic files. 102 | type Fsrv struct { 103 | Srv 104 | Root *srvFile 105 | } 106 | 107 | var lock sync.Mutex 108 | var qnext uint64 109 | var Eexist = &Error{"file already exists", EEXIST} 110 | var Enoent = &Error{"file not found", ENOENT} 111 | var Enotempty = &Error{"directory not empty", EPERM} 112 | 113 | // Creates a file server with root as root directory 114 | func NewsrvFileSrv(root *srvFile) *Fsrv { 115 | srv := new(Fsrv) 116 | srv.Root = root 117 | root.Parent = root // make sure we can .. in root 118 | 119 | return srv 120 | } 121 | 122 | // Initializes the fields of a file and add it to a directory. 123 | // Returns nil if successful, or an error. 124 | func (f *srvFile) Add(dir *srvFile, name string, uid User, gid Group, mode uint32, ops interface{}) error { 125 | 126 | lock.Lock() 127 | qpath := qnext 128 | qnext++ 129 | lock.Unlock() 130 | 131 | f.Qid.Type = uint8(mode >> 24) 132 | f.Qid.Version = 0 133 | f.Qid.Path = qpath 134 | f.Mode = mode 135 | f.Atime = uint32(time.Now().Unix()) 136 | f.Mtime = f.Atime 137 | f.Length = 0 138 | f.Name = name 139 | if uid != nil { 140 | f.Uid = uid.Name() 141 | f.Uidnum = uint32(uid.Id()) 142 | } else { 143 | f.Uid = "none" 144 | f.Uidnum = NOUID 145 | } 146 | 147 | if gid != nil { 148 | f.Gid = gid.Name() 149 | f.Gidnum = uint32(gid.Id()) 150 | } else { 151 | f.Gid = "none" 152 | f.Gidnum = NOUID 153 | } 154 | 155 | f.Muid = "" 156 | f.Muidnum = NOUID 157 | f.Ext = "" 158 | 159 | if dir != nil { 160 | f.Parent = dir 161 | dir.Lock() 162 | for p := dir.cfirst; p != nil; p = p.next { 163 | if name == p.Name { 164 | dir.Unlock() 165 | return Eexist 166 | } 167 | } 168 | 169 | if dir.clast != nil { 170 | dir.clast.next = f 171 | } else { 172 | dir.cfirst = f 173 | } 174 | 175 | f.prev = dir.clast 176 | f.next = nil 177 | dir.clast = f 178 | dir.Unlock() 179 | } else { 180 | f.Parent = f 181 | } 182 | 183 | f.ops = ops 184 | return nil 185 | } 186 | 187 | // Removes a file from its parent directory. 188 | func (f *srvFile) Remove() { 189 | f.Lock() 190 | if (f.flags & Fremoved) != 0 { 191 | f.Unlock() 192 | return 193 | } 194 | 195 | f.flags |= Fremoved 196 | f.Unlock() 197 | 198 | p := f.Parent 199 | p.Lock() 200 | if f.next != nil { 201 | f.next.prev = f.prev 202 | } else { 203 | p.clast = f.prev 204 | } 205 | 206 | if f.prev != nil { 207 | f.prev.next = f.next 208 | } else { 209 | p.cfirst = f.next 210 | } 211 | 212 | f.next = nil 213 | f.prev = nil 214 | p.Unlock() 215 | } 216 | 217 | func (f *srvFile) Rename(name string) error { 218 | p := f.Parent 219 | p.Lock() 220 | defer p.Unlock() 221 | for c := p.cfirst; c != nil; c = c.next { 222 | if name == c.Name { 223 | return Eexist 224 | } 225 | } 226 | 227 | f.Name = name 228 | return nil 229 | } 230 | 231 | // Looks for a file in a directory. Returns nil if the file is not found. 232 | func (p *srvFile) Find(name string) *srvFile { 233 | var f *srvFile 234 | 235 | p.Lock() 236 | for f = p.cfirst; f != nil; f = f.next { 237 | if name == f.Name { 238 | break 239 | } 240 | } 241 | p.Unlock() 242 | return f 243 | } 244 | 245 | // Checks if the specified user has permission to perform 246 | // certain operation on a file. Perm contains one or more 247 | // of DMREAD, DMWRITE, and DMEXEC. 248 | func (f *srvFile) CheckPerm(user User, perm uint32) bool { 249 | if user == nil { 250 | return false 251 | } 252 | 253 | perm &= 7 254 | 255 | /* other permissions */ 256 | fperm := f.Mode & 7 257 | if (fperm & perm) == perm { 258 | return true 259 | } 260 | 261 | /* user permissions */ 262 | if f.Uid == user.Name() || f.Uidnum == uint32(user.Id()) { 263 | fperm |= (f.Mode >> 6) & 7 264 | } 265 | 266 | if (fperm & perm) == perm { 267 | return true 268 | } 269 | 270 | /* group permissions */ 271 | groups := user.Groups() 272 | if groups != nil && len(groups) > 0 { 273 | for i := 0; i < len(groups); i++ { 274 | if f.Gid == groups[i].Name() || f.Gidnum == uint32(groups[i].Id()) { 275 | fperm |= (f.Mode >> 3) & 7 276 | break 277 | } 278 | } 279 | } 280 | 281 | if (fperm & perm) == perm { 282 | return true 283 | } 284 | 285 | return false 286 | } 287 | 288 | func (s *Fsrv) Attach(req *SrvReq) { 289 | fid := new(FFid) 290 | fid.F = s.Root 291 | fid.Fid = req.Fid 292 | req.Fid.Aux = fid 293 | req.RespondRattach(&s.Root.Qid) 294 | } 295 | 296 | func (*Fsrv) Walk(req *SrvReq) { 297 | fid := req.Fid.Aux.(*FFid) 298 | tc := req.Tc 299 | 300 | if req.Newfid.Aux == nil { 301 | nfid := new(FFid) 302 | nfid.Fid = req.Newfid 303 | req.Newfid.Aux = nfid 304 | } 305 | 306 | nfid := req.Newfid.Aux.(*FFid) 307 | wqids := make([]Qid, len(tc.Wname)) 308 | i := 0 309 | f := fid.F 310 | for ; i < len(tc.Wname); i++ { 311 | if tc.Wname[i] == ".." { 312 | // handle dotdot 313 | f = f.Parent 314 | wqids[i] = f.Qid 315 | continue 316 | } 317 | if (wqids[i].Type & QTDIR) > 0 { 318 | if !f.CheckPerm(req.Fid.User, DMEXEC) { 319 | break 320 | } 321 | } 322 | 323 | p := f.Find(tc.Wname[i]) 324 | if p == nil { 325 | break 326 | } 327 | 328 | f = p 329 | wqids[i] = f.Qid 330 | } 331 | 332 | if len(tc.Wname) > 0 && i == 0 { 333 | req.RespondError(Enoent) 334 | return 335 | } 336 | 337 | nfid.F = f 338 | req.RespondRwalk(wqids[0:i]) 339 | } 340 | 341 | func mode2Perm(mode uint8) uint32 { 342 | var perm uint32 = 0 343 | 344 | switch mode & 3 { 345 | case OREAD: 346 | perm = DMREAD 347 | case OWRITE: 348 | perm = DMWRITE 349 | case ORDWR: 350 | perm = DMREAD | DMWRITE 351 | } 352 | 353 | if (mode & OTRUNC) != 0 { 354 | perm |= DMWRITE 355 | } 356 | 357 | return perm 358 | } 359 | 360 | func (*Fsrv) Open(req *SrvReq) { 361 | fid := req.Fid.Aux.(*FFid) 362 | tc := req.Tc 363 | 364 | if !fid.F.CheckPerm(req.Fid.User, mode2Perm(tc.Mode)) { 365 | req.RespondError(Eperm) 366 | return 367 | } 368 | 369 | if op, ok := (fid.F.ops).(FOpenOp); ok { 370 | err := op.Open(fid, tc.Mode) 371 | if err != nil { 372 | req.RespondError(err) 373 | return 374 | } 375 | } 376 | req.RespondRopen(&fid.F.Qid, 0) 377 | } 378 | 379 | func (*Fsrv) Create(req *SrvReq) { 380 | fid := req.Fid.Aux.(*FFid) 381 | tc := req.Tc 382 | 383 | dir := fid.F 384 | if !dir.CheckPerm(req.Fid.User, DMWRITE) { 385 | req.RespondError(Eperm) 386 | return 387 | } 388 | 389 | if cop, ok := (dir.ops).(FCreateOp); ok { 390 | f, err := cop.Create(fid, tc.Name, tc.Perm) 391 | if err != nil { 392 | req.RespondError(err) 393 | } else { 394 | fid.F = f 395 | req.RespondRcreate(&fid.F.Qid, 0) 396 | } 397 | } else { 398 | req.RespondError(Eperm) 399 | } 400 | } 401 | 402 | func (*Fsrv) Read(req *SrvReq) { 403 | var n int 404 | var err error 405 | 406 | fid := req.Fid.Aux.(*FFid) 407 | f := fid.F 408 | tc := req.Tc 409 | rc := req.Rc 410 | InitRread(rc, tc.Count) 411 | 412 | if f.Mode&DMDIR != 0 { 413 | // Get all the directory entries and 414 | // serialize them all into an output buffer. 415 | // This greatly simplifies the directory read. 416 | if tc.Offset == 0 { 417 | var g *srvFile 418 | fid.dirents = nil 419 | f.Lock() 420 | for n, g = 0, f.cfirst; g != nil; n, g = n+1, g.next { 421 | } 422 | fid.dirs = make([]*srvFile, n) 423 | for n, g = 0, f.cfirst; g != nil; n, g = n+1, g.next { 424 | fid.dirs[n] = g 425 | fid.dirents = append(fid.dirents, 426 | PackDir(&g.Dir, req.Conn.Dotu)...) 427 | } 428 | f.Unlock() 429 | } 430 | 431 | switch { 432 | case tc.Offset > uint64(len(fid.dirents)): 433 | n = 0 434 | case len(fid.dirents[tc.Offset:]) > int(tc.Size): 435 | n = int(tc.Size) 436 | default: 437 | n = len(fid.dirents[tc.Offset:]) 438 | } 439 | copy(rc.Data, fid.dirents[tc.Offset:int(tc.Offset)+1+n]) 440 | 441 | } else { 442 | // file 443 | if rop, ok := f.ops.(FReadOp); ok { 444 | n, err = rop.Read(fid, rc.Data, tc.Offset) 445 | if err != nil { 446 | req.RespondError(err) 447 | return 448 | } 449 | } else { 450 | req.RespondError(Eperm) 451 | return 452 | } 453 | } 454 | 455 | SetRreadCount(rc, uint32(n)) 456 | req.Respond() 457 | } 458 | 459 | func (*Fsrv) Write(req *SrvReq) { 460 | fid := req.Fid.Aux.(*FFid) 461 | f := fid.F 462 | tc := req.Tc 463 | 464 | if wop, ok := (f.ops).(FWriteOp); ok { 465 | n, err := wop.Write(fid, tc.Data, tc.Offset) 466 | if err != nil { 467 | req.RespondError(err) 468 | } else { 469 | req.RespondRwrite(uint32(n)) 470 | } 471 | } else { 472 | req.RespondError(Eperm) 473 | } 474 | 475 | } 476 | 477 | func (*Fsrv) Clunk(req *SrvReq) { 478 | fid := req.Fid.Aux.(*FFid) 479 | 480 | if op, ok := (fid.F.ops).(FClunkOp); ok { 481 | err := op.Clunk(fid) 482 | if err != nil { 483 | req.RespondError(err) 484 | } 485 | } 486 | req.RespondRclunk() 487 | } 488 | 489 | func (*Fsrv) Remove(req *SrvReq) { 490 | fid := req.Fid.Aux.(*FFid) 491 | f := fid.F 492 | f.Lock() 493 | if f.cfirst != nil { 494 | f.Unlock() 495 | req.RespondError(Enotempty) 496 | return 497 | } 498 | f.Unlock() 499 | 500 | if rop, ok := (f.ops).(FRemoveOp); ok { 501 | err := rop.Remove(fid) 502 | if err != nil { 503 | req.RespondError(err) 504 | } else { 505 | f.Remove() 506 | req.RespondRremove() 507 | } 508 | } else { 509 | log.Println("remove not implemented") 510 | req.RespondError(Eperm) 511 | } 512 | } 513 | 514 | func (*Fsrv) Stat(req *SrvReq) { 515 | fid := req.Fid.Aux.(*FFid) 516 | f := fid.F 517 | 518 | if sop, ok := (f.ops).(FStatOp); ok { 519 | err := sop.Stat(fid) 520 | if err != nil { 521 | req.RespondError(err) 522 | } else { 523 | req.RespondRstat(&f.Dir) 524 | } 525 | } else { 526 | req.RespondRstat(&f.Dir) 527 | } 528 | } 529 | 530 | func (*Fsrv) Wstat(req *SrvReq) { 531 | tc := req.Tc 532 | fid := req.Fid.Aux.(*FFid) 533 | f := fid.F 534 | 535 | if wop, ok := (f.ops).(FWstatOp); ok { 536 | err := wop.Wstat(fid, &tc.Dir) 537 | if err != nil { 538 | req.RespondError(err) 539 | } else { 540 | req.RespondRwstat() 541 | } 542 | } else { 543 | req.RespondError(Eperm) 544 | } 545 | } 546 | 547 | func (*Fsrv) FidDestroy(ffid *SrvFid) { 548 | if ffid.Aux == nil { 549 | return 550 | } 551 | fid := ffid.Aux.(*FFid) 552 | f := fid.F 553 | 554 | if f == nil { 555 | return // otherwise errs in bad walks 556 | } 557 | 558 | if op, ok := (f.ops).(FDestroyOp); ok { 559 | op.FidDestroy(fid) 560 | } 561 | } 562 | -------------------------------------------------------------------------------- /srv_pipe.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "os" 11 | "strconv" 12 | "syscall" 13 | ) 14 | 15 | type pipeFid struct { 16 | path string 17 | file *os.File 18 | dirs []os.FileInfo 19 | dirents []byte 20 | diroffset uint64 21 | st os.FileInfo 22 | data []uint8 23 | eof bool 24 | } 25 | 26 | type Pipefs struct { 27 | Srv 28 | Root string 29 | } 30 | 31 | func (fid *pipeFid) stat() *Error { 32 | var err error 33 | 34 | fid.st, err = os.Lstat(fid.path) 35 | if err != nil { 36 | return toError(err) 37 | } 38 | 39 | return nil 40 | } 41 | 42 | // Dir is an instantiation of the p.Dir structure 43 | // that can act as a receiver for local methods. 44 | type pipeDir struct { 45 | Dir 46 | } 47 | 48 | func (dir *pipeDir) dotu(path string, d os.FileInfo, upool Users, sysMode *syscall.Stat_t) { 49 | u := upool.Uid2User(int(sysMode.Uid)) 50 | g := upool.Gid2Group(int(sysMode.Gid)) 51 | dir.Uid = u.Name() 52 | if dir.Uid == "" { 53 | dir.Uid = "none" 54 | } 55 | 56 | dir.Gid = g.Name() 57 | if dir.Gid == "" { 58 | dir.Gid = "none" 59 | } 60 | dir.Muid = "none" 61 | dir.Ext = "" 62 | dir.Uidnum = uint32(u.Id()) 63 | dir.Gidnum = uint32(g.Id()) 64 | dir.Muidnum = NOUID 65 | if d.Mode()&os.ModeSymlink != 0 { 66 | var err error 67 | dir.Ext, err = os.Readlink(path) 68 | if err != nil { 69 | dir.Ext = "" 70 | } 71 | } else if isBlock(d) { 72 | dir.Ext = fmt.Sprintf("b %d %d", sysMode.Rdev>>24, sysMode.Rdev&0xFFFFFF) 73 | } else if isChar(d) { 74 | dir.Ext = fmt.Sprintf("c %d %d", sysMode.Rdev>>24, sysMode.Rdev&0xFFFFFF) 75 | } 76 | } 77 | 78 | func (*Pipefs) ConnOpened(conn *Conn) { 79 | if conn.Srv.Debuglevel > 0 { 80 | log.Println("connected") 81 | } 82 | } 83 | 84 | func (*Pipefs) ConnClosed(conn *Conn) { 85 | if conn.Srv.Debuglevel > 0 { 86 | log.Println("disconnected") 87 | } 88 | } 89 | 90 | func (*Pipefs) FidDestroy(sfid *SrvFid) { 91 | var fid *pipeFid 92 | 93 | if sfid.Aux == nil { 94 | return 95 | } 96 | 97 | fid = sfid.Aux.(*pipeFid) 98 | if fid.file != nil { 99 | fid.file.Close() 100 | } 101 | } 102 | 103 | func (pipe *Pipefs) Attach(req *SrvReq) { 104 | if req.Afid != nil { 105 | req.RespondError(Enoauth) 106 | return 107 | } 108 | 109 | tc := req.Tc 110 | fid := new(pipeFid) 111 | if len(tc.Aname) == 0 { 112 | fid.path = pipe.Root 113 | } else { 114 | fid.path = tc.Aname 115 | } 116 | 117 | req.Fid.Aux = fid 118 | err := fid.stat() 119 | if err != nil { 120 | req.RespondError(err) 121 | return 122 | } 123 | 124 | qid := dir2Qid(fid.st) 125 | req.RespondRattach(qid) 126 | } 127 | 128 | func (*Pipefs) Flush(req *SrvReq) {} 129 | 130 | func (*Pipefs) Walk(req *SrvReq) { 131 | fid := req.Fid.Aux.(*pipeFid) 132 | tc := req.Tc 133 | 134 | err := fid.stat() 135 | if err != nil { 136 | req.RespondError(err) 137 | return 138 | } 139 | 140 | if req.Newfid.Aux == nil { 141 | req.Newfid.Aux = new(pipeFid) 142 | } 143 | 144 | nfid := req.Newfid.Aux.(*pipeFid) 145 | wqids := make([]Qid, len(tc.Wname)) 146 | path := fid.path 147 | i := 0 148 | for ; i < len(tc.Wname); i++ { 149 | p := path + "/" + tc.Wname[i] 150 | st, err := os.Lstat(p) 151 | if err != nil { 152 | if i == 0 { 153 | req.RespondError(Enoent) 154 | return 155 | } 156 | 157 | break 158 | } 159 | 160 | wqids[i] = *dir2Qid(st) 161 | path = p 162 | } 163 | 164 | nfid.path = path 165 | req.RespondRwalk(wqids[0:i]) 166 | } 167 | 168 | func (*Pipefs) Open(req *SrvReq) { 169 | fid := req.Fid.Aux.(*pipeFid) 170 | tc := req.Tc 171 | err := fid.stat() 172 | if err != nil { 173 | req.RespondError(err) 174 | return 175 | } 176 | 177 | var e error 178 | fid.file, e = os.OpenFile(fid.path, omode2uflags(tc.Mode), 0) 179 | if e != nil { 180 | req.RespondError(toError(e)) 181 | return 182 | } 183 | 184 | req.RespondRopen(dir2Qid(fid.st), 0) 185 | } 186 | 187 | func (*Pipefs) Create(req *SrvReq) { 188 | fid := req.Fid.Aux.(*pipeFid) 189 | tc := req.Tc 190 | err := fid.stat() 191 | if err != nil { 192 | req.RespondError(err) 193 | return 194 | } 195 | 196 | path := fid.path + "/" + tc.Name 197 | var e error = nil 198 | var file *os.File = nil 199 | switch { 200 | case tc.Perm&DMDIR != 0: 201 | e = os.Mkdir(path, os.FileMode(tc.Perm&0777)) 202 | 203 | case tc.Perm&DMSYMLINK != 0: 204 | e = os.Symlink(tc.Ext, path) 205 | 206 | case tc.Perm&DMLINK != 0: 207 | n, e := strconv.ParseUint(tc.Ext, 10, 0) 208 | if e != nil { 209 | break 210 | } 211 | 212 | ofid := req.Conn.FidGet(uint32(n)) 213 | if ofid == nil { 214 | req.RespondError(Eunknownfid) 215 | return 216 | } 217 | 218 | e = os.Link(ofid.Aux.(*pipeFid).path, path) 219 | ofid.DecRef() 220 | 221 | case tc.Perm&DMNAMEDPIPE != 0: 222 | case tc.Perm&DMDEVICE != 0: 223 | req.RespondError(&Error{"not implemented", EIO}) 224 | return 225 | 226 | default: 227 | var mode uint32 = tc.Perm & 0777 228 | if req.Conn.Dotu { 229 | if tc.Perm&DMSETUID > 0 { 230 | mode |= syscall.S_ISUID 231 | } 232 | if tc.Perm&DMSETGID > 0 { 233 | mode |= syscall.S_ISGID 234 | } 235 | } 236 | file, e = os.OpenFile(path, omode2uflags(tc.Mode)|os.O_CREATE, os.FileMode(mode)) 237 | } 238 | 239 | if file == nil && e == nil { 240 | file, e = os.OpenFile(path, omode2uflags(tc.Mode), 0) 241 | } 242 | 243 | if e != nil { 244 | req.RespondError(toError(e)) 245 | return 246 | } 247 | 248 | fid.path = path 249 | fid.file = file 250 | err = fid.stat() 251 | if err != nil { 252 | req.RespondError(err) 253 | return 254 | } 255 | 256 | req.RespondRcreate(dir2Qid(fid.st), 0) 257 | } 258 | 259 | func min(a, b int) int { 260 | if a < b { 261 | return a 262 | } 263 | return b 264 | } 265 | 266 | func (*Pipefs) Read(req *SrvReq) { 267 | fid := req.Fid.Aux.(*pipeFid) 268 | tc := req.Tc 269 | rc := req.Rc 270 | err := fid.stat() 271 | if err != nil { 272 | req.RespondError(err) 273 | return 274 | } 275 | 276 | InitRread(rc, tc.Count) 277 | var count int 278 | var e error 279 | if fid.st.IsDir() { 280 | if tc.Offset == 0 { 281 | var e error 282 | // If we got here, it was open. Can't really seek 283 | // in most cases, just close and reopen it. 284 | fid.file.Close() 285 | if fid.file, e = os.OpenFile(fid.path, omode2uflags(req.Fid.Omode), 0); e != nil { 286 | req.RespondError(toError(e)) 287 | return 288 | } 289 | 290 | if fid.dirs, e = fid.file.Readdir(-1); e != nil { 291 | req.RespondError(toError(e)) 292 | return 293 | } 294 | fid.dirents = nil 295 | 296 | for i := 0; i < len(fid.dirs); i++ { 297 | path := fid.path + "/" + fid.dirs[i].Name() 298 | st, _ := dir2Dir(path, fid.dirs[i], req.Conn.Dotu, req.Conn.Srv.Upool) 299 | if st == nil { 300 | continue 301 | } 302 | b := PackDir(st, req.Conn.Dotu) 303 | fid.dirents = append(fid.dirents, b...) 304 | count += len(b) 305 | } 306 | } 307 | switch { 308 | case tc.Offset > uint64(len(fid.dirents)): 309 | count = 0 310 | case len(fid.dirents[tc.Offset:]) > int(tc.Count): 311 | count = int(tc.Count) 312 | default: 313 | count = len(fid.dirents[tc.Offset:]) 314 | } 315 | 316 | copy(rc.Data, fid.dirents[tc.Offset:int(tc.Offset)+count]) 317 | 318 | } else { 319 | if fid.eof { 320 | req.RespondError(toError(e)) 321 | return 322 | } 323 | length := min(len(rc.Data), len(fid.data)) 324 | count = length 325 | copy(rc.Data, fid.data[:length]) 326 | fid.data = fid.data[length:] 327 | } 328 | 329 | SetRreadCount(rc, uint32(count)) 330 | req.Respond() 331 | } 332 | 333 | func (*Pipefs) Write(req *SrvReq) { 334 | fid := req.Fid.Aux.(*pipeFid) 335 | tc := req.Tc 336 | err := fid.stat() 337 | if err != nil { 338 | req.RespondError(err) 339 | return 340 | } 341 | 342 | fid.data = append(fid.data, tc.Data...) 343 | 344 | req.RespondRwrite(uint32(len(tc.Data))) 345 | } 346 | 347 | func (*Pipefs) Clunk(req *SrvReq) { req.RespondRclunk() } 348 | 349 | func (*Pipefs) Remove(req *SrvReq) { 350 | fid := req.Fid.Aux.(*pipeFid) 351 | err := fid.stat() 352 | if err != nil { 353 | req.RespondError(err) 354 | return 355 | } 356 | 357 | e := os.Remove(fid.path) 358 | if e != nil { 359 | req.RespondError(toError(e)) 360 | return 361 | } 362 | 363 | req.RespondRremove() 364 | } 365 | 366 | func (*Pipefs) Stat(req *SrvReq) { 367 | fid := req.Fid.Aux.(*pipeFid) 368 | err := fid.stat() 369 | if err != nil { 370 | req.RespondError(err) 371 | return 372 | } 373 | 374 | st, derr := dir2Dir(fid.path, fid.st, req.Conn.Dotu, req.Conn.Srv.Upool) 375 | if st == nil { 376 | req.RespondError(derr) 377 | return 378 | } 379 | 380 | req.RespondRstat(st) 381 | } 382 | 383 | func (*Pipefs) Wstat(req *SrvReq) { 384 | req.RespondError(Eperm) 385 | } 386 | -------------------------------------------------------------------------------- /srv_respond.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | import "fmt" 8 | 9 | // SrvRequest operations. This interface should be implemented by all file servers. 10 | // The operations correspond directly to most of the 9P2000 message types. 11 | type SrvReqOps interface { 12 | Attach(*SrvReq) 13 | Walk(*SrvReq) 14 | Open(*SrvReq) 15 | Create(*SrvReq) 16 | Read(*SrvReq) 17 | Write(*SrvReq) 18 | Clunk(*SrvReq) 19 | Remove(*SrvReq) 20 | Stat(*SrvReq) 21 | Wstat(*SrvReq) 22 | } 23 | 24 | // Respond to the request with Rerror message 25 | func (req *SrvReq) RespondError(err interface{}) { 26 | switch e := err.(type) { 27 | case *Error: 28 | PackRerror(req.Rc, e.Error(), uint32(e.Errornum), req.Conn.Dotu) 29 | case error: 30 | PackRerror(req.Rc, e.Error(), uint32(EIO), req.Conn.Dotu) 31 | default: 32 | PackRerror(req.Rc, fmt.Sprintf("%v", e), uint32(EIO), req.Conn.Dotu) 33 | } 34 | 35 | req.Respond() 36 | } 37 | 38 | // Respond to the request with Rversion message 39 | func (req *SrvReq) RespondRversion(msize uint32, version string) { 40 | err := PackRversion(req.Rc, msize, version) 41 | if err != nil { 42 | req.RespondError(err) 43 | } else { 44 | req.Respond() 45 | } 46 | } 47 | 48 | // Respond to the request with Rauth message 49 | func (req *SrvReq) RespondRauth(aqid *Qid) { 50 | err := PackRauth(req.Rc, aqid) 51 | if err != nil { 52 | req.RespondError(err) 53 | } else { 54 | req.Respond() 55 | } 56 | } 57 | 58 | // Respond to the request with Rflush message 59 | func (req *SrvReq) RespondRflush() { 60 | err := PackRflush(req.Rc) 61 | if err != nil { 62 | req.RespondError(err) 63 | } else { 64 | req.Respond() 65 | } 66 | } 67 | 68 | // Respond to the request with Rattach message 69 | func (req *SrvReq) RespondRattach(aqid *Qid) { 70 | err := PackRattach(req.Rc, aqid) 71 | if err != nil { 72 | req.RespondError(err) 73 | } else { 74 | req.Respond() 75 | } 76 | } 77 | 78 | // Respond to the request with Rwalk message 79 | func (req *SrvReq) RespondRwalk(wqids []Qid) { 80 | err := PackRwalk(req.Rc, wqids) 81 | if err != nil { 82 | req.RespondError(err) 83 | } else { 84 | req.Respond() 85 | } 86 | } 87 | 88 | // Respond to the request with Ropen message 89 | func (req *SrvReq) RespondRopen(qid *Qid, iounit uint32) { 90 | err := PackRopen(req.Rc, qid, iounit) 91 | if err != nil { 92 | req.RespondError(err) 93 | } else { 94 | req.Respond() 95 | } 96 | } 97 | 98 | // Respond to the request with Rcreate message 99 | func (req *SrvReq) RespondRcreate(qid *Qid, iounit uint32) { 100 | err := PackRcreate(req.Rc, qid, iounit) 101 | if err != nil { 102 | req.RespondError(err) 103 | } else { 104 | req.Respond() 105 | } 106 | } 107 | 108 | // Respond to the request with Rread message 109 | func (req *SrvReq) RespondRread(data []byte) { 110 | err := PackRread(req.Rc, data) 111 | if err != nil { 112 | req.RespondError(err) 113 | } else { 114 | req.Respond() 115 | } 116 | } 117 | 118 | // Respond to the request with Rwrite message 119 | func (req *SrvReq) RespondRwrite(count uint32) { 120 | err := PackRwrite(req.Rc, count) 121 | if err != nil { 122 | req.RespondError(err) 123 | } else { 124 | req.Respond() 125 | } 126 | } 127 | 128 | // Respond to the request with Rclunk message 129 | func (req *SrvReq) RespondRclunk() { 130 | err := PackRclunk(req.Rc) 131 | if err != nil { 132 | req.RespondError(err) 133 | } else { 134 | req.Respond() 135 | } 136 | } 137 | 138 | // Respond to the request with Rremove message 139 | func (req *SrvReq) RespondRremove() { 140 | err := PackRremove(req.Rc) 141 | if err != nil { 142 | req.RespondError(err) 143 | } else { 144 | req.Respond() 145 | } 146 | } 147 | 148 | // Respond to the request with Rstat message 149 | func (req *SrvReq) RespondRstat(st *Dir) { 150 | err := PackRstat(req.Rc, st, req.Conn.Dotu) 151 | if err != nil { 152 | req.RespondError(err) 153 | } else { 154 | req.Respond() 155 | } 156 | } 157 | 158 | // Respond to the request with Rwstat message 159 | func (req *SrvReq) RespondRwstat() { 160 | err := PackRwstat(req.Rc) 161 | if err != nil { 162 | req.RespondError(err) 163 | } else { 164 | req.Respond() 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /srv_srv.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // The srv package go9provides definitions and functions used to implement 6 | // a 9P2000 file server. 7 | package go9p 8 | 9 | import ( 10 | "net" 11 | "sync" 12 | ) 13 | 14 | type reqStatus int 15 | 16 | const ( 17 | reqFlush reqStatus = (1 << iota) /* request is flushed (no response will be sent) */ 18 | reqWork /* goroutine is currently working on it */ 19 | reqResponded /* response is already produced */ 20 | reqSaved /* no response was produced after the request is worked on */ 21 | ) 22 | 23 | var Eunknownfid error = &Error{"unknown fid", EINVAL} 24 | var Enoauth error = &Error{"no authentication required", EINVAL} 25 | var Einuse error = &Error{"fid already in use", EINVAL} 26 | var Ebaduse error = &Error{"bad use of fid", EINVAL} 27 | var Eopen error = &Error{"fid already opened", EINVAL} 28 | var Enotdir error = &Error{"not a directory", ENOTDIR} 29 | var Eperm error = &Error{"permission denied", EPERM} 30 | var Etoolarge error = &Error{"i/o count too large", EINVAL} 31 | var Ebadoffset error = &Error{"bad offset in directory read", EINVAL} 32 | var Edirchange error = &Error{"cannot convert between files and directories", EINVAL} 33 | var Enouser error = &Error{"unknown user", EINVAL} 34 | var Enotimpl error = &Error{"not implemented", EINVAL} 35 | 36 | // Authentication operations. The file server should implement them if 37 | // it requires user authentication. The authentication in 9P2000 is 38 | // done by creating special authentication fids and performing I/O 39 | // operations on them. Once the authentication is done, the authentication 40 | // fid can be used by the user to get access to the actual files. 41 | type AuthOps interface { 42 | // AuthInit is called when the user starts the authentication 43 | // process on SrvFid afid. The user that is being authenticated 44 | // is referred by afid.User. The function should return the Qid 45 | // for the authentication file, or an Error if the user can't be 46 | // authenticated 47 | AuthInit(afid *SrvFid, aname string) (*Qid, error) 48 | 49 | // AuthDestroy is called when an authentication fid is destroyed. 50 | AuthDestroy(afid *SrvFid) 51 | 52 | // AuthCheck is called after the authentication process is finished 53 | // when the user tries to attach to the file server. If the function 54 | // returns nil, the authentication was successful and the user has 55 | // permission to access the files. 56 | AuthCheck(fid *SrvFid, afid *SrvFid, aname string) error 57 | 58 | // AuthRead is called when the user attempts to read data from an 59 | // authentication fid. 60 | AuthRead(afid *SrvFid, offset uint64, data []byte) (count int, err error) 61 | 62 | // AuthWrite is called when the user attempts to write data to an 63 | // authentication fid. 64 | AuthWrite(afid *SrvFid, offset uint64, data []byte) (count int, err error) 65 | } 66 | 67 | // Connection operations. These should be implemented if the file server 68 | // needs to be called when a connection is opened or closed. 69 | type ConnOps interface { 70 | ConnOpened(*Conn) 71 | ConnClosed(*Conn) 72 | } 73 | 74 | // SrvFid operations. This interface should be implemented if the file server 75 | // needs to be called when a SrvFid is destroyed. 76 | type SrvFidOps interface { 77 | FidDestroy(*SrvFid) 78 | } 79 | 80 | // Request operations. This interface should be implemented if the file server 81 | // needs to bypass the default request process, or needs to perform certain 82 | // operations before the (any) request is processed, or before (any) response 83 | // sent back to the client. 84 | type SrvReqProcessOps interface { 85 | // Called when a new request is received from the client. If the 86 | // interface is not implemented, (req *SrvReq) srv.Process() method is 87 | // called. If the interface is implemented, it is the user's 88 | // responsibility to call srv.Process. If srv.Process isn't called, 89 | // SrvFid, Afid and Newfid fields in SrvReq are not set, and the SrvReqOps 90 | // methods are not called. 91 | SrvReqProcess(*SrvReq) 92 | 93 | // Called when a request is responded, i.e. when (req *SrvReq)srv.Respond() 94 | // is called and before the response is sent. If the interface is not 95 | // implemented, (req *SrvReq) srv.PostProcess() method is called to finalize 96 | // the request. If the interface is implemented and SrvReqProcess calls 97 | // the srv.Process method, SrvReqRespond should call the srv.PostProcess 98 | // method. 99 | SrvReqRespond(*SrvReq) 100 | } 101 | 102 | // Flush operation. This interface should be implemented if the file server 103 | // can flush pending requests. If the interface is not implemented, requests 104 | // that were passed to the file server implementation won't be flushed. 105 | // The flush method should call the (req *SrvReq) srv.Flush() method if the flush 106 | // was successful so the request can be marked appropriately. 107 | type FlushOp interface { 108 | Flush(*SrvReq) 109 | } 110 | 111 | // The Srv type contains the basic fields used to control the 9P2000 112 | // file server. Each file server implementation should create a value 113 | // of Srv type, initialize the values it cares about and pass the 114 | // struct to the (Srv *) srv.Start(ops) method together with the object 115 | // that implements the file server operations. 116 | type Srv struct { 117 | sync.Mutex 118 | Id string // Used for debugging and stats 119 | Msize uint32 // Maximum size of the 9P2000 messages supported by the server 120 | Dotu bool // If true, the server supports the 9P2000.u extension 121 | Debuglevel int // debug level 122 | Upool Users // Interface for finding users and groups known to the file server 123 | Maxpend int // Maximum pending outgoing requests 124 | Log *Logger 125 | 126 | ops interface{} // operations 127 | conns map[*Conn]*Conn // List of connections 128 | } 129 | 130 | // The Conn type represents a connection from a client to the file server 131 | type Conn struct { 132 | sync.Mutex 133 | Srv *Srv 134 | Msize uint32 // maximum size of 9P2000 messages for the connection 135 | Dotu bool // if true, both the client and the server speak 9P2000.u 136 | Id string // used for debugging and stats 137 | Debuglevel int 138 | 139 | conn net.Conn 140 | fidpool map[uint32]*SrvFid 141 | reqs map[uint16]*SrvReq // all outstanding requests 142 | 143 | reqout chan *SrvReq 144 | rchan chan *Fcall 145 | done chan bool 146 | 147 | // stats 148 | nreqs int // number of requests processed by the server 149 | tsz uint64 // total size of the T messages received 150 | rsz uint64 // total size of the R messages sent 151 | npend int // number of currently pending messages 152 | maxpend int // maximum number of pending messages 153 | nreads int // number of reads 154 | nwrites int // number of writes 155 | } 156 | 157 | // The SrvFid type identifies a file on the file server. 158 | // A new SrvFid is created when the user attaches to the file server (the Attach 159 | // operation), or when Walk-ing to a file. The SrvFid values are created 160 | // automatically by the srv implementation. The SrvFidDestroy operation is called 161 | // when a SrvFid is destroyed. 162 | type SrvFid struct { 163 | sync.Mutex 164 | fid uint32 165 | refcount int 166 | opened bool // True if the SrvFid is opened 167 | Fconn *Conn // Connection the SrvFid belongs to 168 | Omode uint8 // Open mode (O* flags), if the fid is opened 169 | Type uint8 // SrvFid type (QT* flags) 170 | Diroffset uint64 // If directory, the next valid read position 171 | Dirents []byte // If directory, the serialized dirents 172 | User User // The SrvFid's user 173 | Aux interface{} // Can be used by the file server implementation for per-SrvFid data 174 | } 175 | 176 | // The SrvReq type represents a 9P2000 request. Each request has a 177 | // T-message (Tc) and a R-message (Rc). If the SrvReqProcessOps don't 178 | // override the default behavior, the implementation initializes SrvFid, 179 | // Afid and Newfid values and automatically keeps track on when the SrvFids 180 | // should be destroyed. 181 | type SrvReq struct { 182 | sync.Mutex 183 | Tc *Fcall // Incoming 9P2000 message 184 | Rc *Fcall // Outgoing 9P2000 response 185 | Fid *SrvFid // The SrvFid value for all messages that contain fid[4] 186 | Afid *SrvFid // The SrvFid value for the messages that contain afid[4] (Tauth and Tattach) 187 | Newfid *SrvFid // The SrvFid value for the messages that contain newfid[4] (Twalk) 188 | Conn *Conn // Connection that the request belongs to 189 | 190 | status reqStatus 191 | flushreq *SrvReq 192 | prev, next *SrvReq 193 | } 194 | 195 | // The Start method should be called once the file server implementor 196 | // initializes the Srv struct with the preferred values. It sets default 197 | // values to the fields that are not initialized and creates the goroutines 198 | // required for the server's operation. The method receives an empty 199 | // interface value, ops, that should implement the interfaces the file server is 200 | // interested in. Ops must implement the SrvReqOps interface. 201 | func (srv *Srv) Start(ops interface{}) bool { 202 | if _, ok := (ops).(SrvReqOps); !ok { 203 | return false 204 | } 205 | 206 | srv.ops = ops 207 | if srv.Upool == nil { 208 | srv.Upool = OsUsers 209 | } 210 | 211 | if srv.Msize < IOHDRSZ { 212 | srv.Msize = MSIZE 213 | } 214 | 215 | if srv.Log == nil { 216 | srv.Log = NewLogger(1024) 217 | } 218 | 219 | if sop, ok := (interface{}(srv)).(StatsOps); ok { 220 | sop.statsRegister() 221 | } 222 | 223 | return true 224 | } 225 | 226 | func (srv *Srv) String() string { 227 | return srv.Id 228 | } 229 | 230 | func (req *SrvReq) process() { 231 | req.Lock() 232 | flushed := (req.status & reqFlush) != 0 233 | if !flushed { 234 | req.status |= reqWork 235 | } 236 | req.Unlock() 237 | 238 | if flushed { 239 | req.Respond() 240 | } 241 | 242 | if rop, ok := (req.Conn.Srv.ops).(SrvReqProcessOps); ok { 243 | rop.SrvReqProcess(req) 244 | } else { 245 | req.Process() 246 | } 247 | 248 | req.Lock() 249 | req.status &= ^reqWork 250 | if !(req.status&reqResponded != 0) { 251 | req.status |= reqSaved 252 | } 253 | req.Unlock() 254 | } 255 | 256 | // Performs the default processing of a request. Initializes 257 | // the SrvFid, Afid and Newfid fields and calls the appropriate 258 | // SrvReqOps operation for the message. The file server implementer 259 | // should call it only if the file server implements the SrvReqProcessOps 260 | // within the SrvReqProcess operation. 261 | func (req *SrvReq) Process() { 262 | conn := req.Conn 263 | srv := conn.Srv 264 | tc := req.Tc 265 | 266 | if tc.Fid != NOFID && tc.Type != Tattach { 267 | srv.Lock() 268 | req.Fid = conn.FidGet(tc.Fid) 269 | srv.Unlock() 270 | if req.Fid == nil { 271 | req.RespondError(Eunknownfid) 272 | return 273 | } 274 | } 275 | 276 | switch req.Tc.Type { 277 | default: 278 | req.RespondError(&Error{"unknown message type", EINVAL}) 279 | 280 | case Tversion: 281 | srv.version(req) 282 | 283 | case Tauth: 284 | srv.auth(req) 285 | 286 | case Tattach: 287 | srv.attach(req) 288 | 289 | case Tflush: 290 | srv.flush(req) 291 | 292 | case Twalk: 293 | srv.walk(req) 294 | 295 | case Topen: 296 | srv.open(req) 297 | 298 | case Tcreate: 299 | srv.create(req) 300 | 301 | case Tread: 302 | srv.read(req) 303 | 304 | case Twrite: 305 | srv.write(req) 306 | 307 | case Tclunk: 308 | srv.clunk(req) 309 | 310 | case Tremove: 311 | srv.remove(req) 312 | 313 | case Tstat: 314 | srv.stat(req) 315 | 316 | case Twstat: 317 | srv.wstat(req) 318 | } 319 | } 320 | 321 | // Performs the post processing required if the (*SrvReq) Process() method 322 | // is called for a request. The file server implementer should call it 323 | // only if the file server implements the SrvReqProcessOps within the 324 | // SrvReqRespond operation. 325 | func (req *SrvReq) PostProcess() { 326 | srv := req.Conn.Srv 327 | 328 | /* call the post-handlers (if needed) */ 329 | switch req.Tc.Type { 330 | case Tauth: 331 | srv.authPost(req) 332 | 333 | case Tattach: 334 | srv.attachPost(req) 335 | 336 | case Twalk: 337 | srv.walkPost(req) 338 | 339 | case Topen: 340 | srv.openPost(req) 341 | 342 | case Tcreate: 343 | srv.createPost(req) 344 | 345 | case Tread: 346 | srv.readPost(req) 347 | 348 | case Tclunk: 349 | srv.clunkPost(req) 350 | 351 | case Tremove: 352 | srv.removePost(req) 353 | } 354 | 355 | if req.Fid != nil { 356 | req.Fid.DecRef() 357 | req.Fid = nil 358 | } 359 | 360 | if req.Afid != nil { 361 | req.Afid.DecRef() 362 | req.Afid = nil 363 | } 364 | 365 | if req.Newfid != nil { 366 | req.Newfid.DecRef() 367 | req.Newfid = nil 368 | } 369 | } 370 | 371 | // The Respond method sends response back to the client. The req.Rc value 372 | // should be initialized and contain valid 9P2000 message. In most cases 373 | // the file server implementer shouldn't call this method directly. Instead 374 | // one of the RespondR* methods should be used. 375 | func (req *SrvReq) Respond() { 376 | var flushreqs *SrvReq 377 | 378 | conn := req.Conn 379 | req.Lock() 380 | status := req.status 381 | req.status |= reqResponded 382 | req.status &= ^reqWork 383 | req.Unlock() 384 | 385 | if (status & reqResponded) != 0 { 386 | return 387 | } 388 | 389 | /* remove the request and all requests flushing it */ 390 | conn.Lock() 391 | nextreq := req.prev 392 | if nextreq != nil { 393 | nextreq.next = nil 394 | // if there are flush requests, move them to the next request 395 | if req.flushreq != nil { 396 | var p *SrvReq = nil 397 | r := nextreq.flushreq 398 | for ; r != nil; p, r = r, r.flushreq { 399 | } 400 | 401 | if p == nil { 402 | nextreq.flushreq = req.flushreq 403 | } else { 404 | nextreq = req.flushreq 405 | } 406 | } 407 | 408 | flushreqs = nil 409 | } else { 410 | delete(conn.reqs, req.Tc.Tag) 411 | flushreqs = req.flushreq 412 | } 413 | conn.Unlock() 414 | 415 | if rop, ok := (req.Conn.Srv.ops).(SrvReqProcessOps); ok { 416 | rop.SrvReqRespond(req) 417 | } else { 418 | req.PostProcess() 419 | } 420 | 421 | if (status & reqFlush) == 0 { 422 | conn.reqout <- req 423 | } 424 | 425 | // process the next request with the same tag (if available) 426 | if nextreq != nil { 427 | go nextreq.process() 428 | } 429 | 430 | // respond to the flush messages 431 | // can't send the responses directly to conn.reqout, because the 432 | // the flushes may be in a tag group too 433 | for freq := flushreqs; freq != nil; freq = freq.flushreq { 434 | freq.Respond() 435 | } 436 | } 437 | 438 | // Should be called to cancel a request. Should only be called 439 | // from the Flush operation if the FlushOp is implemented. 440 | func (req *SrvReq) Flush() { 441 | req.Lock() 442 | req.status |= reqFlush 443 | req.Unlock() 444 | req.Respond() 445 | } 446 | 447 | // Lookup a SrvFid struct based on the 32-bit identifier sent over the wire. 448 | // Returns nil if the fid is not found. Increases the reference count of 449 | // the returned fid. The user is responsible to call DecRef once it no 450 | // longer needs it. 451 | func (conn *Conn) FidGet(fidno uint32) *SrvFid { 452 | conn.Lock() 453 | fid, present := conn.fidpool[fidno] 454 | conn.Unlock() 455 | if present { 456 | fid.IncRef() 457 | } 458 | 459 | return fid 460 | } 461 | 462 | // Creates a new SrvFid struct for the fidno integer. Returns nil 463 | // if the SrvFid for that number already exists. The returned fid 464 | // has reference count set to 1. 465 | func (conn *Conn) FidNew(fidno uint32) *SrvFid { 466 | conn.Lock() 467 | _, present := conn.fidpool[fidno] 468 | if present { 469 | conn.Unlock() 470 | return nil 471 | } 472 | 473 | fid := new(SrvFid) 474 | fid.fid = fidno 475 | fid.refcount = 1 476 | fid.Fconn = conn 477 | conn.fidpool[fidno] = fid 478 | conn.Unlock() 479 | 480 | return fid 481 | } 482 | 483 | func (conn *Conn) String() string { 484 | return conn.Srv.Id + "/" + conn.Id 485 | } 486 | 487 | // Increase the reference count for the fid. 488 | func (fid *SrvFid) IncRef() { 489 | fid.Lock() 490 | fid.refcount++ 491 | fid.Unlock() 492 | } 493 | 494 | // Decrease the reference count for the fid. When the 495 | // reference count reaches 0, the fid is no longer valid. 496 | func (fid *SrvFid) DecRef() { 497 | fid.Lock() 498 | fid.refcount-- 499 | n := fid.refcount 500 | fid.Unlock() 501 | 502 | if n > 0 { 503 | return 504 | } 505 | 506 | conn := fid.Fconn 507 | conn.Lock() 508 | delete(conn.fidpool, fid.fid) 509 | conn.Unlock() 510 | 511 | if fop, ok := (conn.Srv.ops).(SrvFidOps); ok { 512 | fop.FidDestroy(fid) 513 | } 514 | } 515 | -------------------------------------------------------------------------------- /srv_stats_http.go: -------------------------------------------------------------------------------- 1 | // +build httpstats 2 | 3 | package go9p 4 | 5 | import ( 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "sync" 10 | ) 11 | 12 | var mux sync.RWMutex 13 | var stat map[string]http.Handler 14 | var httponce sync.Once 15 | 16 | func register(s string, h http.Handler) { 17 | mux.Lock() 18 | if stat == nil { 19 | stat = make(map[string]http.Handler) 20 | } 21 | 22 | if h == nil { 23 | delete(stat, s) 24 | } else { 25 | stat[s] = h 26 | } 27 | mux.Unlock() 28 | } 29 | func (srv *Srv) statsRegister() { 30 | httponce.Do(func() { 31 | http.HandleFunc("/go9p/", StatsHandler) 32 | go http.ListenAndServe(":6060", nil) 33 | }) 34 | 35 | register("/go9p/srv/"+srv.Id, srv) 36 | } 37 | 38 | func (srv *Srv) statsUnregister() { 39 | register("/go9p/srv/"+srv.Id, nil) 40 | } 41 | 42 | func (srv *Srv) ServeHTTP(c http.ResponseWriter, r *http.Request) { 43 | io.WriteString(c, fmt.Sprintf("

Server %s

", srv.Id)) 44 | defer io.WriteString(c, "") 45 | 46 | // connections 47 | io.WriteString(c, "

Connections

") 48 | srv.Lock() 49 | defer srv.Unlock() 50 | if len(srv.conns) == 0 { 51 | io.WriteString(c, "none") 52 | return 53 | } 54 | 55 | for _, conn := range srv.conns { 56 | io.WriteString(c, fmt.Sprintf("%s
", srv.Id, conn.Id, conn.Id)) 57 | } 58 | } 59 | 60 | func (conn *Conn) statsRegister() { 61 | register("/go9p/srv/"+conn.Srv.Id+"/conn/"+conn.Id, conn) 62 | } 63 | 64 | func (conn *Conn) statsUnregister() { 65 | register("/go9p/srv/"+conn.Srv.Id+"/conn/"+conn.Id, nil) 66 | } 67 | 68 | func (conn *Conn) ServeHTTP(c http.ResponseWriter, r *http.Request) { 69 | io.WriteString(c, fmt.Sprintf("

Connection %s/%s

", conn.Srv.Id, conn.Id)) 70 | defer io.WriteString(c, "") 71 | 72 | // statistics 73 | conn.Lock() 74 | io.WriteString(c, fmt.Sprintf("

Number of processed requests: %d", conn.nreqs)) 75 | io.WriteString(c, fmt.Sprintf("
Sent %v bytes", conn.rsz)) 76 | io.WriteString(c, fmt.Sprintf("
Received %v bytes", conn.tsz)) 77 | io.WriteString(c, fmt.Sprintf("
Pending requests: %d max %d", conn.npend, conn.maxpend)) 78 | io.WriteString(c, fmt.Sprintf("
Number of reads: %d", conn.nreads)) 79 | io.WriteString(c, fmt.Sprintf("
Number of writes: %d", conn.nwrites)) 80 | conn.Unlock() 81 | 82 | // fcalls 83 | if conn.Debuglevel&DbgLogFcalls != 0 { 84 | fs := conn.Srv.Log.Filter(conn, DbgLogFcalls) 85 | io.WriteString(c, fmt.Sprintf("

Last %d 9P messages

", len(fs))) 86 | for i, l := range fs { 87 | fc := l.Data.(*Fcall) 88 | if fc.Type == 0 { 89 | continue 90 | } 91 | 92 | lbl := "" 93 | if fc.Type%2 == 0 { 94 | // try to find the response for the T message 95 | for j := i + 1; j < len(fs); j++ { 96 | rc := fs[j].Data.(*Fcall) 97 | if rc.Tag == fc.Tag { 98 | lbl = fmt.Sprintf("", j) 99 | break 100 | } 101 | } 102 | } else { 103 | // try to find the request for the R message 104 | for j := i - 1; j >= 0; j-- { 105 | tc := fs[j].Data.(*Fcall) 106 | if tc.Tag == fc.Tag { 107 | lbl = fmt.Sprintf("", j) 108 | break 109 | } 110 | } 111 | } 112 | 113 | io.WriteString(c, fmt.Sprintf("
%d: %s%s", i, i, fc, lbl)) 114 | } 115 | } 116 | } 117 | 118 | func StatsHandler(c http.ResponseWriter, r *http.Request) { 119 | mux.RLock() 120 | if v, ok := stat[r.URL.Path]; ok { 121 | v.ServeHTTP(c, r) 122 | } else if r.URL.Path == "/go9p/" { 123 | io.WriteString(c, fmt.Sprintf("

On offer:


")) 124 | for v := range stat { 125 | io.WriteString(c, fmt.Sprintf("%s
", v, v)) 126 | } 127 | io.WriteString(c, "") 128 | } 129 | mux.RUnlock() 130 | } 131 | -------------------------------------------------------------------------------- /stats.go: -------------------------------------------------------------------------------- 1 | package go9p 2 | 3 | type StatsOps interface { 4 | statsRegister() 5 | statsUnregister() 6 | } 7 | -------------------------------------------------------------------------------- /ufs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "io" 11 | "log" 12 | "os" 13 | "os/user" 14 | "path" 15 | "sort" 16 | "strconv" 17 | "strings" 18 | "syscall" 19 | "time" 20 | ) 21 | 22 | type ufsFid struct { 23 | path string 24 | file *os.File 25 | dirs []os.FileInfo 26 | direntends []int 27 | dirents []byte 28 | diroffset uint64 29 | st os.FileInfo 30 | } 31 | 32 | type Ufs struct { 33 | Srv 34 | Root string 35 | } 36 | 37 | func toError(err error) *Error { 38 | var ecode uint32 39 | 40 | ename := err.Error() 41 | if e, ok := err.(syscall.Errno); ok { 42 | ecode = uint32(e) 43 | } else { 44 | ecode = EIO 45 | } 46 | 47 | return &Error{ename, ecode} 48 | } 49 | 50 | // IsBlock reports if the file is a block device 51 | func isBlock(d os.FileInfo) bool { 52 | stat := d.Sys().(*syscall.Stat_t) 53 | return (stat.Mode & syscall.S_IFMT) == syscall.S_IFBLK 54 | } 55 | 56 | // IsChar reports if the file is a character device 57 | func isChar(d os.FileInfo) bool { 58 | stat := d.Sys().(*syscall.Stat_t) 59 | return (stat.Mode & syscall.S_IFMT) == syscall.S_IFCHR 60 | } 61 | 62 | func (fid *ufsFid) stat() *Error { 63 | var err error 64 | 65 | fid.st, err = os.Lstat(fid.path) 66 | if err != nil { 67 | return toError(err) 68 | } 69 | 70 | return nil 71 | } 72 | 73 | func omode2uflags(mode uint8) int { 74 | ret := int(0) 75 | switch mode & 3 { 76 | case OREAD: 77 | ret = os.O_RDONLY 78 | break 79 | 80 | case ORDWR: 81 | ret = os.O_RDWR 82 | break 83 | 84 | case OWRITE: 85 | ret = os.O_WRONLY 86 | break 87 | 88 | case OEXEC: 89 | ret = os.O_RDONLY 90 | break 91 | } 92 | 93 | if mode&OTRUNC != 0 { 94 | ret |= os.O_TRUNC 95 | } 96 | 97 | return ret 98 | } 99 | 100 | func dir2Qid(d os.FileInfo) *Qid { 101 | var qid Qid 102 | 103 | qid.Path = d.Sys().(*syscall.Stat_t).Ino 104 | qid.Version = uint32(d.ModTime().UnixNano() / 1000000) 105 | qid.Type = dir2QidType(d) 106 | 107 | return &qid 108 | } 109 | 110 | func dir2QidType(d os.FileInfo) uint8 { 111 | ret := uint8(0) 112 | if d.IsDir() { 113 | ret |= QTDIR 114 | } 115 | 116 | if d.Mode()&os.ModeSymlink != 0 { 117 | ret |= QTSYMLINK 118 | } 119 | 120 | return ret 121 | } 122 | 123 | func dir2Npmode(d os.FileInfo, dotu bool) uint32 { 124 | ret := uint32(d.Mode() & 0777) 125 | if d.IsDir() { 126 | ret |= DMDIR 127 | } 128 | 129 | if dotu { 130 | mode := d.Mode() 131 | if mode&os.ModeSymlink != 0 { 132 | ret |= DMSYMLINK 133 | } 134 | 135 | if mode&os.ModeSocket != 0 { 136 | ret |= DMSOCKET 137 | } 138 | 139 | if mode&os.ModeNamedPipe != 0 { 140 | ret |= DMNAMEDPIPE 141 | } 142 | 143 | if mode&os.ModeDevice != 0 { 144 | ret |= DMDEVICE 145 | } 146 | 147 | if mode&os.ModeSetuid != 0 { 148 | ret |= DMSETUID 149 | } 150 | 151 | if mode&os.ModeSetgid != 0 { 152 | ret |= DMSETGID 153 | } 154 | } 155 | 156 | return ret 157 | } 158 | 159 | // Dir is an instantiation of the p.Dir structure 160 | // that can act as a receiver for local methods. 161 | type ufsDir struct { 162 | Dir 163 | } 164 | 165 | func dir2Dir(path string, d os.FileInfo, dotu bool, upool Users) (*Dir, error) { 166 | if r := recover(); r != nil { 167 | fmt.Print("stat failed: ", r) 168 | return nil, &os.PathError{"dir2Dir", path, nil} 169 | } 170 | sysif := d.Sys() 171 | if sysif == nil { 172 | return nil, &os.PathError{"dir2Dir: sysif is nil", path, nil} 173 | } 174 | sysMode := sysif.(*syscall.Stat_t) 175 | 176 | dir := new(ufsDir) 177 | dir.Qid = *dir2Qid(d) 178 | dir.Mode = dir2Npmode(d, dotu) 179 | dir.Atime = uint32(0 /*atime(sysMode).Unix()*/) 180 | dir.Mtime = uint32(d.ModTime().Unix()) 181 | dir.Length = uint64(d.Size()) 182 | dir.Name = path[strings.LastIndex(path, "/")+1:] 183 | 184 | if dotu { 185 | dir.dotu(path, d, upool, sysMode) 186 | return &dir.Dir, nil 187 | } 188 | 189 | unixUid := int(sysMode.Uid) 190 | unixGid := int(sysMode.Gid) 191 | dir.Uid = strconv.Itoa(unixUid) 192 | dir.Gid = strconv.Itoa(unixGid) 193 | 194 | // BUG(akumar): LookupId will never find names for 195 | // groups, as it only operates on user ids. 196 | u, err := user.LookupId(dir.Uid) 197 | if err == nil { 198 | dir.Uid = u.Username 199 | } 200 | g, err := user.LookupId(dir.Gid) 201 | if err == nil { 202 | dir.Gid = g.Username 203 | } 204 | 205 | /* For Akaros, we use the Muid as the link value. */ 206 | if *Akaros && (d.Mode()&os.ModeSymlink != 0) { 207 | dir.Muid, err = os.Readlink(path) 208 | if err == nil { 209 | dir.Mode |= DMSYMLINK 210 | } 211 | } 212 | return &dir.Dir, nil 213 | } 214 | 215 | func (dir *ufsDir) dotu(path string, d os.FileInfo, upool Users, sysMode *syscall.Stat_t) { 216 | u := upool.Uid2User(int(sysMode.Uid)) 217 | g := upool.Gid2Group(int(sysMode.Gid)) 218 | dir.Uid = u.Name() 219 | if dir.Uid == "" { 220 | dir.Uid = "none" 221 | } 222 | 223 | dir.Gid = g.Name() 224 | if dir.Gid == "" { 225 | dir.Gid = "none" 226 | } 227 | dir.Muid = "none" 228 | dir.Ext = "" 229 | dir.Uidnum = uint32(u.Id()) 230 | dir.Gidnum = uint32(g.Id()) 231 | dir.Muidnum = NOUID 232 | if d.Mode()&os.ModeSymlink != 0 { 233 | var err error 234 | dir.Ext, err = os.Readlink(path) 235 | if err != nil { 236 | dir.Ext = "" 237 | } 238 | } else if isBlock(d) { 239 | dir.Ext = fmt.Sprintf("b %d %d", sysMode.Rdev>>24, sysMode.Rdev&0xFFFFFF) 240 | } else if isChar(d) { 241 | dir.Ext = fmt.Sprintf("c %d %d", sysMode.Rdev>>24, sysMode.Rdev&0xFFFFFF) 242 | } 243 | } 244 | 245 | func (*Ufs) ConnOpened(conn *Conn) { 246 | if conn.Srv.Debuglevel > 0 { 247 | log.Println("connected") 248 | } 249 | } 250 | 251 | func (*Ufs) ConnClosed(conn *Conn) { 252 | if conn.Srv.Debuglevel > 0 { 253 | log.Println("disconnected") 254 | } 255 | } 256 | 257 | func (*Ufs) FidDestroy(sfid *SrvFid) { 258 | var fid *ufsFid 259 | 260 | if sfid.Aux == nil { 261 | return 262 | } 263 | 264 | fid = sfid.Aux.(*ufsFid) 265 | if fid.file != nil { 266 | fid.file.Close() 267 | } 268 | } 269 | 270 | func (ufs *Ufs) Attach(req *SrvReq) { 271 | if req.Afid != nil { 272 | req.RespondError(Enoauth) 273 | return 274 | } 275 | 276 | tc := req.Tc 277 | fid := new(ufsFid) 278 | // You can think of the ufs.Root as a 'chroot' of a sort. 279 | // clients attach are not allowed to go outside the 280 | // directory represented by ufs.Root 281 | fid.path = path.Join(ufs.Root, tc.Aname) 282 | 283 | req.Fid.Aux = fid 284 | err := fid.stat() 285 | if err != nil { 286 | req.RespondError(err) 287 | return 288 | } 289 | 290 | qid := dir2Qid(fid.st) 291 | req.RespondRattach(qid) 292 | } 293 | 294 | func (*Ufs) Flush(req *SrvReq) {} 295 | 296 | func (*Ufs) Walk(req *SrvReq) { 297 | fid := req.Fid.Aux.(*ufsFid) 298 | tc := req.Tc 299 | 300 | err := fid.stat() 301 | if err != nil { 302 | req.RespondError(err) 303 | return 304 | } 305 | 306 | if req.Newfid.Aux == nil { 307 | req.Newfid.Aux = new(ufsFid) 308 | } 309 | 310 | nfid := req.Newfid.Aux.(*ufsFid) 311 | wqids := make([]Qid, len(tc.Wname)) 312 | path := fid.path 313 | i := 0 314 | for ; i < len(tc.Wname); i++ { 315 | p := path + "/" + tc.Wname[i] 316 | st, err := os.Lstat(p) 317 | if err != nil { 318 | if i == 0 { 319 | req.RespondError(Enoent) 320 | return 321 | } 322 | 323 | break 324 | } 325 | 326 | wqids[i] = *dir2Qid(st) 327 | path = p 328 | } 329 | 330 | nfid.path = path 331 | req.RespondRwalk(wqids[0:i]) 332 | } 333 | 334 | func (*Ufs) Open(req *SrvReq) { 335 | fid := req.Fid.Aux.(*ufsFid) 336 | tc := req.Tc 337 | err := fid.stat() 338 | if err != nil { 339 | req.RespondError(err) 340 | return 341 | } 342 | 343 | var e error 344 | fid.file, e = os.OpenFile(fid.path, omode2uflags(tc.Mode), 0) 345 | if e != nil { 346 | req.RespondError(toError(e)) 347 | return 348 | } 349 | 350 | req.RespondRopen(dir2Qid(fid.st), 0) 351 | } 352 | 353 | func (*Ufs) Create(req *SrvReq) { 354 | fid := req.Fid.Aux.(*ufsFid) 355 | tc := req.Tc 356 | err := fid.stat() 357 | if err != nil { 358 | req.RespondError(err) 359 | return 360 | } 361 | 362 | path := fid.path + "/" + tc.Name 363 | var e error = nil 364 | var file *os.File = nil 365 | switch { 366 | case tc.Perm&DMDIR != 0: 367 | e = os.Mkdir(path, os.FileMode(tc.Perm&0777)) 368 | 369 | case tc.Perm&DMSYMLINK != 0: 370 | e = os.Symlink(tc.Ext, path) 371 | 372 | case tc.Perm&DMLINK != 0: 373 | n, e := strconv.ParseUint(tc.Ext, 10, 0) 374 | if e != nil { 375 | break 376 | } 377 | 378 | ofid := req.Conn.FidGet(uint32(n)) 379 | if ofid == nil { 380 | req.RespondError(Eunknownfid) 381 | return 382 | } 383 | 384 | e = os.Link(ofid.Aux.(*ufsFid).path, path) 385 | ofid.DecRef() 386 | 387 | case tc.Perm&DMNAMEDPIPE != 0: 388 | case tc.Perm&DMDEVICE != 0: 389 | req.RespondError(&Error{"not implemented", EIO}) 390 | return 391 | 392 | default: 393 | var mode uint32 = tc.Perm & 0777 394 | if req.Conn.Dotu { 395 | if tc.Perm&DMSETUID > 0 { 396 | mode |= syscall.S_ISUID 397 | } 398 | if tc.Perm&DMSETGID > 0 { 399 | mode |= syscall.S_ISGID 400 | } 401 | } 402 | file, e = os.OpenFile(path, omode2uflags(tc.Mode)|os.O_CREATE, os.FileMode(mode)) 403 | } 404 | 405 | if file == nil && e == nil { 406 | file, e = os.OpenFile(path, omode2uflags(tc.Mode), 0) 407 | } 408 | 409 | if e != nil { 410 | req.RespondError(toError(e)) 411 | return 412 | } 413 | 414 | fid.path = path 415 | fid.file = file 416 | err = fid.stat() 417 | if err != nil { 418 | req.RespondError(err) 419 | return 420 | } 421 | 422 | req.RespondRcreate(dir2Qid(fid.st), 0) 423 | } 424 | 425 | func (*Ufs) Read(req *SrvReq) { 426 | fid := req.Fid.Aux.(*ufsFid) 427 | tc := req.Tc 428 | rc := req.Rc 429 | err := fid.stat() 430 | if err != nil { 431 | req.RespondError(err) 432 | return 433 | } 434 | 435 | InitRread(rc, tc.Count) 436 | var count int 437 | var e error 438 | if fid.st.IsDir() { 439 | if tc.Offset == 0 { 440 | var e error 441 | // If we got here, it was open. Can't really seek 442 | // in most cases, just close and reopen it. 443 | fid.file.Close() 444 | if fid.file, e = os.OpenFile(fid.path, omode2uflags(req.Fid.Omode), 0); e != nil { 445 | req.RespondError(toError(e)) 446 | return 447 | } 448 | 449 | if fid.dirs, e = fid.file.Readdir(-1); e != nil { 450 | req.RespondError(toError(e)) 451 | return 452 | } 453 | 454 | fid.dirents = nil 455 | fid.direntends = nil 456 | for i := 0; i < len(fid.dirs); i++ { 457 | path := fid.path + "/" + fid.dirs[i].Name() 458 | st, _ := dir2Dir(path, fid.dirs[i], req.Conn.Dotu, req.Conn.Srv.Upool) 459 | if st == nil { 460 | continue 461 | } 462 | b := PackDir(st, req.Conn.Dotu) 463 | fid.dirents = append(fid.dirents, b...) 464 | count += len(b) 465 | fid.direntends = append(fid.direntends, count) 466 | } 467 | } 468 | 469 | switch { 470 | case tc.Offset > uint64(len(fid.dirents)): 471 | count = 0 472 | case len(fid.dirents[tc.Offset:]) > int(tc.Count): 473 | count = int(tc.Count) 474 | default: 475 | count = len(fid.dirents[tc.Offset:]) 476 | } 477 | 478 | if !*Akaros { 479 | nextend := sort.SearchInts(fid.direntends, int(tc.Offset)+count) 480 | if nextend < len(fid.direntends) { 481 | if fid.direntends[nextend] > int(tc.Offset)+count { 482 | if nextend > 0 { 483 | count = fid.direntends[nextend-1] - int(tc.Offset) 484 | } else { 485 | count = 0 486 | } 487 | } 488 | } 489 | if count == 0 && int(tc.Offset) < len(fid.dirents) && len(fid.dirents) > 0 { 490 | req.RespondError(&Error{"too small read size for dir entry", EINVAL}) 491 | return 492 | } 493 | } 494 | 495 | copy(rc.Data, fid.dirents[tc.Offset:int(tc.Offset)+count]) 496 | 497 | } else { 498 | count, e = fid.file.ReadAt(rc.Data, int64(tc.Offset)) 499 | if e != nil && e != io.EOF { 500 | req.RespondError(toError(e)) 501 | return 502 | } 503 | 504 | } 505 | 506 | SetRreadCount(rc, uint32(count)) 507 | req.Respond() 508 | } 509 | 510 | func (*Ufs) Write(req *SrvReq) { 511 | fid := req.Fid.Aux.(*ufsFid) 512 | tc := req.Tc 513 | err := fid.stat() 514 | if err != nil { 515 | req.RespondError(err) 516 | return 517 | } 518 | 519 | n, e := fid.file.WriteAt(tc.Data, int64(tc.Offset)) 520 | if e != nil { 521 | req.RespondError(toError(e)) 522 | return 523 | } 524 | 525 | req.RespondRwrite(uint32(n)) 526 | } 527 | 528 | func (*Ufs) Clunk(req *SrvReq) { req.RespondRclunk() } 529 | 530 | func (*Ufs) Remove(req *SrvReq) { 531 | fid := req.Fid.Aux.(*ufsFid) 532 | err := fid.stat() 533 | if err != nil { 534 | req.RespondError(err) 535 | return 536 | } 537 | 538 | e := os.Remove(fid.path) 539 | if e != nil { 540 | req.RespondError(toError(e)) 541 | return 542 | } 543 | 544 | req.RespondRremove() 545 | } 546 | 547 | func (*Ufs) Stat(req *SrvReq) { 548 | fid := req.Fid.Aux.(*ufsFid) 549 | err := fid.stat() 550 | if err != nil { 551 | req.RespondError(err) 552 | return 553 | } 554 | 555 | st, derr := dir2Dir(fid.path, fid.st, req.Conn.Dotu, req.Conn.Srv.Upool) 556 | if st == nil { 557 | req.RespondError(derr) 558 | return 559 | } 560 | 561 | req.RespondRstat(st) 562 | } 563 | 564 | func lookup(uid string, group bool) (uint32, *Error) { 565 | if uid == "" { 566 | return NOUID, nil 567 | } 568 | usr, e := user.Lookup(uid) 569 | if e != nil { 570 | return NOUID, toError(e) 571 | } 572 | conv := usr.Uid 573 | if group { 574 | conv = usr.Gid 575 | } 576 | u, e := strconv.Atoi(conv) 577 | if e != nil { 578 | return NOUID, toError(e) 579 | } 580 | return uint32(u), nil 581 | } 582 | 583 | func (u *Ufs) Wstat(req *SrvReq) { 584 | fid := req.Fid.Aux.(*ufsFid) 585 | err := fid.stat() 586 | if err != nil { 587 | req.RespondError(err) 588 | return 589 | } 590 | 591 | dir := &req.Tc.Dir 592 | if dir.Mode != 0xFFFFFFFF { 593 | mode := dir.Mode & 0777 594 | if req.Conn.Dotu { 595 | if dir.Mode&DMSETUID > 0 { 596 | mode |= syscall.S_ISUID 597 | } 598 | if dir.Mode&DMSETGID > 0 { 599 | mode |= syscall.S_ISGID 600 | } 601 | } 602 | e := os.Chmod(fid.path, os.FileMode(mode)) 603 | if e != nil { 604 | req.RespondError(toError(e)) 605 | return 606 | } 607 | } 608 | 609 | uid, gid := NOUID, NOUID 610 | if req.Conn.Dotu { 611 | uid = dir.Uidnum 612 | gid = dir.Gidnum 613 | } 614 | 615 | // Try to find local uid, gid by name. 616 | if (dir.Uid != "" || dir.Gid != "") && !req.Conn.Dotu { 617 | uid, err = lookup(dir.Uid, false) 618 | if err != nil { 619 | req.RespondError(err) 620 | return 621 | } 622 | 623 | // BUG(akumar): Lookup will never find gids 624 | // corresponding to group names, because 625 | // it only operates on user names. 626 | gid, err = lookup(dir.Gid, true) 627 | if err != nil { 628 | req.RespondError(err) 629 | return 630 | } 631 | } 632 | 633 | if uid != NOUID || gid != NOUID { 634 | e := os.Chown(fid.path, int(uid), int(gid)) 635 | if e != nil { 636 | req.RespondError(toError(e)) 637 | return 638 | } 639 | } 640 | 641 | if dir.Name != "" { 642 | fmt.Printf("Rename %s to %s\n", fid.path, dir.Name) 643 | // if first char is / it is relative to root, else relative to 644 | // cwd. 645 | var destpath string 646 | if dir.Name[0] == '/' { 647 | destpath = path.Join(u.Root, dir.Name) 648 | fmt.Printf("/ results in %s\n", destpath) 649 | } else { 650 | fiddir, _ := path.Split(fid.path) 651 | destpath = path.Join(fiddir, dir.Name) 652 | fmt.Printf("rel results in %s\n", destpath) 653 | } 654 | err := syscall.Rename(fid.path, destpath) 655 | fmt.Printf("rename %s to %s gets %v\n", fid.path, destpath, err) 656 | if err != nil { 657 | req.RespondError(toError(err)) 658 | return 659 | } 660 | fid.path = destpath 661 | } 662 | 663 | if dir.Length != 0xFFFFFFFFFFFFFFFF { 664 | e := os.Truncate(fid.path, int64(dir.Length)) 665 | if e != nil { 666 | req.RespondError(toError(e)) 667 | return 668 | } 669 | } 670 | 671 | // If either mtime or atime need to be changed, then 672 | // we must change both. 673 | if dir.Mtime != ^uint32(0) || dir.Atime != ^uint32(0) { 674 | mt, at := time.Unix(int64(dir.Mtime), 0), time.Unix(int64(dir.Atime), 0) 675 | if cmt, cat := (dir.Mtime == ^uint32(0)), (dir.Atime == ^uint32(0)); cmt || cat { 676 | st, e := os.Stat(fid.path) 677 | if e != nil { 678 | req.RespondError(toError(e)) 679 | return 680 | } 681 | switch cmt { 682 | case true: 683 | mt = st.ModTime() 684 | default: 685 | //at = time.Time(0)//atime(st.Sys().(*syscall.Stat_t)) 686 | } 687 | } 688 | e := os.Chtimes(fid.path, at, mt) 689 | if e != nil { 690 | req.RespondError(toError(e)) 691 | return 692 | } 693 | } 694 | 695 | req.RespondRwstat() 696 | } 697 | 698 | /* enables "Akaros" capabilities, which right now means 699 | * a sane error message format. 700 | */ 701 | var Akaros = flag.Bool("akaros", false, "Akaros extensions") 702 | -------------------------------------------------------------------------------- /ufs/ufs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "github.com/rminnich/go9p" 11 | "log" 12 | ) 13 | 14 | var addr = flag.String("addr", ":5640", "network address") 15 | var debug = flag.Int("debug", 0, "print debug messages") 16 | var root = flag.String("root", "/", "root filesystem") 17 | 18 | func main() { 19 | flag.Parse() 20 | ufs := new(go9p.Ufs) 21 | ufs.Dotu = true 22 | ufs.Id = "ufs" 23 | ufs.Root = *root 24 | ufs.Debuglevel = *debug 25 | ufs.Start(ufs) 26 | 27 | fmt.Print("ufs starting\n") 28 | // determined by build tags 29 | //extraFuncs() 30 | err := ufs.StartNetListener("tcp", *addr) 31 | if err != nil { 32 | log.Println(err) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /unpack.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go9p Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package go9p 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | // Creates a Fcall value from the on-the-wire representation. If 12 | // dotu is true, reads 9P2000.u messages. Returns the unpacked message, 13 | // error and how many bytes from the buffer were used by the message. 14 | func Unpack(buf []byte, dotu bool) (fc *Fcall, err error, fcsz int) { 15 | var m uint16 16 | 17 | if len(buf) < 7 { 18 | return nil, &Error{"buffer too short", EINVAL}, 0 19 | } 20 | 21 | fc = new(Fcall) 22 | fc.Fid = NOFID 23 | fc.Afid = NOFID 24 | fc.Newfid = NOFID 25 | 26 | p := buf 27 | fc.Size, p = gint32(p) 28 | fc.Type, p = gint8(p) 29 | fc.Tag, p = gint16(p) 30 | 31 | if int(fc.Size) > len(buf) || fc.Size < 7 { 32 | return nil, &Error{fmt.Sprintf("buffer too short: %d expected %d", 33 | len(buf), fc.Size), 34 | EINVAL}, 35 | 0 36 | } 37 | 38 | p = p[0 : fc.Size-7] 39 | fc.Pkt = buf[0:fc.Size] 40 | fcsz = int(fc.Size) 41 | if fc.Type < Tversion || fc.Type >= Tlast { 42 | return nil, &Error{"invalid id", EINVAL}, 0 43 | } 44 | 45 | var sz uint32 46 | if dotu { 47 | sz = minFcsize[fc.Type-Tversion] 48 | } else { 49 | sz = minFcusize[fc.Type-Tversion] 50 | } 51 | 52 | if fc.Size < sz { 53 | goto szerror 54 | } 55 | 56 | err = nil 57 | switch fc.Type { 58 | default: 59 | return nil, &Error{"invalid message id", EINVAL}, 0 60 | 61 | case Tversion, Rversion: 62 | fc.Msize, p = gint32(p) 63 | fc.Version, p = gstr(p) 64 | if p == nil { 65 | goto szerror 66 | } 67 | 68 | case Tauth: 69 | fc.Afid, p = gint32(p) 70 | fc.Uname, p = gstr(p) 71 | if p == nil { 72 | goto szerror 73 | } 74 | 75 | fc.Aname, p = gstr(p) 76 | if p == nil { 77 | goto szerror 78 | } 79 | 80 | if dotu { 81 | if len(p) > 0 { 82 | fc.Unamenum, p = gint32(p) 83 | } else { 84 | fc.Unamenum = NOUID 85 | } 86 | } else { 87 | fc.Unamenum = NOUID 88 | } 89 | 90 | case Rauth, Rattach: 91 | p = gqid(p, &fc.Qid) 92 | 93 | case Tflush: 94 | fc.Oldtag, p = gint16(p) 95 | 96 | case Tattach: 97 | fc.Fid, p = gint32(p) 98 | fc.Afid, p = gint32(p) 99 | fc.Uname, p = gstr(p) 100 | if p == nil { 101 | goto szerror 102 | } 103 | 104 | fc.Aname, p = gstr(p) 105 | if p == nil { 106 | goto szerror 107 | } 108 | 109 | if dotu { 110 | if len(p) > 0 { 111 | fc.Unamenum, p = gint32(p) 112 | } else { 113 | fc.Unamenum = NOUID 114 | } 115 | } 116 | 117 | case Rerror: 118 | fc.Error, p = gstr(p) 119 | if p == nil { 120 | goto szerror 121 | } 122 | if dotu { 123 | fc.Errornum, p = gint32(p) 124 | } else { 125 | fc.Errornum = 0 126 | } 127 | 128 | case Twalk: 129 | fc.Fid, p = gint32(p) 130 | fc.Newfid, p = gint32(p) 131 | m, p = gint16(p) 132 | fc.Wname = make([]string, m) 133 | for i := 0; i < int(m); i++ { 134 | fc.Wname[i], p = gstr(p) 135 | if p == nil { 136 | goto szerror 137 | } 138 | } 139 | 140 | case Rwalk: 141 | m, p = gint16(p) 142 | fc.Wqid = make([]Qid, m) 143 | for i := 0; i < int(m); i++ { 144 | p = gqid(p, &fc.Wqid[i]) 145 | } 146 | 147 | case Topen: 148 | fc.Fid, p = gint32(p) 149 | fc.Mode, p = gint8(p) 150 | 151 | case Ropen, Rcreate: 152 | p = gqid(p, &fc.Qid) 153 | fc.Iounit, p = gint32(p) 154 | 155 | case Tcreate: 156 | fc.Fid, p = gint32(p) 157 | fc.Name, p = gstr(p) 158 | if p == nil { 159 | goto szerror 160 | } 161 | fc.Perm, p = gint32(p) 162 | fc.Mode, p = gint8(p) 163 | if dotu { 164 | fc.Ext, p = gstr(p) 165 | if p == nil { 166 | goto szerror 167 | } 168 | } 169 | 170 | case Tread: 171 | fc.Fid, p = gint32(p) 172 | fc.Offset, p = gint64(p) 173 | fc.Count, p = gint32(p) 174 | 175 | case Rread: 176 | fc.Count, p = gint32(p) 177 | if len(p) < int(fc.Count) { 178 | goto szerror 179 | } 180 | fc.Data = p 181 | p = p[fc.Count:] 182 | 183 | case Twrite: 184 | fc.Fid, p = gint32(p) 185 | fc.Offset, p = gint64(p) 186 | fc.Count, p = gint32(p) 187 | if len(p) != int(fc.Count) { 188 | fc.Data = make([]byte, fc.Count) 189 | copy(fc.Data, p) 190 | p = p[len(p):] 191 | } else { 192 | fc.Data = p 193 | p = p[fc.Count:] 194 | } 195 | 196 | case Rwrite: 197 | fc.Count, p = gint32(p) 198 | 199 | case Tclunk, Tremove, Tstat: 200 | fc.Fid, p = gint32(p) 201 | 202 | case Rstat: 203 | m, p = gint16(p) 204 | p, err = gstat(p, &fc.Dir, dotu) 205 | if err != nil { 206 | return nil, err, 0 207 | } 208 | 209 | case Twstat: 210 | fc.Fid, p = gint32(p) 211 | m, p = gint16(p) 212 | p, _ = gstat(p, &fc.Dir, dotu) 213 | 214 | case Rflush, Rclunk, Rremove, Rwstat: 215 | } 216 | 217 | if len(p) > 0 { 218 | goto szerror 219 | } 220 | 221 | return 222 | 223 | szerror: 224 | return nil, &Error{"invalid size", EINVAL}, 0 225 | } 226 | --------------------------------------------------------------------------------