]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils/fileutils.go
provider: Ensured Go 1.11 in TravisCI and README
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / fsouza / go-dockerclient / external / github.com / docker / docker / pkg / fileutils / fileutils.go
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/fsouza/go-dockerclient/external/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 }