1package fileserver23import (4 "go.rbn.im/neinp"5 "go.rbn.im/neinp/fid"6 "go.rbn.im/neinp/message"7 "go.rbn.im/neinp/qid"8 "go.rbn.im/neinp/stat"910 "context"11 "errors"12 "io"13 "sync"14)1516type File interface {17 Read(int64, []byte) (int, error)18 Write(int64, []byte) (int, error)19 Close() error20}2122type Cons func() (File, error)2324type FileMap map[string]Cons2526type FileServer struct {27 neinp.NopP200028 root File29 fids *fid.Map3031 stat map[string]*stat.Stat32 open *sync.Map // fid.Fid → File33 cons FileMap34}3536const version = "9P2000"3738// TODO: Currently only supports a flat hierarchy with one root39// directory and several regular files located inside this directory.4041func NewFileServer(files FileMap) *FileServer {42 fs := &FileServer{43 stat: make(map[string]*stat.Stat),44 open: new(sync.Map),45 cons: files,46 fids: fid.New(),47 }4849 rootStat := createStat("/", 0555|stat.Dir)50 rootFile := directory{stat: rootStat}5152 for name, _ := range files {53 s := createStat(name, 0644)54 fs.stat[name] = &s55 rootFile.children = append(rootFile.children, s)56 }5758 fs.stat[rootStat.Name] = &rootStat59 fs.cons[rootStat.Name] = func() (File, error) { return rootFile, nil }6061 return fs62}6364func (s *FileServer) Version(ctx context.Context, msg message.TVersion) (message.RVersion, error) {65 if msg.Version != version {66 return message.RVersion{}, errors.New(message.BotchErrorString)67 }6869 // TODO: Sanity check on msize70 return message.RVersion{Version: version, Msize: msg.Msize}, nil71}7273func (s *FileServer) Attach(ctx context.Context, msg message.TAttach) (message.RAttach, error) {74 stat := s.stat["/"]7576 s.fids.Set(msg.Fid, stat)77 return message.RAttach{Qid: stat.Qid}, nil78}7980func (s *FileServer) Stat(ctx context.Context, msg message.TStat) (message.RStat, error) {81 stat, ok := s.fids.Get(msg.Fid).(*stat.Stat)82 if !ok {83 return message.RStat{}, errors.New(message.NoStatErrorString)84 }8586 return message.RStat{Stat: *stat}, nil87}8889func (s *FileServer) Walk(ctx context.Context, msg message.TWalk) (message.RWalk, error) {90 stat, ok := s.fids.Get(msg.Fid).(*stat.Stat)91 if !ok {92 return message.RWalk{}, errors.New(message.UnknownFidErrorString)93 } else if len(msg.Wname) > 1 {94 return message.RWalk{}, errors.New(message.WalkNoDirErrorString)95 }9697 newStat := stat98 if len(msg.Wname) == 1 {99 name := msg.Wname[0]100 newStat, ok = s.stat[name]101 if !ok {102 return message.RWalk{}, errors.New(message.NotFoundErrorString)103 }104 }105106 if msg.Newfid != msg.Fid {107 if s.fids.Get(msg.Newfid) != nil {108 return message.RWalk{}, errors.New(message.DupFidErrorString)109 }110 s.fids.Set(msg.Newfid, newStat)111 }112113 if len(msg.Wname) == 1 {114 return message.RWalk{Wqid: []qid.Qid{newStat.Qid}}, nil115 } else {116 return message.RWalk{}, nil117 }118}119120func (s *FileServer) Open(ctx context.Context, msg message.TOpen) (message.ROpen, error) {121 stat, ok := s.fids.Get(msg.Fid).(*stat.Stat)122 if !ok {123 return message.ROpen{}, errors.New(message.UnknownFidErrorString)124 }125126 file, err := s.cons[stat.Name]()127 if err != nil {128 return message.ROpen{}, err129 }130131 s.open.Store(msg.Fid, file)132 return message.ROpen{Qid: stat.Qid}, nil133}134135func (s *FileServer) Read(ctx context.Context, msg message.TRead) (message.RRead, error) {136 f, ok := s.open.Load(msg.Fid)137 if !ok {138 return message.RRead{}, errors.New(message.UnknownFidErrorString)139 }140 file := f.(File)141142 // TODO: Sanity check count143 buf := make([]byte, msg.Count)144 n, err := file.Read(int64(msg.Offset), buf)145 if err == io.EOF {146 return message.RRead{Count: 0}, nil147 } else if err != nil {148 return message.RRead{}, err149 }150151 return message.RRead{Count: uint32(n), Data: buf[:n]}, nil152}153154func (s *FileServer) Write(ctx context.Context, msg message.TWrite) (message.RWrite, error) {155 f, ok := s.open.Load(msg.Fid)156 if !ok {157 return message.RWrite{}, errors.New(message.UnknownFidErrorString)158 }159 file := f.(File)160161 n, err := file.Write(int64(msg.Offset), msg.Data)162 if err != nil {163 return message.RWrite{}, err164 }165166 return message.RWrite{Count: uint32(n)}, nil167}168169func (s *FileServer) Clunk(ctx context.Context, msg message.TClunk) (message.RClunk, error) {170 defer s.fids.Delete(msg.Fid)171 defer s.open.Delete(msg.Fid)172173 f, ok := s.open.Load(msg.Fid)174 if ok {175 file := f.(File)176 err := file.Close()177 if err != nil {178 return message.RClunk{}, err179 }180 }181182 return message.RClunk{}, nil183}