fileutils.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. package fileutils // import "github.com/docker/docker/pkg/fileutils"
  2. import (
  3. "errors"
  4. "fmt"
  5. "io"
  6. "os"
  7. "path/filepath"
  8. "regexp"
  9. "strings"
  10. "text/scanner"
  11. "github.com/sirupsen/logrus"
  12. )
  13. // PatternMatcher allows checking paths against a list of patterns
  14. type PatternMatcher struct {
  15. patterns []*Pattern
  16. exclusions bool
  17. }
  18. // NewPatternMatcher creates a new matcher object for specific patterns that can
  19. // be used later to match against patterns against paths
  20. func NewPatternMatcher(patterns []string) (*PatternMatcher, error) {
  21. pm := &PatternMatcher{
  22. patterns: make([]*Pattern, 0, len(patterns)),
  23. }
  24. for _, p := range patterns {
  25. // Eliminate leading and trailing whitespace.
  26. p = strings.TrimSpace(p)
  27. if p == "" {
  28. continue
  29. }
  30. p = filepath.Clean(p)
  31. newp := &Pattern{}
  32. if p[0] == '!' {
  33. if len(p) == 1 {
  34. return nil, errors.New("illegal exclusion pattern: \"!\"")
  35. }
  36. newp.exclusion = true
  37. p = p[1:]
  38. pm.exclusions = true
  39. }
  40. // Do some syntax checking on the pattern.
  41. // filepath's Match() has some really weird rules that are inconsistent
  42. // so instead of trying to dup their logic, just call Match() for its
  43. // error state and if there is an error in the pattern return it.
  44. // If this becomes an issue we can remove this since its really only
  45. // needed in the error (syntax) case - which isn't really critical.
  46. if _, err := filepath.Match(p, "."); err != nil {
  47. return nil, err
  48. }
  49. newp.cleanedPattern = p
  50. newp.dirs = strings.Split(p, string(os.PathSeparator))
  51. pm.patterns = append(pm.patterns, newp)
  52. }
  53. return pm, nil
  54. }
  55. // Matches matches path against all the patterns. Matches is not safe to be
  56. // called concurrently
  57. func (pm *PatternMatcher) Matches(file string) (bool, error) {
  58. matched := false
  59. file = filepath.FromSlash(file)
  60. parentPath := filepath.Dir(file)
  61. parentPathDirs := strings.Split(parentPath, string(os.PathSeparator))
  62. for _, pattern := range pm.patterns {
  63. negative := false
  64. if pattern.exclusion {
  65. negative = true
  66. }
  67. match, err := pattern.match(file)
  68. if err != nil {
  69. return false, err
  70. }
  71. if !match && parentPath != "." {
  72. // Check to see if the pattern matches one of our parent dirs.
  73. if len(pattern.dirs) <= len(parentPathDirs) {
  74. match, _ = pattern.match(strings.Join(parentPathDirs[:len(pattern.dirs)], string(os.PathSeparator)))
  75. }
  76. }
  77. if match {
  78. matched = !negative
  79. }
  80. }
  81. if matched {
  82. logrus.Debugf("Skipping excluded path: %s", file)
  83. }
  84. return matched, nil
  85. }
  86. // Exclusions returns true if any of the patterns define exclusions
  87. func (pm *PatternMatcher) Exclusions() bool {
  88. return pm.exclusions
  89. }
  90. // Patterns returns array of active patterns
  91. func (pm *PatternMatcher) Patterns() []*Pattern {
  92. return pm.patterns
  93. }
  94. // Pattern defines a single regexp used to filter file paths.
  95. type Pattern struct {
  96. cleanedPattern string
  97. dirs []string
  98. regexp *regexp.Regexp
  99. exclusion bool
  100. }
  101. func (p *Pattern) String() string {
  102. return p.cleanedPattern
  103. }
  104. // Exclusion returns true if this pattern defines exclusion
  105. func (p *Pattern) Exclusion() bool {
  106. return p.exclusion
  107. }
  108. func (p *Pattern) match(path string) (bool, error) {
  109. if p.regexp == nil {
  110. if err := p.compile(); err != nil {
  111. return false, filepath.ErrBadPattern
  112. }
  113. }
  114. b := p.regexp.MatchString(path)
  115. return b, nil
  116. }
  117. func (p *Pattern) compile() error {
  118. regStr := "^"
  119. pattern := p.cleanedPattern
  120. // Go through the pattern and convert it to a regexp.
  121. // We use a scanner so we can support utf-8 chars.
  122. var scan scanner.Scanner
  123. scan.Init(strings.NewReader(pattern))
  124. sl := string(os.PathSeparator)
  125. escSL := sl
  126. if sl == `\` {
  127. escSL += `\`
  128. }
  129. for scan.Peek() != scanner.EOF {
  130. ch := scan.Next()
  131. if ch == '*' {
  132. if scan.Peek() == '*' {
  133. // is some flavor of "**"
  134. scan.Next()
  135. // Treat **/ as ** so eat the "/"
  136. if string(scan.Peek()) == sl {
  137. scan.Next()
  138. }
  139. if scan.Peek() == scanner.EOF {
  140. // is "**EOF" - to align with .gitignore just accept all
  141. regStr += ".*"
  142. } else {
  143. // is "**"
  144. // Note that this allows for any # of /'s (even 0) because
  145. // the .* will eat everything, even /'s
  146. regStr += "(.*" + escSL + ")?"
  147. }
  148. } else {
  149. // is "*" so map it to anything but "/"
  150. regStr += "[^" + escSL + "]*"
  151. }
  152. } else if ch == '?' {
  153. // "?" is any char except "/"
  154. regStr += "[^" + escSL + "]"
  155. } else if ch == '.' || ch == '$' {
  156. // Escape some regexp special chars that have no meaning
  157. // in golang's filepath.Match
  158. regStr += `\` + string(ch)
  159. } else if ch == '\\' {
  160. // escape next char. Note that a trailing \ in the pattern
  161. // will be left alone (but need to escape it)
  162. if sl == `\` {
  163. // On windows map "\" to "\\", meaning an escaped backslash,
  164. // and then just continue because filepath.Match on
  165. // Windows doesn't allow escaping at all
  166. regStr += escSL
  167. continue
  168. }
  169. if scan.Peek() != scanner.EOF {
  170. regStr += `\` + string(scan.Next())
  171. } else {
  172. regStr += `\`
  173. }
  174. } else {
  175. regStr += string(ch)
  176. }
  177. }
  178. regStr += "$"
  179. re, err := regexp.Compile(regStr)
  180. if err != nil {
  181. return err
  182. }
  183. p.regexp = re
  184. return nil
  185. }
  186. // Matches returns true if file matches any of the patterns
  187. // and isn't excluded by any of the subsequent patterns.
  188. func Matches(file string, patterns []string) (bool, error) {
  189. pm, err := NewPatternMatcher(patterns)
  190. if err != nil {
  191. return false, err
  192. }
  193. file = filepath.Clean(file)
  194. if file == "." {
  195. // Don't let them exclude everything, kind of silly.
  196. return false, nil
  197. }
  198. return pm.Matches(file)
  199. }
  200. // CopyFile copies from src to dst until either EOF is reached
  201. // on src or an error occurs. It verifies src exists and removes
  202. // the dst if it exists.
  203. func CopyFile(src, dst string) (int64, error) {
  204. cleanSrc := filepath.Clean(src)
  205. cleanDst := filepath.Clean(dst)
  206. if cleanSrc == cleanDst {
  207. return 0, nil
  208. }
  209. sf, err := os.Open(cleanSrc)
  210. if err != nil {
  211. return 0, err
  212. }
  213. defer sf.Close()
  214. if err := os.Remove(cleanDst); err != nil && !os.IsNotExist(err) {
  215. return 0, err
  216. }
  217. df, err := os.Create(cleanDst)
  218. if err != nil {
  219. return 0, err
  220. }
  221. defer df.Close()
  222. return io.Copy(df, sf)
  223. }
  224. // ReadSymlinkedDirectory returns the target directory of a symlink.
  225. // The target of the symbolic link may not be a file.
  226. func ReadSymlinkedDirectory(path string) (string, error) {
  227. var realPath string
  228. var err error
  229. if realPath, err = filepath.Abs(path); err != nil {
  230. return "", fmt.Errorf("unable to get absolute path for %s: %s", path, err)
  231. }
  232. if realPath, err = filepath.EvalSymlinks(realPath); err != nil {
  233. return "", fmt.Errorf("failed to canonicalise path for %s: %s", path, err)
  234. }
  235. realPathInfo, err := os.Stat(realPath)
  236. if err != nil {
  237. return "", fmt.Errorf("failed to stat target '%s' of '%s': %s", realPath, path, err)
  238. }
  239. if !realPathInfo.Mode().IsDir() {
  240. return "", fmt.Errorf("canonical path points to a file '%s'", realPath)
  241. }
  242. return realPath, nil
  243. }
  244. // CreateIfNotExists creates a file or a directory only if it does not already exist.
  245. func CreateIfNotExists(path string, isDir bool) error {
  246. if _, err := os.Stat(path); err != nil {
  247. if os.IsNotExist(err) {
  248. if isDir {
  249. return os.MkdirAll(path, 0755)
  250. }
  251. if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
  252. return err
  253. }
  254. f, err := os.OpenFile(path, os.O_CREATE, 0755)
  255. if err != nil {
  256. return err
  257. }
  258. f.Close()
  259. }
  260. }
  261. return nil
  262. }