368 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			368 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package godirwalk
 | 
						|
 | 
						|
import (
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"sort"
 | 
						|
 | 
						|
	"github.com/pkg/errors"
 | 
						|
)
 | 
						|
 | 
						|
// DefaultScratchBufferSize specifies the size of the scratch buffer that will
 | 
						|
// be allocated by Walk, ReadDirents, or ReadDirnames when a scratch buffer is
 | 
						|
// not provided or the scratch buffer that is provided is smaller than
 | 
						|
// MinimumScratchBufferSize bytes. This may seem like a large value; however,
 | 
						|
// when a program intends to enumerate large directories, having a larger
 | 
						|
// scratch buffer results in fewer operating system calls.
 | 
						|
const DefaultScratchBufferSize = 64 * 1024
 | 
						|
 | 
						|
// MinimumScratchBufferSize specifies the minimum size of the scratch buffer
 | 
						|
// that Walk, ReadDirents, and ReadDirnames will use when reading file entries
 | 
						|
// from the operating system. It is initialized to the result from calling
 | 
						|
// `os.Getpagesize()` during program startup.
 | 
						|
var MinimumScratchBufferSize int
 | 
						|
 | 
						|
func init() {
 | 
						|
	MinimumScratchBufferSize = os.Getpagesize()
 | 
						|
}
 | 
						|
 | 
						|
// Options provide parameters for how the Walk function operates.
 | 
						|
type Options struct {
 | 
						|
	// ErrorCallback specifies a function to be invoked in the case of an error
 | 
						|
	// that could potentially be ignored while walking a file system
 | 
						|
	// hierarchy. When set to nil or left as its zero-value, any error condition
 | 
						|
	// causes Walk to immediately return the error describing what took
 | 
						|
	// place. When non-nil, this user supplied function is invoked with the OS
 | 
						|
	// pathname of the file system object that caused the error along with the
 | 
						|
	// error that took place. The return value of the supplied ErrorCallback
 | 
						|
	// function determines whether the error will cause Walk to halt immediately
 | 
						|
	// as it would were no ErrorCallback value provided, or skip this file
 | 
						|
	// system node yet continue on with the remaining nodes in the file system
 | 
						|
	// hierarchy.
 | 
						|
	//
 | 
						|
	// ErrorCallback is invoked both for errors that are returned by the
 | 
						|
	// runtime, and for errors returned by other user supplied callback
 | 
						|
	// functions.
 | 
						|
	ErrorCallback func(string, error) ErrorAction
 | 
						|
 | 
						|
	// FollowSymbolicLinks specifies whether Walk will follow symbolic links
 | 
						|
	// that refer to directories. When set to false or left as its zero-value,
 | 
						|
	// Walk will still invoke the callback function with symbolic link nodes,
 | 
						|
	// but if the symbolic link refers to a directory, it will not recurse on
 | 
						|
	// that directory. When set to true, Walk will recurse on symbolic links
 | 
						|
	// that refer to a directory.
 | 
						|
	FollowSymbolicLinks bool
 | 
						|
 | 
						|
	// Unsorted controls whether or not Walk will sort the immediate descendants
 | 
						|
	// of a directory by their relative names prior to visiting each of those
 | 
						|
	// entries.
 | 
						|
	//
 | 
						|
	// When set to false or left at its zero-value, Walk will get the list of
 | 
						|
	// immediate descendants of a particular directory, sort that list by
 | 
						|
	// lexical order of their names, and then visit each node in the list in
 | 
						|
	// sorted order. This will cause Walk to always traverse the same directory
 | 
						|
	// tree in the same order, however may be inefficient for directories with
 | 
						|
	// many immediate descendants.
 | 
						|
	//
 | 
						|
	// When set to true, Walk skips sorting the list of immediate descendants
 | 
						|
	// for a directory, and simply visits each node in the order the operating
 | 
						|
	// system enumerated them. This will be more fast, but with the side effect
 | 
						|
	// that the traversal order may be different from one invocation to the
 | 
						|
	// next.
 | 
						|
	Unsorted bool
 | 
						|
 | 
						|
	// Callback is a required function that Walk will invoke for every file
 | 
						|
	// system node it encounters.
 | 
						|
	Callback WalkFunc
 | 
						|
 | 
						|
	// PostChildrenCallback is an option function that Walk will invoke for
 | 
						|
	// every file system directory it encounters after its children have been
 | 
						|
	// processed.
 | 
						|
	PostChildrenCallback WalkFunc
 | 
						|
 | 
						|
	// ScratchBuffer is an optional byte slice to use as a scratch buffer for
 | 
						|
	// Walk to use when reading directory entries, to reduce amount of garbage
 | 
						|
	// generation. Not all architectures take advantage of the scratch
 | 
						|
	// buffer. If omitted or the provided buffer has fewer bytes than
 | 
						|
	// MinimumScratchBufferSize, then a buffer with DefaultScratchBufferSize
 | 
						|
	// bytes will be created and used once per Walk invocation.
 | 
						|
	ScratchBuffer []byte
 | 
						|
}
 | 
						|
 | 
						|
