1
2
3
4
5 package fstest
6
7 import (
8 "io"
9 "io/fs"
10 "path"
11 "slices"
12 "strings"
13 "time"
14 )
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 type MapFS map[string]*MapFile
34
35
36 type MapFile struct {
37 Data []byte
38 Mode fs.FileMode
39 ModTime time.Time
40 Sys any
41 }
42
43 var _ fs.FS = MapFS(nil)
44 var _ fs.ReadLinkFS = MapFS(nil)
45 var _ fs.File = (*openMapFile)(nil)
46
47
48 func (fsys MapFS) Open(name string) (fs.File, error) {
49 if !fs.ValidPath(name) {
50 return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
51 }
52 realName, ok := fsys.resolveSymlinks(name)
53 if !ok {
54 return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
55 }
56
57 file := fsys[realName]
58 if file != nil && file.Mode&fs.ModeDir == 0 {
59
60 return &openMapFile{name, mapFileInfo{path.Base(name), file}, 0}, nil
61 }
62
63
64
65
66
67 var list []mapFileInfo
68 var need = make(map[string]bool)
69 if realName == "." {
70 for fname, f := range fsys {
71 i := strings.Index(fname, "/")
72 if i < 0 {
73 if fname != "." {
74 list = append(list, mapFileInfo{fname, f})
75 }
76 } else {
77 need[fname[:i]] = true
78 }
79 }
80 } else {
81 prefix := realName + "/"
82 for fname, f := range fsys {
83 if strings.HasPrefix(fname, prefix) {
84 felem := fname[len(prefix):]
85 i := strings.Index(felem, "/")
86 if i < 0 {
87 list = append(list, mapFileInfo{felem, f})
88 } else {
89 need[fname[len(prefix):len(prefix)+i]] = true
90 }
91 }
92 }
93
94
95
96 if file == nil && list == nil && len(need) == 0 {
97 return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
98 }
99 }
100 for _, fi := range list {
101 delete(need, fi.name)
102 }
103 for name := range need {
104 list = append(list, mapFileInfo{name, &MapFile{Mode: fs.ModeDir | 0555}})
105 }
106 slices.SortFunc(list, func(a, b mapFileInfo) int {
107 return strings.Compare(a.name, b.name)
108 })
109
110 if file == nil {
111 file = &MapFile{Mode: fs.ModeDir | 0555}
112 }
113 var elem string
114 if name == "." {
115 elem = "."
116 } else {
117 elem = name[strings.LastIndex(name, "/")+1:]
118 }
119 return &mapDir{name, mapFileInfo{elem, file}, list, 0}, nil
120 }
121
122 func (fsys MapFS) resolveSymlinks(name string) (_ string, ok bool) {
123
124 if file := fsys[name]; file != nil && file.Mode.Type() == fs.ModeSymlink {
125 target := string(file.Data)
126 if path.IsAbs(target) {
127 return "", false
128 }
129 return fsys.resolveSymlinks(path.Join(path.Dir(name), target))
130 }
131
132
133 for i := 0; i < len(name); {
134 j := strings.Index(name[i:], "/")
135 var dir string
136 if j < 0 {
137 dir = name
138 i = len(name)
139 } else {
140 dir = name[:i+j]
141 i += j
142 }
143 if file := fsys[dir]; file != nil && file.Mode.Type() == fs.ModeSymlink {
144 target := string(file.Data)
145 if path.IsAbs(target) {
146 return "", false
147 }
148 return fsys.resolveSymlinks(path.Join(path.Dir(dir), target) + name[i:])
149 }
150 i += len("/")
151 }
152 return name, fs.ValidPath(name)
153 }
154
155
156 func (fsys MapFS) ReadLink(name string) (string, error) {
157 info, err := fsys.lstat(name)
158 if err != nil {
159 return "", &fs.PathError{Op: "readlink", Path: name, Err: err}
160 }
161 if info.f.Mode.Type() != fs.ModeSymlink {
162 return "", &fs.PathError{Op: "readlink", Path: name, Err: fs.ErrInvalid}
163 }
164 return string(info.f.Data), nil
165 }
166
167
168
169
170 func (fsys MapFS) Lstat(name string) (fs.FileInfo, error) {
171 info, err := fsys.lstat(name)
172 if err != nil {
173 return nil, &fs.PathError{Op: "lstat", Path: name, Err: err}
174 }
175 return info, nil
176 }
177
178 func (fsys MapFS) lstat(name string) (*mapFileInfo, error) {
179 if !fs.ValidPath(name) {
180 return nil, fs.ErrNotExist
181 }
182 realDir, ok := fsys.resolveSymlinks(path.Dir(name))
183 if !ok {
184 return nil, fs.ErrNotExist
185 }
186 elem := path.Base(name)
187 realName := path.Join(realDir, elem)
188
189 file := fsys[realName]
190 if file != nil {
191 return &mapFileInfo{elem, file}, nil
192 }
193
194 if realName == "." {
195 return &mapFileInfo{elem, &MapFile{Mode: fs.ModeDir | 0555}}, nil
196 }
197
198 prefix := realName + "/"
199 for fname := range fsys {
200 if strings.HasPrefix(fname, prefix) {
201 return &mapFileInfo{elem, &MapFile{Mode: fs.ModeDir | 0555}}, nil
202 }
203 }
204
205
206
207 return nil, fs.ErrNotExist
208 }
209
210
211
212
213
214
215
216 type fsOnly struct{ fs.FS }
217
218 func (fsys MapFS) ReadFile(name string) ([]byte, error) {
219 return fs.ReadFile(fsOnly{fsys}, name)
220 }
221
222 func (fsys MapFS) Stat(name string) (fs.FileInfo, error) {
223 return fs.Stat(fsOnly{fsys}, name)
224 }
225
226 func (fsys MapFS) ReadDir(name string) ([]fs.DirEntry, error) {
227 return fs.ReadDir(fsOnly{fsys}, name)
228 }
229
230 func (fsys MapFS) Glob(pattern string) ([]string, error) {
231 return fs.Glob(fsOnly{fsys}, pattern)
232 }
233
234 type noSub struct {
235 MapFS
236 }
237
238 func (noSub) Sub() {}
239
240 func (fsys MapFS) Sub(dir string) (fs.FS, error) {
241 return fs.Sub(noSub{fsys}, dir)
242 }
243
244
245 type mapFileInfo struct {
246 name string
247 f *MapFile
248 }
249
250 func (i *mapFileInfo) Name() string { return path.Base(i.name) }
251 func (i *mapFileInfo) Size() int64 { return int64(len(i.f.Data)) }
252 func (i *mapFileInfo) Mode() fs.FileMode { return i.f.Mode }
253 func (i *mapFileInfo) Type() fs.FileMode { return i.f.Mode.Type() }
254 func (i *mapFileInfo) ModTime() time.Time { return i.f.ModTime }
255 func (i *mapFileInfo) IsDir() bool { return i.f.Mode&fs.ModeDir != 0 }
256 func (i *mapFileInfo) Sys() any { return i.f.Sys }
257 func (i *mapFileInfo) Info() (fs.FileInfo, error) { return i, nil }
258
259 func (i *mapFileInfo) String() string {
260 return fs.FormatFileInfo(i)
261 }
262
263
264 type openMapFile struct {
265 path string
266 mapFileInfo
267 offset int64
268 }
269
270 func (f *openMapFile) Stat() (fs.FileInfo, error) { return &f.mapFileInfo, nil }
271
272 func (f *openMapFile) Close() error { return nil }
273
274 func (f *openMapFile) Read(b []byte) (int, error) {
275 if f.offset >= int64(len(f.f.Data)) {
276 return 0, io.EOF
277 }
278 if f.offset < 0 {
279 return 0, &fs.PathError{Op: "read", Path: f.path, Err: fs.ErrInvalid}
280 }
281 n := copy(b, f.f.Data[f.offset:])
282 f.offset += int64(n)
283 return n, nil
284 }
285
286 func (f *openMapFile) Seek(offset int64, whence int) (int64, error) {
287 switch whence {
288 case 0:
289
290 case 1:
291 offset += f.offset
292 case 2:
293 offset += int64(len(f.f.Data))
294 }
295 if offset < 0 || offset > int64(len(f.f.Data)) {
296 return 0, &fs.PathError{Op: "seek", Path: f.path, Err: fs.ErrInvalid}
297 }
298 f.offset = offset
299 return offset, nil
300 }
301
302 func (f *openMapFile) ReadAt(b []byte, offset int64) (int, error) {
303 if offset < 0 || offset > int64(len(f.f.Data)) {
304 return 0, &fs.PathError{Op: "read", Path: f.path, Err: fs.ErrInvalid}
305 }
306 n := copy(b, f.f.Data[offset:])
307 if n < len(b) {
308 return n, io.EOF
309 }
310 return n, nil
311 }
312
313
314 type mapDir struct {
315 path string
316 mapFileInfo
317 entry []mapFileInfo
318 offset int
319 }
320
321 func (d *mapDir) Stat() (fs.FileInfo, error) { return &d.mapFileInfo, nil }
322 func (d *mapDir) Close() error { return nil }
323 func (d *mapDir) Read(b []byte) (int, error) {
324 return 0, &fs.PathError{Op: "read", Path: d.path, Err: fs.ErrInvalid}
325 }
326
327 func (d *mapDir) ReadDir(count int) ([]fs.DirEntry, error) {
328 n := len(d.entry) - d.offset
329 if n == 0 && count > 0 {
330 return nil, io.EOF
331 }
332 if count > 0 && n > count {
333 n = count
334 }
335 list := make([]fs.DirEntry, n)
336 for i := range list {
337 list[i] = &d.entry[d.offset+i]
338 }
339 d.offset += n
340 return list, nil
341 }
342
View as plain text