diff --git a/src/io/fs/fs.go b/src/io/fs/fs.go index de5c465d9d..d9f89fc6ee 100644 --- a/src/io/fs/fs.go +++ b/src/io/fs/fs.go @@ -12,6 +12,118 @@ import ( "time" ) +// An FS provides access to a hierarchical file system. +// +// The FS interface is the minimum implementation required of the file system. +// A file system may implement additional interfaces, +// such as fsutil.ReadFileFS, to provide additional or optimized functionality. +// See io/fsutil for details. +type FS interface { + // Open opens the named file. + // + // When Open returns an error, it should be of type *PathError + // with the Op field set to "open", the Path field set to name, + // and the Err field describing the problem. + // + // Open should reject attempts to open names that do not satisfy + // ValidPath(name), returning a *PathError with Err set to + // ErrInvalid or ErrNotExist. + Open(name string) (File, error) +} + +// ValidPath reports whether the given path name +// is valid for use in a call to Open. +// Path names passed to open are unrooted, slash-separated +// sequences of path elements, like “x/y/z”. +// Path names must not contain a “.” or “..” or empty element, +// except for the special case that the root directory is named “.”. +// +// Paths are slash-separated on all systems, even Windows. +// Backslashes must not appear in path names. +func ValidPath(name string) bool { + if name == "." { + // special case + return true + } + + // Iterate over elements in name, checking each. + for { + i := 0 + for i < len(name) && name[i] != '/' { + if name[i] == '\\' { + return false + } + i++ + } + elem := name[:i] + if elem == "" || elem == "." || elem == ".." { + return false + } + if i == len(name) { + return true // reached clean ending + } + name = name[i+1:] + } +} + +// A File provides access to a single file. +// The File interface is the minimum implementation required of the file. +// A file may implement additional interfaces, such as +// ReadDirFile, ReaderAt, or Seeker, to provide additional or optimized functionality. +type File interface { + Stat() (FileInfo, error) + Read([]byte) (int, error) + Close() error +} + +// A DirEntry is an entry read from a directory +// (using the ReadDir function or a ReadDirFile's ReadDir method). +type DirEntry interface { + // Name returns the name of the file (or subdirectory) described by the entry. + // This name is only the final element of the path (the base name), not the entire path. + // For example, Name would return "hello.go" not "/home/gopher/hello.go". + Name() string + + // IsDir reports whether the entry describes a directory. + IsDir() bool + + // Type returns the type bits for the entry. + // The type bits are a subset of the usual FileMode bits, those returned by the FileMode.Type method. + Type() FileMode + + // Info returns the FileInfo for the file or subdirectory described by the entry. + // The returned FileInfo may be from the time of the original directory read + // or from the time of the call to Info. If the file has been removed or renamed + // since the directory read, Info may return an error satisfying errors.Is(err, ErrNotExist). + // If the entry denotes a symbolic link, Info reports the information about the link itself, + // not the link's target. + Info() (FileInfo, error) +} + +// A ReadDirFile is a directory file whose entries can be read with the ReadDir method. +// Every directory file should implement this interface. +// (It is permissible for any file to implement this interface, +// but if so ReadDir should return an error for non-directories.) +type ReadDirFile interface { + File + + // ReadDir reads the contents of the directory and returns + // a slice of up to n DirEntry values in directory order. + // Subsequent calls on the same file will yield further DirEntry values. + // + // If n > 0, ReadDir returns at most n DirEntry structures. + // In this case, if ReadDir returns an empty slice, it will return + // a non-nil error explaining why. + // At the end of a directory, the error is io.EOF. + // + // If n <= 0, ReadDir returns all the DirEntry values from the directory + // in a single slice. In this case, if ReadDir succeeds (reads all the way + // to the end of the directory), it returns the slice and a nil error. + // If it encounters an error before the end of the directory, + // ReadDir returns the DirEntry list read until that point and a non-nil error. + ReadDir(n int) ([]DirEntry, error) +} + // Generic file system errors. // Errors returned by file systems can be tested against these errors // using errors.Is. diff --git a/src/io/fs/fs_test.go b/src/io/fs/fs_test.go new file mode 100644 index 0000000000..8d395fc0db --- /dev/null +++ b/src/io/fs/fs_test.go @@ -0,0 +1,48 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs_test + +import ( + . "io/fs" + "testing" +) + +var isValidPathTests = []struct { + name string + ok bool +}{ + {".", true}, + {"x", true}, + {"x/y", true}, + + {"", false}, + {"..", false}, + {"/", false}, + {"x/", false}, + {"/x", false}, + {"x/y/", false}, + {"/x/y", false}, + {"./", false}, + {"./x", false}, + {"x/.", false}, + {"x/./y", false}, + {"../", false}, + {"../x", false}, + {"x/..", false}, + {"x/../y", false}, + {"x//y", false}, + {`x\`, false}, + {`x\y`, false}, + {`\x`, false}, +} + +func TestValidPath(t *testing.T) { + for _, tt := range isValidPathTests { + ok := ValidPath(tt.name) + if ok != tt.ok { + t.Errorf("ValidPath(%q) = %v, want %v", tt.name, ok, tt.ok) + } + } +} diff --git a/src/os/dir.go b/src/os/dir.go index a312001704..b56d998459 100644 --- a/src/os/dir.go +++ b/src/os/dir.go @@ -4,6 +4,8 @@ package os +import "io/fs" + type readdirMode int const ( @@ -62,27 +64,7 @@ func (f *File) Readdirnames(n int) (names []string, err error) { // A DirEntry is an entry read from a directory // (using the ReadDir function or a File's ReadDir method). -type DirEntry interface { - // Name returns the name of the file (or subdirectory) described by the entry. - // This name is only the final element of the path, not the entire path. - // For example, Name would return "hello.go" not "/home/gopher/hello.go". - Name() string - - // IsDir reports whether the entry describes a subdirectory. - IsDir() bool - - // Type returns the type bits for the entry. - // The type bits are a subset of the usual FileMode bits, those returned by the FileMode.Type method. - Type() FileMode - - // Info returns the FileInfo for the file or subdirectory described by the entry. - // The returned FileInfo may be from the time of the original directory read - // or from the time of the call to Info. If the file has been removed or renamed - // since the directory read, Info may return an error satisfying errors.Is(err, ErrNotExist). - // If the entry denotes a symbolic link, Info reports the information about the link itself, - // not the link's target. - Info() (FileInfo, error) -} +type DirEntry = fs.DirEntry // ReadDir reads the contents of the directory associated with the file f // and returns a slice of DirEntry values in directory order.