// ErrorAction defines a set of actions the Walk function could take based on
 | 
						|
// the occurrence of an error while walking the file system. See the
 | 
						|
// documentation for the ErrorCallback field of the Options structure for more
 | 
						|
// information.
 | 
						|
type ErrorAction int
 | 
						|
 | 
						|
const (
 | 
						|
	// Halt is the ErrorAction return value when the upstream code wants to halt
 | 
						|
	// the walk process when a runtime error takes place. It matches the default
 | 
						|
	// action the Walk function would take were no ErrorCallback provided.
 | 
						|
	Halt ErrorAction = iota
 | 
						|
 | 
						|
	// SkipNode is the ErrorAction return value when the upstream code wants to
 | 
						|
	// ignore the runtime error for the current file system node, skip
 | 
						|
	// processing of the node that caused the error, and continue walking the
 | 
						|
	// file system hierarchy with the remaining nodes.
 | 
						|
	SkipNode
 | 
						|
)
 | 
						|
 | 
						|
// WalkFunc is the type of the function called for each file system node visited
 | 
						|
// by Walk. The pathname argument will contain the argument to Walk as a prefix;
 | 
						|
// that is, if Walk is called with "dir", which is a directory containing the
 | 
						|
// file "a", the provided WalkFunc will be invoked with the argument "dir/a",
 | 
						|
// using the correct os.PathSeparator for the Go Operating System architecture,
 | 
						|
// GOOS. The directory entry argument is a pointer to a Dirent for the node,
 | 
						|
// providing access to both the basename and the mode type of the file system
 | 
						|
// node.
 | 
						|
//
 | 
						|
// If an error is returned by the Callback or PostChildrenCallback functions,
 | 
						|
// and no ErrorCallback function is provided, processing stops. If an
 | 
						|
// ErrorCallback function is provided, then it is invoked with the OS pathname
 | 
						|
// of the node that caused the error along along with the error. The return
 | 
						|
// value of the ErrorCallback function determines whether to halt processing, or
 | 
						|
// skip this node and continue processing remaining file system nodes.
 | 
						|
//
 | 
						|
// The exception is when the function returns the special value
 | 
						|
// filepath.SkipDir. If the function returns filepath.SkipDir when invoked on a
 | 
						|
// directory, Walk skips the directory's contents entirely. If the function
 | 
						|
// returns filepath.SkipDir when invoked on a non-directory file system node,
 | 
						|
// Walk skips the remaining files in the containing directory. Note that any
 | 
						|
// supplied ErrorCallback function is not invoked with filepath.SkipDir when the
 | 
						|
// Callback or PostChildrenCallback functions return that special value.
 | 
						|
type WalkFunc func(osPathname string, directoryEntry *Dirent) error
 | 
						|
 | 
						|
// Walk walks the file tree rooted at the specified directory, calling the
 | 
						|
// specified callback function for each file system node in the tree, including
 | 
						|
// root, symbolic links, and other node types. The nodes are walked in lexical
 | 
						|
// order, which makes the output deterministic but means that for very large
 | 
						|
// directories this function can be inefficient.
 | 
						|
//
 | 
						|
// This function is often much faster than filepath.Walk because it does not
 | 
						|
// invoke os.Stat for every node it encounters, but rather obtains the file
 | 
						|
// system node type when it reads the parent directory.
 | 
						|
//
 | 
						|
// If a runtime error occurs, either from the operating system or from the
 | 
						|
// upstream Callback or PostChildrenCallback functions, processing typically
 | 
						|
// halts. However, when an ErrorCallback function is provided in the provided
 | 
						|
// Options structure, that function is invoked with the error along with the OS
 | 
						|
// pathname of the file system node that caused the error. The ErrorCallback
 | 
						|
