-
Notifications
You must be signed in to change notification settings - Fork 0
/
fs.go
102 lines (91 loc) · 2.89 KB
/
fs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// Copyright 2021 Michael J. Fromberger. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fpath
import (
"context"
"errors"
"io/fs"
slashpath "path"
"github.com/creachadair/ffs/file"
)
func pathErr(op, path string, err error) error {
return &fs.PathError{Op: op, Path: path, Err: err}
}
// FS implements the standard library fs.FS, fs.StatFS, fs.SubFS, and
// fs.ReadDirFS interfaces. Path traversal is rooted at a file assigned when
// the FS is created.
type FS struct {
ctx context.Context
root *file.File
}
// NewFS constructs an FS rooted at the given file. The context is held by the
// FS and must be valid for the full extent of its use.
func NewFS(ctx context.Context, root *file.File) FS {
return FS{ctx: ctx, root: root}
}
// Open implements the fs.FS interface. The concrete type of the file returned
// by a successful Open call is *file.Cursor.
func (fp FS) Open(path string) (fs.File, error) {
target, err := fp.openFile("open", path)
if err != nil {
return nil, err
}
return target.Cursor(fp.ctx), nil
}
// Stat implements the fs.StatFS interface. The concrete type of the info
// record returned by a successful Stat call is file.FileInfo.
func (fp FS) Stat(path string) (fs.FileInfo, error) {
target, err := fp.openFile("stat", path)
if err != nil {
return nil, err
}
return target.Stat().FileInfo(), nil
}
// Sub implements the fs.SubFS interface.
func (fp FS) Sub(dir string) (fs.FS, error) {
target, err := fp.openFile("sub", dir)
if err != nil {
return nil, err
}
return NewFS(fp.ctx, target), nil
}
// ReadDir implements the fs.ReadDirFS interface.
func (fp FS) ReadDir(path string) ([]fs.DirEntry, error) {
target, err := fp.openFile("readdir", path)
if err != nil {
return nil, err
}
kids := target.Child()
out := make([]fs.DirEntry, kids.Len())
for i, name := range kids.Names() {
kid, err := target.Open(fp.ctx, name)
if err != nil {
return nil, pathErr("readdir", slashpath.Join(path, name), err)
}
out[i] = fs.FileInfoToDirEntry(kid.Stat().FileInfo())
}
return out, nil
}
func (fp FS) openFile(op, path string) (*file.File, error) {
if !fs.ValidPath(path) {
return nil, pathErr(op, path, fs.ErrInvalid)
}
target, err := Open(fp.ctx, fp.root, path)
if err == nil {
return target, nil
} else if errors.Is(err, file.ErrChildNotFound) {
return nil, pathErr(op, path, fs.ErrNotExist)
}
return nil, pathErr(op, path, err)
}