pkg os, func OpenRoot(string) (*Root, error) #67002
pkg os, method (*Root) Close() error #67002
pkg os, method (*Root) Create(string) (*File, error) #67002
+pkg os, method (*Root) FS() fs.FS #67002
pkg os, method (*Root) Lstat(string) (fs.FileInfo, error) #67002
pkg os, method (*Root) Mkdir(string, fs.FileMode) error #67002
pkg os, method (*Root) Name() string #67002
// a general substitute for a chroot-style security mechanism when the directory tree
// contains arbitrary content.
//
+// Use [Root.FS] to obtain a fs.FS that prevents escapes from the tree via symbolic links.
+//
// The directory dir must not be "".
//
// The result implements [io/fs.StatFS], [io/fs.ReadFileFS] and
return nil, err
}
defer f.Close()
+ return readFileContents(f)
+}
+func readFileContents(f *File) ([]byte, error) {
var size int
if info, err := f.Stat(); err == nil {
size64 := info.Size()
func TestDirFS(t *testing.T) {
t.Parallel()
+ testDirFS(t, DirFS("./testdata/dirfs"))
+}
+
+func TestRootDirFS(t *testing.T) {
+ t.Parallel()
+ r, err := OpenRoot("./testdata/dirfs")
+ if err != nil {
+ t.Fatal(err)
+ }
+ testDirFS(t, r.FS())
+}
+func testDirFS(t *testing.T, fsys fs.FS) {
forceMFTUpdateOnWindows(t, "./testdata/dirfs")
- fsys := DirFS("./testdata/dirfs")
if err := fstest.TestFS(fsys, "a", "b", "dir/x"); err != nil {
t.Fatal(err)
}
import (
"errors"
+ "internal/bytealg"
+ "internal/stringslite"
"internal/testlog"
+ "io/fs"
"runtime"
+ "slices"
)
// Root may be used to only access files within a single directory tree.
parts = append(parts, suffix...)
return parts, nil
}
+
+// FS returns a file system (an fs.FS) for the tree of files in the root.
+//
+// The result implements [io/fs.StatFS], [io/fs.ReadFileFS] and
+// [io/fs.ReadDirFS].
+func (r *Root) FS() fs.FS {
+ return (*rootFS)(r)
+}
+
+type rootFS Root
+
+func (rfs *rootFS) Open(name string) (fs.File, error) {
+ r := (*Root)(rfs)
+ if !isValidRootFSPath(name) {
+ return nil, &PathError{Op: "open", Path: name, Err: ErrInvalid}
+ }
+ f, err := r.Open(name)
+ if err != nil {
+ return nil, err
+ }
+ return f, nil
+}
+
+func (rfs *rootFS) ReadDir(name string) ([]DirEntry, error) {
+ r := (*Root)(rfs)
+ if !isValidRootFSPath(name) {
+ return nil, &PathError{Op: "readdir", Path: name, Err: ErrInvalid}
+ }
+
+ // This isn't efficient: We just open a regular file and ReadDir it.
+ // Ideally, we would skip creating a *File entirely and operate directly
+ // on the file descriptor, but that will require some extensive reworking
+ // of directory reading in general.
+ //
+ // This suffices for the moment.
+ f, err := r.Open(name)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ dirs, err := f.ReadDir(-1)
+ slices.SortFunc(dirs, func(a, b DirEntry) int {
+ return bytealg.CompareString(a.Name(), b.Name())
+ })
+ return dirs, err
+}
+
+func (rfs *rootFS) ReadFile(name string) ([]byte, error) {
+ r := (*Root)(rfs)
+ if !isValidRootFSPath(name) {
+ return nil, &PathError{Op: "readfile", Path: name, Err: ErrInvalid}
+ }
+ f, err := r.Open(name)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return readFileContents(f)
+}
+
+func (rfs *rootFS) Stat(name string) (FileInfo, error) {
+ r := (*Root)(rfs)
+ if !isValidRootFSPath(name) {
+ return nil, &PathError{Op: "stat", Path: name, Err: ErrInvalid}
+ }
+ return r.Stat(name)
+}
+
+// isValidRootFSPath reprots whether name is a valid filename to pass a Root.FS method.
+func isValidRootFSPath(name string) bool {
+ if !fs.ValidPath(name) {
+ return false
+ }
+ if runtime.GOOS == "windows" {
+ // fs.FS paths are /-separated.
+ // On Windows, reject the path if it contains any \ separators.
+ // Other forms of invalid path (for example, "NUL") are handled by
+ // Root's usual file lookup mechanisms.
+ if stringslite.IndexByte(name, '\\') >= 0 {
+ return false
+ }
+ }
+ return true
+}