lib9p

Go 9P library.
Log | Files | Refs | LICENSE

commit bc92ce34656ccfb2ae4ca7a2b1c440f690578268
parent e7f453a45c687591ceb0b4a6f975966d074cd297
Author: Matsuda Kenji <info@mtkn.jp>
Date:   Fri, 22 Dec 2023 11:56:46 +0900

all pipeline

Diffstat:
Mserver.go | 1264+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
1 file changed, 758 insertions(+), 506 deletions(-)

diff --git a/server.go b/server.go @@ -459,7 +459,7 @@ func sWalk(ctx context.Context, s *Server, c <-chan *Req) { var err error newFid, err = s.fPool.add(ifcall.Newfid) if err != nil { - r.err = fmt.Errorf("alloc: $v", err) + r.err = fmt.Errorf("alloc: %v", err) rc <- r continue } @@ -522,542 +522,782 @@ func rWalk(ctx context.Context, c <-chan *Req) { } } -func sOpen(ctx context.Context, s *Server, r *Req) { - ifcall := r.Ifcall.(*TOpen) - var ok bool - r.Fid, ok = s.fPool.lookup(ifcall.Fid) - if !ok { - Respond(ctx, r, ErrUnknownFid) - return - } - if r.Fid.OMode != -1 { - Respond(ctx, r, ErrBotch) - return - } - var ( - err error - qid Qid - st fs.FileInfo - ) - if afile, ok := r.Fid.File.(*AuthFile); ok { - // s.Auth should set r.Fid.File to a valid *AuthFile, - // so r.Fid.File should not be nil. - st, err = r.Fid.File.Stat() - if err != nil { - Respond(ctx, r, fmt.Errorf("stat: %v", err)) +func sOpen(ctx context.Context, s *Server, c <-chan *Req) { + rc := make(chan *Req) + defer close(rc) + go rOpen(ctx, rc) + for { + select { + case <-ctx.Done(): return + case r := <-c: + if r == nil { + return + } + ifcall := r.Ifcall.(*TOpen) + var ok bool + r.Fid, ok = s.fPool.lookup(ifcall.Fid) + if !ok { + r.err = ErrUnknownFid + rc <- r + continue + } + if r.Fid.OMode != -1 { + r.err = ErrBotch + rc <- r + continue + } + var ( + err error + qid Qid + st fs.FileInfo + ) + if afile, ok := r.Fid.File.(*AuthFile); ok { + // s.Auth should set r.Fid.File to a valid *AuthFile, + // so r.Fid.File should not be nil. + st, err = r.Fid.File.Stat() + if err != nil { + r.err = fmt.Errorf("stat: %v", err) + rc <- r + continue + } + qid = afile.Qid + } else { + // Write attempt to a directory is prohibitted by the protocol. + // See open(5). + // In plan9 implementation, ifcall.Mode() is ANDed with ^ORCLOSE, + // but ORCLOSE is also prohibitted by the protocol... + st, err = fs.Stat(ExportFS{s.fs}, r.Fid.path) + if err != nil { + r.err = fmt.Errorf("stat: %v", err) + rc <- r + continue + } + qid = st.Sys().(*Stat).Qid + } + if qid.Type == QTDIR && ifcall.Mode != OREAD { + r.err = fmt.Errorf("is a directory") + rc <- r + continue + } + var p fs.FileMode + switch ifcall.Mode & 3 { + default: + panic("invalid mode") + case OREAD: + p = AREAD + case OWRITE: + p = AWRITE + case ORDWR: + p = AREAD | AWRITE + case OEXEC: + p = AEXEC + } + if ifcall.Mode&OTRUNC != 0 { + p |= AWRITE + } + if qid.Type&QTDIR != 0 && p != AREAD { + r.err = ErrPerm + rc <- r + continue + } + if !hasPerm(s.fs, st, r.Fid.Uid, p) { + r.err = ErrPerm + rc <- r + continue + } + if ifcall.Mode&ORCLOSE != 0 { + parentPath := path.Dir(r.Fid.path) + st, err := fs.Stat(ExportFS{s.fs}, parentPath) + if err != nil { + r.err = fmt.Errorf("stat parent: %v", err) + rc <- r + continue + } + if !hasPerm(s.fs, st, r.Fid.Uid, AWRITE) { + r.err = ErrPerm + rc <- r + continue + } + } + r.Ofcall = &ROpen{ + Qid: qid, + Iounit: s.mSize() - IOHDRSZ, + } + rc <- r } - qid = afile.Qid - } else { - // Write attempt to a directory is prohibitted by the protocol. - // See open(5). - // In plan9 implementation, ifcall.Mode() is ANDed with ^ORCLOSE, - // but ORCLOSE is also prohibitted by the protocol... - st, err = fs.Stat(ExportFS{s.fs}, r.Fid.path) - if err != nil { - Respond(ctx, r, fmt.Errorf("stat: %v", err)) + } +} + +func rOpen(ctx context.Context, c <-chan *Req) { + for { + select { + case <-ctx.Done(): return + case r := <-c: + if r == nil { + return + } + if r.err != nil { + setError(r, r.err) + r.Srv.respChan <- r + continue + } + r.Fid.OMode = r.Ifcall.(*TOpen).Mode + if _, ok := r.Fid.File.(*AuthFile); ok { + r.Srv.respChan <- r + continue + } + f, err := r.Srv.fs.OpenFile(r.Fid.path, r.Fid.OMode) + if err != nil { + setError(r, err) + r.Srv.respChan <- r + continue + } + r.Fid.File = f + r.Srv.respChan <- r } - qid = st.Sys().(*Stat).Qid - } - if qid.Type == QTDIR && ifcall.Mode != OREAD { - Respond(ctx, r, fmt.Errorf("is a directory")) - return - } - var p fs.FileMode - switch ifcall.Mode & 3 { - default: - panic("invalid mode") - case OREAD: - p = AREAD - case OWRITE: - p = AWRITE - case ORDWR: - p = AREAD | AWRITE - case OEXEC: - p = AEXEC - } - if ifcall.Mode&OTRUNC != 0 { - p |= AWRITE } - if qid.Type&QTDIR != 0 && p != AREAD { - Respond(ctx, r, ErrPerm) - return - } - if !hasPerm(s.fs, st, r.Fid.Uid, p) { - Respond(ctx, r, ErrPerm) - return - } - if ifcall.Mode&ORCLOSE != 0 { - parentPath := path.Dir(r.Fid.path) - st, err := fs.Stat(ExportFS{s.fs}, parentPath) - if err != nil { - Respond(ctx, r, fmt.Errorf("stat parent: %v", err)) +} + +func sCreate(ctx context.Context, s *Server, c <-chan *Req) { + rc := make(chan *Req) + defer close(rc) + go rCreate(ctx, rc) + for { + select { + case <-ctx.Done(): return + case r := <-c: + if r == nil { + return + } + ifcall := r.Ifcall.(*TCreate) + var ok bool + r.Fid, ok = s.fPool.lookup(ifcall.Fid) + if !ok { + r.err = ErrUnknownFid + rc <- r + continue + } + dirstat, err := fs.Stat(ExportFS{s.fs}, r.Fid.path) + if err != nil { + r.err = fmt.Errorf("stat: %v", err) + rc <- r + continue + } + if !dirstat.IsDir() { + r.err = fmt.Errorf("create in non-dir") + rc <- r + continue + } + if !hasPerm(s.fs, dirstat, r.Fid.Uid, AWRITE) { + r.err = ErrPerm + rc <- r + continue + } + cfs, ok := s.fs.(CreaterFS) + if !ok { + r.err = ErrOperation + rc <- r + continue + } + perm := ifcall.Perm + dirperm := dirstat.Mode() + if perm&fs.ModeDir == 0 { + perm &= ^FileMode(0666) | (dirperm & FileMode(0666)) + } else { + perm &= ^FileMode(0777) | (dirperm & FileMode(0777)) + } + cpath := path.Join(r.Fid.path, ifcall.Name) + file, err := cfs.Create(cpath, r.Fid.Uid, ifcall.Mode, perm) + if err != nil { + r.err = fmt.Errorf("create: %v", err) + rc <- r + continue + } + r.Fid.File = file + r.Fid.path = cpath + r.Fid.OMode = ifcall.Mode + st, err := r.Fid.File.Stat() + if err != nil { + r.err = fmt.Errorf("stat: %v", err) + rc <- r + continue + } + r.Ofcall = &RCreate{ + Qid: st.Sys().(*Stat).Qid, + Iounit: s.mSize() - IOHDRSZ, + } + rc <- r } - if !hasPerm(s.fs, st, r.Fid.Uid, AWRITE) { - Respond(ctx, r, ErrPerm) + } +} + +func rCreate(ctx context.Context, c <-chan *Req) { + for { + select { + case <-ctx.Done(): return + case r := <-c: + if r == nil { + return + } + if r.err != nil { + setError(r, r.err) + } + r.Srv.respChan <- r } } - r.Ofcall = &ROpen{ - Qid: qid, - Iounit: s.mSize() - IOHDRSZ, - } - Respond(ctx, r, nil) } -func rOpen(r *Req, err error) { - if err != nil { - setError(r, err) - return - } - r.Fid.OMode = r.Ifcall.(*TOpen).Mode - if _, ok := r.Fid.File.(*AuthFile); ok { - return - } - f, err := r.Srv.fs.OpenFile(r.Fid.path, r.Fid.OMode) - if err != nil { - setError(r, err) - return +// TODO: I think the file should be locked while reading. +func sRead(ctx context.Context, s *Server, c <-chan *Req) { + rc := make(chan *Req) + defer close(rc) + go rRead(ctx, rc) + for { + select { + case <-ctx.Done(): + return + case r := <-c: + if r == nil { + return + } + ifcall := r.Ifcall.(*TRead) + var ok bool + r.Fid, ok = s.fPool.lookup(ifcall.Fid) + if !ok { + r.err = ErrUnknownFid + rc <- r + continue + } + if r.Fid.OMode == -1 { + r.err = fmt.Errorf("not open") + rc <- r + continue + } + if r.Fid.OMode != OREAD && r.Fid.OMode != ORDWR && r.Fid.OMode != OEXEC { + r.err = ErrPerm + rc <- r + continue + } + fi, err := r.Fid.File.Stat() + if err != nil { + r.err = fmt.Errorf("stat: %v", err) + rc <- r + continue + } + data := make([]byte, ifcall.Count) + errc := make(chan error) + var n int + go func() { + defer close(errc) + if fi.IsDir() { + if ifcall.Offset != 0 && ifcall.Offset != r.Fid.dirOffset { + errc <- fmt.Errorf("invalid dir offset") + return + } + children, err := fs.Glob(ExportFS{s.fs}, path.Join(r.Fid.path, "*")) + if err != nil { + errc <- fmt.Errorf("glob children: %v", err) + return + } + if ifcall.Offset == 0 { + r.Fid.dirIndex = 0 + r.Fid.dirOffset = 0 + } + k := r.Fid.dirIndex + for ; k < len(children); k++ { + fi, err := fs.Stat(ExportFS{s.fs}, children[k]) + if err != nil { + log.Printf("stat: %v", err) + continue + } + st := fi.Sys().(*Stat) + buf := st.marshal() + if n+len(buf) > len(data) { + break + } + for i := 0; i < len(buf); i++ { + data[n+i] = buf[i] + } + n += len(buf) + } + r.Fid.dirOffset += uint64(n) + r.Fid.dirIndex = k + } else { + var err error + if reader, ok := r.Fid.File.(io.ReaderAt); ok { + n, err = reader.ReadAt(data, int64(ifcall.Offset)) + } else { + n, err = r.Fid.File.Read(data) + } + if err != io.EOF && err != nil { + errc <- err + return + } + } + }() + select { + case err := <-errc: + if err != nil { + r.err = err + rc <- r + continue + } + case <-ctx.Done(): + return + } + r.Ofcall = &RRead{ + Count: uint32(n), + Data: data[:n], + } + rc <- r + } } - r.Fid.File = f } -func sCreate(ctx context.Context, s *Server, r *Req) { - ifcall := r.Ifcall.(*TCreate) - var ok bool - r.Fid, ok = s.fPool.lookup(ifcall.Fid) - if !ok { - Respond(ctx, r, ErrUnknownFid) - return - } - dirstat, err := fs.Stat(ExportFS{s.fs}, r.Fid.path) - if err != nil { - Respond(ctx, r, fmt.Errorf("stat: %v", err)) - return - } - if !dirstat.IsDir() { - Respond(ctx, r, fmt.Errorf("create in non-dir")) - return - } - if !hasPerm(s.fs, dirstat, r.Fid.Uid, AWRITE) { - Respond(ctx, r, ErrPerm) - return - } - cfs, ok := s.fs.(CreaterFS) - if !ok { - Respond(ctx, r, ErrOperation) - return - } - perm := ifcall.Perm - dirperm := dirstat.Mode() - if perm&fs.ModeDir == 0 { - perm &= ^FileMode(0666) | (dirperm & FileMode(0666)) - } else { - perm &= ^FileMode(0777) | (dirperm & FileMode(0777)) +func rRead(ctx context.Context, c <-chan *Req) { + for { + select { + case <-ctx.Done(): + return + case r := <-c: + if r == nil { + return + } + if r.err != nil { + setError(r, r.err) + } + r.Srv.respChan <- r + } } - cpath := path.Join(r.Fid.path, ifcall.Name) - file, err := cfs.Create(cpath, r.Fid.Uid, ifcall.Mode, perm) - if err != nil { - Respond(ctx, r, fmt.Errorf("create: %v", err)) - return +} + +// TODO: I think the file should be locked while reading. +func sWrite(ctx context.Context, s *Server, c <-chan *Req) { + rc := make(chan *Req) + defer close(rc) + go rWrite(ctx, rc) + for { + select { + case <-ctx.Done(): + return + case r := <-c: + if r == nil { + return + } + ifcall := r.Ifcall.(*TWrite) + var ok bool + r.Fid, ok = s.fPool.lookup(ifcall.Fid) + if !ok { + r.err = ErrUnknownFid + rc <- r + continue + } + if ifcall.Count > s.mSize()-IOHDRSZ { + ifcall.Count = s.mSize() - IOHDRSZ + } + omode := r.Fid.OMode & 3 + if omode != OWRITE && omode != ORDWR { + r.err = fmt.Errorf("write on fid with open mode 0x%x", r.Fid.OMode) + rc <- r + continue + } + ofcall := new(RWrite) + errc := make(chan error) + go func() { + defer close(errc) + switch file := r.Fid.File.(type) { + case io.WriterAt: + n, err := file.WriteAt(ifcall.Data, int64(ifcall.Offset)) + if err != nil { + errc <- fmt.Errorf("write: %v", err) + return + } + ofcall.Count = uint32(n) + case io.Writer: + n, err := file.Write(ifcall.Data) + if err != nil { + errc <- fmt.Errorf("write: %v", err) + return + } + ofcall.Count = uint32(n) + default: + errc <- ErrOperation + return + } + }() + select { + case err := <-errc: + if err != nil { + r.err = err + rc <- r + continue + } + case <-ctx.Done(): + return + } + r.Ofcall = ofcall + rc <- r + } } - r.Fid.File = file - r.Fid.path = cpath - r.Fid.OMode = ifcall.Mode - st, err := r.Fid.File.Stat() - if err != nil { - Respond(ctx, r, fmt.Errorf("stat: %v", err)) - return +} + +func rWrite(ctx context.Context, c <-chan *Req) { + for { + select { + case <-ctx.Done(): + return + case r := <-c: + if r == nil { + return + } + if r.err != nil { + setError(r, r.err) + } + r.Srv.respChan <- r + } } - r.Ofcall = &RCreate{ - Qid: st.Sys().(*Stat).Qid, - Iounit: s.mSize() - IOHDRSZ, +} + +func sClunk(ctx context.Context, s *Server, c <-chan *Req) { + rc := make(chan *Req) + defer close(rc) + go rClunk(ctx, rc) + for { + select { + case <-ctx.Done(): + return + case r := <-c: + if r == nil { + return + } + ifcall := r.Ifcall.(*TClunk) + fid, ok := s.fPool.lookup(ifcall.Fid) + if !ok { + r.err = ErrUnknownFid + rc <- r + continue + } + s.fPool.delete(ifcall.Fid) + if fid.OMode != -1 { + if err := fid.File.Close(); err != nil { + r.err = fmt.Errorf("close: %v", err) + rc <- r + continue + } + } + r.Ofcall = &RClunk{} + rc <- r + } } - Respond(ctx, r, nil) } -func rCreate(r *Req, err error) { - if err != nil { - setError(r, err) - return +func rClunk(ctx context.Context, c <-chan *Req) { + for { + select { + case <-ctx.Done(): + return + case r := <-c: + if r == nil { + return + } + if r.err != nil { + log.Printf("clunk: %v", r.err) + r.Ofcall = &RClunk{} + } + r.Srv.respChan <- r + } } } -// TODO: I think the file should be locked while reading. -func sRead(ctx context.Context, s *Server, r *Req) { - ifcall := r.Ifcall.(*TRead) - var ok bool - r.Fid, ok = s.fPool.lookup(ifcall.Fid) - if !ok { - Respond(ctx, r, ErrUnknownFid) - return - } - if r.Fid.OMode == -1 { - Respond(ctx, r, fmt.Errorf("not open")) - return - } - if r.Fid.OMode != OREAD && r.Fid.OMode != ORDWR && r.Fid.OMode != OEXEC { - Respond(ctx, r, ErrPerm) - return - } - fi, err := r.Fid.File.Stat() - if err != nil { - log.Printf("Stat: %v", err) - Respond(ctx, r, fmt.Errorf("internal error")) - return - } - data := make([]byte, ifcall.Count) - errc := make(chan error) - var n int - go func() { - defer close(errc) - if fi.IsDir() { - if ifcall.Offset != 0 && ifcall.Offset != r.Fid.dirOffset { - errc <- fmt.Errorf("invalid dir offset") +func sRemove(ctx context.Context, s *Server, c <-chan *Req) { + rc := make(chan *Req) + defer close(rc) + go rRemove(ctx, rc) + for { + select { + case <-ctx.Done(): + return + case r := <-c: + if r == nil { return } - children, err := fs.Glob(ExportFS{s.fs}, path.Join(r.Fid.path, "*")) - if err != nil { - errc <- fmt.Errorf("glob children: %v", err) - return + ifcall := r.Ifcall.(*TRemove) + var ok bool + r.Fid, ok = s.fPool.lookup(ifcall.Fid) + if !ok { + r.err = ErrUnknownFid + rc <- r + continue } - if ifcall.Offset == 0 { - r.Fid.dirIndex = 0 - r.Fid.dirOffset = 0 + s.fPool.delete(ifcall.Fid) + if r.Fid.OMode != -1 { + r.Fid.File.Close() } - k := r.Fid.dirIndex - for ; k < len(children); k++ { - fi, err := fs.Stat(ExportFS{s.fs}, children[k]) - if err != nil { - log.Printf("stat: %v", err) - continue - } - st := fi.Sys().(*Stat) - buf := st.marshal() - if n+len(buf) > len(data) { - break - } - for i := 0; i < len(buf); i++ { - data[n+i] = buf[i] - } - n += len(buf) + parentPath := path.Dir(r.Fid.path) + pstat, err := fs.Stat(ExportFS{s.fs}, parentPath) + if err != nil { + r.err = fmt.Errorf("stat parent: %v", err) + rc <- r + continue } - r.Fid.dirOffset += uint64(n) - r.Fid.dirIndex = k - } else { - var err error - if reader, ok := r.Fid.File.(io.ReaderAt); ok { - n, err = reader.ReadAt(data, int64(ifcall.Offset)) - } else { - n, err = r.Fid.File.Read(data) + if !hasPerm(s.fs, pstat, r.Fid.Uid, AWRITE) { + r.err = ErrPerm + rc <- r + continue } - if err != io.EOF && err != nil { - errc <- err - return + rfs, ok := s.fs.(RemoverFS) + if !ok { + r.err = ErrOperation + rc <- r + continue } + if err := rfs.Remove(r.Fid.path); err != nil { + r.err = fmt.Errorf("remove: %v", err) + rc <- r + continue + } + r.Ofcall = &RRemove{} + rc <- r } - }() - select { - case err := <-errc: - if err != nil { - Respond(ctx, r, err) - return - } - case <-ctx.Done(): - return - } - r.Ofcall = &RRead{ - Count: uint32(n), - Data: data[:n], } - Respond(ctx, r, nil) } -func rRead(r *Req, err error) { - if err != nil { - setError(r, err) +func rRemove(ctx context.Context, c <-chan *Req) { + for { + select { + case <-ctx.Done(): + return + case r := <-c: + if r == nil { + return + } + if r.err != nil { + setError(r, r.err) + } + r.Srv.respChan <- r + } } } -// TODO: I think the file should be locked while reading. -func sWrite(ctx context.Context, s *Server, r *Req) { - ifcall := r.Ifcall.(*TWrite) - var ok bool - r.Fid, ok = s.fPool.lookup(ifcall.Fid) - if !ok { - Respond(ctx, r, ErrUnknownFid) - return - } - if ifcall.Count > s.mSize()-IOHDRSZ { - ifcall.Count = s.mSize() - IOHDRSZ - } - omode := r.Fid.OMode & 3 - if omode != OWRITE && omode != ORDWR { - Respond(ctx, r, fmt.Errorf("write on fid with open mode 0x%x", r.Fid.OMode)) - return - } - ofcall := new(RWrite) - errc := make(chan error) - go func() { - defer close(errc) - switch file := r.Fid.File.(type) { - case io.WriterAt: - n, err := file.WriteAt(ifcall.Data, int64(ifcall.Offset)) - if err != nil { - errc <- fmt.Errorf("write: %v", err) +func sStat(ctx context.Context, s *Server, c <-chan *Req) { + rc := make(chan *Req) + defer close(rc) + go rStat(ctx, rc) + for { + select { + case <-ctx.Done(): + return + case r := <-c: + if r == nil { return } - ofcall.Count = uint32(n) - case io.Writer: - n, err := file.Write(ifcall.Data) + ifcall := r.Ifcall.(*TStat) + var ok bool + r.Fid, ok = s.fPool.lookup(ifcall.Fid) + if !ok { + r.err = ErrUnknownFid + rc <- r + continue + } + fi, err := fs.Stat(ExportFS{s.fs}, r.Fid.path) if err != nil { - errc <- fmt.Errorf("write: %v", err) - return + r.err = fmt.Errorf("stat: %v", err) + rc <- r + continue } - ofcall.Count = uint32(n) - default: - errc <- ErrOperation - return - } - }() - select { - case err := <-errc: - if err != nil { - Respond(ctx, r, err) - return + r.Ofcall = &RStat{ + Stat: fi.Sys().(*Stat), + } + rc <- r } - case <-ctx.Done(): - return } - r.Ofcall = ofcall - Respond(ctx, r, nil) } -func rWrite(r *Req, err error) { - if err != nil { - setError(r, err) - return - } -} - -func sClunk(ctx context.Context, s *Server, r *Req) { - ifcall := r.Ifcall.(*TClunk) - fid, ok := s.fPool.lookup(ifcall.Fid) - if !ok { - Respond(ctx, r, ErrUnknownFid) - return - } - s.fPool.delete(ifcall.Fid) - if fid.OMode != -1 { - if err := fid.File.Close(); err != nil { - Respond(ctx, r, fmt.Errorf("close: %v", err)) +func rStat(ctx context.Context, c <-chan *Req) { + for { + select { + case <-ctx.Done(): return + case r := <-c: + if r == nil { + return + } + if r.err != nil { + setError(r, r.err) + } + r.Srv.respChan <- r } } - r.Ofcall = &RClunk{} - Respond(ctx, r, nil) -} - -func rClunk(r *Req, err error) { - if err != nil { - log.Printf("clunk: %v", err) - r.Ofcall = &RClunk{} - } -} - -func sRemove(ctx context.Context, s *Server, r *Req) { - ifcall := r.Ifcall.(*TRemove) - var ok bool - r.Fid, ok = s.fPool.lookup(ifcall.Fid) - if !ok { - Respond(ctx, r, ErrUnknownFid) - return - } - s.fPool.delete(ifcall.Fid) - if r.Fid.OMode != -1 { - r.Fid.File.Close() - } - parentPath := path.Dir(r.Fid.path) - pstat, err := fs.Stat(ExportFS{s.fs}, parentPath) - if err != nil { - Respond(ctx, r, fmt.Errorf("stat parent: %v", err)) - return - } - if !hasPerm(s.fs, pstat, r.Fid.Uid, AWRITE) { - Respond(ctx, r, ErrPerm) - return - } - rfs, ok := s.fs.(RemoverFS) - if !ok { - Respond(ctx, r, ErrOperation) - return - } - if err := rfs.Remove(r.Fid.path); err != nil { - Respond(ctx, r, fmt.Errorf("remove: %v", err)) - return - } - r.Ofcall = &RRemove{} - Respond(ctx, r, nil) -} - -func rRemove(r *Req, err error) { - if err != nil { - setError(r, err) - } -} - -func sStat(ctx context.Context, s *Server, r *Req) { - ifcall := r.Ifcall.(*TStat) - var ok bool - r.Fid, ok = s.fPool.lookup(ifcall.Fid) - if !ok { - Respond(ctx, r, ErrUnknownFid) - return - } - fi, err := fs.Stat(ExportFS{s.fs}, r.Fid.path) - if err != nil { - Respond(ctx, r, fmt.Errorf("stat: %v", err)) - return - } - r.Ofcall = &RStat{ - Stat: fi.Sys().(*Stat), - } - Respond(ctx, r, nil) -} - -func rStat(r *Req, err error) { - if err != nil { - setError(r, err) - } } -func sWStat(ctx context.Context, s *Server, r *Req) { - ifcall := r.Ifcall.(*TWStat) - var ok bool - r.Fid, ok = s.fPool.lookup(ifcall.Fid) - if !ok { - Respond(ctx, r, ErrUnknownFid) - return - } - if r.Fid.OMode == -1 { - var err error - r.Fid.File, err = s.fs.OpenFile(r.Fid.path, OREAD) - if err != nil { - Respond(ctx, r, fmt.Errorf("open: %v", err)) - return - } - defer r.Fid.File.Close() - } - wsfile, ok := r.Fid.File.(WriterStatFile) - if !ok { - Respond(ctx, r, ErrOperation) - return - } - wstat := ifcall.Stat - fi, err := r.Fid.File.Stat() - if err != nil { - Respond(ctx, r, fmt.Errorf("stat: %v", err)) - return - } - newStat := fi.Sys().(*Stat) - if wstat.Type != ^uint16(0) && wstat.Type != newStat.Type || - wstat.Dev != ^uint32(0) && wstat.Dev != newStat.Dev || - wstat.Qid.Type != QidType(^uint8(0)) && wstat.Qid.Type != newStat.Qid.Type || - wstat.Qid.Vers != ^uint32(0) && wstat.Qid.Vers != newStat.Qid.Vers || - wstat.Qid.Path != ^uint64(0) && wstat.Qid.Path != newStat.Qid.Path || - wstat.Atime != ^uint32(0) && wstat.Atime != newStat.Atime || - wstat.Uid != "" && wstat.Uid != newStat.Uid || - wstat.Muid != "" && wstat.Muid != newStat.Muid { - Respond(ctx, r, fmt.Errorf("operation not permitted")) - return - } - if wstat.Name != "" && newStat.Name != wstat.Name { - parentPath := path.Dir(r.Fid.path) - pstat, err := fs.Stat(ExportFS{s.fs}, parentPath) - if err != nil { - Respond(ctx, r, fmt.Errorf("stat parent: %v", err)) - return - } - if !hasPerm(s.fs, pstat, r.Fid.Uid, AWRITE) { - Respond(ctx, r, ErrPerm) +func sWStat(ctx context.Context, s *Server, c <-chan *Req) { + rc := make(chan *Req) + defer close(rc) + go rWStat(ctx, rc) + for { + select { + case <-ctx.Done(): return - } - // TODO: I think 9P protocol prohibits renaming to existent file. - // Wstat(9P) in p9p says: - // it is an error to change the name to that of - // an existing file. - // but 9pfs, 9pfuse does the rename when used with `git init`. - /* - children, err := fs.Glob(ExportFS{s.fs}, path.Join(parentPath, "*")) - if err != nil { - Respond(ctx, r, fmt.Errorf("glob children: %v", err)) + case r := <-c: + if r == nil { return } - for _, f := range children { - if path.Base(f) == wstat.Name { - Respond(ctx, r, fmt.Errorf("file already exists")) - return + ifcall := r.Ifcall.(*TWStat) + var ok bool + r.Fid, ok = s.fPool.lookup(ifcall.Fid) + if !ok { + r.err = ErrUnknownFid + rc <- r + continue + } + if r.Fid.OMode == -1 { + var err error + r.Fid.File, err = s.fs.OpenFile(r.Fid.path, OREAD) + if err != nil { + r.err = fmt.Errorf("open: %v", err) + rc <- r + continue } + defer r.Fid.File.Close() } - */ - newStat.Name = wstat.Name - } - if wstat.Length != ^int64(0) && wstat.Length != newStat.Length { - if fi.IsDir() || !hasPerm(s.fs, fi, r.Fid.Uid, AWRITE) { - Respond(ctx, r, ErrPerm) - return - } - newStat.Length = wstat.Length - } - if wstat.Mode != FileMode(^uint32(0)) && wstat.Mode != newStat.Mode { - // the owner of the file or the group leader of the file's group. - if r.Fid.Uid != newStat.Uid && r.Fid.Uid != newStat.Gid { - Respond(ctx, r, ErrPerm) - return - } - if wstat.Mode&fs.ModeDir != newStat.Mode&fs.ModeDir { - Respond(ctx, r, ErrPerm) - return - } - newStat.Mode = wstat.Mode - } - if wstat.Mtime != ^uint32(0) && wstat.Mtime != newStat.Mtime { - // the owner of the file or the group leader of the file's group. - if r.Fid.Uid != newStat.Uid && r.Fid.Uid != newStat.Gid { - Respond(ctx, r, ErrPerm) - return - } - newStat.Mtime = wstat.Mtime - } - // TODO: check group membership - // for now, group member == group leader == gid - if wstat.Gid != "" && wstat.Gid != newStat.Gid { - // by the owner if also a member of the new group; - // or by the group leader of the file's current group if - // also the leader of the new group. - if r.Fid.Uid == newStat.Uid && s.fs.IsGroupMember(wstat.Gid, r.Fid.Uid) || - s.fs.IsGroupLeader(newStat.Gid, r.Fid.Uid) && - s.fs.IsGroupLeader(wstat.Gid, r.Fid.Uid) { - newStat.Gid = wstat.Gid - } else { - Respond(ctx, r, ErrPerm) - return + wsfile, ok := r.Fid.File.(WriterStatFile) + if !ok { + r.err = ErrOperation + rc <- r + continue + } + wstat := ifcall.Stat + fi, err := r.Fid.File.Stat() + if err != nil { + r.err = fmt.Errorf("stat: %v", err) + rc <- r + continue + } + newStat := fi.Sys().(*Stat) + if wstat.Type != ^uint16(0) && wstat.Type != newStat.Type || + wstat.Dev != ^uint32(0) && wstat.Dev != newStat.Dev || + wstat.Qid.Type != QidType(^uint8(0)) && wstat.Qid.Type != newStat.Qid.Type || + wstat.Qid.Vers != ^uint32(0) && wstat.Qid.Vers != newStat.Qid.Vers || + wstat.Qid.Path != ^uint64(0) && wstat.Qid.Path != newStat.Qid.Path || + wstat.Atime != ^uint32(0) && wstat.Atime != newStat.Atime || + wstat.Uid != "" && wstat.Uid != newStat.Uid || + wstat.Muid != "" && wstat.Muid != newStat.Muid { + r.err = fmt.Errorf("operation not permitted") + rc <- r + continue + } + if wstat.Name != "" && newStat.Name != wstat.Name { + parentPath := path.Dir(r.Fid.path) + pstat, err := fs.Stat(ExportFS{s.fs}, parentPath) + if err != nil { + r.err = fmt.Errorf("stat parent: %v", err) + rc <- r + continue + } + if !hasPerm(s.fs, pstat, r.Fid.Uid, AWRITE) { + r.err = ErrPerm + rc <- r + continue + } + // TODO: I think 9P protocol prohibits renaming to existent file. + // Wstat(9P) in p9p says: + // it is an error to change the name to that of + // an existing file. + // but 9pfs, 9pfuse does the rename when used with `git init`. + /* + children, err := fs.Glob(ExportFS{s.fs}, path.Join(parentPath, "*")) + if err != nil { + r.err = fmt.Errorf("glob children: %v", err) + rc <- r + continue + } + for _, f := range children { + if path.Base(f) == wstat.Name { + r.err = fmt.Errorf("file already exists") + rc <- r + continue + } + } + */ + newStat.Name = wstat.Name + } + if wstat.Length != ^int64(0) && wstat.Length != newStat.Length { + if fi.IsDir() || !hasPerm(s.fs, fi, r.Fid.Uid, AWRITE) { + r.err = ErrPerm + rc <- r + continue + } + newStat.Length = wstat.Length + } + if wstat.Mode != FileMode(^uint32(0)) && wstat.Mode != newStat.Mode { + // the owner of the file or the group leader of the file's group. + if r.Fid.Uid != newStat.Uid && r.Fid.Uid != newStat.Gid { + r.err = ErrPerm + rc <- r + continue + } + if wstat.Mode&fs.ModeDir != newStat.Mode&fs.ModeDir { + r.err = ErrPerm + rc <- r + continue + } + newStat.Mode = wstat.Mode + } + if wstat.Mtime != ^uint32(0) && wstat.Mtime != newStat.Mtime { + // the owner of the file or the group leader of the file's group. + if r.Fid.Uid != newStat.Uid && r.Fid.Uid != newStat.Gid { + r.err = ErrPerm + rc <- r + continue + } + newStat.Mtime = wstat.Mtime + } + // TODO: check group membership + // for now, group member == group leader == gid + if wstat.Gid != "" && wstat.Gid != newStat.Gid { + // by the owner if also a member of the new group; + // or by the group leader of the file's current group if + // also the leader of the new group. + if r.Fid.Uid == newStat.Uid && s.fs.IsGroupMember(wstat.Gid, r.Fid.Uid) || + s.fs.IsGroupLeader(newStat.Gid, r.Fid.Uid) && + s.fs.IsGroupLeader(wstat.Gid, r.Fid.Uid) { + newStat.Gid = wstat.Gid + } else { + r.err = ErrPerm + rc <- r + continue + } + } + err = wsfile.WStat(newStat) + if err != nil { + r.err = fmt.Errorf("wstat: %v", err) + rc <- r + continue + } + r.Ofcall = &RWStat{} + rc <- r } } - err = wsfile.WStat(newStat) - if err != nil { - Respond(ctx, r, fmt.Errorf("wstat: %v", err)) - return - } - r.Ofcall = &RWStat{} - Respond(ctx, r, nil) } -func rWStat(r *Req, err error) { - if err != nil { - setError(r, err) +func rWStat(ctx context.Context, c <-chan *Req) { + for { + select { + case <-ctx.Done(): + return + case r := <-c: + if r == nil { + return + } + if r.err != nil { + setError(r, r.err) + } + r.Srv.respChan <- r + } } } @@ -1073,19 +1313,43 @@ func (s *Server) Serve(ctx context.Context) { attachChan = make(chan *Req) flushChan = make(chan *Req) walkChan = make(chan *Req) - ) + openChan = make(chan *Req) + createChan = make(chan *Req) + readChan = make(chan *Req) + writeChan = make(chan *Req) + clunkChan = make(chan *Req) + removeChan = make(chan *Req) + statChan = make(chan *Req) + wstatChan = make(chan *Req) + ) defer func() { close(versionChan) close(authChan) close(attachChan) close(flushChan) close(walkChan) + close(openChan) + close(createChan) + close(readChan) + close(writeChan) + close(clunkChan) + close(removeChan) + close(statChan) + close(wstatChan) }() go sVersion(ctx, s, versionChan) go sAuth(ctx, s, authChan) go sAttach(ctx, s, attachChan) go sFlush(ctx, s, flushChan) go sWalk(ctx, s, walkChan) + go sOpen(ctx, s, openChan) + go sCreate(ctx, s, createChan) + go sRead(ctx, s, readChan) + go sWrite(ctx, s, writeChan) + go sClunk(ctx, s, clunkChan) + go sRemove(ctx, s, removeChan) + go sStat(ctx, s, statChan) + go sWStat(ctx, s, wstatChan) go Respond2(ctx, s) L: for { @@ -1098,12 +1362,14 @@ L: } continue L } - ctx1, cancel := context.WithCancel(ctx) - r.Cancel = cancel + //ctx1, cancel := context.WithCancel(ctx) + //r.Cancel = cancel go func() { switch r.Ifcall.(type) { default: - Respond(ctx1, r, fmt.Errorf("unknown message type: %d", r.Ifcall.Type())) + r.err = fmt.Errorf("unknown message type: %d", r.Ifcall.Type()) + s.respChan <- r + return case *TVersion: versionChan <- r case *TAuth: @@ -1115,21 +1381,21 @@ L: case *TWalk: walkChan <- r case *TOpen: - sOpen(ctx1, s, r) + openChan <- r case *TCreate: - sCreate(ctx1, s, r) + createChan <- r case *TRead: - sRead(ctx1, s, r) + readChan <- r case *TWrite: - sWrite(ctx1, s, r) + writeChan <- r case *TClunk: - sClunk(ctx1, s, r) + clunkChan <- r case *TRemove: - sRemove(ctx1, s, r) + removeChan <- r case *TStat: - sStat(ctx1, s, r) + statChan <- r case *TWStat: - sWStat(ctx1, s, r) + wstatChan <- r } }() case <-ctx.Done(): @@ -1139,29 +1405,14 @@ L: } } +/* // Respond Responds to the request r with the message r.Ofcall if err is nil, // or if err is not nil, with the Rerror with the error message. // If r is nil, or both r.Ofcall and err are nil it panics. -func Respond(ctx context.Context, r *Req, err error) { +func Respond() { switch r.Ifcall.(type) { default: panic(fmt.Errorf("bug: r.Ifcall: %v", r.Ifcall)) - case *TOpen: - rOpen(r, err) - case *TCreate: - rCreate(r, err) - case *TRead: - rRead(r, err) - case *TWrite: - rWrite(r, err) - case *TClunk: - rClunk(r, err) - case *TRemove: - rRemove(r, err) - case *TStat: - rStat(r, err) - case *TWStat: - rWStat(r, err) } r.Ofcall.SetTag(r.Tag) // free tag. @@ -1189,6 +1440,7 @@ func Respond(ctx context.Context, r *Req, err error) { //log.Printf("respond: %v", ctx.Err()) } } +*/ func Respond2(ctx context.Context, s *Server) { for {