package main import ( "errors" "os" "path" "sort" ) // 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 ReadFileFS, to provide additional or optimized functionality. 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) (*os.File, error) Stat(name string) (os.FileInfo, error) } // ReadDir reads the named directory, // returning all its directory entries sorted by filename. // If an error occurs reading the directory, // ReadDir returns the entries it was able to read before the error, // along with the error. func ReadDir(fsys FS, name string) ([]DirEntry, error) { f, err := fsys.Open(name) if err != nil { return nil, err } defer f.Close() infos, err := f.Readdir(-1) dirs := make([]DirEntry, len(infos)) for i, info := range infos { dirs[i] = &statDirEntry{info} } sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() }) return dirs, err } // DirFS returns a file system (an fs.FS) for the tree of files rooted at the directory dir. // // Note that DirFS("/prefix") only guarantees that the Open calls it makes to the // operating system will begin with "/prefix": DirFS("/prefix").Open("file") is the // same as os.Open("/prefix/file"). So if /prefix/file is a symbolic link pointing outside // the /prefix tree, then using DirFS does not stop the access any more than using // os.Open does. DirFS is therefore not a general substitute for a chroot-style security // mechanism when the directory tree contains arbitrary content. func DirFS(dir string) FS { return dirFS(dir) } type dirFS string func (dir dirFS) Open(name string) (*os.File, error) { //if !fs.ValidPath(name) || runtime.GOOS == "windows" && containsAny(name, `\:`) { // return nil, &PathError{Op: "open", Path: name, Err: ErrInvalid} //} f, err := os.Open(string(dir) + "/" + name) if err != nil { return nil, err } return f, nil } func (dir dirFS) Stat(name string) (os.FileInfo, error) { info, err := os.Stat(string(dir) + "/" + name) if err != nil { return info, err } return info, nil } // SkipDir is used as a return value from WalkDirFuncs to indicate that // the directory named in the call is to be skipped. It is not returned // as an error by any function. var SkipDir = errors.New("skip this directory") // WalkDirFunc is the type of the function called by WalkDir to visit // each file or directory. // // The path argument contains the argument to WalkDir as a prefix. // That is, if WalkDir is called with root argument "dir" and finds a file // named "a" in that directory, the walk function will be called with // argument "dir/a". // // The d argument is the fs.DirEntry for the named path. // // The error result returned by the function controls how WalkDir // continues. If the function returns the special value SkipDir, WalkDir // skips the current directory (path if d.IsDir() is true, otherwise // path's parent directory). Otherwise, if the function returns a non-nil // error, WalkDir stops entirely and returns that error. // // The err argument reports an error related to path, signaling that // WalkDir will not walk into that directory. The function can decide how // to handle that error; as described earlier, returning the error will // cause WalkDir to stop walking the entire tree. // // WalkDir calls the function with a non-nil err argument in two cases. // // First, if the initial fs.Stat on the root directory fails, WalkDir // calls the function with path set to root, d set to nil, and err set to // the error from fs.Stat. // // Second, if a directory's ReadDir method fails, WalkDir calls the // function with path set to the directory's path, d set to an // fs.DirEntry describing the directory, and err set to the error from // ReadDir. In this second case, the function is called twice with the // path of the directory: the first call is before the directory read is // attempted and has err set to nil, giving the function a chance to // return SkipDir and avoid the ReadDir entirely. The second call is // after a failed ReadDir and reports the error from ReadDir. // (If ReadDir succeeds, there is no second call.) // // The differences between WalkDirFunc compared to filepath.WalkFunc are: // // - The second argument has type fs.DirEntry instead of fs.FileInfo. // - The function is called before reading a directory, to allow SkipDir // to bypass the directory read entirely. // - If a directory read fails, the function is called a second time // for that directory to report the error. // type WalkDirFunc func(path string, d DirEntry, err error) error // walkDir recursively descends path, calling walkDirFn. func walkDir(fsys FS, name string, d DirEntry, walkDirFn WalkDirFunc) error { if err := walkDirFn(name, d, nil); err != nil || !d.IsDir() { if err == SkipDir && d.IsDir() { // Successfully skipped directory. err = nil } return err } dirs, err := ReadDir(fsys, name) if err != nil { // Second call, to report ReadDir error. err = walkDirFn(name, d, err) if err != nil { return err } } for _, d1 := range dirs { name1 := path.Join(name, d1.Name()) if err := walkDir(fsys, name1, d1, walkDirFn); err != nil { if err == SkipDir { break } return err } } return nil } // WalkDir walks the file tree rooted at root, calling fn for each file or // directory in the tree, including root. // // All errors that arise visiting files and directories are filtered by fn: // see the fs.WalkDirFunc documentation for details. // // The files are walked in lexical order, which makes the output deterministic // but requires WalkDir to read an entire directory into memory before proceeding // to walk that directory. // // WalkDir does not follow symbolic links found in directories, // but if root itself is a symbolic link, its target will be walked. func WalkDir(fsys FS, root string, fn WalkDirFunc) error { info, err := fsys.Stat(root) if err != nil { err = fn(root, nil, err) } else { err = walkDir(fsys, root, &statDirEntry{info}, fn) } if err == SkipDir { return nil } return err } // 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() os.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() (os.FileInfo, error) } type statDirEntry struct { info os.FileInfo } func (d *statDirEntry) Name() string { return d.info.Name() } func (d *statDirEntry) IsDir() bool { return d.info.IsDir() } func (d *statDirEntry) Type() os.FileMode { return d.info.Mode() & os.ModeType } func (d *statDirEntry) Info() (os.FileInfo, error) { return d.info, nil }