1package fileserver
2
3import (
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"
9
10 "context"
11 "errors"
12 "io"
13 "sync"
14)
15
16type File interface {
17 Read(int64, []byte) (int, error)
18 Write(int64, []byte) (int, error)
19 Close() error
20}
21
22type Cons func() (File, error)
23
24type FileMap map[string]Cons
25
26type FileServer struct {
27 neinp.NopP2000
28 root File
29 fids *fid.Map
30
31 stat map[string]*stat.Stat
32 open *sync.Map // fid.Fid → File
33 cons FileMap
34}
35
36const version = "9P2000"
37
38// TODO: Currently only supports a flat hierarchy with one root
39// directory and several regular files located inside this directory.
40
41func 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 }
48
49 rootStat := createStat("/", 0555|stat.Dir)
50 rootFile := directory{stat: rootStat}
51
52 for name, _ := range files {
53 s := createStat(name, 0644)
54 fs.stat[name] = &s
55 rootFile.children = append(rootFile.children, s)
56 }
57
58 fs.stat[rootStat.Name] = &rootStat
59 fs.cons[rootStat.Name] = func() (File, error) { return rootFile, nil }
60
61 return fs
62}
63
64func (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 }
68
69 // TODO: Sanity check on msize
70 return message.RVersion{Version: version, Msize: msg.Msize}, nil
71}
72
73func (s *FileServer) Attach(ctx context.Context, msg message.TAttach) (message.RAttach, error) {
74 stat := s.stat["/"]
75
76 s.fids.Set(msg.Fid, stat)
77 return message.RAttach{Qid: stat.Qid}, nil
78}
79
80func (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 }
85
86 return message.RStat{Stat: *stat}, nil
87}
88
89func (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 }
96
97 newStat := stat
98 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 }
105
106 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 }
112
113 if len(msg.Wname) == 1 {
114 return message.RWalk{Wqid: []qid.Qid{newStat.Qid}}, nil
115 } else {
116 return message.RWalk{}, nil
117 }
118}
119
120func (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 }
125
126 file, err := s.cons[stat.Name]()
127 if err != nil {
128 return message.ROpen{}, err
129 }
130
131 s.open.Store(msg.Fid, file)
132 return message.ROpen{Qid: stat.Qid}, nil
133}
134
135func (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)
141
142 // TODO: Sanity check count
143 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}, nil
147 } else if err != nil {
148 return message.RRead{}, err
149 }
150
151 return message.RRead{Count: uint32(n), Data: buf[:n]}, nil
152}
153
154func (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)
160
161 n, err := file.Write(int64(msg.Offset), msg.Data)
162 if err != nil {
163 return message.RWrite{}, err
164 }
165
166 return message.RWrite{Count: uint32(n)}, nil
167}
168
169func (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)
172
173 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{}, err
179 }
180 }
181
182 return message.RClunk{}, nil
183}