package fsutil import ( "os" "path/filepath" "runtime" "strings" "time" "github.com/docker/docker/pkg/fileutils" "github.com/pkg/errors" "golang.org/x/net/context" ) type WalkOpt struct { IncludePatterns []string ExcludePatterns []string Map func(*Stat) bool } func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) error { root, err := filepath.EvalSymlinks(p) if err != nil { return errors.Wrapf(err, "failed to resolve %s", root) } fi, err := os.Stat(root) if err != nil { return errors.Wrapf(err, "failed to stat: %s", root) } if !fi.IsDir() { return errors.Errorf("%s is not a directory", root) } var pm *fileutils.PatternMatcher if opt != nil && opt.ExcludePatterns != nil { pm, err = fileutils.NewPatternMatcher(opt.ExcludePatterns) if err != nil { return errors.Wrapf(err, "invalid excludepaths %s", opt.ExcludePatterns) } } seenFiles := make(map[uint64]string) return filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { if err != nil { if os.IsNotExist(err) { return filepath.SkipDir } return err } origpath := path path, err = filepath.Rel(root, path) if err != nil { return err } // Skip root if path == "." { return nil } if opt != nil { if opt.IncludePatterns != nil { matched := false for _, p := range opt.IncludePatterns { if m, _ := filepath.Match(p, path); m { matched = true break } } if !matched { if fi.IsDir() { return filepath.SkipDir } return nil } } if pm != nil { m, err := pm.Matches(path) if err != nil { return errors.Wrap(err, "failed to match excludepatterns") } if m { if fi.IsDir() { if !pm.Exclusions() { return filepath.SkipDir } dirSlash := path + string(filepath.Separator) for _, pat := range pm.Patterns() { if !pat.Exclusion() { continue } patStr := pat.String() + string(filepath.Separator) if strings.HasPrefix(patStr, dirSlash) { goto passedFilter } } return filepath.SkipDir } return nil } } } passedFilter: path = filepath.ToSlash(path) stat := &Stat{ Path: path, Mode: uint32(fi.Mode()), Size_: fi.Size(), ModTime: fi.ModTime().UnixNano(), } setUnixOpt(fi, stat, path, seenFiles) if !fi.IsDir() { if fi.Mode()&os.ModeSymlink != 0 { link, err := os.Readlink(origpath) if err != nil { return errors.Wrapf(err, "failed to readlink %s", origpath) } stat.Linkname = link } } if err := loadXattr(origpath, stat); err != nil { return errors.Wrapf(err, "failed to xattr %s", path) } if runtime.GOOS == "windows" { permPart := stat.Mode & uint32(os.ModePerm) noPermPart := stat.Mode &^ uint32(os.ModePerm) // Add the x bit: make everything +x from windows permPart |= 0111 permPart &= 0755 stat.Mode = noPermPart | permPart } select { case <-ctx.Done(): return ctx.Err() default: if opt != nil && opt.Map != nil { if allowed := opt.Map(stat); !allowed { return nil } } if err := fn(stat.Path, &StatInfo{stat}, nil); err != nil { return err } } return nil }) } type StatInfo struct { *Stat } func (s *StatInfo) Name() string { return filepath.Base(s.Stat.Path) } func (s *StatInfo) Size() int64 { return s.Stat.Size_ } func (s *StatInfo) Mode() os.FileMode { return os.FileMode(s.Stat.Mode) } func (s *StatInfo) ModTime() time.Time { return time.Unix(s.Stat.ModTime/1e9, s.Stat.ModTime%1e9) } func (s *StatInfo) IsDir() bool { return s.Mode().IsDir() } func (s *StatInfo) Sys() interface{} { return s.Stat }