├── .travis.yml ├── max_depth_predicate.go ├── min_depth_predicate.go ├── regex_predicate.go ├── go.mod ├── whole_name_predicate.go ├── name_predicate.go ├── depth.go ├── go.sum ├── LICENSE ├── type_predicate.go ├── predicate.go ├── empty_predicate.go ├── depth_test.go ├── find.go ├── README.md ├── find_test.go ├── cmd └── go-find │ └── main.go └── mount_predicate.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | # n.b. For golang release history, see https://go.dev/doc/devel/release 4 | - tip 5 | - "1.18" 6 | - "1.17" 7 | notifications: 8 | email: 9 | on_success: change 10 | on_failure: always 11 | -------------------------------------------------------------------------------- /max_depth_predicate.go: -------------------------------------------------------------------------------- 1 | package find 2 | 3 | type maxDepthPredicate struct { 4 | n int 5 | } 6 | 7 | func (p *maxDepthPredicate) Match(root string, path string) (bool, error) { 8 | d := depth(root, path) 9 | return d <= p.n, nil 10 | } 11 | -------------------------------------------------------------------------------- /min_depth_predicate.go: -------------------------------------------------------------------------------- 1 | package find 2 | 3 | type minDepthPredicate struct { 4 | n int 5 | } 6 | 7 | func (p *minDepthPredicate) Match(root string, path string) (bool, error) { 8 | d := depth(root, path) 9 | return d >= p.n, nil 10 | } 11 | -------------------------------------------------------------------------------- /regex_predicate.go: -------------------------------------------------------------------------------- 1 | package find 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | type regexPredicate struct { 8 | expr *regexp.Regexp 9 | } 10 | 11 | func (p *regexPredicate) Match(_ string, path string) (bool, error) { 12 | return p.expr.MatchString(path), nil 13 | } 14 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jaytaylor/go-find 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/ryanuber/go-glob v1.0.0 7 | github.com/spf13/cobra v1.4.0 8 | ) 9 | 10 | require ( 11 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 12 | github.com/spf13/pflag v1.0.5 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /whole_name_predicate.go: -------------------------------------------------------------------------------- 1 | package find 2 | 3 | import ( 4 | "github.com/ryanuber/go-glob" 5 | ) 6 | 7 | type wholeNamePredicate struct { 8 | pattern string 9 | } 10 | 11 | func (p *wholeNamePredicate) Match(_ string, path string) (bool, error) { 12 | return glob.Glob(p.pattern, path), nil 13 | } 14 | -------------------------------------------------------------------------------- /name_predicate.go: -------------------------------------------------------------------------------- 1 | package find 2 | 3 | import ( 4 | "path/filepath" 5 | 6 | "github.com/ryanuber/go-glob" 7 | ) 8 | 9 | type namePredicatae struct { 10 | pattern string 11 | } 12 | 13 | func (p *namePredicatae) Match(_ string, path string) (bool, error) { 14 | return glob.Glob(p.pattern, filepath.Base(path)), nil 15 | } 16 | -------------------------------------------------------------------------------- /depth.go: -------------------------------------------------------------------------------- 1 | package find 2 | 3 | import ( 4 | "path/filepath" 5 | "strings" 6 | ) 7 | 8 | // depth calculates the relative depth of a path. 9 | func depth(root string, path string) int { 10 | root = filepath.Clean(root) 11 | path = filepath.Clean(path) 12 | sep := string(filepath.Separator) 13 | if root != sep && !strings.HasSuffix(root, sep) { 14 | root += sep 15 | } 16 | if path != sep && strings.HasSuffix(path, sep) { 17 | path = strings.TrimRight(path, sep) 18 | } 19 | diff := strings.TrimPrefix(path, root) 20 | pieces := strings.Split(diff, sep) 21 | d := len(pieces) 22 | if d == 1 { 23 | return 0 24 | } 25 | return d 26 | } 27 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 2 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 3 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 4 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 5 | github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= 6 | github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= 7 | github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= 8 | github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= 9 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 10 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 12 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jay Elliot Taylor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /type_predicate.go: -------------------------------------------------------------------------------- 1 | package find 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | type typePredicate struct { 9 | t string 10 | } 11 | 12 | func (p *typePredicate) Match(_ string, path string) (bool, error) { 13 | info, err := os.Lstat(path) 14 | if err != nil { 15 | return false, fmt.Errorf("typePredicate: lstat %q: %s", path, err) 16 | } 17 | 18 | var ( 19 | isCharDev = info.Mode()&os.ModeCharDevice != 0 20 | isSymlink = info.Mode()&os.ModeSymlink != 0 // True if the file is a symlink. 21 | isNamedPipe = info.Mode()&os.ModeNamedPipe != 0 22 | isSocket = info.Mode()&os.ModeSocket != 0 23 | ) 24 | 25 | switch p.t { 26 | case "c": // Unix character device. 27 | return isCharDev, nil 28 | case "d": // Directory 29 | return info.IsDir() && !isSymlink && !isNamedPipe && !isSocket, nil 30 | case "f": // Regular file 31 | return !info.IsDir() && !isSymlink && !isNamedPipe && !isSocket, nil 32 | case "l": // Symbolic link 33 | return isSymlink, nil 34 | case "p": // Named pipe 35 | return isNamedPipe, nil 36 | case "s": // Socket 37 | return isSocket, nil 38 | } 39 | return false, p.validate() 40 | } 41 | 42 | func (p *typePredicate) validate() error { 43 | if p.t == "d" || p.t == "f" || p.t == "l" || p.t == "p" || p.t == "s" { 44 | return nil 45 | } 46 | return fmt.Errorf("unrecognized value for -type: %s", p.t) 47 | } 48 | -------------------------------------------------------------------------------- /predicate.go: -------------------------------------------------------------------------------- 1 | package find 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | ) 8 | 9 | type predicate interface { 10 | Match(root string, path string) (bool, error) 11 | } 12 | 13 | type PredicateError struct { 14 | errType error 15 | errMessage string 16 | } 17 | 18 | func (p PredicateError) Error() string { 19 | return fmt.Sprintf("%q: %s", p.errType, p.errMessage) 20 | } 21 | 22 | type predicates []predicate 23 | 24 | func (ps predicates) Evaluate(root string) ([]string, error) { 25 | results := []string{} 26 | err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error { 27 | if err != nil { 28 | return err 29 | } 30 | allMatched := true 31 | for _, p := range ps { 32 | matched := false 33 | if matched, err = p.Match(root, path); err != nil { 34 | if pe, ok := err.(PredicateError); ok { 35 | // ignore the directory if encountered a different filesystem 36 | if pe.errType == ErrorFSType { 37 | return filepath.SkipDir 38 | } else { 39 | fmt.Fprintf(os.Stderr, "error: %s\n", pe.errMessage) 40 | return nil 41 | } 42 | } 43 | return err 44 | } 45 | if !matched { 46 | allMatched = false 47 | break 48 | } 49 | } 50 | if allMatched { 51 | results = append(results, path) 52 | } 53 | return nil 54 | }) 55 | if err != nil { 56 | return nil, err 57 | } 58 | return results, nil 59 | } 60 | -------------------------------------------------------------------------------- /empty_predicate.go: -------------------------------------------------------------------------------- 1 | package find 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | var ( 10 | ErrorEmptyPredStat = errors.New("emptyPredicate: lstat") 11 | ErrorEmptyPredOpen = errors.New("emptyPredicate: opening") 12 | ErrorEmptyPredListing = errors.New("emptyPredicate: listing") 13 | ) 14 | 15 | type emptyPredicate struct{} 16 | 17 | func (p *emptyPredicate) Match(root string, path string) (bool, error) { 18 | info, err := os.Lstat(path) 19 | if err != nil { 20 | return false, PredicateError{errType: ErrorEmptyPredStat, errMessage: err.Error()} 21 | } 22 | 23 | var ( 24 | isCharDev = info.Mode()&os.ModeCharDevice != 0 25 | isSymlink = info.Mode()&os.ModeSymlink != 0 // True if the file is a symlink. 26 | isNamedPipe = info.Mode()&os.ModeNamedPipe != 0 27 | isSocket = info.Mode()&os.ModeSocket != 0 28 | ) 29 | 30 | if isCharDev || isSymlink || isNamedPipe || isSocket { 31 | return false, nil 32 | } 33 | 34 | if info.IsDir() { 35 | f, err := os.Open(path) 36 | if err != nil { 37 | return false, PredicateError{errType: ErrorEmptyPredOpen, errMessage: err.Error()} 38 | } 39 | dirs, err := f.ReadDir(-1) 40 | f.Close() 41 | if err != nil { 42 | fmt.Fprintf(os.Stderr, "error: %s\n", err.Error()) 43 | return false, PredicateError{errType: ErrorEmptyPredListing, errMessage: err.Error()} 44 | } 45 | return len(dirs) == 0, nil 46 | } else { 47 | return info.Size() == 0, nil 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /depth_test.go: -------------------------------------------------------------------------------- 1 | package find 2 | 3 | import ( 4 | "path/filepath" 5 | "testing" 6 | ) 7 | 8 | func TestDepth(t *testing.T) { 9 | sep := string(filepath.Separator) 10 | testCases := []struct { 11 | root string 12 | path string 13 | expected int 14 | }{ 15 | { 16 | root: "", 17 | path: "", 18 | expected: 0, 19 | }, 20 | { 21 | root: "", 22 | path: filepath.Join("foo", "bar", "baz", "a", "b", "c"), 23 | expected: 6, 24 | }, 25 | { 26 | root: "", 27 | path: filepath.Join("foo", "bar", "baz", "a", "b", "c") + sep, 28 | expected: 6, 29 | }, 30 | { 31 | root: filepath.Join("foo", "bar", "baz"), 32 | path: filepath.Join("foo", "bar", "baz", "a", "b", "c"), 33 | expected: 3, 34 | }, 35 | { 36 | root: filepath.Join("foo", "bar", "baz") + sep, 37 | path: filepath.Join("foo", "bar", "baz", "a", "b", "c"), 38 | expected: 3, 39 | }, 40 | } 41 | for i, testCase := range testCases { 42 | if expected, actual := testCase.expected, depth(testCase.root, testCase.path); actual != expected { 43 | t.Errorf("[testCase=%va] Expected depth=%v but result=%v for % #v", i, expected, actual, testCase) 44 | } 45 | testCase.root = sep + testCase.root 46 | testCase.path = sep + testCase.path 47 | if expected, actual := testCase.expected, depth(testCase.root, testCase.path); actual != expected { 48 | t.Errorf("[testCase=%vb] Expected depth=%v but result=%v for % #v", i, expected, actual, testCase) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /find.go: -------------------------------------------------------------------------------- 1 | package find 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | type Find struct { 8 | Paths []string 9 | predicates predicates 10 | } 11 | 12 | func NewFind(paths ...string) *Find { 13 | find := &Find{ 14 | Paths: paths, 15 | } 16 | return find 17 | } 18 | 19 | func (finder *Find) Evaluate() ([]string, error) { 20 | results := []string{} 21 | for _, path := range finder.Paths { 22 | hits, err := finder.predicates.Evaluate(path) 23 | if err != nil { 24 | return nil, err 25 | } 26 | results = append(results, hits...) 27 | } 28 | return results, nil 29 | } 30 | 31 | func (finder *Find) MinDepth(n int) *Find { 32 | c := &minDepthPredicate{ 33 | n: n, 34 | } 35 | finder.predicates = append(finder.predicates, c) 36 | return finder 37 | } 38 | 39 | func (finder *Find) MaxDepth(n int) *Find { 40 | c := &maxDepthPredicate{ 41 | n: n, 42 | } 43 | finder.predicates = append(finder.predicates, c) 44 | return finder 45 | } 46 | 47 | func (finder *Find) Type(t string) *Find { 48 | c := &typePredicate{ 49 | t: t, 50 | } 51 | finder.predicates = append(finder.predicates, c) 52 | return finder 53 | } 54 | 55 | func (finder *Find) Name(pattern string) *Find { 56 | c := &namePredicatae{ 57 | pattern: pattern, 58 | } 59 | finder.predicates = append(finder.predicates, c) 60 | return finder 61 | } 62 | 63 | func (finder *Find) WholeName(pattern string) *Find { 64 | c := &wholeNamePredicate{ 65 | pattern: pattern, 66 | } 67 | finder.predicates = append(finder.predicates, c) 68 | return finder 69 | } 70 | 71 | func (finder *Find) Regex(expr *regexp.Regexp) *Find { 72 | c := ®exPredicate{ 73 | expr: expr, 74 | } 75 | finder.predicates = append(finder.predicates, c) 76 | return finder 77 | } 78 | 79 | func (finder *Find) Empty() *Find { 80 | c := &emptyPredicate{} 81 | finder.predicates = append(finder.predicates, c) 82 | return finder 83 | } 84 | 85 | func (finder *Find) Mount() *Find { 86 | fsType := getFileSystemType(finder.Paths...) 87 | c := &mountPredicate{ 88 | path: finder.Paths, 89 | fsType: fsType, 90 | } 91 | finder.predicates = append(finder.predicates, c) 92 | return finder 93 | } 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-find 2 | 3 | [![Documentation](https://godoc.org/github.com/jaytaylor/go-find?status.svg)](https://godoc.org/github.com/jaytaylor/go-find) 4 | [![Build Status](https://travis-ci.org/jaytaylor/go-find.svg)](https://travis-ci.org/jaytaylor/go-find) 5 | [![Report Card](https://goreportcard.com/badge/jaytaylor/go-find)](https://goreportcard.com/report/jaytaylor/go-find) 6 | 7 | ### [go-find](https://github.com/jaytaylor/go-find) is a programmatically accessible golang implementation of the *nix `find` command. 8 | 9 | TL:DR; This thing can help you find files on disk matching a wide variety of criteria. 10 | 11 | The goal of this project is to achieve equivalent or better capabilities 12 | relative to the GNU find command-line utility. 13 | 14 | ## Get it 15 | 16 | ```bash 17 | # Install the library: 18 | go get -u github.com/jaytaylor/go-find 19 | 20 | # Get the `go-find' CLI: 21 | go install github.com/jaytaylor/go-find/go-find/... 22 | ``` 23 | 24 | ## Example Usage 25 | 26 | ### As a standalone command-line program 27 | 28 | ```bash 29 | go-find . -name 'favorite-quotes.txt' 30 | ``` 31 | 32 | ### From within Go 33 | 34 | ```go 35 | package main 36 | 37 | import ( 38 | "fmt" 39 | "strings" 40 | 41 | "github.com/jaytaylor/go-find" 42 | ) 43 | 44 | func main() { 45 | finder := find.NewFind(".").MinDepth(1).Name("favorite-quotes.txt") 46 | hits, _ := finder.Evaluate() 47 | fmt.Printf("%+v\n", strings.Join(hits, "\n")) 48 | } 49 | ``` 50 | 51 | ## The predicate feature matrix 52 | 53 | _"Predicate tests"_ are find's terminology for what can be intuitively be thought 54 | of as filter operations. 55 | 56 | Even though most of the predicate tests aren't yet implemented, mostly because 57 | many of them are quite obscure, the initial set covers all of my "everyday" 58 | common use-cases. 59 | 60 | Comparison of `go-find` vs `GNU find`: 61 | 62 | | Feature | GNU find has it? | go-find has it? | 63 | | ----------- | ---------------- | --------------- | 64 | | operators | ✅ | | 65 | | -amin | ✅ | | 66 | | -anewer | ✅ | | 67 | | -atime | ✅ | | 68 | | -cmin | ✅ | | 69 | | -cnewer | ✅ | | 70 | | -context | ✅ | | 71 | | -ctime | ✅ | | 72 | | -empty | ✅ | ✅ | 73 | | -executable | ✅ | | 74 | | -false | ✅ | | 75 | | -fls | ✅ | | 76 | | -fprint | ✅ | | 77 | | -fprint0 | ✅ | | 78 | | -fprintf | ✅ | | 79 | | -fstype | ✅ | | 80 | | -gid | ✅ | | 81 | | -group | ✅ | | 82 | | -ilname | ✅ | | 83 | | -iname | ✅ | | 84 | | -inum | ✅ | | 85 | | -ipath | ✅ | | 86 | | -iregex | ✅ | | 87 | | -iwholename | ✅ | | 88 | | -links | ✅ | | 89 | | -lname | ✅ | | 90 | | -ls | ✅ | | 91 | | -maxdepth | ✅ | ✅ | 92 | | -mindepth | ✅ | ✅ | 93 | | -mmin | ✅ | | 94 | | -mount | ✅ | | 95 | | -mtime | ✅ | | 96 | | -name | ✅ | ✅ | 97 | | -newer | ✅ | | 98 | | -newerXY | ✅ | | 99 | | -nogroup | ✅ | | 100 | | -not | ✅ | | 101 | | -nouser | ✅ | | 102 | | -ok | ✅ | | 103 | | -okdir | ✅ | | 104 | | -path | ✅ | | 105 | | -perm | ✅ | | 106 | | -print | ✅ | | 107 | | -print0 | ✅ | ✅ | 108 | | -printf | ✅ | | 109 | | -prune | ✅ | | 110 | | -quit | ✅ | | 111 | | -readable | ✅ | | 112 | | -regex | ✅ | ✅ | 113 | | -regextype | ✅ | | 114 | | -samefile | ✅ | | 115 | | -size | ✅ | | 116 | | -true | ✅ | | 117 | | -type | ✅ | ✅ | 118 | | -uid | ✅ | | 119 | | -used | ✅ | | 120 | | -user | ✅ | | 121 | | -wholename | ✅ | ✅ | 122 | | -writable | ✅ | | 123 | | -xdev | ✅ | | 124 | | -xtype | ✅ | | 125 | 126 | Note: Features involving command execution are an anti-goal and are omitted from the matrix. If you spot an error in this list, please open an issue or PR. 127 | -------------------------------------------------------------------------------- /find_test.go: -------------------------------------------------------------------------------- 1 | package find 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | ) 9 | 10 | func TestFind(t *testing.T) { 11 | basePath := "/tmp/test-go-find" 12 | if err := os.RemoveAll(basePath); err != nil { 13 | t.Fatal(err) 14 | } 15 | 16 | if err := os.MkdirAll(filepath.Join(basePath, "foo", "bar", "baz"), os.FileMode(int(0777))); err != nil { 17 | t.Fatal(err) 18 | } 19 | defer func() { 20 | os.RemoveAll(basePath) 21 | }() 22 | 23 | if err := os.MkdirAll(filepath.Join(basePath, "some", "other", "dir", "foo"), os.FileMode(int(0777))); err != nil { 24 | t.Fatal(err) 25 | } 26 | 27 | if err := ioutil.WriteFile(filepath.Join(basePath, "file0.txt"), nil, os.FileMode(int(0600))); err != nil { 28 | t.Fatal(err) 29 | } 30 | if err := ioutil.WriteFile(filepath.Join(basePath, "file1.txt"), []byte("hello here 1\n"), os.FileMode(int(0600))); err != nil { 31 | t.Fatal(err) 32 | } 33 | if err := ioutil.WriteFile(filepath.Join(basePath, "file2.txt"), []byte("hello here 2\n"), os.FileMode(int(0600))); err != nil { 34 | t.Fatal(err) 35 | } 36 | if err := ioutil.WriteFile(filepath.Join(basePath, "foo", "file1.txt"), []byte("hello there 1\n"), os.FileMode(int(0600))); err != nil { 37 | t.Fatal(err) 38 | } 39 | if err := ioutil.WriteFile(filepath.Join(basePath, "foo", "file2.txt"), []byte("hello there 2\n"), os.FileMode(int(0600))); err != nil { 40 | t.Fatal(err) 41 | } 42 | if err := ioutil.WriteFile(filepath.Join(basePath, "foo", "bar", "baz", "file1.txt"), []byte("hello over there 1\n"), os.FileMode(int(0600))); err != nil { 43 | t.Fatal(err) 44 | } 45 | if err := ioutil.WriteFile(filepath.Join(basePath, "some", "other", "file0.txt"), nil, os.FileMode(int(0600))); err != nil { 46 | t.Fatal(err) 47 | } 48 | if err := ioutil.WriteFile(filepath.Join(basePath, "some", "other", "dir", "null.txt"), nil, os.FileMode(int(0600))); err != nil { 49 | t.Fatal(err) 50 | } 51 | if err := os.Symlink(filepath.Join(basePath, "some", "other", "dir", "null.txt"), filepath.Join(basePath, "lnk")); err != nil { 52 | t.Fatal(err) 53 | } 54 | 55 | // No predicates 56 | { 57 | hits, err := NewFind(basePath).Evaluate() 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | if expected, actual := 17, len(hits); actual != expected { 62 | t.Errorf("Expected num–hits=%v but actual=%v\nHits: % #v)\n", expected, actual, hits) 63 | } 64 | } 65 | 66 | // MinDepth 67 | { 68 | hits, err := NewFind(basePath).MinDepth(1).Name("file1.txt").Evaluate() 69 | if err != nil { 70 | t.Fatal(err) 71 | } 72 | if expected, actual := 2, len(hits); actual != expected { 73 | t.Errorf("Expected num–hits=%v but actual=%v\nHits: % #v)\n", expected, actual, hits) 74 | } 75 | } 76 | 77 | // MaxDepth 78 | { 79 | hits, err := NewFind(basePath).MaxDepth(1).Evaluate() 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | if expected, actual := 6, len(hits); actual != expected { 84 | t.Errorf("Expected num–hits=%v but actual=%v\nHits: % #v)\n", expected, actual, hits) 85 | } 86 | } 87 | 88 | // Name (exact) 89 | { 90 | hits, err := NewFind(basePath).Name("file1.txt").Evaluate() 91 | if err != nil { 92 | t.Fatal(err) 93 | } 94 | if expected, actual := 3, len(hits); actual != expected { 95 | t.Errorf("Expected num–hits=%v but actual=%v\nHits: % #v)\n", expected, actual, hits) 96 | } 97 | } 98 | 99 | // Name (glob) 100 | { 101 | hits, err := NewFind(basePath).Name("*1.txt").Evaluate() 102 | if err != nil { 103 | t.Fatal(err) 104 | } 105 | if expected, actual := 3, len(hits); actual != expected { 106 | t.Errorf("Expected num–hits=%v but actual=%v\nHits: % #v)\n", expected, actual, hits) 107 | } 108 | } 109 | 110 | // MaxDepth + Name 111 | { 112 | hits, err := NewFind(basePath).MaxDepth(1).Name("file1.txt").Evaluate() 113 | if err != nil { 114 | t.Fatal(err) 115 | } 116 | if expected, actual := 1, len(hits); actual != expected { 117 | t.Errorf("Expected num–hits=%v but actual=%v\nHits: % #v)\n", expected, actual, hits) 118 | } 119 | } 120 | 121 | // Wholename 122 | { 123 | hits, err := NewFind(basePath).WholeName("*foo*2.txt").Evaluate() 124 | if err != nil { 125 | t.Fatal(err) 126 | } 127 | if expected, actual := 1, len(hits); actual != expected { 128 | t.Errorf("Expected num–hits=%v but actual=%v\nHits: % #v)\n", expected, actual, hits) 129 | } 130 | } 131 | 132 | // Type dir 133 | { 134 | hits, err := NewFind(basePath).Type("d").Evaluate() 135 | if err != nil { 136 | t.Fatal(err) 137 | } 138 | if expected, actual := 8, len(hits); actual != expected { 139 | t.Errorf("Expected num–hits=%v but actual=%v\nHits: % #v)\n", expected, actual, hits) 140 | } 141 | } 142 | 143 | // Type file 144 | { 145 | hits, err := NewFind(basePath).Type("d").Evaluate() 146 | if err != nil { 147 | t.Fatal(err) 148 | } 149 | if expected, actual := 8, len(hits); actual != expected { 150 | t.Errorf("Expected num–hits=%v but actual=%v\nHits: % #v)\n", expected, actual, hits) 151 | } 152 | } 153 | 154 | // Empty 155 | { 156 | hits, err := NewFind(basePath).Empty().Evaluate() 157 | if err != nil { 158 | t.Fatal(err) 159 | } 160 | if expected, actual := 4, len(hits); actual != expected { 161 | t.Errorf("Expected num–hits=%v but actual=%v\nHits: % #v)\n", expected, actual, hits) 162 | } 163 | } 164 | 165 | // Empty dirs 166 | { 167 | hits, err := NewFind(basePath).Type("d").Empty().Evaluate() 168 | if err != nil { 169 | t.Fatal(err) 170 | } 171 | if expected, actual := 1, len(hits); actual != expected { 172 | t.Errorf("Expected num–hits=%v but actual=%v\nHits: % #v)\n", expected, actual, hits) 173 | } 174 | } 175 | 176 | // Empty files 177 | { 178 | hits, err := NewFind(basePath).Type("f").Empty().Evaluate() 179 | if err != nil { 180 | t.Fatal(err) 181 | } 182 | if expected, actual := 3, len(hits); actual != expected { 183 | t.Errorf("Expected num–hits=%v but actual=%v\nHits: % #v)\n", expected, actual, hits) 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /cmd/go-find/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "math" 8 | "os" 9 | "path/filepath" 10 | "regexp" 11 | "strings" 12 | 13 | "github.com/jaytaylor/go-find" 14 | ) 15 | 16 | var ( 17 | MinDepth int 18 | MaxDepth int 19 | Type string 20 | Name string 21 | WholeName string 22 | Regex string 23 | Empty bool 24 | Print0 bool 25 | Mount bool 26 | ) 27 | 28 | var fs *flag.FlagSet 29 | 30 | func init() { 31 | fs = flag.NewFlagSet("go-find", flag.ExitOnError) 32 | fs.IntVar(&MinDepth, "mindepth", math.MinInt, "Do not apply any tests or actions at levels less than levels (a non-negative integer). -mindepth 1 means process all files except the starting-points.") 33 | fs.IntVar(&MaxDepth, "maxdepth", math.MinInt, "Descend at most levels (a non-negative integer) levels of directories below the starting-points. -maxdepth 0 means only apply the tests and actions to the starting-points themselves.") 34 | fs.StringVar(&Type, "type", "", `File is of type c: 35 | c character (unbuffered) special 36 | d directory 37 | p named pipe (FIFO) 38 | f regular file 39 | l symbolic link; this is never true if the -L option or the -follow option is in effect, unless the symbolic link is broken. If you want to search for symbolic links when -L is in effect, use -xtype. 40 | s socket 41 | To search for more than one type at once, you can supply the combined list of type letters separated by a comma `+"`"+`,' (GNU extension).`) 42 | fs.StringVar(&Name, "name", "", `Base of file name (the path with the leading directories removed) matches shell pattern pattern. Because the leading directories are removed, the file names considered for a match with -name will never include a slash, so `+"`"+`-name a/b' will never match anything (you probably need to use -path instead).`) 43 | fs.StringVar(&WholeName, "wholename", "", `File name matches shell glob pattern.`) 44 | fs.StringVar(&Regex, "regex", "", "File name matches regular expression pattern. This is a match on the whole path, not a search.") 45 | fs.BoolVar(&Empty, "empty", false, "File is empty and is either a regular file or a directory.") 46 | fs.BoolVar(&Mount, "mount", false, "Restrict the search to the given path filesystem.") 47 | fs.BoolVar(&Print0, "print0", false, "Print the full file name on the standard output, followed by a null character (instead of the newline character). This allows file names that contain newlines or other types of white space to be correctly interpreted by programs that process the find output. This option corresponds to the -0 option of xargs.") 48 | 49 | fs.Usage = func() { 50 | fmt.Printf("Usage:\n\n\t%s [flags..] [paths...]\n", os.Args[0]) 51 | fmt.Print("\n\tAvailable predicate tests:\n") 52 | fs.VisitAll(func(f *flag.Flag) { 53 | fmt.Printf("\n\t-%v \t%v\n", f.Name, f.Usage) // f.Name, f.Value 54 | }) 55 | } 56 | } 57 | 58 | func getSearchPathsFromCommandLine(args []string) []string { 59 | var paths []string 60 | for _, arg := range args { 61 | if strings.HasPrefix(arg, "-") { 62 | break 63 | } 64 | paths = append(paths, filepath.Clean(arg)) 65 | } 66 | return paths 67 | } 68 | 69 | func main() { 70 | args := os.Args[1:] 71 | paths := getSearchPathsFromCommandLine(args) // find the search paths to be traversed after the command name 72 | 73 | fs.Parse(args[len(paths):]) // parse the flags after command name and search paths 74 | if len(args) > 1 && (args[1] == "-h" || args[1] == "-help" || args[1] == "--help") { 75 | flag.Usage() 76 | return 77 | } 78 | if len(args) == 0 { 79 | // Default to CWD. 80 | cwd, err := os.Getwd() 81 | if err != nil { 82 | log.Fatalf("Error getting current working directory: %s", err) 83 | } 84 | paths = append(paths, cwd) 85 | } 86 | 87 | finder := find.NewFind(paths...) 88 | if MinDepth != math.MinInt { 89 | finder = finder.MinDepth(MinDepth) 90 | } 91 | if MaxDepth != math.MinInt { 92 | finder = finder.MinDepth(MinDepth) 93 | } 94 | 95 | if Type != "" { 96 | finder = finder.Type(Type) 97 | } 98 | if Name != "" { 99 | finder = finder.Name(Name) 100 | } 101 | if WholeName != "" { 102 | finder = finder.WholeName(WholeName) 103 | } 104 | if Mount { 105 | finder = finder.Mount() 106 | } 107 | if Regex != "" { 108 | expr, err := regexp.Compile(Regex) 109 | if err != nil { 110 | log.Fatalf("invalid regular expression %q: %s", Regex, err) 111 | } 112 | finder = finder.Regex(expr) 113 | } 114 | if Empty { 115 | finder = finder.Empty() 116 | } 117 | results, err := finder.Evaluate() 118 | if err != nil { 119 | log.Fatal(err) 120 | } 121 | for _, result := range results { 122 | if Print0 { 123 | fmt.Printf("%s%v", result, byte(0)) 124 | } else { 125 | fmt.Println(result) 126 | } 127 | } 128 | 129 | //if err := rootCmd.Execute(); err != nil { 130 | // log.Fatal(err) 131 | //} 132 | } 133 | 134 | //var rootCmd = &cobra.Command{ 135 | // Use: "find", 136 | // //Short: "", 137 | // //Long: "", 138 | // //Args: cobra. 139 | // PersistentPreRun: func(_ *cobra.Command, _ []string) { 140 | // }, 141 | // Run: func(_ *cobra.Command, args []string) { 142 | // if len(args) == 0 { 143 | // // Default to CWD. 144 | // cwd, err := os.Getwd() 145 | // if err != nil { 146 | // log.Fatalf("Error getting current working directory: %s", err) 147 | // } 148 | // args = append(args, cwd) 149 | // } 150 | // 151 | // finder := find.NewFind(args...) 152 | // if MinDepth != math.MinInt { 153 | // finder = finder.MinDepth(MinDepth) 154 | // } 155 | // if MaxDepth != math.MinInt { 156 | // finder = finder.MinDepth(MinDepth) 157 | // } 158 | // if Type != "" { 159 | // finder = finder.Type(Type) 160 | // } 161 | // if Name != "" { 162 | // finder = finder.Name(Name) 163 | // } 164 | // if WholeName != "" { 165 | // finder = finder.WholeName(WholeName) 166 | // } 167 | // if Regex != "" { 168 | // expr, err := regexp.Compile(Regex) 169 | // if err != nil { 170 | // log.Fatalf("invalid regular expression %q: %s", Regex, err) 171 | // } 172 | // finder = finder.Regex(expr) 173 | // } 174 | // if Empty { 175 | // finder = finder.Empty() 176 | // } 177 | // results, err := finder.Evaluate() 178 | // if err != nil { 179 | // log.Fatal(err) 180 | // } 181 | // for _, result := range results { 182 | // if Print0 { 183 | // fmt.Printf("%s%v", result, byte(0)) 184 | // } else { 185 | // fmt.Println(result) 186 | // } 187 | // } 188 | // }, 189 | //} 190 | -------------------------------------------------------------------------------- /mount_predicate.go: -------------------------------------------------------------------------------- 1 | package find 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "syscall" 8 | ) 9 | 10 | //nolint:revive,deadcode 11 | const ( 12 | // man statfs 13 | ADFS_SUPER_MAGIC = 0xadf5 14 | AFFS_SUPER_MAGIC = 0xADFF 15 | AUTOFS_SUPER_MAGIC = 0x0187 16 | BDEVFS_MAGIC = 0x62646576 17 | BEFS_SUPER_MAGIC = 0x42465331 18 | BFS_MAGIC = 0x1BADFACE 19 | BINFMTFS_MAGIC = 0x42494e4d 20 | BPF_FS_MAGIC = 0xcafe4a11 21 | BTRFS_SUPER_MAGIC = 0x9123683E 22 | CGROUP_SUPER_MAGIC = 0x27e0eb 23 | CGROUP2_SUPER_MAGIC = 0x63677270 24 | CIFS_MAGIC_NUMBER = 0xFF534D42 25 | CODA_SUPER_MAGIC = 0x73757245 26 | COH_SUPER_MAGIC = 0x012FF7B7 27 | CONFIGFS_MAGIC = 0x62656570 28 | CRAMFS_MAGIC = 0x28cd3d45 29 | DEBUGFS_MAGIC = 0x64626720 30 | DEVFS_SUPER_MAGIC = 0x1373 31 | DEVPTS_SUPER_MAGIC = 0x1cd1 32 | EFIVARFS_MAGIC = 0xde5e81e4 33 | EFS_SUPER_MAGIC = 0x00414A53 34 | EXT_SUPER_MAGIC = 0x137D 35 | EXT2_OLD_SUPER_MAGIC = 0xEF51 36 | EXT2_SUPER_MAGIC = 0xEF53 37 | EXT3_SUPER_MAGIC = 0xEF53 38 | EXT4_SUPER_MAGIC = 0xEF53 39 | FUSE_SUPER_MAGIC = 0x65735546 40 | FUTEXFS_SUPER_MAGIC = 0xBAD1DEA 41 | HFS_SUPER_MAGIC = 0x4244 42 | HFSPLUS_SUPER_MAGIC = 0x482b 43 | HOSTFS_SUPER_MAGIC = 0x00c0ffee 44 | HPFS_SUPER_MAGIC = 0xF995E849 45 | HUGETLBFS_MAGIC = 0x958458f6 46 | ISOFS_SUPER_MAGIC = 0x9660 47 | JFFS2_SUPER_MAGIC = 0x72b6 48 | JFS_SUPER_MAGIC = 0x3153464a 49 | MINIX_SUPER_MAGIC = 0x137F /* orig. minix */ 50 | MINIX_SUPER_MAGIC2 = 0x138F /* 30 char minix */ 51 | MINIX2_SUPER_MAGIC = 0x2468 /* minix V2 */ 52 | MINIX2_SUPER_MAGIC2 = 0x2478 /* minix V2, 30 char names */ 53 | MINIX3_SUPER_MAGIC = 0x4d5a /* minix V3 fs, 60 char names */ 54 | MQUEUE_MAGIC = 0x19800202 55 | MSDOS_SUPER_MAGIC = 0x4d44 56 | NCP_SUPER_MAGIC = 0x564c 57 | NFS_SUPER_MAGIC = 0x6969 58 | NILFS_SUPER_MAGIC = 0x3434 59 | NTFS_SB_MAGIC = 0x5346544e 60 | OCFS2_SUPER_MAGIC = 0x7461636f 61 | OPENPROM_SUPER_MAGIC = 0x9fa1 62 | PIPEFS_MAGIC = 0x50495045 63 | PROC_SUPER_MAGIC = 0x9fa0 64 | PSTOREFS_MAGIC = 0x6165676C 65 | QNX4_SUPER_MAGIC = 0x002f 66 | QNX6_SUPER_MAGIC = 0x68191122 67 | RAMFS_MAGIC = 0x858458f6 68 | REISERFS_SUPER_MAGIC = 0x52654973 69 | ROMFS_MAGIC = 0x7275 70 | SELINUX_MAGIC = 0xf97cff8c 71 | SMACK_MAGIC = 0x43415d53 72 | SMB_SUPER_MAGIC = 0x517B 73 | SMB2_MAGIC_NUMBER = 0xfe534d42 74 | SOCKFS_MAGIC = 0x534F434B 75 | SQUASHFS_MAGIC = 0x73717368 76 | SYSFS_MAGIC = 0x62656572 77 | SYSV2_SUPER_MAGIC = 0x012FF7B6 78 | SYSV4_SUPER_MAGIC = 0x012FF7B5 79 | TMPFS_MAGIC = 0x01021994 80 | TRACEFS_MAGIC = 0x74726163 81 | UDF_SUPER_MAGIC = 0x15013346 82 | UFS_MAGIC = 0x00011954 83 | USBDEVICE_SUPER_MAGIC = 0x9fa2 84 | V9FS_MAGIC = 0x01021997 85 | VXFS_SUPER_MAGIC = 0xa501FCF5 86 | XENFS_SUPER_MAGIC = 0xabba1974 87 | XENIX_SUPER_MAGIC = 0x012FF7B4 88 | XFS_SUPER_MAGIC = 0x58465342 89 | _XIAFS_SUPER_MAGIC = 0x012FD16D 90 | 91 | AFS_SUPER_MAGIC = 0x5346414F 92 | AUFS_SUPER_MAGIC = 0x61756673 93 | ANON_INODE_FS_SUPER_MAGIC = 0x09041934 94 | CEPH_SUPER_MAGIC = 0x00C36400 95 | ECRYPTFS_SUPER_MAGIC = 0xF15F 96 | FAT_SUPER_MAGIC = 0x4006 97 | FHGFS_SUPER_MAGIC = 0x19830326 98 | FUSEBLK_SUPER_MAGIC = 0x65735546 99 | FUSECTL_SUPER_MAGIC = 0x65735543 100 | GFS_SUPER_MAGIC = 0x1161970 101 | GPFS_SUPER_MAGIC = 0x47504653 102 | MTD_INODE_FS_SUPER_MAGIC = 0x11307854 103 | INOTIFYFS_SUPER_MAGIC = 0x2BAD1DEA 104 | ISOFS_R_WIN_SUPER_MAGIC = 0x4004 105 | ISOFS_WIN_SUPER_MAGIC = 0x4000 106 | JFFS_SUPER_MAGIC = 0x07C0 107 | KAFS_SUPER_MAGIC = 0x6B414653 108 | LUSTRE_SUPER_MAGIC = 0x0BD00BD0 109 | NFSD_SUPER_MAGIC = 0x6E667364 110 | PANFS_SUPER_MAGIC = 0xAAD7AAEA 111 | RPC_PIPEFS_SUPER_MAGIC = 0x67596969 112 | SECURITYFS_SUPER_MAGIC = 0x73636673 113 | UFS_BYTESWAPPED_SUPER_MAGIC = 0x54190100 114 | VMHGFS_SUPER_MAGIC = 0xBACBACBC 115 | VZFS_SUPER_MAGIC = 0x565A4653 116 | ZFS_SUPER_MAGIC = 0x2FC12FC1 117 | ) 118 | 119 | // coreutils/src/stat.c 120 | var FsTypeMap = map[int64]string{ 121 | ADFS_SUPER_MAGIC: "adfs", /* 0xADF5 local */ 122 | AFFS_SUPER_MAGIC: "affs", /* 0xADFF local */ 123 | AFS_SUPER_MAGIC: "afs", /* 0x5346414F remote */ 124 | ANON_INODE_FS_SUPER_MAGIC: "anon-inode FS", /* 0x09041934 local */ 125 | AUFS_SUPER_MAGIC: "aufs", /* 0x61756673 remote */ 126 | AUTOFS_SUPER_MAGIC: "autofs", /* 0x0187 local */ 127 | BEFS_SUPER_MAGIC: "befs", /* 0x42465331 local */ 128 | BDEVFS_MAGIC: "bdevfs", /* 0x62646576 local */ 129 | BFS_MAGIC: "bfs", /* 0x1BADFACE local */ 130 | BINFMTFS_MAGIC: "binfmt_misc", /* 0x42494E4D local */ 131 | BTRFS_SUPER_MAGIC: "btrfs", /* 0x9123683E local */ 132 | CEPH_SUPER_MAGIC: "ceph", /* 0x00C36400 remote */ 133 | CGROUP_SUPER_MAGIC: "cgroupfs", /* 0x0027E0EB local */ 134 | CIFS_MAGIC_NUMBER: "cifs", /* 0xFF534D42 remote */ 135 | CODA_SUPER_MAGIC: "coda", /* 0x73757245 remote */ 136 | COH_SUPER_MAGIC: "coh", /* 0x012FF7B7 local */ 137 | CRAMFS_MAGIC: "cramfs", /* 0x28CD3D45 local */ 138 | DEBUGFS_MAGIC: "debugfs", /* 0x64626720 local */ 139 | DEVFS_SUPER_MAGIC: "devfs", /* 0x1373 local */ 140 | DEVPTS_SUPER_MAGIC: "devpts", /* 0x1CD1 local */ 141 | ECRYPTFS_SUPER_MAGIC: "ecryptfs", /* 0xF15F local */ 142 | EFS_SUPER_MAGIC: "efs", /* 0x00414A53 local */ 143 | EXT_SUPER_MAGIC: "ext", /* 0x137D local */ 144 | EXT2_SUPER_MAGIC: "ext2/ext3", /* 0xEF53 local */ 145 | EXT2_OLD_SUPER_MAGIC: "ext2", /* 0xEF51 local */ 146 | FAT_SUPER_MAGIC: "fat", /* 0x4006 local */ 147 | FHGFS_SUPER_MAGIC: "fhgfs", /* 0x19830326 remote */ 148 | FUSEBLK_SUPER_MAGIC: "fuseblk", /* 0x65735546 remote */ 149 | FUSECTL_SUPER_MAGIC: "fusectl", /* 0x65735543 remote */ 150 | FUTEXFS_SUPER_MAGIC: "futexfs", /* 0x0BAD1DEA local */ 151 | GFS_SUPER_MAGIC: "gfs/gfs2", /* 0x1161970 remote */ 152 | GPFS_SUPER_MAGIC: "gpfs", /* 0x47504653 remote */ 153 | HFS_SUPER_MAGIC: "hfs", /* 0x4244 local */ 154 | HFSPLUS_SUPER_MAGIC: "hfsplus", /* 0x482b local */ 155 | HPFS_SUPER_MAGIC: "hpfs", /* 0xF995E849 local */ 156 | HUGETLBFS_MAGIC: "hugetlbfs", /* 0x958458F6 local */ 157 | MTD_INODE_FS_SUPER_MAGIC: "inodefs", /* 0x11307854 local */ 158 | INOTIFYFS_SUPER_MAGIC: "inotifyfs", /* 0x2BAD1DEA local */ 159 | ISOFS_SUPER_MAGIC: "isofs", /* 0x9660 local */ 160 | ISOFS_R_WIN_SUPER_MAGIC: "isofs", /* 0x4004 local */ 161 | ISOFS_WIN_SUPER_MAGIC: "isofs", /* 0x4000 local */ 162 | JFFS_SUPER_MAGIC: "jffs", /* 0x07C0 local */ 163 | JFFS2_SUPER_MAGIC: "jffs2", /* 0x72B6 local */ 164 | JFS_SUPER_MAGIC: "jfs", /* 0x3153464A local */ 165 | KAFS_SUPER_MAGIC: "k-afs", /* 0x6B414653 remote */ 166 | LUSTRE_SUPER_MAGIC: "lustre", /* 0x0BD00BD0 remote */ 167 | MINIX_SUPER_MAGIC: "minix", /* 0x137F local */ 168 | MINIX_SUPER_MAGIC2: "minix (30 char.)", /* 0x138F local */ 169 | MINIX2_SUPER_MAGIC: "minix v2", /* 0x2468 local */ 170 | MINIX2_SUPER_MAGIC2: "minix v2 (30 char.)", /* 0x2478 local */ 171 | MINIX3_SUPER_MAGIC: "minix3", /* 0x4D5A local */ 172 | MQUEUE_MAGIC: "mqueue", /* 0x19800202 local */ 173 | MSDOS_SUPER_MAGIC: "msdos", /* 0x4D44 local */ 174 | NCP_SUPER_MAGIC: "novell", /* 0x564C remote */ 175 | NFS_SUPER_MAGIC: "nfs", /* 0x6969 remote */ 176 | NFSD_SUPER_MAGIC: "nfsd", /* 0x6E667364 remote */ 177 | NILFS_SUPER_MAGIC: "nilfs", /* 0x3434 local */ 178 | NTFS_SB_MAGIC: "ntfs", /* 0x5346544E local */ 179 | OPENPROM_SUPER_MAGIC: "openprom", /* 0x9FA1 local */ 180 | OCFS2_SUPER_MAGIC: "ocfs2", /* 0x7461636f remote */ 181 | PANFS_SUPER_MAGIC: "panfs", /* 0xAAD7AAEA remote */ 182 | PIPEFS_MAGIC: "pipefs", /* 0x50495045 remote */ 183 | PROC_SUPER_MAGIC: "proc", /* 0x9FA0 local */ 184 | PSTOREFS_MAGIC: "pstorefs", /* 0x6165676C local */ 185 | QNX4_SUPER_MAGIC: "qnx4", /* 0x002F local */ 186 | QNX6_SUPER_MAGIC: "qnx6", /* 0x68191122 local */ 187 | RAMFS_MAGIC: "ramfs", /* 0x858458F6 local */ 188 | REISERFS_SUPER_MAGIC: "reiserfs", /* 0x52654973 local */ 189 | ROMFS_MAGIC: "romfs", /* 0x7275 local */ 190 | RPC_PIPEFS_SUPER_MAGIC: "rpc_pipefs", /* 0x67596969 local */ 191 | SECURITYFS_SUPER_MAGIC: "securityfs", /* 0x73636673 local */ 192 | SELINUX_MAGIC: "selinux", /* 0xF97CFF8C local */ 193 | SMB_SUPER_MAGIC: "smb", /* 0x517B remote */ 194 | SMB2_MAGIC_NUMBER: "smb2", /* 0xfe534d42 remote */ 195 | SOCKFS_MAGIC: "sockfs", /* 0x534F434B local */ 196 | SQUASHFS_MAGIC: "squashfs", /* 0x73717368 local */ 197 | SYSFS_MAGIC: "sysfs", /* 0x62656572 local */ 198 | SYSV2_SUPER_MAGIC: "sysv2", /* 0x012FF7B6 local */ 199 | SYSV4_SUPER_MAGIC: "sysv4", /* 0x012FF7B5 local */ 200 | TMPFS_MAGIC: "tmpfs", /* 0x01021994 local */ 201 | UDF_SUPER_MAGIC: "udf", /* 0x15013346 local */ 202 | UFS_MAGIC: "ufs", /* 0x00011954 local */ 203 | UFS_BYTESWAPPED_SUPER_MAGIC: "ufs", /* 0x54190100 local */ 204 | USBDEVICE_SUPER_MAGIC: "usbdevfs", /* 0x9FA2 local */ 205 | V9FS_MAGIC: "v9fs", /* 0x01021997 local */ 206 | VMHGFS_SUPER_MAGIC: "vmhgfs", /* 0xBACBACBC remote */ 207 | VXFS_SUPER_MAGIC: "vxfs", /* 0xA501FCF5 local */ 208 | VZFS_SUPER_MAGIC: "vzfs", /* 0x565A4653 local */ 209 | XENFS_SUPER_MAGIC: "xenfs", /* 0xABBA1974 local */ 210 | XENIX_SUPER_MAGIC: "xenix", /* 0x012FF7B4 local */ 211 | XFS_SUPER_MAGIC: "xfs", /* 0x58465342 local */ 212 | _XIAFS_SUPER_MAGIC: "xia", /* 0x012FD16D local */ 213 | ZFS_SUPER_MAGIC: "zfs", /* 0x2FC12FC1 local */ 214 | } 215 | 216 | var ErrorFSType error = errors.New("filesystem type") 217 | 218 | type mountPredicate struct { 219 | path []string 220 | fsType []string 221 | } 222 | 223 | func (p *mountPredicate) Match(_ string, path string) (bool, error) { 224 | fsType := getFileSystemType(path) 225 | for _, t := range p.fsType { 226 | for _, fst := range fsType { 227 | if t == fst { 228 | return true, nil 229 | } 230 | } 231 | } 232 | return false, PredicateError{errType: ErrorFSType, errMessage: "no such filesystem"} 233 | } 234 | 235 | func getFileSystemType(paths ...string) []string { 236 | var fsTypes []string 237 | 238 | for _, path := range paths { 239 | var stat syscall.Statfs_t 240 | err := syscall.Statfs(path, &stat) 241 | if err != nil { 242 | fmt.Fprintf(os.Stderr, "state failed: %v\n", err) 243 | continue 244 | } 245 | 246 | fsTypes = append(fsTypes, FsTypeMap[stat.Type]) 247 | } 248 | 249 | return fsTypes 250 | } 251 | --------------------------------------------------------------------------------