github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/asserts/findwildcard.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package asserts 21 22 import ( 23 "fmt" 24 "io" 25 "os" 26 "path/filepath" 27 "sort" 28 "strconv" 29 "strings" 30 ) 31 32 /* 33 findWildcard invokes foundCb once for each parent directory of regular files matching: 34 35 <top>/<descendantWithWildcard[0]>/<descendantWithWildcard[1]>... 36 37 where each descendantWithWildcard component can contain the * wildcard. 38 39 One of the descendantWithWildcard components except the last 40 can be "#>" or "#<", in which case that level is assumed to have names 41 that can be parsed as positive integers, which will be enumerated in 42 ascending (#>) or descending order respectively (#<); if seqnum != -1 43 then only the values >seqnum or respectively <seqnum will be 44 considered. 45 46 foundCb is invoked with the paths of the found regular files relative to top (that means top/ is excluded). 47 48 Unlike filepath.Glob any I/O operation error stops the walking and bottoms out, so does a foundCb invocation that returns an error. 49 */ 50 func findWildcard(top string, descendantWithWildcard []string, seqnum int, foundCb func(relpath []string) error) error { 51 return findWildcardDescend(top, top, descendantWithWildcard, seqnum, foundCb) 52 } 53 54 func findWildcardBottom(top, current string, pat string, names []string, foundCb func(relpath []string) error) error { 55 var hits []string 56 for _, name := range names { 57 ok, err := filepath.Match(pat, name) 58 if err != nil { 59 return fmt.Errorf("findWildcard: invoked with malformed wildcard: %v", err) 60 } 61 if !ok { 62 continue 63 } 64 fn := filepath.Join(current, name) 65 finfo, err := os.Stat(fn) 66 if os.IsNotExist(err) { 67 continue 68 } 69 if err != nil { 70 return err 71 } 72 if !finfo.Mode().IsRegular() { 73 return fmt.Errorf("expected a regular file: %v", fn) 74 } 75 relpath, err := filepath.Rel(top, fn) 76 if err != nil { 77 return fmt.Errorf("findWildcard: unexpected to fail at computing rel path of descendant") 78 } 79 hits = append(hits, relpath) 80 } 81 if len(hits) == 0 { 82 return nil 83 } 84 return foundCb(hits) 85 } 86 87 func findWildcardDescend(top, current string, descendantWithWildcard []string, seqnum int, foundCb func(relpath []string) error) error { 88 k := descendantWithWildcard[0] 89 if k == "#>" || k == "#<" { 90 if len(descendantWithWildcard) == 1 { 91 return fmt.Errorf("findWildcard: sequence wildcard (#>|<#) cannot be the last component") 92 } 93 return findWildcardSequence(top, current, k, descendantWithWildcard[1:], seqnum, foundCb) 94 } 95 if len(descendantWithWildcard) > 1 && strings.IndexByte(k, '*') == -1 { 96 return findWildcardDescend(top, filepath.Join(current, k), descendantWithWildcard[1:], seqnum, foundCb) 97 } 98 99 d, err := os.Open(current) 100 // ignore missing directory, higher level will produce 101 // NotFoundError as needed 102 if os.IsNotExist(err) { 103 return nil 104 } 105 if err != nil { 106 return err 107 } 108 defer d.Close() 109 names, err := d.Readdirnames(-1) 110 if err != nil { 111 return err 112 } 113 if len(descendantWithWildcard) == 1 { 114 return findWildcardBottom(top, current, k, names, foundCb) 115 } 116 for _, name := range names { 117 ok, err := filepath.Match(k, name) 118 if err != nil { 119 return fmt.Errorf("findWildcard: invoked with malformed wildcard: %v", err) 120 } 121 if ok { 122 err = findWildcardDescend(top, filepath.Join(current, name), descendantWithWildcard[1:], seqnum, foundCb) 123 if err != nil { 124 return err 125 } 126 } 127 } 128 return nil 129 } 130 131 func findWildcardSequence(top, current, seqWildcard string, descendantWithWildcard []string, seqnum int, foundCb func(relpath []string) error) error { 132 filter := func(i int) bool { return true } 133 if seqnum != -1 { 134 if seqWildcard == "#>" { 135 filter = func(i int) bool { return i > seqnum } 136 } else { // "#<", guaranteed by the caller 137 filter = func(i int) bool { return i < seqnum } 138 } 139 } 140 141 d, err := os.Open(current) 142 // ignore missing directory, higher level will produce 143 // NotFoundError as needed 144 if os.IsNotExist(err) { 145 return nil 146 } 147 if err != nil { 148 return err 149 } 150 defer d.Close() 151 var seq []int 152 for { 153 names, err := d.Readdirnames(100) 154 if err == io.EOF { 155 break 156 } 157 if err != nil { 158 return err 159 } 160 for _, n := range names { 161 sqn, err := strconv.Atoi(n) 162 if err != nil || sqn < 0 || prefixZeros(n) { 163 return fmt.Errorf("cannot parse %q name as a valid sequence number", filepath.Join(current, n)) 164 } 165 if filter(sqn) { 166 seq = append(seq, sqn) 167 } 168 } 169 } 170 sort.Ints(seq) 171 172 var start, direction int 173 if seqWildcard == "#>" { 174 start = 0 175 direction = 1 176 } else { 177 start = len(seq) - 1 178 direction = -1 179 } 180 for i := start; i >= 0 && i < len(seq); i += direction { 181 err = findWildcardDescend(top, filepath.Join(current, strconv.Itoa(seq[i])), descendantWithWildcard, -1, foundCb) 182 if err != nil { 183 return err 184 } 185 } 186 return nil 187 }