github.com/endocode/docker@v1.4.2-0.20160113120958-46eb4700391e/pkg/fileutils/fileutils.go (about) 1 package fileutils 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "regexp" 10 "strings" 11 "text/scanner" 12 13 "github.com/Sirupsen/logrus" 14 ) 15 16 // exclusion return true if the specified pattern is an exclusion 17 func exclusion(pattern string) bool { 18 return pattern[0] == '!' 19 } 20 21 // empty return true if the specified pattern is empty 22 func empty(pattern string) bool { 23 return pattern == "" 24 } 25 26 // CleanPatterns takes a slice of patterns returns a new 27 // slice of patterns cleaned with filepath.Clean, stripped 28 // of any empty patterns and lets the caller know whether the 29 // slice contains any exception patterns (prefixed with !). 30 func CleanPatterns(patterns []string) ([]string, [][]string, bool, error) { 31 // Loop over exclusion patterns and: 32 // 1. Clean them up. 33 // 2. Indicate whether we are dealing with any exception rules. 34 // 3. Error if we see a single exclusion marker on it's own (!). 35 cleanedPatterns := []string{} 36 patternDirs := [][]string{} 37 exceptions := false 38 for _, pattern := range patterns { 39 // Eliminate leading and trailing whitespace. 40 pattern = strings.TrimSpace(pattern) 41 if empty(pattern) { 42 continue 43 } 44 if exclusion(pattern) { 45 if len(pattern) == 1 { 46 return nil, nil, false, errors.New("Illegal exclusion pattern: !") 47 } 48 exceptions = true 49 } 50 pattern = filepath.Clean(pattern) 51 cleanedPatterns = append(cleanedPatterns, pattern) 52 if exclusion(pattern) { 53 pattern = pattern[1:] 54 } 55 patternDirs = append(patternDirs, strings.Split(pattern, "/")) 56 } 57 58 return cleanedPatterns, patternDirs, exceptions, nil 59 } 60 61 // Matches returns true if file matches any of the patterns 62 // and isn't excluded by any of the subsequent patterns. 63 func Matches(file string, patterns []string) (bool, error) { 64 file = filepath.Clean(file) 65 66 if file == "." { 67 // Don't let them exclude everything, kind of silly. 68 return false, nil 69 } 70 71 patterns, patDirs, _, err := CleanPatterns(patterns) 72 if err != nil { 73 return false, err 74 } 75 76 return OptimizedMatches(file, patterns, patDirs) 77 } 78 79 // OptimizedMatches is basically the same as fileutils.Matches() but optimized for archive.go. 80 // It will assume that the inputs have been preprocessed and therefore the function 81 // doesn't need to do as much error checking and clean-up. This was done to avoid 82 // repeating these steps on each file being checked during the archive process. 83 // The more generic fileutils.Matches() can't make these assumptions. 84 func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool, error) { 85 matched := false 86 parentPath := filepath.Dir(file) 87 parentPathDirs := strings.Split(parentPath, "/") 88 89 for i, pattern := range patterns { 90 negative := false 91 92 if exclusion(pattern) { 93 negative = true 94 pattern = pattern[1:] 95 } 96 97 match, err := regexpMatch(pattern, file) 98 if err != nil { 99 return false, fmt.Errorf("Error in pattern (%s): %s", pattern, err) 100 } 101 102 if !match && parentPath != "." { 103 // Check to see if the pattern matches one of our parent dirs. 104 if len(patDirs[i]) <= len(parentPathDirs) { 105 match, _ = regexpMatch(strings.Join(patDirs[i], "/"), 106 strings.Join(parentPathDirs[:len(patDirs[i])], "/")) 107 } 108 } 109 110 if match { 111 matched = !negative 112 } 113 } 114 115 if matched { 116 logrus.Debugf("Skipping excluded path: %s", file) 117 } 118 119 return matched, nil 120 } 121 122 // regexpMatch tries to match the logic of filepath.Match but 123 // does so using regexp logic. We do this so that we can expand the 124 // wildcard set to include other things, like "**" to mean any number 125 // of directories. This means that we should be backwards compatible 126 // with filepath.Match(). We'll end up supporting more stuff, due to 127 // the fact that we're using regexp, but that's ok - it does no harm. 128 func regexpMatch(pattern, path string) (bool, error) { 129 regStr := "^" 130 131 // Do some syntax checking on the pattern. 132 // filepath's Match() has some really weird rules that are inconsistent 133 // so instead of trying to dup their logic, just call Match() for its 134 // error state and if there is an error in the pattern return it. 135 // If this becomes an issue we can remove this since its really only 136 // needed in the error (syntax) case - which isn't really critical. 137 if _, err := filepath.Match(pattern, path); err != nil { 138 return false, err 139 } 140 141 // Go through the pattern and convert it to a regexp. 142 // We use a scanner so we can support utf-8 chars. 143 var scan scanner.Scanner 144 scan.Init(strings.NewReader(pattern)) 145 146 sl := string(os.PathSeparator) 147 escSL := sl 148 if sl == `\` { 149 escSL += `\` 150 } 151 152 for scan.Peek() != scanner.EOF { 153 ch := scan.Next() 154 155 if ch == '*' { 156 if scan.Peek() == '*' { 157 // is some flavor of "**" 158 scan.Next() 159 160 if scan.Peek() == scanner.EOF { 161 // is "**EOF" - to align with .gitignore just accept all 162 regStr += ".*" 163 } else { 164 // is "**" 165 regStr += "((.*" + escSL + ")|([^" + escSL + "]*))" 166 } 167 168 // Treat **/ as ** so eat the "/" 169 if string(scan.Peek()) == sl { 170 scan.Next() 171 } 172 } else { 173 // is "*" so map it to anything but "/" 174 regStr += "[^" + escSL + "]*" 175 } 176 } else if ch == '?' { 177 // "?" is any char except "/" 178 regStr += "[^" + escSL + "]" 179 } else if strings.Index(".$", string(ch)) != -1 { 180 // Escape some regexp special chars that have no meaning 181 // in golang's filepath.Match 182 regStr += `\` + string(ch) 183 } else if ch == '\\' { 184 // escape next char. Note that a trailing \ in the pattern 185 // will be left alone (but need to escape it) 186 if sl == `\` { 187 // On windows map "\" to "\\", meaning an escaped backslash, 188 // and then just continue because filepath.Match on 189 // Windows doesn't allow escaping at all 190 regStr += escSL 191 continue 192 } 193 if scan.Peek() != scanner.EOF { 194 regStr += `\` + string(scan.Next()) 195 } else { 196 regStr += `\` 197 } 198 } else { 199 regStr += string(ch) 200 } 201 } 202 203 regStr += "$" 204 205 res, err := regexp.MatchString(regStr, path) 206 207 // Map regexp's error to filepath's so no one knows we're not using filepath 208 if err != nil { 209 err = filepath.ErrBadPattern 210 } 211 212 return res, err 213 } 214 215 // CopyFile copies from src to dst until either EOF is reached 216 // on src or an error occurs. It verifies src exists and remove 217 // the dst if it exists. 218 func CopyFile(src, dst string) (int64, error) { 219 cleanSrc := filepath.Clean(src) 220 cleanDst := filepath.Clean(dst) 221 if cleanSrc == cleanDst { 222 return 0, nil 223 } 224 sf, err := os.Open(cleanSrc) 225 if err != nil { 226 return 0, err 227 } 228 defer sf.Close() 229 if err := os.Remove(cleanDst); err != nil && !os.IsNotExist(err) { 230 return 0, err 231 } 232 df, err := os.Create(cleanDst) 233 if err != nil { 234 return 0, err 235 } 236 defer df.Close() 237 return io.Copy(df, sf) 238 } 239 240 // ReadSymlinkedDirectory returns the target directory of a symlink. 241 // The target of the symbolic link may not be a file. 242 func ReadSymlinkedDirectory(path string) (string, error) { 243 var realPath string 244 var err error 245 if realPath, err = filepath.Abs(path); err != nil { 246 return "", fmt.Errorf("unable to get absolute path for %s: %s", path, err) 247 } 248 if realPath, err = filepath.EvalSymlinks(realPath); err != nil { 249 return "", fmt.Errorf("failed to canonicalise path for %s: %s", path, err) 250 } 251 realPathInfo, err := os.Stat(realPath) 252 if err != nil { 253 return "", fmt.Errorf("failed to stat target '%s' of '%s': %s", realPath, path, err) 254 } 255 if !realPathInfo.Mode().IsDir() { 256 return "", fmt.Errorf("canonical path points to a file '%s'", realPath) 257 } 258 return realPath, nil 259 } 260 261 // CreateIfNotExists creates a file or a directory only if it does not already exist. 262 func CreateIfNotExists(path string, isDir bool) error { 263 if _, err := os.Stat(path); err != nil { 264 if os.IsNotExist(err) { 265 if isDir { 266 return os.MkdirAll(path, 0755) 267 } 268 if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { 269 return err 270 } 271 f, err := os.OpenFile(path, os.O_CREATE, 0755) 272 if err != nil { 273 return err 274 } 275 f.Close() 276 } 277 } 278 return nil 279 }