| | |
| | |
| | |
| |
|
| | package fstest |
| |
|
| | import ( |
| | "io" |
| | "io/fs" |
| | "path" |
| | "slices" |
| | "strings" |
| | "time" |
| | ) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | type MapFS map[string]*MapFile |
| |
|
| | |
| | type MapFile struct { |
| | Data []byte |
| | Mode fs.FileMode |
| | ModTime time.Time |
| | Sys any |
| | } |
| |
|
| | var _ fs.FS = MapFS(nil) |
| | var _ fs.ReadLinkFS = MapFS(nil) |
| | var _ fs.File = (*openMapFile)(nil) |
| |
|
| | |
| | func (fsys MapFS) Open(name string) (fs.File, error) { |
| | if !fs.ValidPath(name) { |
| | return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} |
| | } |
| | realName, ok := fsys.resolveSymlinks(name) |
| | if !ok { |
| | return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} |
| | } |
| |
|
| | file := fsys[realName] |
| | if file != nil && file.Mode&fs.ModeDir == 0 { |
| | |
| | return &openMapFile{name, mapFileInfo{path.Base(name), file}, 0}, nil |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | var list []mapFileInfo |
| | var need = make(map[string]bool) |
| | if realName == "." { |
| | for fname, f := range fsys { |
| | i := strings.Index(fname, "/") |
| | if i < 0 { |
| | if fname != "." { |
| | list = append(list, mapFileInfo{fname, f}) |
| | } |
| | } else { |
| | need[fname[:i]] = true |
| | } |
| | } |
| | } else { |
| | prefix := realName + "/" |
| | for fname, f := range fsys { |
| | if strings.HasPrefix(fname, prefix) { |
| | felem := fname[len(prefix):] |
| | i := strings.Index(felem, "/") |
| | if i < 0 { |
| | list = append(list, mapFileInfo{felem, f}) |
| | } else { |
| | need[fname[len(prefix):len(prefix)+i]] = true |
| | } |
| | } |
| | } |
| | |
| | |
| | |
| | if file == nil && list == nil && len(need) == 0 { |
| | return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} |
| | } |
| | } |
| | for _, fi := range list { |
| | delete(need, fi.name) |
| | } |
| | for name := range need { |
| | list = append(list, mapFileInfo{name, &MapFile{Mode: fs.ModeDir | 0555}}) |
| | } |
| | slices.SortFunc(list, func(a, b mapFileInfo) int { |
| | return strings.Compare(a.name, b.name) |
| | }) |
| |
|
| | if file == nil { |
| | file = &MapFile{Mode: fs.ModeDir | 0555} |
| | } |
| | var elem string |
| | if name == "." { |
| | elem = "." |
| | } else { |
| | elem = name[strings.LastIndex(name, "/")+1:] |
| | } |
| | return &mapDir{name, mapFileInfo{elem, file}, list, 0}, nil |
| | } |
| |
|
| | func (fsys MapFS) resolveSymlinks(name string) (_ string, ok bool) { |
| | |
| | if file := fsys[name]; file != nil && file.Mode.Type() == fs.ModeSymlink { |
| | target := string(file.Data) |
| | if path.IsAbs(target) { |
| | return "", false |
| | } |
| | return fsys.resolveSymlinks(path.Join(path.Dir(name), target)) |
| | } |
| |
|
| | |
| | for i := 0; i < len(name); { |
| | j := strings.Index(name[i:], "/") |
| | var dir string |
| | if j < 0 { |
| | dir = name |
| | i = len(name) |
| | } else { |
| | dir = name[:i+j] |
| | i += j |
| | } |
| | if file := fsys[dir]; file != nil && file.Mode.Type() == fs.ModeSymlink { |
| | target := string(file.Data) |
| | if path.IsAbs(target) { |
| | return "", false |
| | } |
| | return fsys.resolveSymlinks(path.Join(path.Dir(dir), target) + name[i:]) |
| | } |
| | i += len("/") |
| | } |
| | return name, fs.ValidPath(name) |
| | } |
| |
|
| | |
| | func (fsys MapFS) ReadLink(name string) (string, error) { |
| | info, err := fsys.lstat(name) |
| | if err != nil { |
| | return "", &fs.PathError{Op: "readlink", Path: name, Err: err} |
| | } |
| | if info.f.Mode.Type() != fs.ModeSymlink { |
| | return "", &fs.PathError{Op: "readlink", Path: name, Err: fs.ErrInvalid} |
| | } |
| | return string(info.f.Data), nil |
| | } |
| |
|
| | |
| | |
| | |
| | func (fsys MapFS) Lstat(name string) (fs.FileInfo, error) { |
| | info, err := fsys.lstat(name) |
| | if err != nil { |
| | return nil, &fs.PathError{Op: "lstat", Path: name, Err: err} |
| | } |
| | return info, nil |
| | } |
| |
|
| | func (fsys MapFS) lstat(name string) (*mapFileInfo, error) { |
| | if !fs.ValidPath(name) { |
| | return nil, fs.ErrNotExist |
| | } |
| | realDir, ok := fsys.resolveSymlinks(path.Dir(name)) |
| | if !ok { |
| | return nil, fs.ErrNotExist |
| | } |
| | elem := path.Base(name) |
| | realName := path.Join(realDir, elem) |
| |
|
| | file := fsys[realName] |
| | if file != nil { |
| | return &mapFileInfo{elem, file}, nil |
| | } |
| |
|
| | if realName == "." { |
| | return &mapFileInfo{elem, &MapFile{Mode: fs.ModeDir | 0555}}, nil |
| | } |
| | |
| | prefix := realName + "/" |
| | for fname := range fsys { |
| | if strings.HasPrefix(fname, prefix) { |
| | return &mapFileInfo{elem, &MapFile{Mode: fs.ModeDir | 0555}}, nil |
| | } |
| | } |
| | |
| | |
| | |
| | return nil, fs.ErrNotExist |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | type fsOnly struct{ fs.FS } |
| |
|
| | func (fsys MapFS) ReadFile(name string) ([]byte, error) { |
| | return fs.ReadFile(fsOnly{fsys}, name) |
| | } |
| |
|
| | func (fsys MapFS) Stat(name string) (fs.FileInfo, error) { |
| | return fs.Stat(fsOnly{fsys}, name) |
| | } |
| |
|
| | func (fsys MapFS) ReadDir(name string) ([]fs.DirEntry, error) { |
| | return fs.ReadDir(fsOnly{fsys}, name) |
| | } |
| |
|
| | func (fsys MapFS) Glob(pattern string) ([]string, error) { |
| | return fs.Glob(fsOnly{fsys}, pattern) |
| | } |
| |
|
| | type noSub struct { |
| | MapFS |
| | } |
| |
|
| | func (noSub) Sub() {} |
| |
|
| | func (fsys MapFS) Sub(dir string) (fs.FS, error) { |
| | return fs.Sub(noSub{fsys}, dir) |
| | } |
| |
|
| | |
| | type mapFileInfo struct { |
| | name string |
| | f *MapFile |
| | } |
| |
|
| | func (i *mapFileInfo) Name() string { return path.Base(i.name) } |
| | func (i *mapFileInfo) Size() int64 { return int64(len(i.f.Data)) } |
| | func (i *mapFileInfo) Mode() fs.FileMode { return i.f.Mode } |
| | func (i *mapFileInfo) Type() fs.FileMode { return i.f.Mode.Type() } |
| | func (i *mapFileInfo) ModTime() time.Time { return i.f.ModTime } |
| | func (i *mapFileInfo) IsDir() bool { return i.f.Mode&fs.ModeDir != 0 } |
| | func (i *mapFileInfo) Sys() any { return i.f.Sys } |
| | func (i *mapFileInfo) Info() (fs.FileInfo, error) { return i, nil } |
| |
|
| | func (i *mapFileInfo) String() string { |
| | return fs.FormatFileInfo(i) |
| | } |
| |
|
| | |
| | type openMapFile struct { |
| | path string |
| | mapFileInfo |
| | offset int64 |
| | } |
| |
|
| | func (f *openMapFile) Stat() (fs.FileInfo, error) { return &f.mapFileInfo, nil } |
| |
|
| | func (f *openMapFile) Close() error { return nil } |
| |
|
| | func (f *openMapFile) Read(b []byte) (int, error) { |
| | if f.offset >= int64(len(f.f.Data)) { |
| | return 0, io.EOF |
| | } |
| | if f.offset < 0 { |
| | return 0, &fs.PathError{Op: "read", Path: f.path, Err: fs.ErrInvalid} |
| | } |
| | n := copy(b, f.f.Data[f.offset:]) |
| | f.offset += int64(n) |
| | return n, nil |
| | } |
| |
|
| | func (f *openMapFile) Seek(offset int64, whence int) (int64, error) { |
| | switch whence { |
| | case 0: |
| | |
| | case 1: |
| | offset += f.offset |
| | case 2: |
| | offset += int64(len(f.f.Data)) |
| | } |
| | if offset < 0 || offset > int64(len(f.f.Data)) { |
| | return 0, &fs.PathError{Op: "seek", Path: f.path, Err: fs.ErrInvalid} |
| | } |
| | f.offset = offset |
| | return offset, nil |
| | } |
| |
|
| | func (f *openMapFile) ReadAt(b []byte, offset int64) (int, error) { |
| | if offset < 0 || offset > int64(len(f.f.Data)) { |
| | return 0, &fs.PathError{Op: "read", Path: f.path, Err: fs.ErrInvalid} |
| | } |
| | n := copy(b, f.f.Data[offset:]) |
| | if n < len(b) { |
| | return n, io.EOF |
| | } |
| | return n, nil |
| | } |
| |
|
| | |
| | type mapDir struct { |
| | path string |
| | mapFileInfo |
| | entry []mapFileInfo |
| | offset int |
| | } |
| |
|
| | func (d *mapDir) Stat() (fs.FileInfo, error) { return &d.mapFileInfo, nil } |
| | func (d *mapDir) Close() error { return nil } |
| | func (d *mapDir) Read(b []byte) (int, error) { |
| | return 0, &fs.PathError{Op: "read", Path: d.path, Err: fs.ErrInvalid} |
| | } |
| |
|
| | func (d *mapDir) ReadDir(count int) ([]fs.DirEntry, error) { |
| | n := len(d.entry) - d.offset |
| | if n == 0 && count > 0 { |
| | return nil, io.EOF |
| | } |
| | if count > 0 && n > count { |
| | n = count |
| | } |
| | list := make([]fs.DirEntry, n) |
| | for i := range list { |
| | list[i] = &d.entry[d.offset+i] |
| | } |
| | d.offset += n |
| | return list, nil |
| | } |
| |
|