blob: 0a17be5f60046c655bf7fea5a00da89d20240944 [file] [log] [blame]
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
}