// function's return value determines the action that Walk will then take.
 | 
						|
//
 | 
						|
//    func main() {
 | 
						|
//        dirname := "."
 | 
						|
//        if len(os.Args) > 1 {
 | 
						|
//            dirname = os.Args[1]
 | 
						|
//        }
 | 
						|
//        err := godirwalk.Walk(dirname, &godirwalk.Options{
 | 
						|
//            Callback: func(osPathname string, de *godirwalk.Dirent) error {
 | 
						|
//                fmt.Printf("%s %s\n", de.ModeType(), osPathname)
 | 
						|
//                return nil
 | 
						|
//            },
 | 
						|
//            ErrorCallback: func(osPathname string, err error) godirwalk.ErrorAction {
 | 
						|
//            	// Your program may want to log the error somehow.
 | 
						|
//            	fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
 | 
						|
//
 | 
						|
//            	// For the purposes of this example, a simple SkipNode will suffice,
 | 
						|
//            	// although in reality perhaps additional logic might be called for.
 | 
						|
//            	return godirwalk.SkipNode
 | 
						|
//            },
 | 
						|
//        })
 | 
						|
//        if err != nil {
 | 
						|
//            fmt.Fprintf(os.Stderr, "%s\n", err)
 | 
						|
//            os.Exit(1)
 | 
						|
//        }
 | 
						|
//    }
 | 
						|
