github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/cue/load/fs.go (about) 1 // Copyright 2018 The CUE Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package load 16 17 import ( 18 "bytes" 19 "io" 20 "io/ioutil" 21 "os" 22 "path/filepath" 23 "sort" 24 "strings" 25 "time" 26 27 "github.com/joomcode/cue/cue/ast" 28 "github.com/joomcode/cue/cue/errors" 29 "github.com/joomcode/cue/cue/token" 30 ) 31 32 type overlayFile struct { 33 basename string 34 contents []byte 35 file *ast.File 36 modtime time.Time 37 isDir bool 38 } 39 40 func (f *overlayFile) Name() string { return f.basename } 41 func (f *overlayFile) Size() int64 { return int64(len(f.contents)) } 42 func (f *overlayFile) Mode() os.FileMode { return 0644 } 43 func (f *overlayFile) ModTime() time.Time { return f.modtime } 44 func (f *overlayFile) IsDir() bool { return f.isDir } 45 func (f *overlayFile) Sys() interface{} { return nil } 46 47 // A fileSystem specifies the supporting context for a build. 48 type fileSystem struct { 49 overlayDirs map[string]map[string]*overlayFile 50 cwd string 51 } 52 53 func (fs *fileSystem) getDir(dir string, create bool) map[string]*overlayFile { 54 dir = filepath.Clean(dir) 55 m, ok := fs.overlayDirs[dir] 56 if !ok && create { 57 m = map[string]*overlayFile{} 58 fs.overlayDirs[dir] = m 59 } 60 return m 61 } 62 63 func (fs *fileSystem) init(c *Config) error { 64 fs.cwd = c.Dir 65 66 overlay := c.Overlay 67 fs.overlayDirs = map[string]map[string]*overlayFile{} 68 69 // Organize overlay 70 for filename, src := range overlay { 71 // TODO: do we need to further clean the path or check that the 72 // specified files are within the root/ absolute files? 73 dir, base := filepath.Split(filename) 74 m := fs.getDir(dir, true) 75 76 b, file, err := src.contents() 77 if err != nil { 78 return err 79 } 80 m[base] = &overlayFile{ 81 basename: base, 82 contents: b, 83 file: file, 84 modtime: time.Now(), 85 } 86 87 for { 88 prevdir := dir 89 dir, base = filepath.Split(filepath.Dir(dir)) 90 if dir == prevdir || dir == "" { 91 break 92 } 93 m := fs.getDir(dir, true) 94 if m[base] == nil { 95 m[base] = &overlayFile{ 96 basename: base, 97 modtime: time.Now(), 98 isDir: true, 99 } 100 } 101 } 102 } 103 return nil 104 } 105 106 func (fs *fileSystem) joinPath(elem ...string) string { 107 return filepath.Join(elem...) 108 } 109 110 func (fs *fileSystem) splitPathList(s string) []string { 111 return filepath.SplitList(s) 112 } 113 114 func (fs *fileSystem) isAbsPath(path string) bool { 115 return filepath.IsAbs(path) 116 } 117 118 func (fs *fileSystem) makeAbs(path string) string { 119 if fs.isAbsPath(path) { 120 return path 121 } 122 return filepath.Clean(filepath.Join(fs.cwd, path)) 123 } 124 125 func (fs *fileSystem) isDir(path string) bool { 126 path = fs.makeAbs(path) 127 if fs.getDir(path, false) != nil { 128 return true 129 } 130 fi, err := os.Stat(path) 131 return err == nil && fi.IsDir() 132 } 133 134 func (fs *fileSystem) hasSubdir(root, dir string) (rel string, ok bool) { 135 // Try using paths we received. 136 if rel, ok = hasSubdir(root, dir); ok { 137 return 138 } 139 140 // Try expanding symlinks and comparing 141 // expanded against unexpanded and 142 // expanded against expanded. 143 rootSym, _ := filepath.EvalSymlinks(root) 144 dirSym, _ := filepath.EvalSymlinks(dir) 145 146 if rel, ok = hasSubdir(rootSym, dir); ok { 147 return 148 } 149 if rel, ok = hasSubdir(root, dirSym); ok { 150 return 151 } 152 return hasSubdir(rootSym, dirSym) 153 } 154 155 func hasSubdir(root, dir string) (rel string, ok bool) { 156 const sep = string(filepath.Separator) 157 root = filepath.Clean(root) 158 if !strings.HasSuffix(root, sep) { 159 root += sep 160 } 161 dir = filepath.Clean(dir) 162 if !strings.HasPrefix(dir, root) { 163 return "", false 164 } 165 return filepath.ToSlash(dir[len(root):]), true 166 } 167 168 func (fs *fileSystem) readDir(path string) ([]os.FileInfo, errors.Error) { 169 path = fs.makeAbs(path) 170 m := fs.getDir(path, false) 171 items, err := ioutil.ReadDir(path) 172 if err != nil { 173 if !os.IsNotExist(err) || m == nil { 174 return nil, errors.Wrapf(err, token.NoPos, "readDir") 175 } 176 } 177 if m != nil { 178 done := map[string]bool{} 179 for i, fi := range items { 180 done[fi.Name()] = true 181 if o := m[fi.Name()]; o != nil { 182 items[i] = o 183 } 184 } 185 for _, o := range m { 186 if !done[o.Name()] { 187 items = append(items, o) 188 } 189 } 190 sort.Slice(items, func(i, j int) bool { 191 return items[i].Name() < items[j].Name() 192 }) 193 } 194 return items, nil 195 } 196 197 func (fs *fileSystem) getOverlay(path string) *overlayFile { 198 dir, base := filepath.Split(path) 199 if m := fs.getDir(dir, false); m != nil { 200 return m[base] 201 } 202 return nil 203 } 204 205 func (fs *fileSystem) stat(path string) (os.FileInfo, errors.Error) { 206 path = fs.makeAbs(path) 207 if fi := fs.getOverlay(path); fi != nil { 208 return fi, nil 209 } 210 fi, err := os.Stat(path) 211 if err != nil { 212 return nil, errors.Wrapf(err, token.NoPos, "stat") 213 } 214 return fi, nil 215 } 216 217 func (fs *fileSystem) lstat(path string) (os.FileInfo, errors.Error) { 218 path = fs.makeAbs(path) 219 if fi := fs.getOverlay(path); fi != nil { 220 return fi, nil 221 } 222 fi, err := os.Lstat(path) 223 if err != nil { 224 return nil, errors.Wrapf(err, token.NoPos, "stat") 225 } 226 return fi, nil 227 } 228 229 func (fs *fileSystem) openFile(path string) (io.ReadCloser, errors.Error) { 230 path = fs.makeAbs(path) 231 if fi := fs.getOverlay(path); fi != nil { 232 return ioutil.NopCloser(bytes.NewReader(fi.contents)), nil 233 } 234 235 f, err := os.Open(path) 236 if err != nil { 237 return nil, errors.Wrapf(err, token.NoPos, "load") 238 } 239 return f, nil 240 } 241 242 var skipDir = errors.Newf(token.NoPos, "skip directory") 243 244 type walkFunc func(path string, info os.FileInfo, err errors.Error) errors.Error 245 246 func (fs *fileSystem) walk(root string, f walkFunc) error { 247 fi, err := fs.lstat(root) 248 if err != nil { 249 err = f(root, fi, err) 250 } else if !fi.IsDir() { 251 return errors.Newf(token.NoPos, "path %q is not a directory", root) 252 } else { 253 err = fs.walkRec(root, fi, f) 254 } 255 if err == skipDir { 256 return nil 257 } 258 return err 259 260 } 261 262 func (fs *fileSystem) walkRec(path string, info os.FileInfo, f walkFunc) errors.Error { 263 if !info.IsDir() { 264 return f(path, info, nil) 265 } 266 267 dir, err := fs.readDir(path) 268 err1 := f(path, info, err) 269 270 // If err != nil, walk can't walk into this directory. 271 // err1 != nil means walkFn want walk to skip this directory or stop walking. 272 // Therefore, if one of err and err1 isn't nil, walk will return. 273 if err != nil || err1 != nil { 274 // The caller's behavior is controlled by the return value, which is decided 275 // by walkFn. walkFn may ignore err and return nil. 276 // If walkFn returns SkipDir, it will be handled by the caller. 277 // So walk should return whatever walkFn returns. 278 return err1 279 } 280 281 for _, info := range dir { 282 filename := fs.joinPath(path, info.Name()) 283 err = fs.walkRec(filename, info, f) 284 if err != nil { 285 if !info.IsDir() || err != skipDir { 286 return err 287 } 288 } 289 } 290 return nil 291 }