server.go (31373B)
1 package lib9p 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "io/fs" 8 "log" 9 "os" 10 "path" 11 "strings" 12 "sync" 13 ) 14 15 var ( 16 ErrBotch = fmt.Errorf("botch") 17 ErrPerm = fmt.Errorf("permission denied") 18 ErrOperation = fmt.Errorf("operation not supported") 19 ErrDupTag = fmt.Errorf("duplicate tag") 20 ErrUnknownFid = fmt.Errorf("unknown fid") 21 ErrDupFid = fmt.Errorf("duplicate fid") 22 ErrNotFound = fmt.Errorf("not found") 23 ) 24 25 func setError(r *request, err error) { 26 r.ofcall = &RError{ 27 Ename: err, 28 } 29 } 30 31 // A Server is a 9P server. 32 type Server struct { 33 // If true, the server prints the transcript of the 9P session 34 // to os.Stderr 35 chatty9P bool 36 37 // The file system to export via 9P. 38 fsmap map[string]FS 39 40 // Auth function is passed an auth request when a TAuth message arrives 41 // If authentication is desired, Auth should 42 // set request.Afid.file to an *AuthFile and request.ofcall.Qid, and prepare to 43 // authenticate via the Read()/Write() calls to request.Afid.file. 44 // Auth should clean up everything it creates when ctx is canceled. 45 // If this is nil, no authentication is performed. 46 Auth func(ctx context.Context, r *request) error 47 } 48 49 // NewServer creates a Server. 50 // It serves fsys with the aname "". 51 func NewServer(fsys FS) *Server { 52 s := &Server{ 53 fsmap: map[string]FS{"": fsys}, 54 } 55 return s 56 } 57 58 // NewServerMap creates a Server. 59 // Fsmap is the map of aname-FS pair. 60 func NewServerMap(fsmap map[string]FS) *Server { 61 s := &Server{ 62 fsmap: fsmap, 63 } 64 return s 65 } 66 67 // Chatty enables the server's log messages of 9P messages. 68 func (s *Server) Chatty() { s.chatty9P = true } 69 70 type conn struct { 71 s *Server 72 // Maximum length in byte of 9P messages. 73 msize uint32 74 // Mutex to change msize. 75 mSizeLock *sync.Mutex 76 77 // FidPool of the connection. 78 fPool *fidPool 79 80 // The channel from which incoming requests are delivered by the 81 // listener goroutine. 82 listenChan <-chan *request 83 // r is the io.Reader which the listener goroutine reads from. 84 // It should be accessed only by the listener goroutine. 85 r io.Reader 86 87 // The channel to which outgoing replies are sent to the responder 88 // goroutine. 89 respChan chan *request 90 91 // w is the io.Writer which the responder goroutine writes to. 92 // It should be accessed only by the responder goroutine. 93 w io.Writer 94 } 95 96 func (s *Server) newConn(r io.Reader, w io.Writer) *conn { 97 return &conn{ 98 s: s, 99 msize: 8 * 1024, 100 mSizeLock: new(sync.Mutex), 101 fPool: newFidPool(), 102 r: r, 103 w: w, 104 } 105 } 106 107 // mSize reads the maximum message size of the server. 108 func (c *conn) mSize() uint32 { 109 c.mSizeLock.Lock() 110 defer c.mSizeLock.Unlock() 111 return c.msize 112 } 113 114 // setMSize changes the server's maximum message size. 115 func (c *conn) setMSize(mSize uint32) { 116 c.mSizeLock.Lock() 117 defer c.mSizeLock.Unlock() 118 c.msize = mSize 119 } 120 121 // runListener runs the listener goroutine. 122 // Listener goroutine reads 9P messages from s.r by calling getReq 123 // and sends them to c.listenChan. 124 func (c *conn) runListener(ctx context.Context, rp *reqPool) { 125 rc := make(chan *request) 126 c.listenChan = rc 127 go func() { 128 defer close(rc) 129 for { 130 select { 131 case rc <- getReq(c.r, rp, c.s.chatty9P): 132 case <-ctx.Done(): 133 return 134 } 135 } 136 }() 137 } 138 139 // runResponder runs the responder goroutine. 140 // Responder goroutine waits for reply Requests from c.respChan, 141 // and marshalls each of them into 9P messages and writes it to c.w. 142 func (c *conn) runResponder(ctx context.Context, rp *reqPool) { 143 rc := make(chan *request) 144 c.respChan = rc 145 go func() { 146 for { 147 select { 148 case r, ok := <-rc: 149 if !ok { 150 return 151 } 152 r.ofcall.SetTag(r.tag) 153 rp.delete(r.tag) 154 _, err := c.w.Write(r.ofcall.marshal()) 155 if err != nil { 156 // TODO: handle error. 157 log.Printf("respond: %v", err) 158 continue 159 } 160 if c.s.chatty9P { 161 fmt.Fprintf(os.Stderr, "--> %s\n", r.ofcall) 162 } 163 case <-ctx.Done(): 164 return 165 } 166 } 167 }() 168 } 169 170 // GetReq reads 9P message from r, allocates request, adds it to rp, 171 // and returns it. 172 // Any error it encountered is embedded into the request struct. 173 // This function is called only by the server's listener goroutine, 174 // and does not need to lock r. 175 func getReq(r io.Reader, rp *reqPool, chatty bool) *request { 176 ifcall, err := RecvMsg(r) 177 if err != nil { 178 if err == io.EOF { 179 return &request{listenErr: err} 180 } 181 return &request{listenErr: fmt.Errorf("readMsg: %v", err)} 182 } 183 req, err := rp.add(ifcall.GetTag()) 184 if err != nil { 185 // duplicate tag: cons up a fake request 186 req := new(request) 187 req.ifcall = ifcall 188 req.listenErr = ErrDupTag 189 if chatty { 190 fmt.Fprintf(os.Stderr, "<-- %v\n", req.ifcall) 191 } 192 return req 193 } 194 req.tag = ifcall.GetTag() 195 req.ifcall = ifcall 196 if ifcall, ok := req.ifcall.(*TFlush); ok { 197 req.oldreq, _ = rp.lookup(ifcall.Oldtag) 198 } 199 if chatty { 200 fmt.Fprintf(os.Stderr, "<-- %v\n", req.ifcall) 201 } 202 return req 203 } 204 205 // sVersion processes Tversion messages. 206 // It checks if the version string is "9P2000", and if the version differs from 207 // that, it replies with version string "unknown". 208 // It also checks the msize of the message and if it is bigger than that of the 209 // server, it replies with msize being the server's mSize, otherwise it sets the 210 // server's mSize to the message's one. 211 func sVersion(ctx context.Context, c *conn, rc <-chan *request) { 212 for { 213 select { 214 case <-ctx.Done(): 215 return 216 case r, ok := <-rc: 217 if !ok { 218 return 219 } 220 ifcall := r.ifcall.(*TVersion) 221 version := ifcall.Version 222 if strings.HasPrefix(version, "9P2000") { 223 version = "9P2000" 224 } else { 225 version = "unknown" 226 } 227 msize := ifcall.Msize 228 if msize > c.mSize() { 229 msize = c.mSize() 230 } 231 r.ofcall = &RVersion{ 232 Msize: msize, 233 Version: version, 234 } 235 c.setMSize(r.ofcall.(*RVersion).Msize) 236 select { 237 case c.respChan <- r: 238 case <-ctx.Done(): 239 return 240 } 241 } 242 } 243 } 244 245 // sAuth serves Tauth message. 246 func sAuth(ctx context.Context, c *conn, rc <-chan *request) { 247 for { 248 select { 249 case <-ctx.Done(): 250 return 251 case r, ok := <-rc: 252 if !ok { 253 return 254 } 255 var ( 256 err error 257 ifcall *TAuth 258 ) 259 if c.s.Auth == nil { 260 r.err = fmt.Errorf("authentication not required") 261 goto resp 262 } 263 ifcall = r.ifcall.(*TAuth) 264 if _, ok := c.s.fsmap[ifcall.Aname]; !ok { 265 r.err = fmt.Errorf("no such file system") 266 goto resp 267 } 268 if ifcall.Afid == NOFID { 269 r.err = fmt.Errorf("NOFID can't be used for afid") 270 goto resp 271 } 272 r.afid, err = c.fPool.add(ifcall.Afid) 273 if err != nil { 274 r.err = ErrDupFid 275 goto resp 276 } 277 resp: 278 if r.err != nil { 279 setError(r, r.err) 280 } else { 281 if err := c.s.Auth(ctx, r); err != nil { 282 setError(r, fmt.Errorf("auth: %v", err)) 283 } else { 284 // TODO: should move this code to c.s.Auth? 285 if rauth, ok := r.ofcall.(*RAuth); ok { 286 r.afid.qidpath = rauth.Aqid.Path 287 } 288 } 289 } 290 select { 291 case c.respChan <- r: 292 case <-ctx.Done(): 293 return 294 } 295 } 296 } 297 } 298 299 func sAttach(ctx context.Context, c *conn, rc <-chan *request) { 300 for { 301 select { 302 case <-ctx.Done(): 303 return 304 case r, ok := <-rc: 305 if !ok { 306 return 307 } 308 var ( 309 fsys FS 310 fi fs.FileInfo 311 err error 312 ) 313 ifcall := r.ifcall.(*TAttach) 314 switch { 315 case c.s.Auth == nil && ifcall.Afid == NOFID: 316 case c.s.Auth == nil && ifcall.Afid != NOFID: 317 r.err = ErrBotch 318 goto resp 319 case c.s.Auth != nil && ifcall.Afid == NOFID: 320 r.err = fmt.Errorf("authentication required") 321 goto resp 322 case c.s.Auth != nil && ifcall.Afid != NOFID: 323 afid, ok := c.fPool.lookup(ifcall.Afid) 324 if !ok { 325 r.err = ErrUnknownFid 326 goto resp 327 } 328 af, ok := afid.file.(*AuthFile) 329 if !ok { 330 r.err = fmt.Errorf("not auth file") 331 goto resp 332 } 333 if af.Uname != ifcall.Uname || af.Aname != ifcall.Aname || !af.AuthOK { 334 r.err = fmt.Errorf("not authenticated") 335 goto resp 336 } 337 } 338 r.fid, err = c.fPool.add(ifcall.Fid) 339 if err != nil { 340 r.err = ErrDupFid 341 goto resp 342 } 343 r.fid.path = "." 344 r.fid.uid = ifcall.Uname 345 fsys, ok = c.s.fsmap[ifcall.Aname] 346 if !ok { 347 r.err = fmt.Errorf("no such file system") 348 goto resp 349 } 350 r.fid.fs = fsys 351 fi, err = fs.Stat(ExportFS{c.s.fsmap[ifcall.Aname]}, ".") 352 if err != nil { 353 r.err = fmt.Errorf("stat root: %v", err) 354 goto resp 355 } 356 r.fid.qidpath = fi.Sys().(*Stat).Qid.Path 357 r.ofcall = &RAttach{ 358 Qid: fi.Sys().(*Stat).Qid, 359 } 360 resp: 361 if r.err != nil { 362 if r.fid != nil { 363 c.fPool.delete(r.fid.fid) 364 } 365 setError(r, r.err) 366 } 367 select { 368 case c.respChan <- r: 369 case <-ctx.Done(): 370 return 371 } 372 } 373 } 374 } 375 376 func sFlush(ctx context.Context, c *conn, rc <-chan *request) { 377 for { 378 select { 379 case <-ctx.Done(): 380 return 381 case r, ok := <-rc: 382 if !ok { 383 return 384 } 385 if r.oldreq != nil { 386 r.oldreq.flush() 387 } 388 r.ofcall = &RFlush{} 389 select { 390 case c.respChan <- r: 391 case <-ctx.Done(): 392 return 393 } 394 } 395 } 396 } 397 398 // TODO: implement MAXWELEM. 399 func sWalk(ctx context.Context, c *conn, rc <-chan *request) { 400 for { 401 select { 402 case <-ctx.Done(): 403 return 404 case r, ok := <-rc: 405 if !ok { 406 return 407 } 408 var ( 409 err error 410 oldfi fs.FileInfo 411 newFid *fid 412 wqids []Qid 413 cwdp string 414 ) 415 ifcall := r.ifcall.(*TWalk) 416 oldFid, ok := c.fPool.lookup(ifcall.Fid) 417 if !ok { 418 r.err = ErrUnknownFid 419 goto resp 420 } 421 if oldFid.omode != -1 { 422 r.err = fmt.Errorf("cannot clone open fid") 423 goto resp 424 } 425 oldfi, err = fs.Stat(ExportFS{oldFid.fs}, oldFid.path) 426 if err != nil { 427 r.err = fmt.Errorf("stat: %v", err) 428 goto resp 429 } 430 if len(ifcall.Wnames) > 0 && oldfi.Sys().(*Stat).Qid.Type&QTDIR == 0 { 431 r.err = fmt.Errorf("walk on non-dir") 432 goto resp 433 } 434 if ifcall.Fid == ifcall.Newfid { 435 newFid = oldFid 436 } else { 437 newFid, err = c.fPool.add(ifcall.Newfid) 438 if err != nil { 439 r.err = fmt.Errorf("alloc: %v", err) 440 goto resp 441 } 442 } 443 wqids = make([]Qid, 0, len(ifcall.Wnames)) 444 cwdp = oldFid.path 445 for _, name := range ifcall.Wnames { 446 cwdp = path.Clean(path.Join(cwdp, name)) 447 if cwdp == ".." { 448 cwdp = "." // parent of the root is itself. 449 } 450 stat, err := fs.Stat(ExportFS{oldFid.fs}, cwdp) 451 if err != nil { 452 break 453 } 454 wqids = append(wqids, stat.Sys().(*Stat).Qid) 455 } 456 if len(wqids) == 0 { 457 newFid.qidpath = oldFid.qidpath 458 } else { 459 newFid.qidpath = wqids[len(wqids)-1].Path 460 } 461 newFid.path = cwdp 462 newFid.uid = oldFid.uid 463 newFid.fs = oldFid.fs 464 r.ofcall = &RWalk{ 465 Qids: wqids, 466 } 467 resp: 468 if r.ofcall == nil { 469 setError(r, r.err) 470 select { 471 case c.respChan <- r: 472 continue 473 case <-ctx.Done(): 474 return 475 } 476 } 477 ofcall := r.ofcall.(*RWalk) 478 if r.err != nil || len(ofcall.Qids) < len(ifcall.Wnames) { 479 if ifcall.Fid != ifcall.Newfid { 480 c.fPool.delete(ifcall.Newfid) 481 } 482 if len(ofcall.Qids) == 0 { 483 if r.err == nil && len(ifcall.Wnames) != 0 { 484 setError(r, ErrNotFound) 485 } 486 } 487 } 488 select { 489 case c.respChan <- r: 490 case <-ctx.Done(): 491 return 492 } 493 } 494 } 495 } 496 497 func sOpen(ctx context.Context, c *conn, rc <-chan *request) { 498 for { 499 select { 500 case <-ctx.Done(): 501 return 502 case r, ok := <-rc: 503 if !ok { 504 return 505 } 506 var ( 507 p fs.FileMode 508 err error 509 qid Qid 510 fi fs.FileInfo 511 ) 512 ifcall := r.ifcall.(*TOpen) 513 r.fid, ok = c.fPool.lookup(ifcall.Fid) 514 if !ok { 515 r.err = ErrUnknownFid 516 goto resp 517 } 518 if r.fid.omode != -1 { 519 r.err = ErrBotch 520 goto resp 521 } 522 if afile, ok := r.fid.file.(*AuthFile); ok { 523 // c.s.Auth should set r.fid.file to a valid *AuthFile, 524 // so r.fid.file should not be nil. 525 fi, err = r.fid.file.Stat() 526 if err != nil { 527 r.err = fmt.Errorf("stat: %v", err) 528 goto resp 529 } 530 qid = afile.Qid 531 } else { 532 fi, err = fs.Stat(ExportFS{r.fid.fs}, r.fid.path) 533 if err != nil { 534 r.err = fmt.Errorf("stat: %v", err) 535 goto resp 536 } 537 qid = fi.Sys().(*Stat).Qid 538 } 539 // Write attempt to a directory is prohibitted by the protocol. 540 // In plan9 implementation, ifcall.Mode is ANDed with ^ORCLOSE, 541 // but ORCLOSE is also prohibitted by the protocol... 542 if qid.Type == QTDIR && ifcall.Mode != OREAD { 543 r.err = fmt.Errorf("is a directory") 544 goto resp 545 } 546 switch ifcall.Mode & 3 { 547 case OREAD: 548 p = AREAD 549 case OWRITE: 550 p = AWRITE 551 case ORDWR: 552 p = AREAD | AWRITE 553 case OEXEC: 554 p = AEXEC 555 } 556 if ifcall.Mode&OTRUNC != 0 { 557 p |= AWRITE 558 } 559 if qid.Type&QTDIR != 0 && p != AREAD { 560 r.err = ErrPerm 561 goto resp 562 } 563 if !hasPerm(r.fid.fs, fi, r.fid.uid, p) { 564 r.err = ErrPerm 565 goto resp 566 } 567 if ifcall.Mode&ORCLOSE != 0 { 568 parentPath := path.Dir(r.fid.path) 569 fi, err := fs.Stat(ExportFS{r.fid.fs}, parentPath) 570 if err != nil { 571 r.err = fmt.Errorf("stat parent: %v", err) 572 goto resp 573 } 574 if !hasPerm(r.fid.fs, fi, r.fid.uid, AWRITE) { 575 r.err = ErrPerm 576 goto resp 577 } 578 } 579 r.ofcall = &ROpen{ 580 Qid: qid, 581 Iounit: c.mSize() - IOHDRSZ, 582 } 583 resp: 584 if r.err != nil { 585 setError(r, r.err) 586 goto send 587 } 588 if _, ok := r.fid.file.(*AuthFile); ok { 589 r.fid.omode = ifcall.Mode 590 goto send 591 } 592 r.fid.file, err = r.fid.fs.OpenFile(r.fid.path, ModeToFlag(ifcall.Mode)) 593 if err != nil { 594 setError(r, err) 595 goto send 596 } 597 fi, err = r.fid.file.Stat() 598 if err != nil { 599 r.fid.file.Close() 600 setError(r, err) 601 goto send 602 } 603 if fi.Sys().(*Stat).Qid.Path != r.fid.qidpath { 604 log.Println("open:", fi.Sys().(*Stat).Qid.Path, r.fid.qidpath) 605 r.fid.file.Close() 606 setError(r, fmt.Errorf("qid path mismatch")) 607 goto send 608 } 609 // omode should be set after successfully opening it. 610 r.fid.omode = ifcall.Mode 611 send: 612 select { 613 case c.respChan <- r: 614 case <-ctx.Done(): 615 return 616 } 617 } 618 } 619 } 620 621 func sCreate(ctx context.Context, c *conn, rc <-chan *request) { 622 for { 623 select { 624 case <-ctx.Done(): 625 return 626 case r, ok := <-rc: 627 if !ok { 628 return 629 } 630 var ( 631 fi fs.FileInfo 632 dirfi fs.FileInfo 633 err error 634 cfs CreatorFS 635 cpath string 636 perm FileMode 637 dirperm FileMode 638 ) 639 ifcall := r.ifcall.(*TCreate) 640 r.fid, ok = c.fPool.lookup(ifcall.Fid) 641 if !ok { 642 r.err = ErrUnknownFid 643 goto resp 644 } 645 if r.fid.omode != -1 { 646 r.err = ErrBotch 647 goto resp 648 } 649 dirfi, err = fs.Stat(ExportFS{r.fid.fs}, r.fid.path) 650 if err != nil { 651 r.err = fmt.Errorf("stat: %v", err) 652 goto resp 653 } 654 if !dirfi.IsDir() { 655 r.err = fmt.Errorf("create in non-dir") 656 goto resp 657 } 658 if !hasPerm(r.fid.fs, dirfi, r.fid.uid, AWRITE) { 659 r.err = ErrPerm 660 goto resp 661 } 662 cfs, ok = r.fid.fs.(CreatorFS) 663 if !ok { 664 r.err = ErrOperation 665 goto resp 666 } 667 perm = ifcall.Perm 668 dirperm = dirfi.Mode() 669 if perm&fs.ModeDir == 0 { 670 perm &= ^FileMode(0666) | (dirperm & FileMode(0666)) 671 } else { 672 perm &= ^FileMode(0777) | (dirperm & FileMode(0777)) 673 } 674 cpath = path.Join(r.fid.path, ifcall.Name) 675 r.fid.file, err = cfs.Create(cpath, r.fid.uid, ifcall.Mode, perm) 676 if err != nil { 677 r.err = fmt.Errorf("create: %v", err) 678 goto resp 679 } 680 r.fid.path = cpath 681 r.fid.omode = ifcall.Mode 682 fi, err = r.fid.file.Stat() 683 if err != nil { 684 r.err = fmt.Errorf("stat: %v", err) 685 goto resp 686 } 687 r.fid.qidpath = fi.Sys().(*Stat).Qid.Path 688 r.ofcall = &RCreate{ 689 Qid: fi.Sys().(*Stat).Qid, 690 Iounit: c.mSize() - IOHDRSZ, 691 } 692 resp: 693 if r.err != nil { 694 setError(r, r.err) 695 } 696 select { 697 case c.respChan <- r: 698 case <-ctx.Done(): 699 return 700 } 701 } 702 } 703 } 704 705 // TODO: I think the file should be locked while reading. 706 // or should the undeterminism left for the client side? 707 func sRead(ctx context.Context, c *conn, rc <-chan *request) { 708 for { 709 select { 710 case <-ctx.Done(): 711 return 712 case r, ok := <-rc: 713 if !ok { 714 return 715 } 716 var ( 717 fi fs.FileInfo 718 err error 719 data []byte 720 done chan struct{} 721 n int 722 ) 723 ifcall := r.ifcall.(*TRead) 724 r.fid, ok = c.fPool.lookup(ifcall.Fid) 725 if !ok { 726 r.err = ErrUnknownFid 727 goto resp 728 } 729 if r.fid.omode == -1 { 730 r.err = fmt.Errorf("not open") 731 goto resp 732 } 733 if r.fid.omode != OREAD && r.fid.omode != ORDWR && r.fid.omode != OEXEC { 734 r.err = ErrPerm 735 goto resp 736 } 737 fi, err = r.fid.file.Stat() 738 if err != nil { 739 r.err = fmt.Errorf("stat: %v", err) 740 goto resp 741 } 742 if c.mSize()-IOHDRSZ < ifcall.Count { 743 ifcall.Count = c.mSize() - IOHDRSZ 744 } 745 data = make([]byte, ifcall.Count) 746 done = make(chan struct{}) 747 go func() { 748 defer close(done) 749 if fi.IsDir() { 750 if ifcall.Offset != 0 && ifcall.Offset != r.fid.dirOffset { 751 err = fmt.Errorf("invalid dir offset") 752 return 753 } 754 if ifcall.Offset == 0 && r.fid.dirIndex != 0 { 755 r.fid.dirIndex = 0 756 r.fid.dirOffset = 0 757 r.fid.dirEnts = nil 758 if err = r.fid.file.Close(); err != nil { 759 return 760 } 761 r.fid.file, err = r.fid.fs.OpenFile(r.fid.path, ModeToFlag(r.fid.omode)) 762 if err != nil { 763 return 764 } 765 } 766 if r.fid.dirIndex == 0 { 767 r.fid.dirEnts, err = r.fid.file.(ReadDirFile).ReadDir(-1) 768 if err != nil { 769 return 770 } 771 } 772 k := r.fid.dirIndex 773 for ; k < len(r.fid.dirEnts); k++ { 774 fi, err := r.fid.dirEnts[k].Info() 775 if err != nil { 776 log.Println(err) 777 continue 778 } 779 st := fi.Sys().(*Stat) 780 buf := st.marshal() 781 if n+len(buf) > len(data) { 782 break 783 } 784 for i := 0; i < len(buf); i++ { 785 data[n+i] = buf[i] 786 } 787 n += len(buf) 788 } 789 r.fid.dirOffset += uint64(n) 790 r.fid.dirIndex += k 791 } else { 792 if reader, ok := r.fid.file.(io.ReaderAt); ok { 793 n, err = reader.ReadAt(data, int64(ifcall.Offset)) 794 } else { 795 n, err = r.fid.file.Read(data) 796 } 797 if err == io.EOF { 798 err = nil 799 } 800 } 801 }() 802 select { 803 case <-done: 804 if err != nil { 805 r.err = err 806 goto resp 807 } 808 case <-r.done: 809 continue 810 case <-ctx.Done(): 811 return 812 } 813 r.ofcall = &RRead{ 814 Count: uint32(n), 815 Data: data[:n], 816 } 817 resp: 818 if r.err != nil { 819 setError(r, r.err) 820 } 821 select { 822 case c.respChan <- r: 823 case <-ctx.Done(): 824 return 825 } 826 } 827 } 828 } 829 830 // TODO: I think the file should be locked while writing. 831 func sWrite(ctx context.Context, c *conn, rc <-chan *request) { 832 for { 833 select { 834 case <-ctx.Done(): 835 return 836 case r, ok := <-rc: 837 if !ok { 838 return 839 } 840 var ( 841 ofcall *RWrite 842 done chan struct{} 843 omode OpenMode 844 err error 845 ) 846 ifcall := r.ifcall.(*TWrite) 847 r.fid, ok = c.fPool.lookup(ifcall.Fid) 848 if !ok { 849 r.err = ErrUnknownFid 850 goto resp 851 } 852 if c.mSize()-IOHDRSZ < ifcall.Count { 853 ifcall.Count = c.mSize() - IOHDRSZ 854 } 855 omode = r.fid.omode & 3 856 if omode != OWRITE && omode != ORDWR { 857 r.err = fmt.Errorf("write on fid with open mode 0x%x", r.fid.omode) 858 goto resp 859 } 860 ofcall = new(RWrite) 861 done = make(chan struct{}) 862 go func() { 863 defer close(done) 864 var n int 865 switch file := r.fid.file.(type) { 866 case io.WriterAt: 867 n, err = file.WriteAt(ifcall.Data, int64(ifcall.Offset)) 868 if err != nil { 869 return 870 } 871 ofcall.Count = uint32(n) 872 case io.Writer: 873 n, err = file.Write(ifcall.Data) 874 if err != nil { 875 return 876 } 877 ofcall.Count = uint32(n) 878 default: 879 err = ErrOperation 880 return 881 } 882 }() 883 select { 884 case <-done: 885 if err != nil { 886 r.err = err 887 goto resp 888 } 889 case <-r.done: 890 continue 891 case <-ctx.Done(): 892 return 893 } 894 r.ofcall = ofcall 895 resp: 896 if r.err != nil { 897 setError(r, r.err) 898 } 899 select { 900 case c.respChan <- r: 901 case <-ctx.Done(): 902 return 903 } 904 } 905 } 906 } 907 908 func sClunk(ctx context.Context, c *conn, rc <-chan *request) { 909 for { 910 select { 911 case <-ctx.Done(): 912 return 913 case r, ok := <-rc: 914 if !ok { 915 return 916 } 917 ifcall := r.ifcall.(*TClunk) 918 r.fid, ok = c.fPool.lookup(ifcall.Fid) 919 if !ok { 920 r.err = ErrUnknownFid 921 goto resp 922 } 923 c.fPool.delete(ifcall.Fid) 924 if r.fid.omode != -1 { 925 if err := r.fid.file.Close(); err != nil { 926 r.err = fmt.Errorf("close: %v", err) 927 goto resp 928 } 929 if r.fid.omode&ORCLOSE != 0 { 930 rfs, ok := r.fid.fs.(RemoverFS) 931 if !ok { 932 r.err = ErrOperation 933 goto resp 934 } 935 if err := rfs.Remove(r.fid.path); err != nil { 936 r.err = err 937 goto resp 938 } 939 } 940 } 941 r.ofcall = &RClunk{} 942 resp: 943 if r.err != nil { 944 setError(r, r.err) 945 } 946 select { 947 case c.respChan <- r: 948 case <-ctx.Done(): 949 return 950 } 951 } 952 } 953 } 954 955 // sRemove serves Tremove messages. 956 // TODO: RemoverFS.Remove function takes as argument the file path to be 957 // removed. But in the protocol, files are identified by Qid.Path, not by 958 // path name from the root of the file system. 959 // But os package also uses the file path to remove an file. 960 // And the library should not care the inconsistency of Qid.Path? 961 func sRemove(ctx context.Context, c *conn, rc <-chan *request) { 962 for { 963 select { 964 case <-ctx.Done(): 965 return 966 case r, ok := <-rc: 967 if !ok { 968 return 969 } 970 var ( 971 parentPath string 972 pfi fs.FileInfo 973 fi fs.FileInfo 974 err error 975 rfs RemoverFS 976 ) 977 ifcall := r.ifcall.(*TRemove) 978 r.fid, ok = c.fPool.lookup(ifcall.Fid) 979 if !ok { 980 r.err = ErrUnknownFid 981 goto resp 982 } 983 c.fPool.delete(ifcall.Fid) 984 if r.fid.omode != -1 { 985 r.fid.file.Close() 986 } 987 parentPath = path.Dir(r.fid.path) 988 pfi, err = fs.Stat(ExportFS{r.fid.fs}, parentPath) 989 if err != nil { 990 r.err = fmt.Errorf("stat parent: %v", err) 991 goto resp 992 } 993 if !hasPerm(r.fid.fs, pfi, r.fid.uid, AWRITE) { 994 r.err = ErrPerm 995 goto resp 996 } 997 // BUG: race. Remove call below uses r.fid.path, so I need to 998 // check whether the underlying qid is the same. 999 // But other requests can move the same file and then create 1000 // new one with the same name. 1001 fi, err = fs.Stat(ExportFS{FS: r.fid.fs}, r.fid.path) 1002 if err != nil { 1003 r.err = err 1004 goto resp 1005 } 1006 if r.fid.qidpath != fi.Sys().(*Stat).Qid.Path { 1007 r.err = fmt.Errorf("qid path mismatch") 1008 goto resp 1009 } 1010 rfs, ok = r.fid.fs.(RemoverFS) 1011 if !ok { 1012 r.err = ErrOperation 1013 goto resp 1014 } 1015 // I think the argument of RemoverFS.Remove should be Qid.Path. 1016 if err = rfs.Remove(r.fid.path); err != nil { 1017 r.err = fmt.Errorf("remove: %v", err) 1018 goto resp 1019 } 1020 r.ofcall = &RRemove{} 1021 resp: 1022 if r.err != nil { 1023 setError(r, r.err) 1024 } 1025 select { 1026 case c.respChan <- r: 1027 case <-ctx.Done(): 1028 return 1029 } 1030 } 1031 } 1032 } 1033 1034 func sStat(ctx context.Context, c *conn, rc <-chan *request) { 1035 for { 1036 select { 1037 case <-ctx.Done(): 1038 return 1039 case r, ok := <-rc: 1040 if !ok { 1041 return 1042 } 1043 var ( 1044 fi fs.FileInfo 1045 err error 1046 ) 1047 ifcall := r.ifcall.(*TStat) 1048 r.fid, ok = c.fPool.lookup(ifcall.Fid) 1049 if !ok { 1050 r.err = ErrUnknownFid 1051 goto resp 1052 } 1053 fi, err = fs.Stat(ExportFS{r.fid.fs}, r.fid.path) 1054 if err != nil { 1055 r.err = fmt.Errorf("stat: %v", err) 1056 goto resp 1057 } 1058 r.ofcall = &RStat{ 1059 Stat: fi.Sys().(*Stat), 1060 } 1061 resp: 1062 if r.err != nil { 1063 setError(r, r.err) 1064 } 1065 select { 1066 case c.respChan <- r: 1067 case <-ctx.Done(): 1068 return 1069 } 1070 } 1071 } 1072 } 1073 1074 func sWStat(ctx context.Context, c *conn, rc <-chan *request) { 1075 for { 1076 select { 1077 case <-ctx.Done(): 1078 return 1079 case r, ok := <-rc: 1080 if !ok { 1081 return 1082 } 1083 var ( 1084 wsfile WriterStatFile 1085 wstat *Stat 1086 newStat *Stat 1087 fi fs.FileInfo 1088 err error 1089 ) 1090 ifcall := r.ifcall.(*TWstat) 1091 r.fid, ok = c.fPool.lookup(ifcall.Fid) 1092 if !ok { 1093 r.err = ErrUnknownFid 1094 goto resp 1095 } 1096 if r.fid.omode == -1 { 1097 var err error 1098 r.fid.file, err = r.fid.fs.OpenFile(r.fid.path, O_RDONLY) 1099 if err != nil { 1100 r.err = fmt.Errorf("open: %v", err) 1101 goto resp 1102 } 1103 } 1104 wsfile, ok = r.fid.file.(WriterStatFile) 1105 if !ok { 1106 r.err = ErrOperation 1107 goto resp 1108 } 1109 wstat = ifcall.Stat 1110 fi, err = r.fid.file.Stat() 1111 if err != nil { 1112 r.err = fmt.Errorf("stat: %v", err) 1113 goto resp 1114 } 1115 newStat = fi.Sys().(*Stat) 1116 if r.fid.qidpath != newStat.Qid.Path { 1117 r.err = fmt.Errorf("qid mismatch") 1118 goto resp 1119 } 1120 if wstat.Type != ^uint16(0) && wstat.Type != newStat.Type { 1121 r.err = fmt.Errorf("changing type is not permitted") 1122 goto resp 1123 } 1124 if wstat.Dev != ^uint32(0) && wstat.Dev != newStat.Dev { 1125 r.err = fmt.Errorf("changing dev is not permitted") 1126 goto resp 1127 } 1128 if wstat.Qid.Type != QidType(^uint8(0)) && wstat.Qid.Type != newStat.Qid.Type { 1129 r.err = fmt.Errorf("changing qid type is not permitted") 1130 goto resp 1131 } 1132 if wstat.Qid.Vers != ^uint32(0) && wstat.Qid.Vers != newStat.Qid.Vers { 1133 r.err = fmt.Errorf("changing qid vers is not permitted") 1134 goto resp 1135 } 1136 if wstat.Qid.Path != ^uint64(0) && wstat.Qid.Path != newStat.Qid.Path { 1137 r.err = fmt.Errorf("changing qid path is not permitted") 1138 goto resp 1139 } 1140 // TODO: some unix utilities (e.g. touch, mv, git) tries to change atime. 1141 // But changing atime is prohibited by the protocol. 1142 if wstat.Atime != ^uint32(0) && wstat.Atime != newStat.Atime { 1143 if r.fid.uid != newStat.Uid && !isGroupLeader(r.fid.fs, newStat.Gid, r.fid.uid) { 1144 r.err = ErrPerm 1145 goto resp 1146 } 1147 newStat.Atime = wstat.Atime 1148 //r.err = fmt.Errorf("changing atime is not permitted") 1149 //goto resp 1150 } 1151 if wstat.Uid != "" && wstat.Uid != newStat.Uid { 1152 r.err = fmt.Errorf("changing uid is not permitted") 1153 goto resp 1154 } 1155 if wstat.Muid != "" && wstat.Muid != newStat.Muid { 1156 r.err = fmt.Errorf("changing muid is not permitted") 1157 goto resp 1158 } 1159 if wstat.Name != "" && newStat.Name != wstat.Name { 1160 parentPath := path.Dir(r.fid.path) 1161 pstat, err := fs.Stat(ExportFS{r.fid.fs}, parentPath) 1162 if err != nil { 1163 r.err = fmt.Errorf("stat parent: %v", err) 1164 goto resp 1165 } 1166 if !hasPerm(r.fid.fs, pstat, r.fid.uid, AWRITE) { 1167 r.err = ErrPerm 1168 goto resp 1169 } 1170 // TODO: I think 9P protocol prohibits renaming to existent file. 1171 // Wstat(9P) in p9p says: 1172 // it is an error to change the name to that of 1173 // an existing file. 1174 // but 9pfs, 9pfuse does the rename when used with `git init`. 1175 /* 1176 de, err := fs.ReadDir(ExportFS{FS: r.fid.fs}, parentPath) 1177 if err != nil { 1178 r.err = fmt.Errorf("readdir: %v", err) 1179 goto resp 1180 } 1181 for _, e := range de { 1182 fi, err := e.Info() 1183 if err != nil { 1184 r.err = fmt.Errorf("stat: %v", err) 1185 } 1186 if fi.Name() == wstat.Name { 1187 r.err = fmt.Errorf("file already exists") 1188 goto resp 1189 } 1190 } 1191 */ 1192 newStat.Name = wstat.Name 1193 } 1194 if wstat.Length != ^int64(0) && wstat.Length != newStat.Length { 1195 // TODO: deal with wstat which changes directory length to 0 1196 if fi.IsDir() || !hasPerm(r.fid.fs, fi, r.fid.uid, AWRITE) { 1197 r.err = ErrPerm 1198 goto resp 1199 } 1200 newStat.Length = wstat.Length 1201 } 1202 if wstat.Mode != FileMode(^uint32(0)) && wstat.Mode != newStat.Mode { 1203 // the owner of the file or the group leader of the file'c group. 1204 if r.fid.uid != newStat.Uid && !isGroupLeader(r.fid.fs, newStat.Gid, r.fid.uid) { 1205 r.err = ErrPerm 1206 goto resp 1207 } 1208 if wstat.Mode&fs.ModeDir != newStat.Mode&fs.ModeDir { 1209 r.err = ErrPerm 1210 goto resp 1211 } 1212 newStat.Mode = wstat.Mode 1213 } 1214 if wstat.Mtime != ^uint32(0) && wstat.Mtime != newStat.Mtime { 1215 // the owner of the file or the group leader of the file'c group. 1216 if r.fid.uid != newStat.Uid && !isGroupLeader(r.fid.fs, newStat.Gid, r.fid.uid) { 1217 r.err = ErrPerm 1218 goto resp 1219 } 1220 newStat.Mtime = wstat.Mtime 1221 } 1222 if wstat.Gid != "" && wstat.Gid != newStat.Gid { 1223 // by the owner if also a member of the new group; 1224 // or by the group leader of the file'c current group if 1225 // also the leader of the new group. 1226 if r.fid.uid == newStat.Uid && isGroupMember(r.fid.fs, wstat.Gid, r.fid.uid) || 1227 isGroupLeader(r.fid.fs, newStat.Gid, r.fid.uid) && 1228 isGroupLeader(r.fid.fs, wstat.Gid, r.fid.uid) { 1229 newStat.Gid = wstat.Gid 1230 } else { 1231 r.err = ErrPerm 1232 goto resp 1233 } 1234 } 1235 err = wsfile.WStat(newStat) 1236 if err != nil { 1237 r.err = fmt.Errorf("wstat: %v", err) 1238 goto resp 1239 } 1240 if path.Base(r.fid.path) != newStat.Name { 1241 oldPath := r.fid.path 1242 r.fid.path = path.Join(path.Dir(oldPath), newStat.Name) 1243 // TODO: I think map[fid.path][]*fid is better. 1244 // I think there is a race condition. 1245 // e.g. create a file with the old name? 1246 c.fPool.Lock() 1247 for _, fid := range c.fPool.m { 1248 if fid.path == oldPath { 1249 fid.path = r.fid.path 1250 } 1251 } 1252 c.fPool.Unlock() 1253 } 1254 r.ofcall = &RWstat{} 1255 resp: 1256 if r.fid.omode == -1 && r.fid.file != nil { 1257 r.fid.file.Close() 1258 } 1259 if r.err != nil { 1260 setError(r, r.err) 1261 } 1262 select { 1263 case c.respChan <- r: 1264 case <-ctx.Done(): 1265 return 1266 } 1267 } 1268 } 1269 } 1270 1271 // Serve serves 9P conversation. 1272 func (s *Server) Serve(ctx context.Context, r io.Reader, w io.Writer) { 1273 ctx, cancel := context.WithCancel(ctx) 1274 defer cancel() 1275 rp := newReqPool() 1276 c := s.newConn(r, w) 1277 c.runListener(ctx, rp) 1278 c.runResponder(ctx, rp) 1279 var ( 1280 versionChan = make(chan *request) 1281 authChan = make(chan *request) 1282 attachChan = make(chan *request) 1283 flushChan = make(chan *request) 1284 walkChan = make(chan *request) 1285 openChan = make(chan *request) 1286 createChan = make(chan *request) 1287 readChan = make(chan *request) 1288 writeChan = make(chan *request) 1289 clunkChan = make(chan *request) 1290 removeChan = make(chan *request) 1291 statChan = make(chan *request) 1292 wstatChan = make(chan *request) 1293 ) 1294 defer func() { // TODO: unnecessary? 1295 close(versionChan) 1296 close(authChan) 1297 close(attachChan) 1298 close(flushChan) 1299 close(walkChan) 1300 close(openChan) 1301 close(createChan) 1302 close(readChan) 1303 close(writeChan) 1304 close(clunkChan) 1305 close(removeChan) 1306 close(statChan) 1307 close(wstatChan) 1308 }() 1309 go sVersion(ctx, c, versionChan) 1310 go sAuth(ctx, c, authChan) 1311 go sAttach(ctx, c, attachChan) 1312 go sFlush(ctx, c, flushChan) 1313 go sWalk(ctx, c, walkChan) 1314 go sOpen(ctx, c, openChan) 1315 go sCreate(ctx, c, createChan) 1316 go sRead(ctx, c, readChan) 1317 go sWrite(ctx, c, writeChan) 1318 go sClunk(ctx, c, clunkChan) 1319 go sRemove(ctx, c, removeChan) 1320 go sStat(ctx, c, statChan) 1321 go sWStat(ctx, c, wstatChan) 1322 L: 1323 for { 1324 select { 1325 case <-ctx.Done(): 1326 break L 1327 case r, ok := <-c.listenChan: 1328 if !ok { 1329 break L 1330 } 1331 if r.listenErr != nil { 1332 if r.listenErr == io.EOF { 1333 break L 1334 } 1335 log.Printf("listen: %v", r.listenErr) 1336 continue L 1337 } 1338 switch r.ifcall.(type) { 1339 default: 1340 setError(r, fmt.Errorf("unknown message type: %d", r.ifcall.Type())) 1341 select { 1342 case c.respChan <- r: 1343 case <-ctx.Done(): 1344 return 1345 } 1346 case *TVersion: 1347 versionChan <- r 1348 case *TAuth: 1349 authChan <- r 1350 case *TAttach: 1351 attachChan <- r 1352 case *TFlush: 1353 flushChan <- r 1354 case *TWalk: 1355 walkChan <- r 1356 case *TOpen: 1357 openChan <- r 1358 case *TCreate: 1359 createChan <- r 1360 case *TRead: 1361 readChan <- r 1362 case *TWrite: 1363 writeChan <- r 1364 case *TClunk: 1365 clunkChan <- r 1366 case *TRemove: 1367 removeChan <- r 1368 case *TStat: 1369 statChan <- r 1370 case *TWstat: 1371 wstatChan <- r 1372 } 1373 } 1374 } 1375 }