func Walk(pathname string, options *Options) error {
 | 
						|
	pathname = filepath.Clean(pathname)
 | 
						|
 | 
						|
	var fi os.FileInfo
 | 
						|
	var err error
 | 
						|
 | 
						|
	if options.FollowSymbolicLinks {
 | 
						|
		fi, err = os.Stat(pathname)
 | 
						|
		if err != nil {
 | 
						|
			return errors.Wrap(err, "cannot Stat")
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		fi, err = os.Lstat(pathname)
 | 
						|
		if err != nil {
 | 
						|
			return errors.Wrap(err, "cannot Lstat")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	mode := fi.Mode()
 | 
						|
	if mode&os.ModeDir == 0 {
 | 
						|
		return errors.Errorf("cannot Walk non-directory: %s", pathname)
 | 
						|
	}
 | 
						|
 | 
						|
	dirent := &Dirent{
 | 
						|
		name:     filepath.Base(pathname),
 | 
						|
		modeType: mode & os.ModeType,
 | 
						|
	}
 | 
						|
 | 
						|
	// If ErrorCallback is nil, set to a default value that halts the walk
 | 
						|
	// process on all operating system errors. This is done to allow error
 | 
						|
	// handling to be more succinct in the walk code.
 | 
						|
	if options.ErrorCallback == nil {
 | 
						|
		options.ErrorCallback = defaultErrorCallback
 | 
						|
	}
 | 
						|
 | 
						|
	if len(options.ScratchBuffer) < MinimumScratchBufferSize {
 | 
						|
		options.ScratchBuffer = make([]byte, DefaultScratchBufferSize)
 | 
						|
	}
 | 
						|
 | 
						|
	err = walk(pathname, dirent, options)
 | 
						|
	if err == filepath.SkipDir {
 | 
						|
		return nil // silence SkipDir for top level
 | 
						|
	}
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
// defaultErrorCallback always returns Halt because if the upstream code did not
 | 
						|
// provide an ErrorCallback function, walking the file system hierarchy ought to
 | 
						|
// halt upon any operating system error.
 | 
						|
func defaultErrorCallback(_ string, _ error) ErrorAction { return Halt }
 | 
						|
 | 
						|
// walk recursively traverses the file system node specified by pathname and the
 | 
						|
// Dirent.
 | 
						|
func walk(osPathname string, dirent *Dirent, options *Options) error {
 | 
						|
	err := options.Callback(osPathname, dirent)
 | 
						|
	if err != nil {
 | 
						|
		if err == filepath.SkipDir {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		err = errors.Wrap(err, "Callback") // wrap potential errors returned by callback
 | 
						|
		if action := options.ErrorCallback(osPathname, err); action == SkipNode {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// On some platforms, an entry can have more than one mode type bit set.
 | 
						|
	// For instance, it could have both the symlink bit and the directory bit
 | 
						|
	// set indicating it's a symlink to a directory.
 | 
						|
	if dirent.IsSymlink() {
 | 
						|
		if !options.FollowSymbolicLinks {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		// Only need to Stat entry if platform did not already have os.ModeDir
 | 
						|
		// set, such as would be the case for unix like operating systems. (This
 | 
						|
		// guard eliminates extra os.Stat check on Windows.)
 | 
						|
		if !dirent.IsDir() {
 | 
						|
			referent, err := os.Readlink(osPathname)
 | 
						|
			if err != nil {
 | 
						|
				err = errors.Wrap(err, "cannot Readlink")
 | 
						|
				if action := options.ErrorCallback(osPathname, err); action == SkipNode {
 | 
						|
					return nil
 | 
						|
				}
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			var osp string
 | 
						|
			if filepath.IsAbs(referent) {
 | 
						|
				osp = referent
 | 
						|
			} else {
 | 
						|
				osp = filepath.Join(filepath.Dir(osPathname), referent)
 | 
						|
			}
 | 
						|
 | 
						|
			fi, err := os.Stat(osp)
 | 
						|
			if err != nil {
 | 
						|
				err = errors.Wrap(err, "cannot Stat")
 | 
						|
				if action := options.ErrorCallback(osp, err); action == SkipNode {
 | 
						|
					return nil
 | 
						|
				}
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			dirent.modeType = fi.Mode() & os.ModeType
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if !dirent.IsDir() {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	// If get here, then specified pathname refers to a directory.
 | 
						|
	deChildren, err := ReadDirents(osPathname, options.ScratchBuffer)
 | 
						|
	if err != nil {
 | 
						|
		err = errors.Wrap(err, "cannot ReadDirents")
 | 
						|
		if action := options.ErrorCallback(osPathname, err); action == SkipNode {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if !options.Unsorted {
 | 
						|
		sort.Sort(deChildren) // sort children entries unless upstream says to leave unsorted
 | 
						|
	}
 | 
						|
 | 
						|
	for _, deChild := range deChildren {
 | 
						|
		osChildname := filepath.Join(osPathname, deChild.name)
 | 
						|
		err = walk(osChildname, deChild, options)
 | 
						|
		if err != nil {
 | 
						|
			if err != filepath.SkipDir {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			// If received skipdir on a directory, stop processing that
 | 
						|
			// directory, but continue to its siblings. If received skipdir on a
 | 
						|
			// non-directory, stop processing remaining siblings.
 | 
						|
			if deChild.IsSymlink() {
 | 
						|
				// Only need to Stat entry if platform did not already have
 | 
						|
				// os.ModeDir set, such as would be the case for unix like
 | 
						|
				// operating systems. (This guard eliminates extra os.Stat check
 | 
						|
				// on Windows.)
 | 
						|
				if !deChild.IsDir() {
 | 
						|
					// Resolve symbolic link referent to determine whether node
 | 
						|
					// is directory or not.
 | 
						|
					referent, err := os.Readlink(osChildname)
 | 
						|
					if err != nil {
 | 
						|
						err = errors.Wrap(err, "cannot Readlink")
 | 
						|
						if action := options.ErrorCallback(osChildname, err); action == SkipNode {
 | 
						|
							continue // with next child
 | 
						|
						}
 | 
						|
						return err
 | 
						|
					}
 | 
						|
 | 
						|
					var osp string
 | 
						|
					if filepath.IsAbs(referent) {
 | 
						|
						osp = referent
 | 
						|
					} else {
 | 
						|
						osp = filepath.Join(osPathname, referent)
 | 
						|
					}
 | 
						|
 | 
						|
					fi, err := os.Stat(osp)
 | 
						|
					if err != nil {
 | 
						|
						err = errors.Wrap(err, "cannot Stat")
 | 
						|
						if action := options.ErrorCallback(osp, err); action == SkipNode {
 | 
						|
							continue // with next child
 | 
						|
						}
 | 
						|
						return err
 | 
						|
					}
 | 
						|
					deChild.modeType = fi.Mode() & os.ModeType
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if !deChild.IsDir() {
 | 
						|
				// If not directory, return immediately, thus skipping remainder
 | 
						|
				// of siblings.
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if options.PostChildrenCallback == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	err = options.PostChildrenCallback(osPathname, dirent)
 | 
						|
	if err == nil || err == filepath.SkipDir {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	err = errors.Wrap(err, "PostChildrenCallback") // wrap potential errors returned by callback
 | 
						|
	if action := options.ErrorCallback(osPathname, err); action == SkipNode {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	return err
 | 
						|
}
 |