github.com/tompao/docker@v1.9.1/pkg/fileutils/fileutils.go (about) 1 package fileutils 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "strings" 10 11 "github.com/Sirupsen/logrus" 12 ) 13 14 // exclusion return true if the specified pattern is an exclusion 15 func exclusion(pattern string) bool { 16 return pattern[0] == '!' 17 } 18 19 // empty return true if the specified pattern is empty 20 func empty(pattern string) bool { 21 return pattern == "" 22 } 23 24 // CleanPatterns takes a slice of patterns returns a new 25 // slice of patterns cleaned with filepath.Clean, stripped 26 // of any empty patterns and lets the caller know whether the 27 // slice contains any exception patterns (prefixed with !). 28 func CleanPatterns(patterns []string) ([]string, [][]string, bool, error) { 29 // Loop over exclusion patterns and: 30 // 1. Clean them up. 31 // 2. Indicate whether we are dealing with any exception rules. 32 // 3. Error if we see a single exclusion marker on it's own (!). 33 cleanedPatterns := []string{} 34 patternDirs := [][]string{} 35 exceptions := false 36 for _, pattern := range patterns { 37 // Eliminate leading and trailing whitespace. 38 pattern = strings.TrimSpace(pattern) 39 if empty(pattern) { 40 continue 41 } 42 if exclusion(pattern) { 43 if len(pattern) == 1 { 44 return nil, nil, false, errors.New("Illegal exclusion pattern: !") 45 } 46 exceptions = true 47 } 48 pattern = filepath.Clean(pattern) 49 cleanedPatterns = append(cleanedPatterns, pattern) 50 if exclusion(pattern) { 51 pattern = pattern[1:] 52 } 53 patternDirs = append(patternDirs, strings.Split(pattern, "/")) 54 } 55 56 return cleanedPatterns, patternDirs, exceptions, nil 57 } 58 59 // Matches returns true if file matches any of the patterns 60 // and isn't excluded by any of the subsequent patterns. 61 func Matches(file string, patterns []string) (bool, error) { 62 file = filepath.Clean(file) 63 64 if file == "." { 65 // Don't let them exclude everything, kind of silly. 66 return false, nil 67 } 68 69 patterns, patDirs, _, err := CleanPatterns(patterns) 70 if err != nil { 71 return false, err 72 } 73 74 return OptimizedMatches(file, patterns, patDirs) 75 } 76 77 // OptimizedMatches is basically the same as fileutils.Matches() but optimized for archive.go. 78 // It will assume that the inputs have been preprocessed and therefore the function 79 // doen't need to do as much error checking and clean-up. This was done to avoid 80 // repeating these steps on each file being checked during the archive process. 81 // The more generic fileutils.Matches() can't make these assumptions. 82 func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool, error) { 83 matched := false 84 parentPath := filepath.Dir(file) 85 parentPathDirs := strings.Split(parentPath, "/") 86 87 for i, pattern := range patterns { 88 negative := false 89 90 if exclusion(pattern) { 91 negative = true 92 pattern = pattern[1:] 93 } 94 95 match, err := filepath.Match(pattern, file) 96 if err != nil { 97 return false, err 98 } 99 100 if !match && parentPath != "." { 101 // Check to see if the pattern matches one of our parent dirs. 102 if len(patDirs[i]) <= len(parentPathDirs) { 103 match, _ = filepath.Match(strings.Join(patDirs[i], "/"), 104 strings.Join(parentPathDirs[:len(patDirs[i])], "/")) 105 } 106 } 107 108 if match { 109 matched = !negative 110 } 111 } 112 113 if matched { 114 logrus.Debugf("Skipping excluded path: %s", file) 115 } 116 117 return matched, nil 118 } 119 120 // CopyFile copies from src to dst until either EOF is reached 121 // on src or an error occurs. It verifies src exists and remove 122 // the dst if it exists. 123 func CopyFile(src, dst string) (int64, error) { 124 cleanSrc := filepath.Clean(src) 125 cleanDst := filepath.Clean(dst) 126 if cleanSrc == cleanDst { 127 return 0, nil 128 } 129 sf, err := os.Open(cleanSrc) 130 if err != nil { 131 return 0, err 132 } 133 defer sf.Close() 134 if err := os.Remove(cleanDst); err != nil && !os.IsNotExist(err) { 135 return 0, err 136 } 137 df, err := os.Create(cleanDst) 138 if err != nil { 139 return 0, err 140 } 141 defer df.Close() 142 return io.Copy(df, sf) 143 } 144 145 // ReadSymlinkedDirectory returns the target directory of a symlink. 146 // The target of the symbolic link may not be a file. 147 func ReadSymlinkedDirectory(path string) (string, error) { 148 var realPath string 149 var err error 150 if realPath, err = filepath.Abs(path); err != nil { 151 return "", fmt.Errorf("unable to get absolute path for %s: %s", path, err) 152 } 153 if realPath, err = filepath.EvalSymlinks(realPath); err != nil { 154 return "", fmt.Errorf("failed to canonicalise path for %s: %s", path, err) 155 } 156 realPathInfo, err := os.Stat(realPath) 157 if err != nil { 158 return "", fmt.Errorf("failed to stat target '%s' of '%s': %s", realPath, path, err) 159 } 160 if !realPathInfo.Mode().IsDir() { 161 return "", fmt.Errorf("canonical path points to a file '%s'", realPath) 162 } 163 return realPath, nil 164 } 165 166 // CreateIfNotExists creates a file or a directory only if it does not already exist. 167 func CreateIfNotExists(path string, isDir bool) error { 168 if _, err := os.Stat(path); err != nil { 169 if os.IsNotExist(err) { 170 if isDir { 171 return os.MkdirAll(path, 0755) 172 } 173 if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { 174 return err 175 } 176 f, err := os.OpenFile(path, os.O_CREATE, 0755) 177 if err != nil { 178 return err 179 } 180 f.Close() 181 } 182 } 183 return nil 184 }