github.com/rminnich/u-root@v7.0.0+incompatible/pkg/find/find.go (about)

     1  // Copyright 2015-2017 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package find searches for files in a directory hierarchy recursively.
     6  //
     7  // find can filter out files by file names, paths, and modes.
     8  package find
     9  
    10  import (
    11  	"context"
    12  	"fmt"
    13  	"os"
    14  	"path/filepath"
    15  	"regexp"
    16  
    17  	"github.com/u-root/u-root/pkg/ls"
    18  )
    19  
    20  // File is a found file.
    21  type File struct {
    22  	// Name is the path relative to the root specified in WithRoot.
    23  	Name string
    24  
    25  	os.FileInfo
    26  	Err error
    27  }
    28  
    29  // String implements a fmt.Stringer for File.
    30  //
    31  // String returns a string long-formatted like `ls` would format it.
    32  func (f *File) String() string {
    33  	s := ls.LongStringer{
    34  		Human: true,
    35  		Name:  ls.NameStringer{},
    36  	}
    37  	rec := ls.FromOSFileInfo(f.Name, f.FileInfo)
    38  	rec.Name = f.Name
    39  	return s.FileString(rec)
    40  }
    41  
    42  type finder struct {
    43  	root string
    44  
    45  	// Pattern is used with Match.
    46  	pattern string
    47  
    48  	// Match is a pattern matching function.
    49  	match      func(pattern string, name string) (bool, error)
    50  	mode       os.FileMode
    51  	modeMask   os.FileMode
    52  	debug      func(string, ...interface{})
    53  	files      chan *File
    54  	sendErrors bool
    55  }
    56  
    57  type Set func(*finder)
    58  
    59  // WithRoot sets a root path for the file finder. Only descendants of the root
    60  // will be returned on the channel.
    61  func WithRoot(rootPath string) Set {
    62  	return func(f *finder) {
    63  		f.root = rootPath
    64  	}
    65  }
    66  
    67  // WithoutError filters out files with errors from being sent on the channel.
    68  func WithoutError() Set {
    69  	return func(f *finder) {
    70  		f.sendErrors = false
    71  	}
    72  }
    73  
    74  // WithPathMatch sets up a file path filter.
    75  //
    76  // The file path passed to match will be relative to the finder's root.
    77  func WithPathMatch(pattern string, match func(pattern string, path string) (bool, error)) Set {
    78  	return func(f *finder) {
    79  		f.pattern = pattern
    80  		f.match = match
    81  	}
    82  }
    83  
    84  // WithBasenameMatch sets up a file base name filter.
    85  func WithBasenameMatch(pattern string, match func(pattern string, name string) (bool, error)) Set {
    86  	return WithPathMatch(pattern, func(patt string, path string) (bool, error) {
    87  		return match(pattern, filepath.Base(path))
    88  	})
    89  }
    90  
    91  // WithRegexPathMatch sets up a path filter using regex.
    92  //
    93  // The file path passed to regexp.Match will be relative to the finder's root.
    94  func WithRegexPathMatch(pattern string) Set {
    95  	return WithPathMatch(pattern, func(pattern, path string) (bool, error) {
    96  		return regexp.Match(pattern, []byte(path))
    97  	})
    98  }
    99  
   100  // WithFilenameMatch uses filepath.Match's shell file name matching to filter
   101  // file base names.
   102  func WithFilenameMatch(pattern string) Set {
   103  	return WithBasenameMatch(pattern, filepath.Match)
   104  }
   105  
   106  // WithModeMatch ensures only files with fileMode & modeMask == mode are returned.
   107  func WithModeMatch(mode, modeMask os.FileMode) Set {
   108  	return func(f *finder) {
   109  		f.mode = mode
   110  		f.modeMask = modeMask
   111  	}
   112  }
   113  
   114  // WithDebugLog logs messages to l.
   115  func WithDebugLog(l func(string, ...interface{})) Set {
   116  	return func(f *finder) {
   117  		f.debug = l
   118  	}
   119  }
   120  
   121  // Find finds files according to the settings and matchers given.
   122  //
   123  // e.g.
   124  //
   125  //   names := Find(ctx,
   126  //     WithRoot("/boot"),
   127  //     WithFilenameMatch("sda[0-9]"),
   128  //     WithDebugLog(log.Printf),
   129  //   )
   130  func Find(ctx context.Context, opt ...Set) <-chan *File {
   131  	f := &finder{
   132  		root:       "/",
   133  		debug:      func(string, ...interface{}) {},
   134  		files:      make(chan *File, 128),
   135  		match:      filepath.Match,
   136  		sendErrors: true,
   137  	}
   138  
   139  	for _, o := range opt {
   140  		if o != nil {
   141  			o(f)
   142  		}
   143  	}
   144  
   145  	go func(f *finder) {
   146  		_ = filepath.Walk(f.root, func(n string, fi os.FileInfo, err error) error {
   147  			if err != nil && !f.sendErrors {
   148  				// Don't send file on channel if user doesn't want them.
   149  				return nil
   150  			}
   151  
   152  			file := &File{
   153  				Name:     n,
   154  				FileInfo: fi,
   155  				Err:      err,
   156  			}
   157  			if err == nil {
   158  				// If it matches, then push its name into the result channel,
   159  				// and keep looking.
   160  				f.debug("check pattern %q against name %q", f.pattern, n)
   161  				if f.pattern != "" {
   162  					m, err := f.match(f.pattern, n)
   163  					if err != nil {
   164  						f.debug("%s: err on matching: %v", n, err)
   165  						return nil
   166  					}
   167  					if !m {
   168  						f.debug("%s: name does not match %q", n, f.pattern)
   169  						return nil
   170  					}
   171  				}
   172  				m := fi.Mode()
   173  				f.debug("%s: file mode %v / want mode %s with mask %s", n, m, f.mode, f.modeMask)
   174  				if masked := m & f.modeMask; masked != f.mode {
   175  					f.debug("%s: mode %s (masked %s) does not match expected mode %s", n, m, masked, f.mode)
   176  					return nil
   177  				}
   178  				f.debug("Found: %s", n)
   179  			}
   180  			select {
   181  			case <-ctx.Done():
   182  				return fmt.Errorf("should never be returned to user: stop walking")
   183  
   184  			case f.files <- file:
   185  				return nil
   186  			}
   187  		})
   188  		close(f.files)
   189  	}(f)
   190  
   191  	return f.files
   192  }