| package vfs |
| |
| import ( |
| "context" |
| "io/fs" |
| "os" |
| "path/filepath" |
| "strings" |
| |
| "go.skia.org/infra/go/repo_root" |
| "go.skia.org/infra/go/skerr" |
| ) |
| |
| // Local returns a FS which uses the local filesystem with the given root. |
| // Absolute paths may only be passed to Open() if they are subdirectories of the |
| // given root. Relative paths must not contain ".." |
| func Local(root string) FS { |
| return &localFS{root: root} |
| } |
| |
| // InRepoRoot returns a FS which uses the local filesystem with the root being |
| // the root of the Git repository where the current working directory resides. |
| // Absolute paths may only be passed to Open() if they are subdirectories of the |
| // given root. Relative paths must not contain ".." |
| func InRepoRoot() (FS, error) { |
| root, err := repo_root.GetLocal() |
| if err != nil { |
| return nil, err |
| } |
| return Local(root), nil |
| } |
| |
| // localFS is an implementation of FS which uses the local filesystem. |
| type localFS struct { |
| root string |
| } |
| |
| // Open implements FS. |
| func (fs *localFS) Open(_ context.Context, name string) (File, error) { |
| if !filepath.IsAbs(name) { |
| name = filepath.Join(fs.root, name) |
| } |
| name, err := filepath.Abs(name) |
| if err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| if !strings.HasPrefix(name, fs.root) { |
| return nil, skerr.Fmt("path %s is not rooted within %s", name, fs.root) |
| } |
| f, err := os.Open(name) |
| if err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| return &localFile{file: f}, nil |
| } |
| |
| // Create implements FS. |
| func (fs *localFS) Create(_ context.Context, name string) (File, error) { |
| if !filepath.IsAbs(name) { |
| name = filepath.Join(fs.root, name) |
| } |
| name, err := filepath.Abs(name) |
| if err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| if !strings.HasPrefix(name, fs.root) { |
| return nil, skerr.Fmt("path %s is not rooted within %s", name, fs.root) |
| } |
| f, err := os.Create(name) |
| if err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| return &localFile{file: f}, nil |
| } |
| |
| // Close implements FS. |
| func (fs *localFS) Close(_ context.Context) error { |
| return nil |
| } |
| |
| // localFile is an implementation of File which wraps an os.File. |
| type localFile struct { |
| file *os.File |
| } |
| |
| // Stat implements File. |
| func (f *localFile) Stat(_ context.Context) (fs.FileInfo, error) { |
| return f.file.Stat() |
| } |
| |
| // Read implements File. |
| func (f *localFile) Read(_ context.Context, buf []byte) (int, error) { |
| return f.file.Read(buf) |
| } |
| |
| // ReadDir implements File. |
| func (f *localFile) ReadDir(_ context.Context, n int) ([]fs.FileInfo, error) { |
| return f.file.Readdir(n) |
| } |
| |
| // Close implements File. |
| func (f *localFile) Close(_ context.Context) error { |
| return f.file.Close() |
| } |
| |
| // Write implements File. |
| func (f *localFile) Write(_ context.Context, b []byte) (int, error) { |
| return f.file.Write(b) |
| } |
| |
| // Ensure that localFile implements File. |
| var _ File = &localFile{} |
| |
| // TempDirFS is a FS implementation which is rooted in a temporary directory. |
| // Calling Close causes the directory to be deleted. |
| type TempDirFS struct { |
| FS |
| dir string |
| } |
| |
| // Close deletes the temporary directory. |
| func (fs *TempDirFS) Close(ctx context.Context) error { |
| if err := fs.FS.Close(ctx); err != nil { |
| return skerr.Wrap(err) |
| } |
| return skerr.Wrap(os.RemoveAll(fs.dir)) |
| } |
| |
| // Dir returns the temporary directory. |
| func (fs *TempDirFS) Dir() string { |
| return fs.dir |
| } |
| |
| // TempDir returns a FS which is rooted in a temporary directory. Calling Close |
| // causes the directory to be deleted. |
| func TempDir(_ context.Context, dir, prefix string) (*TempDirFS, error) { |
| tmp, err := os.MkdirTemp(dir, prefix) |
| if err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| return &TempDirFS{ |
| FS: Local(tmp), |
| dir: tmp, |
| }, nil |
| } |